From 79baab6080586f4b6098cb59b29eac8ff5858432 Mon Sep 17 00:00:00 2001 From: toor Date: Thu, 22 Oct 2026 16:55:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20HTTP=20MCP=20Serve?= =?UTF-8?q?r=EF=BC=8C=E8=AE=A9=20AI=20Agent=20=E6=8E=A5=E5=85=A5=E9=80=89?= =?UTF-8?q?=E8=8F=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 packages/mcp,基于 Streamable HTTP 暴露菜谱库能力,复用 packages/cook 的选菜算法:recommend_recipes 按现有食材推荐,search_recipes 条件检索,check_incompatible 食材相克,get_recipe 菜谱详情。容器内与 nginx 共存,映射 2230 端口供 Agent 连接。 --- .gitattributes | 2 + Dockerfile | 28 ++- docker-compose.yaml | 6 + docker/entrypoint.sh | 19 ++ package.json | 2 + packages/mcp/build.config.ts | 14 ++ packages/mcp/package.json | 25 ++ packages/mcp/src/catalog.ts | 132 +++++++++++ packages/mcp/src/index.ts | 52 +++++ packages/mcp/src/server.ts | 186 +++++++++++++++ packages/mcp/tsconfig.json | 16 ++ pnpm-lock.yaml | 438 +++++++++++++++++++++++++++++++++++ 12 files changed, 919 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 docker/entrypoint.sh create mode 100644 packages/mcp/build.config.ts create mode 100644 packages/mcp/package.json create mode 100644 packages/mcp/src/catalog.ts create mode 100644 packages/mcp/src/index.ts create mode 100644 packages/mcp/src/server.ts create mode 100644 packages/mcp/tsconfig.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..20be8f3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# 容器内的 shell 脚本必须保持 LF,CRLF 会让 /bin/sh 无法解析 +*.sh text eol=lf diff --git a/Dockerfile b/Dockerfile index f8d4ca8..f5b3faf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ ENV NUXT_TELEMETRY_DISABLED=1 COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY apps/web/package.json apps/web/package.json COPY packages/cook/package.json packages/cook/package.json +COPY packages/mcp/package.json packages/mcp/package.json COPY docs/package.json docs/package.json RUN pnpm install --frozen-lockfile @@ -33,13 +34,38 @@ ENV CF_PAGES_COMMIT_SHA=${CF_PAGES_COMMIT_SHA} # 构建产物输出到仓库根目录的 dist/(见 scripts/copy-web-output.ts) RUN pnpm run build +# MCP Server 依赖 @yunyoujun/cook 的构建产物,根 build 不含它,需单独构建 +RUN pnpm -C packages/cook run build \ + && pnpm -C packages/mcp run build + +# MCP Server 的生产依赖(含 workspace 包 @yunyoujun/cook 的构建产物) +RUN pnpm --filter @cook/mcp deploy --legacy --prod /app/mcp-deploy + # ---------- 运行阶段 ---------- FROM nginx:stable-alpine +# MCP Server 需要 Node 运行时,与 nginx 共存于同一容器 +RUN apk add --no-cache nodejs npm \ + && mkdir -p /data + COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/dist /usr/share/nginx/html -EXPOSE 2229 +COPY --from=builder /app/mcp-deploy /opt/cook-mcp +COPY --from=builder /app/packages/data/recipe.csv /data/recipe.csv +COPY --from=builder /app/packages/data/incompatible-foods.csv /data/incompatible-foods.csv + +COPY docker/entrypoint.sh /usr/local/bin/cook-entrypoint +RUN chmod +x /usr/local/bin/cook-entrypoint + +ENV COOK_DATA_DIR=/data \ + COOK_MCP_PORT=2230 \ + COOK_MCP_HOST=0.0.0.0 \ + COOK_MCP_PATH=/mcp + +EXPOSE 2229 2230 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget -qO- http://127.0.0.1:2229/ >/dev/null || exit 1 + +ENTRYPOINT ["/usr/local/bin/cook-entrypoint"] diff --git a/docker-compose.yaml b/docker-compose.yaml index ca9a38c..e88955a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,6 +15,12 @@ services: ports: # 容器内 nginx 监听 2229,对外同样暴露 2229 - '2229:2229' + # MCP Server 监听 2230,供 AI Agent 以 Streamable HTTP 连接 + - '2230:2230' + environment: + COOK_MCP_PORT: 2230 + COOK_MCP_HOST: 0.0.0.0 + COOK_MCP_PATH: /mcp healthcheck: test: [CMD, wget, -q, -O, /dev/null, http://127.0.0.1:2229/] interval: 30s diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..e578437 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# 同一容器内同时跑 nginx(Web,2229)和 MCP Server(2230)。 +# 任一进程退出即终止容器,交由 Docker 的 restart 策略整体重启。 +set -e + +node /opt/cook-mcp/dist/index.mjs & +MCP_PID=$! +nginx -g 'daemon off;' & +NGINX_PID=$! + +cleanup() { + kill "$MCP_PID" "$NGINX_PID" 2>/dev/null || true +} +trap cleanup EXIT INT TERM + +# busybox ash 的 wait 不支持 -n,改为轮询两个进程是否都还活着 +while kill -0 "$MCP_PID" 2>/dev/null && kill -0 "$NGINX_PID" 2>/dev/null; do + sleep 1 +done \ No newline at end of file diff --git a/package.json b/package.json index 504ba10..f55623a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "fixtures": "tsx scripts/gen-fixtures.ts", "dev": "pnpm -C apps/web run dev", "dev:pwa": "pnpm -C apps/web run dev:pwa", + "mcp": "pnpm -C packages/mcp run dev", + "mcp:build": "pnpm -C packages/mcp run build", "docs:dev": "pnpm -C docs run docs:dev", "docs:build": "pnpm -C docs run docs:build", "generate": "pnpm -C apps/web run generate && tsx scripts/copy-web-output.ts", diff --git a/packages/mcp/build.config.ts b/packages/mcp/build.config.ts new file mode 100644 index 0000000..35e8ac7 --- /dev/null +++ b/packages/mcp/build.config.ts @@ -0,0 +1,14 @@ +import { defineBuildConfig } from 'unbuild' + +export default defineBuildConfig({ + entries: [ + 'src/index', + 'src/server', + 'src/catalog', + ], + clean: true, + declaration: false, + rollup: { + emitCJS: false, + }, +}) diff --git a/packages/mcp/package.json b/packages/mcp/package.json new file mode 100644 index 0000000..8639fa3 --- /dev/null +++ b/packages/mcp/package.json @@ -0,0 +1,25 @@ +{ + "name": "@cook/mcp", + "type": "module", + "version": "0.1.0", + "private": true, + "description": "食用手册 MCP Server —— 以 HTTP 暴露菜谱检索能力,供 AI Agent 选菜", + "license": "MIT", + "scripts": { + "build": "unbuild", + "dev": "tsx src/index.ts", + "start": "node dist/index.mjs" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.32.1", + "@yunyoujun/cook": "workspace:*", + "express": "^5.2.1", + "zod": "^4.6.7" + }, + "devDependencies": { + "@types/express": "^5.0.6", + "@types/node": "^25.9.5", + "tsx": "^4.23.12", + "unbuild": "^3.6.1" + } +} diff --git a/packages/mcp/src/catalog.ts b/packages/mcp/src/catalog.ts new file mode 100644 index 0000000..ff420dc --- /dev/null +++ b/packages/mcp/src/catalog.ts @@ -0,0 +1,132 @@ +import type { + Difficulty, + IncompatibleRule, + RecipeItem, + RecipeMatch, + RecommendationMode, + SearchOptions, + SearchResult, +} from '@yunyoujun/cook' +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' +import { + parseIncompatibleFoodsCsv, + parseRecipeCsv, + searchRecipes, + uniqueStuff, +} from '@yunyoujun/cook' +import { createRecipeRecommender } from '@yunyoujun/cook/recommend' + +/** 食材、厨具、标签在 CSV 里用中文顿号分隔,Agent 传参时也可能用逗号 */ +const SEP_RE = /[,,、;;\n]/ + +export interface RecommendInput { + stuff: string[] + pantry?: string[] + tool?: string + keyword?: string + mode?: RecommendationMode + avoid?: string[] + methodGroups?: string[] + cuisines?: string[] + noSpicy?: boolean + difficulty?: Difficulty[] + limit?: number +} + +export interface RecipeDetail extends RecipeItem { + videoUrl?: string +} + +/** + * 数据目录:容器里由 COOK_DATA_DIR 指定,本地开发回退到 packages/data + */ +function resolveDataDir(): string { + const moduleDir = path.dirname(fileURLToPath(import.meta.url)) + const candidates = [process.env.COOK_DATA_DIR, path.resolve(moduleDir, '../../data')] + for (const dir of candidates) { + if (dir && fs.existsSync(path.join(dir, 'recipe.csv'))) + return dir + } + throw new Error(`找不到菜谱数据目录,已尝试:${candidates.filter(Boolean).join('、')}。请设置 COOK_DATA_DIR。`) +} + +export interface Catalog { + /** 菜谱总数,让 Agent 对数据规模有预期 */ + recipeCount: number + recommend: (input: RecommendInput) => RecipeMatch[] + search: (options: SearchOptions) => SearchResult + checkIncompatible: (foods: string[]) => IncompatibleRule[] + getRecipe: (idOrName: string | number) => RecipeDetail | undefined +} + +let cached: Catalog | undefined + +/** 菜谱数据只在启动时解析一次,之后所有工具调用复用同一份索引 */ +export function loadCatalog(): Catalog { + if (cached) + return cached + + const dataDir = resolveDataDir() + const recipes = parseRecipeCsv(fs.readFileSync(path.join(dataDir, 'recipe.csv'), 'utf8')) + if (!recipes.length) + throw new Error('菜谱数据为空,请检查 recipe.csv') + + let incompatible: IncompatibleRule[] = [] + const incompatibleFile = path.join(dataDir, 'incompatible-foods.csv') + if (fs.existsSync(incompatibleFile)) + incompatible = parseIncompatibleFoodsCsv(fs.readFileSync(incompatibleFile, 'utf8')) + + const recommend = createRecipeRecommender(recipes) + const byId = new Map(recipes.filter(r => r.id !== undefined).map(r => [r.id!, r])) + const byName = new Map(recipes.map(r => [r.name, r])) + + cached = { + recipeCount: recipes.length, + + recommend(input) { + const matches = recommend({ + stuff: uniqueStuff(input.stuff.flatMap(item => item.split(SEP_RE))), + pantry: input.pantry?.flatMap(item => item.split(SEP_RE)), + tool: input.tool, + keyword: input.keyword, + mode: input.mode, + avoid: input.avoid?.flatMap(item => item.split(SEP_RE)), + methodGroups: input.methodGroups, + cuisines: input.cuisines, + noSpicy: input.noSpicy, + difficulty: input.difficulty, + }) + return input.limit ? matches.slice(0, input.limit) : matches + }, + + search(options) { + return searchRecipes(recipes, incompatible, { + stuff: options.stuff?.flatMap(item => item.split(SEP_RE)), + tool: options.tool, + difficulty: options.difficulty, + tag: options.tag, + method: options.method, + limit: options.limit, + }) + }, + + checkIncompatible(foods) { + const selected = new Set(uniqueStuff(foods.flatMap(item => item.split(SEP_RE)))) + return incompatible.filter(rule => selected.has(rule.foodA) && selected.has(rule.foodB)) + }, + + getRecipe(idOrName) { + const recipe = typeof idOrName === 'number' + ? byId.get(idOrName) + : byId.get(Number(idOrName.trim())) ?? byName.get(idOrName.trim()) + if (!recipe) + return undefined + return { ...recipe, videoUrl: recipe.bv ? `https://www.bilibili.com/video/${recipe.bv}` : undefined } + }, + } + + return cached +} diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts new file mode 100644 index 0000000..37eea70 --- /dev/null +++ b/packages/mcp/src/index.ts @@ -0,0 +1,52 @@ +import process from 'node:process' +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' +import express from 'express' +import { loadCatalog } from './catalog' +import { createServer, logFilterHints } from './server' + +const PORT = Number(process.env.COOK_MCP_PORT ?? 2230) +const HOST = process.env.COOK_MCP_HOST ?? '0.0.0.0' +const PATHNAME = process.env.COOK_MCP_PATH ?? '/mcp' + +const catalog = loadCatalog() +logFilterHints(catalog.recipeCount) + +const app = express() +app.use(express.json({ limit: '1mb' })) + +app.get('/health', (_req, res) => { + res.json({ status: 'ok', recipes: catalog.recipeCount }) +}) + +/** + * 无状态模式:每个请求新建一套 server + transport,不保留会话, + * 这样 Agent 端不需要管理 session id,负载均衡下也能随意扩缩。 + */ +app.post(PATHNAME, async (req, res) => { + const server = createServer() + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }) + + res.on('close', () => { + transport.close() + server.close() + }) + + try { + await server.connect(transport) + await transport.handleRequest(req, res, req.body) + } + catch (error) { + process.stderr.write(`[cook-mcp] 请求处理失败: ${(error as Error).stack ?? error}\n`) + if (!res.headersSent) + res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null }) + } +}) + +// StreamableHTTP 的 GET 用于开启 SSE 流,客户端会探测;无状态模式下直接告知不支持 +app.get(PATHNAME, (_req, res) => { + res.status(405).json({ error: 'SSE 监听未启用,请使用 POST 发起 JSON-RPC 请求' }) +}) + +app.listen(PORT, HOST, () => { + process.stderr.write(`[cook-mcp] HTTP MCP 已启动:http://${HOST}:${PORT}${PATHNAME}\n`) +}) diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts new file mode 100644 index 0000000..4a00810 --- /dev/null +++ b/packages/mcp/src/server.ts @@ -0,0 +1,186 @@ +import type { RecipeMatch } from '@yunyoujun/cook' +import process from 'node:process' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { CUISINE_GROUPS, DIFFICULTY_LEVELS, METHOD_GROUPS } from '@yunyoujun/cook/recommend' +import { z } from 'zod' +import { loadCatalog } from './catalog' + +/** 反复调用时按名称查找会变慢,超过这个数量就建议 Agent 改用关键词筛选 */ +const MAX_RECIPES = 30 + +/** + * Agent 常把列表参数写成「牛肉,土豆」这样的字符串, + * 这里统一收敛成数组,省得在 description 里反复强调格式。 + */ +const ingredientList = z.union([ + z.array(z.string()), + z.string().transform(v => v.split(/[,,、;;\n]/).map(s => s.trim()).filter(Boolean)), +]) + +function toText(data: unknown): string { + return JSON.stringify(data, null, 2) +} + +function describeMatch(match: RecipeMatch) { + const { recipe } = match + return { + id: recipe.id, + 菜名: recipe.name, + 所需食材: recipe.stuff, + 你已有的: match.matchedStuff, + 厨房常备已满足: match.pantryStuff, + 还需购买: match.missingStuff, + 大杂烩可随意加: match.optionalStuff, + 难度: recipe.difficulty || '未标注', + 标签: recipe.tags, + 做法: recipe.methods, + 所需厨具: recipe.tools, + 视频教程: recipe.bv ? `https://www.bilibili.com/video/${recipe.bv}` : '无', + } +} + +export function createServer(): McpServer { + const catalog = loadCatalog() + const server = new McpServer( + { name: 'cook-recipe', version: '1.0.0' }, + { instructions: `食用手册菜谱库,共 ${catalog.recipeCount} 道菜。回答「今天吃什么」类问题时先调用 recommend_recipes,按用户手边食材推荐;需要精确查找或展开某道菜的完整信息时再调用 get_recipe。` }, + ) + + server.registerTool( + 'recommend_recipes', + { + title: '按现有食材推荐菜谱', + description: '根据用户手头已有的食材推荐能做的菜。与「食用手册」App 的选菜算法一致:优先少缺食材的菜,并给出匹配/缺少的食材明细。stuff 必填,至少给一种食材。', + inputSchema: { + stuff: ingredientList.describe('手头已有的食材,如 ["番茄","鸡蛋"],也接受 "番茄,鸡蛋"'), + pantry: ingredientList.optional().describe('厨房常备调料,如 ["油","盐"];常备品不算「缺」,可让结果更准'), + tool: z.string().optional().describe('只能用这个厨具做,如 "电饭煲"、"烤箱"、"空气炸锅"、"微波炉"、"一口大锅"'), + keyword: z.string().optional().describe('菜名关键词,用于在推荐结果里进一步收窄,如 "煲饭"、"鸡"'), + mode: z.enum(['loose', 'strict', 'survival']).optional().describe('loose=允许缺料(默认);strict=必须用上所有已选食材;survival=只推荐食材完全齐备的'), + avoid: ingredientList.optional().describe('忌口食材,用到就排除(如 ["香菜"])'), + methodGroups: z.array(z.enum(['炒菜', '汤羹', '炖煮', '蒸', '炸煎', '烤', '凉拌', '微波', '火锅'])).optional().describe('做法筛选,满足任意一个即可'), + cuisines: z.array(z.enum(['川菜', '湘菜', '粤菜', '苏菜', '东北菜', '西北菜', '日式', '意式', '法式', '西式', '国宴菜'])).optional().describe('菜系筛选,满足任意一个即可'), + noSpicy: z.boolean().optional().describe('不吃辣:排除带「辣」标签的菜'), + difficulty: z.array(z.enum(['简单', '普通', '困难'])).optional().describe('难度筛选,满足任意一个即可'), + limit: z.number().int().min(1).max(MAX_RECIPES).optional().describe(`最大返回数,默认 10,上限 ${MAX_RECIPES}`), + }, + }, + ({ limit = 10, ...input }) => { + const matches = catalog.recommend(input) + if (!matches.length) { + return { + content: [{ type: 'text' as const, text: '没有匹配的菜谱。可以放宽条件:少给忌口/厨具限制,或提示用户再买一样食材。' }], + } + } + return { + content: [{ type: 'text' as const, text: toText({ 菜谱总数: matches.length, 菜谱: matches.slice(0, limit).map(describeMatch) }) }], + } + }, + ) + + server.registerTool( + 'search_recipes', + { + title: '按条件检索菜谱', + description: '按食材/厨具/难度/标签/做法精确检索菜谱,返回匹配与缺少的食材。stuff 可以不给,此时按其他条件筛选。适合「有哪些用烤箱的菜」这类查询。', + inputSchema: { + stuff: ingredientList.optional().describe('按食材筛选,命中任意一样即算匹配'), + tool: z.string().optional().describe('厨具,如 "电饭煲"'), + difficulty: z.enum(['简单', '普通', '困难']).optional().describe('难度,需完全一致'), + tag: z.string().optional().describe('标签关键词,如 "懒人"、"下饭"、"汤"'), + method: z.string().optional().describe('做法关键词,如 "炒"、"蒸"'), + limit: z.number().int().min(1).max(MAX_RECIPES).optional().describe(`最大返回数,默认 10,上限 ${MAX_RECIPES}`), + }, + }, + ({ limit = 10, ...options }) => { + const result = catalog.search({ ...options, limit }) + const payload = { + 菜谱总数: result.recipes.length, + 菜谱: result.recipes.map(r => ({ + id: r.id, + 菜名: r.name, + 所需食材: r.stuff, + 命中食材: r.matchedStuff, + 缺少食材: r.missingStuff, + 难度: r.difficulty || '未标注', + 标签: r.tags, + 做法: r.methods, + 所需厨具: r.tools, + 视频教程: r.videoUrl ?? '无', + })), + 食物相克提醒: result.warnings.map(w => `${w.foodA} + ${w.foodB}:${w.reason}`), + } + return { content: [{ type: 'text' as const, text: toText(payload) }] } + }, + ) + + server.registerTool( + 'check_incompatible', + { + title: '检查食材相克', + description: '检查一组食材里是否存在「不宜同食」的搭配,依据项目自带的相克表。理性提示影响程度,不要夸大恐慌。', + inputSchema: { + foods: ingredientList.describe('要一起检查的食材,至少两种才可能命中相克规则'), + }, + }, + ({ foods }) => { + if (!foods.length) + return { content: [{ type: 'text' as const, text: '请至少提供一种食材。' }] } + const warnings = catalog.checkIncompatible(foods) + if (!warnings.length) + return { content: [{ type: 'text' as const, text: '这组食材没有已知的相克记录,可以放心搭配。' }] } + return { + content: [{ + type: 'text' as const, + text: toText({ + 提醒: warnings.map(w => ({ + 搭配: `${w.foodA} + ${w.foodB}`, + 原因: w.reason, + })), + }), + }], + } + }, + ) + + server.registerTool( + 'get_recipe', + { + title: '查看菜谱详情', + description: '按菜名或 id 取单道菜的完整信息,含所需食材、厨具、难度、标签和 B 站视频教程链接。推荐结果里已含大部分信息,需要展开单道菜时再调用。', + inputSchema: { + idOrName: z.string().describe('菜名或 id,如 "电饭煲版广式腊肠煲饭" 或 "0"'), + }, + }, + ({ idOrName }) => { + const recipe = catalog.getRecipe(idOrName) + if (!recipe) + return { content: [{ type: 'text' as const, text: '没找到这道菜,请确认菜名或 id 是否正确。' }] } + return { + content: [{ + type: 'text' as const, + text: toText({ + id: recipe.id, + 菜名: recipe.name, + 所需食材: recipe.stuff, + 难度: recipe.difficulty || '未标注', + 标签: recipe.tags, + 做法: recipe.methods, + 所需厨具: recipe.tools, + 视频教程: recipe.videoUrl ?? '无', + }), + }], + } + }, + ) + + return server +} + +/** 打印筛选取值,方便直接命令行排查参数写没写错 */ +export function logFilterHints(recipeCount: number) { + process.stderr.write( + `[cook-mcp] 菜谱 ${recipeCount} 道;做法 ${METHOD_GROUPS.map(g => g.label).join('/')};` + + `菜系 ${CUISINE_GROUPS.map(g => g.label).join('/')};难度 ${DIFFICULTY_LEVELS.join('/')}\n`, + ) +} diff --git a/packages/mcp/tsconfig.json b/packages/mcp/tsconfig.json new file mode 100644 index 0000000..695f864 --- /dev/null +++ b/packages/mcp/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext"], + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "types": ["node"], + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e7b864..b2c1455 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -247,6 +247,34 @@ importers: specifier: ^3.6.1 version: 3.6.1(sass@1.102.0)(typescript@5.9.3)(vue-tsc@3.3.9(typescript@5.9.3))(vue@3.5.41(typescript@5.9.3)) + packages/mcp: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.32.1 + version: 1.32.1(supports-color@10.2.2)(zod@4.6.7) + '@yunyoujun/cook': + specifier: workspace:* + version: link:../cook + express: + specifier: ^5.2.1 + version: 5.3.0(supports-color@10.2.2) + zod: + specifier: ^4.6.7 + version: 4.6.7 + devDependencies: + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/node': + specifier: ^25.9.5 + version: 25.9.5 + tsx: + specifier: ^4.23.12 + version: 4.23.12 + unbuild: + specifier: ^3.6.1 + version: 3.6.1(sass@1.102.0)(typescript@5.9.3)(vue-tsc@3.3.9(typescript@5.9.3))(vue@3.5.41(typescript@5.9.3)) + packages: '@antfu/eslint-config@8.3.0': @@ -1481,6 +1509,12 @@ packages: peerDependencies: vue: ^3.2.0 + '@hono/node-server@2.1.5': + resolution: {integrity: sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 || ^5.0.0-0 + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -1651,6 +1685,16 @@ packages: resolution: {integrity: sha512-oV4PyZfwJNtmFWhvlJLqYIX1Nn22ML8FZpS16ZUKv0hg7414xV1fjsGqxQzLT2dyK92TKxsJSwMOd7VNHAtPmA==} engines: {node: '>= 12'} + '@modelcontextprotocol/sdk@1.32.1': + resolution: {integrity: sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@napi-rs/lzma-linux-x64-gnu@1.5.1': resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} engines: {node: ^22.20 || ^24.12 || >=25} @@ -2895,9 +2939,15 @@ packages: '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -2910,12 +2960,21 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/express-serve-static-core@5.1.3': + resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/fs-extra@8.1.5': resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} '@types/hast@3.0.5': resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} @@ -2946,9 +3005,21 @@ packages: '@types/papaparse@5.5.2': resolution: {integrity: sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/slice-ansi@4.0.0': resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==} @@ -3570,6 +3641,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -3589,6 +3664,9 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} @@ -3818,6 +3896,10 @@ packages: birpc@4.1.0: resolution: {integrity: sha512-O8L9vALWGqdEe0cG4HJckauw3WeJETlJnDRPUYpgwB7wrU43b/5NGMdVjdVcRo+4ROgd3ih2wha1glDe4HRVgw==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -4115,6 +4197,18 @@ packages: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + convert-hrtime@5.0.0: resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} engines: {node: '>=12'} @@ -4131,6 +4225,14 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + 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'} + cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} @@ -4148,6 +4250,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -4880,6 +4986,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} @@ -4896,6 +5010,16 @@ packages: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.7.1: + resolution: {integrity: sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.3.0: + resolution: {integrity: sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==} + engines: {node: '>= 18'} + exsolve@1.1.1: resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} @@ -4981,6 +5105,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -5038,6 +5166,10 @@ packages: resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} engines: {node: '>=14.0.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -5256,6 +5388,10 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hono@4.13.13: + resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==} + engines: {node: '>=16.9.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -5307,6 +5443,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -5382,6 +5522,10 @@ packages: resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} engines: {node: '>= 12'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -5510,6 +5654,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-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -5609,6 +5756,9 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jose@6.2.13: + resolution: {integrity: sha512-7TIg10ZE1x82YediN+lcrPNvGMKge9yFHQD+Qho/b8y2adfugU3AsvvHfxV0fzyFllT6l9xCjf4RWY9g/Z9PJQ==} + js-beautify@1.15.4: resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} engines: {node: '>=14'} @@ -5668,6 +5818,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-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -6019,6 +6172,14 @@ packages: mdurl@2.1.0: resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -6277,6 +6438,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + netmask@2.1.1: resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} engines: {node: '>= 0.4.0'} @@ -6372,6 +6537,10 @@ packages: engines: {node: '>=18'} hasBin: true + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-deep-merge@2.0.1: resolution: {integrity: sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==} @@ -6586,6 +6755,9 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -6621,6 +6793,10 @@ packages: typescript: optional: true + 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==} @@ -7026,6 +7202,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-addr@2.0.8: + resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} + engines: {node: '>= 0.10'} + proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} engines: {node: '>= 14'} @@ -7048,6 +7228,10 @@ packages: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + qs@6.16.1: + resolution: {integrity: sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -7072,6 +7256,10 @@ packages: resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} engines: {node: '>= 0.6'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} @@ -7262,6 +7450,10 @@ packages: rou3@0.9.1: resolution: {integrity: sha512-z/sSmzvtwMDDnxsPVhfWMuG6F6mbmhFDXoVqLmMfbpDD9qfV3GDmSQpf0+W296/ZDIpW2wcMmBfpVFzcnOi/nA==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + rsvp@3.6.2: resolution: {integrity: sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==} engines: {node: 0.12.* || 4.* || 6.* || >= 7.*} @@ -7879,6 +8071,10 @@ packages: resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} engines: {node: '>=20'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + type-level-regexp@0.1.17: resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==} @@ -8080,6 +8276,10 @@ packages: '@unocss/webpack': optional: true + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin-utils@0.3.2: resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==} engines: {node: '>=20.19.0'} @@ -8755,6 +8955,14 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.6.7: + resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -10072,6 +10280,10 @@ snapshots: '@tanstack/vue-virtual': 3.13.35(vue@3.5.41(typescript@5.9.3)) vue: 3.5.41(typescript@5.9.3) + '@hono/node-server@2.1.5(hono@4.13.13)': + dependencies: + hono: 4.13.13 + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -10375,6 +10587,28 @@ snapshots: '@mattiasbuelens/web-streams-adapter@0.1.0': {} + '@modelcontextprotocol/sdk@1.32.1(supports-color@10.2.2)(zod@4.6.7)': + dependencies: + '@hono/node-server': 2.1.5(hono@4.13.13) + ajv: 8.20.0 + ajv-formats: 3.0.1 + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.3.0(supports-color@10.2.2) + express-rate-limit: 8.7.1(express@5.3.0(supports-color@10.2.2))(supports-color@10.2.2) + hono: 4.13.13 + jose: 6.2.13 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.6.7 + zod-to-json-schema: 3.25.2(zod@4.6.7) + transitivePeerDependencies: + - supports-color + '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true @@ -11703,11 +11937,20 @@ snapshots: tslib: 2.8.1 optional: true + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 25.9.5 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.9.5 + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -11718,6 +11961,19 @@ snapshots: '@types/estree@1.0.9': {} + '@types/express-serve-static-core@5.1.3': + dependencies: + '@types/node': 25.9.5 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.3 + '@types/serve-static': 2.2.0 + '@types/fs-extra@8.1.5': dependencies: '@types/node': 25.9.5 @@ -11726,6 +11982,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/http-errors@2.0.5': {} + '@types/jsesc@2.5.1': {} '@types/json-schema@7.0.15': {} @@ -11755,8 +12013,21 @@ snapshots: dependencies: '@types/node': 25.9.5 + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + '@types/resolve@1.20.2': {} + '@types/send@1.2.1': + dependencies: + '@types/node': 25.9.5 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 25.9.5 + '@types/slice-ansi@4.0.0': {} '@types/trusted-types@2.0.7': {} @@ -12559,6 +12830,11 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + acorn-import-attributes@1.9.5(acorn@8.18.0): dependencies: acorn: 8.18.0 @@ -12571,6 +12847,10 @@ snapshots: agent-base@7.1.4: {} + ajv-formats@3.0.1: + dependencies: + ajv: 8.20.0 + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -12799,6 +13079,20 @@ snapshots: birpc@4.1.0: {} + body-parser@2.3.0(supports-color@10.2.2): + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3(supports-color@10.2.2) + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} boxen@7.0.0: @@ -13089,6 +13383,12 @@ snapshots: content-disposition@0.5.2: {} + content-disposition@2.0.1: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + convert-hrtime@5.0.0: {} convert-source-map@2.0.0: {} @@ -13099,6 +13399,10 @@ snapshots: cookie-es@3.1.1: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + cookiejar@2.1.4: {} copy-anything@4.0.5: @@ -13113,6 +13417,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + crc-32@1.2.2: {} crc32-stream@6.0.0: @@ -14042,6 +14351,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + execa@4.1.0: dependencies: cross-spawn: 7.0.6 @@ -14080,6 +14395,47 @@ snapshots: expect-type@1.4.0: {} + express-rate-limit@8.7.1(express@5.3.0(supports-color@10.2.2))(supports-color@10.2.2): + dependencies: + debug: 4.4.3(supports-color@10.2.2) + express: 5.3.0(supports-color@10.2.2) + ip-address: 10.5.0 + transitivePeerDependencies: + - supports-color + + express@5.3.0(supports-color@10.2.2): + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0(supports-color@10.2.2) + content-disposition: 2.0.1 + content-type: 2.1.0 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3(supports-color@10.2.2) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1(supports-color@10.2.2) + 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.8 + qs: 6.16.1 + range-parser: 1.3.0 + router: 2.2.0(supports-color@10.2.2) + send: 1.2.1(supports-color@10.2.2) + serve-static: 2.2.1(supports-color@10.2.2) + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.1.1: {} external-editor@3.1.0: @@ -14160,6 +14516,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1(supports-color@10.2.2): + dependencies: + debug: 4.4.3(supports-color@10.2.2) + 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-simple@1.0.1: {} find-up@5.0.0: @@ -14224,6 +14591,8 @@ snapshots: dezalgo: 1.0.4 once: 1.4.0 + forwarded@0.2.0: {} + fraction.js@5.3.4: {} fresh@2.0.0: {} @@ -14468,6 +14837,8 @@ snapshots: dependencies: '@types/hast': 3.0.5 + hono@4.13.13: {} + hookable@5.5.3: {} hookable@6.1.1: {} @@ -14518,6 +14889,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + idb@7.1.1: {} identifier-regex@1.1.0: @@ -14611,6 +14986,8 @@ snapshots: ip-address@10.5.0: {} + ipaddr.js@1.9.1: {} + iron-webcrypto@1.2.1: {} is-array-buffer@3.0.5: @@ -14726,6 +15103,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.9 @@ -14815,6 +15194,8 @@ snapshots: jiti@2.7.0: {} + jose@6.2.13: {} + js-beautify@1.15.4: dependencies: config-chain: 1.1.13 @@ -14880,6 +15261,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -15346,6 +15729,10 @@ snapshots: mdurl@2.1.0: {} + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -15691,6 +16078,10 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + netmask@2.1.1: {} nitropack@2.13.4(@parcel/watcher@2.6.0)(oxc-parser@0.140.0)(rolldown@1.2.3)(srvx@0.12.5)(supports-color@10.2.2)(vite@8.2.1(@types/node@25.9.5)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.102.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(xml2js@0.6.2): @@ -16001,6 +16392,8 @@ snapshots: pathe: 2.0.3 tinyexec: 1.3.0 + object-assign@4.1.1: {} + object-deep-merge@2.0.1: {} object-identity@0.2.3: {} @@ -16274,6 +16667,8 @@ snapshots: path-to-regexp@3.3.0: {} + path-to-regexp@8.4.2: {} + pathe@1.1.2: {} pathe@2.0.3: {} @@ -16297,6 +16692,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + pkce-challenge@5.0.1: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -16672,6 +17069,11 @@ snapshots: proto-list@1.2.4: {} + proxy-addr@2.0.8: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-agent@6.5.0(supports-color@10.2.2): dependencies: agent-base: 7.1.4 @@ -16701,6 +17103,11 @@ snapshots: es-define-property: 1.0.1 side-channel: 1.1.1 + qs@6.16.1: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + quansync@0.2.11: {} quansync@1.0.0: {} @@ -16715,6 +17122,13 @@ snapshots: range-parser@1.3.0: {} + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + rc9@3.0.1: dependencies: defu: 6.1.7 @@ -16973,6 +17387,16 @@ snapshots: rou3@0.9.1: {} + router@2.2.0(supports-color@10.2.2): + dependencies: + debug: 4.4.3(supports-color@10.2.2) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + rsvp@3.6.2: {} run-applescript@7.1.0: {} @@ -17669,6 +18093,12 @@ snapshots: dependencies: tagged-tag: 1.0.0 + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + type-level-regexp@0.1.17: {} typed-array-buffer@1.0.3: @@ -17953,6 +18383,8 @@ snapshots: transitivePeerDependencies: - vite + unpipe@1.0.0: {} + unplugin-utils@0.3.2: dependencies: pathe: 2.0.3 @@ -18777,4 +19209,10 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 + zod-to-json-schema@3.25.2(zod@4.6.7): + dependencies: + zod: 4.6.7 + + zod@4.6.7: {} + zwitch@2.0.4: {}