{"slug": "code-mode-ts", "title": "code-mode.ts", "summary": "A developer built a Cloudflare Workers-based MCP (Model Context Protocol) server that exposes the YNAB (You Need A Budget) API through a secure, authenticated WebSocket connection. The implementation uses Cloudflare Access for JWT-based authentication and the Effect TypeScript library for robust error handling, enabling AI agents to interact with personal finance data through standardized MCP tool calls.", "body_md": "|\nimport type { AgentContext } from \"agents\"; |\n|\nimport { McpAgent } from \"agents/mcp\"; |\n|\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"; |\n|\n|\n|\nimport { Effect, Redacted } from \"effect\"; |\n|\nimport { |\n|\nCloudflareAccessConfig, |\n|\nCloudflareAccessInvalidError, |\n|\nCloudflareAccessJwtVerifier, |\n|\nCloudflareAccessKeyResolver, |\n|\nCloudflareAccessMisconfiguredError, |\n|\nCloudflareAccessRequiredError, |\n|\nCloudflareAccessUnsupportedIdentityError, |\n|\ntype CloudflareAccessPrincipal, |\n|\n} from \"@mulroy/cloudflare-access\"; |\n|\n|\n|\nimport { createYnabCodeModeServerFromEnv } from \"./ynab-api.ts\"; |\n|\n|\n|\nfunction getYnabMcpRequestLogContext(request: Request): Record<string, string | null> { |\n|\nreturn { |\n|\naccept: request.headers.get(\"accept\"), |\n|\ncfMcpMethod: request.headers.get(\"cf-mcp-method\"), |\n|\ncfRay: request.headers.get(\"cf-ray\"), |\n|\ncfWorker: request.headers.get(\"cf-worker\"), |\n|\ncontentLength: request.headers.get(\"content-length\"), |\n|\nmethod: request.method, |\n|\nsessionId: request.headers.get(\"mcp-session-id\"), |\n|\nupgrade: request.headers.get(\"upgrade\"), |\n|\nuserAgent: request.headers.get(\"user-agent\"), |\n|\n}; |\n|\n} |\n|\n|\n|\nasync function requireYnabMcpAccess( |\n|\nrequest: Request, |\n|\nenv: Env, |\n|\n): Promise<CloudflareAccessPrincipal | Response> { |\n|\ntry { |\n|\nconst principal = await Effect.runPromise( |\n|\nEffect.gen(function* () { |\n|\nconst verifier = yield* CloudflareAccessJwtVerifier; |\n|\nreturn yield* verifier.verify( |\n|\nRedacted.make(request.headers.get(\"cf-access-jwt-assertion\") ?? \"\"), |\n|\n); |\n|\n}).pipe( |\n|\nEffect.provide(CloudflareAccessJwtVerifier.layer), |\n|\nEffect.provide(CloudflareAccessKeyResolver.layerRemoteJwks), |\n|\nEffect.provide(CloudflareAccessConfig.layerFromEnv(env)), |\n|\n), |\n|\n); |\n|\n|\n|\nreturn principal; |\n|\n} catch (error) { |\n|\nif ( |\n|\nerror instanceof CloudflareAccessRequiredError || |\n|\nerror instanceof CloudflareAccessInvalidError || |\n|\nerror instanceof CloudflareAccessUnsupportedIdentityError || |\n|\nerror instanceof CloudflareAccessMisconfiguredError |\n|\n) { |\n|\nreturn Response.json( |\n|\n{ error: error.error, message: error.message }, |\n|\n{ status: error.status }, |\n|\n); |\n|\n} |\n|\n|\n|\nreturn Response.json( |\n|\n{ |\n|\nerror: \"cloudflare_access_invalid\", |\n|\nmessage: \"Unexpected Cloudflare Access verification failure\", |\n|\n}, |\n|\n{ status: 500 }, |\n|\n); |\n|\n} |\n|\n} |\n|\n|\n|\nexport class YnabMcpAgent extends McpAgent { |\n|\nprivate readonly runtimeEnv: Env; |\n|\n|\n|\nserver = new McpServer({ |\n|\nname: \"for-ynab-api\", |\n|\nversion: \"1.0.0\", |\n|\n}); |\n|\n|\n|\nconstructor(ctx: AgentContext, env: Env) { |\n|\nsuper(ctx, env); |\n|\nthis.runtimeEnv = env; |\n|\n} |\n|\n|\n|\nasync init(): Promise<void> { |\n|\nconsole.log(\"ynab mcp agent init\", { |\n|\nagentName: this.name, |\n|\nsessionId: this.getSessionId(), |\n|\ntransportType: this.getTransportType(), |\n|\n}); |\n|\n|\n|\nthis.server = createYnabCodeModeServerFromEnv(this.runtimeEnv); |\n|\n} |\n|\n|\n|\nasync onConnect(...args: Parameters<McpAgent[\"onConnect\"]>): Promise<void> { |\n|\nconst [connection, context] = args; |\n|\n|\n|\nconsole.log(\"ynab mcp agent websocket connect\", { |\n|\nactiveConnectionCount: Array.from(this.getConnections()).length, |\n|\nagentName: this.name, |\n|\ncfMcpMethod: context.request.headers.get(\"cf-mcp-method\"), |\n|\nconnectionId: connection.id, |\n|\nrequestSessionId: context.request.headers.get(\"mcp-session-id\"), |\n|\nsessionId: this.getSessionId(), |\n|\ntransportType: this.getTransportType(), |\n|\nupgrade: context.request.headers.get(\"upgrade\"), |\n|\n}); |\n|\n|\n|\nawait super.onConnect(...args); |\n|\n} |\n|\n|\n|\nasync onClose(...args: Parameters<McpAgent[\"onClose\"]>): Promise<void> { |\n|\nconst [connection, code, reason, wasClean] = args; |\n|\n|\n|\nconsole.log(\"ynab mcp agent websocket close\", { |\n|\nactiveConnectionCount: Array.from(this.getConnections()).length, |\n|\nagentName: this.name, |\n|\ncode, |\n|\nconnectionId: connection.id, |\n|\nreason, |\n|\nsessionId: this.getSessionId(), |\n|\ntransportType: this.getTransportType(), |\n|\nwasClean, |\n|\n}); |\n|\n|\n|\nawait super.onClose(...args); |\n|\n} |\n|\n} |\n|\n|\n|\nconst mcpHandler = YnabMcpAgent.serve(\"/s/ynab\", { binding: \"YnabMcpAgent\" }); |\n|\n|\n|\nexport default { |\n|\nasync fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { |\n|\nconst url = new URL(request.url); |\n|\n|\n|\nif (url.pathname === \"/health\") { |\n|\nreturn Response.json({ ok: true }); |\n|\n} |\n|\n|\n|\nif (url.pathname.startsWith(\"/s/ynab\")) { |\n|\nconst requestLogContext = getYnabMcpRequestLogContext(request); |\n|\n|\n|\nconsole.log(\"ynab mcp request received\", requestLogContext); |\n|\n|\n|\nconst result = await requireYnabMcpAccess(request, env); |\n|\n|\n|\nif (result instanceof Response) { |\n|\nconsole.warn(\"ynab mcp access denied\", { |\n|\n...requestLogContext, |\n|\nstatus: result.status, |\n|\n}); |\n|\n|\n|\nreturn result; |\n|\n} |\n|\n|\n|\nconsole.log(\"MCP access granted\", { |\n|\n...requestLogContext, |\n|\nprincipalKind: result.kind, |\n|\n}); |\n|\n} |\n|\n|\n|\nconst handlerStartedAt = Date.now(); |\n|\nconst response = await mcpHandler.fetch(request, env, ctx); |\n|\n|\n|\nif (url.pathname.startsWith(\"/s/ynab\")) { |\n|\nconsole.log(\"ynab mcp handler response\", { |\n|\ndurationMs: Date.now() - handlerStartedAt, |\n|\nmethod: request.method, |\n|\nrequestSessionId: request.headers.get(\"mcp-session-id\"), |\n|\nresponseContentType: response.headers.get(\"content-type\"), |\n|\nresponseSessionId: response.headers.get(\"mcp-session-id\"), |\n|\nstatus: response.status, |\n|\n}); |\n|\n} |\n|\n|\n|\nreturn response; |\n|\n}, |\n|\n}; |", "url": "https://wpnews.pro/news/code-mode-ts", "canonical_source": "https://gist.github.com/dmmulroy/3678b14a06c2977ea62660fe549a8d62", "published_at": "2026-06-03 02:34:17+00:00", "updated_at": "2026-06-03 06:13:55.858970+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-infrastructure", "ai-products"], "entities": ["Cloudflare", "YNAB", "Effect", "McpAgent", "McpServer", "CloudflareAccessJwtVerifier", "CloudflareAccessKeyResolver", "CloudflareAccessPrincipal"], "alternates": {"html": "https://wpnews.pro/news/code-mode-ts", "markdown": "https://wpnews.pro/news/code-mode-ts.md", "text": "https://wpnews.pro/news/code-mode-ts.txt", "jsonld": "https://wpnews.pro/news/code-mode-ts.jsonld"}}