managed-deepagents 0.6.2-dev.7 → 0.6.2-dev.9
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/channels/schedule-delivery.d.ts +2 -0
- package/dist/channels/schedule-delivery.d.ts.map +1 -1
- package/dist/channels/schedule-delivery.js +40 -4
- package/dist/channels/schedule-delivery.js.map +1 -1
- package/dist/channels/trigger/client.d.ts +1 -0
- package/dist/channels/trigger/client.d.ts.map +1 -1
- package/dist/channels/trigger/client.js +19 -8
- package/dist/channels/trigger/client.js.map +1 -1
- package/dist/channels/trigger/types.d.ts +1 -1
- package/dist/channels/trigger/types.d.ts.map +1 -1
- package/dist/connectors/mcp/index.d.ts +89 -29
- package/dist/connectors/mcp/index.d.ts.map +1 -1
- package/dist/connectors/mcp/index.js +50 -227
- package/dist/connectors/mcp/index.js.map +1 -1
- package/dist/connectors/mcp/load.d.ts +12 -0
- package/dist/connectors/mcp/load.d.ts.map +1 -0
- package/dist/connectors/mcp/load.js +122 -0
- package/dist/connectors/mcp/load.js.map +1 -0
- package/dist/connectors/tool-selection.d.ts +18 -9
- package/dist/connectors/tool-selection.d.ts.map +1 -1
- package/dist/connectors/tool-selection.js +58 -32
- package/dist/connectors/tool-selection.js.map +1 -1
- package/dist/identity/index.d.ts +1 -1
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/types.d.ts +5 -45
- package/dist/identity/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/auth.d.ts +4 -9
- package/dist/runtime/auth.d.ts.map +1 -1
- package/dist/runtime/auth.js.map +1 -1
- package/dist/runtime/http.d.ts +1 -1
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/identity-runtime.d.ts +5 -55
- package/dist/runtime/identity-runtime.d.ts.map +1 -1
- package/dist/runtime/identity-runtime.js +0 -107
- package/dist/runtime/identity-runtime.js.map +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +7 -12
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/managed-middleware.d.ts +2 -2
- package/dist/runtime/managed-middleware.js +6 -6
- package/dist/runtime/managed-tools.d.ts +15 -19
- package/dist/runtime/managed-tools.d.ts.map +1 -1
- package/dist/runtime/managed-tools.js +13 -14
- package/dist/runtime/managed-tools.js.map +1 -1
- package/dist/runtime/types.d.ts +2 -2
- package/package.json +7 -7
- package/dist/agent-auth.d.ts +0 -104
- package/dist/agent-auth.d.ts.map +0 -1
- package/dist/agent-auth.js +0 -224
- package/dist/agent-auth.js.map +0 -1
- package/dist/connectors/mcp/schema.d.ts +0 -12
- package/dist/connectors/mcp/schema.d.ts.map +0 -1
- package/dist/connectors/mcp/schema.js +0 -55
- package/dist/connectors/mcp/schema.js.map +0 -1
- package/dist/connectors/mcp/types.d.ts +0 -62
- package/dist/connectors/mcp/types.d.ts.map +0 -1
- package/dist/connectors/mcp/types.js +0 -2
- package/dist/connectors/mcp/types.js.map +0 -1
- package/dist/credential-gate.d.ts +0 -39
- package/dist/credential-gate.d.ts.map +0 -1
- package/dist/credential-gate.js +0 -209
- package/dist/credential-gate.js.map +0 -1
- package/dist/credentials.d.ts +0 -88
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js +0 -246
- package/dist/credentials.js.map +0 -1
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* @example
|
|
19
19
|
* ```ts
|
|
20
20
|
* // connectors/mcp.ts
|
|
21
|
-
* import { connectors
|
|
21
|
+
* import { connectors } from "managed-deepagents";
|
|
22
22
|
*
|
|
23
23
|
* export const connector = connectors.mcp({
|
|
24
24
|
* mcpServers: {
|
|
@@ -26,246 +26,69 @@
|
|
|
26
26
|
* transport: "http",
|
|
27
27
|
* url: "https://docs.example.com/mcp",
|
|
28
28
|
* },
|
|
29
|
-
* notion: {
|
|
30
|
-
* transport: "http",
|
|
31
|
-
* url: "https://mcp.notion.com/mcp",
|
|
32
|
-
* credential: credentials.get("notion", { type: "user" }),
|
|
33
|
-
* },
|
|
34
29
|
* },
|
|
35
30
|
* });
|
|
36
31
|
* ```
|
|
37
32
|
*/
|
|
38
|
-
import {
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
import { checkToolSelectionOverlap, toolSelectionConnectorFields, } from "../tool-selection.js";
|
|
39
35
|
import { defineExtension } from "../../extension.js";
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* run can be retried on the next, but a misconfigured deployment surfaces the
|
|
63
|
-
* error rather than silently starting with no tools.
|
|
64
|
-
*/
|
|
65
|
-
export function loadManagedMcpTools(connectors) {
|
|
66
|
-
const cached = cachedTools.get(connectors);
|
|
67
|
-
if (cached) {
|
|
68
|
-
return cached;
|
|
69
|
-
}
|
|
70
|
-
const loading = createClientAndLoadTools(connectors).catch((error) => {
|
|
71
|
-
cachedTools.delete(connectors);
|
|
72
|
-
throw error;
|
|
73
|
-
});
|
|
74
|
-
cachedTools.set(connectors, loading);
|
|
75
|
-
return loading;
|
|
76
|
-
}
|
|
77
|
-
/** Clear the process-level MCP tool cache. Test-only. */
|
|
78
|
-
export function clearManagedMcpToolsCacheForTests(connectors) {
|
|
79
|
-
if (connectors) {
|
|
80
|
-
cachedTools.delete(connectors);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
cachedTools.clear();
|
|
84
|
-
}
|
|
36
|
+
/** Stdio servers are out of scope; the message points at the alternatives. */
|
|
37
|
+
const STDIO_REJECTION = "uses a stdio transport, which is not supported. Managed Deep Agents " +
|
|
38
|
+
"support remote MCP servers over HTTP/SSE only; expose the server over HTTP " +
|
|
39
|
+
"or write a normal authored tool instead.";
|
|
40
|
+
const remoteMcpServer = z
|
|
41
|
+
.looseObject({
|
|
42
|
+
// Present only to reject it: a `command` is how a stdio server is spelled.
|
|
43
|
+
command: z.never({ error: STDIO_REJECTION }).optional(),
|
|
44
|
+
transport: z.enum(["http", "sse"], {
|
|
45
|
+
error: (issue) => issue.input === "stdio"
|
|
46
|
+
? STDIO_REJECTION
|
|
47
|
+
: 'must declare a remote transport ("http" or "sse")',
|
|
48
|
+
}),
|
|
49
|
+
url: z
|
|
50
|
+
.string("must be a non-empty URL")
|
|
51
|
+
.refine((url) => url.trim().length > 0, "must be a non-empty URL"),
|
|
52
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
53
|
+
...toolSelectionConnectorFields,
|
|
54
|
+
})
|
|
55
|
+
.check((ctx) => {
|
|
56
|
+
checkToolSelectionOverlap(ctx, ctx.value);
|
|
57
|
+
});
|
|
85
58
|
/**
|
|
86
|
-
* The
|
|
59
|
+
* The MCP connector's arguments.
|
|
87
60
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
61
|
+
* Shape, types, and tool-name lists are the schema's job; the rules that need
|
|
62
|
+
* more than one field — a stdio server, a tool selected and deselected at once
|
|
63
|
+
* — are checks with hand-written messages. Server configs stay loose because
|
|
64
|
+
* `@langchain/mcp-adapters` accepts more per-transport options than MDA models.
|
|
90
65
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
out.push({
|
|
105
|
-
declaration: definition,
|
|
106
|
-
credentials,
|
|
107
|
-
load: (accessTokens) => loadUserCredentialMcpTools(definition, accessTokens),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
return out;
|
|
111
|
-
}
|
|
112
|
-
/** The declared references themselves: the gate checks their owner. */
|
|
113
|
-
function userCredentialReferences(connectors) {
|
|
114
|
-
const bySlug = new Map();
|
|
115
|
-
for (const server of Object.values(connectors.mcpServers)) {
|
|
116
|
-
if (usesUserCredential(server)) {
|
|
117
|
-
const credential = server.credential;
|
|
118
|
-
bySlug.set(credential.slug, credential);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return [...bySlug.values()];
|
|
122
|
-
}
|
|
123
|
-
async function createClientAndLoadTools(connectors) {
|
|
124
|
-
const { eager, userCredential } = partitionMcpServers(connectors.mcpServers);
|
|
125
|
-
if (userCredential.length > 0) {
|
|
126
|
-
warnDeferredUserCredentialServers(userCredential);
|
|
127
|
-
}
|
|
128
|
-
if (Object.keys(eager).length === 0) {
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
131
|
-
const { tools } = await loadToolsFromServers(connectors, await withAgentCredentials(eager));
|
|
132
|
-
return tools;
|
|
133
|
-
}
|
|
66
|
+
const mcpServersConfig = z.object({
|
|
67
|
+
mcpServers: z
|
|
68
|
+
.record(z.string(), remoteMcpServer, {
|
|
69
|
+
error: "must be an object mapping server names to remote MCP server configs",
|
|
70
|
+
})
|
|
71
|
+
.refine((servers) => Object.keys(servers).length > 0, "requires at least one entry"),
|
|
72
|
+
prefixToolNameWithServerName: z.boolean().optional(),
|
|
73
|
+
throwOnLoadError: z.boolean().optional(),
|
|
74
|
+
useStandardContentBlocks: z.boolean().optional(),
|
|
75
|
+
onConnectionError: z.literal("throw").optional(),
|
|
76
|
+
});
|
|
134
77
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
78
|
+
* Declare the MCP servers whose tools should be loaded and added to the managed
|
|
79
|
+
* Deep Agent at runtime.
|
|
137
80
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
81
|
+
* The tools hook runs at agent-graph construction time (inside the managed
|
|
82
|
+
* runtime), so the MCP client loader is imported lazily — authoring-only
|
|
83
|
+
* installs never pull in `@langchain/mcp-adapters`.
|
|
140
84
|
*/
|
|
141
|
-
export async function loadUserCredentialMcpTools(connectors, accessTokens) {
|
|
142
|
-
const { userCredential } = partitionMcpServers(connectors.mcpServers);
|
|
143
|
-
const connectable = {};
|
|
144
|
-
for (const name of userCredential) {
|
|
145
|
-
const server = connectors.mcpServers[name];
|
|
146
|
-
const credential = server?.credential;
|
|
147
|
-
if (!server || !isCredentialReference(credential)) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const accessToken = accessTokens.get(credential.slug);
|
|
151
|
-
if (!accessToken) {
|
|
152
|
-
throw new Error(`MCP user credential "${credential.slug}" has no access token`);
|
|
153
|
-
}
|
|
154
|
-
connectable[name] = withBearer(server, accessToken);
|
|
155
|
-
}
|
|
156
|
-
if (Object.keys(connectable).length === 0) {
|
|
157
|
-
return { tools: [], close: async () => { } };
|
|
158
|
-
}
|
|
159
|
-
const { tools, client } = await loadToolsFromServers(connectors, connectable);
|
|
160
|
-
return { tools, close: () => client.close() };
|
|
161
|
-
}
|
|
162
|
-
/** Resolve each agent-owned credential into a static bearer header. */
|
|
163
|
-
async function withAgentCredentials(servers) {
|
|
164
|
-
const out = {};
|
|
165
|
-
for (const [name, server] of Object.entries(servers)) {
|
|
166
|
-
const credential = server.credential;
|
|
167
|
-
out[name] = isCredentialReference(credential)
|
|
168
|
-
? withBearer(server, await credentials.resolveDeploymentSecret(credential.slug))
|
|
169
|
-
: server;
|
|
170
|
-
}
|
|
171
|
-
return out;
|
|
172
|
-
}
|
|
173
|
-
function withBearer(server, token) {
|
|
174
|
-
return {
|
|
175
|
-
...server,
|
|
176
|
-
headers: { ...server.headers, Authorization: `Bearer ${token}` },
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
function usesUserCredential(server) {
|
|
180
|
-
return (isCredentialReference(server.credential) &&
|
|
181
|
-
server.credential.owner.type === "user");
|
|
182
|
-
}
|
|
183
|
-
function partitionMcpServers(servers) {
|
|
184
|
-
const eager = {};
|
|
185
|
-
const userCredential = [];
|
|
186
|
-
for (const [name, server] of Object.entries(servers)) {
|
|
187
|
-
if (usesUserCredential(server)) {
|
|
188
|
-
userCredential.push(name);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
eager[name] = server;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return { eager, userCredential };
|
|
195
|
-
}
|
|
196
|
-
function warnDeferredUserCredentialServers(serverNames) {
|
|
197
|
-
console.warn(`[managed-deepagents] MCP server(s) ${serverNames.join(", ")} declare ` +
|
|
198
|
-
`a user credential; tools load after the pre-run credential gate ` +
|
|
199
|
-
`confirms grants for the caller.`);
|
|
200
|
-
}
|
|
201
|
-
function stripManagedToolSelection(servers) {
|
|
202
|
-
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
203
|
-
const { includeTools: _includeTools, excludeTools: _excludeTools, credential: _credential, ...clientConfig } = server;
|
|
204
|
-
return [name, clientConfig];
|
|
205
|
-
}));
|
|
206
|
-
}
|
|
207
|
-
async function loadToolsFromServers(connectors, connectable) {
|
|
208
|
-
const prefixToolNameWithServerName = connectors.prefixToolNameWithServerName ?? true;
|
|
209
|
-
const client = new MultiServerMCPClient({
|
|
210
|
-
mcpServers: stripManagedToolSelection(connectable),
|
|
211
|
-
prefixToolNameWithServerName,
|
|
212
|
-
throwOnLoadError: connectors.throwOnLoadError ?? true,
|
|
213
|
-
useStandardContentBlocks: connectors.useStandardContentBlocks ?? true,
|
|
214
|
-
onConnectionError: connectors.onConnectionError ?? "throw",
|
|
215
|
-
});
|
|
216
|
-
try {
|
|
217
|
-
const tools = await loadToolsByServer(client, connectable, prefixToolNameWithServerName);
|
|
218
|
-
if (!prefixToolNameWithServerName) {
|
|
219
|
-
assertNoToolNameCollisions(tools);
|
|
220
|
-
}
|
|
221
|
-
return { tools, client };
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
await client.close().catch(() => undefined);
|
|
225
|
-
throw error;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
async function loadToolsByServer(client, servers, prefixToolNameWithServerName) {
|
|
229
|
-
const tools = [];
|
|
230
|
-
for (const [serverName, server] of Object.entries(servers)) {
|
|
231
|
-
const serverTools = await client.getTools(serverName);
|
|
232
|
-
tools.push(...filterToolsForServer(serverName, server, serverTools, prefixToolNameWithServerName));
|
|
233
|
-
}
|
|
234
|
-
return tools;
|
|
235
|
-
}
|
|
236
|
-
function filterToolsForServer(serverName, selection, tools, prefixToolNameWithServerName) {
|
|
237
|
-
return selectTools(`MCP server "${serverName}"`, selection, tools, (tool) => {
|
|
238
|
-
const name = tool.name;
|
|
239
|
-
if (typeof name !== "string") {
|
|
240
|
-
return undefined;
|
|
241
|
-
}
|
|
242
|
-
const prefix = `${serverName}__`;
|
|
243
|
-
return prefixToolNameWithServerName && name.startsWith(prefix)
|
|
244
|
-
? name.slice(prefix.length)
|
|
245
|
-
: name;
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
function assertNoToolNameCollisions(tools) {
|
|
249
|
-
const seen = new Set();
|
|
250
|
-
const collisions = new Set();
|
|
251
|
-
for (const tool of tools) {
|
|
252
|
-
const name = tool.name;
|
|
253
|
-
if (typeof name !== "string")
|
|
254
|
-
continue;
|
|
255
|
-
if (seen.has(name))
|
|
256
|
-
collisions.add(name);
|
|
257
|
-
seen.add(name);
|
|
258
|
-
}
|
|
259
|
-
if (collisions.size > 0) {
|
|
260
|
-
throw new Error(`MCP tool name collision: ${[...collisions].sort().join(", ")}. ` +
|
|
261
|
-
"Set `prefixToolNameWithServerName: true` (the default) so each tool is " +
|
|
262
|
-
"namespaced by its server, or rename the conflicting tools.");
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
85
|
export const mcpConnector = defineExtension("connector", {
|
|
266
86
|
kind: "mcp_servers",
|
|
267
87
|
label: "connectors.mcp",
|
|
268
88
|
args: mcpServersConfig,
|
|
269
|
-
tools
|
|
89
|
+
async tools(_ctx, self) {
|
|
90
|
+
const { loadManagedMcpTools } = await import("./load.js");
|
|
91
|
+
return loadManagedMcpTools(self);
|
|
92
|
+
},
|
|
270
93
|
});
|
|
271
94
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/connectors/mcp/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/connectors/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EACL,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA8FrD,8EAA8E;AAC9E,MAAM,eAAe,GACnB,sEAAsE;IACtE,6EAA6E;IAC7E,0CAA0C,CAAC;AAE7C,MAAM,eAAe,GAAG,CAAC;KACtB,WAAW,CAAC;IACX,2EAA2E;IAC3E,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC,QAAQ,EAAE;IACvD,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;QACjC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,KAAK,CAAC,KAAK,KAAK,OAAO;YACrB,CAAC,CAAC,eAAe;YACjB,CAAC,CAAC,mDAAmD;KAC1D,CAAC;IACF,GAAG,EAAE,CAAC;SACH,MAAM,CAAC,yBAAyB,CAAC;SACjC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,yBAAyB,CAAC;IACpE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACpD,GAAG,4BAA4B;CAChC,CAAC;KACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,KAAsB,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEL;;;;;;;GAOG;AACH,MAAM,gBAAgB,GACpB,CAAC,CAAC,MAAM,CAAC;IACP,UAAU,EAAE,CAAC;SACV,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,eAAe,EAAE;QACnC,KAAK,EACH,qEAAqE;KACxE,CAAC;SACD,MAAM,CACL,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAC5C,6BAA6B,CAC9B;IACH,4BAA4B,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACpD,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACxC,wBAAwB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAChD,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE;CACjD,CAAC,CAAC;AAEL;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GACvB,eAAe,CAAC,WAAW,EAAE;IAC3B,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,gBAAgB;IACvB,IAAI,EAAE,gBAAgB;IACtB,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI;QACpB,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAC1D,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpServersDefinition } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Load (and cache) the MCP tools for one connector declaration.
|
|
4
|
+
*
|
|
5
|
+
* A failed load is never cached, so a transient connection error on the first
|
|
6
|
+
* run can be retried on the next, but a misconfigured deployment surfaces the
|
|
7
|
+
* error rather than silently starting with no tools.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadManagedMcpTools(connectors: McpServersDefinition): Promise<unknown[]>;
|
|
10
|
+
/** Clear the process-level MCP tool cache. @internal Test-only. */
|
|
11
|
+
export declare function clearManagedMcpToolsCacheForTests(connectors?: McpServersDefinition): void;
|
|
12
|
+
//# sourceMappingURL=load.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../src/connectors/mcp/load.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAA0B,MAAM,YAAY,CAAC;AAc/E;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,oBAAoB,GAC/B,OAAO,CAAC,OAAO,EAAE,CAAC,CAWpB;AAED,mEAAmE;AACnE,wBAAgB,iCAAiC,CAC/C,UAAU,CAAC,EAAE,oBAAoB,GAChC,IAAI,CAMN"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
|
|
2
|
+
import { selectTools } from "../tool-selection.js";
|
|
3
|
+
/**
|
|
4
|
+
* MCP tools loaded per declared connector, cached for the process lifetime.
|
|
5
|
+
*
|
|
6
|
+
* The compiled entry's graph factory runs `compileManagedAgent` on every
|
|
7
|
+
* invocation, but each connector's MCP connection and tool surface are stable
|
|
8
|
+
* for the life of the deployment — so the client is created and its tools
|
|
9
|
+
* loaded once per declaration, and every subsequent run reuses them. Storing
|
|
10
|
+
* the in-flight promise also dedupes concurrent first runs for the same
|
|
11
|
+
* connector. Multiple `connectors.mcp(...)` modules each get their own entry.
|
|
12
|
+
*/
|
|
13
|
+
const cachedTools = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Load (and cache) the MCP tools for one connector declaration.
|
|
16
|
+
*
|
|
17
|
+
* A failed load is never cached, so a transient connection error on the first
|
|
18
|
+
* run can be retried on the next, but a misconfigured deployment surfaces the
|
|
19
|
+
* error rather than silently starting with no tools.
|
|
20
|
+
*/
|
|
21
|
+
export function loadManagedMcpTools(connectors) {
|
|
22
|
+
const cached = cachedTools.get(connectors);
|
|
23
|
+
if (cached) {
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
26
|
+
const loading = createClientAndLoadTools(connectors).catch((error) => {
|
|
27
|
+
cachedTools.delete(connectors);
|
|
28
|
+
throw error;
|
|
29
|
+
});
|
|
30
|
+
cachedTools.set(connectors, loading);
|
|
31
|
+
return loading;
|
|
32
|
+
}
|
|
33
|
+
/** Clear the process-level MCP tool cache. @internal Test-only. */
|
|
34
|
+
export function clearManagedMcpToolsCacheForTests(connectors) {
|
|
35
|
+
if (connectors) {
|
|
36
|
+
cachedTools.delete(connectors);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
cachedTools.clear();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create the `@langchain/mcp-adapters` client from the declaration and load its
|
|
43
|
+
* tools, applying the managed defaults the spec recommends.
|
|
44
|
+
*
|
|
45
|
+
* `@langchain/mcp-adapters` is a direct dependency of `managed-deepagents`, so
|
|
46
|
+
* the client is imported statically at the top of the module.
|
|
47
|
+
*/
|
|
48
|
+
async function createClientAndLoadTools(connectors) {
|
|
49
|
+
const prefixToolNameWithServerName = connectors.prefixToolNameWithServerName ?? true;
|
|
50
|
+
const client = new MultiServerMCPClient({
|
|
51
|
+
mcpServers: stripManagedToolSelection(connectors.mcpServers),
|
|
52
|
+
prefixToolNameWithServerName,
|
|
53
|
+
throwOnLoadError: connectors.throwOnLoadError ?? true,
|
|
54
|
+
useStandardContentBlocks: connectors.useStandardContentBlocks ?? true,
|
|
55
|
+
onConnectionError: connectors.onConnectionError ?? "throw",
|
|
56
|
+
});
|
|
57
|
+
const tools = await loadToolsByServer(client, connectors.mcpServers, prefixToolNameWithServerName);
|
|
58
|
+
// When prefixing is disabled, names can collide with authored tools or across
|
|
59
|
+
// servers. The spec only allows opting out if MDA validates no collisions.
|
|
60
|
+
if (!prefixToolNameWithServerName) {
|
|
61
|
+
assertNoToolNameCollisions(tools);
|
|
62
|
+
}
|
|
63
|
+
return tools;
|
|
64
|
+
}
|
|
65
|
+
function stripManagedToolSelection(servers) {
|
|
66
|
+
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
67
|
+
const { includeTools: _includeTools, excludeTools: _excludeTools, ...clientConfig } = server;
|
|
68
|
+
// `defaultToolTimeout` is a first-class `@langchain/mcp-adapters` option
|
|
69
|
+
// and stays on the server config. The Python adapter has no equivalent
|
|
70
|
+
// connection kwarg, so the PyPI loader strips it and wraps tools itself.
|
|
71
|
+
return [name, clientConfig];
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
async function loadToolsByServer(client, servers, prefixToolNameWithServerName) {
|
|
75
|
+
const tools = [];
|
|
76
|
+
for (const [serverName, server] of Object.entries(servers)) {
|
|
77
|
+
const serverTools = await client.getTools(serverName);
|
|
78
|
+
tools.push(...filterToolsForServer(serverName, server, serverTools, prefixToolNameWithServerName));
|
|
79
|
+
}
|
|
80
|
+
return tools;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Apply a server's selection, matching against raw MCP tool names.
|
|
84
|
+
*
|
|
85
|
+
* The adapter has already applied the `{server}__` prefix by the time tools get
|
|
86
|
+
* here, so the prefix is stripped back off for matching — an author selects the
|
|
87
|
+
* name the server publishes, not the one MDA renamed it to.
|
|
88
|
+
*/
|
|
89
|
+
function filterToolsForServer(serverName, selection, tools, prefixToolNameWithServerName) {
|
|
90
|
+
return selectTools(`MCP server "${serverName}"`, selection, tools, (tool) => {
|
|
91
|
+
const name = tool.name;
|
|
92
|
+
if (typeof name !== "string") {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const prefix = `${serverName}__`;
|
|
96
|
+
return prefixToolNameWithServerName && name.startsWith(prefix)
|
|
97
|
+
? name.slice(prefix.length)
|
|
98
|
+
: name;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Reject duplicate tool names among the loaded MCP tools. Only reached when the
|
|
103
|
+
* author opted out of server-name prefixing.
|
|
104
|
+
*/
|
|
105
|
+
function assertNoToolNameCollisions(tools) {
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
const collisions = new Set();
|
|
108
|
+
for (const tool of tools) {
|
|
109
|
+
const name = tool.name;
|
|
110
|
+
if (typeof name !== "string")
|
|
111
|
+
continue;
|
|
112
|
+
if (seen.has(name))
|
|
113
|
+
collisions.add(name);
|
|
114
|
+
seen.add(name);
|
|
115
|
+
}
|
|
116
|
+
if (collisions.size > 0) {
|
|
117
|
+
throw new Error(`MCP tool name collision: ${[...collisions].sort().join(", ")}. ` +
|
|
118
|
+
"Set `prefixToolNameWithServerName: true` (the default) so each tool is " +
|
|
119
|
+
"namespaced by its server, or rename the conflicting tools.");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../../src/connectors/mcp/load.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,18 +20,27 @@ export interface ToolSelection<TTool extends string = string> {
|
|
|
20
20
|
/** Tool names to hide. Applied after {@link ToolSelection.includeTools}. */
|
|
21
21
|
excludeTools?: TTool[];
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Shared authoring options for connectors that load remote tools.
|
|
25
|
+
*/
|
|
26
|
+
export interface ToolSelectionConnectorConfig<TTool extends string = string> extends ToolSelection<TTool> {
|
|
27
|
+
/** Timeout for each tool call (milliseconds on the TypeScript runtime). */
|
|
28
|
+
defaultToolTimeout?: number;
|
|
29
|
+
}
|
|
23
30
|
/** Tool names: non-empty, no duplicates within one list. */
|
|
24
|
-
export declare const
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
export declare const toolSelectionConnectorFields: {
|
|
32
|
+
includeTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
33
|
+
excludeTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
34
|
+
defaultToolTimeout: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
};
|
|
36
|
+
/** Shared include/exclude overlap check for connector arg schemas. */
|
|
37
|
+
export declare function checkToolSelectionOverlap(ctx: {
|
|
27
38
|
issues: {
|
|
28
|
-
push
|
|
29
|
-
code: "custom";
|
|
30
|
-
input: unknown;
|
|
31
|
-
message: string;
|
|
32
|
-
}) => void;
|
|
39
|
+
push(issue: unknown): void;
|
|
33
40
|
};
|
|
34
|
-
|
|
41
|
+
value: unknown;
|
|
42
|
+
}, value: ToolSelection): void;
|
|
43
|
+
export declare function toolSelectionConnectorSchema<TArgs, TInput>(extra?: z.ZodRawShape): z.ZodType<TArgs, TInput>;
|
|
35
44
|
/** Read the name a selection matches a loaded tool by. */
|
|
36
45
|
export type ToolNameReader = (tool: unknown) => string | undefined;
|
|
37
46
|
/** Read a loaded tool's own `name`. The default for connectors that don't rename. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-selection.d.ts","sourceRoot":"","sources":["../../src/connectors/tool-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC1D,6DAA6D;IAC7D,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;IACvB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;CACxB;AAED,4DAA4D;AAC5D,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"tool-selection.d.ts","sourceRoot":"","sources":["../../src/connectors/tool-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC1D,6DAA6D;IAC7D,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;IACvB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B,CAC3C,KAAK,SAAS,MAAM,GAAG,MAAM,CAC7B,SAAQ,aAAa,CAAC,KAAK,CAAC;IAC5B,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAOD,4DAA4D;AAC5D,eAAO,MAAM,4BAA4B;IACvC,YAAY;IAgBZ,YAAY;IAgBZ,kBAAkB;CAInB,CAAC;AAEF,sEAAsE;AACtE,wBAAgB,yBAAyB,CACvC,GAAG,EAAE;IAAE,MAAM,EAAE;QAAE,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAC/D,KAAK,EAAE,aAAa,GACnB,IAAI,CAgBN;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,EACxD,KAAK,GAAE,CAAC,CAAC,WAAgB,GACxB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAO1B;AAED,0DAA0D;AAC1D,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;AAEnE,qFAAqF;AACrF,eAAO,MAAM,QAAQ,EAAE,cAGtB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,QAAQ,GAAE,cAAyB,GAClC,OAAO,EAAE,CAwBX"}
|
|
@@ -8,47 +8,73 @@
|
|
|
8
8
|
* silently dropping a tool the agent was meant to have.
|
|
9
9
|
*/
|
|
10
10
|
import { z } from "zod";
|
|
11
|
+
function nonEmpty(field) {
|
|
12
|
+
const message = `${field} must be a non-empty string`;
|
|
13
|
+
return z.string(message).refine((value) => value.trim().length > 0, message);
|
|
14
|
+
}
|
|
11
15
|
/** Tool names: non-empty, no duplicates within one list. */
|
|
12
|
-
export const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
16
|
+
export const toolSelectionConnectorFields = {
|
|
17
|
+
includeTools: z
|
|
18
|
+
.array(nonEmpty("tool name"))
|
|
19
|
+
.check((ctx) => {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (const name of ctx.value) {
|
|
22
|
+
if (seen.has(name)) {
|
|
23
|
+
ctx.issues.push({
|
|
24
|
+
code: "custom",
|
|
25
|
+
input: ctx.value,
|
|
26
|
+
message: `contains duplicate tool name "${name}"`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
seen.add(name);
|
|
25
30
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
})
|
|
32
|
+
.optional(),
|
|
33
|
+
excludeTools: z
|
|
34
|
+
.array(nonEmpty("tool name"))
|
|
35
|
+
.check((ctx) => {
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
for (const name of ctx.value) {
|
|
38
|
+
if (seen.has(name)) {
|
|
39
|
+
ctx.issues.push({
|
|
40
|
+
code: "custom",
|
|
41
|
+
input: ctx.value,
|
|
42
|
+
message: `contains duplicate tool name "${name}"`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
seen.add(name);
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.optional(),
|
|
49
|
+
defaultToolTimeout: z
|
|
50
|
+
.number("defaultToolTimeout must be a positive number")
|
|
51
|
+
.positive("defaultToolTimeout must be a positive number")
|
|
52
|
+
.optional(),
|
|
53
|
+
};
|
|
54
|
+
/** Shared include/exclude overlap check for connector arg schemas. */
|
|
55
|
+
export function checkToolSelectionOverlap(ctx, value) {
|
|
56
|
+
const { includeTools, excludeTools } = value;
|
|
32
57
|
const overlap = (includeTools ?? []).filter((name) => (excludeTools ?? []).includes(name));
|
|
33
|
-
if (overlap.length
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
return `selects tool(s) in both \`includeTools\` and \`excludeTools\`: ${[
|
|
37
|
-
...new Set(overlap),
|
|
38
|
-
]
|
|
39
|
-
.sort()
|
|
40
|
-
.join(", ")}`;
|
|
41
|
-
}
|
|
42
|
-
export function appendToolSelectionOverlapIssue(ctx) {
|
|
43
|
-
const message = toolSelectionOverlapMessage(ctx.value);
|
|
44
|
-
if (message) {
|
|
58
|
+
if (overlap.length > 0) {
|
|
45
59
|
ctx.issues.push({
|
|
46
60
|
code: "custom",
|
|
47
61
|
input: ctx.value,
|
|
48
|
-
message
|
|
62
|
+
message: `selects tool(s) in both \`includeTools\` and \`excludeTools\`: ${[
|
|
63
|
+
...new Set(overlap),
|
|
64
|
+
]
|
|
65
|
+
.sort()
|
|
66
|
+
.join(", ")}`,
|
|
49
67
|
});
|
|
50
68
|
}
|
|
51
69
|
}
|
|
70
|
+
export function toolSelectionConnectorSchema(extra = {}) {
|
|
71
|
+
return z
|
|
72
|
+
.strictObject({ ...toolSelectionConnectorFields, ...extra })
|
|
73
|
+
.check((ctx) => {
|
|
74
|
+
checkToolSelectionOverlap(ctx, ctx.value);
|
|
75
|
+
})
|
|
76
|
+
.prefault({});
|
|
77
|
+
}
|
|
52
78
|
/** Read a loaded tool's own `name`. The default for connectors that don't rename. */
|
|
53
79
|
export const toolName = (tool) => {
|
|
54
80
|
const name = tool.name;
|