@mastra/connect 1.0.0-alpha.2 → 1.0.0-alpha.4

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.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { MCPClient } from "@mastra/mcp";
2
2
  import { z } from "zod";
3
+ import { SpanType, resolveCurrentSpan } from "@mastra/core/observability";
3
4
  import { createTool } from "@mastra/core/tools";
4
5
  //#region src/errors.ts
5
6
  const MAX_DETAIL_LENGTH = 2e3;
@@ -104,6 +105,98 @@ async function extractProblemDetail(response) {
104
105
  return { isProblemJson };
105
106
  }
106
107
  //#endregion
108
+ //#region src/instrumentation.ts
109
+ const ID_SEGMENT_ROUTES = [{
110
+ prefix: "/v2/connections/",
111
+ placeholder: "{connectionId}",
112
+ key: "connectionId"
113
+ }, {
114
+ prefix: "/v2/projects/",
115
+ placeholder: "{projectId}",
116
+ key: "projectId"
117
+ }];
118
+ const PROXY_SEGMENT = "/proxy/";
119
+ /** Describes a platform request for span naming without leaking query params, ids, or vendor path segments. */
120
+ function describePlatformCall(method, rawPath) {
121
+ const path = rawPath.split("?")[0];
122
+ let route = path;
123
+ const descriptor = {
124
+ method,
125
+ route,
126
+ isProxy: false
127
+ };
128
+ for (const idRoute of ID_SEGMENT_ROUTES) {
129
+ if (!path.startsWith(idRoute.prefix)) continue;
130
+ const rest = path.slice(idRoute.prefix.length);
131
+ const slash = rest.indexOf("/");
132
+ const id = slash === -1 ? rest : rest.slice(0, slash);
133
+ const tail = slash === -1 ? "" : rest.slice(slash);
134
+ descriptor[idRoute.key] = safeDecode(id);
135
+ route = `${idRoute.prefix}${idRoute.placeholder}${tail}`;
136
+ break;
137
+ }
138
+ const proxyIndex = route.indexOf(PROXY_SEGMENT);
139
+ if (proxyIndex !== -1) {
140
+ descriptor.isProxy = true;
141
+ route = `${route.slice(0, proxyIndex + 7)}*`;
142
+ }
143
+ descriptor.route = route;
144
+ return descriptor;
145
+ }
146
+ function safeDecode(segment) {
147
+ try {
148
+ return decodeURIComponent(segment);
149
+ } catch {
150
+ return segment;
151
+ }
152
+ }
153
+ /**
154
+ * Starts a GENERIC child span for an outbound connect HTTP call, or returns
155
+ * `undefined` when no ambient span is active. Span creation failures are
156
+ * swallowed: instrumentation must never break a request.
157
+ */
158
+ function startPlatformCallSpan(descriptor) {
159
+ let parent;
160
+ try {
161
+ parent = resolveCurrentSpan();
162
+ if (!parent) return void 0;
163
+ return parent.createChildSpan({
164
+ type: SpanType.GENERIC,
165
+ name: `connect.${descriptor.isProxy ? "proxy" : "platform"} ${descriptor.method} ${descriptor.route}`,
166
+ metadata: {
167
+ method: descriptor.method,
168
+ route: descriptor.route,
169
+ ...descriptor.connectionId ? { connectionId: descriptor.connectionId } : {},
170
+ ...descriptor.projectId ? { projectId: descriptor.projectId } : {}
171
+ }
172
+ });
173
+ } catch {
174
+ return;
175
+ }
176
+ }
177
+ /** Ends a platform-call span with the response status. Never throws. */
178
+ function endPlatformCallSpan(span, status) {
179
+ if (!span) return;
180
+ try {
181
+ span.end({ metadata: { status } });
182
+ } catch {}
183
+ }
184
+ /**
185
+ * Records a transport-level failure on a platform-call span. Nothing from the
186
+ * original error is recorded — fetch failures can embed the request URL in
187
+ * both `message` and `name`, which may carry secret query params — so the
188
+ * span gets a fixed, constant error. Never throws.
189
+ */
190
+ function errorPlatformCallSpan(span, _error) {
191
+ if (!span) return;
192
+ try {
193
+ span.error({
194
+ error: /* @__PURE__ */ new Error("connect request failed"),
195
+ endSpan: true
196
+ });
197
+ } catch {}
198
+ }
199
+ //#endregion
107
200
  //#region src/client.ts
108
201
  const DEFAULT_INTEGRATIONS_URL = "https://integrations.mastra.ai";
109
202
  const REGIONAL_INTEGRATIONS_URLS = {
@@ -146,20 +239,25 @@ function redact(message, accessToken) {
146
239
  return message.split(accessToken).join("[REDACTED]");
147
240
  }
148
241
  async function platformFetch(client, path, init) {
242
+ const span = startPlatformCallSpan(describePlatformCall(init?.method ?? "GET", path));
149
243
  try {
150
- return await client.fetch(`${client.baseUrl}${path}`, {
244
+ const response = await client.fetch(`${client.baseUrl}${path}`, {
151
245
  ...init,
152
246
  headers: {
153
247
  ...client.headers,
154
248
  ...init?.headers
155
249
  }
156
250
  });
251
+ endPlatformCallSpan(span, response.status);
252
+ return response;
157
253
  } catch (error) {
158
254
  if (error instanceof Error && error.message.includes(client.accessToken)) {
159
255
  const redacted = new Error(redact(error.message, client.accessToken));
160
256
  redacted.name = error.name;
257
+ errorPlatformCallSpan(span, redacted);
161
258
  throw redacted;
162
259
  }
260
+ errorPlatformCallSpan(span, error);
163
261
  throw error;
164
262
  }
165
263
  }
@@ -338,9 +436,6 @@ function assertValidBaseUrlOverride(baseUrlOverride) {
338
436
  if (parsed.protocol !== "https:") throw new MastraConnectError("invalid_options", `Invalid baseUrlOverride '${baseUrlOverride}': only https:// URLs are allowed.`);
339
437
  if (parsed.username || parsed.password) throw new MastraConnectError("invalid_options", `Invalid baseUrlOverride '${baseUrlOverride}': embedded credentials are not allowed.`);
340
438
  }
341
- async function proxyRequest(client, connectionId, options) {
342
- return (await proxyRequestWithResponse(client, connectionId, options)).data;
343
- }
344
439
  /** Preserves HTTP metadata for tools with status-dependent provider contracts. */
345
440
  async function proxyRequestWithResponse(client, connectionId, options) {
346
441
  const cleanPath = options.path.replace(/^\/+/, "");
@@ -427,37 +522,64 @@ async function proxyRequestWithResponse(client, connectionId, options) {
427
522
  }
428
523
  //#endregion
429
524
  //#region src/toolset.ts
430
- /** Resolves a connection id lazily at execute time: option → env var → typed error naming the env var. */
431
- function resolveConnectionId(envVar, connectionId) {
432
- const resolved = connectionId?.trim() || process.env[envVar]?.trim();
433
- if (!resolved) throw new MastraConnectError("missing_connection_id", `Missing connection id: set ${envVar} or pass connectionId.`);
434
- return resolved;
525
+ /**
526
+ * Compiles a tool-filter entry containing `*` wildcards into an anchored
527
+ * regex; every other character matches literally.
528
+ */
529
+ function compileToolPattern(pattern) {
530
+ const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
531
+ return new RegExp(`^${escaped}$`);
435
532
  }
436
533
  /**
437
- * Wraps `createTool` so the tool executes through the platform connection
438
- * proxy. Connection id and client config resolve lazily inside execute, so
439
- * building tools without env vars set never throws.
534
+ * Returns a predicate testing tool keys against filter `entries`, where an
535
+ * entry containing `*` is a glob and anything else matches literally. Used
536
+ * where keys are only known later (MCP approval predicates); list-shaped
537
+ * call sites expand eagerly with {@link expandToolPatterns} instead.
440
538
  */
441
- function defineProxyTool(context, config) {
442
- return createTool({
443
- id: config.id,
444
- description: config.description,
445
- inputSchema: config.inputSchema,
446
- outputSchema: config.outputSchema,
447
- execute: async (input) => {
448
- const connectionId = resolveConnectionId(context.envVar, context.options?.connectionId);
449
- const raw = await proxyRequest(resolveClient(context.options?.client), connectionId, config.request(input));
450
- return config.transform(raw, input);
539
+ function compileToolMatcher(entries) {
540
+ const literals = /* @__PURE__ */ new Set();
541
+ const patterns = [];
542
+ for (const entry of entries) if (entry.includes("*")) patterns.push(compileToolPattern(entry));
543
+ else literals.add(entry);
544
+ return (toolKey) => literals.has(toolKey) || patterns.some((pattern) => pattern.test(toolKey));
545
+ }
546
+ /**
547
+ * Expands `*` glob entries against the known tool keys, preserving order and
548
+ * de-duplicating. Literal entries pass through untouched (callers validate
549
+ * those against their own key sets). A glob that matches nothing throws:
550
+ * like an unknown literal name, a dead pattern in access-limiting config is
551
+ * a typo that must surface instead of silently filtering nothing.
552
+ */
553
+ function expandToolPatterns(entries, knownKeys, label) {
554
+ const expanded = [];
555
+ const seen = /* @__PURE__ */ new Set();
556
+ const push = (key) => {
557
+ if (seen.has(key)) return;
558
+ seen.add(key);
559
+ expanded.push(key);
560
+ };
561
+ for (const entry of entries) {
562
+ if (!entry.includes("*")) {
563
+ push(entry);
564
+ continue;
451
565
  }
452
- });
566
+ const pattern = compileToolPattern(entry);
567
+ const matches = knownKeys.filter((key) => pattern.test(key));
568
+ if (matches.length === 0) throw new MastraConnectConfigError(`Pattern '${entry}' in ${label} matched no tools. Known tools: ${knownKeys.join(", ")}.`);
569
+ for (const match of matches) push(match);
570
+ }
571
+ return expanded;
453
572
  }
454
573
  /**
455
- * Filters a toolset by tool key. Throws at build time on unknown names so
456
- * typos in access-limiting config surface immediately.
574
+ * Filters a toolset by tool key. Entries containing `*` are globs expanded
575
+ * against the toolset's keys. Throws at build time on unknown names and on
576
+ * globs that match nothing, so typos in access-limiting config surface
577
+ * immediately.
457
578
  */
458
- function applyAllowTools(tools, allowTools) {
459
- if (!allowTools) return tools;
579
+ function applyAllowTools(tools, allowToolsInput) {
580
+ if (!allowToolsInput) return tools;
460
581
  const known = Object.keys(tools);
582
+ const allowTools = expandToolPatterns(allowToolsInput, known, "allowTools");
461
583
  const unknown = allowTools.filter((name) => !known.includes(name));
462
584
  if (unknown.length > 0) throw new Error(`Unknown tool name(s) in allowTools: ${unknown.join(", ")}. Known tools: ${known.join(", ")}.`);
463
585
  const filtered = {};
@@ -465,13 +587,15 @@ function applyAllowTools(tools, allowTools) {
465
587
  return filtered;
466
588
  }
467
589
  /**
468
- * Removes tool keys from a toolset. Throws at build time on unknown names so
469
- * typos in access-limiting config surface immediately instead of silently
470
- * removing nothing.
590
+ * Removes tool keys from a toolset. Entries containing `*` are globs
591
+ * expanded against the toolset's keys. Throws at build time on unknown names
592
+ * and on globs that match nothing, so typos in access-limiting config
593
+ * surface immediately instead of silently removing nothing.
471
594
  */
472
- function applyDisallowTools(tools, disallowTools) {
473
- if (!disallowTools || disallowTools.length === 0) return tools;
595
+ function applyDisallowTools(tools, disallowToolsInput) {
596
+ if (!disallowToolsInput || disallowToolsInput.length === 0) return tools;
474
597
  const known = Object.keys(tools);
598
+ const disallowTools = expandToolPatterns(disallowToolsInput, known, "disallowTools");
475
599
  const unknown = disallowTools.filter((name) => !known.includes(name));
476
600
  if (unknown.length > 0) throw new Error(`Unknown tool name(s) in disallowTools: ${unknown.join(", ")}. Known tools: ${known.join(", ")}.`);
477
601
  const remove = new Set(disallowTools);
@@ -709,7 +833,7 @@ function buildProxyMultiConnectionTools(input) {
709
833
  */
710
834
  async function buildMcpMultiConnectionTools(input) {
711
835
  const { registration, connections, allowTools, disallowTools, requireApproval, client, mcpClients, resolverId } = input;
712
- const requireApprovalFor = Array.isArray(requireApproval) ? new Set(requireApproval) : void 0;
836
+ const requireApprovalFor = Array.isArray(requireApproval) ? compileToolMatcher(requireApproval) : void 0;
713
837
  const listToolKey = listConnectionsToolKey(registration.integrationId);
714
838
  const innerAllowTools = allowTools?.filter((name) => name !== listToolKey);
715
839
  const innerDisallowTools = disallowTools?.filter((name) => name !== listToolKey);
@@ -733,7 +857,7 @@ async function buildMcpMultiConnectionTools(input) {
733
857
  id: `mastra-connect-${resolverId}-${registration.integrationId}-${connection.id}`,
734
858
  servers: { [registration.integrationId]: {
735
859
  ...transport,
736
- ...requireApproval === true ? { requireToolApproval: true } : requireApprovalFor ? { requireToolApproval: ({ toolName }) => requireApprovalFor.has(`${registration.integrationId}_${String(toolName)}`) } : {}
860
+ ...requireApproval === true ? { requireToolApproval: true } : requireApprovalFor ? { requireToolApproval: ({ toolName }) => requireApprovalFor(`${registration.integrationId}_${String(toolName)}`) } : {}
737
861
  } }
738
862
  })
739
863
  };
@@ -750,8 +874,8 @@ async function buildMcpMultiConnectionTools(input) {
750
874
  }
751
875
  const catalogUnion = /* @__PURE__ */ new Set();
752
876
  for (const catalog of rawInnerCatalogs.values()) for (const key of Object.keys(catalog)) catalogUnion.add(key);
753
- if (requireApprovalFor) {
754
- const unknown = [...requireApprovalFor].filter((name) => !catalogUnion.has(name));
877
+ if (Array.isArray(requireApproval)) {
878
+ const unknown = expandToolPatterns(requireApproval, [...catalogUnion], `requireApproval for '${registration.integrationId}'`).filter((name) => !catalogUnion.has(name));
755
879
  if (unknown.length > 0) throw new MastraConnectConfigError(`Unknown tool name(s) in requireApproval for '${registration.integrationId}': ${unknown.join(", ")}. Known tools: ${[...catalogUnion].join(", ")}.`);
756
880
  }
757
881
  innerToolsByConnectionId = /* @__PURE__ */ new Map();
@@ -781,7 +905,7 @@ async function buildMcpMultiConnectionTools(input) {
781
905
  }
782
906
  //#endregion
783
907
  //#region src/providers/channels.ts
784
- function credentialToken$1(credential) {
908
+ function credentialToken(credential) {
785
909
  switch (credential.type) {
786
910
  case "oauth2": return credential.accessToken;
787
911
  case "two_step": return credential.token;
@@ -835,7 +959,7 @@ const CHANNELS$1 = [
835
959
  const mod = await import("@mastra/slack");
836
960
  const safeOptions = stripReservedOptions("slack-channels", options);
837
961
  const tokenResolver = async () => {
838
- return credentialToken$1(await runtime.getCredential());
962
+ return credentialToken(await runtime.getCredential());
839
963
  };
840
964
  return { provider: new mod.SlackProvider({
841
965
  tokenResolver,
@@ -849,7 +973,7 @@ const CHANNELS$1 = [
849
973
  const mod = await import("@mastra/telegram");
850
974
  const safeOptions = stripReservedOptions("telegram", options);
851
975
  const tokenResolver = async () => {
852
- return credentialToken$1(await runtime.getCredential());
976
+ return credentialToken(await runtime.getCredential());
853
977
  };
854
978
  return { provider: new mod.TelegramProvider({
855
979
  tokenResolver,
@@ -890,7 +1014,7 @@ const CHANNELS$1 = [
890
1014
  const safeOptions = stripReservedOptions("microsoft-teams", options);
891
1015
  const tokenResolver = async (scope) => {
892
1016
  const fresh = await runtime.getCredential();
893
- if (!(Array.isArray(scope) ? scope : [scope]).includes(mod.TEAMS_DEV_PORTAL_SCOPE)) return credentialToken$1(fresh);
1017
+ if (!(Array.isArray(scope) ? scope : [scope]).includes(mod.TEAMS_DEV_PORTAL_SCOPE)) return credentialToken(fresh);
894
1018
  const token = fresh.type === "oauth2" ? fresh.secondaryAccessTokens?.devPortalAccessToken?.accessToken : void 0;
895
1019
  if (!token) throw new MastraConnectError("no_active_connection", `Teams connection ${runtime.getConnectionId()} has no Dev Portal token on its credential. Reconnect with an integration that requests the ${mod.TEAMS_DEV_PORTAL_SCOPE} scope.`);
896
1020
  return token;
@@ -34617,12 +34741,35 @@ const getCreateIssueMetadataInputSchema = z.object({
34617
34741
  issuetypeIds: z.array(z.string()).optional().describe("List of issue type IDs to filter by. Example: [\"10000\", \"10001\"]"),
34618
34742
  expand: z.string().optional().describe("Use expand to include additional information about issue metadata in the response. Supported values: projects, projects.issuetypes, projects.issuetypes.fields")
34619
34743
  });
34744
+ const CreateIssueFieldSchema = z.object({
34745
+ required: z.boolean().optional(),
34746
+ schema: z.record(z.string(), z.unknown()).optional(),
34747
+ name: z.string().optional(),
34748
+ key: z.string().optional(),
34749
+ hasDefaultValue: z.boolean().optional(),
34750
+ operations: z.array(z.string()).optional(),
34751
+ allowedValues: z.array(z.unknown()).optional(),
34752
+ defaultValue: z.unknown().optional(),
34753
+ autoCompleteUrl: z.string().optional()
34754
+ });
34755
+ const IssueTypeSchema$1 = z.object({
34756
+ id: z.string(),
34757
+ name: z.string(),
34758
+ self: z.string().optional(),
34759
+ description: z.string().optional(),
34760
+ iconUrl: z.string().optional(),
34761
+ avatarId: z.number().optional(),
34762
+ subtask: z.boolean().optional(),
34763
+ hierarchyLevel: z.number().optional(),
34764
+ fields: z.record(z.string(), CreateIssueFieldSchema).optional()
34765
+ });
34620
34766
  const ProjectSchema$3 = z.object({
34621
34767
  id: z.string(),
34622
34768
  key: z.string(),
34623
34769
  name: z.string(),
34624
34770
  self: z.string().optional(),
34625
- avatarUrls: z.record(z.string(), z.string()).optional()
34771
+ avatarUrls: z.record(z.string(), z.string()).optional(),
34772
+ issuetypes: z.array(IssueTypeSchema$1).optional()
34626
34773
  });
34627
34774
  const ProviderResponseSchema$149 = z.object({ projects: z.array(ProjectSchema$3).optional() });
34628
34775
  const getCreateIssueMetadataOutputSchema = z.object({ projects: z.array(ProjectSchema$3).optional() });
@@ -35742,7 +35889,7 @@ z.object({
35742
35889
  cloudId: z.string().optional(),
35743
35890
  baseUrl: z.string().optional()
35744
35891
  });
35745
- const listIssueTypesInputSchema = z.object({});
35892
+ const listIssueTypesInputSchema = z.object({ projectId: z.string().optional().describe("Project ID used to list only issue types available in that project.") });
35746
35893
  const IssueTypeSchema = z.object({
35747
35894
  id: z.string(),
35748
35895
  name: z.string(),
@@ -35771,7 +35918,7 @@ const AccessibleResourceSchema$1 = z.object({
35771
35918
  function listIssueTypesTool(proxy) {
35772
35919
  return createTool({
35773
35920
  id: "jira_list_issue_types",
35774
- description: "List Jira issue types available to the user",
35921
+ description: "List Jira issue types available to the user. Pass projectId to list only the issue types available in that project (recommended before creating an issue).",
35775
35922
  inputSchema: listIssueTypesInputSchema,
35776
35923
  outputSchema: listIssueTypesOutputSchema,
35777
35924
  execute: async (input, { requestContext }) => {
@@ -35806,7 +35953,12 @@ function listIssueTypesTool(proxy) {
35806
35953
  baseUrl
35807
35954
  });
35808
35955
  }
35809
- const config = {
35956
+ const config = input.projectId ? {
35957
+ endpoint: `/ex/jira/${cloudId}/rest/api/3/issuetype/project`,
35958
+ params: { projectId: input.projectId },
35959
+ headers: { "X-Atlassian-Token": "no-check" },
35960
+ retries: 3
35961
+ } : {
35810
35962
  endpoint: `/ex/jira/${cloudId}/rest/api/3/issuetype`,
35811
35963
  headers: { "X-Atlassian-Token": "no-check" },
35812
35964
  retries: 3
@@ -74095,7 +74247,7 @@ function createWorkosTools(options) {
74095
74247
  * Providers with checked-in HTTP toolsets. MCP-backed providers are discovered
74096
74248
  * from the Platform integration catalog at runtime.
74097
74249
  */
74098
- const TOOLS = [
74250
+ const PROVIDERS = [
74099
74251
  anthropicProvider,
74100
74252
  clerkProvider,
74101
74253
  discordProvider,
@@ -74128,27 +74280,16 @@ const TOOLS = [
74128
74280
  }
74129
74281
  ];
74130
74282
  /**
74131
- * @deprecated Use `TOOLS`. This alias is retained for one release cycle so
74132
- * downstream consumers keep working; it will be removed in the next minor.
74133
- */
74134
- const PROVIDERS = TOOLS;
74135
- function findRegistration(integrationId) {
74136
- return TOOLS.find((p) => p.integrationId === integrationId);
74137
- }
74138
- /**
74139
74283
  * Channel-capable providers with a hand-maintained `channels()` registration.
74140
74284
  * The launch ships three (Slack, Telegram, Discord); the provider generator
74141
74285
  * grows to cover new entries when a fourth lands.
74142
74286
  */
74143
74287
  const CHANNELS = CHANNELS$1;
74144
- function findChannelRegistration(integrationId) {
74145
- return CHANNELS.find((c) => c.integrationId === integrationId);
74146
- }
74147
74288
  //#endregion
74148
74289
  //#region src/resolution.ts
74149
74290
  const INTEGRATION_ID_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/;
74150
- function validateIntegrationOverrides(integrations) {
74151
- for (const integrationId of Object.keys(integrations ?? {})) if (!INTEGRATION_ID_PATTERN.test(integrationId)) throw new MastraConnectError("invalid_options", `Invalid provider '${integrationId}' in integrations option: expected 1-128 letters, numbers, underscores, or hyphens.`);
74291
+ function validateProviderIds(providers) {
74292
+ for (const integrationId of Object.keys(providers ?? {})) if (!INTEGRATION_ID_PATTERN.test(integrationId)) throw new MastraConnectError("invalid_options", `Invalid provider '${integrationId}' in providers option: expected 1-128 letters, numbers, underscores, or hyphens.`);
74152
74293
  }
74153
74294
  function connectionIdEnvVar(integrationId) {
74154
74295
  return `MASTRA_${integrationId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_CONNECTION_ID`;
@@ -74163,42 +74304,8 @@ function groupByIntegrationId(connections) {
74163
74304
  return byIntegrationId;
74164
74305
  }
74165
74306
  /**
74166
- * Resolves the connection to use for one provider, per the contract:
74167
- * option/env var wins; else a single active connection; anything else
74168
- * (ambiguity, needs_reauth, no usable candidate) warns and skips so one bad
74169
- * integration never takes down the whole resolution. A needs_reauth
74170
- * connection is never silently mapped.
74171
- */
74172
- function resolveConnection(request, candidates) {
74173
- const integrationId = request.integrationId;
74174
- const directed = request.connectionId?.trim() || process.env[request.envVar]?.trim() || void 0;
74175
- if (directed) {
74176
- const match = candidates.find((connection) => connection.id === directed);
74177
- if (!match) {
74178
- console.warn(`[@mastra/connect] Skipping ${integrationId}: pinned connection ${directed} is not attached to this project.`);
74179
- return;
74180
- }
74181
- if (match.status === "needs_reauth") {
74182
- console.warn(`[@mastra/connect] Skipping ${integrationId}: connection ${directed} needs re-auth.`);
74183
- return;
74184
- }
74185
- if (match.status !== "active") {
74186
- console.warn(`[@mastra/connect] Skipping ${integrationId}: connection ${directed} is not active (status '${match.status}').`);
74187
- return;
74188
- }
74189
- return directed;
74190
- }
74191
- const active = candidates.filter((connection) => connection.status === "active");
74192
- if (active.length === 1) return active[0].id;
74193
- if (active.length === 0) {
74194
- console.warn(`[@mastra/connect] Skipping ${integrationId}: no active connections (found ${candidates.length} in other states).`);
74195
- return;
74196
- }
74197
- console.warn(`[@mastra/connect] Skipping ${integrationId}: ${active.length} active connections; pin one with connectionId or ${request.envVar}.`);
74198
- }
74199
- /**
74200
74307
  * Resolves how to configure one provider given its candidate connections:
74201
- * - An explicit pin (option or env var) forces `single` at that id.
74308
+ * - An explicit pin (`connectionId`) forces `single` at that id.
74202
74309
  * - Exactly one active connection → `single`.
74203
74310
  * - Two or more active connections → `multi`; the caller wraps tools with
74204
74311
  * `connection_name` so the agent chooses per call at execute time.
@@ -74249,12 +74356,12 @@ const FAILURE_COOLDOWN_MS$1 = 3e4;
74249
74356
  let nextResolverId = 0;
74250
74357
  /**
74251
74358
  * Returns a live toolset resolver over the project's Platform connections.
74252
- * HTTP providers are loaded from the shipped `TOOLS` registry. MCP providers
74359
+ * HTTP providers are loaded from the shipped `PROVIDERS` registry. MCP providers
74253
74360
  * are discovered from the Platform integration catalog. Tools from every supported
74254
74361
  * provider with a matching project connection are merged into one flat record
74255
74362
  * (matched by `integrationId`). The resolver serves a cached snapshot,
74256
- * revalidating from the platform every `ttlMs`, so integrations attached
74257
- * to (or detached from) the project are picked up (or dropped) without a
74363
+ * revalidating from the platform every `ttlMs`, so providers connected
74364
+ * to (or disconnected from) the project are picked up (or dropped) without a
74258
74365
  * restart.
74259
74366
  *
74260
74367
  * Configuration errors (missing project id, bad ttlMs, malformed integration id,
@@ -74273,11 +74380,14 @@ function tools(options = {}) {
74273
74380
  const client = resolveClient(options.client);
74274
74381
  const resolverId = ++nextResolverId;
74275
74382
  const mcpClients = /* @__PURE__ */ new Map();
74276
- const integrationOverrides = normalizeIntegrationOverrides(options.integrations);
74277
- validateIntegrationOverrides(integrationOverrides);
74278
- validateIntegrationXor(integrationOverrides);
74279
- validateRequireApproval(integrationOverrides);
74280
- rejectRemovedAutoApproveTools(integrationOverrides);
74383
+ const normalizedProviders = normalizeProviderOverrides(options.providers);
74384
+ const providerOverrides = normalizedProviders.overrides;
74385
+ validateProviderIds(providerOverrides);
74386
+ validateProviderXor(providerOverrides);
74387
+ validateRequireApproval(providerOverrides);
74388
+ rejectRemovedAutoApproveTools(providerOverrides);
74389
+ 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.");
74390
+ 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.");
74281
74391
  let cache;
74282
74392
  let inflight;
74283
74393
  let closing;
@@ -74293,15 +74403,17 @@ function tools(options = {}) {
74293
74403
  const connections = connectionsResult.value;
74294
74404
  if (catalogResult.status === "fulfilled") return {
74295
74405
  connections,
74296
- catalog: catalogResult.value
74406
+ catalog: catalogResult.value,
74407
+ catalogAvailable: true
74297
74408
  };
74298
- const checkedIn = new Set(TOOLS.map((registration) => registration.integrationId));
74299
- if (connections.some((connection) => connection.status === "active" && !checkedIn.has(connection.integrationId) && !integrationOverrides[connection.integrationId]?.disabled)) throw catalogResult.reason;
74409
+ const checkedIn = new Set(PROVIDERS.map((registration) => registration.integrationId));
74410
+ if (connections.some((connection) => connection.status === "active" && !checkedIn.has(connection.integrationId) && !providerOverrides[connection.integrationId]?.disabled && (normalizedProviders.only === void 0 || normalizedProviders.only.has(connection.integrationId)))) throw catalogResult.reason;
74300
74411
  const reason = catalogResult.reason;
74301
74412
  console.warn(`[@mastra/connect] Platform catalog unavailable (${reason instanceof Error ? reason.message : String(reason)}); resolving checked-in providers only.`);
74302
74413
  return {
74303
74414
  connections,
74304
- catalog: []
74415
+ catalog: [],
74416
+ catalogAvailable: false
74305
74417
  };
74306
74418
  };
74307
74419
  /**
@@ -74312,8 +74424,8 @@ function tools(options = {}) {
74312
74424
  if (closing) return closing.then(refresh);
74313
74425
  if (!inflight) inflight = (async () => {
74314
74426
  try {
74315
- const { connections, catalog } = await loadSnapshotInputs();
74316
- const snapshot = await mapTools(connections, buildRequests(integrationOverrides, catalog), options, client, mcpClients, resolverId);
74427
+ const { connections, catalog, catalogAvailable } = await loadSnapshotInputs();
74428
+ const snapshot = await mapTools(connections, buildRequests(normalizedProviders, catalog, catalogAvailable), options, client, mcpClients, resolverId);
74317
74429
  cache = {
74318
74430
  snapshot,
74319
74431
  fetchedAt: Date.now()
@@ -74343,10 +74455,7 @@ function tools(options = {}) {
74343
74455
  }
74344
74456
  return refresh();
74345
74457
  };
74346
- return Object.assign(resolve, {
74347
- invalidate: () => {
74348
- cache = void 0;
74349
- },
74458
+ const resolver = Object.assign(resolve, {
74350
74459
  refresh,
74351
74460
  disconnect: () => {
74352
74461
  closing ??= (async () => {
@@ -74361,28 +74470,75 @@ function tools(options = {}) {
74361
74470
  }
74362
74471
  })();
74363
74472
  return closing;
74364
- }
74473
+ },
74474
+ with: (extra) => withExtraTools(resolver, extra)
74365
74475
  });
74476
+ return resolver;
74366
74477
  }
74367
74478
  /**
74368
- * Turns the two accepted `integrations` shapes into the internal Record form.
74369
- * The string-array shorthand (`["linear", "github"]`) becomes
74370
- * `{ linear: {}, github: {} }`; the object form passes through unchanged.
74371
- * Also rejects malformed inputs early (non-string array entries, duplicates)
74372
- * so a bad option throws at tools() time rather than at first refresh.
74479
+ * Builds the resolver `.with()` returns: resolutions merge the base
74480
+ * resolver's tools with `extra` (extra wins on key collision), cache handles
74481
+ * delegate to the base, and further `.with()` calls chain.
74373
74482
  */
74374
- function normalizeIntegrationOverrides(integrations) {
74375
- if (integrations === void 0) return {};
74376
- if (Array.isArray(integrations)) {
74377
- const record = {};
74378
- for (const entry of integrations) {
74379
- if (typeof entry !== "string") throw new MastraConnectError("invalid_options", `Invalid integrations entry: expected a string integration id, got ${typeof entry}.`);
74380
- if (record[entry] !== void 0) throw new MastraConnectError("invalid_options", `Duplicate integration '${entry}' in integrations array.`);
74381
- record[entry] = {};
74382
- }
74383
- return record;
74483
+ function withExtraTools(base, extra) {
74484
+ const resolveExtra = typeof extra === "function" ? extra : () => extra;
74485
+ const resolve = async (ctx) => {
74486
+ const [baseTools, extraTools] = await Promise.all([base(ctx), resolveExtra(ctx)]);
74487
+ return {
74488
+ ...baseTools,
74489
+ ...extraTools
74490
+ };
74491
+ };
74492
+ const resolver = Object.assign(resolve, {
74493
+ refresh: async () => {
74494
+ const [baseTools, extraTools] = await Promise.all([base.refresh(), resolveExtra()]);
74495
+ return {
74496
+ ...baseTools,
74497
+ ...extraTools
74498
+ };
74499
+ },
74500
+ disconnect: () => base.disconnect(),
74501
+ with: (more) => withExtraTools(resolver, more)
74502
+ });
74503
+ return resolver;
74504
+ }
74505
+ /**
74506
+ * Turns the two accepted `providers` shapes into the internal form. The
74507
+ * array form (`["linear", "github"]`) becomes an allowlist with default
74508
+ * options; the record form expands boolean shorthands (`true` → `{}`,
74509
+ * `false` → an internal exclusion marker). Malformed inputs (non-string
74510
+ * array entries, duplicates, non-boolean/non-object record values) throw at
74511
+ * tools() time rather than at first refresh.
74512
+ */
74513
+ function normalizeProviderOverrides(providers) {
74514
+ if (providers === void 0) return {
74515
+ overrides: {},
74516
+ only: void 0
74517
+ };
74518
+ if (Array.isArray(providers)) {
74519
+ const overrides = {};
74520
+ for (const entry of providers) {
74521
+ if (typeof entry !== "string") throw new MastraConnectError("invalid_options", `Invalid providers entry: expected a string provider id, got ${typeof entry}.`);
74522
+ if (overrides[entry] !== void 0) throw new MastraConnectError("invalid_options", `Duplicate provider '${entry}' in providers array.`);
74523
+ overrides[entry] = {};
74524
+ }
74525
+ return {
74526
+ overrides,
74527
+ only: new Set(Object.keys(overrides))
74528
+ };
74384
74529
  }
74385
- return integrations;
74530
+ const overrides = {};
74531
+ for (const [providerId, value] of Object.entries(providers)) {
74532
+ if (value === void 0) continue;
74533
+ if (value === true) overrides[providerId] = {};
74534
+ else if (value === false) overrides[providerId] = { disabled: true };
74535
+ else if (typeof value === "object" && value !== null && !Array.isArray(value)) overrides[providerId] = value;
74536
+ else throw new MastraConnectError("invalid_options", `Invalid providers entry for '${providerId}': expected true, false, or an options object, got ${value === null ? "null" : Array.isArray(value) ? "an array" : typeof value}.`);
74537
+ }
74538
+ return {
74539
+ overrides,
74540
+ only: void 0
74541
+ };
74386
74542
  }
74387
74543
  /**
74388
74544
  * Rejects any provider that sets both `allowTools` and `disallowTools`.
@@ -74390,8 +74546,8 @@ function normalizeIntegrationOverrides(integrations) {
74390
74546
  * runtime guard catches loosely typed inputs (e.g. built from JSON or a
74391
74547
  * `Record<string, unknown>` upstream).
74392
74548
  */
74393
- function validateIntegrationXor(integrations) {
74394
- for (const [integrationId, providerOptions] of Object.entries(integrations)) {
74549
+ function validateProviderXor(providers) {
74550
+ for (const [integrationId, providerOptions] of Object.entries(providers)) {
74395
74551
  const filters = providerOptions;
74396
74552
  if (filters.allowTools !== void 0 && filters.disallowTools !== void 0) throw new MastraConnectError("invalid_options", `Invalid options for '${integrationId}': allowTools and disallowTools are mutually exclusive; set at most one.`);
74397
74553
  }
@@ -74403,8 +74559,8 @@ function validateIntegrationXor(integrations) {
74403
74559
  * approval on MCP providers silently (and crash the HTTP path into
74404
74560
  * warn-and-skip) instead of gating tools as the author intended.
74405
74561
  */
74406
- function validateRequireApproval(integrations) {
74407
- for (const [integrationId, providerOptions] of Object.entries(integrations)) {
74562
+ function validateRequireApproval(providers) {
74563
+ for (const [integrationId, providerOptions] of Object.entries(providers)) {
74408
74564
  const requireApproval = providerOptions.requireApproval;
74409
74565
  if (requireApproval === void 0 || typeof requireApproval === "boolean") continue;
74410
74566
  if (Array.isArray(requireApproval) && requireApproval.every((name) => typeof name === "string")) continue;
@@ -74417,12 +74573,12 @@ function validateRequireApproval(integrations) {
74417
74573
  * carrying this key would otherwise be ignored silently and its tools would
74418
74574
  * run without the prompts the author expected.
74419
74575
  */
74420
- function rejectRemovedAutoApproveTools(integrations) {
74421
- for (const [integrationId, providerOptions] of Object.entries(integrations)) if ("autoApproveTools" in providerOptions) throw new MastraConnectError("invalid_options", `Invalid options for '${integrationId}': autoApproveTools was removed. Tools no longer require approval by default; opt in with requireApproval: true or a list of tool keys to gate.`);
74576
+ function rejectRemovedAutoApproveTools(providers) {
74577
+ for (const [integrationId, providerOptions] of Object.entries(providers)) if ("autoApproveTools" in providerOptions) throw new MastraConnectError("invalid_options", `Invalid options for '${integrationId}': autoApproveTools was removed. Tools no longer require approval by default; opt in with requireApproval: true or a list of tool keys to gate.`);
74422
74578
  }
74423
- function buildRequests(integrations, catalog) {
74424
- const overrides = integrations;
74425
- const registrations = new Map(TOOLS.map((registration) => [registration.integrationId, registration]));
74579
+ function buildRequests(providers, catalog, catalogAvailable) {
74580
+ const { overrides, only } = providers;
74581
+ const registrations = new Map(PROVIDERS.map((registration) => [registration.integrationId, registration]));
74426
74582
  const catalogIds = new Set(catalog.map((integration) => integration.id));
74427
74583
  for (const integration of catalog) {
74428
74584
  if (!integration.capabilities.mcp) continue;
@@ -74432,9 +74588,14 @@ function buildRequests(integrations, catalog) {
74432
74588
  transport: "mcp"
74433
74589
  });
74434
74590
  }
74435
- for (const integrationId of Object.keys(overrides)) if (!registrations.has(integrationId) && !catalogIds.has(integrationId)) console.warn(`[@mastra/connect] Ignoring unknown integration override '${integrationId}'.`);
74591
+ const unknown = Object.keys(overrides).filter((integrationId) => !registrations.has(integrationId) && !catalogIds.has(integrationId) && !overrides[integrationId]?.disabled);
74592
+ if (unknown.length > 0) {
74593
+ if (catalogAvailable) throw new MastraConnectConfigError(`Unknown provider${unknown.length > 1 ? "s" : ""} in the providers option: ${unknown.map((id) => `'${id}'`).join(", ")}. Expected a checked-in provider id or a platform catalog integration id.`);
74594
+ for (const integrationId of unknown) console.warn(`[@mastra/connect] Platform catalog unavailable; cannot verify provider '${integrationId}'. Skipping it this resolution.`);
74595
+ }
74436
74596
  const requests = [];
74437
74597
  for (const registration of registrations.values()) {
74598
+ if (only !== void 0 && !only.has(registration.integrationId)) continue;
74438
74599
  const providerOptions = overrides[registration.integrationId] ?? {};
74439
74600
  if (providerOptions.disabled) continue;
74440
74601
  requests.push({
@@ -74450,6 +74611,7 @@ async function mapTools(connections, requests, options, client, mcpClients, reso
74450
74611
  const activeMcpKeys = /* @__PURE__ */ new Set();
74451
74612
  const result = {};
74452
74613
  const toolOwners = /* @__PURE__ */ new Map();
74614
+ const matchedDefaultEntries = /* @__PURE__ */ new Set();
74453
74615
  for (const request of requests) {
74454
74616
  const integrationId = request.registration.integrationId;
74455
74617
  let providerTools;
@@ -74509,6 +74671,11 @@ async function mapTools(connections, requests, options, client, mcpClients, reso
74509
74671
  console.warn(`[@mastra/connect] Skipping ${integrationId}: ${error instanceof Error ? error.message : String(error)}`);
74510
74672
  continue;
74511
74673
  }
74674
+ if (!(request.options.allowTools !== void 0 || request.options.disallowTools !== void 0) && (options.allowTools !== void 0 || options.disallowTools !== void 0)) providerTools = applyDefaultFilter(providerTools, options, integrationId, matchedDefaultEntries);
74675
+ if (request.options.requireApproval === void 0 && options.requireApproval !== void 0) {
74676
+ const policy = options.requireApproval;
74677
+ if (policy !== false) applyDefaultRequireApproval(providerTools, policy, integrationId, matchedDefaultEntries);
74678
+ }
74512
74679
  for (const toolKey of Object.keys(providerTools)) {
74513
74680
  const existingOwner = toolOwners.get(toolKey);
74514
74681
  if (existingOwner) throw new MastraConnectError("invalid_options", `Duplicate tool key '${toolKey}' from providers '${existingOwner}' and '${integrationId}'.`);
@@ -74516,13 +74683,78 @@ async function mapTools(connections, requests, options, client, mcpClients, reso
74516
74683
  }
74517
74684
  Object.assign(result, providerTools);
74518
74685
  }
74686
+ if (Object.keys(result).length > 0) {
74687
+ const defaultEntries = [
74688
+ ...options.allowTools ?? [],
74689
+ ...options.disallowTools ?? [],
74690
+ ...Array.isArray(options.requireApproval) ? options.requireApproval : []
74691
+ ];
74692
+ for (const entry of defaultEntries) if (!matchedDefaultEntries.has(entry)) console.warn(`[@mastra/connect] Top-level entry '${entry}' matched no tools on any resolved provider.`);
74693
+ }
74519
74694
  const staleClients = Array.from(mcpClients.entries()).filter(([key]) => !activeMcpKeys.has(key));
74520
74695
  for (const [key] of staleClients) mcpClients.delete(key);
74521
74696
  await Promise.allSettled(staleClients.map(([, entry]) => entry.client.disconnect()));
74522
74697
  return result;
74523
74698
  }
74524
74699
  /**
74525
- * Extracts whichever provider tool filter is set. Since `ToolsIntegrationOptions`
74700
+ * Applies the top-level default filter to a provider that set no filter of
74701
+ * its own. Lenient by design: the provider set is live, so a default entry
74702
+ * that matches nothing on this provider simply does not apply here. Entries
74703
+ * that match are recorded in `matchedEntries` so mapTools can warn about
74704
+ * defaults that matched nothing anywhere. The synthetic
74705
+ * `<provider>__list_connections` wrapper is never filtered by defaults,
74706
+ * matching the per-provider multi-connection behavior.
74707
+ */
74708
+ function applyDefaultFilter(providerTools, defaults, integrationId, matchedEntries) {
74709
+ const listToolKey = listConnectionsToolKey(integrationId);
74710
+ const matchers = (defaults.allowTools ?? defaults.disallowTools ?? []).map((entry) => ({
74711
+ entry,
74712
+ matches: compileToolMatcher([entry])
74713
+ }));
74714
+ const filtered = {};
74715
+ for (const [key, tool] of Object.entries(providerTools)) {
74716
+ if (key === listToolKey) {
74717
+ filtered[key] = tool;
74718
+ continue;
74719
+ }
74720
+ let matchedAny = false;
74721
+ for (const { entry, matches } of matchers) if (matches(key)) {
74722
+ matchedEntries.add(entry);
74723
+ matchedAny = true;
74724
+ }
74725
+ if (defaults.allowTools !== void 0 ? matchedAny : !matchedAny) filtered[key] = tool;
74726
+ }
74727
+ return filtered;
74728
+ }
74729
+ /**
74730
+ * Applies the top-level default `requireApproval` policy to a provider that
74731
+ * set no policy of its own. `true` gates every tool except the synthetic
74732
+ * `<provider>__list_connections` wrapper; an array gates matching keys with
74733
+ * the same lenient semantics as {@link applyDefaultFilter}.
74734
+ */
74735
+ function applyDefaultRequireApproval(providerTools, policy, integrationId, matchedEntries) {
74736
+ const listToolKey = listConnectionsToolKey(integrationId);
74737
+ if (policy === true) {
74738
+ for (const [key, tool] of Object.entries(providerTools)) {
74739
+ if (key === listToolKey) continue;
74740
+ tool.requireApproval = true;
74741
+ }
74742
+ return;
74743
+ }
74744
+ const matchers = policy.map((entry) => ({
74745
+ entry,
74746
+ matches: compileToolMatcher([entry])
74747
+ }));
74748
+ for (const [key, tool] of Object.entries(providerTools)) {
74749
+ if (key === listToolKey) continue;
74750
+ for (const { entry, matches } of matchers) if (matches(key)) {
74751
+ matchedEntries.add(entry);
74752
+ tool.requireApproval = true;
74753
+ }
74754
+ }
74755
+ }
74756
+ /**
74757
+ * Extracts whichever provider tool filter is set. Since `ToolsProviderOptions`
74526
74758
  * is an XOR union and tools() validates co-occurrence up front, at most one
74527
74759
  * of the two will ever be defined here.
74528
74760
  */
@@ -74534,14 +74766,16 @@ function providerFilterOptions(options) {
74534
74766
  * toolset by marking the selected tools. `true` gates every provider tool
74535
74767
  * (but never the synthetic `<provider>__list_connections` wrapper tool,
74536
74768
  * matching the MCP paths, where only server-discovered tools are gated);
74537
- * an array gates the listed keys and rejects unknown names with the same
74538
- * `invalid_options` error MCP discovery raises, so a typo never silently
74539
- * widens access.
74769
+ * an array gates the listed keys — entries containing `*` are globs,
74770
+ * expanded against the provider tools minus the synthetic wrapper so a
74771
+ * broad glob behaves like `true` — and rejects unknown names and dead
74772
+ * globs with the same `invalid_options` error MCP discovery raises, so a
74773
+ * typo never silently widens access.
74540
74774
  */
74541
74775
  function applyProxyRequireApproval(providerTools, integrationId, requireApproval, excludeKeys = []) {
74542
74776
  if (!requireApproval) return;
74777
+ const excluded = new Set(excludeKeys);
74543
74778
  if (requireApproval === true) {
74544
- const excluded = new Set(excludeKeys);
74545
74779
  for (const [toolKey, tool] of Object.entries(providerTools)) {
74546
74780
  if (excluded.has(toolKey)) continue;
74547
74781
  tool.requireApproval = true;
@@ -74549,13 +74783,14 @@ function applyProxyRequireApproval(providerTools, integrationId, requireApproval
74549
74783
  return;
74550
74784
  }
74551
74785
  const known = Object.keys(providerTools);
74552
- const unknown = requireApproval.filter((name) => !known.includes(name));
74786
+ const expanded = expandToolPatterns(requireApproval, known.filter((key) => !excluded.has(key)), `requireApproval for '${integrationId}'`);
74787
+ const unknown = expanded.filter((name) => !known.includes(name));
74553
74788
  if (unknown.length > 0) throw new MastraConnectConfigError(`Unknown tool name(s) in requireApproval for '${integrationId}': ${unknown.join(", ")}. Known tools: ${known.join(", ")}.`);
74554
- for (const name of requireApproval) providerTools[name].requireApproval = true;
74789
+ for (const name of expanded) providerTools[name].requireApproval = true;
74555
74790
  }
74556
74791
  async function discoverMcpTools(input) {
74557
74792
  const { registration, connectionId, allowTools, disallowTools, requireApproval, client, mcpClients, resolverId } = input;
74558
- const requireApprovalFor = Array.isArray(requireApproval) ? new Set(requireApproval) : void 0;
74793
+ const requireApprovalFor = Array.isArray(requireApproval) ? compileToolMatcher(requireApproval) : void 0;
74559
74794
  const cacheKey = `${registration.integrationId}::${connectionId}`;
74560
74795
  let entry = mcpClients.get(cacheKey);
74561
74796
  if (!entry) {
@@ -74567,7 +74802,7 @@ async function discoverMcpTools(input) {
74567
74802
  id: `mastra-connect-${resolverId}-${registration.integrationId}-${connectionId}`,
74568
74803
  servers: { [registration.integrationId]: {
74569
74804
  ...transport,
74570
- ...requireApproval === true ? { requireToolApproval: true } : requireApprovalFor ? { requireToolApproval: ({ toolName }) => requireApprovalFor.has(`${registration.integrationId}_${String(toolName)}`) } : {}
74805
+ ...requireApproval === true ? { requireToolApproval: true } : requireApprovalFor ? { requireToolApproval: ({ toolName }) => requireApprovalFor(`${registration.integrationId}_${String(toolName)}`) } : {}
74571
74806
  } }
74572
74807
  })
74573
74808
  };
@@ -74576,8 +74811,8 @@ async function discoverMcpTools(input) {
74576
74811
  const discovery = await entry.client.listToolsWithErrors();
74577
74812
  const error = discovery.errors[registration.integrationId];
74578
74813
  if (error) throw new Error(`MCP tool discovery failed: ${error}`);
74579
- if (requireApprovalFor) {
74580
- const unknown = [...requireApprovalFor].filter((name) => !(name in discovery.tools));
74814
+ if (Array.isArray(requireApproval)) {
74815
+ const unknown = expandToolPatterns(requireApproval, Object.keys(discovery.tools), `requireApproval for '${registration.integrationId}'`).filter((name) => !(name in discovery.tools));
74581
74816
  if (unknown.length > 0) throw new MastraConnectConfigError(`Unknown tool name(s) in requireApproval for '${registration.integrationId}': ${unknown.join(", ")}. Known tools: ${Object.keys(discovery.tools).join(", ")}.`);
74582
74817
  }
74583
74818
  return applyToolFilter(discovery.tools, {
@@ -74586,23 +74821,6 @@ async function discoverMcpTools(input) {
74586
74821
  });
74587
74822
  }
74588
74823
  //#endregion
74589
- //#region src/connect.ts
74590
- /**
74591
- * @deprecated Use `tools` from `@mastra/connect` instead. This module is a
74592
- * thin re-export retained for one release cycle so downstream consumers keep
74593
- * working; it will be removed in the next minor.
74594
- */
74595
- /**
74596
- * @deprecated Renamed to {@link tools}.
74597
- *
74598
- * `connect(options)` is a drop-in alias for `tools(options)`. Prefer `tools`
74599
- * from `@mastra/connect` in new code; this export will be removed in the next
74600
- * minor version.
74601
- */
74602
- function connect(options = {}) {
74603
- return tools(options);
74604
- }
74605
- //#endregion
74606
74824
  //#region src/channels.ts
74607
74825
  const DEFAULT_TTL_MS = 3e4;
74608
74826
  /** Minimum wait after a failed platform fetch before another background revalidation. */
@@ -74615,7 +74833,7 @@ const FAILURE_COOLDOWN_MS = 3e4;
74615
74833
  * Discord) are constructed once, up front and credential-less, so their
74616
74834
  * webhook/OAuth routes can mount at Mastra construction. Each resolution
74617
74835
  * fetches the project's connections and late-binds credentials into the live
74618
- * instances: integrations with an active connection appear in the resolved
74836
+ * instances: providers with an active connection appear in the resolved
74619
74837
  * map, others don't — so connecting a new channel on the platform takes
74620
74838
  * effect without redeploying the app.
74621
74839
  *
@@ -74640,12 +74858,17 @@ async function channels(options = {}) {
74640
74858
  const projectId = options.projectId?.trim() || process.env.MASTRA_PROJECT_ID?.trim();
74641
74859
  if (!projectId) throw new MastraConnectError("missing_project_id", "Missing project id: set MASTRA_PROJECT_ID or pass projectId.");
74642
74860
  if (options.ttlMs !== void 0 && (!Number.isFinite(options.ttlMs) || options.ttlMs < 0)) throw new MastraConnectError("invalid_options", `Invalid ttlMs (${options.ttlMs}): expected a finite number of milliseconds >= 0.`);
74643
- validateIntegrationOverrides(options.integrations);
74861
+ const { overrides: providerOverrides, only } = normalizeChannelProviders(options.providers);
74862
+ validateProviderIds(providerOverrides);
74863
+ const knownChannelIds = new Set(CHANNELS.map((registration) => registration.integrationId));
74864
+ const unknownChannelIds = Object.keys(providerOverrides).filter((integrationId) => !knownChannelIds.has(integrationId) && !providerOverrides[integrationId]?.disabled);
74865
+ if (unknownChannelIds.length > 0) throw new MastraConnectError("invalid_options", `Unknown channel${unknownChannelIds.length > 1 ? "s" : ""} in the providers option: ${unknownChannelIds.map((id) => `'${id}'`).join(", ")}. Known channels: ${[...knownChannelIds].join(", ")}.`);
74644
74866
  const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
74645
74867
  const client = resolveClient(options.client);
74646
74868
  const states = [];
74647
74869
  for (const registration of CHANNELS) {
74648
- const overrides = options.integrations?.[registration.integrationId] ?? {};
74870
+ if (only !== void 0 && !only.has(registration.integrationId)) continue;
74871
+ const overrides = providerOverrides[registration.integrationId] ?? {};
74649
74872
  if (overrides.disabled) continue;
74650
74873
  const state = {
74651
74874
  registration,
@@ -74698,7 +74921,7 @@ async function channels(options = {}) {
74698
74921
  }
74699
74922
  for (const state of states) {
74700
74923
  const integrationId = state.registration.integrationId;
74701
- const overrides = options.integrations?.[integrationId] ?? {};
74924
+ const overrides = providerOverrides[integrationId] ?? {};
74702
74925
  const candidates = byIntegrationId.get(integrationId) ?? [];
74703
74926
  const connectionId = candidates.length === 0 ? void 0 : selectChannelConnection(integrationId, overrides.connectionId, candidates);
74704
74927
  state.connectionId = connectionId;
@@ -74745,9 +74968,6 @@ async function channels(options = {}) {
74745
74968
  };
74746
74969
  const invocable = ((_context) => resolve());
74747
74970
  invocable.getRoutes = () => states.flatMap((state) => state.instance.provider.getRoutes());
74748
- invocable.invalidate = () => {
74749
- cache = void 0;
74750
- };
74751
74971
  invocable.refresh = refresh;
74752
74972
  invocable.disconnect = async () => {
74753
74973
  cache = void 0;
@@ -74755,15 +74975,73 @@ async function channels(options = {}) {
74755
74975
  return invocable;
74756
74976
  }
74757
74977
  /**
74978
+ * Friendly channel-key aliases. The Slack channel is keyed `slack-channels`
74979
+ * on the platform (plain `slack` names the Slack tools provider), but inside
74980
+ * a channels() config `slack` is unambiguous — accept it and canonicalize to
74981
+ * the platform key for registration and connection lookup.
74982
+ */
74983
+ const CHANNEL_KEY_ALIASES = { slack: "slack-channels" };
74984
+ const canonicalChannelId = (id) => CHANNEL_KEY_ALIASES[id] ?? id;
74985
+ /** Throws when two option keys (e.g. `slack` and `slack-channels`) name the same channel. */
74986
+ function rejectAliasCollision(sourceKeys, canonical, key) {
74987
+ const existing = sourceKeys[canonical];
74988
+ if (existing === void 0) return;
74989
+ throw new MastraConnectError("invalid_options", existing === key ? `Duplicate provider '${key}' in providers array.` : `Both '${existing}' and '${key}' name the '${canonical}' channel in the providers option; use one key.`);
74990
+ }
74991
+ /**
74992
+ * Turns the two accepted `providers` shapes into the internal form, mirroring
74993
+ * `tools()`. The array form becomes an allowlist with default options; the
74994
+ * record form expands boolean shorthands (`true` → `{}`, `false` → an
74995
+ * internal exclusion marker). Alias keys canonicalize first, so `slack` and
74996
+ * `slack-channels` configure the same channel (and collide loudly). Malformed
74997
+ * inputs throw at channels() time.
74998
+ */
74999
+ function normalizeChannelProviders(providers) {
75000
+ if (providers === void 0) return {
75001
+ overrides: {},
75002
+ only: void 0
75003
+ };
75004
+ const sourceKeys = {};
75005
+ if (Array.isArray(providers)) {
75006
+ const overrides = {};
75007
+ for (const entry of providers) {
75008
+ if (typeof entry !== "string") throw new MastraConnectError("invalid_options", `Invalid providers entry: expected a string channel id, got ${typeof entry}.`);
75009
+ const canonical = canonicalChannelId(entry);
75010
+ rejectAliasCollision(sourceKeys, canonical, entry);
75011
+ sourceKeys[canonical] = entry;
75012
+ overrides[canonical] = {};
75013
+ }
75014
+ return {
75015
+ overrides,
75016
+ only: new Set(Object.keys(overrides))
75017
+ };
75018
+ }
75019
+ const overrides = {};
75020
+ for (const [providerId, value] of Object.entries(providers)) {
75021
+ if (value === void 0) continue;
75022
+ const canonical = canonicalChannelId(providerId);
75023
+ rejectAliasCollision(sourceKeys, canonical, providerId);
75024
+ sourceKeys[canonical] = providerId;
75025
+ if (value === true) overrides[canonical] = {};
75026
+ else if (value === false) overrides[canonical] = { disabled: true };
75027
+ else if (typeof value === "object" && value !== null && !Array.isArray(value)) overrides[canonical] = value;
75028
+ else throw new MastraConnectError("invalid_options", `Invalid providers entry for '${providerId}': expected true, false, or an options object, got ${value === null ? "null" : Array.isArray(value) ? "an array" : typeof value}.`);
75029
+ }
75030
+ return {
75031
+ overrides,
75032
+ only: void 0
75033
+ };
75034
+ }
75035
+ /**
74758
75036
  * Selects the connection `channels()` should use for one provider.
74759
75037
  *
74760
- * - Pinned `connectionId` (from `integrations.<id>.connectionId`) wins, but is
75038
+ * - Pinned `connectionId` (from `providers.<id>.connectionId`) wins, but is
74761
75039
  * skipped if the pinned id isn't attached to the project or the connection
74762
75040
  * isn't `active`.
74763
75041
  * - Otherwise, the single active connection is used.
74764
75042
  * - When more than one active connection exists, this warns naming the chosen
74765
75043
  * id and picks the first active connection. There is no env-var fallback;
74766
- * pin explicitly with `integrations.<id>.connectionId` when the deterministic
75044
+ * pin explicitly with `providers.<id>.connectionId` when the deterministic
74767
75045
  * choice matters.
74768
75046
  */
74769
75047
  function selectChannelConnection(integrationId, pinnedId, candidates) {
@@ -74792,7 +75070,7 @@ function selectChannelConnection(integrationId, pinnedId, candidates) {
74792
75070
  if (active.length === 1) return active[0].id;
74793
75071
  const chosen = active[0].id;
74794
75072
  const others = active.slice(1).map((connection) => connection.id).join(", ");
74795
- console.warn(`[@mastra/connect] ${integrationId} channel: found ${active.length} active connections; using ${chosen}. Ignoring ${others}. Pin one with integrations.${integrationId}.connectionId to silence this warning.`);
75073
+ console.warn(`[@mastra/connect] ${integrationId} channel: found ${active.length} active connections; using ${chosen}. Ignoring ${others}. Pin one with providers.${integrationId}.connectionId to silence this warning.`);
74796
75074
  return chosen;
74797
75075
  }
74798
75076
  //#endregion
@@ -74805,110 +75083,6 @@ async function credential(connectionId, options) {
74805
75083
  return getCredential(resolveClient(options?.client), connectionId);
74806
75084
  }
74807
75085
  //#endregion
74808
- //#region src/environment.ts
74809
- function credentialToken(credential) {
74810
- if (credential.type === "oauth2") return credential.accessToken;
74811
- if (credential.type === "two_step") return credential.token;
74812
- return credential.apiKey;
74813
- }
74814
- /**
74815
- * Wires git's credential helper to read the token from the environment at use
74816
- * time, so the secret lives in the process env rather than in git config or
74817
- * remote URLs. `gh` needs no setup: it honors GH_TOKEN directly.
74818
- */
74819
- const GITHUB_GIT_CREDENTIAL_HELPER = "!f() { echo username=x-access-token; echo \"password=$GH_TOKEN\"; }; f";
74820
- /** Providers that know how to materialize sandbox credentials. */
74821
- const ENVIRONMENT_CONTRIBUTORS = [{
74822
- integrationId: "github",
74823
- envVar: "MASTRA_GITHUB_CONNECTION_ID",
74824
- build(credential) {
74825
- const token = credentialToken(credential);
74826
- return {
74827
- env: {
74828
- GH_TOKEN: token,
74829
- GITHUB_TOKEN: token
74830
- },
74831
- onStart: async ({ sandbox }) => {
74832
- if (!sandbox.executeCommand) {
74833
- console.warn("[@mastra/connect] github environment: sandbox does not support executeCommand; skipping git credential setup (gh still works via GH_TOKEN).");
74834
- return;
74835
- }
74836
- await sandbox.executeCommand("git", [
74837
- "config",
74838
- "--global",
74839
- "credential.https://github.com.helper",
74840
- GITHUB_GIT_CREDENTIAL_HELPER
74841
- ]);
74842
- }
74843
- };
74844
- }
74845
- }];
74846
- /**
74847
- * Resolves the project's Platform connections into sandbox credentials:
74848
- * environment variables plus a start hook that finishes in-sandbox setup.
74849
- * Currently GitHub-aware (`GH_TOKEN`/`GITHUB_TOKEN` + git credential helper);
74850
- * more providers plug in over time.
74851
- *
74852
- * Connection resolution matches `connect()`: a per-integration `connectionId`
74853
- * or the provider's `MASTRA_*_CONNECTION_ID` env var wins, else a single
74854
- * active connection; ambiguity, needs_reauth, and credential-fetch failures
74855
- * warn and skip that provider. Configuration errors (missing project id,
74856
- * malformed integration id) throw here so they surface at startup.
74857
- *
74858
- * The returned `env` is a snapshot: call `environment()` again for a fresh
74859
- * token when starting new sandboxes.
74860
- *
74861
- * @example
74862
- * const { env, onStart } = await environment({ projectId });
74863
- * const sandbox = new E2BSandbox({ env, onStart });
74864
- * // in the sandbox: `gh pr list`, `git clone https://github.com/org/repo` just work
74865
- */
74866
- async function environment(options = {}) {
74867
- const projectId = options.projectId?.trim() || process.env.MASTRA_PROJECT_ID?.trim();
74868
- if (!projectId) throw new MastraConnectError("missing_project_id", "Missing project id: set MASTRA_PROJECT_ID or pass projectId.");
74869
- validateIntegrationOverrides(options.integrations);
74870
- const client = resolveClient(options.client);
74871
- const byIntegrationId = groupByIntegrationId(await listProjectConnections(client, projectId));
74872
- const env = {};
74873
- const hooks = [];
74874
- for (const contributor of ENVIRONMENT_CONTRIBUTORS) {
74875
- const integrationId = contributor.integrationId;
74876
- const overrides = options.integrations?.[integrationId] ?? {};
74877
- if (overrides.disabled) continue;
74878
- const candidates = byIntegrationId.get(integrationId) ?? [];
74879
- if (candidates.length === 0) continue;
74880
- const connectionId = resolveConnection({
74881
- integrationId,
74882
- envVar: contributor.envVar,
74883
- connectionId: overrides.connectionId
74884
- }, candidates);
74885
- if (!connectionId) continue;
74886
- let credential;
74887
- try {
74888
- credential = await getCredential(client, connectionId);
74889
- } catch (error) {
74890
- console.warn(`[@mastra/connect] Skipping ${integrationId} environment: ${error instanceof Error ? error.message : String(error)}`);
74891
- continue;
74892
- }
74893
- const contribution = contributor.build(credential);
74894
- for (const [key, value] of Object.entries(contribution.env)) {
74895
- if (key in env) console.warn(`[@mastra/connect] ${integrationId} environment overwrites env var '${key}' set by an earlier provider.`);
74896
- env[key] = value;
74897
- }
74898
- if (contribution.onStart) hooks.push({
74899
- integrationId,
74900
- hook: contribution.onStart
74901
- });
74902
- }
74903
- const onStart = async (args) => {
74904
- for (const { hook } of hooks) await hook(args);
74905
- };
74906
- return {
74907
- env,
74908
- onStart
74909
- };
74910
- }
74911
- //#endregion
74912
- export { CHANNELS, MastraConnectError, PROVIDERS, TOOLS, applyAllowTools, channels, connect, credential, defineProxyTool, environment, findChannelRegistration, findRegistration, resolveConnectionId, tools };
75086
+ export { CHANNELS, MastraConnectError, PROVIDERS, channels, credential, tools };
74913
75087
 
74914
75088
  //# sourceMappingURL=index.js.map