History

Harden requestState binding, add inbound rollback switch, fix CLI bridge era handling

Rhys Sullivan committed 3 months ago

Commit ce870831 parent c25ace226 files · +556 −83Browse files

Showing the changes to apps (15 of 26 files). Show all

Unified · Split

15 files
  1. apps/cli/package.json +1 −0
  2. apps/cli/src/main.ts +1 −1
  3. apps/cloud/src/env-augment.d.ts +2 −0
  4. apps/cloud/src/mcp/agent-handler.ts +4 −0
  5. apps/cloud/wrangler.jsonc +3 −0
  6. apps/host-cloudflare/src/config.ts +2 −0
  7. apps/host-cloudflare/src/mcp/agent-handler.ts +4 −0
  8. apps/host-cloudflare/wrangler.jsonc +3 −0
  9. apps/host-selfhost/src/app.ts +6 −1
  10. apps/host-selfhost/src/config.ts +3 −0
  11. apps/host-selfhost/src/mcp/index.ts +2 −1
  12. apps/host-selfhost/src/mcp/session-store.ts +2 −0
  13. apps/local/src/main.ts +1 −0
  14. apps/local/src/mcp-modern.test.ts +52 −0
  15. apps/local/src/mcp.ts +18 −0
apps/cli/package.json+1−0
@@ -30,6 +30,7 @@ · 29 unmodified lines
30 "@executor-js/runtime-quickjs": "workspace:*",30 "@executor-js/runtime-quickjs": "workspace:*",
31 "@executor-js/sdk": "workspace:*",31 "@executor-js/sdk": "workspace:*",
32 "@jitl/quickjs-wasmfile-release-sync": "catalog:",32 "@jitl/quickjs-wasmfile-release-sync": "catalog:",
33 "@modelcontextprotocol/client": "2.0.0",
33 "@modelcontextprotocol/sdk": "^1.29.0",34 "@modelcontextprotocol/sdk": "^1.29.0",
34 "@sentry/bun": "^10.57.0",35 "@sentry/bun": "^10.57.0",
35 "effect": "catalog:",36 "effect": "catalog:",
apps/cli/src/main.ts+1−1
@@ -75,7 +75,7 @@ import type { PlatformError } from "effect/PlatformError"; · 74 unmodified lines
75import * as Effect from "effect/Effect";75import * as Effect from "effect/Effect";
76import * as Option from "effect/Option";76import * as Option from "effect/Option";
77import * as Cause from "effect/Cause";77import * as Cause from "effect/Cause";
78import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";78import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
79import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";79import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
80import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";80import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
81 81
apps/cloud/src/env-augment.d.ts+2−0
@@ -69,6 +69,8 @@ declare global { · 68 unmodified lines
69 MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS?: string;69 MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS?: string;
70 /** HMAC key for MCP 2026-07-28 continuation state (32+ byte secret). */70 /** HMAC key for MCP 2026-07-28 continuation state (32+ byte secret). */
71 MCP_REQUEST_STATE_KEY?: string;71 MCP_REQUEST_STATE_KEY?: string;
72 /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */
73 MCP_2026_07_28_ENABLED?: string;
72 NODE_ENV?: string;74 NODE_ENV?: string;
73 75
74 // Shared with frontend76 // Shared with frontend
apps/cloud/src/mcp/agent-handler.ts+4−0
@@ -4,6 +4,7 @@ import { Effect, Predicate } from "effect"; · 3 unmodified lines
4import {4import {
5 McpAuthProvider,5 McpAuthProvider,
6 jsonRpcErrorBody,6 jsonRpcErrorBody,
7 mcpModernDisabledResponse,
7 defaultMcpResource,8 defaultMcpResource,
8 UNAVAILABLE_RETRY_AFTER_SECONDS,9 UNAVAILABLE_RETRY_AFTER_SECONDS,
9 type AuthOutcome,10 type AuthOutcome,
@@ -174,6 +175,9 @@ export const makeCloudMcpAgentHandler = () => { · 164 unmodified lines
174 const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));175 const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));
175 const era = await classifyMcpProtocolEra(request, parsedBody);176 const era = await classifyMcpProtocolEra(request, parsedBody);
176 if (era === "modern") {177 if (era === "modern") {
178 if (env.MCP_2026_07_28_ENABLED === "false") {
179 return mcpModernDisabledResponse();
180 }
177 const resource = resourceFromPath(request);181 const resource = resourceFromPath(request);
178 const props = await runTraced(182 const props = await runTraced(
179 request,183 request,
apps/cloud/wrangler.jsonc+3−0
@@ -101,6 +101,9 @@ · 100 unmodified lines
101 // secret for both with `wrangler secret put MCP_REQUEST_STATE_KEY`.101 // secret for both with `wrangler secret put MCP_REQUEST_STATE_KEY`.
102 // It must never be placed in `vars` or generated independently per isolate.102 // It must never be placed in `vars` or generated independently per isolate.
103 "vars": {103 "vars": {
104 // MCP_2026_07_28_ENABLED is intentionally absent: unset enables modern
105 // inbound serving. Set the Worker var to "false" for emergency rollback;
106 // legacy serving remains available.
104 "VITE_PUBLIC_SITE_URL": "https://executor.sh",107 "VITE_PUBLIC_SITE_URL": "https://executor.sh",
105 "VITE_PUBLIC_POSTHOG_KEY": "phc_nNLrNMALpRsfrEkZovUkfMxYbcJvHnsJHeoSPavprgLL",108 "VITE_PUBLIC_POSTHOG_KEY": "phc_nNLrNMALpRsfrEkZovUkfMxYbcJvHnsJHeoSPavprgLL",
106 // Browser OTLP spans → same-origin, forwarded to Axiom by the worker109 // Browser OTLP spans → same-origin, forwarded to Axiom by the worker
apps/host-cloudflare/src/config.ts+2−0
@@ -49,6 +49,8 @@ export interface CloudflareEnv { · 48 unmodified lines
49 readonly EXECUTOR_SECRET_KEY?: string;49 readonly EXECUTOR_SECRET_KEY?: string;
50 /** HMAC key for MCP 2026-07-28 continuation state (32+ byte secret). */50 /** HMAC key for MCP 2026-07-28 continuation state (32+ byte secret). */
51 readonly MCP_REQUEST_STATE_KEY?: string;51 readonly MCP_REQUEST_STATE_KEY?: string;
52 /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */
53 readonly MCP_2026_07_28_ENABLED?: string;
52 readonly ALLOW_LOCAL_NETWORK?: string;54 readonly ALLOW_LOCAL_NETWORK?: string;
53 readonly VITE_PUBLIC_SITE_URL?: string;55 readonly VITE_PUBLIC_SITE_URL?: string;
54 /**56 /**
apps/host-cloudflare/src/mcp/agent-handler.ts+4−0
@@ -3,6 +3,7 @@ import { Effect, Predicate } from "effect"; · 2 unmodified lines
3import {3import {
4 McpAuthProvider,4 McpAuthProvider,
5 jsonRpcErrorBody,5 jsonRpcErrorBody,
6 mcpModernDisabledResponse,
6 defaultMcpResource,7 defaultMcpResource,
7 type AuthOutcome,8 type AuthOutcome,
8 type Principal,9 type Principal,
@@ -112,6 +113,9 @@ export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => { · 103 unmodified lines
112 const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));113 const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));
113 const era = await classifyMcpProtocolEra(request, parsedBody);114 const era = await classifyMcpProtocolEra(request, parsedBody);
114 if (era === "modern") {115 if (era === "modern") {
116 if (env.MCP_2026_07_28_ENABLED === "false") {
117 return mcpModernDisabledResponse();
118 }
115 const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal));119 const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal));
116 (ctx as ExecutionContext & { props?: McpSessionProps }).props = props;120 (ctx as ExecutionContext & { props?: McpSessionProps }).props = props;
117 const forwarded = withVerifiedIdentityHeaders(121 const forwarded = withVerifiedIdentityHeaders(
apps/host-cloudflare/wrangler.jsonc+3−0
@@ -73,6 +73,9 @@ · 72 unmodified lines
73 // MCP key is a deployment prerequisite shared by Worker and session DOs;73 // MCP key is a deployment prerequisite shared by Worker and session DOs;
74 // never generate it independently per isolate.74 // never generate it independently per isolate.
75 "vars": {75 "vars": {
76 // MCP_2026_07_28_ENABLED is intentionally absent: unset enables modern
77 // inbound serving. Set the Worker var to "false" for emergency rollback;
78 // legacy serving remains available.
76 "ACCESS_NAME_CLAIM": "name",79 "ACCESS_NAME_CLAIM": "name",
77 "ACCESS_GROUPS_CLAIM": "groups",80 "ACCESS_GROUPS_CLAIM": "groups",
78 // Never preserve a production dev-auth override through keep_vars.81 // Never preserve a production dev-auth override through keep_vars.
apps/host-selfhost/src/app.ts+6−1
@@ -78,7 +78,12 @@ export const makeSelfHostApp = async (options: MakeSelfHostAppOptions = {}) => { · 77 unmodified lines
78 // ---- the in-process MCP serving seams (+ shutdown hook) ----------------78 // ---- the in-process MCP serving seams (+ shutdown hook) ----------------
79 // Pass the pinned public origin so browser-approval URLs are reachable behind79 // Pass the pinned public origin so browser-approval URLs are reachable behind
80 // a reverse proxy (not the internal 127.0.0.1 bind from the request URL).80 // a reverse proxy (not the internal 127.0.0.1 bind from the request URL).
81 const mcp = makeSelfHostMcpSeams(dbHandle, betterAuth, config.webBaseUrl);81 const mcp = makeSelfHostMcpSeams(
82 dbHandle,
83 betterAuth,
84 config.webBaseUrl,
85 config.mcp20260728Enabled,
86 );
82 87
83 // CLI device-login discovery (`executor login`). Points the CLI at Better88 // CLI device-login discovery (`executor login`). Points the CLI at Better
84 // Auth's device endpoints; `requestFormat: "json"` because those endpoints89 // Auth's device endpoints; `requestFormat: "json"` because those endpoints
apps/host-selfhost/src/config.ts+3−0
@@ -32,6 +32,8 @@ export interface SelfHostConfig { · 31 unmodified lines
32 * internal network unless an operator opts in.32 * internal network unless an operator opts in.
33 */33 */
34 readonly allowLocalNetwork: boolean;34 readonly allowLocalNetwork: boolean;
35 /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */
36 readonly mcp20260728Enabled: boolean;
35 // Better Auth session secret. Always resolved (env, else generated + persisted37 // Better Auth session secret. Always resolved (env, else generated + persisted
36 // under the data dir) so a single-container deploy boots with no env; the auth38 // under the data dir) so a single-container deploy boots with no env; the auth
37 // layer still validates an explicitly-set env secret is long enough.39 // layer still validates an explicitly-set env secret is long enough.
@@ -142,6 +144,7 @@ export const loadConfig = (): SelfHostConfig => { · 104 unmodified lines
142 dbPath: process.env.EXECUTOR_DB_PATH ?? join(dataDir, "data.db"),144 dbPath: process.env.EXECUTOR_DB_PATH ?? join(dataDir, "data.db"),
143 webBaseUrl: resolveWebBaseUrl(port),145 webBaseUrl: resolveWebBaseUrl(port),
144 allowLocalNetwork: process.env.EXECUTOR_ALLOW_LOCAL_NETWORK === "true",146 allowLocalNetwork: process.env.EXECUTOR_ALLOW_LOCAL_NETWORK === "true",
147 mcp20260728Enabled: process.env.MCP_2026_07_28_ENABLED !== "false",
145 authSecret: resolveAuthSecret(),148 authSecret: resolveAuthSecret(),
146 bootstrapAdminEmail: process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL,149 bootstrapAdminEmail: process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL,
147 bootstrapAdminPassword: process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD,150 bootstrapAdminPassword: process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD,
apps/host-selfhost/src/mcp/index.ts+2−1
@@ -140,6 +140,7 @@ export const makeSelfHostMcpSeams = ( · 139 unmodified lines
140 dbHandle: SelfHostDbHandle,140 dbHandle: SelfHostDbHandle,
141 betterAuth: BetterAuthHandle,141 betterAuth: BetterAuthHandle,
142 webBaseUrl?: string,142 webBaseUrl?: string,
143 modernEnabled = true,
143): SelfHostMcpSeams => {144): SelfHostMcpSeams => {
144 const sessionStore = makeSelfHostMcpSessionStore(dbHandle, webBaseUrl);145 const sessionStore = makeSelfHostMcpSessionStore(dbHandle, webBaseUrl);
145 const auth: Layer.Layer<McpAuthProvider, never, IdentityProvider> = selfHostMcpAuth.pipe(146 const auth: Layer.Layer<McpAuthProvider, never, IdentityProvider> = selfHostMcpAuth.pipe(
@@ -148,7 +149,7 @@ export const makeSelfHostMcpSeams = ( · 2 unmodified lines
148 return {149 return {
149 auth,150 auth,
150 sessions: selfHostMcpSessions(sessionStore),151 sessions: selfHostMcpSessions(sessionStore),
151 modern: makeSelfHostMcpModernServerBuilder(dbHandle),152 modern: makeSelfHostMcpModernServerBuilder(dbHandle, modernEnabled),
152 reporter: selfHostMcpReporter,153 reporter: selfHostMcpReporter,
153 approvalHandler: makeApprovalHandler(sessionStore, betterAuth),154 approvalHandler: makeApprovalHandler(sessionStore, betterAuth),
154 close: sessionStore.close,155 close: sessionStore.close,
apps/host-selfhost/src/mcp/session-store.ts+2−0
@@ -54,8 +54,10 @@ export const makeSelfHostMcpSessionStore = ( · 53 unmodified lines
54/** Build the stateless MCP server seam over the same self-host stack/config. */54/** Build the stateless MCP server seam over the same self-host stack/config. */
55export const makeSelfHostMcpModernServerBuilder = (55export const makeSelfHostMcpModernServerBuilder = (
56 db: SelfHostDbHandle,56 db: SelfHostDbHandle,
57 enabled = true,
57): Layer.Layer<McpModernServerBuilder> =>58): Layer.Layer<McpModernServerBuilder> =>
58 Layer.succeed(McpModernServerBuilder)({59 Layer.succeed(McpModernServerBuilder)({
60 enabled,
59 build: makeMcpBuildServer(61 build: makeMcpBuildServer(
60 SelfHostExecutionStackLayer.pipe(Layer.provide(Layer.succeed(SelfHostDb)(db))),62 SelfHostExecutionStackLayer.pipe(Layer.provide(Layer.succeed(SelfHostDb)(db))),
61 {63 {
apps/local/src/main.ts+1−0
@@ -119,6 +119,7 @@ export const createServerHandlers = async (token: string): Promise<ServerHandler · 118 unmodified lines
119 localAnalytics.record(`artifact_${action}`, { via: "agent" }),119 localAnalytics.record(`artifact_${action}`, { via: "agent" }),
120 };120 };
121 mcp = createMcpRequestHandler({121 mcp = createMcpRequestHandler({
122 modernEnabled: process.env.MCP_2026_07_28_ENABLED !== "false",
122 defaultConfig: {123 defaultConfig: {
123 engine,124 engine,
124 artifacts: executor.artifacts,125 artifacts: executor.artifacts,
apps/local/src/mcp-modern.test.ts+52−0
@@ -51,4 +51,56 @@ describe("local modern MCP HTTP", () => { · 50 unmodified lines
51 await mcp.close();51 await mcp.close();
52 }52 }
53 });53 });
54
55 it("rejects a pinned modern client with the unsupported-version protocol error", async () => {
56 const mcp = createMcpRequestHandler({ defaultConfig: { engine }, modernEnabled: false });
57 const transport = new StreamableHTTPClientTransport(new URL("http://local.test/mcp"), {
58 fetch: (input, init) =>
59 mcp.handleRequest(
60 input instanceof Request ? new Request(input, init) : new Request(input.toString(), init),
61 ),
62 });
63 const client = new Client(
64 { name: "local-modern-disabled-test", version: "1.0.0" },
65 { capabilities: {}, versionNegotiation: { mode: { pin: "2026-07-28" } } },
66 );
67
68 // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: always close a client whose pinned negotiation is expected to fail
69 try {
70 await expect(client.connect(transport)).rejects.toThrow(/version negotiation failed/i);
71 } finally {
72 await client.close();
73 await mcp.close();
74 }
75 });
76
77 it("lets an auto-mode v2 client fall back to legacy when modern inbound is disabled", async () => {
78 const mcp = createMcpRequestHandler({ defaultConfig: { engine }, modernEnabled: false });
79 const transport = new StreamableHTTPClientTransport(new URL("http://local.test/mcp"), {
80 fetch: (input, init) =>
81 mcp.handleRequest(
82 input instanceof Request ? new Request(input, init) : new Request(input.toString(), init),
83 ),
84 });
85 const client = new Client(
86 { name: "local-auto-fallback-test", version: "1.0.0" },
87 { capabilities: {}, versionNegotiation: { mode: "auto" } },
88 );
89
90 await client.connect(transport);
91 // oxlint-disable-next-line executor/no-try-catch-or-throw -- test boundary: always close the fallback client and local handler
92 try {
93 expect(client.getProtocolEra()).toBe("legacy");
94 expect((await client.listTools()).tools.map(({ name }) => name)).toContain("execute");
95 const result = await client.callTool({
96 name: "execute",
97 arguments: { code: "3 + 4" },
98 });
99 expect(result.content).toEqual([{ type: "text", text: "ran: 3 + 4" }]);
100 expect(transport.sessionId).toBeTruthy();
101 } finally {
102 await client.close();
103 await mcp.close();
104 }
105 });
54});106});
apps/local/src/mcp.ts+18−0
@@ -11,6 +11,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio"; · 10 unmodified lines
11import {11import {
12 defaultMcpResource,12 defaultMcpResource,
13 jsonRpcErrorBody,13 jsonRpcErrorBody,
14 mcpModernDisabledResponse,
14 mcpResourceKey,15 mcpResourceKey,
15 type McpResource,16 type McpResource,
16} from "@executor-js/host-mcp";17} from "@executor-js/host-mcp";
@@ -18,6 +19,7 @@ import { · 1 unmodified line
18 appsEnabledForClientCapabilities,19 appsEnabledForClientCapabilities,
19 buildMcpServer,20 buildMcpServer,
20 clientCapabilitiesFromRequest,21 clientCapabilitiesFromRequest,
22 mcpRequestStateBindingFromBody,
21 requestBodyFromRequest,23 requestBodyFromRequest,
22 type ExecutorMcpToolConfig,24 type ExecutorMcpToolConfig,
23} from "@executor-js/host-mcp/tool-server";25} from "@executor-js/host-mcp/tool-server";
@@ -59,6 +61,8 @@ export interface LocalMcpServerConfig { · 35 unmodified lines
59 61
60export interface LocalMcpRequestHandlerConfig {62export interface LocalMcpRequestHandlerConfig {
61 readonly defaultConfig: ExecutorMcpToolConfig;63 readonly defaultConfig: ExecutorMcpToolConfig;
64 /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */
65 readonly modernEnabled?: boolean;
62 readonly createConfigForResource?: (66 readonly createConfigForResource?: (
63 resource: McpResource,67 resource: McpResource,
64 ) => Promise<LocalMcpServerConfig> | LocalMcpServerConfig;68 ) => Promise<LocalMcpServerConfig> | LocalMcpServerConfig;
@@ -138,6 +142,7 @@ export const createMcpRequestHandler = ( · 73 unmodified lines
138 const sessionEngines = new Map<string, AnyExecutionEngine>();142 const sessionEngines = new Map<string, AnyExecutionEngine>();
139 const sessionClosers = new Map<string, () => Promise<void>>();143 const sessionClosers = new Map<string, () => Promise<void>>();
140 const modernHandlers = new Map<string, McpHttpHandler>();144 const modernHandlers = new Map<string, McpHttpHandler>();
145 const modernRequestBodies = new WeakMap<Request, unknown>();
141 const approvals = makeInProcessBrowserApprovalStore();146 const approvals = makeInProcessBrowserApprovalStore();
142 const defaultEngine = engineFromConfig(handlerConfig.defaultConfig);147 const defaultEngine = engineFromConfig(handlerConfig.defaultConfig);
143 let requestStateSigningKey: Uint8Array | undefined;148 let requestStateSigningKey: Uint8Array | undefined;
@@ -189,16 +194,25 @@ export const createMcpRequestHandler = ( · 45 unmodified lines
189 // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: the third-party McpServerFactory Promise contract has no typed failure channel; missing documented request context is an SDK defect194 // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: the third-party McpServerFactory Promise contract has no typed failure channel; missing documented request context is an SDK defect
190 return Effect.runPromise(Effect.die("Modern MCP request context has no request"));195 return Effect.runPromise(Effect.die("Modern MCP request context has no request"));
191 }196 }
197 const parsedBody = modernRequestBodies.get(request);
192 return Effect.runPromise(198 return Effect.runPromise(
193 Effect.gen(function* () {199 Effect.gen(function* () {
194 const resourceConfig = yield* Effect.promise(() => configForResource(resource));200 const resourceConfig = yield* Effect.promise(() => configForResource(resource));
195 const clientCapabilities = yield* clientCapabilitiesFromRequest(request);201 const clientCapabilities = yield* clientCapabilitiesFromRequest(request);
202 const requestStateBinding = yield* Effect.promise(() =>
203 mcpRequestStateBindingFromBody({
204 body: parsedBody,
205 principal: "local",
206 resource,
207 }),
208 );
196 const server = yield* buildMcpServer({209 const server = yield* buildMcpServer({
197 ...resourceConfig.config,210 ...resourceConfig.config,
198 artifactsEnabled: readArtifactsEnabled(request),211 artifactsEnabled: readArtifactsEnabled(request),
199 appsEnabled: appsEnabledForClientCapabilities(clientCapabilities),212 appsEnabled: appsEnabledForClientCapabilities(clientCapabilities),
200 requestStateSigningKey: signingKey(),213 requestStateSigningKey: signingKey(),
201 requestStatePrincipal: "local",214 requestStatePrincipal: "local",
215 ...(requestStateBinding === null ? {} : { requestStateBinding }),
202 });216 });
203 if (resourceConfig.close) {217 if (resourceConfig.close) {
204 const closeServer = server.close.bind(server);218 const closeServer = server.close.bind(server);
@@ -226,7 +240,11 @@ export const createMcpRequestHandler = ( · 21 unmodified lines
226 const resource = resourceFromRequest(request);240 const resource = resourceFromRequest(request);
227 if (!resource) return jsonError(404, -32001, "MCP resource not found");241 if (!resource) return jsonError(404, -32001, "MCP resource not found");
228 if (!(await isLegacyRequest(request))) {242 if (!(await isLegacyRequest(request))) {
243 if (handlerConfig.modernEnabled === false) {
244 return mcpModernDisabledResponse({ cors: false });
245 }
229 const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));246 const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));
247 modernRequestBodies.set(request, parsedBody);
230 return modernHandlerFor(resource).fetch(request, { parsedBody });248 return modernHandlerFor(resource).fetch(request, { parsedBody });
231 }249 }
232 const sessionId = request.headers.get("mcp-session-id");250 const sessionId = request.headers.get("mcp-session-id");

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t