diff --git a/src/worker.ts b/src/worker.ts index 67d2423..8b8589d 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -34,7 +34,10 @@ export class MotionMCPAgent extends McpAgent { registry ); const enabledTools = configurator.getEnabledTools(); - validator.initializeValidators(enabledTools); + // No AJV validator init here: ajv.compile() uses runtime code generation, + // which Cloudflare Workers disallows (EvalError). Input validation in the + // Worker is handled by the Zod schemas passed to server.tool() below; + // validateInput() is only called from the stdio entry point. for (const tool of enabledTools) { const zodShape = jsonSchemaToZodShape(tool.inputSchema as Parameters[0]); @@ -67,6 +70,22 @@ export default { // Validate secret path: /mcp/{secret}/... // Clients configure URL as: https://your-worker.workers.dev/mcp/YOUR_SECRET const pathParts = url.pathname.split("/").filter(Boolean); + + // SSE message endpoint: the SSE stream's `endpoint` event advertises the + // rewritten path (/mcp/message?sessionId=...), which has no secret segment. + // The sessionId is unguessable and only issued on a stream opened with the + // secret, so it authenticates these POSTs. + if ( + pathParts[0] === "mcp" && + pathParts[1] === "message" && + request.method === "POST" && + url.searchParams.has("sessionId") + ) { + return ( + MotionMCPAgent.mount("/mcp") as { fetch: (req: Request, env: Env, ctx: ExecutionContext) => Promise } + ).fetch(request, env, ctx); + } + if (pathParts[0] !== "mcp" || pathParts[1] !== env.MOTION_MCP_SECRET) { return new Response("Not found", { status: 404 }); } @@ -77,8 +96,16 @@ export default { const cleanUrl = new URL(cleanPath, url.origin); const cleanRequest = new Request(cleanUrl, request); + // Streamable HTTP (POST/DELETE /mcp, or GET with an mcp-session-id header) + // is served by serve(); a bare GET on /mcp is a legacy SSE stream via mount(). + const isStreamableHttp = + cleanPath === "/mcp" && + (request.method !== "GET" || request.headers.has("mcp-session-id")); + return ( - MotionMCPAgent.mount("/mcp") as { fetch: (req: Request, env: Env, ctx: ExecutionContext) => Promise } + (isStreamableHttp + ? MotionMCPAgent.serve("/mcp") + : MotionMCPAgent.mount("/mcp")) as { fetch: (req: Request, env: Env, ctx: ExecutionContext) => Promise } ).fetch(cleanRequest, env, ctx); }, }; diff --git a/wrangler.toml b/wrangler.toml index 7a89728..e026fb8 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -10,7 +10,7 @@ bindings = [ [[migrations]] tag = "v1" -new_classes = ["MotionMCPAgent"] +new_sqlite_classes = ["MotionMCPAgent"] [vars] MOTION_MCP_TOOLS = "essential"