diff --git a/README.md b/README.md index 0105894..1732a12 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Run an MCP Server on Vercel +**Uses [Vercel MCP Adapter](https://github.com/vercel/vercel/mcp-adapter/tree/main)** + ## Usage Update `api/server.ts` with your tools, prompts, and resources following the [MCP TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk/tree/main?tab=readme-ov-file#server). @@ -8,9 +10,9 @@ Update `api/server.ts` with your tools, prompts, and resources following the [MC ## Notes for running on Vercel -- Requires a Redis attached to the project under `process.env.REDIS_URL` +- To use the SSE transport, requires a Redis attached to the project under `process.env.REDIS_URL` - Make sure you have [Fluid compute](https://vercel.com/docs/functions/fluid-compute) enabled for efficient execution -- After enabling Fluid compute, open `vercel.json` and adjust max duration to 800 if you using a Vercel Pro or Enterprise account +- After enabling Fluid compute, open `app/server.ts` and adjust `maxDuration` to 800 if you using a Vercel Pro or Enterprise account - [Deploy the MCP template](https://vercel.com/templates/other/model-context-protocol-mcp-with-vercel-functions) ## Sample Client diff --git a/api/server.ts b/api/server.ts index 3459a78..c4ee288 100644 --- a/api/server.ts +++ b/api/server.ts @@ -1,22 +1,12 @@ -import { z } from "zod"; -import { initializeMcpApiHandler } from "../lib/mcp-api-handler"; +const z = require("zod"); -const handler = initializeMcpApiHandler( - (server) => { - // Add more tools, resources, and prompts here - server.tool("echo", { message: z.string() }, async ({ message }) => ({ - content: [{ type: "text", text: `Tool echo: ${message}` }], - })); - }, - { - capabilities: { - tools: { - echo: { - description: "Echo a message", - }, - }, - }, - } -); +// TODO this is exported from `next` but is generic to all isomorphic web API signatures +const createMcpApiHandler = require("@vercel/mcp-adapter/next"); -export default handler; +const handler = createMcpApiHandler((server) => { + server.tool("echo", { message: z.string() }, async ({ message }) => ({ + content: [{ type: "text", text: `Tool echo: ${message}` }], + })); +}); + +export { handler as GET, handler as POST, handler as DELETE }; diff --git a/lib/mcp-api-handler.ts b/lib/mcp-api-handler.ts deleted file mode 100644 index 1fa6759..0000000 --- a/lib/mcp-api-handler.ts +++ /dev/null @@ -1,329 +0,0 @@ -import getRawBody from "raw-body"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "http"; -import { createClient } from "redis"; -import { Socket } from "net"; -import { Readable } from "stream"; -import { ServerOptions } from "@modelcontextprotocol/sdk/server/index.js"; -import vercelJson from "../vercel.json"; - -interface SerializedRequest { - requestId: string; - url: string; - method: string; - body: string; - headers: IncomingHttpHeaders; -} - -export function initializeMcpApiHandler( - initializeServer: (server: McpServer) => void, - serverOptions: ServerOptions = {} -) { - const maxDuration = - vercelJson?.functions?.["api/server.ts"]?.maxDuration || 800; - const redisUrl = process.env.REDIS_URL || process.env.KV_URL; - if (!redisUrl) { - throw new Error("REDIS_URL environment variable is not set"); - } - const redis = createClient({ - url: redisUrl, - }); - const redisPublisher = createClient({ - url: redisUrl, - }); - redis.on("error", (err) => { - console.error("Redis error", err); - }); - redisPublisher.on("error", (err) => { - console.error("Redis error", err); - }); - const redisPromise = Promise.all([redis.connect(), redisPublisher.connect()]); - - let servers: McpServer[] = []; - - let statelessServer: McpServer; - const statelessTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - }); - - return async function mcpApiHandler( - req: IncomingMessage, - res: ServerResponse - ) { - await redisPromise; - const url = new URL(req.url || "", "https://example.com"); - if (url.pathname === "/mcp") { - if (req.method === "GET") { - console.log("Received GET MCP request"); - res.writeHead(405).end( - JSON.stringify({ - jsonrpc: "2.0", - error: { - code: -32000, - message: "Method not allowed.", - }, - id: null, - }) - ); - return; - } - if (req.method === "DELETE") { - console.log("Received DELETE MCP request"); - res.writeHead(405).end( - JSON.stringify({ - jsonrpc: "2.0", - error: { - code: -32000, - message: "Method not allowed.", - }, - id: null, - }) - ); - return; - } - console.log("Got new MCP connection", req.url, req.method); - - if (!statelessServer) { - statelessServer = new McpServer( - { - name: "mcp-typescript server on vercel", - version: "0.1.0", - }, - serverOptions - ); - - initializeServer(statelessServer); - await statelessServer.connect(statelessTransport); - } - await statelessTransport.handleRequest(req, res); - } else if (url.pathname === "/sse") { - console.log("Got new SSE connection"); - - const transport = new SSEServerTransport("/message", res); - const sessionId = transport.sessionId; - const server = new McpServer( - { - name: "mcp-typescript server on vercel", - version: "0.1.0", - }, - serverOptions - ); - initializeServer(server); - - servers.push(server); - - server.server.onclose = () => { - console.log("SSE connection closed"); - servers = servers.filter((s) => s !== server); - }; - - let logs: { - type: "log" | "error"; - messages: string[]; - }[] = []; - // This ensures that we logs in the context of the right invocation since the subscriber - // is not itself invoked in request context. - function logInContext(severity: "log" | "error", ...messages: string[]) { - logs.push({ - type: severity, - messages, - }); - } - - // Handles messages originally received via /message - const handleMessage = async (message: string) => { - console.log("Received message from Redis", message); - logInContext("log", "Received message from Redis", message); - const request = JSON.parse(message) as SerializedRequest; - - // Make in IncomingMessage object because that is what the SDK expects. - const req = createFakeIncomingMessage({ - method: request.method, - url: request.url, - headers: request.headers, - body: request.body, - }); - const syntheticRes = new ServerResponse(req); - let status = 100; - let body = ""; - syntheticRes.writeHead = (statusCode: number) => { - status = statusCode; - return syntheticRes; - }; - syntheticRes.end = (b: unknown) => { - body = b as string; - return syntheticRes; - }; - await transport.handlePostMessage(req, syntheticRes); - - await redisPublisher.publish( - `responses:${sessionId}:${request.requestId}`, - JSON.stringify({ - status, - body, - }) - ); - - if (status >= 200 && status < 300) { - logInContext( - "log", - `Request ${sessionId}:${request.requestId} succeeded: ${body}` - ); - } else { - logInContext( - "error", - `Message for ${sessionId}:${request.requestId} failed with status ${status}: ${body}` - ); - } - }; - - const interval = setInterval(() => { - for (const log of logs) { - console[log.type].call(console, ...log.messages); - } - logs = []; - }, 100); - - await redis.subscribe(`requests:${sessionId}`, handleMessage); - console.log(`Subscribed to requests:${sessionId}`); - - let timeout: NodeJS.Timeout; - let resolveTimeout: (value: unknown) => void; - const waitPromise = new Promise((resolve) => { - resolveTimeout = resolve; - timeout = setTimeout(() => { - resolve("max duration reached"); - }, (maxDuration - 5) * 1000); - }); - - async function cleanup() { - clearTimeout(timeout); - clearInterval(interval); - await redis.unsubscribe(`requests:${sessionId}`, handleMessage); - console.log("Done"); - res.statusCode = 200; - res.end(); - } - req.on("close", () => resolveTimeout("client hang up")); - - await server.connect(transport); - const closeReason = await waitPromise; - console.log(closeReason); - await cleanup(); - } else if (url.pathname === "/message") { - console.log("Received message"); - - const body = await getRawBody(req, { - length: req.headers["content-length"], - encoding: "utf-8", - }); - - const sessionId = url.searchParams.get("sessionId") || ""; - if (!sessionId) { - res.statusCode = 400; - res.end("No sessionId provided"); - return; - } - const requestId = crypto.randomUUID(); - const serializedRequest: SerializedRequest = { - requestId, - url: req.url || "", - method: req.method || "", - body: body, - headers: req.headers, - }; - - // Handles responses from the /sse endpoint. - await redis.subscribe( - `responses:${sessionId}:${requestId}`, - (message) => { - clearTimeout(timeout); - const response = JSON.parse(message) as { - status: number; - body: string; - }; - res.statusCode = response.status; - res.end(response.body); - } - ); - - // Queue the request in Redis so that a subscriber can pick it up. - // One queue per session. - await redisPublisher.publish( - `requests:${sessionId}`, - JSON.stringify(serializedRequest) - ); - console.log(`Published requests:${sessionId}`, serializedRequest); - - let timeout = setTimeout(async () => { - await redis.unsubscribe(`responses:${sessionId}:${requestId}`); - res.statusCode = 408; - res.end("Request timed out"); - }, 10 * 1000); - - res.on("close", async () => { - clearTimeout(timeout); - await redis.unsubscribe(`responses:${sessionId}:${requestId}`); - }); - } else { - res.statusCode = 404; - res.end("Not found"); - } - }; -} - -// Define the options interface -interface FakeIncomingMessageOptions { - method?: string; - url?: string; - headers?: IncomingHttpHeaders; - body?: string | Buffer | Record | null; - socket?: Socket; -} - -// Create a fake IncomingMessage -function createFakeIncomingMessage( - options: FakeIncomingMessageOptions = {} -): IncomingMessage { - const { - method = "GET", - url = "/", - headers = {}, - body = null, - socket = new Socket(), - } = options; - - // Create a readable stream that will be used as the base for IncomingMessage - const readable = new Readable(); - readable._read = (): void => {}; // Required implementation - - // Add the body content if provided - if (body) { - if (typeof body === "string") { - readable.push(body); - } else if (Buffer.isBuffer(body)) { - readable.push(body); - } else { - readable.push(JSON.stringify(body)); - } - readable.push(null); // Signal the end of the stream - } - - // Create the IncomingMessage instance - const req = new IncomingMessage(socket); - - // Set the properties - req.method = method; - req.url = url; - req.headers = headers; - - // Copy over the stream methods - req.push = readable.push.bind(readable); - req.read = readable.read.bind(readable); - req.on = readable.on.bind(readable); - req.pipe = readable.pipe.bind(readable); - - return req; -} diff --git a/package.json b/package.json index 4d6d926..11e16f9 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,7 @@ "license": "ISC", "packageManager": "pnpm@8.15.7+sha512.c85cd21b6da10332156b1ca2aa79c0a61ee7ad2eb0453b88ab299289e9e8ca93e6091232b25c07cbf61f6df77128d9c849e5c9ac6e44854dbd211c49f3a67adc", "dependencies": { - "@modelcontextprotocol/sdk": "^1.10.2", - "content-type": "^1.0.5", - "raw-body": "^3.0.0", - "redis": "^4.7.0", + "@vercel/mcp-adapter": "^0.2.1", "zod": "^3.24.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8b2dbe..23aa495 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,18 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: - '@modelcontextprotocol/sdk': - specifier: ^1.10.2 - version: 1.10.2 - content-type: - specifier: ^1.0.5 - version: 1.0.5 - raw-body: - specifier: ^3.0.0 - version: 3.0.0 - redis: - specifier: ^4.7.0 - version: 4.7.0 + '@vercel/mcp-adapter': + specifier: ^0.2.1 + version: 0.2.1(@modelcontextprotocol/sdk@1.10.2)(next@15.3.2)(redis@4.7.0) zod: specifier: ^3.24.2 version: 3.24.2 @@ -28,6 +19,202 @@ devDependencies: packages: + /@emnapi/runtime@1.4.3: + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + requiresBuild: true + dependencies: + tslib: 2.8.1 + dev: false + optional: true + + /@img/sharp-darwin-arm64@0.34.1: + resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.1.0 + dev: false + optional: true + + /@img/sharp-darwin-x64@0.34.1: + resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.1.0 + dev: false + optional: true + + /@img/sharp-libvips-darwin-arm64@1.1.0: + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-darwin-x64@1.1.0: + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm64@1.1.0: + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm@1.1.0: + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-ppc64@1.1.0: + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-s390x@1.1.0: + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-x64@1.1.0: + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.1.0: + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.1.0: + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-linux-arm64@0.34.1: + resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.1.0 + dev: false + optional: true + + /@img/sharp-linux-arm@0.34.1: + resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.1.0 + dev: false + optional: true + + /@img/sharp-linux-s390x@0.34.1: + resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.1.0 + dev: false + optional: true + + /@img/sharp-linux-x64@0.34.1: + resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.1.0 + dev: false + optional: true + + /@img/sharp-linuxmusl-arm64@0.34.1: + resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + dev: false + optional: true + + /@img/sharp-linuxmusl-x64@0.34.1: + resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + dev: false + optional: true + + /@img/sharp-wasm32@0.34.1: + resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 1.4.3 + dev: false + optional: true + + /@img/sharp-win32-ia32@0.34.1: + resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-win32-x64@0.34.1: + resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@modelcontextprotocol/sdk@1.10.2: resolution: {integrity: sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==} engines: {node: '>=18'} @@ -46,6 +233,82 @@ packages: - supports-color dev: false + /@next/env@15.3.2: + resolution: {integrity: sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==} + dev: false + + /@next/swc-darwin-arm64@15.3.2: + resolution: {integrity: sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-x64@15.3.2: + resolution: {integrity: sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-gnu@15.3.2: + resolution: {integrity: sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-musl@15.3.2: + resolution: {integrity: sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-gnu@15.3.2: + resolution: {integrity: sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-musl@15.3.2: + resolution: {integrity: sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-arm64-msvc@15.3.2: + resolution: {integrity: sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-x64-msvc@15.3.2: + resolution: {integrity: sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@redis/bloom@1.2.0(@redis/client@1.6.0): resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} peerDependencies: @@ -95,12 +358,34 @@ packages: '@redis/client': 1.6.0 dev: false + /@swc/counter@0.1.3: + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + dev: false + + /@swc/helpers@0.5.15: + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + dependencies: + tslib: 2.8.1 + dev: false + /@types/node@22.13.10: resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} dependencies: undici-types: 6.20.0 dev: true + /@vercel/mcp-adapter@0.2.1(@modelcontextprotocol/sdk@1.10.2)(next@15.3.2)(redis@4.7.0): + resolution: {integrity: sha512-8hmaoiDbZ1UGpXbiKwKBeoffLuf4SQzcJgR4CxVSgMfhSDY3PChxuKSnQN/wHpek4dXlrx6De89mRRrSfa4ovQ==} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.10.2 + next: '>=13.0.0' + redis: ^4.6.0 + dependencies: + '@modelcontextprotocol/sdk': 1.10.2 + next: 15.3.2(react-dom@19.1.0)(react@19.1.0) + redis: 4.7.0 + dev: false + /accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -126,6 +411,13 @@ packages: - supports-color dev: false + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -147,11 +439,53 @@ packages: get-intrinsic: 1.3.0 dev: false + /caniuse-lite@1.0.30001717: + resolution: {integrity: sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==} + dev: false + + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false + /cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} dev: false + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + requiresBuild: true + dependencies: + color-name: 1.1.4 + dev: false + optional: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + requiresBuild: true + dev: false + optional: true + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + requiresBuild: true + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + optional: true + + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + requiresBuild: true + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + optional: true + /content-disposition@1.0.0: resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} engines: {node: '>= 0.6'} @@ -225,6 +559,13 @@ packages: engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dev: false + /detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + requiresBuild: true + dev: false + optional: true + /dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -443,6 +784,12 @@ packages: engines: {node: '>= 0.10'} dev: false + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + requiresBuild: true + dev: false + optional: true + /is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} dev: false @@ -503,11 +850,62 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false + /nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} dev: false + /next@15.3.2(react-dom@19.1.0)(react@19.1.0): + resolution: {integrity: sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + dependencies: + '@next/env': 15.3.2 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001717 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.3.2 + '@next/swc-darwin-x64': 15.3.2 + '@next/swc-linux-arm64-gnu': 15.3.2 + '@next/swc-linux-arm64-musl': 15.3.2 + '@next/swc-linux-x64-gnu': 15.3.2 + '@next/swc-linux-x64-musl': 15.3.2 + '@next/swc-win32-arm64-msvc': 15.3.2 + '@next/swc-win32-x64-msvc': 15.3.2 + sharp: 0.34.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -546,11 +944,24 @@ packages: engines: {node: '>=16'} dev: false + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: false + /pkce-challenge@5.0.0: resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} engines: {node: '>=16.20.0'} dev: false + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + dev: false + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -588,6 +999,20 @@ packages: unpipe: 1.0.0 dev: false + /react-dom@19.1.0(react@19.1.0): + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + dev: false + + /react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + dev: false + /redis@4.7.0: resolution: {integrity: sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==} dependencies: @@ -616,6 +1041,18 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false + /scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + dev: false + + /semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true + dev: false + optional: true + /send@1.1.0: resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} engines: {node: '>= 18'} @@ -652,6 +1089,38 @@ packages: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false + /sharp@0.34.1: + resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.1 + '@img/sharp-darwin-x64': 0.34.1 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.1 + '@img/sharp-linux-arm64': 0.34.1 + '@img/sharp-linux-s390x': 0.34.1 + '@img/sharp-linux-x64': 0.34.1 + '@img/sharp-linuxmusl-arm64': 0.34.1 + '@img/sharp-linuxmusl-x64': 0.34.1 + '@img/sharp-wasm32': 0.34.1 + '@img/sharp-win32-ia32': 0.34.1 + '@img/sharp-win32-x64': 0.34.1 + dev: false + optional: true + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -704,16 +1173,55 @@ packages: side-channel-weakmap: 1.0.2 dev: false + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + requiresBuild: true + dependencies: + is-arrayish: 0.3.2 + dev: false + optional: true + + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + dev: false + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} dev: false + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + + /styled-jsx@5.1.6(react@19.1.0): + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + client-only: 0.0.1 + react: 19.1.0 + dev: false + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} dev: false + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + dev: false + /type-is@2.0.0: resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} engines: {node: '>= 0.6'}