| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- /**
- * SDK MCP Transport Bridge
- *
- * This file implements a transport bridge that allows MCP servers running in the SDK process
- * to communicate with the Claude Code CLI process through control messages.
- *
- * ## Architecture Overview
- *
- * Unlike regular MCP servers that run as separate processes, SDK MCP servers run in-process
- * within the SDK. This requires a special transport mechanism to bridge communication between:
- * - The CLI process (where the MCP client runs)
- * - The SDK process (where the SDK MCP server runs)
- *
- * ## Message Flow
- *
- * ### CLI → SDK (via SdkControlClientTransport)
- * 1. CLI's MCP Client calls a tool → sends JSONRPC request to SdkControlClientTransport
- * 2. Transport wraps the message in a control request with server_name and request_id
- * 3. Control request is sent via stdout to the SDK process
- * 4. SDK's StructuredIO receives the control response and routes it back to the transport
- * 5. Transport unwraps the response and returns it to the MCP Client
- *
- * ### SDK → CLI (via SdkControlServerTransport)
- * 1. Query receives control request with MCP message and calls transport.onmessage
- * 2. MCP server processes the message and calls transport.send() with response
- * 3. Transport calls sendMcpMessage callback with the response
- * 4. Query's callback resolves the pending promise with the response
- * 5. Query returns the response to complete the control request
- *
- * ## Key Design Points
- *
- * - SdkControlClientTransport: StructuredIO tracks pending requests
- * - SdkControlServerTransport: Query tracks pending requests
- * - The control request wrapper includes server_name to route to the correct SDK server
- * - The system supports multiple SDK MCP servers running simultaneously
- * - Message IDs are preserved through the entire flow for proper correlation
- */
- import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
- import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'
- /**
- * Callback function to send an MCP message and get the response
- */
- export type SendMcpMessageCallback = (
- serverName: string,
- message: JSONRPCMessage,
- ) => Promise<JSONRPCMessage>
- /**
- * CLI-side transport for SDK MCP servers.
- *
- * This transport is used in the CLI process to bridge communication between:
- * - The CLI's MCP Client (which wants to call tools on SDK MCP servers)
- * - The SDK process (where the actual MCP server runs)
- *
- * It converts MCP protocol messages into control requests that can be sent
- * through stdout/stdin to the SDK process.
- */
- export class SdkControlClientTransport implements Transport {
- private isClosed = false
- onclose?: () => void
- onerror?: (error: Error) => void
- onmessage?: (message: JSONRPCMessage) => void
- constructor(
- private serverName: string,
- private sendMcpMessage: SendMcpMessageCallback,
- ) {}
- async start(): Promise<void> {}
- async send(message: JSONRPCMessage): Promise<void> {
- if (this.isClosed) {
- throw new Error('Transport is closed')
- }
- // Send the message and wait for the response
- const response = await this.sendMcpMessage(this.serverName, message)
- // Pass the response back to the MCP client
- if (this.onmessage) {
- this.onmessage(response)
- }
- }
- async close(): Promise<void> {
- if (this.isClosed) {
- return
- }
- this.isClosed = true
- this.onclose?.()
- }
- }
- /**
- * SDK-side transport for SDK MCP servers.
- *
- * This transport is used in the SDK process to bridge communication between:
- * - Control requests coming from the CLI (via stdin)
- * - The actual MCP server running in the SDK process
- *
- * It acts as a simple pass-through that forwards messages to the MCP server
- * and sends responses back via a callback.
- *
- * Note: Query handles all request/response correlation and async flow.
- */
- export class SdkControlServerTransport implements Transport {
- private isClosed = false
- constructor(private sendMcpMessage: (message: JSONRPCMessage) => void) {}
- onclose?: () => void
- onerror?: (error: Error) => void
- onmessage?: (message: JSONRPCMessage) => void
- async start(): Promise<void> {}
- async send(message: JSONRPCMessage): Promise<void> {
- if (this.isClosed) {
- throw new Error('Transport is closed')
- }
- // Simply pass the response back through the callback
- this.sendMcpMessage(message)
- }
- async close(): Promise<void> {
- if (this.isClosed) {
- return
- }
- this.isClosed = true
- this.onclose?.()
- }
- }
|