| 82 | * (direct mode) or per socket connection (daemon mode). |
| 83 | */ |
| 84 | export class MCPSession { |
| 85 | private clientSupportsRoots = false; |
| 86 | /** From the initialize handshake — attributes usage rollups to the agent host. */ |
| 87 | private clientInfo: ClientInfo | undefined; |
| 88 | private rootsAttempted = false; |
| 89 | private resolvePromise: Promise<void> | null = null; |
| 90 | private explicitProjectPath: string | null; |
| 91 | |
| 92 | constructor( |
| 93 | private transport: JsonRpcTransport, |
| 94 | private engine: MCPEngine, |
| 95 | opts: MCPSessionOptions = {}, |
| 96 | ) { |
| 97 | this.explicitProjectPath = opts.explicitProjectPath ?? null; |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Start handling messages from the transport. Returns immediately — the |
| 102 | * session lives for as long as the transport is open. |
| 103 | */ |
| 104 | start(): void { |
| 105 | this.transport.start(this.handleMessage.bind(this)); |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Tear down the session. Does NOT touch the engine (the engine may serve |
| 110 | * other sessions) or call `process.exit` (the daemon decides when to exit). |
| 111 | */ |
| 112 | stop(): void { |
| 113 | this.transport.stop(); |
| 114 | } |
| 115 | |
| 116 | /** Underlying transport — exposed for daemon-side close hooks. */ |
| 117 | getTransport(): JsonRpcTransport { |
| 118 | return this.transport; |
| 119 | } |
| 120 | |
| 121 | private async handleMessage(message: JsonRpcRequest | JsonRpcNotification): Promise<void> { |
| 122 | const isRequest = 'id' in message; |
| 123 | switch (message.method) { |
| 124 | case 'initialize': |
| 125 | if (isRequest) await this.handleInitialize(message as JsonRpcRequest); |
| 126 | break; |
| 127 | case 'initialized': |
| 128 | // Notification that client has finished initialization — no action needed. |
| 129 | break; |
| 130 | case 'tools/list': |
| 131 | if (isRequest) await this.handleToolsList(message as JsonRpcRequest); |
| 132 | break; |
| 133 | case 'tools/call': |
| 134 | if (isRequest) await this.handleToolsCall(message as JsonRpcRequest); |
| 135 | break; |
| 136 | case 'ping': |
| 137 | if (isRequest) this.transport.sendResult((message as JsonRpcRequest).id, {}); |
| 138 | break; |
| 139 | case 'resources/list': |
| 140 | // We expose no MCP resources, but some clients (opencode, Codex) probe |
| 141 | // for them on connect; reply with an empty list instead of a |
nothing calls this directly
no outgoing calls
no test coverage detected