@4onstudios/iris-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/api/acp/acpServer.d.ts +23 -0
- package/dist/api/acp/acpServer.js +814 -0
- package/dist/api/acp/index.d.ts +2 -0
- package/dist/api/acp/index.js +2 -0
- package/dist/api/acp/irisClient.d.ts +58 -0
- package/dist/api/acp/irisClient.js +214 -0
- package/dist/api/agent.d.ts +14 -0
- package/dist/api/agent.js +4171 -0
- package/dist/api/backendService.d.ts +4 -0
- package/dist/api/backendService.js +14 -0
- package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
- package/dist/api/core/agent/host/AgentContract.js +1 -0
- package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
- package/dist/api/core/agent/host/AgentRegistry.js +125 -0
- package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
- package/dist/api/core/agent/host/defaultRegistry.js +46 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
- package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
- package/dist/api/core/agent/host/hostSessionManager.js +352 -0
- package/dist/api/core/agent/host/index.d.ts +5 -0
- package/dist/api/core/agent/host/index.js +5 -0
- package/dist/api/core/agent/index.d.ts +87 -0
- package/dist/api/core/agent/index.js +1024 -0
- package/dist/api/core/agent/publicContracts.d.ts +35 -0
- package/dist/api/core/agent/publicContracts.js +103 -0
- package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
- package/dist/api/core/agent/tools/applyDiff.js +131 -0
- package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
- package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
- package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
- package/dist/api/core/agent/tools/createDirectory.js +77 -0
- package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
- package/dist/api/core/agent/tools/deleteFile.js +66 -0
- package/dist/api/core/agent/tools/editFile.d.ts +73 -0
- package/dist/api/core/agent/tools/editFile.js +173 -0
- package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
- package/dist/api/core/agent/tools/executeCommand.js +250 -0
- package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
- package/dist/api/core/agent/tools/fileContent.js +227 -0
- package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
- package/dist/api/core/agent/tools/findDefinition.js +109 -0
- package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
- package/dist/api/core/agent/tools/findReferences.js +135 -0
- package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
- package/dist/api/core/agent/tools/formatDocument.js +90 -0
- package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeActions.js +97 -0
- package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
- package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
- package/dist/api/core/agent/tools/getCodeContext.js +282 -0
- package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
- package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
- package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
- package/dist/api/core/agent/tools/getSymbols.js +164 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
- package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
- package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
- package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
- package/dist/api/core/agent/tools/grepSearch.js +211 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
- package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
- package/dist/api/core/agent/tools/listDirectory.js +161 -0
- package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
- package/dist/api/core/agent/tools/mcpTools.js +442 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
- package/dist/api/core/agent/tools/readFile.d.ts +92 -0
- package/dist/api/core/agent/tools/readFile.js +337 -0
- package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
- package/dist/api/core/agent/tools/renameFile.js +86 -0
- package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
- package/dist/api/core/agent/tools/renameSymbol.js +106 -0
- package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
- package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
- package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
- package/dist/api/core/agent/tools/searchFiles.js +280 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
- package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
- package/dist/api/core/agent/tools/treeTraversal.js +217 -0
- package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
- package/dist/api/core/agent/tools/webSearch.js +80 -0
- package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
- package/dist/api/core/agent/tools/writeFile.js +97 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
- package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
- package/dist/api/core/agent/utils/diffUtils.js +39 -0
- package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
- package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
- package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
- package/dist/api/core/agent/utils/errorRecovery.js +229 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
- package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
- package/dist/api/core/agent/utils/pathRecovery.js +29 -0
- package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
- package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
- package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
- package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
- package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
- package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
- package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
- package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
- package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
- package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
- package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
- package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
- package/dist/api/core/library/BrowserManager.d.ts +11 -0
- package/dist/api/core/library/BrowserManager.js +66 -0
- package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
- package/dist/api/core/library/desktopWorkspace.js +3 -0
- package/dist/api/core/library/extensionManager.d.ts +10 -0
- package/dist/api/core/library/extensionManager.js +27 -0
- package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
- package/dist/api/core/library/knowledgeGraph.js +989 -0
- package/dist/api/core/library/languageModelTools.d.ts +141 -0
- package/dist/api/core/library/languageModelTools.js +207 -0
- package/dist/api/core/library/localRuntime.d.ts +23 -0
- package/dist/api/core/library/localRuntime.js +144 -0
- package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
- package/dist/api/core/library/lsp/coreLsp.js +2076 -0
- package/dist/api/core/library/lsp/protocol.d.ts +25 -0
- package/dist/api/core/library/lsp/protocol.js +1 -0
- package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
- package/dist/api/core/library/lsp/serverManager.js +427 -0
- package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
- package/dist/api/core/library/mcpServerProvider.js +152 -0
- package/dist/api/core/library/mcpSettings.d.ts +20 -0
- package/dist/api/core/library/mcpSettings.js +131 -0
- package/dist/api/core/library/patternMatcher.d.ts +66 -0
- package/dist/api/core/library/patternMatcher.js +284 -0
- package/dist/api/core/library/regexEscape.d.ts +4 -0
- package/dist/api/core/library/regexEscape.js +4 -0
- package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
- package/dist/api/core/library/runtimeEventBus.js +78 -0
- package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
- package/dist/api/core/library/safetyMiddleware.js +215 -0
- package/dist/api/core/library/tauri.d.ts +71 -0
- package/dist/api/core/library/tauri.js +222 -0
- package/dist/api/core/library/tauriImport.d.ts +5 -0
- package/dist/api/core/library/tauriImport.js +7 -0
- package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
- package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
- package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
- package/dist/api/core/library/workspaceIdentity.js +49 -0
- package/dist/api/core/library/workspaceSummary.d.ts +19 -0
- package/dist/api/core/library/workspaceSummary.js +35 -0
- package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
- package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
- package/dist/api/core/skills/create-pr/SKILL.md +32 -0
- package/dist/api/core/skills/dev-server/SKILL.md +20 -0
- package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
- package/dist/api/core/skills/github-search/SKILL.md +21 -0
- package/dist/api/core/skills/github-summary/SKILL.md +25 -0
- package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
- package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
- package/dist/api/core/skills/project-setup/SKILL.md +20 -0
- package/dist/api/core/skills/search-integration/SKILL.md +19 -0
- package/dist/api/core/skills/search-results/SKILL.md +19 -0
- package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
- package/dist/api/data/runStore.d.ts +46 -0
- package/dist/api/data/runStore.js +244 -0
- package/dist/api/helpers/agentUtils.d.ts +11 -0
- package/dist/api/helpers/agentUtils.js +23 -0
- package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
- package/dist/api/helpers/modelTokenLimits.js +158 -0
- package/dist/api/helpers/observationalMemory.d.ts +10 -0
- package/dist/api/helpers/observationalMemory.js +36 -0
- package/dist/api/helpers/promptBudget.d.ts +30 -0
- package/dist/api/helpers/promptBudget.js +216 -0
- package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
- package/dist/api/helpers/resolveImageMessageParts.js +140 -0
- package/dist/api/helpers/slashCommands.d.ts +31 -0
- package/dist/api/helpers/slashCommands.js +129 -0
- package/dist/api/helpers/tokenUsage.d.ts +19 -0
- package/dist/api/helpers/tokenUsage.js +81 -0
- package/dist/api/routes/fileRoutes.d.ts +2 -0
- package/dist/api/routes/fileRoutes.js +108 -0
- package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspDocumentRoutes.js +84 -0
- package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
- package/dist/api/routes/lspHierarchyRoutes.js +122 -0
- package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
- package/dist/api/routes/lspPositionRoutes.js +263 -0
- package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
- package/dist/api/routes/lspQueryRoutes.js +679 -0
- package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
- package/dist/api/routes/lspResolveRoutes.js +143 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
- package/dist/api/routes/semanticRoutes.d.ts +2 -0
- package/dist/api/routes/semanticRoutes.js +126 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +160 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +38 -0
- package/package.json +100 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { createTool } from "@mastra/core/tools";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import Ajv from "ajv";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport, } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
const MCP_TIMEOUT_MS = 20000;
|
|
8
|
+
const ajv = new Ajv({ allErrors: true });
|
|
9
|
+
const safeToolToken = (value) => value.replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80);
|
|
10
|
+
export const toMcpToolKey = (serverName, toolName) => `mcp_${safeToolToken(serverName)}_${safeToolToken(toolName)}`;
|
|
11
|
+
const formatAjvErrorPath = (instancePath, missingProperty) => {
|
|
12
|
+
const path = instancePath || "/";
|
|
13
|
+
return missingProperty ? `${path}${path.endsWith("/") ? "" : "/"}${missingProperty}` : path;
|
|
14
|
+
};
|
|
15
|
+
const summarizeMcpContent = (content) => {
|
|
16
|
+
if (!Array.isArray(content))
|
|
17
|
+
return undefined;
|
|
18
|
+
const parts = content
|
|
19
|
+
.map((item) => {
|
|
20
|
+
if (!item || typeof item !== "object")
|
|
21
|
+
return null;
|
|
22
|
+
const entry = item;
|
|
23
|
+
if (typeof entry.text === "string" && entry.text.trim()) {
|
|
24
|
+
return entry.text.trim();
|
|
25
|
+
}
|
|
26
|
+
if (typeof entry.message === "string" && entry.message.trim()) {
|
|
27
|
+
return entry.message.trim();
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
})
|
|
31
|
+
.filter((value) => Boolean(value));
|
|
32
|
+
if (parts.length === 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
return parts.join("\n");
|
|
35
|
+
};
|
|
36
|
+
const summarizeStructuredError = (structuredContent) => {
|
|
37
|
+
if (!structuredContent || typeof structuredContent !== "object" || Array.isArray(structuredContent)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const record = structuredContent;
|
|
41
|
+
const directMessage = [record.error, record.message, record.detail]
|
|
42
|
+
.find((value) => typeof value === "string" && value.trim().length > 0);
|
|
43
|
+
if (typeof directMessage === "string") {
|
|
44
|
+
return directMessage.trim();
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
48
|
+
const toMcpToolParams = (params) => {
|
|
49
|
+
if (params === null || params === undefined)
|
|
50
|
+
return {};
|
|
51
|
+
if (typeof params === "object" && !Array.isArray(params)) {
|
|
52
|
+
return params;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
export const validateMcpToolInput = (toolName, inputSchema, params) => {
|
|
57
|
+
if (!inputSchema || Object.keys(inputSchema).length === 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const validate = ajv.compile(inputSchema);
|
|
61
|
+
const valid = validate(params);
|
|
62
|
+
if (valid) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const details = (validate.errors || [])
|
|
66
|
+
.map((error) => {
|
|
67
|
+
const missingProperty = error.keyword === "required" &&
|
|
68
|
+
typeof error.params?.missingProperty === "string"
|
|
69
|
+
? (error.params.missingProperty || undefined)
|
|
70
|
+
: undefined;
|
|
71
|
+
const path = formatAjvErrorPath(error.dataPath || "", missingProperty);
|
|
72
|
+
return `${path}: ${error.message || "invalid value"}`;
|
|
73
|
+
})
|
|
74
|
+
.join("; ");
|
|
75
|
+
return `Invalid arguments for MCP tool '${toolName}'. ${details}`;
|
|
76
|
+
};
|
|
77
|
+
export const summarizeMcpToolError = (result) => {
|
|
78
|
+
if (!result.isError)
|
|
79
|
+
return undefined;
|
|
80
|
+
return summarizeStructuredError(result.structuredContent) || summarizeMcpContent(result.content);
|
|
81
|
+
};
|
|
82
|
+
const buildAugmentedPath = () => {
|
|
83
|
+
const home = process.env.HOME || os.homedir() || "";
|
|
84
|
+
const currentPath = process.env.PATH || "";
|
|
85
|
+
const pathParts = [
|
|
86
|
+
currentPath,
|
|
87
|
+
"/opt/homebrew/bin",
|
|
88
|
+
"/usr/local/bin",
|
|
89
|
+
"/usr/bin",
|
|
90
|
+
"/bin",
|
|
91
|
+
home ? `${home}/.local/bin` : "",
|
|
92
|
+
home ? `${home}/.cargo/bin` : "",
|
|
93
|
+
].filter(Boolean);
|
|
94
|
+
return Array.from(new Set(pathParts.join(":").split(":"))).join(":");
|
|
95
|
+
};
|
|
96
|
+
const shellEscape = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
97
|
+
const toShellCommand = (command, args) => [command, ...args].map((part) => shellEscape(part)).join(" ");
|
|
98
|
+
const getCommandCandidates = (command) => {
|
|
99
|
+
const base = command.trim();
|
|
100
|
+
if (!base)
|
|
101
|
+
return [];
|
|
102
|
+
const candidates = [{ command: base }];
|
|
103
|
+
if (base === "npx") {
|
|
104
|
+
candidates.push({ command: "/opt/homebrew/bin/npx" }, { command: "/usr/local/bin/npx" }, { command: "/usr/bin/npx" });
|
|
105
|
+
}
|
|
106
|
+
if (base === "uvx") {
|
|
107
|
+
const home = process.env.HOME || os.homedir() || "";
|
|
108
|
+
candidates.push({ command: "/opt/homebrew/bin/uvx" }, { command: "/usr/local/bin/uvx" }, { command: "/usr/bin/uvx" }, ...(home ? [{ command: `${home}/.cargo/bin/uvx` }] : []), ...(home ? [{ command: `${home}/.local/bin/uvx` }] : []), { command: "uv", prependArgs: ["x"] }, { command: "/opt/homebrew/bin/uv", prependArgs: ["x"] }, { command: "/usr/local/bin/uv", prependArgs: ["x"] }, ...(home ? [{ command: `${home}/.cargo/bin/uv`, prependArgs: ["x"] }] : []), ...(home ? [{ command: `${home}/.local/bin/uv`, prependArgs: ["x"] }] : []));
|
|
109
|
+
}
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
return candidates.filter((candidate) => {
|
|
112
|
+
const key = `${candidate.command}::${(candidate.prependArgs || []).join(" ")}`;
|
|
113
|
+
if (seen.has(key)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
seen.add(key);
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
const createTransportParams = (server, workspacePath) => {
|
|
121
|
+
const env = {
|
|
122
|
+
...process.env,
|
|
123
|
+
...server.env,
|
|
124
|
+
};
|
|
125
|
+
env.PATH = buildAugmentedPath();
|
|
126
|
+
return {
|
|
127
|
+
command: server.command.trim(),
|
|
128
|
+
args: [...server.args],
|
|
129
|
+
env,
|
|
130
|
+
cwd: workspacePath,
|
|
131
|
+
stderr: "pipe",
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const connectClient = async (server, workspacePath) => {
|
|
135
|
+
const command = server.command.trim();
|
|
136
|
+
const args = [...server.args];
|
|
137
|
+
const commandCandidates = getCommandCandidates(command);
|
|
138
|
+
const attempted = [];
|
|
139
|
+
let lastError = null;
|
|
140
|
+
for (const candidate of commandCandidates) {
|
|
141
|
+
const client = new Client({ name: "iris-mcp", version: "1.0.0" });
|
|
142
|
+
const transport = new StdioClientTransport({
|
|
143
|
+
...createTransportParams(server, workspacePath),
|
|
144
|
+
command: candidate.command,
|
|
145
|
+
args: [
|
|
146
|
+
...(candidate.prependArgs || []),
|
|
147
|
+
...args,
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
attempted.push(`${candidate.command}${(candidate.prependArgs || []).length > 0
|
|
151
|
+
? ` ${(candidate.prependArgs || []).join(" ")}`
|
|
152
|
+
: ""}`);
|
|
153
|
+
try {
|
|
154
|
+
await client.connect(transport, { timeout: MCP_TIMEOUT_MS });
|
|
155
|
+
return { client, transport };
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
await closeTransport(transport);
|
|
159
|
+
lastError = error;
|
|
160
|
+
const message = lastError.message || "";
|
|
161
|
+
const isEnoent = /ENOENT/i.test(message);
|
|
162
|
+
if (!isEnoent) {
|
|
163
|
+
throw lastError;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Some desktop launch contexts have constrained PATH/environment.
|
|
168
|
+
// If direct spawn failed with ENOENT, retry through a login shell.
|
|
169
|
+
if (lastError && /ENOENT/i.test(lastError.message || "")) {
|
|
170
|
+
const shellCommand = toShellCommand(command, args);
|
|
171
|
+
const shellCandidates = ["/bin/zsh", "/bin/bash"];
|
|
172
|
+
for (const shellPath of shellCandidates) {
|
|
173
|
+
const client = new Client({ name: "iris-mcp", version: "1.0.0" });
|
|
174
|
+
const transport = new StdioClientTransport({
|
|
175
|
+
...createTransportParams(server, workspacePath),
|
|
176
|
+
command: shellPath,
|
|
177
|
+
args: ["-lc", shellCommand],
|
|
178
|
+
});
|
|
179
|
+
attempted.push(`${shellPath} -lc ${shellCommand}`);
|
|
180
|
+
try {
|
|
181
|
+
await client.connect(transport, { timeout: MCP_TIMEOUT_MS });
|
|
182
|
+
return { client, transport };
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
await closeTransport(transport);
|
|
186
|
+
lastError = error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (lastError) {
|
|
191
|
+
const detail = attempted.length > 0 ? ` Tried: ${attempted.join(", ")}.` : "";
|
|
192
|
+
throw new Error(`${lastError.message}${detail}`);
|
|
193
|
+
}
|
|
194
|
+
throw new Error("Failed to start MCP client transport");
|
|
195
|
+
};
|
|
196
|
+
const closeTransport = async (transport) => {
|
|
197
|
+
if (!transport)
|
|
198
|
+
return;
|
|
199
|
+
try {
|
|
200
|
+
await transport.close();
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// No-op; transport teardown should not fail request handling.
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
export const listMcpServerTools = async (server, workspacePath) => {
|
|
207
|
+
let transport;
|
|
208
|
+
try {
|
|
209
|
+
const { client, transport: activeTransport } = await connectClient(server, workspacePath);
|
|
210
|
+
transport = activeTransport;
|
|
211
|
+
const listed = await client.listTools(undefined, { timeout: MCP_TIMEOUT_MS });
|
|
212
|
+
return (listed.tools || []).map((tool) => ({
|
|
213
|
+
name: tool.name,
|
|
214
|
+
description: tool.description,
|
|
215
|
+
inputSchema: (tool.inputSchema || {}),
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
await closeTransport(transport);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const callServerTool = async (server, workspacePath, toolName, input) => {
|
|
223
|
+
let transport;
|
|
224
|
+
try {
|
|
225
|
+
const { client, transport: activeTransport } = await connectClient(server, workspacePath);
|
|
226
|
+
transport = activeTransport;
|
|
227
|
+
const result = await client.callTool({
|
|
228
|
+
name: toolName,
|
|
229
|
+
arguments: input,
|
|
230
|
+
}, undefined, { timeout: MCP_TIMEOUT_MS });
|
|
231
|
+
return {
|
|
232
|
+
success: !result.isError,
|
|
233
|
+
server: server.name,
|
|
234
|
+
tool: toolName,
|
|
235
|
+
isError: !!result.isError,
|
|
236
|
+
content: result.content,
|
|
237
|
+
structuredContent: result.structuredContent,
|
|
238
|
+
error: summarizeMcpToolError({
|
|
239
|
+
isError: !!result.isError,
|
|
240
|
+
content: result.content,
|
|
241
|
+
structuredContent: result.structuredContent,
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const err = error;
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
server: server.name,
|
|
250
|
+
tool: toolName,
|
|
251
|
+
isError: true,
|
|
252
|
+
error: err.message,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
await closeTransport(transport);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
export const executeMcpToolByKey = async (servers, workspacePath, toolKey, input) => {
|
|
260
|
+
console.log("🔍 executeMcpToolByKey called with:", {
|
|
261
|
+
toolKey,
|
|
262
|
+
inputKeys: Object.keys(input),
|
|
263
|
+
serversCount: servers.length,
|
|
264
|
+
});
|
|
265
|
+
for (const server of servers) {
|
|
266
|
+
if (!server.enabled) {
|
|
267
|
+
console.log(`⏭️ Skipping disabled server: ${server.name}`);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
console.log(`🔍 Checking server: ${server.name}`);
|
|
271
|
+
let tools = [];
|
|
272
|
+
try {
|
|
273
|
+
console.log(`📋 Listing tools from ${server.name}...`);
|
|
274
|
+
tools = await listMcpServerTools(server, workspacePath);
|
|
275
|
+
console.log(`✅ ${server.name} reports ${tools.length} tools available`);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
console.error(`❌ Failed to list tools from ${server.name}:`, error.message);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
for (const tool of tools) {
|
|
282
|
+
const computedKey = toMcpToolKey(server.name, tool.name);
|
|
283
|
+
console.log(`🔑 Checking tool: ${tool.name} (key: ${computedKey})`);
|
|
284
|
+
if (computedKey !== toolKey) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
console.log(`🎯 Match found! Validating input for ${tool.name}...`);
|
|
288
|
+
const validationError = validateMcpToolInput(tool.name, tool.inputSchema, input);
|
|
289
|
+
if (validationError) {
|
|
290
|
+
console.error("❌ Input validation failed:", validationError);
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
server: server.name,
|
|
294
|
+
tool: tool.name,
|
|
295
|
+
isError: true,
|
|
296
|
+
error: validationError,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
console.log("🚀 Calling MCP tool with input:", input);
|
|
300
|
+
const result = await callServerTool(server, workspacePath, tool.name, input);
|
|
301
|
+
console.log("✅ MCP tool call completed");
|
|
302
|
+
console.log("📤 Result:", result);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
console.warn("⚠️ Tool key not found on any server:", toolKey);
|
|
307
|
+
return {
|
|
308
|
+
success: false,
|
|
309
|
+
isError: true,
|
|
310
|
+
error: `MCP tool '${toolKey}' is not available on the configured servers`,
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* Generate MCP tools documentation for persistence in knowledge base.
|
|
315
|
+
* Only includes servers that are connected and available.
|
|
316
|
+
* Returns tool metadata that can be stored in the workspace for semantic search.
|
|
317
|
+
* Returns null if no connected servers with tools are available.
|
|
318
|
+
*/
|
|
319
|
+
export const generateMcpToolsDocs = async (servers, workspacePath) => {
|
|
320
|
+
const sections = [
|
|
321
|
+
"# MCP Tools Documentation",
|
|
322
|
+
"",
|
|
323
|
+
"This document contains all available MCP (Model Context Protocol) tools registered in this workspace.",
|
|
324
|
+
"Only connected and available servers are documented here.",
|
|
325
|
+
"",
|
|
326
|
+
];
|
|
327
|
+
let hasAnyTools = false;
|
|
328
|
+
for (const server of servers) {
|
|
329
|
+
if (!server.enabled)
|
|
330
|
+
continue;
|
|
331
|
+
let tools = [];
|
|
332
|
+
try {
|
|
333
|
+
tools = await listMcpServerTools(server, workspacePath);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
const err = error;
|
|
337
|
+
console.warn(`[mcp-docs] Skipping unavailable server ${server.name}: ${err.message}`);
|
|
338
|
+
// Skip unavailable servers - don't document them
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
// Skip servers with no tools
|
|
342
|
+
if (tools.length === 0) {
|
|
343
|
+
console.warn(`[mcp-docs] Skipping server ${server.name}: no tools exposed`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
hasAnyTools = true;
|
|
347
|
+
sections.push(`## ${server.name}`);
|
|
348
|
+
sections.push(`- **Command**: \`${server.command}\``);
|
|
349
|
+
sections.push(`- **Tools**: ${tools.length}`);
|
|
350
|
+
sections.push(`- **Status**: ✅ Connected`);
|
|
351
|
+
sections.push("");
|
|
352
|
+
for (const tool of tools) {
|
|
353
|
+
const toolKey = toMcpToolKey(server.name, tool.name);
|
|
354
|
+
sections.push(`### \`${toolKey}\``);
|
|
355
|
+
sections.push("");
|
|
356
|
+
sections.push(`**Description**: ${tool.description || "No description available"}`);
|
|
357
|
+
sections.push("");
|
|
358
|
+
if (tool.inputSchema) {
|
|
359
|
+
sections.push("**Input Schema**:");
|
|
360
|
+
sections.push("```json");
|
|
361
|
+
sections.push(JSON.stringify(tool.inputSchema, null, 2));
|
|
362
|
+
sections.push("```");
|
|
363
|
+
sections.push("");
|
|
364
|
+
}
|
|
365
|
+
sections.push(`**Usage Example**:`);
|
|
366
|
+
sections.push("```javascript");
|
|
367
|
+
sections.push(`${toolKey}({ /* args matching schema */ })`);
|
|
368
|
+
sections.push("```");
|
|
369
|
+
sections.push("");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Return null if no servers have tools available
|
|
373
|
+
return hasAnyTools ? sections.join("\n") : null;
|
|
374
|
+
};
|
|
375
|
+
export const buildMcpTools = async (servers, workspacePath) => {
|
|
376
|
+
const registered = {};
|
|
377
|
+
for (const server of servers) {
|
|
378
|
+
if (!server.enabled)
|
|
379
|
+
continue;
|
|
380
|
+
let tools = [];
|
|
381
|
+
try {
|
|
382
|
+
tools = await listMcpServerTools(server, workspacePath);
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
const err = error;
|
|
386
|
+
console.warn(`[mcp] Failed to list tools for ${server.name} (${server.command}): ${err.message}`);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
for (const tool of tools) {
|
|
390
|
+
const key = toMcpToolKey(server.name, tool.name);
|
|
391
|
+
if (!key || registered[key])
|
|
392
|
+
continue;
|
|
393
|
+
const schemaPreview = tool.inputSchema
|
|
394
|
+
? JSON.stringify(tool.inputSchema).slice(0, 800)
|
|
395
|
+
: "{}";
|
|
396
|
+
// Expose the MCP server's declared JSON Schema directly to the model's
|
|
397
|
+
// function-calling API instead of an unconstrained passthrough object.
|
|
398
|
+
// Previously every MCP tool used `z.object({}).passthrough()`, so the
|
|
399
|
+
// model had no structured schema to call against and had to *guess*
|
|
400
|
+
// the parameter shape from a text blob in the description. That caused
|
|
401
|
+
// repeated invalid tool calls (caught by Ajv validation below) until
|
|
402
|
+
// the tool-call budget's repeated-call guard kicked in and forced an
|
|
403
|
+
// abrupt synthesis/response. Passing the real JSON Schema lets
|
|
404
|
+
// providers that support structured tool calling constrain arguments
|
|
405
|
+
// correctly on the first attempt.
|
|
406
|
+
const toolInputSchema = tool.inputSchema && Object.keys(tool.inputSchema).length > 0
|
|
407
|
+
? tool.inputSchema
|
|
408
|
+
: z.object({}).passthrough();
|
|
409
|
+
registered[key] = createTool({
|
|
410
|
+
id: key,
|
|
411
|
+
description: `MCP tool from server '${server.name}' (${server.command}) named '${tool.name}'. ` +
|
|
412
|
+
`Pass the tool arguments directly as fields in the input object and match the declared schema exactly.` +
|
|
413
|
+
`\n\nDeclared input schema:\n${schemaPreview}`,
|
|
414
|
+
inputSchema: toolInputSchema,
|
|
415
|
+
execute: async (params) => {
|
|
416
|
+
const input = toMcpToolParams(params);
|
|
417
|
+
if (!input) {
|
|
418
|
+
return {
|
|
419
|
+
success: false,
|
|
420
|
+
server: server.name,
|
|
421
|
+
tool: tool.name,
|
|
422
|
+
isError: true,
|
|
423
|
+
error: `Invalid arguments for MCP tool '${tool.name}'. Expected an object.`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const validationError = validateMcpToolInput(tool.name, tool.inputSchema, input);
|
|
427
|
+
if (validationError) {
|
|
428
|
+
return {
|
|
429
|
+
success: false,
|
|
430
|
+
server: server.name,
|
|
431
|
+
tool: tool.name,
|
|
432
|
+
isError: true,
|
|
433
|
+
error: validationError,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return callServerTool(server, workspacePath, tool.name, input);
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return registered;
|
|
442
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
type QueryType = "summary" | "relationships" | "components" | "related-files" | "stats" | "question";
|
|
3
|
+
type QueryKnowledgeGraphParams = {
|
|
4
|
+
filePath?: string;
|
|
5
|
+
queryType?: QueryType;
|
|
6
|
+
question?: string;
|
|
7
|
+
maxDepth?: number;
|
|
8
|
+
maxTokens?: number;
|
|
9
|
+
};
|
|
10
|
+
type ComponentSummary = {
|
|
11
|
+
type: string;
|
|
12
|
+
name: string;
|
|
13
|
+
line: number | undefined;
|
|
14
|
+
tokenCount: number;
|
|
15
|
+
};
|
|
16
|
+
type RelationshipInfo = {
|
|
17
|
+
nodeId: string;
|
|
18
|
+
type: string;
|
|
19
|
+
name: string;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
type FileSummary = {
|
|
23
|
+
fileName: unknown;
|
|
24
|
+
language: unknown;
|
|
25
|
+
tokenCount: number;
|
|
26
|
+
accessCount: number;
|
|
27
|
+
lastAccessed: string;
|
|
28
|
+
componentCount: number;
|
|
29
|
+
components: ComponentSummary[];
|
|
30
|
+
relationshipCount: number;
|
|
31
|
+
};
|
|
32
|
+
type RelationshipSummary = {
|
|
33
|
+
imports: RelationshipInfo[];
|
|
34
|
+
exports: RelationshipInfo[];
|
|
35
|
+
calls: RelationshipInfo[];
|
|
36
|
+
usedBy: Array<{
|
|
37
|
+
nodeId: string;
|
|
38
|
+
type: string;
|
|
39
|
+
name: string;
|
|
40
|
+
}>;
|
|
41
|
+
contains: RelationshipInfo[];
|
|
42
|
+
};
|
|
43
|
+
type ComponentGroups = {
|
|
44
|
+
functions: Array<Record<string, unknown>>;
|
|
45
|
+
classes: Array<Record<string, unknown>>;
|
|
46
|
+
imports: Array<Record<string, unknown>>;
|
|
47
|
+
methods: Array<Record<string, unknown>>;
|
|
48
|
+
other: Array<Record<string, unknown>>;
|
|
49
|
+
};
|
|
50
|
+
type RelatedFilesSummary = {
|
|
51
|
+
imports: string[];
|
|
52
|
+
importedBy: string[];
|
|
53
|
+
allDependencies: string[];
|
|
54
|
+
};
|
|
55
|
+
type GraphStatistics = {
|
|
56
|
+
nodeCount: number;
|
|
57
|
+
cacheSize: number;
|
|
58
|
+
cacheHits: number;
|
|
59
|
+
cacheMisses: number;
|
|
60
|
+
hitRate: string;
|
|
61
|
+
totalTokensSaved: number;
|
|
62
|
+
estimatedCostSaved: string;
|
|
63
|
+
};
|
|
64
|
+
type QuestionSummary = {
|
|
65
|
+
question: string;
|
|
66
|
+
matchedTerms: string[];
|
|
67
|
+
anchorNodes: Array<{
|
|
68
|
+
id: string;
|
|
69
|
+
type: string;
|
|
70
|
+
name: unknown;
|
|
71
|
+
}>;
|
|
72
|
+
nodeCount: number;
|
|
73
|
+
tokenCount: number;
|
|
74
|
+
contextPreview: string;
|
|
75
|
+
};
|
|
76
|
+
type QueryKnowledgeGraphData = FileSummary | RelationshipSummary | ComponentGroups | RelatedFilesSummary | GraphStatistics | QuestionSummary;
|
|
77
|
+
type QueryKnowledgeGraphResult = {
|
|
78
|
+
success: true;
|
|
79
|
+
filePath: string;
|
|
80
|
+
queryType: QueryType;
|
|
81
|
+
data: QueryKnowledgeGraphData;
|
|
82
|
+
} | {
|
|
83
|
+
success: false;
|
|
84
|
+
error: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Query the knowledge graph for code insights
|
|
88
|
+
* @param {Object} params
|
|
89
|
+
* @param {string} params.filePath - The file path to query
|
|
90
|
+
* @param {string} [params.queryType] - Type of query: 'summary', 'relationships', 'components', 'related-files'
|
|
91
|
+
* @returns {Promise<Object>} Query results from knowledge graph
|
|
92
|
+
*/
|
|
93
|
+
export declare function queryKnowledgeGraph({ filePath, queryType, question, maxDepth, maxTokens, }: QueryKnowledgeGraphParams): Promise<QueryKnowledgeGraphResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Tool metadata for agent system
|
|
96
|
+
*/
|
|
97
|
+
export declare const queryKnowledgeGraphTool: {
|
|
98
|
+
description: string;
|
|
99
|
+
parameters: z.ZodObject<{
|
|
100
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
101
|
+
queryType: z.ZodDefault<z.ZodEnum<["summary", "relationships", "components", "related-files", "stats", "question"]>>;
|
|
102
|
+
question: z.ZodOptional<z.ZodString>;
|
|
103
|
+
maxDepth: z.ZodDefault<z.ZodNumber>;
|
|
104
|
+
maxTokens: z.ZodDefault<z.ZodNumber>;
|
|
105
|
+
}, "strip", z.ZodTypeAny, {
|
|
106
|
+
maxDepth?: number;
|
|
107
|
+
maxTokens?: number;
|
|
108
|
+
filePath?: string;
|
|
109
|
+
question?: string;
|
|
110
|
+
queryType?: "question" | "components" | "relationships" | "summary" | "related-files" | "stats";
|
|
111
|
+
}, {
|
|
112
|
+
maxDepth?: number;
|
|
113
|
+
maxTokens?: number;
|
|
114
|
+
filePath?: string;
|
|
115
|
+
question?: string;
|
|
116
|
+
queryType?: "question" | "components" | "relationships" | "summary" | "related-files" | "stats";
|
|
117
|
+
}>;
|
|
118
|
+
execute: typeof queryKnowledgeGraph;
|
|
119
|
+
};
|
|
120
|
+
export default queryKnowledgeGraphTool;
|