Rebasely Beta: data may be purged

examples/MCP-tools Public

TypeScript · 61 lines · 3.2 KiB · at e74616c

import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";import { createServer } from "./server.ts";import { MAX_DAYS, countCall, readUsage } from "./usage.ts";export type Env = { USAGE: KVNamespace };// Browser clients (the MCP Inspector in direct mode, web agents) call /mcp cross-origin. The// server has no cookies or credentials to protect, so any origin may call it.const CORS = {  "access-control-allow-origin": "*",  "access-control-allow-methods": "GET, POST, OPTIONS",  "access-control-allow-headers": "content-type, accept, authorization, mcp-protocol-version, mcp-session-id, last-event-id",  "access-control-expose-headers": "mcp-session-id, mcp-protocol-version",  "access-control-max-age": "86400",};export async function handle(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {  const { pathname } = new URL(request.url);  if (pathname === "/mcp") return withCors(await serveMcp(request, env, ctx));  if (pathname === "/stats" && request.method === "GET") return withCors(await stats(request, env));  if (pathname === "/" && request.method === "GET") {    return new Response("mcp-tools: an MCP server. Point a client at /mcp (Streamable HTTP).\n", { headers: { "content-type": "text/plain; charset=utf-8" } });  }  return new Response("Not found\n", { status: 404 });}async function serveMcp(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {  if (request.method === "OPTIONS") return new Response(null, { status: 204 });  // Stateless: no sessions, so there is no standalone SSE stream to GET and nothing to DELETE.  if (request.method !== "POST") {    return Response.json(      { jsonrpc: "2.0", error: { code: -32000, message: "This server is stateless: send JSON-RPC requests with POST." }, id: null },      { status: 405, headers: { allow: "POST, OPTIONS" } },    );  }  // A fresh server and transport for every request. Nothing carries over, so any isolate can  // answer any request and there is no session to lose.  // Counting happens after the response is sent; a KV hiccup never fails a tool call.  const server = createServer({    onCall: (tool, ok) => ctx.waitUntil(countCall(env.USAGE, tool, ok).catch((err) => console.error("usage count failed", err))),  });  const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });  await server.connect(transport);  return transport.handleRequest(request);}// GET /stats?days=7: calls and errors per tool for the last few UTC days, newest first.async function stats(request: Request, env: Env): Promise<Response> {  const param = new URL(request.url).searchParams.get("days") ?? "7";  const days = Number(param);  if (!Number.isInteger(days) || days < 1 || days > MAX_DAYS) {    return Response.json({ error: `days must be a whole number from 1 to ${MAX_DAYS}` }, { status: 400 });  }  return Response.json({ days: await readUsage(env.USAGE, days) }, { headers: { "cache-control": "no-store" } });}function withCors(response: Response): Response {  const out = new Response(response.body, response);  for (const [k, v] of Object.entries(CORS)) out.headers.set(k, v);  return out;}