feat(runtime): configurable MCP server on BuiltInAgent (Bearer + per-call user header)#4420
Merged
mme merged 9 commits intoMay 7, 2026
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📣 Social Copy GeneratorGenerate social media copies (Twitter/X, LinkedIn, Blog Post) for this PR using Claude.
|
@copilotkit/a2ui-renderer
@copilotkit/agentcore-runner
@copilotkitnext/angular
@copilotkit/core
@copilotkit/react-core
@copilotkit/react-native
@copilotkit/react-textarea
@copilotkit/react-ui
@copilotkit/runtime
@copilotkit/runtime-client-gql
@copilotkit/sdk-js
@copilotkit/shared
@copilotkit/sqlite-runner
@copilotkit/voice
@copilotkit/web-inspector
commit: |
lukasmoschitz
added a commit
that referenced
this pull request
Apr 29, 2026
The original PR for per-call MCP header resolution went one layer below
@ai-sdk/mcp and instantiated @modelcontextprotocol/sdk's transports
directly. Vercel has since graduated MCP support to a stable surface —
createMCPClient, MCPClient, MCPClientConfig, MCPTransport,
MCPTransportConfig, OAuthClientProvider — and the rest of the agent
already runs on Vercel (streamText, tool, message conversion). Going
deeper just for MCP was an architectural inconsistency without a
payoff.
Re-architect on top of the stable Vercel surface:
- Drop the deprecated experimental_createMCPClient alias for stable
createMCPClient. The agent module no longer imports
@modelcontextprotocol/sdk; that's confined to mcp-transport.ts.
- Replace the discriminated MCPClientConfigHTTP|SSE pair with a unified
MCPClientConfig shape that mirrors Vercel's MCPTransportConfig and
adds the two CopilotKit extensions Vercel doesn't cover: authToken
(Bearer shorthand) and getHeaders (per-call resolver).
- New helper openMcpClient(config, requestHeaders, input) branches on
whether getHeaders is set: if so, instantiate CopilotKitMCPTransport
(new file) and pass to createMCPClient as a custom transport; if not,
hand the static-only config straight to createMCPClient and let
Vercel's HttpMCPTransport / SseMCPTransport handle the wire.
- CopilotKitMCPTransport implements @ai-sdk/mcp's MCPTransport interface.
Internally it wraps StreamableHTTPClientTransport with a fetch that
applies authToken + getHeaders per outbound HTTP request — same
pattern the buildHttpTransportOptions helper used, just behind a
proper transport contract.
- MCPClientProvider becomes Pick<MCPClient, "tools">. Existing inline
mocks ({ tools: vi.fn() }) still satisfy the structural type, and
Vercel-built MCPClient instances pass through without a wrapper.
- intelligence.toMCPServer() returns the unified MCPClientConfig and
tightens the user-id guard to userId?.trim() so whitespace-only
values don't slip through.
- Re-export MCPClient, MCPTransport, OAuthClientProvider, OAuthTokens,
UnauthorizedError from @copilotkit/runtime/v2 so consumers don't
need a direct @ai-sdk/mcp dependency.
- Side-effect bug fix: SSE config's static headers now actually reach
the wire. The old direct-SDK construction passed the headers map as
the wrong-shape options arg and the SDK silently ignored it; Vercel's
SseMCPTransport applies them via commonHeaders() on every request.
Refs CPK-7526, addresses PR #4420 review feedback.
Convenience re-exports so consumers wiring custom MCP clients (via `mcpClients`) or custom transports don't need to add `@ai-sdk/mcp` to their dependencies just to type the values.
…sors Adds `mcpServer?: boolean` to `CopilotKitIntelligenceConfig` (default `false`). When true, the runtime emits the per-request bag the agent needs to attach the platform's MCP server. Internal accessors `ɵisMcpServerEnabled()` and `ɵgetApiKey()` round out the existing `ɵgetApiUrl()`. Used by the runtime layer in the forthcoming auto-attach commit; not part of the public surface.
…rwardedProps Runtime side (intelligence/run.ts): when `runtime.intelligence.mcpServer` is enabled, build a `copilotkitIntelligence` bag carrying the resolved user-id, project apiKey, and the platform MCP URL, and pass it through on `RunAgentInput.forwardedProps` to the agent. Skipped when the flag is off — runs that don't go through this Intelligence path simply don't see the bag. Agent side (BuiltInAgent's run code): if `forwardedProps.copilotkitIntelligence` contains all three string values AND the user's static `config.mcpServers` doesn't already include the same URL, append a per-request `MCPClientConfigHTTP`. Its `options.fetch` closes over apiKey + userId and stamps `Authorization: Bearer <apiKey>` and `X-Cpki-User-Id: <userId>` on every outbound MCP call. The custom fetch is the MCP TypeScript SDK's documented extension point for per-request header injection — no extra wrapper class, no separate framework concept. The agent class is otherwise untouched: no new fields, no per-request side channels, no typed reference to `CopilotKitIntelligence`. Other agents that don't read `forwardedProps.copilotkitIntelligence` ignore the bag. Pulls in the AI SDK's stable `createMCPClient` export (rename from `experimental_createMCPClient`) — the experimental name was deprecated; `mcp-clients.test.ts`'s mock setup follows.
Four cases at the agent layer (BuiltInAgent reading forwardedProps):
* attaches when `copilotkitIntelligence` carries all three strings
(userId, apiKey, mcpUrl) — outbound headers carry Authorization +
X-Cpki-User-Id.
* does NOT attach when the bag is absent (no Intelligence wiring on
this run).
* does NOT attach when the bag is partial (e.g. mcpUrl missing).
* does NOT attach when the user has already configured an MCP server
pointing at the same URL — explicit user config wins, with the
user's headers and resolver hitting the wire.
9c0cc84 to
faa2a55
Compare
mme
added a commit
that referenced
this pull request
May 6, 2026
Contributor
|
Things Claude wanted me to tell you:
|
…rops.auth.copilotkitIntelligence Move the per-request Intelligence MCP bag from forwardedProps.copilotkitIntelligence to forwardedProps.auth.copilotkitIntelligence so the Intelligence-side redaction policy strips it. The 'auth' namespace is the convention for credentials; persistence sinks (Postgres, Redis, S3) and FE replay paths in apps/realtime-gateway already strip everything under it. Updates: - Emitter (handlers/intelligence/run.ts): merge the bag into a single forwardedProps.auth object alongside any upstream auth keys, and only emit the auth namespace when there is something to put in it. - Reader (agent/index.ts): read from forwardedProps.auth.copilotkitIntelligence instead of forwardedProps.copilotkitIntelligence. - Tests (intelligence-mcp-helper.test.ts): three fixtures rewritten to the nested shape.
…tkitruntimev2-configurable-mcp-server-on-builtinagent
…DER constant Two CR comments addressed: - Rename the local destructure of forwardedProps.auth.copilotkitIntelligence from 'cki' to 'cpki' so it matches the project-wide abbreviation already used in metadata fields (cpki_event_id, cpki_event_seq, etc). - Replace the inline 'X-Cpki-User-Id' string literal with the existing INTELLIGENCE_USER_ID_HEADER constant exported from intelligence-platform/client. Applies to the runtime auto-attach in agent/index.ts and to the three test sites in intelligence-mcp-helper.test.ts so the user-side and runtime-side stay in sync.
…ble-mcp-server-on-builtinagent
AlemTuzlak
approved these changes
May 7, 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.
Summary
Closes CPK-7526.
@copilotkit/runtime/v2'sBuiltInAgentalready supported MCP servers, but only with static headers set at client construction. CopilotKit Intelligence's/mcpendpoint requires a two-axis auth contract that breaks under static headers:Authorization: Bearer cpk-{projectId}_{shortToken}_{longToken}. Same on every call.X-Cpki-User-Id. Fresh per outbound MCP HTTP request — never cached. Determines which user's persistent bash sandbox +/threadsview a call sees.Without per-call user-id, every demo session for a given project would share one bash sandbox, breaking the user-isolation contract that
cpki.shell_contextsandThreadsFsenforce on the platform side.This PR adds the missing per-call resolver hook and is the precondition for CPK-7527 (Phase 4b SL integration), which wires it into
Intelligence/demos/simple-agent.What's in here
Public API surface
MCPClientConfigshape mirrors@ai-sdk/mcp'sMCPTransportConfig(single discriminatedtype: "http" | "sse",url,headers,authProvider) plus two CopilotKit extensions:authToken(static Bearer shorthand) andgetHeaders(per-call resolver invoked on every outbound HTTP request — initialize, tools/list, tools/call, reconnects).MCPRequestContextcarriesrequestHeaders(snapshot of the agent's per-run forwarded headers),input, andmcpServerUrl.MCPHeaderResolverErrorwraps resolver throws soRUN_ERRORattributes the failure to the resolver via ES2022Error.cause.BuiltInAgent.headers: Record<string, string> = {}is now declared on the class. The runtime's existingextractForwardableHeadersfeature-detect (if (agent.headers)inconfigureAgentForRequest) only forwards headers to agents that already declare a truthy field — initializing to{}activates that path so the BFF's per-request headers reach the resolver via context.INTELLIGENCE_USER_ID_HEADERconstant exported as the canonical header name ("x-cpki-user-id") — re-exported through bothintelligence-platformandv2/runtimebarrels.Architecture
The agent's MCP layer now sits directly on
@ai-sdk/mcp's stable surface (createMCPClient,MCPClient,MCPTransport) instead of bypassing it to@modelcontextprotocol/sdk. The agent module no longer imports@modelcontextprotocol/sdkat all — that's confined to a smallCopilotKitMCPTransportclass that implements Vercel'sMCPTransportinterface and is only instantiated when agetHeadersresolver is present.For static-only configs (no per-call hooks), the config is handed straight to
createMCPClientand Vercel's built-inHttpMCPTransport/SseMCPTransporthandle the wire.MCPClient,MCPTransport,OAuthClientProvider,OAuthTokens, andUnauthorizedErrorare re-exported from@copilotkit/runtime/v2so consumers don't need a direct@ai-sdk/mcpdependency.Side-effect bug fix: SSE static headers
The pre-existing direct-SDK construction passed
serverConfig.headersas the wrong-shape options arg toSSEClientTransport, and the SDK silently ignored it — auth on SSE simply didn't work. Vercel'sSseMCPTransportcorrectly appliesheadersvia itscommonHeaders()pipeline. The new architecture inherits the fix; a regression test inmcp-servers-integration.test.tsproves staticheadersnow reach the wire on the SSE path.What's intentionally NOT in here
Intelligence/demos/simple-agent(CPK-7527)./mcproute changes in Intelligence (already shipped onmme/integrate-sl).Notes for reviewers
BuiltInAgent.headersis a real public field, not just an ad-hoc property. It's load-bearing for the runtime's existing header-forwarding feature-detect.aimockredactsAuthorizationto[REDACTED]in its journal, so tests that need to verify the actual outgoing Bearer use avi.spyOn(globalThis, "fetch")recorder instead of the journal. Thex-cpki-user-idheader isn't redacted, so per-call user-id assertions read from the journal directly.toMCPServer()helper. Earlier iterations of this PR shipped aCopilotKitIntelligence.toMCPServer()shortcut. It conflated thread management (the class's actual job) with MCP transport configuration and baked in opinionated behavior that not every deployment will want; dropped in favor of the inline pattern shown above using the exportedINTELLIGENCE_USER_ID_HEADERconstant. Same line count as the helper-call form, more flexible.tsc --noEmitOOM:pnpm nx run @copilotkit/runtime:check-typesruns out of heap onmainat 8GB. Reproduces on a clean checkout without any changes from this PR. Worth tracking as a separate issue. Build (tsdown) is unaffected; the full vitest suite (1419 tests, 101 files) passes.Test plan
pnpm nx run @copilotkit/runtime:test -- mcp-servers-integration— 15 cases pass (8 existing + 6 per-call/static-header + 1 new SSE regression test)pnpm nx run @copilotkit/runtime:test -- mcp-clients— 8 cases pass (existing user-managed-clients suite, includingMCPClient↔MCPClientProvidertype-compat check)pnpm nx run @copilotkit/runtime:test --skip-nx-cache— full suite 1419/1419pnpm nx run @copilotkit/runtime:build—tsdowncleanoxlinton changed files — 0 errors