@ai-ecoverse/mcpcat 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/mcpcat.mjs +1547 -105
- package/dist/kernel/mcpcat.mjs +1555 -101
- package/package.json +1 -1
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -25,8 +25,10 @@ __export(node_stub_exports, {
|
|
|
25
25
|
access: () => access,
|
|
26
26
|
basename: () => basename,
|
|
27
27
|
constants: () => constants,
|
|
28
|
+
createHash: () => createHash,
|
|
28
29
|
createInterface: () => createInterface,
|
|
29
30
|
createRequire: () => createRequire,
|
|
31
|
+
createServer: () => createServer,
|
|
30
32
|
default: () => node_stub_default,
|
|
31
33
|
deprecate: () => deprecate,
|
|
32
34
|
dirname: () => dirname,
|
|
@@ -42,6 +44,7 @@ __export(node_stub_exports, {
|
|
|
42
44
|
pid: () => pid,
|
|
43
45
|
platform: () => platform,
|
|
44
46
|
promisify: () => promisify,
|
|
47
|
+
randomBytes: () => randomBytes,
|
|
45
48
|
readFile: () => readFile,
|
|
46
49
|
rename: () => rename,
|
|
47
50
|
resolve: () => resolve,
|
|
@@ -115,6 +118,15 @@ function deprecate() {
|
|
|
115
118
|
function createRequire() {
|
|
116
119
|
return () => "";
|
|
117
120
|
}
|
|
121
|
+
function createServer() {
|
|
122
|
+
throw new Error("not available in kernel");
|
|
123
|
+
}
|
|
124
|
+
function createHash() {
|
|
125
|
+
throw new Error("not available in kernel");
|
|
126
|
+
}
|
|
127
|
+
function randomBytes() {
|
|
128
|
+
throw new Error("not available in kernel");
|
|
129
|
+
}
|
|
118
130
|
var node_stub_default, constants, Readable, Writable, Stream, env, pid, platform, EventEmitter, Worker, workerData, parentPort;
|
|
119
131
|
var init_node_stub = __esm({
|
|
120
132
|
"src/stubs/node-stub.ts"() {
|
|
@@ -5090,6 +5102,176 @@ function mergeArgs(base, flags) {
|
|
|
5090
5102
|
return result;
|
|
5091
5103
|
}
|
|
5092
5104
|
|
|
5105
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/callback.js
|
|
5106
|
+
init_node_stub();
|
|
5107
|
+
function plainText(page) {
|
|
5108
|
+
if (page.ok)
|
|
5109
|
+
return "Authorization complete. You may close this window.";
|
|
5110
|
+
return page.details ? `${page.message}
|
|
5111
|
+
|
|
5112
|
+
${page.details}` : page.message;
|
|
5113
|
+
}
|
|
5114
|
+
var OAuthCallbackServer = class _OAuthCallbackServer {
|
|
5115
|
+
redirectUrl;
|
|
5116
|
+
server;
|
|
5117
|
+
paths;
|
|
5118
|
+
timeoutMs;
|
|
5119
|
+
renderPage;
|
|
5120
|
+
pending = /* @__PURE__ */ new Map();
|
|
5121
|
+
constructor(server, redirectUrl, paths, timeoutMs, renderPage) {
|
|
5122
|
+
this.server = server;
|
|
5123
|
+
this.redirectUrl = redirectUrl;
|
|
5124
|
+
this.paths = paths;
|
|
5125
|
+
this.timeoutMs = timeoutMs;
|
|
5126
|
+
this.renderPage = renderPage;
|
|
5127
|
+
}
|
|
5128
|
+
static async listen(options = {}) {
|
|
5129
|
+
const host = options.host ?? "127.0.0.1";
|
|
5130
|
+
const redirectHost = options.redirectHost ?? host;
|
|
5131
|
+
const path = options.path ?? "/callback";
|
|
5132
|
+
let instance;
|
|
5133
|
+
const server = createServer((request, response) => instance?.handle(request.url ?? "/", response));
|
|
5134
|
+
await new Promise((resolve2, reject) => {
|
|
5135
|
+
server.once("error", reject);
|
|
5136
|
+
server.listen(options.port ?? 0, host, () => {
|
|
5137
|
+
server.off("error", reject);
|
|
5138
|
+
resolve2();
|
|
5139
|
+
});
|
|
5140
|
+
});
|
|
5141
|
+
const address = server.address();
|
|
5142
|
+
if (!address || typeof address === "string")
|
|
5143
|
+
throw new Error("OAuth callback server did not bind to TCP");
|
|
5144
|
+
instance = new _OAuthCallbackServer(server, `http://${redirectHost.includes(":") ? `[${redirectHost}]` : redirectHost}:${address.port}${path}`, [path, ...options.extraPaths ?? []], options.timeoutMs ?? 5 * 6e4, options.renderPage);
|
|
5145
|
+
return instance;
|
|
5146
|
+
}
|
|
5147
|
+
/**
|
|
5148
|
+
* Wait for the authorization response with `state`. With `path`, a response on another path fails, so
|
|
5149
|
+
* a server-specific redirect URI can tell authorization servers apart (RFC 9700 section 4.4.2.2).
|
|
5150
|
+
*/
|
|
5151
|
+
waitForCallback(state, path) {
|
|
5152
|
+
if (this.pending.has(state))
|
|
5153
|
+
throw new Error("OAuth state is already pending");
|
|
5154
|
+
return new Promise((resolve2, reject) => {
|
|
5155
|
+
const timer = setTimeout(() => {
|
|
5156
|
+
this.pending.delete(state);
|
|
5157
|
+
reject(new Error("OAuth callback timed out"));
|
|
5158
|
+
}, this.timeoutMs);
|
|
5159
|
+
this.pending.set(state, { resolve: resolve2, reject, timer, path });
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
async close() {
|
|
5163
|
+
for (const pending of this.pending.values()) {
|
|
5164
|
+
clearTimeout(pending.timer);
|
|
5165
|
+
pending.reject(new Error("OAuth callback server closed"));
|
|
5166
|
+
}
|
|
5167
|
+
this.pending.clear();
|
|
5168
|
+
await new Promise((resolve2, reject) => {
|
|
5169
|
+
this.server.close((error) => error ? reject(error) : resolve2());
|
|
5170
|
+
});
|
|
5171
|
+
}
|
|
5172
|
+
reply(response, status, page) {
|
|
5173
|
+
if (this.renderPage) {
|
|
5174
|
+
response.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
5175
|
+
response.end(this.renderPage(page));
|
|
5176
|
+
} else {
|
|
5177
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8" }).end(plainText(page));
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
handle(rawUrl, response) {
|
|
5181
|
+
const url = new URL(rawUrl, this.redirectUrl);
|
|
5182
|
+
if (!this.paths.includes(url.pathname)) {
|
|
5183
|
+
this.reply(response, 404, { ok: false, message: "Not found" });
|
|
5184
|
+
return;
|
|
5185
|
+
}
|
|
5186
|
+
const state = url.searchParams.get("state");
|
|
5187
|
+
const pending = state ? this.pending.get(state) : void 0;
|
|
5188
|
+
if (!state || !pending) {
|
|
5189
|
+
this.reply(response, 400, { ok: false, message: "Invalid or expired OAuth state" });
|
|
5190
|
+
return;
|
|
5191
|
+
}
|
|
5192
|
+
clearTimeout(pending.timer);
|
|
5193
|
+
this.pending.delete(state);
|
|
5194
|
+
if (pending.path !== void 0 && url.pathname !== pending.path) {
|
|
5195
|
+
pending.reject(new Error("The authorization response arrived on another redirect URI"));
|
|
5196
|
+
this.reply(response, 400, { ok: false, message: "Unexpected redirect URI" });
|
|
5197
|
+
return;
|
|
5198
|
+
}
|
|
5199
|
+
const error = url.searchParams.get("error");
|
|
5200
|
+
if (error) {
|
|
5201
|
+
const description = url.searchParams.get("error_description") ?? error;
|
|
5202
|
+
pending.reject(new Error(description));
|
|
5203
|
+
this.reply(response, 200, {
|
|
5204
|
+
ok: false,
|
|
5205
|
+
message: "Authorization failed. You may close this window.",
|
|
5206
|
+
details: description
|
|
5207
|
+
});
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
const code = url.searchParams.get("code");
|
|
5211
|
+
if (!code) {
|
|
5212
|
+
pending.reject(new Error("OAuth callback did not include an authorization code"));
|
|
5213
|
+
this.reply(response, 400, { ok: false, message: "Missing authorization code" });
|
|
5214
|
+
return;
|
|
5215
|
+
}
|
|
5216
|
+
const iss = url.searchParams.get("iss");
|
|
5217
|
+
pending.resolve({ code, state, ...iss ? { iss } : {} });
|
|
5218
|
+
this.reply(response, 200, { ok: true });
|
|
5219
|
+
}
|
|
5220
|
+
};
|
|
5221
|
+
|
|
5222
|
+
// node_modules/@earendil-works/pi-mcp/dist/protocol/types.js
|
|
5223
|
+
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
5224
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05"];
|
|
5225
|
+
|
|
5226
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/errors.js
|
|
5227
|
+
var OAuthError = class extends Error {
|
|
5228
|
+
code;
|
|
5229
|
+
errorUri;
|
|
5230
|
+
constructor(code, message, errorUri) {
|
|
5231
|
+
super(message || code);
|
|
5232
|
+
this.name = "OAuthError";
|
|
5233
|
+
this.code = code;
|
|
5234
|
+
this.errorUri = errorUri;
|
|
5235
|
+
}
|
|
5236
|
+
};
|
|
5237
|
+
var OAuthIssuerMismatchError = class extends Error {
|
|
5238
|
+
/** `undefined` when no authorization server metadata was discovered. */
|
|
5239
|
+
expected;
|
|
5240
|
+
/** `undefined` when an authorization response lacks the `iss` parameter its server promised (RFC 9207). */
|
|
5241
|
+
received;
|
|
5242
|
+
constructor(expected, received) {
|
|
5243
|
+
const format = (issuer) => issuer === void 0 ? "none" : JSON.stringify(issuer);
|
|
5244
|
+
super(`OAuth issuer mismatch: expected ${format(expected)}, received ${format(received)}`);
|
|
5245
|
+
this.name = "OAuthIssuerMismatchError";
|
|
5246
|
+
this.expected = expected;
|
|
5247
|
+
this.received = received;
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
var OAuthInsecureEndpointError = class extends Error {
|
|
5251
|
+
endpoint;
|
|
5252
|
+
constructor(endpoint) {
|
|
5253
|
+
super(`Refusing to send OAuth credentials to non-HTTPS endpoint ${endpoint}`);
|
|
5254
|
+
this.name = "OAuthInsecureEndpointError";
|
|
5255
|
+
this.endpoint = endpoint;
|
|
5256
|
+
}
|
|
5257
|
+
};
|
|
5258
|
+
var OAuthRegistrationError = class extends Error {
|
|
5259
|
+
status;
|
|
5260
|
+
body;
|
|
5261
|
+
constructor(status, body) {
|
|
5262
|
+
super(`OAuth dynamic client registration failed with status ${status}: ${body}`);
|
|
5263
|
+
this.name = "OAuthRegistrationError";
|
|
5264
|
+
this.status = status;
|
|
5265
|
+
this.body = body;
|
|
5266
|
+
}
|
|
5267
|
+
};
|
|
5268
|
+
var McpOAuthAuthorizationRequiredError = class extends Error {
|
|
5269
|
+
constructor() {
|
|
5270
|
+
super("MCP OAuth authorization requires user interaction");
|
|
5271
|
+
this.name = "McpOAuthAuthorizationRequiredError";
|
|
5272
|
+
}
|
|
5273
|
+
};
|
|
5274
|
+
|
|
5093
5275
|
// node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
|
|
5094
5276
|
var JSON_RPC_ERROR_CODES = {
|
|
5095
5277
|
parseError: -32700,
|
|
@@ -5158,9 +5340,726 @@ function parseJsonRpcMessage(value) {
|
|
|
5158
5340
|
throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid JSON-RPC message");
|
|
5159
5341
|
}
|
|
5160
5342
|
|
|
5161
|
-
// node_modules/@earendil-works/pi-mcp/dist/
|
|
5162
|
-
|
|
5163
|
-
|
|
5343
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/types.js
|
|
5344
|
+
function object(value, name) {
|
|
5345
|
+
if (!isObject(value))
|
|
5346
|
+
throw new Error(`Invalid ${name}`);
|
|
5347
|
+
return value;
|
|
5348
|
+
}
|
|
5349
|
+
function compact(value) {
|
|
5350
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
|
|
5351
|
+
}
|
|
5352
|
+
function requiredString(value, name) {
|
|
5353
|
+
if (typeof value !== "string" || value.length === 0)
|
|
5354
|
+
throw new Error(`Invalid ${name}`);
|
|
5355
|
+
return value;
|
|
5356
|
+
}
|
|
5357
|
+
function absent(value) {
|
|
5358
|
+
return value === void 0 || value === null || value === "";
|
|
5359
|
+
}
|
|
5360
|
+
function optionalString(value, name) {
|
|
5361
|
+
if (absent(value))
|
|
5362
|
+
return void 0;
|
|
5363
|
+
return requiredString(value, name);
|
|
5364
|
+
}
|
|
5365
|
+
function optionalStrings(value, name) {
|
|
5366
|
+
if (value === void 0 || value === null)
|
|
5367
|
+
return void 0;
|
|
5368
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
5369
|
+
throw new Error(`Invalid ${name}`);
|
|
5370
|
+
return [...value];
|
|
5371
|
+
}
|
|
5372
|
+
function safeUrl(value, name) {
|
|
5373
|
+
const text = requiredString(value, name);
|
|
5374
|
+
if (!URL.canParse(text))
|
|
5375
|
+
throw new Error(`Invalid ${name}`);
|
|
5376
|
+
const url = new URL(text);
|
|
5377
|
+
if (["javascript:", "data:", "vbscript:"].includes(url.protocol))
|
|
5378
|
+
throw new Error(`Invalid ${name}`);
|
|
5379
|
+
return text;
|
|
5380
|
+
}
|
|
5381
|
+
function optionalUrl(value, name) {
|
|
5382
|
+
return absent(value) ? void 0 : safeUrl(value, name);
|
|
5383
|
+
}
|
|
5384
|
+
function parseProtectedResourceMetadata(value) {
|
|
5385
|
+
const input = object(value, "OAuth protected resource metadata");
|
|
5386
|
+
return compact({
|
|
5387
|
+
...input,
|
|
5388
|
+
resource: safeUrl(input.resource, "OAuth protected resource metadata resource"),
|
|
5389
|
+
authorization_servers: optionalStrings(input.authorization_servers, "authorization_servers")?.map((url) => safeUrl(url, "authorization server URL")),
|
|
5390
|
+
scopes_supported: optionalStrings(input.scopes_supported, "scopes_supported")
|
|
5391
|
+
});
|
|
5392
|
+
}
|
|
5393
|
+
function parseAuthorizationServerMetadata(value) {
|
|
5394
|
+
const input = object(value, "authorization server metadata");
|
|
5395
|
+
const responseTypes = optionalStrings(input.response_types_supported, "response_types_supported");
|
|
5396
|
+
if (!responseTypes)
|
|
5397
|
+
throw new Error("Invalid response_types_supported");
|
|
5398
|
+
return compact({
|
|
5399
|
+
...input,
|
|
5400
|
+
issuer: safeUrl(input.issuer, "authorization server issuer"),
|
|
5401
|
+
authorization_endpoint: safeUrl(input.authorization_endpoint, "authorization endpoint"),
|
|
5402
|
+
token_endpoint: safeUrl(input.token_endpoint, "token endpoint"),
|
|
5403
|
+
registration_endpoint: optionalUrl(input.registration_endpoint, "registration endpoint"),
|
|
5404
|
+
scopes_supported: optionalStrings(input.scopes_supported, "scopes_supported"),
|
|
5405
|
+
response_types_supported: responseTypes,
|
|
5406
|
+
grant_types_supported: optionalStrings(input.grant_types_supported, "grant_types_supported"),
|
|
5407
|
+
token_endpoint_auth_methods_supported: optionalStrings(input.token_endpoint_auth_methods_supported, "token_endpoint_auth_methods_supported"),
|
|
5408
|
+
code_challenge_methods_supported: optionalStrings(input.code_challenge_methods_supported, "code_challenge_methods_supported"),
|
|
5409
|
+
client_id_metadata_document_supported: typeof input.client_id_metadata_document_supported === "boolean" ? input.client_id_metadata_document_supported : void 0,
|
|
5410
|
+
authorization_response_iss_parameter_supported: typeof input.authorization_response_iss_parameter_supported === "boolean" ? input.authorization_response_iss_parameter_supported : void 0
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
function parseOAuthTokens(value) {
|
|
5414
|
+
const input = object(value, "OAuth token response");
|
|
5415
|
+
const expires = absent(input.expires_in) ? void 0 : Number(input.expires_in);
|
|
5416
|
+
if (expires !== void 0 && !Number.isFinite(expires))
|
|
5417
|
+
throw new Error("Invalid expires_in");
|
|
5418
|
+
return compact({
|
|
5419
|
+
access_token: requiredString(input.access_token, "access_token"),
|
|
5420
|
+
token_type: requiredString(input.token_type, "token_type"),
|
|
5421
|
+
expires_in: expires,
|
|
5422
|
+
scope: optionalString(input.scope, "scope"),
|
|
5423
|
+
refresh_token: optionalString(input.refresh_token, "refresh_token"),
|
|
5424
|
+
id_token: optionalString(input.id_token, "id_token")
|
|
5425
|
+
});
|
|
5426
|
+
}
|
|
5427
|
+
function parseClientInformation(value) {
|
|
5428
|
+
const input = object(value, "OAuth client registration response");
|
|
5429
|
+
return compact({
|
|
5430
|
+
...input,
|
|
5431
|
+
client_id: requiredString(input.client_id, "client_id"),
|
|
5432
|
+
client_secret: optionalString(input.client_secret, "client_secret"),
|
|
5433
|
+
client_id_issued_at: typeof input.client_id_issued_at === "number" ? input.client_id_issued_at : void 0,
|
|
5434
|
+
client_secret_expires_at: typeof input.client_secret_expires_at === "number" ? input.client_secret_expires_at : void 0,
|
|
5435
|
+
redirect_uris: optionalStrings(input.redirect_uris, "redirect_uris") ?? []
|
|
5436
|
+
});
|
|
5437
|
+
}
|
|
5438
|
+
|
|
5439
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/discovery.js
|
|
5440
|
+
function discard(response) {
|
|
5441
|
+
void response?.body?.cancel().catch(() => {
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5444
|
+
function isDiscoveryMiss(status) {
|
|
5445
|
+
return status >= 400 && status < 500 || status === 502;
|
|
5446
|
+
}
|
|
5447
|
+
function pathSuffix(pathname) {
|
|
5448
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
5449
|
+
}
|
|
5450
|
+
function field(header, name) {
|
|
5451
|
+
const match = header.match(new RegExp(String.raw`(?:^|[,\s])${name}=(?:"([^"]*)"|([^\s,]+))`, "i"));
|
|
5452
|
+
return match?.[1] || match?.[2] || void 0;
|
|
5453
|
+
}
|
|
5454
|
+
function parseWwwAuthenticate(header) {
|
|
5455
|
+
if (!header)
|
|
5456
|
+
return {};
|
|
5457
|
+
const scheme = header.trimStart().split(/\s+/, 1)[0]?.toLowerCase();
|
|
5458
|
+
if (scheme !== "bearer" && scheme !== "dpop")
|
|
5459
|
+
return {};
|
|
5460
|
+
const resourceMetadata = field(header, "resource_metadata");
|
|
5461
|
+
let resourceMetadataUrl;
|
|
5462
|
+
if (resourceMetadata) {
|
|
5463
|
+
try {
|
|
5464
|
+
resourceMetadataUrl = new URL(resourceMetadata);
|
|
5465
|
+
} catch {
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
return {
|
|
5469
|
+
resourceMetadataUrl,
|
|
5470
|
+
scope: field(header, "scope"),
|
|
5471
|
+
error: field(header, "error"),
|
|
5472
|
+
errorDescription: field(header, "error_description")
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
async function fetchMetadata(url, fetch, protocolVersion, signal) {
|
|
5476
|
+
return fetch(url, {
|
|
5477
|
+
headers: { Accept: "application/json", "MCP-Protocol-Version": protocolVersion },
|
|
5478
|
+
signal
|
|
5479
|
+
});
|
|
5480
|
+
}
|
|
5481
|
+
async function discoverProtectedResourceMetadata(serverUrl, options = {}) {
|
|
5482
|
+
const server = new URL(serverUrl);
|
|
5483
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
5484
|
+
const version = options.protocolVersion ?? LATEST_PROTOCOL_VERSION;
|
|
5485
|
+
let response = await fetchMetadata(options.resourceMetadataUrl ? new URL(options.resourceMetadataUrl) : new URL(`/.well-known/oauth-protected-resource${pathSuffix(server.pathname)}`, server.origin), fetch, version, options.signal);
|
|
5486
|
+
if (!options.resourceMetadataUrl && server.pathname !== "/" && isDiscoveryMiss(response.status)) {
|
|
5487
|
+
discard(response);
|
|
5488
|
+
response = await fetchMetadata(new URL("/.well-known/oauth-protected-resource", server.origin), fetch, version, options.signal);
|
|
5489
|
+
}
|
|
5490
|
+
if (!response.ok) {
|
|
5491
|
+
discard(response);
|
|
5492
|
+
throw new Error(`HTTP ${response.status} loading OAuth protected resource metadata`);
|
|
5493
|
+
}
|
|
5494
|
+
return parseProtectedResourceMetadata(await response.json());
|
|
5495
|
+
}
|
|
5496
|
+
function buildAuthorizationServerDiscoveryUrls(authorizationServerUrl) {
|
|
5497
|
+
const issuer = new URL(authorizationServerUrl);
|
|
5498
|
+
const path = pathSuffix(issuer.pathname);
|
|
5499
|
+
const urls = [
|
|
5500
|
+
{ url: new URL(`/.well-known/oauth-authorization-server${path}`, issuer.origin), type: "oauth" },
|
|
5501
|
+
{ url: new URL(`/.well-known/openid-configuration${path}`, issuer.origin), type: "oidc" }
|
|
5502
|
+
];
|
|
5503
|
+
if (path)
|
|
5504
|
+
urls.push({ url: new URL(`${path}/.well-known/openid-configuration`, issuer.origin), type: "oidc" });
|
|
5505
|
+
return urls;
|
|
5506
|
+
}
|
|
5507
|
+
async function discoverAuthorizationServerMetadata(authorizationServerUrl, options = {}) {
|
|
5508
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
5509
|
+
for (const { url } of buildAuthorizationServerDiscoveryUrls(authorizationServerUrl)) {
|
|
5510
|
+
const response = await fetchMetadata(url, fetch, options.protocolVersion ?? LATEST_PROTOCOL_VERSION, options.signal);
|
|
5511
|
+
if (!response.ok) {
|
|
5512
|
+
discard(response);
|
|
5513
|
+
if (isDiscoveryMiss(response.status))
|
|
5514
|
+
continue;
|
|
5515
|
+
throw new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);
|
|
5516
|
+
}
|
|
5517
|
+
const metadata = parseAuthorizationServerMetadata(await response.json());
|
|
5518
|
+
if (!options.skipIssuerValidation) {
|
|
5519
|
+
const expected = String(authorizationServerUrl);
|
|
5520
|
+
const trim = (value) => value.endsWith("/") ? value.slice(0, -1) : value;
|
|
5521
|
+
if (trim(metadata.issuer) !== trim(expected))
|
|
5522
|
+
throw new OAuthIssuerMismatchError(expected, metadata.issuer);
|
|
5523
|
+
}
|
|
5524
|
+
return metadata;
|
|
5525
|
+
}
|
|
5526
|
+
return void 0;
|
|
5527
|
+
}
|
|
5528
|
+
async function discoverOAuthServerInfo(serverUrl, options = {}) {
|
|
5529
|
+
let resourceMetadata;
|
|
5530
|
+
try {
|
|
5531
|
+
resourceMetadata = await discoverProtectedResourceMetadata(serverUrl, {
|
|
5532
|
+
resourceMetadataUrl: options.resourceMetadataUrl,
|
|
5533
|
+
fetch: options.fetch,
|
|
5534
|
+
signal: options.signal
|
|
5535
|
+
});
|
|
5536
|
+
} catch (error) {
|
|
5537
|
+
if (error instanceof TypeError)
|
|
5538
|
+
throw error;
|
|
5539
|
+
}
|
|
5540
|
+
if (options.authorizationServerMetadataUrl) {
|
|
5541
|
+
const url = options.authorizationServerMetadataUrl;
|
|
5542
|
+
const response = await fetchMetadata(url, options.fetch ?? globalThis.fetch, LATEST_PROTOCOL_VERSION, options.signal);
|
|
5543
|
+
if (!response.ok) {
|
|
5544
|
+
discard(response);
|
|
5545
|
+
throw new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);
|
|
5546
|
+
}
|
|
5547
|
+
const metadata = parseAuthorizationServerMetadata(await response.json());
|
|
5548
|
+
return { authorizationServerUrl: metadata.issuer, authorizationServerMetadata: metadata, resourceMetadata };
|
|
5549
|
+
}
|
|
5550
|
+
const authorizationServerUrl = resourceMetadata?.authorization_servers?.[0] ?? String(new URL("/", serverUrl));
|
|
5551
|
+
return {
|
|
5552
|
+
authorizationServerUrl,
|
|
5553
|
+
authorizationServerMetadata: await discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
5554
|
+
fetch: options.fetch,
|
|
5555
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
5556
|
+
signal: options.signal
|
|
5557
|
+
}),
|
|
5558
|
+
resourceMetadata
|
|
5559
|
+
};
|
|
5560
|
+
}
|
|
5561
|
+
function resourceUrlFromServerUrl(value) {
|
|
5562
|
+
const url = new URL(value);
|
|
5563
|
+
url.hash = "";
|
|
5564
|
+
return url;
|
|
5565
|
+
}
|
|
5566
|
+
function selectResource(serverUrl, metadata) {
|
|
5567
|
+
if (!metadata)
|
|
5568
|
+
return void 0;
|
|
5569
|
+
const requested = resourceUrlFromServerUrl(serverUrl);
|
|
5570
|
+
const configured = new URL(metadata.resource);
|
|
5571
|
+
if (requested.origin !== configured.origin) {
|
|
5572
|
+
throw new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);
|
|
5573
|
+
}
|
|
5574
|
+
const requestedPath = requested.pathname.endsWith("/") ? requested.pathname : `${requested.pathname}/`;
|
|
5575
|
+
const configuredPath = configured.pathname.endsWith("/") ? configured.pathname : `${configured.pathname}/`;
|
|
5576
|
+
if (!requestedPath.startsWith(configuredPath)) {
|
|
5577
|
+
throw new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);
|
|
5578
|
+
}
|
|
5579
|
+
return metadata.resource;
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5582
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/flow.js
|
|
5583
|
+
function loopback(hostname) {
|
|
5584
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
|
|
5585
|
+
}
|
|
5586
|
+
function applicationType(redirectUris) {
|
|
5587
|
+
const native = redirectUris.some((uri) => {
|
|
5588
|
+
if (!URL.canParse(uri))
|
|
5589
|
+
return false;
|
|
5590
|
+
const url = new URL(uri);
|
|
5591
|
+
return url.protocol !== "http:" && url.protocol !== "https:" || loopback(url.hostname);
|
|
5592
|
+
});
|
|
5593
|
+
return native ? "native" : "web";
|
|
5594
|
+
}
|
|
5595
|
+
function secureEndpoint(value) {
|
|
5596
|
+
const url = new URL(value);
|
|
5597
|
+
if (url.protocol !== "https:" && !loopback(url.hostname))
|
|
5598
|
+
throw new OAuthInsecureEndpointError(url.href);
|
|
5599
|
+
return url;
|
|
5600
|
+
}
|
|
5601
|
+
function selectClientAuthMethod(information, supported) {
|
|
5602
|
+
const hinted = "token_endpoint_auth_method" in information ? information.token_endpoint_auth_method : void 0;
|
|
5603
|
+
if (hinted && ["client_secret_basic", "client_secret_post", "none"].includes(hinted) && (supported.length === 0 || supported.includes(hinted))) {
|
|
5604
|
+
return hinted;
|
|
5605
|
+
}
|
|
5606
|
+
if (supported.length === 0)
|
|
5607
|
+
return information.client_secret ? "client_secret_basic" : "none";
|
|
5608
|
+
if (information.client_secret && supported.includes("client_secret_basic"))
|
|
5609
|
+
return "client_secret_basic";
|
|
5610
|
+
if (information.client_secret && supported.includes("client_secret_post"))
|
|
5611
|
+
return "client_secret_post";
|
|
5612
|
+
if (supported.includes("none"))
|
|
5613
|
+
return "none";
|
|
5614
|
+
return information.client_secret ? "client_secret_post" : "none";
|
|
5615
|
+
}
|
|
5616
|
+
function applyClientAuthentication(method, information, headers, params) {
|
|
5617
|
+
if (method === "client_secret_basic") {
|
|
5618
|
+
if (!information.client_secret)
|
|
5619
|
+
throw new Error("client_secret_basic requires a client secret");
|
|
5620
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${information.client_id}:${information.client_secret}`).toString("base64")}`);
|
|
5621
|
+
} else {
|
|
5622
|
+
params.set("client_id", information.client_id);
|
|
5623
|
+
if (method === "client_secret_post" && information.client_secret)
|
|
5624
|
+
params.set("client_secret", information.client_secret);
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
async function pkce() {
|
|
5628
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
5629
|
+
const verifier = Buffer.from(bytes).toString("base64url");
|
|
5630
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
5631
|
+
return { verifier, challenge: Buffer.from(digest).toString("base64url") };
|
|
5632
|
+
}
|
|
5633
|
+
async function startAuthorization(authorizationServerUrl, options) {
|
|
5634
|
+
const metadata = options.metadata;
|
|
5635
|
+
if (metadata && !metadata.response_types_supported.includes("code")) {
|
|
5636
|
+
throw new Error("Authorization server does not support authorization codes");
|
|
5637
|
+
}
|
|
5638
|
+
if (metadata?.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes("S256")) {
|
|
5639
|
+
throw new Error("Authorization server does not support PKCE S256");
|
|
5640
|
+
}
|
|
5641
|
+
const url = new URL(metadata?.authorization_endpoint ?? new URL("/authorize", authorizationServerUrl));
|
|
5642
|
+
const { verifier, challenge } = await pkce();
|
|
5643
|
+
url.searchParams.set("response_type", "code");
|
|
5644
|
+
url.searchParams.set("client_id", options.clientInformation.client_id);
|
|
5645
|
+
url.searchParams.set("code_challenge", challenge);
|
|
5646
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
5647
|
+
url.searchParams.set("redirect_uri", String(options.redirectUrl));
|
|
5648
|
+
if (options.state)
|
|
5649
|
+
url.searchParams.set("state", options.state);
|
|
5650
|
+
if (options.scope)
|
|
5651
|
+
url.searchParams.set("scope", options.scope);
|
|
5652
|
+
if (options.scope?.split(/\s+/).includes("offline_access"))
|
|
5653
|
+
url.searchParams.set("prompt", "consent");
|
|
5654
|
+
if (options.resource)
|
|
5655
|
+
url.searchParams.set("resource", options.resource);
|
|
5656
|
+
return { authorizationUrl: url, codeVerifier: verifier };
|
|
5657
|
+
}
|
|
5658
|
+
async function tokenRequest(authorizationServerUrl, options, params) {
|
|
5659
|
+
const url = secureEndpoint(options.metadata?.token_endpoint ?? new URL("/token", authorizationServerUrl));
|
|
5660
|
+
const headers = new Headers({ Accept: "application/json", "content-type": "application/x-www-form-urlencoded" });
|
|
5661
|
+
if (options.resource)
|
|
5662
|
+
params.set("resource", options.resource);
|
|
5663
|
+
if (options.addClientAuthentication) {
|
|
5664
|
+
await options.addClientAuthentication(headers, params, url, options.metadata);
|
|
5665
|
+
} else {
|
|
5666
|
+
applyClientAuthentication(selectClientAuthMethod(options.clientInformation, options.metadata?.token_endpoint_auth_methods_supported ?? []), options.clientInformation, headers, params);
|
|
5667
|
+
}
|
|
5668
|
+
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
5669
|
+
method: "POST",
|
|
5670
|
+
headers,
|
|
5671
|
+
body: params,
|
|
5672
|
+
signal: options.signal
|
|
5673
|
+
});
|
|
5674
|
+
const text = await response.text();
|
|
5675
|
+
let value;
|
|
5676
|
+
try {
|
|
5677
|
+
value = JSON.parse(text);
|
|
5678
|
+
} catch {
|
|
5679
|
+
}
|
|
5680
|
+
if (isObject(value) && typeof value.error === "string") {
|
|
5681
|
+
throw new OAuthError(value.error, typeof value.error_description === "string" ? value.error_description : value.error, typeof value.error_uri === "string" ? value.error_uri : void 0);
|
|
5682
|
+
}
|
|
5683
|
+
if (!response.ok)
|
|
5684
|
+
throw new OAuthError("server_error", `HTTP ${response.status}: ${text}`);
|
|
5685
|
+
return parseOAuthTokens(value);
|
|
5686
|
+
}
|
|
5687
|
+
async function registerClient(authorizationServerUrl, options) {
|
|
5688
|
+
const endpoint = options.metadata?.registration_endpoint;
|
|
5689
|
+
if (options.metadata && !endpoint)
|
|
5690
|
+
throw new Error("Authorization server does not support dynamic client registration");
|
|
5691
|
+
const response = await (options.fetch ?? globalThis.fetch)(new URL(endpoint ?? new URL("/register", authorizationServerUrl)), {
|
|
5692
|
+
method: "POST",
|
|
5693
|
+
headers: { Accept: "application/json", "content-type": "application/json" },
|
|
5694
|
+
body: JSON.stringify({
|
|
5695
|
+
...options.clientMetadata,
|
|
5696
|
+
application_type: options.clientMetadata.application_type ?? applicationType(options.clientMetadata.redirect_uris),
|
|
5697
|
+
...options.scope ? { scope: options.scope } : {}
|
|
5698
|
+
}),
|
|
5699
|
+
signal: options.signal
|
|
5700
|
+
});
|
|
5701
|
+
if (!response.ok)
|
|
5702
|
+
throw new OAuthRegistrationError(response.status, await response.text());
|
|
5703
|
+
return parseClientInformation(await response.json());
|
|
5704
|
+
}
|
|
5705
|
+
async function exchangeAuthorizationCode(authorizationServerUrl, options) {
|
|
5706
|
+
return tokenRequest(authorizationServerUrl, options, new URLSearchParams({
|
|
5707
|
+
grant_type: "authorization_code",
|
|
5708
|
+
code: options.code,
|
|
5709
|
+
code_verifier: options.codeVerifier,
|
|
5710
|
+
redirect_uri: String(options.redirectUrl)
|
|
5711
|
+
}));
|
|
5712
|
+
}
|
|
5713
|
+
async function refreshAuthorization(authorizationServerUrl, options) {
|
|
5714
|
+
const tokens = await tokenRequest(authorizationServerUrl, options, new URLSearchParams({ grant_type: "refresh_token", refresh_token: options.refreshToken }));
|
|
5715
|
+
return { refresh_token: options.refreshToken, ...tokens };
|
|
5716
|
+
}
|
|
5717
|
+
function withScope(tokens, scope) {
|
|
5718
|
+
return tokens.scope === void 0 && scope ? { ...tokens, scope } : tokens;
|
|
5719
|
+
}
|
|
5720
|
+
function stepUpScope(granted, challenged) {
|
|
5721
|
+
if (!challenged)
|
|
5722
|
+
return void 0;
|
|
5723
|
+
const scopes = [granted, challenged].flatMap((scope) => scope?.split(/\s+/).filter(Boolean) ?? []);
|
|
5724
|
+
return [...new Set(scopes)].join(" ");
|
|
5725
|
+
}
|
|
5726
|
+
async function runFlow(provider, options) {
|
|
5727
|
+
const metadataUrl = options.authorizationServerMetadataUrl && secureEndpoint(options.authorizationServerMetadataUrl);
|
|
5728
|
+
const cached = metadataUrl ? void 0 : await provider.discoveryState?.();
|
|
5729
|
+
const discovered = cached?.authorizationServerUrl ? {
|
|
5730
|
+
authorizationServerUrl: cached.authorizationServerUrl,
|
|
5731
|
+
authorizationServerMetadata: cached.authorizationServerMetadata ?? await discoverAuthorizationServerMetadata(cached.authorizationServerUrl, {
|
|
5732
|
+
fetch: options.fetch,
|
|
5733
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
5734
|
+
signal: options.signal
|
|
5735
|
+
}),
|
|
5736
|
+
resourceMetadata: cached.resourceMetadata
|
|
5737
|
+
} : await discoverOAuthServerInfo(options.serverUrl, {
|
|
5738
|
+
resourceMetadataUrl: options.resourceMetadataUrl,
|
|
5739
|
+
authorizationServerMetadataUrl: metadataUrl,
|
|
5740
|
+
fetch: options.fetch,
|
|
5741
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
5742
|
+
signal: options.signal
|
|
5743
|
+
});
|
|
5744
|
+
if (!metadataUrl) {
|
|
5745
|
+
await provider.saveDiscoveryState?.({
|
|
5746
|
+
...discovered,
|
|
5747
|
+
...options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl.href } : {}
|
|
5748
|
+
});
|
|
5749
|
+
}
|
|
5750
|
+
const metadata = discovered.authorizationServerMetadata;
|
|
5751
|
+
const resource = selectResource(options.serverUrl, discovered.resourceMetadata);
|
|
5752
|
+
const scope = options.scope || discovered.resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
|
|
5753
|
+
const stored = await provider.clientInformation();
|
|
5754
|
+
const clientDocument = stored ? void 0 : provider.clientMetadataDocument?.(metadata);
|
|
5755
|
+
if (clientDocument) {
|
|
5756
|
+
const url = new URL(clientDocument.url);
|
|
5757
|
+
if (url.protocol !== "https:" || url.pathname === "/")
|
|
5758
|
+
throw new Error("Invalid OAuth client metadata URL");
|
|
5759
|
+
}
|
|
5760
|
+
let client = stored ?? (clientDocument && { client_id: clientDocument.url });
|
|
5761
|
+
if (!client) {
|
|
5762
|
+
if (options.authorizationCode)
|
|
5763
|
+
throw new Error("OAuth client information is missing during code exchange");
|
|
5764
|
+
if (!provider.saveClientInformation)
|
|
5765
|
+
throw new Error("OAuth client information cannot be persisted");
|
|
5766
|
+
client = await registerClient(discovered.authorizationServerUrl, {
|
|
5767
|
+
metadata,
|
|
5768
|
+
clientMetadata: provider.clientMetadata,
|
|
5769
|
+
scope,
|
|
5770
|
+
fetch: options.fetch,
|
|
5771
|
+
signal: options.signal
|
|
5772
|
+
});
|
|
5773
|
+
await provider.saveClientInformation(client);
|
|
5774
|
+
}
|
|
5775
|
+
const redirectUrl = clientDocument?.redirectUrl ?? provider.redirectUrl;
|
|
5776
|
+
const tokenOptions = {
|
|
5777
|
+
metadata,
|
|
5778
|
+
clientInformation: client,
|
|
5779
|
+
resource,
|
|
5780
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
5781
|
+
fetch: options.fetch,
|
|
5782
|
+
signal: options.signal
|
|
5783
|
+
};
|
|
5784
|
+
if (options.authorizationCode) {
|
|
5785
|
+
const iss = options.iss;
|
|
5786
|
+
if (iss !== void 0 || metadata?.authorization_response_iss_parameter_supported) {
|
|
5787
|
+
if (iss !== metadata?.issuer)
|
|
5788
|
+
throw new OAuthIssuerMismatchError(metadata?.issuer, iss);
|
|
5789
|
+
}
|
|
5790
|
+
const tokens = await exchangeAuthorizationCode(discovered.authorizationServerUrl, {
|
|
5791
|
+
...tokenOptions,
|
|
5792
|
+
code: options.authorizationCode,
|
|
5793
|
+
codeVerifier: await provider.codeVerifier(),
|
|
5794
|
+
redirectUrl
|
|
5795
|
+
});
|
|
5796
|
+
await provider.saveTokens(withScope(tokens, scope));
|
|
5797
|
+
return "AUTHORIZED";
|
|
5798
|
+
}
|
|
5799
|
+
const existing = options.skipRefresh ? void 0 : await provider.tokens();
|
|
5800
|
+
if (existing?.refresh_token) {
|
|
5801
|
+
try {
|
|
5802
|
+
const tokens = await refreshAuthorization(discovered.authorizationServerUrl, {
|
|
5803
|
+
...tokenOptions,
|
|
5804
|
+
refreshToken: existing.refresh_token
|
|
5805
|
+
});
|
|
5806
|
+
await provider.saveTokens(withScope(tokens, existing.scope));
|
|
5807
|
+
return "AUTHORIZED";
|
|
5808
|
+
} catch (error) {
|
|
5809
|
+
if (options.signal?.aborted || error instanceof OAuthInsecureEndpointError)
|
|
5810
|
+
throw error;
|
|
5811
|
+
if (error instanceof OAuthError && error.code !== "server_error")
|
|
5812
|
+
throw error;
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
const state = await provider.state?.();
|
|
5816
|
+
const authorization = await startAuthorization(discovered.authorizationServerUrl, {
|
|
5817
|
+
metadata,
|
|
5818
|
+
clientInformation: client,
|
|
5819
|
+
redirectUrl,
|
|
5820
|
+
scope,
|
|
5821
|
+
state,
|
|
5822
|
+
resource
|
|
5823
|
+
});
|
|
5824
|
+
await provider.saveCodeVerifier(authorization.codeVerifier);
|
|
5825
|
+
await provider.redirectToAuthorization(authorization.authorizationUrl);
|
|
5826
|
+
return "REDIRECT";
|
|
5827
|
+
}
|
|
5828
|
+
async function authorizeMcp(provider, options) {
|
|
5829
|
+
try {
|
|
5830
|
+
return await runFlow(provider, options);
|
|
5831
|
+
} catch (error) {
|
|
5832
|
+
if (error instanceof OAuthError && ["invalid_client", "unauthorized_client"].includes(error.code)) {
|
|
5833
|
+
await provider.invalidateCredentials?.("all");
|
|
5834
|
+
return runFlow(provider, options);
|
|
5835
|
+
}
|
|
5836
|
+
if (error instanceof OAuthError && error.code === "invalid_grant") {
|
|
5837
|
+
await provider.invalidateCredentials?.("tokens");
|
|
5838
|
+
return runFlow(provider, options);
|
|
5839
|
+
}
|
|
5840
|
+
throw error;
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/provider.js
|
|
5845
|
+
var MemoryOAuthStateStore = class {
|
|
5846
|
+
value;
|
|
5847
|
+
load() {
|
|
5848
|
+
return this.value === void 0 ? void 0 : structuredClone(this.value);
|
|
5849
|
+
}
|
|
5850
|
+
save(state) {
|
|
5851
|
+
this.value = structuredClone(state);
|
|
5852
|
+
}
|
|
5853
|
+
};
|
|
5854
|
+
var McpOAuthProvider = class {
|
|
5855
|
+
redirectUrl;
|
|
5856
|
+
clientMetadata;
|
|
5857
|
+
clientMetadataDocument;
|
|
5858
|
+
serverUrl;
|
|
5859
|
+
configuredClient;
|
|
5860
|
+
store;
|
|
5861
|
+
onRedirect;
|
|
5862
|
+
writes = Promise.resolve();
|
|
5863
|
+
constructor(options) {
|
|
5864
|
+
this.serverUrl = String(new URL(options.serverUrl));
|
|
5865
|
+
this.redirectUrl = String(options.redirectUrl);
|
|
5866
|
+
this.clientMetadata = {
|
|
5867
|
+
...options.clientMetadata,
|
|
5868
|
+
redirect_uris: options.clientMetadata.redirect_uris ?? [this.redirectUrl],
|
|
5869
|
+
grant_types: options.clientMetadata.grant_types ?? ["authorization_code", "refresh_token"],
|
|
5870
|
+
response_types: options.clientMetadata.response_types ?? ["code"],
|
|
5871
|
+
token_endpoint_auth_method: options.clientMetadata.token_endpoint_auth_method ?? (options.clientSecret ? "client_secret_post" : "none")
|
|
5872
|
+
};
|
|
5873
|
+
this.clientMetadataDocument = options.clientMetadataDocument;
|
|
5874
|
+
this.configuredClient = options.clientId ? { client_id: options.clientId, ...options.clientSecret ? { client_secret: options.clientSecret } : {} } : void 0;
|
|
5875
|
+
this.store = options.store ?? new MemoryOAuthStateStore();
|
|
5876
|
+
this.onRedirect = options.onRedirect;
|
|
5877
|
+
}
|
|
5878
|
+
async state() {
|
|
5879
|
+
const existing = (await this.load()).oauthState;
|
|
5880
|
+
if (existing)
|
|
5881
|
+
return existing;
|
|
5882
|
+
const state = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex");
|
|
5883
|
+
await this.update((value) => ({ ...value, oauthState: state }));
|
|
5884
|
+
return state;
|
|
5885
|
+
}
|
|
5886
|
+
async clientInformation() {
|
|
5887
|
+
return this.configuredClient ?? (await this.load()).clientInformation;
|
|
5888
|
+
}
|
|
5889
|
+
async saveClientInformation(information) {
|
|
5890
|
+
if (this.configuredClient)
|
|
5891
|
+
return;
|
|
5892
|
+
await this.update((value) => ({ ...value, clientInformation: information }));
|
|
5893
|
+
}
|
|
5894
|
+
async tokens() {
|
|
5895
|
+
return (await this.load()).tokens;
|
|
5896
|
+
}
|
|
5897
|
+
async saveTokens(tokens) {
|
|
5898
|
+
const expiresAt = tokens.expires_in === void 0 ? void 0 : Date.now() + tokens.expires_in * 1e3;
|
|
5899
|
+
await this.update((value) => {
|
|
5900
|
+
const next = { ...value, tokens };
|
|
5901
|
+
if (expiresAt === void 0)
|
|
5902
|
+
delete next.tokensExpireAt;
|
|
5903
|
+
else
|
|
5904
|
+
next.tokensExpireAt = expiresAt;
|
|
5905
|
+
return next;
|
|
5906
|
+
});
|
|
5907
|
+
}
|
|
5908
|
+
async redirectToAuthorization(url) {
|
|
5909
|
+
await this.onRedirect(url);
|
|
5910
|
+
}
|
|
5911
|
+
async saveCodeVerifier(verifier) {
|
|
5912
|
+
await this.update((value) => ({ ...value, codeVerifier: verifier }));
|
|
5913
|
+
}
|
|
5914
|
+
async codeVerifier() {
|
|
5915
|
+
const verifier = (await this.load()).codeVerifier;
|
|
5916
|
+
if (!verifier)
|
|
5917
|
+
throw new Error("No OAuth PKCE code verifier is stored");
|
|
5918
|
+
return verifier;
|
|
5919
|
+
}
|
|
5920
|
+
async invalidateCredentials(kind) {
|
|
5921
|
+
await this.update((value) => {
|
|
5922
|
+
const next = { ...value };
|
|
5923
|
+
if (kind === "all" || kind === "client")
|
|
5924
|
+
delete next.clientInformation;
|
|
5925
|
+
if (kind === "all" || kind === "tokens") {
|
|
5926
|
+
delete next.tokens;
|
|
5927
|
+
delete next.tokensExpireAt;
|
|
5928
|
+
}
|
|
5929
|
+
if (kind === "all" || kind === "verifier")
|
|
5930
|
+
delete next.codeVerifier;
|
|
5931
|
+
if (kind === "all" || kind === "discovery")
|
|
5932
|
+
delete next.discovery;
|
|
5933
|
+
if (kind === "all")
|
|
5934
|
+
delete next.oauthState;
|
|
5935
|
+
return next;
|
|
5936
|
+
});
|
|
5937
|
+
}
|
|
5938
|
+
async saveDiscoveryState(discovery) {
|
|
5939
|
+
await this.update((value) => ({ ...value, discovery }));
|
|
5940
|
+
}
|
|
5941
|
+
async discoveryState() {
|
|
5942
|
+
return (await this.load()).discovery;
|
|
5943
|
+
}
|
|
5944
|
+
async load() {
|
|
5945
|
+
await this.writes;
|
|
5946
|
+
return this.own(await this.store.load());
|
|
5947
|
+
}
|
|
5948
|
+
async update(update) {
|
|
5949
|
+
this.writes = this.writes.then(async () => {
|
|
5950
|
+
await this.store.save(update(this.own(await this.store.load())));
|
|
5951
|
+
});
|
|
5952
|
+
await this.writes;
|
|
5953
|
+
}
|
|
5954
|
+
/** Stored state for another server URL is ignored so credentials never leak across servers. */
|
|
5955
|
+
own(state) {
|
|
5956
|
+
return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };
|
|
5957
|
+
}
|
|
5958
|
+
};
|
|
5959
|
+
|
|
5960
|
+
// src/auth/auth-provider.ts
|
|
5961
|
+
var CIMD_URL = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
5962
|
+
var CLIENT_NAME = "mcpcat";
|
|
5963
|
+
var CALLBACK_HOST = "127.0.0.1";
|
|
5964
|
+
var CALLBACK_PATH = "/callback";
|
|
5965
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
5966
|
+
var REFRESH_SKEW_MS = 3e4;
|
|
5967
|
+
var FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
5968
|
+
function timedFetch(fetchImpl) {
|
|
5969
|
+
return (input, init) => {
|
|
5970
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
5971
|
+
return fetchImpl(input, {
|
|
5972
|
+
...init,
|
|
5973
|
+
signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
|
|
5974
|
+
});
|
|
5975
|
+
};
|
|
5976
|
+
}
|
|
5977
|
+
var SourceBackedStore = class {
|
|
5978
|
+
source;
|
|
5979
|
+
name;
|
|
5980
|
+
serverUrl;
|
|
5981
|
+
constructor(source, name, serverUrl) {
|
|
5982
|
+
this.source = source;
|
|
5983
|
+
this.name = name;
|
|
5984
|
+
this.serverUrl = serverUrl;
|
|
5985
|
+
}
|
|
5986
|
+
async load() {
|
|
5987
|
+
return this.source.load(this.name, this.serverUrl);
|
|
5988
|
+
}
|
|
5989
|
+
async save(state) {
|
|
5990
|
+
await this.source.save(this.name, this.serverUrl, state);
|
|
5991
|
+
}
|
|
5992
|
+
};
|
|
5993
|
+
function cimdFn(redirectUrl) {
|
|
5994
|
+
return (metadata) => {
|
|
5995
|
+
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
5996
|
+
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
5997
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
5998
|
+
return { url: CIMD_URL, redirectUrl };
|
|
5999
|
+
};
|
|
6000
|
+
}
|
|
6001
|
+
var AuthStepUpError = class extends Error {
|
|
6002
|
+
scope;
|
|
6003
|
+
target;
|
|
6004
|
+
constructor(scope, target) {
|
|
6005
|
+
super(scope ? `insufficient_scope: ${scope}` : "insufficient_scope");
|
|
6006
|
+
this.name = "AuthStepUpError";
|
|
6007
|
+
this.scope = scope;
|
|
6008
|
+
this.target = target;
|
|
6009
|
+
}
|
|
6010
|
+
};
|
|
6011
|
+
function createMcpAuthProvider(options) {
|
|
6012
|
+
const { name, serverUrl, source, oauth, platform: platform2 } = options;
|
|
6013
|
+
const store = new SourceBackedStore(source, name, serverUrl);
|
|
6014
|
+
let refreshing;
|
|
6015
|
+
const refresh = async (staleToken) => {
|
|
6016
|
+
refreshing ??= (async () => {
|
|
6017
|
+
const state = await store.load();
|
|
6018
|
+
if (state?.tokens?.access_token !== staleToken) return;
|
|
6019
|
+
if (!state?.tokens?.refresh_token) throw new McpOAuthAuthorizationRequiredError();
|
|
6020
|
+
const registered = state.clientInformation && "redirect_uris" in state.clientInformation ? state.clientInformation.redirect_uris : [];
|
|
6021
|
+
const redirectUrl = registered[0] ?? FALLBACK_REDIRECT_URL;
|
|
6022
|
+
const provider = new McpOAuthProvider({
|
|
6023
|
+
serverUrl,
|
|
6024
|
+
redirectUrl,
|
|
6025
|
+
clientMetadata: { client_name: oauth?.clientName ?? CLIENT_NAME },
|
|
6026
|
+
clientMetadataDocument: oauth?.clientId ? void 0 : cimdFn(redirectUrl),
|
|
6027
|
+
clientId: oauth?.clientId,
|
|
6028
|
+
clientSecret: oauth?.clientSecret,
|
|
6029
|
+
store,
|
|
6030
|
+
onRedirect() {
|
|
6031
|
+
}
|
|
6032
|
+
});
|
|
6033
|
+
const result = await authorizeMcp(provider, {
|
|
6034
|
+
serverUrl,
|
|
6035
|
+
authorizationServerMetadataUrl: oauth?.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : void 0,
|
|
6036
|
+
fetch: timedFetch(platform2.fetch)
|
|
6037
|
+
});
|
|
6038
|
+
if (result === "REDIRECT") throw new McpOAuthAuthorizationRequiredError();
|
|
6039
|
+
})().finally(() => {
|
|
6040
|
+
refreshing = void 0;
|
|
6041
|
+
});
|
|
6042
|
+
return refreshing;
|
|
6043
|
+
};
|
|
6044
|
+
return {
|
|
6045
|
+
async token() {
|
|
6046
|
+
await refreshing?.catch(() => void 0);
|
|
6047
|
+
const state = await store.load();
|
|
6048
|
+
const token = state?.tokens?.access_token;
|
|
6049
|
+
const expired = state?.tokensExpireAt !== void 0 && state.tokensExpireAt - REFRESH_SKEW_MS <= Date.now();
|
|
6050
|
+
if (!expired || !state?.tokens?.refresh_token) return token;
|
|
6051
|
+
await refresh(token).catch(() => void 0);
|
|
6052
|
+
return (await store.load())?.tokens?.access_token;
|
|
6053
|
+
},
|
|
6054
|
+
async onUnauthorized(context) {
|
|
6055
|
+
const challenge = parseWwwAuthenticate(context.response.headers.get("www-authenticate"));
|
|
6056
|
+
if (challenge.error === "insufficient_scope") {
|
|
6057
|
+
throw new AuthStepUpError(challenge.scope, options.name);
|
|
6058
|
+
}
|
|
6059
|
+
await refresh(context.token);
|
|
6060
|
+
}
|
|
6061
|
+
};
|
|
6062
|
+
}
|
|
5164
6063
|
|
|
5165
6064
|
// node_modules/@earendil-works/pi-mcp/dist/client.js
|
|
5166
6065
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -5858,21 +6757,21 @@ async function consumeSseStream(stream, options) {
|
|
|
5858
6757
|
if (line.startsWith(":"))
|
|
5859
6758
|
return;
|
|
5860
6759
|
const colon = line.indexOf(":");
|
|
5861
|
-
const
|
|
6760
|
+
const field2 = colon < 0 ? line : line.slice(0, colon);
|
|
5862
6761
|
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
5863
6762
|
if (value.startsWith(" "))
|
|
5864
6763
|
value = value.slice(1);
|
|
5865
|
-
if (
|
|
6764
|
+
if (field2 === "data") {
|
|
5866
6765
|
dataBytes += __mcpcat_byteLength(value) + (dataLines.length > 0 ? 1 : 0);
|
|
5867
6766
|
if (dataBytes > maxEventBytes)
|
|
5868
6767
|
throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
|
|
5869
6768
|
dataLines.push(value);
|
|
5870
|
-
} else if (
|
|
6769
|
+
} else if (field2 === "event")
|
|
5871
6770
|
eventName = value;
|
|
5872
|
-
else if (
|
|
6771
|
+
else if (field2 === "id" && !value.includes("\0")) {
|
|
5873
6772
|
eventId = value;
|
|
5874
6773
|
options.onId?.(value);
|
|
5875
|
-
} else if (
|
|
6774
|
+
} else if (field2 === "retry" && /^\d+$/.test(value))
|
|
5876
6775
|
options.onRetry?.(Number(value));
|
|
5877
6776
|
};
|
|
5878
6777
|
try {
|
|
@@ -5935,7 +6834,7 @@ function needsAuthorization(response) {
|
|
|
5935
6834
|
function isTransientStatus(status) {
|
|
5936
6835
|
return status === 408 || status === 429 || status >= 500;
|
|
5937
6836
|
}
|
|
5938
|
-
function
|
|
6837
|
+
function discard2(response) {
|
|
5939
6838
|
return response.body?.cancel().catch(() => {
|
|
5940
6839
|
}) ?? Promise.resolve();
|
|
5941
6840
|
}
|
|
@@ -5986,7 +6885,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
5986
6885
|
await this.checkResponse(response);
|
|
5987
6886
|
this.captureSession(response);
|
|
5988
6887
|
if (!isJsonRpcRequest(message)) {
|
|
5989
|
-
await
|
|
6888
|
+
await discard2(response);
|
|
5990
6889
|
if ("method" in message && message.method === "notifications/initialized")
|
|
5991
6890
|
this.startGetStream();
|
|
5992
6891
|
return;
|
|
@@ -6005,7 +6904,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6005
6904
|
void this.consumeResponseStream(response.body, message.id);
|
|
6006
6905
|
return;
|
|
6007
6906
|
}
|
|
6008
|
-
await
|
|
6907
|
+
await discard2(response);
|
|
6009
6908
|
throw new McpHttpError(response.status, `Unsupported MCP response content type: ${type ?? "missing"}`);
|
|
6010
6909
|
}
|
|
6011
6910
|
async close() {
|
|
@@ -6020,7 +6919,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6020
6919
|
headers: this.buildHeaders({}, this.lastToken),
|
|
6021
6920
|
signal: AbortSignal.timeout(1e3)
|
|
6022
6921
|
});
|
|
6023
|
-
await
|
|
6922
|
+
await discard2(response);
|
|
6024
6923
|
} catch {
|
|
6025
6924
|
}
|
|
6026
6925
|
}
|
|
@@ -6051,7 +6950,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6051
6950
|
try {
|
|
6052
6951
|
await onUnauthorized({ response, serverUrl: this.url, fetch: this.fetch, token });
|
|
6053
6952
|
} finally {
|
|
6054
|
-
await
|
|
6953
|
+
await discard2(response);
|
|
6055
6954
|
}
|
|
6056
6955
|
}
|
|
6057
6956
|
}
|
|
@@ -6208,14 +7107,14 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6208
7107
|
}
|
|
6209
7108
|
});
|
|
6210
7109
|
if (response.status === 405) {
|
|
6211
|
-
await
|
|
7110
|
+
await discard2(response);
|
|
6212
7111
|
return void 0;
|
|
6213
7112
|
}
|
|
6214
7113
|
await this.checkResponse(response);
|
|
6215
7114
|
this.captureSession(response);
|
|
6216
7115
|
const type = contentType(response);
|
|
6217
7116
|
if (type !== "text/event-stream" || !response.body) {
|
|
6218
|
-
await
|
|
7117
|
+
await discard2(response);
|
|
6219
7118
|
throw new McpHttpError(response.status, `Unsupported MCP GET response content type: ${type ?? "missing"}`);
|
|
6220
7119
|
}
|
|
6221
7120
|
return response.body;
|
|
@@ -6487,6 +7386,14 @@ var McpAuthError = class extends Error {
|
|
|
6487
7386
|
this.target = target;
|
|
6488
7387
|
}
|
|
6489
7388
|
};
|
|
7389
|
+
var AuthRetryableError = class extends Error {
|
|
7390
|
+
response;
|
|
7391
|
+
constructor(response) {
|
|
7392
|
+
super(`HTTP ${response.status}: auth retry`);
|
|
7393
|
+
this.name = "AuthRetryableError";
|
|
7394
|
+
this.response = response;
|
|
7395
|
+
}
|
|
7396
|
+
};
|
|
6490
7397
|
function selectSseFrame(body, targetId) {
|
|
6491
7398
|
const text = new TextDecoder().decode(body);
|
|
6492
7399
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -6511,6 +7418,42 @@ function mcpNameForMethod(method, params) {
|
|
|
6511
7418
|
if (method === "resources/read") return params.uri;
|
|
6512
7419
|
return void 0;
|
|
6513
7420
|
}
|
|
7421
|
+
function makeAbort(timeoutMs, externalSignal) {
|
|
7422
|
+
const controller = new AbortController();
|
|
7423
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7424
|
+
if (externalSignal) {
|
|
7425
|
+
if (externalSignal.aborted) {
|
|
7426
|
+
clearTimeout(timer);
|
|
7427
|
+
controller.abort();
|
|
7428
|
+
} else {
|
|
7429
|
+
externalSignal.addEventListener(
|
|
7430
|
+
"abort",
|
|
7431
|
+
() => {
|
|
7432
|
+
clearTimeout(timer);
|
|
7433
|
+
controller.abort();
|
|
7434
|
+
},
|
|
7435
|
+
{ once: true }
|
|
7436
|
+
);
|
|
7437
|
+
}
|
|
7438
|
+
}
|
|
7439
|
+
return { controller, timer };
|
|
7440
|
+
}
|
|
7441
|
+
function logHeaders(verbose, dir, entries) {
|
|
7442
|
+
for (const [k, v] of entries) {
|
|
7443
|
+
if (dir === ">") verbose.headerOut(k, v);
|
|
7444
|
+
else verbose.headerIn(k, v);
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
function buildServerInfo(discoverResult2) {
|
|
7448
|
+
return {
|
|
7449
|
+
name: discoverResult2?.serverInfo?.name,
|
|
7450
|
+
version: discoverResult2?.serverInfo?.version,
|
|
7451
|
+
title: discoverResult2?.serverInfo?.title,
|
|
7452
|
+
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
7453
|
+
capabilities: discoverResult2?.capabilities ?? {},
|
|
7454
|
+
instructions: discoverResult2?.instructions
|
|
7455
|
+
};
|
|
7456
|
+
}
|
|
6514
7457
|
async function createStatelessConnection(options) {
|
|
6515
7458
|
const {
|
|
6516
7459
|
url,
|
|
@@ -6519,11 +7462,12 @@ async function createStatelessConnection(options) {
|
|
|
6519
7462
|
timeoutMs = 6e4,
|
|
6520
7463
|
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
6521
7464
|
signal: externalSignal,
|
|
6522
|
-
verbose
|
|
7465
|
+
verbose,
|
|
7466
|
+
authProvider
|
|
6523
7467
|
} = options;
|
|
6524
7468
|
let nextId2 = 1;
|
|
6525
7469
|
let negotiatedVersion;
|
|
6526
|
-
function buildHeaders(method, params) {
|
|
7470
|
+
function buildHeaders(method, params, token) {
|
|
6527
7471
|
const h = {
|
|
6528
7472
|
"Content-Type": "application/json",
|
|
6529
7473
|
Accept: "application/json, text/event-stream",
|
|
@@ -6533,36 +7477,12 @@ async function createStatelessConnection(options) {
|
|
|
6533
7477
|
const n = mcpNameForMethod(method, params);
|
|
6534
7478
|
if (n) h["Mcp-Name"] = n;
|
|
6535
7479
|
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
7480
|
+
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
6536
7481
|
return h;
|
|
6537
7482
|
}
|
|
6538
|
-
function makeAbort() {
|
|
6539
|
-
const controller = new AbortController();
|
|
6540
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6541
|
-
if (externalSignal) {
|
|
6542
|
-
if (externalSignal.aborted) {
|
|
6543
|
-
clearTimeout(timer);
|
|
6544
|
-
controller.abort();
|
|
6545
|
-
} else {
|
|
6546
|
-
externalSignal.addEventListener(
|
|
6547
|
-
"abort",
|
|
6548
|
-
() => {
|
|
6549
|
-
clearTimeout(timer);
|
|
6550
|
-
controller.abort();
|
|
6551
|
-
},
|
|
6552
|
-
{ once: true }
|
|
6553
|
-
);
|
|
6554
|
-
}
|
|
6555
|
-
}
|
|
6556
|
-
return { controller, timer };
|
|
6557
|
-
}
|
|
6558
|
-
function logHeaders(dir, entries) {
|
|
6559
|
-
if (!verbose?.enabled) return;
|
|
6560
|
-
for (const [k, v] of entries) {
|
|
6561
|
-
if (dir === ">") verbose.headerOut(k, v);
|
|
6562
|
-
else verbose.headerIn(k, v);
|
|
6563
|
-
}
|
|
6564
|
-
}
|
|
6565
7483
|
async function handleErrorStatus(res, method) {
|
|
7484
|
+
if ((res.status === 401 || res.status === 403) && authProvider)
|
|
7485
|
+
throw new AuthRetryableError(res);
|
|
6566
7486
|
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
6567
7487
|
const text = await res.text();
|
|
6568
7488
|
let rpc;
|
|
@@ -6574,34 +7494,27 @@ async function createStatelessConnection(options) {
|
|
|
6574
7494
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
6575
7495
|
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
6576
7496
|
}
|
|
6577
|
-
async function
|
|
6578
|
-
const
|
|
6579
|
-
const headers = buildHeaders(method, params);
|
|
6580
|
-
const meta = {
|
|
6581
|
-
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
6582
|
-
clientInfo
|
|
6583
|
-
};
|
|
6584
|
-
const body = JSON.stringify({
|
|
6585
|
-
jsonrpc: "2.0",
|
|
6586
|
-
id,
|
|
6587
|
-
method,
|
|
6588
|
-
params: { ...params, _meta: meta }
|
|
6589
|
-
});
|
|
7497
|
+
async function doFetch(method, params, body) {
|
|
7498
|
+
const token = await authProvider?.token();
|
|
7499
|
+
const headers = buildHeaders(method, params, token);
|
|
6590
7500
|
if (verbose?.enabled) {
|
|
6591
7501
|
verbose.request(method, body);
|
|
6592
|
-
logHeaders(">", Object.entries(headers));
|
|
7502
|
+
logHeaders(verbose, ">", Object.entries(headers));
|
|
6593
7503
|
}
|
|
6594
|
-
const { controller, timer } = makeAbort();
|
|
6595
|
-
let res;
|
|
7504
|
+
const { controller, timer } = makeAbort(timeoutMs, externalSignal);
|
|
6596
7505
|
try {
|
|
6597
|
-
res = await fetchImpl(url, {
|
|
7506
|
+
const res = await fetchImpl(url, {
|
|
6598
7507
|
method: "POST",
|
|
6599
7508
|
headers,
|
|
6600
7509
|
body,
|
|
6601
7510
|
signal: controller.signal
|
|
6602
7511
|
});
|
|
7512
|
+
if (verbose?.enabled) {
|
|
7513
|
+
verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
|
|
7514
|
+
logHeaders(verbose, "<", res.headers.entries());
|
|
7515
|
+
}
|
|
7516
|
+
return { res, token };
|
|
6603
7517
|
} catch (err) {
|
|
6604
|
-
clearTimeout(timer);
|
|
6605
7518
|
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
6606
7519
|
throw new McpTimeoutError2(method, timeoutMs);
|
|
6607
7520
|
}
|
|
@@ -6609,11 +7522,8 @@ async function createStatelessConnection(options) {
|
|
|
6609
7522
|
} finally {
|
|
6610
7523
|
clearTimeout(timer);
|
|
6611
7524
|
}
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
logHeaders("<", res.headers.entries());
|
|
6615
|
-
}
|
|
6616
|
-
if (res.status >= 400) await handleErrorStatus(res, method);
|
|
7525
|
+
}
|
|
7526
|
+
async function parseResponse(res, id) {
|
|
6617
7527
|
const ct = res.headers.get("content-type") ?? "";
|
|
6618
7528
|
let frame;
|
|
6619
7529
|
if (ct.toLowerCase().includes("text/event-stream")) {
|
|
@@ -6627,10 +7537,53 @@ async function createStatelessConnection(options) {
|
|
|
6627
7537
|
}
|
|
6628
7538
|
return frame.result;
|
|
6629
7539
|
}
|
|
7540
|
+
async function handleAuthError(err, token) {
|
|
7541
|
+
if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url);
|
|
7542
|
+
try {
|
|
7543
|
+
await authProvider.onUnauthorized({
|
|
7544
|
+
response: err.response,
|
|
7545
|
+
serverUrl: new URL(url),
|
|
7546
|
+
fetch: fetchImpl,
|
|
7547
|
+
token
|
|
7548
|
+
});
|
|
7549
|
+
} catch (authErr) {
|
|
7550
|
+
if (authErr instanceof AuthStepUpError) throw authErr;
|
|
7551
|
+
throw new McpAuthError(err.response.status, url);
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
async function request(method, params) {
|
|
7555
|
+
const id = nextId2++;
|
|
7556
|
+
const meta = {
|
|
7557
|
+
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
7558
|
+
clientInfo
|
|
7559
|
+
};
|
|
7560
|
+
const body = JSON.stringify({
|
|
7561
|
+
jsonrpc: "2.0",
|
|
7562
|
+
id,
|
|
7563
|
+
method,
|
|
7564
|
+
params: { ...params, _meta: meta }
|
|
7565
|
+
});
|
|
7566
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7567
|
+
const { res, token } = await doFetch(method, params, body);
|
|
7568
|
+
if (res.status >= 400) {
|
|
7569
|
+
try {
|
|
7570
|
+
await handleErrorStatus(res, method);
|
|
7571
|
+
} catch (err) {
|
|
7572
|
+
if (err instanceof AuthRetryableError && attempt === 0) {
|
|
7573
|
+
await handleAuthError(err, token);
|
|
7574
|
+
continue;
|
|
7575
|
+
}
|
|
7576
|
+
throw err;
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
return parseResponse(res, id);
|
|
7580
|
+
}
|
|
7581
|
+
throw new McpAuthError(401, url);
|
|
7582
|
+
}
|
|
6630
7583
|
const discoverResult2 = await request("server/discover", {
|
|
6631
7584
|
protocolVersion: STATELESS_PROTOCOL_VERSION
|
|
6632
7585
|
});
|
|
6633
|
-
const supported =
|
|
7586
|
+
const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
|
|
6634
7587
|
if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
|
|
6635
7588
|
throw new Error(
|
|
6636
7589
|
`Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
@@ -6641,15 +7594,7 @@ async function createStatelessConnection(options) {
|
|
|
6641
7594
|
return {
|
|
6642
7595
|
async info() {
|
|
6643
7596
|
if (serverInfoCache) return serverInfoCache;
|
|
6644
|
-
|
|
6645
|
-
serverInfoCache = {
|
|
6646
|
-
name: r?.serverInfo?.name,
|
|
6647
|
-
version: r?.serverInfo?.version,
|
|
6648
|
-
title: r?.serverInfo?.title,
|
|
6649
|
-
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
6650
|
-
capabilities: r?.capabilities ?? {},
|
|
6651
|
-
instructions: r?.instructions
|
|
6652
|
-
};
|
|
7597
|
+
serverInfoCache = buildServerInfo(discoverResult2);
|
|
6653
7598
|
return serverInfoCache;
|
|
6654
7599
|
},
|
|
6655
7600
|
async listTools() {
|
|
@@ -6686,7 +7631,8 @@ function buildTransport(options) {
|
|
|
6686
7631
|
return new StreamableHttpTransport({
|
|
6687
7632
|
url: options.url,
|
|
6688
7633
|
headers: options.headers,
|
|
6689
|
-
fetch: options.fetchImpl
|
|
7634
|
+
fetch: options.fetchImpl,
|
|
7635
|
+
authProvider: options.authProvider
|
|
6690
7636
|
});
|
|
6691
7637
|
}
|
|
6692
7638
|
if (!options.command) throw new Error("PiConnection requires either a url or a command");
|
|
@@ -6803,6 +7749,7 @@ async function connect(platform2, options) {
|
|
|
6803
7749
|
url: options.url,
|
|
6804
7750
|
headers: options.headers,
|
|
6805
7751
|
fetchImpl: platform2.fetch,
|
|
7752
|
+
authProvider: options.authProvider,
|
|
6806
7753
|
...shared
|
|
6807
7754
|
});
|
|
6808
7755
|
}
|
|
@@ -6814,16 +7761,20 @@ async function connect(platform2, options) {
|
|
|
6814
7761
|
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
|
|
6815
7762
|
timeoutMs: options.timeoutMs,
|
|
6816
7763
|
signal: options.signal,
|
|
6817
|
-
verbose: options.verbose
|
|
7764
|
+
verbose: options.verbose,
|
|
7765
|
+
authProvider: options.authProvider
|
|
6818
7766
|
});
|
|
6819
7767
|
} catch (err) {
|
|
6820
7768
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
6821
7769
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
7770
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
7771
|
+
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
6822
7772
|
if (isMethodNotFound) {
|
|
6823
7773
|
return createPiConnection({
|
|
6824
7774
|
url: options.url,
|
|
6825
7775
|
headers: options.headers,
|
|
6826
7776
|
fetchImpl: platform2.fetch,
|
|
7777
|
+
authProvider: options.authProvider,
|
|
6827
7778
|
...shared
|
|
6828
7779
|
});
|
|
6829
7780
|
}
|
|
@@ -7298,8 +8249,8 @@ async function formatOutput(platform2, result, json) {
|
|
|
7298
8249
|
return result.ok ? 0 : isTimeout ? 124 : 1;
|
|
7299
8250
|
}
|
|
7300
8251
|
async function createSandbox(platform2, tools, globals, timeoutMs) {
|
|
7301
|
-
const
|
|
7302
|
-
if (
|
|
8252
|
+
const isKernel2 = platform2.packageDir !== void 0;
|
|
8253
|
+
if (isKernel2) {
|
|
7303
8254
|
const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
|
|
7304
8255
|
const wasm = await loadWasmKernel(platform2);
|
|
7305
8256
|
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
|
|
@@ -7424,8 +8375,8 @@ function resolveAliases(value) {
|
|
|
7424
8375
|
}
|
|
7425
8376
|
return out;
|
|
7426
8377
|
}
|
|
7427
|
-
function fail(server, message,
|
|
7428
|
-
return { server, ...
|
|
8378
|
+
function fail(server, message, field2) {
|
|
8379
|
+
return { server, ...field2 ? { field: field2 } : {}, message };
|
|
7429
8380
|
}
|
|
7430
8381
|
function validateCommon(name, value) {
|
|
7431
8382
|
const exposures = EXPOSURES.map((e) => `"${e}"`).join(", ");
|
|
@@ -8846,7 +9797,7 @@ function argvForCommand(command, args) {
|
|
|
8846
9797
|
if (args.error) return { error: args.error };
|
|
8847
9798
|
const argv = [...command.path];
|
|
8848
9799
|
for (const positional of command.positionals) {
|
|
8849
|
-
const found = args.fields.find((
|
|
9800
|
+
const found = args.fields.find((field2) => field2.name === positional.name);
|
|
8850
9801
|
if (!found) {
|
|
8851
9802
|
if (positional.required) return { error: `missing ${positional.name}` };
|
|
8852
9803
|
continue;
|
|
@@ -8865,7 +9816,7 @@ function argvForCommand(command, args) {
|
|
|
8865
9816
|
function appendFlags(flags, fields) {
|
|
8866
9817
|
const argv = [];
|
|
8867
9818
|
for (const flag of flags) {
|
|
8868
|
-
const found = fields.find((
|
|
9819
|
+
const found = fields.find((field2) => field2.name === flag.name);
|
|
8869
9820
|
if (!found || found.value === false) continue;
|
|
8870
9821
|
const encoded = encodeFlag(flag, found.value);
|
|
8871
9822
|
if ("error" in encoded) return encoded;
|
|
@@ -10574,6 +11525,478 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
|
|
|
10574
11525
|
};
|
|
10575
11526
|
}
|
|
10576
11527
|
|
|
11528
|
+
// src/auth/oauth.ts
|
|
11529
|
+
var CALLBACK_HOST2 = "127.0.0.1";
|
|
11530
|
+
var CALLBACK_PATH2 = "/callback";
|
|
11531
|
+
var CIMD_URL2 = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
11532
|
+
var CLIENT_NAME2 = "mcpcat";
|
|
11533
|
+
var REQUEST_TIMEOUT_MS2 = 15e3;
|
|
11534
|
+
function timedFetch2(fetchImpl) {
|
|
11535
|
+
return (input, init) => {
|
|
11536
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS2);
|
|
11537
|
+
return fetchImpl(input, {
|
|
11538
|
+
...init,
|
|
11539
|
+
signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
|
|
11540
|
+
});
|
|
11541
|
+
};
|
|
11542
|
+
}
|
|
11543
|
+
function callbackSettings(oauth) {
|
|
11544
|
+
const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
|
|
11545
|
+
const url = new URL(raw);
|
|
11546
|
+
const address = url.hostname.replace(/^\[|\]$/g, "");
|
|
11547
|
+
const port = url.port ? Number(url.port) : oauth?.callbackPort;
|
|
11548
|
+
let fixedRedirectUrl;
|
|
11549
|
+
if (url.port) fixedRedirectUrl = oauth?.callbackUrl;
|
|
11550
|
+
else if (port !== void 0) {
|
|
11551
|
+
url.port = String(port);
|
|
11552
|
+
fixedRedirectUrl = url.href;
|
|
11553
|
+
}
|
|
11554
|
+
return {
|
|
11555
|
+
host: address === "localhost" ? CALLBACK_HOST2 : address,
|
|
11556
|
+
redirectHost: address,
|
|
11557
|
+
port,
|
|
11558
|
+
path: url.pathname,
|
|
11559
|
+
fixedRedirectUrl
|
|
11560
|
+
};
|
|
11561
|
+
}
|
|
11562
|
+
function cimdForServer(metadata, redirectUrl) {
|
|
11563
|
+
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
11564
|
+
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
11565
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11566
|
+
return {
|
|
11567
|
+
url: CIMD_URL2,
|
|
11568
|
+
redirectUrl
|
|
11569
|
+
};
|
|
11570
|
+
}
|
|
11571
|
+
function buildClientMetadataDocumentFn(oauth, redirectUrl) {
|
|
11572
|
+
if (oauth?.clientRegistration === "dcr") return void 0;
|
|
11573
|
+
if (oauth?.clientId) return void 0;
|
|
11574
|
+
return (metadata) => {
|
|
11575
|
+
if (oauth?.clientRegistration === "cimd") {
|
|
11576
|
+
if (!metadata?.client_id_metadata_document_supported || !metadata.token_endpoint_auth_methods_supported?.includes("none")) {
|
|
11577
|
+
return void 0;
|
|
11578
|
+
}
|
|
11579
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11580
|
+
return { url: CIMD_URL2, redirectUrl };
|
|
11581
|
+
}
|
|
11582
|
+
return cimdForServer(metadata, redirectUrl);
|
|
11583
|
+
};
|
|
11584
|
+
}
|
|
11585
|
+
function createProvider(serverUrl, store, oauth, redirectUrl, onRedirect) {
|
|
11586
|
+
const cimdFn2 = buildClientMetadataDocumentFn(oauth, redirectUrl);
|
|
11587
|
+
return new McpOAuthProvider({
|
|
11588
|
+
serverUrl,
|
|
11589
|
+
redirectUrl,
|
|
11590
|
+
clientMetadata: { client_name: oauth?.clientName ?? CLIENT_NAME2 },
|
|
11591
|
+
clientMetadataDocument: cimdFn2,
|
|
11592
|
+
clientId: oauth?.clientId,
|
|
11593
|
+
clientSecret: oauth?.clientSecret,
|
|
11594
|
+
store,
|
|
11595
|
+
onRedirect
|
|
11596
|
+
});
|
|
11597
|
+
}
|
|
11598
|
+
var SourceBackedStateStore = class {
|
|
11599
|
+
source;
|
|
11600
|
+
name;
|
|
11601
|
+
serverUrl;
|
|
11602
|
+
constructor(source, name, serverUrl) {
|
|
11603
|
+
this.source = source;
|
|
11604
|
+
this.name = name;
|
|
11605
|
+
this.serverUrl = serverUrl;
|
|
11606
|
+
}
|
|
11607
|
+
async load() {
|
|
11608
|
+
return this.source.load(this.name, this.serverUrl);
|
|
11609
|
+
}
|
|
11610
|
+
async save(state) {
|
|
11611
|
+
await this.source.save(this.name, this.serverUrl, state);
|
|
11612
|
+
}
|
|
11613
|
+
};
|
|
11614
|
+
var SignInCancelledError = class extends Error {
|
|
11615
|
+
constructor() {
|
|
11616
|
+
super("Sign-in cancelled");
|
|
11617
|
+
this.name = "SignInCancelledError";
|
|
11618
|
+
}
|
|
11619
|
+
};
|
|
11620
|
+
function responseFromRedirectUrl(input, state, redirectUrl) {
|
|
11621
|
+
let url;
|
|
11622
|
+
try {
|
|
11623
|
+
url = new URL(input.trim());
|
|
11624
|
+
} catch {
|
|
11625
|
+
throw new Error("Expected the full redirect URL from the browser address bar");
|
|
11626
|
+
}
|
|
11627
|
+
if (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {
|
|
11628
|
+
throw new Error("The redirect URL does not match this sign-in's redirect URI");
|
|
11629
|
+
}
|
|
11630
|
+
const error = url.searchParams.get("error");
|
|
11631
|
+
if (error) throw new Error(url.searchParams.get("error_description") ?? error);
|
|
11632
|
+
if (url.searchParams.get("state") !== state) {
|
|
11633
|
+
throw new Error("The redirect URL belongs to a different sign-in");
|
|
11634
|
+
}
|
|
11635
|
+
const code = url.searchParams.get("code");
|
|
11636
|
+
if (!code) throw new Error("The redirect URL does not contain an authorization code");
|
|
11637
|
+
return { code, iss: url.searchParams.get("iss") ?? void 0 };
|
|
11638
|
+
}
|
|
11639
|
+
async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt, signal) {
|
|
11640
|
+
const controller = new AbortController();
|
|
11641
|
+
const promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
11642
|
+
const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
|
|
11643
|
+
const fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {
|
|
11644
|
+
if (!input?.trim()) throw new SignInCancelledError();
|
|
11645
|
+
return responseFromRedirectUrl(input, state, redirectUrl);
|
|
11646
|
+
});
|
|
11647
|
+
try {
|
|
11648
|
+
return await Promise.race([fromBrowser, fromUser]);
|
|
11649
|
+
} finally {
|
|
11650
|
+
controller.abort();
|
|
11651
|
+
fromBrowser.catch(() => void 0);
|
|
11652
|
+
fromUser.catch(() => void 0);
|
|
11653
|
+
}
|
|
11654
|
+
}
|
|
11655
|
+
function mergeScopes(...scopes) {
|
|
11656
|
+
const merged = [...new Set(scopes.flatMap((s) => s?.split(/\s+/).filter(Boolean) ?? []))];
|
|
11657
|
+
return merged.length > 0 ? merged.join(" ") : void 0;
|
|
11658
|
+
}
|
|
11659
|
+
async function signIn(options) {
|
|
11660
|
+
const { name, serverUrl, source, oauth, prompt, platform: platform2, signal } = options;
|
|
11661
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
11662
|
+
const store = new SourceBackedStateStore(source, name, serverUrl);
|
|
11663
|
+
const stored = await store.load();
|
|
11664
|
+
const cbSettings = callbackSettings(oauth);
|
|
11665
|
+
const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
|
|
11666
|
+
const preferredPort = cbSettings.port ?? (registered[0] ? Number(new URL(registered[0]).port) || void 0 : void 0);
|
|
11667
|
+
const callback = await OAuthCallbackServer.listen({
|
|
11668
|
+
host: cbSettings.host,
|
|
11669
|
+
redirectHost: cbSettings.redirectHost,
|
|
11670
|
+
port: preferredPort ?? 0,
|
|
11671
|
+
path: cbSettings.path
|
|
11672
|
+
});
|
|
11673
|
+
const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
|
|
11674
|
+
try {
|
|
11675
|
+
if (stored) {
|
|
11676
|
+
const next = { ...stored };
|
|
11677
|
+
delete next.oauthState;
|
|
11678
|
+
const hasCimd = buildClientMetadataDocumentFn(oauth, redirectUrl) !== void 0;
|
|
11679
|
+
const keepClient = oauth?.clientId || (hasCimd ? !stored.clientInformation : registered.includes(redirectUrl));
|
|
11680
|
+
if (!keepClient) {
|
|
11681
|
+
delete next.clientInformation;
|
|
11682
|
+
delete next.tokens;
|
|
11683
|
+
delete next.tokensExpireAt;
|
|
11684
|
+
}
|
|
11685
|
+
await store.save(next);
|
|
11686
|
+
}
|
|
11687
|
+
let authorizationUrl;
|
|
11688
|
+
const provider = createProvider(serverUrl, store, oauth, redirectUrl, (url) => {
|
|
11689
|
+
authorizationUrl = url;
|
|
11690
|
+
});
|
|
11691
|
+
const flow = {
|
|
11692
|
+
serverUrl,
|
|
11693
|
+
fetch: timedFetch2(platform2.fetch),
|
|
11694
|
+
signal,
|
|
11695
|
+
authorizationServerMetadataUrl: oauth?.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : void 0,
|
|
11696
|
+
scope: mergeScopes(
|
|
11697
|
+
oauth?.scope,
|
|
11698
|
+
options.stepUp ? stepUpScope(stored?.tokens?.scope, options.scope) : options.scope
|
|
11699
|
+
)
|
|
11700
|
+
};
|
|
11701
|
+
const skipRefresh = options.stepUp === true;
|
|
11702
|
+
if (await authorizeMcp(provider, { ...flow, skipRefresh }) === "AUTHORIZED") return;
|
|
11703
|
+
if (!authorizationUrl) throw new Error("OAuth flow did not produce an authorization URL");
|
|
11704
|
+
const state = await provider.state();
|
|
11705
|
+
const authRedirectUrl = new URL(
|
|
11706
|
+
authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl
|
|
11707
|
+
);
|
|
11708
|
+
prompt.showAuthorizationUrl(authorizationUrl);
|
|
11709
|
+
const { code, iss } = await waitForAuthorizationResponse(
|
|
11710
|
+
callback,
|
|
11711
|
+
state,
|
|
11712
|
+
authRedirectUrl,
|
|
11713
|
+
prompt,
|
|
11714
|
+
signal
|
|
11715
|
+
);
|
|
11716
|
+
await authorizeMcp(provider, { ...flow, authorizationCode: code, iss });
|
|
11717
|
+
} catch (err) {
|
|
11718
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
11719
|
+
throw err;
|
|
11720
|
+
} finally {
|
|
11721
|
+
await callback.close();
|
|
11722
|
+
}
|
|
11723
|
+
}
|
|
11724
|
+
|
|
11725
|
+
// src/auth/token-source.ts
|
|
11726
|
+
function mcpNamespace(server) {
|
|
11727
|
+
return `mcp__${server.replace(/-/g, "_")}`;
|
|
11728
|
+
}
|
|
11729
|
+
function storeKey(name, serverUrl) {
|
|
11730
|
+
const ns = mcpNamespace(name);
|
|
11731
|
+
const url = String(new URL(serverUrl));
|
|
11732
|
+
return `${ns}|${url}`;
|
|
11733
|
+
}
|
|
11734
|
+
function isKernel(platform2) {
|
|
11735
|
+
return platform2.kind === "kernel";
|
|
11736
|
+
}
|
|
11737
|
+
|
|
11738
|
+
// src/auth/pi-store.ts
|
|
11739
|
+
var AUTH_FILE = "mcp-auth.json";
|
|
11740
|
+
var FILE_MODE = 384;
|
|
11741
|
+
function parseStates(raw) {
|
|
11742
|
+
if (!raw?.trim()) return {};
|
|
11743
|
+
const parsed = JSON.parse(raw);
|
|
11744
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
11745
|
+
}
|
|
11746
|
+
function serializeStates(states) {
|
|
11747
|
+
return `${JSON.stringify(states, null, 2)}
|
|
11748
|
+
`;
|
|
11749
|
+
}
|
|
11750
|
+
function legacyKey(serverUrl) {
|
|
11751
|
+
return String(new URL(serverUrl));
|
|
11752
|
+
}
|
|
11753
|
+
var PiFileTokenSource = class {
|
|
11754
|
+
platform;
|
|
11755
|
+
authPath;
|
|
11756
|
+
dirPath;
|
|
11757
|
+
constructor(platform2) {
|
|
11758
|
+
this.platform = platform2;
|
|
11759
|
+
this.dirPath = userConfigDir(platform2);
|
|
11760
|
+
this.authPath = `${this.dirPath}/${AUTH_FILE}`;
|
|
11761
|
+
}
|
|
11762
|
+
get path() {
|
|
11763
|
+
return this.authPath;
|
|
11764
|
+
}
|
|
11765
|
+
async getToken(name, serverUrl) {
|
|
11766
|
+
const state = await this.load(name, serverUrl);
|
|
11767
|
+
if (!state?.tokens?.access_token) return void 0;
|
|
11768
|
+
return {
|
|
11769
|
+
accessToken: state.tokens.access_token,
|
|
11770
|
+
expiresAt: state.tokensExpireAt
|
|
11771
|
+
};
|
|
11772
|
+
}
|
|
11773
|
+
async load(name, serverUrl) {
|
|
11774
|
+
const key = storeKey(name, serverUrl);
|
|
11775
|
+
const legacy = legacyKey(serverUrl);
|
|
11776
|
+
const release = await this.acquireLock();
|
|
11777
|
+
try {
|
|
11778
|
+
const raw = await this.readRaw();
|
|
11779
|
+
const states = parseStates(raw);
|
|
11780
|
+
if (states[key]) return states[key];
|
|
11781
|
+
if (states[legacy]) {
|
|
11782
|
+
states[key] = states[legacy];
|
|
11783
|
+
delete states[legacy];
|
|
11784
|
+
await this.writeStates(states);
|
|
11785
|
+
return states[key];
|
|
11786
|
+
}
|
|
11787
|
+
return void 0;
|
|
11788
|
+
} finally {
|
|
11789
|
+
await release();
|
|
11790
|
+
}
|
|
11791
|
+
}
|
|
11792
|
+
async save(name, serverUrl, state) {
|
|
11793
|
+
const key = storeKey(name, serverUrl);
|
|
11794
|
+
const release = await this.acquireLock();
|
|
11795
|
+
try {
|
|
11796
|
+
const raw = await this.readRaw();
|
|
11797
|
+
const states = parseStates(raw);
|
|
11798
|
+
states[key] = state;
|
|
11799
|
+
await this.writeStates(states);
|
|
11800
|
+
} finally {
|
|
11801
|
+
await release();
|
|
11802
|
+
}
|
|
11803
|
+
}
|
|
11804
|
+
async remove(name, serverUrl) {
|
|
11805
|
+
const key = storeKey(name, serverUrl);
|
|
11806
|
+
const legacy = legacyKey(serverUrl);
|
|
11807
|
+
const release = await this.acquireLock();
|
|
11808
|
+
try {
|
|
11809
|
+
const raw = await this.readRaw();
|
|
11810
|
+
const states = parseStates(raw);
|
|
11811
|
+
const stored = key in states ? key : legacy in states ? legacy : void 0;
|
|
11812
|
+
if (!stored) return false;
|
|
11813
|
+
delete states[stored];
|
|
11814
|
+
await this.writeStates(states);
|
|
11815
|
+
return true;
|
|
11816
|
+
} finally {
|
|
11817
|
+
await release();
|
|
11818
|
+
}
|
|
11819
|
+
}
|
|
11820
|
+
async acquireLock() {
|
|
11821
|
+
await this.ensureDir();
|
|
11822
|
+
return acquireLock(this.platform.fs, this.authPath);
|
|
11823
|
+
}
|
|
11824
|
+
async ensureDir() {
|
|
11825
|
+
try {
|
|
11826
|
+
await this.platform.fs.mkdir(this.dirPath);
|
|
11827
|
+
} catch {
|
|
11828
|
+
}
|
|
11829
|
+
}
|
|
11830
|
+
async readRaw() {
|
|
11831
|
+
try {
|
|
11832
|
+
const data = await this.platform.fs.readFile(this.authPath);
|
|
11833
|
+
return new TextDecoder().decode(data);
|
|
11834
|
+
} catch {
|
|
11835
|
+
return void 0;
|
|
11836
|
+
}
|
|
11837
|
+
}
|
|
11838
|
+
async writeStates(states) {
|
|
11839
|
+
await this.platform.fs.writeFile(this.authPath, serializeStates(states), { mode: FILE_MODE });
|
|
11840
|
+
}
|
|
11841
|
+
};
|
|
11842
|
+
|
|
11843
|
+
// src/cli/auth-verbs.ts
|
|
11844
|
+
function createTokenSource(platform2) {
|
|
11845
|
+
return new PiFileTokenSource(platform2);
|
|
11846
|
+
}
|
|
11847
|
+
function resolveServerName(target) {
|
|
11848
|
+
try {
|
|
11849
|
+
const u = new URL(target.startsWith("http") ? target : `https://${target}`);
|
|
11850
|
+
if (target.includes(".") || target.includes(":")) {
|
|
11851
|
+
return { name: `url:${u.origin}${u.pathname}`, isUrl: true };
|
|
11852
|
+
}
|
|
11853
|
+
} catch {
|
|
11854
|
+
}
|
|
11855
|
+
return { name: target, isUrl: false };
|
|
11856
|
+
}
|
|
11857
|
+
async function resolveLoginTarget(platform2, target) {
|
|
11858
|
+
const config = await MergedConfigSource.load(platform2);
|
|
11859
|
+
const entry = config.entry(target);
|
|
11860
|
+
if (entry && "url" in entry.config && entry.config.url) {
|
|
11861
|
+
const httpConfig = entry.config;
|
|
11862
|
+
return {
|
|
11863
|
+
name: target,
|
|
11864
|
+
serverUrl: httpConfig.url,
|
|
11865
|
+
oauth: httpConfig.oauth
|
|
11866
|
+
};
|
|
11867
|
+
}
|
|
11868
|
+
const resolved = resolveServerName(target);
|
|
11869
|
+
if (resolved.isUrl) {
|
|
11870
|
+
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
11871
|
+
return { name: resolved.name, serverUrl: url, oauth: void 0 };
|
|
11872
|
+
}
|
|
11873
|
+
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
11874
|
+
}
|
|
11875
|
+
async function runLogin(platform2, args, source) {
|
|
11876
|
+
if (isKernel(platform2)) {
|
|
11877
|
+
await platform2.write(
|
|
11878
|
+
2,
|
|
11879
|
+
"mcpcat: login is not available in seven. Use the agent's MCP sign-in in Settings, or run mcpcat login on the host.\n"
|
|
11880
|
+
);
|
|
11881
|
+
return 4;
|
|
11882
|
+
}
|
|
11883
|
+
const noBrowser = args.includes("--no-browser");
|
|
11884
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
11885
|
+
if (positional.length === 0) {
|
|
11886
|
+
await platform2.write(2, "mcpcat: login requires a target\n");
|
|
11887
|
+
return 2;
|
|
11888
|
+
}
|
|
11889
|
+
const target = positional[0];
|
|
11890
|
+
let resolved;
|
|
11891
|
+
try {
|
|
11892
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
11893
|
+
} catch (err) {
|
|
11894
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
11895
|
+
`);
|
|
11896
|
+
return 2;
|
|
11897
|
+
}
|
|
11898
|
+
try {
|
|
11899
|
+
await signIn({
|
|
11900
|
+
name: resolved.name,
|
|
11901
|
+
serverUrl: resolved.serverUrl,
|
|
11902
|
+
source,
|
|
11903
|
+
oauth: resolved.oauth,
|
|
11904
|
+
platform: platform2,
|
|
11905
|
+
prompt: {
|
|
11906
|
+
showAuthorizationUrl(url) {
|
|
11907
|
+
if (!noBrowser && platform2.openUrl) {
|
|
11908
|
+
platform2.openUrl(url.href).catch(() => {
|
|
11909
|
+
});
|
|
11910
|
+
}
|
|
11911
|
+
void platform2.write(2, `Open this URL to sign in:
|
|
11912
|
+
${url.href}
|
|
11913
|
+
`);
|
|
11914
|
+
if (noBrowser || !platform2.openUrl) {
|
|
11915
|
+
void platform2.write(2, "\nPaste the redirect URL from your browser address bar:\n");
|
|
11916
|
+
}
|
|
11917
|
+
},
|
|
11918
|
+
async promptForRedirectUrl(signal) {
|
|
11919
|
+
if (!noBrowser && platform2.openUrl) {
|
|
11920
|
+
return new Promise((_resolve, reject) => {
|
|
11921
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
11922
|
+
});
|
|
11923
|
+
}
|
|
11924
|
+
const reader = platform2.stdin.getReader();
|
|
11925
|
+
const chunks = [];
|
|
11926
|
+
try {
|
|
11927
|
+
while (true) {
|
|
11928
|
+
const read = reader.read();
|
|
11929
|
+
const result = await Promise.race([
|
|
11930
|
+
read,
|
|
11931
|
+
new Promise((_, reject) => {
|
|
11932
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
11933
|
+
})
|
|
11934
|
+
]);
|
|
11935
|
+
if (result.done) break;
|
|
11936
|
+
chunks.push(result.value);
|
|
11937
|
+
const text = new TextDecoder().decode(concatUint8Arrays(chunks));
|
|
11938
|
+
if (text.includes("\n")) return text.trim();
|
|
11939
|
+
}
|
|
11940
|
+
} finally {
|
|
11941
|
+
reader.releaseLock();
|
|
11942
|
+
}
|
|
11943
|
+
return new TextDecoder().decode(concatUint8Arrays(chunks)).trim() || void 0;
|
|
11944
|
+
}
|
|
11945
|
+
}
|
|
11946
|
+
});
|
|
11947
|
+
await platform2.write(1, `Signed in to ${target}
|
|
11948
|
+
`);
|
|
11949
|
+
return 0;
|
|
11950
|
+
} catch (err) {
|
|
11951
|
+
if (err instanceof SignInCancelledError) {
|
|
11952
|
+
await platform2.write(2, "mcpcat: sign-in cancelled\n");
|
|
11953
|
+
return 1;
|
|
11954
|
+
}
|
|
11955
|
+
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
11956
|
+
`);
|
|
11957
|
+
return 4;
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
async function runLogout(platform2, args, source) {
|
|
11961
|
+
if (isKernel(platform2)) {
|
|
11962
|
+
await platform2.write(2, "mcpcat: logout is not available in seven.\n");
|
|
11963
|
+
return 4;
|
|
11964
|
+
}
|
|
11965
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
11966
|
+
if (positional.length === 0) {
|
|
11967
|
+
await platform2.write(2, "mcpcat: logout requires a target\n");
|
|
11968
|
+
return 2;
|
|
11969
|
+
}
|
|
11970
|
+
const target = positional[0];
|
|
11971
|
+
let resolved;
|
|
11972
|
+
try {
|
|
11973
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
11974
|
+
} catch (err) {
|
|
11975
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
11976
|
+
`);
|
|
11977
|
+
return 2;
|
|
11978
|
+
}
|
|
11979
|
+
const removed = await source.remove(resolved.name, resolved.serverUrl);
|
|
11980
|
+
if (removed) {
|
|
11981
|
+
await platform2.write(1, `Signed out of ${target}
|
|
11982
|
+
`);
|
|
11983
|
+
} else {
|
|
11984
|
+
await platform2.write(1, `No credentials stored for ${target}
|
|
11985
|
+
`);
|
|
11986
|
+
}
|
|
11987
|
+
return 0;
|
|
11988
|
+
}
|
|
11989
|
+
function concatUint8Arrays(arrays) {
|
|
11990
|
+
const total = arrays.reduce((s, a) => s + a.length, 0);
|
|
11991
|
+
const result = new Uint8Array(total);
|
|
11992
|
+
let offset = 0;
|
|
11993
|
+
for (const a of arrays) {
|
|
11994
|
+
result.set(a, offset);
|
|
11995
|
+
offset += a.length;
|
|
11996
|
+
}
|
|
11997
|
+
return result;
|
|
11998
|
+
}
|
|
11999
|
+
|
|
10577
12000
|
// src/config/import.ts
|
|
10578
12001
|
function isRecord5(value) {
|
|
10579
12002
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -11517,16 +12940,7 @@ var CONFIG_VERBS = {
|
|
|
11517
12940
|
exposure: true,
|
|
11518
12941
|
import: true
|
|
11519
12942
|
};
|
|
11520
|
-
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set([
|
|
11521
|
-
"resources",
|
|
11522
|
-
"read",
|
|
11523
|
-
"prompts",
|
|
11524
|
-
"prompt",
|
|
11525
|
-
"complete",
|
|
11526
|
-
"task",
|
|
11527
|
-
"login",
|
|
11528
|
-
"logout"
|
|
11529
|
-
]);
|
|
12943
|
+
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["resources", "read", "prompts", "prompt", "complete", "task"]);
|
|
11530
12944
|
var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe", "serve"]);
|
|
11531
12945
|
function helpText(version) {
|
|
11532
12946
|
return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
|
|
@@ -11550,8 +12964,8 @@ Configuration verbs:
|
|
|
11550
12964
|
enable|disable <name> Toggle a server
|
|
11551
12965
|
exposure <name> <mode> Set exposure mode
|
|
11552
12966
|
import <file> Import from other configs
|
|
11553
|
-
login <target> OAuth sign-in
|
|
11554
|
-
logout <target> Remove tokens
|
|
12967
|
+
login <target> OAuth sign-in
|
|
12968
|
+
logout <target> Remove stored tokens
|
|
11555
12969
|
|
|
11556
12970
|
Server and code verbs:
|
|
11557
12971
|
serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
|
|
@@ -12142,6 +13556,14 @@ async function run(platform2, version, args) {
|
|
|
12142
13556
|
}
|
|
12143
13557
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args);
|
|
12144
13558
|
if (discResult !== void 0) return discResult;
|
|
13559
|
+
if (parsed.verb === "login") {
|
|
13560
|
+
const source = createTokenSource(platform2);
|
|
13561
|
+
return runLogin(platform2, args.slice(1), source);
|
|
13562
|
+
}
|
|
13563
|
+
if (parsed.verb === "logout") {
|
|
13564
|
+
const source = createTokenSource(platform2);
|
|
13565
|
+
return runLogout(platform2, args.slice(1), source);
|
|
13566
|
+
}
|
|
12145
13567
|
if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
|
|
12146
13568
|
await platform2.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
|
|
12147
13569
|
`);
|
|
@@ -12190,6 +13612,17 @@ function classifyError(err) {
|
|
|
12190
13612
|
if (err instanceof McpAuthError) {
|
|
12191
13613
|
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
12192
13614
|
}
|
|
13615
|
+
if (err instanceof AuthStepUpError) {
|
|
13616
|
+
const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
|
|
13617
|
+
return {
|
|
13618
|
+
code: EXIT_AUTH,
|
|
13619
|
+
kind: "auth",
|
|
13620
|
+
msg: `insufficient scope. Run: mcpcat login ${err.target ?? "<target>"}${scopeHint}`
|
|
13621
|
+
};
|
|
13622
|
+
}
|
|
13623
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
13624
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
13625
|
+
}
|
|
12193
13626
|
if (err instanceof McpcatError) {
|
|
12194
13627
|
return { code: err.exitCode, kind: "error", msg: err.message };
|
|
12195
13628
|
}
|
|
@@ -12200,6 +13633,14 @@ function classifyError(err) {
|
|
|
12200
13633
|
}
|
|
12201
13634
|
return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
|
|
12202
13635
|
}
|
|
13636
|
+
function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
13637
|
+
if (hasAuthHeader || !url) return void 0;
|
|
13638
|
+
const source = createTokenSource(platform2);
|
|
13639
|
+
const entry = config.entry(target);
|
|
13640
|
+
const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
|
|
13641
|
+
const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
|
|
13642
|
+
return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
|
|
13643
|
+
}
|
|
12203
13644
|
async function runClientVerb(platform2, version, parsed) {
|
|
12204
13645
|
const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
|
|
12205
13646
|
const verbose = createVerboseLogger(platform2, parsed.verbose);
|
|
@@ -12229,6 +13670,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
12229
13670
|
await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
|
|
12230
13671
|
return EXIT_USAGE;
|
|
12231
13672
|
}
|
|
13673
|
+
const hasAuthHeader = "Authorization" in expanded;
|
|
13674
|
+
const authProvider = resolveAuthProvider(platform2, hasAuthHeader, resolved.url, target, config);
|
|
12232
13675
|
let conn;
|
|
12233
13676
|
try {
|
|
12234
13677
|
conn = await connect(platform2, {
|
|
@@ -12238,11 +13681,22 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
12238
13681
|
version,
|
|
12239
13682
|
timeoutMs,
|
|
12240
13683
|
signal: sigintController.signal,
|
|
12241
|
-
verbose
|
|
13684
|
+
verbose,
|
|
13685
|
+
authProvider
|
|
12242
13686
|
});
|
|
12243
13687
|
} catch (err) {
|
|
12244
|
-
if (err instanceof McpAuthError) {
|
|
12245
|
-
|
|
13688
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) {
|
|
13689
|
+
const e = classifyError(err);
|
|
13690
|
+
await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
13691
|
+
return EXIT_AUTH;
|
|
13692
|
+
}
|
|
13693
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
13694
|
+
await writeErr(
|
|
13695
|
+
platform2,
|
|
13696
|
+
parsed.jsonOutput,
|
|
13697
|
+
"auth",
|
|
13698
|
+
`unauthorized. Run: mcpcat login ${target}`
|
|
13699
|
+
);
|
|
12246
13700
|
return EXIT_AUTH;
|
|
12247
13701
|
}
|
|
12248
13702
|
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
@@ -12389,7 +13843,7 @@ async function main(ctx) {
|
|
|
12389
13843
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
12390
13844
|
});
|
|
12391
13845
|
const args = ctx.argv.slice(1);
|
|
12392
|
-
return run(platform2, "0.
|
|
13846
|
+
return run(platform2, "0.11.0", args);
|
|
12393
13847
|
}
|
|
12394
13848
|
export {
|
|
12395
13849
|
main as default
|