| /** | |
| * 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?.() | |
| } | |
| } | |