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 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;AAIrE,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,iFAAiF;AACjF,MAAM,WAAW,wCAAwC;IACvD,IAAI,EAAE,mCAAmC,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;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,SAAoD,GAC1D,KAAK,CAaP;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
+ {"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 = "Connect the following integrations to continue.") {
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
- const payload = {
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":"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"}
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,EAEL,KAAK,YAAY,EACjB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,mCAAmC,CAAC;AAO3C,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,0BAA0B,CAAC;AAUlC,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,CA+BtB;AAmMD;;;;;GAKG;AACH,wBAAsB,qCAAqC,CACzD,UAAU,EAAE,oBAAoB,EAChC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAM9B;AAmVD;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAC7C,eAAe,GAAG;IAAE,YAAY,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC;AA4FpD,+EAA+E;AAC/E,eAAO,MAAM,SAAS,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,oBAOlD,CAAC"}
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 plus any pending credentials.
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). Transport failures follow `throwOnLoadError`;
215
- * authorization failures are never skipped.
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 = pendingCredentialsFromLangSmithProbe(name, probe);
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
- servers[name] = {
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. Returns an empty
300
- * list when the server is fully accessible.
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 pendingCredentialsFromLangSmithProbe(serverName, probe) {
349
+ function credentialsFromLangSmithProbe(serverName, probe, jitAuth) {
303
350
  if (probe.status === 200) {
304
- return pendingCredentialsFromLangSmithMeta(probe.body, serverName);
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 pendingCredentialsFromLangsmithGateway428(credentials, serverName);
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 pendingCredentialsFromLangSmithMeta(body, serverName) {
330
- const result = body.result;
331
- const meta = typeof result === "object" && result !== null
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 = typeof entry === "object" && entry !== null
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 credentials) {
378
- if (typeof entry !== "object" || entry === null) {
379
- unresolved.push(serverName);
380
- continue;
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 = body.display_name || body.slug;
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
- if (unresolved.length > 0 || pending.length === 0) {
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
- const serverTools = toolsets[serverName] ?? [];
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;