@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/README.md +47 -42
- package/dist/channels.d.ts +38 -22
- package/dist/channels.d.ts.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/index.cjs +454 -288
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +455 -281
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.d.ts +48 -0
- package/dist/instrumentation.d.ts.map +1 -0
- package/dist/multi-connection.d.ts.map +1 -1
- package/dist/providers/jira/tools/get-create-issue-metadata.d.ts +42 -0
- package/dist/providers/jira/tools/get-create-issue-metadata.d.ts.map +1 -1
- package/dist/providers/jira/tools/list-issue-types.d.ts +6 -2
- package/dist/providers/jira/tools/list-issue-types.d.ts.map +1 -1
- package/dist/registry.d.ts +1 -8
- package/dist/registry.d.ts.map +1 -1
- package/dist/resolution.d.ts +3 -11
- package/dist/resolution.d.ts.map +1 -1
- package/dist/tools.d.ts +85 -29
- package/dist/tools.d.ts.map +1 -1
- package/dist/toolset.d.ts +39 -13
- package/dist/toolset.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/connect.d.ts +0 -22
- package/dist/connect.d.ts.map +0 -1
- package/dist/environment.d.ts +0 -48
- package/dist/environment.d.ts.map +0 -1
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
|
-
|
|
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
|
-
/**
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
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
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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.
|
|
456
|
-
*
|
|
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,
|
|
459
|
-
if (!
|
|
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.
|
|
469
|
-
*
|
|
470
|
-
*
|
|
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,
|
|
473
|
-
if (!
|
|
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) ?
|
|
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
|
|
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 (
|
|
754
|
-
const unknown = [...
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
74151
|
-
for (const integrationId of Object.keys(
|
|
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 (
|
|
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 `
|
|
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
|
|
74257
|
-
* to (or
|
|
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
|
|
74277
|
-
|
|
74278
|
-
|
|
74279
|
-
|
|
74280
|
-
|
|
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(
|
|
74299
|
-
if (connections.some((connection) => connection.status === "active" && !checkedIn.has(connection.integrationId) && !
|
|
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(
|
|
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
|
-
|
|
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
|
-
*
|
|
74369
|
-
*
|
|
74370
|
-
*
|
|
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
|
|
74375
|
-
|
|
74376
|
-
|
|
74377
|
-
const
|
|
74378
|
-
|
|
74379
|
-
|
|
74380
|
-
|
|
74381
|
-
|
|
74382
|
-
|
|
74383
|
-
|
|
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
|
-
|
|
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
|
|
74394
|
-
for (const [integrationId, providerOptions] of Object.entries(
|
|
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(
|
|
74407
|
-
for (const [integrationId, providerOptions] of Object.entries(
|
|
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(
|
|
74421
|
-
for (const [integrationId, providerOptions] of Object.entries(
|
|
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(
|
|
74424
|
-
const overrides =
|
|
74425
|
-
const registrations = new Map(
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
74538
|
-
*
|
|
74539
|
-
*
|
|
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
|
|
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
|
|
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) ?
|
|
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
|
|
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 (
|
|
74580
|
-
const unknown =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
-
|
|
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
|