feat(api): MCP server endpoint — expose AI tools to external clients

Mount an MCP (Model Context Protocol) server at /api/v1/mcp in the
unified Hono API. External clients like Claude Desktop, Cursor, and
VS Code Copilot can discover and call all 29 Mana tools via the
standard MCP protocol.

Architecture:
- WebStandardStreamableHTTPServerTransport for Bun/Hono compatibility
- AI_TOOL_CATALOG → MCP tool definitions with JSON Schema (via Zod)
- Stateful sessions with Mcp-Session-Id header
- Auth via existing authMiddleware (JWT or API key)

Phase 1 scope: tools/list returns all 29 tools with schemas,
tools/call acknowledges with descriptive messages. Phase 2 will add
actual DB reads/writes via sync_changes.

Usage:
  Claude Desktop config:
  {"mcpServers": {"mana": {"url": "http://localhost:3060/api/v1/mcp"}}}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-16 13:37:52 +02:00
parent 827b252090
commit db4dd437bd
6 changed files with 508 additions and 5 deletions

View file

@ -15,10 +15,12 @@
"dependencies": {
"@ai-sdk/openai-compatible": "^2.0.41",
"@mana/media-client": "workspace:*",
"@mana/shared-ai": "workspace:*",
"@mana/shared-hono": "workspace:*",
"@mana/shared-rss": "workspace:*",
"@mana/shared-storage": "workspace:*",
"@mana/shared-types": "workspace:^",
"@modelcontextprotocol/sdk": "^1.29.0",
"ai": "^6.0.154",
"drizzle-orm": "^0.38.0",
"hono": "^4.7.0",

View file

@ -15,6 +15,9 @@ import {
rateLimitMiddleware,
} from '@mana/shared-hono';
// MCP server
import { handleMcpRequest } from './mcp/server';
// Module routes
import { calendarRoutes } from './modules/calendar/routes';
import { contactsRoutes } from './modules/contacts/routes';
@ -48,6 +51,10 @@ app.route('/health', healthRoute('mana-api'));
app.use('/api/*', rateLimitMiddleware({ max: 200, windowMs: 60_000 }));
app.use('/api/*', authMiddleware());
// ─── MCP Endpoint ──────────────────────────────────────────
// Streamable HTTP transport: POST (messages), GET (SSE stream), DELETE (close)
app.all('/api/v1/mcp', (c) => handleMcpRequest(c.req.raw));
// ─── Module Routes ──────────────────────────────────────────
app.route('/api/v1/calendar', calendarRoutes);
app.route('/api/v1/contacts', contactsRoutes);

View file

@ -0,0 +1,67 @@
/**
* MCP Tool Executor handles tools/call requests by routing to module
* handlers or returning status messages.
*
* Phase 1: Read-only tools query the sync database directly.
* Phase 2: Write tools will insert into sync_changes (like mana-ai does).
*/
import { AI_TOOL_CATALOG_BY_NAME } from '@mana/shared-ai';
export interface McpToolResult {
[key: string]: unknown;
content: Array<{ type: 'text'; text: string }>;
isError?: boolean;
}
/**
* Execute an MCP tool call. Returns MCP-formatted result content.
*
* Phase 1 scope:
* - All tools are listed (via tools/list from AI_TOOL_CATALOG)
* - Write tools return a "coming soon" message
* - Read tools are planned for Phase 2 (requires sync DB queries)
*/
export async function executeMcpTool(
toolName: string,
args: Record<string, unknown>,
_userId: string
): Promise<McpToolResult> {
const schema = AI_TOOL_CATALOG_BY_NAME.get(toolName);
if (!schema) {
return {
content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],
isError: true,
};
}
// Phase 1: all tools return a descriptive message about what they will do.
// Phase 2 will implement actual DB reads and sync_changes writes.
if (schema.defaultPolicy === 'auto') {
return {
content: [
{
type: 'text',
text:
`[Mana MCP] Read-tool "${toolName}" (${schema.module}) acknowledged.\n` +
`Args: ${JSON.stringify(args)}\n` +
`Note: Server-side execution coming in Phase 2. ` +
`This tool will query the sync database for user data.`,
},
],
};
}
return {
content: [
{
type: 'text',
text:
`[Mana MCP] Write-tool "${toolName}" (${schema.module}) acknowledged.\n` +
`Args: ${JSON.stringify(args)}\n` +
`Note: Server-side execution coming in Phase 2. ` +
`This tool will write to the sync database and appear on your devices.`,
},
],
};
}

113
apps/api/src/mcp/server.ts Normal file
View file

@ -0,0 +1,113 @@
/**
* Mana MCP Server exposes AI_TOOL_CATALOG as MCP tools for external
* clients (Claude Desktop, Cursor, VS Code Copilot, etc.).
*
* Uses the Streamable HTTP transport (WebStandard variant) which works
* natively with Hono/Bun. Clients connect via:
*
* POST /api/v1/mcp send JSON-RPC messages
* GET /api/v1/mcp open SSE stream for responses
* DELETE /api/v1/mcp close session
*
* Auth: inherits the existing authMiddleware() from the parent Hono app,
* so every MCP request carries a valid JWT or API key.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
import { AI_TOOL_CATALOG } from '@mana/shared-ai';
import { executeMcpTool } from './executor';
import { z } from 'zod';
/** Convert ToolSchema parameters → Zod shape for McpServer.tool(). */
function toZodShape(
params: (typeof AI_TOOL_CATALOG)[number]['parameters']
): Record<string, z.ZodTypeAny> {
const shape: Record<string, z.ZodTypeAny> = {};
for (const p of params) {
let field: z.ZodTypeAny;
if (p.type === 'number') field = z.number().describe(p.description);
else if (p.type === 'boolean') field = z.boolean().describe(p.description);
else if (p.enum) field = z.enum(p.enum as [string, ...string[]]).describe(p.description);
else field = z.string().describe(p.description);
if (!p.required) field = field.optional();
shape[p.name] = field;
}
return shape;
}
/**
* Create a new McpServer instance with all Mana tools registered.
*/
function createMcpServer(): McpServer {
const server = new McpServer({ name: 'mana', version: '1.0.0' }, { capabilities: { tools: {} } });
// Register all 29 tools from the AI Tool Catalog
for (const tool of AI_TOOL_CATALOG) {
const zodShape = toZodShape(tool.parameters);
const hasParams = Object.keys(zodShape).length > 0;
if (hasParams) {
server.tool(tool.name, tool.description, zodShape, async (args) => {
return executeMcpTool(tool.name, args, 'mcp-user');
});
} else {
server.tool(tool.name, tool.description, async () => {
return executeMcpTool(tool.name, {}, 'mcp-user');
});
}
}
return server;
}
/** Map of active sessions → their transport instances. */
const sessions = new Map<string, WebStandardStreamableHTTPServerTransport>();
/**
* Handle an incoming HTTP request on the MCP endpoint.
*
* Supports stateful sessions: the transport generates a session ID on
* initialization, and subsequent requests must carry it via the
* `Mcp-Session-Id` header.
*/
export async function handleMcpRequest(req: Request): Promise<Response> {
const sessionId = req.headers.get('mcp-session-id');
// Existing session — route to its transport
if (sessionId && sessions.has(sessionId)) {
const transport = sessions.get(sessionId)!;
return transport.handleRequest(req);
}
// New session (POST without session ID = initialization)
if (req.method === 'POST' && !sessionId) {
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
onsessioninitialized: (id) => {
sessions.set(id, transport);
},
onsessionclosed: (id) => {
sessions.delete(id);
},
});
const server = createMcpServer();
await server.connect(transport);
return transport.handleRequest(req);
}
// Invalid request
if (sessionId && !sessions.has(sessionId)) {
return new Response(JSON.stringify({ error: 'Session not found' }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
});
}
return new Response(JSON.stringify({ error: 'Bad request' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}

33
apps/api/src/mcp/tools.ts Normal file
View file

@ -0,0 +1,33 @@
/**
* MCP Tool Definitions transforms AI_TOOL_CATALOG into MCP-compatible
* tool listings with JSON Schema inputSchema.
*
* The catalog in @mana/shared-ai is the single source of truth. This
* module provides the MCP wire format for tools/list responses.
*/
import { AI_TOOL_CATALOG, type ToolSchema } from '@mana/shared-ai';
/** Convert ToolSchema parameters → JSON Schema for MCP inputSchema. */
function toJsonSchema(params: ToolSchema['parameters']): {
type: 'object';
properties: Record<string, Record<string, unknown>>;
required: string[];
} {
const properties: Record<string, Record<string, unknown>> = {};
const required: string[] = [];
for (const p of params) {
const prop: Record<string, unknown> = { type: p.type, description: p.description };
if (p.enum) prop.enum = p.enum;
properties[p.name] = prop;
if (p.required) required.push(p.name);
}
return { type: 'object', properties, required };
}
/** MCP tool definitions derived from the AI Tool Catalog. */
export const MCP_TOOLS = AI_TOOL_CATALOG.map((t) => ({
name: t.name,
description: t.description,
inputSchema: toJsonSchema(t.parameters),
}));

291
pnpm-lock.yaml generated
View file

@ -69,6 +69,9 @@ importers:
'@mana/media-client':
specifier: workspace:*
version: link:../../services/mana-media/packages/client
'@mana/shared-ai':
specifier: workspace:*
version: link:../../packages/shared-ai
'@mana/shared-hono':
specifier: workspace:*
version: link:../../packages/shared-hono
@ -81,6 +84,9 @@ importers:
'@mana/shared-types':
specifier: workspace:^
version: link:../../packages/shared-types
'@modelcontextprotocol/sdk':
specifier: ^1.29.0
version: 1.29.0(zod@3.25.76)
ai:
specifier: ^6.0.154
version: 6.0.154(zod@3.25.76)
@ -1078,9 +1084,6 @@ importers:
'@mana/spiral-db':
specifier: workspace:*
version: link:../../../../packages/spiral-db
'@mana/subscriptions':
specifier: workspace:*
version: link:../../../../packages/subscriptions
'@mana/wallpaper-generator':
specifier: workspace:*
version: link:../../../../packages/wallpaper-generator
@ -2447,7 +2450,7 @@ importers:
version: 0.65.0(zod@3.25.76)
'@google/genai':
specifier: ^1.14.0
version: 1.48.0
version: 1.48.0(@modelcontextprotocol/sdk@1.29.0(zod@3.25.76))
'@mana/shared-hono':
specifier: workspace:*
version: link:../../../../packages/shared-hono
@ -6318,6 +6321,12 @@ packages:
'@modelcontextprotocol/sdk':
optional: true
'@hono/node-server@1.19.14':
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
'@huggingface/jinja@0.5.6':
resolution: {integrity: sha512-MyMWyLnjqo+KRJYSH7oWNbsOn5onuIvfXYPcc0WOGxU0eHUV7oAYUoQTl2BMdu7ml+ea/bu11UM+EshbeHwtIA==}
engines: {node: '>=18'}
@ -6746,6 +6755,16 @@ packages:
'@mdx-js/mdx@3.1.1':
resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==}
'@modelcontextprotocol/sdk@1.29.0':
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
engines: {node: '>=18'}
peerDependencies:
'@cfworker/json-schema': ^4.1.1
zod: ^3.25 || ^4.0
peerDependenciesMeta:
'@cfworker/json-schema':
optional: true
'@mozilla/readability@0.5.0':
resolution: {integrity: sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==}
engines: {node: '>=14.0.0'}
@ -9352,6 +9371,14 @@ packages:
ajv:
optional: true
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv-keywords@3.5.2:
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
peerDependencies:
@ -9815,6 +9842,10 @@ packages:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@ -10233,6 +10264,10 @@ packages:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
content-disposition@1.1.0:
resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
engines: {node: '>=18'}
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
@ -10246,6 +10281,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.6.0:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
@ -11625,6 +11664,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'}
execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
@ -12111,10 +12154,20 @@ packages:
exponential-backoff@3.1.3:
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
express-rate-limit@8.3.2:
resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==}
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'}
expressive-code@0.40.2:
resolution: {integrity: sha512-1zIda2rB0qiDZACawzw2rbdBQiWHBT56uBctS+ezFe5XMAaFaHLnnSYND/Kd+dVzO9HfCXRDpzH3d+3fvOWRcw==}
@ -12258,6 +12311,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-babel-config@2.1.2:
resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==}
@ -12363,6 +12420,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-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
@ -12803,6 +12864,10 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
iconv-lite@0.7.2:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
idb@7.1.1:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
@ -12889,6 +12954,10 @@ packages:
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
engines: {node: '>=12.22.0'}
ip-address@10.1.0:
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
engines: {node: '>= 12'}
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
@ -13049,6 +13118,9 @@ packages:
is-promise@2.2.2:
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
@ -13389,6 +13461,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==}
@ -13895,6 +13970,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'}
memfs@3.5.3:
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
engines: {node: '>= 4.0.0'}
@ -13912,6 +13991,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'}
merge-options@3.0.4:
resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
engines: {node: '>=10'}
@ -14709,6 +14792,9 @@ packages:
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@ -14785,6 +14871,10 @@ packages:
pixi.js@8.17.1:
resolution: {integrity: sha512-OB4TpZHrP5RYy+7FqmFrAc0IHRhfOoNIfF4sVeinvK3aG1r2pYrSMneJAKi9+WvGKC70Dj7GEpZ2OZGB6o/xdg==}
pkce-challenge@5.0.1:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
pkg-dir@4.2.0:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
@ -15196,6 +15286,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'}
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@ -15797,6 +15891,10 @@ packages:
rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
rrule@2.8.1:
resolution: {integrity: sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==}
@ -15922,6 +16020,10 @@ packages:
resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==}
engines: {node: '>= 0.8.0'}
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
serialize-error@2.1.0:
resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==}
engines: {node: '>=0.10.0'}
@ -15937,6 +16039,10 @@ packages:
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
engines: {node: '>= 0.8.0'}
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
@ -16721,6 +16827,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'}
type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
@ -21464,17 +21574,23 @@ snapshots:
dependencies:
tslib: 2.8.1
'@google/genai@1.48.0':
'@google/genai@1.48.0(@modelcontextprotocol/sdk@1.29.0(zod@3.25.76))':
dependencies:
google-auth-library: 10.6.2
p-retry: 4.6.2
protobufjs: 7.5.4
ws: 8.20.0
optionalDependencies:
'@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76)
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
'@hono/node-server@1.19.14(hono@4.12.12)':
dependencies:
hono: 4.12.12
'@huggingface/jinja@0.5.6': {}
'@huggingface/tokenizers@0.1.3': {}
@ -21984,6 +22100,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
dependencies:
'@hono/node-server': 1.19.14(hono@4.12.12)
ajv: 8.18.0
ajv-formats: 3.0.1(ajv@8.18.0)
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: 8.3.2(express@5.2.1)
hono: 4.12.12
jose: 6.2.2
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
raw-body: 3.0.2
zod: 3.25.76
zod-to-json-schema: 3.25.2(zod@3.25.76)
transitivePeerDependencies:
- supports-color
'@mozilla/readability@0.5.0': {}
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
@ -26146,6 +26284,10 @@ snapshots:
optionalDependencies:
ajv: 8.18.0
ajv-formats@3.0.1(ajv@8.18.0):
optionalDependencies:
ajv: 8.18.0
ajv-keywords@3.5.2(ajv@6.14.0):
dependencies:
ajv: 6.14.0
@ -27111,6 +27253,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 4.4.3
http-errors: 2.0.1
iconv-lite: 0.7.2
on-finished: 2.4.1
qs: 6.15.0
raw-body: 3.0.2
type-is: 2.0.1
transitivePeerDependencies:
- supports-color
boolbase@1.0.0: {}
boolean@3.2.0: {}
@ -27562,6 +27718,8 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
content-disposition@1.1.0: {}
content-type@1.0.5: {}
convert-source-map@2.0.0: {}
@ -27570,6 +27728,8 @@ snapshots:
cookie-signature@1.0.7: {}
cookie-signature@1.2.2: {}
cookie@0.6.0: {}
cookie@0.7.2: {}
@ -29344,6 +29504,10 @@ snapshots:
eventsource-parser@3.0.6: {}
eventsource@3.0.7:
dependencies:
eventsource-parser: 3.0.6
execa@5.1.1:
dependencies:
cross-spawn: 7.0.6
@ -30555,6 +30719,11 @@ snapshots:
exponential-backoff@3.1.3: {}
express-rate-limit@8.3.2(express@5.2.1):
dependencies:
express: 5.2.1
ip-address: 10.1.0
express@4.22.1:
dependencies:
accepts: 1.3.8
@ -30591,6 +30760,39 @@ snapshots:
transitivePeerDependencies:
- supports-color
express@5.2.1:
dependencies:
accepts: 2.0.0
body-parser: 2.2.2
content-disposition: 1.1.0
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.15.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
expressive-code@0.40.2:
dependencies:
'@expressive-code/core': 0.40.2
@ -30768,6 +30970,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-babel-config@2.1.2:
dependencies:
json5: 2.2.3
@ -30885,6 +31098,8 @@ snapshots:
fresh@0.5.2: {}
fresh@2.0.0: {}
fs-extra@10.1.0:
dependencies:
graceful-fs: 4.2.11
@ -31501,6 +31716,10 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
iconv-lite@0.7.2:
dependencies:
safer-buffer: 2.1.2
idb@7.1.1: {}
idb@8.0.3: {}
@ -31622,6 +31841,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
ip-address@10.1.0: {}
ipaddr.js@1.9.1: {}
ipaddr.js@2.3.0: {}
@ -31754,6 +31975,8 @@ snapshots:
is-promise@2.2.2: {}
is-promise@4.0.0: {}
is-reference@1.2.1:
dependencies:
'@types/estree': 1.0.8
@ -32345,6 +32568,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: {}
@ -32914,6 +33139,8 @@ snapshots:
media-typer@0.3.0: {}
media-typer@1.1.0: {}
memfs@3.5.3:
dependencies:
fs-monkey: 1.1.0
@ -32935,6 +33162,8 @@ snapshots:
merge-descriptors@1.0.3: {}
merge-descriptors@2.0.0: {}
merge-options@3.0.4:
dependencies:
is-plain-obj: 2.1.0
@ -34216,6 +34445,8 @@ snapshots:
path-to-regexp@6.3.0: {}
path-to-regexp@8.4.2: {}
path-type@4.0.0: {}
pathe@1.1.2: {}
@ -34282,6 +34513,8 @@ snapshots:
parse-svg-path: 0.1.2
tiny-lru: 11.4.7
pkce-challenge@5.0.1: {}
pkg-dir@4.2.0:
dependencies:
find-up: 4.1.0
@ -34585,6 +34818,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.2
unpipe: 1.0.0
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
@ -35813,6 +36053,16 @@ snapshots:
rou3@0.7.12: {}
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.4.2
transitivePeerDependencies:
- supports-color
rrule@2.8.1:
dependencies:
tslib: 2.8.1
@ -35944,6 +36194,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
serialize-error@2.1.0: {}
serialize-error@7.0.1:
@ -35963,6 +36229,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
server-only@0.0.1: {}
set-blocking@2.0.0: {}
@ -36853,6 +37128,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
type@2.7.3: {}
typed-array-buffer@1.0.3: