| 226 | const ALLOWED_METHODS = new Set(["GET", "POST", "DELETE", "OPTIONS"]); |
| 227 | |
| 228 | const handle = async (request: Request, env: Env, ctx: ExecutionContext): Promise<Response> => { |
| 229 | if (request.method === "OPTIONS") { |
| 230 | return mcpCorsPreflightResponse(request.headers.get("access-control-request-headers")); |
| 231 | } |
| 232 | // Preserve the old envelope's JSON-RPC 405 before authenticating, so |
| 233 | // unsupported methods never reach the session engine. |
| 234 | if (!ALLOWED_METHODS.has(request.method)) { |
| 235 | return jsonRpcResponse(405, -32001, "Method not allowed"); |
| 236 | } |
| 237 | const sessionId = request.headers.get("mcp-session-id"); |
| 238 | |
| 239 | const { auth, outcome } = await runTraced(request, authenticate(request, authProvider)); |
| 240 | if (!Predicate.isTagged(outcome, "Authenticated")) { |
| 241 | // Destroying a live session on auth grounds requires a POSITIVE |
| 242 | // determination that access is genuinely gone — only `Forbidden` carries |
| 243 | // that (valid bearer, org absent/revoked). `Unavailable` (transient WorkOS |
| 244 | // / JWKS failure) and `Unauthorized` (retry with a fresh token) must leave |
| 245 | // the session intact, so the condemn path is gated on `Forbidden` alone. |
| 246 | if (Predicate.isTagged(outcome, "Forbidden") && sessionId) { |
| 247 | const session = mcpSessionStub(env.MCP_SESSION, sessionId); |
| 248 | await Effect.runPromise( |
| 249 | Effect.ignore( |
| 250 | session ? Effect.tryPromise(() => session._cf_scheduleDestroy()) : Effect.void, |
| 251 | ), |
| 252 | ); |
| 253 | } |
| 254 | return renderAuthError(auth, request, outcome); |
| 255 | } |
| 256 | |
| 257 | const parsedBody = await Effect.runPromise(requestBodyFromRequest(request)); |
| 258 | const era = await classifyMcpProtocolEra(request, parsedBody); |
| 259 | if (era === "modern") { |
| 260 | if (env.MCP_2026_07_28_ENABLED === "false") { |
| 261 | return mcpModernDisabledResponse(); |
| 262 | } |
| 263 | const resource = resourceFromPath(request); |
| 264 | const props = await runTraced( |
| 265 | request, |
| 266 | propsForPrincipal(request, outcome.principal, resource), |
| 267 | ); |
| 268 | (ctx as ExecutionContext & { props?: McpSessionProps }).props = props; |
| 269 | const forwarded = withVerifiedIdentityHeaders( |
| 270 | request, |
| 271 | { |
| 272 | accountId: outcome.principal.accountId, |
| 273 | organizationId: outcome.principal.organizationId, |
| 274 | }, |
| 275 | resource, |
| 276 | ); |
| 277 | return modern.fetch({ |
| 278 | request: forwarded, |
| 279 | parsedBody, |
| 280 | principal: outcome.principal, |
| 281 | resource, |
| 282 | props, |
| 283 | requestStateSigningKey: requireMcpRequestStateKey(env.MCP_REQUEST_STATE_KEY), |
| 284 | builder: options.makeModernServerBuilder(props.session), |
| 285 | sessions: env.MCP_SESSION, |