यह वह call है जो कोई MCP client handshake के ठीक बाद भेजता है। इसे 2026-11-06 को Tokyo के एक Mac से curl के ज़रिए osFoundry के server पर भेजा गया। इसके लिए किसी token की ज़रूरत नहीं होती:
$ curl -s -D - -X POST https://api.osfoundry.io/api/v1/external-mcp \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' HTTP/2 200 content-type: application/json content-length: 45368 {"jsonrpc":"2.0","id":2,"result":{"tools":[ …59 tools… ]}} 59 में से एक entry:
{"name": "read_note_tab", "title": "Read one Note tab", "description": "Read a selected Note, Markdown, Slides, Sheet, Mermaid, Board, Whiteboard, Design, or Artifacts tab by instance ID. Use list_note_attachments for Files and Sources.", "inputSchema": {"type": "object", "properties": {"note_id": {"type": "string"}, "tab_id": {"type": "string"}}, "required": ["note_id", "tab_id"], "additionalProperties": false}, "annotations": {"title": "Read one Note tab", "readOnlyHint": true, "openWorldHint": false, "destructiveHint": false}, "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:read"]}]} Codex CLI इस server से एक [mcp_servers.osfoundry] table और एक url के ज़रिए जुड़ता है। जब तक client इनमें से कुछ को defer न करे, Codex का model को भेजा गया हर request इन 59 declarations को किसी न किसी रूप में साथ ले जाता है। नीचे वही सवाल हैं जो आपके सामने तब आते हैं जब आप तय करते हैं कि इनमें से कितने साथ जाने चाहिए।
पहले turn से पहले Codex क्या fetch करता है?
एक ही URL पर तीन POST: initialize, notifications/initialized notification, और tools/list। Live server इनका जवाब ऐसे देता है:
| Request | Status | Body |
|---|---|---|
initialize | 200 | 308 bytes: protocol 2025-06-18, osfoundry 0.1.0, tools.listChanged: false, instructions |
notifications/initialized | 202 | खाली |
tools/list | 200 | 45,368 bytes, 59 tools |
बिना token के tools/call | 401 | resource_metadata के साथ WWW-Authenticate |
listChanged: false का मतलब है कि session के बीच में list बदलने पर server कोई सूचना नहीं देगा। Startup पर Codex को जो list मिलती है, पूरे session के लिए वही list रहती है।
instructions string 125 characters की है: "Use list_granted_resources first. Every Note edit must use the exact tab ID. Local-only data is unavailable to cloud clients." OpenAI के Codex docs के मुताबिक Codex इस field को पूरे server के लिए guidance के तौर पर पढ़ता है, और इसके पहले 512 characters अपने आप में पूरे होने चाहिए। यह string उस limit से काफ़ी नीचे है।
401 वह जगह है जहाँ से OAuth शुरू होता है। वह हिस्सा, और osFoundry हर call पर access क्यों check करता है, उसी endpoint पर Claude की तरफ़ से लिखे गए एक अलग post में cover किया गया है। यहाँ सिर्फ़ catalogue मायने रखता है।
59 tools कितने token के होते हैं?
Model तक raw JSON नहीं पहुँचता। Codex हर MCP tool को एक function declaration में बदलता है। Model को name, description और input schema चाहिए। title, annotations और securitySchemes protocol metadata हैं जिन्हें client ख़ुद इस्तेमाल करता है। OpenAI यह publish नहीं करता कि उसकी तरफ़ tool declarations कैसे गिने जाते हैं, इसलिए नीचे के numbers payload गिनते हैं, bill नहीं।
tiktoken 0.14.0 और o200k_base encoding से, compact JSON पर नापा गया:
| क्या | Token |
|---|---|
Raw tools/list result, सारे fields | 10,494 |
59 declarations (mcp__osfoundry__ prefix वाला name, description, schema) | 7,609 |
| इनमें से descriptions | 2,194 |
| इनमें से input schemas | 4,118 |
इनमें से mcp__osfoundry__ prefixes | 413 |
| Client में ही रहने वाला metadata (title, annotations, securitySchemes) | 3,590 |
एक declaration का median 116 token है। सबसे बड़ा edit_note_tab है, 336 पर, फिर send_channel_message (237) और upload_linked_chat_artifact (233)। सबसे छोटा list_granted_resources है, 57 पर।
Scope के हिसाब से: 30 osfoundry:read tools मिलाकर 2,906 token, 26 osfoundry:write tools 4,378, और 3 osfoundry:run tools 329। Write tools प्रति tool ज़्यादा महँगे हैं क्योंकि उनमें expected_version, idempotency_key और लिखा जाने वाला content होता है।
लगभग 7,600 token हर request के आगे बैठे रहते हैं, चाहे model osFoundry को call करे या न करे। Cached prompt prefixes की वजह से यह जितना सुनाई देता है उससे सस्ता पड़ता है। फिर भी यह context की जगह घेरता है, और जब model तय करता है कि कौन-सा tool सही है, तो 59 names उसका ध्यान खींचने के लिए आपस में होड़ करते हैं।
Codex enabled_tools को कहाँ apply करता है?
ये keys OpenAI के Codex MCP page पर documented हैं: enabled_tools एक allow list है, और disabled_tools एक deny list जो "applied after enabled_tools" होती है। Implementation openai/codex repository में codex-rs/codex-mcp/src/tools.rs में है:
pub(crate) fn allows(&self, tool_name: &str) -> bool { if let Some(enabled) = &self.enabled && !enabled.contains(tool_name) { return false; } !self.disabled.contains(tool_name) } pub(crate) fn filter_tools(tools: Vec<ToolInfo>, filter: &ToolFilter) -> Vec<ToolInfo> { tools .into_iter() .filter(|tool| filter.allows(&tool.tool.name)) .collect() } इसके दो नतीजे हैं। पहला, filter tool.tool.name यानी raw MCP name से compare करता है, इसलिए config.toml में आप read_note_tab लिखते हैं, mcp__osfoundry__read_note_tab नहीं। Prefix बाद में उसी file में जोड़ा जाता है, जब model को दिखने वाले names बनाए जाते हैं। दूसरा, set lookup कभी fail नहीं होता। जो name tools/list में मौजूद नहीं है, वह किसी से match नहीं करता और कोई error भी नहीं देता।
Filter tools/list आने के बाद client में चलता है। इससे prompt छोटा होता है, download नहीं, और server को इसके बारे में कुछ पता नहीं होता।
Client side को दोबारा बनाना
जिस Mac (Apple M1) पर यह नापा गया, उस पर codex binary install नहीं है। catalog.py यहाँ Codex के startup की जगह काम करता है। यह 92 lines का है और uv run --python 3.12 --with tiktoken से चलता है। यह एक TOML file से [mcp_servers.*] tables पढ़ता है, http.client के ज़रिए live endpoint पर तीनों requests भेजता है, raw names पर वही allow-फिर-deny rule लगाता है, mcp__osfoundry__<tool> declarations बनाता है और उन्हें गिनता है:
kept = [t for t in tools if (allow is None or t["name"] in allow) and t["name"] not in deny] decls = [model_decl("osfoundry", t) for t in kept] unknown = sorted(((allow or set()) | deny) - names) Profile file में हर configuration के लिए एक table है। असली config.toml में एक ही [mcp_servers.osfoundry] table होगी, global या हर repository के लिए अलग:
[mcp_servers.notes_kb_records] url = "https://api.osfoundry.io/api/v1/external-mcp" enabled_tools = [ "list_granted_resources", "list_project_resources", "list_note_tabs", "read_note_tab", "read_sheet_range", "search_knowledge_base", "read_knowledge_article", "list_record_items", "read_record_item", ] [mcp_servers.deny_list] url = "https://api.osfoundry.io/api/v1/external-mcp" disabled_tools = ["request_session_run", "run_graph_session", "cancel_graph_run", "propose_gitnest_work", "…the other propose_ tools…", "join_agent_network", "send_agent_message", "wait_for_messages", "list_agents", "set_status", "claim_connected_profile"] [mcp_servers.typo] url = "https://api.osfoundry.io/api/v1/external-mcp" enabled_tools = ["list_granted_resources", "list_project_resources", "read_note", "search_knowledge_base"] Output:
$ uv run --python 3.12 --with tiktoken python catalog.py profiles.toml --runs 10 status (200, 202, 200) initialize 308 B tools/list 45368 B 59 tools instructions 125 chars raw tools/list result: 10494 tokens (o200k_base) profile tools bytes tokens unknown names all_59 59 33559 7609 - read_only 30 13181 2906 - notes_kb_records 9 3937 881 - coding_project 16 7245 1604 - deny_list 40 20943 4693 - typo 3 1449 309 read_note read_only में वे सारे tools हैं जिन पर readOnlyHint: true है। coding_project में नौ Note/KB/Record readers के साथ पाँच GitNest readers, edit_record_item और post_session_note हैं। deny_list वे 19 tools हटाती है जिनका local checkout वाले coding assistant के लिए ज़्यादा काम नहीं: तीन run tools, दस propose_… tools और छह agent-network व profile tools।
59 tools से घटाकर नौ readers पर आने से declarations 7,609 से 881 token पर आ जाते हैं, यानी 88% कम। Deny list सिर्फ़ 4,693 तक पहुँचती है, क्योंकि write tools ही महँगे हैं और यह उनमें से ज़्यादातर को रहने देती है। Allow list ज़्यादा predictable choice है। Server नए tools जोड़े तब भी यह छोटी ही रहती है, और इसे चुनने की असली वजह यही है।
typo वाली row वह failure है जो आपको सच में देखने को मिलेगी। read_note कोई tool नहीं है (read_note_tab है), इसलिए profile में तीन ही tools बचते हैं, और कोई शिकायत नहीं करता। Codex आराम से Note की list दिखा देगा, लेकिन फिर उसे खोलने का कोई तरीका नहीं होगा। यह simulator unknown names print करता है। Docs और filter code से जितना दिखता है, Codex ऐसा नहीं करता।
Handshake में कितना समय लगता है?
catalog.py --runs 10 ने तीनों requests का शुरू से आख़िर तक समय दो बार नापा, एक बार एक ही TLS connection पर और एक बार हर request के लिए नए connection के साथ:
| Batch (हर एक n=10) | initialize median | tools/list median | पूरा startup median | min–max |
|---|---|---|---|---|
| एक connection, batch 1 | 482 ms | 826 ms | 1,515 ms | 1,034–2,899 ms |
| नया connection, batch 1 | 521 ms | 704 ms | 1,715 ms | 1,122–3,107 ms |
| एक connection, batch 2 | 454 ms | 828 ms | 1,441 ms | 741–1,915 ms |
| नया connection, batch 2 | 432 ms | 478 ms | 1,257 ms | 1,088–1,557 ms |
Connection reuse से कोई consistent फ़र्क़ नहीं पड़ा। Runs के बीच का variation असर से ज़्यादा था। Filtering इस number को बिल्कुल नहीं बदलती, क्योंकि Codex दोनों हालात में पूरी list download करता है।
Codex की तीन documented settings इस number से जुड़ती हैं। startup_timeout_sec का default 10 seconds है, और यहाँ सबसे धीमा run 3.1 s का था। mcp_optional_startup_grace_ms का default 1,000 ms है और docs के मुताबिक यह "controls how long Codex waits for optional MCP servers when building the initial tool catalog"। यहाँ नापा गया हर startup इससे ज़्यादा समय ले गया। अगर osFoundry के tools पहले turn से ही मौजूद होने चाहिए, तो documented विकल्प हैं required = true, जो startup को server का इंतज़ार करवाता है और server तैयार न हो तो fail हो जाता है, और startup_readiness = "catalog", जो live connection बनने तक एक valid cached catalogue से startup पूरा होने देता है। Tool calls के लिए फिर भी live connection चाहिए।
Annotations से आपको क्या मिलता है?
default_tools_approval_mode में auto, prompt, writes या approve दिया जा सकता है, और "the writes mode prompts for tools that aren't marked read-only"। osFoundry अपने 59 में से 30 tools पर readOnlyHint: true लगाता है। writes के साथ Codex इन्हें बिना पूछे चलाता है और बाकी 29 के लिए पूछता है, यानी वही बँटवारा जो आप हाथ से करते। किसी एक tool के लिए tools.<tool>.approval_mode default को override करता है, और tools.<tool>.output_token_limit यह limit करता है कि एक tool का result context में कितना जोड़ सकता है।
OAuth layer पर scope भी इसी तरह काम करता है। अगर config में scopes set है तो Codex उसे इस्तेमाल करता है। वरना वह server के challenge या उसके protected-resource metadata से scopes लेता है, और osFoundry की list में osfoundry:read, osfoundry:write, osfoundry:run और osfoundry:models हैं। scopes = ["osfoundry:read"] के साथ token सिर्फ़ पढ़ने के लिए issue होता है, और हर write tool अपने securitySchemes में osfoundry:write declare करता है, चाहे enabled_tools में कुछ भी लिखा हो।
असली filter कौन-सा है?
तीन filters हैं, और उनमें से सिर्फ़ एक security boundary है।
-
config.tomlमेंenabled_tools/disabled_toolsतय करते हैं कि model को क्या offer किया जाए। जो भी इस file को edit कर सकता है, वह इसे बदल सकता है। - osFoundry के Connected tab में Tools checklist, connection पर
allowed_toolsके रूप में store होती है।nullका मतलब है "हर मौजूदा और आने वाला supported tool"। इसे owner set करता है, और कोई local file इसे बढ़ा नहीं सकती। - Project membership और हर call पर होने वाला access check तय करते हैं कि कोई भी tool किस चीज़ को छू सकता है। बिना किसी project वाले connection पर पूरी tool list भी कुछ नहीं पढ़ पाती।
Client filter prompt की साफ़-सफ़ाई के लिए है। इसे token बचाने और model के choices के लिए इस्तेमाल करें, और permissions के लिए बाकी दो का।
अगर आप इसे ख़ुद wire up करें
- दर्जनों tools वाले server के लिए
disabled_toolsकी जगहenabled_toolsचुनें। यह token में छोटा है और server नए tools जोड़े तब भी stable रहता है। - Raw MCP names लिखें, और server update होने पर अपनी list को एक fresh
tools/listसे diff करें। Unknown names चुपचाप fail होते हैं। -
scopesको explicitly set करें, ताकि token में config के इरादे से ज़्यादा कुछ न हो। -
default_tools_approval_mode = "writes"को ऐसे server के साथ इस्तेमाल करें जोreadOnlyHintईमानदारी से set करता हो। Prompt का बँटवारा उतना ही अच्छा है जितने annotations। - अगर server एक second या उससे ज़्यादा दूर है, तो 1,000 ms grace default के भरोसे छोड़ने के बजाय
required = trueऔरstartup_readiness = "catalog"में से एक चुनें। - Server के
instructionsको 512 characters से कम और अपने आप में पूरा रखें। यह tool list की तरह हर session के साथ भेजा जाता है।
Tool list का आम तौर पर context budget से क्या रिश्ता है, यह osFoundry के context window glossary page पर है।


