| 120 | ): LocalMcpRequestHandlerConfig => ("defaultConfig" in input ? input : { defaultConfig: input }); |
| 121 | |
| 122 | export const createMcpRequestHandler = ( |
| 123 | input: ExecutorMcpServerConfig | LocalMcpRequestHandlerConfig, |
| 124 | ): McpRequestHandler => { |
| 125 | const handlerConfig = normalizeHandlerConfig(input); |
| 126 | const transports = new Map<string, WebStandardStreamableHTTPServerTransport>(); |
| 127 | const servers = new Map<string, McpServer>(); |
| 128 | const resources = new Map<string, McpResource>(); |
| 129 | const sessionEngines = new Map<string, AnyExecutionEngine>(); |
| 130 | const sessionClosers = new Map<string, () => Promise<void>>(); |
| 131 | const approvals = makeInProcessBrowserApprovalStore(); |
| 132 | const defaultEngine = engineFromConfig(handlerConfig.defaultConfig); |
| 133 | |
| 134 | const pausedDetail = ( |
| 135 | sessionId: string, |
| 136 | executionId: string, |
| 137 | ): Promise<ReturnType<typeof formatPausedExecution> | null> => |
| 138 | (sessionEngines.get(sessionId) ?? defaultEngine) |
| 139 | ? Effect.runPromise( |
| 140 | (sessionEngines.get(sessionId) ?? defaultEngine)!.getPausedExecution(executionId).pipe( |
| 141 | Effect.map((paused) => (paused ? formatPausedExecution(paused) : null)), |
| 142 | Effect.orElseSucceed(() => null), |
| 143 | ), |
| 144 | ) |
| 145 | : Promise.resolve(null); |
| 146 | |
| 147 | const configForResource = async (resource: McpResource): Promise<LocalMcpServerConfig> => { |
| 148 | if (!handlerConfig.createConfigForResource) return { config: handlerConfig.defaultConfig }; |
| 149 | return handlerConfig.createConfigForResource(resource); |
| 150 | }; |
| 151 | |
| 152 | const dispose = async (id: string, opts: { transport?: boolean; server?: boolean } = {}) => { |
| 153 | const t = transports.get(id); |
| 154 | const s = servers.get(id); |
| 155 | const close = sessionClosers.get(id); |
| 156 | transports.delete(id); |
| 157 | servers.delete(id); |
| 158 | resources.delete(id); |
| 159 | sessionEngines.delete(id); |
| 160 | sessionClosers.delete(id); |
| 161 | if (opts.transport) await ignoreClose(t ? () => t.close() : undefined); |
| 162 | if (opts.server) await ignoreClose(s ? () => s.close() : undefined); |
| 163 | await ignoreClose(close); |
| 164 | }; |
| 165 | |
| 166 | return { |
| 167 | handleRequest: async (request) => { |
| 168 | const resource = resourceFromRequest(request); |
| 169 | if (!resource) return jsonError(404, -32001, "MCP resource not found"); |
| 170 | const sessionId = request.headers.get("mcp-session-id"); |
| 171 | |
| 172 | if (sessionId) { |
| 173 | const transport = transports.get(sessionId); |
| 174 | if (!transport) return jsonError(404, -32001, "Session not found"); |
| 175 | const sessionResource = resources.get(sessionId); |
| 176 | if (!sessionResource || mcpResourceKey(sessionResource) !== mcpResourceKey(resource)) { |
| 177 | return jsonError(403, -32003, "Session belongs to a different MCP resource"); |
| 178 | } |
| 179 | return transport.handleRequest(request); |