| |
| import { getPublicCorsHeaders } from '../_cors.js'; |
| import { |
| applyAnonDiscoveryLimit, |
| applyPerMinuteLimit, |
| PRODUCTION_DEPS, |
| resolveAuthContext, |
| runContextPreChecks, |
| wwwAuthHeader, |
| } from './auth'; |
| import { |
| MCP_LOG_LEVELS, |
| negotiateProtocolVersion, |
| SERVER_INSTRUCTIONS, |
| SERVER_NAME, |
| SERVER_VERSION, |
| } from './constants'; |
| import { dispatchToolsCall } from './dispatch'; |
| import { buildPromptResponse, PROMPT_LIST_RESPONSE } from './prompts/index'; |
| import { TOOL_LIST_BYTES, TOOL_LIST_RESPONSE } from './registry/index'; |
| import { |
| buildPublicResourceResponse, |
| buildResourceResponse, |
| isPublicResourceUri, |
| RESOURCE_LIST_RESPONSE, |
| RESOURCE_TEMPLATE_LIST_RESPONSE, |
| } from './resources/index'; |
| import { rpcError, rpcOk, withMcpNoStore } from './rpc'; |
| import { buildUiResourceRead, isUiResourceUri, UI_RESOURCE_LIST_RESPONSE } from './ui/registry'; |
| import { emitTelemetry, principalIdForLog } from './telemetry'; |
| import { createMcpUsage, emitMcpRequestEvent, setUsageContext, type McpUsage } from './usage'; |
| import type { McpAuthContext, McpHandlerDeps } from './types'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const PUBLIC_MCP_METHODS: ReadonlySet<string> = new Set([ |
| 'initialize', |
| 'notifications/initialized', |
| 'ping', |
| 'tools/list', |
| 'prompts/list', |
| 'prompts/get', |
| 'resources/list', |
| 'resources/templates/list', |
| 'logging/setLevel', |
| ]); |
|
|
| |
| |
| |
| |
| function hasCredentials(req: Request): boolean { |
| if ((req.headers.get('Authorization') ?? '').startsWith('Bearer ')) return true; |
| return (req.headers.get('X-WorldMonitor-Key') ?? '') !== ''; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function authRequiredResponse(id: unknown, resourceMetadataUrl: string, corsHeaders: Record<string, string>): Response { |
| return new Response( |
| JSON.stringify({ jsonrpc: '2.0', id: id ?? null, error: { code: -32001, message: 'Authentication required.' } }), |
| { status: 401, headers: withMcpNoStore({ 'Content-Type': 'application/json', 'WWW-Authenticate': wwwAuthHeader(resourceMetadataUrl), ...corsHeaders }) }, |
| ); |
| } |
|
|
| type StoredSseEvent = { |
| id: string; |
| data: string; |
| }; |
|
|
| const SSE_CONTENT_TYPE = 'text/event-stream; charset=utf-8'; |
| |
| |
| |
| const MCP_CACHE_CONTROL = 'no-store, no-transform'; |
| const MAX_SSE_SESSIONS = 500; |
| const MAX_SSE_STREAMS_PER_SESSION = 25; |
| const mcpSseStreamsBySession = new Map<string, Map<string, StoredSseEvent[]>>(); |
|
|
| function getMcpCorsHeaders(methods = 'POST, GET, HEAD, OPTIONS'): Record<string, string> { |
| return { |
| ...getPublicCorsHeaders(methods), |
| 'Cache-Control': MCP_CACHE_CONTROL, |
| }; |
| } |
|
|
| function clientAcceptsSse(req: Request): boolean { |
| const accept = req.headers.get('accept') ?? ''; |
| return accept.split(',').some((entry) => { |
| const [type, ...params] = entry.split(';').map((part) => part.trim().toLowerCase()); |
| if (type !== 'text/event-stream') return false; |
| const qParam = params.find((part) => part.startsWith('q=')); |
| if (!qParam) return true; |
| const q = Number(qParam.slice(2)); |
| return Number.isFinite(q) && q > 0; |
| }); |
| } |
|
|
| function formatSseEvent(event: StoredSseEvent): string { |
| const lines = [`id: ${event.id}`]; |
| if (event.data === '') { |
| lines.push('data:'); |
| } else { |
| for (const line of event.data.split(/\r?\n/)) lines.push(`data: ${line}`); |
| } |
| return `${lines.join('\n')}\n\n`; |
| } |
|
|
| function encodeSseEvent(event: StoredSseEvent): Uint8Array { |
| return new TextEncoder().encode(formatSseEvent(event)); |
| } |
|
|
| function createSseStream(events: StoredSseEvent[]): ReadableStream<Uint8Array> { |
| return new ReadableStream<Uint8Array>({ |
| start(controller) { |
| const [first, ...rest] = events; |
| if (!first) { |
| controller.close(); |
| return; |
| } |
| controller.enqueue(encodeSseEvent(first)); |
| setTimeout(() => { |
| try { |
| for (const event of rest) controller.enqueue(encodeSseEvent(event)); |
| controller.close(); |
| } catch (err) { |
| controller.error(err); |
| } |
| }, 0); |
| }, |
| }); |
| } |
|
|
| function sessionStreamsForWrite(sessionId: string): Map<string, StoredSseEvent[]> { |
| let streams = mcpSseStreamsBySession.get(sessionId); |
| if (!streams) { |
| streams = new Map(); |
| mcpSseStreamsBySession.set(sessionId, streams); |
| if (mcpSseStreamsBySession.size > MAX_SSE_SESSIONS) { |
| const oldestSessionId = mcpSseStreamsBySession.keys().next().value; |
| if (oldestSessionId) mcpSseStreamsBySession.delete(oldestSessionId); |
| } |
| } |
| return streams; |
| } |
|
|
| function storeSseStream(sessionId: string, streamId: string, events: StoredSseEvent[]) { |
| const streams = sessionStreamsForWrite(sessionId); |
| streams.set(streamId, events); |
| while (streams.size > MAX_SSE_STREAMS_PER_SESSION) { |
| const oldestStreamId = streams.keys().next().value; |
| if (!oldestStreamId) break; |
| streams.delete(oldestStreamId); |
| } |
| } |
|
|
| function parseEventCursor(eventId: string): { streamId: string; sequence: number } | null { |
| const separator = eventId.lastIndexOf(':'); |
| if (separator <= 0) return null; |
| const sequence = Number(eventId.slice(separator + 1)); |
| if (!Number.isInteger(sequence) || sequence < 0) return null; |
| return { streamId: eventId.slice(0, separator), sequence }; |
| } |
|
|
| function replayEventsAfter(sessionId: string, lastEventId: string): StoredSseEvent[] | null { |
| const cursor = parseEventCursor(lastEventId); |
| if (!cursor) return null; |
| const events = mcpSseStreamsBySession.get(sessionId)?.get(cursor.streamId); |
| if (!events) return null; |
| return events.slice(cursor.sequence + 1); |
| } |
|
|
| function sseHeadersFrom(headers: Headers): Headers { |
| const out = new Headers(headers); |
| out.set('Content-Type', SSE_CONTENT_TYPE); |
| |
| |
| |
| out.set('Cache-Control', MCP_CACHE_CONTROL); |
| return out; |
| } |
|
|
| async function maybeStreamJsonRpcResponse(req: Request, response: Response): Promise<Response> { |
| if (req.method !== 'POST' || response.status !== 200 || !clientAcceptsSse(req)) return response; |
| if (!(response.headers.get('content-type') ?? '').toLowerCase().includes('application/json')) return response; |
|
|
| const sessionId = response.headers.get('mcp-session-id') ?? req.headers.get('mcp-session-id'); |
| if (!sessionId) return response; |
|
|
| const streamId = crypto.randomUUID(); |
| const responseBody = await response.text(); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const events: StoredSseEvent[] = [{ id: `${streamId}:0`, data: responseBody }]; |
| storeSseStream(sessionId, streamId, events); |
| return new Response(createSseStream(events), { |
| status: 200, |
| headers: sseHeadersFrom(response.headers), |
| }); |
| } |
|
|
| function handleSseReplay(req: Request, corsHeaders: Record<string, string>, headOnly = false): Response { |
| const lastEventId = req.headers.get('last-event-id'); |
| if (!clientAcceptsSse(req)) { |
| return new Response( |
| JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32600, message: 'SSE replay requires Accept: text/event-stream' } }), |
| { status: 406, headers: withMcpNoStore({ 'Content-Type': 'application/json', ...corsHeaders }) }, |
| ); |
| } |
| |
| |
| |
| |
| |
| |
| if (!lastEventId) { |
| return new Response( |
| JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Missing Last-Event-ID for SSE replay' } }), |
| { status: 400, headers: withMcpNoStore({ 'Content-Type': 'application/json', ...corsHeaders }) }, |
| ); |
| } |
|
|
| const sessionId = req.headers.get('mcp-session-id'); |
| if (!sessionId) { |
| return new Response( |
| JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Missing Mcp-Session-Id for SSE replay' } }), |
| { status: 400, headers: withMcpNoStore({ 'Content-Type': 'application/json', ...corsHeaders }) }, |
| ); |
| } |
|
|
| const events = replayEventsAfter(sessionId, lastEventId); |
| if (!events) { |
| return new Response( |
| JSON.stringify({ |
| jsonrpc: '2.0', |
| id: null, |
| error: { |
| code: -32004, |
| message: 'SSE replay cursor not found for this session; the stream may have expired or the reconnect may have reached a different server instance', |
| }, |
| }), |
| { status: 404, headers: withMcpNoStore({ 'Content-Type': 'application/json', ...corsHeaders }) }, |
| ); |
| } |
|
|
| return new Response(headOnly ? null : createSseStream(events), { |
| status: 200, |
| |
| |
| |
| headers: { 'Content-Type': SSE_CONTENT_TYPE, ...corsHeaders }, |
| }); |
| } |
|
|
| async function handleAuthenticatedSseReplay( |
| req: Request, |
| deps: McpHandlerDeps, |
| resourceMetadataUrl: string, |
| corsHeaders: Record<string, string>, |
| usage: McpUsage, |
| ctx: { waitUntil: (p: Promise<unknown>) => void } | undefined, |
| headOnly = false, |
| ): Promise<Response> { |
| const auth = await resolveAuthContext(req, deps, resourceMetadataUrl, corsHeaders); |
| if (!auth.ok) { |
| usage.phase = 'auth'; |
| return auth.response; |
| } |
| setUsageContext(usage, auth.context); |
| const getPreCheck = await runContextPreChecks(auth.context, deps, resourceMetadataUrl, corsHeaders, ctx); |
| if (!getPreCheck.ok) { |
| usage.phase = getPreCheck.response.headers.get('X-Billing-Verification') ? 'billing' : 'precheck'; |
| return getPreCheck.response; |
| } |
| const getLimited = await applyPerMinuteLimit(auth.context, corsHeaders); |
| if (getLimited) { |
| usage.phase = 'limit'; |
| return getLimited; |
| } |
| const replay = handleSseReplay(req, corsHeaders, headOnly); |
| if (replay.status !== 200) usage.phase = 'transport'; |
| return replay; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const WELL_KNOWN_MCP_PATHS = new Set(['/.well-known/mcp', '/.well-known/mcp.json']); |
| const MCP_TRANSPORT_PATH = '/mcp'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const DISCOVERY_VARY = 'Accept, Last-Event-ID'; |
| const STATIC_ASSET_FETCH_TIMEOUT_MS = 5_000; |
| const STATIC_ASSET_USER_AGENT = 'WorldMonitor-MCP/1.0 (+https://worldmonitor.app)'; |
|
|
| |
| let serverCardCache: string | null = null; |
| let mcpGuideCache: string | null = null; |
|
|
| |
| |
| |
| |
| |
| async function fetchStaticAsset(req: Request, path: string): Promise<string | null> { |
| const controller = new AbortController(); |
| const timeout = setTimeout(() => controller.abort(), STATIC_ASSET_FETCH_TIMEOUT_MS); |
| try { |
| const res = await fetch(new URL(path, req.url), { |
| headers: { 'User-Agent': STATIC_ASSET_USER_AGENT }, |
| signal: controller.signal, |
| }); |
| if (!res.ok) return null; |
| return await res.text(); |
| } catch { |
| return null; |
| } finally { |
| clearTimeout(timeout); |
| } |
| } |
|
|
| async function serveServerCard(req: Request, corsHeaders: Record<string, string>, headOnly = false): Promise<Response> { |
| if (serverCardCache === null) { |
| const text = await fetchStaticAsset(req, '/.well-known/mcp/server-card.json'); |
| if (text === null) { |
| |
| |
| return new Response(null, { |
| status: 302, |
| headers: { Location: '/.well-known/mcp/server-card.json', Vary: DISCOVERY_VARY, ...corsHeaders }, |
| }); |
| } |
| serverCardCache = text; |
| } |
| return new Response(headOnly ? null : serverCardCache, { |
| status: 200, |
| |
| |
| |
| |
| |
| |
| |
| headers: { |
| 'Content-Type': 'application/json; charset=utf-8', |
| ...corsHeaders, |
| 'Cache-Control': 'public, max-age=3600', |
| Vary: DISCOVERY_VARY, |
| }, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function serveMcpGuide(req: Request, corsHeaders: Record<string, string>, headOnly = false): Promise<Response> { |
| if (mcpGuideCache === null) { |
| const text = await fetchStaticAsset(req, '/mcp-server.md'); |
| if (text === null) { |
| return new Response(null, { |
| status: 302, |
| headers: { Location: '/mcp-server.md', Vary: DISCOVERY_VARY, ...corsHeaders }, |
| }); |
| } |
| mcpGuideCache = text; |
| } |
| return new Response(headOnly ? null : mcpGuideCache, { |
| status: 200, |
| |
| |
| |
| |
| |
| headers: { |
| 'Content-Type': 'text/markdown; charset=utf-8', |
| ...corsHeaders, |
| Vary: DISCOVERY_VARY, |
| Link: '<https://worldmonitor.app/mcp>; rel="canonical"', |
| }, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function mcpHandler( |
| req: Request, |
| deps: McpHandlerDeps, |
| ctx?: { waitUntil: (p: Promise<unknown>) => void }, |
| ): Promise<Response> { |
| const t0 = Date.now(); |
| const usage = createMcpUsage(); |
| let res: Response; |
| try { |
| res = await mcpHandlerInner(req, deps, usage, ctx); |
| } catch (err) { |
| emitMcpRequestEvent(req, new Response(null, { status: 500 }), usage, Date.now() - t0, ctx); |
| throw err; |
| } |
| emitMcpRequestEvent(req, res, usage, Date.now() - t0, ctx); |
| return res; |
| } |
|
|
| async function mcpHandlerInner( |
| req: Request, |
| deps: McpHandlerDeps, |
| usage: McpUsage, |
| ctx?: { waitUntil: (p: Promise<unknown>) => void }, |
| ): Promise<Response> { |
| |
| const corsHeaders = getMcpCorsHeaders(); |
|
|
| if (req.method === 'OPTIONS') { |
| usage.skip = true; |
| return new Response(null, { status: 204, headers: withMcpNoStore(corsHeaders) }); |
| } |
|
|
| |
| const requestHost = req.headers.get('host') ?? new URL(req.url).host; |
| const resourceMetadataUrl = `https://${requestHost}/.well-known/oauth-protected-resource`; |
|
|
| if (req.method === 'HEAD') { |
| |
| |
| if (req.headers.get('last-event-id')) { |
| return handleAuthenticatedSseReplay(req, deps, resourceMetadataUrl, corsHeaders, usage, ctx, true); |
| } |
| if (clientAcceptsSse(req)) { |
| usage.phase = 'transport'; |
| return new Response(null, { |
| status: 405, |
| headers: withMcpNoStore({ Allow: 'POST, GET, HEAD, OPTIONS', ...corsHeaders }), |
| }); |
| } |
|
|
| usage.skip = true; |
| |
| |
| |
| const pathname = new URL(req.url).pathname; |
| if (WELL_KNOWN_MCP_PATHS.has(pathname)) { |
| return serveServerCard(req, corsHeaders, true); |
| } |
| if (pathname === MCP_TRANSPORT_PATH) { |
| return serveMcpGuide(req, corsHeaders, true); |
| } |
| return new Response(null, { |
| status: 200, |
| headers: withMcpNoStore({ 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders }), |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| if ( |
| req.method === 'GET' && |
| !req.headers.get('last-event-id') && |
| !clientAcceptsSse(req) |
| ) { |
| const pathname = new URL(req.url).pathname; |
| if (WELL_KNOWN_MCP_PATHS.has(pathname)) { |
| usage.skip = true; |
| return serveServerCard(req, corsHeaders); |
| } |
| if (pathname === MCP_TRANSPORT_PATH) { |
| usage.skip = true; |
| return serveMcpGuide(req, corsHeaders); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| if (req.method !== 'POST' && req.method !== 'GET') { |
| usage.phase = 'transport'; |
| return new Response(null, { status: 405, headers: withMcpNoStore({ Allow: 'POST, GET, HEAD, OPTIONS', ...corsHeaders }) }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (req.method === 'GET') { |
| if (!req.headers.get('last-event-id')) { |
| usage.phase = 'transport'; |
| return new Response(null, { |
| status: 405, |
| headers: withMcpNoStore({ Allow: 'POST, GET, HEAD, OPTIONS', ...corsHeaders }), |
| }); |
| } |
| return handleAuthenticatedSseReplay(req, deps, resourceMetadataUrl, corsHeaders, usage, ctx); |
| } |
|
|
| |
| |
| |
| |
| let body: { jsonrpc?: string; id?: unknown; method?: string; params?: unknown }; |
| try { |
| body = await req.json(); |
| } catch { |
| usage.phase = 'malformed'; |
| return rpcError(null, -32600, 'Invalid request: malformed JSON', corsHeaders); |
| } |
|
|
| if (!body || typeof body.method !== 'string') { |
| usage.phase = 'malformed'; |
| return rpcError(body?.id ?? null, -32600, 'Invalid request: missing method', corsHeaders); |
| } |
|
|
| const { id, method } = body; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const resourceReadUri = method === 'resources/read' |
| ? ((body.params as { uri?: unknown } | null)?.uri) |
| : undefined; |
| const uiResourceReadUri = typeof resourceReadUri === 'string' && isUiResourceUri(resourceReadUri) |
| ? resourceReadUri |
| : null; |
| const isPublicResourceRead = typeof resourceReadUri === 'string' && isPublicResourceUri(resourceReadUri); |
| const isAnonResourceRead = uiResourceReadUri !== null || isPublicResourceRead; |
|
|
| |
| |
| let context: McpAuthContext | null = null; |
| |
| |
| let mcpDailyLimit: number | null | undefined; |
| if (PUBLIC_MCP_METHODS.has(method) || isAnonResourceRead) { |
| if (hasCredentials(req)) { |
| |
| |
| |
| const auth = await resolveAuthContext(req, deps, resourceMetadataUrl, corsHeaders); |
| if (!auth.ok) { |
| usage.phase = 'auth'; |
| return auth.response; |
| } |
| context = auth.context; |
| setUsageContext(usage, context); |
| const limited = await applyPerMinuteLimit(context, corsHeaders); |
| if (limited) { |
| usage.phase = 'limit'; |
| return limited; |
| } |
| } else { |
| const anonLimited = await applyAnonDiscoveryLimit(req, corsHeaders); |
| if (anonLimited) { |
| usage.phase = 'limit'; |
| return anonLimited; |
| } |
| } |
| } else { |
| const auth = await resolveAuthContext(req, deps, resourceMetadataUrl, corsHeaders); |
| if (!auth.ok) { |
| usage.phase = 'auth'; |
| return auth.response; |
| } |
| context = auth.context; |
| setUsageContext(usage, context); |
| const preCheck = await runContextPreChecks(context, deps, resourceMetadataUrl, corsHeaders, ctx); |
| if (!preCheck.ok) { |
| usage.phase = preCheck.response.headers.get('X-Billing-Verification') ? 'billing' : 'precheck'; |
| return preCheck.response; |
| } |
| |
| |
| |
| mcpDailyLimit = preCheck.mcpDailyLimit; |
| const limited = await applyPerMinuteLimit(context, corsHeaders); |
| if (limited) { |
| usage.phase = 'limit'; |
| return limited; |
| } |
| } |
|
|
| |
| switch (method) { |
| case 'initialize': { |
| const sessionId = crypto.randomUUID(); |
| const clientRequestedVersion = (body.params as { protocolVersion?: unknown } | null | undefined)?.protocolVersion; |
| const negotiatedVersion = negotiateProtocolVersion(clientRequestedVersion); |
| |
| |
| |
| |
| emitTelemetry('mcp.tools_list_emitted', { |
| auth_kind: context?.kind ?? 'anon', |
| user_id: context ? principalIdForLog(context) : 'anon', |
| tools_array_bytes: TOOL_LIST_BYTES, |
| tool_count: TOOL_LIST_RESPONSE.length, |
| client_user_agent: (req.headers.get('User-Agent') ?? '').slice(0, 256), |
| }); |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, { |
| protocolVersion: negotiatedVersion, |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| capabilities: { |
| tools: {}, |
| logging: {}, |
| prompts: { listChanged: false }, |
| resources: { subscribe: false, listChanged: false }, |
| extensions: { 'io.modelcontextprotocol/ui': {} }, |
| }, |
| serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }, |
| instructions: SERVER_INSTRUCTIONS, |
| }, { 'Mcp-Session-Id': sessionId, ...corsHeaders })); |
| } |
| case 'notifications/initialized': |
| return new Response(null, { status: 202, headers: withMcpNoStore(corsHeaders) }); |
| case 'ping': |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, {}, corsHeaders)); |
| case 'tools/list': |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, { tools: TOOL_LIST_RESPONSE }, corsHeaders)); |
| case 'tools/call': { |
| |
| |
| if (!context) { |
| usage.phase = 'auth'; |
| return authRequiredResponse(id, resourceMetadataUrl, corsHeaders); |
| } |
| const dispatched = await dispatchToolsCall(req, context, deps, body, corsHeaders, ctx, mcpDailyLimit); |
| |
| |
| |
| if (dispatched.headers.get('X-Billing-Verification')) { |
| usage.phase = 'billing'; |
| } else if (dispatched.status === 429 || dispatched.status === 503) { |
| usage.phase = 'dispatch'; |
| } |
| return maybeStreamJsonRpcResponse(req, dispatched); |
| } |
| |
| |
| |
| |
| |
| case 'prompts/list': |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, { prompts: PROMPT_LIST_RESPONSE }, corsHeaders)); |
| case 'prompts/get': { |
| const params = body.params as { name?: unknown; arguments?: Record<string, unknown> } | null; |
| if (!params || typeof params.name !== 'string') { |
| return maybeStreamJsonRpcResponse(req, rpcError(id, -32602, 'Invalid params: missing prompt name', corsHeaders)); |
| } |
| const built = buildPromptResponse(params.name, params.arguments); |
| if (!built.ok) return maybeStreamJsonRpcResponse(req, rpcError(id, built.code, built.message, corsHeaders)); |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, { description: built.description, messages: built.messages }, corsHeaders)); |
| } |
| |
| |
| |
| |
| |
| |
| |
| case 'resources/list': |
| |
| |
| |
| |
| |
| |
| |
| |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, { resources: [...RESOURCE_LIST_RESPONSE, ...UI_RESOURCE_LIST_RESPONSE] }, corsHeaders)); |
| case 'resources/templates/list': |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, { resourceTemplates: RESOURCE_TEMPLATE_LIST_RESPONSE }, corsHeaders)); |
| case 'resources/read': |
| |
| |
| |
| if (uiResourceReadUri) { |
| return maybeStreamJsonRpcResponse(req, buildUiResourceRead(id, uiResourceReadUri, corsHeaders)); |
| } |
| |
| |
| |
| if (isPublicResourceRead) { |
| return maybeStreamJsonRpcResponse(req, await buildPublicResourceResponse(body, corsHeaders)); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| if (!context) { |
| usage.phase = 'auth'; |
| return authRequiredResponse(id, resourceMetadataUrl, corsHeaders); |
| } |
| { |
| const resourceRes = await buildResourceResponse(req, context, deps, body, corsHeaders, ctx, mcpDailyLimit); |
| if (resourceRes.status === 429 || resourceRes.status === 503) usage.phase = 'dispatch'; |
| return maybeStreamJsonRpcResponse(req, resourceRes); |
| } |
| case 'logging/setLevel': { |
| const level = (body.params as { level?: string } | null)?.level; |
| if (typeof level !== 'string' || !MCP_LOG_LEVELS.has(level)) { |
| return maybeStreamJsonRpcResponse(req, rpcError(id, -32602, |
| `Invalid params: level must be one of ${[...MCP_LOG_LEVELS].join(', ')}`, |
| corsHeaders, |
| )); |
| } |
| return maybeStreamJsonRpcResponse(req, rpcOk(id, {}, corsHeaders)); |
| } |
| default: |
| return maybeStreamJsonRpcResponse(req, rpcError(id, -32601, `Method not found: ${method}`, corsHeaders)); |
| } |
| } |
|
|
| |
| |
| |
| |
| export default async function handler( |
| req: Request, |
| ctx?: { waitUntil: (p: Promise<unknown>) => void }, |
| ): Promise<Response> { |
| return mcpHandler(req, PRODUCTION_DEPS, ctx); |
| } |
|
|