SdkControlTransport.ts
services/mcp/SdkControlTransport.ts
137
Lines
4503
Bytes
3
Exports
2
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with integrations, mcp. It contains 137 lines, 2 detected imports, and 3 detected exports.
Important relationships
Detected exports
SendMcpMessageCallbackSdkControlClientTransportSdkControlServerTransport
Keywords
transportmessageresponsevoidprocesscontrolpromisejsonrpcmessageisclosedservers
Detected imports
@modelcontextprotocol/sdk/shared/transport.js@modelcontextprotocol/sdk/types.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
/**
* 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?.()
}
}