@archon-claw/cli 0.0.2
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/dist/agent.d.ts +2 -0
- package/dist/agent.js +152 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +141 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +161 -0
- package/dist/eval/assertions.d.ts +9 -0
- package/dist/eval/assertions.js +137 -0
- package/dist/eval/execute.d.ts +13 -0
- package/dist/eval/execute.js +260 -0
- package/dist/eval/formatter.d.ts +10 -0
- package/dist/eval/formatter.js +62 -0
- package/dist/eval/judge.d.ts +7 -0
- package/dist/eval/judge.js +116 -0
- package/dist/eval/runner.d.ts +9 -0
- package/dist/eval/runner.js +156 -0
- package/dist/eval/types.d.ts +67 -0
- package/dist/eval/types.js +1 -0
- package/dist/llm.d.ts +7 -0
- package/dist/llm.js +52 -0
- package/dist/mcp-manager.d.ts +51 -0
- package/dist/mcp-manager.js +268 -0
- package/dist/pending-tool-results.d.ts +4 -0
- package/dist/pending-tool-results.js +39 -0
- package/dist/public/assets/chat-input-BBnVJs9h.js +151 -0
- package/dist/public/assets/chat-input-CISJdhF2.css +1 -0
- package/dist/public/assets/embed-DhIUBDdf.js +1 -0
- package/dist/public/assets/main-Bfvj6DnV.js +16 -0
- package/dist/public/embed/widget.js +233 -0
- package/dist/public/embed.html +14 -0
- package/dist/public/index.html +14 -0
- package/dist/scaffold.d.ts +2 -0
- package/dist/scaffold.js +82 -0
- package/dist/schemas.d.ts +899 -0
- package/dist/schemas.js +134 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +258 -0
- package/dist/session.d.ts +8 -0
- package/dist/session.js +70 -0
- package/dist/templates/agent/model.json +6 -0
- package/dist/templates/agent/system-prompt.md +9 -0
- package/dist/templates/agent/tool-impls/greeting.impl.js +9 -0
- package/dist/templates/agent/tools/greeting.json +14 -0
- package/dist/templates/workspace/.claude/skills/create-agent/SKILL.md +90 -0
- package/dist/templates/workspace/.claude/skills/create-dataset/SKILL.md +57 -0
- package/dist/templates/workspace/.claude/skills/create-eval-case/SKILL.md +159 -0
- package/dist/templates/workspace/.claude/skills/create-eval-judge/SKILL.md +128 -0
- package/dist/templates/workspace/.claude/skills/create-mcp-config/SKILL.md +151 -0
- package/dist/templates/workspace/.claude/skills/create-model-config/SKILL.md +45 -0
- package/dist/templates/workspace/.claude/skills/create-skill/SKILL.md +63 -0
- package/dist/templates/workspace/.claude/skills/create-system-prompt/SKILL.md +168 -0
- package/dist/templates/workspace/.claude/skills/create-tool/SKILL.md +56 -0
- package/dist/templates/workspace/.claude/skills/create-tool-impl/SKILL.md +83 -0
- package/dist/templates/workspace/.claude/skills/create-tool-test/SKILL.md +117 -0
- package/dist/templates/workspace/.claude/skills/create-tool-ui/SKILL.md +218 -0
- package/dist/test-runner.d.ts +22 -0
- package/dist/test-runner.js +166 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +1 -0
- package/dist/validator/index.d.ts +16 -0
- package/dist/validator/index.js +54 -0
- package/dist/validator/plugin.d.ts +21 -0
- package/dist/validator/plugin.js +1 -0
- package/dist/validator/plugins/agent-dir.d.ts +2 -0
- package/dist/validator/plugins/agent-dir.js +171 -0
- package/dist/validator/plugins/agent-skill.d.ts +2 -0
- package/dist/validator/plugins/agent-skill.js +31 -0
- package/dist/validator/plugins/dataset.d.ts +2 -0
- package/dist/validator/plugins/dataset.js +20 -0
- package/dist/validator/plugins/mcp.d.ts +2 -0
- package/dist/validator/plugins/mcp.js +20 -0
- package/dist/validator/plugins/model.d.ts +2 -0
- package/dist/validator/plugins/model.js +20 -0
- package/dist/validator/plugins/system-prompt.d.ts +2 -0
- package/dist/validator/plugins/system-prompt.js +25 -0
- package/dist/validator/plugins/tool.d.ts +2 -0
- package/dist/validator/plugins/tool.js +20 -0
- package/dist/validator/zod-utils.d.ts +3 -0
- package/dist/validator/zod-utils.js +7 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/llm.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import type { ModelConfig, ToolSchema } from "./types.js";
|
|
3
|
+
export declare function createClient(config: ModelConfig): OpenAI;
|
|
4
|
+
/** Convert ToolSchema[] to OpenAI function tools format */
|
|
5
|
+
export declare function toOpenAITools(tools: ToolSchema[]): OpenAI.Chat.Completions.ChatCompletionFunctionTool[];
|
|
6
|
+
/** Stream a chat completion, returns the raw stream */
|
|
7
|
+
export declare function streamChat(client: OpenAI, config: ModelConfig, messages: OpenAI.ChatCompletionMessageParam[], tools?: OpenAI.Chat.Completions.ChatCompletionFunctionTool[]): AsyncGenerator<OpenAI.ChatCompletionChunk>;
|
package/dist/llm.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
const BAILIAN_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
3
|
+
export function createClient(config) {
|
|
4
|
+
let baseURL;
|
|
5
|
+
let apiKey;
|
|
6
|
+
if (config.baseURL) {
|
|
7
|
+
baseURL = config.baseURL;
|
|
8
|
+
apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
|
|
9
|
+
}
|
|
10
|
+
else if (config.provider === "bailian") {
|
|
11
|
+
baseURL = BAILIAN_BASE_URL;
|
|
12
|
+
apiKey = config.apiKey ?? process.env.DASHSCOPE_API_KEY;
|
|
13
|
+
}
|
|
14
|
+
else if (config.provider === "openai") {
|
|
15
|
+
apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// Provider-specific env var: ANTHROPIC_API_KEY, DEEPSEEK_API_KEY, etc.
|
|
19
|
+
const envKey = `${config.provider.toUpperCase()}_API_KEY`;
|
|
20
|
+
apiKey = config.apiKey ?? process.env[envKey] ?? process.env.OPENAI_API_KEY;
|
|
21
|
+
}
|
|
22
|
+
if (!apiKey) {
|
|
23
|
+
throw new Error(`No API key found for provider "${config.provider}". ` +
|
|
24
|
+
`Set apiKey in model.json or the corresponding environment variable.`);
|
|
25
|
+
}
|
|
26
|
+
return new OpenAI({ apiKey, baseURL });
|
|
27
|
+
}
|
|
28
|
+
/** Convert ToolSchema[] to OpenAI function tools format */
|
|
29
|
+
export function toOpenAITools(tools) {
|
|
30
|
+
return tools.map((t) => ({
|
|
31
|
+
type: "function",
|
|
32
|
+
function: {
|
|
33
|
+
name: t.name,
|
|
34
|
+
description: t.description,
|
|
35
|
+
parameters: t.input_schema,
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/** Stream a chat completion, returns the raw stream */
|
|
40
|
+
export async function* streamChat(client, config, messages, tools) {
|
|
41
|
+
const stream = await client.chat.completions.create({
|
|
42
|
+
model: config.model,
|
|
43
|
+
messages,
|
|
44
|
+
max_tokens: config.maxTokens ?? 4096,
|
|
45
|
+
temperature: config.temperature ?? 0.7,
|
|
46
|
+
stream: true,
|
|
47
|
+
...(tools && tools.length > 0 ? { tools } : {}),
|
|
48
|
+
});
|
|
49
|
+
for await (const chunk of stream) {
|
|
50
|
+
yield chunk;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { McpConfig, ToolSchema } from "./schemas.js";
|
|
2
|
+
/**
|
|
3
|
+
* Manages MCP server connections and tool discovery.
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* - Creates transport + client for each enabled server
|
|
7
|
+
* - Discovers tools via listTools (with pagination)
|
|
8
|
+
* - Applies include/exclude/rename filters
|
|
9
|
+
* - Converts MCP tool schemas to internal ToolSchema format
|
|
10
|
+
* - Routes callTool to the correct MCP client
|
|
11
|
+
*/
|
|
12
|
+
export declare class McpManager {
|
|
13
|
+
private config;
|
|
14
|
+
private connections;
|
|
15
|
+
private toolEntries;
|
|
16
|
+
private toolRoutes;
|
|
17
|
+
constructor(config: McpConfig);
|
|
18
|
+
initialize(): Promise<void>;
|
|
19
|
+
private connectServer;
|
|
20
|
+
/**
|
|
21
|
+
* Create a Client + Transport pair and connect.
|
|
22
|
+
*/
|
|
23
|
+
private createAndConnect;
|
|
24
|
+
/**
|
|
25
|
+
* Connect to an MCP server, with automatic streamable-http → SSE fallback
|
|
26
|
+
* when transport is not explicitly specified and url is provided.
|
|
27
|
+
*/
|
|
28
|
+
private connectWithFallback;
|
|
29
|
+
private createTransport;
|
|
30
|
+
private resolveEnvVars;
|
|
31
|
+
private listAllTools;
|
|
32
|
+
private filterTools;
|
|
33
|
+
private resolveToolName;
|
|
34
|
+
/** Normalize name to match ^[a-z][a-z0-9_]*$ */
|
|
35
|
+
private normalizeName;
|
|
36
|
+
private convertToToolSchema;
|
|
37
|
+
private convertProperty;
|
|
38
|
+
/** Get all discovered tool schemas */
|
|
39
|
+
getTools(): ToolSchema[];
|
|
40
|
+
/** Get server names that were successfully connected */
|
|
41
|
+
getServerNames(): string[];
|
|
42
|
+
/** Get tools grouped by server name */
|
|
43
|
+
getToolsByServer(): Record<string, {
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
}[]>;
|
|
47
|
+
/** Call an MCP tool by its normalized name */
|
|
48
|
+
callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
49
|
+
/** Shut down all MCP connections */
|
|
50
|
+
shutdown(): Promise<void>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
4
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import picomatch from "picomatch";
|
|
6
|
+
/**
|
|
7
|
+
* Manages MCP server connections and tool discovery.
|
|
8
|
+
*
|
|
9
|
+
* Responsibilities:
|
|
10
|
+
* - Creates transport + client for each enabled server
|
|
11
|
+
* - Discovers tools via listTools (with pagination)
|
|
12
|
+
* - Applies include/exclude/rename filters
|
|
13
|
+
* - Converts MCP tool schemas to internal ToolSchema format
|
|
14
|
+
* - Routes callTool to the correct MCP client
|
|
15
|
+
*/
|
|
16
|
+
export class McpManager {
|
|
17
|
+
config;
|
|
18
|
+
connections = [];
|
|
19
|
+
toolEntries = [];
|
|
20
|
+
toolRoutes = new Map();
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
}
|
|
24
|
+
async initialize() {
|
|
25
|
+
const entries = Object.entries(this.config.mcpServers);
|
|
26
|
+
const results = await Promise.allSettled(entries.map(([name, serverConfig]) => this.connectServer(name, serverConfig)));
|
|
27
|
+
for (let i = 0; i < results.length; i++) {
|
|
28
|
+
const result = results[i];
|
|
29
|
+
if (result.status === "rejected") {
|
|
30
|
+
console.warn(`[mcp] Failed to connect to "${entries[i][0]}": ${result.reason}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async connectServer(serverName, serverConfig) {
|
|
35
|
+
if (serverConfig.enabled === false)
|
|
36
|
+
return;
|
|
37
|
+
const { client, transport } = await this.connectWithFallback(serverName, serverConfig);
|
|
38
|
+
const connection = { client, transport, serverName };
|
|
39
|
+
this.connections.push(connection);
|
|
40
|
+
// Discover tools (with pagination)
|
|
41
|
+
const tools = await this.listAllTools(client);
|
|
42
|
+
// Apply filters
|
|
43
|
+
const filtered = this.filterTools(tools, serverConfig);
|
|
44
|
+
// Convert and register
|
|
45
|
+
for (const tool of filtered) {
|
|
46
|
+
const name = this.resolveToolName(serverName, tool.name, serverConfig);
|
|
47
|
+
const schema = this.convertToToolSchema(name, tool);
|
|
48
|
+
const entry = {
|
|
49
|
+
name,
|
|
50
|
+
originalName: tool.name,
|
|
51
|
+
serverName,
|
|
52
|
+
schema,
|
|
53
|
+
};
|
|
54
|
+
this.toolEntries.push(entry);
|
|
55
|
+
this.toolRoutes.set(name, { connection, originalName: tool.name });
|
|
56
|
+
}
|
|
57
|
+
console.log(`[mcp] Connected to "${serverName}": ${filtered.length} tool(s) registered`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a Client + Transport pair and connect.
|
|
61
|
+
*/
|
|
62
|
+
async createAndConnect(config, transportType) {
|
|
63
|
+
const transport = this.createTransport(config, transportType);
|
|
64
|
+
const client = new Client({ name: "archon-claw", version: "1.0.0" });
|
|
65
|
+
await client.connect(transport);
|
|
66
|
+
return { client, transport };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Connect to an MCP server, with automatic streamable-http → SSE fallback
|
|
70
|
+
* when transport is not explicitly specified and url is provided.
|
|
71
|
+
*/
|
|
72
|
+
async connectWithFallback(serverName, config) {
|
|
73
|
+
const explicit = config.transport ?? (config.command ? "stdio" : undefined);
|
|
74
|
+
// Explicit transport or stdio — no fallback needed
|
|
75
|
+
if (explicit) {
|
|
76
|
+
return this.createAndConnect(config, explicit);
|
|
77
|
+
}
|
|
78
|
+
// URL-based with no explicit transport: try streamable-http first, fallback to SSE
|
|
79
|
+
try {
|
|
80
|
+
const result = await this.createAndConnect(config, "streamable-http");
|
|
81
|
+
console.log(`[mcp] "${serverName}": connected via streamable-http`);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
86
|
+
console.log(`[mcp] "${serverName}": streamable-http failed (${msg}), falling back to SSE`);
|
|
87
|
+
return this.createAndConnect(config, "sse");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
createTransport(config, transport) {
|
|
91
|
+
const env = config.env ? this.resolveEnvVars(config.env) : undefined;
|
|
92
|
+
switch (transport) {
|
|
93
|
+
case "stdio": {
|
|
94
|
+
if (!config.command)
|
|
95
|
+
throw new Error("stdio transport requires 'command'");
|
|
96
|
+
return new StdioClientTransport({
|
|
97
|
+
command: config.command,
|
|
98
|
+
args: config.args,
|
|
99
|
+
env: env ? { ...process.env, ...env } : undefined,
|
|
100
|
+
cwd: config.cwd,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
case "sse": {
|
|
104
|
+
if (!config.url)
|
|
105
|
+
throw new Error("sse transport requires 'url'");
|
|
106
|
+
return new SSEClientTransport(new URL(config.url), {
|
|
107
|
+
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
case "streamable-http": {
|
|
111
|
+
if (!config.url)
|
|
112
|
+
throw new Error("streamable-http transport requires 'url'");
|
|
113
|
+
return new StreamableHTTPClientTransport(new URL(config.url), {
|
|
114
|
+
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
throw new Error(`Unknown transport: ${transport}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
resolveEnvVars(env) {
|
|
122
|
+
const resolved = {};
|
|
123
|
+
for (const [key, val] of Object.entries(env)) {
|
|
124
|
+
resolved[key] = val.replace(/\$\{(\w+)\}/g, (_, name) => {
|
|
125
|
+
if (!(name in process.env)) {
|
|
126
|
+
console.warn(`[mcp] Environment variable "\${${name}}" is not set (used in env.${key})`);
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
return process.env[name];
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
134
|
+
async listAllTools(client) {
|
|
135
|
+
const tools = [];
|
|
136
|
+
let cursor;
|
|
137
|
+
do {
|
|
138
|
+
const result = await client.listTools(cursor ? { cursor } : undefined);
|
|
139
|
+
for (const t of result.tools) {
|
|
140
|
+
tools.push(t);
|
|
141
|
+
}
|
|
142
|
+
cursor = result.nextCursor;
|
|
143
|
+
} while (cursor);
|
|
144
|
+
return tools;
|
|
145
|
+
}
|
|
146
|
+
filterTools(tools, config) {
|
|
147
|
+
const filter = config.tools;
|
|
148
|
+
if (!filter)
|
|
149
|
+
return tools;
|
|
150
|
+
let result = tools;
|
|
151
|
+
if (filter.include?.length) {
|
|
152
|
+
const matcher = picomatch(filter.include);
|
|
153
|
+
result = result.filter((t) => matcher(t.name));
|
|
154
|
+
}
|
|
155
|
+
if (filter.exclude?.length) {
|
|
156
|
+
const matcher = picomatch(filter.exclude);
|
|
157
|
+
result = result.filter((t) => !matcher(t.name));
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
resolveToolName(serverName, toolName, config) {
|
|
162
|
+
// If renamed, use the rename value directly (no prefix)
|
|
163
|
+
const renamed = config.tools?.rename?.[toolName];
|
|
164
|
+
if (renamed)
|
|
165
|
+
return this.normalizeName(renamed);
|
|
166
|
+
// Default: normalize parts separately, then join with __
|
|
167
|
+
return `${this.normalizeName(serverName)}__${this.normalizeName(toolName)}`;
|
|
168
|
+
}
|
|
169
|
+
/** Normalize name to match ^[a-z][a-z0-9_]*$ */
|
|
170
|
+
normalizeName(name) {
|
|
171
|
+
return name
|
|
172
|
+
.toLowerCase()
|
|
173
|
+
.replace(/[^a-z0-9_]/g, "_")
|
|
174
|
+
.replace(/^([^a-z])/, "x$1")
|
|
175
|
+
.replace(/_+/g, "_");
|
|
176
|
+
}
|
|
177
|
+
convertToToolSchema(name, tool) {
|
|
178
|
+
const inputSchema = tool.inputSchema ?? { type: "object", properties: {} };
|
|
179
|
+
// Convert MCP's inputSchema properties to our JSONSchemaProperty format
|
|
180
|
+
const properties = {};
|
|
181
|
+
if (inputSchema.properties) {
|
|
182
|
+
for (const [key, val] of Object.entries(inputSchema.properties)) {
|
|
183
|
+
properties[key] = this.convertProperty(val);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
name,
|
|
188
|
+
description: tool.description ?? `MCP tool: ${tool.name}`,
|
|
189
|
+
input_schema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: properties,
|
|
192
|
+
required: inputSchema.required,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
convertProperty(prop) {
|
|
197
|
+
const result = {};
|
|
198
|
+
const type = prop.type;
|
|
199
|
+
result.type = type ?? "string";
|
|
200
|
+
if (prop.description)
|
|
201
|
+
result.description = prop.description;
|
|
202
|
+
if (prop.enum)
|
|
203
|
+
result.enum = prop.enum;
|
|
204
|
+
if (type === "array" && prop.items) {
|
|
205
|
+
result.items = this.convertProperty(prop.items);
|
|
206
|
+
}
|
|
207
|
+
if (type === "object" && prop.properties) {
|
|
208
|
+
const nested = {};
|
|
209
|
+
for (const [k, v] of Object.entries(prop.properties)) {
|
|
210
|
+
nested[k] = this.convertProperty(v);
|
|
211
|
+
}
|
|
212
|
+
result.properties = nested;
|
|
213
|
+
if (prop.required)
|
|
214
|
+
result.required = prop.required;
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
/** Get all discovered tool schemas */
|
|
219
|
+
getTools() {
|
|
220
|
+
return this.toolEntries.map((e) => e.schema);
|
|
221
|
+
}
|
|
222
|
+
/** Get server names that were successfully connected */
|
|
223
|
+
getServerNames() {
|
|
224
|
+
return this.connections.map((c) => c.serverName);
|
|
225
|
+
}
|
|
226
|
+
/** Get tools grouped by server name */
|
|
227
|
+
getToolsByServer() {
|
|
228
|
+
const grouped = {};
|
|
229
|
+
for (const entry of this.toolEntries) {
|
|
230
|
+
const list = (grouped[entry.serverName] ??= []);
|
|
231
|
+
list.push({ name: entry.name, description: entry.schema.description });
|
|
232
|
+
}
|
|
233
|
+
return grouped;
|
|
234
|
+
}
|
|
235
|
+
/** Call an MCP tool by its normalized name */
|
|
236
|
+
async callTool(name, args) {
|
|
237
|
+
const route = this.toolRoutes.get(name);
|
|
238
|
+
if (!route)
|
|
239
|
+
throw new Error(`Unknown MCP tool: ${name}`);
|
|
240
|
+
const serverConfig = this.config.mcpServers[route.connection.serverName];
|
|
241
|
+
const timeout = serverConfig?.timeout ?? 30_000;
|
|
242
|
+
const result = await route.connection.client.callTool({ name: route.originalName, arguments: args }, undefined, { signal: AbortSignal.timeout(timeout) });
|
|
243
|
+
// Extract value from MCP response
|
|
244
|
+
if ("toolResult" in result)
|
|
245
|
+
return result.toolResult;
|
|
246
|
+
const content = result.content;
|
|
247
|
+
if (!content || content.length === 0)
|
|
248
|
+
return null;
|
|
249
|
+
// Single text content → return the text string
|
|
250
|
+
if (content.length === 1 && content[0].type === "text") {
|
|
251
|
+
return content[0].text;
|
|
252
|
+
}
|
|
253
|
+
// Multiple content items → return the array
|
|
254
|
+
return content;
|
|
255
|
+
}
|
|
256
|
+
/** Shut down all MCP connections */
|
|
257
|
+
async shutdown() {
|
|
258
|
+
const results = await Promise.allSettled(this.connections.map((conn) => conn.transport.close()));
|
|
259
|
+
for (let i = 0; i < results.length; i++) {
|
|
260
|
+
if (results[i].status === "rejected") {
|
|
261
|
+
console.warn(`[mcp] Error closing "${this.connections[i].serverName}"`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
this.connections = [];
|
|
265
|
+
this.toolEntries = [];
|
|
266
|
+
this.toolRoutes.clear();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function waitForToolResult(toolCallId: string): Promise<unknown>;
|
|
2
|
+
export declare function resolveToolResult(toolCallId: string, result: unknown): boolean;
|
|
3
|
+
export declare function rejectToolResult(toolCallId: string, error: string): boolean;
|
|
4
|
+
export declare function cancelAllPending(toolCallIds: string[]): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const TIMEOUT_MS = 120_000; // 2 minutes
|
|
2
|
+
const pending = new Map();
|
|
3
|
+
export function waitForToolResult(toolCallId) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const timer = setTimeout(() => {
|
|
6
|
+
pending.delete(toolCallId);
|
|
7
|
+
reject(new Error(`Tool result timeout after ${TIMEOUT_MS / 1000}s for ${toolCallId}`));
|
|
8
|
+
}, TIMEOUT_MS);
|
|
9
|
+
pending.set(toolCallId, { resolve, reject, timer });
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function resolveToolResult(toolCallId, result) {
|
|
13
|
+
const entry = pending.get(toolCallId);
|
|
14
|
+
if (!entry)
|
|
15
|
+
return false;
|
|
16
|
+
clearTimeout(entry.timer);
|
|
17
|
+
pending.delete(toolCallId);
|
|
18
|
+
entry.resolve(result);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
export function rejectToolResult(toolCallId, error) {
|
|
22
|
+
const entry = pending.get(toolCallId);
|
|
23
|
+
if (!entry)
|
|
24
|
+
return false;
|
|
25
|
+
clearTimeout(entry.timer);
|
|
26
|
+
pending.delete(toolCallId);
|
|
27
|
+
entry.reject(new Error(error));
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
export function cancelAllPending(toolCallIds) {
|
|
31
|
+
for (const id of toolCallIds) {
|
|
32
|
+
const entry = pending.get(id);
|
|
33
|
+
if (entry) {
|
|
34
|
+
clearTimeout(entry.timer);
|
|
35
|
+
pending.delete(id);
|
|
36
|
+
entry.reject(new Error("Client disconnected"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|