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