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.'));
});