MCPcopy Create free account
hub / github.com/UsefulSoftwareCo/executor / makeCloudMcpAgentHandler

Function makeCloudMcpAgentHandler

apps/cloud/src/mcp/agent-handler.ts:114–209  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

112 });
113
114export const makeCloudMcpAgentHandler = () => {
115 const serveOptions = { binding: "MCP_SESSION", transport: "streamable-http" } as const;
116 // The agents SDK builds an exact-match `URLPattern` from the path handed to
117 // `serve` (see `createStreamingHttpHandler` in `agents/dist/mcp/index.js`) —
118 // a single `/mcp` handler never matches `/mcp/toolkits/<slug>` and falls
119 // through to its own internal 404. A second `serve` mounted on the
120 // parameterized path picks it up (`URLPattern` supports `:slug` segments);
121 // the auth/ownership/props logic above is unchanged and shared, only the
122 // final dispatch target differs.
123 const serve = McpSessionDOSqlite.serve("/mcp", serveOptions);
124 const serveToolkit = McpSessionDOSqlite.serve("/mcp/toolkits/:slug", serveOptions);
125
126 const ALLOWED_METHODS = new Set(["GET", "POST", "DELETE", "OPTIONS"]);
127
128 return async (request: Request, env: Env, ctx: ExecutionContext): Promise<Response> => {
129 if (request.method === "OPTIONS") return corsPreflightResponse();
130 // The old envelope (packages/hosts/mcp/src/envelope.ts) answered anything
131 // outside GET/POST/DELETE/OPTIONS with a JSON-RPC 405; the agents SDK
132 // handler only understands its own transport verbs and falls through to
133 // a bare 404. Reject before authenticating so PUT/PATCH/etc never reach
134 // the session engine.
135 if (!ALLOWED_METHODS.has(request.method)) {
136 return jsonRpcResponse(405, -32001, "Method not allowed");
137 }
138 const sessionId = request.headers.get("mcp-session-id");
139
140 const { auth, outcome } = await Effect.runPromise(authenticate(request));
141 if (!Predicate.isTagged(outcome, "Authenticated")) {
142 if (Predicate.isTagged(outcome, "Forbidden") && sessionId) {
143 await Effect.runPromise(
144 Effect.ignore(Effect.tryPromise(() => sessionStub(env, sessionId)._cf_scheduleDestroy())),
145 );
146 }
147 return renderAuthError(auth, request, outcome);
148 }
149
150 if (!sessionId && request.method === "DELETE") {
151 // Matches the old envelope's contract (@modelcontextprotocol/sdk's
152 // `WebStandardStreamableHTTPServerTransport.handleDeleteRequest`): 200,
153 // not 204 — see e2e/cloud/mcp-protocol.test.ts.
154 return new Response(null, { status: 200, headers: { "access-control-allow-origin": "*" } });
155 }
156
157 if (sessionId) {
158 const owner = await sessionStub(env, sessionId).validateMcpSessionOwner({
159 accountId: outcome.principal.accountId,
160 organizationId: outcome.principal.organizationId,
161 });
162 if (owner === "not_found") {
163 return jsonRpcResponse(404, -32001, "Session not found");
164 }
165 if (owner === "forbidden") {
166 return jsonRpcResponse(403, -32003, "MCP session does not belong to the current bearer");
167 }
168 }
169
170 const resource = resourceFromPath(request);
171 const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal, resource));

Callers 1

server.tsFile · 0.90

Calls 11

wrapMcpSseResponseFunction · 0.90
resourceFromPathFunction · 0.85
corsPreflightResponseFunction · 0.70
jsonRpcResponseFunction · 0.70
authenticateFunction · 0.70
sessionStubFunction · 0.70
renderAuthErrorFunction · 0.70
propsForPrincipalFunction · 0.70
getMethod · 0.65
fetchMethod · 0.65

Tested by

no test coverage detected