(betterAuth: BetterAuthHandle)
| 51 | // aside from scrubbing any client-supplied value of that header so it can't be |
| 52 | // spoofed into an unrewritten request. |
| 53 | const selfHostHttpMiddleware = (betterAuth: BetterAuthHandle) => |
| 54 | HttpMiddleware.make((httpApp) => |
| 55 | Effect.gen(function* () { |
| 56 | const request = yield* HttpServerRequest.HttpServerRequest; |
| 57 | const url = new URL(request.url, "http://host.internal"); |
| 58 | // Streamable HTTP does not define HEAD. Reject it before the SPA's |
| 59 | // GET/HEAD fallback can claim `/mcp` and return a misleading empty |
| 60 | // `200 application/octet-stream` response. The shared MCP envelope |
| 61 | // rejects unsupported methods the same way, but the static route wins |
| 62 | // HEAD routing at the composed production-server boundary. |
| 63 | if (request.method === "HEAD" && isMcpServingPath(url.pathname)) { |
| 64 | const response = jsonRpcErrorBody(405, -32001, "Method not allowed"); |
| 65 | return HttpServerResponse.raw(response, { |
| 66 | status: response.status, |
| 67 | statusText: response.statusText, |
| 68 | headers: response.headers, |
| 69 | }); |
| 70 | } |
| 71 | if ( |
| 72 | url.pathname === OAUTH_CALLBACK_PATH && |
| 73 | (request.method === "GET" || request.method === "HEAD") |
| 74 | ) { |
| 75 | const headers = new Headers(request.headers as Record<string, string>); |
| 76 | const webRequest = new Request(url, { method: request.method, headers }); |
| 77 | const location = yield* Effect.promise(() => |
| 78 | oauthCallbackSignInRedirectLocation(webRequest, betterAuth.auth), |
| 79 | ); |
| 80 | if (location) return HttpServerResponse.redirect(location, { status: 302 }); |
| 81 | } |
| 82 | |
| 83 | const rewritten = stripMcpOrgSegment(url.pathname); |
| 84 | if (rewritten === null) { |
| 85 | // Never let a client dictate the org-scoped echo below by smuggling |
| 86 | // this header in directly — it's only ever trustworthy when WE set it |
| 87 | // a few lines down, for a request we ourselves just rewrote. |
| 88 | if (!EffectHeaders.has(request.headers, MCP_ORIGINAL_PATH_HEADER)) return yield* httpApp; |
| 89 | return yield* httpApp.pipe( |
| 90 | Effect.provideService( |
| 91 | HttpServerRequest.HttpServerRequest, |
| 92 | request.modify({ |
| 93 | headers: EffectHeaders.remove(request.headers, MCP_ORIGINAL_PATH_HEADER), |
| 94 | }), |
| 95 | ), |
| 96 | ); |
| 97 | } |
| 98 | return yield* httpApp.pipe( |
| 99 | Effect.provideService( |
| 100 | HttpServerRequest.HttpServerRequest, |
| 101 | request.modify({ |
| 102 | url: `${rewritten}${url.search}`, |
| 103 | headers: EffectHeaders.set(request.headers, MCP_ORIGINAL_PATH_HEADER, url.pathname), |
| 104 | }), |
| 105 | ), |
| 106 | ); |
| 107 | }), |
| 108 | ); |
| 109 | |
| 110 | export const startServer = async (): Promise<void> => { |
no test coverage detected