@botbotgo/agent-harness 0.0.17 → 0.0.19

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.
@@ -45,3 +45,26 @@ spec:
45
45
  codebase, choose {{secondaryAgentId}}.
46
46
 
47
47
  When uncertain, prefer {{secondaryAgentId}}.
48
+
49
+ # agent-harness feature: optional workspace-level background maintenance policies.
50
+ # This is the framework-owned place to configure periodic housekeeping that should run without
51
+ # coupling cleanup to user-triggered checkpoint reads or writes.
52
+ #
53
+ # Current support:
54
+ # - checkpoint cleanup for `SqliteSaver` checkpointers only
55
+ # - oldest-first deletion by time policy and/or size policy
56
+ # - background scheduling inside the harness lifecycle
57
+ #
58
+ # Example:
59
+ # maintenance:
60
+ # checkpoints:
61
+ # enabled: true
62
+ # schedule:
63
+ # intervalSeconds: 3600
64
+ # runOnStartup: true
65
+ # policies:
66
+ # maxAgeSeconds: 604800
67
+ # maxBytes: 1073741824
68
+ # sqlite:
69
+ # sweepBatchSize: 200
70
+ # vacuum: false
@@ -8,6 +8,7 @@ export type ParsedAgentObject = {
8
8
  modelRef: string;
9
9
  runRoot?: string;
10
10
  toolRefs: string[];
11
+ mcpServers?: Array<Record<string, unknown>>;
11
12
  skillPathRefs: string[];
12
13
  memorySources: string[];
13
14
  subagentRefs: string[];
@@ -52,6 +53,18 @@ export type ParsedVectorStoreObject = {
52
53
  metadata?: Record<string, unknown>;
53
54
  sourcePath: string;
54
55
  };
56
+ export type ParsedMcpServerObject = {
57
+ id: string;
58
+ transport: "stdio" | "http" | "sse" | "websocket";
59
+ url?: string;
60
+ command?: string;
61
+ args?: string[];
62
+ env?: Record<string, string>;
63
+ cwd?: string;
64
+ token?: string;
65
+ headers?: Record<string, string>;
66
+ sourcePath: string;
67
+ };
55
68
  export type ParsedToolObject = {
56
69
  id: string;
57
70
  type: string;
@@ -174,6 +187,7 @@ export type WorkspaceBundle = {
174
187
  models: Map<string, ParsedModelObject>;
175
188
  embeddings: Map<string, ParsedEmbeddingModelObject>;
176
189
  vectorStores: Map<string, ParsedVectorStoreObject>;
190
+ mcpServers: Map<string, ParsedMcpServerObject>;
177
191
  tools: Map<string, ParsedToolObject>;
178
192
  agents: Map<string, ParsedAgentObject>;
179
193
  bindings: Map<string, CompiledAgentBinding>;
package/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
- export { createAgentHarness, getThread, run, subscribe, stop } from "./api.js";
1
+ export { createAgentHarness, createToolMcpServer, getThread, run, serveToolsOverStdio, subscribe, stop } from "./api.js";
2
+ export type { ToolMcpServerOptions } from "./mcp.js";
3
+ export { tool } from "./tools.js";
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- export { createAgentHarness, getThread, run, subscribe, stop } from "./api.js";
1
+ export { createAgentHarness, createToolMcpServer, getThread, run, serveToolsOverStdio, subscribe, stop } from "./api.js";
2
+ export { tool } from "./tools.js";
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { AgentHarness } from "./runtime/harness.js";
3
+ export type ToolMcpServerOptions = {
4
+ agentId: string;
5
+ serverInfo?: {
6
+ name?: string;
7
+ version?: string;
8
+ };
9
+ includeToolNames?: string[];
10
+ };
11
+ export declare function createToolMcpServerFromHarness(harness: AgentHarness, options: ToolMcpServerOptions): Promise<McpServer>;
12
+ export declare function serveToolsOverStdioFromHarness(harness: AgentHarness, options: ToolMcpServerOptions): Promise<McpServer>;
package/dist/mcp.js ADDED
@@ -0,0 +1,112 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { pathToFileURL } from "node:url";
4
+ import { z } from "zod";
5
+ import { loadToolModuleDefinition } from "./tool-modules.js";
6
+ function asResolvedTool(value) {
7
+ return typeof value === "object" && value !== null ? value : null;
8
+ }
9
+ async function buildRawShapeForTool(harness, toolId, resolvedTool) {
10
+ const tool = harness.getWorkspace().tools.get(toolId);
11
+ if (!tool) {
12
+ return undefined;
13
+ }
14
+ if (tool.type === "function") {
15
+ const imported = await import(pathToFileURL(tool.sourcePath).href);
16
+ const definition = loadToolModuleDefinition(imported, tool.implementationName ?? tool.id);
17
+ return typeof definition.schema.shape === "object" && definition.schema.shape !== null
18
+ ? definition.schema.shape
19
+ : undefined;
20
+ }
21
+ const typedResolvedTool = asResolvedTool(resolvedTool);
22
+ if (!typedResolvedTool?.inputSchemaPromise) {
23
+ return undefined;
24
+ }
25
+ return jsonSchemaObjectToRawShape(await typedResolvedTool.inputSchemaPromise);
26
+ }
27
+ async function invokeResolvedTool(resolvedTool, input) {
28
+ const typed = asResolvedTool(resolvedTool);
29
+ if (!typed) {
30
+ throw new Error("Resolved tool is not invokable.");
31
+ }
32
+ if (typeof typed.invoke === "function") {
33
+ return typed.invoke(input);
34
+ }
35
+ if (typeof typed.call === "function") {
36
+ return typed.call(input);
37
+ }
38
+ if (typeof typed.func === "function") {
39
+ return typed.func(input);
40
+ }
41
+ throw new Error("Resolved tool does not expose invoke, call, or func.");
42
+ }
43
+ function renderToolOutput(output) {
44
+ if (typeof output === "string") {
45
+ return output;
46
+ }
47
+ return JSON.stringify(output, null, 2);
48
+ }
49
+ function jsonSchemaObjectToRawShape(schema) {
50
+ if (!schema || schema.type !== "object") {
51
+ return undefined;
52
+ }
53
+ const properties = typeof schema.properties === "object" && schema.properties
54
+ ? schema.properties
55
+ : {};
56
+ const required = Array.isArray(schema.required)
57
+ ? new Set(schema.required.filter((item) => typeof item === "string"))
58
+ : new Set();
59
+ const shape = {};
60
+ for (const [key, propertySchema] of Object.entries(properties)) {
61
+ const built = jsonSchemaToZod(propertySchema);
62
+ shape[key] = required.has(key) ? built : built.optional();
63
+ }
64
+ return shape;
65
+ }
66
+ function jsonSchemaToZod(schema) {
67
+ switch (schema?.type) {
68
+ case "string":
69
+ return z.string();
70
+ case "number":
71
+ return z.number();
72
+ case "integer":
73
+ return z.number().int();
74
+ case "boolean":
75
+ return z.boolean();
76
+ case "array":
77
+ return z.array(jsonSchemaToZod(typeof schema.items === "object" && schema.items ? schema.items : undefined));
78
+ case "object": {
79
+ const shape = jsonSchemaObjectToRawShape(schema);
80
+ return z.object(shape ?? {}).passthrough();
81
+ }
82
+ default:
83
+ return z.any();
84
+ }
85
+ }
86
+ export async function createToolMcpServerFromHarness(harness, options) {
87
+ const server = new McpServer({
88
+ name: options.serverInfo?.name ?? `agent-harness-${options.agentId}`,
89
+ version: options.serverInfo?.version ?? "0.0.16",
90
+ });
91
+ const allowedNames = options.includeToolNames ? new Set(options.includeToolNames) : null;
92
+ for (const { compiledTool, resolvedTool } of harness.resolveAgentTools(options.agentId)) {
93
+ if (allowedNames && !allowedNames.has(compiledTool.name)) {
94
+ continue;
95
+ }
96
+ const rawShape = await buildRawShapeForTool(harness, compiledTool.id, resolvedTool);
97
+ server.tool(compiledTool.name, compiledTool.description, rawShape ?? {}, async (input) => ({
98
+ content: [
99
+ {
100
+ type: "text",
101
+ text: renderToolOutput(await invokeResolvedTool(resolvedTool, input)),
102
+ },
103
+ ],
104
+ }));
105
+ }
106
+ return server;
107
+ }
108
+ export async function serveToolsOverStdioFromHarness(harness, options) {
109
+ const server = await createToolMcpServerFromHarness(harness, options);
110
+ await server.connect(new StdioServerTransport());
111
+ return server;
112
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ensureIsolatedResourcePackageRoot(packageRoot: string): Promise<string>;
2
+ export declare function resolveIsolatedResourceModulePath(packageRoot: string, sourcePath: string): Promise<string>;
@@ -0,0 +1,79 @@
1
+ import { createRequire } from "node:module";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { existsSync } from "node:fs";
6
+ import { cp, mkdir, readFile, rm, symlink } from "node:fs/promises";
7
+ const isolatedResourceRootCache = new Map();
8
+ function isolationCacheRoot() {
9
+ return path.join(os.tmpdir(), "agent-harness-resource-isolation");
10
+ }
11
+ function isolationDir(packageRoot) {
12
+ const digest = createHash("sha256").update(packageRoot).digest("hex").slice(0, 16);
13
+ return path.join(isolationCacheRoot(), `${path.basename(packageRoot)}-${digest}`);
14
+ }
15
+ function declaredDependencyNames(manifest) {
16
+ return Array.from(new Set([
17
+ ...Object.keys(manifest.dependencies ?? {}),
18
+ ...Object.keys(manifest.optionalDependencies ?? {}),
19
+ ...Object.keys(manifest.peerDependencies ?? {}),
20
+ ])).sort();
21
+ }
22
+ function dependencyLinkPath(isolatedRoot, dependencyName) {
23
+ return path.join(isolatedRoot, "node_modules", ...dependencyName.split("/"));
24
+ }
25
+ function dependencyPackageRoot(packageJsonPath) {
26
+ return path.dirname(packageJsonPath);
27
+ }
28
+ function resolveDeclaredDependency(packageRoot, dependencyName) {
29
+ const localPath = path.join(packageRoot, "node_modules", ...dependencyName.split("/"), "package.json");
30
+ if (existsSync(localPath)) {
31
+ return dependencyPackageRoot(localPath);
32
+ }
33
+ try {
34
+ const requireFromPackage = createRequire(path.join(packageRoot, "package.json"));
35
+ return dependencyPackageRoot(requireFromPackage.resolve(`${dependencyName}/package.json`));
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ async function linkDeclaredDependencies(isolatedRoot, packageRoot, manifest) {
42
+ for (const dependencyName of declaredDependencyNames(manifest)) {
43
+ const resolved = resolveDeclaredDependency(packageRoot, dependencyName);
44
+ if (!resolved) {
45
+ continue;
46
+ }
47
+ const targetPath = dependencyLinkPath(isolatedRoot, dependencyName);
48
+ await mkdir(path.dirname(targetPath), { recursive: true });
49
+ await symlink(resolved, targetPath, "junction");
50
+ }
51
+ }
52
+ async function buildIsolatedResourceRoot(packageRoot) {
53
+ const packageJsonPath = path.join(packageRoot, "package.json");
54
+ const manifest = JSON.parse(await readFile(packageJsonPath, "utf8"));
55
+ const isolatedRoot = isolationDir(packageRoot);
56
+ await rm(isolatedRoot, { recursive: true, force: true });
57
+ await mkdir(path.dirname(isolatedRoot), { recursive: true });
58
+ await cp(packageRoot, isolatedRoot, {
59
+ recursive: true,
60
+ force: true,
61
+ filter: (source) => path.basename(source) !== "node_modules",
62
+ });
63
+ await mkdir(path.join(isolatedRoot, "node_modules"), { recursive: true });
64
+ await linkDeclaredDependencies(isolatedRoot, packageRoot, manifest);
65
+ return isolatedRoot;
66
+ }
67
+ export async function ensureIsolatedResourcePackageRoot(packageRoot) {
68
+ const cached = isolatedResourceRootCache.get(packageRoot);
69
+ if (cached) {
70
+ return cached;
71
+ }
72
+ const loading = buildIsolatedResourceRoot(packageRoot);
73
+ isolatedResourceRootCache.set(packageRoot, loading);
74
+ return loading;
75
+ }
76
+ export async function resolveIsolatedResourceModulePath(packageRoot, sourcePath) {
77
+ const isolatedRoot = await ensureIsolatedResourcePackageRoot(packageRoot);
78
+ return path.join(isolatedRoot, path.relative(packageRoot, sourcePath));
79
+ }
@@ -1,3 +1,4 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1
2
  import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
2
3
  export declare function resolveLocalBuiltinsEntry(currentResourceDir: string, resolveInstalledEntry?: () => string | null): string;
3
4
  export type ResourceToolInfo = {
@@ -10,6 +11,23 @@ export type ResourceToolInfo = {
10
11
  allow: Array<"approve" | "edit" | "reject">;
11
12
  };
12
13
  };
14
+ export type McpServerConfig = {
15
+ transport?: "stdio" | "http" | "sse" | "websocket";
16
+ command?: string;
17
+ args?: string[];
18
+ env?: Record<string, string>;
19
+ cwd?: string;
20
+ url?: string;
21
+ token?: string;
22
+ headers?: Record<string, string>;
23
+ };
24
+ export type McpToolDescriptor = {
25
+ name: string;
26
+ description?: string;
27
+ inputSchema?: Record<string, unknown>;
28
+ };
29
+ export declare function getOrCreateMcpClient(config: McpServerConfig): Promise<Client>;
30
+ export declare function listRemoteMcpTools(config: McpServerConfig): Promise<McpToolDescriptor[]>;
13
31
  export declare function ensureResourceSources(sources?: string[], workspaceRoot?: string): Promise<void>;
14
32
  export declare function defaultResourceSkillsRoot(): string;
15
33
  export declare function defaultResourceConfigRoot(): string;
@@ -4,6 +4,13 @@ import path from "node:path";
4
4
  import { stat } from "node:fs/promises";
5
5
  import { readFile } from "node:fs/promises";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
9
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
10
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
11
+ import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
12
+ import { isSupportedToolModulePath, loadToolModuleDefinition } from "../tool-modules.js";
13
+ import { resolveIsolatedResourceModulePath } from "./isolation.js";
7
14
  import { ensureExternalResourceSource, ensureExternalSource, isExternalSourceLocator, parseExternalSourceLocator } from "./sources.js";
8
15
  const resourceDir = path.dirname(fileURLToPath(import.meta.url));
9
16
  const require = createRequire(import.meta.url);
@@ -77,6 +84,7 @@ async function findPackageRoot(startPath) {
77
84
  }
78
85
  }
79
86
  const functionToolModuleCache = new Map();
87
+ const mcpClientCache = new Map();
80
88
  async function loadFunctionToolModule(tool) {
81
89
  const cacheKey = `${tool.sourcePath}:${tool.implementationName ?? tool.id}`;
82
90
  const cached = functionToolModuleCache.get(cacheKey);
@@ -84,24 +92,145 @@ async function loadFunctionToolModule(tool) {
84
92
  return cached;
85
93
  }
86
94
  const loading = (async () => {
87
- const imported = await import(pathToFileURL(tool.sourcePath).href);
95
+ const packageRoot = await findPackageRoot(tool.sourcePath);
96
+ const isolatedSourcePath = await resolveIsolatedResourceModulePath(packageRoot, tool.sourcePath);
97
+ const imported = await import(pathToFileURL(isolatedSourcePath).href);
88
98
  const implementationName = tool.implementationName ?? tool.id;
89
- const invoke = imported[implementationName];
90
- const schema = imported[`${implementationName}Schema`];
91
- if (typeof invoke !== "function") {
92
- throw new Error(`Tool module ${tool.sourcePath} must export function ${implementationName}.`);
93
- }
94
- if (!schema?.parse) {
95
- throw new Error(`Tool module ${tool.sourcePath} must export ${implementationName}Schema as a Zod schema.`);
96
- }
97
- return { invoke, schema };
99
+ const loaded = loadToolModuleDefinition(imported, implementationName);
100
+ return { invoke: loaded.invoke, schema: loaded.schema, description: loaded.description };
98
101
  })();
99
102
  functionToolModuleCache.set(cacheKey, loading);
100
103
  return loading;
101
104
  }
105
+ function normalizeMcpTransport(value) {
106
+ if (value === "stdio" || value === "http" || value === "sse" || value === "websocket") {
107
+ return value;
108
+ }
109
+ return undefined;
110
+ }
111
+ function readMcpServerConfig(workspace, tool) {
112
+ const mcpConfig = typeof tool.config?.mcp === "object" && tool.config?.mcp
113
+ ? tool.config.mcp
114
+ : undefined;
115
+ const serverRef = typeof mcpConfig?.serverRef === "string" ? mcpConfig.serverRef : undefined;
116
+ if (serverRef) {
117
+ const serverId = serverRef.startsWith("mcp/") ? serverRef.slice(4) : serverRef;
118
+ const server = workspace.mcpServers.get(serverId);
119
+ if (!server) {
120
+ throw new Error(`MCP tool ${tool.id} references missing MCP server ${serverRef}`);
121
+ }
122
+ return {
123
+ transport: server.transport,
124
+ command: server.command,
125
+ args: server.args,
126
+ env: server.env,
127
+ cwd: server.cwd,
128
+ url: server.url,
129
+ token: server.token,
130
+ headers: server.headers,
131
+ };
132
+ }
133
+ const config = typeof tool.config === "object" && tool.config
134
+ ? tool.config
135
+ : undefined;
136
+ const mcpServer = typeof config?.mcpServer === "object" && config.mcpServer
137
+ ? config.mcpServer
138
+ : config;
139
+ if (!mcpServer) {
140
+ return null;
141
+ }
142
+ const envRecord = typeof mcpServer.env === "object" && mcpServer.env
143
+ ? Object.fromEntries(Object.entries(mcpServer.env).filter((entry) => typeof entry[1] === "string"))
144
+ : undefined;
145
+ return {
146
+ transport: normalizeMcpTransport(mcpServer.transport) ?? ((typeof mcpServer.url === "string" && mcpServer.url.trim()) ? "http" : "stdio"),
147
+ command: typeof mcpServer.command === "string" ? mcpServer.command.trim() : undefined,
148
+ args: Array.isArray(mcpServer.args) ? mcpServer.args.filter((item) => typeof item === "string") : undefined,
149
+ env: envRecord,
150
+ cwd: typeof mcpServer.cwd === "string" ? mcpServer.cwd : undefined,
151
+ url: typeof mcpServer.url === "string" ? mcpServer.url.trim() : undefined,
152
+ token: typeof mcpServer.token === "string" ? mcpServer.token : undefined,
153
+ headers: typeof mcpServer.headers === "object" && mcpServer.headers
154
+ ? Object.fromEntries(Object.entries(mcpServer.headers).filter((entry) => typeof entry[1] === "string"))
155
+ : undefined,
156
+ };
157
+ }
158
+ function createMcpCacheKey(config) {
159
+ return JSON.stringify({
160
+ transport: config.transport ?? "stdio",
161
+ command: config.command,
162
+ args: config.args ?? [],
163
+ env: config.env ?? {},
164
+ cwd: config.cwd ?? "",
165
+ url: config.url ?? "",
166
+ token: config.token ?? "",
167
+ headers: config.headers ?? {},
168
+ });
169
+ }
170
+ export async function getOrCreateMcpClient(config) {
171
+ const cacheKey = createMcpCacheKey(config);
172
+ const cached = mcpClientCache.get(cacheKey);
173
+ if (cached) {
174
+ return cached;
175
+ }
176
+ const loading = (async () => {
177
+ const client = new Client({
178
+ name: "agent-harness",
179
+ version: "0.0.16",
180
+ });
181
+ const headers = {
182
+ ...(config.headers ?? {}),
183
+ ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),
184
+ };
185
+ const transport = config.transport === "http"
186
+ ? new StreamableHTTPClientTransport(new URL(config.url ?? ""), {
187
+ requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
188
+ })
189
+ : config.transport === "sse"
190
+ ? new SSEClientTransport(new URL(config.url ?? ""), {
191
+ requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
192
+ })
193
+ : config.transport === "websocket"
194
+ ? new WebSocketClientTransport(new URL(config.url ?? ""))
195
+ : new StdioClientTransport({
196
+ command: config.command ?? "",
197
+ args: config.args,
198
+ env: config.env,
199
+ cwd: config.cwd,
200
+ });
201
+ await client.connect(transport);
202
+ return client;
203
+ })();
204
+ mcpClientCache.set(cacheKey, loading);
205
+ return loading;
206
+ }
207
+ async function getRemoteMcpToolDescriptor(config, remoteToolName) {
208
+ const client = await getOrCreateMcpClient(config);
209
+ const result = await client.listTools();
210
+ const tool = result.tools.find((item) => typeof item.name === "string" && item.name === remoteToolName);
211
+ if (!tool || typeof tool.name !== "string") {
212
+ return null;
213
+ }
214
+ return {
215
+ name: tool.name,
216
+ description: typeof tool.description === "string" ? tool.description : undefined,
217
+ inputSchema: typeof tool.inputSchema === "object" && tool.inputSchema ? tool.inputSchema : undefined,
218
+ };
219
+ }
220
+ export async function listRemoteMcpTools(config) {
221
+ const client = await getOrCreateMcpClient(config);
222
+ const result = await client.listTools();
223
+ return result.tools
224
+ .filter((tool) => typeof tool.name === "string")
225
+ .map((tool) => ({
226
+ name: tool.name,
227
+ description: typeof tool.description === "string" ? tool.description : undefined,
228
+ inputSchema: typeof tool.inputSchema === "object" && tool.inputSchema ? tool.inputSchema : undefined,
229
+ }));
230
+ }
102
231
  function createFunctionToolResolver(workspace) {
103
232
  const functionTools = new Map(Array.from(workspace.tools.values())
104
- .filter((tool) => tool.type === "function" && tool.sourcePath.endsWith(".mjs"))
233
+ .filter((tool) => tool.type === "function" && isSupportedToolModulePath(tool.sourcePath))
105
234
  .map((tool) => [tool.id, tool]));
106
235
  return (toolIds) => toolIds.flatMap((toolId) => {
107
236
  const tool = functionTools.get(toolId);
@@ -127,6 +256,43 @@ function createFunctionToolResolver(workspace) {
127
256
  ];
128
257
  });
129
258
  }
259
+ function createMcpToolResolver(workspace) {
260
+ const mcpTools = new Map(Array.from(workspace.tools.values())
261
+ .filter((tool) => tool.type === "mcp")
262
+ .map((tool) => [tool.id, tool]));
263
+ return (toolIds) => toolIds.flatMap((toolId) => {
264
+ const tool = mcpTools.get(toolId);
265
+ if (!tool) {
266
+ return [];
267
+ }
268
+ const serverConfig = readMcpServerConfig(workspace, tool);
269
+ if (!serverConfig) {
270
+ throw new Error(`MCP tool ${tool.id} must define mcp.serverRef or inline MCP server config.`);
271
+ }
272
+ const remoteToolName = tool.mcpRef ?? tool.name;
273
+ const descriptorPromise = getRemoteMcpToolDescriptor(serverConfig, remoteToolName);
274
+ return [
275
+ {
276
+ name: tool.name,
277
+ description: tool.description,
278
+ inputSchemaPromise: descriptorPromise.then((descriptor) => descriptor?.inputSchema),
279
+ async invoke(input) {
280
+ const client = await getOrCreateMcpClient(serverConfig);
281
+ const result = await client.callTool({
282
+ name: remoteToolName,
283
+ arguments: typeof input === "object" && input !== null ? input : {},
284
+ });
285
+ const textParts = Array.isArray(result.content)
286
+ ? result.content
287
+ .filter((item) => typeof item === "object" && item !== null && "type" in item)
288
+ .flatMap((item) => item.type === "text" && typeof item.text === "string" ? [item.text] : [])
289
+ : [];
290
+ return textParts.length > 0 ? textParts.join("\n\n") : JSON.stringify(result);
291
+ },
292
+ },
293
+ ];
294
+ });
295
+ }
130
296
  function resolvePackageEntry(packageRoot, pkg) {
131
297
  const exportsField = pkg.exports;
132
298
  if (typeof exportsField === "string") {
@@ -222,6 +388,7 @@ export function createResourceBackendResolver(workspace) {
222
388
  }
223
389
  export function createResourceToolResolver(workspace, options = {}) {
224
390
  const functionResolver = createFunctionToolResolver(workspace);
391
+ const mcpResolver = createMcpToolResolver(workspace);
225
392
  const localResolver = createProviderToolResolver(localResource, workspace, options);
226
393
  const remoteResolvers = (workspace.resourceSources ?? workspace.builtinSources ?? [])
227
394
  .map((source) => remoteResourceCache.get(source))
@@ -231,6 +398,7 @@ export function createResourceToolResolver(workspace, options = {}) {
231
398
  return (toolIds, binding) => {
232
399
  const resolved = [
233
400
  ...functionResolver(toolIds, binding),
401
+ ...mcpResolver(toolIds, binding),
234
402
  ...(localResolver?.(toolIds, binding) ?? []),
235
403
  ...remoteResolvers.flatMap((resolver) => resolver(toolIds, binding)),
236
404
  ];
@@ -2,6 +2,9 @@ type ParsedLocator = {
2
2
  kind: "npm" | "tgz" | "file";
3
3
  spec: string;
4
4
  subpath?: string;
5
+ integrity?: string;
6
+ signature?: string;
7
+ publicKey?: string;
5
8
  };
6
9
  export declare function resolveResourcePackageRoot(root: string): string | null;
7
10
  export declare function isExternalSourceLocator(value: unknown): boolean;