@ai-ecoverse/mcpcat 0.8.0 → 0.10.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 +2299 -341
- package/dist/kernel/mcpcat.mjs +2209 -239
- package/package.json +2 -2
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -5298,6 +5298,367 @@ var init_portable = __esm({
|
|
|
5298
5298
|
}
|
|
5299
5299
|
});
|
|
5300
5300
|
|
|
5301
|
+
// src/args/coerce.ts
|
|
5302
|
+
function asSchemaObject(schema) {
|
|
5303
|
+
return schema ?? {};
|
|
5304
|
+
}
|
|
5305
|
+
function emptyArgObject() {
|
|
5306
|
+
return /* @__PURE__ */ Object.create(null);
|
|
5307
|
+
}
|
|
5308
|
+
function isUnsafeObjectKey(key) {
|
|
5309
|
+
return key === "__proto__" || key === "prototype" || key === "constructor";
|
|
5310
|
+
}
|
|
5311
|
+
function isPlainObject(value) {
|
|
5312
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && value !== Object.prototype;
|
|
5313
|
+
}
|
|
5314
|
+
function splitFlag(a) {
|
|
5315
|
+
const eq = a.indexOf("=");
|
|
5316
|
+
if (eq > 2) return { key: a.slice(2, eq), inlineValue: a.slice(eq + 1) };
|
|
5317
|
+
return { key: a.slice(2), inlineValue: void 0 };
|
|
5318
|
+
}
|
|
5319
|
+
function coerceScalar(raw, type) {
|
|
5320
|
+
switch (type) {
|
|
5321
|
+
case "integer": {
|
|
5322
|
+
if (!/^-?\d+$/.test(raw)) return { ok: false, error: `expected integer, got "${raw}"` };
|
|
5323
|
+
return { ok: true, value: Number(raw) };
|
|
5324
|
+
}
|
|
5325
|
+
case "number": {
|
|
5326
|
+
const n = Number(raw);
|
|
5327
|
+
if (!Number.isFinite(n)) return { ok: false, error: `expected number, got "${raw}"` };
|
|
5328
|
+
return { ok: true, value: n };
|
|
5329
|
+
}
|
|
5330
|
+
case "boolean": {
|
|
5331
|
+
if (raw === "true" || raw === "1" || raw === "yes") return { ok: true, value: true };
|
|
5332
|
+
if (raw === "false" || raw === "0" || raw === "no") return { ok: true, value: false };
|
|
5333
|
+
return { ok: false, error: `expected boolean, got "${raw}"` };
|
|
5334
|
+
}
|
|
5335
|
+
case "object":
|
|
5336
|
+
return coerceJsonObject(raw);
|
|
5337
|
+
default:
|
|
5338
|
+
return { ok: true, value: raw };
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
function coerceJsonObject(raw) {
|
|
5342
|
+
let parsed;
|
|
5343
|
+
try {
|
|
5344
|
+
parsed = JSON.parse(raw);
|
|
5345
|
+
} catch {
|
|
5346
|
+
return { ok: false, error: `expected object JSON, got "${raw}"` };
|
|
5347
|
+
}
|
|
5348
|
+
if (!isPlainObject(parsed)) {
|
|
5349
|
+
return { ok: false, error: `expected object JSON, got "${raw}"` };
|
|
5350
|
+
}
|
|
5351
|
+
return { ok: true, value: parsed };
|
|
5352
|
+
}
|
|
5353
|
+
function resolveToolFlag(key, properties) {
|
|
5354
|
+
if (Object.hasOwn(properties, key)) {
|
|
5355
|
+
return { ok: true, rootKey: key, dottedPath: null, meta: properties[key] ?? {} };
|
|
5356
|
+
}
|
|
5357
|
+
const dot = key.indexOf(".");
|
|
5358
|
+
if (dot <= 0) return { ok: false, error: `unknown flag: --${key}` };
|
|
5359
|
+
const rootKey = key.slice(0, dot);
|
|
5360
|
+
const rest = key.slice(dot + 1);
|
|
5361
|
+
if (!Object.hasOwn(properties, rootKey) || rest.length === 0) {
|
|
5362
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
5363
|
+
}
|
|
5364
|
+
const parts = rest.split(".");
|
|
5365
|
+
if (isUnsafeObjectKey(rootKey) || parts.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
|
|
5366
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
5367
|
+
}
|
|
5368
|
+
return { ok: true, rootKey, dottedPath: parts, meta: properties[rootKey] ?? {} };
|
|
5369
|
+
}
|
|
5370
|
+
function readFlagRaw(args2, i, key, inlineValue, type, dottedPath) {
|
|
5371
|
+
if (inlineValue !== void 0) {
|
|
5372
|
+
return { ok: true, raw: inlineValue, nextIndex: i + 1, bareBoolean: false };
|
|
5373
|
+
}
|
|
5374
|
+
const next = args2[i + 1];
|
|
5375
|
+
if (!dottedPath && type === "boolean" && (next === void 0 || next.startsWith("--"))) {
|
|
5376
|
+
return { ok: true, raw: "", nextIndex: i + 1, bareBoolean: true };
|
|
5377
|
+
}
|
|
5378
|
+
if (next === void 0) {
|
|
5379
|
+
return { ok: false, error: `flag --${key} requires a value` };
|
|
5380
|
+
}
|
|
5381
|
+
return { ok: true, raw: next, nextIndex: i + 2, bareBoolean: false };
|
|
5382
|
+
}
|
|
5383
|
+
function setDotted(out, rootKey, path, value) {
|
|
5384
|
+
if (isUnsafeObjectKey(rootKey) || path.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
|
|
5385
|
+
return { ok: false, error: `unknown flag: --${rootKey}.${path.join(".")}` };
|
|
5386
|
+
}
|
|
5387
|
+
const existing = Object.hasOwn(out, rootKey) ? out[rootKey] : void 0;
|
|
5388
|
+
if (existing === void 0) {
|
|
5389
|
+
out[rootKey] = emptyArgObject();
|
|
5390
|
+
} else if (!isPlainObject(existing)) {
|
|
5391
|
+
return { ok: false, error: `--${rootKey}: cannot nest into a ${typeof existing} value` };
|
|
5392
|
+
}
|
|
5393
|
+
let cursor = out[rootKey];
|
|
5394
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
5395
|
+
const seg = path[i];
|
|
5396
|
+
const next = Object.hasOwn(cursor, seg) ? cursor[seg] : void 0;
|
|
5397
|
+
if (next === void 0) {
|
|
5398
|
+
cursor[seg] = emptyArgObject();
|
|
5399
|
+
} else if (!isPlainObject(next)) {
|
|
5400
|
+
return {
|
|
5401
|
+
ok: false,
|
|
5402
|
+
error: `--${rootKey}.${path.slice(0, i + 1).join(".")}: cannot nest into a ${typeof next} value`
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
cursor = cursor[seg];
|
|
5406
|
+
}
|
|
5407
|
+
cursor[path[path.length - 1]] = value;
|
|
5408
|
+
return { ok: true };
|
|
5409
|
+
}
|
|
5410
|
+
function assignFlagValue(out, rootKey, dottedPath, value, isArray) {
|
|
5411
|
+
if (dottedPath) return setDotted(out, rootKey, dottedPath, value);
|
|
5412
|
+
if (isArray) {
|
|
5413
|
+
const prev = out[rootKey];
|
|
5414
|
+
if (Array.isArray(prev)) prev.push(value);
|
|
5415
|
+
else out[rootKey] = [value];
|
|
5416
|
+
return { ok: true };
|
|
5417
|
+
}
|
|
5418
|
+
const existing = out[rootKey];
|
|
5419
|
+
if (isPlainObject(existing) && isPlainObject(value)) {
|
|
5420
|
+
Object.assign(existing, value);
|
|
5421
|
+
return { ok: true };
|
|
5422
|
+
}
|
|
5423
|
+
out[rootKey] = value;
|
|
5424
|
+
return { ok: true };
|
|
5425
|
+
}
|
|
5426
|
+
function parseOneFlag(args2, i, properties, out) {
|
|
5427
|
+
const a = args2[i];
|
|
5428
|
+
if (a === "--") return { ok: true, nextIndex: args2.length };
|
|
5429
|
+
if (a.startsWith("--no-") && a.length > 5) {
|
|
5430
|
+
const key2 = a.slice(5);
|
|
5431
|
+
if (Object.hasOwn(properties, key2) && properties[key2]?.type === "boolean") {
|
|
5432
|
+
out[key2] = false;
|
|
5433
|
+
return { ok: true, nextIndex: i + 1 };
|
|
5434
|
+
}
|
|
5435
|
+
return { ok: false, error: `unknown flag: ${a}` };
|
|
5436
|
+
}
|
|
5437
|
+
if (!a.startsWith("--")) {
|
|
5438
|
+
return { ok: false, error: `unexpected positional argument "${a}"` };
|
|
5439
|
+
}
|
|
5440
|
+
const { key, inlineValue } = splitFlag(a);
|
|
5441
|
+
const resolved = resolveToolFlag(key, properties);
|
|
5442
|
+
if (!resolved.ok) return resolved;
|
|
5443
|
+
const { rootKey, dottedPath, meta } = resolved;
|
|
5444
|
+
const declaredType = meta.type;
|
|
5445
|
+
const type = declaredType ?? "string";
|
|
5446
|
+
const isArray = type === "array";
|
|
5447
|
+
const itemType = (isArray ? meta.items?.type : void 0) ?? "string";
|
|
5448
|
+
if (dottedPath && declaredType !== void 0 && declaredType !== "object") {
|
|
5449
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
5450
|
+
}
|
|
5451
|
+
const taken = readFlagRaw(args2, i, key, inlineValue, type, dottedPath);
|
|
5452
|
+
if (!taken.ok) return taken;
|
|
5453
|
+
if (taken.bareBoolean) {
|
|
5454
|
+
out[rootKey] = true;
|
|
5455
|
+
return { ok: true, nextIndex: taken.nextIndex };
|
|
5456
|
+
}
|
|
5457
|
+
const valueType = dottedPath ? "string" : isArray ? itemType : type;
|
|
5458
|
+
const coerced = coerceScalar(taken.raw, valueType);
|
|
5459
|
+
if (!coerced.ok) return { ok: false, error: `--${key}: ${coerced.error}` };
|
|
5460
|
+
const assigned = assignFlagValue(out, rootKey, dottedPath, coerced.value, isArray);
|
|
5461
|
+
if (!assigned.ok) return assigned;
|
|
5462
|
+
return { ok: true, nextIndex: taken.nextIndex };
|
|
5463
|
+
}
|
|
5464
|
+
function coerceArgsBySchema(args2, schema, options) {
|
|
5465
|
+
const s = asSchemaObject(schema);
|
|
5466
|
+
const properties = s.properties ?? {};
|
|
5467
|
+
const required = new Set(Array.isArray(s.required) ? s.required : []);
|
|
5468
|
+
const out = emptyArgObject();
|
|
5469
|
+
let i = 0;
|
|
5470
|
+
while (i < args2.length) {
|
|
5471
|
+
const result = parseOneFlag(args2, i, properties, out);
|
|
5472
|
+
if (!result.ok) return result;
|
|
5473
|
+
i = result.nextIndex;
|
|
5474
|
+
}
|
|
5475
|
+
if (!options?.noValidate) {
|
|
5476
|
+
for (const r of required) {
|
|
5477
|
+
if (!Object.hasOwn(out, r)) {
|
|
5478
|
+
return { ok: false, error: `missing required flag --${r}` };
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
return { ok: true, value: out };
|
|
5483
|
+
}
|
|
5484
|
+
function mergeArgs(base, flags) {
|
|
5485
|
+
const result = { ...base };
|
|
5486
|
+
for (const [k, v] of Object.entries(flags)) {
|
|
5487
|
+
result[k] = v;
|
|
5488
|
+
}
|
|
5489
|
+
return result;
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5492
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/callback.js
|
|
5493
|
+
import { createServer } from "node:http";
|
|
5494
|
+
function plainText(page) {
|
|
5495
|
+
if (page.ok)
|
|
5496
|
+
return "Authorization complete. You may close this window.";
|
|
5497
|
+
return page.details ? `${page.message}
|
|
5498
|
+
|
|
5499
|
+
${page.details}` : page.message;
|
|
5500
|
+
}
|
|
5501
|
+
var OAuthCallbackServer = class _OAuthCallbackServer {
|
|
5502
|
+
redirectUrl;
|
|
5503
|
+
server;
|
|
5504
|
+
paths;
|
|
5505
|
+
timeoutMs;
|
|
5506
|
+
renderPage;
|
|
5507
|
+
pending = /* @__PURE__ */ new Map();
|
|
5508
|
+
constructor(server, redirectUrl, paths, timeoutMs, renderPage) {
|
|
5509
|
+
this.server = server;
|
|
5510
|
+
this.redirectUrl = redirectUrl;
|
|
5511
|
+
this.paths = paths;
|
|
5512
|
+
this.timeoutMs = timeoutMs;
|
|
5513
|
+
this.renderPage = renderPage;
|
|
5514
|
+
}
|
|
5515
|
+
static async listen(options = {}) {
|
|
5516
|
+
const host = options.host ?? "127.0.0.1";
|
|
5517
|
+
const redirectHost = options.redirectHost ?? host;
|
|
5518
|
+
const path = options.path ?? "/callback";
|
|
5519
|
+
let instance;
|
|
5520
|
+
const server = createServer((request, response) => instance?.handle(request.url ?? "/", response));
|
|
5521
|
+
await new Promise((resolve, reject) => {
|
|
5522
|
+
server.once("error", reject);
|
|
5523
|
+
server.listen(options.port ?? 0, host, () => {
|
|
5524
|
+
server.off("error", reject);
|
|
5525
|
+
resolve();
|
|
5526
|
+
});
|
|
5527
|
+
});
|
|
5528
|
+
const address = server.address();
|
|
5529
|
+
if (!address || typeof address === "string")
|
|
5530
|
+
throw new Error("OAuth callback server did not bind to TCP");
|
|
5531
|
+
instance = new _OAuthCallbackServer(server, `http://${redirectHost.includes(":") ? `[${redirectHost}]` : redirectHost}:${address.port}${path}`, [path, ...options.extraPaths ?? []], options.timeoutMs ?? 5 * 6e4, options.renderPage);
|
|
5532
|
+
return instance;
|
|
5533
|
+
}
|
|
5534
|
+
/**
|
|
5535
|
+
* Wait for the authorization response with `state`. With `path`, a response on another path fails, so
|
|
5536
|
+
* a server-specific redirect URI can tell authorization servers apart (RFC 9700 section 4.4.2.2).
|
|
5537
|
+
*/
|
|
5538
|
+
waitForCallback(state, path) {
|
|
5539
|
+
if (this.pending.has(state))
|
|
5540
|
+
throw new Error("OAuth state is already pending");
|
|
5541
|
+
return new Promise((resolve, reject) => {
|
|
5542
|
+
const timer = setTimeout(() => {
|
|
5543
|
+
this.pending.delete(state);
|
|
5544
|
+
reject(new Error("OAuth callback timed out"));
|
|
5545
|
+
}, this.timeoutMs);
|
|
5546
|
+
this.pending.set(state, { resolve, reject, timer, path });
|
|
5547
|
+
});
|
|
5548
|
+
}
|
|
5549
|
+
async close() {
|
|
5550
|
+
for (const pending of this.pending.values()) {
|
|
5551
|
+
clearTimeout(pending.timer);
|
|
5552
|
+
pending.reject(new Error("OAuth callback server closed"));
|
|
5553
|
+
}
|
|
5554
|
+
this.pending.clear();
|
|
5555
|
+
await new Promise((resolve, reject) => {
|
|
5556
|
+
this.server.close((error) => error ? reject(error) : resolve());
|
|
5557
|
+
});
|
|
5558
|
+
}
|
|
5559
|
+
reply(response, status, page) {
|
|
5560
|
+
if (this.renderPage) {
|
|
5561
|
+
response.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
5562
|
+
response.end(this.renderPage(page));
|
|
5563
|
+
} else {
|
|
5564
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8" }).end(plainText(page));
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
handle(rawUrl, response) {
|
|
5568
|
+
const url = new URL(rawUrl, this.redirectUrl);
|
|
5569
|
+
if (!this.paths.includes(url.pathname)) {
|
|
5570
|
+
this.reply(response, 404, { ok: false, message: "Not found" });
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
const state = url.searchParams.get("state");
|
|
5574
|
+
const pending = state ? this.pending.get(state) : void 0;
|
|
5575
|
+
if (!state || !pending) {
|
|
5576
|
+
this.reply(response, 400, { ok: false, message: "Invalid or expired OAuth state" });
|
|
5577
|
+
return;
|
|
5578
|
+
}
|
|
5579
|
+
clearTimeout(pending.timer);
|
|
5580
|
+
this.pending.delete(state);
|
|
5581
|
+
if (pending.path !== void 0 && url.pathname !== pending.path) {
|
|
5582
|
+
pending.reject(new Error("The authorization response arrived on another redirect URI"));
|
|
5583
|
+
this.reply(response, 400, { ok: false, message: "Unexpected redirect URI" });
|
|
5584
|
+
return;
|
|
5585
|
+
}
|
|
5586
|
+
const error = url.searchParams.get("error");
|
|
5587
|
+
if (error) {
|
|
5588
|
+
const description = url.searchParams.get("error_description") ?? error;
|
|
5589
|
+
pending.reject(new Error(description));
|
|
5590
|
+
this.reply(response, 200, {
|
|
5591
|
+
ok: false,
|
|
5592
|
+
message: "Authorization failed. You may close this window.",
|
|
5593
|
+
details: description
|
|
5594
|
+
});
|
|
5595
|
+
return;
|
|
5596
|
+
}
|
|
5597
|
+
const code2 = url.searchParams.get("code");
|
|
5598
|
+
if (!code2) {
|
|
5599
|
+
pending.reject(new Error("OAuth callback did not include an authorization code"));
|
|
5600
|
+
this.reply(response, 400, { ok: false, message: "Missing authorization code" });
|
|
5601
|
+
return;
|
|
5602
|
+
}
|
|
5603
|
+
const iss = url.searchParams.get("iss");
|
|
5604
|
+
pending.resolve({ code: code2, state, ...iss ? { iss } : {} });
|
|
5605
|
+
this.reply(response, 200, { ok: true });
|
|
5606
|
+
}
|
|
5607
|
+
};
|
|
5608
|
+
|
|
5609
|
+
// node_modules/@earendil-works/pi-mcp/dist/protocol/types.js
|
|
5610
|
+
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
5611
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05"];
|
|
5612
|
+
|
|
5613
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/errors.js
|
|
5614
|
+
var OAuthError = class extends Error {
|
|
5615
|
+
code;
|
|
5616
|
+
errorUri;
|
|
5617
|
+
constructor(code2, message, errorUri) {
|
|
5618
|
+
super(message || code2);
|
|
5619
|
+
this.name = "OAuthError";
|
|
5620
|
+
this.code = code2;
|
|
5621
|
+
this.errorUri = errorUri;
|
|
5622
|
+
}
|
|
5623
|
+
};
|
|
5624
|
+
var OAuthIssuerMismatchError = class extends Error {
|
|
5625
|
+
/** `undefined` when no authorization server metadata was discovered. */
|
|
5626
|
+
expected;
|
|
5627
|
+
/** `undefined` when an authorization response lacks the `iss` parameter its server promised (RFC 9207). */
|
|
5628
|
+
received;
|
|
5629
|
+
constructor(expected, received) {
|
|
5630
|
+
const format = (issuer) => issuer === void 0 ? "none" : JSON.stringify(issuer);
|
|
5631
|
+
super(`OAuth issuer mismatch: expected ${format(expected)}, received ${format(received)}`);
|
|
5632
|
+
this.name = "OAuthIssuerMismatchError";
|
|
5633
|
+
this.expected = expected;
|
|
5634
|
+
this.received = received;
|
|
5635
|
+
}
|
|
5636
|
+
};
|
|
5637
|
+
var OAuthInsecureEndpointError = class extends Error {
|
|
5638
|
+
endpoint;
|
|
5639
|
+
constructor(endpoint) {
|
|
5640
|
+
super(`Refusing to send OAuth credentials to non-HTTPS endpoint ${endpoint}`);
|
|
5641
|
+
this.name = "OAuthInsecureEndpointError";
|
|
5642
|
+
this.endpoint = endpoint;
|
|
5643
|
+
}
|
|
5644
|
+
};
|
|
5645
|
+
var OAuthRegistrationError = class extends Error {
|
|
5646
|
+
status;
|
|
5647
|
+
body;
|
|
5648
|
+
constructor(status, body) {
|
|
5649
|
+
super(`OAuth dynamic client registration failed with status ${status}: ${body}`);
|
|
5650
|
+
this.name = "OAuthRegistrationError";
|
|
5651
|
+
this.status = status;
|
|
5652
|
+
this.body = body;
|
|
5653
|
+
}
|
|
5654
|
+
};
|
|
5655
|
+
var McpOAuthAuthorizationRequiredError = class extends Error {
|
|
5656
|
+
constructor() {
|
|
5657
|
+
super("MCP OAuth authorization requires user interaction");
|
|
5658
|
+
this.name = "McpOAuthAuthorizationRequiredError";
|
|
5659
|
+
}
|
|
5660
|
+
};
|
|
5661
|
+
|
|
5301
5662
|
// node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
|
|
5302
5663
|
var JSON_RPC_ERROR_CODES = {
|
|
5303
5664
|
parseError: -32700,
|
|
@@ -5366,9 +5727,726 @@ function parseJsonRpcMessage(value) {
|
|
|
5366
5727
|
throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid JSON-RPC message");
|
|
5367
5728
|
}
|
|
5368
5729
|
|
|
5369
|
-
// node_modules/@earendil-works/pi-mcp/dist/
|
|
5370
|
-
|
|
5371
|
-
|
|
5730
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/types.js
|
|
5731
|
+
function object(value, name) {
|
|
5732
|
+
if (!isObject(value))
|
|
5733
|
+
throw new Error(`Invalid ${name}`);
|
|
5734
|
+
return value;
|
|
5735
|
+
}
|
|
5736
|
+
function compact(value) {
|
|
5737
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
|
|
5738
|
+
}
|
|
5739
|
+
function requiredString(value, name) {
|
|
5740
|
+
if (typeof value !== "string" || value.length === 0)
|
|
5741
|
+
throw new Error(`Invalid ${name}`);
|
|
5742
|
+
return value;
|
|
5743
|
+
}
|
|
5744
|
+
function absent(value) {
|
|
5745
|
+
return value === void 0 || value === null || value === "";
|
|
5746
|
+
}
|
|
5747
|
+
function optionalString(value, name) {
|
|
5748
|
+
if (absent(value))
|
|
5749
|
+
return void 0;
|
|
5750
|
+
return requiredString(value, name);
|
|
5751
|
+
}
|
|
5752
|
+
function optionalStrings(value, name) {
|
|
5753
|
+
if (value === void 0 || value === null)
|
|
5754
|
+
return void 0;
|
|
5755
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
5756
|
+
throw new Error(`Invalid ${name}`);
|
|
5757
|
+
return [...value];
|
|
5758
|
+
}
|
|
5759
|
+
function safeUrl(value, name) {
|
|
5760
|
+
const text = requiredString(value, name);
|
|
5761
|
+
if (!URL.canParse(text))
|
|
5762
|
+
throw new Error(`Invalid ${name}`);
|
|
5763
|
+
const url = new URL(text);
|
|
5764
|
+
if (["javascript:", "data:", "vbscript:"].includes(url.protocol))
|
|
5765
|
+
throw new Error(`Invalid ${name}`);
|
|
5766
|
+
return text;
|
|
5767
|
+
}
|
|
5768
|
+
function optionalUrl(value, name) {
|
|
5769
|
+
return absent(value) ? void 0 : safeUrl(value, name);
|
|
5770
|
+
}
|
|
5771
|
+
function parseProtectedResourceMetadata(value) {
|
|
5772
|
+
const input = object(value, "OAuth protected resource metadata");
|
|
5773
|
+
return compact({
|
|
5774
|
+
...input,
|
|
5775
|
+
resource: safeUrl(input.resource, "OAuth protected resource metadata resource"),
|
|
5776
|
+
authorization_servers: optionalStrings(input.authorization_servers, "authorization_servers")?.map((url) => safeUrl(url, "authorization server URL")),
|
|
5777
|
+
scopes_supported: optionalStrings(input.scopes_supported, "scopes_supported")
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5780
|
+
function parseAuthorizationServerMetadata(value) {
|
|
5781
|
+
const input = object(value, "authorization server metadata");
|
|
5782
|
+
const responseTypes = optionalStrings(input.response_types_supported, "response_types_supported");
|
|
5783
|
+
if (!responseTypes)
|
|
5784
|
+
throw new Error("Invalid response_types_supported");
|
|
5785
|
+
return compact({
|
|
5786
|
+
...input,
|
|
5787
|
+
issuer: safeUrl(input.issuer, "authorization server issuer"),
|
|
5788
|
+
authorization_endpoint: safeUrl(input.authorization_endpoint, "authorization endpoint"),
|
|
5789
|
+
token_endpoint: safeUrl(input.token_endpoint, "token endpoint"),
|
|
5790
|
+
registration_endpoint: optionalUrl(input.registration_endpoint, "registration endpoint"),
|
|
5791
|
+
scopes_supported: optionalStrings(input.scopes_supported, "scopes_supported"),
|
|
5792
|
+
response_types_supported: responseTypes,
|
|
5793
|
+
grant_types_supported: optionalStrings(input.grant_types_supported, "grant_types_supported"),
|
|
5794
|
+
token_endpoint_auth_methods_supported: optionalStrings(input.token_endpoint_auth_methods_supported, "token_endpoint_auth_methods_supported"),
|
|
5795
|
+
code_challenge_methods_supported: optionalStrings(input.code_challenge_methods_supported, "code_challenge_methods_supported"),
|
|
5796
|
+
client_id_metadata_document_supported: typeof input.client_id_metadata_document_supported === "boolean" ? input.client_id_metadata_document_supported : void 0,
|
|
5797
|
+
authorization_response_iss_parameter_supported: typeof input.authorization_response_iss_parameter_supported === "boolean" ? input.authorization_response_iss_parameter_supported : void 0
|
|
5798
|
+
});
|
|
5799
|
+
}
|
|
5800
|
+
function parseOAuthTokens(value) {
|
|
5801
|
+
const input = object(value, "OAuth token response");
|
|
5802
|
+
const expires = absent(input.expires_in) ? void 0 : Number(input.expires_in);
|
|
5803
|
+
if (expires !== void 0 && !Number.isFinite(expires))
|
|
5804
|
+
throw new Error("Invalid expires_in");
|
|
5805
|
+
return compact({
|
|
5806
|
+
access_token: requiredString(input.access_token, "access_token"),
|
|
5807
|
+
token_type: requiredString(input.token_type, "token_type"),
|
|
5808
|
+
expires_in: expires,
|
|
5809
|
+
scope: optionalString(input.scope, "scope"),
|
|
5810
|
+
refresh_token: optionalString(input.refresh_token, "refresh_token"),
|
|
5811
|
+
id_token: optionalString(input.id_token, "id_token")
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5814
|
+
function parseClientInformation(value) {
|
|
5815
|
+
const input = object(value, "OAuth client registration response");
|
|
5816
|
+
return compact({
|
|
5817
|
+
...input,
|
|
5818
|
+
client_id: requiredString(input.client_id, "client_id"),
|
|
5819
|
+
client_secret: optionalString(input.client_secret, "client_secret"),
|
|
5820
|
+
client_id_issued_at: typeof input.client_id_issued_at === "number" ? input.client_id_issued_at : void 0,
|
|
5821
|
+
client_secret_expires_at: typeof input.client_secret_expires_at === "number" ? input.client_secret_expires_at : void 0,
|
|
5822
|
+
redirect_uris: optionalStrings(input.redirect_uris, "redirect_uris") ?? []
|
|
5823
|
+
});
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/discovery.js
|
|
5827
|
+
function discard(response) {
|
|
5828
|
+
void response?.body?.cancel().catch(() => {
|
|
5829
|
+
});
|
|
5830
|
+
}
|
|
5831
|
+
function isDiscoveryMiss(status) {
|
|
5832
|
+
return status >= 400 && status < 500 || status === 502;
|
|
5833
|
+
}
|
|
5834
|
+
function pathSuffix(pathname) {
|
|
5835
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
5836
|
+
}
|
|
5837
|
+
function field(header, name) {
|
|
5838
|
+
const match = header.match(new RegExp(String.raw`(?:^|[,\s])${name}=(?:"([^"]*)"|([^\s,]+))`, "i"));
|
|
5839
|
+
return match?.[1] || match?.[2] || void 0;
|
|
5840
|
+
}
|
|
5841
|
+
function parseWwwAuthenticate(header) {
|
|
5842
|
+
if (!header)
|
|
5843
|
+
return {};
|
|
5844
|
+
const scheme = header.trimStart().split(/\s+/, 1)[0]?.toLowerCase();
|
|
5845
|
+
if (scheme !== "bearer" && scheme !== "dpop")
|
|
5846
|
+
return {};
|
|
5847
|
+
const resourceMetadata = field(header, "resource_metadata");
|
|
5848
|
+
let resourceMetadataUrl;
|
|
5849
|
+
if (resourceMetadata) {
|
|
5850
|
+
try {
|
|
5851
|
+
resourceMetadataUrl = new URL(resourceMetadata);
|
|
5852
|
+
} catch {
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
return {
|
|
5856
|
+
resourceMetadataUrl,
|
|
5857
|
+
scope: field(header, "scope"),
|
|
5858
|
+
error: field(header, "error"),
|
|
5859
|
+
errorDescription: field(header, "error_description")
|
|
5860
|
+
};
|
|
5861
|
+
}
|
|
5862
|
+
async function fetchMetadata(url, fetch, protocolVersion, signal) {
|
|
5863
|
+
return fetch(url, {
|
|
5864
|
+
headers: { Accept: "application/json", "MCP-Protocol-Version": protocolVersion },
|
|
5865
|
+
signal
|
|
5866
|
+
});
|
|
5867
|
+
}
|
|
5868
|
+
async function discoverProtectedResourceMetadata(serverUrl, options = {}) {
|
|
5869
|
+
const server = new URL(serverUrl);
|
|
5870
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
5871
|
+
const version = options.protocolVersion ?? LATEST_PROTOCOL_VERSION;
|
|
5872
|
+
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);
|
|
5873
|
+
if (!options.resourceMetadataUrl && server.pathname !== "/" && isDiscoveryMiss(response.status)) {
|
|
5874
|
+
discard(response);
|
|
5875
|
+
response = await fetchMetadata(new URL("/.well-known/oauth-protected-resource", server.origin), fetch, version, options.signal);
|
|
5876
|
+
}
|
|
5877
|
+
if (!response.ok) {
|
|
5878
|
+
discard(response);
|
|
5879
|
+
throw new Error(`HTTP ${response.status} loading OAuth protected resource metadata`);
|
|
5880
|
+
}
|
|
5881
|
+
return parseProtectedResourceMetadata(await response.json());
|
|
5882
|
+
}
|
|
5883
|
+
function buildAuthorizationServerDiscoveryUrls(authorizationServerUrl) {
|
|
5884
|
+
const issuer = new URL(authorizationServerUrl);
|
|
5885
|
+
const path = pathSuffix(issuer.pathname);
|
|
5886
|
+
const urls = [
|
|
5887
|
+
{ url: new URL(`/.well-known/oauth-authorization-server${path}`, issuer.origin), type: "oauth" },
|
|
5888
|
+
{ url: new URL(`/.well-known/openid-configuration${path}`, issuer.origin), type: "oidc" }
|
|
5889
|
+
];
|
|
5890
|
+
if (path)
|
|
5891
|
+
urls.push({ url: new URL(`${path}/.well-known/openid-configuration`, issuer.origin), type: "oidc" });
|
|
5892
|
+
return urls;
|
|
5893
|
+
}
|
|
5894
|
+
async function discoverAuthorizationServerMetadata(authorizationServerUrl, options = {}) {
|
|
5895
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
5896
|
+
for (const { url } of buildAuthorizationServerDiscoveryUrls(authorizationServerUrl)) {
|
|
5897
|
+
const response = await fetchMetadata(url, fetch, options.protocolVersion ?? LATEST_PROTOCOL_VERSION, options.signal);
|
|
5898
|
+
if (!response.ok) {
|
|
5899
|
+
discard(response);
|
|
5900
|
+
if (isDiscoveryMiss(response.status))
|
|
5901
|
+
continue;
|
|
5902
|
+
throw new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);
|
|
5903
|
+
}
|
|
5904
|
+
const metadata = parseAuthorizationServerMetadata(await response.json());
|
|
5905
|
+
if (!options.skipIssuerValidation) {
|
|
5906
|
+
const expected = String(authorizationServerUrl);
|
|
5907
|
+
const trim = (value) => value.endsWith("/") ? value.slice(0, -1) : value;
|
|
5908
|
+
if (trim(metadata.issuer) !== trim(expected))
|
|
5909
|
+
throw new OAuthIssuerMismatchError(expected, metadata.issuer);
|
|
5910
|
+
}
|
|
5911
|
+
return metadata;
|
|
5912
|
+
}
|
|
5913
|
+
return void 0;
|
|
5914
|
+
}
|
|
5915
|
+
async function discoverOAuthServerInfo(serverUrl, options = {}) {
|
|
5916
|
+
let resourceMetadata;
|
|
5917
|
+
try {
|
|
5918
|
+
resourceMetadata = await discoverProtectedResourceMetadata(serverUrl, {
|
|
5919
|
+
resourceMetadataUrl: options.resourceMetadataUrl,
|
|
5920
|
+
fetch: options.fetch,
|
|
5921
|
+
signal: options.signal
|
|
5922
|
+
});
|
|
5923
|
+
} catch (error) {
|
|
5924
|
+
if (error instanceof TypeError)
|
|
5925
|
+
throw error;
|
|
5926
|
+
}
|
|
5927
|
+
if (options.authorizationServerMetadataUrl) {
|
|
5928
|
+
const url = options.authorizationServerMetadataUrl;
|
|
5929
|
+
const response = await fetchMetadata(url, options.fetch ?? globalThis.fetch, LATEST_PROTOCOL_VERSION, options.signal);
|
|
5930
|
+
if (!response.ok) {
|
|
5931
|
+
discard(response);
|
|
5932
|
+
throw new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);
|
|
5933
|
+
}
|
|
5934
|
+
const metadata = parseAuthorizationServerMetadata(await response.json());
|
|
5935
|
+
return { authorizationServerUrl: metadata.issuer, authorizationServerMetadata: metadata, resourceMetadata };
|
|
5936
|
+
}
|
|
5937
|
+
const authorizationServerUrl = resourceMetadata?.authorization_servers?.[0] ?? String(new URL("/", serverUrl));
|
|
5938
|
+
return {
|
|
5939
|
+
authorizationServerUrl,
|
|
5940
|
+
authorizationServerMetadata: await discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
5941
|
+
fetch: options.fetch,
|
|
5942
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
5943
|
+
signal: options.signal
|
|
5944
|
+
}),
|
|
5945
|
+
resourceMetadata
|
|
5946
|
+
};
|
|
5947
|
+
}
|
|
5948
|
+
function resourceUrlFromServerUrl(value) {
|
|
5949
|
+
const url = new URL(value);
|
|
5950
|
+
url.hash = "";
|
|
5951
|
+
return url;
|
|
5952
|
+
}
|
|
5953
|
+
function selectResource(serverUrl, metadata) {
|
|
5954
|
+
if (!metadata)
|
|
5955
|
+
return void 0;
|
|
5956
|
+
const requested = resourceUrlFromServerUrl(serverUrl);
|
|
5957
|
+
const configured = new URL(metadata.resource);
|
|
5958
|
+
if (requested.origin !== configured.origin) {
|
|
5959
|
+
throw new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);
|
|
5960
|
+
}
|
|
5961
|
+
const requestedPath = requested.pathname.endsWith("/") ? requested.pathname : `${requested.pathname}/`;
|
|
5962
|
+
const configuredPath = configured.pathname.endsWith("/") ? configured.pathname : `${configured.pathname}/`;
|
|
5963
|
+
if (!requestedPath.startsWith(configuredPath)) {
|
|
5964
|
+
throw new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);
|
|
5965
|
+
}
|
|
5966
|
+
return metadata.resource;
|
|
5967
|
+
}
|
|
5968
|
+
|
|
5969
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/flow.js
|
|
5970
|
+
function loopback(hostname) {
|
|
5971
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
|
|
5972
|
+
}
|
|
5973
|
+
function applicationType(redirectUris) {
|
|
5974
|
+
const native = redirectUris.some((uri) => {
|
|
5975
|
+
if (!URL.canParse(uri))
|
|
5976
|
+
return false;
|
|
5977
|
+
const url = new URL(uri);
|
|
5978
|
+
return url.protocol !== "http:" && url.protocol !== "https:" || loopback(url.hostname);
|
|
5979
|
+
});
|
|
5980
|
+
return native ? "native" : "web";
|
|
5981
|
+
}
|
|
5982
|
+
function secureEndpoint(value) {
|
|
5983
|
+
const url = new URL(value);
|
|
5984
|
+
if (url.protocol !== "https:" && !loopback(url.hostname))
|
|
5985
|
+
throw new OAuthInsecureEndpointError(url.href);
|
|
5986
|
+
return url;
|
|
5987
|
+
}
|
|
5988
|
+
function selectClientAuthMethod(information, supported) {
|
|
5989
|
+
const hinted = "token_endpoint_auth_method" in information ? information.token_endpoint_auth_method : void 0;
|
|
5990
|
+
if (hinted && ["client_secret_basic", "client_secret_post", "none"].includes(hinted) && (supported.length === 0 || supported.includes(hinted))) {
|
|
5991
|
+
return hinted;
|
|
5992
|
+
}
|
|
5993
|
+
if (supported.length === 0)
|
|
5994
|
+
return information.client_secret ? "client_secret_basic" : "none";
|
|
5995
|
+
if (information.client_secret && supported.includes("client_secret_basic"))
|
|
5996
|
+
return "client_secret_basic";
|
|
5997
|
+
if (information.client_secret && supported.includes("client_secret_post"))
|
|
5998
|
+
return "client_secret_post";
|
|
5999
|
+
if (supported.includes("none"))
|
|
6000
|
+
return "none";
|
|
6001
|
+
return information.client_secret ? "client_secret_post" : "none";
|
|
6002
|
+
}
|
|
6003
|
+
function applyClientAuthentication(method, information, headers, params) {
|
|
6004
|
+
if (method === "client_secret_basic") {
|
|
6005
|
+
if (!information.client_secret)
|
|
6006
|
+
throw new Error("client_secret_basic requires a client secret");
|
|
6007
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${information.client_id}:${information.client_secret}`).toString("base64")}`);
|
|
6008
|
+
} else {
|
|
6009
|
+
params.set("client_id", information.client_id);
|
|
6010
|
+
if (method === "client_secret_post" && information.client_secret)
|
|
6011
|
+
params.set("client_secret", information.client_secret);
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
async function pkce() {
|
|
6015
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
6016
|
+
const verifier = Buffer.from(bytes).toString("base64url");
|
|
6017
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
6018
|
+
return { verifier, challenge: Buffer.from(digest).toString("base64url") };
|
|
6019
|
+
}
|
|
6020
|
+
async function startAuthorization(authorizationServerUrl, options) {
|
|
6021
|
+
const metadata = options.metadata;
|
|
6022
|
+
if (metadata && !metadata.response_types_supported.includes("code")) {
|
|
6023
|
+
throw new Error("Authorization server does not support authorization codes");
|
|
6024
|
+
}
|
|
6025
|
+
if (metadata?.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes("S256")) {
|
|
6026
|
+
throw new Error("Authorization server does not support PKCE S256");
|
|
6027
|
+
}
|
|
6028
|
+
const url = new URL(metadata?.authorization_endpoint ?? new URL("/authorize", authorizationServerUrl));
|
|
6029
|
+
const { verifier, challenge } = await pkce();
|
|
6030
|
+
url.searchParams.set("response_type", "code");
|
|
6031
|
+
url.searchParams.set("client_id", options.clientInformation.client_id);
|
|
6032
|
+
url.searchParams.set("code_challenge", challenge);
|
|
6033
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
6034
|
+
url.searchParams.set("redirect_uri", String(options.redirectUrl));
|
|
6035
|
+
if (options.state)
|
|
6036
|
+
url.searchParams.set("state", options.state);
|
|
6037
|
+
if (options.scope)
|
|
6038
|
+
url.searchParams.set("scope", options.scope);
|
|
6039
|
+
if (options.scope?.split(/\s+/).includes("offline_access"))
|
|
6040
|
+
url.searchParams.set("prompt", "consent");
|
|
6041
|
+
if (options.resource)
|
|
6042
|
+
url.searchParams.set("resource", options.resource);
|
|
6043
|
+
return { authorizationUrl: url, codeVerifier: verifier };
|
|
6044
|
+
}
|
|
6045
|
+
async function tokenRequest(authorizationServerUrl, options, params) {
|
|
6046
|
+
const url = secureEndpoint(options.metadata?.token_endpoint ?? new URL("/token", authorizationServerUrl));
|
|
6047
|
+
const headers = new Headers({ Accept: "application/json", "content-type": "application/x-www-form-urlencoded" });
|
|
6048
|
+
if (options.resource)
|
|
6049
|
+
params.set("resource", options.resource);
|
|
6050
|
+
if (options.addClientAuthentication) {
|
|
6051
|
+
await options.addClientAuthentication(headers, params, url, options.metadata);
|
|
6052
|
+
} else {
|
|
6053
|
+
applyClientAuthentication(selectClientAuthMethod(options.clientInformation, options.metadata?.token_endpoint_auth_methods_supported ?? []), options.clientInformation, headers, params);
|
|
6054
|
+
}
|
|
6055
|
+
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
6056
|
+
method: "POST",
|
|
6057
|
+
headers,
|
|
6058
|
+
body: params,
|
|
6059
|
+
signal: options.signal
|
|
6060
|
+
});
|
|
6061
|
+
const text = await response.text();
|
|
6062
|
+
let value;
|
|
6063
|
+
try {
|
|
6064
|
+
value = JSON.parse(text);
|
|
6065
|
+
} catch {
|
|
6066
|
+
}
|
|
6067
|
+
if (isObject(value) && typeof value.error === "string") {
|
|
6068
|
+
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);
|
|
6069
|
+
}
|
|
6070
|
+
if (!response.ok)
|
|
6071
|
+
throw new OAuthError("server_error", `HTTP ${response.status}: ${text}`);
|
|
6072
|
+
return parseOAuthTokens(value);
|
|
6073
|
+
}
|
|
6074
|
+
async function registerClient(authorizationServerUrl, options) {
|
|
6075
|
+
const endpoint = options.metadata?.registration_endpoint;
|
|
6076
|
+
if (options.metadata && !endpoint)
|
|
6077
|
+
throw new Error("Authorization server does not support dynamic client registration");
|
|
6078
|
+
const response = await (options.fetch ?? globalThis.fetch)(new URL(endpoint ?? new URL("/register", authorizationServerUrl)), {
|
|
6079
|
+
method: "POST",
|
|
6080
|
+
headers: { Accept: "application/json", "content-type": "application/json" },
|
|
6081
|
+
body: JSON.stringify({
|
|
6082
|
+
...options.clientMetadata,
|
|
6083
|
+
application_type: options.clientMetadata.application_type ?? applicationType(options.clientMetadata.redirect_uris),
|
|
6084
|
+
...options.scope ? { scope: options.scope } : {}
|
|
6085
|
+
}),
|
|
6086
|
+
signal: options.signal
|
|
6087
|
+
});
|
|
6088
|
+
if (!response.ok)
|
|
6089
|
+
throw new OAuthRegistrationError(response.status, await response.text());
|
|
6090
|
+
return parseClientInformation(await response.json());
|
|
6091
|
+
}
|
|
6092
|
+
async function exchangeAuthorizationCode(authorizationServerUrl, options) {
|
|
6093
|
+
return tokenRequest(authorizationServerUrl, options, new URLSearchParams({
|
|
6094
|
+
grant_type: "authorization_code",
|
|
6095
|
+
code: options.code,
|
|
6096
|
+
code_verifier: options.codeVerifier,
|
|
6097
|
+
redirect_uri: String(options.redirectUrl)
|
|
6098
|
+
}));
|
|
6099
|
+
}
|
|
6100
|
+
async function refreshAuthorization(authorizationServerUrl, options) {
|
|
6101
|
+
const tokens = await tokenRequest(authorizationServerUrl, options, new URLSearchParams({ grant_type: "refresh_token", refresh_token: options.refreshToken }));
|
|
6102
|
+
return { refresh_token: options.refreshToken, ...tokens };
|
|
6103
|
+
}
|
|
6104
|
+
function withScope(tokens, scope) {
|
|
6105
|
+
return tokens.scope === void 0 && scope ? { ...tokens, scope } : tokens;
|
|
6106
|
+
}
|
|
6107
|
+
function stepUpScope(granted, challenged) {
|
|
6108
|
+
if (!challenged)
|
|
6109
|
+
return void 0;
|
|
6110
|
+
const scopes = [granted, challenged].flatMap((scope) => scope?.split(/\s+/).filter(Boolean) ?? []);
|
|
6111
|
+
return [...new Set(scopes)].join(" ");
|
|
6112
|
+
}
|
|
6113
|
+
async function runFlow(provider, options) {
|
|
6114
|
+
const metadataUrl = options.authorizationServerMetadataUrl && secureEndpoint(options.authorizationServerMetadataUrl);
|
|
6115
|
+
const cached = metadataUrl ? void 0 : await provider.discoveryState?.();
|
|
6116
|
+
const discovered = cached?.authorizationServerUrl ? {
|
|
6117
|
+
authorizationServerUrl: cached.authorizationServerUrl,
|
|
6118
|
+
authorizationServerMetadata: cached.authorizationServerMetadata ?? await discoverAuthorizationServerMetadata(cached.authorizationServerUrl, {
|
|
6119
|
+
fetch: options.fetch,
|
|
6120
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
6121
|
+
signal: options.signal
|
|
6122
|
+
}),
|
|
6123
|
+
resourceMetadata: cached.resourceMetadata
|
|
6124
|
+
} : await discoverOAuthServerInfo(options.serverUrl, {
|
|
6125
|
+
resourceMetadataUrl: options.resourceMetadataUrl,
|
|
6126
|
+
authorizationServerMetadataUrl: metadataUrl,
|
|
6127
|
+
fetch: options.fetch,
|
|
6128
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
6129
|
+
signal: options.signal
|
|
6130
|
+
});
|
|
6131
|
+
if (!metadataUrl) {
|
|
6132
|
+
await provider.saveDiscoveryState?.({
|
|
6133
|
+
...discovered,
|
|
6134
|
+
...options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl.href } : {}
|
|
6135
|
+
});
|
|
6136
|
+
}
|
|
6137
|
+
const metadata = discovered.authorizationServerMetadata;
|
|
6138
|
+
const resource = selectResource(options.serverUrl, discovered.resourceMetadata);
|
|
6139
|
+
const scope = options.scope || discovered.resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
|
|
6140
|
+
const stored = await provider.clientInformation();
|
|
6141
|
+
const clientDocument = stored ? void 0 : provider.clientMetadataDocument?.(metadata);
|
|
6142
|
+
if (clientDocument) {
|
|
6143
|
+
const url = new URL(clientDocument.url);
|
|
6144
|
+
if (url.protocol !== "https:" || url.pathname === "/")
|
|
6145
|
+
throw new Error("Invalid OAuth client metadata URL");
|
|
6146
|
+
}
|
|
6147
|
+
let client = stored ?? (clientDocument && { client_id: clientDocument.url });
|
|
6148
|
+
if (!client) {
|
|
6149
|
+
if (options.authorizationCode)
|
|
6150
|
+
throw new Error("OAuth client information is missing during code exchange");
|
|
6151
|
+
if (!provider.saveClientInformation)
|
|
6152
|
+
throw new Error("OAuth client information cannot be persisted");
|
|
6153
|
+
client = await registerClient(discovered.authorizationServerUrl, {
|
|
6154
|
+
metadata,
|
|
6155
|
+
clientMetadata: provider.clientMetadata,
|
|
6156
|
+
scope,
|
|
6157
|
+
fetch: options.fetch,
|
|
6158
|
+
signal: options.signal
|
|
6159
|
+
});
|
|
6160
|
+
await provider.saveClientInformation(client);
|
|
6161
|
+
}
|
|
6162
|
+
const redirectUrl = clientDocument?.redirectUrl ?? provider.redirectUrl;
|
|
6163
|
+
const tokenOptions = {
|
|
6164
|
+
metadata,
|
|
6165
|
+
clientInformation: client,
|
|
6166
|
+
resource,
|
|
6167
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
6168
|
+
fetch: options.fetch,
|
|
6169
|
+
signal: options.signal
|
|
6170
|
+
};
|
|
6171
|
+
if (options.authorizationCode) {
|
|
6172
|
+
const iss = options.iss;
|
|
6173
|
+
if (iss !== void 0 || metadata?.authorization_response_iss_parameter_supported) {
|
|
6174
|
+
if (iss !== metadata?.issuer)
|
|
6175
|
+
throw new OAuthIssuerMismatchError(metadata?.issuer, iss);
|
|
6176
|
+
}
|
|
6177
|
+
const tokens = await exchangeAuthorizationCode(discovered.authorizationServerUrl, {
|
|
6178
|
+
...tokenOptions,
|
|
6179
|
+
code: options.authorizationCode,
|
|
6180
|
+
codeVerifier: await provider.codeVerifier(),
|
|
6181
|
+
redirectUrl
|
|
6182
|
+
});
|
|
6183
|
+
await provider.saveTokens(withScope(tokens, scope));
|
|
6184
|
+
return "AUTHORIZED";
|
|
6185
|
+
}
|
|
6186
|
+
const existing = options.skipRefresh ? void 0 : await provider.tokens();
|
|
6187
|
+
if (existing?.refresh_token) {
|
|
6188
|
+
try {
|
|
6189
|
+
const tokens = await refreshAuthorization(discovered.authorizationServerUrl, {
|
|
6190
|
+
...tokenOptions,
|
|
6191
|
+
refreshToken: existing.refresh_token
|
|
6192
|
+
});
|
|
6193
|
+
await provider.saveTokens(withScope(tokens, existing.scope));
|
|
6194
|
+
return "AUTHORIZED";
|
|
6195
|
+
} catch (error) {
|
|
6196
|
+
if (options.signal?.aborted || error instanceof OAuthInsecureEndpointError)
|
|
6197
|
+
throw error;
|
|
6198
|
+
if (error instanceof OAuthError && error.code !== "server_error")
|
|
6199
|
+
throw error;
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
const state = await provider.state?.();
|
|
6203
|
+
const authorization = await startAuthorization(discovered.authorizationServerUrl, {
|
|
6204
|
+
metadata,
|
|
6205
|
+
clientInformation: client,
|
|
6206
|
+
redirectUrl,
|
|
6207
|
+
scope,
|
|
6208
|
+
state,
|
|
6209
|
+
resource
|
|
6210
|
+
});
|
|
6211
|
+
await provider.saveCodeVerifier(authorization.codeVerifier);
|
|
6212
|
+
await provider.redirectToAuthorization(authorization.authorizationUrl);
|
|
6213
|
+
return "REDIRECT";
|
|
6214
|
+
}
|
|
6215
|
+
async function authorizeMcp(provider, options) {
|
|
6216
|
+
try {
|
|
6217
|
+
return await runFlow(provider, options);
|
|
6218
|
+
} catch (error) {
|
|
6219
|
+
if (error instanceof OAuthError && ["invalid_client", "unauthorized_client"].includes(error.code)) {
|
|
6220
|
+
await provider.invalidateCredentials?.("all");
|
|
6221
|
+
return runFlow(provider, options);
|
|
6222
|
+
}
|
|
6223
|
+
if (error instanceof OAuthError && error.code === "invalid_grant") {
|
|
6224
|
+
await provider.invalidateCredentials?.("tokens");
|
|
6225
|
+
return runFlow(provider, options);
|
|
6226
|
+
}
|
|
6227
|
+
throw error;
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
|
|
6231
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/provider.js
|
|
6232
|
+
var MemoryOAuthStateStore = class {
|
|
6233
|
+
value;
|
|
6234
|
+
load() {
|
|
6235
|
+
return this.value === void 0 ? void 0 : structuredClone(this.value);
|
|
6236
|
+
}
|
|
6237
|
+
save(state) {
|
|
6238
|
+
this.value = structuredClone(state);
|
|
6239
|
+
}
|
|
6240
|
+
};
|
|
6241
|
+
var McpOAuthProvider = class {
|
|
6242
|
+
redirectUrl;
|
|
6243
|
+
clientMetadata;
|
|
6244
|
+
clientMetadataDocument;
|
|
6245
|
+
serverUrl;
|
|
6246
|
+
configuredClient;
|
|
6247
|
+
store;
|
|
6248
|
+
onRedirect;
|
|
6249
|
+
writes = Promise.resolve();
|
|
6250
|
+
constructor(options) {
|
|
6251
|
+
this.serverUrl = String(new URL(options.serverUrl));
|
|
6252
|
+
this.redirectUrl = String(options.redirectUrl);
|
|
6253
|
+
this.clientMetadata = {
|
|
6254
|
+
...options.clientMetadata,
|
|
6255
|
+
redirect_uris: options.clientMetadata.redirect_uris ?? [this.redirectUrl],
|
|
6256
|
+
grant_types: options.clientMetadata.grant_types ?? ["authorization_code", "refresh_token"],
|
|
6257
|
+
response_types: options.clientMetadata.response_types ?? ["code"],
|
|
6258
|
+
token_endpoint_auth_method: options.clientMetadata.token_endpoint_auth_method ?? (options.clientSecret ? "client_secret_post" : "none")
|
|
6259
|
+
};
|
|
6260
|
+
this.clientMetadataDocument = options.clientMetadataDocument;
|
|
6261
|
+
this.configuredClient = options.clientId ? { client_id: options.clientId, ...options.clientSecret ? { client_secret: options.clientSecret } : {} } : void 0;
|
|
6262
|
+
this.store = options.store ?? new MemoryOAuthStateStore();
|
|
6263
|
+
this.onRedirect = options.onRedirect;
|
|
6264
|
+
}
|
|
6265
|
+
async state() {
|
|
6266
|
+
const existing = (await this.load()).oauthState;
|
|
6267
|
+
if (existing)
|
|
6268
|
+
return existing;
|
|
6269
|
+
const state = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex");
|
|
6270
|
+
await this.update((value) => ({ ...value, oauthState: state }));
|
|
6271
|
+
return state;
|
|
6272
|
+
}
|
|
6273
|
+
async clientInformation() {
|
|
6274
|
+
return this.configuredClient ?? (await this.load()).clientInformation;
|
|
6275
|
+
}
|
|
6276
|
+
async saveClientInformation(information) {
|
|
6277
|
+
if (this.configuredClient)
|
|
6278
|
+
return;
|
|
6279
|
+
await this.update((value) => ({ ...value, clientInformation: information }));
|
|
6280
|
+
}
|
|
6281
|
+
async tokens() {
|
|
6282
|
+
return (await this.load()).tokens;
|
|
6283
|
+
}
|
|
6284
|
+
async saveTokens(tokens) {
|
|
6285
|
+
const expiresAt = tokens.expires_in === void 0 ? void 0 : Date.now() + tokens.expires_in * 1e3;
|
|
6286
|
+
await this.update((value) => {
|
|
6287
|
+
const next = { ...value, tokens };
|
|
6288
|
+
if (expiresAt === void 0)
|
|
6289
|
+
delete next.tokensExpireAt;
|
|
6290
|
+
else
|
|
6291
|
+
next.tokensExpireAt = expiresAt;
|
|
6292
|
+
return next;
|
|
6293
|
+
});
|
|
6294
|
+
}
|
|
6295
|
+
async redirectToAuthorization(url) {
|
|
6296
|
+
await this.onRedirect(url);
|
|
6297
|
+
}
|
|
6298
|
+
async saveCodeVerifier(verifier) {
|
|
6299
|
+
await this.update((value) => ({ ...value, codeVerifier: verifier }));
|
|
6300
|
+
}
|
|
6301
|
+
async codeVerifier() {
|
|
6302
|
+
const verifier = (await this.load()).codeVerifier;
|
|
6303
|
+
if (!verifier)
|
|
6304
|
+
throw new Error("No OAuth PKCE code verifier is stored");
|
|
6305
|
+
return verifier;
|
|
6306
|
+
}
|
|
6307
|
+
async invalidateCredentials(kind) {
|
|
6308
|
+
await this.update((value) => {
|
|
6309
|
+
const next = { ...value };
|
|
6310
|
+
if (kind === "all" || kind === "client")
|
|
6311
|
+
delete next.clientInformation;
|
|
6312
|
+
if (kind === "all" || kind === "tokens") {
|
|
6313
|
+
delete next.tokens;
|
|
6314
|
+
delete next.tokensExpireAt;
|
|
6315
|
+
}
|
|
6316
|
+
if (kind === "all" || kind === "verifier")
|
|
6317
|
+
delete next.codeVerifier;
|
|
6318
|
+
if (kind === "all" || kind === "discovery")
|
|
6319
|
+
delete next.discovery;
|
|
6320
|
+
if (kind === "all")
|
|
6321
|
+
delete next.oauthState;
|
|
6322
|
+
return next;
|
|
6323
|
+
});
|
|
6324
|
+
}
|
|
6325
|
+
async saveDiscoveryState(discovery) {
|
|
6326
|
+
await this.update((value) => ({ ...value, discovery }));
|
|
6327
|
+
}
|
|
6328
|
+
async discoveryState() {
|
|
6329
|
+
return (await this.load()).discovery;
|
|
6330
|
+
}
|
|
6331
|
+
async load() {
|
|
6332
|
+
await this.writes;
|
|
6333
|
+
return this.own(await this.store.load());
|
|
6334
|
+
}
|
|
6335
|
+
async update(update) {
|
|
6336
|
+
this.writes = this.writes.then(async () => {
|
|
6337
|
+
await this.store.save(update(this.own(await this.store.load())));
|
|
6338
|
+
});
|
|
6339
|
+
await this.writes;
|
|
6340
|
+
}
|
|
6341
|
+
/** Stored state for another server URL is ignored so credentials never leak across servers. */
|
|
6342
|
+
own(state) {
|
|
6343
|
+
return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };
|
|
6344
|
+
}
|
|
6345
|
+
};
|
|
6346
|
+
|
|
6347
|
+
// src/auth/auth-provider.ts
|
|
6348
|
+
var CIMD_URL = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
6349
|
+
var CLIENT_NAME = "mcpcat";
|
|
6350
|
+
var CALLBACK_HOST = "127.0.0.1";
|
|
6351
|
+
var CALLBACK_PATH = "/callback";
|
|
6352
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
6353
|
+
var REFRESH_SKEW_MS = 3e4;
|
|
6354
|
+
var FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
6355
|
+
function timedFetch(fetchImpl) {
|
|
6356
|
+
return (input, init) => {
|
|
6357
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
6358
|
+
return fetchImpl(input, {
|
|
6359
|
+
...init,
|
|
6360
|
+
signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
|
|
6361
|
+
});
|
|
6362
|
+
};
|
|
6363
|
+
}
|
|
6364
|
+
var SourceBackedStore = class {
|
|
6365
|
+
source;
|
|
6366
|
+
name;
|
|
6367
|
+
serverUrl;
|
|
6368
|
+
constructor(source, name, serverUrl) {
|
|
6369
|
+
this.source = source;
|
|
6370
|
+
this.name = name;
|
|
6371
|
+
this.serverUrl = serverUrl;
|
|
6372
|
+
}
|
|
6373
|
+
async load() {
|
|
6374
|
+
return this.source.load(this.name, this.serverUrl);
|
|
6375
|
+
}
|
|
6376
|
+
async save(state) {
|
|
6377
|
+
await this.source.save(this.name, this.serverUrl, state);
|
|
6378
|
+
}
|
|
6379
|
+
};
|
|
6380
|
+
function cimdFn(redirectUrl) {
|
|
6381
|
+
return (metadata) => {
|
|
6382
|
+
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
6383
|
+
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
6384
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
6385
|
+
return { url: CIMD_URL, redirectUrl };
|
|
6386
|
+
};
|
|
6387
|
+
}
|
|
6388
|
+
var AuthStepUpError = class extends Error {
|
|
6389
|
+
scope;
|
|
6390
|
+
target;
|
|
6391
|
+
constructor(scope, target) {
|
|
6392
|
+
super(scope ? `insufficient_scope: ${scope}` : "insufficient_scope");
|
|
6393
|
+
this.name = "AuthStepUpError";
|
|
6394
|
+
this.scope = scope;
|
|
6395
|
+
this.target = target;
|
|
6396
|
+
}
|
|
6397
|
+
};
|
|
6398
|
+
function createMcpAuthProvider(options) {
|
|
6399
|
+
const { name, serverUrl, source, oauth, platform: platform2 } = options;
|
|
6400
|
+
const store = new SourceBackedStore(source, name, serverUrl);
|
|
6401
|
+
let refreshing;
|
|
6402
|
+
const refresh = async (staleToken) => {
|
|
6403
|
+
refreshing ??= (async () => {
|
|
6404
|
+
const state = await store.load();
|
|
6405
|
+
if (state?.tokens?.access_token !== staleToken) return;
|
|
6406
|
+
if (!state?.tokens?.refresh_token) throw new McpOAuthAuthorizationRequiredError();
|
|
6407
|
+
const registered = state.clientInformation && "redirect_uris" in state.clientInformation ? state.clientInformation.redirect_uris : [];
|
|
6408
|
+
const redirectUrl = registered[0] ?? FALLBACK_REDIRECT_URL;
|
|
6409
|
+
const provider = new McpOAuthProvider({
|
|
6410
|
+
serverUrl,
|
|
6411
|
+
redirectUrl,
|
|
6412
|
+
clientMetadata: { client_name: oauth?.clientName ?? CLIENT_NAME },
|
|
6413
|
+
clientMetadataDocument: oauth?.clientId ? void 0 : cimdFn(redirectUrl),
|
|
6414
|
+
clientId: oauth?.clientId,
|
|
6415
|
+
clientSecret: oauth?.clientSecret,
|
|
6416
|
+
store,
|
|
6417
|
+
onRedirect() {
|
|
6418
|
+
}
|
|
6419
|
+
});
|
|
6420
|
+
const result = await authorizeMcp(provider, {
|
|
6421
|
+
serverUrl,
|
|
6422
|
+
authorizationServerMetadataUrl: oauth?.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : void 0,
|
|
6423
|
+
fetch: timedFetch(platform2.fetch)
|
|
6424
|
+
});
|
|
6425
|
+
if (result === "REDIRECT") throw new McpOAuthAuthorizationRequiredError();
|
|
6426
|
+
})().finally(() => {
|
|
6427
|
+
refreshing = void 0;
|
|
6428
|
+
});
|
|
6429
|
+
return refreshing;
|
|
6430
|
+
};
|
|
6431
|
+
return {
|
|
6432
|
+
async token() {
|
|
6433
|
+
await refreshing?.catch(() => void 0);
|
|
6434
|
+
const state = await store.load();
|
|
6435
|
+
const token = state?.tokens?.access_token;
|
|
6436
|
+
const expired = state?.tokensExpireAt !== void 0 && state.tokensExpireAt - REFRESH_SKEW_MS <= Date.now();
|
|
6437
|
+
if (!expired || !state?.tokens?.refresh_token) return token;
|
|
6438
|
+
await refresh(token).catch(() => void 0);
|
|
6439
|
+
return (await store.load())?.tokens?.access_token;
|
|
6440
|
+
},
|
|
6441
|
+
async onUnauthorized(context) {
|
|
6442
|
+
const challenge = parseWwwAuthenticate(context.response.headers.get("www-authenticate"));
|
|
6443
|
+
if (challenge.error === "insufficient_scope") {
|
|
6444
|
+
throw new AuthStepUpError(challenge.scope, options.name);
|
|
6445
|
+
}
|
|
6446
|
+
await refresh(context.token);
|
|
6447
|
+
}
|
|
6448
|
+
};
|
|
6449
|
+
}
|
|
5372
6450
|
|
|
5373
6451
|
// node_modules/@earendil-works/pi-mcp/dist/client.js
|
|
5374
6452
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -6066,21 +7144,21 @@ async function consumeSseStream(stream, options) {
|
|
|
6066
7144
|
if (line.startsWith(":"))
|
|
6067
7145
|
return;
|
|
6068
7146
|
const colon = line.indexOf(":");
|
|
6069
|
-
const
|
|
7147
|
+
const field2 = colon < 0 ? line : line.slice(0, colon);
|
|
6070
7148
|
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
6071
7149
|
if (value.startsWith(" "))
|
|
6072
7150
|
value = value.slice(1);
|
|
6073
|
-
if (
|
|
7151
|
+
if (field2 === "data") {
|
|
6074
7152
|
dataBytes += Buffer.byteLength(value) + (dataLines.length > 0 ? 1 : 0);
|
|
6075
7153
|
if (dataBytes > maxEventBytes)
|
|
6076
7154
|
throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
|
|
6077
7155
|
dataLines.push(value);
|
|
6078
|
-
} else if (
|
|
7156
|
+
} else if (field2 === "event")
|
|
6079
7157
|
eventName = value;
|
|
6080
|
-
else if (
|
|
7158
|
+
else if (field2 === "id" && !value.includes("\0")) {
|
|
6081
7159
|
eventId = value;
|
|
6082
7160
|
options.onId?.(value);
|
|
6083
|
-
} else if (
|
|
7161
|
+
} else if (field2 === "retry" && /^\d+$/.test(value))
|
|
6084
7162
|
options.onRetry?.(Number(value));
|
|
6085
7163
|
};
|
|
6086
7164
|
try {
|
|
@@ -6143,7 +7221,7 @@ function needsAuthorization(response) {
|
|
|
6143
7221
|
function isTransientStatus(status) {
|
|
6144
7222
|
return status === 408 || status === 429 || status >= 500;
|
|
6145
7223
|
}
|
|
6146
|
-
function
|
|
7224
|
+
function discard2(response) {
|
|
6147
7225
|
return response.body?.cancel().catch(() => {
|
|
6148
7226
|
}) ?? Promise.resolve();
|
|
6149
7227
|
}
|
|
@@ -6194,7 +7272,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6194
7272
|
await this.checkResponse(response);
|
|
6195
7273
|
this.captureSession(response);
|
|
6196
7274
|
if (!isJsonRpcRequest(message)) {
|
|
6197
|
-
await
|
|
7275
|
+
await discard2(response);
|
|
6198
7276
|
if ("method" in message && message.method === "notifications/initialized")
|
|
6199
7277
|
this.startGetStream();
|
|
6200
7278
|
return;
|
|
@@ -6213,7 +7291,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6213
7291
|
void this.consumeResponseStream(response.body, message.id);
|
|
6214
7292
|
return;
|
|
6215
7293
|
}
|
|
6216
|
-
await
|
|
7294
|
+
await discard2(response);
|
|
6217
7295
|
throw new McpHttpError(response.status, `Unsupported MCP response content type: ${type ?? "missing"}`);
|
|
6218
7296
|
}
|
|
6219
7297
|
async close() {
|
|
@@ -6228,7 +7306,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6228
7306
|
headers: this.buildHeaders({}, this.lastToken),
|
|
6229
7307
|
signal: AbortSignal.timeout(1e3)
|
|
6230
7308
|
});
|
|
6231
|
-
await
|
|
7309
|
+
await discard2(response);
|
|
6232
7310
|
} catch {
|
|
6233
7311
|
}
|
|
6234
7312
|
}
|
|
@@ -6259,7 +7337,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6259
7337
|
try {
|
|
6260
7338
|
await onUnauthorized({ response, serverUrl: this.url, fetch: this.fetch, token });
|
|
6261
7339
|
} finally {
|
|
6262
|
-
await
|
|
7340
|
+
await discard2(response);
|
|
6263
7341
|
}
|
|
6264
7342
|
}
|
|
6265
7343
|
}
|
|
@@ -6416,14 +7494,14 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6416
7494
|
}
|
|
6417
7495
|
});
|
|
6418
7496
|
if (response.status === 405) {
|
|
6419
|
-
await
|
|
7497
|
+
await discard2(response);
|
|
6420
7498
|
return void 0;
|
|
6421
7499
|
}
|
|
6422
7500
|
await this.checkResponse(response);
|
|
6423
7501
|
this.captureSession(response);
|
|
6424
7502
|
const type = contentType(response);
|
|
6425
7503
|
if (type !== "text/event-stream" || !response.body) {
|
|
6426
|
-
await
|
|
7504
|
+
await discard2(response);
|
|
6427
7505
|
throw new McpHttpError(response.status, `Unsupported MCP GET response content type: ${type ?? "missing"}`);
|
|
6428
7506
|
}
|
|
6429
7507
|
return response.body;
|
|
@@ -6651,67 +7729,6 @@ var PlatformStdioTransport = class {
|
|
|
6651
7729
|
}
|
|
6652
7730
|
};
|
|
6653
7731
|
|
|
6654
|
-
// src/client/pi-connection.ts
|
|
6655
|
-
async function createPiConnection(options) {
|
|
6656
|
-
const clientOptions = {
|
|
6657
|
-
name: "mcpcat",
|
|
6658
|
-
version: options.version ?? "0.0.0"
|
|
6659
|
-
};
|
|
6660
|
-
const client = new McpClient(clientOptions);
|
|
6661
|
-
let transport;
|
|
6662
|
-
if (options.url) {
|
|
6663
|
-
transport = new StreamableHttpTransport({
|
|
6664
|
-
url: options.url,
|
|
6665
|
-
headers: options.headers,
|
|
6666
|
-
fetch: options.fetchImpl
|
|
6667
|
-
});
|
|
6668
|
-
} else if (options.command) {
|
|
6669
|
-
if (options.platform) {
|
|
6670
|
-
transport = new PlatformStdioTransport({
|
|
6671
|
-
command: options.command,
|
|
6672
|
-
args: options.args,
|
|
6673
|
-
env: options.env,
|
|
6674
|
-
cwd: options.cwd,
|
|
6675
|
-
inheritEnv: true,
|
|
6676
|
-
platform: options.platform
|
|
6677
|
-
});
|
|
6678
|
-
} else {
|
|
6679
|
-
transport = new StdioTransport({
|
|
6680
|
-
command: options.command,
|
|
6681
|
-
args: options.args,
|
|
6682
|
-
env: options.env,
|
|
6683
|
-
cwd: options.cwd,
|
|
6684
|
-
inheritEnv: true,
|
|
6685
|
-
stderr: "pipe"
|
|
6686
|
-
});
|
|
6687
|
-
}
|
|
6688
|
-
} else {
|
|
6689
|
-
throw new Error("PiConnection requires either a url or a command");
|
|
6690
|
-
}
|
|
6691
|
-
const initResult = await client.connect(transport);
|
|
6692
|
-
return {
|
|
6693
|
-
async info() {
|
|
6694
|
-
return {
|
|
6695
|
-
name: initResult.serverInfo?.name,
|
|
6696
|
-
version: initResult.serverInfo?.version,
|
|
6697
|
-
title: initResult.serverInfo?.title,
|
|
6698
|
-
protocolVersion: initResult.protocolVersion,
|
|
6699
|
-
capabilities: initResult.capabilities,
|
|
6700
|
-
instructions: initResult.instructions
|
|
6701
|
-
};
|
|
6702
|
-
},
|
|
6703
|
-
async listTools() {
|
|
6704
|
-
return client.listTools();
|
|
6705
|
-
},
|
|
6706
|
-
async callTool(name, args2) {
|
|
6707
|
-
return client.callTool(name, args2);
|
|
6708
|
-
},
|
|
6709
|
-
async close() {
|
|
6710
|
-
await client.close();
|
|
6711
|
-
}
|
|
6712
|
-
};
|
|
6713
|
-
}
|
|
6714
|
-
|
|
6715
7732
|
// src/client/stateless-connection.ts
|
|
6716
7733
|
var STATELESS_PROTOCOL_VERSION = "2026-07-28";
|
|
6717
7734
|
var StatelessRpcError = class extends Error {
|
|
@@ -6740,6 +7757,22 @@ var InputRequiredError = class extends Error {
|
|
|
6740
7757
|
this.requestJson = requestJson;
|
|
6741
7758
|
}
|
|
6742
7759
|
};
|
|
7760
|
+
var McpTimeoutError2 = class extends Error {
|
|
7761
|
+
constructor(method, ms) {
|
|
7762
|
+
super(`timeout after ${ms}ms waiting for ${method}`);
|
|
7763
|
+
this.name = "McpTimeoutError";
|
|
7764
|
+
}
|
|
7765
|
+
};
|
|
7766
|
+
var McpAuthError = class extends Error {
|
|
7767
|
+
status;
|
|
7768
|
+
target;
|
|
7769
|
+
constructor(status, target) {
|
|
7770
|
+
super(`HTTP ${status}: authentication required. Run: mcpcat login ${target}`);
|
|
7771
|
+
this.name = "McpAuthError";
|
|
7772
|
+
this.status = status;
|
|
7773
|
+
this.target = target;
|
|
7774
|
+
}
|
|
7775
|
+
};
|
|
6743
7776
|
function selectSseFrame(body, targetId) {
|
|
6744
7777
|
const text = new TextDecoder().decode(body);
|
|
6745
7778
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -6770,25 +7803,66 @@ async function createStatelessConnection(options) {
|
|
|
6770
7803
|
headers: extraHeaders,
|
|
6771
7804
|
fetchImpl,
|
|
6772
7805
|
timeoutMs = 6e4,
|
|
6773
|
-
clientInfo = { name: "mcpcat", version: "0.0.0" }
|
|
7806
|
+
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
7807
|
+
signal: externalSignal,
|
|
7808
|
+
verbose
|
|
6774
7809
|
} = options;
|
|
6775
7810
|
let nextId2 = 1;
|
|
6776
7811
|
let negotiatedVersion;
|
|
6777
|
-
|
|
6778
|
-
const
|
|
6779
|
-
const headers = {
|
|
7812
|
+
function buildHeaders(method, params) {
|
|
7813
|
+
const h = {
|
|
6780
7814
|
"Content-Type": "application/json",
|
|
6781
7815
|
Accept: "application/json, text/event-stream",
|
|
6782
7816
|
"Mcp-Method": method,
|
|
6783
7817
|
...extraHeaders
|
|
6784
7818
|
};
|
|
6785
|
-
const
|
|
6786
|
-
if (
|
|
6787
|
-
|
|
7819
|
+
const n = mcpNameForMethod(method, params);
|
|
7820
|
+
if (n) h["Mcp-Name"] = n;
|
|
7821
|
+
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
7822
|
+
return h;
|
|
7823
|
+
}
|
|
7824
|
+
function makeAbort() {
|
|
7825
|
+
const controller = new AbortController();
|
|
7826
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7827
|
+
if (externalSignal) {
|
|
7828
|
+
if (externalSignal.aborted) {
|
|
7829
|
+
clearTimeout(timer);
|
|
7830
|
+
controller.abort();
|
|
7831
|
+
} else {
|
|
7832
|
+
externalSignal.addEventListener(
|
|
7833
|
+
"abort",
|
|
7834
|
+
() => {
|
|
7835
|
+
clearTimeout(timer);
|
|
7836
|
+
controller.abort();
|
|
7837
|
+
},
|
|
7838
|
+
{ once: true }
|
|
7839
|
+
);
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7842
|
+
return { controller, timer };
|
|
7843
|
+
}
|
|
7844
|
+
function logHeaders(dir, entries) {
|
|
7845
|
+
if (!verbose?.enabled) return;
|
|
7846
|
+
for (const [k, v] of entries) {
|
|
7847
|
+
if (dir === ">") verbose.headerOut(k, v);
|
|
7848
|
+
else verbose.headerIn(k, v);
|
|
6788
7849
|
}
|
|
6789
|
-
|
|
6790
|
-
|
|
7850
|
+
}
|
|
7851
|
+
async function handleErrorStatus(res, method) {
|
|
7852
|
+
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
7853
|
+
const text = await res.text();
|
|
7854
|
+
let rpc;
|
|
7855
|
+
try {
|
|
7856
|
+
const p = JSON.parse(text);
|
|
7857
|
+
if (p.error && typeof p.error.code === "number") rpc = p.error;
|
|
7858
|
+
} catch {
|
|
6791
7859
|
}
|
|
7860
|
+
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
7861
|
+
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
7862
|
+
}
|
|
7863
|
+
async function request(method, params) {
|
|
7864
|
+
const id = nextId2++;
|
|
7865
|
+
const headers = buildHeaders(method, params);
|
|
6792
7866
|
const meta = {
|
|
6793
7867
|
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
6794
7868
|
clientInfo
|
|
@@ -6799,8 +7873,11 @@ async function createStatelessConnection(options) {
|
|
|
6799
7873
|
method,
|
|
6800
7874
|
params: { ...params, _meta: meta }
|
|
6801
7875
|
});
|
|
6802
|
-
|
|
6803
|
-
|
|
7876
|
+
if (verbose?.enabled) {
|
|
7877
|
+
verbose.request(method, body);
|
|
7878
|
+
logHeaders(">", Object.entries(headers));
|
|
7879
|
+
}
|
|
7880
|
+
const { controller, timer } = makeAbort();
|
|
6804
7881
|
let res;
|
|
6805
7882
|
try {
|
|
6806
7883
|
res = await fetchImpl(url, {
|
|
@@ -6809,35 +7886,28 @@ async function createStatelessConnection(options) {
|
|
|
6809
7886
|
body,
|
|
6810
7887
|
signal: controller.signal
|
|
6811
7888
|
});
|
|
7889
|
+
} catch (err) {
|
|
7890
|
+
clearTimeout(timer);
|
|
7891
|
+
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
7892
|
+
throw new McpTimeoutError2(method, timeoutMs);
|
|
7893
|
+
}
|
|
7894
|
+
throw err;
|
|
6812
7895
|
} finally {
|
|
6813
7896
|
clearTimeout(timer);
|
|
6814
7897
|
}
|
|
6815
|
-
if (
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
if (res.status >= 400) {
|
|
6819
|
-
const text = await res.text();
|
|
6820
|
-
let rpcError3;
|
|
6821
|
-
try {
|
|
6822
|
-
const parsed = JSON.parse(text);
|
|
6823
|
-
if (parsed.error && typeof parsed.error.code === "number") {
|
|
6824
|
-
rpcError3 = parsed.error;
|
|
6825
|
-
}
|
|
6826
|
-
} catch {
|
|
6827
|
-
}
|
|
6828
|
-
if (rpcError3) {
|
|
6829
|
-
throw new StatelessRpcError(rpcError3.code, rpcError3.message, rpcError3.data);
|
|
6830
|
-
}
|
|
6831
|
-
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
7898
|
+
if (verbose?.enabled) {
|
|
7899
|
+
verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
|
|
7900
|
+
logHeaders("<", res.headers.entries());
|
|
6832
7901
|
}
|
|
7902
|
+
if (res.status >= 400) await handleErrorStatus(res, method);
|
|
6833
7903
|
const ct = res.headers.get("content-type") ?? "";
|
|
6834
7904
|
let frame;
|
|
6835
7905
|
if (ct.toLowerCase().includes("text/event-stream")) {
|
|
6836
|
-
|
|
6837
|
-
frame = selectSseFrame(bytes, id);
|
|
7906
|
+
frame = selectSseFrame(new Uint8Array(await res.arrayBuffer()), id);
|
|
6838
7907
|
} else {
|
|
6839
7908
|
frame = await res.json();
|
|
6840
7909
|
}
|
|
7910
|
+
if (verbose?.enabled) verbose.response(JSON.stringify(frame));
|
|
6841
7911
|
if (frame.error) {
|
|
6842
7912
|
throw new StatelessRpcError(frame.error.code, frame.error.message, frame.error.data);
|
|
6843
7913
|
}
|
|
@@ -6857,28 +7927,26 @@ async function createStatelessConnection(options) {
|
|
|
6857
7927
|
return {
|
|
6858
7928
|
async info() {
|
|
6859
7929
|
if (serverInfoCache) return serverInfoCache;
|
|
6860
|
-
const
|
|
7930
|
+
const r = discoverResult2;
|
|
6861
7931
|
serverInfoCache = {
|
|
6862
|
-
name:
|
|
6863
|
-
version:
|
|
6864
|
-
title:
|
|
7932
|
+
name: r?.serverInfo?.name,
|
|
7933
|
+
version: r?.serverInfo?.version,
|
|
7934
|
+
title: r?.serverInfo?.title,
|
|
6865
7935
|
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
6866
|
-
capabilities:
|
|
6867
|
-
instructions:
|
|
7936
|
+
capabilities: r?.capabilities ?? {},
|
|
7937
|
+
instructions: r?.instructions
|
|
6868
7938
|
};
|
|
6869
7939
|
return serverInfoCache;
|
|
6870
7940
|
},
|
|
6871
7941
|
async listTools() {
|
|
6872
|
-
|
|
6873
|
-
return result?.tools ?? [];
|
|
7942
|
+
return (await request("tools/list", {}))?.tools ?? [];
|
|
6874
7943
|
},
|
|
6875
|
-
async callTool(name, args2) {
|
|
7944
|
+
async callTool(name, args2, _callOptions) {
|
|
6876
7945
|
const result = await request("tools/call", {
|
|
6877
7946
|
name,
|
|
6878
7947
|
arguments: args2 ?? {}
|
|
6879
7948
|
});
|
|
6880
|
-
|
|
6881
|
-
if (resultType === "input_required") {
|
|
7949
|
+
if ((result.resultType ?? "complete") === "input_required") {
|
|
6882
7950
|
throw new InputRequiredError(
|
|
6883
7951
|
JSON.stringify({ name, arguments: args2 ?? {}, result }, null, 2)
|
|
6884
7952
|
);
|
|
@@ -6890,28 +7958,151 @@ async function createStatelessConnection(options) {
|
|
|
6890
7958
|
};
|
|
6891
7959
|
}
|
|
6892
7960
|
|
|
7961
|
+
// src/client/pi-connection.ts
|
|
7962
|
+
function buildStderrHandler(options) {
|
|
7963
|
+
if (!options.verbose?.enabled) return void 0;
|
|
7964
|
+
return (chunk) => {
|
|
7965
|
+
for (const l of chunk.split("\n")) {
|
|
7966
|
+
if (l.trim()) options.verbose.server(l);
|
|
7967
|
+
}
|
|
7968
|
+
};
|
|
7969
|
+
}
|
|
7970
|
+
function buildTransport(options) {
|
|
7971
|
+
if (options.url) {
|
|
7972
|
+
return new StreamableHttpTransport({
|
|
7973
|
+
url: options.url,
|
|
7974
|
+
headers: options.headers,
|
|
7975
|
+
fetch: options.fetchImpl,
|
|
7976
|
+
authProvider: options.authProvider
|
|
7977
|
+
});
|
|
7978
|
+
}
|
|
7979
|
+
if (!options.command) throw new Error("PiConnection requires either a url or a command");
|
|
7980
|
+
if (options.platform) {
|
|
7981
|
+
return new PlatformStdioTransport({
|
|
7982
|
+
command: options.command,
|
|
7983
|
+
args: options.args,
|
|
7984
|
+
env: options.env,
|
|
7985
|
+
cwd: options.cwd,
|
|
7986
|
+
inheritEnv: true,
|
|
7987
|
+
platform: options.platform,
|
|
7988
|
+
onStderr: buildStderrHandler(options)
|
|
7989
|
+
});
|
|
7990
|
+
}
|
|
7991
|
+
return new StdioTransport({
|
|
7992
|
+
command: options.command,
|
|
7993
|
+
args: options.args,
|
|
7994
|
+
env: options.env,
|
|
7995
|
+
cwd: options.cwd,
|
|
7996
|
+
inheritEnv: true,
|
|
7997
|
+
stderr: "pipe",
|
|
7998
|
+
onStderr: buildStderrHandler(options)
|
|
7999
|
+
});
|
|
8000
|
+
}
|
|
8001
|
+
async function createPiConnection(options) {
|
|
8002
|
+
const clientOptions = {
|
|
8003
|
+
name: "mcpcat",
|
|
8004
|
+
version: options.version ?? "0.0.0",
|
|
8005
|
+
requestTimeoutMs: options.timeoutMs
|
|
8006
|
+
};
|
|
8007
|
+
const client = new McpClient(clientOptions);
|
|
8008
|
+
const transport = buildTransport(options);
|
|
8009
|
+
if (options.verbose?.enabled) {
|
|
8010
|
+
transport.onMessage((msg) => {
|
|
8011
|
+
options.verbose.response(JSON.stringify(msg));
|
|
8012
|
+
});
|
|
8013
|
+
}
|
|
8014
|
+
try {
|
|
8015
|
+
let reqOpts2 = function() {
|
|
8016
|
+
const o = {};
|
|
8017
|
+
if (options.signal) o.signal = options.signal;
|
|
8018
|
+
if (options.verbose?.enabled) {
|
|
8019
|
+
o.onProgress = (p) => {
|
|
8020
|
+
options.verbose.progress(`progress: ${p.message ?? ""}`);
|
|
8021
|
+
};
|
|
8022
|
+
}
|
|
8023
|
+
return o;
|
|
8024
|
+
};
|
|
8025
|
+
var reqOpts = reqOpts2;
|
|
8026
|
+
const initResult = await client.connect(transport);
|
|
8027
|
+
return {
|
|
8028
|
+
async info() {
|
|
8029
|
+
return {
|
|
8030
|
+
name: initResult.serverInfo?.name,
|
|
8031
|
+
version: initResult.serverInfo?.version,
|
|
8032
|
+
title: initResult.serverInfo?.title,
|
|
8033
|
+
protocolVersion: initResult.protocolVersion,
|
|
8034
|
+
capabilities: initResult.capabilities,
|
|
8035
|
+
instructions: initResult.instructions
|
|
8036
|
+
};
|
|
8037
|
+
},
|
|
8038
|
+
async listTools() {
|
|
8039
|
+
return client.listTools(reqOpts2());
|
|
8040
|
+
},
|
|
8041
|
+
async callTool(name, args2, callOptions) {
|
|
8042
|
+
const o = reqOpts2();
|
|
8043
|
+
if (callOptions?.onProgress) {
|
|
8044
|
+
const outer = callOptions.onProgress;
|
|
8045
|
+
const inner = o.onProgress;
|
|
8046
|
+
o.onProgress = (p) => {
|
|
8047
|
+
outer(p.progress, p.total, p.message);
|
|
8048
|
+
inner?.(p);
|
|
8049
|
+
};
|
|
8050
|
+
}
|
|
8051
|
+
return client.callTool(name, args2, o);
|
|
8052
|
+
},
|
|
8053
|
+
async close() {
|
|
8054
|
+
await client.close();
|
|
8055
|
+
}
|
|
8056
|
+
};
|
|
8057
|
+
} catch (err) {
|
|
8058
|
+
const msg = err.message ?? "";
|
|
8059
|
+
if (msg.includes("401") || msg.includes("403") || msg.includes("Unauthorized")) {
|
|
8060
|
+
throw new McpAuthError(401, options.url ?? options.command ?? "");
|
|
8061
|
+
}
|
|
8062
|
+
if (msg.includes("timeout") || msg.includes("Timeout") || msg.includes("ETIMEDOUT")) {
|
|
8063
|
+
throw new McpTimeoutError2("connect", options.timeoutMs ?? 6e4);
|
|
8064
|
+
}
|
|
8065
|
+
throw err;
|
|
8066
|
+
}
|
|
8067
|
+
}
|
|
8068
|
+
|
|
6893
8069
|
// src/client/connect.ts
|
|
6894
8070
|
async function connect(platform2, options) {
|
|
8071
|
+
const shared = {
|
|
8072
|
+
version: options.version,
|
|
8073
|
+
timeoutMs: options.timeoutMs,
|
|
8074
|
+
signal: options.signal,
|
|
8075
|
+
verbose: options.verbose,
|
|
8076
|
+
platform: platform2
|
|
8077
|
+
};
|
|
6895
8078
|
if (options.command) {
|
|
6896
8079
|
return createPiConnection({
|
|
6897
8080
|
command: options.command,
|
|
6898
8081
|
args: options.args,
|
|
6899
8082
|
env: options.env,
|
|
6900
8083
|
cwd: options.cwd,
|
|
6901
|
-
|
|
6902
|
-
platform: platform2
|
|
8084
|
+
...shared
|
|
6903
8085
|
});
|
|
6904
8086
|
}
|
|
6905
|
-
if (!options.url)
|
|
6906
|
-
|
|
8087
|
+
if (!options.url) throw new Error("connect requires either a url or a command");
|
|
8088
|
+
if (options.authProvider) {
|
|
8089
|
+
const token = await options.authProvider.token();
|
|
8090
|
+
if (token) {
|
|
8091
|
+
return createPiConnection({
|
|
8092
|
+
url: options.url,
|
|
8093
|
+
headers: options.headers,
|
|
8094
|
+
fetchImpl: platform2.fetch,
|
|
8095
|
+
authProvider: options.authProvider,
|
|
8096
|
+
...shared
|
|
8097
|
+
});
|
|
8098
|
+
}
|
|
6907
8099
|
}
|
|
6908
8100
|
if (options.protocolVersion && !options.protocolVersion.startsWith("2026")) {
|
|
6909
8101
|
return createPiConnection({
|
|
6910
8102
|
url: options.url,
|
|
6911
8103
|
headers: options.headers,
|
|
6912
|
-
version: options.version,
|
|
6913
8104
|
fetchImpl: platform2.fetch,
|
|
6914
|
-
|
|
8105
|
+
...shared
|
|
6915
8106
|
});
|
|
6916
8107
|
}
|
|
6917
8108
|
try {
|
|
@@ -6919,20 +8110,21 @@ async function connect(platform2, options) {
|
|
|
6919
8110
|
url: options.url,
|
|
6920
8111
|
headers: options.headers,
|
|
6921
8112
|
fetchImpl: platform2.fetch,
|
|
6922
|
-
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" }
|
|
8113
|
+
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
|
|
8114
|
+
timeoutMs: options.timeoutMs,
|
|
8115
|
+
signal: options.signal,
|
|
8116
|
+
verbose: options.verbose
|
|
6923
8117
|
});
|
|
6924
8118
|
} catch (err) {
|
|
6925
8119
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
6926
|
-
if (options.protocolVersion?.startsWith("2026"))
|
|
6927
|
-
throw err;
|
|
6928
|
-
}
|
|
8120
|
+
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
6929
8121
|
if (isMethodNotFound) {
|
|
6930
8122
|
return createPiConnection({
|
|
6931
8123
|
url: options.url,
|
|
6932
8124
|
headers: options.headers,
|
|
6933
|
-
version: options.version,
|
|
6934
8125
|
fetchImpl: platform2.fetch,
|
|
6935
|
-
|
|
8126
|
+
authProvider: options.authProvider,
|
|
8127
|
+
...shared
|
|
6936
8128
|
});
|
|
6937
8129
|
}
|
|
6938
8130
|
throw err;
|
|
@@ -7406,8 +8598,8 @@ async function formatOutput(platform2, result, json) {
|
|
|
7406
8598
|
return result.ok ? 0 : isTimeout ? 124 : 1;
|
|
7407
8599
|
}
|
|
7408
8600
|
async function createSandbox(platform2, tools, globals, timeoutMs) {
|
|
7409
|
-
const
|
|
7410
|
-
if (
|
|
8601
|
+
const isKernel2 = platform2.packageDir !== void 0;
|
|
8602
|
+
if (isKernel2) {
|
|
7411
8603
|
const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
|
|
7412
8604
|
const wasm = await loadWasmKernel(platform2);
|
|
7413
8605
|
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
|
|
@@ -7484,6 +8676,28 @@ async function readStream(stream) {
|
|
|
7484
8676
|
return new TextDecoder().decode(merged);
|
|
7485
8677
|
}
|
|
7486
8678
|
|
|
8679
|
+
// src/config/expand.ts
|
|
8680
|
+
var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
8681
|
+
var PLACEHOLDER_TEST = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
|
|
8682
|
+
function containsPlaceholders(value) {
|
|
8683
|
+
return PLACEHOLDER_TEST.test(value);
|
|
8684
|
+
}
|
|
8685
|
+
function expandPlaceholders(value, env) {
|
|
8686
|
+
return value.replace(PLACEHOLDER, (_, name) => env[name] ?? "");
|
|
8687
|
+
}
|
|
8688
|
+
function expandPlaceholdersStrict(value, env) {
|
|
8689
|
+
let hasUndefined = false;
|
|
8690
|
+
const result = value.replace(PLACEHOLDER, (_, name) => {
|
|
8691
|
+
if (env[name] === void 0) {
|
|
8692
|
+
hasUndefined = true;
|
|
8693
|
+
return "";
|
|
8694
|
+
}
|
|
8695
|
+
return env[name];
|
|
8696
|
+
});
|
|
8697
|
+
if (hasUndefined) return void 0;
|
|
8698
|
+
return result;
|
|
8699
|
+
}
|
|
8700
|
+
|
|
7487
8701
|
// src/config/parse.ts
|
|
7488
8702
|
var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
7489
8703
|
var EXPOSURES = ["codemode", "deferred", "direct", "hidden"];
|
|
@@ -7510,8 +8724,8 @@ function resolveAliases(value) {
|
|
|
7510
8724
|
}
|
|
7511
8725
|
return out;
|
|
7512
8726
|
}
|
|
7513
|
-
function fail(server, message,
|
|
7514
|
-
return { server, ...
|
|
8727
|
+
function fail(server, message, field2) {
|
|
8728
|
+
return { server, ...field2 ? { field: field2 } : {}, message };
|
|
7515
8729
|
}
|
|
7516
8730
|
function validateCommon(name, value) {
|
|
7517
8731
|
const exposures = EXPOSURES.map((e) => `"${e}"`).join(", ");
|
|
@@ -8720,23 +9934,98 @@ async function runDiscover(platform2, args2) {
|
|
|
8720
9934
|
return 0;
|
|
8721
9935
|
}
|
|
8722
9936
|
|
|
8723
|
-
// src/
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
return
|
|
9937
|
+
// src/render/result.ts
|
|
9938
|
+
function formatBytes(n) {
|
|
9939
|
+
if (n < 1024) return `${n} bytes`;
|
|
9940
|
+
const kb = n / 1024;
|
|
9941
|
+
if (kb < 1024) return `${kb.toFixed(1)} KB`;
|
|
9942
|
+
return `${(kb / 1024).toFixed(1)} MB`;
|
|
9943
|
+
}
|
|
9944
|
+
function truncateMiddle(text, max) {
|
|
9945
|
+
if (text.length <= max) return text;
|
|
9946
|
+
const half = Math.floor((max - 5) / 2);
|
|
9947
|
+
return `${text.slice(0, half)}
|
|
9948
|
+
[\u2026]
|
|
9949
|
+
${text.slice(-half)}`;
|
|
9950
|
+
}
|
|
9951
|
+
function renderContentBlock(block) {
|
|
9952
|
+
if (block.type === "text") return block.text;
|
|
9953
|
+
if (block.type === "image") {
|
|
9954
|
+
const size = formatBytes(Math.ceil(block.data.length * 3 / 4));
|
|
9955
|
+
return `[image: ${block.mimeType}, ${size}]`;
|
|
9956
|
+
}
|
|
9957
|
+
if (block.type === "audio") {
|
|
9958
|
+
const audioBlock = block;
|
|
9959
|
+
const size = formatBytes(Math.ceil(audioBlock.data.length * 3 / 4));
|
|
9960
|
+
return `[audio: ${audioBlock.mimeType}, ${size}]`;
|
|
9961
|
+
}
|
|
9962
|
+
if (block.type === "resource_link") return `[resource: ${block.uri}]`;
|
|
9963
|
+
if (block.type === "resource") {
|
|
9964
|
+
return "text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`;
|
|
9965
|
+
}
|
|
9966
|
+
return void 0;
|
|
8728
9967
|
}
|
|
8729
|
-
function
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
9968
|
+
function renderCallResultText(result, options) {
|
|
9969
|
+
const parts = [];
|
|
9970
|
+
if (result.content) {
|
|
9971
|
+
for (const block of result.content) {
|
|
9972
|
+
const rendered = renderContentBlock(block);
|
|
9973
|
+
if (rendered !== void 0) parts.push(rendered);
|
|
8735
9974
|
}
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
9975
|
+
}
|
|
9976
|
+
if (parts.length === 0 && result.structuredContent !== void 0) {
|
|
9977
|
+
parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
9978
|
+
}
|
|
9979
|
+
let text = parts.join("\n");
|
|
9980
|
+
if (options?.maxChars && text.length > options.maxChars) {
|
|
9981
|
+
text = truncateMiddle(text, options.maxChars);
|
|
9982
|
+
}
|
|
9983
|
+
return text;
|
|
9984
|
+
}
|
|
9985
|
+
function renderCallResultJson(result) {
|
|
9986
|
+
return JSON.stringify(result, null, 2);
|
|
9987
|
+
}
|
|
9988
|
+
async function renderCallResult(platform2, result, options) {
|
|
9989
|
+
if (options?.json) {
|
|
9990
|
+
const json = renderCallResultJson(result);
|
|
9991
|
+
if (result.isError) {
|
|
9992
|
+
await platform2.write(
|
|
9993
|
+
2,
|
|
9994
|
+
`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
|
|
9995
|
+
`
|
|
9996
|
+
);
|
|
9997
|
+
return 1;
|
|
9998
|
+
}
|
|
9999
|
+
await platform2.write(1, `${json}
|
|
10000
|
+
`);
|
|
10001
|
+
return 0;
|
|
10002
|
+
}
|
|
10003
|
+
const text = renderCallResultText(result, options);
|
|
10004
|
+
if (result.isError) {
|
|
10005
|
+
await platform2.write(2, `${text || "(tool reported error)"}
|
|
10006
|
+
`);
|
|
10007
|
+
return 1;
|
|
10008
|
+
}
|
|
10009
|
+
if (text) {
|
|
10010
|
+
await platform2.write(1, `${text}
|
|
10011
|
+
`);
|
|
10012
|
+
}
|
|
10013
|
+
return 0;
|
|
10014
|
+
}
|
|
10015
|
+
async function renderToolsJson(platform2, tools) {
|
|
10016
|
+
await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
|
|
10017
|
+
`);
|
|
10018
|
+
}
|
|
10019
|
+
async function renderInfoJson(platform2, info) {
|
|
10020
|
+
await platform2.write(1, `${JSON.stringify(info, null, 2)}
|
|
10021
|
+
`);
|
|
10022
|
+
}
|
|
10023
|
+
async function renderErrorJson(platform2, kind, message, code2, data) {
|
|
10024
|
+
const errorObj = { kind, message };
|
|
10025
|
+
if (code2 !== void 0) errorObj.code = code2;
|
|
10026
|
+
if (data !== void 0) errorObj.data = data;
|
|
10027
|
+
await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
|
|
10028
|
+
`);
|
|
8740
10029
|
}
|
|
8741
10030
|
|
|
8742
10031
|
// src/serve/catalog.ts
|
|
@@ -8857,7 +10146,7 @@ function argvForCommand(command, args2) {
|
|
|
8857
10146
|
if (args2.error) return { error: args2.error };
|
|
8858
10147
|
const argv = [...command.path];
|
|
8859
10148
|
for (const positional of command.positionals) {
|
|
8860
|
-
const found = args2.fields.find((
|
|
10149
|
+
const found = args2.fields.find((field2) => field2.name === positional.name);
|
|
8861
10150
|
if (!found) {
|
|
8862
10151
|
if (positional.required) return { error: `missing ${positional.name}` };
|
|
8863
10152
|
continue;
|
|
@@ -8876,7 +10165,7 @@ function argvForCommand(command, args2) {
|
|
|
8876
10165
|
function appendFlags(flags, fields) {
|
|
8877
10166
|
const argv = [];
|
|
8878
10167
|
for (const flag of flags) {
|
|
8879
|
-
const found = fields.find((
|
|
10168
|
+
const found = fields.find((field2) => field2.name === flag.name);
|
|
8880
10169
|
if (!found || found.value === false) continue;
|
|
8881
10170
|
const encoded = encodeFlag(flag, found.value);
|
|
8882
10171
|
if ("error" in encoded) return encoded;
|
|
@@ -10469,120 +11758,592 @@ async function runStdioServer(platform2, tools, call, serverName, version) {
|
|
|
10469
11758
|
} finally {
|
|
10470
11759
|
reader.releaseLock();
|
|
10471
11760
|
}
|
|
10472
|
-
return 0;
|
|
11761
|
+
return 0;
|
|
11762
|
+
}
|
|
11763
|
+
async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer) {
|
|
11764
|
+
if (!platform2.listen) {
|
|
11765
|
+
await platform2.write(2, "mcpcat serve: HTTP transport requires platform.listen\n");
|
|
11766
|
+
return 3;
|
|
11767
|
+
}
|
|
11768
|
+
const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
|
|
11769
|
+
if (!boundLoopback && !resolvedBearer) {
|
|
11770
|
+
await platform2.write(2, "mcpcat serve: --bearer is required when binding off loopback\n");
|
|
11771
|
+
return 2;
|
|
11772
|
+
}
|
|
11773
|
+
const allowedOrigins = new Set(options.allowOrigins);
|
|
11774
|
+
const listener = await platform2.listen({ host: options.host, port: options.port });
|
|
11775
|
+
const actualPort = listener.port;
|
|
11776
|
+
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
11777
|
+
if (options.json) {
|
|
11778
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
11779
|
+
`);
|
|
11780
|
+
}
|
|
11781
|
+
await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
|
|
11782
|
+
`);
|
|
11783
|
+
let shutdownRequested = false;
|
|
11784
|
+
const shutdown = () => {
|
|
11785
|
+
if (shutdownRequested) return;
|
|
11786
|
+
shutdownRequested = true;
|
|
11787
|
+
listener.close();
|
|
11788
|
+
void killInflight();
|
|
11789
|
+
};
|
|
11790
|
+
if (platform2.onSignal) {
|
|
11791
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
11792
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
11793
|
+
}
|
|
11794
|
+
const handler = (request) => handleMcpRequest(
|
|
11795
|
+
request,
|
|
11796
|
+
tools,
|
|
11797
|
+
call,
|
|
11798
|
+
serverName,
|
|
11799
|
+
version,
|
|
11800
|
+
allowedOrigins,
|
|
11801
|
+
boundLoopback,
|
|
11802
|
+
resolvedBearer
|
|
11803
|
+
);
|
|
11804
|
+
try {
|
|
11805
|
+
for await (const connection of listener) {
|
|
11806
|
+
if (shutdownRequested) break;
|
|
11807
|
+
const conn = connection;
|
|
11808
|
+
handleConnection(conn.readable, conn.writable, handler).catch(() => {
|
|
11809
|
+
}).finally(() => {
|
|
11810
|
+
try {
|
|
11811
|
+
conn.close();
|
|
11812
|
+
} catch {
|
|
11813
|
+
}
|
|
11814
|
+
});
|
|
11815
|
+
}
|
|
11816
|
+
} catch {
|
|
11817
|
+
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
11818
|
+
}
|
|
11819
|
+
return 0;
|
|
11820
|
+
}
|
|
11821
|
+
async function handleMcpRequest(request, tools, call, serverName, version, allowedOrigins, boundLoopback, resolvedBearer) {
|
|
11822
|
+
if (resolvedBearer) {
|
|
11823
|
+
const authHeader = request.headers.authorization;
|
|
11824
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
11825
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
11826
|
+
return {
|
|
11827
|
+
status: 401,
|
|
11828
|
+
reason: "Unauthorized",
|
|
11829
|
+
headers: { "content-type": "text/plain" },
|
|
11830
|
+
body: "Unauthorized"
|
|
11831
|
+
};
|
|
11832
|
+
}
|
|
11833
|
+
}
|
|
11834
|
+
const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
|
|
11835
|
+
if (!originCheck.ok) {
|
|
11836
|
+
return {
|
|
11837
|
+
status: 403,
|
|
11838
|
+
reason: "Forbidden",
|
|
11839
|
+
headers: { "content-type": "text/plain" },
|
|
11840
|
+
body: originCheck.reason ?? "Forbidden"
|
|
11841
|
+
};
|
|
11842
|
+
}
|
|
11843
|
+
const hostCheck = checkHost(request.headers.host, boundLoopback);
|
|
11844
|
+
if (!hostCheck.ok) {
|
|
11845
|
+
return {
|
|
11846
|
+
status: 403,
|
|
11847
|
+
reason: "Forbidden",
|
|
11848
|
+
headers: { "content-type": "text/plain" },
|
|
11849
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
11850
|
+
};
|
|
11851
|
+
}
|
|
11852
|
+
if (request.target !== "/mcp") {
|
|
11853
|
+
return {
|
|
11854
|
+
status: 404,
|
|
11855
|
+
reason: "Not Found",
|
|
11856
|
+
headers: { "content-type": "text/plain" },
|
|
11857
|
+
body: "Not Found"
|
|
11858
|
+
};
|
|
11859
|
+
}
|
|
11860
|
+
if (request.method !== "POST") {
|
|
11861
|
+
return {
|
|
11862
|
+
status: 405,
|
|
11863
|
+
reason: "Method Not Allowed",
|
|
11864
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
11865
|
+
body: "Method Not Allowed"
|
|
11866
|
+
};
|
|
11867
|
+
}
|
|
11868
|
+
const outcome = await handleJsonRpc(request.body, tools, call, serverName, version);
|
|
11869
|
+
return {
|
|
11870
|
+
status: outcome.status,
|
|
11871
|
+
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
11872
|
+
headers: { "content-type": outcome.contentType },
|
|
11873
|
+
body: outcome.body
|
|
11874
|
+
};
|
|
11875
|
+
}
|
|
11876
|
+
|
|
11877
|
+
// src/auth/oauth.ts
|
|
11878
|
+
var CALLBACK_HOST2 = "127.0.0.1";
|
|
11879
|
+
var CALLBACK_PATH2 = "/callback";
|
|
11880
|
+
var CIMD_URL2 = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
11881
|
+
var CLIENT_NAME2 = "mcpcat";
|
|
11882
|
+
var REQUEST_TIMEOUT_MS2 = 15e3;
|
|
11883
|
+
function timedFetch2(fetchImpl) {
|
|
11884
|
+
return (input, init) => {
|
|
11885
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS2);
|
|
11886
|
+
return fetchImpl(input, {
|
|
11887
|
+
...init,
|
|
11888
|
+
signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
|
|
11889
|
+
});
|
|
11890
|
+
};
|
|
11891
|
+
}
|
|
11892
|
+
function callbackSettings(oauth) {
|
|
11893
|
+
const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
|
|
11894
|
+
const url = new URL(raw);
|
|
11895
|
+
const address = url.hostname.replace(/^\[|\]$/g, "");
|
|
11896
|
+
const port = url.port ? Number(url.port) : oauth?.callbackPort;
|
|
11897
|
+
let fixedRedirectUrl;
|
|
11898
|
+
if (url.port) fixedRedirectUrl = oauth?.callbackUrl;
|
|
11899
|
+
else if (port !== void 0) {
|
|
11900
|
+
url.port = String(port);
|
|
11901
|
+
fixedRedirectUrl = url.href;
|
|
11902
|
+
}
|
|
11903
|
+
return {
|
|
11904
|
+
host: address === "localhost" ? CALLBACK_HOST2 : address,
|
|
11905
|
+
redirectHost: address,
|
|
11906
|
+
port,
|
|
11907
|
+
path: url.pathname,
|
|
11908
|
+
fixedRedirectUrl
|
|
11909
|
+
};
|
|
11910
|
+
}
|
|
11911
|
+
function cimdForServer(metadata, redirectUrl) {
|
|
11912
|
+
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
11913
|
+
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
11914
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11915
|
+
return {
|
|
11916
|
+
url: CIMD_URL2,
|
|
11917
|
+
redirectUrl
|
|
11918
|
+
};
|
|
11919
|
+
}
|
|
11920
|
+
function buildClientMetadataDocumentFn(oauth, redirectUrl) {
|
|
11921
|
+
if (oauth?.clientRegistration === "dcr") return void 0;
|
|
11922
|
+
if (oauth?.clientId) return void 0;
|
|
11923
|
+
return (metadata) => {
|
|
11924
|
+
if (oauth?.clientRegistration === "cimd") {
|
|
11925
|
+
if (!metadata?.client_id_metadata_document_supported || !metadata.token_endpoint_auth_methods_supported?.includes("none")) {
|
|
11926
|
+
return void 0;
|
|
11927
|
+
}
|
|
11928
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11929
|
+
return { url: CIMD_URL2, redirectUrl };
|
|
11930
|
+
}
|
|
11931
|
+
return cimdForServer(metadata, redirectUrl);
|
|
11932
|
+
};
|
|
11933
|
+
}
|
|
11934
|
+
function createProvider(serverUrl, store, oauth, redirectUrl, onRedirect) {
|
|
11935
|
+
const cimdFn2 = buildClientMetadataDocumentFn(oauth, redirectUrl);
|
|
11936
|
+
return new McpOAuthProvider({
|
|
11937
|
+
serverUrl,
|
|
11938
|
+
redirectUrl,
|
|
11939
|
+
clientMetadata: { client_name: oauth?.clientName ?? CLIENT_NAME2 },
|
|
11940
|
+
clientMetadataDocument: cimdFn2,
|
|
11941
|
+
clientId: oauth?.clientId,
|
|
11942
|
+
clientSecret: oauth?.clientSecret,
|
|
11943
|
+
store,
|
|
11944
|
+
onRedirect
|
|
11945
|
+
});
|
|
11946
|
+
}
|
|
11947
|
+
var SourceBackedStateStore = class {
|
|
11948
|
+
source;
|
|
11949
|
+
name;
|
|
11950
|
+
serverUrl;
|
|
11951
|
+
constructor(source, name, serverUrl) {
|
|
11952
|
+
this.source = source;
|
|
11953
|
+
this.name = name;
|
|
11954
|
+
this.serverUrl = serverUrl;
|
|
11955
|
+
}
|
|
11956
|
+
async load() {
|
|
11957
|
+
return this.source.load(this.name, this.serverUrl);
|
|
11958
|
+
}
|
|
11959
|
+
async save(state) {
|
|
11960
|
+
await this.source.save(this.name, this.serverUrl, state);
|
|
11961
|
+
}
|
|
11962
|
+
};
|
|
11963
|
+
var SignInCancelledError = class extends Error {
|
|
11964
|
+
constructor() {
|
|
11965
|
+
super("Sign-in cancelled");
|
|
11966
|
+
this.name = "SignInCancelledError";
|
|
11967
|
+
}
|
|
11968
|
+
};
|
|
11969
|
+
function responseFromRedirectUrl(input, state, redirectUrl) {
|
|
11970
|
+
let url;
|
|
11971
|
+
try {
|
|
11972
|
+
url = new URL(input.trim());
|
|
11973
|
+
} catch {
|
|
11974
|
+
throw new Error("Expected the full redirect URL from the browser address bar");
|
|
11975
|
+
}
|
|
11976
|
+
if (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {
|
|
11977
|
+
throw new Error("The redirect URL does not match this sign-in's redirect URI");
|
|
11978
|
+
}
|
|
11979
|
+
const error = url.searchParams.get("error");
|
|
11980
|
+
if (error) throw new Error(url.searchParams.get("error_description") ?? error);
|
|
11981
|
+
if (url.searchParams.get("state") !== state) {
|
|
11982
|
+
throw new Error("The redirect URL belongs to a different sign-in");
|
|
11983
|
+
}
|
|
11984
|
+
const code2 = url.searchParams.get("code");
|
|
11985
|
+
if (!code2) throw new Error("The redirect URL does not contain an authorization code");
|
|
11986
|
+
return { code: code2, iss: url.searchParams.get("iss") ?? void 0 };
|
|
11987
|
+
}
|
|
11988
|
+
async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt, signal) {
|
|
11989
|
+
const controller = new AbortController();
|
|
11990
|
+
const promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
11991
|
+
const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
|
|
11992
|
+
const fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {
|
|
11993
|
+
if (!input?.trim()) throw new SignInCancelledError();
|
|
11994
|
+
return responseFromRedirectUrl(input, state, redirectUrl);
|
|
11995
|
+
});
|
|
11996
|
+
try {
|
|
11997
|
+
return await Promise.race([fromBrowser, fromUser]);
|
|
11998
|
+
} finally {
|
|
11999
|
+
controller.abort();
|
|
12000
|
+
fromBrowser.catch(() => void 0);
|
|
12001
|
+
fromUser.catch(() => void 0);
|
|
12002
|
+
}
|
|
12003
|
+
}
|
|
12004
|
+
function mergeScopes(...scopes) {
|
|
12005
|
+
const merged = [...new Set(scopes.flatMap((s) => s?.split(/\s+/).filter(Boolean) ?? []))];
|
|
12006
|
+
return merged.length > 0 ? merged.join(" ") : void 0;
|
|
12007
|
+
}
|
|
12008
|
+
async function signIn(options) {
|
|
12009
|
+
const { name, serverUrl, source, oauth, prompt, platform: platform2, signal } = options;
|
|
12010
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
12011
|
+
const store = new SourceBackedStateStore(source, name, serverUrl);
|
|
12012
|
+
const stored = await store.load();
|
|
12013
|
+
const cbSettings = callbackSettings(oauth);
|
|
12014
|
+
const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
|
|
12015
|
+
const preferredPort = cbSettings.port ?? (registered[0] ? Number(new URL(registered[0]).port) || void 0 : void 0);
|
|
12016
|
+
const callback = await OAuthCallbackServer.listen({
|
|
12017
|
+
host: cbSettings.host,
|
|
12018
|
+
redirectHost: cbSettings.redirectHost,
|
|
12019
|
+
port: preferredPort ?? 0,
|
|
12020
|
+
path: cbSettings.path
|
|
12021
|
+
});
|
|
12022
|
+
const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
|
|
12023
|
+
try {
|
|
12024
|
+
if (stored) {
|
|
12025
|
+
const next = { ...stored };
|
|
12026
|
+
delete next.oauthState;
|
|
12027
|
+
const hasCimd = buildClientMetadataDocumentFn(oauth, redirectUrl) !== void 0;
|
|
12028
|
+
const keepClient = oauth?.clientId || (hasCimd ? !stored.clientInformation : registered.includes(redirectUrl));
|
|
12029
|
+
if (!keepClient) {
|
|
12030
|
+
delete next.clientInformation;
|
|
12031
|
+
delete next.tokens;
|
|
12032
|
+
delete next.tokensExpireAt;
|
|
12033
|
+
}
|
|
12034
|
+
await store.save(next);
|
|
12035
|
+
}
|
|
12036
|
+
let authorizationUrl;
|
|
12037
|
+
const provider = createProvider(serverUrl, store, oauth, redirectUrl, (url) => {
|
|
12038
|
+
authorizationUrl = url;
|
|
12039
|
+
});
|
|
12040
|
+
const flow = {
|
|
12041
|
+
serverUrl,
|
|
12042
|
+
fetch: timedFetch2(platform2.fetch),
|
|
12043
|
+
signal,
|
|
12044
|
+
authorizationServerMetadataUrl: oauth?.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : void 0,
|
|
12045
|
+
scope: mergeScopes(
|
|
12046
|
+
oauth?.scope,
|
|
12047
|
+
options.stepUp ? stepUpScope(stored?.tokens?.scope, options.scope) : options.scope
|
|
12048
|
+
)
|
|
12049
|
+
};
|
|
12050
|
+
const skipRefresh = options.stepUp === true;
|
|
12051
|
+
if (await authorizeMcp(provider, { ...flow, skipRefresh }) === "AUTHORIZED") return;
|
|
12052
|
+
if (!authorizationUrl) throw new Error("OAuth flow did not produce an authorization URL");
|
|
12053
|
+
const state = await provider.state();
|
|
12054
|
+
const authRedirectUrl = new URL(
|
|
12055
|
+
authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl
|
|
12056
|
+
);
|
|
12057
|
+
prompt.showAuthorizationUrl(authorizationUrl);
|
|
12058
|
+
const { code: code2, iss } = await waitForAuthorizationResponse(
|
|
12059
|
+
callback,
|
|
12060
|
+
state,
|
|
12061
|
+
authRedirectUrl,
|
|
12062
|
+
prompt,
|
|
12063
|
+
signal
|
|
12064
|
+
);
|
|
12065
|
+
await authorizeMcp(provider, { ...flow, authorizationCode: code2, iss });
|
|
12066
|
+
} catch (err) {
|
|
12067
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
12068
|
+
throw err;
|
|
12069
|
+
} finally {
|
|
12070
|
+
await callback.close();
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
|
|
12074
|
+
// src/auth/token-source.ts
|
|
12075
|
+
function mcpNamespace(server) {
|
|
12076
|
+
return `mcp__${server.replace(/-/g, "_")}`;
|
|
12077
|
+
}
|
|
12078
|
+
function storeKey(name, serverUrl) {
|
|
12079
|
+
const ns = mcpNamespace(name);
|
|
12080
|
+
const url = String(new URL(serverUrl));
|
|
12081
|
+
return `${ns}|${url}`;
|
|
12082
|
+
}
|
|
12083
|
+
function isKernel(platform2) {
|
|
12084
|
+
return platform2.kind === "kernel";
|
|
12085
|
+
}
|
|
12086
|
+
|
|
12087
|
+
// src/auth/pi-store.ts
|
|
12088
|
+
var AUTH_FILE = "mcp-auth.json";
|
|
12089
|
+
var FILE_MODE = 384;
|
|
12090
|
+
function parseStates(raw) {
|
|
12091
|
+
if (!raw?.trim()) return {};
|
|
12092
|
+
const parsed = JSON.parse(raw);
|
|
12093
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
12094
|
+
}
|
|
12095
|
+
function serializeStates(states) {
|
|
12096
|
+
return `${JSON.stringify(states, null, 2)}
|
|
12097
|
+
`;
|
|
12098
|
+
}
|
|
12099
|
+
function legacyKey(serverUrl) {
|
|
12100
|
+
return String(new URL(serverUrl));
|
|
12101
|
+
}
|
|
12102
|
+
var PiFileTokenSource = class {
|
|
12103
|
+
platform;
|
|
12104
|
+
authPath;
|
|
12105
|
+
dirPath;
|
|
12106
|
+
constructor(platform2) {
|
|
12107
|
+
this.platform = platform2;
|
|
12108
|
+
this.dirPath = userConfigDir(platform2);
|
|
12109
|
+
this.authPath = `${this.dirPath}/${AUTH_FILE}`;
|
|
12110
|
+
}
|
|
12111
|
+
get path() {
|
|
12112
|
+
return this.authPath;
|
|
12113
|
+
}
|
|
12114
|
+
async getToken(name, serverUrl) {
|
|
12115
|
+
const state = await this.load(name, serverUrl);
|
|
12116
|
+
if (!state?.tokens?.access_token) return void 0;
|
|
12117
|
+
return {
|
|
12118
|
+
accessToken: state.tokens.access_token,
|
|
12119
|
+
expiresAt: state.tokensExpireAt
|
|
12120
|
+
};
|
|
12121
|
+
}
|
|
12122
|
+
async load(name, serverUrl) {
|
|
12123
|
+
const key = storeKey(name, serverUrl);
|
|
12124
|
+
const legacy = legacyKey(serverUrl);
|
|
12125
|
+
const release = await this.acquireLock();
|
|
12126
|
+
try {
|
|
12127
|
+
const raw = await this.readRaw();
|
|
12128
|
+
const states = parseStates(raw);
|
|
12129
|
+
if (states[key]) return states[key];
|
|
12130
|
+
if (states[legacy]) {
|
|
12131
|
+
states[key] = states[legacy];
|
|
12132
|
+
delete states[legacy];
|
|
12133
|
+
await this.writeStates(states);
|
|
12134
|
+
return states[key];
|
|
12135
|
+
}
|
|
12136
|
+
return void 0;
|
|
12137
|
+
} finally {
|
|
12138
|
+
await release();
|
|
12139
|
+
}
|
|
12140
|
+
}
|
|
12141
|
+
async save(name, serverUrl, state) {
|
|
12142
|
+
const key = storeKey(name, serverUrl);
|
|
12143
|
+
const release = await this.acquireLock();
|
|
12144
|
+
try {
|
|
12145
|
+
const raw = await this.readRaw();
|
|
12146
|
+
const states = parseStates(raw);
|
|
12147
|
+
states[key] = state;
|
|
12148
|
+
await this.writeStates(states);
|
|
12149
|
+
} finally {
|
|
12150
|
+
await release();
|
|
12151
|
+
}
|
|
12152
|
+
}
|
|
12153
|
+
async remove(name, serverUrl) {
|
|
12154
|
+
const key = storeKey(name, serverUrl);
|
|
12155
|
+
const legacy = legacyKey(serverUrl);
|
|
12156
|
+
const release = await this.acquireLock();
|
|
12157
|
+
try {
|
|
12158
|
+
const raw = await this.readRaw();
|
|
12159
|
+
const states = parseStates(raw);
|
|
12160
|
+
const stored = key in states ? key : legacy in states ? legacy : void 0;
|
|
12161
|
+
if (!stored) return false;
|
|
12162
|
+
delete states[stored];
|
|
12163
|
+
await this.writeStates(states);
|
|
12164
|
+
return true;
|
|
12165
|
+
} finally {
|
|
12166
|
+
await release();
|
|
12167
|
+
}
|
|
12168
|
+
}
|
|
12169
|
+
async acquireLock() {
|
|
12170
|
+
await this.ensureDir();
|
|
12171
|
+
return acquireLock(this.platform.fs, this.authPath);
|
|
12172
|
+
}
|
|
12173
|
+
async ensureDir() {
|
|
12174
|
+
try {
|
|
12175
|
+
await this.platform.fs.mkdir(this.dirPath);
|
|
12176
|
+
} catch {
|
|
12177
|
+
}
|
|
12178
|
+
}
|
|
12179
|
+
async readRaw() {
|
|
12180
|
+
try {
|
|
12181
|
+
const data = await this.platform.fs.readFile(this.authPath);
|
|
12182
|
+
return new TextDecoder().decode(data);
|
|
12183
|
+
} catch {
|
|
12184
|
+
return void 0;
|
|
12185
|
+
}
|
|
12186
|
+
}
|
|
12187
|
+
async writeStates(states) {
|
|
12188
|
+
await this.platform.fs.writeFile(this.authPath, serializeStates(states), { mode: FILE_MODE });
|
|
12189
|
+
}
|
|
12190
|
+
};
|
|
12191
|
+
|
|
12192
|
+
// src/cli/auth-verbs.ts
|
|
12193
|
+
function createTokenSource(platform2) {
|
|
12194
|
+
return new PiFileTokenSource(platform2);
|
|
12195
|
+
}
|
|
12196
|
+
function resolveServerName(target) {
|
|
12197
|
+
try {
|
|
12198
|
+
const u = new URL(target.startsWith("http") ? target : `https://${target}`);
|
|
12199
|
+
if (target.includes(".") || target.includes(":")) {
|
|
12200
|
+
return { name: `url:${u.origin}${u.pathname}`, isUrl: true };
|
|
12201
|
+
}
|
|
12202
|
+
} catch {
|
|
12203
|
+
}
|
|
12204
|
+
return { name: target, isUrl: false };
|
|
10473
12205
|
}
|
|
10474
|
-
async function
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
12206
|
+
async function resolveLoginTarget(platform2, target) {
|
|
12207
|
+
const config = await MergedConfigSource.load(platform2);
|
|
12208
|
+
const entry = config.entry(target);
|
|
12209
|
+
if (entry && "url" in entry.config && entry.config.url) {
|
|
12210
|
+
const httpConfig = entry.config;
|
|
12211
|
+
return {
|
|
12212
|
+
name: target,
|
|
12213
|
+
serverUrl: httpConfig.url,
|
|
12214
|
+
oauth: httpConfig.oauth
|
|
12215
|
+
};
|
|
10478
12216
|
}
|
|
10479
|
-
const
|
|
10480
|
-
if (
|
|
10481
|
-
|
|
10482
|
-
return
|
|
12217
|
+
const resolved = resolveServerName(target);
|
|
12218
|
+
if (resolved.isUrl) {
|
|
12219
|
+
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
12220
|
+
return { name: resolved.name, serverUrl: url, oauth: void 0 };
|
|
10483
12221
|
}
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
12222
|
+
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
12223
|
+
}
|
|
12224
|
+
async function runLogin(platform2, args2, source) {
|
|
12225
|
+
if (isKernel(platform2)) {
|
|
12226
|
+
await platform2.write(
|
|
12227
|
+
2,
|
|
12228
|
+
"mcpcat: login is not available in seven. Use the agent's MCP sign-in in Settings, or run mcpcat login on the host.\n"
|
|
12229
|
+
);
|
|
12230
|
+
return 4;
|
|
10491
12231
|
}
|
|
10492
|
-
|
|
12232
|
+
const noBrowser = args2.includes("--no-browser");
|
|
12233
|
+
const positional = args2.filter((a) => !a.startsWith("-"));
|
|
12234
|
+
if (positional.length === 0) {
|
|
12235
|
+
await platform2.write(2, "mcpcat: login requires a target\n");
|
|
12236
|
+
return 2;
|
|
12237
|
+
}
|
|
12238
|
+
const target = positional[0];
|
|
12239
|
+
let resolved;
|
|
12240
|
+
try {
|
|
12241
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
12242
|
+
} catch (err) {
|
|
12243
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
10493
12244
|
`);
|
|
10494
|
-
|
|
10495
|
-
const shutdown = () => {
|
|
10496
|
-
if (shutdownRequested) return;
|
|
10497
|
-
shutdownRequested = true;
|
|
10498
|
-
listener.close();
|
|
10499
|
-
void killInflight();
|
|
10500
|
-
};
|
|
10501
|
-
if (platform2.onSignal) {
|
|
10502
|
-
platform2.onSignal("SIGINT", shutdown);
|
|
10503
|
-
platform2.onSignal("SIGTERM", shutdown);
|
|
12245
|
+
return 2;
|
|
10504
12246
|
}
|
|
10505
|
-
const handler = (request) => handleMcpRequest(
|
|
10506
|
-
request,
|
|
10507
|
-
tools,
|
|
10508
|
-
call,
|
|
10509
|
-
serverName,
|
|
10510
|
-
version,
|
|
10511
|
-
allowedOrigins,
|
|
10512
|
-
boundLoopback,
|
|
10513
|
-
resolvedBearer
|
|
10514
|
-
);
|
|
10515
12247
|
try {
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
12248
|
+
await signIn({
|
|
12249
|
+
name: resolved.name,
|
|
12250
|
+
serverUrl: resolved.serverUrl,
|
|
12251
|
+
source,
|
|
12252
|
+
oauth: resolved.oauth,
|
|
12253
|
+
platform: platform2,
|
|
12254
|
+
prompt: {
|
|
12255
|
+
showAuthorizationUrl(url) {
|
|
12256
|
+
if (!noBrowser && platform2.openUrl) {
|
|
12257
|
+
platform2.openUrl(url.href).catch(() => {
|
|
12258
|
+
});
|
|
12259
|
+
}
|
|
12260
|
+
void platform2.write(2, `Open this URL to sign in:
|
|
12261
|
+
${url.href}
|
|
12262
|
+
`);
|
|
12263
|
+
if (noBrowser || !platform2.openUrl) {
|
|
12264
|
+
void platform2.write(2, "\nPaste the redirect URL from your browser address bar:\n");
|
|
12265
|
+
}
|
|
12266
|
+
},
|
|
12267
|
+
async promptForRedirectUrl(signal) {
|
|
12268
|
+
if (!noBrowser && platform2.openUrl) {
|
|
12269
|
+
return new Promise((_resolve, reject) => {
|
|
12270
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
12271
|
+
});
|
|
12272
|
+
}
|
|
12273
|
+
const reader = platform2.stdin.getReader();
|
|
12274
|
+
const chunks = [];
|
|
12275
|
+
try {
|
|
12276
|
+
while (true) {
|
|
12277
|
+
const read = reader.read();
|
|
12278
|
+
const result = await Promise.race([
|
|
12279
|
+
read,
|
|
12280
|
+
new Promise((_, reject) => {
|
|
12281
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
12282
|
+
})
|
|
12283
|
+
]);
|
|
12284
|
+
if (result.done) break;
|
|
12285
|
+
chunks.push(result.value);
|
|
12286
|
+
const text = new TextDecoder().decode(concatUint8Arrays(chunks));
|
|
12287
|
+
if (text.includes("\n")) return text.trim();
|
|
12288
|
+
}
|
|
12289
|
+
} finally {
|
|
12290
|
+
reader.releaseLock();
|
|
12291
|
+
}
|
|
12292
|
+
return new TextDecoder().decode(concatUint8Arrays(chunks)).trim() || void 0;
|
|
10524
12293
|
}
|
|
10525
|
-
}
|
|
12294
|
+
}
|
|
12295
|
+
});
|
|
12296
|
+
await platform2.write(1, `Signed in to ${target}
|
|
12297
|
+
`);
|
|
12298
|
+
return 0;
|
|
12299
|
+
} catch (err) {
|
|
12300
|
+
if (err instanceof SignInCancelledError) {
|
|
12301
|
+
await platform2.write(2, "mcpcat: sign-in cancelled\n");
|
|
12302
|
+
return 1;
|
|
10526
12303
|
}
|
|
10527
|
-
|
|
10528
|
-
|
|
12304
|
+
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
12305
|
+
`);
|
|
12306
|
+
return 4;
|
|
10529
12307
|
}
|
|
10530
|
-
return 0;
|
|
10531
12308
|
}
|
|
10532
|
-
async function
|
|
10533
|
-
if (
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
10537
|
-
return {
|
|
10538
|
-
status: 401,
|
|
10539
|
-
reason: "Unauthorized",
|
|
10540
|
-
headers: { "content-type": "text/plain" },
|
|
10541
|
-
body: "Unauthorized"
|
|
10542
|
-
};
|
|
10543
|
-
}
|
|
12309
|
+
async function runLogout(platform2, args2, source) {
|
|
12310
|
+
if (isKernel(platform2)) {
|
|
12311
|
+
await platform2.write(2, "mcpcat: logout is not available in seven.\n");
|
|
12312
|
+
return 4;
|
|
10544
12313
|
}
|
|
10545
|
-
const
|
|
10546
|
-
if (
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
reason: "Forbidden",
|
|
10550
|
-
headers: { "content-type": "text/plain" },
|
|
10551
|
-
body: originCheck.reason ?? "Forbidden"
|
|
10552
|
-
};
|
|
12314
|
+
const positional = args2.filter((a) => !a.startsWith("-"));
|
|
12315
|
+
if (positional.length === 0) {
|
|
12316
|
+
await platform2.write(2, "mcpcat: logout requires a target\n");
|
|
12317
|
+
return 2;
|
|
10553
12318
|
}
|
|
10554
|
-
const
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
12319
|
+
const target = positional[0];
|
|
12320
|
+
let resolved;
|
|
12321
|
+
try {
|
|
12322
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
12323
|
+
} catch (err) {
|
|
12324
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
12325
|
+
`);
|
|
12326
|
+
return 2;
|
|
10562
12327
|
}
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
12328
|
+
const removed = await source.remove(resolved.name, resolved.serverUrl);
|
|
12329
|
+
if (removed) {
|
|
12330
|
+
await platform2.write(1, `Signed out of ${target}
|
|
12331
|
+
`);
|
|
12332
|
+
} else {
|
|
12333
|
+
await platform2.write(1, `No credentials stored for ${target}
|
|
12334
|
+
`);
|
|
10570
12335
|
}
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
12336
|
+
return 0;
|
|
12337
|
+
}
|
|
12338
|
+
function concatUint8Arrays(arrays) {
|
|
12339
|
+
const total = arrays.reduce((s, a) => s + a.length, 0);
|
|
12340
|
+
const result = new Uint8Array(total);
|
|
12341
|
+
let offset = 0;
|
|
12342
|
+
for (const a of arrays) {
|
|
12343
|
+
result.set(a, offset);
|
|
12344
|
+
offset += a.length;
|
|
10578
12345
|
}
|
|
10579
|
-
|
|
10580
|
-
return {
|
|
10581
|
-
status: outcome.status,
|
|
10582
|
-
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
10583
|
-
headers: { "content-type": outcome.contentType },
|
|
10584
|
-
body: outcome.body
|
|
10585
|
-
};
|
|
12346
|
+
return result;
|
|
10586
12347
|
}
|
|
10587
12348
|
|
|
10588
12349
|
// src/config/import.ts
|
|
@@ -11405,6 +13166,91 @@ bridge does not survive a reload; runs in the foreground (start with &)
|
|
|
11405
13166
|
return runServe(platform2, serveOptions, version);
|
|
11406
13167
|
}
|
|
11407
13168
|
|
|
13169
|
+
// src/cli/exit-codes.ts
|
|
13170
|
+
var EXIT_OK = 0;
|
|
13171
|
+
var EXIT_TOOL_ERROR = 1;
|
|
13172
|
+
var EXIT_USAGE = 2;
|
|
13173
|
+
var EXIT_CONNECTION = 3;
|
|
13174
|
+
var EXIT_AUTH = 4;
|
|
13175
|
+
var EXIT_RPC_ERROR = 5;
|
|
13176
|
+
var EXIT_INPUT_REQUIRED = 6;
|
|
13177
|
+
var EXIT_TIMEOUT = 124;
|
|
13178
|
+
var EXIT_INTERRUPTED = 130;
|
|
13179
|
+
var McpcatError = class extends Error {
|
|
13180
|
+
exitCode;
|
|
13181
|
+
constructor(exitCode, message) {
|
|
13182
|
+
super(message);
|
|
13183
|
+
this.name = "McpcatError";
|
|
13184
|
+
this.exitCode = exitCode;
|
|
13185
|
+
}
|
|
13186
|
+
};
|
|
13187
|
+
|
|
13188
|
+
// src/cli/verbose.ts
|
|
13189
|
+
var SENSITIVE_HEADER_RE = /^(authorization|cookie|proxy-authorization)$/i;
|
|
13190
|
+
var SENSITIVE_NAME_RE = /token|key|secret|password|session/i;
|
|
13191
|
+
function isSensitiveHeader(name) {
|
|
13192
|
+
return SENSITIVE_HEADER_RE.test(name) || SENSITIVE_NAME_RE.test(name);
|
|
13193
|
+
}
|
|
13194
|
+
function redact(text, secrets) {
|
|
13195
|
+
let result = text;
|
|
13196
|
+
for (const secret of secrets) {
|
|
13197
|
+
if (secret.length > 0) {
|
|
13198
|
+
while (result.includes(secret)) {
|
|
13199
|
+
result = result.replace(secret, "[REDACTED]");
|
|
13200
|
+
}
|
|
13201
|
+
}
|
|
13202
|
+
}
|
|
13203
|
+
return result;
|
|
13204
|
+
}
|
|
13205
|
+
function createVerboseLogger(platform2, enabled) {
|
|
13206
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
13207
|
+
function write(line) {
|
|
13208
|
+
const safe = redact(line, secrets);
|
|
13209
|
+
platform2.write(2, `${safe}
|
|
13210
|
+
`).catch(() => {
|
|
13211
|
+
});
|
|
13212
|
+
}
|
|
13213
|
+
return {
|
|
13214
|
+
enabled,
|
|
13215
|
+
secrets,
|
|
13216
|
+
request(method, body) {
|
|
13217
|
+
if (!enabled) return;
|
|
13218
|
+
write(`> ${method}`);
|
|
13219
|
+
for (const line of body.split("\n")) {
|
|
13220
|
+
write(`> ${line}`);
|
|
13221
|
+
}
|
|
13222
|
+
},
|
|
13223
|
+
response(body) {
|
|
13224
|
+
if (!enabled) return;
|
|
13225
|
+
for (const line of body.split("\n")) {
|
|
13226
|
+
write(`< ${line}`);
|
|
13227
|
+
}
|
|
13228
|
+
},
|
|
13229
|
+
http(direction, line) {
|
|
13230
|
+
if (!enabled) return;
|
|
13231
|
+
write(`${direction} ${line}`);
|
|
13232
|
+
},
|
|
13233
|
+
server(line) {
|
|
13234
|
+
if (!enabled) return;
|
|
13235
|
+
write(`[server] ${line}`);
|
|
13236
|
+
},
|
|
13237
|
+
headerOut(name, value) {
|
|
13238
|
+
if (!enabled) return;
|
|
13239
|
+
const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
|
|
13240
|
+
write(`> ${name}: ${display}`);
|
|
13241
|
+
},
|
|
13242
|
+
headerIn(name, value) {
|
|
13243
|
+
if (!enabled) return;
|
|
13244
|
+
const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
|
|
13245
|
+
write(`< ${name}: ${display}`);
|
|
13246
|
+
},
|
|
13247
|
+
progress(message) {
|
|
13248
|
+
if (!enabled) return;
|
|
13249
|
+
write(`* ${message}`);
|
|
13250
|
+
}
|
|
13251
|
+
};
|
|
13252
|
+
}
|
|
13253
|
+
|
|
11408
13254
|
// src/cli/main.ts
|
|
11409
13255
|
var VERBS = [
|
|
11410
13256
|
"info",
|
|
@@ -11443,16 +13289,7 @@ var CONFIG_VERBS = {
|
|
|
11443
13289
|
exposure: true,
|
|
11444
13290
|
import: true
|
|
11445
13291
|
};
|
|
11446
|
-
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set([
|
|
11447
|
-
"resources",
|
|
11448
|
-
"read",
|
|
11449
|
-
"prompts",
|
|
11450
|
-
"prompt",
|
|
11451
|
-
"complete",
|
|
11452
|
-
"task",
|
|
11453
|
-
"login",
|
|
11454
|
-
"logout"
|
|
11455
|
-
]);
|
|
13292
|
+
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["resources", "read", "prompts", "prompt", "complete", "task"]);
|
|
11456
13293
|
var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe", "serve"]);
|
|
11457
13294
|
function helpText(version) {
|
|
11458
13295
|
return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
|
|
@@ -11476,8 +13313,8 @@ Configuration verbs:
|
|
|
11476
13313
|
enable|disable <name> Toggle a server
|
|
11477
13314
|
exposure <name> <mode> Set exposure mode
|
|
11478
13315
|
import <file> Import from other configs
|
|
11479
|
-
login <target> OAuth sign-in
|
|
11480
|
-
logout <target> Remove tokens
|
|
13316
|
+
login <target> OAuth sign-in
|
|
13317
|
+
logout <target> Remove stored tokens
|
|
11481
13318
|
|
|
11482
13319
|
Server and code verbs:
|
|
11483
13320
|
serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
|
|
@@ -11522,6 +13359,8 @@ function parseFlags(args2) {
|
|
|
11522
13359
|
const serverTargets = [];
|
|
11523
13360
|
let allowShell = false;
|
|
11524
13361
|
let timeoutMs;
|
|
13362
|
+
let verbose = false;
|
|
13363
|
+
let silent = false;
|
|
11525
13364
|
const positional = [];
|
|
11526
13365
|
let trailingCommand;
|
|
11527
13366
|
for (let i = 0; i < args2.length; i++) {
|
|
@@ -11558,6 +13397,10 @@ function parseFlags(args2) {
|
|
|
11558
13397
|
serverTargets.push(args2[++i]);
|
|
11559
13398
|
} else if (arg === "--allow-shell") {
|
|
11560
13399
|
allowShell = true;
|
|
13400
|
+
} else if (arg === "-v" || arg === "--verbose") {
|
|
13401
|
+
verbose = true;
|
|
13402
|
+
} else if (arg === "--silent") {
|
|
13403
|
+
silent = true;
|
|
11561
13404
|
} else if ((arg === "-m" || arg === "--timeout") && args2[i + 1]) {
|
|
11562
13405
|
timeoutMs = Number(args2[++i]) * 1e3;
|
|
11563
13406
|
} else if (!arg.startsWith("-")) {
|
|
@@ -11578,6 +13421,8 @@ function parseFlags(args2) {
|
|
|
11578
13421
|
serverTargets,
|
|
11579
13422
|
allowShell,
|
|
11580
13423
|
timeoutMs,
|
|
13424
|
+
verbose,
|
|
13425
|
+
silent,
|
|
11581
13426
|
positional,
|
|
11582
13427
|
trailingCommand
|
|
11583
13428
|
};
|
|
@@ -11626,7 +13471,9 @@ function parseArgs(args2) {
|
|
|
11626
13471
|
inlineSource: flags.inlineSource,
|
|
11627
13472
|
serverTargets: flags.serverTargets,
|
|
11628
13473
|
allowShell: flags.allowShell,
|
|
11629
|
-
timeoutMs: flags.timeoutMs
|
|
13474
|
+
timeoutMs: flags.timeoutMs,
|
|
13475
|
+
verbose: flags.verbose,
|
|
13476
|
+
silent: flags.silent
|
|
11630
13477
|
};
|
|
11631
13478
|
}
|
|
11632
13479
|
function formatToolLine(tool) {
|
|
@@ -11650,26 +13497,6 @@ function formatToolSchema(tool) {
|
|
|
11650
13497
|
}
|
|
11651
13498
|
return lines.join("\n");
|
|
11652
13499
|
}
|
|
11653
|
-
function renderCallResult(result) {
|
|
11654
|
-
const parts = [];
|
|
11655
|
-
if (result.content) {
|
|
11656
|
-
for (const block of result.content) {
|
|
11657
|
-
if (block.type === "text") parts.push(block.text);
|
|
11658
|
-
else if (block.type === "image")
|
|
11659
|
-
parts.push(`[image: ${block.mimeType}, ${Math.ceil(block.data.length * 3 / 4)} bytes]`);
|
|
11660
|
-
else if (block.type === "resource_link") parts.push(`[resource: ${block.uri}]`);
|
|
11661
|
-
else if (block.type === "resource") {
|
|
11662
|
-
parts.push(
|
|
11663
|
-
"text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`
|
|
11664
|
-
);
|
|
11665
|
-
}
|
|
11666
|
-
}
|
|
11667
|
-
}
|
|
11668
|
-
if (parts.length === 0 && result.structuredContent !== void 0) {
|
|
11669
|
-
parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
11670
|
-
}
|
|
11671
|
-
return parts.join("\n");
|
|
11672
|
-
}
|
|
11673
13500
|
var NETWORK_HINT = "connection failed (use slicc-node, slicc-swift, or the browser extension for servers without CORS)";
|
|
11674
13501
|
function transportHint(err) {
|
|
11675
13502
|
if (err instanceof TypeError) {
|
|
@@ -11712,8 +13539,12 @@ async function readDataArg(platform2, dataArg) {
|
|
|
11712
13539
|
}
|
|
11713
13540
|
return JSON.parse(raw);
|
|
11714
13541
|
}
|
|
11715
|
-
async function runInfo(platform2, conn) {
|
|
13542
|
+
async function runInfo(platform2, conn, json) {
|
|
11716
13543
|
const info = await conn.info();
|
|
13544
|
+
if (json) {
|
|
13545
|
+
await renderInfoJson(platform2, info);
|
|
13546
|
+
return EXIT_OK;
|
|
13547
|
+
}
|
|
11717
13548
|
const lines = [];
|
|
11718
13549
|
if (info.name) lines.push(`Server: ${info.name}${info.version ? ` ${info.version}` : ""}`);
|
|
11719
13550
|
if (info.title) lines.push(`Title: ${info.title}`);
|
|
@@ -11730,48 +13561,66 @@ async function runInfo(platform2, conn) {
|
|
|
11730
13561
|
}
|
|
11731
13562
|
await platform2.write(1, `${lines.join("\n")}
|
|
11732
13563
|
`);
|
|
11733
|
-
return
|
|
13564
|
+
return EXIT_OK;
|
|
11734
13565
|
}
|
|
11735
|
-
async function runTools(platform2, conn) {
|
|
13566
|
+
async function runTools(platform2, conn, json) {
|
|
11736
13567
|
const tools = await conn.listTools();
|
|
11737
|
-
if (
|
|
11738
|
-
await platform2
|
|
11739
|
-
|
|
11740
|
-
await platform2.write(1, `${tools.map(formatToolLine).join("\n")}
|
|
11741
|
-
`);
|
|
13568
|
+
if (json) {
|
|
13569
|
+
await renderToolsJson(platform2, tools);
|
|
13570
|
+
return EXIT_OK;
|
|
11742
13571
|
}
|
|
11743
|
-
|
|
13572
|
+
await platform2.write(
|
|
13573
|
+
1,
|
|
13574
|
+
tools.length === 0 ? "(no tools)\n" : `${tools.map(formatToolLine).join("\n")}
|
|
13575
|
+
`
|
|
13576
|
+
);
|
|
13577
|
+
return EXIT_OK;
|
|
11744
13578
|
}
|
|
11745
|
-
async function runCall(platform2, conn, toolName, dataArg) {
|
|
11746
|
-
let
|
|
13579
|
+
async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
13580
|
+
let base = {};
|
|
11747
13581
|
if (dataArg) {
|
|
11748
13582
|
try {
|
|
11749
|
-
|
|
13583
|
+
base = await readDataArg(platform2, dataArg);
|
|
11750
13584
|
} catch (err) {
|
|
11751
|
-
await
|
|
11752
|
-
|
|
11753
|
-
|
|
13585
|
+
await writeErr(
|
|
13586
|
+
platform2,
|
|
13587
|
+
opts.json ?? false,
|
|
13588
|
+
"usage",
|
|
13589
|
+
`invalid -d argument: ${err.message}`
|
|
13590
|
+
);
|
|
13591
|
+
return EXIT_USAGE;
|
|
11754
13592
|
}
|
|
11755
13593
|
}
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
13594
|
+
let fin = base;
|
|
13595
|
+
if (toolArgs.length > 0) {
|
|
13596
|
+
const tools = await conn.listTools();
|
|
13597
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
13598
|
+
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
13599
|
+
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: opts.noValidate });
|
|
13600
|
+
if (!c.ok) {
|
|
13601
|
+
await writeErr(platform2, opts.json ?? false, "usage", c.error);
|
|
13602
|
+
return EXIT_USAGE;
|
|
13603
|
+
}
|
|
13604
|
+
fin = mergeArgs(base, c.value);
|
|
11762
13605
|
}
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
13606
|
+
return renderCallResult(
|
|
13607
|
+
platform2,
|
|
13608
|
+
await conn.callTool(toolName, fin, { onProgress: opts.onProgress }),
|
|
13609
|
+
{ json: opts.json }
|
|
13610
|
+
);
|
|
11766
13611
|
}
|
|
11767
|
-
async function runDescribe(platform2, conn, toolName, showSchema) {
|
|
13612
|
+
async function runDescribe(platform2, conn, toolName, showSchema, json) {
|
|
11768
13613
|
const tools = await conn.listTools();
|
|
11769
13614
|
if (toolName) {
|
|
11770
13615
|
const tool = tools.find((t) => t.name === toolName);
|
|
11771
13616
|
if (!tool) {
|
|
11772
|
-
await platform2
|
|
13617
|
+
await writeErr(platform2, json, "usage", `tool '${toolName}' not found`);
|
|
13618
|
+
return EXIT_USAGE;
|
|
13619
|
+
}
|
|
13620
|
+
if (json) {
|
|
13621
|
+
await platform2.write(1, `${JSON.stringify(tool, null, 2)}
|
|
11773
13622
|
`);
|
|
11774
|
-
return
|
|
13623
|
+
return EXIT_OK;
|
|
11775
13624
|
}
|
|
11776
13625
|
if (showSchema && tool.inputSchema) {
|
|
11777
13626
|
await platform2.write(1, `${JSON.stringify(tool.inputSchema, null, 2)}
|
|
@@ -11781,28 +13630,36 @@ async function runDescribe(platform2, conn, toolName, showSchema) {
|
|
|
11781
13630
|
`);
|
|
11782
13631
|
}
|
|
11783
13632
|
} else {
|
|
13633
|
+
if (json) {
|
|
13634
|
+
await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
|
|
13635
|
+
`);
|
|
13636
|
+
return EXIT_OK;
|
|
13637
|
+
}
|
|
11784
13638
|
for (const tool of tools) {
|
|
11785
13639
|
await platform2.write(1, `${formatToolSchema(tool)}
|
|
11786
13640
|
|
|
11787
13641
|
`);
|
|
11788
13642
|
}
|
|
11789
13643
|
}
|
|
11790
|
-
return
|
|
13644
|
+
return EXIT_OK;
|
|
11791
13645
|
}
|
|
11792
|
-
async function runVerb(platform2, parsed, conn) {
|
|
13646
|
+
async function runVerb(platform2, parsed, conn, onProgress) {
|
|
11793
13647
|
switch (parsed.verb) {
|
|
11794
13648
|
case "info":
|
|
11795
|
-
return runInfo(platform2, conn);
|
|
13649
|
+
return runInfo(platform2, conn, parsed.jsonOutput);
|
|
11796
13650
|
case "tools":
|
|
11797
|
-
return runTools(platform2, conn);
|
|
13651
|
+
return runTools(platform2, conn, parsed.jsonOutput);
|
|
11798
13652
|
case "call":
|
|
11799
|
-
return runCall(platform2, conn, parsed.toolName, parsed.dataArg
|
|
13653
|
+
return runCall(platform2, conn, parsed.toolName, parsed.dataArg, [], {
|
|
13654
|
+
json: parsed.jsonOutput,
|
|
13655
|
+
onProgress
|
|
13656
|
+
});
|
|
11800
13657
|
case "describe":
|
|
11801
|
-
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema);
|
|
13658
|
+
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
|
|
11802
13659
|
default:
|
|
11803
13660
|
await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
|
|
11804
13661
|
`);
|
|
11805
|
-
return
|
|
13662
|
+
return EXIT_USAGE;
|
|
11806
13663
|
}
|
|
11807
13664
|
}
|
|
11808
13665
|
async function probeServer(_name, config, platform2) {
|
|
@@ -12048,10 +13905,18 @@ async function run(platform2, version, args2) {
|
|
|
12048
13905
|
}
|
|
12049
13906
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args2);
|
|
12050
13907
|
if (discResult !== void 0) return discResult;
|
|
13908
|
+
if (parsed.verb === "login") {
|
|
13909
|
+
const source = createTokenSource(platform2);
|
|
13910
|
+
return runLogin(platform2, args2.slice(1), source);
|
|
13911
|
+
}
|
|
13912
|
+
if (parsed.verb === "logout") {
|
|
13913
|
+
const source = createTokenSource(platform2);
|
|
13914
|
+
return runLogout(platform2, args2.slice(1), source);
|
|
13915
|
+
}
|
|
12051
13916
|
if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
|
|
12052
13917
|
await platform2.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
|
|
12053
13918
|
`);
|
|
12054
|
-
return
|
|
13919
|
+
return EXIT_TOOL_ERROR;
|
|
12055
13920
|
}
|
|
12056
13921
|
if (parsed.verb === "serve") {
|
|
12057
13922
|
return dispatchServeVerb(platform2, args2, version);
|
|
@@ -12073,44 +13938,132 @@ async function run(platform2, version, args2) {
|
|
|
12073
13938
|
`);
|
|
12074
13939
|
return 2;
|
|
12075
13940
|
}
|
|
13941
|
+
return runClientVerb(platform2, version, parsed);
|
|
13942
|
+
}
|
|
13943
|
+
function expandAndCollect(headers, env) {
|
|
13944
|
+
const secrets = [];
|
|
13945
|
+
const result = {};
|
|
13946
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
13947
|
+
const e = expandPlaceholders(v, env);
|
|
13948
|
+
result[k] = e;
|
|
13949
|
+
if (e !== v) secrets.push(e);
|
|
13950
|
+
if (v.length > 0) secrets.push(v);
|
|
13951
|
+
}
|
|
13952
|
+
return { expanded: result, secrets };
|
|
13953
|
+
}
|
|
13954
|
+
function classifyError(err) {
|
|
13955
|
+
if (err instanceof InputRequiredError) {
|
|
13956
|
+
return { code: EXIT_INPUT_REQUIRED, kind: "input_required", msg: err.requestJson };
|
|
13957
|
+
}
|
|
13958
|
+
if (err instanceof McpTimeoutError2) {
|
|
13959
|
+
return { code: EXIT_TIMEOUT, kind: "timeout", msg: err.message };
|
|
13960
|
+
}
|
|
13961
|
+
if (err instanceof McpAuthError) {
|
|
13962
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
13963
|
+
}
|
|
13964
|
+
if (err instanceof AuthStepUpError) {
|
|
13965
|
+
const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
|
|
13966
|
+
return {
|
|
13967
|
+
code: EXIT_AUTH,
|
|
13968
|
+
kind: "auth",
|
|
13969
|
+
msg: `insufficient scope. Run: mcpcat login ${err.target ?? "<target>"}${scopeHint}`
|
|
13970
|
+
};
|
|
13971
|
+
}
|
|
13972
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
13973
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
13974
|
+
}
|
|
13975
|
+
if (err instanceof McpcatError) {
|
|
13976
|
+
return { code: err.exitCode, kind: "error", msg: err.message };
|
|
13977
|
+
}
|
|
13978
|
+
const msg = err.message ?? String(err);
|
|
13979
|
+
const name = err.name ?? "";
|
|
13980
|
+
if (name === "McpTimeoutError" || msg.includes("timed out") || msg.includes("timeout") || msg.includes("Timeout")) {
|
|
13981
|
+
return { code: EXIT_TIMEOUT, kind: "timeout", msg };
|
|
13982
|
+
}
|
|
13983
|
+
return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
|
|
13984
|
+
}
|
|
13985
|
+
function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
13986
|
+
if (hasAuthHeader || !url) return void 0;
|
|
13987
|
+
const source = createTokenSource(platform2);
|
|
13988
|
+
const entry = config.entry(target);
|
|
13989
|
+
const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
|
|
13990
|
+
const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
|
|
13991
|
+
return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
|
|
13992
|
+
}
|
|
13993
|
+
async function runClientVerb(platform2, version, parsed) {
|
|
13994
|
+
const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
|
|
13995
|
+
const verbose = createVerboseLogger(platform2, parsed.verbose);
|
|
13996
|
+
for (const s of secrets) verbose.secrets.add(s);
|
|
13997
|
+
const timeoutMs = parsed.timeoutMs;
|
|
13998
|
+
const sigintController = new AbortController();
|
|
13999
|
+
let interrupted = false;
|
|
14000
|
+
platform2.onSignal?.("SIGINT", () => {
|
|
14001
|
+
interrupted = true;
|
|
14002
|
+
sigintController.abort();
|
|
14003
|
+
});
|
|
14004
|
+
const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
|
|
14005
|
+
const onProgress = (progress, total, message) => {
|
|
14006
|
+
if (!showProgress) return;
|
|
14007
|
+
const pct = total ? ` ${Math.round(progress / total * 100)}%` : "";
|
|
14008
|
+
const msg = message ? ` ${message}` : "";
|
|
14009
|
+
platform2.write(2, `* progress:${pct}${msg}
|
|
14010
|
+
`).catch(() => {
|
|
14011
|
+
});
|
|
14012
|
+
};
|
|
12076
14013
|
const config = await MergedConfigSource.load(platform2);
|
|
14014
|
+
const target = parsed.target === "__trailing__" ? "" : parsed.target;
|
|
12077
14015
|
let resolved;
|
|
12078
14016
|
try {
|
|
12079
|
-
resolved = await resolveTarget(
|
|
12080
|
-
parsed.target === "__trailing__" ? "" : parsed.target,
|
|
12081
|
-
parsed.trailingCommand,
|
|
12082
|
-
config
|
|
12083
|
-
);
|
|
14017
|
+
resolved = await resolveTarget(target, parsed.trailingCommand, config);
|
|
12084
14018
|
} catch (err) {
|
|
12085
|
-
await platform2.
|
|
12086
|
-
|
|
12087
|
-
return 2;
|
|
14019
|
+
await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
|
|
14020
|
+
return EXIT_USAGE;
|
|
12088
14021
|
}
|
|
14022
|
+
const hasAuthHeader = "Authorization" in expanded;
|
|
14023
|
+
const authProvider = resolveAuthProvider(platform2, hasAuthHeader, resolved.url, target, config);
|
|
12089
14024
|
let conn;
|
|
12090
14025
|
try {
|
|
12091
14026
|
conn = await connect(platform2, {
|
|
12092
14027
|
...resolved,
|
|
12093
|
-
headers: { ...resolved.headers, ...
|
|
14028
|
+
headers: { ...resolved.headers, ...expanded },
|
|
12094
14029
|
protocolVersion: parsed.protocolVersion,
|
|
12095
|
-
version
|
|
14030
|
+
version,
|
|
14031
|
+
timeoutMs,
|
|
14032
|
+
signal: sigintController.signal,
|
|
14033
|
+
verbose,
|
|
14034
|
+
authProvider
|
|
12096
14035
|
});
|
|
12097
14036
|
} catch (err) {
|
|
14037
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) {
|
|
14038
|
+
const e = classifyError(err);
|
|
14039
|
+
await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
14040
|
+
return EXIT_AUTH;
|
|
14041
|
+
}
|
|
14042
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
14043
|
+
await writeErr(
|
|
14044
|
+
platform2,
|
|
14045
|
+
parsed.jsonOutput,
|
|
14046
|
+
"auth",
|
|
14047
|
+
`unauthorized. Run: mcpcat login ${target}`
|
|
14048
|
+
);
|
|
14049
|
+
return EXIT_AUTH;
|
|
14050
|
+
}
|
|
12098
14051
|
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
12099
|
-
await platform2.
|
|
12100
|
-
|
|
12101
|
-
return 3;
|
|
14052
|
+
await writeErr(platform2, parsed.jsonOutput, "connection", msg);
|
|
14053
|
+
return EXIT_CONNECTION;
|
|
12102
14054
|
}
|
|
12103
14055
|
try {
|
|
12104
|
-
return await runVerb(platform2, parsed, conn);
|
|
14056
|
+
return await runVerb(platform2, parsed, conn, onProgress);
|
|
12105
14057
|
} catch (err) {
|
|
12106
|
-
if (
|
|
12107
|
-
await platform2.write(2,
|
|
12108
|
-
|
|
12109
|
-
return 6;
|
|
14058
|
+
if (interrupted) {
|
|
14059
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
14060
|
+
return EXIT_INTERRUPTED;
|
|
12110
14061
|
}
|
|
12111
|
-
|
|
14062
|
+
const e = classifyError(err);
|
|
14063
|
+
if (err instanceof InputRequiredError) await platform2.write(2, `${e.msg}
|
|
12112
14064
|
`);
|
|
12113
|
-
|
|
14065
|
+
else await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
14066
|
+
return e.code;
|
|
12114
14067
|
} finally {
|
|
12115
14068
|
try {
|
|
12116
14069
|
await conn.close();
|
|
@@ -12118,12 +14071,17 @@ async function run(platform2, version, args2) {
|
|
|
12118
14071
|
}
|
|
12119
14072
|
}
|
|
12120
14073
|
}
|
|
14074
|
+
async function writeErr(p, json, kind, msg) {
|
|
14075
|
+
if (json) await renderErrorJson(p, kind, msg);
|
|
14076
|
+
else await p.write(2, `mcpcat: ${msg}
|
|
14077
|
+
`);
|
|
14078
|
+
}
|
|
12121
14079
|
|
|
12122
14080
|
// src/platform/host.ts
|
|
12123
14081
|
import { spawn as cpSpawn } from "node:child_process";
|
|
12124
14082
|
import { constants } from "node:fs";
|
|
12125
14083
|
import { access, mkdir, readFile as readFile3, rename, rmdir, stat, writeFile } from "node:fs/promises";
|
|
12126
|
-
import { createServer } from "node:net";
|
|
14084
|
+
import { createServer as createServer2 } from "node:net";
|
|
12127
14085
|
import { Readable, Writable } from "node:stream";
|
|
12128
14086
|
import * as tty from "node:tty";
|
|
12129
14087
|
var USE_PROCESS_GROUPS2 = process.platform !== "win32";
|
|
@@ -12182,8 +14140,8 @@ var hostFs = {
|
|
|
12182
14140
|
async readFile(path) {
|
|
12183
14141
|
return new Uint8Array(await readFile3(path));
|
|
12184
14142
|
},
|
|
12185
|
-
async writeFile(path, data) {
|
|
12186
|
-
await writeFile(path, data);
|
|
14143
|
+
async writeFile(path, data, options) {
|
|
14144
|
+
await writeFile(path, data, options?.mode !== void 0 ? { mode: options.mode } : void 0);
|
|
12187
14145
|
},
|
|
12188
14146
|
async mkdir(path) {
|
|
12189
14147
|
await mkdir(path, { recursive: true });
|
|
@@ -12221,7 +14179,7 @@ function createStderrStream() {
|
|
|
12221
14179
|
}
|
|
12222
14180
|
function nodeListen(options) {
|
|
12223
14181
|
return new Promise((resolve, reject) => {
|
|
12224
|
-
const server =
|
|
14182
|
+
const server = createServer2();
|
|
12225
14183
|
const pending = [];
|
|
12226
14184
|
const ready = [];
|
|
12227
14185
|
let closed = false;
|
|
@@ -12319,5 +14277,5 @@ function createHostPlatform() {
|
|
|
12319
14277
|
// src/host.ts
|
|
12320
14278
|
var platform = createHostPlatform();
|
|
12321
14279
|
var args = process.argv.slice(2);
|
|
12322
|
-
var code = await run(platform, "0.
|
|
14280
|
+
var code = await run(platform, "0.10.0", args);
|
|
12323
14281
|
process.exit(code);
|