diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf48d30cfc..77fb555cb6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2318,6 +2318,34 @@ importers: specifier: 'catalog:' version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.5.2)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.4) + services/mcp-gateway: + dependencies: + hono: + specifier: 4.12.18 + version: 4.12.18 + zod: + specifier: 'catalog:' + version: 4.4.3 + devDependencies: + '@cloudflare/workers-types': + specifier: 'catalog:' + version: 4.20260511.1 + '@types/node': + specifier: 'catalog:' + version: 24.12.4 + '@typescript/native-preview': + specifier: 'catalog:' + version: 7.0.0-dev.20260514.1 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(@vitest/ui@4.1.6)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.4) + wrangler: + specifier: 'catalog:' + version: 4.90.1(@cloudflare/workers-types@4.20260511.1)(bufferutil@4.1.0)(utf-8-validate@6.0.6) + services/model-eval-ingest: dependencies: '@kilocode/db': diff --git a/services/mcp-gateway/.dev.vars.example b/services/mcp-gateway/.dev.vars.example new file mode 100644 index 0000000000..adc36fd866 --- /dev/null +++ b/services/mcp-gateway/.dev.vars.example @@ -0,0 +1,9 @@ +# MCP Gateway Worker local development values. +# +# This service is intentionally non-functional in PR1. It has no OAuth, +# provider, database, or proxy behavior yet. Wrangler reads non-secret vars +# from wrangler.jsonc; keep secrets out of this file until PR2 introduces +# the corresponding runtime behavior. +# +# Hyperdrive is configured in wrangler.jsonc. For local Postgres development, +# the binding uses the local connection string declared there. diff --git a/services/mcp-gateway/AGENTS.md b/services/mcp-gateway/AGENTS.md new file mode 100644 index 0000000000..59373d9e4a --- /dev/null +++ b/services/mcp-gateway/AGENTS.md @@ -0,0 +1,91 @@ +# MCP Gateway Conventions + +## Scope + +`services/mcp-gateway` is the runtime plane for the Kilo MCP Gateway. The Next.js +app owns interactive OAuth, configuration CRUD, assignment management, provider +callbacks, gateway token issuance, and control-plane audit. This Worker owns scoped +runtime routing, protected-resource metadata, gateway-token verification, runtime +Postgres rechecks, upstream credential injection, streaming proxying, per-instance +refresh coordination, and runtime telemetry. + +The Worker MUST NOT implement first-level OAuth authorization, token, registration, +provider callback, JWKS, user-info, config CRUD, assignment CRUD, or app management +routes in v1. + +## File naming + +- Add a suffix matching the module type, for example `mcp-gateway.worker.ts`, + `MCPGatewayInstance.do.ts`, `connect.handler.ts`, `routes.schema.ts`, and + `instances.table.ts`. +- Modules that predominantly export a class should be named after that class. +- Keep pure helpers in `lib/` and keep route handlers in `handlers/`. + +## HTTP routes + +- Define every exposed Hono route in `src/mcp-gateway.worker.ts` so the public + surface is visible in one file. +- Do not mount Hono sub-apps. +- Move route logic into `handlers/*.handler.ts` modules. +- Each handler takes the Hono context and a plain parsed params object. The route + declaration remains the source of truth for path-to-param shape. +- Runtime routes are scoped connect resources only: + - `/mcp-connect/user/{user_id}/{config_id}/{route_key}` + - `/mcp-connect/org/{org_id}/{config_id}/{route_key}` +- Protected-resource metadata is the only other public gateway surface owned by + this Worker. + +## IO boundaries + +- Validate every IO boundary with Zod: MCP messages, route params, query params, + behavior-affecting headers, upstream responses, JSON parse results, SSE payloads, + subprocess output, and persisted session records. +- Raw parse and fetch helpers return `unknown`; callers parse with the relevant + Zod schema. +- Do not use `as` casts for IO shapes. Use schemas, `.passthrough()`, or explicit + catch-all schemas when the shape is intentionally broad. +- The gateway is stricter than Gastown at MCP protocol, header, query, upstream + response, and persisted-session boundaries. + +## Hyperdrive and Postgres + +- Use `getWorkerDb(env.HYPERDRIVE.connectionString, { statement_timeout: ... })` + per request or per Durable Object use. +- Never cache pg pools, Drizzle clients, transaction objects, request-scoped state, + or other transport-owning SDK objects in module scope. +- Postgres remains the shared system of record for config, route, assignment, + identity, instance, and grant state. +- The Worker must re-check current Postgres state on every authenticated runtime + request before proxying, even when a Durable Object cache has older material. + +## Durable Objects + +- `MCPGatewayInstance` is the per-instance runtime coordination atom. Its + deterministic key is `{owner_scope}:{owner_id}:{config_id}:{user_id}`. +- Do not introduce a global gateway Durable Object or a config-level DO that + serializes all users of a shared org config. +- Every DO module exports a `get{ClassName}Stub` helper, and callers use that + helper instead of accessing the namespace binding directly. +- Keep the DO class thin: RPC surface, alarms, and orchestration only. Move large + domain logic into plain-function submodules under a sibling directory when the + class grows beyond a few hundred lines. +- DO cache state is never authoritative for config, assignment, identity, route, + or grant eligibility. +- If DO SQLite is used, use tracked schema migrations from day one instead of ad + hoc `CREATE TABLE IF NOT EXISTS` drift. +- Use table interpolator objects and Zod row schemas for DO SQLite queries instead + of raw table or column strings and unsafe casts. + +## Security and streaming + +- Route knowledge is not an authorization boundary. Every authenticated runtime + request must verify the exact scoped route, token audience, route key, config + status, identity, org membership, assignment, execution context, and instance + status. +- The client `Authorization` header is only for gateway authentication and must + never be forwarded upstream. +- Strip credential-like client headers before proxying, including `Authorization`, + `Proxy-Authorization`, `Cookie`, `X-API-Key`, `X-Auth-*`, and `X-Token-*`. +- Stream unknown request and response bodies. Do not buffer unbounded payloads. +- Do not log tokens, credentials, auth headers, cookies, webhook secrets, raw + provider payloads, or other secret material. diff --git a/services/mcp-gateway/README.md b/services/mcp-gateway/README.md new file mode 100644 index 0000000000..f45d5cf477 --- /dev/null +++ b/services/mcp-gateway/README.md @@ -0,0 +1,35 @@ +# MCP Gateway + +`services/mcp-gateway` is the Kilo MCP Gateway runtime Worker. PR1 intentionally +ships only the route skeleton for scoped MCP connect resources and protected-resource +metadata. The Worker is not attached to `mcp.kilo.ai` yet and does not implement OAuth, +provider discovery, database state, credential injection, or proxying. + +## Public surface in PR1 + +- `GET /health` +- `GET|POST /mcp-connect/user/{user_id}/{config_id}/{route_key}` +- `GET|POST /mcp-connect/org/{org_id}/{config_id}/{route_key}` +- Optional descendant paths under each scoped connect route +- `GET /.well-known/oauth-protected-resource` +- `GET /.well-known/oauth-protected-resource/mcp-connect/user/{user_id}/{config_id}/{route_key}` +- `GET /.well-known/oauth-protected-resource/mcp-connect/org/{org_id}/{config_id}/{route_key}` + +All runtime and protected-resource routes return `501 Not Implemented` in PR1. + +## Commands + +```bash +pnpm --filter cloudflare-mcp-gateway types +pnpm --filter cloudflare-mcp-gateway typecheck +pnpm --filter cloudflare-mcp-gateway test +pnpm --filter cloudflare-mcp-gateway lint +pnpm --filter cloudflare-mcp-gateway dev +``` + +## Architecture + +The Next.js app owns the interactive OAuth and control plane. This Worker owns the +runtime plane: protected-resource discovery, gateway-token verification, runtime +rechecks, upstream credential injection, streaming proxying, and per-instance refresh +coordination. The gateway architecture notes remain in the planning workspace until PR2. diff --git a/services/mcp-gateway/package.json b/services/mcp-gateway/package.json new file mode 100644 index 0000000000..68402a8071 --- /dev/null +++ b/services/mcp-gateway/package.json @@ -0,0 +1,29 @@ +{ + "name": "cloudflare-mcp-gateway", + "version": "1.0.0", + "type": "module", + "private": true, + "description": "Kilo MCP Gateway runtime worker", + "scripts": { + "dev": "wrangler dev --env dev --ip 0.0.0.0", + "start": "wrangler dev --env dev --ip 0.0.0.0", + "deploy": "wrangler deploy", + "types": "wrangler types --include-runtime=false", + "typecheck": "tsgo --noEmit", + "lint": "pnpm -w exec oxlint --config .oxlintrc.json services/mcp-gateway/src", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "hono": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "@cloudflare/workers-types": "catalog:", + "@types/node": "catalog:", + "@typescript/native-preview": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:", + "wrangler": "catalog:" + } +} diff --git a/services/mcp-gateway/src/durable-objects/MCPGatewayInstance.do.ts b/services/mcp-gateway/src/durable-objects/MCPGatewayInstance.do.ts new file mode 100644 index 0000000000..3ff5db081d --- /dev/null +++ b/services/mcp-gateway/src/durable-objects/MCPGatewayInstance.do.ts @@ -0,0 +1,12 @@ +import { DurableObject } from 'cloudflare:workers'; + +export class MCPGatewayInstance extends DurableObject { + constructor(state: DurableObjectState, env: Env) { + super(state, env); + } +} + +export function getMCPGatewayInstanceStub(env: Env, instanceKey: string) { + const id = env.MCP_GATEWAY_INSTANCE.idFromName(instanceKey); + return env.MCP_GATEWAY_INSTANCE.get(id); +} diff --git a/services/mcp-gateway/src/handlers/connect.handler.ts b/services/mcp-gateway/src/handlers/connect.handler.ts new file mode 100644 index 0000000000..66b8930ed0 --- /dev/null +++ b/services/mcp-gateway/src/handlers/connect.handler.ts @@ -0,0 +1,19 @@ +import type { Context } from 'hono'; +import type { MCPGatewayEnv } from '../types'; +import { + OrgConnectRouteParamsSchema, + UserConnectRouteParamsSchema, + type OrgConnectRouteParams, + type UserConnectRouteParams, +} from '../schemas/routes.schema'; +import { notImplementedResponse } from '../lib/responses'; + +export function handleUserConnect(c: Context, params: UserConnectRouteParams) { + UserConnectRouteParamsSchema.parse(params); + return notImplementedResponse(c); +} + +export function handleOrgConnect(c: Context, params: OrgConnectRouteParams) { + OrgConnectRouteParamsSchema.parse(params); + return notImplementedResponse(c); +} diff --git a/services/mcp-gateway/src/handlers/health.handler.ts b/services/mcp-gateway/src/handlers/health.handler.ts new file mode 100644 index 0000000000..a3d0f229d6 --- /dev/null +++ b/services/mcp-gateway/src/handlers/health.handler.ts @@ -0,0 +1,6 @@ +import type { Context } from 'hono'; +import type { MCPGatewayEnv } from '../types'; + +export function handleHealth(c: Context) { + return c.json({ status: 'ok', service: 'mcp-gateway' }); +} diff --git a/services/mcp-gateway/src/handlers/protected-resource.handler.ts b/services/mcp-gateway/src/handlers/protected-resource.handler.ts new file mode 100644 index 0000000000..3f6e1194b7 --- /dev/null +++ b/services/mcp-gateway/src/handlers/protected-resource.handler.ts @@ -0,0 +1,29 @@ +import type { Context } from 'hono'; +import type { MCPGatewayEnv } from '../types'; +import { + OrgConnectRouteParamsSchema, + UserConnectRouteParamsSchema, + type OrgConnectRouteParams, + type UserConnectRouteParams, +} from '../schemas/routes.schema'; +import { notImplementedResponse } from '../lib/responses'; + +export function handleProtectedResourceMetadata(c: Context) { + return notImplementedResponse(c); +} + +export function handleUserProtectedResourceMetadata( + c: Context, + params: UserConnectRouteParams +) { + UserConnectRouteParamsSchema.parse(params); + return notImplementedResponse(c); +} + +export function handleOrgProtectedResourceMetadata( + c: Context, + params: OrgConnectRouteParams +) { + OrgConnectRouteParamsSchema.parse(params); + return notImplementedResponse(c); +} diff --git a/services/mcp-gateway/src/lib/responses.ts b/services/mcp-gateway/src/lib/responses.ts new file mode 100644 index 0000000000..df01bb0386 --- /dev/null +++ b/services/mcp-gateway/src/lib/responses.ts @@ -0,0 +1,6 @@ +import type { Context } from 'hono'; +import type { MCPGatewayEnv } from '../types'; + +export function notImplementedResponse(c: Context) { + return c.json({ status: 'not_implemented' }, 501); +} diff --git a/services/mcp-gateway/src/mcp-gateway.worker.test.ts b/services/mcp-gateway/src/mcp-gateway.worker.test.ts new file mode 100644 index 0000000000..4df375e6be --- /dev/null +++ b/services/mcp-gateway/src/mcp-gateway.worker.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('cloudflare:workers', () => ({ + DurableObject: class FakeDurableObject { + constructor(..._args: unknown[]) {} + }, +})); + +import { app } from './mcp-gateway.worker'; + +const userRoute = '/mcp-connect/user/user-123/config-123/route-123'; +const orgRoute = '/mcp-connect/org/org-123/config-123/route-123'; +const userMetadataRoute = `/.well-known/oauth-protected-resource${userRoute}`; +const orgMetadataRoute = `/.well-known/oauth-protected-resource${orgRoute}`; + +async function request(path: string, method = 'GET') { + return app.request(`https://mcp.kilo.ai${path}`, { method }); +} + +describe('MCP gateway route surface', () => { + it('returns health independently of runtime stubs', async () => { + const response = await request('/health'); + + expect(response.status).toBe(200); + await expect(response.json()).resolves.toEqual({ status: 'ok', service: 'mcp-gateway' }); + }); + + it('returns 501 for scoped runtime root routes', async () => { + const responses = await Promise.all([ + request(userRoute), + request(userRoute, 'POST'), + request(orgRoute), + request(orgRoute, 'POST'), + ]); + + for (const response of responses) { + expect(response.status).toBe(501); + await expect(response.json()).resolves.toEqual({ status: 'not_implemented' }); + } + }); + + it('returns 501 for scoped runtime descendant routes', async () => { + const responses = await Promise.all([ + request(`${userRoute}/tools/list`), + request(`${userRoute}/tools/list`, 'POST'), + request(`${orgRoute}/tools/list`), + request(`${orgRoute}/tools/list`, 'POST'), + ]); + + for (const response of responses) { + expect(response.status).toBe(501); + } + }); + + it('returns 501 for generic and scoped protected-resource metadata routes', async () => { + const responses = await Promise.all([ + request('/.well-known/oauth-protected-resource'), + request(userMetadataRoute), + request(orgMetadataRoute), + ]); + + for (const response of responses) { + expect(response.status).toBe(501); + await expect(response.json()).resolves.toEqual({ status: 'not_implemented' }); + } + }); + + it('does not expose app-owned OAuth or management routes', async () => { + const responses = await Promise.all([ + request('/oauth/authorize'), + request('/oauth/token', 'POST'), + request('/oauth/register', 'POST'), + request('/oauth/jwks.json'), + request('/oauth/userinfo'), + request('/oauth/mcp/callback'), + request('/api/mcp-gateway/available'), + request('/api/mcp-gateway/personal/configs'), + ]); + + for (const response of responses) { + expect(response.status).toBe(404); + } + }); + + it('does not expose legacy opaque connect routes', async () => { + const response = await request('/mcp-connect/opaque-connect-id'); + + expect(response.status).toBe(404); + }); +}); diff --git a/services/mcp-gateway/src/mcp-gateway.worker.ts b/services/mcp-gateway/src/mcp-gateway.worker.ts new file mode 100644 index 0000000000..cbadaac177 --- /dev/null +++ b/services/mcp-gateway/src/mcp-gateway.worker.ts @@ -0,0 +1,44 @@ +import { Hono } from 'hono'; +import { handleOrgConnect, handleUserConnect } from './handlers/connect.handler'; +import { handleHealth } from './handlers/health.handler'; +import { + handleOrgProtectedResourceMetadata, + handleProtectedResourceMetadata, + handleUserProtectedResourceMetadata, +} from './handlers/protected-resource.handler'; +import type { MCPGatewayEnv } from './types'; + +export { MCPGatewayInstance } from './durable-objects/MCPGatewayInstance.do'; + +export const app = new Hono(); + +app.get('/health', c => handleHealth(c)); + +app.get('/mcp-connect/user/:userId/:configId/:routeKey', c => handleUserConnect(c, c.req.param())); +app.post('/mcp-connect/user/:userId/:configId/:routeKey', c => handleUserConnect(c, c.req.param())); +app.get('/mcp-connect/user/:userId/:configId/:routeKey/*', c => + handleUserConnect(c, c.req.param()) +); +app.post('/mcp-connect/user/:userId/:configId/:routeKey/*', c => + handleUserConnect(c, c.req.param()) +); + +app.get('/mcp-connect/org/:orgId/:configId/:routeKey', c => handleOrgConnect(c, c.req.param())); +app.post('/mcp-connect/org/:orgId/:configId/:routeKey', c => handleOrgConnect(c, c.req.param())); +app.get('/mcp-connect/org/:orgId/:configId/:routeKey/*', c => handleOrgConnect(c, c.req.param())); +app.post('/mcp-connect/org/:orgId/:configId/:routeKey/*', c => handleOrgConnect(c, c.req.param())); + +app.get('/.well-known/oauth-protected-resource', c => handleProtectedResourceMetadata(c)); +app.get('/.well-known/oauth-protected-resource/mcp-connect/user/:userId/:configId/:routeKey', c => + handleUserProtectedResourceMetadata(c, c.req.param()) +); +app.get('/.well-known/oauth-protected-resource/mcp-connect/org/:orgId/:configId/:routeKey', c => + handleOrgProtectedResourceMetadata(c, c.req.param()) +); + +const fetchHandler: ExportedHandler['fetch'] = (request, env, ctx) => + app.fetch(request, env, ctx); + +export default { + fetch: fetchHandler, +} satisfies ExportedHandler; diff --git a/services/mcp-gateway/src/routes/.gitkeep b/services/mcp-gateway/src/routes/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/mcp-gateway/src/schemas/routes.schema.ts b/services/mcp-gateway/src/schemas/routes.schema.ts new file mode 100644 index 0000000000..0f8bbfb02c --- /dev/null +++ b/services/mcp-gateway/src/schemas/routes.schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +const routeSegmentSchema = z.string().min(1); + +export const UserConnectRouteParamsSchema = z.object({ + userId: routeSegmentSchema, + configId: routeSegmentSchema, + routeKey: routeSegmentSchema, +}); + +export const OrgConnectRouteParamsSchema = z.object({ + orgId: routeSegmentSchema, + configId: routeSegmentSchema, + routeKey: routeSegmentSchema, +}); + +export type UserConnectRouteParams = z.infer; +export type OrgConnectRouteParams = z.infer; diff --git a/services/mcp-gateway/src/types.ts b/services/mcp-gateway/src/types.ts new file mode 100644 index 0000000000..cb7b762185 --- /dev/null +++ b/services/mcp-gateway/src/types.ts @@ -0,0 +1,3 @@ +export type MCPGatewayEnv = { + Bindings: Env; +}; diff --git a/services/mcp-gateway/tsconfig.json b/services/mcp-gateway/tsconfig.json new file mode 100644 index 0000000000..ba1a68e45c --- /dev/null +++ b/services/mcp-gateway/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "bundler", + "types": ["@types/node", "@cloudflare/workers-types", "./worker-configuration.d.ts"], + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["worker-configuration.d.ts", "src/**/*.ts", "vitest.config.ts"] +} diff --git a/services/mcp-gateway/vitest.config.ts b/services/mcp-gateway/vitest.config.ts new file mode 100644 index 0000000000..fc33515465 --- /dev/null +++ b/services/mcp-gateway/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + name: 'unit', + globals: true, + environment: 'node', + include: ['src/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: ['node_modules/', 'dist/', '**/*.test.ts'], + }, + }, +}); diff --git a/services/mcp-gateway/worker-configuration.d.ts b/services/mcp-gateway/worker-configuration.d.ts new file mode 100644 index 0000000000..290afb0a75 --- /dev/null +++ b/services/mcp-gateway/worker-configuration.d.ts @@ -0,0 +1,37 @@ +/* eslint-disable */ +// Generated by Wrangler by running `wrangler types --include-runtime=false` (hash: dccf822a4081d840a9731ce567abe251) +declare namespace Cloudflare { + interface GlobalProps { + mainModule: typeof import("./src/mcp-gateway.worker"); + durableNamespaces: "MCPGatewayInstance"; + } + interface DevEnv { + HYPERDRIVE: Hyperdrive; + MCP_GATEWAY_ANALYTICS: AnalyticsEngineDataset; + ENVIRONMENT: "development"; + APP_BASE_URL: "http://localhost:3000"; + MCP_GATEWAY_BASE_URL: "http://localhost:8806"; + SUPPORTED_SCOPES: "profile"; + GATEWAY_ACCESS_TOKEN_TTL_SECONDS: "900"; + AUDIT_RETENTION_DAYS: "60"; + MCP_GATEWAY_INSTANCE: DurableObjectNamespace; + } + interface Env { + HYPERDRIVE: Hyperdrive; + MCP_GATEWAY_ANALYTICS: AnalyticsEngineDataset; + ENVIRONMENT: "development" | "production"; + APP_BASE_URL: "http://localhost:3000" | "https://app.kilo.ai"; + MCP_GATEWAY_BASE_URL: "http://localhost:8806" | "https://mcp.kilo.ai"; + SUPPORTED_SCOPES: "profile"; + GATEWAY_ACCESS_TOKEN_TTL_SECONDS: "900"; + AUDIT_RETENTION_DAYS: "60"; + MCP_GATEWAY_INSTANCE: DurableObjectNamespace; + } +} +interface Env extends Cloudflare.Env {} +type StringifyValues> = { + [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; +}; +declare namespace NodeJS { + interface ProcessEnv extends StringifyValues> {} +} diff --git a/services/mcp-gateway/wrangler.jsonc b/services/mcp-gateway/wrangler.jsonc new file mode 100644 index 0000000000..b12f800c66 --- /dev/null +++ b/services/mcp-gateway/wrangler.jsonc @@ -0,0 +1,86 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "account_id": "e115e769bcdd4c3d66af59d3332cb394", + "name": "mcp-gateway", + "main": "src/mcp-gateway.worker.ts", + "compatibility_date": "2026-05-15", + "compatibility_flags": ["nodejs_compat"], + "workers_dev": false, + "preview_urls": false, + "dev": { + "port": 8806, + }, + "observability": { + "enabled": true, + }, + "durable_objects": { + "bindings": [ + { + "name": "MCP_GATEWAY_INSTANCE", + "class_name": "MCPGatewayInstance", + }, + ], + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["MCPGatewayInstance"], + }, + ], + "hyperdrive": [ + { + "binding": "HYPERDRIVE", + "id": "624ec80650dd414199349f4e217ddb10", + "localConnectionString": "postgres://postgres:postgres@localhost:5432/postgres", + }, + ], + "analytics_engine_datasets": [ + { + "binding": "MCP_GATEWAY_ANALYTICS", + "dataset": "mcp_gateway_events", + }, + ], + "vars": { + "ENVIRONMENT": "production", + "APP_BASE_URL": "https://app.kilo.ai", + "MCP_GATEWAY_BASE_URL": "https://mcp.kilo.ai", + "SUPPORTED_SCOPES": "profile", + "GATEWAY_ACCESS_TOKEN_TTL_SECONDS": "900", + "AUDIT_RETENTION_DAYS": "60", + }, + "env": { + "dev": { + "name": "mcp-gateway-dev", + "workers_dev": true, + "vars": { + "ENVIRONMENT": "development", + "APP_BASE_URL": "http://localhost:3000", + "MCP_GATEWAY_BASE_URL": "http://localhost:8806", + "SUPPORTED_SCOPES": "profile", + "GATEWAY_ACCESS_TOKEN_TTL_SECONDS": "900", + "AUDIT_RETENTION_DAYS": "60", + }, + "durable_objects": { + "bindings": [ + { + "name": "MCP_GATEWAY_INSTANCE", + "class_name": "MCPGatewayInstance", + }, + ], + }, + "hyperdrive": [ + { + "binding": "HYPERDRIVE", + "id": "624ec80650dd414199349f4e217ddb10", + "localConnectionString": "postgres://postgres:postgres@localhost:5432/postgres", + }, + ], + "analytics_engine_datasets": [ + { + "binding": "MCP_GATEWAY_ANALYTICS", + "dataset": "mcp_gateway_events", + }, + ], + }, + }, +}