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