@ai-ecoverse/mcpcat 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/mcpcat.mjs +1547 -105
- package/dist/kernel/mcpcat.mjs +1555 -101
- package/package.json +1 -1
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -5489,6 +5489,176 @@ function mergeArgs(base, flags) {
|
|
|
5489
5489
|
return result;
|
|
5490
5490
|
}
|
|
5491
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
|
+
|
|
5492
5662
|
// node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
|
|
5493
5663
|
var JSON_RPC_ERROR_CODES = {
|
|
5494
5664
|
parseError: -32700,
|
|
@@ -5557,9 +5727,726 @@ function parseJsonRpcMessage(value) {
|
|
|
5557
5727
|
throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid JSON-RPC message");
|
|
5558
5728
|
}
|
|
5559
5729
|
|
|
5560
|
-
// node_modules/@earendil-works/pi-mcp/dist/
|
|
5561
|
-
|
|
5562
|
-
|
|
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
|
+
}
|
|
5563
6450
|
|
|
5564
6451
|
// node_modules/@earendil-works/pi-mcp/dist/client.js
|
|
5565
6452
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -6257,21 +7144,21 @@ async function consumeSseStream(stream, options) {
|
|
|
6257
7144
|
if (line.startsWith(":"))
|
|
6258
7145
|
return;
|
|
6259
7146
|
const colon = line.indexOf(":");
|
|
6260
|
-
const
|
|
7147
|
+
const field2 = colon < 0 ? line : line.slice(0, colon);
|
|
6261
7148
|
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
6262
7149
|
if (value.startsWith(" "))
|
|
6263
7150
|
value = value.slice(1);
|
|
6264
|
-
if (
|
|
7151
|
+
if (field2 === "data") {
|
|
6265
7152
|
dataBytes += Buffer.byteLength(value) + (dataLines.length > 0 ? 1 : 0);
|
|
6266
7153
|
if (dataBytes > maxEventBytes)
|
|
6267
7154
|
throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
|
|
6268
7155
|
dataLines.push(value);
|
|
6269
|
-
} else if (
|
|
7156
|
+
} else if (field2 === "event")
|
|
6270
7157
|
eventName = value;
|
|
6271
|
-
else if (
|
|
7158
|
+
else if (field2 === "id" && !value.includes("\0")) {
|
|
6272
7159
|
eventId = value;
|
|
6273
7160
|
options.onId?.(value);
|
|
6274
|
-
} else if (
|
|
7161
|
+
} else if (field2 === "retry" && /^\d+$/.test(value))
|
|
6275
7162
|
options.onRetry?.(Number(value));
|
|
6276
7163
|
};
|
|
6277
7164
|
try {
|
|
@@ -6334,7 +7221,7 @@ function needsAuthorization(response) {
|
|
|
6334
7221
|
function isTransientStatus(status) {
|
|
6335
7222
|
return status === 408 || status === 429 || status >= 500;
|
|
6336
7223
|
}
|
|
6337
|
-
function
|
|
7224
|
+
function discard2(response) {
|
|
6338
7225
|
return response.body?.cancel().catch(() => {
|
|
6339
7226
|
}) ?? Promise.resolve();
|
|
6340
7227
|
}
|
|
@@ -6385,7 +7272,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6385
7272
|
await this.checkResponse(response);
|
|
6386
7273
|
this.captureSession(response);
|
|
6387
7274
|
if (!isJsonRpcRequest(message)) {
|
|
6388
|
-
await
|
|
7275
|
+
await discard2(response);
|
|
6389
7276
|
if ("method" in message && message.method === "notifications/initialized")
|
|
6390
7277
|
this.startGetStream();
|
|
6391
7278
|
return;
|
|
@@ -6404,7 +7291,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6404
7291
|
void this.consumeResponseStream(response.body, message.id);
|
|
6405
7292
|
return;
|
|
6406
7293
|
}
|
|
6407
|
-
await
|
|
7294
|
+
await discard2(response);
|
|
6408
7295
|
throw new McpHttpError(response.status, `Unsupported MCP response content type: ${type ?? "missing"}`);
|
|
6409
7296
|
}
|
|
6410
7297
|
async close() {
|
|
@@ -6419,7 +7306,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6419
7306
|
headers: this.buildHeaders({}, this.lastToken),
|
|
6420
7307
|
signal: AbortSignal.timeout(1e3)
|
|
6421
7308
|
});
|
|
6422
|
-
await
|
|
7309
|
+
await discard2(response);
|
|
6423
7310
|
} catch {
|
|
6424
7311
|
}
|
|
6425
7312
|
}
|
|
@@ -6450,7 +7337,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6450
7337
|
try {
|
|
6451
7338
|
await onUnauthorized({ response, serverUrl: this.url, fetch: this.fetch, token });
|
|
6452
7339
|
} finally {
|
|
6453
|
-
await
|
|
7340
|
+
await discard2(response);
|
|
6454
7341
|
}
|
|
6455
7342
|
}
|
|
6456
7343
|
}
|
|
@@ -6607,14 +7494,14 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6607
7494
|
}
|
|
6608
7495
|
});
|
|
6609
7496
|
if (response.status === 405) {
|
|
6610
|
-
await
|
|
7497
|
+
await discard2(response);
|
|
6611
7498
|
return void 0;
|
|
6612
7499
|
}
|
|
6613
7500
|
await this.checkResponse(response);
|
|
6614
7501
|
this.captureSession(response);
|
|
6615
7502
|
const type = contentType(response);
|
|
6616
7503
|
if (type !== "text/event-stream" || !response.body) {
|
|
6617
|
-
await
|
|
7504
|
+
await discard2(response);
|
|
6618
7505
|
throw new McpHttpError(response.status, `Unsupported MCP GET response content type: ${type ?? "missing"}`);
|
|
6619
7506
|
}
|
|
6620
7507
|
return response.body;
|
|
@@ -6886,6 +7773,14 @@ var McpAuthError = class extends Error {
|
|
|
6886
7773
|
this.target = target;
|
|
6887
7774
|
}
|
|
6888
7775
|
};
|
|
7776
|
+
var AuthRetryableError = class extends Error {
|
|
7777
|
+
response;
|
|
7778
|
+
constructor(response) {
|
|
7779
|
+
super(`HTTP ${response.status}: auth retry`);
|
|
7780
|
+
this.name = "AuthRetryableError";
|
|
7781
|
+
this.response = response;
|
|
7782
|
+
}
|
|
7783
|
+
};
|
|
6889
7784
|
function selectSseFrame(body, targetId) {
|
|
6890
7785
|
const text = new TextDecoder().decode(body);
|
|
6891
7786
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -6910,6 +7805,42 @@ function mcpNameForMethod(method, params) {
|
|
|
6910
7805
|
if (method === "resources/read") return params.uri;
|
|
6911
7806
|
return void 0;
|
|
6912
7807
|
}
|
|
7808
|
+
function makeAbort(timeoutMs, externalSignal) {
|
|
7809
|
+
const controller = new AbortController();
|
|
7810
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7811
|
+
if (externalSignal) {
|
|
7812
|
+
if (externalSignal.aborted) {
|
|
7813
|
+
clearTimeout(timer);
|
|
7814
|
+
controller.abort();
|
|
7815
|
+
} else {
|
|
7816
|
+
externalSignal.addEventListener(
|
|
7817
|
+
"abort",
|
|
7818
|
+
() => {
|
|
7819
|
+
clearTimeout(timer);
|
|
7820
|
+
controller.abort();
|
|
7821
|
+
},
|
|
7822
|
+
{ once: true }
|
|
7823
|
+
);
|
|
7824
|
+
}
|
|
7825
|
+
}
|
|
7826
|
+
return { controller, timer };
|
|
7827
|
+
}
|
|
7828
|
+
function logHeaders(verbose, dir, entries) {
|
|
7829
|
+
for (const [k, v] of entries) {
|
|
7830
|
+
if (dir === ">") verbose.headerOut(k, v);
|
|
7831
|
+
else verbose.headerIn(k, v);
|
|
7832
|
+
}
|
|
7833
|
+
}
|
|
7834
|
+
function buildServerInfo(discoverResult2) {
|
|
7835
|
+
return {
|
|
7836
|
+
name: discoverResult2?.serverInfo?.name,
|
|
7837
|
+
version: discoverResult2?.serverInfo?.version,
|
|
7838
|
+
title: discoverResult2?.serverInfo?.title,
|
|
7839
|
+
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
7840
|
+
capabilities: discoverResult2?.capabilities ?? {},
|
|
7841
|
+
instructions: discoverResult2?.instructions
|
|
7842
|
+
};
|
|
7843
|
+
}
|
|
6913
7844
|
async function createStatelessConnection(options) {
|
|
6914
7845
|
const {
|
|
6915
7846
|
url,
|
|
@@ -6918,11 +7849,12 @@ async function createStatelessConnection(options) {
|
|
|
6918
7849
|
timeoutMs = 6e4,
|
|
6919
7850
|
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
6920
7851
|
signal: externalSignal,
|
|
6921
|
-
verbose
|
|
7852
|
+
verbose,
|
|
7853
|
+
authProvider
|
|
6922
7854
|
} = options;
|
|
6923
7855
|
let nextId2 = 1;
|
|
6924
7856
|
let negotiatedVersion;
|
|
6925
|
-
function buildHeaders(method, params) {
|
|
7857
|
+
function buildHeaders(method, params, token) {
|
|
6926
7858
|
const h = {
|
|
6927
7859
|
"Content-Type": "application/json",
|
|
6928
7860
|
Accept: "application/json, text/event-stream",
|
|
@@ -6932,36 +7864,12 @@ async function createStatelessConnection(options) {
|
|
|
6932
7864
|
const n = mcpNameForMethod(method, params);
|
|
6933
7865
|
if (n) h["Mcp-Name"] = n;
|
|
6934
7866
|
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
7867
|
+
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
6935
7868
|
return h;
|
|
6936
7869
|
}
|
|
6937
|
-
function makeAbort() {
|
|
6938
|
-
const controller = new AbortController();
|
|
6939
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6940
|
-
if (externalSignal) {
|
|
6941
|
-
if (externalSignal.aborted) {
|
|
6942
|
-
clearTimeout(timer);
|
|
6943
|
-
controller.abort();
|
|
6944
|
-
} else {
|
|
6945
|
-
externalSignal.addEventListener(
|
|
6946
|
-
"abort",
|
|
6947
|
-
() => {
|
|
6948
|
-
clearTimeout(timer);
|
|
6949
|
-
controller.abort();
|
|
6950
|
-
},
|
|
6951
|
-
{ once: true }
|
|
6952
|
-
);
|
|
6953
|
-
}
|
|
6954
|
-
}
|
|
6955
|
-
return { controller, timer };
|
|
6956
|
-
}
|
|
6957
|
-
function logHeaders(dir, entries) {
|
|
6958
|
-
if (!verbose?.enabled) return;
|
|
6959
|
-
for (const [k, v] of entries) {
|
|
6960
|
-
if (dir === ">") verbose.headerOut(k, v);
|
|
6961
|
-
else verbose.headerIn(k, v);
|
|
6962
|
-
}
|
|
6963
|
-
}
|
|
6964
7870
|
async function handleErrorStatus(res, method) {
|
|
7871
|
+
if ((res.status === 401 || res.status === 403) && authProvider)
|
|
7872
|
+
throw new AuthRetryableError(res);
|
|
6965
7873
|
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
6966
7874
|
const text = await res.text();
|
|
6967
7875
|
let rpc;
|
|
@@ -6973,34 +7881,27 @@ async function createStatelessConnection(options) {
|
|
|
6973
7881
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
6974
7882
|
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
6975
7883
|
}
|
|
6976
|
-
async function
|
|
6977
|
-
const
|
|
6978
|
-
const headers = buildHeaders(method, params);
|
|
6979
|
-
const meta = {
|
|
6980
|
-
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
6981
|
-
clientInfo
|
|
6982
|
-
};
|
|
6983
|
-
const body = JSON.stringify({
|
|
6984
|
-
jsonrpc: "2.0",
|
|
6985
|
-
id,
|
|
6986
|
-
method,
|
|
6987
|
-
params: { ...params, _meta: meta }
|
|
6988
|
-
});
|
|
7884
|
+
async function doFetch(method, params, body) {
|
|
7885
|
+
const token = await authProvider?.token();
|
|
7886
|
+
const headers = buildHeaders(method, params, token);
|
|
6989
7887
|
if (verbose?.enabled) {
|
|
6990
7888
|
verbose.request(method, body);
|
|
6991
|
-
logHeaders(">", Object.entries(headers));
|
|
7889
|
+
logHeaders(verbose, ">", Object.entries(headers));
|
|
6992
7890
|
}
|
|
6993
|
-
const { controller, timer } = makeAbort();
|
|
6994
|
-
let res;
|
|
7891
|
+
const { controller, timer } = makeAbort(timeoutMs, externalSignal);
|
|
6995
7892
|
try {
|
|
6996
|
-
res = await fetchImpl(url, {
|
|
7893
|
+
const res = await fetchImpl(url, {
|
|
6997
7894
|
method: "POST",
|
|
6998
7895
|
headers,
|
|
6999
7896
|
body,
|
|
7000
7897
|
signal: controller.signal
|
|
7001
7898
|
});
|
|
7899
|
+
if (verbose?.enabled) {
|
|
7900
|
+
verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
|
|
7901
|
+
logHeaders(verbose, "<", res.headers.entries());
|
|
7902
|
+
}
|
|
7903
|
+
return { res, token };
|
|
7002
7904
|
} catch (err) {
|
|
7003
|
-
clearTimeout(timer);
|
|
7004
7905
|
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
7005
7906
|
throw new McpTimeoutError2(method, timeoutMs);
|
|
7006
7907
|
}
|
|
@@ -7008,11 +7909,8 @@ async function createStatelessConnection(options) {
|
|
|
7008
7909
|
} finally {
|
|
7009
7910
|
clearTimeout(timer);
|
|
7010
7911
|
}
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
logHeaders("<", res.headers.entries());
|
|
7014
|
-
}
|
|
7015
|
-
if (res.status >= 400) await handleErrorStatus(res, method);
|
|
7912
|
+
}
|
|
7913
|
+
async function parseResponse(res, id) {
|
|
7016
7914
|
const ct = res.headers.get("content-type") ?? "";
|
|
7017
7915
|
let frame;
|
|
7018
7916
|
if (ct.toLowerCase().includes("text/event-stream")) {
|
|
@@ -7026,10 +7924,53 @@ async function createStatelessConnection(options) {
|
|
|
7026
7924
|
}
|
|
7027
7925
|
return frame.result;
|
|
7028
7926
|
}
|
|
7927
|
+
async function handleAuthError(err, token) {
|
|
7928
|
+
if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url);
|
|
7929
|
+
try {
|
|
7930
|
+
await authProvider.onUnauthorized({
|
|
7931
|
+
response: err.response,
|
|
7932
|
+
serverUrl: new URL(url),
|
|
7933
|
+
fetch: fetchImpl,
|
|
7934
|
+
token
|
|
7935
|
+
});
|
|
7936
|
+
} catch (authErr) {
|
|
7937
|
+
if (authErr instanceof AuthStepUpError) throw authErr;
|
|
7938
|
+
throw new McpAuthError(err.response.status, url);
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7941
|
+
async function request(method, params) {
|
|
7942
|
+
const id = nextId2++;
|
|
7943
|
+
const meta = {
|
|
7944
|
+
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
7945
|
+
clientInfo
|
|
7946
|
+
};
|
|
7947
|
+
const body = JSON.stringify({
|
|
7948
|
+
jsonrpc: "2.0",
|
|
7949
|
+
id,
|
|
7950
|
+
method,
|
|
7951
|
+
params: { ...params, _meta: meta }
|
|
7952
|
+
});
|
|
7953
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7954
|
+
const { res, token } = await doFetch(method, params, body);
|
|
7955
|
+
if (res.status >= 400) {
|
|
7956
|
+
try {
|
|
7957
|
+
await handleErrorStatus(res, method);
|
|
7958
|
+
} catch (err) {
|
|
7959
|
+
if (err instanceof AuthRetryableError && attempt === 0) {
|
|
7960
|
+
await handleAuthError(err, token);
|
|
7961
|
+
continue;
|
|
7962
|
+
}
|
|
7963
|
+
throw err;
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
return parseResponse(res, id);
|
|
7967
|
+
}
|
|
7968
|
+
throw new McpAuthError(401, url);
|
|
7969
|
+
}
|
|
7029
7970
|
const discoverResult2 = await request("server/discover", {
|
|
7030
7971
|
protocolVersion: STATELESS_PROTOCOL_VERSION
|
|
7031
7972
|
});
|
|
7032
|
-
const supported =
|
|
7973
|
+
const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
|
|
7033
7974
|
if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
|
|
7034
7975
|
throw new Error(
|
|
7035
7976
|
`Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
@@ -7040,15 +7981,7 @@ async function createStatelessConnection(options) {
|
|
|
7040
7981
|
return {
|
|
7041
7982
|
async info() {
|
|
7042
7983
|
if (serverInfoCache) return serverInfoCache;
|
|
7043
|
-
|
|
7044
|
-
serverInfoCache = {
|
|
7045
|
-
name: r?.serverInfo?.name,
|
|
7046
|
-
version: r?.serverInfo?.version,
|
|
7047
|
-
title: r?.serverInfo?.title,
|
|
7048
|
-
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
7049
|
-
capabilities: r?.capabilities ?? {},
|
|
7050
|
-
instructions: r?.instructions
|
|
7051
|
-
};
|
|
7984
|
+
serverInfoCache = buildServerInfo(discoverResult2);
|
|
7052
7985
|
return serverInfoCache;
|
|
7053
7986
|
},
|
|
7054
7987
|
async listTools() {
|
|
@@ -7085,7 +8018,8 @@ function buildTransport(options) {
|
|
|
7085
8018
|
return new StreamableHttpTransport({
|
|
7086
8019
|
url: options.url,
|
|
7087
8020
|
headers: options.headers,
|
|
7088
|
-
fetch: options.fetchImpl
|
|
8021
|
+
fetch: options.fetchImpl,
|
|
8022
|
+
authProvider: options.authProvider
|
|
7089
8023
|
});
|
|
7090
8024
|
}
|
|
7091
8025
|
if (!options.command) throw new Error("PiConnection requires either a url or a command");
|
|
@@ -7202,6 +8136,7 @@ async function connect(platform2, options) {
|
|
|
7202
8136
|
url: options.url,
|
|
7203
8137
|
headers: options.headers,
|
|
7204
8138
|
fetchImpl: platform2.fetch,
|
|
8139
|
+
authProvider: options.authProvider,
|
|
7205
8140
|
...shared
|
|
7206
8141
|
});
|
|
7207
8142
|
}
|
|
@@ -7213,16 +8148,20 @@ async function connect(platform2, options) {
|
|
|
7213
8148
|
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
|
|
7214
8149
|
timeoutMs: options.timeoutMs,
|
|
7215
8150
|
signal: options.signal,
|
|
7216
|
-
verbose: options.verbose
|
|
8151
|
+
verbose: options.verbose,
|
|
8152
|
+
authProvider: options.authProvider
|
|
7217
8153
|
});
|
|
7218
8154
|
} catch (err) {
|
|
7219
8155
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
7220
8156
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
8157
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
8158
|
+
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
7221
8159
|
if (isMethodNotFound) {
|
|
7222
8160
|
return createPiConnection({
|
|
7223
8161
|
url: options.url,
|
|
7224
8162
|
headers: options.headers,
|
|
7225
8163
|
fetchImpl: platform2.fetch,
|
|
8164
|
+
authProvider: options.authProvider,
|
|
7226
8165
|
...shared
|
|
7227
8166
|
});
|
|
7228
8167
|
}
|
|
@@ -7697,8 +8636,8 @@ async function formatOutput(platform2, result, json) {
|
|
|
7697
8636
|
return result.ok ? 0 : isTimeout ? 124 : 1;
|
|
7698
8637
|
}
|
|
7699
8638
|
async function createSandbox(platform2, tools, globals, timeoutMs) {
|
|
7700
|
-
const
|
|
7701
|
-
if (
|
|
8639
|
+
const isKernel2 = platform2.packageDir !== void 0;
|
|
8640
|
+
if (isKernel2) {
|
|
7702
8641
|
const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
|
|
7703
8642
|
const wasm = await loadWasmKernel(platform2);
|
|
7704
8643
|
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
|
|
@@ -7823,8 +8762,8 @@ function resolveAliases(value) {
|
|
|
7823
8762
|
}
|
|
7824
8763
|
return out;
|
|
7825
8764
|
}
|
|
7826
|
-
function fail(server, message,
|
|
7827
|
-
return { server, ...
|
|
8765
|
+
function fail(server, message, field2) {
|
|
8766
|
+
return { server, ...field2 ? { field: field2 } : {}, message };
|
|
7828
8767
|
}
|
|
7829
8768
|
function validateCommon(name, value) {
|
|
7830
8769
|
const exposures = EXPOSURES.map((e) => `"${e}"`).join(", ");
|
|
@@ -9245,7 +10184,7 @@ function argvForCommand(command, args2) {
|
|
|
9245
10184
|
if (args2.error) return { error: args2.error };
|
|
9246
10185
|
const argv = [...command.path];
|
|
9247
10186
|
for (const positional of command.positionals) {
|
|
9248
|
-
const found = args2.fields.find((
|
|
10187
|
+
const found = args2.fields.find((field2) => field2.name === positional.name);
|
|
9249
10188
|
if (!found) {
|
|
9250
10189
|
if (positional.required) return { error: `missing ${positional.name}` };
|
|
9251
10190
|
continue;
|
|
@@ -9264,7 +10203,7 @@ function argvForCommand(command, args2) {
|
|
|
9264
10203
|
function appendFlags(flags, fields) {
|
|
9265
10204
|
const argv = [];
|
|
9266
10205
|
for (const flag of flags) {
|
|
9267
|
-
const found = fields.find((
|
|
10206
|
+
const found = fields.find((field2) => field2.name === flag.name);
|
|
9268
10207
|
if (!found || found.value === false) continue;
|
|
9269
10208
|
const encoded = encodeFlag(flag, found.value);
|
|
9270
10209
|
if ("error" in encoded) return encoded;
|
|
@@ -10973,6 +11912,478 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
|
|
|
10973
11912
|
};
|
|
10974
11913
|
}
|
|
10975
11914
|
|
|
11915
|
+
// src/auth/oauth.ts
|
|
11916
|
+
var CALLBACK_HOST2 = "127.0.0.1";
|
|
11917
|
+
var CALLBACK_PATH2 = "/callback";
|
|
11918
|
+
var CIMD_URL2 = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
11919
|
+
var CLIENT_NAME2 = "mcpcat";
|
|
11920
|
+
var REQUEST_TIMEOUT_MS2 = 15e3;
|
|
11921
|
+
function timedFetch2(fetchImpl) {
|
|
11922
|
+
return (input, init) => {
|
|
11923
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS2);
|
|
11924
|
+
return fetchImpl(input, {
|
|
11925
|
+
...init,
|
|
11926
|
+
signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
|
|
11927
|
+
});
|
|
11928
|
+
};
|
|
11929
|
+
}
|
|
11930
|
+
function callbackSettings(oauth) {
|
|
11931
|
+
const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
|
|
11932
|
+
const url = new URL(raw);
|
|
11933
|
+
const address = url.hostname.replace(/^\[|\]$/g, "");
|
|
11934
|
+
const port = url.port ? Number(url.port) : oauth?.callbackPort;
|
|
11935
|
+
let fixedRedirectUrl;
|
|
11936
|
+
if (url.port) fixedRedirectUrl = oauth?.callbackUrl;
|
|
11937
|
+
else if (port !== void 0) {
|
|
11938
|
+
url.port = String(port);
|
|
11939
|
+
fixedRedirectUrl = url.href;
|
|
11940
|
+
}
|
|
11941
|
+
return {
|
|
11942
|
+
host: address === "localhost" ? CALLBACK_HOST2 : address,
|
|
11943
|
+
redirectHost: address,
|
|
11944
|
+
port,
|
|
11945
|
+
path: url.pathname,
|
|
11946
|
+
fixedRedirectUrl
|
|
11947
|
+
};
|
|
11948
|
+
}
|
|
11949
|
+
function cimdForServer(metadata, redirectUrl) {
|
|
11950
|
+
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
11951
|
+
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
11952
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11953
|
+
return {
|
|
11954
|
+
url: CIMD_URL2,
|
|
11955
|
+
redirectUrl
|
|
11956
|
+
};
|
|
11957
|
+
}
|
|
11958
|
+
function buildClientMetadataDocumentFn(oauth, redirectUrl) {
|
|
11959
|
+
if (oauth?.clientRegistration === "dcr") return void 0;
|
|
11960
|
+
if (oauth?.clientId) return void 0;
|
|
11961
|
+
return (metadata) => {
|
|
11962
|
+
if (oauth?.clientRegistration === "cimd") {
|
|
11963
|
+
if (!metadata?.client_id_metadata_document_supported || !metadata.token_endpoint_auth_methods_supported?.includes("none")) {
|
|
11964
|
+
return void 0;
|
|
11965
|
+
}
|
|
11966
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11967
|
+
return { url: CIMD_URL2, redirectUrl };
|
|
11968
|
+
}
|
|
11969
|
+
return cimdForServer(metadata, redirectUrl);
|
|
11970
|
+
};
|
|
11971
|
+
}
|
|
11972
|
+
function createProvider(serverUrl, store, oauth, redirectUrl, onRedirect) {
|
|
11973
|
+
const cimdFn2 = buildClientMetadataDocumentFn(oauth, redirectUrl);
|
|
11974
|
+
return new McpOAuthProvider({
|
|
11975
|
+
serverUrl,
|
|
11976
|
+
redirectUrl,
|
|
11977
|
+
clientMetadata: { client_name: oauth?.clientName ?? CLIENT_NAME2 },
|
|
11978
|
+
clientMetadataDocument: cimdFn2,
|
|
11979
|
+
clientId: oauth?.clientId,
|
|
11980
|
+
clientSecret: oauth?.clientSecret,
|
|
11981
|
+
store,
|
|
11982
|
+
onRedirect
|
|
11983
|
+
});
|
|
11984
|
+
}
|
|
11985
|
+
var SourceBackedStateStore = class {
|
|
11986
|
+
source;
|
|
11987
|
+
name;
|
|
11988
|
+
serverUrl;
|
|
11989
|
+
constructor(source, name, serverUrl) {
|
|
11990
|
+
this.source = source;
|
|
11991
|
+
this.name = name;
|
|
11992
|
+
this.serverUrl = serverUrl;
|
|
11993
|
+
}
|
|
11994
|
+
async load() {
|
|
11995
|
+
return this.source.load(this.name, this.serverUrl);
|
|
11996
|
+
}
|
|
11997
|
+
async save(state) {
|
|
11998
|
+
await this.source.save(this.name, this.serverUrl, state);
|
|
11999
|
+
}
|
|
12000
|
+
};
|
|
12001
|
+
var SignInCancelledError = class extends Error {
|
|
12002
|
+
constructor() {
|
|
12003
|
+
super("Sign-in cancelled");
|
|
12004
|
+
this.name = "SignInCancelledError";
|
|
12005
|
+
}
|
|
12006
|
+
};
|
|
12007
|
+
function responseFromRedirectUrl(input, state, redirectUrl) {
|
|
12008
|
+
let url;
|
|
12009
|
+
try {
|
|
12010
|
+
url = new URL(input.trim());
|
|
12011
|
+
} catch {
|
|
12012
|
+
throw new Error("Expected the full redirect URL from the browser address bar");
|
|
12013
|
+
}
|
|
12014
|
+
if (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {
|
|
12015
|
+
throw new Error("The redirect URL does not match this sign-in's redirect URI");
|
|
12016
|
+
}
|
|
12017
|
+
const error = url.searchParams.get("error");
|
|
12018
|
+
if (error) throw new Error(url.searchParams.get("error_description") ?? error);
|
|
12019
|
+
if (url.searchParams.get("state") !== state) {
|
|
12020
|
+
throw new Error("The redirect URL belongs to a different sign-in");
|
|
12021
|
+
}
|
|
12022
|
+
const code2 = url.searchParams.get("code");
|
|
12023
|
+
if (!code2) throw new Error("The redirect URL does not contain an authorization code");
|
|
12024
|
+
return { code: code2, iss: url.searchParams.get("iss") ?? void 0 };
|
|
12025
|
+
}
|
|
12026
|
+
async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt, signal) {
|
|
12027
|
+
const controller = new AbortController();
|
|
12028
|
+
const promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
12029
|
+
const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
|
|
12030
|
+
const fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {
|
|
12031
|
+
if (!input?.trim()) throw new SignInCancelledError();
|
|
12032
|
+
return responseFromRedirectUrl(input, state, redirectUrl);
|
|
12033
|
+
});
|
|
12034
|
+
try {
|
|
12035
|
+
return await Promise.race([fromBrowser, fromUser]);
|
|
12036
|
+
} finally {
|
|
12037
|
+
controller.abort();
|
|
12038
|
+
fromBrowser.catch(() => void 0);
|
|
12039
|
+
fromUser.catch(() => void 0);
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
function mergeScopes(...scopes) {
|
|
12043
|
+
const merged = [...new Set(scopes.flatMap((s) => s?.split(/\s+/).filter(Boolean) ?? []))];
|
|
12044
|
+
return merged.length > 0 ? merged.join(" ") : void 0;
|
|
12045
|
+
}
|
|
12046
|
+
async function signIn(options) {
|
|
12047
|
+
const { name, serverUrl, source, oauth, prompt, platform: platform2, signal } = options;
|
|
12048
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
12049
|
+
const store = new SourceBackedStateStore(source, name, serverUrl);
|
|
12050
|
+
const stored = await store.load();
|
|
12051
|
+
const cbSettings = callbackSettings(oauth);
|
|
12052
|
+
const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
|
|
12053
|
+
const preferredPort = cbSettings.port ?? (registered[0] ? Number(new URL(registered[0]).port) || void 0 : void 0);
|
|
12054
|
+
const callback = await OAuthCallbackServer.listen({
|
|
12055
|
+
host: cbSettings.host,
|
|
12056
|
+
redirectHost: cbSettings.redirectHost,
|
|
12057
|
+
port: preferredPort ?? 0,
|
|
12058
|
+
path: cbSettings.path
|
|
12059
|
+
});
|
|
12060
|
+
const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
|
|
12061
|
+
try {
|
|
12062
|
+
if (stored) {
|
|
12063
|
+
const next = { ...stored };
|
|
12064
|
+
delete next.oauthState;
|
|
12065
|
+
const hasCimd = buildClientMetadataDocumentFn(oauth, redirectUrl) !== void 0;
|
|
12066
|
+
const keepClient = oauth?.clientId || (hasCimd ? !stored.clientInformation : registered.includes(redirectUrl));
|
|
12067
|
+
if (!keepClient) {
|
|
12068
|
+
delete next.clientInformation;
|
|
12069
|
+
delete next.tokens;
|
|
12070
|
+
delete next.tokensExpireAt;
|
|
12071
|
+
}
|
|
12072
|
+
await store.save(next);
|
|
12073
|
+
}
|
|
12074
|
+
let authorizationUrl;
|
|
12075
|
+
const provider = createProvider(serverUrl, store, oauth, redirectUrl, (url) => {
|
|
12076
|
+
authorizationUrl = url;
|
|
12077
|
+
});
|
|
12078
|
+
const flow = {
|
|
12079
|
+
serverUrl,
|
|
12080
|
+
fetch: timedFetch2(platform2.fetch),
|
|
12081
|
+
signal,
|
|
12082
|
+
authorizationServerMetadataUrl: oauth?.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : void 0,
|
|
12083
|
+
scope: mergeScopes(
|
|
12084
|
+
oauth?.scope,
|
|
12085
|
+
options.stepUp ? stepUpScope(stored?.tokens?.scope, options.scope) : options.scope
|
|
12086
|
+
)
|
|
12087
|
+
};
|
|
12088
|
+
const skipRefresh = options.stepUp === true;
|
|
12089
|
+
if (await authorizeMcp(provider, { ...flow, skipRefresh }) === "AUTHORIZED") return;
|
|
12090
|
+
if (!authorizationUrl) throw new Error("OAuth flow did not produce an authorization URL");
|
|
12091
|
+
const state = await provider.state();
|
|
12092
|
+
const authRedirectUrl = new URL(
|
|
12093
|
+
authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl
|
|
12094
|
+
);
|
|
12095
|
+
prompt.showAuthorizationUrl(authorizationUrl);
|
|
12096
|
+
const { code: code2, iss } = await waitForAuthorizationResponse(
|
|
12097
|
+
callback,
|
|
12098
|
+
state,
|
|
12099
|
+
authRedirectUrl,
|
|
12100
|
+
prompt,
|
|
12101
|
+
signal
|
|
12102
|
+
);
|
|
12103
|
+
await authorizeMcp(provider, { ...flow, authorizationCode: code2, iss });
|
|
12104
|
+
} catch (err) {
|
|
12105
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
12106
|
+
throw err;
|
|
12107
|
+
} finally {
|
|
12108
|
+
await callback.close();
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
|
|
12112
|
+
// src/auth/token-source.ts
|
|
12113
|
+
function mcpNamespace(server) {
|
|
12114
|
+
return `mcp__${server.replace(/-/g, "_")}`;
|
|
12115
|
+
}
|
|
12116
|
+
function storeKey(name, serverUrl) {
|
|
12117
|
+
const ns = mcpNamespace(name);
|
|
12118
|
+
const url = String(new URL(serverUrl));
|
|
12119
|
+
return `${ns}|${url}`;
|
|
12120
|
+
}
|
|
12121
|
+
function isKernel(platform2) {
|
|
12122
|
+
return platform2.kind === "kernel";
|
|
12123
|
+
}
|
|
12124
|
+
|
|
12125
|
+
// src/auth/pi-store.ts
|
|
12126
|
+
var AUTH_FILE = "mcp-auth.json";
|
|
12127
|
+
var FILE_MODE = 384;
|
|
12128
|
+
function parseStates(raw) {
|
|
12129
|
+
if (!raw?.trim()) return {};
|
|
12130
|
+
const parsed = JSON.parse(raw);
|
|
12131
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
12132
|
+
}
|
|
12133
|
+
function serializeStates(states) {
|
|
12134
|
+
return `${JSON.stringify(states, null, 2)}
|
|
12135
|
+
`;
|
|
12136
|
+
}
|
|
12137
|
+
function legacyKey(serverUrl) {
|
|
12138
|
+
return String(new URL(serverUrl));
|
|
12139
|
+
}
|
|
12140
|
+
var PiFileTokenSource = class {
|
|
12141
|
+
platform;
|
|
12142
|
+
authPath;
|
|
12143
|
+
dirPath;
|
|
12144
|
+
constructor(platform2) {
|
|
12145
|
+
this.platform = platform2;
|
|
12146
|
+
this.dirPath = userConfigDir(platform2);
|
|
12147
|
+
this.authPath = `${this.dirPath}/${AUTH_FILE}`;
|
|
12148
|
+
}
|
|
12149
|
+
get path() {
|
|
12150
|
+
return this.authPath;
|
|
12151
|
+
}
|
|
12152
|
+
async getToken(name, serverUrl) {
|
|
12153
|
+
const state = await this.load(name, serverUrl);
|
|
12154
|
+
if (!state?.tokens?.access_token) return void 0;
|
|
12155
|
+
return {
|
|
12156
|
+
accessToken: state.tokens.access_token,
|
|
12157
|
+
expiresAt: state.tokensExpireAt
|
|
12158
|
+
};
|
|
12159
|
+
}
|
|
12160
|
+
async load(name, serverUrl) {
|
|
12161
|
+
const key = storeKey(name, serverUrl);
|
|
12162
|
+
const legacy = legacyKey(serverUrl);
|
|
12163
|
+
const release = await this.acquireLock();
|
|
12164
|
+
try {
|
|
12165
|
+
const raw = await this.readRaw();
|
|
12166
|
+
const states = parseStates(raw);
|
|
12167
|
+
if (states[key]) return states[key];
|
|
12168
|
+
if (states[legacy]) {
|
|
12169
|
+
states[key] = states[legacy];
|
|
12170
|
+
delete states[legacy];
|
|
12171
|
+
await this.writeStates(states);
|
|
12172
|
+
return states[key];
|
|
12173
|
+
}
|
|
12174
|
+
return void 0;
|
|
12175
|
+
} finally {
|
|
12176
|
+
await release();
|
|
12177
|
+
}
|
|
12178
|
+
}
|
|
12179
|
+
async save(name, serverUrl, state) {
|
|
12180
|
+
const key = storeKey(name, serverUrl);
|
|
12181
|
+
const release = await this.acquireLock();
|
|
12182
|
+
try {
|
|
12183
|
+
const raw = await this.readRaw();
|
|
12184
|
+
const states = parseStates(raw);
|
|
12185
|
+
states[key] = state;
|
|
12186
|
+
await this.writeStates(states);
|
|
12187
|
+
} finally {
|
|
12188
|
+
await release();
|
|
12189
|
+
}
|
|
12190
|
+
}
|
|
12191
|
+
async remove(name, serverUrl) {
|
|
12192
|
+
const key = storeKey(name, serverUrl);
|
|
12193
|
+
const legacy = legacyKey(serverUrl);
|
|
12194
|
+
const release = await this.acquireLock();
|
|
12195
|
+
try {
|
|
12196
|
+
const raw = await this.readRaw();
|
|
12197
|
+
const states = parseStates(raw);
|
|
12198
|
+
const stored = key in states ? key : legacy in states ? legacy : void 0;
|
|
12199
|
+
if (!stored) return false;
|
|
12200
|
+
delete states[stored];
|
|
12201
|
+
await this.writeStates(states);
|
|
12202
|
+
return true;
|
|
12203
|
+
} finally {
|
|
12204
|
+
await release();
|
|
12205
|
+
}
|
|
12206
|
+
}
|
|
12207
|
+
async acquireLock() {
|
|
12208
|
+
await this.ensureDir();
|
|
12209
|
+
return acquireLock(this.platform.fs, this.authPath);
|
|
12210
|
+
}
|
|
12211
|
+
async ensureDir() {
|
|
12212
|
+
try {
|
|
12213
|
+
await this.platform.fs.mkdir(this.dirPath);
|
|
12214
|
+
} catch {
|
|
12215
|
+
}
|
|
12216
|
+
}
|
|
12217
|
+
async readRaw() {
|
|
12218
|
+
try {
|
|
12219
|
+
const data = await this.platform.fs.readFile(this.authPath);
|
|
12220
|
+
return new TextDecoder().decode(data);
|
|
12221
|
+
} catch {
|
|
12222
|
+
return void 0;
|
|
12223
|
+
}
|
|
12224
|
+
}
|
|
12225
|
+
async writeStates(states) {
|
|
12226
|
+
await this.platform.fs.writeFile(this.authPath, serializeStates(states), { mode: FILE_MODE });
|
|
12227
|
+
}
|
|
12228
|
+
};
|
|
12229
|
+
|
|
12230
|
+
// src/cli/auth-verbs.ts
|
|
12231
|
+
function createTokenSource(platform2) {
|
|
12232
|
+
return new PiFileTokenSource(platform2);
|
|
12233
|
+
}
|
|
12234
|
+
function resolveServerName(target) {
|
|
12235
|
+
try {
|
|
12236
|
+
const u = new URL(target.startsWith("http") ? target : `https://${target}`);
|
|
12237
|
+
if (target.includes(".") || target.includes(":")) {
|
|
12238
|
+
return { name: `url:${u.origin}${u.pathname}`, isUrl: true };
|
|
12239
|
+
}
|
|
12240
|
+
} catch {
|
|
12241
|
+
}
|
|
12242
|
+
return { name: target, isUrl: false };
|
|
12243
|
+
}
|
|
12244
|
+
async function resolveLoginTarget(platform2, target) {
|
|
12245
|
+
const config = await MergedConfigSource.load(platform2);
|
|
12246
|
+
const entry = config.entry(target);
|
|
12247
|
+
if (entry && "url" in entry.config && entry.config.url) {
|
|
12248
|
+
const httpConfig = entry.config;
|
|
12249
|
+
return {
|
|
12250
|
+
name: target,
|
|
12251
|
+
serverUrl: httpConfig.url,
|
|
12252
|
+
oauth: httpConfig.oauth
|
|
12253
|
+
};
|
|
12254
|
+
}
|
|
12255
|
+
const resolved = resolveServerName(target);
|
|
12256
|
+
if (resolved.isUrl) {
|
|
12257
|
+
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
12258
|
+
return { name: resolved.name, serverUrl: url, oauth: void 0 };
|
|
12259
|
+
}
|
|
12260
|
+
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
12261
|
+
}
|
|
12262
|
+
async function runLogin(platform2, args2, source) {
|
|
12263
|
+
if (isKernel(platform2)) {
|
|
12264
|
+
await platform2.write(
|
|
12265
|
+
2,
|
|
12266
|
+
"mcpcat: login is not available in seven. Use the agent's MCP sign-in in Settings, or run mcpcat login on the host.\n"
|
|
12267
|
+
);
|
|
12268
|
+
return 4;
|
|
12269
|
+
}
|
|
12270
|
+
const noBrowser = args2.includes("--no-browser");
|
|
12271
|
+
const positional = args2.filter((a) => !a.startsWith("-"));
|
|
12272
|
+
if (positional.length === 0) {
|
|
12273
|
+
await platform2.write(2, "mcpcat: login requires a target\n");
|
|
12274
|
+
return 2;
|
|
12275
|
+
}
|
|
12276
|
+
const target = positional[0];
|
|
12277
|
+
let resolved;
|
|
12278
|
+
try {
|
|
12279
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
12280
|
+
} catch (err) {
|
|
12281
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
12282
|
+
`);
|
|
12283
|
+
return 2;
|
|
12284
|
+
}
|
|
12285
|
+
try {
|
|
12286
|
+
await signIn({
|
|
12287
|
+
name: resolved.name,
|
|
12288
|
+
serverUrl: resolved.serverUrl,
|
|
12289
|
+
source,
|
|
12290
|
+
oauth: resolved.oauth,
|
|
12291
|
+
platform: platform2,
|
|
12292
|
+
prompt: {
|
|
12293
|
+
showAuthorizationUrl(url) {
|
|
12294
|
+
if (!noBrowser && platform2.openUrl) {
|
|
12295
|
+
platform2.openUrl(url.href).catch(() => {
|
|
12296
|
+
});
|
|
12297
|
+
}
|
|
12298
|
+
void platform2.write(2, `Open this URL to sign in:
|
|
12299
|
+
${url.href}
|
|
12300
|
+
`);
|
|
12301
|
+
if (noBrowser || !platform2.openUrl) {
|
|
12302
|
+
void platform2.write(2, "\nPaste the redirect URL from your browser address bar:\n");
|
|
12303
|
+
}
|
|
12304
|
+
},
|
|
12305
|
+
async promptForRedirectUrl(signal) {
|
|
12306
|
+
if (!noBrowser && platform2.openUrl) {
|
|
12307
|
+
return new Promise((_resolve, reject) => {
|
|
12308
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
12309
|
+
});
|
|
12310
|
+
}
|
|
12311
|
+
const reader = platform2.stdin.getReader();
|
|
12312
|
+
const chunks = [];
|
|
12313
|
+
try {
|
|
12314
|
+
while (true) {
|
|
12315
|
+
const read = reader.read();
|
|
12316
|
+
const result = await Promise.race([
|
|
12317
|
+
read,
|
|
12318
|
+
new Promise((_, reject) => {
|
|
12319
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
12320
|
+
})
|
|
12321
|
+
]);
|
|
12322
|
+
if (result.done) break;
|
|
12323
|
+
chunks.push(result.value);
|
|
12324
|
+
const text = new TextDecoder().decode(concatUint8Arrays(chunks));
|
|
12325
|
+
if (text.includes("\n")) return text.trim();
|
|
12326
|
+
}
|
|
12327
|
+
} finally {
|
|
12328
|
+
reader.releaseLock();
|
|
12329
|
+
}
|
|
12330
|
+
return new TextDecoder().decode(concatUint8Arrays(chunks)).trim() || void 0;
|
|
12331
|
+
}
|
|
12332
|
+
}
|
|
12333
|
+
});
|
|
12334
|
+
await platform2.write(1, `Signed in to ${target}
|
|
12335
|
+
`);
|
|
12336
|
+
return 0;
|
|
12337
|
+
} catch (err) {
|
|
12338
|
+
if (err instanceof SignInCancelledError) {
|
|
12339
|
+
await platform2.write(2, "mcpcat: sign-in cancelled\n");
|
|
12340
|
+
return 1;
|
|
12341
|
+
}
|
|
12342
|
+
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
12343
|
+
`);
|
|
12344
|
+
return 4;
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12347
|
+
async function runLogout(platform2, args2, source) {
|
|
12348
|
+
if (isKernel(platform2)) {
|
|
12349
|
+
await platform2.write(2, "mcpcat: logout is not available in seven.\n");
|
|
12350
|
+
return 4;
|
|
12351
|
+
}
|
|
12352
|
+
const positional = args2.filter((a) => !a.startsWith("-"));
|
|
12353
|
+
if (positional.length === 0) {
|
|
12354
|
+
await platform2.write(2, "mcpcat: logout requires a target\n");
|
|
12355
|
+
return 2;
|
|
12356
|
+
}
|
|
12357
|
+
const target = positional[0];
|
|
12358
|
+
let resolved;
|
|
12359
|
+
try {
|
|
12360
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
12361
|
+
} catch (err) {
|
|
12362
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
12363
|
+
`);
|
|
12364
|
+
return 2;
|
|
12365
|
+
}
|
|
12366
|
+
const removed = await source.remove(resolved.name, resolved.serverUrl);
|
|
12367
|
+
if (removed) {
|
|
12368
|
+
await platform2.write(1, `Signed out of ${target}
|
|
12369
|
+
`);
|
|
12370
|
+
} else {
|
|
12371
|
+
await platform2.write(1, `No credentials stored for ${target}
|
|
12372
|
+
`);
|
|
12373
|
+
}
|
|
12374
|
+
return 0;
|
|
12375
|
+
}
|
|
12376
|
+
function concatUint8Arrays(arrays) {
|
|
12377
|
+
const total = arrays.reduce((s, a) => s + a.length, 0);
|
|
12378
|
+
const result = new Uint8Array(total);
|
|
12379
|
+
let offset = 0;
|
|
12380
|
+
for (const a of arrays) {
|
|
12381
|
+
result.set(a, offset);
|
|
12382
|
+
offset += a.length;
|
|
12383
|
+
}
|
|
12384
|
+
return result;
|
|
12385
|
+
}
|
|
12386
|
+
|
|
10976
12387
|
// src/config/import.ts
|
|
10977
12388
|
function isRecord5(value) {
|
|
10978
12389
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -11916,16 +13327,7 @@ var CONFIG_VERBS = {
|
|
|
11916
13327
|
exposure: true,
|
|
11917
13328
|
import: true
|
|
11918
13329
|
};
|
|
11919
|
-
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set([
|
|
11920
|
-
"resources",
|
|
11921
|
-
"read",
|
|
11922
|
-
"prompts",
|
|
11923
|
-
"prompt",
|
|
11924
|
-
"complete",
|
|
11925
|
-
"task",
|
|
11926
|
-
"login",
|
|
11927
|
-
"logout"
|
|
11928
|
-
]);
|
|
13330
|
+
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["resources", "read", "prompts", "prompt", "complete", "task"]);
|
|
11929
13331
|
var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe", "serve"]);
|
|
11930
13332
|
function helpText(version) {
|
|
11931
13333
|
return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
|
|
@@ -11949,8 +13351,8 @@ Configuration verbs:
|
|
|
11949
13351
|
enable|disable <name> Toggle a server
|
|
11950
13352
|
exposure <name> <mode> Set exposure mode
|
|
11951
13353
|
import <file> Import from other configs
|
|
11952
|
-
login <target> OAuth sign-in
|
|
11953
|
-
logout <target> Remove tokens
|
|
13354
|
+
login <target> OAuth sign-in
|
|
13355
|
+
logout <target> Remove stored tokens
|
|
11954
13356
|
|
|
11955
13357
|
Server and code verbs:
|
|
11956
13358
|
serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
|
|
@@ -12541,6 +13943,14 @@ async function run(platform2, version, args2) {
|
|
|
12541
13943
|
}
|
|
12542
13944
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args2);
|
|
12543
13945
|
if (discResult !== void 0) return discResult;
|
|
13946
|
+
if (parsed.verb === "login") {
|
|
13947
|
+
const source = createTokenSource(platform2);
|
|
13948
|
+
return runLogin(platform2, args2.slice(1), source);
|
|
13949
|
+
}
|
|
13950
|
+
if (parsed.verb === "logout") {
|
|
13951
|
+
const source = createTokenSource(platform2);
|
|
13952
|
+
return runLogout(platform2, args2.slice(1), source);
|
|
13953
|
+
}
|
|
12544
13954
|
if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
|
|
12545
13955
|
await platform2.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
|
|
12546
13956
|
`);
|
|
@@ -12589,6 +13999,17 @@ function classifyError(err) {
|
|
|
12589
13999
|
if (err instanceof McpAuthError) {
|
|
12590
14000
|
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
12591
14001
|
}
|
|
14002
|
+
if (err instanceof AuthStepUpError) {
|
|
14003
|
+
const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
|
|
14004
|
+
return {
|
|
14005
|
+
code: EXIT_AUTH,
|
|
14006
|
+
kind: "auth",
|
|
14007
|
+
msg: `insufficient scope. Run: mcpcat login ${err.target ?? "<target>"}${scopeHint}`
|
|
14008
|
+
};
|
|
14009
|
+
}
|
|
14010
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
14011
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
14012
|
+
}
|
|
12592
14013
|
if (err instanceof McpcatError) {
|
|
12593
14014
|
return { code: err.exitCode, kind: "error", msg: err.message };
|
|
12594
14015
|
}
|
|
@@ -12599,6 +14020,14 @@ function classifyError(err) {
|
|
|
12599
14020
|
}
|
|
12600
14021
|
return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
|
|
12601
14022
|
}
|
|
14023
|
+
function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
14024
|
+
if (hasAuthHeader || !url) return void 0;
|
|
14025
|
+
const source = createTokenSource(platform2);
|
|
14026
|
+
const entry = config.entry(target);
|
|
14027
|
+
const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
|
|
14028
|
+
const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
|
|
14029
|
+
return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
|
|
14030
|
+
}
|
|
12602
14031
|
async function runClientVerb(platform2, version, parsed) {
|
|
12603
14032
|
const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
|
|
12604
14033
|
const verbose = createVerboseLogger(platform2, parsed.verbose);
|
|
@@ -12628,6 +14057,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
12628
14057
|
await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
|
|
12629
14058
|
return EXIT_USAGE;
|
|
12630
14059
|
}
|
|
14060
|
+
const hasAuthHeader = "Authorization" in expanded;
|
|
14061
|
+
const authProvider = resolveAuthProvider(platform2, hasAuthHeader, resolved.url, target, config);
|
|
12631
14062
|
let conn;
|
|
12632
14063
|
try {
|
|
12633
14064
|
conn = await connect(platform2, {
|
|
@@ -12637,11 +14068,22 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
12637
14068
|
version,
|
|
12638
14069
|
timeoutMs,
|
|
12639
14070
|
signal: sigintController.signal,
|
|
12640
|
-
verbose
|
|
14071
|
+
verbose,
|
|
14072
|
+
authProvider
|
|
12641
14073
|
});
|
|
12642
14074
|
} catch (err) {
|
|
12643
|
-
if (err instanceof McpAuthError) {
|
|
12644
|
-
|
|
14075
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) {
|
|
14076
|
+
const e = classifyError(err);
|
|
14077
|
+
await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
14078
|
+
return EXIT_AUTH;
|
|
14079
|
+
}
|
|
14080
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
14081
|
+
await writeErr(
|
|
14082
|
+
platform2,
|
|
14083
|
+
parsed.jsonOutput,
|
|
14084
|
+
"auth",
|
|
14085
|
+
`unauthorized. Run: mcpcat login ${target}`
|
|
14086
|
+
);
|
|
12645
14087
|
return EXIT_AUTH;
|
|
12646
14088
|
}
|
|
12647
14089
|
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
@@ -12677,7 +14119,7 @@ async function writeErr(p, json, kind, msg) {
|
|
|
12677
14119
|
import { spawn as cpSpawn } from "node:child_process";
|
|
12678
14120
|
import { constants } from "node:fs";
|
|
12679
14121
|
import { access, mkdir, readFile as readFile3, rename, rmdir, stat, writeFile } from "node:fs/promises";
|
|
12680
|
-
import { createServer } from "node:net";
|
|
14122
|
+
import { createServer as createServer2 } from "node:net";
|
|
12681
14123
|
import { Readable, Writable } from "node:stream";
|
|
12682
14124
|
import * as tty from "node:tty";
|
|
12683
14125
|
var USE_PROCESS_GROUPS2 = process.platform !== "win32";
|
|
@@ -12736,8 +14178,8 @@ var hostFs = {
|
|
|
12736
14178
|
async readFile(path) {
|
|
12737
14179
|
return new Uint8Array(await readFile3(path));
|
|
12738
14180
|
},
|
|
12739
|
-
async writeFile(path, data) {
|
|
12740
|
-
await writeFile(path, data);
|
|
14181
|
+
async writeFile(path, data, options) {
|
|
14182
|
+
await writeFile(path, data, options?.mode !== void 0 ? { mode: options.mode } : void 0);
|
|
12741
14183
|
},
|
|
12742
14184
|
async mkdir(path) {
|
|
12743
14185
|
await mkdir(path, { recursive: true });
|
|
@@ -12775,7 +14217,7 @@ function createStderrStream() {
|
|
|
12775
14217
|
}
|
|
12776
14218
|
function nodeListen(options) {
|
|
12777
14219
|
return new Promise((resolve, reject) => {
|
|
12778
|
-
const server =
|
|
14220
|
+
const server = createServer2();
|
|
12779
14221
|
const pending = [];
|
|
12780
14222
|
const ready = [];
|
|
12781
14223
|
let closed = false;
|
|
@@ -12873,5 +14315,5 @@ function createHostPlatform() {
|
|
|
12873
14315
|
// src/host.ts
|
|
12874
14316
|
var platform = createHostPlatform();
|
|
12875
14317
|
var args = process.argv.slice(2);
|
|
12876
|
-
var code = await run(platform, "0.
|
|
14318
|
+
var code = await run(platform, "0.11.0", args);
|
|
12877
14319
|
process.exit(code);
|