managed-deepagents 0.10.2-dev.5 → 0.10.2-dev.7
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/README.md +12 -0
- package/dist/connection-authorization.d.ts +22 -0
- package/dist/connection-authorization.d.ts.map +1 -1
- package/dist/connection-authorization.js +40 -7
- package/dist/connection-authorization.js.map +1 -1
- package/dist/connectors/mcp/index.d.ts.map +1 -1
- package/dist/connectors/mcp/index.js +255 -50
- package/dist/connectors/mcp/index.js.map +1 -1
- package/dist/connectors/mcp/schema.d.ts.map +1 -1
- package/dist/connectors/mcp/schema.js +1 -0
- package/dist/connectors/mcp/schema.js.map +1 -1
- package/dist/connectors/mcp/types.d.ts +10 -0
- package/dist/connectors/mcp/types.d.ts.map +1 -1
- package/dist/credential-gate.d.ts +9 -0
- package/dist/credential-gate.d.ts.map +1 -1
- package/dist/credential-gate.js +178 -18
- package/dist/credential-gate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/backend.d.ts +2 -0
- package/dist/runtime/backend.d.ts.map +1 -1
- package/dist/runtime/backend.js +5 -0
- package/dist/runtime/backend.js.map +1 -1
- package/dist/sandbox.d.ts +7 -0
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +17 -0
- package/dist/sandbox.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1629,6 +1629,9 @@ the installed backend. Use `uploadFiles` and `downloadFiles` for binary data.
|
|
|
1629
1629
|
All paths refer to the current sandbox. Context Hub routes for skills and
|
|
1630
1630
|
memory are not part of this backend.
|
|
1631
1631
|
|
|
1632
|
+
The sandbox opens on its first use. `openSandbox(runtime.backend)` opens it now
|
|
1633
|
+
and returns its name.
|
|
1634
|
+
|
|
1632
1635
|
Private published images can declare registry credentials by environment
|
|
1633
1636
|
variable name; MDA creates or updates the deployment-owned Host registry:
|
|
1634
1637
|
|
|
@@ -1696,6 +1699,15 @@ reports its connection URL; complete OAuth there, then retry the run. API-key
|
|
|
1696
1699
|
MCP servers require the caller to connect the key in LangSmith Tools before
|
|
1697
1700
|
invoking one of their tools.
|
|
1698
1701
|
|
|
1702
|
+
A LangSmith tool kit asks for every unconnected provider before the run starts.
|
|
1703
|
+
Set `jitAuth: true` on `defineMcp` to ask on use instead: the kit's tools for
|
|
1704
|
+
unconnected providers are still offered to the model. When the model calls
|
|
1705
|
+
them, the run interrupts once per model step with the connect links for the
|
|
1706
|
+
providers those calls need, then runs the calls after it resumes. If the run
|
|
1707
|
+
resumes without a connection, or nobody can connect in this run (no signed-in
|
|
1708
|
+
caller, or a scheduled run), those calls return a tool error and the run
|
|
1709
|
+
continues.
|
|
1710
|
+
|
|
1699
1711
|
Deploy the project first, then provision its connections with
|
|
1700
1712
|
`mda connections create`. Agent-owned opaque secrets are scoped to that
|
|
1701
1713
|
deployment, so the command refuses to create one before `mda deploy`.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Resolve user-owned connections or interrupt for authorization. */
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import { type AgentAuth } from "./agent-auth.js";
|
|
3
4
|
export interface PendingCredential {
|
|
4
5
|
slug: string;
|
|
@@ -8,12 +9,33 @@ export interface PendingCredential {
|
|
|
8
9
|
/** Optional display label (e.g. MCP server name). Slack prefers this over slug. */
|
|
9
10
|
provider?: string;
|
|
10
11
|
}
|
|
12
|
+
export declare const pendingCredentialSchema: z.ZodType<PendingCredential>;
|
|
13
|
+
/** Tool metadata key naming the credential a tool needs before its first call. */
|
|
14
|
+
export declare const CONNECT_ON_USE_METADATA = "mda_connect_on_use";
|
|
15
|
+
/** The {@link CONNECT_ON_USE_METADATA} value: the server and credential a tool needs. */
|
|
16
|
+
export interface ConnectOnUse {
|
|
17
|
+
server: string;
|
|
18
|
+
credential: PendingCredential;
|
|
19
|
+
}
|
|
20
|
+
export declare const connectOnUseSchema: z.ZodType<ConnectOnUse>;
|
|
11
21
|
/** Interrupt payload when the caller has not granted one or more connections. */
|
|
12
22
|
export interface CredentialAuthorizationRequiredInterrupt {
|
|
13
23
|
type: "credential_authorization_required";
|
|
14
24
|
message: string;
|
|
15
25
|
credentials: PendingCredential[];
|
|
16
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A tool call was refused until the caller connects its credentials.
|
|
29
|
+
*
|
|
30
|
+
* `credentials` is empty when the refusal carried no link to connect from.
|
|
31
|
+
*/
|
|
32
|
+
export declare class ToolCredentialRequiredError extends Error {
|
|
33
|
+
readonly provider: string;
|
|
34
|
+
readonly credentials: PendingCredential[];
|
|
35
|
+
constructor(provider: string, credentials: readonly PendingCredential[]);
|
|
36
|
+
}
|
|
37
|
+
/** The interrupt payload that asks the caller to connect `credentials`. */
|
|
38
|
+
export declare function credentialAuthorizationInterrupt(credentials: readonly PendingCredential[], message?: string): CredentialAuthorizationRequiredInterrupt;
|
|
17
39
|
interface UserConnectionGrant {
|
|
18
40
|
slug: string;
|
|
19
41
|
kind: "oauth2" | "secret";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-authorization.d.ts","sourceRoot":"","sources":["../src/connection-authorization.ts"],"names":[],"mappings":"AAAA,qEAAqE;
|
|
1
|
+
{"version":3,"file":"connection-authorization.d.ts","sourceRoot":"","sources":["../src/connection-authorization.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAGrE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,uBAAuB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAM/D,CAAC;AAEH,kFAAkF;AAClF,eAAO,MAAM,uBAAuB,uBAAuB,CAAC;AAE5D,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,iBAAiB,CAAC;CAC/B;AAED,eAAO,MAAM,kBAAkB,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAGrD,CAAC;AAEH,iFAAiF;AACjF,MAAM,WAAW,wCAAwC;IACvD,IAAI,EAAE,mCAAmC,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;AAKD;;;;GAIG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IAIlD,QAAQ,CAAC,QAAQ,EAAE,MAAM;IAH3B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,CAAC;IAE1C,YACW,QAAQ,EAAE,MAAM,EACzB,WAAW,EAAE,SAAS,iBAAiB,EAAE,EAK1C;CACF;AAED,2EAA2E;AAC3E,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,SAAS,iBAAiB,EAAE,EACzC,OAAO,SAAgC,GACtC,wCAAwC,CAM1C;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,WAAW,EAAE,SAAS,iBAAiB,EAAE,EACzC,OAAO,SAAgC,GACtC,KAAK,CAQP;AAED,0EAA0E;AAC1E,wBAAsB,uCAAuC,CAC3D,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAK9B;AAED,iFAAiF;AACjF,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAS9B;AAgCD,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,mBAAmB,CAAC,CAa9B"}
|
|
@@ -1,22 +1,55 @@
|
|
|
1
1
|
/** Resolve user-owned connections or interrupt for authorization. */
|
|
2
2
|
import { interrupt } from "@langchain/langgraph";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
import { CredentialAuthorizationRequiredError, CredentialStoreError, } from "./agent-auth.js";
|
|
5
|
+
export const pendingCredentialSchema = z.object({
|
|
6
|
+
slug: z.string(),
|
|
7
|
+
kind: z.enum(["oauth2", "secret"]),
|
|
8
|
+
connect_url: z.string().optional(),
|
|
9
|
+
auth_id: z.string().optional(),
|
|
10
|
+
provider: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
/** Tool metadata key naming the credential a tool needs before its first call. */
|
|
13
|
+
export const CONNECT_ON_USE_METADATA = "mda_connect_on_use";
|
|
14
|
+
export const connectOnUseSchema = z.object({
|
|
15
|
+
server: z.string(),
|
|
16
|
+
credential: pendingCredentialSchema,
|
|
17
|
+
});
|
|
18
|
+
const DEFAULT_AUTHORIZATION_MESSAGE = "Connect the following integrations to continue.";
|
|
19
|
+
/**
|
|
20
|
+
* A tool call was refused until the caller connects its credentials.
|
|
21
|
+
*
|
|
22
|
+
* `credentials` is empty when the refusal carried no link to connect from.
|
|
23
|
+
*/
|
|
24
|
+
export class ToolCredentialRequiredError extends Error {
|
|
25
|
+
provider;
|
|
26
|
+
credentials;
|
|
27
|
+
constructor(provider, credentials) {
|
|
28
|
+
super(`${provider} is not connected`);
|
|
29
|
+
this.provider = provider;
|
|
30
|
+
this.name = "ToolCredentialRequiredError";
|
|
31
|
+
this.credentials = [...credentials];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** The interrupt payload that asks the caller to connect `credentials`. */
|
|
35
|
+
export function credentialAuthorizationInterrupt(credentials, message = DEFAULT_AUTHORIZATION_MESSAGE) {
|
|
36
|
+
return {
|
|
37
|
+
type: "credential_authorization_required",
|
|
38
|
+
message,
|
|
39
|
+
credentials: [...credentials],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
4
42
|
/**
|
|
5
43
|
* Interrupt once with the given missing credentials, then throw.
|
|
6
44
|
*
|
|
7
45
|
* Shared by the credential gate (ordinary MCP connections) and LMT collectors so
|
|
8
46
|
* every missing grant becomes one `credential_authorization_required`.
|
|
9
47
|
*/
|
|
10
|
-
export function raiseCredentialAuthorizationRequired(credentials, message =
|
|
48
|
+
export function raiseCredentialAuthorizationRequired(credentials, message = DEFAULT_AUTHORIZATION_MESSAGE) {
|
|
11
49
|
if (credentials.length === 0) {
|
|
12
50
|
throw new Error("credentials must be non-empty");
|
|
13
51
|
}
|
|
14
|
-
|
|
15
|
-
type: "credential_authorization_required",
|
|
16
|
-
message,
|
|
17
|
-
credentials: [...credentials],
|
|
18
|
-
};
|
|
19
|
-
interrupt(payload);
|
|
52
|
+
interrupt(credentialAuthorizationInterrupt(credentials, message));
|
|
20
53
|
throw new CredentialAuthorizationRequiredError(credentials.map((credential) => credential.slug));
|
|
21
54
|
}
|
|
22
55
|
/** Return pending credentials for missing grants without interrupting. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-authorization.js","sourceRoot":"","sources":["../src/connection-authorization.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAErE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"connection-authorization.js","sourceRoot":"","sources":["../src/connection-authorization.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connectors/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connectors/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,mCAAmC,CAAC;AAO3C,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,0BAA0B,CAAC;AAWlC,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAEpB,mBAAmB,YAAY,CAAC;AAqBhC;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,oBAAoB,GAC/B,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAWlC;AAED,yDAAyD;AACzD,wBAAgB,iCAAiC,CAC/C,UAAU,CAAC,EAAE,oBAAoB,GAChC,IAAI,CAMN;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GACzC,kBAAkB,EAAE,CAoCtB;AAuOD;;;;;GAKG;AACH,wBAAsB,qCAAqC,CACzD,UAAU,EAAE,oBAAoB,EAChC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAM9B;AAuaD;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAC7C,eAAe,GAAG;IAAE,YAAY,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC;AA2PpD,+EAA+E;AAC/E,eAAO,MAAM,SAAS,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,oBAOlD,CAAC"}
|
|
@@ -34,13 +34,14 @@
|
|
|
34
34
|
* });
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
-
import { MCPAdapter, } from "@langchain/mcp-adapters";
|
|
37
|
+
import { loadMcpTools, MCPAdapter, } from "@langchain/mcp-adapters";
|
|
38
38
|
import { AGENT_AUTH_TIMEOUT_MS, agentAuthHeaders, AgentAuth, } from "../../agent-auth.js";
|
|
39
|
-
import { raiseCredentialAuthorizationRequired, readUserConnectionGrant, } from "../../connection-authorization.js";
|
|
39
|
+
import { CONNECT_ON_USE_METADATA, raiseCredentialAuthorizationRequired, readUserConnectionGrant, ToolCredentialRequiredError, } from "../../connection-authorization.js";
|
|
40
40
|
import { defineExtension } from "../../extension.js";
|
|
41
41
|
import { connections, isConnectionReference, } from "../../connections.js";
|
|
42
42
|
import { fetchWithTimeout } from "../../runtime/http.js";
|
|
43
43
|
import { logger } from "../../runtime/log.js";
|
|
44
|
+
import { isRecord } from "../../values.js";
|
|
44
45
|
import { mcpServersConfig } from "./schema.js";
|
|
45
46
|
import { isLangSmithManagedToolsPath, isLangSmithManagedToolsUrl, } from "./langsmith-url.js";
|
|
46
47
|
import { selectTools } from "../tool-selection.js";
|
|
@@ -113,6 +114,9 @@ export function mcpCredentialConsumers(connectors) {
|
|
|
113
114
|
collectPendingCredentials: hasLangSmith
|
|
114
115
|
? (context) => collectPendingManagedToolsCredentials(definition, context)
|
|
115
116
|
: undefined,
|
|
117
|
+
connectLinks: hasLangSmith && definition.jitAuth === true
|
|
118
|
+
? (context, wanted) => managedToolsConnectLinks(definition, context, wanted)
|
|
119
|
+
: undefined,
|
|
116
120
|
load: (accessTokens, context) => loadCallerMcpTools(definition, accessTokens, context),
|
|
117
121
|
});
|
|
118
122
|
}
|
|
@@ -199,28 +203,34 @@ async function loadCallerMcpTools(connectors, accessTokens, context) {
|
|
|
199
203
|
const slug = connection.slug;
|
|
200
204
|
connectable[name] = withConnectionProvider(server, slug, accessToken, () => readUserConnectionGrant(agentAuth, slug).then((grant) => grant.token));
|
|
201
205
|
}
|
|
202
|
-
const loadableLangsmith = await loadableLangSmithServers(connectors, langsmith, context);
|
|
206
|
+
const { servers: loadableLangsmith, deferred } = await loadableLangSmithServers(connectors, langsmith, context);
|
|
203
207
|
Object.assign(connectable, loadableLangsmith);
|
|
204
208
|
if (Object.keys(connectable).length === 0) {
|
|
205
209
|
return { tools: [], close: async () => { } };
|
|
206
210
|
}
|
|
207
|
-
const { tools, client } = await loadToolsFromServers(connectors, connectable
|
|
211
|
+
const { tools, client } = await loadToolsFromServers(connectors, connectable, {
|
|
212
|
+
stored: deferred,
|
|
213
|
+
askToConnect: new Set(connectors.jitAuth === true ? Object.keys(loadableLangsmith) : []),
|
|
214
|
+
});
|
|
208
215
|
return { tools, close: () => client.close() };
|
|
209
216
|
}
|
|
210
217
|
/**
|
|
211
|
-
* Probe LangSmith MCP servers once: ready configs
|
|
218
|
+
* Probe LangSmith MCP servers once: ready configs, pending and deferred credentials.
|
|
212
219
|
*
|
|
213
220
|
* Shared by load (interrupt if pending) and the credential-gate collector
|
|
214
|
-
* (return pending only).
|
|
215
|
-
*
|
|
221
|
+
* (return pending only). With `jitAuth`, a kit's missing credentials that
|
|
222
|
+
* come with stored tools are deferred to first use instead of pending.
|
|
223
|
+
* Transport failures follow `throwOnLoadError`; authorization failures are
|
|
224
|
+
* never skipped.
|
|
216
225
|
*/
|
|
217
226
|
async function resolveLangSmithServers(connectors, serverNames, context) {
|
|
218
227
|
const prepared = prepareLangSmithServers(connectors, serverNames, context);
|
|
219
228
|
if (!prepared)
|
|
220
|
-
return { servers: {}, pending: [] };
|
|
229
|
+
return { servers: {}, pending: [], deferred: {} };
|
|
221
230
|
const { matching, headers } = prepared;
|
|
222
231
|
const servers = {};
|
|
223
232
|
const pending = [];
|
|
233
|
+
const deferred = {};
|
|
224
234
|
for (const { name, declared, url } of matching) {
|
|
225
235
|
let probe;
|
|
226
236
|
try {
|
|
@@ -232,26 +242,34 @@ async function resolveLangSmithServers(connectors, serverNames, context) {
|
|
|
232
242
|
console.warn(`[managed-deepagents] MCP server "${name}" failed to load and was skipped: ${error}`);
|
|
233
243
|
continue;
|
|
234
244
|
}
|
|
235
|
-
const required =
|
|
236
|
-
if (required.length > 0) {
|
|
237
|
-
pending.push(...required);
|
|
245
|
+
const required = credentialsFromLangSmithProbe(name, probe, connectors.jitAuth === true);
|
|
246
|
+
if (required.pending.length > 0) {
|
|
247
|
+
pending.push(...required.pending);
|
|
238
248
|
continue;
|
|
239
249
|
}
|
|
240
|
-
|
|
250
|
+
const server = {
|
|
241
251
|
...declared,
|
|
242
252
|
transport: "http",
|
|
243
253
|
headers,
|
|
244
254
|
};
|
|
255
|
+
// A call that declares elicitation makes LangSmith answer with a bare URL
|
|
256
|
+
// elicitation instead of the 428 naming the credential to connect.
|
|
257
|
+
servers[name] =
|
|
258
|
+
connectors.jitAuth === true && server.mode !== "legacy"
|
|
259
|
+
? { ...server, elicitation: false }
|
|
260
|
+
: server;
|
|
261
|
+
if (required.deferred.length > 0)
|
|
262
|
+
deferred[name] = required.deferred;
|
|
245
263
|
}
|
|
246
|
-
return { servers, pending };
|
|
264
|
+
return { servers, pending, deferred };
|
|
247
265
|
}
|
|
248
266
|
async function loadableLangSmithServers(connectors, serverNames, context) {
|
|
249
|
-
const { servers, pending } = await resolveLangSmithServers(connectors, serverNames, context);
|
|
267
|
+
const { servers, pending, deferred } = await resolveLangSmithServers(connectors, serverNames, context);
|
|
250
268
|
// Authorization failures must still stop the load so it remains uncached.
|
|
251
269
|
if (pending.length > 0) {
|
|
252
270
|
raiseCredentialAuthorizationRequired(pending);
|
|
253
271
|
}
|
|
254
|
-
return servers;
|
|
272
|
+
return { servers, deferred };
|
|
255
273
|
}
|
|
256
274
|
/**
|
|
257
275
|
* Collect pending Managed Tools credentials without interrupting.
|
|
@@ -266,6 +284,34 @@ export async function collectPendingManagedToolsCredentials(connectors, context)
|
|
|
266
284
|
return (await resolveLangSmithServers(connectors, serverNames, context))
|
|
267
285
|
.pending;
|
|
268
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Fresh connect links for the wanted slugs of each kit, and only those kits.
|
|
289
|
+
*
|
|
290
|
+
* The gate caches loaded tools, so the links their metadata carries can
|
|
291
|
+
* predate a connection; a new listing names only what is still missing.
|
|
292
|
+
*/
|
|
293
|
+
async function managedToolsConnectLinks(connectors, context, wanted) {
|
|
294
|
+
const prepared = prepareLangSmithServers(connectors, [...wanted.keys()], context);
|
|
295
|
+
if (!prepared)
|
|
296
|
+
return [];
|
|
297
|
+
const links = [];
|
|
298
|
+
for (const { name, url } of prepared.matching) {
|
|
299
|
+
let pending;
|
|
300
|
+
try {
|
|
301
|
+
const probe = await probeLangSmithServer(url, prepared.headers);
|
|
302
|
+
pending = credentialsFromLangSmithProbe(name, probe, false).pending;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
// Each refused call still asks on its own with the link from its 428.
|
|
306
|
+
logger.warn("could not list connect links for LangSmith MCP server " +
|
|
307
|
+
`"${logger.logIdentifier(name)}": ${logger.safeErrorLabel(error)}`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const slugs = wanted.get(name);
|
|
311
|
+
links.push(...pending.filter((credential) => slugs?.has(credential.slug)));
|
|
312
|
+
}
|
|
313
|
+
return links;
|
|
314
|
+
}
|
|
269
315
|
function prepareLangSmithServers(connectors, serverNames, context) {
|
|
270
316
|
if (serverNames.length === 0)
|
|
271
317
|
return undefined;
|
|
@@ -296,22 +342,26 @@ function prepareLangSmithServers(connectors, serverNames, context) {
|
|
|
296
342
|
}
|
|
297
343
|
/**
|
|
298
344
|
* Map a Managed Tools probe result to pending credentials from a complete 428
|
|
299
|
-
* or a partial kit's `_meta`, or throw for incomplete 428s.
|
|
300
|
-
*
|
|
345
|
+
* or a partial kit's `_meta`, or throw for incomplete 428s. With `jitAuth`,
|
|
346
|
+
* `_meta` entries that carry stored tools are deferred instead of pending. No
|
|
347
|
+
* pending credentials means the server's connected tools load.
|
|
301
348
|
*/
|
|
302
|
-
function
|
|
349
|
+
function credentialsFromLangSmithProbe(serverName, probe, jitAuth) {
|
|
303
350
|
if (probe.status === 200) {
|
|
304
|
-
return
|
|
351
|
+
return credentialsFromLangSmithMeta(probe.body, serverName, jitAuth);
|
|
305
352
|
}
|
|
306
353
|
if (probe.status !== 428)
|
|
307
|
-
return [];
|
|
354
|
+
return { pending: [], deferred: [] };
|
|
308
355
|
const credentials = probe.body.credentials;
|
|
309
356
|
if (Array.isArray(credentials)) {
|
|
310
|
-
return
|
|
357
|
+
return {
|
|
358
|
+
pending: pendingCredentialsFromLangsmithGateway428(credentials, serverName),
|
|
359
|
+
deferred: [],
|
|
360
|
+
};
|
|
311
361
|
}
|
|
312
362
|
const pending = pendingCredentialFromLangsmith428(probe.body, serverName);
|
|
313
363
|
if (pending !== undefined)
|
|
314
|
-
return [pending];
|
|
364
|
+
return { pending: [pending], deferred: [] };
|
|
315
365
|
const verificationUrl = probe.body.verification_url;
|
|
316
366
|
if (typeof verificationUrl === "string" && verificationUrl) {
|
|
317
367
|
throw new Error(`LangSmith MCP server "${serverName}" requires authorization. ` +
|
|
@@ -325,29 +375,57 @@ function pendingCredentialsFromLangSmithProbe(serverName, probe) {
|
|
|
325
375
|
*
|
|
326
376
|
* The kit's connected tools work without these, so entries without a
|
|
327
377
|
* complete link are skipped with a warning instead of failing the run.
|
|
378
|
+
* With `jitAuth`, an entry with at least one valid stored tool is deferred to
|
|
379
|
+
* first use; invalid stored definitions are skipped with a warning.
|
|
328
380
|
*/
|
|
329
|
-
function
|
|
330
|
-
const
|
|
331
|
-
const
|
|
332
|
-
? result._meta
|
|
333
|
-
: undefined;
|
|
334
|
-
const entries = typeof meta === "object" && meta !== null
|
|
381
|
+
function credentialsFromLangSmithMeta(body, serverName, jitAuth) {
|
|
382
|
+
const meta = isRecord(body.result) ? body.result._meta : undefined;
|
|
383
|
+
const entries = isRecord(meta)
|
|
335
384
|
? meta["com.langchain.langsmith/missingCredentials"]
|
|
336
385
|
: undefined;
|
|
337
386
|
if (!Array.isArray(entries))
|
|
338
|
-
return [];
|
|
387
|
+
return { pending: [], deferred: [] };
|
|
339
388
|
const pending = [];
|
|
389
|
+
const deferred = [];
|
|
340
390
|
for (const entry of entries) {
|
|
341
|
-
const credential =
|
|
391
|
+
const credential = isRecord(entry)
|
|
342
392
|
? pendingCredentialFromLangsmith428(entry, serverName)
|
|
343
393
|
: undefined;
|
|
344
|
-
if (credential === undefined) {
|
|
394
|
+
if (!isRecord(entry) || credential === undefined) {
|
|
345
395
|
warnSkippedMetaCredential(serverName, entry);
|
|
346
396
|
continue;
|
|
347
397
|
}
|
|
398
|
+
const tools = jitAuth ? storedToolDefinitions(serverName, entry.tools) : [];
|
|
399
|
+
if (tools.length > 0) {
|
|
400
|
+
deferred.push({ credential, tools });
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
348
403
|
pending.push(credential);
|
|
349
404
|
}
|
|
350
|
-
return pending;
|
|
405
|
+
return { pending, deferred };
|
|
406
|
+
}
|
|
407
|
+
/** The valid MCP tool definitions LMT stored for a provider; warns if any were not. */
|
|
408
|
+
function storedToolDefinitions(serverName, tools) {
|
|
409
|
+
if (!Array.isArray(tools))
|
|
410
|
+
return [];
|
|
411
|
+
const valid = tools.filter(isMcpTool);
|
|
412
|
+
if (valid.length < tools.length) {
|
|
413
|
+
logger.warn("skipped invalid stored tools for LangSmith MCP server " +
|
|
414
|
+
`"${logger.logIdentifier(serverName)}"`);
|
|
415
|
+
}
|
|
416
|
+
return valid;
|
|
417
|
+
}
|
|
418
|
+
function isMcpTool(value) {
|
|
419
|
+
if (!isRecord(value))
|
|
420
|
+
return false;
|
|
421
|
+
const { name, title, description, inputSchema, annotations } = value;
|
|
422
|
+
return (typeof name === "string" &&
|
|
423
|
+
name.length > 0 &&
|
|
424
|
+
(title === undefined || typeof title === "string") &&
|
|
425
|
+
(description === undefined || typeof description === "string") &&
|
|
426
|
+
isRecord(inputSchema) &&
|
|
427
|
+
inputSchema.type === "object" &&
|
|
428
|
+
(annotations === undefined || isRecord(annotations)));
|
|
351
429
|
}
|
|
352
430
|
/** Name the skipped entry's provider and missing fields, never their values. */
|
|
353
431
|
function warnSkippedMetaCredential(serverName, entry) {
|
|
@@ -372,28 +450,35 @@ function warnSkippedMetaCredential(serverName, entry) {
|
|
|
372
450
|
* any of them throw with every missing name before an interrupt is shown.
|
|
373
451
|
*/
|
|
374
452
|
function pendingCredentialsFromLangsmithGateway428(credentials, serverName) {
|
|
453
|
+
const { pending, unresolved } = partitionLangSmith428Entries(credentials, serverName);
|
|
454
|
+
if (unresolved.length > 0 || pending.length === 0) {
|
|
455
|
+
const names = unresolved.length > 0 ? unresolved.join(", ") : serverName;
|
|
456
|
+
throw new Error(`LangSmith MCP server "${serverName}" requires credentials for ${names}; ` +
|
|
457
|
+
"connect them in LangSmith Tools before running the agent");
|
|
458
|
+
}
|
|
459
|
+
return pending;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Split 428 entries into interrupt credentials and the labels of entries
|
|
463
|
+
* without a complete link (`display_name`, `slug`, else the server name).
|
|
464
|
+
*/
|
|
465
|
+
function partitionLangSmith428Entries(entries, serverName) {
|
|
375
466
|
const pending = [];
|
|
376
467
|
const unresolved = [];
|
|
377
|
-
for (const entry of
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
const body = entry;
|
|
383
|
-
const credential = pendingCredentialFromLangsmith428(body, serverName);
|
|
468
|
+
for (const entry of entries) {
|
|
469
|
+
const credential = isRecord(entry)
|
|
470
|
+
? pendingCredentialFromLangsmith428(entry, serverName)
|
|
471
|
+
: undefined;
|
|
384
472
|
if (credential !== undefined) {
|
|
385
473
|
pending.push(credential);
|
|
386
474
|
continue;
|
|
387
475
|
}
|
|
388
|
-
const label =
|
|
476
|
+
const label = isRecord(entry)
|
|
477
|
+
? entry.display_name || entry.slug
|
|
478
|
+
: undefined;
|
|
389
479
|
unresolved.push(typeof label === "string" && label ? label : serverName);
|
|
390
480
|
}
|
|
391
|
-
|
|
392
|
-
const names = unresolved.length > 0 ? unresolved.join(", ") : serverName;
|
|
393
|
-
throw new Error(`LangSmith MCP server "${serverName}" requires credentials for ${names}; ` +
|
|
394
|
-
"connect them in LangSmith Tools before running the agent");
|
|
395
|
-
}
|
|
396
|
-
return pending;
|
|
481
|
+
return { pending, unresolved };
|
|
397
482
|
}
|
|
398
483
|
/**
|
|
399
484
|
* Build an interrupt credential only when LMT returns the full triple.
|
|
@@ -516,7 +601,7 @@ function warnDeferredUserConnectionServers(serverNames) {
|
|
|
516
601
|
`a user connection; tools load after the pre-run credential gate ` +
|
|
517
602
|
`confirms grants for the caller.`);
|
|
518
603
|
}
|
|
519
|
-
async function loadToolsFromServers(connectors, connectable) {
|
|
604
|
+
async function loadToolsFromServers(connectors, connectable, kits = {}) {
|
|
520
605
|
const { prefixToolNameWithServerName, throwOnLoadError, onConnectionError } = connectors;
|
|
521
606
|
const client = new MCPAdapter({
|
|
522
607
|
servers: Object.fromEntries(Object.entries(connectable).map(([name, { includeTools: _includeTools, excludeTools: _excludeTools, connection: _connection, ...server },]) => [name, server])),
|
|
@@ -527,7 +612,7 @@ async function loadToolsFromServers(connectors, connectable) {
|
|
|
527
612
|
// The adapter prefixes by default; tool selection strips the same prefix.
|
|
528
613
|
const prefixed = prefixToolNameWithServerName ?? true;
|
|
529
614
|
try {
|
|
530
|
-
const tools = await loadToolsByServer(client, connectable, prefixed);
|
|
615
|
+
const tools = await loadToolsByServer(client, connectable, prefixed, kits);
|
|
531
616
|
assertNoToolNameCollisions(tools);
|
|
532
617
|
return { tools, client };
|
|
533
618
|
}
|
|
@@ -536,16 +621,136 @@ async function loadToolsFromServers(connectors, connectable) {
|
|
|
536
621
|
throw error;
|
|
537
622
|
}
|
|
538
623
|
}
|
|
539
|
-
async function loadToolsByServer(client, servers, prefixToolNameWithServerName) {
|
|
624
|
+
async function loadToolsByServer(client, servers, prefixToolNameWithServerName, { stored = {}, askToConnect = new Set() } = {}) {
|
|
540
625
|
// One discovery: listTools(name) would rediscover every server on each call.
|
|
541
626
|
const toolsets = await client.listToolsets();
|
|
542
627
|
const tools = [];
|
|
543
628
|
for (const [serverName, server] of Object.entries(servers)) {
|
|
544
|
-
|
|
629
|
+
let serverTools = toolsets[serverName] ?? [];
|
|
630
|
+
const definitions = stored[serverName] ?? [];
|
|
631
|
+
if (serverName in toolsets && definitions.length > 0) {
|
|
632
|
+
serverTools = [
|
|
633
|
+
...serverTools,
|
|
634
|
+
...(await storedTools(client, serverName, server, definitions, serverTools, prefixToolNameWithServerName)),
|
|
635
|
+
];
|
|
636
|
+
}
|
|
637
|
+
if (askToConnect.has(serverName)) {
|
|
638
|
+
serverTools = serverTools.map((tool) => askingToConnect(serverName, tool));
|
|
639
|
+
}
|
|
545
640
|
tools.push(...filterToolsForServer(serverName, server, serverTools, prefixToolNameWithServerName));
|
|
546
641
|
}
|
|
547
642
|
return tools;
|
|
548
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* Bind a kit's stored definitions through the adapter's own tool path: the
|
|
646
|
+
* server's connected client serves them as its listing, so calls reach the
|
|
647
|
+
* kit by raw name exactly as live tools do. Live tools win a name clash.
|
|
648
|
+
*
|
|
649
|
+
* Each carries the credential it needs, so the gate can ask once per model step.
|
|
650
|
+
*/
|
|
651
|
+
async function storedTools(adapter, serverName, server, deferred, live, prefixToolNameWithServerName) {
|
|
652
|
+
const connected = await adapter.getClient(serverName);
|
|
653
|
+
if (!connected) {
|
|
654
|
+
logger.warn("skipped stored tools for LangSmith MCP server " +
|
|
655
|
+
`"${logger.logIdentifier(serverName)}": it is not connected`);
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
const names = new Set(live.map((tool) => tool.name));
|
|
659
|
+
const listing = [];
|
|
660
|
+
const connects = new Map();
|
|
661
|
+
for (const { credential, tools } of deferred) {
|
|
662
|
+
for (const definition of tools) {
|
|
663
|
+
const name = prefixToolNameWithServerName
|
|
664
|
+
? `${serverName}_${definition.name}`
|
|
665
|
+
: definition.name;
|
|
666
|
+
if (names.has(name))
|
|
667
|
+
continue;
|
|
668
|
+
names.add(name);
|
|
669
|
+
listing.push(definition);
|
|
670
|
+
connects.set(name, { server: serverName, credential });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (listing.length === 0)
|
|
674
|
+
return [];
|
|
675
|
+
const listTools = async () => ({ tools: listing });
|
|
676
|
+
// The live listing omits unconnected tools, so loadMcpTools builds them from
|
|
677
|
+
// the stored definitions.
|
|
678
|
+
const view = new Proxy(connected, {
|
|
679
|
+
get(target, property) {
|
|
680
|
+
if (property === "listTools")
|
|
681
|
+
return listTools;
|
|
682
|
+
const value = Reflect.get(target, property, target);
|
|
683
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
const { defaultToolTimeout, outputHandling, logLevel } = server;
|
|
687
|
+
const tools = await loadMcpTools(serverName, view, {
|
|
688
|
+
prefixToolNameWithServerName,
|
|
689
|
+
throwOnLoadError: false,
|
|
690
|
+
// Stored tools exist only with jitAuth; see `resolveLangSmithServers`.
|
|
691
|
+
elicitation: false,
|
|
692
|
+
...(defaultToolTimeout !== undefined && { defaultToolTimeout }),
|
|
693
|
+
...(outputHandling !== undefined && { outputHandling }),
|
|
694
|
+
...(logLevel !== undefined && { logLevel }),
|
|
695
|
+
});
|
|
696
|
+
for (const tool of tools) {
|
|
697
|
+
tool.metadata = {
|
|
698
|
+
...tool.metadata,
|
|
699
|
+
[CONNECT_ON_USE_METADATA]: connects.get(tool.name),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
return tools;
|
|
703
|
+
}
|
|
704
|
+
/** Turn a kit's 428 for one call into a {@link ToolCredentialRequiredError}. */
|
|
705
|
+
function askingToConnect(serverName, tool) {
|
|
706
|
+
const call = tool.func.bind(tool);
|
|
707
|
+
tool.func = async (...args) => {
|
|
708
|
+
try {
|
|
709
|
+
return await call(...args);
|
|
710
|
+
}
|
|
711
|
+
catch (error) {
|
|
712
|
+
throw toolCredentialRequired(serverName, error) ?? error;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
return tool;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Map a kit's 428 anywhere in `error`'s cause chain; credentials stay empty
|
|
719
|
+
* without a complete link. The SDK transport reports the status and body as
|
|
720
|
+
* an HTTP error's `data`, which the adapter wraps in a `ToolException`.
|
|
721
|
+
*/
|
|
722
|
+
function toolCredentialRequired(serverName, error) {
|
|
723
|
+
for (let cause = error; cause instanceof Error; cause = cause.cause) {
|
|
724
|
+
// The SDK's `SdkHttpError` carries `data: { status, text }`, but neither
|
|
725
|
+
// the adapter nor a direct dependency exports that class to check against.
|
|
726
|
+
const data = cause.data;
|
|
727
|
+
if (isRecord(data) && data.status === 428) {
|
|
728
|
+
return refusalFromLangSmith428(serverName, data.text);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
function refusalFromLangSmith428(serverName, text) {
|
|
734
|
+
let body;
|
|
735
|
+
try {
|
|
736
|
+
body = typeof text === "string" ? JSON.parse(text) : undefined;
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
body = undefined;
|
|
740
|
+
}
|
|
741
|
+
const listed = isRecord(body) ? body.credentials : undefined;
|
|
742
|
+
const { pending, unresolved } = partitionLangSmith428Entries(Array.isArray(listed) ? listed : [body], serverName);
|
|
743
|
+
if (unresolved.length > 0 || pending.length === 0) {
|
|
744
|
+
logger.warn("could not read the connect link from a LangSmith MCP server " +
|
|
745
|
+
`"${logger.logIdentifier(serverName)}" refusal`);
|
|
746
|
+
const labels = [...new Set(unresolved)].join(", ");
|
|
747
|
+
return new ToolCredentialRequiredError(labels || serverName, []);
|
|
748
|
+
}
|
|
749
|
+
const provider = [
|
|
750
|
+
...new Set(pending.map((credential) => credential.provider ?? serverName)),
|
|
751
|
+
].join(", ");
|
|
752
|
+
return new ToolCredentialRequiredError(provider, pending);
|
|
753
|
+
}
|
|
549
754
|
function filterToolsForServer(serverName, selection, tools, prefixToolNameWithServerName) {
|
|
550
755
|
return selectTools(`MCP server "${serverName}"`, selection, tools, (tool) => {
|
|
551
756
|
const { name } = tool;
|