@mastra/mcp 1.14.0 → 1.15.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/CHANGELOG.md +11 -0
- package/dist/client/client.d.ts +41 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/configuration.d.ts +71 -1
- package/dist/client/configuration.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/oauth-callback-server.d.ts +117 -0
- package/dist/client/oauth-callback-server.d.ts.map +1 -0
- package/dist/client/oauth-provider.d.ts +38 -2
- package/dist/client/oauth-provider.d.ts.map +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-mcp-overview.md +1 -1
- package/dist/docs/references/reference-tools-mcp-client.md +96 -0
- package/dist/index.cjs +690 -231
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +691 -233
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -10,8 +10,11 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
|
|
10
10
|
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
11
11
|
import { LoggingMessageNotificationSchema, ListRootsRequestSchema, ListResourcesResultSchema, ReadResourceResultSchema, EmptyResultSchema, ListResourceTemplatesResultSchema, ListPromptsResultSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolListChangedNotificationSchema, ResourceUpdatedNotificationSchema, ResourceListChangedNotificationSchema, ElicitRequestSchema, ProgressNotificationSchema, ListToolsRequestSchema, CallToolRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, McpError, ErrorCode, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, ListPromptsRequestSchema, PromptSchema, GetPromptRequestSchema, CallToolResultSchema, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
12
12
|
import { asyncExitHook, gracefulExit } from 'exit-hook';
|
|
13
|
-
import {
|
|
13
|
+
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
14
|
+
export { UnauthorizedError, auth, buildDiscoveryUrls, discoverAuthorizationServerMetadata, discoverOAuthMetadata, discoverOAuthProtectedResourceMetadata, exchangeAuthorization, extractResourceMetadataUrl, parseErrorResponse, refreshAuthorization, registerClient, selectResourceURL, startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
15
|
+
import { createHash, randomUUID, timingSafeEqual } from 'crypto';
|
|
14
16
|
import equal from 'fast-deep-equal';
|
|
17
|
+
import { createServer } from 'http';
|
|
15
18
|
import { MCPServerBase } from '@mastra/core/mcp';
|
|
16
19
|
import { isStandardSchemaWithJSON as isStandardSchemaWithJSON$1, standardSchemaToJSONSchema } from '@mastra/core/schema';
|
|
17
20
|
import { readFileSync } from 'fs';
|
|
@@ -22,7 +25,6 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
22
25
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
23
26
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
24
27
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
25
|
-
export { UnauthorizedError, auth, buildDiscoveryUrls, discoverAuthorizationServerMetadata, discoverOAuthMetadata, discoverOAuthProtectedResourceMetadata, exchangeAuthorization, extractResourceMetadataUrl, parseErrorResponse, refreshAuthorization, registerClient, selectResourceURL, startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
26
28
|
|
|
27
29
|
var __defProp = Object.defineProperty;
|
|
28
30
|
var __export = (target, all) => {
|
|
@@ -31076,6 +31078,44 @@ function getMastraToolStrictMeta(meta3) {
|
|
|
31076
31078
|
const strict = mastraMeta[STRICT_META_KEY];
|
|
31077
31079
|
return typeof strict === "boolean" ? strict : void 0;
|
|
31078
31080
|
}
|
|
31081
|
+
function escapeHeaderValue(value) {
|
|
31082
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
31083
|
+
}
|
|
31084
|
+
function generateWWWAuthenticateHeader(options = {}) {
|
|
31085
|
+
const params = [];
|
|
31086
|
+
if (options.resourceMetadataUrl) {
|
|
31087
|
+
params.push(`resource_metadata="${escapeHeaderValue(options.resourceMetadataUrl)}"`);
|
|
31088
|
+
}
|
|
31089
|
+
if (options.additionalParams) {
|
|
31090
|
+
for (const [key, value] of Object.entries(options.additionalParams)) {
|
|
31091
|
+
params.push(`${key}="${escapeHeaderValue(value)}"`);
|
|
31092
|
+
}
|
|
31093
|
+
}
|
|
31094
|
+
if (params.length === 0) {
|
|
31095
|
+
return "Bearer";
|
|
31096
|
+
}
|
|
31097
|
+
return `Bearer ${params.join(", ")}`;
|
|
31098
|
+
}
|
|
31099
|
+
function generateProtectedResourceMetadata(config2) {
|
|
31100
|
+
return {
|
|
31101
|
+
resource: config2.resource,
|
|
31102
|
+
authorization_servers: config2.authorizationServers,
|
|
31103
|
+
scopes_supported: config2.scopesSupported ?? ["mcp:read", "mcp:write"],
|
|
31104
|
+
bearer_methods_supported: ["header"],
|
|
31105
|
+
...config2.resourceName && { resource_name: config2.resourceName },
|
|
31106
|
+
...config2.resourceDocumentation && {
|
|
31107
|
+
resource_documentation: config2.resourceDocumentation
|
|
31108
|
+
}
|
|
31109
|
+
};
|
|
31110
|
+
}
|
|
31111
|
+
function extractBearerToken(authHeader) {
|
|
31112
|
+
if (!authHeader) return void 0;
|
|
31113
|
+
const prefix = "bearer ";
|
|
31114
|
+
if (authHeader.length <= prefix.length) return void 0;
|
|
31115
|
+
if (authHeader.slice(0, prefix.length).toLowerCase() !== prefix) return void 0;
|
|
31116
|
+
const token = authHeader.slice(prefix.length).trim();
|
|
31117
|
+
return token || void 0;
|
|
31118
|
+
}
|
|
31079
31119
|
|
|
31080
31120
|
// src/client/actions/elicitation.ts
|
|
31081
31121
|
var ElicitationClientActions = class {
|
|
@@ -31532,6 +31572,8 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
31532
31572
|
enableProgressTracking;
|
|
31533
31573
|
serverConfig;
|
|
31534
31574
|
transport;
|
|
31575
|
+
pendingAuthTransport;
|
|
31576
|
+
_authState;
|
|
31535
31577
|
operationContextStore = new AsyncLocalStorage();
|
|
31536
31578
|
exitHookUnsubscribe;
|
|
31537
31579
|
sigTermHandler;
|
|
@@ -31716,14 +31758,14 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
31716
31758
|
this.log("debug", `Attempting to connect to URL: ${url2}`);
|
|
31717
31759
|
let shouldTrySSE = url2.pathname.endsWith(`/sse`);
|
|
31718
31760
|
if (!shouldTrySSE) {
|
|
31761
|
+
const streamableTransport = new StreamableHTTPClientTransport(url2, {
|
|
31762
|
+
requestInit,
|
|
31763
|
+
reconnectionOptions: this.serverConfig.reconnectionOptions,
|
|
31764
|
+
authProvider,
|
|
31765
|
+
fetch: fetch2
|
|
31766
|
+
});
|
|
31719
31767
|
try {
|
|
31720
31768
|
this.log("debug", "Trying Streamable HTTP transport...");
|
|
31721
|
-
const streamableTransport = new StreamableHTTPClientTransport(url2, {
|
|
31722
|
-
requestInit,
|
|
31723
|
-
reconnectionOptions: this.serverConfig.reconnectionOptions,
|
|
31724
|
-
authProvider,
|
|
31725
|
-
fetch: fetch2
|
|
31726
|
-
});
|
|
31727
31769
|
await this.client.connect(streamableTransport, {
|
|
31728
31770
|
timeout: connectTimeout ?? DEFAULT_SERVER_CONNECT_TIMEOUT_MSEC
|
|
31729
31771
|
});
|
|
@@ -31731,6 +31773,10 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
31731
31773
|
this.log("debug", "Successfully connected using Streamable HTTP transport.");
|
|
31732
31774
|
} catch (error51) {
|
|
31733
31775
|
this.log("debug", `Streamable HTTP transport failed: ${error51}`);
|
|
31776
|
+
if (authProvider && error51 instanceof UnauthorizedError) {
|
|
31777
|
+
this.markNeedsAuth(streamableTransport);
|
|
31778
|
+
throw error51;
|
|
31779
|
+
}
|
|
31734
31780
|
const status = error51?.code;
|
|
31735
31781
|
if (status !== void 0 && !SSE_FALLBACK_STATUS_CODES.includes(status)) {
|
|
31736
31782
|
throw error51;
|
|
@@ -31740,18 +31786,22 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
31740
31786
|
}
|
|
31741
31787
|
if (shouldTrySSE) {
|
|
31742
31788
|
this.log("debug", "Falling back to deprecated HTTP+SSE transport...");
|
|
31789
|
+
const sseEventSourceInit = { ...eventSourceInit, fetch: eventSourceInit?.fetch ?? fetch2 };
|
|
31790
|
+
const sseTransport = new SSEClientTransport(url2, {
|
|
31791
|
+
requestInit,
|
|
31792
|
+
eventSourceInit: sseEventSourceInit,
|
|
31793
|
+
authProvider,
|
|
31794
|
+
fetch: fetch2
|
|
31795
|
+
});
|
|
31743
31796
|
try {
|
|
31744
|
-
const sseEventSourceInit = { ...eventSourceInit, fetch: fetch2 };
|
|
31745
|
-
const sseTransport = new SSEClientTransport(url2, {
|
|
31746
|
-
requestInit,
|
|
31747
|
-
eventSourceInit: sseEventSourceInit,
|
|
31748
|
-
authProvider,
|
|
31749
|
-
fetch: fetch2
|
|
31750
|
-
});
|
|
31751
31797
|
await this.client.connect(sseTransport, { timeout: this.serverConfig.timeout ?? this.timeout });
|
|
31752
31798
|
this.transport = sseTransport;
|
|
31753
31799
|
this.log("debug", "Successfully connected using deprecated HTTP+SSE transport.");
|
|
31754
31800
|
} catch (sseError) {
|
|
31801
|
+
if (authProvider && sseError instanceof UnauthorizedError) {
|
|
31802
|
+
this.markNeedsAuth(sseTransport);
|
|
31803
|
+
throw sseError;
|
|
31804
|
+
}
|
|
31755
31805
|
this.log(
|
|
31756
31806
|
"error",
|
|
31757
31807
|
`Failed to connect with SSE transport after failing to connect to Streamable HTTP transport first. SSE error: ${sseError}`
|
|
@@ -31759,6 +31809,64 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
31759
31809
|
throw new Error("Could not connect to server with any available HTTP transport");
|
|
31760
31810
|
}
|
|
31761
31811
|
}
|
|
31812
|
+
this.closePendingAuthTransport();
|
|
31813
|
+
if (authProvider) {
|
|
31814
|
+
this._authState = "authorized";
|
|
31815
|
+
}
|
|
31816
|
+
}
|
|
31817
|
+
/**
|
|
31818
|
+
* Closes and clears any transport retained from an unfinished authorization
|
|
31819
|
+
* flow. Safe to call when nothing is pending. Centralizes the cleanup so
|
|
31820
|
+
* success, disconnect, and forceReconnect all release the same resource.
|
|
31821
|
+
*/
|
|
31822
|
+
closePendingAuthTransport(replacement) {
|
|
31823
|
+
const pending = this.pendingAuthTransport;
|
|
31824
|
+
this.pendingAuthTransport = replacement;
|
|
31825
|
+
if (pending && pending !== replacement) {
|
|
31826
|
+
void pending.close().catch(() => {
|
|
31827
|
+
});
|
|
31828
|
+
}
|
|
31829
|
+
}
|
|
31830
|
+
/**
|
|
31831
|
+
* Records that the server rejected the connection with a 401 and keeps the
|
|
31832
|
+
* transport that started the authorization flow so finishAuth can complete it.
|
|
31833
|
+
*/
|
|
31834
|
+
markNeedsAuth(transport) {
|
|
31835
|
+
this.closePendingAuthTransport(transport);
|
|
31836
|
+
this._authState = "needs-auth";
|
|
31837
|
+
this.log("debug", "Server requires OAuth authorization before connecting.");
|
|
31838
|
+
}
|
|
31839
|
+
/**
|
|
31840
|
+
* OAuth authorization state of this server connection, when it has an authProvider.
|
|
31841
|
+
*
|
|
31842
|
+
* @internal
|
|
31843
|
+
*/
|
|
31844
|
+
get authState() {
|
|
31845
|
+
return this._authState;
|
|
31846
|
+
}
|
|
31847
|
+
/**
|
|
31848
|
+
* Completes a pending OAuth authorization-code flow.
|
|
31849
|
+
*
|
|
31850
|
+
* Exchanges the authorization code captured at the redirect URI on the same
|
|
31851
|
+
* transport that started the flow, then leaves the client ready to connect().
|
|
31852
|
+
*
|
|
31853
|
+
* @param authorizationCode - The authorization code captured at the redirect URI
|
|
31854
|
+
* @throws {Error} If no authorization flow is pending for this server
|
|
31855
|
+
*
|
|
31856
|
+
* @internal
|
|
31857
|
+
*/
|
|
31858
|
+
async finishAuth(authorizationCode) {
|
|
31859
|
+
const pending = this.pendingAuthTransport;
|
|
31860
|
+
if (!pending) {
|
|
31861
|
+
throw new Error("No OAuth authorization is pending for this server. Call connect() first.");
|
|
31862
|
+
}
|
|
31863
|
+
this.pendingAuthTransport = void 0;
|
|
31864
|
+
try {
|
|
31865
|
+
await pending.finishAuth(authorizationCode);
|
|
31866
|
+
} finally {
|
|
31867
|
+
void pending.close().catch(() => {
|
|
31868
|
+
});
|
|
31869
|
+
}
|
|
31762
31870
|
}
|
|
31763
31871
|
isConnected = null;
|
|
31764
31872
|
/**
|
|
@@ -31869,6 +31977,7 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
31869
31977
|
this.serverInstructions = this.client.getInstructions();
|
|
31870
31978
|
}
|
|
31871
31979
|
async disconnect() {
|
|
31980
|
+
this.closePendingAuthTransport();
|
|
31872
31981
|
if (!this.transport) {
|
|
31873
31982
|
this.log("debug", "Disconnect called but no transport was connected.");
|
|
31874
31983
|
return;
|
|
@@ -31913,6 +32022,7 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
31913
32022
|
*/
|
|
31914
32023
|
async forceReconnect() {
|
|
31915
32024
|
this.log("debug", "Forcing reconnection to MCP server...");
|
|
32025
|
+
this.closePendingAuthTransport();
|
|
31916
32026
|
try {
|
|
31917
32027
|
if (this.transport) {
|
|
31918
32028
|
await this.transport.close();
|
|
@@ -32191,6 +32301,399 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
32191
32301
|
};
|
|
32192
32302
|
}
|
|
32193
32303
|
};
|
|
32304
|
+
var CALLBACK_PORT_FALLBACK_RANGE = 10;
|
|
32305
|
+
var DEFAULT_CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
32306
|
+
var SUCCESS_HTML = `<!DOCTYPE html>
|
|
32307
|
+
<html>
|
|
32308
|
+
<head><meta charset="utf-8"><title>Authentication complete</title></head>
|
|
32309
|
+
<body style="font-family: system-ui, sans-serif; text-align: center; padding-top: 4rem;">
|
|
32310
|
+
<h1>Authentication complete</h1>
|
|
32311
|
+
<p>You can close this tab and return to your application.</p>
|
|
32312
|
+
</body>
|
|
32313
|
+
</html>`;
|
|
32314
|
+
var ERROR_HTML = `<!DOCTYPE html>
|
|
32315
|
+
<html>
|
|
32316
|
+
<head><meta charset="utf-8"><title>Authentication failed</title></head>
|
|
32317
|
+
<body style="font-family: system-ui, sans-serif; text-align: center; padding-top: 4rem;">
|
|
32318
|
+
<h1>Authentication failed</h1>
|
|
32319
|
+
<p>You can close this tab and retry from your application.</p>
|
|
32320
|
+
</body>
|
|
32321
|
+
</html>`;
|
|
32322
|
+
function getCallbackUrlCandidates(redirectUrl) {
|
|
32323
|
+
const base = new URL(redirectUrl.toString());
|
|
32324
|
+
const preferredPort = base.port ? Number(base.port) : base.protocol === "https:" ? 443 : 80;
|
|
32325
|
+
const candidates = [];
|
|
32326
|
+
for (let offset = 0; offset <= CALLBACK_PORT_FALLBACK_RANGE; offset++) {
|
|
32327
|
+
const port = preferredPort + offset;
|
|
32328
|
+
if (port > 65535) break;
|
|
32329
|
+
const candidate = new URL(base.toString());
|
|
32330
|
+
candidate.port = String(port);
|
|
32331
|
+
candidates.push(candidate);
|
|
32332
|
+
}
|
|
32333
|
+
return candidates;
|
|
32334
|
+
}
|
|
32335
|
+
function stateMatches(receivedState, expectedState) {
|
|
32336
|
+
const received = Buffer.from(receivedState);
|
|
32337
|
+
const expected = Buffer.from(expectedState);
|
|
32338
|
+
return received.length === expected.length && timingSafeEqual(received, expected);
|
|
32339
|
+
}
|
|
32340
|
+
function listen(server, port, hostname3) {
|
|
32341
|
+
return new Promise((resolve, reject) => {
|
|
32342
|
+
const onError = (error51) => {
|
|
32343
|
+
server.off("listening", onListening);
|
|
32344
|
+
if (error51.code === "EADDRINUSE") {
|
|
32345
|
+
resolve(error51);
|
|
32346
|
+
} else {
|
|
32347
|
+
reject(error51);
|
|
32348
|
+
}
|
|
32349
|
+
};
|
|
32350
|
+
const onListening = () => {
|
|
32351
|
+
server.off("error", onError);
|
|
32352
|
+
resolve(null);
|
|
32353
|
+
};
|
|
32354
|
+
server.once("error", onError);
|
|
32355
|
+
server.once("listening", onListening);
|
|
32356
|
+
server.listen(port, hostname3);
|
|
32357
|
+
});
|
|
32358
|
+
}
|
|
32359
|
+
async function createOAuthCallbackServer(options) {
|
|
32360
|
+
const candidates = getCallbackUrlCandidates(options.redirectUrl);
|
|
32361
|
+
const callbackPath = candidates[0].pathname;
|
|
32362
|
+
let settled = false;
|
|
32363
|
+
let resolveCode;
|
|
32364
|
+
let rejectCode;
|
|
32365
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
32366
|
+
resolveCode = resolve;
|
|
32367
|
+
rejectCode = reject;
|
|
32368
|
+
});
|
|
32369
|
+
codePromise.catch(() => {
|
|
32370
|
+
});
|
|
32371
|
+
const settle = (outcome) => {
|
|
32372
|
+
if (settled) return;
|
|
32373
|
+
settled = true;
|
|
32374
|
+
if ("result" in outcome) {
|
|
32375
|
+
resolveCode(outcome.result);
|
|
32376
|
+
} else {
|
|
32377
|
+
rejectCode(outcome.error);
|
|
32378
|
+
}
|
|
32379
|
+
};
|
|
32380
|
+
const server = createServer((req, res) => {
|
|
32381
|
+
const url2 = new URL(req.url ?? "", "http://localhost");
|
|
32382
|
+
if (url2.pathname !== callbackPath) {
|
|
32383
|
+
res.statusCode = 404;
|
|
32384
|
+
res.end("Not found");
|
|
32385
|
+
return;
|
|
32386
|
+
}
|
|
32387
|
+
if (settled) {
|
|
32388
|
+
res.statusCode = 410;
|
|
32389
|
+
res.end("Callback already handled");
|
|
32390
|
+
return;
|
|
32391
|
+
}
|
|
32392
|
+
const respond = (statusCode, html) => {
|
|
32393
|
+
res.statusCode = statusCode;
|
|
32394
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
32395
|
+
res.end(html);
|
|
32396
|
+
};
|
|
32397
|
+
const state = url2.searchParams.get("state");
|
|
32398
|
+
if (state === null || !stateMatches(state, options.state)) {
|
|
32399
|
+
respond(400, ERROR_HTML);
|
|
32400
|
+
return;
|
|
32401
|
+
}
|
|
32402
|
+
const error51 = url2.searchParams.get("error");
|
|
32403
|
+
if (error51) {
|
|
32404
|
+
const description = url2.searchParams.get("error_description");
|
|
32405
|
+
respond(400, ERROR_HTML);
|
|
32406
|
+
settle({ error: new Error(`Authorization failed: ${error51}${description ? ` (${description})` : ""}`) });
|
|
32407
|
+
return;
|
|
32408
|
+
}
|
|
32409
|
+
const code = url2.searchParams.get("code");
|
|
32410
|
+
if (!code) {
|
|
32411
|
+
respond(400, ERROR_HTML);
|
|
32412
|
+
settle({ error: new Error("Authorization failed: missing authorization code") });
|
|
32413
|
+
return;
|
|
32414
|
+
}
|
|
32415
|
+
respond(200, SUCCESS_HTML);
|
|
32416
|
+
settle({ result: { code, state } });
|
|
32417
|
+
});
|
|
32418
|
+
const hostname3 = candidates[0].hostname.replace(/^\[|\]$/g, "");
|
|
32419
|
+
let boundUrl;
|
|
32420
|
+
let boundPort;
|
|
32421
|
+
for (const candidate of candidates) {
|
|
32422
|
+
const candidatePort = Number(candidate.port);
|
|
32423
|
+
const error51 = await listen(server, candidatePort, hostname3);
|
|
32424
|
+
if (!error51) {
|
|
32425
|
+
boundUrl = candidate;
|
|
32426
|
+
boundPort = candidatePort;
|
|
32427
|
+
break;
|
|
32428
|
+
}
|
|
32429
|
+
}
|
|
32430
|
+
if (!boundUrl || boundPort === void 0) {
|
|
32431
|
+
const firstPort = Number(candidates[0].port);
|
|
32432
|
+
const lastPort = Number(candidates[candidates.length - 1].port);
|
|
32433
|
+
throw new Error(`Failed to start OAuth callback server: ports ${firstPort}-${lastPort} are all in use`);
|
|
32434
|
+
}
|
|
32435
|
+
server.on("error", (error51) => {
|
|
32436
|
+
settle({ error: error51 instanceof Error ? error51 : new Error(String(error51)) });
|
|
32437
|
+
});
|
|
32438
|
+
return {
|
|
32439
|
+
url: boundUrl,
|
|
32440
|
+
port: boundPort,
|
|
32441
|
+
waitForCode({ timeoutMs = DEFAULT_CALLBACK_TIMEOUT_MS } = {}) {
|
|
32442
|
+
let timer;
|
|
32443
|
+
const timeout = new Promise((_, reject) => {
|
|
32444
|
+
timer = setTimeout(
|
|
32445
|
+
() => reject(new Error(`Timed out waiting for OAuth callback after ${timeoutMs}ms`)),
|
|
32446
|
+
timeoutMs
|
|
32447
|
+
);
|
|
32448
|
+
timer.unref?.();
|
|
32449
|
+
});
|
|
32450
|
+
return Promise.race([codePromise, timeout]).finally(() => clearTimeout(timer));
|
|
32451
|
+
},
|
|
32452
|
+
close() {
|
|
32453
|
+
settle({ error: new Error("OAuth callback server closed before receiving an authorization code") });
|
|
32454
|
+
server.closeIdleConnections();
|
|
32455
|
+
return new Promise((resolve, reject) => {
|
|
32456
|
+
server.close((error51) => {
|
|
32457
|
+
if (error51 && error51.code !== "ERR_SERVER_NOT_RUNNING") {
|
|
32458
|
+
reject(error51);
|
|
32459
|
+
} else {
|
|
32460
|
+
resolve();
|
|
32461
|
+
}
|
|
32462
|
+
});
|
|
32463
|
+
});
|
|
32464
|
+
}
|
|
32465
|
+
};
|
|
32466
|
+
}
|
|
32467
|
+
|
|
32468
|
+
// src/client/oauth-provider.ts
|
|
32469
|
+
var InMemoryOAuthStorage = class {
|
|
32470
|
+
data = /* @__PURE__ */ new Map();
|
|
32471
|
+
set(key, value) {
|
|
32472
|
+
this.data.set(key, value);
|
|
32473
|
+
}
|
|
32474
|
+
get(key) {
|
|
32475
|
+
return this.data.get(key);
|
|
32476
|
+
}
|
|
32477
|
+
delete(key) {
|
|
32478
|
+
this.data.delete(key);
|
|
32479
|
+
}
|
|
32480
|
+
clear() {
|
|
32481
|
+
this.data.clear();
|
|
32482
|
+
}
|
|
32483
|
+
};
|
|
32484
|
+
var MCPOAuthClientProvider = class {
|
|
32485
|
+
_redirectUrl;
|
|
32486
|
+
_clientMetadata;
|
|
32487
|
+
storage;
|
|
32488
|
+
onRedirect;
|
|
32489
|
+
generateState;
|
|
32490
|
+
_clientInfo;
|
|
32491
|
+
_sessionState;
|
|
32492
|
+
_sessionRedirectUrl;
|
|
32493
|
+
constructor(options) {
|
|
32494
|
+
this._redirectUrl = options.redirectUrl;
|
|
32495
|
+
this._clientMetadata = options.clientMetadata;
|
|
32496
|
+
this._clientInfo = options.clientInformation;
|
|
32497
|
+
this.storage = options.storage ?? new InMemoryOAuthStorage();
|
|
32498
|
+
this.onRedirect = options.onRedirectToAuthorization;
|
|
32499
|
+
this.generateState = options.stateGenerator ?? (() => crypto.randomUUID());
|
|
32500
|
+
}
|
|
32501
|
+
/**
|
|
32502
|
+
* The URL to redirect the user agent to after authorization.
|
|
32503
|
+
*/
|
|
32504
|
+
get redirectUrl() {
|
|
32505
|
+
return this._redirectUrl;
|
|
32506
|
+
}
|
|
32507
|
+
/**
|
|
32508
|
+
* Metadata about this OAuth client.
|
|
32509
|
+
*/
|
|
32510
|
+
get clientMetadata() {
|
|
32511
|
+
return this._clientMetadata;
|
|
32512
|
+
}
|
|
32513
|
+
/**
|
|
32514
|
+
* Returns a OAuth2 state parameter.
|
|
32515
|
+
*
|
|
32516
|
+
* While an authorization session is active (see beginAuthorizationSession),
|
|
32517
|
+
* the pinned session state is returned so a callback server can validate
|
|
32518
|
+
* the redirect against a known value.
|
|
32519
|
+
*/
|
|
32520
|
+
async state() {
|
|
32521
|
+
return this._sessionState ?? this.generateState();
|
|
32522
|
+
}
|
|
32523
|
+
/**
|
|
32524
|
+
* Pins the OAuth state parameter for the next authorization request.
|
|
32525
|
+
*
|
|
32526
|
+
* Hosts driving an interactive authorization flow (e.g. MCPClient.authenticate)
|
|
32527
|
+
* call this before triggering the flow so the loopback callback server knows
|
|
32528
|
+
* which state value to expect. Call endAuthorizationSession once the flow settles.
|
|
32529
|
+
*
|
|
32530
|
+
* @returns The pinned state value, generated with the configured stateGenerator
|
|
32531
|
+
*/
|
|
32532
|
+
async beginAuthorizationSession() {
|
|
32533
|
+
this._sessionState = await this.generateState();
|
|
32534
|
+
this._sessionRedirectUrl = this._redirectUrl;
|
|
32535
|
+
return this._sessionState;
|
|
32536
|
+
}
|
|
32537
|
+
/**
|
|
32538
|
+
* Clears the pinned authorization state (see beginAuthorizationSession) and
|
|
32539
|
+
* restores the configured redirect URL if applyResolvedRedirectUrl rebased
|
|
32540
|
+
* it to a fallback port during the session, so the next flow starts from
|
|
32541
|
+
* the preferred port again.
|
|
32542
|
+
*/
|
|
32543
|
+
endAuthorizationSession() {
|
|
32544
|
+
this._sessionState = void 0;
|
|
32545
|
+
if (this._sessionRedirectUrl !== void 0) {
|
|
32546
|
+
this._redirectUrl = this._sessionRedirectUrl;
|
|
32547
|
+
this._sessionRedirectUrl = void 0;
|
|
32548
|
+
}
|
|
32549
|
+
}
|
|
32550
|
+
/**
|
|
32551
|
+
* Points the provider at the callback URL that is actually bound.
|
|
32552
|
+
*
|
|
32553
|
+
* Loopback callback servers may bind a fallback port when the preferred one
|
|
32554
|
+
* is in use. Call this before triggering authorization so the authorization
|
|
32555
|
+
* request's redirect_uri matches the listening server, and so dynamic client
|
|
32556
|
+
* registration registers every candidate callback URL (see
|
|
32557
|
+
* getCallbackUrlCandidates) rather than only the preferred one.
|
|
32558
|
+
*
|
|
32559
|
+
* @param redirectUrl - The callback URL that is actually bound
|
|
32560
|
+
* @param registeredRedirectUris - The redirect URIs to register during dynamic client registration
|
|
32561
|
+
*/
|
|
32562
|
+
applyResolvedRedirectUrl(redirectUrl, registeredRedirectUris) {
|
|
32563
|
+
this._redirectUrl = redirectUrl;
|
|
32564
|
+
this._clientMetadata = {
|
|
32565
|
+
...this._clientMetadata,
|
|
32566
|
+
redirect_uris: registeredRedirectUris.map((uri) => uri.toString())
|
|
32567
|
+
};
|
|
32568
|
+
}
|
|
32569
|
+
/**
|
|
32570
|
+
* Loads information about this OAuth client.
|
|
32571
|
+
*/
|
|
32572
|
+
async clientInformation() {
|
|
32573
|
+
if (this._clientInfo) {
|
|
32574
|
+
return this._clientInfo;
|
|
32575
|
+
}
|
|
32576
|
+
const stored = await this.storage.get("client_info");
|
|
32577
|
+
if (stored) {
|
|
32578
|
+
try {
|
|
32579
|
+
return JSON.parse(stored);
|
|
32580
|
+
} catch {
|
|
32581
|
+
}
|
|
32582
|
+
}
|
|
32583
|
+
return void 0;
|
|
32584
|
+
}
|
|
32585
|
+
/**
|
|
32586
|
+
* Saves dynamically registered client information.
|
|
32587
|
+
*/
|
|
32588
|
+
async saveClientInformation(clientInformation) {
|
|
32589
|
+
this._clientInfo = clientInformation;
|
|
32590
|
+
await this.storage.set("client_info", JSON.stringify(clientInformation));
|
|
32591
|
+
}
|
|
32592
|
+
/**
|
|
32593
|
+
* Loads existing OAuth tokens.
|
|
32594
|
+
*/
|
|
32595
|
+
async tokens() {
|
|
32596
|
+
const stored = await this.storage.get("tokens");
|
|
32597
|
+
if (stored) {
|
|
32598
|
+
try {
|
|
32599
|
+
return JSON.parse(stored);
|
|
32600
|
+
} catch {
|
|
32601
|
+
}
|
|
32602
|
+
}
|
|
32603
|
+
return void 0;
|
|
32604
|
+
}
|
|
32605
|
+
/**
|
|
32606
|
+
* Stores new OAuth tokens after successful authorization.
|
|
32607
|
+
*/
|
|
32608
|
+
async saveTokens(tokens) {
|
|
32609
|
+
await this.storage.set("tokens", JSON.stringify(tokens));
|
|
32610
|
+
}
|
|
32611
|
+
/**
|
|
32612
|
+
* Invoked to redirect the user agent to the authorization URL.
|
|
32613
|
+
*/
|
|
32614
|
+
async redirectToAuthorization(authorizationUrl) {
|
|
32615
|
+
if (this.onRedirect) {
|
|
32616
|
+
await this.onRedirect(authorizationUrl);
|
|
32617
|
+
} else {
|
|
32618
|
+
console.info(`Authorization required. Please visit: ${authorizationUrl.toString()}`);
|
|
32619
|
+
}
|
|
32620
|
+
}
|
|
32621
|
+
/**
|
|
32622
|
+
* Saves a PKCE code verifier before redirecting to authorization.
|
|
32623
|
+
*/
|
|
32624
|
+
async saveCodeVerifier(codeVerifier) {
|
|
32625
|
+
await this.storage.set("code_verifier", codeVerifier);
|
|
32626
|
+
}
|
|
32627
|
+
/**
|
|
32628
|
+
* Loads the PKCE code verifier for validating authorization result.
|
|
32629
|
+
*/
|
|
32630
|
+
async codeVerifier() {
|
|
32631
|
+
const verifier = await this.storage.get("code_verifier");
|
|
32632
|
+
if (!verifier) {
|
|
32633
|
+
throw new Error("No code verifier found. Authorization flow may not have started properly.");
|
|
32634
|
+
}
|
|
32635
|
+
return verifier;
|
|
32636
|
+
}
|
|
32637
|
+
/**
|
|
32638
|
+
* Invalidate credentials when server indicates they're no longer valid.
|
|
32639
|
+
*/
|
|
32640
|
+
async invalidateCredentials(scope) {
|
|
32641
|
+
switch (scope) {
|
|
32642
|
+
case "all":
|
|
32643
|
+
await this.storage.delete("tokens");
|
|
32644
|
+
await this.storage.delete("client_info");
|
|
32645
|
+
await this.storage.delete("code_verifier");
|
|
32646
|
+
this._clientInfo = void 0;
|
|
32647
|
+
break;
|
|
32648
|
+
case "client":
|
|
32649
|
+
await this.storage.delete("client_info");
|
|
32650
|
+
this._clientInfo = void 0;
|
|
32651
|
+
break;
|
|
32652
|
+
case "tokens":
|
|
32653
|
+
await this.storage.delete("tokens");
|
|
32654
|
+
break;
|
|
32655
|
+
case "verifier":
|
|
32656
|
+
await this.storage.delete("code_verifier");
|
|
32657
|
+
break;
|
|
32658
|
+
}
|
|
32659
|
+
}
|
|
32660
|
+
/**
|
|
32661
|
+
* Clear all stored OAuth data.
|
|
32662
|
+
* Useful for logging out or resetting state.
|
|
32663
|
+
*/
|
|
32664
|
+
async clear() {
|
|
32665
|
+
await this.invalidateCredentials("all");
|
|
32666
|
+
}
|
|
32667
|
+
/**
|
|
32668
|
+
* Check if the provider has valid (non-expired) tokens.
|
|
32669
|
+
*/
|
|
32670
|
+
async hasValidTokens() {
|
|
32671
|
+
const currentTokens = await this.tokens();
|
|
32672
|
+
if (!currentTokens) return false;
|
|
32673
|
+
if (!currentTokens.access_token) return false;
|
|
32674
|
+
return true;
|
|
32675
|
+
}
|
|
32676
|
+
};
|
|
32677
|
+
function createSimpleTokenProvider(accessToken, options) {
|
|
32678
|
+
const tokens = {
|
|
32679
|
+
access_token: accessToken,
|
|
32680
|
+
token_type: options.tokenType ?? "Bearer",
|
|
32681
|
+
refresh_token: options.refreshToken,
|
|
32682
|
+
expires_in: options.expiresIn,
|
|
32683
|
+
scope: options.scope
|
|
32684
|
+
};
|
|
32685
|
+
const storage = new InMemoryOAuthStorage();
|
|
32686
|
+
storage.set("tokens", JSON.stringify(tokens));
|
|
32687
|
+
if (options.clientInformation) {
|
|
32688
|
+
storage.set("client_info", JSON.stringify(options.clientInformation));
|
|
32689
|
+
}
|
|
32690
|
+
return new MCPOAuthClientProvider({
|
|
32691
|
+
redirectUrl: options.redirectUrl,
|
|
32692
|
+
clientMetadata: options.clientMetadata,
|
|
32693
|
+
clientInformation: options.clientInformation,
|
|
32694
|
+
storage
|
|
32695
|
+
});
|
|
32696
|
+
}
|
|
32194
32697
|
var MCPClientServerProxy = class extends MCPServerBase {
|
|
32195
32698
|
clientGetter;
|
|
32196
32699
|
cachedClient = null;
|
|
@@ -32330,12 +32833,25 @@ var MCPClientServerProxy = class extends MCPServerBase {
|
|
|
32330
32833
|
// src/client/configuration.ts
|
|
32331
32834
|
var mcpClientInstances = /* @__PURE__ */ new Map();
|
|
32332
32835
|
var TOOL_DISCOVERY_MAX_ATTEMPTS = 2;
|
|
32836
|
+
var LOOPBACK_IPV4 = /^127\.(?:\d{1,3})\.(?:\d{1,3})\.(?:\d{1,3})$/;
|
|
32837
|
+
function isLoopbackHostname(hostname3) {
|
|
32838
|
+
return hostname3 === "localhost" || hostname3 === "[::1]" || hostname3 === "::1" || LOOPBACK_IPV4.test(hostname3);
|
|
32839
|
+
}
|
|
32333
32840
|
var MCPClient = class extends MastraBase {
|
|
32334
32841
|
serverConfigs = {};
|
|
32335
32842
|
id;
|
|
32336
32843
|
defaultTimeout;
|
|
32337
32844
|
mcpClientsById = /* @__PURE__ */ new Map();
|
|
32338
32845
|
disconnectPromise = null;
|
|
32846
|
+
authFlowsByServer = /* @__PURE__ */ new Map();
|
|
32847
|
+
authCallbackServersByServer = /* @__PURE__ */ new Map();
|
|
32848
|
+
/**
|
|
32849
|
+
* Per-server abort controllers for in-flight authorization flows. Created
|
|
32850
|
+
* synchronously at the start of {@link runAuthorizationFlow} — before the
|
|
32851
|
+
* callback server exists — so cancel/disconnect can interrupt the setup phase
|
|
32852
|
+
* (discovery, registration, port binding) and not just the waitForCode wait.
|
|
32853
|
+
*/
|
|
32854
|
+
authAbortControllersByServer = /* @__PURE__ */ new Map();
|
|
32339
32855
|
/**
|
|
32340
32856
|
* Creates a new MCPClient instance for managing MCP server connections.
|
|
32341
32857
|
*
|
|
@@ -32995,6 +33511,17 @@ To fix this you have three different options:
|
|
|
32995
33511
|
this.disconnectPromise = (async () => {
|
|
32996
33512
|
try {
|
|
32997
33513
|
mcpClientInstances.delete(this.id);
|
|
33514
|
+
const pendingFlows = Array.from(this.authFlowsByServer.values());
|
|
33515
|
+
for (const controller of this.authAbortControllersByServer.values()) {
|
|
33516
|
+
controller.abort();
|
|
33517
|
+
}
|
|
33518
|
+
await Promise.allSettled(
|
|
33519
|
+
Array.from(this.authCallbackServersByServer.values()).map((server) => server.close())
|
|
33520
|
+
);
|
|
33521
|
+
await Promise.allSettled(pendingFlows);
|
|
33522
|
+
this.authAbortControllersByServer.clear();
|
|
33523
|
+
this.authCallbackServersByServer.clear();
|
|
33524
|
+
this.authFlowsByServer.clear();
|
|
32998
33525
|
await Promise.allSettled(Array.from(this.mcpClientsById.values()).map((client) => client.disconnect()));
|
|
32999
33526
|
this.mcpClientsById.clear();
|
|
33000
33527
|
} finally {
|
|
@@ -33026,6 +33553,151 @@ To fix this you have three different options:
|
|
|
33026
33553
|
await this.getConnectedClientForServer(serverName);
|
|
33027
33554
|
}
|
|
33028
33555
|
}
|
|
33556
|
+
/**
|
|
33557
|
+
* Runs the interactive OAuth authorization-code flow for a server.
|
|
33558
|
+
*
|
|
33559
|
+
* Requires the server to be configured with an MCPOAuthClientProvider whose
|
|
33560
|
+
* redirect URL points at a loopback address. The flow:
|
|
33561
|
+
*
|
|
33562
|
+
* 1. Starts a loopback callback server on the redirect URL's port (falling
|
|
33563
|
+
* back to the next sequential ports when it is in use)
|
|
33564
|
+
* 2. Attempts a connection so the SDK runs discovery and dynamic client
|
|
33565
|
+
* registration, delivering the authorization URL through the provider's
|
|
33566
|
+
* `onRedirectToAuthorization` callback — the host directs the user there
|
|
33567
|
+
* 3. Waits for the browser to deliver the authorization code, validates the
|
|
33568
|
+
* OAuth state, exchanges the code for tokens, and reconnects
|
|
33569
|
+
*
|
|
33570
|
+
* Concurrent calls for the same server join the pending flow (the joiner's
|
|
33571
|
+
* options are ignored — the pending flow keeps its own timeout); different
|
|
33572
|
+
* servers authenticate independently. Each server needs its own provider
|
|
33573
|
+
* instance: the flow pins session state on the provider, so sharing one
|
|
33574
|
+
* MCPOAuthClientProvider across servers is not supported. Hosts with custom
|
|
33575
|
+
* redirect handling (e.g. a web app with an HTTPS redirect URL) should
|
|
33576
|
+
* drive MCPOAuthClientProvider directly instead.
|
|
33577
|
+
*
|
|
33578
|
+
* @param serverName - The name of the server to authenticate (must match a key in `servers`)
|
|
33579
|
+
* @param options.timeoutMs - How long to wait for the browser callback (default 5 minutes)
|
|
33580
|
+
* @throws {Error} If the server has no MCPOAuthClientProvider or its redirect URL is not loopback
|
|
33581
|
+
*
|
|
33582
|
+
* @example
|
|
33583
|
+
* ```typescript
|
|
33584
|
+
* if (mcp.getServerAuthState('weatherServer') === 'needs-auth') {
|
|
33585
|
+
* await mcp.authenticate('weatherServer');
|
|
33586
|
+
* }
|
|
33587
|
+
* ```
|
|
33588
|
+
*/
|
|
33589
|
+
async authenticate(serverName, options) {
|
|
33590
|
+
if (this.disconnectPromise) {
|
|
33591
|
+
await this.disconnectPromise.catch(() => {
|
|
33592
|
+
});
|
|
33593
|
+
}
|
|
33594
|
+
const pendingFlow = this.authFlowsByServer.get(serverName);
|
|
33595
|
+
if (pendingFlow) {
|
|
33596
|
+
return pendingFlow;
|
|
33597
|
+
}
|
|
33598
|
+
const flow = this.runAuthorizationFlow(serverName, options).finally(() => {
|
|
33599
|
+
this.authFlowsByServer.delete(serverName);
|
|
33600
|
+
});
|
|
33601
|
+
this.authFlowsByServer.set(serverName, flow);
|
|
33602
|
+
return flow;
|
|
33603
|
+
}
|
|
33604
|
+
/**
|
|
33605
|
+
* OAuth authorization state of a configured server.
|
|
33606
|
+
*
|
|
33607
|
+
* Returns `undefined` for servers without an authProvider and for servers
|
|
33608
|
+
* that have not attempted a connection yet.
|
|
33609
|
+
*/
|
|
33610
|
+
getServerAuthState(serverName) {
|
|
33611
|
+
return this.mcpClientsById.get(serverName)?.authState;
|
|
33612
|
+
}
|
|
33613
|
+
async runAuthorizationFlow(serverName, options) {
|
|
33614
|
+
const abortController = new AbortController();
|
|
33615
|
+
this.authAbortControllersByServer.set(serverName, abortController);
|
|
33616
|
+
const throwIfAborted = () => {
|
|
33617
|
+
if (abortController.signal.aborted) {
|
|
33618
|
+
throw new Error(`Authentication for MCP server ${serverName} was cancelled.`);
|
|
33619
|
+
}
|
|
33620
|
+
};
|
|
33621
|
+
let provider;
|
|
33622
|
+
let sessionStarted = false;
|
|
33623
|
+
let callbackServer;
|
|
33624
|
+
try {
|
|
33625
|
+
const config2 = this.getServerConfig(serverName);
|
|
33626
|
+
const candidateProvider = config2.authProvider;
|
|
33627
|
+
if (!(candidateProvider instanceof MCPOAuthClientProvider)) {
|
|
33628
|
+
throw new Error(
|
|
33629
|
+
`Cannot authenticate MCP server ${serverName}: it is not configured with an MCPOAuthClientProvider.`
|
|
33630
|
+
);
|
|
33631
|
+
}
|
|
33632
|
+
provider = candidateProvider;
|
|
33633
|
+
const redirectUrl = new URL(provider.redirectUrl.toString());
|
|
33634
|
+
if (redirectUrl.protocol !== "http:" || !isLoopbackHostname(redirectUrl.hostname)) {
|
|
33635
|
+
throw new Error(
|
|
33636
|
+
`Cannot authenticate MCP server ${serverName}: the provider's redirect URL must be a loopback address, got ${redirectUrl.origin}.`
|
|
33637
|
+
);
|
|
33638
|
+
}
|
|
33639
|
+
const state = await provider.beginAuthorizationSession();
|
|
33640
|
+
sessionStarted = true;
|
|
33641
|
+
throwIfAborted();
|
|
33642
|
+
callbackServer = await createOAuthCallbackServer({ redirectUrl, state });
|
|
33643
|
+
throwIfAborted();
|
|
33644
|
+
this.authCallbackServersByServer.set(serverName, callbackServer);
|
|
33645
|
+
provider.applyResolvedRedirectUrl(callbackServer.url, getCallbackUrlCandidates(redirectUrl));
|
|
33646
|
+
const clientInfo = await provider.clientInformation();
|
|
33647
|
+
if (clientInfo?.redirect_uris && !clientInfo.redirect_uris.includes(callbackServer.url.toString())) {
|
|
33648
|
+
await provider.invalidateCredentials("client");
|
|
33649
|
+
}
|
|
33650
|
+
const client = await this.getClientForServer(serverName);
|
|
33651
|
+
try {
|
|
33652
|
+
await client.connect();
|
|
33653
|
+
return;
|
|
33654
|
+
} catch (error51) {
|
|
33655
|
+
if (!(error51 instanceof UnauthorizedError)) {
|
|
33656
|
+
throw this.handleConnectError(serverName, error51);
|
|
33657
|
+
}
|
|
33658
|
+
}
|
|
33659
|
+
const { code } = await callbackServer.waitForCode(options);
|
|
33660
|
+
await client.finishAuth(code);
|
|
33661
|
+
try {
|
|
33662
|
+
await client.connect();
|
|
33663
|
+
} catch (error51) {
|
|
33664
|
+
throw error51 instanceof UnauthorizedError ? error51 : this.handleConnectError(serverName, error51);
|
|
33665
|
+
}
|
|
33666
|
+
} finally {
|
|
33667
|
+
this.authCallbackServersByServer.delete(serverName);
|
|
33668
|
+
this.authAbortControllersByServer.delete(serverName);
|
|
33669
|
+
if (sessionStarted) {
|
|
33670
|
+
provider?.endAuthorizationSession();
|
|
33671
|
+
}
|
|
33672
|
+
await callbackServer?.close();
|
|
33673
|
+
}
|
|
33674
|
+
}
|
|
33675
|
+
/**
|
|
33676
|
+
* Cancels a pending {@link authenticate} flow for a server.
|
|
33677
|
+
*
|
|
33678
|
+
* Tears down the loopback callback server immediately — the pending
|
|
33679
|
+
* authenticate() call rejects. Useful when the user closed the browser
|
|
33680
|
+
* without completing consent, which the host cannot observe.
|
|
33681
|
+
*
|
|
33682
|
+
* The resulting auth state depends on how far the flow had progressed: a flow
|
|
33683
|
+
* cancelled after the server rejected the connection with a 401 stays in the
|
|
33684
|
+
* `needs-auth` state so it can be retried right away, while a flow cancelled
|
|
33685
|
+
* during the setup phase (before any connection was attempted) leaves the
|
|
33686
|
+
* state unchanged — typically `undefined`.
|
|
33687
|
+
*
|
|
33688
|
+
* @param serverName - The name of the server whose flow to cancel
|
|
33689
|
+
* @returns `true` if a pending flow was cancelled, `false` when no flow was pending
|
|
33690
|
+
*/
|
|
33691
|
+
async cancelAuthentication(serverName) {
|
|
33692
|
+
const pendingFlow = this.authFlowsByServer.get(serverName);
|
|
33693
|
+
if (!pendingFlow) {
|
|
33694
|
+
return false;
|
|
33695
|
+
}
|
|
33696
|
+
this.authAbortControllersByServer.get(serverName)?.abort();
|
|
33697
|
+
await this.authCallbackServersByServer.get(serverName)?.close();
|
|
33698
|
+
await pendingFlow.catch(() => void 0);
|
|
33699
|
+
return true;
|
|
33700
|
+
}
|
|
33029
33701
|
/**
|
|
33030
33702
|
* Returns instructions advertised by connected MCP servers during initialize.
|
|
33031
33703
|
*
|
|
@@ -33311,7 +33983,9 @@ To fix this you have three different options:
|
|
|
33311
33983
|
);
|
|
33312
33984
|
this.logger.trackException(mastraError);
|
|
33313
33985
|
this.logger.error("MCPClient errored connecting to MCP server:", { error: mastraError.toString() });
|
|
33314
|
-
|
|
33986
|
+
if (!(error51 instanceof UnauthorizedError)) {
|
|
33987
|
+
this.mcpClientsById.delete(name);
|
|
33988
|
+
}
|
|
33315
33989
|
return mastraError;
|
|
33316
33990
|
}
|
|
33317
33991
|
async getConnectedClientForServer(serverName) {
|
|
@@ -33342,184 +34016,6 @@ To fix this you have three different options:
|
|
|
33342
34016
|
}
|
|
33343
34017
|
};
|
|
33344
34018
|
|
|
33345
|
-
// src/client/oauth-provider.ts
|
|
33346
|
-
var InMemoryOAuthStorage = class {
|
|
33347
|
-
data = /* @__PURE__ */ new Map();
|
|
33348
|
-
set(key, value) {
|
|
33349
|
-
this.data.set(key, value);
|
|
33350
|
-
}
|
|
33351
|
-
get(key) {
|
|
33352
|
-
return this.data.get(key);
|
|
33353
|
-
}
|
|
33354
|
-
delete(key) {
|
|
33355
|
-
this.data.delete(key);
|
|
33356
|
-
}
|
|
33357
|
-
clear() {
|
|
33358
|
-
this.data.clear();
|
|
33359
|
-
}
|
|
33360
|
-
};
|
|
33361
|
-
var MCPOAuthClientProvider = class {
|
|
33362
|
-
_redirectUrl;
|
|
33363
|
-
_clientMetadata;
|
|
33364
|
-
storage;
|
|
33365
|
-
onRedirect;
|
|
33366
|
-
generateState;
|
|
33367
|
-
_clientInfo;
|
|
33368
|
-
constructor(options) {
|
|
33369
|
-
this._redirectUrl = options.redirectUrl;
|
|
33370
|
-
this._clientMetadata = options.clientMetadata;
|
|
33371
|
-
this._clientInfo = options.clientInformation;
|
|
33372
|
-
this.storage = options.storage ?? new InMemoryOAuthStorage();
|
|
33373
|
-
this.onRedirect = options.onRedirectToAuthorization;
|
|
33374
|
-
this.generateState = options.stateGenerator ?? (() => crypto.randomUUID());
|
|
33375
|
-
}
|
|
33376
|
-
/**
|
|
33377
|
-
* The URL to redirect the user agent to after authorization.
|
|
33378
|
-
*/
|
|
33379
|
-
get redirectUrl() {
|
|
33380
|
-
return this._redirectUrl;
|
|
33381
|
-
}
|
|
33382
|
-
/**
|
|
33383
|
-
* Metadata about this OAuth client.
|
|
33384
|
-
*/
|
|
33385
|
-
get clientMetadata() {
|
|
33386
|
-
return this._clientMetadata;
|
|
33387
|
-
}
|
|
33388
|
-
/**
|
|
33389
|
-
* Returns a OAuth2 state parameter.
|
|
33390
|
-
*/
|
|
33391
|
-
async state() {
|
|
33392
|
-
return this.generateState();
|
|
33393
|
-
}
|
|
33394
|
-
/**
|
|
33395
|
-
* Loads information about this OAuth client.
|
|
33396
|
-
*/
|
|
33397
|
-
async clientInformation() {
|
|
33398
|
-
if (this._clientInfo) {
|
|
33399
|
-
return this._clientInfo;
|
|
33400
|
-
}
|
|
33401
|
-
const stored = await this.storage.get("client_info");
|
|
33402
|
-
if (stored) {
|
|
33403
|
-
try {
|
|
33404
|
-
return JSON.parse(stored);
|
|
33405
|
-
} catch {
|
|
33406
|
-
}
|
|
33407
|
-
}
|
|
33408
|
-
return void 0;
|
|
33409
|
-
}
|
|
33410
|
-
/**
|
|
33411
|
-
* Saves dynamically registered client information.
|
|
33412
|
-
*/
|
|
33413
|
-
async saveClientInformation(clientInformation) {
|
|
33414
|
-
this._clientInfo = clientInformation;
|
|
33415
|
-
await this.storage.set("client_info", JSON.stringify(clientInformation));
|
|
33416
|
-
}
|
|
33417
|
-
/**
|
|
33418
|
-
* Loads existing OAuth tokens.
|
|
33419
|
-
*/
|
|
33420
|
-
async tokens() {
|
|
33421
|
-
const stored = await this.storage.get("tokens");
|
|
33422
|
-
if (stored) {
|
|
33423
|
-
try {
|
|
33424
|
-
return JSON.parse(stored);
|
|
33425
|
-
} catch {
|
|
33426
|
-
}
|
|
33427
|
-
}
|
|
33428
|
-
return void 0;
|
|
33429
|
-
}
|
|
33430
|
-
/**
|
|
33431
|
-
* Stores new OAuth tokens after successful authorization.
|
|
33432
|
-
*/
|
|
33433
|
-
async saveTokens(tokens) {
|
|
33434
|
-
await this.storage.set("tokens", JSON.stringify(tokens));
|
|
33435
|
-
}
|
|
33436
|
-
/**
|
|
33437
|
-
* Invoked to redirect the user agent to the authorization URL.
|
|
33438
|
-
*/
|
|
33439
|
-
async redirectToAuthorization(authorizationUrl) {
|
|
33440
|
-
if (this.onRedirect) {
|
|
33441
|
-
await this.onRedirect(authorizationUrl);
|
|
33442
|
-
} else {
|
|
33443
|
-
console.info(`Authorization required. Please visit: ${authorizationUrl.toString()}`);
|
|
33444
|
-
}
|
|
33445
|
-
}
|
|
33446
|
-
/**
|
|
33447
|
-
* Saves a PKCE code verifier before redirecting to authorization.
|
|
33448
|
-
*/
|
|
33449
|
-
async saveCodeVerifier(codeVerifier) {
|
|
33450
|
-
await this.storage.set("code_verifier", codeVerifier);
|
|
33451
|
-
}
|
|
33452
|
-
/**
|
|
33453
|
-
* Loads the PKCE code verifier for validating authorization result.
|
|
33454
|
-
*/
|
|
33455
|
-
async codeVerifier() {
|
|
33456
|
-
const verifier = await this.storage.get("code_verifier");
|
|
33457
|
-
if (!verifier) {
|
|
33458
|
-
throw new Error("No code verifier found. Authorization flow may not have started properly.");
|
|
33459
|
-
}
|
|
33460
|
-
return verifier;
|
|
33461
|
-
}
|
|
33462
|
-
/**
|
|
33463
|
-
* Invalidate credentials when server indicates they're no longer valid.
|
|
33464
|
-
*/
|
|
33465
|
-
async invalidateCredentials(scope) {
|
|
33466
|
-
switch (scope) {
|
|
33467
|
-
case "all":
|
|
33468
|
-
await this.storage.delete("tokens");
|
|
33469
|
-
await this.storage.delete("client_info");
|
|
33470
|
-
await this.storage.delete("code_verifier");
|
|
33471
|
-
this._clientInfo = void 0;
|
|
33472
|
-
break;
|
|
33473
|
-
case "client":
|
|
33474
|
-
await this.storage.delete("client_info");
|
|
33475
|
-
this._clientInfo = void 0;
|
|
33476
|
-
break;
|
|
33477
|
-
case "tokens":
|
|
33478
|
-
await this.storage.delete("tokens");
|
|
33479
|
-
break;
|
|
33480
|
-
case "verifier":
|
|
33481
|
-
await this.storage.delete("code_verifier");
|
|
33482
|
-
break;
|
|
33483
|
-
}
|
|
33484
|
-
}
|
|
33485
|
-
/**
|
|
33486
|
-
* Clear all stored OAuth data.
|
|
33487
|
-
* Useful for logging out or resetting state.
|
|
33488
|
-
*/
|
|
33489
|
-
async clear() {
|
|
33490
|
-
await this.invalidateCredentials("all");
|
|
33491
|
-
}
|
|
33492
|
-
/**
|
|
33493
|
-
* Check if the provider has valid (non-expired) tokens.
|
|
33494
|
-
*/
|
|
33495
|
-
async hasValidTokens() {
|
|
33496
|
-
const currentTokens = await this.tokens();
|
|
33497
|
-
if (!currentTokens) return false;
|
|
33498
|
-
if (!currentTokens.access_token) return false;
|
|
33499
|
-
return true;
|
|
33500
|
-
}
|
|
33501
|
-
};
|
|
33502
|
-
function createSimpleTokenProvider(accessToken, options) {
|
|
33503
|
-
const tokens = {
|
|
33504
|
-
access_token: accessToken,
|
|
33505
|
-
token_type: options.tokenType ?? "Bearer",
|
|
33506
|
-
refresh_token: options.refreshToken,
|
|
33507
|
-
expires_in: options.expiresIn,
|
|
33508
|
-
scope: options.scope
|
|
33509
|
-
};
|
|
33510
|
-
const storage = new InMemoryOAuthStorage();
|
|
33511
|
-
storage.set("tokens", JSON.stringify(tokens));
|
|
33512
|
-
if (options.clientInformation) {
|
|
33513
|
-
storage.set("client_info", JSON.stringify(options.clientInformation));
|
|
33514
|
-
}
|
|
33515
|
-
return new MCPOAuthClientProvider({
|
|
33516
|
-
redirectUrl: options.redirectUrl,
|
|
33517
|
-
clientMetadata: options.clientMetadata,
|
|
33518
|
-
clientInformation: options.clientInformation,
|
|
33519
|
-
storage
|
|
33520
|
-
});
|
|
33521
|
-
}
|
|
33522
|
-
|
|
33523
34019
|
// ../../../../../setup-pnpm/node_modules/.bin/store/v11/links/@/hono/4.12.30/1b2e4fcdfb5fc9f7f51bffc1540f859608c39e01fb7d73d8ab30d60a680eaffc/node_modules/hono/dist/utils/stream.js
|
|
33524
34020
|
var StreamingApi = class {
|
|
33525
34021
|
writer;
|
|
@@ -36208,44 +36704,6 @@ Provided arguments: ${JSON.stringify(args, null, 2)}`,
|
|
|
36208
36704
|
return { resources };
|
|
36209
36705
|
}
|
|
36210
36706
|
};
|
|
36211
|
-
function escapeHeaderValue(value) {
|
|
36212
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
36213
|
-
}
|
|
36214
|
-
function generateWWWAuthenticateHeader(options = {}) {
|
|
36215
|
-
const params = [];
|
|
36216
|
-
if (options.resourceMetadataUrl) {
|
|
36217
|
-
params.push(`resource_metadata="${escapeHeaderValue(options.resourceMetadataUrl)}"`);
|
|
36218
|
-
}
|
|
36219
|
-
if (options.additionalParams) {
|
|
36220
|
-
for (const [key, value] of Object.entries(options.additionalParams)) {
|
|
36221
|
-
params.push(`${key}="${escapeHeaderValue(value)}"`);
|
|
36222
|
-
}
|
|
36223
|
-
}
|
|
36224
|
-
if (params.length === 0) {
|
|
36225
|
-
return "Bearer";
|
|
36226
|
-
}
|
|
36227
|
-
return `Bearer ${params.join(", ")}`;
|
|
36228
|
-
}
|
|
36229
|
-
function generateProtectedResourceMetadata(config2) {
|
|
36230
|
-
return {
|
|
36231
|
-
resource: config2.resource,
|
|
36232
|
-
authorization_servers: config2.authorizationServers,
|
|
36233
|
-
scopes_supported: config2.scopesSupported ?? ["mcp:read", "mcp:write"],
|
|
36234
|
-
bearer_methods_supported: ["header"],
|
|
36235
|
-
...config2.resourceName && { resource_name: config2.resourceName },
|
|
36236
|
-
...config2.resourceDocumentation && {
|
|
36237
|
-
resource_documentation: config2.resourceDocumentation
|
|
36238
|
-
}
|
|
36239
|
-
};
|
|
36240
|
-
}
|
|
36241
|
-
function extractBearerToken(authHeader) {
|
|
36242
|
-
if (!authHeader) return void 0;
|
|
36243
|
-
const prefix = "bearer ";
|
|
36244
|
-
if (authHeader.length <= prefix.length) return void 0;
|
|
36245
|
-
if (authHeader.slice(0, prefix.length).toLowerCase() !== prefix) return void 0;
|
|
36246
|
-
const token = authHeader.slice(prefix.length).trim();
|
|
36247
|
-
return token || void 0;
|
|
36248
|
-
}
|
|
36249
36707
|
|
|
36250
36708
|
// src/server/oauth-middleware.ts
|
|
36251
36709
|
function createOAuthMiddleware(options) {
|
|
@@ -36412,6 +36870,6 @@ function createIntrospectionValidator(introspectionEndpoint, clientCredentials)
|
|
|
36412
36870
|
};
|
|
36413
36871
|
}
|
|
36414
36872
|
|
|
36415
|
-
export { InMemoryOAuthStorage, InternalMastraMCPClient, MCPClient, MCPClientServerProxy, MCPOAuthClientProvider, MCPServer, createIntrospectionValidator, createOAuthMiddleware, createSimpleTokenProvider, createStaticTokenValidator, extractBearerToken, generateProtectedResourceMetadata, generateWWWAuthenticateHeader };
|
|
36873
|
+
export { InMemoryOAuthStorage, InternalMastraMCPClient, MCPClient, MCPClientServerProxy, MCPOAuthClientProvider, MCPServer, createIntrospectionValidator, createOAuthCallbackServer, createOAuthMiddleware, createSimpleTokenProvider, createStaticTokenValidator, extractBearerToken, generateProtectedResourceMetadata, generateWWWAuthenticateHeader, getCallbackUrlCandidates };
|
|
36416
36874
|
//# sourceMappingURL=index.js.map
|
|
36417
36875
|
//# sourceMappingURL=index.js.map
|