feat: add MCP endpoint for collections

Add Model Context Protocol (MCP) server endpoint for collections,
allowing users to connect Claude Desktop, Cursor, and other MCP
clients to use tools from their collections.

- POST /api/collections/[id]/mcp - JSON-RPC endpoint for MCP
- GET /api/collections/[id]/mcp - Server info for discovery
- Tool name conversion (e.g., @tpmjs/hello → tpmjs-hello--helloWorldTool)
- Handlers for initialize, tools/list, tools/call methods
- Executes tools via existing sandbox executor

Only public collections are accessible (no auth required).
This commit is contained in:
Ajax Davis 2026-01-02 04:25:33 +10:00
parent abd9682f5e
commit 828801f463
6 changed files with 585 additions and 0 deletions

View file

@ -16,6 +16,7 @@
},
"dependencies": {
"@ai-sdk/openai": "3.0.1",
"@modelcontextprotocol/sdk": "^1.25.1",
"@prisma/client": "^6.19.0",
"@tpmjs/db": "workspace:*",
"@tpmjs/env": "workspace:*",

View file

@ -0,0 +1,106 @@
import { prisma } from '@tpmjs/db';
import { type NextRequest, NextResponse } from 'next/server';
import { handleInitialize, handleToolsCall, handleToolsList } from '~/lib/mcp/handlers';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 300;
interface RouteContext {
params: Promise<{ id: string }>;
}
interface JsonRpcRequest {
jsonrpc: string;
method: string;
params?: unknown;
id?: string | number;
}
/**
* POST /api/collections/[id]/mcp
* MCP JSON-RPC endpoint for tool execution
*/
export async function POST(request: NextRequest, context: RouteContext): Promise<NextResponse> {
const { id } = await context.params;
// Validate collection exists and is public
const collection = await prisma.collection.findUnique({
where: { id, isPublic: true },
select: { id: true, name: true },
});
if (!collection) {
return NextResponse.json(
{ jsonrpc: '2.0', error: { code: -32001, message: 'Collection not found' }, id: null },
{ status: 404 }
);
}
let body: JsonRpcRequest;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ jsonrpc: '2.0', error: { code: -32700, message: 'Parse error' }, id: null },
{ status: 400 }
);
}
const requestId = body.id ?? null;
switch (body.method) {
case 'initialize':
return NextResponse.json(handleInitialize(collection.name, requestId));
case 'tools/list':
return NextResponse.json(await handleToolsList(id, requestId));
case 'tools/call':
return NextResponse.json(
await handleToolsCall(
id,
body.params as { name: string; arguments?: Record<string, unknown> },
requestId
)
);
case 'notifications/initialized':
case 'ping':
// Acknowledge notifications
return NextResponse.json({ jsonrpc: '2.0', id: requestId, result: {} });
default:
return NextResponse.json({
jsonrpc: '2.0',
id: requestId,
error: { code: -32601, message: `Method not found: ${body.method}` },
});
}
}
/**
* GET /api/collections/[id]/mcp
* Returns server info (optional but helpful for discovery)
*/
export async function GET(_request: NextRequest, context: RouteContext): Promise<NextResponse> {
const { id } = await context.params;
const collection = await prisma.collection.findUnique({
where: { id, isPublic: true },
select: { id: true, name: true, description: true },
});
if (!collection) {
return NextResponse.json({ error: 'Collection not found' }, { status: 404 });
}
return NextResponse.json({
name: `TPMJS: ${collection.name}`,
description: collection.description,
protocol: 'mcp',
transport: 'streamable-http',
endpoint: `/api/collections/${id}/mcp`,
});
}

View file

@ -0,0 +1,135 @@
import { prisma } from '@tpmjs/db';
import { executePackage } from '@tpmjs/package-executor';
import { convertToMcpTool, parseToolName } from './tool-converter';
type JsonRpcId = string | number | null;
interface JsonRpcResponse {
jsonrpc: '2.0';
id: JsonRpcId;
result?: unknown;
error?: { code: number; message: string };
}
/**
* Handle MCP initialize request
*/
export function handleInitialize(collectionName: string, requestId: JsonRpcId): JsonRpcResponse {
return {
jsonrpc: '2.0',
id: requestId,
result: {
protocolVersion: '2024-11-05',
serverInfo: {
name: `TPMJS: ${collectionName}`,
version: '1.0.0',
},
capabilities: { tools: {} },
},
};
}
/**
* Handle MCP tools/list request
*/
export async function handleToolsList(
collectionId: string,
requestId: JsonRpcId
): Promise<JsonRpcResponse> {
const collection = await prisma.collection.findUnique({
where: { id: collectionId },
include: {
tools: {
include: { tool: { include: { package: true } } },
orderBy: { position: 'asc' },
},
},
});
const tools = collection?.tools.map((ct) => convertToMcpTool(ct.tool)) ?? [];
return {
jsonrpc: '2.0',
id: requestId,
result: { tools },
};
}
interface ToolsCallParams {
name: string;
arguments?: Record<string, unknown>;
}
/**
* Handle MCP tools/call request
*/
export async function handleToolsCall(
collectionId: string,
params: ToolsCallParams,
requestId: JsonRpcId
): Promise<JsonRpcResponse> {
const parsed = parseToolName(params.name);
if (!parsed) {
return {
jsonrpc: '2.0',
id: requestId,
error: { code: -32602, message: `Invalid tool name: ${params.name}` },
};
}
// Verify tool exists in collection
const collection = await prisma.collection.findUnique({
where: { id: collectionId },
include: {
tools: {
include: { tool: { include: { package: true } } },
},
},
});
const collectionTool = collection?.tools.find(
(ct) =>
ct.tool.package.npmPackageName === parsed.packageName && ct.tool.name === parsed.toolName
);
if (!collectionTool) {
return {
jsonrpc: '2.0',
id: requestId,
error: { code: -32602, message: `Tool not found in collection: ${params.name}` },
};
}
// Execute via sandbox
const result = await executePackage(parsed.packageName, parsed.toolName, params.arguments ?? {}, {
timeout: 30000,
});
if (!result.success) {
return {
jsonrpc: '2.0',
id: requestId,
result: {
content: [{ type: 'text', text: `Error: ${result.error}` }],
isError: true,
},
};
}
return {
jsonrpc: '2.0',
id: requestId,
result: {
content: [
{
type: 'text',
text:
typeof result.output === 'string'
? result.output
: JSON.stringify(result.output, null, 2),
},
],
},
};
}

View file

@ -0,0 +1,4 @@
export { convertToMcpTool, parseToolName, sanitizeMcpName } from './tool-converter';
export type { McpToolDefinition } from './tool-converter';
export { handleInitialize, handleToolsCall, handleToolsList } from './handlers';

View file

@ -0,0 +1,52 @@
import type { Package, Tool } from '@prisma/client';
export interface McpToolDefinition {
name: string;
description: string;
inputSchema: Record<string, unknown>;
}
/**
* Sanitize package name and tool name into a valid MCP tool name.
* MCP tool names must match ^[a-zA-Z0-9_-]+
*
* Example: @tpmjs/hello + helloWorldTool tpmjs-hello--helloWorldTool
*/
export function sanitizeMcpName(packageName: string, toolName: string): string {
const sanitizedPkg = packageName.replace(/^@/, '').replace(/\//g, '-');
return `${sanitizedPkg}--${toolName}`;
}
/**
* Convert a TPMJS Tool to an MCP tool definition.
*/
export function convertToMcpTool(tool: Tool & { package: Package }): McpToolDefinition {
return {
name: sanitizeMcpName(tool.package.npmPackageName, tool.name),
description: tool.description,
inputSchema: (tool.inputSchema as Record<string, unknown>) ?? {
type: 'object',
properties: {},
},
};
}
/**
* Parse an MCP tool name back into package name and tool name.
* Returns null if the name doesn't match the expected format.
*
* Example: tpmjs-hello--helloWorldTool { packageName: "@tpmjs/hello", toolName: "helloWorldTool" }
*/
export function parseToolName(mcpName: string): { packageName: string; toolName: string } | null {
const match = mcpName.match(/^(.+)--(.+)$/);
if (!match || !match[1] || !match[2]) return null;
const pkg = match[1];
const toolName = match[2];
// Reconstruct @scope/name format if it looks scoped
// tpmjs-hello → @tpmjs/hello (first dash becomes @scope/)
const packageName = pkg.includes('-') ? `@${pkg.replace('-', '/')}` : pkg;
return { packageName, toolName };
}

287
pnpm-lock.yaml generated
View file

@ -226,6 +226,9 @@ importers:
'@ai-sdk/openai':
specifier: 3.0.1
version: 3.0.1(zod@4.1.13)
'@modelcontextprotocol/sdk':
specifier: ^1.25.1
version: 1.25.1(hono@4.11.3)(zod@4.1.13)
'@prisma/client':
specifier: ^6.19.0
version: 6.19.0(prisma@6.19.0(typescript@5.9.3))(typescript@5.9.3)
@ -4802,6 +4805,12 @@ packages:
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'}
'@hono/node-server@1.19.7':
resolution: {integrity: sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@ -5076,6 +5085,16 @@ packages:
'@mixmark-io/domino@2.2.0':
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
'@modelcontextprotocol/sdk@1.25.1':
resolution: {integrity: sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==}
engines: {node: '>=18'}
peerDependencies:
'@cfworker/json-schema': ^4.1.1
zod: ^3.25 || ^4.0
peerDependenciesMeta:
'@cfworker/json-schema':
optional: true
'@mongodb-js/saslprep@1.4.4':
resolution: {integrity: sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==}
@ -6198,6 +6217,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
acorn-jsx-walk@2.0.0:
resolution: {integrity: sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==}
@ -6547,6 +6570,10 @@ packages:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
body-parser@2.2.1:
resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==}
engines: {node: '>=18'}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@ -6783,6 +6810,10 @@ packages:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
content-disposition@1.0.1:
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
engines: {node: '>=18'}
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
@ -6793,6 +6824,10 @@ packages:
cookie-signature@1.0.7:
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
@ -7598,6 +7633,10 @@ packages:
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
engines: {node: '>=18.0.0'}
eventsource@3.0.7:
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
engines: {node: '>=18.0.0'}
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
@ -7606,10 +7645,20 @@ packages:
resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
engines: {node: '>=12.0.0'}
express-rate-limit@7.5.1:
resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==}
engines: {node: '>= 16'}
peerDependencies:
express: '>= 4.11'
express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
engines: {node: '>= 0.10.0'}
express@5.2.1:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@ -7687,6 +7736,10 @@ packages:
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
engines: {node: '>= 0.8'}
finalhandler@2.1.1:
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
engines: {node: '>= 18.0.0'}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@ -7771,6 +7824,10 @@ packages:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@ -8005,6 +8062,10 @@ packages:
highlightjs-vue@1.0.0:
resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==}
hono@4.11.3:
resolution: {integrity: sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==}
engines: {node: '>=16.9.0'}
html-encoding-sniffer@3.0.0:
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
engines: {node: '>=12'}
@ -8255,6 +8316,9 @@ packages:
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@ -8414,6 +8478,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
@ -8722,6 +8789,10 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
memjs@1.3.2:
resolution: {integrity: sha512-qUEg2g8vxPe+zPn09KidjIStHPtoBO8Cttm8bgJFWWabbsjQ9Av9Ky+6UcvKx6ue0LLb/LEhtcyQpRyKfzeXcg==}
engines: {node: '>=0.10.0'}
@ -8739,6 +8810,10 @@ packages:
merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
merge-descriptors@2.0.0:
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
engines: {node: '>=18'}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@ -8874,10 +8949,18 @@ packages:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime-types@3.0.2:
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
engines: {node: '>=18'}
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
@ -9038,6 +9121,10 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
@ -9296,6 +9383,9 @@ packages:
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
path-to-regexp@8.3.0:
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@ -9373,6 +9463,10 @@ packages:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pkce-challenge@5.0.1:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
@ -9565,6 +9659,10 @@ packages:
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
engines: {node: '>= 0.8'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
rc9@2.1.2:
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
@ -9793,6 +9891,10 @@ packages:
roughjs@4.6.6:
resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
rrweb-cssom@0.8.0:
resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
@ -9872,10 +9974,18 @@ packages:
resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==}
engines: {node: '>= 0.8.0'}
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
serve-static@1.16.2:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
@ -10423,6 +10533,10 @@ packages:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@ -11973,6 +12087,10 @@ snapshots:
'@fastify/busboy@2.1.1': {}
'@hono/node-server@1.19.7(hono@4.11.3)':
dependencies:
hono: 4.11.3
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.7':
@ -12212,6 +12330,28 @@ snapshots:
'@mixmark-io/domino@2.2.0': {}
'@modelcontextprotocol/sdk@1.25.1(hono@4.11.3)(zod@4.1.13)':
dependencies:
'@hono/node-server': 1.19.7(hono@4.11.3)
ajv: 8.17.1
ajv-formats: 3.0.1(ajv@8.17.1)
content-type: 1.0.5
cors: 2.8.5
cross-spawn: 7.0.6
eventsource: 3.0.7
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 7.5.1(express@5.2.1)
jose: 6.1.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
raw-body: 3.0.2
zod: 4.1.13
zod-to-json-schema: 3.25.0(zod@4.1.13)
transitivePeerDependencies:
- hono
- supports-color
'@mongodb-js/saslprep@1.4.4':
dependencies:
sparse-bitfield: 3.0.3
@ -13383,6 +13523,11 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
negotiator: 1.0.0
acorn-jsx-walk@2.0.0: {}
acorn-jsx@5.3.2(acorn@8.15.0):
@ -13760,6 +13905,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
body-parser@2.2.1:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 4.4.3
http-errors: 2.0.1
iconv-lite: 0.7.0
on-finished: 2.4.1
qs: 6.14.0
raw-body: 3.0.2
type-is: 2.0.1
transitivePeerDependencies:
- supports-color
boolbase@1.0.0: {}
brace-expansion@1.1.12:
@ -13996,12 +14155,16 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
content-disposition@1.0.1: {}
content-type@1.0.5: {}
convert-source-map@2.0.0: {}
cookie-signature@1.0.7: {}
cookie-signature@1.2.2: {}
cookie@0.7.2: {}
cookie@1.0.2: {}
@ -15198,10 +15361,18 @@ snapshots:
eventsource-parser@3.0.6: {}
eventsource@3.0.7:
dependencies:
eventsource-parser: 3.0.6
expand-template@2.0.3: {}
expect-type@1.2.2: {}
express-rate-limit@7.5.1(express@5.2.1):
dependencies:
express: 5.2.1
express@4.22.1:
dependencies:
accepts: 1.3.8
@ -15238,6 +15409,39 @@ snapshots:
transitivePeerDependencies:
- supports-color
express@5.2.1:
dependencies:
accepts: 2.0.0
body-parser: 2.2.1
content-disposition: 1.0.1
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
debug: 4.4.3
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 2.1.1
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
mime-types: 3.0.2
on-finished: 2.4.1
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
qs: 6.14.0
range-parser: 1.2.1
router: 2.2.0
send: 1.2.1
serve-static: 2.2.1
statuses: 2.0.2
type-is: 2.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
exsolve@1.0.8: {}
extend@3.0.2: {}
@ -15318,6 +15522,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
finalhandler@2.1.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
@ -15397,6 +15612,8 @@ snapshots:
fresh@0.5.2: {}
fresh@2.0.0: {}
fs-constants@1.0.0: {}
fs-extra@7.0.1:
@ -15723,6 +15940,8 @@ snapshots:
highlightjs-vue@1.0.0: {}
hono@4.11.3: {}
html-encoding-sniffer@3.0.0:
dependencies:
whatwg-encoding: 2.0.0
@ -15985,6 +16204,8 @@ snapshots:
is-potential-custom-element-name@1.0.1: {}
is-promise@4.0.0: {}
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
@ -16174,6 +16395,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
json-schema-typed@8.0.2: {}
json-schema@0.4.0: {}
json-stable-stringify-without-jsonify@1.0.1: {}
@ -16570,6 +16793,8 @@ snapshots:
media-typer@0.3.0: {}
media-typer@1.1.0: {}
memjs@1.3.2: {}
memoize@10.2.0:
@ -16584,6 +16809,8 @@ snapshots:
merge-descriptors@1.0.3: {}
merge-descriptors@2.0.0: {}
merge2@1.4.1: {}
mermaid@11.12.2:
@ -16851,10 +17078,16 @@ snapshots:
mime-db@1.52.0: {}
mime-db@1.54.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
mime-types@3.0.2:
dependencies:
mime-db: 1.54.0
mime@1.6.0: {}
mimic-function@5.0.1: {}
@ -17022,6 +17255,8 @@ snapshots:
negotiator@0.6.3: {}
negotiator@1.0.0: {}
next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
react: 19.2.0
@ -17316,6 +17551,8 @@ snapshots:
path-to-regexp@6.3.0: {}
path-to-regexp@8.3.0: {}
path-type@4.0.0: {}
pathe@1.1.2: {}
@ -17375,6 +17612,8 @@ snapshots:
pirates@4.0.7: {}
pkce-challenge@5.0.1: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
@ -17572,6 +17811,13 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
iconv-lite: 0.7.0
unpipe: 1.0.0
rc9@2.1.2:
dependencies:
defu: 6.1.4
@ -17924,6 +18170,16 @@ snapshots:
points-on-curve: 0.2.0
points-on-path: 0.2.1
router@2.2.0:
dependencies:
debug: 4.4.3
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
path-to-regexp: 8.3.0
transitivePeerDependencies:
- supports-color
rrweb-cssom@0.8.0: {}
rss-parser@3.13.0:
@ -18033,6 +18289,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
send@1.2.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 2.0.0
http-errors: 2.0.1
mime-types: 3.0.2
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
serve-static@1.16.2:
dependencies:
encodeurl: 2.0.0
@ -18042,6 +18314,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 1.2.1
transitivePeerDependencies:
- supports-color
set-cookie-parser@2.7.2: {}
set-function-length@1.2.2:
@ -18698,6 +18979,12 @@ snapshots:
media-typer: 0.3.0
mime-types: 2.1.35
type-is@2.0.1:
dependencies:
content-type: 1.0.5
media-typer: 1.1.0
mime-types: 3.0.2
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4