SdkControlTransport.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /**
  2. * SDK MCP Transport Bridge
  3. *
  4. * This file implements a transport bridge that allows MCP servers running in the SDK process
  5. * to communicate with the Claude Code CLI process through control messages.
  6. *
  7. * ## Architecture Overview
  8. *
  9. * Unlike regular MCP servers that run as separate processes, SDK MCP servers run in-process
  10. * within the SDK. This requires a special transport mechanism to bridge communication between:
  11. * - The CLI process (where the MCP client runs)
  12. * - The SDK process (where the SDK MCP server runs)
  13. *
  14. * ## Message Flow
  15. *
  16. * ### CLI → SDK (via SdkControlClientTransport)
  17. * 1. CLI's MCP Client calls a tool → sends JSONRPC request to SdkControlClientTransport
  18. * 2. Transport wraps the message in a control request with server_name and request_id
  19. * 3. Control request is sent via stdout to the SDK process
  20. * 4. SDK's StructuredIO receives the control response and routes it back to the transport
  21. * 5. Transport unwraps the response and returns it to the MCP Client
  22. *
  23. * ### SDK → CLI (via SdkControlServerTransport)
  24. * 1. Query receives control request with MCP message and calls transport.onmessage
  25. * 2. MCP server processes the message and calls transport.send() with response
  26. * 3. Transport calls sendMcpMessage callback with the response
  27. * 4. Query's callback resolves the pending promise with the response
  28. * 5. Query returns the response to complete the control request
  29. *
  30. * ## Key Design Points
  31. *
  32. * - SdkControlClientTransport: StructuredIO tracks pending requests
  33. * - SdkControlServerTransport: Query tracks pending requests
  34. * - The control request wrapper includes server_name to route to the correct SDK server
  35. * - The system supports multiple SDK MCP servers running simultaneously
  36. * - Message IDs are preserved through the entire flow for proper correlation
  37. */
  38. import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
  39. import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'
  40. /**
  41. * Callback function to send an MCP message and get the response
  42. */
  43. export type SendMcpMessageCallback = (
  44. serverName: string,
  45. message: JSONRPCMessage,
  46. ) => Promise<JSONRPCMessage>
  47. /**
  48. * CLI-side transport for SDK MCP servers.
  49. *
  50. * This transport is used in the CLI process to bridge communication between:
  51. * - The CLI's MCP Client (which wants to call tools on SDK MCP servers)
  52. * - The SDK process (where the actual MCP server runs)
  53. *
  54. * It converts MCP protocol messages into control requests that can be sent
  55. * through stdout/stdin to the SDK process.
  56. */
  57. export class SdkControlClientTransport implements Transport {
  58. private isClosed = false
  59. onclose?: () => void
  60. onerror?: (error: Error) => void
  61. onmessage?: (message: JSONRPCMessage) => void
  62. constructor(
  63. private serverName: string,
  64. private sendMcpMessage: SendMcpMessageCallback,
  65. ) {}
  66. async start(): Promise<void> {}
  67. async send(message: JSONRPCMessage): Promise<void> {
  68. if (this.isClosed) {
  69. throw new Error('Transport is closed')
  70. }
  71. // Send the message and wait for the response
  72. const response = await this.sendMcpMessage(this.serverName, message)
  73. // Pass the response back to the MCP client
  74. if (this.onmessage) {
  75. this.onmessage(response)
  76. }
  77. }
  78. async close(): Promise<void> {
  79. if (this.isClosed) {
  80. return
  81. }
  82. this.isClosed = true
  83. this.onclose?.()
  84. }
  85. }
  86. /**
  87. * SDK-side transport for SDK MCP servers.
  88. *
  89. * This transport is used in the SDK process to bridge communication between:
  90. * - Control requests coming from the CLI (via stdin)
  91. * - The actual MCP server running in the SDK process
  92. *
  93. * It acts as a simple pass-through that forwards messages to the MCP server
  94. * and sends responses back via a callback.
  95. *
  96. * Note: Query handles all request/response correlation and async flow.
  97. */
  98. export class SdkControlServerTransport implements Transport {
  99. private isClosed = false
  100. constructor(private sendMcpMessage: (message: JSONRPCMessage) => void) {}
  101. onclose?: () => void
  102. onerror?: (error: Error) => void
  103. onmessage?: (message: JSONRPCMessage) => void
  104. async start(): Promise<void> {}
  105. async send(message: JSONRPCMessage): Promise<void> {
  106. if (this.isClosed) {
  107. throw new Error('Transport is closed')
  108. }
  109. // Simply pass the response back through the callback
  110. this.sendMcpMessage(message)
  111. }
  112. async close(): Promise<void> {
  113. if (this.isClosed) {
  114. return
  115. }
  116. this.isClosed = true
  117. this.onclose?.()
  118. }
  119. }