Skip to content

feat(runtime/codex): execute agent MCP/function tools via the shim#641

Merged
yaozheng-fang merged 2 commits into
mainfrom
feat/codex-mcp-passthrough
Jul 7, 2026
Merged

feat(runtime/codex): execute agent MCP/function tools via the shim#641
yaozheng-fang merged 2 commits into
mainfrom
feat/codex-mcp-passthrough

Conversation

@yaozheng-fang

@yaozheng-fang yaozheng-fang commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What

Makes an agent's MCP and function tools usable on runtime="codex" (against a chat backend like Ark).

Why not just configure the MCP server on the Codex side?

Codex can connect to an MCP server, but it presents the tools to the model as a type:"namespace" object, and it won't route a plain function_call back to a namespaced tool. On Ark that's a dead end (verified directly against /responses):

tool shape Ark /responses Codex
flat function ✅ accepted, model calls it ❌ "unsupported call" for a namespaced MCP tool
namespace unknown tool type: namespace ✅ only form it emits

No Codex config/version reconciles this — create_tool_spec hardcodes MCP tools to namespace, and Ark rejects that type.

Approach

Don't hand the tools to Codex. The Responses shim (which already mediates every model call) advertises the agent's ADK tools to the backend as plain function tools and executes them itself in a bounded loop — feeding results back until the model stops calling them and returning only the final turn to Codex. Codex never sees these tools, so the namespace path is never taken.

Changes (3 files)

  • tools_bridge.py (new): builds (flat function specs, executors) from agent.tools, reusing ADK end to end — BaseToolset.get_tools (incl. MCP), BaseTool._get_declaration + ADK's own _function_declaration_to_tool_param for the schema, and BaseTool.run_async for execution. No schema or dispatch is reimplemented.
  • proxy.py: runs the agent's tools in a shim-internal loop (registered per turn via set_agent_tools); adds get_shim. The tool-less path is unchanged.
  • runtime.py: bridges the agent's tools onto the shim; closes MCP sessions when the turn ends.

Validation

End to end through the real CodexRuntime on the bundled Codex binary (no version bump, no config flag): an Agent(runtime="codex") with an MCPToolset pointing at a live MCP server (get_launch_code) — the model calls the tool and answers with the secret only obtainable via the tool:

tools_bridge: bridging 1 agent tool(s): ['get_launch_code']
"Today's launch code is ZX-7741-QUASAR."

FunctionTools go through the same BaseTool path.

Notes

  • Codex web search is off by default here. Codex's own non-function tools (its hosted web_search, whose external_web_access field Ark rejects) are dropped. This PR also removes the opt-in web-search restoration added in fix/codex-runtime-longtask (the CODEX_SHIM_MAX_TOOL_ITERS shim web loop); the retry/timeout knobs from that change are kept.
  • Tools execute in the shim, so they're invisible to Codex — they don't currently surface as separate ADK events for tracing/UI. A shim→runtime event channel can add that later.
  • The shim is shared per backend; agent tools are registered per turn. Concurrent turns with different tool sets against the same backend could interleave — fine for the common one-turn-per-agent case; can be tightened by keying the shim on the tool set.

🤖 Generated with Claude Code

@yaozheng-fang
yaozheng-fang force-pushed the feat/codex-mcp-passthrough branch from 4103f22 to 412f8bf Compare July 7, 2026 02:33
Codex presents MCP/function tools to the model as a namespace tool that Ark
rejects, and it won't route a plain function_call back to a namespaced tool.
So instead of configuring tools on the Codex side, the Responses shim now
advertises the agent's ADK tools to the backend as plain function tools and
executes them itself in a bounded tool loop, invisibly to Codex.

- New tools_bridge builds (flat function specs, executors) from the agent's
  tools, reusing ADK end to end: BaseToolset.get_tools (incl. MCP),
  BaseTool._get_declaration + _function_declaration_to_tool_param for the
  schema, and BaseTool.run_async for execution.
- proxy runs the agent's tools in a shim-internal loop (registered per turn
  via set_agent_tools); the tool-less path is unchanged. Codex's own
  non-function tools (e.g. its hosted web_search, whose schema Ark rejects)
  are dropped, so Codex web search is disabled by default on a chat backend.
- runtime bridges the agent's tools onto the shim and closes MCP sessions
  when the turn ends.

Note: this also removes the opt-in web-search restoration added in
fix/codex-runtime-longtask (the CODEX_SHIM_MAX_TOOL_ITERS shim web loop),
keeping Codex web search off by default. The retry/timeout knobs from that
change are retained.

Validated end to end: an Agent(runtime=codex) with an MCPToolset pointing at a
live MCP server calls the tool and answers from its result, on the bundled
Codex binary (no namespace, no config flag, no binary bump).
@yaozheng-fang
yaozheng-fang force-pushed the feat/codex-mcp-passthrough branch from 412f8bf to 4f0eb0c Compare July 7, 2026 02:34
…rough

# Conflicts:
#	veadk/runtime/codex/runtime.py
@yaozheng-fang
yaozheng-fang merged commit cd39533 into main Jul 7, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants