@mastra/connect 1.1.0-alpha.5 → 1.2.0-alpha.0

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/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export type { ChannelProviderRegistration, ChannelInstance, ChannelRuntime } fro
6
6
  export { credential } from './credential.js';
7
7
  export { userConnectRoutes } from './user-connect-routes.js';
8
8
  export type { UserConnectRoutesOptions, UserConnectIdentityContext } from './user-connect-routes.js';
9
+ export { CONNECT_PROMPT_TOOL_KEY } from './connect-prompt-tool.js';
10
+ export type { ConnectPromptPayload, ConnectPromptProvider } from './connect-prompt-tool.js';
9
11
  export { MastraConnectError } from './errors.js';
10
12
  export type { MastraConnectErrorCode } from './errors.js';
11
13
  export type { ConnectClientOptions, ConnectionCredential, ProjectConnection } from './client.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,2BAA2B,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACrG,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,2BAA2B,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACrG,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,YAAY,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -372,6 +372,13 @@ const connectionListSchema = z.object({ connections: z.array(connectionSchema) }
372
372
  */
373
373
  const catalogAuthFieldSchema = z.object({
374
374
  name: z.string(),
375
+ /**
376
+ * Where the value goes in a headless auth call: `credentials` are
377
+ * exchanged with the provider (API keys, passwords), `params` configure
378
+ * the flow (e.g. an MCP server URL). Absent on older platforms; clients
379
+ * treat missing as `credentials`.
380
+ */
381
+ target: z.enum(["credentials", "params"]).nullish(),
375
382
  label: z.string().nullish(),
376
383
  description: z.string().nullish(),
377
384
  placeholder: z.string().nullish(),
@@ -388,6 +395,9 @@ const integrationCatalogEntrySchema = z.object({
388
395
  authFields: z.array(catalogAuthFieldSchema).nullish()
389
396
  });
390
397
  const integrationCatalogResponseSchema = z.object({ integrations: z.array(integrationCatalogEntrySchema) });
398
+ const projectIntegrationSettingsResponseSchema = z.object({
399
+ /** Null inherits the full public catalog; an empty array explicitly disables end-user connections. */
400
+ endUserProviders: z.array(z.string()).nullable() });
391
401
  /**
392
402
  * Secondary access token minted by the vendor alongside the primary oauth2
393
403
  * credential (e.g. `devPortalAccessToken` on Microsoft Teams connections).
@@ -439,6 +449,14 @@ async function listIntegrations(client) {
439
449
  if (!parsed.success) throw new MastraConnectError("platform_error", "Platform returned an unexpected integration catalog shape.");
440
450
  return parsed.data.integrations;
441
451
  }
452
+ async function getProjectIntegrationSettings(client, projectId) {
453
+ const response = await platformFetch(client, `/v2/projects/${encodeURIComponent(projectId)}/integration-settings`);
454
+ if (response.status === 404) throw new MastraConnectError("platform_error", `Integration settings were not found for project ${projectId}.`, { status: 404 });
455
+ if (!response.ok) await throwPlatformError(response, `retrieving integration settings for project ${projectId}`);
456
+ const parsed = projectIntegrationSettingsResponseSchema.safeParse(await parsePlatformJson(response, `retrieving integration settings for project ${projectId}`));
457
+ if (!parsed.success) throw new MastraConnectError("platform_error", `Platform returned an unexpected integration settings shape for project ${projectId}.`);
458
+ return parsed.data;
459
+ }
442
460
  async function getConnectionContext(client, connectionId) {
443
461
  const response = await platformFetch(client, `/v2/connections/${encodeURIComponent(connectionId)}/context`);
444
462
  if (!response.ok) await throwPlatformError(response, `retrieving context for connection ${connectionId}`);
@@ -583,8 +601,19 @@ async function proxyRequestWithResponse(client, connectionId, options) {
583
601
  const connectSessionSchema = z.object({
584
602
  connectionId: z.string(),
585
603
  integrationId: z.string(),
586
- connectUrl: z.string(),
587
- sessionToken: z.string(),
604
+ /**
605
+ * Vendor connect link. The platform nulls this for resource-scoped
606
+ * sessions: their link is a bearer capability served only by the launch
607
+ * gate (`getConnectUrl`) under a matching resource assertion, so it never
608
+ * appears in a creation response that could reach a browser.
609
+ */
610
+ connectUrl: z.string().nullish(),
611
+ /**
612
+ * Nango connect session token. Nulled by the platform for resource-scoped
613
+ * sessions for the same reason as `connectUrl`: the token alone can open
614
+ * the vendor Connect UI, so it is an equivalent launch capability.
615
+ */
616
+ sessionToken: z.string().nullish(),
588
617
  expiresAt: z.string()
589
618
  });
590
619
  /**
@@ -603,12 +632,89 @@ async function createProjectConnectSession(client, projectId, integrationId) {
603
632
  if (!parsed.success) throw new MastraConnectError("platform_error", `Platform returned an unexpected connect-session shape for ${integrationId}.`);
604
633
  return parsed.data;
605
634
  }
635
+ const connectUrlSchema = z.object({
636
+ connectUrl: z.string(),
637
+ expiresAt: z.string()
638
+ });
639
+ /**
640
+ * Fetches the vendor connect link for a pending resource-scoped connection
641
+ * from the platform's launch gate. The platform serves it only while the
642
+ * session is pending and unexpired, and only when the client's
643
+ * `x-mastra-resource-id` assertion matches the row — any mismatch is a 404,
644
+ * which surfaces here as `connection_not_found`. This is how the browser's
645
+ * popup enters OAuth: the link itself never reaches browser-visible state.
646
+ */
647
+ async function getConnectUrl(client, connectionId) {
648
+ const response = await platformFetch(client, `/v2/connections/${encodeURIComponent(connectionId)}/connect-url`);
649
+ if (response.status === 404) throw new MastraConnectError("connection_not_found", `No launchable connect session found for connection ${connectionId}.`);
650
+ if (!response.ok) await throwPlatformError(response, `fetching the connect URL for ${connectionId}`);
651
+ const parsed = connectUrlSchema.safeParse(await parsePlatformJson(response, `fetching the connect URL for ${connectionId}`));
652
+ if (!parsed.success) throw new MastraConnectError("platform_error", `Platform returned an unexpected connect-url shape for ${connectionId}.`);
653
+ return parsed.data;
654
+ }
606
655
  /** Disconnects a connection. The platform tears down the vendor side asynchronously. */
607
656
  async function disconnectConnection(client, connectionId) {
608
657
  const response = await platformFetch(client, `/v2/connections/${encodeURIComponent(connectionId)}`, { method: "DELETE" });
609
658
  if (!response.ok) await throwPlatformError(response, `disconnecting connection ${connectionId}`);
610
659
  }
611
660
  //#endregion
661
+ //#region src/connect-prompt-tool.ts
662
+ /**
663
+ * Record key of the built-in connect-prompt tool. Reserved: a provider tool
664
+ * resolving to the same key is a hard error, same as any duplicate.
665
+ */
666
+ const CONNECT_PROMPT_TOOL_KEY = "request_connection";
667
+ /**
668
+ * Builds the built-in tool that lets an agent prompt the end user to connect
669
+ * a provider account mid-conversation. Only added to resource-scoped
670
+ * resolutions: without an end-user identity there is nobody to scope a new
671
+ * connection to.
672
+ *
673
+ * The connectable set respects the effective project policy: an explicit
674
+ * `tools({ providers })` allowlist wins, otherwise the project's provider
675
+ * configuration is loaded from Mastra Platform.
676
+ *
677
+ * `loadInputs` is called per tool invocation so provider policy and connection state are fresh —
678
+ * after the user connects through the rendered card, the agent can call the
679
+ * tool again and see `connected: true` without waiting out the snapshot TTL.
680
+ */
681
+ function buildConnectPromptTool({ loadInputs, resourceId }) {
682
+ return createTool({
683
+ id: CONNECT_PROMPT_TOOL_KEY,
684
+ description: "Ask the current user to connect a provider account (e.g. Linear, Slack). Call this when a task needs a provider whose tools are unavailable because no account is connected, or when the user asks to connect one. The result renders as a connect prompt in the UI — tell the user to use it, and do not fabricate links. Pass `provider` to request one specific provider; omit it to list everything the user can connect.",
685
+ inputSchema: z.object({ provider: z.string().optional().describe("Provider id to request (e.g. \"linear\"). Omit to list all connectable providers.") }),
686
+ execute: async (input) => {
687
+ const { catalog, connections, allowlist } = await loadInputs();
688
+ const allow = allowlist === void 0 ? void 0 : new Set(Array.from(allowlist, (id) => id.toLowerCase()));
689
+ const connectable = catalog.filter((entry) => allow === void 0 || allow.has(entry.id.toLowerCase()));
690
+ const toProvider = (entry) => {
691
+ const usable = connections.filter((connection) => connection.integrationId.toLowerCase() === entry.id.toLowerCase() && connection.status === "active");
692
+ const own = usable.some((connection) => connection.resourceId === resourceId);
693
+ return {
694
+ id: entry.id,
695
+ displayName: entry.displayName ?? null,
696
+ logoUrl: entry.logoUrl ?? null,
697
+ authType: entry.authType ?? null,
698
+ authFields: entry.authFields ?? null,
699
+ connected: usable.length > 0,
700
+ scope: own ? "user" : usable.length > 0 ? "global" : null
701
+ };
702
+ };
703
+ const requestedId = input.provider?.toLowerCase();
704
+ const requested = requestedId === void 0 ? void 0 : connectable.find((entry) => entry.id.toLowerCase() === requestedId);
705
+ if (requested) return {
706
+ type: "connect-prompt",
707
+ requestedProvider: requested.id,
708
+ providers: [toProvider(requested)]
709
+ };
710
+ return {
711
+ type: "connect-prompt",
712
+ providers: connectable.map(toProvider)
713
+ };
714
+ }
715
+ });
716
+ }
717
+ //#endregion
612
718
  //#region src/toolset.ts
613
719
  /**
614
720
  * Compiles a tool-filter entry containing `*` wildcards into an anchored
@@ -75351,6 +75457,7 @@ function sharedStateKey(projectId, ttlMs, options) {
75351
75457
  allowTools: options.allowTools,
75352
75458
  disallowTools: options.disallowTools,
75353
75459
  requireApproval: options.requireApproval,
75460
+ connectPrompt: options.connectPrompt,
75354
75461
  client: {
75355
75462
  baseUrl: options.client?.baseUrl,
75356
75463
  orgId: options.client?.orgId,
@@ -75487,6 +75594,18 @@ async function loadSnapshotInputs(state, client) {
75487
75594
  catalogAvailable: false
75488
75595
  };
75489
75596
  }
75597
+ /** Loads fresh prompt inputs and resolves project policy only when code did not provide an allowlist. */
75598
+ async function loadConnectPromptInputs(state, client) {
75599
+ const codeAllowlist = state.normalizedProviders.only;
75600
+ const [inputs, settings] = await Promise.all([loadSnapshotInputs(state, client), codeAllowlist === void 0 ? getProjectIntegrationSettings(client, state.projectId) : Promise.resolve(void 0)]);
75601
+ const projectProviders = settings?.endUserProviders;
75602
+ const allowlist = codeAllowlist ?? (projectProviders === null || projectProviders === void 0 ? void 0 : new Set(projectProviders));
75603
+ return {
75604
+ catalog: inputs.catalog,
75605
+ connections: inputs.connections,
75606
+ allowlist
75607
+ };
75608
+ }
75490
75609
  /**
75491
75610
  * Fetches a fresh snapshot for one scope, deduplicating concurrent calls per
75492
75611
  * scope. Rejects on failure. A refresh requested while the state is closing
@@ -75505,6 +75624,14 @@ function refreshScope(state, scope) {
75505
75624
  ...scope.resourceId === void 0 ? {} : { resourceId: scope.resourceId }
75506
75625
  };
75507
75626
  const { record, mcpKeys } = await mapTools(connections, requests, state.options, clientOptions, scope.client, state.mcpClients, state.resolverId);
75627
+ if (scope.resourceId !== void 0 && state.options.connectPrompt !== false) {
75628
+ if (record["request_connection"]) throw new MastraConnectConfigError(`Tool key '${CONNECT_PROMPT_TOOL_KEY}' is reserved for the built-in connect-prompt tool; a provider resolved a tool with the same key. Disable it with connectPrompt: false or exclude the conflicting provider tool.`);
75629
+ const client = scope.client;
75630
+ record[CONNECT_PROMPT_TOOL_KEY] = buildConnectPromptTool({
75631
+ loadInputs: () => loadConnectPromptInputs(state, client),
75632
+ resourceId: scope.resourceId
75633
+ });
75634
+ }
75508
75635
  scope.cache = {
75509
75636
  snapshot: record,
75510
75637
  fetchedAt: Date.now(),
@@ -75609,6 +75736,7 @@ function tools(options = {}) {
75609
75736
  rejectRemovedAutoApproveTools(providerOverrides);
75610
75737
  if (options.allowTools !== void 0 && options.disallowTools !== void 0) throw new MastraConnectError("invalid_options", "Top-level allowTools and disallowTools are mutually exclusive; set at most one.");
75611
75738
  if (options.requireApproval !== void 0 && typeof options.requireApproval !== "boolean" && !(Array.isArray(options.requireApproval) && options.requireApproval.every((name) => typeof name === "string"))) throw new MastraConnectError("invalid_options", "Top-level requireApproval must be a boolean or an array of tool keys.");
75739
+ if (options.connectPrompt !== void 0 && typeof options.connectPrompt !== "boolean") throw new MastraConnectError("invalid_options", `Invalid connectPrompt: expected a boolean, got ${typeof options.connectPrompt}.`);
75612
75740
  const state = acquireSharedState(projectId, ttlMs, options, normalizedProviders);
75613
75741
  const validateDynamicResourceId = (raw) => raw === void 0 || raw === null ? void 0 : validateResourceId(raw);
75614
75742
  const resolve = (ctx) => {
@@ -76518,14 +76646,18 @@ function renderConnectPage(params) {
76518
76646
  return res.json();
76519
76647
  })
76520
76648
  .then(function (session) {
76649
+ // The popup enters OAuth through the launch gate (same-origin,
76650
+ // authenticated) rather than a raw vendor link: the server re-checks
76651
+ // that this browser's identity owns the pending session immediately
76652
+ // before redirecting into the provider's consent screen.
76521
76653
  if (popup && !popup.closed) {
76522
- popup.location = session.connectUrl;
76654
+ popup.location = session.launchUrl;
76523
76655
  setStatus('Finish authorizing in the popup…', false);
76524
76656
  } else {
76525
76657
  // Popup blocked: offer a manual link instead of auto-navigating away.
76526
76658
  status.textContent = '';
76527
76659
  var link = document.createElement('a');
76528
- link.href = session.connectUrl;
76660
+ link.href = session.launchUrl;
76529
76661
  link.target = '_blank';
76530
76662
  link.rel = 'noopener';
76531
76663
  link.textContent = 'Popup blocked — click here to authorize';
@@ -76553,6 +76685,7 @@ const RESERVED_PROVIDER_IDS = /* @__PURE__ */ new Set([
76553
76685
  "sessions"
76554
76686
  ]);
76555
76687
  const NO_STORE = "no-store, private";
76688
+ const PROJECT_PROVIDER_POLICY_TTL_MS = 3e4;
76556
76689
  function errorResponse(c, error) {
76557
76690
  c.header("cache-control", NO_STORE);
76558
76691
  return c.json({ error: {
@@ -76562,8 +76695,10 @@ function errorResponse(c, error) {
76562
76695
  }
76563
76696
  /**
76564
76697
  * Maps a platform-call failure to a safe end-user response. Platform detail
76565
- * strings never reach the end user — this is an unauthenticated-adjacent
76566
- * surface, and the details describe the developer's platform account.
76698
+ * strings do not reach the end user — this is an unauthenticated-adjacent
76699
+ * surface, and the details describe the developer's platform account. The one
76700
+ * exception is 409 conflicts, whose details describe the caller's own
76701
+ * connection state and are authored by the platform for display.
76567
76702
  */
76568
76703
  function toRouteError(error) {
76569
76704
  if (error instanceof MastraConnectError) {
@@ -76577,6 +76712,16 @@ function toRouteError(error) {
76577
76712
  code: "misconfigured",
76578
76713
  message: "Connect routes are not configured correctly."
76579
76714
  };
76715
+ if (error.status === 409) return {
76716
+ status: 409,
76717
+ code: "conflict",
76718
+ message: error.detail ?? "The request conflicts with the current state of this connection."
76719
+ };
76720
+ if (error.status === 429) return {
76721
+ status: 429,
76722
+ code: "rate_limited",
76723
+ message: "Too many requests. Try again shortly."
76724
+ };
76580
76725
  return {
76581
76726
  status: 502,
76582
76727
  code: "platform_error",
@@ -76664,18 +76809,44 @@ function connectionScope(connection) {
76664
76809
  * ```
76665
76810
  */
76666
76811
  function userConnectRoutes(options) {
76667
- if (!Array.isArray(options.providers) || options.providers.length === 0) throw new MastraConnectError("invalid_options", "userConnectRoutes requires a non-empty providers allowlist: the end-user surface never defaults open.");
76668
- const providers = options.providers.map((provider) => {
76812
+ if (options.providers !== void 0 && (!Array.isArray(options.providers) || options.providers.length === 0)) throw new MastraConnectError("invalid_options", "Invalid providers: pass a non-empty allowlist, or omit the option to use the project provider configuration.");
76813
+ const providers = options.providers?.map((provider) => {
76669
76814
  const id = typeof provider === "string" ? provider.trim().toLowerCase() : "";
76670
76815
  if (!id) throw new MastraConnectError("invalid_options", "Invalid provider id in userConnectRoutes providers allowlist.");
76671
76816
  if (RESERVED_PROVIDER_IDS.has(id) || id.includes("/")) throw new MastraConnectError("invalid_options", `Invalid provider id '${id}': reserved route segment.`);
76672
76817
  return id;
76673
76818
  });
76674
- const allowlist = new Set(providers);
76819
+ /** Explicit code configuration wins without reading project settings from the platform. */
76820
+ const codeAllowlist = providers === void 0 ? void 0 : new Set(providers);
76675
76821
  const basePath = options.basePath ?? "/connect";
76676
76822
  if (!basePath.startsWith("/") || basePath.endsWith("/") || basePath.includes(":") || basePath.includes("*")) throw new MastraConnectError("invalid_options", `Invalid basePath '${basePath}': expected an absolute path without a trailing slash or route parameters.`);
76677
76823
  const projectId = options.projectId?.trim() || process.env.MASTRA_PROJECT_ID?.trim();
76678
76824
  if (!projectId) throw new MastraConnectError("missing_project_id", "Missing project id: set MASTRA_PROJECT_ID or pass projectId.");
76825
+ let cachedProjectPolicy;
76826
+ let projectPolicyInflight;
76827
+ /** Lazily loads project policy, sharing one short-lived snapshot across end-user requests and status polling. */
76828
+ const resolveProviderPolicy = (client) => {
76829
+ if (providers !== void 0) return Promise.resolve({
76830
+ providers,
76831
+ allowlist: codeAllowlist
76832
+ });
76833
+ if (cachedProjectPolicy && Date.now() - cachedProjectPolicy.fetchedAt < PROJECT_PROVIDER_POLICY_TTL_MS) return Promise.resolve(cachedProjectPolicy.policy);
76834
+ if (!projectPolicyInflight) projectPolicyInflight = getProjectIntegrationSettings(client, projectId).then((settings) => {
76835
+ const configured = settings.endUserProviders?.map((provider) => provider.toLowerCase());
76836
+ const policy = {
76837
+ providers: configured,
76838
+ allowlist: configured === void 0 ? void 0 : new Set(configured)
76839
+ };
76840
+ cachedProjectPolicy = {
76841
+ policy,
76842
+ fetchedAt: Date.now()
76843
+ };
76844
+ return policy;
76845
+ }).finally(() => {
76846
+ projectPolicyInflight = void 0;
76847
+ });
76848
+ return projectPolicyInflight;
76849
+ };
76679
76850
  if (options.client?.resourceId !== void 0) throw new MastraConnectError("invalid_options", "userConnectRoutes derives the resource key per request; do not set client.resourceId.");
76680
76851
  if (options.resourceId !== void 0 && typeof options.resourceId !== "function") throw new MastraConnectError("invalid_options", "Invalid resourceId: userConnectRoutes expects a callback deriving the key per request.");
76681
76852
  const allowedOrigins = options.returnTo?.allowedOrigins ?? [];
@@ -76727,13 +76898,20 @@ function userConnectRoutes(options) {
76727
76898
  return errorResponse(c, toRouteError(error));
76728
76899
  }
76729
76900
  };
76730
- /** Lists the caller's visible connections (global + own), filtered to the allowlist. */
76731
- const listVisibleConnections = async (client) => {
76732
- return (await listProjectConnections(client, projectId)).filter((connection) => allowlist.has(connection.integrationId.toLowerCase()));
76733
- };
76734
- const providerParam = (c) => {
76901
+ /**
76902
+ * Lists the caller's visible connections (global + own, scoped by the
76903
+ * platform via the resource header). Deliberately NOT filtered to the
76904
+ * provider policy: the allowlist gates what end users can newly connect,
76905
+ * not what already exists — agents keep resolving tools from existing
76906
+ * connections, and narrowing the allowlist must not hide (or orphan) a
76907
+ * user's own rows, which they still need to see and disconnect.
76908
+ */
76909
+ const listVisibleConnections = async (client) => listProjectConnections(client, projectId);
76910
+ const providerParam = (c, policy) => {
76735
76911
  const provider = String(c.req.param("provider") ?? "").trim().toLowerCase();
76736
- return allowlist.has(provider) ? provider : null;
76912
+ if (!provider || RESERVED_PROVIDER_IDS.has(provider) || provider.includes("/")) return null;
76913
+ if (policy.allowlist !== void 0 && !policy.allowlist.has(provider)) return null;
76914
+ return provider;
76737
76915
  };
76738
76916
  const notFound = (c, code, message) => errorResponse(c, {
76739
76917
  status: 404,
@@ -76746,18 +76924,19 @@ function userConnectRoutes(options) {
76746
76924
  method: "GET",
76747
76925
  requiresAuth: true,
76748
76926
  handler: gated(async (c, client) => {
76749
- const catalog = await listIntegrations(client);
76927
+ const [catalog, policy] = await Promise.all([listIntegrations(client), resolveProviderPolicy(client)]);
76928
+ const toListed = (entry) => ({
76929
+ id: entry.id,
76930
+ displayName: entry.displayName ?? null,
76931
+ logoUrl: entry.logoUrl ?? null,
76932
+ authType: entry.authType ?? null,
76933
+ authFields: entry.authFields ?? null
76934
+ });
76935
+ if (policy.providers === void 0) return c.json({ providers: catalog.map(toListed) });
76750
76936
  const byId = new Map(catalog.map((entry) => [entry.id.toLowerCase(), entry]));
76751
- const visible = providers.flatMap((id) => {
76937
+ const visible = policy.providers.flatMap((id) => {
76752
76938
  const entry = byId.get(id);
76753
- if (!entry) return [];
76754
- return [{
76755
- id: entry.id,
76756
- displayName: entry.displayName ?? null,
76757
- logoUrl: entry.logoUrl ?? null,
76758
- authType: entry.authType ?? null,
76759
- authFields: entry.authFields ?? null
76760
- }];
76939
+ return entry ? [toListed(entry)] : [];
76761
76940
  });
76762
76941
  return c.json({ providers: visible });
76763
76942
  })
@@ -76767,14 +76946,20 @@ function userConnectRoutes(options) {
76767
76946
  method: "GET",
76768
76947
  requiresAuth: true,
76769
76948
  handler: gated(async (c, client) => {
76770
- const connections = await listVisibleConnections(client);
76771
- return c.json({ connections: connections.map((connection) => ({
76772
- id: connection.id,
76773
- integrationId: connection.integrationId,
76774
- status: connection.status,
76775
- accountLabel: connection.accountLabel ?? null,
76776
- scope: connectionScope(connection)
76777
- })) });
76949
+ const [connections, catalog] = await Promise.all([listVisibleConnections(client), listIntegrations(client).catch(() => [])]);
76950
+ const catalogById = new Map(catalog.map((entry) => [entry.id.toLowerCase(), entry]));
76951
+ return c.json({ connections: connections.map((connection) => {
76952
+ const entry = catalogById.get(connection.integrationId.toLowerCase());
76953
+ return {
76954
+ id: connection.id,
76955
+ integrationId: connection.integrationId,
76956
+ displayName: entry?.displayName ?? null,
76957
+ logoUrl: entry?.logoUrl ?? null,
76958
+ status: connection.status,
76959
+ accountLabel: connection.accountLabel ?? null,
76960
+ scope: connectionScope(connection)
76961
+ };
76962
+ }) });
76778
76963
  })
76779
76964
  },
76780
76965
  {
@@ -76809,19 +76994,46 @@ function userConnectRoutes(options) {
76809
76994
  return c.body(null, 204);
76810
76995
  })
76811
76996
  },
76997
+ {
76998
+ path: `${basePath}/connections/:id/launch`,
76999
+ method: "GET",
77000
+ requiresAuth: true,
77001
+ handler: gated(async (c, client) => {
77002
+ const id = String(c.req.param("id") ?? "");
77003
+ if (!id) return notFound(c, "connection_not_found", "Connection not found.");
77004
+ const { connectUrl, expiresAt } = await getConnectUrl(client, id);
77005
+ c.header("Cache-Control", "no-store");
77006
+ if ((c.req.header("accept") ?? "").includes("application/json")) {
77007
+ let sessionToken = null;
77008
+ try {
77009
+ sessionToken = new URL(connectUrl).searchParams.get("session_token");
77010
+ } catch {
77011
+ sessionToken = null;
77012
+ }
77013
+ return c.json({
77014
+ sessionToken,
77015
+ expiresAt
77016
+ });
77017
+ }
77018
+ return c.redirect(connectUrl, 302);
77019
+ })
77020
+ },
76812
77021
  {
76813
77022
  path: `${basePath}/:provider/sessions`,
76814
77023
  method: "POST",
76815
77024
  requiresAuth: true,
76816
77025
  handler: gated(async (c, client) => {
76817
- const provider = providerParam(c);
77026
+ const policy = await resolveProviderPolicy(client);
77027
+ const provider = providerParam(c, policy);
76818
77028
  if (!provider) return notFound(c, "provider_not_found", "Provider not available.");
77029
+ if (policy.allowlist === void 0) {
77030
+ if (!(await listIntegrations(client)).some((entry) => entry.id.toLowerCase() === provider)) return notFound(c, "provider_not_found", "Provider not available.");
77031
+ }
76819
77032
  const session = await createProjectConnectSession(client, projectId, provider);
76820
77033
  return c.json({
76821
77034
  connectionId: session.connectionId,
76822
77035
  integrationId: session.integrationId,
76823
- connectUrl: session.connectUrl,
76824
- sessionToken: session.sessionToken,
77036
+ launchUrl: `${basePath}/connections/${encodeURIComponent(session.connectionId)}/launch`,
76825
77037
  expiresAt: session.expiresAt
76826
77038
  }, 201);
76827
77039
  })
@@ -76831,7 +77043,8 @@ function userConnectRoutes(options) {
76831
77043
  method: "GET",
76832
77044
  requiresAuth: true,
76833
77045
  handler: gated(async (c, client) => {
76834
- const provider = providerParam(c);
77046
+ const policy = await resolveProviderPolicy(client);
77047
+ const provider = providerParam(c, policy);
76835
77048
  if (!provider) return notFound(c, "provider_not_found", "Provider not available.");
76836
77049
  const returnTo = validateReturnTo(c.req.query("returnTo") ?? void 0, allowedOrigins);
76837
77050
  if (!returnTo.ok) return errorResponse(c, {
@@ -76865,6 +77078,6 @@ function userConnectRoutes(options) {
76865
77078
  ];
76866
77079
  }
76867
77080
  //#endregion
76868
- export { CHANNELS, MastraConnectError, PROVIDERS, channels, credential, tools, userConnectRoutes };
77081
+ export { CHANNELS, CONNECT_PROMPT_TOOL_KEY, MastraConnectError, PROVIDERS, channels, credential, tools, userConnectRoutes };
76869
77082
 
76870
77083
  //# sourceMappingURL=index.js.map