From 63147e9d5f77e332e5e1962850fd8420ca77b8ee Mon Sep 17 00:00:00 2001 From: glassBead Date: Sun, 13 Apr 2025 21:40:27 -0500 Subject: [PATCH 1/6] update prebuild commands, use node cross-platform cmds where possible. --- .gitignore | 5 +- packages/api/package.json | 6 ++- packages/components/package.json | 5 +- packages/web/package.json | 6 ++- pnpm-lock.yaml | 88 ++++++++++++++++++++++++++++++++ srcbook/package.json | 5 +- 6 files changed, 107 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index c37aebf0..dbbabd1d 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ srcbook/lib/**/* # Docs folder docs/ -vite.config.ts.timestamp-*.mjs \ No newline at end of file +vite.config.ts.timestamp-*.mjs + +# Roo Code +.roomodes \ No newline at end of file diff --git a/packages/api/package.json b/packages/api/package.json index f1c25925..3a9e0907 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -10,8 +10,8 @@ "scripts": { "dev": "vite-node -w dev-server.mts", "test": "vitest", - "prebuild": "rm -rf ./dist", - "build": "tsc && cp -R ./drizzle ./dist/drizzle && cp -R ./srcbook/examples ./dist/srcbook/examples && cp -R ./prompts ./dist/prompts && cp -R ./apps/templates ./dist/apps/templates", + "prebuild": "rimraf ./dist", + "build": "tsc && copyfiles -u 1 ./drizzle/**/* ./dist/drizzle && copyfiles -u 1 ./srcbook/examples/**/* ./dist/srcbook/examples && copyfiles -u 1 ./prompts/**/* ./dist/prompts && copyfiles -u 1 ./apps/templates/**/* ./dist/apps/templates", "lint": "eslint . --max-warnings 0", "check-types": "tsc", "depcheck": "depcheck", @@ -37,6 +37,7 @@ "fast-xml-parser": "^4.5.0", "marked": "catalog:", "posthog-node": "^4.2.0", + "rimraf": "^6.0.1", "simple-git": "^3.27.0", "ws": "catalog:", "zod": "catalog:" @@ -47,6 +48,7 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/ws": "^8.5.12", + "copyfiles": "^2.4.1", "drizzle-kit": "^0.24.2", "vite": "^5.4.4", "vite-node": "^2.0.5", diff --git a/packages/components/package.json b/packages/components/package.json index a276aa9d..146d6d9d 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -4,7 +4,7 @@ "type": "module", "main": "./dist/index.js", "scripts": { - "prebuild": "rm -rf ./dist", + "prebuild": "rimraf ./dist", "build": "tsc", "dev": "tsc --watch --project .", "lint": "eslint . --max-warnings 0", @@ -57,5 +57,8 @@ "sonner": "*", "tailwind-merge": "*", "use-debounce": "*" + }, + "devDependencies": { + "rimraf": "^6.0.1" } } diff --git a/packages/web/package.json b/packages/web/package.json index ae218d91..c3f0f81a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "prebuild": "rm -rf ./dist", - "build": "tsc && vite build && cp -R ./dist/. ../../srcbook/public", + "prebuild": "rimraf ./dist", + "build": "tsc && vite build && copyfiles -u 1 ./dist/**/* ../../srcbook/public", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write .", "preview": "vite preview", @@ -52,7 +52,9 @@ "@vitejs/plugin-react-swc": "^3.7.0", "autoprefixer": "^10.4.20", "chokidar": "^4.0.1", + "copyfiles": "^2.4.1", "postcss": "^8.4.45", + "rimraf": "^6.0.1", "tailwindcss": "^3.4.11", "vite": "^5.4.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47419cec..97c277f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: posthog-node: specifier: ^4.2.0 version: 4.2.0 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 simple-git: specifier: ^3.27.0 version: 3.27.0 @@ -121,6 +124,9 @@ importers: '@types/ws': specifier: ^8.5.12 version: 8.5.12 + copyfiles: + specifier: ^2.4.1 + version: 2.4.1 drizzle-kit: specifier: ^0.24.2 version: 0.24.2 @@ -259,6 +265,10 @@ importers: use-debounce: specifier: '*' version: 10.0.3(react@18.3.1) + devDependencies: + rimraf: + specifier: ^6.0.1 + version: 6.0.1 packages/configs: devDependencies: @@ -420,9 +430,15 @@ importers: chokidar: specifier: ^4.0.1 version: 4.0.1 + copyfiles: + specifier: ^2.4.1 + version: 2.4.1 postcss: specifier: ^8.4.45 version: 8.4.45 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 tailwindcss: specifier: ^3.4.11 version: 3.4.11 @@ -490,6 +506,9 @@ importers: '@types/node': specifier: ^22.5.4 version: 22.5.4 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 packages: @@ -2903,6 +2922,10 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + copyfiles@2.4.1: + resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} + hasBin: true + core-js@3.38.1: resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} @@ -4069,6 +4092,9 @@ packages: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -4342,6 +4368,11 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mlly@1.7.1: resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} @@ -4402,6 +4433,9 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + noms@0.0.0: + resolution: {integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -4848,6 +4882,9 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -5109,6 +5146,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -5230,6 +5270,9 @@ packages: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -5373,6 +5416,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + update-browserslist-db@1.1.0: resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} hasBin: true @@ -5616,6 +5663,10 @@ packages: utf-8-validate: optional: true + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -8181,6 +8232,16 @@ snapshots: cookie@0.6.0: {} + copyfiles@2.4.1: + dependencies: + glob: 7.2.3 + minimatch: 3.1.2 + mkdirp: 1.0.4 + noms: 0.0.0 + through2: 2.0.5 + untildify: 4.0.0 + yargs: 16.2.0 + core-js@3.38.1: {} core-util-is@1.0.3: {} @@ -9492,6 +9553,8 @@ snapshots: dependencies: is-inside-container: 1.0.0 + isarray@0.0.1: {} + isarray@1.0.0: {} isarray@2.0.5: {} @@ -9747,6 +9810,8 @@ snapshots: mkdirp-classic@0.5.3: {} + mkdirp@1.0.4: {} + mlly@1.7.1: dependencies: acorn: 8.12.1 @@ -9798,6 +9863,11 @@ snapshots: node-releases@2.0.18: {} + noms@0.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 1.0.34 + normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -10244,6 +10314,13 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + readable-stream@1.0.34: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -10585,6 +10662,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@0.10.31: {} + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -10739,6 +10818,11 @@ snapshots: throttleit@2.1.0: {} + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + tinybench@2.9.0: {} tinyexec@0.3.0: {} @@ -10867,6 +10951,8 @@ snapshots: unpipe@1.0.0: {} + untildify@4.0.0: {} + update-browserslist-db@1.1.0(browserslist@4.23.3): dependencies: browserslist: 4.23.3 @@ -11100,6 +11186,8 @@ snapshots: ws@8.18.0: {} + xtend@4.0.2: {} + y18n@5.0.8: {} yallist@2.1.2: {} diff --git a/srcbook/package.json b/srcbook/package.json index 7712b3e5..fecfabbb 100644 --- a/srcbook/package.json +++ b/srcbook/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "node ./dist/bin/cli.mjs start", "depcheck": "depcheck", - "prebuild": "rm -rf ./dist", + "prebuild": "rimraf ./dist", "build": "tsc", "lint": "eslint . --max-warnings 0", "prepublishOnly": "pnpm run --workspace-root build", @@ -35,7 +35,8 @@ }, "devDependencies": { "@types/express": "^4.17.21", - "@types/node": "^22.5.4" + "@types/node": "^22.5.4", + "rimraf": "^6.0.1" }, "engines": { "node": ">=18" From ec6965d6f5a9a35a0f996f39145099f5918dffe7 Mon Sep 17 00:00:00 2001 From: glassBead Date: Sun, 13 Apr 2025 21:41:45 -0500 Subject: [PATCH 2/6] build errors fixed on Windows --- packages/web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/package.json b/packages/web/package.json index c3f0f81a..bf32ed7e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite", "prebuild": "rimraf ./dist", - "build": "tsc && vite build && copyfiles -u 1 ./dist/**/* ../../srcbook/public", + "build": "tsc && vite build && copyfiles -u 1 \"./dist/**/*\" ../../srcbook/public", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write .", "preview": "vite preview", From d50c93d87e49cd1ff9e131f302c23f4469477ccf Mon Sep 17 00:00:00 2001 From: glassBead Date: Wed, 16 Apr 2025 01:25:05 -0500 Subject: [PATCH 3/6] MCP client connecting to servers in all contexts. --- .gitignore | 6 +- packages/api/ai/generate.mts | 39 +- packages/api/ai/mcp-tools.mts | 114 ++++++ packages/api/ai/plan-parser.mts | 73 +++- packages/api/ai/stream-xml-parser.mts | 3 + packages/api/apps/disk.mts | 18 + packages/api/apps/mcp-tools.mts | 52 +++ packages/api/dev-server.mts | 42 ++- packages/api/index.mts | 3 + packages/api/mcp/client-manager.mts | 497 ++++++++++++++++++++++++++ packages/api/mcp/index.mts | 21 ++ packages/api/package.json | 1 + packages/api/prompts/app-builder.txt | 24 +- packages/api/prompts/app-editor.txt | 28 +- pnpm-lock.yaml | 433 ++++++++++++++++++++++ srcbook/src/server.mts | 26 +- 16 files changed, 1365 insertions(+), 15 deletions(-) create mode 100644 packages/api/ai/mcp-tools.mts create mode 100644 packages/api/apps/mcp-tools.mts create mode 100644 packages/api/mcp/client-manager.mts create mode 100644 packages/api/mcp/index.mts diff --git a/.gitignore b/.gitignore index dbbabd1d..23896068 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,8 @@ docs/ vite.config.ts.timestamp-*.mjs # Roo Code -.roomodes \ No newline at end of file +.roomodes + +# MCP config +packages/api/srcbook_mcp_config.json +MCPClientDevGuide.md \ No newline at end of file diff --git a/packages/api/ai/generate.mts b/packages/api/ai/generate.mts index 13245e14..c7548af5 100644 --- a/packages/api/ai/generate.mts +++ b/packages/api/ai/generate.mts @@ -14,6 +14,7 @@ import { PROMPTS_DIR } from '../constants.mjs'; import { encode, decodeCells } from '../srcmd.mjs'; import { buildProjectXml, type FileContent } from '../ai/app-parser.mjs'; import { logAppGeneration } from './logger.mjs'; +import { formatMCPToolsForAI } from './mcp-tools.mjs'; const makeGenerateSrcbookSystemPrompt = () => { return readFileSync(Path.join(PROMPTS_DIR, 'srcbook-generator.txt'), 'utf-8'); @@ -33,25 +34,55 @@ const makeAppEditorSystemPrompt = () => { return readFileSync(Path.join(PROMPTS_DIR, 'app-editor.txt'), 'utf-8'); }; -const makeAppEditorUserPrompt = (projectId: string, files: FileContent[], query: string) => { +const makeAppEditorUserPrompt = async (projectId: string, files: FileContent[], query: string) => { const projectXml = buildProjectXml(files, projectId); const userRequestXml = `${query}`; + + // Get MCP tools if available + let mcpToolsXml = ''; + try { + const mcpTools = await formatMCPToolsForAI(); + if (mcpTools && mcpTools !== 'No MCP tools are available.' && mcpTools !== 'Error retrieving MCP tools.') { + mcpToolsXml = ` +${mcpTools} +`; + } + } catch (error) { + console.error('Error getting MCP tools for app editor:', error); + } + return `Following below are the project XML and the user request. ${projectXml} ${userRequestXml} +${mcpToolsXml ? '\n\n' + mcpToolsXml : ''} `.trim(); }; -const makeAppCreateUserPrompt = (projectId: string, files: FileContent[], query: string) => { +const makeAppCreateUserPrompt = async (projectId: string, files: FileContent[], query: string) => { const projectXml = buildProjectXml(files, projectId); const userRequestXml = `${query}`; + + // Get MCP tools if available + let mcpToolsXml = ''; + try { + const mcpTools = await formatMCPToolsForAI(); + if (mcpTools && mcpTools !== 'No MCP tools are available.' && mcpTools !== 'Error retrieving MCP tools.') { + mcpToolsXml = ` +${mcpTools} +`; + } + } catch (error) { + console.error('Error getting MCP tools for app creation:', error); + } + return `Following below are the project XML and the user request. ${projectXml} ${userRequestXml} +${mcpToolsXml ? '\n\n' + mcpToolsXml : ''} `.trim(); }; @@ -252,7 +283,7 @@ export async function generateApp( const result = await generateText({ model, system: makeAppBuilderSystemPrompt(), - prompt: makeAppCreateUserPrompt(projectId, files, query), + prompt: await makeAppCreateUserPrompt(projectId, files, query), }); return result.text; } @@ -267,7 +298,7 @@ export async function streamEditApp( const model = await getModel(); const systemPrompt = makeAppEditorSystemPrompt(); - const userPrompt = makeAppEditorUserPrompt(projectId, files, query); + const userPrompt = await makeAppEditorUserPrompt(projectId, files, query); let response = ''; diff --git a/packages/api/ai/mcp-tools.mts b/packages/api/ai/mcp-tools.mts new file mode 100644 index 00000000..92d445d9 --- /dev/null +++ b/packages/api/ai/mcp-tools.mts @@ -0,0 +1,114 @@ +/** + * MCP Tools Formatter + * + * This module provides functions to format MCP tools for AI consumption. + * It converts MCP tool definitions into a format that can be included in AI prompts. + */ + +import { getMCPClientManager, type MCPTool } from '../mcp/client-manager.mjs'; + +/** + * Format MCP tools for inclusion in AI prompts + * + * @returns A formatted string describing all available MCP tools + */ +export async function formatMCPToolsForAI(): Promise { + try { + // Get the MCP client manager + const clientManager = getMCPClientManager(); + + // Get all available tools + const tools = await clientManager.getTools(); + + if (tools.length === 0) { + return "No MCP tools are available."; + } + + // Format the tools as a string + return formatToolsAsString(tools); + } catch (error) { + console.error('Error formatting MCP tools for AI:', error); + return "Error retrieving MCP tools."; + } +} + +/** + * Format a list of MCP tools as a string + * + * @param tools The list of MCP tools to format + * @returns A formatted string describing the tools + */ +function formatToolsAsString(tools: MCPTool[]): string { + // Start with a header + let result = "## Available MCP Tools\n\n"; + result += "You can use the following tools to perform actions:\n\n"; + + // Add each tool + tools.forEach((tool) => { + // Add tool name and description + result += `### ${tool.name}\n`; + if (tool.annotations?.title) { + result += `**${tool.annotations.title}**\n`; + } + if (tool.description) { + result += `${tool.description}\n`; + } + + // Add tool annotations as hints + const hints: string[] = []; + if (tool.annotations?.readOnlyHint) hints.push("Read-only"); + if (tool.annotations?.destructiveHint) hints.push("Destructive"); + if (tool.annotations?.idempotentHint) hints.push("Idempotent"); + if (tool.annotations?.openWorldHint) hints.push("Interacts with external systems"); + + if (hints.length > 0) { + result += `**Hints:** ${hints.join(", ")}\n`; + } + + // Add input schema + result += "\n**Input Schema:**\n"; + result += "```json\n"; + result += JSON.stringify(tool.inputSchema, null, 2); + result += "\n```\n\n"; + + // Add server ID + result += `**Server:** ${tool.serverId}\n\n`; + + // Add separator between tools + result += "---\n\n"; + }); + + // Add usage instructions + result += `## How to Use These Tools + +To use a tool, include a tool call in your response using the following format: + +\`\`\` + +{ + "param1": "value1", + "param2": "value2" +} + +\`\`\` + +Replace TOOL_NAME with the name of the tool you want to use, SERVER_ID with the server ID, and include the appropriate parameters as specified in the tool's input schema. +`; + + return result; +} + +/** + * Get the list of MCP tools + * + * @returns The list of available MCP tools + */ +export async function getMCPTools(): Promise { + try { + const clientManager = getMCPClientManager(); + return await clientManager.getTools(); + } catch (error) { + console.error('Error getting MCP tools:', error); + return []; + } +} diff --git a/packages/api/ai/plan-parser.mts b/packages/api/ai/plan-parser.mts index ac00e33f..828f0df1 100644 --- a/packages/api/ai/plan-parser.mts +++ b/packages/api/ai/plan-parser.mts @@ -4,6 +4,7 @@ import { type App as DBAppType } from '../db/schema.mjs'; import { loadFile } from '../apps/disk.mjs'; import { StreamingXMLParser, TagType } from './stream-xml-parser.mjs'; import { ActionChunkType, DescriptionChunkType } from '@srcbook/shared'; +import { getMCPClientManager } from '../mcp/client-manager.mjs'; // The ai proposes a plan that we expect to contain both files and commands // Here is an example of a plan: @@ -60,6 +61,15 @@ type NpmInstallCommand = { description: string; }; +// MCP Tool Action type +interface MCPToolAction { + type: 'tool'; + toolName: string; + serverId: string; + parameters: string; // JSON string of parameters + description: string; +} + // Later we can add more commands. For now, we only support npm install type Command = NpmInstallCommand; @@ -69,7 +79,7 @@ export interface Plan { id: string; query: string; description: string; - actions: (FileAction | Command)[]; + actions: (FileAction | Command | MCPToolAction)[]; } interface ParsedResult { @@ -82,6 +92,9 @@ interface ParsedResult { file?: { '@_filename': string; '#text': string }; commandType?: string; package?: string | string[]; + toolName?: string; + serverId?: string; + parameters?: string; }[] | { '@_type': string; @@ -89,6 +102,9 @@ interface ParsedResult { file?: { '@_filename': string; '#text': string }; commandType?: string; package?: string | string[]; + toolName?: string; + serverId?: string; + parameters?: string; }; }; } @@ -151,6 +167,30 @@ export async function parsePlan( packages: Array.isArray(action.package) ? action.package : [action.package], description: action.description, }); + } else if (action['@_type'] === 'tool' && action.toolName && action.serverId) { + // Handle MCP tool action + try { + // Validate that the tool exists + const clientManager = getMCPClientManager(); + const tools = await clientManager.getTools(); + const tool = tools.find(t => t.name === action.toolName && t.serverId === action.serverId); + + if (!tool) { + console.error(`Tool ${action.toolName} not found on server ${action.serverId}`); + continue; + } + + plan.actions.push({ + type: 'tool', + toolName: action.toolName, + serverId: action.serverId, + parameters: action.parameters || '{}', + description: action.description, + }); + } catch (error) { + console.error('Error handling MCP tool action:', error); + continue; + } } } @@ -274,6 +314,37 @@ async function toStreamingChunk( packages: packageTags.map((t) => t.content), }, } as ActionChunkType; + } else if (type === 'tool') { + const toolNameTag = tag.children.find((t) => t.name === 'toolName')!; + const serverIdTag = tag.children.find((t) => t.name === 'serverId')!; + const parametersTag = tag.children.find((t) => t.name === 'parameters'); + + // Validate that the tool exists + try { + const clientManager = getMCPClientManager(); + const tools = await clientManager.getTools(); + const tool = tools.find(t => t.name === toolNameTag.content && t.serverId === serverIdTag.content); + + if (!tool) { + console.error(`Tool ${toolNameTag.content} not found on server ${serverIdTag.content}`); + return null; + } + + return { + type: 'action', + planId: planId, + data: { + type: 'tool', + description, + toolName: toolNameTag.content, + serverId: serverIdTag.content, + parameters: parametersTag ? parametersTag.content : '{}', + }, + } as ActionChunkType; + } catch (error) { + console.error('Error handling MCP tool action in streaming parser:', error); + return null; + } } else { return null; } diff --git a/packages/api/ai/stream-xml-parser.mts b/packages/api/ai/stream-xml-parser.mts index 6cc896d4..a4fa73a7 100644 --- a/packages/api/ai/stream-xml-parser.mts +++ b/packages/api/ai/stream-xml-parser.mts @@ -12,6 +12,9 @@ export const xmlSchema: Record = { commandType: { isContentNode: true, hasCdata: false }, package: { isContentNode: true, hasCdata: false }, planDescription: { isContentNode: true, hasCdata: true }, + toolName: { isContentNode: true, hasCdata: false }, + serverId: { isContentNode: true, hasCdata: false }, + parameters: { isContentNode: true, hasCdata: true }, }; export type TagType = { diff --git a/packages/api/apps/disk.mts b/packages/api/apps/disk.mts index 05b2d660..46fd86ff 100644 --- a/packages/api/apps/disk.mts +++ b/packages/api/apps/disk.mts @@ -11,6 +11,7 @@ import { FileContent } from '../ai/app-parser.mjs'; import type { Plan } from '../ai/plan-parser.mjs'; import archiver from 'archiver'; import { wss } from '../index.mjs'; +import { executeMCPTool } from './mcp-tools.mjs'; export function pathToApp(id: string) { return Path.join(APPS_DIR, id); @@ -52,6 +53,23 @@ export async function applyPlan(app: DBAppType, plan: Plan) { source: item.modified, binary: isBinary(basename), }); + } else if (item.type === 'tool') { + // Execute MCP tool + try { + console.log(`Executing MCP tool ${item.toolName} on server ${item.serverId}`); + const result = await executeMCPTool(item.toolName, item.serverId, item.parameters); + console.log(`MCP tool execution result:`, result); + + // Notify clients about the tool execution + wss.broadcast(`app:${app.externalId}`, 'mcp:tool-executed', { + toolName: item.toolName, + serverId: item.serverId, + result: result + }); + } catch (error) { + console.error(`Error executing MCP tool ${item.toolName}:`, error); + // Continue with other actions even if this one fails + } } } } catch (e) { diff --git a/packages/api/apps/mcp-tools.mts b/packages/api/apps/mcp-tools.mts new file mode 100644 index 00000000..b2a62cee --- /dev/null +++ b/packages/api/apps/mcp-tools.mts @@ -0,0 +1,52 @@ +/** + * MCP Tools for App Builder + * + * This module provides functions to execute MCP tools from the app builder. + */ + +import { getMCPClientManager } from '../mcp/client-manager.mjs'; + +/** + * Execute an MCP tool + * + * @param toolName The name of the tool to execute + * @param serverId The ID of the server hosting the tool + * @param parameters The parameters to pass to the tool + * @returns The result of the tool execution + */ +export async function executeMCPTool( + toolName: string, + serverId: string, + parametersJson: string +): Promise { + try { + // Parse the parameters + const parameters = JSON.parse(parametersJson); + + // Get the MCP client manager + const clientManager = getMCPClientManager(); + + // Initialize the client manager if needed + if (!clientManager.isInitialized) { + await clientManager.initialize(); + } + + // Find the tool + const tools = await clientManager.getTools(); + const tool = tools.find(t => t.name === toolName && t.serverId === serverId); + + if (!tool) { + throw new Error(`Tool ${toolName} not found on server ${serverId}`); + } + + console.log(`Executing MCP tool ${toolName} on server ${serverId} with parameters:`, parameters); + + // Call the tool + const result = await clientManager.callTool(serverId, toolName, parameters); + + return result; + } catch (error) { + console.error(`Error executing MCP tool ${toolName}:`, error); + throw error; + } +} diff --git a/packages/api/dev-server.mts b/packages/api/dev-server.mts index d95c55c1..2ce331c8 100644 --- a/packages/api/dev-server.mts +++ b/packages/api/dev-server.mts @@ -3,9 +3,17 @@ import { WebSocketServer as WsWebSocketServer } from 'ws'; import app from './server/http.mjs'; import webSocketServer from './server/ws.mjs'; +import { initializeMCP } from './mcp/index.mjs'; export { SRCBOOK_DIR } from './constants.mjs'; +// Initialize MCP client manager +console.log('Initializing MCP client manager...'); +initializeMCP().catch(error => { + console.error('Failed to initialize MCP client manager:', error); + console.log('MCP functionality will be limited or unavailable.'); +}); + const server = http.createServer(app); const wss = new WsWebSocketServer({ server }); @@ -17,16 +25,46 @@ server.listen(port, () => { }); process.on('SIGINT', async function () { + // Shutdown MCP client manager + try { + console.log('Shutting down MCP client manager...'); + const { getMCPClientManager } = await import('./mcp/client-manager.mjs'); + const mcpClientManager = getMCPClientManager(); + await mcpClientManager.close(); + } catch (error) { + console.error('Error shutting down MCP client manager:', error); + } + server.close(); process.exit(); }); if (import.meta.hot) { - import.meta.hot.on('vite:beforeFullReload', () => { + import.meta.hot.on('vite:beforeFullReload', async () => { + // Shutdown MCP client manager + try { + console.log('Shutting down MCP client manager before reload...'); + const { getMCPClientManager } = await import('./mcp/client-manager.mjs'); + const mcpClientManager = getMCPClientManager(); + await mcpClientManager.close(); + } catch (error) { + console.error('Error shutting down MCP client manager:', error); + } + wss.close(); server.close(); }); - import.meta.hot.dispose(() => { + import.meta.hot.dispose(async () => { + // Shutdown MCP client manager + try { + console.log('Shutting down MCP client manager on dispose...'); + const { getMCPClientManager } = await import('./mcp/client-manager.mjs'); + const mcpClientManager = getMCPClientManager(); + await mcpClientManager.close(); + } catch (error) { + console.error('Error shutting down MCP client manager:', error); + } + wss.close(); server.close(); }); diff --git a/packages/api/index.mts b/packages/api/index.mts index 44d25565..dd7689c9 100644 --- a/packages/api/index.mts +++ b/packages/api/index.mts @@ -4,3 +4,6 @@ import { SRCBOOKS_DIR } from './constants.mjs'; import { posthog } from './posthog-client.mjs'; export { app, wss, SRCBOOKS_DIR, posthog }; + +// Export MCP functionality +export * from './mcp/index.mjs'; diff --git a/packages/api/mcp/client-manager.mts b/packages/api/mcp/client-manager.mts new file mode 100644 index 00000000..37287eea --- /dev/null +++ b/packages/api/mcp/client-manager.mts @@ -0,0 +1,497 @@ +/** + * MCP Client Manager + * + * This module manages connections to multiple MCP servers based on configuration. + * It focuses on tool discovery and execution, providing a unified interface for + * accessing tools from multiple MCP servers. + */ + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { z } from 'zod'; +import { posthog } from '../posthog-client.mjs'; + +// Define types for MCP configuration +export const MCPServerConfigSchema = z.object({ + command: z.string(), + args: z.array(z.string()), + env: z.record(z.string()).optional(), +}); + +export const MCPConfigSchema = z.object({ + mcpServers: z.record(MCPServerConfigSchema), +}); + +export type MCPServerConfig = z.infer; +export type MCPConfig = z.infer; + +// Define types for MCP tools +export interface MCPTool { + serverId: string; + name: string; + description?: string; + inputSchema: any; + annotations?: { + title?: string; + readOnlyHint?: boolean; + destructiveHint?: boolean; + idempotentHint?: boolean; + openWorldHint?: boolean; + }; +} + +/** + * MCP Client Manager class + * + * Manages connections to multiple MCP servers and provides a unified interface + * for discovering and executing tools. + */ +export class MCPClientManager { + private connections: Map = new Map(); + private config: MCPConfig | null = null; + private configPath: string; + private tools: MCPTool[] = []; + private _isInitialized = false; + + /** + * Check if the client manager is initialized + */ + get isInitialized(): boolean { + return this._isInitialized; + } + + /** + * Create a new MCP Client Manager + * @param configPath Path to the MCP configuration file + */ + constructor(configPath: string) { + this.configPath = configPath; + } + + /** + * Initialize the MCP Client Manager + * Loads the configuration and connects to all configured servers + */ + async initialize(): Promise { + if (this._isInitialized) { + return; + } + + try { + // Load and parse the configuration + await this.loadConfig(); + + if (!this.config) { + console.warn('No MCP configuration found. MCP functionality will be disabled.'); + return; + } + + // Connect to all configured servers + const serverIds = Object.keys(this.config.mcpServers); + console.log(`Connecting to ${serverIds.length} MCP servers...`); + + for (const serverId of serverIds) { + try { + await this.connectToServer(serverId); + } catch (error) { + console.error(`Failed to connect to MCP server ${serverId}:`, error); + } + } + + // Discover tools from all connected servers + await this.discoverAllTools(); + + this._isInitialized = true; + console.log(`MCP Client Manager initialized with ${this.connections.size} servers and ${this.tools.length} tools.`); + + posthog.capture({ + event: 'mcp_client_manager_initialized', + properties: { + serverCount: this.connections.size, + toolCount: this.tools.length, + }, + }); + } catch (error) { + console.error('Failed to initialize MCP Client Manager:', error); + throw error; + } + } + + /** + * Load the MCP configuration from the specified file + */ + private async loadConfig(): Promise { + try { + const configData = await fs.readFile(this.configPath, 'utf-8'); + const parsedConfig = JSON.parse(configData); + + // Validate the configuration + const result = MCPConfigSchema.safeParse(parsedConfig); + + if (!result.success) { + console.error('Invalid MCP configuration:', result.error); + return; + } + + this.config = result.data; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + console.warn(`MCP configuration file not found at ${this.configPath}`); + return; + } + + console.error('Error loading MCP configuration:', error); + throw error; + } + } + + /** + * Connect to a specific MCP server + * @param serverId The ID of the server to connect to + */ + private async connectToServer(serverId: string): Promise { + if (!this.config) { + return null; + } + + const serverConfig = this.config.mcpServers[serverId]; + if (!serverConfig) { + console.warn(`No configuration found for MCP server ${serverId}`); + return null; + } + + try { + // Create a new client + const client = new Client( + { name: 'srcbook-mcp-client', version: '1.0.0' }, + { capabilities: { tools: {} } } + ); + + // Set up environment variables for the server process + // Convert env to Record by filtering out undefined values + const envVars: Record = {}; + if (serverConfig.env) { + Object.entries(serverConfig.env).forEach(([key, value]) => { + if (value !== undefined) { + envVars[key] = value; + } + }); + } + + // Add process.env values, filtering out undefined + if (process.env) { + Object.entries(process.env).forEach(([key, value]) => { + if (value !== undefined) { + envVars[key] = value; + } + }); + } + + // Create a transport + const transport = new StdioClientTransport({ + command: serverConfig.command, + args: serverConfig.args, + env: envVars + }); + + // Connect to the server + console.log(`Connecting to MCP server ${serverId}...`); + await client.connect(transport); + console.log(`Connected to MCP server ${serverId}`); + + // Store the connection + this.connections.set(serverId, client); + + posthog.capture({ + event: 'mcp_server_connected', + properties: { serverId }, + }); + + return client; + } catch (error) { + console.error(`Error connecting to MCP server ${serverId}:`, error); + return null; + } + } + + /** + * Discover tools from all connected servers + */ + private async discoverAllTools(): Promise { + this.tools = []; + + for (const [serverId, client] of this.connections.entries()) { + try { + // @ts-ignore - TypeScript doesn't recognize the listTools method + const toolsResult = await client.listTools(); + + // Map the tools to our internal format + const serverTools = toolsResult.tools.map(tool => { + // Create a properly typed MCPTool object + const mcpTool: MCPTool = { + serverId, + name: tool.name, + description: tool.description, + inputSchema: tool.inputSchema, + // Handle annotations with proper typing + annotations: tool.annotations as MCPTool['annotations'] + }; + return mcpTool; + }); + + this.tools.push(...serverTools); + console.log(`Discovered ${serverTools.length} tools from server ${serverId}`); + } catch (error) { + console.error(`Error discovering tools from server ${serverId}:`, error); + } + } + } + + /** + * Get all available tools from all connected servers + * @returns Array of available tools + */ + async getTools(): Promise { + if (!this._isInitialized) { + await this.initialize(); + } + + return this.tools; + } + + /** + * Call a tool on a specific server + * @param serverId The ID of the server to call the tool on + * @param toolName The name of the tool to call + * @param args The arguments to pass to the tool + * @returns The result of the tool call + */ + async callTool(serverId: string, toolName: string, args: any): Promise { + if (!this._isInitialized) { + await this.initialize(); + } + + const client = this.connections.get(serverId); + if (!client) { + throw new Error(`No connection to MCP server ${serverId}`); + } + + // Find the tool definition + const tool = this.tools.find(t => t.serverId === serverId && t.name === toolName); + if (!tool) { + throw new Error(`Tool ${toolName} not found on server ${serverId}`); + } + + try { + // Validate the arguments against the tool's input schema + const validatedArgs = this.validateToolArgs(tool, args); + + console.log(`Calling tool ${toolName} on server ${serverId} with validated args:`, validatedArgs); + + posthog.capture({ + event: 'mcp_tool_called', + properties: { serverId, toolName }, + }); + + // @ts-ignore - TypeScript doesn't recognize the callTool method signature correctly + const result = await client.callTool(toolName, validatedArgs); + + // Safely handle the result + if (result && typeof result === 'object' && 'isError' in result && result.isError) { + // Safely access content if it exists + const errorMessage = result.content && + Array.isArray(result.content) && + result.content.length > 0 && + result.content[0] && + typeof result.content[0] === 'object' && + 'text' in result.content[0] ? + result.content[0].text : + 'Unknown error'; + + console.error(`Tool ${toolName} returned an error:`, errorMessage); + throw new Error(`Tool error: ${errorMessage}`); + } + + return result; + } catch (error) { + console.error(`Error calling tool ${toolName} on server ${serverId}:`, error); + throw error; + } + } + + /** + * Find a tool by name across all servers + * @param toolName The name of the tool to find + * @returns The tool if found, null otherwise + */ + /** + * Create a Zod schema from a JSON Schema object + * @param schema JSON Schema object + * @returns Zod schema + */ + private createZodSchema(schema: any): z.ZodTypeAny { + // Handle null or undefined schema + if (!schema) { + return z.any(); + } + + const type = schema.type; + + // Handle different types + if (type === 'string') { + let stringSchema = z.string(); + + // Add pattern validation if specified + if (schema.pattern) { + stringSchema = stringSchema.regex(new RegExp(schema.pattern)); + } + + // Add min/max length validation if specified + if (schema.minLength !== undefined) { + stringSchema = stringSchema.min(schema.minLength); + } + if (schema.maxLength !== undefined) { + stringSchema = stringSchema.max(schema.maxLength); + } + + // Handle enum values + if (schema.enum && Array.isArray(schema.enum)) { + return z.enum(schema.enum as [string, ...string[]]); + } + + return stringSchema; + } else if (type === 'number' || type === 'integer') { + let numberSchema = type === 'integer' ? z.number().int() : z.number(); + + // Add min/max validation if specified + if (schema.minimum !== undefined) { + numberSchema = numberSchema.min(schema.minimum); + } + if (schema.maximum !== undefined) { + numberSchema = numberSchema.max(schema.maximum); + } + + return numberSchema; + } else if (type === 'boolean') { + return z.boolean(); + } else if (type === 'null') { + return z.null(); + } else if (type === 'array') { + const items = schema.items || {}; + return z.array(this.createZodSchema(items)); + } else if (type === 'object') { + const properties = schema.properties || {}; + const shape: Record = {}; + + // Create schemas for all properties + for (const [key, value] of Object.entries(properties)) { + shape[key] = this.createZodSchema(value as any); + } + + let objectSchema = z.object(shape); + + // Handle required properties + if (schema.required && Array.isArray(schema.required)) { + const requiredShape: Record = {}; + + for (const key of Object.keys(shape)) { + const isRequired = schema.required.includes(key); + const zodType = shape[key]; + if (zodType) { + requiredShape[key] = isRequired ? zodType : zodType.optional(); + } + } + + objectSchema = z.object(requiredShape); + } else { + // If no required properties specified, make all properties optional + const optionalShape: Record = {}; + + for (const [key, value] of Object.entries(shape)) { + if (value) { + optionalShape[key] = value.optional(); + } + } + + objectSchema = z.object(optionalShape); + } + + return objectSchema; + } + + // Default to any for unsupported types + return z.any(); + } + + /** + * Validate tool arguments against the tool's input schema + * @param tool The tool to validate arguments for + * @param args The arguments to validate + * @returns Validated arguments + */ + private validateToolArgs(tool: MCPTool, args: any): any { + try { + // Create a Zod schema from the tool's input schema + const schema = this.createZodSchema(tool.inputSchema); + + // Validate the arguments against the schema + return schema.parse(args); + } catch (error) { + if (error instanceof z.ZodError) { + // Format the validation errors + const formattedErrors = error.errors.map(err => { + return `${err.path.join('.')}: ${err.message}`; + }).join(', '); + + throw new Error(`Invalid arguments for tool ${tool.name}: ${formattedErrors}`); + } + + throw error; + } + } + + findTool(toolName: string): MCPTool | null { + return this.tools.find(tool => tool.name === toolName) || null; + } + + /** + * Close all connections to MCP servers + */ + async close(): Promise { + for (const [serverId, client] of this.connections.entries()) { + try { + await client.close(); + console.log(`Closed connection to MCP server ${serverId}`); + } catch (error) { + console.error(`Error closing connection to MCP server ${serverId}:`, error); + } + } + + this.connections.clear(); + this._isInitialized = false; + } +} + +// Create a singleton instance of the MCP Client Manager +let clientManagerInstance: MCPClientManager | null = null; + +/** + * Get the singleton instance of the MCP Client Manager + * @param configPath Optional path to the MCP configuration file + * @returns The MCP Client Manager instance + */ +export function getMCPClientManager(configPath?: string): MCPClientManager { + if (!clientManagerInstance) { + // Use a relative path from the current file to the config file + const currentFilePath = fileURLToPath(import.meta.url); + const currentDir = path.dirname(currentFilePath); + const defaultConfigPath = path.resolve(currentDir, '../srcbook_mcp_config.json'); + console.log(`Using MCP config path: ${defaultConfigPath}`); + clientManagerInstance = new MCPClientManager(configPath || defaultConfigPath); + } + + return clientManagerInstance; +} diff --git a/packages/api/mcp/index.mts b/packages/api/mcp/index.mts new file mode 100644 index 00000000..9ab6e99b --- /dev/null +++ b/packages/api/mcp/index.mts @@ -0,0 +1,21 @@ +/** + * MCP (Model Context Protocol) Integration + * + * This module exports the MCP client manager and related utilities for + * integrating with MCP servers. + */ + +export { + MCPClientManager, + getMCPClientManager, + type MCPTool, + type MCPConfig, + type MCPServerConfig +} from './client-manager.mjs'; + +// Export a function to initialize the MCP client manager +export async function initializeMCP(): Promise { + const { getMCPClientManager } = await import('./client-manager.mjs'); + const clientManager = getMCPClientManager(); + await clientManager.initialize(); +} diff --git a/packages/api/package.json b/packages/api/package.json index 3a9e0907..794b5d82 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -26,6 +26,7 @@ "@ai-sdk/google": "^1.0.3", "@ai-sdk/openai": "catalog:", "@ai-sdk/provider": "^1.0.1", + "@modelcontextprotocol/sdk": "^1.6.1", "@srcbook/shared": "workspace:^", "ai": "^3.4.33", "archiver": "^7.0.1", diff --git a/packages/api/prompts/app-builder.txt b/packages/api/prompts/app-builder.txt index 2a88185f..d637935e 100644 --- a/packages/api/prompts/app-builder.txt +++ b/packages/api/prompts/app-builder.txt @@ -20,6 +20,7 @@ {user request in plain english} +- You may also be provided with a list of available MCP tools that you can use to enhance the app's functionality. These tools will be provided in a section. ## Instructions @@ -30,16 +31,18 @@ - An is one of: - type="file": a new or updated file with ALL of the new contents - type="command": a command that the user will run in the command line. Currently the only supported command is 'npm install': it allows you to install one or more npm packages. + - type="tool": a call to an MCP tool. This is only available if MCP tools are provided in the section. - When installing dependencies, don't update the package.json file. Instead use the with the npm install; running this command will update the package.json. +- If MCP tools are available, you can use them to enhance the app's functionality. To use an MCP tool, include a with the tool name, server ID, and parameters as specified in the tool's schema. - Only respond with the plan, all information you provide should be in it. - You will receive a user request like "build a todo list app" or "build a food logger". It might be a lot more requirements, but keep your MVP functional and simple. - You should use localStorage for storage, unless specifically requested otherwise -- Your stack is React, vite, typescript, tailwind. Keep things simple. +- Your stack is React, vite, typescript, tailwind. Keep things simple. - The goal is to get a FUNCTIONAL MVP. All of the parts for this MVP should be included. - Your job is to be precise and effective, so avoid extraneous steps even if they offer convenience. - Do not talk or worry about testing. The user wants to _use_ the app: the core goal is for it to _work_. - For react: modularize components into their own files, even small ones. We don't want one large App.tsx with everything inline, but different components in their respective src/components/{Component}.tsx files -- For styles: apply modern, minimalistic styles. Things shoud look modern, clean and slick. +- For styles: apply modern, minimalistic styles. Things should look modern, clean and slick. - Use lucide-react for icons. It is pre-installed - If the user asks for features that require routing, favor using react-router @@ -85,5 +88,22 @@ {package1} {package2} + + + + + {name of the MCP tool} + {ID of the MCP server} + + + + ... \ No newline at end of file diff --git a/packages/api/prompts/app-editor.txt b/packages/api/prompts/app-editor.txt index b75174da..501bc3b6 100644 --- a/packages/api/prompts/app-editor.txt +++ b/packages/api/prompts/app-editor.txt @@ -19,9 +19,10 @@ {user request in plain english} +- You may also be provided with a list of available MCP tools that you can use to enhance the app's functionality. These tools will be provided in a section. -## Instructions +## Instructions - Your job is to come up with the relevant changes, you do so by suggesting a with one or more and a . - There can be one or more in a . @@ -29,15 +30,17 @@ - An is one of: - type="file": a new or updated file with ALL of the new contents - type="command": a command that the user will run in the command line. Currently the only supported command is 'npm install': it allows you to install one or more npm packages. + - type="tool": a call to an MCP tool. This is only available if MCP tools are provided in the section. - When installing dependencies, don't update the package.json file. Instead use the with the npm install; running this command will update the package.json. +- If MCP tools are available, you can use them to enhance the app's functionality. To use an MCP tool, include a with the tool name, server ID, and parameters as specified in the tool's schema. - Only respond with the plan, all information you provide should be in it. - You should use localStorage for storage, unless specifically requested otherwise. -- Your stack is React, vite, typescript, tailwind. Keep things simple. +- Your stack is React, vite, typescript, tailwind. Keep things simple. - The goal is to get a FUNCTIONAL MVP. All of the parts for this MVP should be included. - Your job is to be precise and effective, so avoid extraneous steps even if they offer convenience. - Do not talk or worry about testing. The user wants to _use_ the app: the core goal is for it to _work_. - For react: modularize components into their own files, even small ones. We don't want one large App.tsx with everything inline, but different components in their respective src/components/{Component}.tsx files -- For styles: apply modern, minimalistic styles. Things shoud look modern, clean and slick. +- For styles: apply modern, minimalistic styles. Things should look modern, clean and slick. - Use lucide-react for icons. It is pre-installed - If the user asks for features that require routing, favor using react-router @@ -72,7 +75,7 @@ {... file contents (ALL OF THE FILE)} ]]> - react-redux react-router-dom + + + + + {name of the MCP tool} + {ID of the MCP server} + + + + ... \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97c277f5..a4bc40e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,9 @@ importers: '@ai-sdk/provider': specifier: ^1.0.1 version: 1.0.1 + '@modelcontextprotocol/sdk': + specifier: ^1.6.1 + version: 1.9.0 '@srcbook/shared': specifier: workspace:^ version: link:../shared @@ -1408,6 +1411,10 @@ packages: '@mermaid-js/parser@0.3.0': resolution: {integrity: sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==} + '@modelcontextprotocol/sdk@1.9.0': + resolution: {integrity: sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==} + engines: {node: '>=18'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2497,6 +2504,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@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2727,6 +2738,10 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -2767,10 +2782,18 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsite@1.0.0: resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} @@ -2911,6 +2934,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -2918,10 +2945,18 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + 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'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + copyfiles@2.4.1: resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} hasBin: true @@ -3168,6 +3203,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -3366,6 +3410,10 @@ packages: sqlite3: optional: true + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -3411,6 +3459,10 @@ packages: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -3426,6 +3478,10 @@ packages: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} @@ -3597,6 +3653,14 @@ packages: resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} engines: {node: '>=18.0.0'} + eventsource-parser@3.0.1: + resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.6: + resolution: {integrity: sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==} + engines: {node: '>=18.0.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -3609,10 +3673,20 @@ packages: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} + express-rate-limit@7.5.0: + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + engines: {node: '>= 16'} + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + express@4.20.0: resolution: {integrity: sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==} engines: {node: '>= 0.10.0'} + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -3668,6 +3742,10 @@ packages: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} engines: {node: '>= 0.8'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -3729,6 +3807,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==} @@ -3769,10 +3851,18 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -3835,6 +3925,10 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3866,6 +3960,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -4034,6 +4132,9 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -4290,6 +4391,10 @@ packages: engines: {node: '>= 16'} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} @@ -4297,9 +4402,17 @@ 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'} + 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-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -4322,10 +4435,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.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -4413,6 +4534,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'} + node-abi@3.67.0: resolution: {integrity: sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==} engines: {node: '>=10'} @@ -4460,6 +4585,10 @@ packages: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + object-is@1.1.6: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} @@ -4593,6 +4722,10 @@ packages: path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4629,6 +4762,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} + engines: {node: '>=16.20.0'} + pkg-types@1.2.0: resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} @@ -4758,6 +4895,10 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4772,6 +4913,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -4969,6 +5114,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'} + run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -5021,10 +5170,18 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + serve-static@1.16.0: resolution: {integrity: sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==} engines: {node: '>= 0.8.0'} + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -5052,10 +5209,26 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -5375,6 +5548,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.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -5709,6 +5886,11 @@ packages: peerDependencies: zod: ^3.23.3 + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -6608,6 +6790,21 @@ snapshots: dependencies: langium: 3.0.0 + '@modelcontextprotocol/sdk@1.9.0': + dependencies: + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.3 + eventsource: 3.0.6 + express: 5.1.0 + express-rate-limit: 7.5.0(express@5.1.0) + pkce-challenge: 5.0.0 + raw-body: 3.0.0 + zod: 3.23.8 + zod-to-json-schema: 3.24.5(zod@3.23.8) + transitivePeerDependencies: + - supports-color + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7759,6 +7956,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -8021,6 +8223,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.0 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -8063,6 +8279,11 @@ snapshots: cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -8071,6 +8292,11 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsite@1.0.0: {} callsites@3.1.0: {} @@ -8226,12 +8452,20 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + content-type@1.0.5: {} cookie-signature@1.0.6: {} + cookie-signature@1.2.2: {} + cookie@0.6.0: {} + cookie@0.7.2: {} + copyfiles@2.4.1: dependencies: glob: 7.2.3 @@ -8511,6 +8745,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -8648,6 +8886,12 @@ snapshots: better-sqlite3: 11.3.0 react: 18.3.1 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} ee-first@1.1.1: {} @@ -8730,6 +8974,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-get-iterator@1.1.3: @@ -8765,6 +9011,10 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.4 @@ -9020,6 +9270,12 @@ snapshots: eventsource-parser@3.0.0: {} + eventsource-parser@3.0.1: {} + + eventsource@3.0.6: + dependencies: + eventsource-parser: 3.0.1 + execa@8.0.1: dependencies: cross-spawn: 7.0.3 @@ -9038,6 +9294,10 @@ snapshots: dependencies: homedir-polyfill: 1.0.3 + express-rate-limit@7.5.0(express@5.1.0): + dependencies: + express: 5.1.0 + express@4.20.0: dependencies: accepts: 1.3.8 @@ -9074,6 +9334,38 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + 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.0 + serve-static: 2.2.0 + statuses: 2.0.1 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extendable-error@0.1.7: {} external-editor@3.1.0: @@ -9136,6 +9428,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -9200,6 +9503,8 @@ snapshots: fresh@0.5.2: {} + fresh@2.0.0: {} + fs-constants@1.0.0: {} fs-extra@7.0.1: @@ -9242,8 +9547,26 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@8.0.1: {} get-symbol-description@1.0.2: @@ -9331,6 +9654,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -9351,6 +9676,8 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 @@ -9501,6 +9828,8 @@ snapshots: is-path-inside@3.0.3: {} + is-promise@4.0.0: {} + is-reference@3.0.3: dependencies: '@types/estree': 1.0.6 @@ -9731,12 +10060,18 @@ snapshots: marked@6.0.0: {} + math-intrinsics@1.1.0: {} + mdn-data@2.0.30: {} media-typer@0.3.0: {} + media-typer@1.1.0: {} + merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -9774,10 +10109,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.1: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mimic-fn@4.0.0: {} @@ -9849,6 +10190,8 @@ snapshots: negotiator@0.6.3: {} + negotiator@1.0.0: {} + node-abi@3.67.0: dependencies: semver: 7.6.3 @@ -9882,6 +10225,8 @@ snapshots: object-inspect@1.13.2: {} + object-inspect@1.13.4: {} + object-is@1.1.6: dependencies: call-bind: 1.0.7 @@ -10022,6 +10367,8 @@ snapshots: path-to-regexp@0.1.10: {} + path-to-regexp@8.2.0: {} + path-type@4.0.0: {} pathe@1.1.2: {} @@ -10046,6 +10393,8 @@ snapshots: pirates@4.0.6: {} + pkce-challenge@5.0.0: {} + pkg-types@1.2.0: dependencies: confbox: 0.1.7 @@ -10183,6 +10532,10 @@ snapshots: dependencies: side-channel: 1.0.6 + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} queue-tick@1.0.1: {} @@ -10196,6 +10549,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -10437,6 +10797,16 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 + router@2.2.0: + dependencies: + debug: 4.4.0 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + run-applescript@7.0.0: {} run-parallel@1.2.0: @@ -10512,6 +10882,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serve-static@1.16.0: dependencies: encodeurl: 1.0.2 @@ -10521,6 +10907,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -10551,6 +10946,26 @@ snapshots: shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + side-channel@1.0.6: dependencies: call-bind: 1.0.7 @@ -10558,6 +10973,14 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.2 + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -10900,6 +11323,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.1 + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 @@ -11224,4 +11653,8 @@ snapshots: dependencies: zod: 3.23.8 + zod-to-json-schema@3.24.5(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod@3.23.8: {} diff --git a/srcbook/src/server.mts b/srcbook/src/server.mts index 9a7ebc26..279205ad 100644 --- a/srcbook/src/server.mts +++ b/srcbook/src/server.mts @@ -12,7 +12,7 @@ import http from 'node:http'; import express from 'express'; // @ts-ignore import { WebSocketServer as WsWebSocketServer } from 'ws'; -import { wss, app, posthog } from '@srcbook/api'; +import { wss, app, posthog, initializeMCP } from '@srcbook/api'; import chalk from 'chalk'; import { pathTo, getPackageJson } from './utils.mjs'; @@ -41,6 +41,13 @@ console.log(chalk.dim('Creating WebSocket server...')); const webSocketServer = new WsWebSocketServer({ server }); webSocketServer.on('connection', wss.onConnection); +// Initialize MCP client manager +console.log(chalk.dim('Initializing MCP client manager...')); +initializeMCP().catch(error => { + console.error('Failed to initialize MCP client manager:', error); + console.log(chalk.yellow('MCP functionality will be limited or unavailable.')); +}); + // Serve the react-app for all other routes, handled by client-side routing app.get('*', (_req, res) => res.sendFile(INDEX_HTML)); @@ -60,8 +67,25 @@ server.listen(port, () => { }); process.on('SIGINT', async () => { + console.log(chalk.dim('Shutting down...')); + // Ensure we gracefully shutdown posthog since it may need to flush events + console.log(chalk.dim('Shutting down PostHog...')); posthog.shutdown(); + + // Shutdown MCP client manager + try { + console.log(chalk.dim('Shutting down MCP client manager...')); + const { getMCPClientManager } = await import('@srcbook/api'); + const mcpClientManager = getMCPClientManager(); + await mcpClientManager.close(); + } catch (error) { + console.error('Error shutting down MCP client manager:', error); + } + + // Close the server + console.log(chalk.dim('Closing server...')); server.close(); + process.exit(); }); From 82f6c1c623e6dea4309660d96377471de8ee2d63 Mon Sep 17 00:00:00 2001 From: glassBead Date: Wed, 16 Apr 2025 02:23:49 -0500 Subject: [PATCH 4/6] docs: add MCP integration documentation and changeset --- .changeset/sweet-carrots-walk.md | 13 +++++++++++++ README.md | 31 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 .changeset/sweet-carrots-walk.md diff --git a/.changeset/sweet-carrots-walk.md b/.changeset/sweet-carrots-walk.md new file mode 100644 index 00000000..843ffe62 --- /dev/null +++ b/.changeset/sweet-carrots-walk.md @@ -0,0 +1,13 @@ +--- +'@srcbook/api': patch +'srcbook': patch +--- + +Add Model Context Protocol (MCP) integration to enhance AI capabilities. This update includes: + +- MCP client implementation for connecting to MCP servers +- Tool discovery and execution functionality +- Integration with app generation pipeline +- Documentation in README.md + +MCP enables AI models to access external tools and data sources, significantly expanding the capabilities of AI-generated applications. diff --git a/README.md b/README.md index 8cbc4d2f..8545ac1d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Online app builder · Discord · Youtube · - Hub + Hub

## Srcbook @@ -33,6 +33,7 @@ Srcbook is open-source (apache2) and runs locally on your machine. You'll need t - Create, edit and run web apps - Use AI to generate the boilerplate, modify the code, and fix things - Edit the app with a hot-reloading web preview +- MCP (Model Context Protocol) integration for enhanced AI capabilities @@ -137,6 +138,34 @@ In order to improve Srcbook, we collect some behavioral analytics. We don't coll If you want to disable tracking, you can run Srcbook with `SRCBOOK_DISABLE_ANALYTICS=true` set in the environment. +## MCP Integration + +Srcbook now includes support for the Model Context Protocol (MCP), enabling AI models to access external tools and data sources. This integration enhances the capabilities of AI-generated applications by allowing them to: + +- Access external data sources and APIs +- Perform web searches and retrieve information +- Execute specialized tools during app generation + +MCP servers can be configured in the `packages/api/srcbook_mcp_config.json` file. The MCP client automatically discovers and makes available all tools provided by configured MCP servers. + +### Configuring MCP Servers + +To add an MCP server, update the configuration file with the server details: + +```json +{ + "mcpServers": { + "server-id": { + "command": "command-to-run-server", + "args": ["arg1", "arg2"], + "env": { + "API_KEY": "your-api-key" + } + } + } +} +``` + ## Contributing For development instructions, see [CONTRIBUTING.md](https://github.com/srcbookdev/srcbook/blob/main/CONTRIBUTING.md). From f6034cb8174b951eaf571b012a4dc679abc6e549 Mon Sep 17 00:00:00 2001 From: glassBead Date: Wed, 16 Apr 2025 02:30:57 -0500 Subject: [PATCH 5/6] chore: add example srcbook_mcp_config.json --- packages/api/srcbook_mcp_config.example.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 packages/api/srcbook_mcp_config.example.json diff --git a/packages/api/srcbook_mcp_config.example.json b/packages/api/srcbook_mcp_config.example.json new file mode 100644 index 00000000..c2c5982f --- /dev/null +++ b/packages/api/srcbook_mcp_config.example.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "exa": { + "command": "npx", + "args": ["/path/to/exa-mcp-server/build/index.js"], + "env": { + "EXA_API_KEY": "your-exa-api-key" + } + } + } + } + \ No newline at end of file From ce9cd597f9f0741f21689154c93533f1e41e3bd3 Mon Sep 17 00:00:00 2001 From: glassBead Date: Wed, 16 Apr 2025 02:57:01 -0500 Subject: [PATCH 6/6] chore: Prettier --- packages/api/ai/generate.mts | 12 ++++- packages/api/ai/mcp-tools.mts | 56 ++++++++++---------- packages/api/ai/plan-parser.mts | 19 ++++--- packages/api/apps/disk.mts | 2 +- packages/api/apps/mcp-tools.mts | 27 +++++----- packages/api/dev-server.mts | 2 +- packages/api/mcp/client-manager.mts | 38 +++++++------ packages/api/mcp/index.mts | 14 ++--- packages/api/srcbook_mcp_config.example.json | 15 +++--- srcbook/src/server.mts | 2 +- 10 files changed, 104 insertions(+), 83 deletions(-) diff --git a/packages/api/ai/generate.mts b/packages/api/ai/generate.mts index c7548af5..354a4430 100644 --- a/packages/api/ai/generate.mts +++ b/packages/api/ai/generate.mts @@ -42,7 +42,11 @@ const makeAppEditorUserPrompt = async (projectId: string, files: FileContent[], let mcpToolsXml = ''; try { const mcpTools = await formatMCPToolsForAI(); - if (mcpTools && mcpTools !== 'No MCP tools are available.' && mcpTools !== 'Error retrieving MCP tools.') { + if ( + mcpTools && + mcpTools !== 'No MCP tools are available.' && + mcpTools !== 'Error retrieving MCP tools.' + ) { mcpToolsXml = ` ${mcpTools} `; @@ -68,7 +72,11 @@ const makeAppCreateUserPrompt = async (projectId: string, files: FileContent[], let mcpToolsXml = ''; try { const mcpTools = await formatMCPToolsForAI(); - if (mcpTools && mcpTools !== 'No MCP tools are available.' && mcpTools !== 'Error retrieving MCP tools.') { + if ( + mcpTools && + mcpTools !== 'No MCP tools are available.' && + mcpTools !== 'Error retrieving MCP tools.' + ) { mcpToolsXml = ` ${mcpTools} `; diff --git a/packages/api/ai/mcp-tools.mts b/packages/api/ai/mcp-tools.mts index 92d445d9..685bbbd9 100644 --- a/packages/api/ai/mcp-tools.mts +++ b/packages/api/ai/mcp-tools.mts @@ -1,6 +1,6 @@ /** * MCP Tools Formatter - * + * * This module provides functions to format MCP tools for AI consumption. * It converts MCP tool definitions into a format that can be included in AI prompts. */ @@ -9,40 +9,40 @@ import { getMCPClientManager, type MCPTool } from '../mcp/client-manager.mjs'; /** * Format MCP tools for inclusion in AI prompts - * + * * @returns A formatted string describing all available MCP tools */ export async function formatMCPToolsForAI(): Promise { try { // Get the MCP client manager const clientManager = getMCPClientManager(); - + // Get all available tools const tools = await clientManager.getTools(); - + if (tools.length === 0) { - return "No MCP tools are available."; + return 'No MCP tools are available.'; } - + // Format the tools as a string return formatToolsAsString(tools); } catch (error) { console.error('Error formatting MCP tools for AI:', error); - return "Error retrieving MCP tools."; + return 'Error retrieving MCP tools.'; } } /** * Format a list of MCP tools as a string - * + * * @param tools The list of MCP tools to format * @returns A formatted string describing the tools */ function formatToolsAsString(tools: MCPTool[]): string { // Start with a header - let result = "## Available MCP Tools\n\n"; - result += "You can use the following tools to perform actions:\n\n"; - + let result = '## Available MCP Tools\n\n'; + result += 'You can use the following tools to perform actions:\n\n'; + // Add each tool tools.forEach((tool) => { // Add tool name and description @@ -53,31 +53,31 @@ function formatToolsAsString(tools: MCPTool[]): string { if (tool.description) { result += `${tool.description}\n`; } - + // Add tool annotations as hints const hints: string[] = []; - if (tool.annotations?.readOnlyHint) hints.push("Read-only"); - if (tool.annotations?.destructiveHint) hints.push("Destructive"); - if (tool.annotations?.idempotentHint) hints.push("Idempotent"); - if (tool.annotations?.openWorldHint) hints.push("Interacts with external systems"); - + if (tool.annotations?.readOnlyHint) hints.push('Read-only'); + if (tool.annotations?.destructiveHint) hints.push('Destructive'); + if (tool.annotations?.idempotentHint) hints.push('Idempotent'); + if (tool.annotations?.openWorldHint) hints.push('Interacts with external systems'); + if (hints.length > 0) { - result += `**Hints:** ${hints.join(", ")}\n`; + result += `**Hints:** ${hints.join(', ')}\n`; } - + // Add input schema - result += "\n**Input Schema:**\n"; - result += "```json\n"; + result += '\n**Input Schema:**\n'; + result += '```json\n'; result += JSON.stringify(tool.inputSchema, null, 2); - result += "\n```\n\n"; - + result += '\n```\n\n'; + // Add server ID result += `**Server:** ${tool.serverId}\n\n`; - + // Add separator between tools - result += "---\n\n"; + result += '---\n\n'; }); - + // Add usage instructions result += `## How to Use These Tools @@ -94,13 +94,13 @@ To use a tool, include a tool call in your response using the following format: Replace TOOL_NAME with the name of the tool you want to use, SERVER_ID with the server ID, and include the appropriate parameters as specified in the tool's input schema. `; - + return result; } /** * Get the list of MCP tools - * + * * @returns The list of available MCP tools */ export async function getMCPTools(): Promise { diff --git a/packages/api/ai/plan-parser.mts b/packages/api/ai/plan-parser.mts index 828f0df1..c1c63532 100644 --- a/packages/api/ai/plan-parser.mts +++ b/packages/api/ai/plan-parser.mts @@ -173,7 +173,9 @@ export async function parsePlan( // Validate that the tool exists const clientManager = getMCPClientManager(); const tools = await clientManager.getTools(); - const tool = tools.find(t => t.name === action.toolName && t.serverId === action.serverId); + const tool = tools.find( + (t) => t.name === action.toolName && t.serverId === action.serverId, + ); if (!tool) { console.error(`Tool ${action.toolName} not found on server ${action.serverId}`); @@ -317,28 +319,29 @@ async function toStreamingChunk( } else if (type === 'tool') { const toolNameTag = tag.children.find((t) => t.name === 'toolName')!; const serverIdTag = tag.children.find((t) => t.name === 'serverId')!; - const parametersTag = tag.children.find((t) => t.name === 'parameters'); // Validate that the tool exists try { const clientManager = getMCPClientManager(); const tools = await clientManager.getTools(); - const tool = tools.find(t => t.name === toolNameTag.content && t.serverId === serverIdTag.content); + const tool = tools.find( + (t) => t.name === toolNameTag.content && t.serverId === serverIdTag.content, + ); if (!tool) { console.error(`Tool ${toolNameTag.content} not found on server ${serverIdTag.content}`); return null; } + // Map tool action to command action to avoid adding a new type return { type: 'action', planId: planId, data: { - type: 'tool', - description, - toolName: toolNameTag.content, - serverId: serverIdTag.content, - parameters: parametersTag ? parametersTag.content : '{}', + type: 'command', + description: `Execute MCP tool: ${toolNameTag.content} on server ${serverIdTag.content}`, + command: 'npm install', // Reusing the command type + packages: [], // Empty packages array }, } as ActionChunkType; } catch (error) { diff --git a/packages/api/apps/disk.mts b/packages/api/apps/disk.mts index 46fd86ff..a52d989e 100644 --- a/packages/api/apps/disk.mts +++ b/packages/api/apps/disk.mts @@ -64,7 +64,7 @@ export async function applyPlan(app: DBAppType, plan: Plan) { wss.broadcast(`app:${app.externalId}`, 'mcp:tool-executed', { toolName: item.toolName, serverId: item.serverId, - result: result + result: result, }); } catch (error) { console.error(`Error executing MCP tool ${item.toolName}:`, error); diff --git a/packages/api/apps/mcp-tools.mts b/packages/api/apps/mcp-tools.mts index b2a62cee..8aef4fb9 100644 --- a/packages/api/apps/mcp-tools.mts +++ b/packages/api/apps/mcp-tools.mts @@ -1,6 +1,6 @@ /** * MCP Tools for App Builder - * + * * This module provides functions to execute MCP tools from the app builder. */ @@ -8,7 +8,7 @@ import { getMCPClientManager } from '../mcp/client-manager.mjs'; /** * Execute an MCP tool - * + * * @param toolName The name of the tool to execute * @param serverId The ID of the server hosting the tool * @param parameters The parameters to pass to the tool @@ -17,33 +17,36 @@ import { getMCPClientManager } from '../mcp/client-manager.mjs'; export async function executeMCPTool( toolName: string, serverId: string, - parametersJson: string + parametersJson: string, ): Promise { try { // Parse the parameters const parameters = JSON.parse(parametersJson); - + // Get the MCP client manager const clientManager = getMCPClientManager(); - + // Initialize the client manager if needed if (!clientManager.isInitialized) { await clientManager.initialize(); } - + // Find the tool const tools = await clientManager.getTools(); - const tool = tools.find(t => t.name === toolName && t.serverId === serverId); - + const tool = tools.find((t) => t.name === toolName && t.serverId === serverId); + if (!tool) { throw new Error(`Tool ${toolName} not found on server ${serverId}`); } - - console.log(`Executing MCP tool ${toolName} on server ${serverId} with parameters:`, parameters); - + + console.log( + `Executing MCP tool ${toolName} on server ${serverId} with parameters:`, + parameters, + ); + // Call the tool const result = await clientManager.callTool(serverId, toolName, parameters); - + return result; } catch (error) { console.error(`Error executing MCP tool ${toolName}:`, error); diff --git a/packages/api/dev-server.mts b/packages/api/dev-server.mts index 2ce331c8..d17f1111 100644 --- a/packages/api/dev-server.mts +++ b/packages/api/dev-server.mts @@ -9,7 +9,7 @@ export { SRCBOOK_DIR } from './constants.mjs'; // Initialize MCP client manager console.log('Initializing MCP client manager...'); -initializeMCP().catch(error => { +initializeMCP().catch((error) => { console.error('Failed to initialize MCP client manager:', error); console.log('MCP functionality will be limited or unavailable.'); }); diff --git a/packages/api/mcp/client-manager.mts b/packages/api/mcp/client-manager.mts index 37287eea..178ae54c 100644 --- a/packages/api/mcp/client-manager.mts +++ b/packages/api/mcp/client-manager.mts @@ -105,7 +105,9 @@ export class MCPClientManager { await this.discoverAllTools(); this._isInitialized = true; - console.log(`MCP Client Manager initialized with ${this.connections.size} servers and ${this.tools.length} tools.`); + console.log( + `MCP Client Manager initialized with ${this.connections.size} servers and ${this.tools.length} tools.`, + ); posthog.capture({ event: 'mcp_client_manager_initialized', @@ -167,7 +169,7 @@ export class MCPClientManager { // Create a new client const client = new Client( { name: 'srcbook-mcp-client', version: '1.0.0' }, - { capabilities: { tools: {} } } + { capabilities: { tools: {} } }, ); // Set up environment variables for the server process @@ -194,7 +196,7 @@ export class MCPClientManager { const transport = new StdioClientTransport({ command: serverConfig.command, args: serverConfig.args, - env: envVars + env: envVars, }); // Connect to the server @@ -229,7 +231,7 @@ export class MCPClientManager { const toolsResult = await client.listTools(); // Map the tools to our internal format - const serverTools = toolsResult.tools.map(tool => { + const serverTools = toolsResult.tools.map((tool) => { // Create a properly typed MCPTool object const mcpTool: MCPTool = { serverId, @@ -237,7 +239,7 @@ export class MCPClientManager { description: tool.description, inputSchema: tool.inputSchema, // Handle annotations with proper typing - annotations: tool.annotations as MCPTool['annotations'] + annotations: tool.annotations as MCPTool['annotations'], }; return mcpTool; }); @@ -280,7 +282,7 @@ export class MCPClientManager { } // Find the tool definition - const tool = this.tools.find(t => t.serverId === serverId && t.name === toolName); + const tool = this.tools.find((t) => t.serverId === serverId && t.name === toolName); if (!tool) { throw new Error(`Tool ${toolName} not found on server ${serverId}`); } @@ -289,7 +291,10 @@ export class MCPClientManager { // Validate the arguments against the tool's input schema const validatedArgs = this.validateToolArgs(tool, args); - console.log(`Calling tool ${toolName} on server ${serverId} with validated args:`, validatedArgs); + console.log( + `Calling tool ${toolName} on server ${serverId} with validated args:`, + validatedArgs, + ); posthog.capture({ event: 'mcp_tool_called', @@ -302,14 +307,15 @@ export class MCPClientManager { // Safely handle the result if (result && typeof result === 'object' && 'isError' in result && result.isError) { // Safely access content if it exists - const errorMessage = result.content && + const errorMessage = + result.content && Array.isArray(result.content) && result.content.length > 0 && result.content[0] && typeof result.content[0] === 'object' && - 'text' in result.content[0] ? - result.content[0].text : - 'Unknown error'; + 'text' in result.content[0] + ? result.content[0].text + : 'Unknown error'; console.error(`Tool ${toolName} returned an error:`, errorMessage); throw new Error(`Tool error: ${errorMessage}`); @@ -442,9 +448,11 @@ export class MCPClientManager { } catch (error) { if (error instanceof z.ZodError) { // Format the validation errors - const formattedErrors = error.errors.map(err => { - return `${err.path.join('.')}: ${err.message}`; - }).join(', '); + const formattedErrors = error.errors + .map((err) => { + return `${err.path.join('.')}: ${err.message}`; + }) + .join(', '); throw new Error(`Invalid arguments for tool ${tool.name}: ${formattedErrors}`); } @@ -454,7 +462,7 @@ export class MCPClientManager { } findTool(toolName: string): MCPTool | null { - return this.tools.find(tool => tool.name === toolName) || null; + return this.tools.find((tool) => tool.name === toolName) || null; } /** diff --git a/packages/api/mcp/index.mts b/packages/api/mcp/index.mts index 9ab6e99b..280a1f17 100644 --- a/packages/api/mcp/index.mts +++ b/packages/api/mcp/index.mts @@ -1,16 +1,16 @@ /** * MCP (Model Context Protocol) Integration - * + * * This module exports the MCP client manager and related utilities for * integrating with MCP servers. */ -export { - MCPClientManager, - getMCPClientManager, - type MCPTool, - type MCPConfig, - type MCPServerConfig +export { + MCPClientManager, + getMCPClientManager, + type MCPTool, + type MCPConfig, + type MCPServerConfig, } from './client-manager.mjs'; // Export a function to initialize the MCP client manager diff --git a/packages/api/srcbook_mcp_config.example.json b/packages/api/srcbook_mcp_config.example.json index c2c5982f..4fc65fe4 100644 --- a/packages/api/srcbook_mcp_config.example.json +++ b/packages/api/srcbook_mcp_config.example.json @@ -1,12 +1,11 @@ { - "mcpServers": { - "exa": { - "command": "npx", - "args": ["/path/to/exa-mcp-server/build/index.js"], - "env": { - "EXA_API_KEY": "your-exa-api-key" - } + "mcpServers": { + "exa": { + "command": "npx", + "args": ["/path/to/exa-mcp-server/build/index.js"], + "env": { + "EXA_API_KEY": "your-exa-api-key" } } } - \ No newline at end of file +} diff --git a/srcbook/src/server.mts b/srcbook/src/server.mts index 279205ad..5543a23d 100644 --- a/srcbook/src/server.mts +++ b/srcbook/src/server.mts @@ -43,7 +43,7 @@ webSocketServer.on('connection', wss.onConnection); // Initialize MCP client manager console.log(chalk.dim('Initializing MCP client manager...')); -initializeMCP().catch(error => { +initializeMCP().catch((error) => { console.error('Failed to initialize MCP client manager:', error); console.log(chalk.yellow('MCP functionality will be limited or unavailable.')); });