@ai-ecoverse/mcpcat 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/mcpcat.mjs +2299 -341
- package/dist/kernel/mcpcat.mjs +2209 -239
- package/package.json +2 -2
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -25,8 +25,10 @@ __export(node_stub_exports, {
|
|
|
25
25
|
access: () => access,
|
|
26
26
|
basename: () => basename,
|
|
27
27
|
constants: () => constants,
|
|
28
|
+
createHash: () => createHash,
|
|
28
29
|
createInterface: () => createInterface,
|
|
29
30
|
createRequire: () => createRequire,
|
|
31
|
+
createServer: () => createServer,
|
|
30
32
|
default: () => node_stub_default,
|
|
31
33
|
deprecate: () => deprecate,
|
|
32
34
|
dirname: () => dirname,
|
|
@@ -42,6 +44,7 @@ __export(node_stub_exports, {
|
|
|
42
44
|
pid: () => pid,
|
|
43
45
|
platform: () => platform,
|
|
44
46
|
promisify: () => promisify,
|
|
47
|
+
randomBytes: () => randomBytes,
|
|
45
48
|
readFile: () => readFile,
|
|
46
49
|
rename: () => rename,
|
|
47
50
|
resolve: () => resolve,
|
|
@@ -115,6 +118,15 @@ function deprecate() {
|
|
|
115
118
|
function createRequire() {
|
|
116
119
|
return () => "";
|
|
117
120
|
}
|
|
121
|
+
function createServer() {
|
|
122
|
+
throw new Error("not available in kernel");
|
|
123
|
+
}
|
|
124
|
+
function createHash() {
|
|
125
|
+
throw new Error("not available in kernel");
|
|
126
|
+
}
|
|
127
|
+
function randomBytes() {
|
|
128
|
+
throw new Error("not available in kernel");
|
|
129
|
+
}
|
|
118
130
|
var node_stub_default, constants, Readable, Writable, Stream, env, pid, platform, EventEmitter, Worker, workerData, parentPort;
|
|
119
131
|
var init_node_stub = __esm({
|
|
120
132
|
"src/stubs/node-stub.ts"() {
|
|
@@ -4899,6 +4911,367 @@ var init_portable = __esm({
|
|
|
4899
4911
|
}
|
|
4900
4912
|
});
|
|
4901
4913
|
|
|
4914
|
+
// src/args/coerce.ts
|
|
4915
|
+
function asSchemaObject(schema) {
|
|
4916
|
+
return schema ?? {};
|
|
4917
|
+
}
|
|
4918
|
+
function emptyArgObject() {
|
|
4919
|
+
return /* @__PURE__ */ Object.create(null);
|
|
4920
|
+
}
|
|
4921
|
+
function isUnsafeObjectKey(key) {
|
|
4922
|
+
return key === "__proto__" || key === "prototype" || key === "constructor";
|
|
4923
|
+
}
|
|
4924
|
+
function isPlainObject(value) {
|
|
4925
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && value !== Object.prototype;
|
|
4926
|
+
}
|
|
4927
|
+
function splitFlag(a) {
|
|
4928
|
+
const eq = a.indexOf("=");
|
|
4929
|
+
if (eq > 2) return { key: a.slice(2, eq), inlineValue: a.slice(eq + 1) };
|
|
4930
|
+
return { key: a.slice(2), inlineValue: void 0 };
|
|
4931
|
+
}
|
|
4932
|
+
function coerceScalar(raw, type) {
|
|
4933
|
+
switch (type) {
|
|
4934
|
+
case "integer": {
|
|
4935
|
+
if (!/^-?\d+$/.test(raw)) return { ok: false, error: `expected integer, got "${raw}"` };
|
|
4936
|
+
return { ok: true, value: Number(raw) };
|
|
4937
|
+
}
|
|
4938
|
+
case "number": {
|
|
4939
|
+
const n = Number(raw);
|
|
4940
|
+
if (!Number.isFinite(n)) return { ok: false, error: `expected number, got "${raw}"` };
|
|
4941
|
+
return { ok: true, value: n };
|
|
4942
|
+
}
|
|
4943
|
+
case "boolean": {
|
|
4944
|
+
if (raw === "true" || raw === "1" || raw === "yes") return { ok: true, value: true };
|
|
4945
|
+
if (raw === "false" || raw === "0" || raw === "no") return { ok: true, value: false };
|
|
4946
|
+
return { ok: false, error: `expected boolean, got "${raw}"` };
|
|
4947
|
+
}
|
|
4948
|
+
case "object":
|
|
4949
|
+
return coerceJsonObject(raw);
|
|
4950
|
+
default:
|
|
4951
|
+
return { ok: true, value: raw };
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
function coerceJsonObject(raw) {
|
|
4955
|
+
let parsed;
|
|
4956
|
+
try {
|
|
4957
|
+
parsed = JSON.parse(raw);
|
|
4958
|
+
} catch {
|
|
4959
|
+
return { ok: false, error: `expected object JSON, got "${raw}"` };
|
|
4960
|
+
}
|
|
4961
|
+
if (!isPlainObject(parsed)) {
|
|
4962
|
+
return { ok: false, error: `expected object JSON, got "${raw}"` };
|
|
4963
|
+
}
|
|
4964
|
+
return { ok: true, value: parsed };
|
|
4965
|
+
}
|
|
4966
|
+
function resolveToolFlag(key, properties) {
|
|
4967
|
+
if (Object.hasOwn(properties, key)) {
|
|
4968
|
+
return { ok: true, rootKey: key, dottedPath: null, meta: properties[key] ?? {} };
|
|
4969
|
+
}
|
|
4970
|
+
const dot = key.indexOf(".");
|
|
4971
|
+
if (dot <= 0) return { ok: false, error: `unknown flag: --${key}` };
|
|
4972
|
+
const rootKey = key.slice(0, dot);
|
|
4973
|
+
const rest = key.slice(dot + 1);
|
|
4974
|
+
if (!Object.hasOwn(properties, rootKey) || rest.length === 0) {
|
|
4975
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
4976
|
+
}
|
|
4977
|
+
const parts = rest.split(".");
|
|
4978
|
+
if (isUnsafeObjectKey(rootKey) || parts.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
|
|
4979
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
4980
|
+
}
|
|
4981
|
+
return { ok: true, rootKey, dottedPath: parts, meta: properties[rootKey] ?? {} };
|
|
4982
|
+
}
|
|
4983
|
+
function readFlagRaw(args, i, key, inlineValue, type, dottedPath) {
|
|
4984
|
+
if (inlineValue !== void 0) {
|
|
4985
|
+
return { ok: true, raw: inlineValue, nextIndex: i + 1, bareBoolean: false };
|
|
4986
|
+
}
|
|
4987
|
+
const next = args[i + 1];
|
|
4988
|
+
if (!dottedPath && type === "boolean" && (next === void 0 || next.startsWith("--"))) {
|
|
4989
|
+
return { ok: true, raw: "", nextIndex: i + 1, bareBoolean: true };
|
|
4990
|
+
}
|
|
4991
|
+
if (next === void 0) {
|
|
4992
|
+
return { ok: false, error: `flag --${key} requires a value` };
|
|
4993
|
+
}
|
|
4994
|
+
return { ok: true, raw: next, nextIndex: i + 2, bareBoolean: false };
|
|
4995
|
+
}
|
|
4996
|
+
function setDotted(out, rootKey, path, value) {
|
|
4997
|
+
if (isUnsafeObjectKey(rootKey) || path.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
|
|
4998
|
+
return { ok: false, error: `unknown flag: --${rootKey}.${path.join(".")}` };
|
|
4999
|
+
}
|
|
5000
|
+
const existing = Object.hasOwn(out, rootKey) ? out[rootKey] : void 0;
|
|
5001
|
+
if (existing === void 0) {
|
|
5002
|
+
out[rootKey] = emptyArgObject();
|
|
5003
|
+
} else if (!isPlainObject(existing)) {
|
|
5004
|
+
return { ok: false, error: `--${rootKey}: cannot nest into a ${typeof existing} value` };
|
|
5005
|
+
}
|
|
5006
|
+
let cursor = out[rootKey];
|
|
5007
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
5008
|
+
const seg = path[i];
|
|
5009
|
+
const next = Object.hasOwn(cursor, seg) ? cursor[seg] : void 0;
|
|
5010
|
+
if (next === void 0) {
|
|
5011
|
+
cursor[seg] = emptyArgObject();
|
|
5012
|
+
} else if (!isPlainObject(next)) {
|
|
5013
|
+
return {
|
|
5014
|
+
ok: false,
|
|
5015
|
+
error: `--${rootKey}.${path.slice(0, i + 1).join(".")}: cannot nest into a ${typeof next} value`
|
|
5016
|
+
};
|
|
5017
|
+
}
|
|
5018
|
+
cursor = cursor[seg];
|
|
5019
|
+
}
|
|
5020
|
+
cursor[path[path.length - 1]] = value;
|
|
5021
|
+
return { ok: true };
|
|
5022
|
+
}
|
|
5023
|
+
function assignFlagValue(out, rootKey, dottedPath, value, isArray) {
|
|
5024
|
+
if (dottedPath) return setDotted(out, rootKey, dottedPath, value);
|
|
5025
|
+
if (isArray) {
|
|
5026
|
+
const prev = out[rootKey];
|
|
5027
|
+
if (Array.isArray(prev)) prev.push(value);
|
|
5028
|
+
else out[rootKey] = [value];
|
|
5029
|
+
return { ok: true };
|
|
5030
|
+
}
|
|
5031
|
+
const existing = out[rootKey];
|
|
5032
|
+
if (isPlainObject(existing) && isPlainObject(value)) {
|
|
5033
|
+
Object.assign(existing, value);
|
|
5034
|
+
return { ok: true };
|
|
5035
|
+
}
|
|
5036
|
+
out[rootKey] = value;
|
|
5037
|
+
return { ok: true };
|
|
5038
|
+
}
|
|
5039
|
+
function parseOneFlag(args, i, properties, out) {
|
|
5040
|
+
const a = args[i];
|
|
5041
|
+
if (a === "--") return { ok: true, nextIndex: args.length };
|
|
5042
|
+
if (a.startsWith("--no-") && a.length > 5) {
|
|
5043
|
+
const key2 = a.slice(5);
|
|
5044
|
+
if (Object.hasOwn(properties, key2) && properties[key2]?.type === "boolean") {
|
|
5045
|
+
out[key2] = false;
|
|
5046
|
+
return { ok: true, nextIndex: i + 1 };
|
|
5047
|
+
}
|
|
5048
|
+
return { ok: false, error: `unknown flag: ${a}` };
|
|
5049
|
+
}
|
|
5050
|
+
if (!a.startsWith("--")) {
|
|
5051
|
+
return { ok: false, error: `unexpected positional argument "${a}"` };
|
|
5052
|
+
}
|
|
5053
|
+
const { key, inlineValue } = splitFlag(a);
|
|
5054
|
+
const resolved = resolveToolFlag(key, properties);
|
|
5055
|
+
if (!resolved.ok) return resolved;
|
|
5056
|
+
const { rootKey, dottedPath, meta } = resolved;
|
|
5057
|
+
const declaredType = meta.type;
|
|
5058
|
+
const type = declaredType ?? "string";
|
|
5059
|
+
const isArray = type === "array";
|
|
5060
|
+
const itemType = (isArray ? meta.items?.type : void 0) ?? "string";
|
|
5061
|
+
if (dottedPath && declaredType !== void 0 && declaredType !== "object") {
|
|
5062
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
5063
|
+
}
|
|
5064
|
+
const taken = readFlagRaw(args, i, key, inlineValue, type, dottedPath);
|
|
5065
|
+
if (!taken.ok) return taken;
|
|
5066
|
+
if (taken.bareBoolean) {
|
|
5067
|
+
out[rootKey] = true;
|
|
5068
|
+
return { ok: true, nextIndex: taken.nextIndex };
|
|
5069
|
+
}
|
|
5070
|
+
const valueType = dottedPath ? "string" : isArray ? itemType : type;
|
|
5071
|
+
const coerced = coerceScalar(taken.raw, valueType);
|
|
5072
|
+
if (!coerced.ok) return { ok: false, error: `--${key}: ${coerced.error}` };
|
|
5073
|
+
const assigned = assignFlagValue(out, rootKey, dottedPath, coerced.value, isArray);
|
|
5074
|
+
if (!assigned.ok) return assigned;
|
|
5075
|
+
return { ok: true, nextIndex: taken.nextIndex };
|
|
5076
|
+
}
|
|
5077
|
+
function coerceArgsBySchema(args, schema, options) {
|
|
5078
|
+
const s = asSchemaObject(schema);
|
|
5079
|
+
const properties = s.properties ?? {};
|
|
5080
|
+
const required = new Set(Array.isArray(s.required) ? s.required : []);
|
|
5081
|
+
const out = emptyArgObject();
|
|
5082
|
+
let i = 0;
|
|
5083
|
+
while (i < args.length) {
|
|
5084
|
+
const result = parseOneFlag(args, i, properties, out);
|
|
5085
|
+
if (!result.ok) return result;
|
|
5086
|
+
i = result.nextIndex;
|
|
5087
|
+
}
|
|
5088
|
+
if (!options?.noValidate) {
|
|
5089
|
+
for (const r of required) {
|
|
5090
|
+
if (!Object.hasOwn(out, r)) {
|
|
5091
|
+
return { ok: false, error: `missing required flag --${r}` };
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
return { ok: true, value: out };
|
|
5096
|
+
}
|
|
5097
|
+
function mergeArgs(base, flags) {
|
|
5098
|
+
const result = { ...base };
|
|
5099
|
+
for (const [k, v] of Object.entries(flags)) {
|
|
5100
|
+
result[k] = v;
|
|
5101
|
+
}
|
|
5102
|
+
return result;
|
|
5103
|
+
}
|
|
5104
|
+
|
|
5105
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/callback.js
|
|
5106
|
+
init_node_stub();
|
|
5107
|
+
function plainText(page) {
|
|
5108
|
+
if (page.ok)
|
|
5109
|
+
return "Authorization complete. You may close this window.";
|
|
5110
|
+
return page.details ? `${page.message}
|
|
5111
|
+
|
|
5112
|
+
${page.details}` : page.message;
|
|
5113
|
+
}
|
|
5114
|
+
var OAuthCallbackServer = class _OAuthCallbackServer {
|
|
5115
|
+
redirectUrl;
|
|
5116
|
+
server;
|
|
5117
|
+
paths;
|
|
5118
|
+
timeoutMs;
|
|
5119
|
+
renderPage;
|
|
5120
|
+
pending = /* @__PURE__ */ new Map();
|
|
5121
|
+
constructor(server, redirectUrl, paths, timeoutMs, renderPage) {
|
|
5122
|
+
this.server = server;
|
|
5123
|
+
this.redirectUrl = redirectUrl;
|
|
5124
|
+
this.paths = paths;
|
|
5125
|
+
this.timeoutMs = timeoutMs;
|
|
5126
|
+
this.renderPage = renderPage;
|
|
5127
|
+
}
|
|
5128
|
+
static async listen(options = {}) {
|
|
5129
|
+
const host = options.host ?? "127.0.0.1";
|
|
5130
|
+
const redirectHost = options.redirectHost ?? host;
|
|
5131
|
+
const path = options.path ?? "/callback";
|
|
5132
|
+
let instance;
|
|
5133
|
+
const server = createServer((request, response) => instance?.handle(request.url ?? "/", response));
|
|
5134
|
+
await new Promise((resolve2, reject) => {
|
|
5135
|
+
server.once("error", reject);
|
|
5136
|
+
server.listen(options.port ?? 0, host, () => {
|
|
5137
|
+
server.off("error", reject);
|
|
5138
|
+
resolve2();
|
|
5139
|
+
});
|
|
5140
|
+
});
|
|
5141
|
+
const address = server.address();
|
|
5142
|
+
if (!address || typeof address === "string")
|
|
5143
|
+
throw new Error("OAuth callback server did not bind to TCP");
|
|
5144
|
+
instance = new _OAuthCallbackServer(server, `http://${redirectHost.includes(":") ? `[${redirectHost}]` : redirectHost}:${address.port}${path}`, [path, ...options.extraPaths ?? []], options.timeoutMs ?? 5 * 6e4, options.renderPage);
|
|
5145
|
+
return instance;
|
|
5146
|
+
}
|
|
5147
|
+
/**
|
|
5148
|
+
* Wait for the authorization response with `state`. With `path`, a response on another path fails, so
|
|
5149
|
+
* a server-specific redirect URI can tell authorization servers apart (RFC 9700 section 4.4.2.2).
|
|
5150
|
+
*/
|
|
5151
|
+
waitForCallback(state, path) {
|
|
5152
|
+
if (this.pending.has(state))
|
|
5153
|
+
throw new Error("OAuth state is already pending");
|
|
5154
|
+
return new Promise((resolve2, reject) => {
|
|
5155
|
+
const timer = setTimeout(() => {
|
|
5156
|
+
this.pending.delete(state);
|
|
5157
|
+
reject(new Error("OAuth callback timed out"));
|
|
5158
|
+
}, this.timeoutMs);
|
|
5159
|
+
this.pending.set(state, { resolve: resolve2, reject, timer, path });
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
async close() {
|
|
5163
|
+
for (const pending of this.pending.values()) {
|
|
5164
|
+
clearTimeout(pending.timer);
|
|
5165
|
+
pending.reject(new Error("OAuth callback server closed"));
|
|
5166
|
+
}
|
|
5167
|
+
this.pending.clear();
|
|
5168
|
+
await new Promise((resolve2, reject) => {
|
|
5169
|
+
this.server.close((error) => error ? reject(error) : resolve2());
|
|
5170
|
+
});
|
|
5171
|
+
}
|
|
5172
|
+
reply(response, status, page) {
|
|
5173
|
+
if (this.renderPage) {
|
|
5174
|
+
response.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
5175
|
+
response.end(this.renderPage(page));
|
|
5176
|
+
} else {
|
|
5177
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8" }).end(plainText(page));
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
handle(rawUrl, response) {
|
|
5181
|
+
const url = new URL(rawUrl, this.redirectUrl);
|
|
5182
|
+
if (!this.paths.includes(url.pathname)) {
|
|
5183
|
+
this.reply(response, 404, { ok: false, message: "Not found" });
|
|
5184
|
+
return;
|
|
5185
|
+
}
|
|
5186
|
+
const state = url.searchParams.get("state");
|
|
5187
|
+
const pending = state ? this.pending.get(state) : void 0;
|
|
5188
|
+
if (!state || !pending) {
|
|
5189
|
+
this.reply(response, 400, { ok: false, message: "Invalid or expired OAuth state" });
|
|
5190
|
+
return;
|
|
5191
|
+
}
|
|
5192
|
+
clearTimeout(pending.timer);
|
|
5193
|
+
this.pending.delete(state);
|
|
5194
|
+
if (pending.path !== void 0 && url.pathname !== pending.path) {
|
|
5195
|
+
pending.reject(new Error("The authorization response arrived on another redirect URI"));
|
|
5196
|
+
this.reply(response, 400, { ok: false, message: "Unexpected redirect URI" });
|
|
5197
|
+
return;
|
|
5198
|
+
}
|
|
5199
|
+
const error = url.searchParams.get("error");
|
|
5200
|
+
if (error) {
|
|
5201
|
+
const description = url.searchParams.get("error_description") ?? error;
|
|
5202
|
+
pending.reject(new Error(description));
|
|
5203
|
+
this.reply(response, 200, {
|
|
5204
|
+
ok: false,
|
|
5205
|
+
message: "Authorization failed. You may close this window.",
|
|
5206
|
+
details: description
|
|
5207
|
+
});
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
const code = url.searchParams.get("code");
|
|
5211
|
+
if (!code) {
|
|
5212
|
+
pending.reject(new Error("OAuth callback did not include an authorization code"));
|
|
5213
|
+
this.reply(response, 400, { ok: false, message: "Missing authorization code" });
|
|
5214
|
+
return;
|
|
5215
|
+
}
|
|
5216
|
+
const iss = url.searchParams.get("iss");
|
|
5217
|
+
pending.resolve({ code, state, ...iss ? { iss } : {} });
|
|
5218
|
+
this.reply(response, 200, { ok: true });
|
|
5219
|
+
}
|
|
5220
|
+
};
|
|
5221
|
+
|
|
5222
|
+
// node_modules/@earendil-works/pi-mcp/dist/protocol/types.js
|
|
5223
|
+
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
5224
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05"];
|
|
5225
|
+
|
|
5226
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/errors.js
|
|
5227
|
+
var OAuthError = class extends Error {
|
|
5228
|
+
code;
|
|
5229
|
+
errorUri;
|
|
5230
|
+
constructor(code, message, errorUri) {
|
|
5231
|
+
super(message || code);
|
|
5232
|
+
this.name = "OAuthError";
|
|
5233
|
+
this.code = code;
|
|
5234
|
+
this.errorUri = errorUri;
|
|
5235
|
+
}
|
|
5236
|
+
};
|
|
5237
|
+
var OAuthIssuerMismatchError = class extends Error {
|
|
5238
|
+
/** `undefined` when no authorization server metadata was discovered. */
|
|
5239
|
+
expected;
|
|
5240
|
+
/** `undefined` when an authorization response lacks the `iss` parameter its server promised (RFC 9207). */
|
|
5241
|
+
received;
|
|
5242
|
+
constructor(expected, received) {
|
|
5243
|
+
const format = (issuer) => issuer === void 0 ? "none" : JSON.stringify(issuer);
|
|
5244
|
+
super(`OAuth issuer mismatch: expected ${format(expected)}, received ${format(received)}`);
|
|
5245
|
+
this.name = "OAuthIssuerMismatchError";
|
|
5246
|
+
this.expected = expected;
|
|
5247
|
+
this.received = received;
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
var OAuthInsecureEndpointError = class extends Error {
|
|
5251
|
+
endpoint;
|
|
5252
|
+
constructor(endpoint) {
|
|
5253
|
+
super(`Refusing to send OAuth credentials to non-HTTPS endpoint ${endpoint}`);
|
|
5254
|
+
this.name = "OAuthInsecureEndpointError";
|
|
5255
|
+
this.endpoint = endpoint;
|
|
5256
|
+
}
|
|
5257
|
+
};
|
|
5258
|
+
var OAuthRegistrationError = class extends Error {
|
|
5259
|
+
status;
|
|
5260
|
+
body;
|
|
5261
|
+
constructor(status, body) {
|
|
5262
|
+
super(`OAuth dynamic client registration failed with status ${status}: ${body}`);
|
|
5263
|
+
this.name = "OAuthRegistrationError";
|
|
5264
|
+
this.status = status;
|
|
5265
|
+
this.body = body;
|
|
5266
|
+
}
|
|
5267
|
+
};
|
|
5268
|
+
var McpOAuthAuthorizationRequiredError = class extends Error {
|
|
5269
|
+
constructor() {
|
|
5270
|
+
super("MCP OAuth authorization requires user interaction");
|
|
5271
|
+
this.name = "McpOAuthAuthorizationRequiredError";
|
|
5272
|
+
}
|
|
5273
|
+
};
|
|
5274
|
+
|
|
4902
5275
|
// node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
|
|
4903
5276
|
var JSON_RPC_ERROR_CODES = {
|
|
4904
5277
|
parseError: -32700,
|
|
@@ -4967,9 +5340,726 @@ function parseJsonRpcMessage(value) {
|
|
|
4967
5340
|
throw new McpError(JSON_RPC_ERROR_CODES.invalidRequest, "Invalid JSON-RPC message");
|
|
4968
5341
|
}
|
|
4969
5342
|
|
|
4970
|
-
// node_modules/@earendil-works/pi-mcp/dist/
|
|
4971
|
-
|
|
4972
|
-
|
|
5343
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/types.js
|
|
5344
|
+
function object(value, name) {
|
|
5345
|
+
if (!isObject(value))
|
|
5346
|
+
throw new Error(`Invalid ${name}`);
|
|
5347
|
+
return value;
|
|
5348
|
+
}
|
|
5349
|
+
function compact(value) {
|
|
5350
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
|
|
5351
|
+
}
|
|
5352
|
+
function requiredString(value, name) {
|
|
5353
|
+
if (typeof value !== "string" || value.length === 0)
|
|
5354
|
+
throw new Error(`Invalid ${name}`);
|
|
5355
|
+
return value;
|
|
5356
|
+
}
|
|
5357
|
+
function absent(value) {
|
|
5358
|
+
return value === void 0 || value === null || value === "";
|
|
5359
|
+
}
|
|
5360
|
+
function optionalString(value, name) {
|
|
5361
|
+
if (absent(value))
|
|
5362
|
+
return void 0;
|
|
5363
|
+
return requiredString(value, name);
|
|
5364
|
+
}
|
|
5365
|
+
function optionalStrings(value, name) {
|
|
5366
|
+
if (value === void 0 || value === null)
|
|
5367
|
+
return void 0;
|
|
5368
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
5369
|
+
throw new Error(`Invalid ${name}`);
|
|
5370
|
+
return [...value];
|
|
5371
|
+
}
|
|
5372
|
+
function safeUrl(value, name) {
|
|
5373
|
+
const text = requiredString(value, name);
|
|
5374
|
+
if (!URL.canParse(text))
|
|
5375
|
+
throw new Error(`Invalid ${name}`);
|
|
5376
|
+
const url = new URL(text);
|
|
5377
|
+
if (["javascript:", "data:", "vbscript:"].includes(url.protocol))
|
|
5378
|
+
throw new Error(`Invalid ${name}`);
|
|
5379
|
+
return text;
|
|
5380
|
+
}
|
|
5381
|
+
function optionalUrl(value, name) {
|
|
5382
|
+
return absent(value) ? void 0 : safeUrl(value, name);
|
|
5383
|
+
}
|
|
5384
|
+
function parseProtectedResourceMetadata(value) {
|
|
5385
|
+
const input = object(value, "OAuth protected resource metadata");
|
|
5386
|
+
return compact({
|
|
5387
|
+
...input,
|
|
5388
|
+
resource: safeUrl(input.resource, "OAuth protected resource metadata resource"),
|
|
5389
|
+
authorization_servers: optionalStrings(input.authorization_servers, "authorization_servers")?.map((url) => safeUrl(url, "authorization server URL")),
|
|
5390
|
+
scopes_supported: optionalStrings(input.scopes_supported, "scopes_supported")
|
|
5391
|
+
});
|
|
5392
|
+
}
|
|
5393
|
+
function parseAuthorizationServerMetadata(value) {
|
|
5394
|
+
const input = object(value, "authorization server metadata");
|
|
5395
|
+
const responseTypes = optionalStrings(input.response_types_supported, "response_types_supported");
|
|
5396
|
+
if (!responseTypes)
|
|
5397
|
+
throw new Error("Invalid response_types_supported");
|
|
5398
|
+
return compact({
|
|
5399
|
+
...input,
|
|
5400
|
+
issuer: safeUrl(input.issuer, "authorization server issuer"),
|
|
5401
|
+
authorization_endpoint: safeUrl(input.authorization_endpoint, "authorization endpoint"),
|
|
5402
|
+
token_endpoint: safeUrl(input.token_endpoint, "token endpoint"),
|
|
5403
|
+
registration_endpoint: optionalUrl(input.registration_endpoint, "registration endpoint"),
|
|
5404
|
+
scopes_supported: optionalStrings(input.scopes_supported, "scopes_supported"),
|
|
5405
|
+
response_types_supported: responseTypes,
|
|
5406
|
+
grant_types_supported: optionalStrings(input.grant_types_supported, "grant_types_supported"),
|
|
5407
|
+
token_endpoint_auth_methods_supported: optionalStrings(input.token_endpoint_auth_methods_supported, "token_endpoint_auth_methods_supported"),
|
|
5408
|
+
code_challenge_methods_supported: optionalStrings(input.code_challenge_methods_supported, "code_challenge_methods_supported"),
|
|
5409
|
+
client_id_metadata_document_supported: typeof input.client_id_metadata_document_supported === "boolean" ? input.client_id_metadata_document_supported : void 0,
|
|
5410
|
+
authorization_response_iss_parameter_supported: typeof input.authorization_response_iss_parameter_supported === "boolean" ? input.authorization_response_iss_parameter_supported : void 0
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
function parseOAuthTokens(value) {
|
|
5414
|
+
const input = object(value, "OAuth token response");
|
|
5415
|
+
const expires = absent(input.expires_in) ? void 0 : Number(input.expires_in);
|
|
5416
|
+
if (expires !== void 0 && !Number.isFinite(expires))
|
|
5417
|
+
throw new Error("Invalid expires_in");
|
|
5418
|
+
return compact({
|
|
5419
|
+
access_token: requiredString(input.access_token, "access_token"),
|
|
5420
|
+
token_type: requiredString(input.token_type, "token_type"),
|
|
5421
|
+
expires_in: expires,
|
|
5422
|
+
scope: optionalString(input.scope, "scope"),
|
|
5423
|
+
refresh_token: optionalString(input.refresh_token, "refresh_token"),
|
|
5424
|
+
id_token: optionalString(input.id_token, "id_token")
|
|
5425
|
+
});
|
|
5426
|
+
}
|
|
5427
|
+
function parseClientInformation(value) {
|
|
5428
|
+
const input = object(value, "OAuth client registration response");
|
|
5429
|
+
return compact({
|
|
5430
|
+
...input,
|
|
5431
|
+
client_id: requiredString(input.client_id, "client_id"),
|
|
5432
|
+
client_secret: optionalString(input.client_secret, "client_secret"),
|
|
5433
|
+
client_id_issued_at: typeof input.client_id_issued_at === "number" ? input.client_id_issued_at : void 0,
|
|
5434
|
+
client_secret_expires_at: typeof input.client_secret_expires_at === "number" ? input.client_secret_expires_at : void 0,
|
|
5435
|
+
redirect_uris: optionalStrings(input.redirect_uris, "redirect_uris") ?? []
|
|
5436
|
+
});
|
|
5437
|
+
}
|
|
5438
|
+
|
|
5439
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/discovery.js
|
|
5440
|
+
function discard(response) {
|
|
5441
|
+
void response?.body?.cancel().catch(() => {
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5444
|
+
function isDiscoveryMiss(status) {
|
|
5445
|
+
return status >= 400 && status < 500 || status === 502;
|
|
5446
|
+
}
|
|
5447
|
+
function pathSuffix(pathname) {
|
|
5448
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
5449
|
+
}
|
|
5450
|
+
function field(header, name) {
|
|
5451
|
+
const match = header.match(new RegExp(String.raw`(?:^|[,\s])${name}=(?:"([^"]*)"|([^\s,]+))`, "i"));
|
|
5452
|
+
return match?.[1] || match?.[2] || void 0;
|
|
5453
|
+
}
|
|
5454
|
+
function parseWwwAuthenticate(header) {
|
|
5455
|
+
if (!header)
|
|
5456
|
+
return {};
|
|
5457
|
+
const scheme = header.trimStart().split(/\s+/, 1)[0]?.toLowerCase();
|
|
5458
|
+
if (scheme !== "bearer" && scheme !== "dpop")
|
|
5459
|
+
return {};
|
|
5460
|
+
const resourceMetadata = field(header, "resource_metadata");
|
|
5461
|
+
let resourceMetadataUrl;
|
|
5462
|
+
if (resourceMetadata) {
|
|
5463
|
+
try {
|
|
5464
|
+
resourceMetadataUrl = new URL(resourceMetadata);
|
|
5465
|
+
} catch {
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
return {
|
|
5469
|
+
resourceMetadataUrl,
|
|
5470
|
+
scope: field(header, "scope"),
|
|
5471
|
+
error: field(header, "error"),
|
|
5472
|
+
errorDescription: field(header, "error_description")
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
async function fetchMetadata(url, fetch, protocolVersion, signal) {
|
|
5476
|
+
return fetch(url, {
|
|
5477
|
+
headers: { Accept: "application/json", "MCP-Protocol-Version": protocolVersion },
|
|
5478
|
+
signal
|
|
5479
|
+
});
|
|
5480
|
+
}
|
|
5481
|
+
async function discoverProtectedResourceMetadata(serverUrl, options = {}) {
|
|
5482
|
+
const server = new URL(serverUrl);
|
|
5483
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
5484
|
+
const version = options.protocolVersion ?? LATEST_PROTOCOL_VERSION;
|
|
5485
|
+
let response = await fetchMetadata(options.resourceMetadataUrl ? new URL(options.resourceMetadataUrl) : new URL(`/.well-known/oauth-protected-resource${pathSuffix(server.pathname)}`, server.origin), fetch, version, options.signal);
|
|
5486
|
+
if (!options.resourceMetadataUrl && server.pathname !== "/" && isDiscoveryMiss(response.status)) {
|
|
5487
|
+
discard(response);
|
|
5488
|
+
response = await fetchMetadata(new URL("/.well-known/oauth-protected-resource", server.origin), fetch, version, options.signal);
|
|
5489
|
+
}
|
|
5490
|
+
if (!response.ok) {
|
|
5491
|
+
discard(response);
|
|
5492
|
+
throw new Error(`HTTP ${response.status} loading OAuth protected resource metadata`);
|
|
5493
|
+
}
|
|
5494
|
+
return parseProtectedResourceMetadata(await response.json());
|
|
5495
|
+
}
|
|
5496
|
+
function buildAuthorizationServerDiscoveryUrls(authorizationServerUrl) {
|
|
5497
|
+
const issuer = new URL(authorizationServerUrl);
|
|
5498
|
+
const path = pathSuffix(issuer.pathname);
|
|
5499
|
+
const urls = [
|
|
5500
|
+
{ url: new URL(`/.well-known/oauth-authorization-server${path}`, issuer.origin), type: "oauth" },
|
|
5501
|
+
{ url: new URL(`/.well-known/openid-configuration${path}`, issuer.origin), type: "oidc" }
|
|
5502
|
+
];
|
|
5503
|
+
if (path)
|
|
5504
|
+
urls.push({ url: new URL(`${path}/.well-known/openid-configuration`, issuer.origin), type: "oidc" });
|
|
5505
|
+
return urls;
|
|
5506
|
+
}
|
|
5507
|
+
async function discoverAuthorizationServerMetadata(authorizationServerUrl, options = {}) {
|
|
5508
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
5509
|
+
for (const { url } of buildAuthorizationServerDiscoveryUrls(authorizationServerUrl)) {
|
|
5510
|
+
const response = await fetchMetadata(url, fetch, options.protocolVersion ?? LATEST_PROTOCOL_VERSION, options.signal);
|
|
5511
|
+
if (!response.ok) {
|
|
5512
|
+
discard(response);
|
|
5513
|
+
if (isDiscoveryMiss(response.status))
|
|
5514
|
+
continue;
|
|
5515
|
+
throw new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);
|
|
5516
|
+
}
|
|
5517
|
+
const metadata = parseAuthorizationServerMetadata(await response.json());
|
|
5518
|
+
if (!options.skipIssuerValidation) {
|
|
5519
|
+
const expected = String(authorizationServerUrl);
|
|
5520
|
+
const trim = (value) => value.endsWith("/") ? value.slice(0, -1) : value;
|
|
5521
|
+
if (trim(metadata.issuer) !== trim(expected))
|
|
5522
|
+
throw new OAuthIssuerMismatchError(expected, metadata.issuer);
|
|
5523
|
+
}
|
|
5524
|
+
return metadata;
|
|
5525
|
+
}
|
|
5526
|
+
return void 0;
|
|
5527
|
+
}
|
|
5528
|
+
async function discoverOAuthServerInfo(serverUrl, options = {}) {
|
|
5529
|
+
let resourceMetadata;
|
|
5530
|
+
try {
|
|
5531
|
+
resourceMetadata = await discoverProtectedResourceMetadata(serverUrl, {
|
|
5532
|
+
resourceMetadataUrl: options.resourceMetadataUrl,
|
|
5533
|
+
fetch: options.fetch,
|
|
5534
|
+
signal: options.signal
|
|
5535
|
+
});
|
|
5536
|
+
} catch (error) {
|
|
5537
|
+
if (error instanceof TypeError)
|
|
5538
|
+
throw error;
|
|
5539
|
+
}
|
|
5540
|
+
if (options.authorizationServerMetadataUrl) {
|
|
5541
|
+
const url = options.authorizationServerMetadataUrl;
|
|
5542
|
+
const response = await fetchMetadata(url, options.fetch ?? globalThis.fetch, LATEST_PROTOCOL_VERSION, options.signal);
|
|
5543
|
+
if (!response.ok) {
|
|
5544
|
+
discard(response);
|
|
5545
|
+
throw new Error(`HTTP ${response.status} loading authorization server metadata from ${url}`);
|
|
5546
|
+
}
|
|
5547
|
+
const metadata = parseAuthorizationServerMetadata(await response.json());
|
|
5548
|
+
return { authorizationServerUrl: metadata.issuer, authorizationServerMetadata: metadata, resourceMetadata };
|
|
5549
|
+
}
|
|
5550
|
+
const authorizationServerUrl = resourceMetadata?.authorization_servers?.[0] ?? String(new URL("/", serverUrl));
|
|
5551
|
+
return {
|
|
5552
|
+
authorizationServerUrl,
|
|
5553
|
+
authorizationServerMetadata: await discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
5554
|
+
fetch: options.fetch,
|
|
5555
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
5556
|
+
signal: options.signal
|
|
5557
|
+
}),
|
|
5558
|
+
resourceMetadata
|
|
5559
|
+
};
|
|
5560
|
+
}
|
|
5561
|
+
function resourceUrlFromServerUrl(value) {
|
|
5562
|
+
const url = new URL(value);
|
|
5563
|
+
url.hash = "";
|
|
5564
|
+
return url;
|
|
5565
|
+
}
|
|
5566
|
+
function selectResource(serverUrl, metadata) {
|
|
5567
|
+
if (!metadata)
|
|
5568
|
+
return void 0;
|
|
5569
|
+
const requested = resourceUrlFromServerUrl(serverUrl);
|
|
5570
|
+
const configured = new URL(metadata.resource);
|
|
5571
|
+
if (requested.origin !== configured.origin) {
|
|
5572
|
+
throw new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);
|
|
5573
|
+
}
|
|
5574
|
+
const requestedPath = requested.pathname.endsWith("/") ? requested.pathname : `${requested.pathname}/`;
|
|
5575
|
+
const configuredPath = configured.pathname.endsWith("/") ? configured.pathname : `${configured.pathname}/`;
|
|
5576
|
+
if (!requestedPath.startsWith(configuredPath)) {
|
|
5577
|
+
throw new Error(`Protected resource ${metadata.resource} does not match MCP server ${requested}`);
|
|
5578
|
+
}
|
|
5579
|
+
return metadata.resource;
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5582
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/flow.js
|
|
5583
|
+
function loopback(hostname) {
|
|
5584
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
|
|
5585
|
+
}
|
|
5586
|
+
function applicationType(redirectUris) {
|
|
5587
|
+
const native = redirectUris.some((uri) => {
|
|
5588
|
+
if (!URL.canParse(uri))
|
|
5589
|
+
return false;
|
|
5590
|
+
const url = new URL(uri);
|
|
5591
|
+
return url.protocol !== "http:" && url.protocol !== "https:" || loopback(url.hostname);
|
|
5592
|
+
});
|
|
5593
|
+
return native ? "native" : "web";
|
|
5594
|
+
}
|
|
5595
|
+
function secureEndpoint(value) {
|
|
5596
|
+
const url = new URL(value);
|
|
5597
|
+
if (url.protocol !== "https:" && !loopback(url.hostname))
|
|
5598
|
+
throw new OAuthInsecureEndpointError(url.href);
|
|
5599
|
+
return url;
|
|
5600
|
+
}
|
|
5601
|
+
function selectClientAuthMethod(information, supported) {
|
|
5602
|
+
const hinted = "token_endpoint_auth_method" in information ? information.token_endpoint_auth_method : void 0;
|
|
5603
|
+
if (hinted && ["client_secret_basic", "client_secret_post", "none"].includes(hinted) && (supported.length === 0 || supported.includes(hinted))) {
|
|
5604
|
+
return hinted;
|
|
5605
|
+
}
|
|
5606
|
+
if (supported.length === 0)
|
|
5607
|
+
return information.client_secret ? "client_secret_basic" : "none";
|
|
5608
|
+
if (information.client_secret && supported.includes("client_secret_basic"))
|
|
5609
|
+
return "client_secret_basic";
|
|
5610
|
+
if (information.client_secret && supported.includes("client_secret_post"))
|
|
5611
|
+
return "client_secret_post";
|
|
5612
|
+
if (supported.includes("none"))
|
|
5613
|
+
return "none";
|
|
5614
|
+
return information.client_secret ? "client_secret_post" : "none";
|
|
5615
|
+
}
|
|
5616
|
+
function applyClientAuthentication(method, information, headers, params) {
|
|
5617
|
+
if (method === "client_secret_basic") {
|
|
5618
|
+
if (!information.client_secret)
|
|
5619
|
+
throw new Error("client_secret_basic requires a client secret");
|
|
5620
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${information.client_id}:${information.client_secret}`).toString("base64")}`);
|
|
5621
|
+
} else {
|
|
5622
|
+
params.set("client_id", information.client_id);
|
|
5623
|
+
if (method === "client_secret_post" && information.client_secret)
|
|
5624
|
+
params.set("client_secret", information.client_secret);
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
async function pkce() {
|
|
5628
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
5629
|
+
const verifier = Buffer.from(bytes).toString("base64url");
|
|
5630
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
5631
|
+
return { verifier, challenge: Buffer.from(digest).toString("base64url") };
|
|
5632
|
+
}
|
|
5633
|
+
async function startAuthorization(authorizationServerUrl, options) {
|
|
5634
|
+
const metadata = options.metadata;
|
|
5635
|
+
if (metadata && !metadata.response_types_supported.includes("code")) {
|
|
5636
|
+
throw new Error("Authorization server does not support authorization codes");
|
|
5637
|
+
}
|
|
5638
|
+
if (metadata?.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes("S256")) {
|
|
5639
|
+
throw new Error("Authorization server does not support PKCE S256");
|
|
5640
|
+
}
|
|
5641
|
+
const url = new URL(metadata?.authorization_endpoint ?? new URL("/authorize", authorizationServerUrl));
|
|
5642
|
+
const { verifier, challenge } = await pkce();
|
|
5643
|
+
url.searchParams.set("response_type", "code");
|
|
5644
|
+
url.searchParams.set("client_id", options.clientInformation.client_id);
|
|
5645
|
+
url.searchParams.set("code_challenge", challenge);
|
|
5646
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
5647
|
+
url.searchParams.set("redirect_uri", String(options.redirectUrl));
|
|
5648
|
+
if (options.state)
|
|
5649
|
+
url.searchParams.set("state", options.state);
|
|
5650
|
+
if (options.scope)
|
|
5651
|
+
url.searchParams.set("scope", options.scope);
|
|
5652
|
+
if (options.scope?.split(/\s+/).includes("offline_access"))
|
|
5653
|
+
url.searchParams.set("prompt", "consent");
|
|
5654
|
+
if (options.resource)
|
|
5655
|
+
url.searchParams.set("resource", options.resource);
|
|
5656
|
+
return { authorizationUrl: url, codeVerifier: verifier };
|
|
5657
|
+
}
|
|
5658
|
+
async function tokenRequest(authorizationServerUrl, options, params) {
|
|
5659
|
+
const url = secureEndpoint(options.metadata?.token_endpoint ?? new URL("/token", authorizationServerUrl));
|
|
5660
|
+
const headers = new Headers({ Accept: "application/json", "content-type": "application/x-www-form-urlencoded" });
|
|
5661
|
+
if (options.resource)
|
|
5662
|
+
params.set("resource", options.resource);
|
|
5663
|
+
if (options.addClientAuthentication) {
|
|
5664
|
+
await options.addClientAuthentication(headers, params, url, options.metadata);
|
|
5665
|
+
} else {
|
|
5666
|
+
applyClientAuthentication(selectClientAuthMethod(options.clientInformation, options.metadata?.token_endpoint_auth_methods_supported ?? []), options.clientInformation, headers, params);
|
|
5667
|
+
}
|
|
5668
|
+
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
5669
|
+
method: "POST",
|
|
5670
|
+
headers,
|
|
5671
|
+
body: params,
|
|
5672
|
+
signal: options.signal
|
|
5673
|
+
});
|
|
5674
|
+
const text = await response.text();
|
|
5675
|
+
let value;
|
|
5676
|
+
try {
|
|
5677
|
+
value = JSON.parse(text);
|
|
5678
|
+
} catch {
|
|
5679
|
+
}
|
|
5680
|
+
if (isObject(value) && typeof value.error === "string") {
|
|
5681
|
+
throw new OAuthError(value.error, typeof value.error_description === "string" ? value.error_description : value.error, typeof value.error_uri === "string" ? value.error_uri : void 0);
|
|
5682
|
+
}
|
|
5683
|
+
if (!response.ok)
|
|
5684
|
+
throw new OAuthError("server_error", `HTTP ${response.status}: ${text}`);
|
|
5685
|
+
return parseOAuthTokens(value);
|
|
5686
|
+
}
|
|
5687
|
+
async function registerClient(authorizationServerUrl, options) {
|
|
5688
|
+
const endpoint = options.metadata?.registration_endpoint;
|
|
5689
|
+
if (options.metadata && !endpoint)
|
|
5690
|
+
throw new Error("Authorization server does not support dynamic client registration");
|
|
5691
|
+
const response = await (options.fetch ?? globalThis.fetch)(new URL(endpoint ?? new URL("/register", authorizationServerUrl)), {
|
|
5692
|
+
method: "POST",
|
|
5693
|
+
headers: { Accept: "application/json", "content-type": "application/json" },
|
|
5694
|
+
body: JSON.stringify({
|
|
5695
|
+
...options.clientMetadata,
|
|
5696
|
+
application_type: options.clientMetadata.application_type ?? applicationType(options.clientMetadata.redirect_uris),
|
|
5697
|
+
...options.scope ? { scope: options.scope } : {}
|
|
5698
|
+
}),
|
|
5699
|
+
signal: options.signal
|
|
5700
|
+
});
|
|
5701
|
+
if (!response.ok)
|
|
5702
|
+
throw new OAuthRegistrationError(response.status, await response.text());
|
|
5703
|
+
return parseClientInformation(await response.json());
|
|
5704
|
+
}
|
|
5705
|
+
async function exchangeAuthorizationCode(authorizationServerUrl, options) {
|
|
5706
|
+
return tokenRequest(authorizationServerUrl, options, new URLSearchParams({
|
|
5707
|
+
grant_type: "authorization_code",
|
|
5708
|
+
code: options.code,
|
|
5709
|
+
code_verifier: options.codeVerifier,
|
|
5710
|
+
redirect_uri: String(options.redirectUrl)
|
|
5711
|
+
}));
|
|
5712
|
+
}
|
|
5713
|
+
async function refreshAuthorization(authorizationServerUrl, options) {
|
|
5714
|
+
const tokens = await tokenRequest(authorizationServerUrl, options, new URLSearchParams({ grant_type: "refresh_token", refresh_token: options.refreshToken }));
|
|
5715
|
+
return { refresh_token: options.refreshToken, ...tokens };
|
|
5716
|
+
}
|
|
5717
|
+
function withScope(tokens, scope) {
|
|
5718
|
+
return tokens.scope === void 0 && scope ? { ...tokens, scope } : tokens;
|
|
5719
|
+
}
|
|
5720
|
+
function stepUpScope(granted, challenged) {
|
|
5721
|
+
if (!challenged)
|
|
5722
|
+
return void 0;
|
|
5723
|
+
const scopes = [granted, challenged].flatMap((scope) => scope?.split(/\s+/).filter(Boolean) ?? []);
|
|
5724
|
+
return [...new Set(scopes)].join(" ");
|
|
5725
|
+
}
|
|
5726
|
+
async function runFlow(provider, options) {
|
|
5727
|
+
const metadataUrl = options.authorizationServerMetadataUrl && secureEndpoint(options.authorizationServerMetadataUrl);
|
|
5728
|
+
const cached = metadataUrl ? void 0 : await provider.discoveryState?.();
|
|
5729
|
+
const discovered = cached?.authorizationServerUrl ? {
|
|
5730
|
+
authorizationServerUrl: cached.authorizationServerUrl,
|
|
5731
|
+
authorizationServerMetadata: cached.authorizationServerMetadata ?? await discoverAuthorizationServerMetadata(cached.authorizationServerUrl, {
|
|
5732
|
+
fetch: options.fetch,
|
|
5733
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
5734
|
+
signal: options.signal
|
|
5735
|
+
}),
|
|
5736
|
+
resourceMetadata: cached.resourceMetadata
|
|
5737
|
+
} : await discoverOAuthServerInfo(options.serverUrl, {
|
|
5738
|
+
resourceMetadataUrl: options.resourceMetadataUrl,
|
|
5739
|
+
authorizationServerMetadataUrl: metadataUrl,
|
|
5740
|
+
fetch: options.fetch,
|
|
5741
|
+
skipIssuerValidation: options.skipIssuerValidation,
|
|
5742
|
+
signal: options.signal
|
|
5743
|
+
});
|
|
5744
|
+
if (!metadataUrl) {
|
|
5745
|
+
await provider.saveDiscoveryState?.({
|
|
5746
|
+
...discovered,
|
|
5747
|
+
...options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl.href } : {}
|
|
5748
|
+
});
|
|
5749
|
+
}
|
|
5750
|
+
const metadata = discovered.authorizationServerMetadata;
|
|
5751
|
+
const resource = selectResource(options.serverUrl, discovered.resourceMetadata);
|
|
5752
|
+
const scope = options.scope || discovered.resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
|
|
5753
|
+
const stored = await provider.clientInformation();
|
|
5754
|
+
const clientDocument = stored ? void 0 : provider.clientMetadataDocument?.(metadata);
|
|
5755
|
+
if (clientDocument) {
|
|
5756
|
+
const url = new URL(clientDocument.url);
|
|
5757
|
+
if (url.protocol !== "https:" || url.pathname === "/")
|
|
5758
|
+
throw new Error("Invalid OAuth client metadata URL");
|
|
5759
|
+
}
|
|
5760
|
+
let client = stored ?? (clientDocument && { client_id: clientDocument.url });
|
|
5761
|
+
if (!client) {
|
|
5762
|
+
if (options.authorizationCode)
|
|
5763
|
+
throw new Error("OAuth client information is missing during code exchange");
|
|
5764
|
+
if (!provider.saveClientInformation)
|
|
5765
|
+
throw new Error("OAuth client information cannot be persisted");
|
|
5766
|
+
client = await registerClient(discovered.authorizationServerUrl, {
|
|
5767
|
+
metadata,
|
|
5768
|
+
clientMetadata: provider.clientMetadata,
|
|
5769
|
+
scope,
|
|
5770
|
+
fetch: options.fetch,
|
|
5771
|
+
signal: options.signal
|
|
5772
|
+
});
|
|
5773
|
+
await provider.saveClientInformation(client);
|
|
5774
|
+
}
|
|
5775
|
+
const redirectUrl = clientDocument?.redirectUrl ?? provider.redirectUrl;
|
|
5776
|
+
const tokenOptions = {
|
|
5777
|
+
metadata,
|
|
5778
|
+
clientInformation: client,
|
|
5779
|
+
resource,
|
|
5780
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
5781
|
+
fetch: options.fetch,
|
|
5782
|
+
signal: options.signal
|
|
5783
|
+
};
|
|
5784
|
+
if (options.authorizationCode) {
|
|
5785
|
+
const iss = options.iss;
|
|
5786
|
+
if (iss !== void 0 || metadata?.authorization_response_iss_parameter_supported) {
|
|
5787
|
+
if (iss !== metadata?.issuer)
|
|
5788
|
+
throw new OAuthIssuerMismatchError(metadata?.issuer, iss);
|
|
5789
|
+
}
|
|
5790
|
+
const tokens = await exchangeAuthorizationCode(discovered.authorizationServerUrl, {
|
|
5791
|
+
...tokenOptions,
|
|
5792
|
+
code: options.authorizationCode,
|
|
5793
|
+
codeVerifier: await provider.codeVerifier(),
|
|
5794
|
+
redirectUrl
|
|
5795
|
+
});
|
|
5796
|
+
await provider.saveTokens(withScope(tokens, scope));
|
|
5797
|
+
return "AUTHORIZED";
|
|
5798
|
+
}
|
|
5799
|
+
const existing = options.skipRefresh ? void 0 : await provider.tokens();
|
|
5800
|
+
if (existing?.refresh_token) {
|
|
5801
|
+
try {
|
|
5802
|
+
const tokens = await refreshAuthorization(discovered.authorizationServerUrl, {
|
|
5803
|
+
...tokenOptions,
|
|
5804
|
+
refreshToken: existing.refresh_token
|
|
5805
|
+
});
|
|
5806
|
+
await provider.saveTokens(withScope(tokens, existing.scope));
|
|
5807
|
+
return "AUTHORIZED";
|
|
5808
|
+
} catch (error) {
|
|
5809
|
+
if (options.signal?.aborted || error instanceof OAuthInsecureEndpointError)
|
|
5810
|
+
throw error;
|
|
5811
|
+
if (error instanceof OAuthError && error.code !== "server_error")
|
|
5812
|
+
throw error;
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
const state = await provider.state?.();
|
|
5816
|
+
const authorization = await startAuthorization(discovered.authorizationServerUrl, {
|
|
5817
|
+
metadata,
|
|
5818
|
+
clientInformation: client,
|
|
5819
|
+
redirectUrl,
|
|
5820
|
+
scope,
|
|
5821
|
+
state,
|
|
5822
|
+
resource
|
|
5823
|
+
});
|
|
5824
|
+
await provider.saveCodeVerifier(authorization.codeVerifier);
|
|
5825
|
+
await provider.redirectToAuthorization(authorization.authorizationUrl);
|
|
5826
|
+
return "REDIRECT";
|
|
5827
|
+
}
|
|
5828
|
+
async function authorizeMcp(provider, options) {
|
|
5829
|
+
try {
|
|
5830
|
+
return await runFlow(provider, options);
|
|
5831
|
+
} catch (error) {
|
|
5832
|
+
if (error instanceof OAuthError && ["invalid_client", "unauthorized_client"].includes(error.code)) {
|
|
5833
|
+
await provider.invalidateCredentials?.("all");
|
|
5834
|
+
return runFlow(provider, options);
|
|
5835
|
+
}
|
|
5836
|
+
if (error instanceof OAuthError && error.code === "invalid_grant") {
|
|
5837
|
+
await provider.invalidateCredentials?.("tokens");
|
|
5838
|
+
return runFlow(provider, options);
|
|
5839
|
+
}
|
|
5840
|
+
throw error;
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
// node_modules/@earendil-works/pi-mcp/dist/oauth/provider.js
|
|
5845
|
+
var MemoryOAuthStateStore = class {
|
|
5846
|
+
value;
|
|
5847
|
+
load() {
|
|
5848
|
+
return this.value === void 0 ? void 0 : structuredClone(this.value);
|
|
5849
|
+
}
|
|
5850
|
+
save(state) {
|
|
5851
|
+
this.value = structuredClone(state);
|
|
5852
|
+
}
|
|
5853
|
+
};
|
|
5854
|
+
var McpOAuthProvider = class {
|
|
5855
|
+
redirectUrl;
|
|
5856
|
+
clientMetadata;
|
|
5857
|
+
clientMetadataDocument;
|
|
5858
|
+
serverUrl;
|
|
5859
|
+
configuredClient;
|
|
5860
|
+
store;
|
|
5861
|
+
onRedirect;
|
|
5862
|
+
writes = Promise.resolve();
|
|
5863
|
+
constructor(options) {
|
|
5864
|
+
this.serverUrl = String(new URL(options.serverUrl));
|
|
5865
|
+
this.redirectUrl = String(options.redirectUrl);
|
|
5866
|
+
this.clientMetadata = {
|
|
5867
|
+
...options.clientMetadata,
|
|
5868
|
+
redirect_uris: options.clientMetadata.redirect_uris ?? [this.redirectUrl],
|
|
5869
|
+
grant_types: options.clientMetadata.grant_types ?? ["authorization_code", "refresh_token"],
|
|
5870
|
+
response_types: options.clientMetadata.response_types ?? ["code"],
|
|
5871
|
+
token_endpoint_auth_method: options.clientMetadata.token_endpoint_auth_method ?? (options.clientSecret ? "client_secret_post" : "none")
|
|
5872
|
+
};
|
|
5873
|
+
this.clientMetadataDocument = options.clientMetadataDocument;
|
|
5874
|
+
this.configuredClient = options.clientId ? { client_id: options.clientId, ...options.clientSecret ? { client_secret: options.clientSecret } : {} } : void 0;
|
|
5875
|
+
this.store = options.store ?? new MemoryOAuthStateStore();
|
|
5876
|
+
this.onRedirect = options.onRedirect;
|
|
5877
|
+
}
|
|
5878
|
+
async state() {
|
|
5879
|
+
const existing = (await this.load()).oauthState;
|
|
5880
|
+
if (existing)
|
|
5881
|
+
return existing;
|
|
5882
|
+
const state = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex");
|
|
5883
|
+
await this.update((value) => ({ ...value, oauthState: state }));
|
|
5884
|
+
return state;
|
|
5885
|
+
}
|
|
5886
|
+
async clientInformation() {
|
|
5887
|
+
return this.configuredClient ?? (await this.load()).clientInformation;
|
|
5888
|
+
}
|
|
5889
|
+
async saveClientInformation(information) {
|
|
5890
|
+
if (this.configuredClient)
|
|
5891
|
+
return;
|
|
5892
|
+
await this.update((value) => ({ ...value, clientInformation: information }));
|
|
5893
|
+
}
|
|
5894
|
+
async tokens() {
|
|
5895
|
+
return (await this.load()).tokens;
|
|
5896
|
+
}
|
|
5897
|
+
async saveTokens(tokens) {
|
|
5898
|
+
const expiresAt = tokens.expires_in === void 0 ? void 0 : Date.now() + tokens.expires_in * 1e3;
|
|
5899
|
+
await this.update((value) => {
|
|
5900
|
+
const next = { ...value, tokens };
|
|
5901
|
+
if (expiresAt === void 0)
|
|
5902
|
+
delete next.tokensExpireAt;
|
|
5903
|
+
else
|
|
5904
|
+
next.tokensExpireAt = expiresAt;
|
|
5905
|
+
return next;
|
|
5906
|
+
});
|
|
5907
|
+
}
|
|
5908
|
+
async redirectToAuthorization(url) {
|
|
5909
|
+
await this.onRedirect(url);
|
|
5910
|
+
}
|
|
5911
|
+
async saveCodeVerifier(verifier) {
|
|
5912
|
+
await this.update((value) => ({ ...value, codeVerifier: verifier }));
|
|
5913
|
+
}
|
|
5914
|
+
async codeVerifier() {
|
|
5915
|
+
const verifier = (await this.load()).codeVerifier;
|
|
5916
|
+
if (!verifier)
|
|
5917
|
+
throw new Error("No OAuth PKCE code verifier is stored");
|
|
5918
|
+
return verifier;
|
|
5919
|
+
}
|
|
5920
|
+
async invalidateCredentials(kind) {
|
|
5921
|
+
await this.update((value) => {
|
|
5922
|
+
const next = { ...value };
|
|
5923
|
+
if (kind === "all" || kind === "client")
|
|
5924
|
+
delete next.clientInformation;
|
|
5925
|
+
if (kind === "all" || kind === "tokens") {
|
|
5926
|
+
delete next.tokens;
|
|
5927
|
+
delete next.tokensExpireAt;
|
|
5928
|
+
}
|
|
5929
|
+
if (kind === "all" || kind === "verifier")
|
|
5930
|
+
delete next.codeVerifier;
|
|
5931
|
+
if (kind === "all" || kind === "discovery")
|
|
5932
|
+
delete next.discovery;
|
|
5933
|
+
if (kind === "all")
|
|
5934
|
+
delete next.oauthState;
|
|
5935
|
+
return next;
|
|
5936
|
+
});
|
|
5937
|
+
}
|
|
5938
|
+
async saveDiscoveryState(discovery) {
|
|
5939
|
+
await this.update((value) => ({ ...value, discovery }));
|
|
5940
|
+
}
|
|
5941
|
+
async discoveryState() {
|
|
5942
|
+
return (await this.load()).discovery;
|
|
5943
|
+
}
|
|
5944
|
+
async load() {
|
|
5945
|
+
await this.writes;
|
|
5946
|
+
return this.own(await this.store.load());
|
|
5947
|
+
}
|
|
5948
|
+
async update(update) {
|
|
5949
|
+
this.writes = this.writes.then(async () => {
|
|
5950
|
+
await this.store.save(update(this.own(await this.store.load())));
|
|
5951
|
+
});
|
|
5952
|
+
await this.writes;
|
|
5953
|
+
}
|
|
5954
|
+
/** Stored state for another server URL is ignored so credentials never leak across servers. */
|
|
5955
|
+
own(state) {
|
|
5956
|
+
return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };
|
|
5957
|
+
}
|
|
5958
|
+
};
|
|
5959
|
+
|
|
5960
|
+
// src/auth/auth-provider.ts
|
|
5961
|
+
var CIMD_URL = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
5962
|
+
var CLIENT_NAME = "mcpcat";
|
|
5963
|
+
var CALLBACK_HOST = "127.0.0.1";
|
|
5964
|
+
var CALLBACK_PATH = "/callback";
|
|
5965
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
5966
|
+
var REFRESH_SKEW_MS = 3e4;
|
|
5967
|
+
var FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
5968
|
+
function timedFetch(fetchImpl) {
|
|
5969
|
+
return (input, init) => {
|
|
5970
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
5971
|
+
return fetchImpl(input, {
|
|
5972
|
+
...init,
|
|
5973
|
+
signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
|
|
5974
|
+
});
|
|
5975
|
+
};
|
|
5976
|
+
}
|
|
5977
|
+
var SourceBackedStore = class {
|
|
5978
|
+
source;
|
|
5979
|
+
name;
|
|
5980
|
+
serverUrl;
|
|
5981
|
+
constructor(source, name, serverUrl) {
|
|
5982
|
+
this.source = source;
|
|
5983
|
+
this.name = name;
|
|
5984
|
+
this.serverUrl = serverUrl;
|
|
5985
|
+
}
|
|
5986
|
+
async load() {
|
|
5987
|
+
return this.source.load(this.name, this.serverUrl);
|
|
5988
|
+
}
|
|
5989
|
+
async save(state) {
|
|
5990
|
+
await this.source.save(this.name, this.serverUrl, state);
|
|
5991
|
+
}
|
|
5992
|
+
};
|
|
5993
|
+
function cimdFn(redirectUrl) {
|
|
5994
|
+
return (metadata) => {
|
|
5995
|
+
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
5996
|
+
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
5997
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
5998
|
+
return { url: CIMD_URL, redirectUrl };
|
|
5999
|
+
};
|
|
6000
|
+
}
|
|
6001
|
+
var AuthStepUpError = class extends Error {
|
|
6002
|
+
scope;
|
|
6003
|
+
target;
|
|
6004
|
+
constructor(scope, target) {
|
|
6005
|
+
super(scope ? `insufficient_scope: ${scope}` : "insufficient_scope");
|
|
6006
|
+
this.name = "AuthStepUpError";
|
|
6007
|
+
this.scope = scope;
|
|
6008
|
+
this.target = target;
|
|
6009
|
+
}
|
|
6010
|
+
};
|
|
6011
|
+
function createMcpAuthProvider(options) {
|
|
6012
|
+
const { name, serverUrl, source, oauth, platform: platform2 } = options;
|
|
6013
|
+
const store = new SourceBackedStore(source, name, serverUrl);
|
|
6014
|
+
let refreshing;
|
|
6015
|
+
const refresh = async (staleToken) => {
|
|
6016
|
+
refreshing ??= (async () => {
|
|
6017
|
+
const state = await store.load();
|
|
6018
|
+
if (state?.tokens?.access_token !== staleToken) return;
|
|
6019
|
+
if (!state?.tokens?.refresh_token) throw new McpOAuthAuthorizationRequiredError();
|
|
6020
|
+
const registered = state.clientInformation && "redirect_uris" in state.clientInformation ? state.clientInformation.redirect_uris : [];
|
|
6021
|
+
const redirectUrl = registered[0] ?? FALLBACK_REDIRECT_URL;
|
|
6022
|
+
const provider = new McpOAuthProvider({
|
|
6023
|
+
serverUrl,
|
|
6024
|
+
redirectUrl,
|
|
6025
|
+
clientMetadata: { client_name: oauth?.clientName ?? CLIENT_NAME },
|
|
6026
|
+
clientMetadataDocument: oauth?.clientId ? void 0 : cimdFn(redirectUrl),
|
|
6027
|
+
clientId: oauth?.clientId,
|
|
6028
|
+
clientSecret: oauth?.clientSecret,
|
|
6029
|
+
store,
|
|
6030
|
+
onRedirect() {
|
|
6031
|
+
}
|
|
6032
|
+
});
|
|
6033
|
+
const result = await authorizeMcp(provider, {
|
|
6034
|
+
serverUrl,
|
|
6035
|
+
authorizationServerMetadataUrl: oauth?.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : void 0,
|
|
6036
|
+
fetch: timedFetch(platform2.fetch)
|
|
6037
|
+
});
|
|
6038
|
+
if (result === "REDIRECT") throw new McpOAuthAuthorizationRequiredError();
|
|
6039
|
+
})().finally(() => {
|
|
6040
|
+
refreshing = void 0;
|
|
6041
|
+
});
|
|
6042
|
+
return refreshing;
|
|
6043
|
+
};
|
|
6044
|
+
return {
|
|
6045
|
+
async token() {
|
|
6046
|
+
await refreshing?.catch(() => void 0);
|
|
6047
|
+
const state = await store.load();
|
|
6048
|
+
const token = state?.tokens?.access_token;
|
|
6049
|
+
const expired = state?.tokensExpireAt !== void 0 && state.tokensExpireAt - REFRESH_SKEW_MS <= Date.now();
|
|
6050
|
+
if (!expired || !state?.tokens?.refresh_token) return token;
|
|
6051
|
+
await refresh(token).catch(() => void 0);
|
|
6052
|
+
return (await store.load())?.tokens?.access_token;
|
|
6053
|
+
},
|
|
6054
|
+
async onUnauthorized(context) {
|
|
6055
|
+
const challenge = parseWwwAuthenticate(context.response.headers.get("www-authenticate"));
|
|
6056
|
+
if (challenge.error === "insufficient_scope") {
|
|
6057
|
+
throw new AuthStepUpError(challenge.scope, options.name);
|
|
6058
|
+
}
|
|
6059
|
+
await refresh(context.token);
|
|
6060
|
+
}
|
|
6061
|
+
};
|
|
6062
|
+
}
|
|
4973
6063
|
|
|
4974
6064
|
// node_modules/@earendil-works/pi-mcp/dist/client.js
|
|
4975
6065
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -5667,21 +6757,21 @@ async function consumeSseStream(stream, options) {
|
|
|
5667
6757
|
if (line.startsWith(":"))
|
|
5668
6758
|
return;
|
|
5669
6759
|
const colon = line.indexOf(":");
|
|
5670
|
-
const
|
|
6760
|
+
const field2 = colon < 0 ? line : line.slice(0, colon);
|
|
5671
6761
|
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
5672
6762
|
if (value.startsWith(" "))
|
|
5673
6763
|
value = value.slice(1);
|
|
5674
|
-
if (
|
|
6764
|
+
if (field2 === "data") {
|
|
5675
6765
|
dataBytes += __mcpcat_byteLength(value) + (dataLines.length > 0 ? 1 : 0);
|
|
5676
6766
|
if (dataBytes > maxEventBytes)
|
|
5677
6767
|
throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
|
|
5678
6768
|
dataLines.push(value);
|
|
5679
|
-
} else if (
|
|
6769
|
+
} else if (field2 === "event")
|
|
5680
6770
|
eventName = value;
|
|
5681
|
-
else if (
|
|
6771
|
+
else if (field2 === "id" && !value.includes("\0")) {
|
|
5682
6772
|
eventId = value;
|
|
5683
6773
|
options.onId?.(value);
|
|
5684
|
-
} else if (
|
|
6774
|
+
} else if (field2 === "retry" && /^\d+$/.test(value))
|
|
5685
6775
|
options.onRetry?.(Number(value));
|
|
5686
6776
|
};
|
|
5687
6777
|
try {
|
|
@@ -5744,7 +6834,7 @@ function needsAuthorization(response) {
|
|
|
5744
6834
|
function isTransientStatus(status) {
|
|
5745
6835
|
return status === 408 || status === 429 || status >= 500;
|
|
5746
6836
|
}
|
|
5747
|
-
function
|
|
6837
|
+
function discard2(response) {
|
|
5748
6838
|
return response.body?.cancel().catch(() => {
|
|
5749
6839
|
}) ?? Promise.resolve();
|
|
5750
6840
|
}
|
|
@@ -5795,7 +6885,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
5795
6885
|
await this.checkResponse(response);
|
|
5796
6886
|
this.captureSession(response);
|
|
5797
6887
|
if (!isJsonRpcRequest(message)) {
|
|
5798
|
-
await
|
|
6888
|
+
await discard2(response);
|
|
5799
6889
|
if ("method" in message && message.method === "notifications/initialized")
|
|
5800
6890
|
this.startGetStream();
|
|
5801
6891
|
return;
|
|
@@ -5814,7 +6904,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
5814
6904
|
void this.consumeResponseStream(response.body, message.id);
|
|
5815
6905
|
return;
|
|
5816
6906
|
}
|
|
5817
|
-
await
|
|
6907
|
+
await discard2(response);
|
|
5818
6908
|
throw new McpHttpError(response.status, `Unsupported MCP response content type: ${type ?? "missing"}`);
|
|
5819
6909
|
}
|
|
5820
6910
|
async close() {
|
|
@@ -5829,7 +6919,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
5829
6919
|
headers: this.buildHeaders({}, this.lastToken),
|
|
5830
6920
|
signal: AbortSignal.timeout(1e3)
|
|
5831
6921
|
});
|
|
5832
|
-
await
|
|
6922
|
+
await discard2(response);
|
|
5833
6923
|
} catch {
|
|
5834
6924
|
}
|
|
5835
6925
|
}
|
|
@@ -5860,7 +6950,7 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
5860
6950
|
try {
|
|
5861
6951
|
await onUnauthorized({ response, serverUrl: this.url, fetch: this.fetch, token });
|
|
5862
6952
|
} finally {
|
|
5863
|
-
await
|
|
6953
|
+
await discard2(response);
|
|
5864
6954
|
}
|
|
5865
6955
|
}
|
|
5866
6956
|
}
|
|
@@ -6017,14 +7107,14 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
6017
7107
|
}
|
|
6018
7108
|
});
|
|
6019
7109
|
if (response.status === 405) {
|
|
6020
|
-
await
|
|
7110
|
+
await discard2(response);
|
|
6021
7111
|
return void 0;
|
|
6022
7112
|
}
|
|
6023
7113
|
await this.checkResponse(response);
|
|
6024
7114
|
this.captureSession(response);
|
|
6025
7115
|
const type = contentType(response);
|
|
6026
7116
|
if (type !== "text/event-stream" || !response.body) {
|
|
6027
|
-
await
|
|
7117
|
+
await discard2(response);
|
|
6028
7118
|
throw new McpHttpError(response.status, `Unsupported MCP GET response content type: ${type ?? "missing"}`);
|
|
6029
7119
|
}
|
|
6030
7120
|
return response.body;
|
|
@@ -6252,67 +7342,6 @@ var PlatformStdioTransport = class {
|
|
|
6252
7342
|
}
|
|
6253
7343
|
};
|
|
6254
7344
|
|
|
6255
|
-
// src/client/pi-connection.ts
|
|
6256
|
-
async function createPiConnection(options) {
|
|
6257
|
-
const clientOptions = {
|
|
6258
|
-
name: "mcpcat",
|
|
6259
|
-
version: options.version ?? "0.0.0"
|
|
6260
|
-
};
|
|
6261
|
-
const client = new McpClient(clientOptions);
|
|
6262
|
-
let transport;
|
|
6263
|
-
if (options.url) {
|
|
6264
|
-
transport = new StreamableHttpTransport({
|
|
6265
|
-
url: options.url,
|
|
6266
|
-
headers: options.headers,
|
|
6267
|
-
fetch: options.fetchImpl
|
|
6268
|
-
});
|
|
6269
|
-
} else if (options.command) {
|
|
6270
|
-
if (options.platform) {
|
|
6271
|
-
transport = new PlatformStdioTransport({
|
|
6272
|
-
command: options.command,
|
|
6273
|
-
args: options.args,
|
|
6274
|
-
env: options.env,
|
|
6275
|
-
cwd: options.cwd,
|
|
6276
|
-
inheritEnv: true,
|
|
6277
|
-
platform: options.platform
|
|
6278
|
-
});
|
|
6279
|
-
} else {
|
|
6280
|
-
transport = new StdioTransport({
|
|
6281
|
-
command: options.command,
|
|
6282
|
-
args: options.args,
|
|
6283
|
-
env: options.env,
|
|
6284
|
-
cwd: options.cwd,
|
|
6285
|
-
inheritEnv: true,
|
|
6286
|
-
stderr: "pipe"
|
|
6287
|
-
});
|
|
6288
|
-
}
|
|
6289
|
-
} else {
|
|
6290
|
-
throw new Error("PiConnection requires either a url or a command");
|
|
6291
|
-
}
|
|
6292
|
-
const initResult = await client.connect(transport);
|
|
6293
|
-
return {
|
|
6294
|
-
async info() {
|
|
6295
|
-
return {
|
|
6296
|
-
name: initResult.serverInfo?.name,
|
|
6297
|
-
version: initResult.serverInfo?.version,
|
|
6298
|
-
title: initResult.serverInfo?.title,
|
|
6299
|
-
protocolVersion: initResult.protocolVersion,
|
|
6300
|
-
capabilities: initResult.capabilities,
|
|
6301
|
-
instructions: initResult.instructions
|
|
6302
|
-
};
|
|
6303
|
-
},
|
|
6304
|
-
async listTools() {
|
|
6305
|
-
return client.listTools();
|
|
6306
|
-
},
|
|
6307
|
-
async callTool(name, args) {
|
|
6308
|
-
return client.callTool(name, args);
|
|
6309
|
-
},
|
|
6310
|
-
async close() {
|
|
6311
|
-
await client.close();
|
|
6312
|
-
}
|
|
6313
|
-
};
|
|
6314
|
-
}
|
|
6315
|
-
|
|
6316
7345
|
// src/client/stateless-connection.ts
|
|
6317
7346
|
var STATELESS_PROTOCOL_VERSION = "2026-07-28";
|
|
6318
7347
|
var StatelessRpcError = class extends Error {
|
|
@@ -6341,6 +7370,22 @@ var InputRequiredError = class extends Error {
|
|
|
6341
7370
|
this.requestJson = requestJson;
|
|
6342
7371
|
}
|
|
6343
7372
|
};
|
|
7373
|
+
var McpTimeoutError2 = class extends Error {
|
|
7374
|
+
constructor(method, ms) {
|
|
7375
|
+
super(`timeout after ${ms}ms waiting for ${method}`);
|
|
7376
|
+
this.name = "McpTimeoutError";
|
|
7377
|
+
}
|
|
7378
|
+
};
|
|
7379
|
+
var McpAuthError = class extends Error {
|
|
7380
|
+
status;
|
|
7381
|
+
target;
|
|
7382
|
+
constructor(status, target) {
|
|
7383
|
+
super(`HTTP ${status}: authentication required. Run: mcpcat login ${target}`);
|
|
7384
|
+
this.name = "McpAuthError";
|
|
7385
|
+
this.status = status;
|
|
7386
|
+
this.target = target;
|
|
7387
|
+
}
|
|
7388
|
+
};
|
|
6344
7389
|
function selectSseFrame(body, targetId) {
|
|
6345
7390
|
const text = new TextDecoder().decode(body);
|
|
6346
7391
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -6371,25 +7416,66 @@ async function createStatelessConnection(options) {
|
|
|
6371
7416
|
headers: extraHeaders,
|
|
6372
7417
|
fetchImpl,
|
|
6373
7418
|
timeoutMs = 6e4,
|
|
6374
|
-
clientInfo = { name: "mcpcat", version: "0.0.0" }
|
|
7419
|
+
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
7420
|
+
signal: externalSignal,
|
|
7421
|
+
verbose
|
|
6375
7422
|
} = options;
|
|
6376
7423
|
let nextId2 = 1;
|
|
6377
7424
|
let negotiatedVersion;
|
|
6378
|
-
|
|
6379
|
-
const
|
|
6380
|
-
const headers = {
|
|
7425
|
+
function buildHeaders(method, params) {
|
|
7426
|
+
const h = {
|
|
6381
7427
|
"Content-Type": "application/json",
|
|
6382
7428
|
Accept: "application/json, text/event-stream",
|
|
6383
7429
|
"Mcp-Method": method,
|
|
6384
7430
|
...extraHeaders
|
|
6385
7431
|
};
|
|
6386
|
-
const
|
|
6387
|
-
if (
|
|
6388
|
-
|
|
7432
|
+
const n = mcpNameForMethod(method, params);
|
|
7433
|
+
if (n) h["Mcp-Name"] = n;
|
|
7434
|
+
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
7435
|
+
return h;
|
|
7436
|
+
}
|
|
7437
|
+
function makeAbort() {
|
|
7438
|
+
const controller = new AbortController();
|
|
7439
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7440
|
+
if (externalSignal) {
|
|
7441
|
+
if (externalSignal.aborted) {
|
|
7442
|
+
clearTimeout(timer);
|
|
7443
|
+
controller.abort();
|
|
7444
|
+
} else {
|
|
7445
|
+
externalSignal.addEventListener(
|
|
7446
|
+
"abort",
|
|
7447
|
+
() => {
|
|
7448
|
+
clearTimeout(timer);
|
|
7449
|
+
controller.abort();
|
|
7450
|
+
},
|
|
7451
|
+
{ once: true }
|
|
7452
|
+
);
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
return { controller, timer };
|
|
7456
|
+
}
|
|
7457
|
+
function logHeaders(dir, entries) {
|
|
7458
|
+
if (!verbose?.enabled) return;
|
|
7459
|
+
for (const [k, v] of entries) {
|
|
7460
|
+
if (dir === ">") verbose.headerOut(k, v);
|
|
7461
|
+
else verbose.headerIn(k, v);
|
|
6389
7462
|
}
|
|
6390
|
-
|
|
6391
|
-
|
|
7463
|
+
}
|
|
7464
|
+
async function handleErrorStatus(res, method) {
|
|
7465
|
+
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
7466
|
+
const text = await res.text();
|
|
7467
|
+
let rpc;
|
|
7468
|
+
try {
|
|
7469
|
+
const p = JSON.parse(text);
|
|
7470
|
+
if (p.error && typeof p.error.code === "number") rpc = p.error;
|
|
7471
|
+
} catch {
|
|
6392
7472
|
}
|
|
7473
|
+
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
7474
|
+
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
7475
|
+
}
|
|
7476
|
+
async function request(method, params) {
|
|
7477
|
+
const id = nextId2++;
|
|
7478
|
+
const headers = buildHeaders(method, params);
|
|
6393
7479
|
const meta = {
|
|
6394
7480
|
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
6395
7481
|
clientInfo
|
|
@@ -6400,8 +7486,11 @@ async function createStatelessConnection(options) {
|
|
|
6400
7486
|
method,
|
|
6401
7487
|
params: { ...params, _meta: meta }
|
|
6402
7488
|
});
|
|
6403
|
-
|
|
6404
|
-
|
|
7489
|
+
if (verbose?.enabled) {
|
|
7490
|
+
verbose.request(method, body);
|
|
7491
|
+
logHeaders(">", Object.entries(headers));
|
|
7492
|
+
}
|
|
7493
|
+
const { controller, timer } = makeAbort();
|
|
6405
7494
|
let res;
|
|
6406
7495
|
try {
|
|
6407
7496
|
res = await fetchImpl(url, {
|
|
@@ -6410,35 +7499,28 @@ async function createStatelessConnection(options) {
|
|
|
6410
7499
|
body,
|
|
6411
7500
|
signal: controller.signal
|
|
6412
7501
|
});
|
|
7502
|
+
} catch (err) {
|
|
7503
|
+
clearTimeout(timer);
|
|
7504
|
+
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
7505
|
+
throw new McpTimeoutError2(method, timeoutMs);
|
|
7506
|
+
}
|
|
7507
|
+
throw err;
|
|
6413
7508
|
} finally {
|
|
6414
7509
|
clearTimeout(timer);
|
|
6415
7510
|
}
|
|
6416
|
-
if (
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
if (res.status >= 400) {
|
|
6420
|
-
const text = await res.text();
|
|
6421
|
-
let rpcError3;
|
|
6422
|
-
try {
|
|
6423
|
-
const parsed = JSON.parse(text);
|
|
6424
|
-
if (parsed.error && typeof parsed.error.code === "number") {
|
|
6425
|
-
rpcError3 = parsed.error;
|
|
6426
|
-
}
|
|
6427
|
-
} catch {
|
|
6428
|
-
}
|
|
6429
|
-
if (rpcError3) {
|
|
6430
|
-
throw new StatelessRpcError(rpcError3.code, rpcError3.message, rpcError3.data);
|
|
6431
|
-
}
|
|
6432
|
-
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
7511
|
+
if (verbose?.enabled) {
|
|
7512
|
+
verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
|
|
7513
|
+
logHeaders("<", res.headers.entries());
|
|
6433
7514
|
}
|
|
7515
|
+
if (res.status >= 400) await handleErrorStatus(res, method);
|
|
6434
7516
|
const ct = res.headers.get("content-type") ?? "";
|
|
6435
7517
|
let frame;
|
|
6436
7518
|
if (ct.toLowerCase().includes("text/event-stream")) {
|
|
6437
|
-
|
|
6438
|
-
frame = selectSseFrame(bytes, id);
|
|
7519
|
+
frame = selectSseFrame(new Uint8Array(await res.arrayBuffer()), id);
|
|
6439
7520
|
} else {
|
|
6440
7521
|
frame = await res.json();
|
|
6441
7522
|
}
|
|
7523
|
+
if (verbose?.enabled) verbose.response(JSON.stringify(frame));
|
|
6442
7524
|
if (frame.error) {
|
|
6443
7525
|
throw new StatelessRpcError(frame.error.code, frame.error.message, frame.error.data);
|
|
6444
7526
|
}
|
|
@@ -6458,28 +7540,26 @@ async function createStatelessConnection(options) {
|
|
|
6458
7540
|
return {
|
|
6459
7541
|
async info() {
|
|
6460
7542
|
if (serverInfoCache) return serverInfoCache;
|
|
6461
|
-
const
|
|
7543
|
+
const r = discoverResult2;
|
|
6462
7544
|
serverInfoCache = {
|
|
6463
|
-
name:
|
|
6464
|
-
version:
|
|
6465
|
-
title:
|
|
7545
|
+
name: r?.serverInfo?.name,
|
|
7546
|
+
version: r?.serverInfo?.version,
|
|
7547
|
+
title: r?.serverInfo?.title,
|
|
6466
7548
|
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
6467
|
-
capabilities:
|
|
6468
|
-
instructions:
|
|
7549
|
+
capabilities: r?.capabilities ?? {},
|
|
7550
|
+
instructions: r?.instructions
|
|
6469
7551
|
};
|
|
6470
7552
|
return serverInfoCache;
|
|
6471
7553
|
},
|
|
6472
7554
|
async listTools() {
|
|
6473
|
-
|
|
6474
|
-
return result?.tools ?? [];
|
|
7555
|
+
return (await request("tools/list", {}))?.tools ?? [];
|
|
6475
7556
|
},
|
|
6476
|
-
async callTool(name, args) {
|
|
7557
|
+
async callTool(name, args, _callOptions) {
|
|
6477
7558
|
const result = await request("tools/call", {
|
|
6478
7559
|
name,
|
|
6479
7560
|
arguments: args ?? {}
|
|
6480
7561
|
});
|
|
6481
|
-
|
|
6482
|
-
if (resultType === "input_required") {
|
|
7562
|
+
if ((result.resultType ?? "complete") === "input_required") {
|
|
6483
7563
|
throw new InputRequiredError(
|
|
6484
7564
|
JSON.stringify({ name, arguments: args ?? {}, result }, null, 2)
|
|
6485
7565
|
);
|
|
@@ -6491,28 +7571,151 @@ async function createStatelessConnection(options) {
|
|
|
6491
7571
|
};
|
|
6492
7572
|
}
|
|
6493
7573
|
|
|
7574
|
+
// src/client/pi-connection.ts
|
|
7575
|
+
function buildStderrHandler(options) {
|
|
7576
|
+
if (!options.verbose?.enabled) return void 0;
|
|
7577
|
+
return (chunk) => {
|
|
7578
|
+
for (const l of chunk.split("\n")) {
|
|
7579
|
+
if (l.trim()) options.verbose.server(l);
|
|
7580
|
+
}
|
|
7581
|
+
};
|
|
7582
|
+
}
|
|
7583
|
+
function buildTransport(options) {
|
|
7584
|
+
if (options.url) {
|
|
7585
|
+
return new StreamableHttpTransport({
|
|
7586
|
+
url: options.url,
|
|
7587
|
+
headers: options.headers,
|
|
7588
|
+
fetch: options.fetchImpl,
|
|
7589
|
+
authProvider: options.authProvider
|
|
7590
|
+
});
|
|
7591
|
+
}
|
|
7592
|
+
if (!options.command) throw new Error("PiConnection requires either a url or a command");
|
|
7593
|
+
if (options.platform) {
|
|
7594
|
+
return new PlatformStdioTransport({
|
|
7595
|
+
command: options.command,
|
|
7596
|
+
args: options.args,
|
|
7597
|
+
env: options.env,
|
|
7598
|
+
cwd: options.cwd,
|
|
7599
|
+
inheritEnv: true,
|
|
7600
|
+
platform: options.platform,
|
|
7601
|
+
onStderr: buildStderrHandler(options)
|
|
7602
|
+
});
|
|
7603
|
+
}
|
|
7604
|
+
return new StdioTransport({
|
|
7605
|
+
command: options.command,
|
|
7606
|
+
args: options.args,
|
|
7607
|
+
env: options.env,
|
|
7608
|
+
cwd: options.cwd,
|
|
7609
|
+
inheritEnv: true,
|
|
7610
|
+
stderr: "pipe",
|
|
7611
|
+
onStderr: buildStderrHandler(options)
|
|
7612
|
+
});
|
|
7613
|
+
}
|
|
7614
|
+
async function createPiConnection(options) {
|
|
7615
|
+
const clientOptions = {
|
|
7616
|
+
name: "mcpcat",
|
|
7617
|
+
version: options.version ?? "0.0.0",
|
|
7618
|
+
requestTimeoutMs: options.timeoutMs
|
|
7619
|
+
};
|
|
7620
|
+
const client = new McpClient(clientOptions);
|
|
7621
|
+
const transport = buildTransport(options);
|
|
7622
|
+
if (options.verbose?.enabled) {
|
|
7623
|
+
transport.onMessage((msg) => {
|
|
7624
|
+
options.verbose.response(JSON.stringify(msg));
|
|
7625
|
+
});
|
|
7626
|
+
}
|
|
7627
|
+
try {
|
|
7628
|
+
let reqOpts2 = function() {
|
|
7629
|
+
const o = {};
|
|
7630
|
+
if (options.signal) o.signal = options.signal;
|
|
7631
|
+
if (options.verbose?.enabled) {
|
|
7632
|
+
o.onProgress = (p) => {
|
|
7633
|
+
options.verbose.progress(`progress: ${p.message ?? ""}`);
|
|
7634
|
+
};
|
|
7635
|
+
}
|
|
7636
|
+
return o;
|
|
7637
|
+
};
|
|
7638
|
+
var reqOpts = reqOpts2;
|
|
7639
|
+
const initResult = await client.connect(transport);
|
|
7640
|
+
return {
|
|
7641
|
+
async info() {
|
|
7642
|
+
return {
|
|
7643
|
+
name: initResult.serverInfo?.name,
|
|
7644
|
+
version: initResult.serverInfo?.version,
|
|
7645
|
+
title: initResult.serverInfo?.title,
|
|
7646
|
+
protocolVersion: initResult.protocolVersion,
|
|
7647
|
+
capabilities: initResult.capabilities,
|
|
7648
|
+
instructions: initResult.instructions
|
|
7649
|
+
};
|
|
7650
|
+
},
|
|
7651
|
+
async listTools() {
|
|
7652
|
+
return client.listTools(reqOpts2());
|
|
7653
|
+
},
|
|
7654
|
+
async callTool(name, args, callOptions) {
|
|
7655
|
+
const o = reqOpts2();
|
|
7656
|
+
if (callOptions?.onProgress) {
|
|
7657
|
+
const outer = callOptions.onProgress;
|
|
7658
|
+
const inner = o.onProgress;
|
|
7659
|
+
o.onProgress = (p) => {
|
|
7660
|
+
outer(p.progress, p.total, p.message);
|
|
7661
|
+
inner?.(p);
|
|
7662
|
+
};
|
|
7663
|
+
}
|
|
7664
|
+
return client.callTool(name, args, o);
|
|
7665
|
+
},
|
|
7666
|
+
async close() {
|
|
7667
|
+
await client.close();
|
|
7668
|
+
}
|
|
7669
|
+
};
|
|
7670
|
+
} catch (err) {
|
|
7671
|
+
const msg = err.message ?? "";
|
|
7672
|
+
if (msg.includes("401") || msg.includes("403") || msg.includes("Unauthorized")) {
|
|
7673
|
+
throw new McpAuthError(401, options.url ?? options.command ?? "");
|
|
7674
|
+
}
|
|
7675
|
+
if (msg.includes("timeout") || msg.includes("Timeout") || msg.includes("ETIMEDOUT")) {
|
|
7676
|
+
throw new McpTimeoutError2("connect", options.timeoutMs ?? 6e4);
|
|
7677
|
+
}
|
|
7678
|
+
throw err;
|
|
7679
|
+
}
|
|
7680
|
+
}
|
|
7681
|
+
|
|
6494
7682
|
// src/client/connect.ts
|
|
6495
7683
|
async function connect(platform2, options) {
|
|
7684
|
+
const shared = {
|
|
7685
|
+
version: options.version,
|
|
7686
|
+
timeoutMs: options.timeoutMs,
|
|
7687
|
+
signal: options.signal,
|
|
7688
|
+
verbose: options.verbose,
|
|
7689
|
+
platform: platform2
|
|
7690
|
+
};
|
|
6496
7691
|
if (options.command) {
|
|
6497
7692
|
return createPiConnection({
|
|
6498
7693
|
command: options.command,
|
|
6499
7694
|
args: options.args,
|
|
6500
7695
|
env: options.env,
|
|
6501
7696
|
cwd: options.cwd,
|
|
6502
|
-
|
|
6503
|
-
platform: platform2
|
|
7697
|
+
...shared
|
|
6504
7698
|
});
|
|
6505
7699
|
}
|
|
6506
|
-
if (!options.url)
|
|
6507
|
-
|
|
7700
|
+
if (!options.url) throw new Error("connect requires either a url or a command");
|
|
7701
|
+
if (options.authProvider) {
|
|
7702
|
+
const token = await options.authProvider.token();
|
|
7703
|
+
if (token) {
|
|
7704
|
+
return createPiConnection({
|
|
7705
|
+
url: options.url,
|
|
7706
|
+
headers: options.headers,
|
|
7707
|
+
fetchImpl: platform2.fetch,
|
|
7708
|
+
authProvider: options.authProvider,
|
|
7709
|
+
...shared
|
|
7710
|
+
});
|
|
7711
|
+
}
|
|
6508
7712
|
}
|
|
6509
7713
|
if (options.protocolVersion && !options.protocolVersion.startsWith("2026")) {
|
|
6510
7714
|
return createPiConnection({
|
|
6511
7715
|
url: options.url,
|
|
6512
7716
|
headers: options.headers,
|
|
6513
|
-
version: options.version,
|
|
6514
7717
|
fetchImpl: platform2.fetch,
|
|
6515
|
-
|
|
7718
|
+
...shared
|
|
6516
7719
|
});
|
|
6517
7720
|
}
|
|
6518
7721
|
try {
|
|
@@ -6520,20 +7723,21 @@ async function connect(platform2, options) {
|
|
|
6520
7723
|
url: options.url,
|
|
6521
7724
|
headers: options.headers,
|
|
6522
7725
|
fetchImpl: platform2.fetch,
|
|
6523
|
-
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" }
|
|
7726
|
+
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
|
|
7727
|
+
timeoutMs: options.timeoutMs,
|
|
7728
|
+
signal: options.signal,
|
|
7729
|
+
verbose: options.verbose
|
|
6524
7730
|
});
|
|
6525
7731
|
} catch (err) {
|
|
6526
7732
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
6527
|
-
if (options.protocolVersion?.startsWith("2026"))
|
|
6528
|
-
throw err;
|
|
6529
|
-
}
|
|
7733
|
+
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
6530
7734
|
if (isMethodNotFound) {
|
|
6531
7735
|
return createPiConnection({
|
|
6532
7736
|
url: options.url,
|
|
6533
7737
|
headers: options.headers,
|
|
6534
|
-
version: options.version,
|
|
6535
7738
|
fetchImpl: platform2.fetch,
|
|
6536
|
-
|
|
7739
|
+
authProvider: options.authProvider,
|
|
7740
|
+
...shared
|
|
6537
7741
|
});
|
|
6538
7742
|
}
|
|
6539
7743
|
throw err;
|
|
@@ -7007,8 +8211,8 @@ async function formatOutput(platform2, result, json) {
|
|
|
7007
8211
|
return result.ok ? 0 : isTimeout ? 124 : 1;
|
|
7008
8212
|
}
|
|
7009
8213
|
async function createSandbox(platform2, tools, globals, timeoutMs) {
|
|
7010
|
-
const
|
|
7011
|
-
if (
|
|
8214
|
+
const isKernel2 = platform2.packageDir !== void 0;
|
|
8215
|
+
if (isKernel2) {
|
|
7012
8216
|
const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
|
|
7013
8217
|
const wasm = await loadWasmKernel(platform2);
|
|
7014
8218
|
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
|
|
@@ -7085,6 +8289,28 @@ async function readStream(stream) {
|
|
|
7085
8289
|
return new TextDecoder().decode(merged);
|
|
7086
8290
|
}
|
|
7087
8291
|
|
|
8292
|
+
// src/config/expand.ts
|
|
8293
|
+
var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
8294
|
+
var PLACEHOLDER_TEST = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
|
|
8295
|
+
function containsPlaceholders(value) {
|
|
8296
|
+
return PLACEHOLDER_TEST.test(value);
|
|
8297
|
+
}
|
|
8298
|
+
function expandPlaceholders(value, env2) {
|
|
8299
|
+
return value.replace(PLACEHOLDER, (_, name) => env2[name] ?? "");
|
|
8300
|
+
}
|
|
8301
|
+
function expandPlaceholdersStrict(value, env2) {
|
|
8302
|
+
let hasUndefined = false;
|
|
8303
|
+
const result = value.replace(PLACEHOLDER, (_, name) => {
|
|
8304
|
+
if (env2[name] === void 0) {
|
|
8305
|
+
hasUndefined = true;
|
|
8306
|
+
return "";
|
|
8307
|
+
}
|
|
8308
|
+
return env2[name];
|
|
8309
|
+
});
|
|
8310
|
+
if (hasUndefined) return void 0;
|
|
8311
|
+
return result;
|
|
8312
|
+
}
|
|
8313
|
+
|
|
7088
8314
|
// src/config/parse.ts
|
|
7089
8315
|
var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
7090
8316
|
var EXPOSURES = ["codemode", "deferred", "direct", "hidden"];
|
|
@@ -7111,8 +8337,8 @@ function resolveAliases(value) {
|
|
|
7111
8337
|
}
|
|
7112
8338
|
return out;
|
|
7113
8339
|
}
|
|
7114
|
-
function fail(server, message,
|
|
7115
|
-
return { server, ...
|
|
8340
|
+
function fail(server, message, field2) {
|
|
8341
|
+
return { server, ...field2 ? { field: field2 } : {}, message };
|
|
7116
8342
|
}
|
|
7117
8343
|
function validateCommon(name, value) {
|
|
7118
8344
|
const exposures = EXPOSURES.map((e) => `"${e}"`).join(", ");
|
|
@@ -8321,23 +9547,98 @@ async function runDiscover(platform2, args) {
|
|
|
8321
9547
|
return 0;
|
|
8322
9548
|
}
|
|
8323
9549
|
|
|
8324
|
-
// src/
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
return
|
|
9550
|
+
// src/render/result.ts
|
|
9551
|
+
function formatBytes(n) {
|
|
9552
|
+
if (n < 1024) return `${n} bytes`;
|
|
9553
|
+
const kb = n / 1024;
|
|
9554
|
+
if (kb < 1024) return `${kb.toFixed(1)} KB`;
|
|
9555
|
+
return `${(kb / 1024).toFixed(1)} MB`;
|
|
9556
|
+
}
|
|
9557
|
+
function truncateMiddle(text, max) {
|
|
9558
|
+
if (text.length <= max) return text;
|
|
9559
|
+
const half = Math.floor((max - 5) / 2);
|
|
9560
|
+
return `${text.slice(0, half)}
|
|
9561
|
+
[\u2026]
|
|
9562
|
+
${text.slice(-half)}`;
|
|
9563
|
+
}
|
|
9564
|
+
function renderContentBlock(block) {
|
|
9565
|
+
if (block.type === "text") return block.text;
|
|
9566
|
+
if (block.type === "image") {
|
|
9567
|
+
const size = formatBytes(Math.ceil(block.data.length * 3 / 4));
|
|
9568
|
+
return `[image: ${block.mimeType}, ${size}]`;
|
|
9569
|
+
}
|
|
9570
|
+
if (block.type === "audio") {
|
|
9571
|
+
const audioBlock = block;
|
|
9572
|
+
const size = formatBytes(Math.ceil(audioBlock.data.length * 3 / 4));
|
|
9573
|
+
return `[audio: ${audioBlock.mimeType}, ${size}]`;
|
|
9574
|
+
}
|
|
9575
|
+
if (block.type === "resource_link") return `[resource: ${block.uri}]`;
|
|
9576
|
+
if (block.type === "resource") {
|
|
9577
|
+
return "text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`;
|
|
9578
|
+
}
|
|
9579
|
+
return void 0;
|
|
8329
9580
|
}
|
|
8330
|
-
function
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
9581
|
+
function renderCallResultText(result, options) {
|
|
9582
|
+
const parts = [];
|
|
9583
|
+
if (result.content) {
|
|
9584
|
+
for (const block of result.content) {
|
|
9585
|
+
const rendered = renderContentBlock(block);
|
|
9586
|
+
if (rendered !== void 0) parts.push(rendered);
|
|
8336
9587
|
}
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
9588
|
+
}
|
|
9589
|
+
if (parts.length === 0 && result.structuredContent !== void 0) {
|
|
9590
|
+
parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
9591
|
+
}
|
|
9592
|
+
let text = parts.join("\n");
|
|
9593
|
+
if (options?.maxChars && text.length > options.maxChars) {
|
|
9594
|
+
text = truncateMiddle(text, options.maxChars);
|
|
9595
|
+
}
|
|
9596
|
+
return text;
|
|
9597
|
+
}
|
|
9598
|
+
function renderCallResultJson(result) {
|
|
9599
|
+
return JSON.stringify(result, null, 2);
|
|
9600
|
+
}
|
|
9601
|
+
async function renderCallResult(platform2, result, options) {
|
|
9602
|
+
if (options?.json) {
|
|
9603
|
+
const json = renderCallResultJson(result);
|
|
9604
|
+
if (result.isError) {
|
|
9605
|
+
await platform2.write(
|
|
9606
|
+
2,
|
|
9607
|
+
`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
|
|
9608
|
+
`
|
|
9609
|
+
);
|
|
9610
|
+
return 1;
|
|
9611
|
+
}
|
|
9612
|
+
await platform2.write(1, `${json}
|
|
9613
|
+
`);
|
|
9614
|
+
return 0;
|
|
9615
|
+
}
|
|
9616
|
+
const text = renderCallResultText(result, options);
|
|
9617
|
+
if (result.isError) {
|
|
9618
|
+
await platform2.write(2, `${text || "(tool reported error)"}
|
|
9619
|
+
`);
|
|
9620
|
+
return 1;
|
|
9621
|
+
}
|
|
9622
|
+
if (text) {
|
|
9623
|
+
await platform2.write(1, `${text}
|
|
9624
|
+
`);
|
|
9625
|
+
}
|
|
9626
|
+
return 0;
|
|
9627
|
+
}
|
|
9628
|
+
async function renderToolsJson(platform2, tools) {
|
|
9629
|
+
await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
|
|
9630
|
+
`);
|
|
9631
|
+
}
|
|
9632
|
+
async function renderInfoJson(platform2, info) {
|
|
9633
|
+
await platform2.write(1, `${JSON.stringify(info, null, 2)}
|
|
9634
|
+
`);
|
|
9635
|
+
}
|
|
9636
|
+
async function renderErrorJson(platform2, kind, message, code, data) {
|
|
9637
|
+
const errorObj = { kind, message };
|
|
9638
|
+
if (code !== void 0) errorObj.code = code;
|
|
9639
|
+
if (data !== void 0) errorObj.data = data;
|
|
9640
|
+
await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
|
|
9641
|
+
`);
|
|
8341
9642
|
}
|
|
8342
9643
|
|
|
8343
9644
|
// src/serve/catalog.ts
|
|
@@ -8458,7 +9759,7 @@ function argvForCommand(command, args) {
|
|
|
8458
9759
|
if (args.error) return { error: args.error };
|
|
8459
9760
|
const argv = [...command.path];
|
|
8460
9761
|
for (const positional of command.positionals) {
|
|
8461
|
-
const found = args.fields.find((
|
|
9762
|
+
const found = args.fields.find((field2) => field2.name === positional.name);
|
|
8462
9763
|
if (!found) {
|
|
8463
9764
|
if (positional.required) return { error: `missing ${positional.name}` };
|
|
8464
9765
|
continue;
|
|
@@ -8477,7 +9778,7 @@ function argvForCommand(command, args) {
|
|
|
8477
9778
|
function appendFlags(flags, fields) {
|
|
8478
9779
|
const argv = [];
|
|
8479
9780
|
for (const flag of flags) {
|
|
8480
|
-
const found = fields.find((
|
|
9781
|
+
const found = fields.find((field2) => field2.name === flag.name);
|
|
8481
9782
|
if (!found || found.value === false) continue;
|
|
8482
9783
|
const encoded = encodeFlag(flag, found.value);
|
|
8483
9784
|
if ("error" in encoded) return encoded;
|
|
@@ -10186,6 +11487,478 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
|
|
|
10186
11487
|
};
|
|
10187
11488
|
}
|
|
10188
11489
|
|
|
11490
|
+
// src/auth/oauth.ts
|
|
11491
|
+
var CALLBACK_HOST2 = "127.0.0.1";
|
|
11492
|
+
var CALLBACK_PATH2 = "/callback";
|
|
11493
|
+
var CIMD_URL2 = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
11494
|
+
var CLIENT_NAME2 = "mcpcat";
|
|
11495
|
+
var REQUEST_TIMEOUT_MS2 = 15e3;
|
|
11496
|
+
function timedFetch2(fetchImpl) {
|
|
11497
|
+
return (input, init) => {
|
|
11498
|
+
const timeout = AbortSignal.timeout(REQUEST_TIMEOUT_MS2);
|
|
11499
|
+
return fetchImpl(input, {
|
|
11500
|
+
...init,
|
|
11501
|
+
signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout
|
|
11502
|
+
});
|
|
11503
|
+
};
|
|
11504
|
+
}
|
|
11505
|
+
function callbackSettings(oauth) {
|
|
11506
|
+
const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
|
|
11507
|
+
const url = new URL(raw);
|
|
11508
|
+
const address = url.hostname.replace(/^\[|\]$/g, "");
|
|
11509
|
+
const port = url.port ? Number(url.port) : oauth?.callbackPort;
|
|
11510
|
+
let fixedRedirectUrl;
|
|
11511
|
+
if (url.port) fixedRedirectUrl = oauth?.callbackUrl;
|
|
11512
|
+
else if (port !== void 0) {
|
|
11513
|
+
url.port = String(port);
|
|
11514
|
+
fixedRedirectUrl = url.href;
|
|
11515
|
+
}
|
|
11516
|
+
return {
|
|
11517
|
+
host: address === "localhost" ? CALLBACK_HOST2 : address,
|
|
11518
|
+
redirectHost: address,
|
|
11519
|
+
port,
|
|
11520
|
+
path: url.pathname,
|
|
11521
|
+
fixedRedirectUrl
|
|
11522
|
+
};
|
|
11523
|
+
}
|
|
11524
|
+
function cimdForServer(metadata, redirectUrl) {
|
|
11525
|
+
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
11526
|
+
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
11527
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11528
|
+
return {
|
|
11529
|
+
url: CIMD_URL2,
|
|
11530
|
+
redirectUrl
|
|
11531
|
+
};
|
|
11532
|
+
}
|
|
11533
|
+
function buildClientMetadataDocumentFn(oauth, redirectUrl) {
|
|
11534
|
+
if (oauth?.clientRegistration === "dcr") return void 0;
|
|
11535
|
+
if (oauth?.clientId) return void 0;
|
|
11536
|
+
return (metadata) => {
|
|
11537
|
+
if (oauth?.clientRegistration === "cimd") {
|
|
11538
|
+
if (!metadata?.client_id_metadata_document_supported || !metadata.token_endpoint_auth_methods_supported?.includes("none")) {
|
|
11539
|
+
return void 0;
|
|
11540
|
+
}
|
|
11541
|
+
if (!metadata.authorization_response_iss_parameter_supported) return void 0;
|
|
11542
|
+
return { url: CIMD_URL2, redirectUrl };
|
|
11543
|
+
}
|
|
11544
|
+
return cimdForServer(metadata, redirectUrl);
|
|
11545
|
+
};
|
|
11546
|
+
}
|
|
11547
|
+
function createProvider(serverUrl, store, oauth, redirectUrl, onRedirect) {
|
|
11548
|
+
const cimdFn2 = buildClientMetadataDocumentFn(oauth, redirectUrl);
|
|
11549
|
+
return new McpOAuthProvider({
|
|
11550
|
+
serverUrl,
|
|
11551
|
+
redirectUrl,
|
|
11552
|
+
clientMetadata: { client_name: oauth?.clientName ?? CLIENT_NAME2 },
|
|
11553
|
+
clientMetadataDocument: cimdFn2,
|
|
11554
|
+
clientId: oauth?.clientId,
|
|
11555
|
+
clientSecret: oauth?.clientSecret,
|
|
11556
|
+
store,
|
|
11557
|
+
onRedirect
|
|
11558
|
+
});
|
|
11559
|
+
}
|
|
11560
|
+
var SourceBackedStateStore = class {
|
|
11561
|
+
source;
|
|
11562
|
+
name;
|
|
11563
|
+
serverUrl;
|
|
11564
|
+
constructor(source, name, serverUrl) {
|
|
11565
|
+
this.source = source;
|
|
11566
|
+
this.name = name;
|
|
11567
|
+
this.serverUrl = serverUrl;
|
|
11568
|
+
}
|
|
11569
|
+
async load() {
|
|
11570
|
+
return this.source.load(this.name, this.serverUrl);
|
|
11571
|
+
}
|
|
11572
|
+
async save(state) {
|
|
11573
|
+
await this.source.save(this.name, this.serverUrl, state);
|
|
11574
|
+
}
|
|
11575
|
+
};
|
|
11576
|
+
var SignInCancelledError = class extends Error {
|
|
11577
|
+
constructor() {
|
|
11578
|
+
super("Sign-in cancelled");
|
|
11579
|
+
this.name = "SignInCancelledError";
|
|
11580
|
+
}
|
|
11581
|
+
};
|
|
11582
|
+
function responseFromRedirectUrl(input, state, redirectUrl) {
|
|
11583
|
+
let url;
|
|
11584
|
+
try {
|
|
11585
|
+
url = new URL(input.trim());
|
|
11586
|
+
} catch {
|
|
11587
|
+
throw new Error("Expected the full redirect URL from the browser address bar");
|
|
11588
|
+
}
|
|
11589
|
+
if (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {
|
|
11590
|
+
throw new Error("The redirect URL does not match this sign-in's redirect URI");
|
|
11591
|
+
}
|
|
11592
|
+
const error = url.searchParams.get("error");
|
|
11593
|
+
if (error) throw new Error(url.searchParams.get("error_description") ?? error);
|
|
11594
|
+
if (url.searchParams.get("state") !== state) {
|
|
11595
|
+
throw new Error("The redirect URL belongs to a different sign-in");
|
|
11596
|
+
}
|
|
11597
|
+
const code = url.searchParams.get("code");
|
|
11598
|
+
if (!code) throw new Error("The redirect URL does not contain an authorization code");
|
|
11599
|
+
return { code, iss: url.searchParams.get("iss") ?? void 0 };
|
|
11600
|
+
}
|
|
11601
|
+
async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt, signal) {
|
|
11602
|
+
const controller = new AbortController();
|
|
11603
|
+
const promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
11604
|
+
const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
|
|
11605
|
+
const fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {
|
|
11606
|
+
if (!input?.trim()) throw new SignInCancelledError();
|
|
11607
|
+
return responseFromRedirectUrl(input, state, redirectUrl);
|
|
11608
|
+
});
|
|
11609
|
+
try {
|
|
11610
|
+
return await Promise.race([fromBrowser, fromUser]);
|
|
11611
|
+
} finally {
|
|
11612
|
+
controller.abort();
|
|
11613
|
+
fromBrowser.catch(() => void 0);
|
|
11614
|
+
fromUser.catch(() => void 0);
|
|
11615
|
+
}
|
|
11616
|
+
}
|
|
11617
|
+
function mergeScopes(...scopes) {
|
|
11618
|
+
const merged = [...new Set(scopes.flatMap((s) => s?.split(/\s+/).filter(Boolean) ?? []))];
|
|
11619
|
+
return merged.length > 0 ? merged.join(" ") : void 0;
|
|
11620
|
+
}
|
|
11621
|
+
async function signIn(options) {
|
|
11622
|
+
const { name, serverUrl, source, oauth, prompt, platform: platform2, signal } = options;
|
|
11623
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
11624
|
+
const store = new SourceBackedStateStore(source, name, serverUrl);
|
|
11625
|
+
const stored = await store.load();
|
|
11626
|
+
const cbSettings = callbackSettings(oauth);
|
|
11627
|
+
const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
|
|
11628
|
+
const preferredPort = cbSettings.port ?? (registered[0] ? Number(new URL(registered[0]).port) || void 0 : void 0);
|
|
11629
|
+
const callback = await OAuthCallbackServer.listen({
|
|
11630
|
+
host: cbSettings.host,
|
|
11631
|
+
redirectHost: cbSettings.redirectHost,
|
|
11632
|
+
port: preferredPort ?? 0,
|
|
11633
|
+
path: cbSettings.path
|
|
11634
|
+
});
|
|
11635
|
+
const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
|
|
11636
|
+
try {
|
|
11637
|
+
if (stored) {
|
|
11638
|
+
const next = { ...stored };
|
|
11639
|
+
delete next.oauthState;
|
|
11640
|
+
const hasCimd = buildClientMetadataDocumentFn(oauth, redirectUrl) !== void 0;
|
|
11641
|
+
const keepClient = oauth?.clientId || (hasCimd ? !stored.clientInformation : registered.includes(redirectUrl));
|
|
11642
|
+
if (!keepClient) {
|
|
11643
|
+
delete next.clientInformation;
|
|
11644
|
+
delete next.tokens;
|
|
11645
|
+
delete next.tokensExpireAt;
|
|
11646
|
+
}
|
|
11647
|
+
await store.save(next);
|
|
11648
|
+
}
|
|
11649
|
+
let authorizationUrl;
|
|
11650
|
+
const provider = createProvider(serverUrl, store, oauth, redirectUrl, (url) => {
|
|
11651
|
+
authorizationUrl = url;
|
|
11652
|
+
});
|
|
11653
|
+
const flow = {
|
|
11654
|
+
serverUrl,
|
|
11655
|
+
fetch: timedFetch2(platform2.fetch),
|
|
11656
|
+
signal,
|
|
11657
|
+
authorizationServerMetadataUrl: oauth?.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : void 0,
|
|
11658
|
+
scope: mergeScopes(
|
|
11659
|
+
oauth?.scope,
|
|
11660
|
+
options.stepUp ? stepUpScope(stored?.tokens?.scope, options.scope) : options.scope
|
|
11661
|
+
)
|
|
11662
|
+
};
|
|
11663
|
+
const skipRefresh = options.stepUp === true;
|
|
11664
|
+
if (await authorizeMcp(provider, { ...flow, skipRefresh }) === "AUTHORIZED") return;
|
|
11665
|
+
if (!authorizationUrl) throw new Error("OAuth flow did not produce an authorization URL");
|
|
11666
|
+
const state = await provider.state();
|
|
11667
|
+
const authRedirectUrl = new URL(
|
|
11668
|
+
authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl
|
|
11669
|
+
);
|
|
11670
|
+
prompt.showAuthorizationUrl(authorizationUrl);
|
|
11671
|
+
const { code, iss } = await waitForAuthorizationResponse(
|
|
11672
|
+
callback,
|
|
11673
|
+
state,
|
|
11674
|
+
authRedirectUrl,
|
|
11675
|
+
prompt,
|
|
11676
|
+
signal
|
|
11677
|
+
);
|
|
11678
|
+
await authorizeMcp(provider, { ...flow, authorizationCode: code, iss });
|
|
11679
|
+
} catch (err) {
|
|
11680
|
+
if (signal?.aborted) throw new SignInCancelledError();
|
|
11681
|
+
throw err;
|
|
11682
|
+
} finally {
|
|
11683
|
+
await callback.close();
|
|
11684
|
+
}
|
|
11685
|
+
}
|
|
11686
|
+
|
|
11687
|
+
// src/auth/token-source.ts
|
|
11688
|
+
function mcpNamespace(server) {
|
|
11689
|
+
return `mcp__${server.replace(/-/g, "_")}`;
|
|
11690
|
+
}
|
|
11691
|
+
function storeKey(name, serverUrl) {
|
|
11692
|
+
const ns = mcpNamespace(name);
|
|
11693
|
+
const url = String(new URL(serverUrl));
|
|
11694
|
+
return `${ns}|${url}`;
|
|
11695
|
+
}
|
|
11696
|
+
function isKernel(platform2) {
|
|
11697
|
+
return platform2.kind === "kernel";
|
|
11698
|
+
}
|
|
11699
|
+
|
|
11700
|
+
// src/auth/pi-store.ts
|
|
11701
|
+
var AUTH_FILE = "mcp-auth.json";
|
|
11702
|
+
var FILE_MODE = 384;
|
|
11703
|
+
function parseStates(raw) {
|
|
11704
|
+
if (!raw?.trim()) return {};
|
|
11705
|
+
const parsed = JSON.parse(raw);
|
|
11706
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
11707
|
+
}
|
|
11708
|
+
function serializeStates(states) {
|
|
11709
|
+
return `${JSON.stringify(states, null, 2)}
|
|
11710
|
+
`;
|
|
11711
|
+
}
|
|
11712
|
+
function legacyKey(serverUrl) {
|
|
11713
|
+
return String(new URL(serverUrl));
|
|
11714
|
+
}
|
|
11715
|
+
var PiFileTokenSource = class {
|
|
11716
|
+
platform;
|
|
11717
|
+
authPath;
|
|
11718
|
+
dirPath;
|
|
11719
|
+
constructor(platform2) {
|
|
11720
|
+
this.platform = platform2;
|
|
11721
|
+
this.dirPath = userConfigDir(platform2);
|
|
11722
|
+
this.authPath = `${this.dirPath}/${AUTH_FILE}`;
|
|
11723
|
+
}
|
|
11724
|
+
get path() {
|
|
11725
|
+
return this.authPath;
|
|
11726
|
+
}
|
|
11727
|
+
async getToken(name, serverUrl) {
|
|
11728
|
+
const state = await this.load(name, serverUrl);
|
|
11729
|
+
if (!state?.tokens?.access_token) return void 0;
|
|
11730
|
+
return {
|
|
11731
|
+
accessToken: state.tokens.access_token,
|
|
11732
|
+
expiresAt: state.tokensExpireAt
|
|
11733
|
+
};
|
|
11734
|
+
}
|
|
11735
|
+
async load(name, serverUrl) {
|
|
11736
|
+
const key = storeKey(name, serverUrl);
|
|
11737
|
+
const legacy = legacyKey(serverUrl);
|
|
11738
|
+
const release = await this.acquireLock();
|
|
11739
|
+
try {
|
|
11740
|
+
const raw = await this.readRaw();
|
|
11741
|
+
const states = parseStates(raw);
|
|
11742
|
+
if (states[key]) return states[key];
|
|
11743
|
+
if (states[legacy]) {
|
|
11744
|
+
states[key] = states[legacy];
|
|
11745
|
+
delete states[legacy];
|
|
11746
|
+
await this.writeStates(states);
|
|
11747
|
+
return states[key];
|
|
11748
|
+
}
|
|
11749
|
+
return void 0;
|
|
11750
|
+
} finally {
|
|
11751
|
+
await release();
|
|
11752
|
+
}
|
|
11753
|
+
}
|
|
11754
|
+
async save(name, serverUrl, state) {
|
|
11755
|
+
const key = storeKey(name, serverUrl);
|
|
11756
|
+
const release = await this.acquireLock();
|
|
11757
|
+
try {
|
|
11758
|
+
const raw = await this.readRaw();
|
|
11759
|
+
const states = parseStates(raw);
|
|
11760
|
+
states[key] = state;
|
|
11761
|
+
await this.writeStates(states);
|
|
11762
|
+
} finally {
|
|
11763
|
+
await release();
|
|
11764
|
+
}
|
|
11765
|
+
}
|
|
11766
|
+
async remove(name, serverUrl) {
|
|
11767
|
+
const key = storeKey(name, serverUrl);
|
|
11768
|
+
const legacy = legacyKey(serverUrl);
|
|
11769
|
+
const release = await this.acquireLock();
|
|
11770
|
+
try {
|
|
11771
|
+
const raw = await this.readRaw();
|
|
11772
|
+
const states = parseStates(raw);
|
|
11773
|
+
const stored = key in states ? key : legacy in states ? legacy : void 0;
|
|
11774
|
+
if (!stored) return false;
|
|
11775
|
+
delete states[stored];
|
|
11776
|
+
await this.writeStates(states);
|
|
11777
|
+
return true;
|
|
11778
|
+
} finally {
|
|
11779
|
+
await release();
|
|
11780
|
+
}
|
|
11781
|
+
}
|
|
11782
|
+
async acquireLock() {
|
|
11783
|
+
await this.ensureDir();
|
|
11784
|
+
return acquireLock(this.platform.fs, this.authPath);
|
|
11785
|
+
}
|
|
11786
|
+
async ensureDir() {
|
|
11787
|
+
try {
|
|
11788
|
+
await this.platform.fs.mkdir(this.dirPath);
|
|
11789
|
+
} catch {
|
|
11790
|
+
}
|
|
11791
|
+
}
|
|
11792
|
+
async readRaw() {
|
|
11793
|
+
try {
|
|
11794
|
+
const data = await this.platform.fs.readFile(this.authPath);
|
|
11795
|
+
return new TextDecoder().decode(data);
|
|
11796
|
+
} catch {
|
|
11797
|
+
return void 0;
|
|
11798
|
+
}
|
|
11799
|
+
}
|
|
11800
|
+
async writeStates(states) {
|
|
11801
|
+
await this.platform.fs.writeFile(this.authPath, serializeStates(states), { mode: FILE_MODE });
|
|
11802
|
+
}
|
|
11803
|
+
};
|
|
11804
|
+
|
|
11805
|
+
// src/cli/auth-verbs.ts
|
|
11806
|
+
function createTokenSource(platform2) {
|
|
11807
|
+
return new PiFileTokenSource(platform2);
|
|
11808
|
+
}
|
|
11809
|
+
function resolveServerName(target) {
|
|
11810
|
+
try {
|
|
11811
|
+
const u = new URL(target.startsWith("http") ? target : `https://${target}`);
|
|
11812
|
+
if (target.includes(".") || target.includes(":")) {
|
|
11813
|
+
return { name: `url:${u.origin}${u.pathname}`, isUrl: true };
|
|
11814
|
+
}
|
|
11815
|
+
} catch {
|
|
11816
|
+
}
|
|
11817
|
+
return { name: target, isUrl: false };
|
|
11818
|
+
}
|
|
11819
|
+
async function resolveLoginTarget(platform2, target) {
|
|
11820
|
+
const config = await MergedConfigSource.load(platform2);
|
|
11821
|
+
const entry = config.entry(target);
|
|
11822
|
+
if (entry && "url" in entry.config && entry.config.url) {
|
|
11823
|
+
const httpConfig = entry.config;
|
|
11824
|
+
return {
|
|
11825
|
+
name: target,
|
|
11826
|
+
serverUrl: httpConfig.url,
|
|
11827
|
+
oauth: httpConfig.oauth
|
|
11828
|
+
};
|
|
11829
|
+
}
|
|
11830
|
+
const resolved = resolveServerName(target);
|
|
11831
|
+
if (resolved.isUrl) {
|
|
11832
|
+
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
11833
|
+
return { name: resolved.name, serverUrl: url, oauth: void 0 };
|
|
11834
|
+
}
|
|
11835
|
+
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
11836
|
+
}
|
|
11837
|
+
async function runLogin(platform2, args, source) {
|
|
11838
|
+
if (isKernel(platform2)) {
|
|
11839
|
+
await platform2.write(
|
|
11840
|
+
2,
|
|
11841
|
+
"mcpcat: login is not available in seven. Use the agent's MCP sign-in in Settings, or run mcpcat login on the host.\n"
|
|
11842
|
+
);
|
|
11843
|
+
return 4;
|
|
11844
|
+
}
|
|
11845
|
+
const noBrowser = args.includes("--no-browser");
|
|
11846
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
11847
|
+
if (positional.length === 0) {
|
|
11848
|
+
await platform2.write(2, "mcpcat: login requires a target\n");
|
|
11849
|
+
return 2;
|
|
11850
|
+
}
|
|
11851
|
+
const target = positional[0];
|
|
11852
|
+
let resolved;
|
|
11853
|
+
try {
|
|
11854
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
11855
|
+
} catch (err) {
|
|
11856
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
11857
|
+
`);
|
|
11858
|
+
return 2;
|
|
11859
|
+
}
|
|
11860
|
+
try {
|
|
11861
|
+
await signIn({
|
|
11862
|
+
name: resolved.name,
|
|
11863
|
+
serverUrl: resolved.serverUrl,
|
|
11864
|
+
source,
|
|
11865
|
+
oauth: resolved.oauth,
|
|
11866
|
+
platform: platform2,
|
|
11867
|
+
prompt: {
|
|
11868
|
+
showAuthorizationUrl(url) {
|
|
11869
|
+
if (!noBrowser && platform2.openUrl) {
|
|
11870
|
+
platform2.openUrl(url.href).catch(() => {
|
|
11871
|
+
});
|
|
11872
|
+
}
|
|
11873
|
+
void platform2.write(2, `Open this URL to sign in:
|
|
11874
|
+
${url.href}
|
|
11875
|
+
`);
|
|
11876
|
+
if (noBrowser || !platform2.openUrl) {
|
|
11877
|
+
void platform2.write(2, "\nPaste the redirect URL from your browser address bar:\n");
|
|
11878
|
+
}
|
|
11879
|
+
},
|
|
11880
|
+
async promptForRedirectUrl(signal) {
|
|
11881
|
+
if (!noBrowser && platform2.openUrl) {
|
|
11882
|
+
return new Promise((_resolve, reject) => {
|
|
11883
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
11884
|
+
});
|
|
11885
|
+
}
|
|
11886
|
+
const reader = platform2.stdin.getReader();
|
|
11887
|
+
const chunks = [];
|
|
11888
|
+
try {
|
|
11889
|
+
while (true) {
|
|
11890
|
+
const read = reader.read();
|
|
11891
|
+
const result = await Promise.race([
|
|
11892
|
+
read,
|
|
11893
|
+
new Promise((_, reject) => {
|
|
11894
|
+
signal.addEventListener("abort", () => reject(new SignInCancelledError()));
|
|
11895
|
+
})
|
|
11896
|
+
]);
|
|
11897
|
+
if (result.done) break;
|
|
11898
|
+
chunks.push(result.value);
|
|
11899
|
+
const text = new TextDecoder().decode(concatUint8Arrays(chunks));
|
|
11900
|
+
if (text.includes("\n")) return text.trim();
|
|
11901
|
+
}
|
|
11902
|
+
} finally {
|
|
11903
|
+
reader.releaseLock();
|
|
11904
|
+
}
|
|
11905
|
+
return new TextDecoder().decode(concatUint8Arrays(chunks)).trim() || void 0;
|
|
11906
|
+
}
|
|
11907
|
+
}
|
|
11908
|
+
});
|
|
11909
|
+
await platform2.write(1, `Signed in to ${target}
|
|
11910
|
+
`);
|
|
11911
|
+
return 0;
|
|
11912
|
+
} catch (err) {
|
|
11913
|
+
if (err instanceof SignInCancelledError) {
|
|
11914
|
+
await platform2.write(2, "mcpcat: sign-in cancelled\n");
|
|
11915
|
+
return 1;
|
|
11916
|
+
}
|
|
11917
|
+
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
11918
|
+
`);
|
|
11919
|
+
return 4;
|
|
11920
|
+
}
|
|
11921
|
+
}
|
|
11922
|
+
async function runLogout(platform2, args, source) {
|
|
11923
|
+
if (isKernel(platform2)) {
|
|
11924
|
+
await platform2.write(2, "mcpcat: logout is not available in seven.\n");
|
|
11925
|
+
return 4;
|
|
11926
|
+
}
|
|
11927
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
11928
|
+
if (positional.length === 0) {
|
|
11929
|
+
await platform2.write(2, "mcpcat: logout requires a target\n");
|
|
11930
|
+
return 2;
|
|
11931
|
+
}
|
|
11932
|
+
const target = positional[0];
|
|
11933
|
+
let resolved;
|
|
11934
|
+
try {
|
|
11935
|
+
resolved = await resolveLoginTarget(platform2, target);
|
|
11936
|
+
} catch (err) {
|
|
11937
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
11938
|
+
`);
|
|
11939
|
+
return 2;
|
|
11940
|
+
}
|
|
11941
|
+
const removed = await source.remove(resolved.name, resolved.serverUrl);
|
|
11942
|
+
if (removed) {
|
|
11943
|
+
await platform2.write(1, `Signed out of ${target}
|
|
11944
|
+
`);
|
|
11945
|
+
} else {
|
|
11946
|
+
await platform2.write(1, `No credentials stored for ${target}
|
|
11947
|
+
`);
|
|
11948
|
+
}
|
|
11949
|
+
return 0;
|
|
11950
|
+
}
|
|
11951
|
+
function concatUint8Arrays(arrays) {
|
|
11952
|
+
const total = arrays.reduce((s, a) => s + a.length, 0);
|
|
11953
|
+
const result = new Uint8Array(total);
|
|
11954
|
+
let offset = 0;
|
|
11955
|
+
for (const a of arrays) {
|
|
11956
|
+
result.set(a, offset);
|
|
11957
|
+
offset += a.length;
|
|
11958
|
+
}
|
|
11959
|
+
return result;
|
|
11960
|
+
}
|
|
11961
|
+
|
|
10189
11962
|
// src/config/import.ts
|
|
10190
11963
|
function isRecord5(value) {
|
|
10191
11964
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -11006,6 +12779,91 @@ bridge does not survive a reload; runs in the foreground (start with &)
|
|
|
11006
12779
|
return runServe(platform2, serveOptions, version);
|
|
11007
12780
|
}
|
|
11008
12781
|
|
|
12782
|
+
// src/cli/exit-codes.ts
|
|
12783
|
+
var EXIT_OK = 0;
|
|
12784
|
+
var EXIT_TOOL_ERROR = 1;
|
|
12785
|
+
var EXIT_USAGE = 2;
|
|
12786
|
+
var EXIT_CONNECTION = 3;
|
|
12787
|
+
var EXIT_AUTH = 4;
|
|
12788
|
+
var EXIT_RPC_ERROR = 5;
|
|
12789
|
+
var EXIT_INPUT_REQUIRED = 6;
|
|
12790
|
+
var EXIT_TIMEOUT = 124;
|
|
12791
|
+
var EXIT_INTERRUPTED = 130;
|
|
12792
|
+
var McpcatError = class extends Error {
|
|
12793
|
+
exitCode;
|
|
12794
|
+
constructor(exitCode, message) {
|
|
12795
|
+
super(message);
|
|
12796
|
+
this.name = "McpcatError";
|
|
12797
|
+
this.exitCode = exitCode;
|
|
12798
|
+
}
|
|
12799
|
+
};
|
|
12800
|
+
|
|
12801
|
+
// src/cli/verbose.ts
|
|
12802
|
+
var SENSITIVE_HEADER_RE = /^(authorization|cookie|proxy-authorization)$/i;
|
|
12803
|
+
var SENSITIVE_NAME_RE = /token|key|secret|password|session/i;
|
|
12804
|
+
function isSensitiveHeader(name) {
|
|
12805
|
+
return SENSITIVE_HEADER_RE.test(name) || SENSITIVE_NAME_RE.test(name);
|
|
12806
|
+
}
|
|
12807
|
+
function redact(text, secrets) {
|
|
12808
|
+
let result = text;
|
|
12809
|
+
for (const secret of secrets) {
|
|
12810
|
+
if (secret.length > 0) {
|
|
12811
|
+
while (result.includes(secret)) {
|
|
12812
|
+
result = result.replace(secret, "[REDACTED]");
|
|
12813
|
+
}
|
|
12814
|
+
}
|
|
12815
|
+
}
|
|
12816
|
+
return result;
|
|
12817
|
+
}
|
|
12818
|
+
function createVerboseLogger(platform2, enabled) {
|
|
12819
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
12820
|
+
function write(line) {
|
|
12821
|
+
const safe = redact(line, secrets);
|
|
12822
|
+
platform2.write(2, `${safe}
|
|
12823
|
+
`).catch(() => {
|
|
12824
|
+
});
|
|
12825
|
+
}
|
|
12826
|
+
return {
|
|
12827
|
+
enabled,
|
|
12828
|
+
secrets,
|
|
12829
|
+
request(method, body) {
|
|
12830
|
+
if (!enabled) return;
|
|
12831
|
+
write(`> ${method}`);
|
|
12832
|
+
for (const line of body.split("\n")) {
|
|
12833
|
+
write(`> ${line}`);
|
|
12834
|
+
}
|
|
12835
|
+
},
|
|
12836
|
+
response(body) {
|
|
12837
|
+
if (!enabled) return;
|
|
12838
|
+
for (const line of body.split("\n")) {
|
|
12839
|
+
write(`< ${line}`);
|
|
12840
|
+
}
|
|
12841
|
+
},
|
|
12842
|
+
http(direction, line) {
|
|
12843
|
+
if (!enabled) return;
|
|
12844
|
+
write(`${direction} ${line}`);
|
|
12845
|
+
},
|
|
12846
|
+
server(line) {
|
|
12847
|
+
if (!enabled) return;
|
|
12848
|
+
write(`[server] ${line}`);
|
|
12849
|
+
},
|
|
12850
|
+
headerOut(name, value) {
|
|
12851
|
+
if (!enabled) return;
|
|
12852
|
+
const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
|
|
12853
|
+
write(`> ${name}: ${display}`);
|
|
12854
|
+
},
|
|
12855
|
+
headerIn(name, value) {
|
|
12856
|
+
if (!enabled) return;
|
|
12857
|
+
const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
|
|
12858
|
+
write(`< ${name}: ${display}`);
|
|
12859
|
+
},
|
|
12860
|
+
progress(message) {
|
|
12861
|
+
if (!enabled) return;
|
|
12862
|
+
write(`* ${message}`);
|
|
12863
|
+
}
|
|
12864
|
+
};
|
|
12865
|
+
}
|
|
12866
|
+
|
|
11009
12867
|
// src/cli/main.ts
|
|
11010
12868
|
var VERBS = [
|
|
11011
12869
|
"info",
|
|
@@ -11044,16 +12902,7 @@ var CONFIG_VERBS = {
|
|
|
11044
12902
|
exposure: true,
|
|
11045
12903
|
import: true
|
|
11046
12904
|
};
|
|
11047
|
-
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set([
|
|
11048
|
-
"resources",
|
|
11049
|
-
"read",
|
|
11050
|
-
"prompts",
|
|
11051
|
-
"prompt",
|
|
11052
|
-
"complete",
|
|
11053
|
-
"task",
|
|
11054
|
-
"login",
|
|
11055
|
-
"logout"
|
|
11056
|
-
]);
|
|
12905
|
+
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["resources", "read", "prompts", "prompt", "complete", "task"]);
|
|
11057
12906
|
var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe", "serve"]);
|
|
11058
12907
|
function helpText(version) {
|
|
11059
12908
|
return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
|
|
@@ -11077,8 +12926,8 @@ Configuration verbs:
|
|
|
11077
12926
|
enable|disable <name> Toggle a server
|
|
11078
12927
|
exposure <name> <mode> Set exposure mode
|
|
11079
12928
|
import <file> Import from other configs
|
|
11080
|
-
login <target> OAuth sign-in
|
|
11081
|
-
logout <target> Remove tokens
|
|
12929
|
+
login <target> OAuth sign-in
|
|
12930
|
+
logout <target> Remove stored tokens
|
|
11082
12931
|
|
|
11083
12932
|
Server and code verbs:
|
|
11084
12933
|
serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
|
|
@@ -11123,6 +12972,8 @@ function parseFlags(args) {
|
|
|
11123
12972
|
const serverTargets = [];
|
|
11124
12973
|
let allowShell = false;
|
|
11125
12974
|
let timeoutMs;
|
|
12975
|
+
let verbose = false;
|
|
12976
|
+
let silent = false;
|
|
11126
12977
|
const positional = [];
|
|
11127
12978
|
let trailingCommand;
|
|
11128
12979
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -11159,6 +13010,10 @@ function parseFlags(args) {
|
|
|
11159
13010
|
serverTargets.push(args[++i]);
|
|
11160
13011
|
} else if (arg === "--allow-shell") {
|
|
11161
13012
|
allowShell = true;
|
|
13013
|
+
} else if (arg === "-v" || arg === "--verbose") {
|
|
13014
|
+
verbose = true;
|
|
13015
|
+
} else if (arg === "--silent") {
|
|
13016
|
+
silent = true;
|
|
11162
13017
|
} else if ((arg === "-m" || arg === "--timeout") && args[i + 1]) {
|
|
11163
13018
|
timeoutMs = Number(args[++i]) * 1e3;
|
|
11164
13019
|
} else if (!arg.startsWith("-")) {
|
|
@@ -11179,6 +13034,8 @@ function parseFlags(args) {
|
|
|
11179
13034
|
serverTargets,
|
|
11180
13035
|
allowShell,
|
|
11181
13036
|
timeoutMs,
|
|
13037
|
+
verbose,
|
|
13038
|
+
silent,
|
|
11182
13039
|
positional,
|
|
11183
13040
|
trailingCommand
|
|
11184
13041
|
};
|
|
@@ -11227,7 +13084,9 @@ function parseArgs(args) {
|
|
|
11227
13084
|
inlineSource: flags.inlineSource,
|
|
11228
13085
|
serverTargets: flags.serverTargets,
|
|
11229
13086
|
allowShell: flags.allowShell,
|
|
11230
|
-
timeoutMs: flags.timeoutMs
|
|
13087
|
+
timeoutMs: flags.timeoutMs,
|
|
13088
|
+
verbose: flags.verbose,
|
|
13089
|
+
silent: flags.silent
|
|
11231
13090
|
};
|
|
11232
13091
|
}
|
|
11233
13092
|
function formatToolLine(tool) {
|
|
@@ -11251,26 +13110,6 @@ function formatToolSchema(tool) {
|
|
|
11251
13110
|
}
|
|
11252
13111
|
return lines.join("\n");
|
|
11253
13112
|
}
|
|
11254
|
-
function renderCallResult(result) {
|
|
11255
|
-
const parts = [];
|
|
11256
|
-
if (result.content) {
|
|
11257
|
-
for (const block of result.content) {
|
|
11258
|
-
if (block.type === "text") parts.push(block.text);
|
|
11259
|
-
else if (block.type === "image")
|
|
11260
|
-
parts.push(`[image: ${block.mimeType}, ${Math.ceil(block.data.length * 3 / 4)} bytes]`);
|
|
11261
|
-
else if (block.type === "resource_link") parts.push(`[resource: ${block.uri}]`);
|
|
11262
|
-
else if (block.type === "resource") {
|
|
11263
|
-
parts.push(
|
|
11264
|
-
"text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`
|
|
11265
|
-
);
|
|
11266
|
-
}
|
|
11267
|
-
}
|
|
11268
|
-
}
|
|
11269
|
-
if (parts.length === 0 && result.structuredContent !== void 0) {
|
|
11270
|
-
parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
11271
|
-
}
|
|
11272
|
-
return parts.join("\n");
|
|
11273
|
-
}
|
|
11274
13113
|
var NETWORK_HINT = "connection failed (use slicc-node, slicc-swift, or the browser extension for servers without CORS)";
|
|
11275
13114
|
function transportHint(err) {
|
|
11276
13115
|
if (err instanceof TypeError) {
|
|
@@ -11313,8 +13152,12 @@ async function readDataArg(platform2, dataArg) {
|
|
|
11313
13152
|
}
|
|
11314
13153
|
return JSON.parse(raw);
|
|
11315
13154
|
}
|
|
11316
|
-
async function runInfo(platform2, conn) {
|
|
13155
|
+
async function runInfo(platform2, conn, json) {
|
|
11317
13156
|
const info = await conn.info();
|
|
13157
|
+
if (json) {
|
|
13158
|
+
await renderInfoJson(platform2, info);
|
|
13159
|
+
return EXIT_OK;
|
|
13160
|
+
}
|
|
11318
13161
|
const lines = [];
|
|
11319
13162
|
if (info.name) lines.push(`Server: ${info.name}${info.version ? ` ${info.version}` : ""}`);
|
|
11320
13163
|
if (info.title) lines.push(`Title: ${info.title}`);
|
|
@@ -11331,48 +13174,66 @@ async function runInfo(platform2, conn) {
|
|
|
11331
13174
|
}
|
|
11332
13175
|
await platform2.write(1, `${lines.join("\n")}
|
|
11333
13176
|
`);
|
|
11334
|
-
return
|
|
13177
|
+
return EXIT_OK;
|
|
11335
13178
|
}
|
|
11336
|
-
async function runTools(platform2, conn) {
|
|
13179
|
+
async function runTools(platform2, conn, json) {
|
|
11337
13180
|
const tools = await conn.listTools();
|
|
11338
|
-
if (
|
|
11339
|
-
await platform2
|
|
11340
|
-
|
|
11341
|
-
await platform2.write(1, `${tools.map(formatToolLine).join("\n")}
|
|
11342
|
-
`);
|
|
13181
|
+
if (json) {
|
|
13182
|
+
await renderToolsJson(platform2, tools);
|
|
13183
|
+
return EXIT_OK;
|
|
11343
13184
|
}
|
|
11344
|
-
|
|
13185
|
+
await platform2.write(
|
|
13186
|
+
1,
|
|
13187
|
+
tools.length === 0 ? "(no tools)\n" : `${tools.map(formatToolLine).join("\n")}
|
|
13188
|
+
`
|
|
13189
|
+
);
|
|
13190
|
+
return EXIT_OK;
|
|
11345
13191
|
}
|
|
11346
|
-
async function runCall(platform2, conn, toolName, dataArg) {
|
|
11347
|
-
let
|
|
13192
|
+
async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
13193
|
+
let base = {};
|
|
11348
13194
|
if (dataArg) {
|
|
11349
13195
|
try {
|
|
11350
|
-
|
|
13196
|
+
base = await readDataArg(platform2, dataArg);
|
|
11351
13197
|
} catch (err) {
|
|
11352
|
-
await
|
|
11353
|
-
|
|
11354
|
-
|
|
13198
|
+
await writeErr(
|
|
13199
|
+
platform2,
|
|
13200
|
+
opts.json ?? false,
|
|
13201
|
+
"usage",
|
|
13202
|
+
`invalid -d argument: ${err.message}`
|
|
13203
|
+
);
|
|
13204
|
+
return EXIT_USAGE;
|
|
11355
13205
|
}
|
|
11356
13206
|
}
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
13207
|
+
let fin = base;
|
|
13208
|
+
if (toolArgs.length > 0) {
|
|
13209
|
+
const tools = await conn.listTools();
|
|
13210
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
13211
|
+
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
13212
|
+
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: opts.noValidate });
|
|
13213
|
+
if (!c.ok) {
|
|
13214
|
+
await writeErr(platform2, opts.json ?? false, "usage", c.error);
|
|
13215
|
+
return EXIT_USAGE;
|
|
13216
|
+
}
|
|
13217
|
+
fin = mergeArgs(base, c.value);
|
|
11363
13218
|
}
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
13219
|
+
return renderCallResult(
|
|
13220
|
+
platform2,
|
|
13221
|
+
await conn.callTool(toolName, fin, { onProgress: opts.onProgress }),
|
|
13222
|
+
{ json: opts.json }
|
|
13223
|
+
);
|
|
11367
13224
|
}
|
|
11368
|
-
async function runDescribe(platform2, conn, toolName, showSchema) {
|
|
13225
|
+
async function runDescribe(platform2, conn, toolName, showSchema, json) {
|
|
11369
13226
|
const tools = await conn.listTools();
|
|
11370
13227
|
if (toolName) {
|
|
11371
13228
|
const tool = tools.find((t) => t.name === toolName);
|
|
11372
13229
|
if (!tool) {
|
|
11373
|
-
await platform2
|
|
13230
|
+
await writeErr(platform2, json, "usage", `tool '${toolName}' not found`);
|
|
13231
|
+
return EXIT_USAGE;
|
|
13232
|
+
}
|
|
13233
|
+
if (json) {
|
|
13234
|
+
await platform2.write(1, `${JSON.stringify(tool, null, 2)}
|
|
11374
13235
|
`);
|
|
11375
|
-
return
|
|
13236
|
+
return EXIT_OK;
|
|
11376
13237
|
}
|
|
11377
13238
|
if (showSchema && tool.inputSchema) {
|
|
11378
13239
|
await platform2.write(1, `${JSON.stringify(tool.inputSchema, null, 2)}
|
|
@@ -11382,28 +13243,36 @@ async function runDescribe(platform2, conn, toolName, showSchema) {
|
|
|
11382
13243
|
`);
|
|
11383
13244
|
}
|
|
11384
13245
|
} else {
|
|
13246
|
+
if (json) {
|
|
13247
|
+
await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
|
|
13248
|
+
`);
|
|
13249
|
+
return EXIT_OK;
|
|
13250
|
+
}
|
|
11385
13251
|
for (const tool of tools) {
|
|
11386
13252
|
await platform2.write(1, `${formatToolSchema(tool)}
|
|
11387
13253
|
|
|
11388
13254
|
`);
|
|
11389
13255
|
}
|
|
11390
13256
|
}
|
|
11391
|
-
return
|
|
13257
|
+
return EXIT_OK;
|
|
11392
13258
|
}
|
|
11393
|
-
async function runVerb(platform2, parsed, conn) {
|
|
13259
|
+
async function runVerb(platform2, parsed, conn, onProgress) {
|
|
11394
13260
|
switch (parsed.verb) {
|
|
11395
13261
|
case "info":
|
|
11396
|
-
return runInfo(platform2, conn);
|
|
13262
|
+
return runInfo(platform2, conn, parsed.jsonOutput);
|
|
11397
13263
|
case "tools":
|
|
11398
|
-
return runTools(platform2, conn);
|
|
13264
|
+
return runTools(platform2, conn, parsed.jsonOutput);
|
|
11399
13265
|
case "call":
|
|
11400
|
-
return runCall(platform2, conn, parsed.toolName, parsed.dataArg
|
|
13266
|
+
return runCall(platform2, conn, parsed.toolName, parsed.dataArg, [], {
|
|
13267
|
+
json: parsed.jsonOutput,
|
|
13268
|
+
onProgress
|
|
13269
|
+
});
|
|
11401
13270
|
case "describe":
|
|
11402
|
-
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema);
|
|
13271
|
+
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
|
|
11403
13272
|
default:
|
|
11404
13273
|
await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
|
|
11405
13274
|
`);
|
|
11406
|
-
return
|
|
13275
|
+
return EXIT_USAGE;
|
|
11407
13276
|
}
|
|
11408
13277
|
}
|
|
11409
13278
|
async function probeServer(_name, config, platform2) {
|
|
@@ -11649,10 +13518,18 @@ async function run(platform2, version, args) {
|
|
|
11649
13518
|
}
|
|
11650
13519
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args);
|
|
11651
13520
|
if (discResult !== void 0) return discResult;
|
|
13521
|
+
if (parsed.verb === "login") {
|
|
13522
|
+
const source = createTokenSource(platform2);
|
|
13523
|
+
return runLogin(platform2, args.slice(1), source);
|
|
13524
|
+
}
|
|
13525
|
+
if (parsed.verb === "logout") {
|
|
13526
|
+
const source = createTokenSource(platform2);
|
|
13527
|
+
return runLogout(platform2, args.slice(1), source);
|
|
13528
|
+
}
|
|
11652
13529
|
if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
|
|
11653
13530
|
await platform2.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
|
|
11654
13531
|
`);
|
|
11655
|
-
return
|
|
13532
|
+
return EXIT_TOOL_ERROR;
|
|
11656
13533
|
}
|
|
11657
13534
|
if (parsed.verb === "serve") {
|
|
11658
13535
|
return dispatchServeVerb(platform2, args, version);
|
|
@@ -11674,44 +13551,132 @@ async function run(platform2, version, args) {
|
|
|
11674
13551
|
`);
|
|
11675
13552
|
return 2;
|
|
11676
13553
|
}
|
|
13554
|
+
return runClientVerb(platform2, version, parsed);
|
|
13555
|
+
}
|
|
13556
|
+
function expandAndCollect(headers, env2) {
|
|
13557
|
+
const secrets = [];
|
|
13558
|
+
const result = {};
|
|
13559
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
13560
|
+
const e = expandPlaceholders(v, env2);
|
|
13561
|
+
result[k] = e;
|
|
13562
|
+
if (e !== v) secrets.push(e);
|
|
13563
|
+
if (v.length > 0) secrets.push(v);
|
|
13564
|
+
}
|
|
13565
|
+
return { expanded: result, secrets };
|
|
13566
|
+
}
|
|
13567
|
+
function classifyError(err) {
|
|
13568
|
+
if (err instanceof InputRequiredError) {
|
|
13569
|
+
return { code: EXIT_INPUT_REQUIRED, kind: "input_required", msg: err.requestJson };
|
|
13570
|
+
}
|
|
13571
|
+
if (err instanceof McpTimeoutError2) {
|
|
13572
|
+
return { code: EXIT_TIMEOUT, kind: "timeout", msg: err.message };
|
|
13573
|
+
}
|
|
13574
|
+
if (err instanceof McpAuthError) {
|
|
13575
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
13576
|
+
}
|
|
13577
|
+
if (err instanceof AuthStepUpError) {
|
|
13578
|
+
const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
|
|
13579
|
+
return {
|
|
13580
|
+
code: EXIT_AUTH,
|
|
13581
|
+
kind: "auth",
|
|
13582
|
+
msg: `insufficient scope. Run: mcpcat login ${err.target ?? "<target>"}${scopeHint}`
|
|
13583
|
+
};
|
|
13584
|
+
}
|
|
13585
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
13586
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
13587
|
+
}
|
|
13588
|
+
if (err instanceof McpcatError) {
|
|
13589
|
+
return { code: err.exitCode, kind: "error", msg: err.message };
|
|
13590
|
+
}
|
|
13591
|
+
const msg = err.message ?? String(err);
|
|
13592
|
+
const name = err.name ?? "";
|
|
13593
|
+
if (name === "McpTimeoutError" || msg.includes("timed out") || msg.includes("timeout") || msg.includes("Timeout")) {
|
|
13594
|
+
return { code: EXIT_TIMEOUT, kind: "timeout", msg };
|
|
13595
|
+
}
|
|
13596
|
+
return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
|
|
13597
|
+
}
|
|
13598
|
+
function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
13599
|
+
if (hasAuthHeader || !url) return void 0;
|
|
13600
|
+
const source = createTokenSource(platform2);
|
|
13601
|
+
const entry = config.entry(target);
|
|
13602
|
+
const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
|
|
13603
|
+
const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
|
|
13604
|
+
return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
|
|
13605
|
+
}
|
|
13606
|
+
async function runClientVerb(platform2, version, parsed) {
|
|
13607
|
+
const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
|
|
13608
|
+
const verbose = createVerboseLogger(platform2, parsed.verbose);
|
|
13609
|
+
for (const s of secrets) verbose.secrets.add(s);
|
|
13610
|
+
const timeoutMs = parsed.timeoutMs;
|
|
13611
|
+
const sigintController = new AbortController();
|
|
13612
|
+
let interrupted = false;
|
|
13613
|
+
platform2.onSignal?.("SIGINT", () => {
|
|
13614
|
+
interrupted = true;
|
|
13615
|
+
sigintController.abort();
|
|
13616
|
+
});
|
|
13617
|
+
const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
|
|
13618
|
+
const onProgress = (progress, total, message) => {
|
|
13619
|
+
if (!showProgress) return;
|
|
13620
|
+
const pct = total ? ` ${Math.round(progress / total * 100)}%` : "";
|
|
13621
|
+
const msg = message ? ` ${message}` : "";
|
|
13622
|
+
platform2.write(2, `* progress:${pct}${msg}
|
|
13623
|
+
`).catch(() => {
|
|
13624
|
+
});
|
|
13625
|
+
};
|
|
11677
13626
|
const config = await MergedConfigSource.load(platform2);
|
|
13627
|
+
const target = parsed.target === "__trailing__" ? "" : parsed.target;
|
|
11678
13628
|
let resolved;
|
|
11679
13629
|
try {
|
|
11680
|
-
resolved = await resolveTarget(
|
|
11681
|
-
parsed.target === "__trailing__" ? "" : parsed.target,
|
|
11682
|
-
parsed.trailingCommand,
|
|
11683
|
-
config
|
|
11684
|
-
);
|
|
13630
|
+
resolved = await resolveTarget(target, parsed.trailingCommand, config);
|
|
11685
13631
|
} catch (err) {
|
|
11686
|
-
await platform2.
|
|
11687
|
-
|
|
11688
|
-
return 2;
|
|
13632
|
+
await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
|
|
13633
|
+
return EXIT_USAGE;
|
|
11689
13634
|
}
|
|
13635
|
+
const hasAuthHeader = "Authorization" in expanded;
|
|
13636
|
+
const authProvider = resolveAuthProvider(platform2, hasAuthHeader, resolved.url, target, config);
|
|
11690
13637
|
let conn;
|
|
11691
13638
|
try {
|
|
11692
13639
|
conn = await connect(platform2, {
|
|
11693
13640
|
...resolved,
|
|
11694
|
-
headers: { ...resolved.headers, ...
|
|
13641
|
+
headers: { ...resolved.headers, ...expanded },
|
|
11695
13642
|
protocolVersion: parsed.protocolVersion,
|
|
11696
|
-
version
|
|
13643
|
+
version,
|
|
13644
|
+
timeoutMs,
|
|
13645
|
+
signal: sigintController.signal,
|
|
13646
|
+
verbose,
|
|
13647
|
+
authProvider
|
|
11697
13648
|
});
|
|
11698
13649
|
} catch (err) {
|
|
13650
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) {
|
|
13651
|
+
const e = classifyError(err);
|
|
13652
|
+
await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
13653
|
+
return EXIT_AUTH;
|
|
13654
|
+
}
|
|
13655
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
13656
|
+
await writeErr(
|
|
13657
|
+
platform2,
|
|
13658
|
+
parsed.jsonOutput,
|
|
13659
|
+
"auth",
|
|
13660
|
+
`unauthorized. Run: mcpcat login ${target}`
|
|
13661
|
+
);
|
|
13662
|
+
return EXIT_AUTH;
|
|
13663
|
+
}
|
|
11699
13664
|
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
11700
|
-
await platform2.
|
|
11701
|
-
|
|
11702
|
-
return 3;
|
|
13665
|
+
await writeErr(platform2, parsed.jsonOutput, "connection", msg);
|
|
13666
|
+
return EXIT_CONNECTION;
|
|
11703
13667
|
}
|
|
11704
13668
|
try {
|
|
11705
|
-
return await runVerb(platform2, parsed, conn);
|
|
13669
|
+
return await runVerb(platform2, parsed, conn, onProgress);
|
|
11706
13670
|
} catch (err) {
|
|
11707
|
-
if (
|
|
11708
|
-
await platform2.write(2,
|
|
11709
|
-
|
|
11710
|
-
return 6;
|
|
13671
|
+
if (interrupted) {
|
|
13672
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
13673
|
+
return EXIT_INTERRUPTED;
|
|
11711
13674
|
}
|
|
11712
|
-
|
|
13675
|
+
const e = classifyError(err);
|
|
13676
|
+
if (err instanceof InputRequiredError) await platform2.write(2, `${e.msg}
|
|
11713
13677
|
`);
|
|
11714
|
-
|
|
13678
|
+
else await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
13679
|
+
return e.code;
|
|
11715
13680
|
} finally {
|
|
11716
13681
|
try {
|
|
11717
13682
|
await conn.close();
|
|
@@ -11719,6 +13684,11 @@ async function run(platform2, version, args) {
|
|
|
11719
13684
|
}
|
|
11720
13685
|
}
|
|
11721
13686
|
}
|
|
13687
|
+
async function writeErr(p, json, kind, msg) {
|
|
13688
|
+
if (json) await renderErrorJson(p, kind, msg);
|
|
13689
|
+
else await p.write(2, `mcpcat: ${msg}
|
|
13690
|
+
`);
|
|
13691
|
+
}
|
|
11722
13692
|
|
|
11723
13693
|
// src/platform/kernel.ts
|
|
11724
13694
|
function kernelFs(ctx) {
|
|
@@ -11835,7 +13805,7 @@ async function main(ctx) {
|
|
|
11835
13805
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
11836
13806
|
});
|
|
11837
13807
|
const args = ctx.argv.slice(1);
|
|
11838
|
-
return run(platform2, "0.
|
|
13808
|
+
return run(platform2, "0.10.0", args);
|
|
11839
13809
|
}
|
|
11840
13810
|
export {
|
|
11841
13811
|
main as default
|