Repository navigation
feat(core): add mcp support - #34513
Merged
Merged
Conversation
Connect configured MCP servers in per-server child scopes at layer boot, tracking status and a startup deferred per server. Query methods wait for the relevant server(s) to finish startup before serving. Drop the in-memory connect/disconnect/add overrides; runtime enable/disable will come later through config updates. List methods (tools/instructions/ prompts/resourceCatalog) are aggregate across all servers with per-item provenance; prompt/readResource stay server-scoped for routing. Add a Connection abstraction in client.ts so the SDK Client never leaks into the rest of core.
Capture each server's initialize instructions on the Connection at connect
time and expose them via MCP.instructions(). Add McpGuidance, a system-context
source modeled on SkillGuidance, that renders the <mcp_instructions> block and
hides servers whose contributed tools are all denied for the agent.
instructions() returns just { server, instructions }; the guidance layer
fetches tools() separately and does the agent-aware permission correlation,
mirroring how SkillGuidance composes raw data with agent permissions.
Add tools() and onToolsChanged() to the MCPClient.Connection abstraction: list is capability-gated, paginated with cursor-dedup, and tolerates unresolvable outputSchemas via a per-page fallback. Tool listing is folded into connect so a listing failure marks the server failed rather than leaving it connected with a silently empty list. Cache MCP.Tool values per server, refresh on tools/list_changed notifications, and publish McpEvent.ToolsChanged. MCP.tools() aggregates the cached tools across all connected servers.
Add MCP.callTool plus a dynamic jsonSchema mode for Tool.make, and register each MCP server's tools as canonical Location-scoped tools via an McpTool producer that reconciles on tools-changed notifications. Content-first tool results preserve image/audio blocks; remove the arbitrary pagination page cap; log mcp connection outcomes. Tool naming keeps v1 parity (server_tool) so existing deny rules apply.
isV1 only triggered on a fixed set of top-level keys, so a config with just $schema and a v1-shaped mcp block (servers directly under mcp, with enabled) was parsed as v2 and silently produced zero servers. Detect the v1 mcp shape (no servers wrapper, entries with a type) so these files migrate correctly.
Thin OAuthClientProvider factory plus a Store contract for persisting an MCP server's OAuth artifacts (DCR client info, PKCE verifier, tokens). The SDK drives registration, PKCE, and refresh through these callbacks; storage is injected so the provider stays decoupled from Credential and Effect.
A successful connect set entry.tools but never published ToolsChanged, so the tool registry only registered servers that were connected during the initial sweep or that emit their own list-changed notification. Servers that finish connecting later stayed invisible to the model. Publish ToolsChanged on connect so their tools get registered.
Register every remote MCP server as an OAuth integration so credentials live in the global store instead of committed config. Identity is keyed on name + url, so same-url servers under different names hold separate accounts. The connect path builds an auth provider from any stored credential (refreshing and persisting back to the same row) and, when none exists, an empty in-memory provider so an auth-gated connect reports needs_auth instead of a generic failure. A ConnectionUpdated subscriber reconnects a server when its credential changes, so login/logout takes effect without a restart. The interactive authorize flow stands up a loopback callback server (port from callback_port, then redirect_uri, then ephemeral), validates a CSRF state on the redirect, supports static client credentials, and exchanges the code for a storable credential. Invalid credentials the SDK rejects are dropped via the raw credential service to avoid racing the reconnect subscriber.
Add a GET /api/mcp route returning each configured server's name and connection status, modeled on the integration group. The status union becomes a canonical wire contract in @opencode-ai/schema/mcp that core re-exports, and a new mcp.status.changed event fires whenever a server settles (connected, failed, needs_auth, closed) so clients can refresh without polling. Regenerates the client SDK.
Wire the new /api/mcp endpoint into the data context: bootstrap fetches the server list and a mcp.status.changed subscription refreshes it live. Re-point the status dialog, footer connected-count, and the /mcps dialog (now read-only) off the dead sync stub onto data.location.mcp, and toast once when a server enters a failed or needs-auth state. Regenerates the legacy js SDK.
MCP.servers() constructed ServerInfo with config: entry.config, which re-validated the stored config against ConfigMCP.Server. Config objects from the document pipeline carry present-but-undefined optional keys (cwd, environment) that the schema rejects, so servers() threw and GET /api/mcp returned 500 with an empty body. Nothing consumes ServerInfo.config, so removing it fixes the endpoint; name, status, integrationID, and connection remain.
# Conflicts: # packages/client/src/generated-effect/client.ts # packages/client/src/generated/client.ts # packages/client/src/generated/types.ts # packages/sdk/js/src/v2/gen/types.gen.ts
This was referenced Jul 19, 2026
jinhuang712
pushed a commit
to jinhuang712/opencode
that referenced
this pull request
Sep 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes #34435