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