Skip to content

Commit ce87083

Browse files
committed
Harden requestState binding, add inbound rollback switch, fix CLI bridge era handling
1 parent c25ace2 commit ce87083

26 files changed

Lines changed: 556 additions & 83 deletions

apps/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@executor-js/runtime-quickjs": "workspace:*",
3131
"@executor-js/sdk": "workspace:*",
3232
"@jitl/quickjs-wasmfile-release-sync": "catalog:",
33+
"@modelcontextprotocol/client": "2.0.0",
3334
"@modelcontextprotocol/sdk": "^1.29.0",
3435
"@sentry/bun": "^10.57.0",
3536
"effect": "catalog:",

apps/cli/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ import type { PlatformError } from "effect/PlatformError";
7575
import * as Effect from "effect/Effect";
7676
import * as Option from "effect/Option";
7777
import * as Cause from "effect/Cause";
78-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
78+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
7979
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8080
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
8181

apps/cloud/src/env-augment.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ declare global {
6969
MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS?: string;
7070
/** HMAC key for MCP 2026-07-28 continuation state (32+ byte secret). */
7171
MCP_REQUEST_STATE_KEY?: string;
72+
/** Emergency rollback for inbound MCP 2026-07-28 traffic only. */
73+
MCP_2026_07_28_ENABLED?: string;
7274
NODE_ENV?: string;
7375

7476
// Shared with frontend

apps/cloud/src/mcp/agent-handler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Effect, Predicate } from "effect";
44
import {
55
McpAuthProvider,
66
jsonRpcErrorBody,
7+
mcpModernDisabledResponse,
78
defaultMcpResource,
89
UNAVAILABLE_RETRY_AFTER_SECONDS,
910
type AuthOutcome,
@@ -174,6 +175,9 @@ export const makeCloudMcpAgentHandler = () => {
174175
const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));
175176
const era = await classifyMcpProtocolEra(request, parsedBody);
176177
if (era === "modern") {
178+
if (env.MCP_2026_07_28_ENABLED === "false") {
179+
return mcpModernDisabledResponse();
180+
}
177181
const resource = resourceFromPath(request);
178182
const props = await runTraced(
179183
request,

apps/cloud/wrangler.jsonc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@
101101
// secret for both with `wrangler secret put MCP_REQUEST_STATE_KEY`.
102102
// It must never be placed in `vars` or generated independently per isolate.
103103
"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.
104107
"VITE_PUBLIC_SITE_URL": "https://executor.sh",
105108
"VITE_PUBLIC_POSTHOG_KEY": "phc_nNLrNMALpRsfrEkZovUkfMxYbcJvHnsJHeoSPavprgLL",
106109
// Browser OTLP spans → same-origin, forwarded to Axiom by the worker

apps/host-cloudflare/src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export interface CloudflareEnv {
4949
readonly EXECUTOR_SECRET_KEY?: string;
5050
/** HMAC key for MCP 2026-07-28 continuation state (32+ byte secret). */
5151
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;
5254
readonly ALLOW_LOCAL_NETWORK?: string;
5355
readonly VITE_PUBLIC_SITE_URL?: string;
5456
/**

apps/host-cloudflare/src/mcp/agent-handler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Effect, Predicate } from "effect";
33
import {
44
McpAuthProvider,
55
jsonRpcErrorBody,
6+
mcpModernDisabledResponse,
67
defaultMcpResource,
78
type AuthOutcome,
89
type Principal,
@@ -112,6 +113,9 @@ export const makeCloudflareMcpAgentHandler = (config: CloudflareConfig) => {
112113
const parsedBody = await Effect.runPromise(requestBodyFromRequest(request));
113114
const era = await classifyMcpProtocolEra(request, parsedBody);
114115
if (era === "modern") {
116+
if (env.MCP_2026_07_28_ENABLED === "false") {
117+
return mcpModernDisabledResponse();
118+
}
115119
const props = await Effect.runPromise(propsForPrincipal(request, outcome.principal));
116120
(ctx as ExecutionContext & { props?: McpSessionProps }).props = props;
117121
const forwarded = withVerifiedIdentityHeaders(

apps/host-cloudflare/wrangler.jsonc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
// MCP key is a deployment prerequisite shared by Worker and session DOs;
7474
// never generate it independently per isolate.
7575
"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.
7679
"ACCESS_NAME_CLAIM": "name",
7780
"ACCESS_GROUPS_CLAIM": "groups",
7881
// Never preserve a production dev-auth override through keep_vars.

apps/host-selfhost/src/app.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,12 @@ export const makeSelfHostApp = async (options: MakeSelfHostAppOptions = {}) => {
7878
// ---- the in-process MCP serving seams (+ shutdown hook) ----------------
7979
// Pass the pinned public origin so browser-approval URLs are reachable behind
8080
// 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+
);
8287

8388
// CLI device-login discovery (`executor login`). Points the CLI at Better
8489
// Auth's device endpoints; `requestFormat: "json"` because those endpoints

apps/host-selfhost/src/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export interface SelfHostConfig {
3232
* internal network unless an operator opts in.
3333
*/
3434
readonly allowLocalNetwork: boolean;
35+
/** Emergency rollback for inbound MCP 2026-07-28 traffic only. */
36+
readonly mcp20260728Enabled: boolean;
3537
// Better Auth session secret. Always resolved (env, else generated + persisted
3638
// under the data dir) so a single-container deploy boots with no env; the auth
3739
// layer still validates an explicitly-set env secret is long enough.
@@ -142,6 +144,7 @@ export const loadConfig = (): SelfHostConfig => {
142144
dbPath: process.env.EXECUTOR_DB_PATH ?? join(dataDir, "data.db"),
143145
webBaseUrl: resolveWebBaseUrl(port),
144146
allowLocalNetwork: process.env.EXECUTOR_ALLOW_LOCAL_NETWORK === "true",
147+
mcp20260728Enabled: process.env.MCP_2026_07_28_ENABLED !== "false",
145148
authSecret: resolveAuthSecret(),
146149
bootstrapAdminEmail: process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL,
147150
bootstrapAdminPassword: process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD,

0 commit comments

Comments
 (0)