@ai-ecoverse/mcpcat 0.10.0 → 0.12.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/README.md +8 -2
- package/dist/host/mcpcat.mjs +129 -79
- package/dist/kernel/mcpcat.mjs +129 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
A curl for the Model Context Protocol. One command that talks to any MCP server from a shell, and turns any shell command into an MCP server.
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
curl -fsSL https://github.com/ai-ecoverse/mcpcat/releases/latest/download/install.sh | sh
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or via npm:
|
|
12
|
+
|
|
5
13
|
```sh
|
|
6
14
|
npm install -g @ai-ecoverse/mcpcat
|
|
7
|
-
mcpcat --version
|
|
8
|
-
mcpcat --help
|
|
9
15
|
```
|
|
10
16
|
|
|
11
17
|
mcpcat runs natively on macOS, Linux and Windows (Node ≥ 24), and inside Sliccy Seven as a kernel program, with the same flags, the same output and the same config file in both places.
|
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -7773,6 +7773,14 @@ var McpAuthError = class extends Error {
|
|
|
7773
7773
|
this.target = target;
|
|
7774
7774
|
}
|
|
7775
7775
|
};
|
|
7776
|
+
var AuthRetryableError = class extends Error {
|
|
7777
|
+
response;
|
|
7778
|
+
constructor(response) {
|
|
7779
|
+
super(`HTTP ${response.status}: auth retry`);
|
|
7780
|
+
this.name = "AuthRetryableError";
|
|
7781
|
+
this.response = response;
|
|
7782
|
+
}
|
|
7783
|
+
};
|
|
7776
7784
|
function selectSseFrame(body, targetId) {
|
|
7777
7785
|
const text = new TextDecoder().decode(body);
|
|
7778
7786
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -7797,6 +7805,42 @@ function mcpNameForMethod(method, params) {
|
|
|
7797
7805
|
if (method === "resources/read") return params.uri;
|
|
7798
7806
|
return void 0;
|
|
7799
7807
|
}
|
|
7808
|
+
function makeAbort(timeoutMs, externalSignal) {
|
|
7809
|
+
const controller = new AbortController();
|
|
7810
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7811
|
+
if (externalSignal) {
|
|
7812
|
+
if (externalSignal.aborted) {
|
|
7813
|
+
clearTimeout(timer);
|
|
7814
|
+
controller.abort();
|
|
7815
|
+
} else {
|
|
7816
|
+
externalSignal.addEventListener(
|
|
7817
|
+
"abort",
|
|
7818
|
+
() => {
|
|
7819
|
+
clearTimeout(timer);
|
|
7820
|
+
controller.abort();
|
|
7821
|
+
},
|
|
7822
|
+
{ once: true }
|
|
7823
|
+
);
|
|
7824
|
+
}
|
|
7825
|
+
}
|
|
7826
|
+
return { controller, timer };
|
|
7827
|
+
}
|
|
7828
|
+
function logHeaders(verbose, dir, entries) {
|
|
7829
|
+
for (const [k, v] of entries) {
|
|
7830
|
+
if (dir === ">") verbose.headerOut(k, v);
|
|
7831
|
+
else verbose.headerIn(k, v);
|
|
7832
|
+
}
|
|
7833
|
+
}
|
|
7834
|
+
function buildServerInfo(discoverResult2) {
|
|
7835
|
+
return {
|
|
7836
|
+
name: discoverResult2?.serverInfo?.name,
|
|
7837
|
+
version: discoverResult2?.serverInfo?.version,
|
|
7838
|
+
title: discoverResult2?.serverInfo?.title,
|
|
7839
|
+
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
7840
|
+
capabilities: discoverResult2?.capabilities ?? {},
|
|
7841
|
+
instructions: discoverResult2?.instructions
|
|
7842
|
+
};
|
|
7843
|
+
}
|
|
7800
7844
|
async function createStatelessConnection(options) {
|
|
7801
7845
|
const {
|
|
7802
7846
|
url,
|
|
@@ -7805,11 +7849,12 @@ async function createStatelessConnection(options) {
|
|
|
7805
7849
|
timeoutMs = 6e4,
|
|
7806
7850
|
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
7807
7851
|
signal: externalSignal,
|
|
7808
|
-
verbose
|
|
7852
|
+
verbose,
|
|
7853
|
+
authProvider
|
|
7809
7854
|
} = options;
|
|
7810
7855
|
let nextId2 = 1;
|
|
7811
7856
|
let negotiatedVersion;
|
|
7812
|
-
function buildHeaders(method, params) {
|
|
7857
|
+
function buildHeaders(method, params, token) {
|
|
7813
7858
|
const h = {
|
|
7814
7859
|
"Content-Type": "application/json",
|
|
7815
7860
|
Accept: "application/json, text/event-stream",
|
|
@@ -7819,36 +7864,12 @@ async function createStatelessConnection(options) {
|
|
|
7819
7864
|
const n = mcpNameForMethod(method, params);
|
|
7820
7865
|
if (n) h["Mcp-Name"] = n;
|
|
7821
7866
|
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
7867
|
+
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
7822
7868
|
return h;
|
|
7823
7869
|
}
|
|
7824
|
-
function makeAbort() {
|
|
7825
|
-
const controller = new AbortController();
|
|
7826
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7827
|
-
if (externalSignal) {
|
|
7828
|
-
if (externalSignal.aborted) {
|
|
7829
|
-
clearTimeout(timer);
|
|
7830
|
-
controller.abort();
|
|
7831
|
-
} else {
|
|
7832
|
-
externalSignal.addEventListener(
|
|
7833
|
-
"abort",
|
|
7834
|
-
() => {
|
|
7835
|
-
clearTimeout(timer);
|
|
7836
|
-
controller.abort();
|
|
7837
|
-
},
|
|
7838
|
-
{ once: true }
|
|
7839
|
-
);
|
|
7840
|
-
}
|
|
7841
|
-
}
|
|
7842
|
-
return { controller, timer };
|
|
7843
|
-
}
|
|
7844
|
-
function logHeaders(dir, entries) {
|
|
7845
|
-
if (!verbose?.enabled) return;
|
|
7846
|
-
for (const [k, v] of entries) {
|
|
7847
|
-
if (dir === ">") verbose.headerOut(k, v);
|
|
7848
|
-
else verbose.headerIn(k, v);
|
|
7849
|
-
}
|
|
7850
|
-
}
|
|
7851
7870
|
async function handleErrorStatus(res, method) {
|
|
7871
|
+
if ((res.status === 401 || res.status === 403) && authProvider)
|
|
7872
|
+
throw new AuthRetryableError(res);
|
|
7852
7873
|
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
7853
7874
|
const text = await res.text();
|
|
7854
7875
|
let rpc;
|
|
@@ -7860,34 +7881,27 @@ async function createStatelessConnection(options) {
|
|
|
7860
7881
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
7861
7882
|
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
7862
7883
|
}
|
|
7863
|
-
async function
|
|
7864
|
-
const
|
|
7865
|
-
const headers = buildHeaders(method, params);
|
|
7866
|
-
const meta = {
|
|
7867
|
-
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
7868
|
-
clientInfo
|
|
7869
|
-
};
|
|
7870
|
-
const body = JSON.stringify({
|
|
7871
|
-
jsonrpc: "2.0",
|
|
7872
|
-
id,
|
|
7873
|
-
method,
|
|
7874
|
-
params: { ...params, _meta: meta }
|
|
7875
|
-
});
|
|
7884
|
+
async function doFetch(method, params, body) {
|
|
7885
|
+
const token = await authProvider?.token();
|
|
7886
|
+
const headers = buildHeaders(method, params, token);
|
|
7876
7887
|
if (verbose?.enabled) {
|
|
7877
7888
|
verbose.request(method, body);
|
|
7878
|
-
logHeaders(">", Object.entries(headers));
|
|
7889
|
+
logHeaders(verbose, ">", Object.entries(headers));
|
|
7879
7890
|
}
|
|
7880
|
-
const { controller, timer } = makeAbort();
|
|
7881
|
-
let res;
|
|
7891
|
+
const { controller, timer } = makeAbort(timeoutMs, externalSignal);
|
|
7882
7892
|
try {
|
|
7883
|
-
res = await fetchImpl(url, {
|
|
7893
|
+
const res = await fetchImpl(url, {
|
|
7884
7894
|
method: "POST",
|
|
7885
7895
|
headers,
|
|
7886
7896
|
body,
|
|
7887
7897
|
signal: controller.signal
|
|
7888
7898
|
});
|
|
7899
|
+
if (verbose?.enabled) {
|
|
7900
|
+
verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
|
|
7901
|
+
logHeaders(verbose, "<", res.headers.entries());
|
|
7902
|
+
}
|
|
7903
|
+
return { res, token };
|
|
7889
7904
|
} catch (err) {
|
|
7890
|
-
clearTimeout(timer);
|
|
7891
7905
|
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
7892
7906
|
throw new McpTimeoutError2(method, timeoutMs);
|
|
7893
7907
|
}
|
|
@@ -7895,11 +7909,8 @@ async function createStatelessConnection(options) {
|
|
|
7895
7909
|
} finally {
|
|
7896
7910
|
clearTimeout(timer);
|
|
7897
7911
|
}
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
logHeaders("<", res.headers.entries());
|
|
7901
|
-
}
|
|
7902
|
-
if (res.status >= 400) await handleErrorStatus(res, method);
|
|
7912
|
+
}
|
|
7913
|
+
async function parseResponse(res, id) {
|
|
7903
7914
|
const ct = res.headers.get("content-type") ?? "";
|
|
7904
7915
|
let frame;
|
|
7905
7916
|
if (ct.toLowerCase().includes("text/event-stream")) {
|
|
@@ -7913,10 +7924,53 @@ async function createStatelessConnection(options) {
|
|
|
7913
7924
|
}
|
|
7914
7925
|
return frame.result;
|
|
7915
7926
|
}
|
|
7927
|
+
async function handleAuthError(err, token) {
|
|
7928
|
+
if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url);
|
|
7929
|
+
try {
|
|
7930
|
+
await authProvider.onUnauthorized({
|
|
7931
|
+
response: err.response,
|
|
7932
|
+
serverUrl: new URL(url),
|
|
7933
|
+
fetch: fetchImpl,
|
|
7934
|
+
token
|
|
7935
|
+
});
|
|
7936
|
+
} catch (authErr) {
|
|
7937
|
+
if (authErr instanceof AuthStepUpError) throw authErr;
|
|
7938
|
+
throw new McpAuthError(err.response.status, url);
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7941
|
+
async function request(method, params) {
|
|
7942
|
+
const id = nextId2++;
|
|
7943
|
+
const meta = {
|
|
7944
|
+
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
7945
|
+
clientInfo
|
|
7946
|
+
};
|
|
7947
|
+
const body = JSON.stringify({
|
|
7948
|
+
jsonrpc: "2.0",
|
|
7949
|
+
id,
|
|
7950
|
+
method,
|
|
7951
|
+
params: { ...params, _meta: meta }
|
|
7952
|
+
});
|
|
7953
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7954
|
+
const { res, token } = await doFetch(method, params, body);
|
|
7955
|
+
if (res.status >= 400) {
|
|
7956
|
+
try {
|
|
7957
|
+
await handleErrorStatus(res, method);
|
|
7958
|
+
} catch (err) {
|
|
7959
|
+
if (err instanceof AuthRetryableError && attempt === 0) {
|
|
7960
|
+
await handleAuthError(err, token);
|
|
7961
|
+
continue;
|
|
7962
|
+
}
|
|
7963
|
+
throw err;
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
return parseResponse(res, id);
|
|
7967
|
+
}
|
|
7968
|
+
throw new McpAuthError(401, url);
|
|
7969
|
+
}
|
|
7916
7970
|
const discoverResult2 = await request("server/discover", {
|
|
7917
7971
|
protocolVersion: STATELESS_PROTOCOL_VERSION
|
|
7918
7972
|
});
|
|
7919
|
-
const supported =
|
|
7973
|
+
const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
|
|
7920
7974
|
if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
|
|
7921
7975
|
throw new Error(
|
|
7922
7976
|
`Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
@@ -7927,15 +7981,7 @@ async function createStatelessConnection(options) {
|
|
|
7927
7981
|
return {
|
|
7928
7982
|
async info() {
|
|
7929
7983
|
if (serverInfoCache) return serverInfoCache;
|
|
7930
|
-
|
|
7931
|
-
serverInfoCache = {
|
|
7932
|
-
name: r?.serverInfo?.name,
|
|
7933
|
-
version: r?.serverInfo?.version,
|
|
7934
|
-
title: r?.serverInfo?.title,
|
|
7935
|
-
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
7936
|
-
capabilities: r?.capabilities ?? {},
|
|
7937
|
-
instructions: r?.instructions
|
|
7938
|
-
};
|
|
7984
|
+
serverInfoCache = buildServerInfo(discoverResult2);
|
|
7939
7985
|
return serverInfoCache;
|
|
7940
7986
|
},
|
|
7941
7987
|
async listTools() {
|
|
@@ -8085,23 +8131,12 @@ async function connect(platform2, options) {
|
|
|
8085
8131
|
});
|
|
8086
8132
|
}
|
|
8087
8133
|
if (!options.url) throw new Error("connect requires either a url or a command");
|
|
8088
|
-
if (options.authProvider) {
|
|
8089
|
-
const token = await options.authProvider.token();
|
|
8090
|
-
if (token) {
|
|
8091
|
-
return createPiConnection({
|
|
8092
|
-
url: options.url,
|
|
8093
|
-
headers: options.headers,
|
|
8094
|
-
fetchImpl: platform2.fetch,
|
|
8095
|
-
authProvider: options.authProvider,
|
|
8096
|
-
...shared
|
|
8097
|
-
});
|
|
8098
|
-
}
|
|
8099
|
-
}
|
|
8100
8134
|
if (options.protocolVersion && !options.protocolVersion.startsWith("2026")) {
|
|
8101
8135
|
return createPiConnection({
|
|
8102
8136
|
url: options.url,
|
|
8103
8137
|
headers: options.headers,
|
|
8104
8138
|
fetchImpl: platform2.fetch,
|
|
8139
|
+
authProvider: options.authProvider,
|
|
8105
8140
|
...shared
|
|
8106
8141
|
});
|
|
8107
8142
|
}
|
|
@@ -8113,11 +8148,14 @@ async function connect(platform2, options) {
|
|
|
8113
8148
|
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
|
|
8114
8149
|
timeoutMs: options.timeoutMs,
|
|
8115
8150
|
signal: options.signal,
|
|
8116
|
-
verbose: options.verbose
|
|
8151
|
+
verbose: options.verbose,
|
|
8152
|
+
authProvider: options.authProvider
|
|
8117
8153
|
});
|
|
8118
8154
|
} catch (err) {
|
|
8119
8155
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
8120
8156
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
8157
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
8158
|
+
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
8121
8159
|
if (isMethodNotFound) {
|
|
8122
8160
|
return createPiConnection({
|
|
8123
8161
|
url: options.url,
|
|
@@ -8605,13 +8643,16 @@ async function createSandbox(platform2, tools, globals, timeoutMs) {
|
|
|
8605
8643
|
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
|
|
8606
8644
|
}
|
|
8607
8645
|
const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
8608
|
-
const
|
|
8646
|
+
const getSeaDir = globalThis.__mcpcat_get_sea_asset_dir;
|
|
8647
|
+
const seaDir = getSeaDir?.();
|
|
8648
|
+
const wasmPath = seaDir ? `${seaDir}/quickjs.wasm` : await resolveWasmPath(platform2);
|
|
8649
|
+
const workerUrl = seaDir ? new URL(`file://${seaDir}/codemode-worker.mjs`) : new URL("./codemode-worker.mjs", import.meta.url);
|
|
8609
8650
|
return new CodemodeSandbox2({
|
|
8610
8651
|
tools,
|
|
8611
8652
|
globals,
|
|
8612
8653
|
timeoutMs,
|
|
8613
8654
|
wasm: loadQuickJSWasm2(wasmPath),
|
|
8614
|
-
workerUrl
|
|
8655
|
+
workerUrl
|
|
8615
8656
|
});
|
|
8616
8657
|
}
|
|
8617
8658
|
async function resolveWasmPath(platform2) {
|
|
@@ -14049,11 +14090,20 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
14049
14090
|
return EXIT_AUTH;
|
|
14050
14091
|
}
|
|
14051
14092
|
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
14093
|
+
if (interrupted) {
|
|
14094
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
14095
|
+
return EXIT_INTERRUPTED;
|
|
14096
|
+
}
|
|
14052
14097
|
await writeErr(platform2, parsed.jsonOutput, "connection", msg);
|
|
14053
14098
|
return EXIT_CONNECTION;
|
|
14054
14099
|
}
|
|
14055
14100
|
try {
|
|
14056
|
-
|
|
14101
|
+
const code2 = await runVerb(platform2, parsed, conn, onProgress);
|
|
14102
|
+
if (interrupted) {
|
|
14103
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
14104
|
+
return EXIT_INTERRUPTED;
|
|
14105
|
+
}
|
|
14106
|
+
return code2;
|
|
14057
14107
|
} catch (err) {
|
|
14058
14108
|
if (interrupted) {
|
|
14059
14109
|
await platform2.write(2, "mcpcat: interrupted\n");
|
|
@@ -14277,5 +14327,5 @@ function createHostPlatform() {
|
|
|
14277
14327
|
// src/host.ts
|
|
14278
14328
|
var platform = createHostPlatform();
|
|
14279
14329
|
var args = process.argv.slice(2);
|
|
14280
|
-
var code = await run(platform, "0.
|
|
14330
|
+
var code = await run(platform, "0.12.0", args);
|
|
14281
14331
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -7386,6 +7386,14 @@ var McpAuthError = class extends Error {
|
|
|
7386
7386
|
this.target = target;
|
|
7387
7387
|
}
|
|
7388
7388
|
};
|
|
7389
|
+
var AuthRetryableError = class extends Error {
|
|
7390
|
+
response;
|
|
7391
|
+
constructor(response) {
|
|
7392
|
+
super(`HTTP ${response.status}: auth retry`);
|
|
7393
|
+
this.name = "AuthRetryableError";
|
|
7394
|
+
this.response = response;
|
|
7395
|
+
}
|
|
7396
|
+
};
|
|
7389
7397
|
function selectSseFrame(body, targetId) {
|
|
7390
7398
|
const text = new TextDecoder().decode(body);
|
|
7391
7399
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -7410,6 +7418,42 @@ function mcpNameForMethod(method, params) {
|
|
|
7410
7418
|
if (method === "resources/read") return params.uri;
|
|
7411
7419
|
return void 0;
|
|
7412
7420
|
}
|
|
7421
|
+
function makeAbort(timeoutMs, externalSignal) {
|
|
7422
|
+
const controller = new AbortController();
|
|
7423
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7424
|
+
if (externalSignal) {
|
|
7425
|
+
if (externalSignal.aborted) {
|
|
7426
|
+
clearTimeout(timer);
|
|
7427
|
+
controller.abort();
|
|
7428
|
+
} else {
|
|
7429
|
+
externalSignal.addEventListener(
|
|
7430
|
+
"abort",
|
|
7431
|
+
() => {
|
|
7432
|
+
clearTimeout(timer);
|
|
7433
|
+
controller.abort();
|
|
7434
|
+
},
|
|
7435
|
+
{ once: true }
|
|
7436
|
+
);
|
|
7437
|
+
}
|
|
7438
|
+
}
|
|
7439
|
+
return { controller, timer };
|
|
7440
|
+
}
|
|
7441
|
+
function logHeaders(verbose, dir, entries) {
|
|
7442
|
+
for (const [k, v] of entries) {
|
|
7443
|
+
if (dir === ">") verbose.headerOut(k, v);
|
|
7444
|
+
else verbose.headerIn(k, v);
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
function buildServerInfo(discoverResult2) {
|
|
7448
|
+
return {
|
|
7449
|
+
name: discoverResult2?.serverInfo?.name,
|
|
7450
|
+
version: discoverResult2?.serverInfo?.version,
|
|
7451
|
+
title: discoverResult2?.serverInfo?.title,
|
|
7452
|
+
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
7453
|
+
capabilities: discoverResult2?.capabilities ?? {},
|
|
7454
|
+
instructions: discoverResult2?.instructions
|
|
7455
|
+
};
|
|
7456
|
+
}
|
|
7413
7457
|
async function createStatelessConnection(options) {
|
|
7414
7458
|
const {
|
|
7415
7459
|
url,
|
|
@@ -7418,11 +7462,12 @@ async function createStatelessConnection(options) {
|
|
|
7418
7462
|
timeoutMs = 6e4,
|
|
7419
7463
|
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
7420
7464
|
signal: externalSignal,
|
|
7421
|
-
verbose
|
|
7465
|
+
verbose,
|
|
7466
|
+
authProvider
|
|
7422
7467
|
} = options;
|
|
7423
7468
|
let nextId2 = 1;
|
|
7424
7469
|
let negotiatedVersion;
|
|
7425
|
-
function buildHeaders(method, params) {
|
|
7470
|
+
function buildHeaders(method, params, token) {
|
|
7426
7471
|
const h = {
|
|
7427
7472
|
"Content-Type": "application/json",
|
|
7428
7473
|
Accept: "application/json, text/event-stream",
|
|
@@ -7432,36 +7477,12 @@ async function createStatelessConnection(options) {
|
|
|
7432
7477
|
const n = mcpNameForMethod(method, params);
|
|
7433
7478
|
if (n) h["Mcp-Name"] = n;
|
|
7434
7479
|
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
7480
|
+
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
7435
7481
|
return h;
|
|
7436
7482
|
}
|
|
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);
|
|
7462
|
-
}
|
|
7463
|
-
}
|
|
7464
7483
|
async function handleErrorStatus(res, method) {
|
|
7484
|
+
if ((res.status === 401 || res.status === 403) && authProvider)
|
|
7485
|
+
throw new AuthRetryableError(res);
|
|
7465
7486
|
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
7466
7487
|
const text = await res.text();
|
|
7467
7488
|
let rpc;
|
|
@@ -7473,34 +7494,27 @@ async function createStatelessConnection(options) {
|
|
|
7473
7494
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
7474
7495
|
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
7475
7496
|
}
|
|
7476
|
-
async function
|
|
7477
|
-
const
|
|
7478
|
-
const headers = buildHeaders(method, params);
|
|
7479
|
-
const meta = {
|
|
7480
|
-
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
7481
|
-
clientInfo
|
|
7482
|
-
};
|
|
7483
|
-
const body = JSON.stringify({
|
|
7484
|
-
jsonrpc: "2.0",
|
|
7485
|
-
id,
|
|
7486
|
-
method,
|
|
7487
|
-
params: { ...params, _meta: meta }
|
|
7488
|
-
});
|
|
7497
|
+
async function doFetch(method, params, body) {
|
|
7498
|
+
const token = await authProvider?.token();
|
|
7499
|
+
const headers = buildHeaders(method, params, token);
|
|
7489
7500
|
if (verbose?.enabled) {
|
|
7490
7501
|
verbose.request(method, body);
|
|
7491
|
-
logHeaders(">", Object.entries(headers));
|
|
7502
|
+
logHeaders(verbose, ">", Object.entries(headers));
|
|
7492
7503
|
}
|
|
7493
|
-
const { controller, timer } = makeAbort();
|
|
7494
|
-
let res;
|
|
7504
|
+
const { controller, timer } = makeAbort(timeoutMs, externalSignal);
|
|
7495
7505
|
try {
|
|
7496
|
-
res = await fetchImpl(url, {
|
|
7506
|
+
const res = await fetchImpl(url, {
|
|
7497
7507
|
method: "POST",
|
|
7498
7508
|
headers,
|
|
7499
7509
|
body,
|
|
7500
7510
|
signal: controller.signal
|
|
7501
7511
|
});
|
|
7512
|
+
if (verbose?.enabled) {
|
|
7513
|
+
verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
|
|
7514
|
+
logHeaders(verbose, "<", res.headers.entries());
|
|
7515
|
+
}
|
|
7516
|
+
return { res, token };
|
|
7502
7517
|
} catch (err) {
|
|
7503
|
-
clearTimeout(timer);
|
|
7504
7518
|
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
7505
7519
|
throw new McpTimeoutError2(method, timeoutMs);
|
|
7506
7520
|
}
|
|
@@ -7508,11 +7522,8 @@ async function createStatelessConnection(options) {
|
|
|
7508
7522
|
} finally {
|
|
7509
7523
|
clearTimeout(timer);
|
|
7510
7524
|
}
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
logHeaders("<", res.headers.entries());
|
|
7514
|
-
}
|
|
7515
|
-
if (res.status >= 400) await handleErrorStatus(res, method);
|
|
7525
|
+
}
|
|
7526
|
+
async function parseResponse(res, id) {
|
|
7516
7527
|
const ct = res.headers.get("content-type") ?? "";
|
|
7517
7528
|
let frame;
|
|
7518
7529
|
if (ct.toLowerCase().includes("text/event-stream")) {
|
|
@@ -7526,10 +7537,53 @@ async function createStatelessConnection(options) {
|
|
|
7526
7537
|
}
|
|
7527
7538
|
return frame.result;
|
|
7528
7539
|
}
|
|
7540
|
+
async function handleAuthError(err, token) {
|
|
7541
|
+
if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url);
|
|
7542
|
+
try {
|
|
7543
|
+
await authProvider.onUnauthorized({
|
|
7544
|
+
response: err.response,
|
|
7545
|
+
serverUrl: new URL(url),
|
|
7546
|
+
fetch: fetchImpl,
|
|
7547
|
+
token
|
|
7548
|
+
});
|
|
7549
|
+
} catch (authErr) {
|
|
7550
|
+
if (authErr instanceof AuthStepUpError) throw authErr;
|
|
7551
|
+
throw new McpAuthError(err.response.status, url);
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
async function request(method, params) {
|
|
7555
|
+
const id = nextId2++;
|
|
7556
|
+
const meta = {
|
|
7557
|
+
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
7558
|
+
clientInfo
|
|
7559
|
+
};
|
|
7560
|
+
const body = JSON.stringify({
|
|
7561
|
+
jsonrpc: "2.0",
|
|
7562
|
+
id,
|
|
7563
|
+
method,
|
|
7564
|
+
params: { ...params, _meta: meta }
|
|
7565
|
+
});
|
|
7566
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7567
|
+
const { res, token } = await doFetch(method, params, body);
|
|
7568
|
+
if (res.status >= 400) {
|
|
7569
|
+
try {
|
|
7570
|
+
await handleErrorStatus(res, method);
|
|
7571
|
+
} catch (err) {
|
|
7572
|
+
if (err instanceof AuthRetryableError && attempt === 0) {
|
|
7573
|
+
await handleAuthError(err, token);
|
|
7574
|
+
continue;
|
|
7575
|
+
}
|
|
7576
|
+
throw err;
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
return parseResponse(res, id);
|
|
7580
|
+
}
|
|
7581
|
+
throw new McpAuthError(401, url);
|
|
7582
|
+
}
|
|
7529
7583
|
const discoverResult2 = await request("server/discover", {
|
|
7530
7584
|
protocolVersion: STATELESS_PROTOCOL_VERSION
|
|
7531
7585
|
});
|
|
7532
|
-
const supported =
|
|
7586
|
+
const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
|
|
7533
7587
|
if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
|
|
7534
7588
|
throw new Error(
|
|
7535
7589
|
`Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
@@ -7540,15 +7594,7 @@ async function createStatelessConnection(options) {
|
|
|
7540
7594
|
return {
|
|
7541
7595
|
async info() {
|
|
7542
7596
|
if (serverInfoCache) return serverInfoCache;
|
|
7543
|
-
|
|
7544
|
-
serverInfoCache = {
|
|
7545
|
-
name: r?.serverInfo?.name,
|
|
7546
|
-
version: r?.serverInfo?.version,
|
|
7547
|
-
title: r?.serverInfo?.title,
|
|
7548
|
-
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
7549
|
-
capabilities: r?.capabilities ?? {},
|
|
7550
|
-
instructions: r?.instructions
|
|
7551
|
-
};
|
|
7597
|
+
serverInfoCache = buildServerInfo(discoverResult2);
|
|
7552
7598
|
return serverInfoCache;
|
|
7553
7599
|
},
|
|
7554
7600
|
async listTools() {
|
|
@@ -7698,23 +7744,12 @@ async function connect(platform2, options) {
|
|
|
7698
7744
|
});
|
|
7699
7745
|
}
|
|
7700
7746
|
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
|
-
}
|
|
7712
|
-
}
|
|
7713
7747
|
if (options.protocolVersion && !options.protocolVersion.startsWith("2026")) {
|
|
7714
7748
|
return createPiConnection({
|
|
7715
7749
|
url: options.url,
|
|
7716
7750
|
headers: options.headers,
|
|
7717
7751
|
fetchImpl: platform2.fetch,
|
|
7752
|
+
authProvider: options.authProvider,
|
|
7718
7753
|
...shared
|
|
7719
7754
|
});
|
|
7720
7755
|
}
|
|
@@ -7726,11 +7761,14 @@ async function connect(platform2, options) {
|
|
|
7726
7761
|
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
|
|
7727
7762
|
timeoutMs: options.timeoutMs,
|
|
7728
7763
|
signal: options.signal,
|
|
7729
|
-
verbose: options.verbose
|
|
7764
|
+
verbose: options.verbose,
|
|
7765
|
+
authProvider: options.authProvider
|
|
7730
7766
|
});
|
|
7731
7767
|
} catch (err) {
|
|
7732
7768
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
7733
7769
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
7770
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
7771
|
+
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
7734
7772
|
if (isMethodNotFound) {
|
|
7735
7773
|
return createPiConnection({
|
|
7736
7774
|
url: options.url,
|
|
@@ -8218,13 +8256,16 @@ async function createSandbox(platform2, tools, globals, timeoutMs) {
|
|
|
8218
8256
|
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
|
|
8219
8257
|
}
|
|
8220
8258
|
const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
8221
|
-
const
|
|
8259
|
+
const getSeaDir = globalThis.__mcpcat_get_sea_asset_dir;
|
|
8260
|
+
const seaDir = getSeaDir?.();
|
|
8261
|
+
const wasmPath = seaDir ? `${seaDir}/quickjs.wasm` : await resolveWasmPath(platform2);
|
|
8262
|
+
const workerUrl = seaDir ? new URL(`file://${seaDir}/codemode-worker.mjs`) : new URL("./codemode-worker.mjs", import.meta.url);
|
|
8222
8263
|
return new CodemodeSandbox2({
|
|
8223
8264
|
tools,
|
|
8224
8265
|
globals,
|
|
8225
8266
|
timeoutMs,
|
|
8226
8267
|
wasm: loadQuickJSWasm2(wasmPath),
|
|
8227
|
-
workerUrl
|
|
8268
|
+
workerUrl
|
|
8228
8269
|
});
|
|
8229
8270
|
}
|
|
8230
8271
|
async function resolveWasmPath(platform2) {
|
|
@@ -13662,11 +13703,20 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
13662
13703
|
return EXIT_AUTH;
|
|
13663
13704
|
}
|
|
13664
13705
|
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
13706
|
+
if (interrupted) {
|
|
13707
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
13708
|
+
return EXIT_INTERRUPTED;
|
|
13709
|
+
}
|
|
13665
13710
|
await writeErr(platform2, parsed.jsonOutput, "connection", msg);
|
|
13666
13711
|
return EXIT_CONNECTION;
|
|
13667
13712
|
}
|
|
13668
13713
|
try {
|
|
13669
|
-
|
|
13714
|
+
const code = await runVerb(platform2, parsed, conn, onProgress);
|
|
13715
|
+
if (interrupted) {
|
|
13716
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
13717
|
+
return EXIT_INTERRUPTED;
|
|
13718
|
+
}
|
|
13719
|
+
return code;
|
|
13670
13720
|
} catch (err) {
|
|
13671
13721
|
if (interrupted) {
|
|
13672
13722
|
await platform2.write(2, "mcpcat: interrupted\n");
|
|
@@ -13805,7 +13855,7 @@ async function main(ctx) {
|
|
|
13805
13855
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
13806
13856
|
});
|
|
13807
13857
|
const args = ctx.argv.slice(1);
|
|
13808
|
-
return run(platform2, "0.
|
|
13858
|
+
return run(platform2, "0.12.0", args);
|
|
13809
13859
|
}
|
|
13810
13860
|
export {
|
|
13811
13861
|
main as default
|