Skip to content

V2 session runner does not include MCP tools in LLM requests #45333

Closed as not planned

Description

@Vojtech-Pet

Description

When using the V2 interactive session API (POST /api/session/{id}/prompt, used by the
desktop/web app), tool definitions from connected MCP servers are never included in the
outgoing request to the model. Built-in tools (read, glob, grep, bash, edit, etc.) are
included correctly. The MCP server shows as connected and its tools are discoverable, but
the model is never given them, so it can't call them.

The legacy V1 pipeline (POST /session/{id}/message) does include MCP tools correctly, so
this looks specific to the V2 session runner's tool materialization.

Reproduction

  1. Configure any MCP server with one simple tool, e.g. an example_ping tool that takes no
    arguments and returns a string.
  2. Confirm the server connects (GET /mcp shows "status": "connected" for it).
  3. Start a session via the interactive UI (or POST /api/session then
    POST /api/session/{id}/prompt) with any agent.
  4. Send a prompt that unambiguously requires the tool, e.g. "Call example_ping and show me
    the result. Call the tool, don't explain first."
  5. Inspect the outgoing request to the model provider (e.g. via a local proxy in front of
    an OpenAI-compatible endpoint, or any provider request logging you have).
  6. Compare: built-in tools (read, glob, etc.) are present in tools[]; example_ping
    is absent.

If there's a built-in way to inspect the materialized V2 tool set for a session without an
external proxy, it'd be useful to know for future debugging — I didn't find one and had to
tap the outgoing HTTP request directly.

Expected behavior

The V2 ToolRegistry should materialize a policy-filtered union of built-in tools, MCP
tools, plugin tools, and structured-output tools before the LLM request is built — matching
the intent already documented in the runner's own implementation checklist (see below).

Actual behavior

The V2 interactive request only ever contains the subset of tools that
packages/core/src/tool/registry.ts's ToolRegistry.materialize() currently supports:
built-in ApplicationTools entries plus generic locally-registered tools. Tools from
configured MCP servers are never added to that set, so they never reach the model.

Evidence

Reproduced with two independent, unrelated MCP servers (a stdio-local MCP server we wrote,
and an already-established, previously-working MCP server used for something unrelated to
this bug), ruling out a problem specific to one server or its tool schema:

  • Server A: 4 tools expected in the outgoing request → 0/4 present.
  • Server B: its tools also absent from the outgoing request.

Reproduced on two builds:

  • Global release install, opencode --version → 1.18.18.
  • This repo's own dev checkout, run directly via
    bun run --cwd packages/opencode --conditions=browser src/index.ts serve, at commit
    5c76f80b8898fc62c743b5768d6329f5bd8bad2e (git describe →
    github-v1.2.25-1646-g5c76f80b88, packages/opencode/package.json version 1.18.15).

Confirmed the request that actually reaches the model provider (via a transparent tap in
front of the OpenAI-compatible endpoint) contains e.g.:

Activity

  1. github-actions commented on Aug 26, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If #45333 is specifically about the ToolRegistry.materialize() gap rather than the per-request instance lifetime described in #38470, they may be related but distinct root causes — worth cross-referencing either way.

  2. Vojtech-Pet commented on Aug 26, 2026

    @Vojtech-Pet
    Author

    Thanks for the references — read through both.

    #38470: its root cause (confirmed in the thread, with PR #41728) is that opencode serve runs with instance: false, so the lazy Plugin/InstanceState bootstrap that initializes MCP never runs for a bare POST /api/session HTTP session — unlike the TUI or opencode run, which both trigger that bootstrap. That's a different mechanism from what I found here: in my reproduction, GET /mcp already reports the server as connected (MCP is live and populated) for the exact session that still gets no MCP tools. So this isn't an initialization-timing gap — ToolRegistry.materialize() in packages/core/src/tool/registry.ts (the V2 registry backing /api/session/{id}/prompt) structurally only sources built-in ApplicationTools and local registrations; there's no MCP merge step in that file at all, regardless of whether MCP itself is already up. PR #41728 also only touches packages/opencode/src/cli/cmd/serve.ts, so it wouldn't affect this V2 path either way.

    #33027: same user-visible symptom (connected but not exposed), but no source-level cause was pinned down there (older version, TUI, and the issue itself notes the binary couldn't be inspected directly) — I can't tell from it whether it's the same gap or something else.

    So: #38470 looks related but distinct (different code path, already has a proposed fix that doesn't touch V2's ToolRegistry), and #33027 is inconclusive relative to this one. Happy to have maintainers merge/link these as they see fit — just wanted to share what the V2 trace actually shows in case it's useful for triage.

  3. doberkofler commented on Oct 18, 2026

    @doberkofler

    Confirmed on v2.0.32 (current release)

    This still reproduces on the installed release, not only on the older 1.18.x
    builds in the report. I drove the same V2 path described here:
    POST /api/session followed by POST /api/session/{id}/prompt, with the agent
    loop waited to idle through POST /api/experimental/session/{id}/wait.

    To remove my own MCP server and tool schema from the equation, I used a
    dependency-free stdio server with exactly one tool (example_ping, no
    arguments, returns "pong") and a local OpenAI-compatible provider that records
    the outgoing tools[] array. The provider calls the tool when it is offered and
    otherwise answers NO_PING_TOOL, so the result is visible in the transcript as
    well as in the captured request.

    Reproduction: https://github.com/doberkofler/opencode-mcp-v2-repro

    Measured result:

    opencode --version        -> 2.0.32
    GET /api/mcp              -> reproping: connected
    MCP tools/list            -> example_ping
    provider tools[]          -> edit, glob, grep, question, read, shell, skill,
                                 subagent, webfetch, websearch, write, execute
    tools containing "ping"   -> none
    server name in request    -> absent from system prompt, tools, and Code Mode
    assistant reply           -> NO_PING_TOOL
    

    The execute tool is present. Its Code Mode catalog does not mention the MCP
    server either, so the tool is not reachable through that path in this run. The
    server is connected and its catalog has been read before the session is
    created.

    The capture is from a real provider request, not from session metadata. Built-in
    tools are present in the same request, which matches your observation that only
    the MCP materialization step is missing.

    I can contribute this as a regression test if useful. The harness is three
    small Node files with no dependencies: MCP server, recording provider, and a
    driver that starts opencode serve --standalone, calls the V2 endpoints, and
    asserts on the captured tools[]. It should map naturally onto a test around
    ToolRegistry.materialize(); tell me where you want it and I will open a PR.

    Happy to also re-run it against a candidate build and post the before/after
    request capture here.

  4. opencode-agent commented on Oct 18, 2026

    @opencode-agent
    Contributor

    Closing as stale. This describes an old preview of the V2 engine that shipped in the 1.x builds (1.18.x), not the current V2 runner. The code it quotes (tool/registry.ts, ToolRegistry.materialize(), the runner checklist) has since been removed.

    Current V2 has sent MCP tools to the model since #34513. By default they are exposed through Code Mode: they appear in the execute tool's catalog rather than as top-level entries in the request's tools[]. Checking tools[] for an MCP tool name is therefore not a valid test unless that tool has codemode: false set.

    @doberkofler your 2.0.32 repro is a different problem: the startup race tracked in #51812. In your logs the MCP server was started twice under two project instances (the session used the /var path, the project used /private/var), and the model request went out about 2ms after the second start, before that server could connect. Please follow #51812 for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions