File size: 4,503 Bytes
064bfd6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | /**
* 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?.()
}
}
|