@ai-ecoverse/mcpcat 0.11.0 → 0.13.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/THIRD_PARTY_NOTICES +173 -0
- package/dist/host/mcpcat.mjs +1707 -227
- package/dist/kernel/mcpcat.mjs +1833 -218
- package/package.json +1 -1
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -48,9 +48,11 @@ __export(node_stub_exports, {
|
|
|
48
48
|
readFile: () => readFile,
|
|
49
49
|
rename: () => rename,
|
|
50
50
|
resolve: () => resolve,
|
|
51
|
+
rmdir: () => rmdir,
|
|
51
52
|
spawn: () => spawn,
|
|
52
53
|
stat: () => stat,
|
|
53
54
|
tmpdir: () => tmpdir,
|
|
55
|
+
unlink: () => unlink,
|
|
54
56
|
workerData: () => workerData,
|
|
55
57
|
writeFile: () => writeFile
|
|
56
58
|
});
|
|
@@ -78,6 +80,12 @@ function rename() {
|
|
|
78
80
|
function stat() {
|
|
79
81
|
throw new Error("not available in kernel");
|
|
80
82
|
}
|
|
83
|
+
function rmdir() {
|
|
84
|
+
throw new Error("not available in kernel");
|
|
85
|
+
}
|
|
86
|
+
function unlink() {
|
|
87
|
+
throw new Error("not available in kernel");
|
|
88
|
+
}
|
|
81
89
|
function isatty() {
|
|
82
90
|
return false;
|
|
83
91
|
}
|
|
@@ -5957,6 +5965,133 @@ var McpOAuthProvider = class {
|
|
|
5957
5965
|
}
|
|
5958
5966
|
};
|
|
5959
5967
|
|
|
5968
|
+
// src/auth/jerk-token-source.ts
|
|
5969
|
+
var MIN_VERSION = [0, 13, 0];
|
|
5970
|
+
var VERSION_TIMEOUT_MS = 2e3;
|
|
5971
|
+
var cachedAvailable;
|
|
5972
|
+
function parseVersion(raw) {
|
|
5973
|
+
const match = raw.trim().match(/(\d+)\.(\d+)\.(\d+)/);
|
|
5974
|
+
if (!match) return void 0;
|
|
5975
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
5976
|
+
}
|
|
5977
|
+
function versionSatisfied(actual) {
|
|
5978
|
+
for (let i = 0; i < 3; i++) {
|
|
5979
|
+
if (actual[i] > MIN_VERSION[i]) return true;
|
|
5980
|
+
if (actual[i] < MIN_VERSION[i]) return false;
|
|
5981
|
+
}
|
|
5982
|
+
return true;
|
|
5983
|
+
}
|
|
5984
|
+
async function readAll(stream) {
|
|
5985
|
+
const reader = stream.getReader();
|
|
5986
|
+
const chunks = [];
|
|
5987
|
+
for (; ; ) {
|
|
5988
|
+
const { done, value } = await reader.read();
|
|
5989
|
+
if (done) break;
|
|
5990
|
+
chunks.push(value);
|
|
5991
|
+
}
|
|
5992
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
5993
|
+
const buf = new Uint8Array(total);
|
|
5994
|
+
let off = 0;
|
|
5995
|
+
for (const c of chunks) {
|
|
5996
|
+
buf.set(c, off);
|
|
5997
|
+
off += c.length;
|
|
5998
|
+
}
|
|
5999
|
+
return new TextDecoder().decode(buf);
|
|
6000
|
+
}
|
|
6001
|
+
async function jerkOnPath(platform2) {
|
|
6002
|
+
const pathVar = platform2.env.PATH ?? platform2.env.Path ?? "";
|
|
6003
|
+
const sep = pathVar.includes(";") ? ";" : ":";
|
|
6004
|
+
const dirs = pathVar.split(sep).filter(Boolean);
|
|
6005
|
+
for (const dir of dirs) {
|
|
6006
|
+
if (await platform2.fs.exists(`${dir}/jerk`)) return true;
|
|
6007
|
+
}
|
|
6008
|
+
return false;
|
|
6009
|
+
}
|
|
6010
|
+
async function jerkAvailable(platform2) {
|
|
6011
|
+
if (cachedAvailable !== void 0) return cachedAvailable;
|
|
6012
|
+
if (!await jerkOnPath(platform2)) {
|
|
6013
|
+
cachedAvailable = false;
|
|
6014
|
+
return false;
|
|
6015
|
+
}
|
|
6016
|
+
try {
|
|
6017
|
+
const proc = await platform2.spawn({ argv: ["jerk", "--version"] });
|
|
6018
|
+
const timeout = new Promise((_, reject) => {
|
|
6019
|
+
setTimeout(() => reject(new Error("timeout")), VERSION_TIMEOUT_MS);
|
|
6020
|
+
});
|
|
6021
|
+
const work = Promise.all([readAll(proc.stdout), proc.wait()]).then(([out, result]) => {
|
|
6022
|
+
proc.stderr.cancel().catch(() => {
|
|
6023
|
+
});
|
|
6024
|
+
if (result.status !== 0) return false;
|
|
6025
|
+
const ver = parseVersion(out);
|
|
6026
|
+
return ver !== void 0 && versionSatisfied(ver);
|
|
6027
|
+
});
|
|
6028
|
+
cachedAvailable = await Promise.race([work, timeout]);
|
|
6029
|
+
} catch {
|
|
6030
|
+
cachedAvailable = false;
|
|
6031
|
+
}
|
|
6032
|
+
return cachedAvailable;
|
|
6033
|
+
}
|
|
6034
|
+
function jerkId(name) {
|
|
6035
|
+
if (name.startsWith("mcp:")) return name;
|
|
6036
|
+
if (name.startsWith("url:")) return `mcp:${name}`;
|
|
6037
|
+
return `mcp:${name}`;
|
|
6038
|
+
}
|
|
6039
|
+
var JERK_EXIT_SIGN_IN = 3;
|
|
6040
|
+
var JERK_EXIT_UNAVAILABLE = 4;
|
|
6041
|
+
var JerkSignInRequiredError = class extends Error {
|
|
6042
|
+
id;
|
|
6043
|
+
scope;
|
|
6044
|
+
constructor(id, scope) {
|
|
6045
|
+
const scopeHint = scope ? ` --scope ${scope}` : "";
|
|
6046
|
+
super(`sign-in required. Run: jerk ${id} create${scopeHint}`);
|
|
6047
|
+
this.name = "JerkSignInRequiredError";
|
|
6048
|
+
this.id = id;
|
|
6049
|
+
this.scope = scope;
|
|
6050
|
+
}
|
|
6051
|
+
};
|
|
6052
|
+
var JerkTokenSource = class {
|
|
6053
|
+
platform;
|
|
6054
|
+
constructor(platform2) {
|
|
6055
|
+
this.platform = platform2;
|
|
6056
|
+
}
|
|
6057
|
+
async getToken(name, serverUrl, scope) {
|
|
6058
|
+
const id = jerkId(name);
|
|
6059
|
+
const argv = ["jerk", id, "get", "--reveal", "--json"];
|
|
6060
|
+
if (scope) {
|
|
6061
|
+
argv.push("--scope", scope);
|
|
6062
|
+
}
|
|
6063
|
+
const proc = await this.platform.spawn({ argv });
|
|
6064
|
+
const [outRaw, , result] = await Promise.all([
|
|
6065
|
+
readAll(proc.stdout),
|
|
6066
|
+
readAll(proc.stderr),
|
|
6067
|
+
proc.wait()
|
|
6068
|
+
]);
|
|
6069
|
+
if (result.status === JERK_EXIT_SIGN_IN) {
|
|
6070
|
+
throw new JerkSignInRequiredError(id, scope);
|
|
6071
|
+
}
|
|
6072
|
+
if (result.status === JERK_EXIT_UNAVAILABLE) {
|
|
6073
|
+
return void 0;
|
|
6074
|
+
}
|
|
6075
|
+
if (result.status !== 0) {
|
|
6076
|
+
return void 0;
|
|
6077
|
+
}
|
|
6078
|
+
const parsed = JSON.parse(outRaw.trim());
|
|
6079
|
+
if (!parsed.token) return void 0;
|
|
6080
|
+
return {
|
|
6081
|
+
accessToken: parsed.token,
|
|
6082
|
+
expiresAt: parsed.expires_at
|
|
6083
|
+
};
|
|
6084
|
+
}
|
|
6085
|
+
async load(_name, _serverUrl) {
|
|
6086
|
+
return void 0;
|
|
6087
|
+
}
|
|
6088
|
+
async save(_name, _serverUrl, _state) {
|
|
6089
|
+
}
|
|
6090
|
+
async remove(_name, _serverUrl) {
|
|
6091
|
+
return false;
|
|
6092
|
+
}
|
|
6093
|
+
};
|
|
6094
|
+
|
|
5960
6095
|
// src/auth/auth-provider.ts
|
|
5961
6096
|
var CIMD_URL = "https://mcpcat.sliccy.ai/oauth/client.json";
|
|
5962
6097
|
var CLIENT_NAME = "mcpcat";
|
|
@@ -6009,6 +6144,47 @@ var AuthStepUpError = class extends Error {
|
|
|
6009
6144
|
}
|
|
6010
6145
|
};
|
|
6011
6146
|
function createMcpAuthProvider(options) {
|
|
6147
|
+
if (options.source instanceof JerkTokenSource) {
|
|
6148
|
+
return createJerkAuthProvider({ ...options, source: options.source });
|
|
6149
|
+
}
|
|
6150
|
+
return createPiAuthProvider(options);
|
|
6151
|
+
}
|
|
6152
|
+
function createJerkAuthProvider(options) {
|
|
6153
|
+
const { name, serverUrl, source } = options;
|
|
6154
|
+
let currentToken;
|
|
6155
|
+
return {
|
|
6156
|
+
async token() {
|
|
6157
|
+
const result = await source.getToken(name, serverUrl);
|
|
6158
|
+
currentToken = result?.accessToken;
|
|
6159
|
+
return currentToken;
|
|
6160
|
+
},
|
|
6161
|
+
async onUnauthorized(context) {
|
|
6162
|
+
const challenge = parseWwwAuthenticate(context.response.headers.get("www-authenticate"));
|
|
6163
|
+
if (challenge.error === "insufficient_scope") {
|
|
6164
|
+
try {
|
|
6165
|
+
const result2 = await source.getToken(name, serverUrl, challenge.scope);
|
|
6166
|
+
if (result2) {
|
|
6167
|
+
currentToken = result2.accessToken;
|
|
6168
|
+
return;
|
|
6169
|
+
}
|
|
6170
|
+
} catch (err) {
|
|
6171
|
+
if (err instanceof JerkSignInRequiredError) {
|
|
6172
|
+
throw new AuthStepUpError(challenge.scope, options.name);
|
|
6173
|
+
}
|
|
6174
|
+
throw err;
|
|
6175
|
+
}
|
|
6176
|
+
throw new AuthStepUpError(challenge.scope, options.name);
|
|
6177
|
+
}
|
|
6178
|
+
const result = await source.getToken(name, serverUrl);
|
|
6179
|
+
if (result) {
|
|
6180
|
+
currentToken = result.accessToken;
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
throw new McpOAuthAuthorizationRequiredError();
|
|
6184
|
+
}
|
|
6185
|
+
};
|
|
6186
|
+
}
|
|
6187
|
+
function createPiAuthProvider(options) {
|
|
6012
6188
|
const { name, serverUrl, source, oauth, platform: platform2 } = options;
|
|
6013
6189
|
const store = new SourceBackedStore(source, name, serverUrl);
|
|
6014
6190
|
let refreshing;
|
|
@@ -7454,6 +7630,53 @@ function buildServerInfo(discoverResult2) {
|
|
|
7454
7630
|
instructions: discoverResult2?.instructions
|
|
7455
7631
|
};
|
|
7456
7632
|
}
|
|
7633
|
+
function buildStatelessMethods(requestFn, discoverResult2) {
|
|
7634
|
+
let serverInfoCache;
|
|
7635
|
+
return {
|
|
7636
|
+
async info() {
|
|
7637
|
+
if (serverInfoCache) return serverInfoCache;
|
|
7638
|
+
serverInfoCache = buildServerInfo(discoverResult2);
|
|
7639
|
+
return serverInfoCache;
|
|
7640
|
+
},
|
|
7641
|
+
async listTools() {
|
|
7642
|
+
return (await requestFn("tools/list", {}))?.tools ?? [];
|
|
7643
|
+
},
|
|
7644
|
+
async callTool(name, args, _callOptions) {
|
|
7645
|
+
const params = {
|
|
7646
|
+
name,
|
|
7647
|
+
arguments: args ?? {}
|
|
7648
|
+
};
|
|
7649
|
+
if (_callOptions?.inputResponses) {
|
|
7650
|
+
params._inputResponses = _callOptions.inputResponses;
|
|
7651
|
+
}
|
|
7652
|
+
const result = await requestFn("tools/call", params);
|
|
7653
|
+
if ((result.resultType ?? "complete") === "input_required") {
|
|
7654
|
+
throw new InputRequiredError(
|
|
7655
|
+
JSON.stringify({ name, arguments: args ?? {}, result }, null, 2)
|
|
7656
|
+
);
|
|
7657
|
+
}
|
|
7658
|
+
return result;
|
|
7659
|
+
},
|
|
7660
|
+
async listResources() {
|
|
7661
|
+
return (await requestFn("resources/list", {}))?.resources ?? [];
|
|
7662
|
+
},
|
|
7663
|
+
async listResourceTemplates() {
|
|
7664
|
+
return (await requestFn("resources/templates/list", {}))?.resourceTemplates ?? [];
|
|
7665
|
+
},
|
|
7666
|
+
async readResource(uri) {
|
|
7667
|
+
return await requestFn("resources/read", { uri });
|
|
7668
|
+
},
|
|
7669
|
+
async listPrompts() {
|
|
7670
|
+
return (await requestFn("prompts/list", {}))?.prompts ?? [];
|
|
7671
|
+
},
|
|
7672
|
+
async getPrompt(name, args) {
|
|
7673
|
+
return await requestFn("prompts/get", {
|
|
7674
|
+
name,
|
|
7675
|
+
arguments: args ?? {}
|
|
7676
|
+
});
|
|
7677
|
+
}
|
|
7678
|
+
};
|
|
7679
|
+
}
|
|
7457
7680
|
async function createStatelessConnection(options) {
|
|
7458
7681
|
const {
|
|
7459
7682
|
url,
|
|
@@ -7557,11 +7780,16 @@ async function createStatelessConnection(options) {
|
|
|
7557
7780
|
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
7558
7781
|
clientInfo
|
|
7559
7782
|
};
|
|
7783
|
+
const cleanParams = { ...params };
|
|
7784
|
+
if (cleanParams._inputResponses) {
|
|
7785
|
+
meta.inputResponses = cleanParams._inputResponses;
|
|
7786
|
+
delete cleanParams._inputResponses;
|
|
7787
|
+
}
|
|
7560
7788
|
const body = JSON.stringify({
|
|
7561
7789
|
jsonrpc: "2.0",
|
|
7562
7790
|
id,
|
|
7563
7791
|
method,
|
|
7564
|
-
params: { ...
|
|
7792
|
+
params: { ...cleanParams, _meta: meta }
|
|
7565
7793
|
});
|
|
7566
7794
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7567
7795
|
const { res, token } = await doFetch(method, params, body);
|
|
@@ -7590,28 +7818,9 @@ async function createStatelessConnection(options) {
|
|
|
7590
7818
|
);
|
|
7591
7819
|
}
|
|
7592
7820
|
negotiatedVersion = STATELESS_PROTOCOL_VERSION;
|
|
7593
|
-
|
|
7821
|
+
const extra = buildStatelessMethods(request, discoverResult2);
|
|
7594
7822
|
return {
|
|
7595
|
-
|
|
7596
|
-
if (serverInfoCache) return serverInfoCache;
|
|
7597
|
-
serverInfoCache = buildServerInfo(discoverResult2);
|
|
7598
|
-
return serverInfoCache;
|
|
7599
|
-
},
|
|
7600
|
-
async listTools() {
|
|
7601
|
-
return (await request("tools/list", {}))?.tools ?? [];
|
|
7602
|
-
},
|
|
7603
|
-
async callTool(name, args, _callOptions) {
|
|
7604
|
-
const result = await request("tools/call", {
|
|
7605
|
-
name,
|
|
7606
|
-
arguments: args ?? {}
|
|
7607
|
-
});
|
|
7608
|
-
if ((result.resultType ?? "complete") === "input_required") {
|
|
7609
|
-
throw new InputRequiredError(
|
|
7610
|
-
JSON.stringify({ name, arguments: args ?? {}, result }, null, 2)
|
|
7611
|
-
);
|
|
7612
|
-
}
|
|
7613
|
-
return result;
|
|
7614
|
-
},
|
|
7823
|
+
...extra,
|
|
7615
7824
|
async close() {
|
|
7616
7825
|
}
|
|
7617
7826
|
};
|
|
@@ -7661,7 +7870,9 @@ async function createPiConnection(options) {
|
|
|
7661
7870
|
const clientOptions = {
|
|
7662
7871
|
name: "mcpcat",
|
|
7663
7872
|
version: options.version ?? "0.0.0",
|
|
7664
|
-
requestTimeoutMs: options.timeoutMs
|
|
7873
|
+
requestTimeoutMs: options.timeoutMs,
|
|
7874
|
+
capabilities: { elicitation: {} },
|
|
7875
|
+
roots: options.roots
|
|
7665
7876
|
};
|
|
7666
7877
|
const client = new McpClient(clientOptions);
|
|
7667
7878
|
const transport = buildTransport(options);
|
|
@@ -7707,7 +7918,32 @@ async function createPiConnection(options) {
|
|
|
7707
7918
|
inner?.(p);
|
|
7708
7919
|
};
|
|
7709
7920
|
}
|
|
7710
|
-
|
|
7921
|
+
const callArgs = callOptions?.inputResponses ? { ...args, _meta: { inputResponses: callOptions.inputResponses } } : args;
|
|
7922
|
+
return client.callTool(name, callArgs, o);
|
|
7923
|
+
},
|
|
7924
|
+
async listResources() {
|
|
7925
|
+
return client.listResources(reqOpts2());
|
|
7926
|
+
},
|
|
7927
|
+
async listResourceTemplates() {
|
|
7928
|
+
return client.listResourceTemplates(reqOpts2());
|
|
7929
|
+
},
|
|
7930
|
+
async readResource(uri) {
|
|
7931
|
+
return client.readResource(uri, reqOpts2());
|
|
7932
|
+
},
|
|
7933
|
+
async listPrompts() {
|
|
7934
|
+
const result = await client.request(
|
|
7935
|
+
"prompts/list",
|
|
7936
|
+
{},
|
|
7937
|
+
reqOpts2()
|
|
7938
|
+
);
|
|
7939
|
+
return result?.prompts ?? [];
|
|
7940
|
+
},
|
|
7941
|
+
async getPrompt(name, args) {
|
|
7942
|
+
return client.request(
|
|
7943
|
+
"prompts/get",
|
|
7944
|
+
{ name, arguments: args ?? {} },
|
|
7945
|
+
reqOpts2()
|
|
7946
|
+
);
|
|
7711
7947
|
},
|
|
7712
7948
|
async close() {
|
|
7713
7949
|
await client.close();
|
|
@@ -7725,6 +7961,192 @@ async function createPiConnection(options) {
|
|
|
7725
7961
|
}
|
|
7726
7962
|
}
|
|
7727
7963
|
|
|
7964
|
+
// src/client/stdio-stateless.ts
|
|
7965
|
+
var PROTOCOL_VERSION = "2026-07-28";
|
|
7966
|
+
function createStdioJsonRpc(proc, platform2, options) {
|
|
7967
|
+
const encoder2 = new TextEncoder();
|
|
7968
|
+
const decoder3 = new TextDecoder();
|
|
7969
|
+
let nextId2 = 1;
|
|
7970
|
+
let readBuffer = "";
|
|
7971
|
+
const reader = proc.stdout.getReader();
|
|
7972
|
+
let negotiatedVersion;
|
|
7973
|
+
const { version = "0.0.0", timeoutMs = 6e4, signal: externalSignal, verbose } = options;
|
|
7974
|
+
async function readLine2() {
|
|
7975
|
+
while (true) {
|
|
7976
|
+
const nlIdx = readBuffer.indexOf("\n");
|
|
7977
|
+
if (nlIdx >= 0) {
|
|
7978
|
+
const line = readBuffer.slice(0, nlIdx).trim();
|
|
7979
|
+
readBuffer = readBuffer.slice(nlIdx + 1);
|
|
7980
|
+
if (line) return line;
|
|
7981
|
+
continue;
|
|
7982
|
+
}
|
|
7983
|
+
const { done, value } = await reader.read();
|
|
7984
|
+
if (done) throw new Error("stdio server closed unexpectedly");
|
|
7985
|
+
readBuffer += decoder3.decode(value, { stream: true });
|
|
7986
|
+
}
|
|
7987
|
+
}
|
|
7988
|
+
async function send(msg) {
|
|
7989
|
+
const writer = proc.stdin.getWriter();
|
|
7990
|
+
try {
|
|
7991
|
+
await writer.write(encoder2.encode(`${msg}
|
|
7992
|
+
`));
|
|
7993
|
+
} finally {
|
|
7994
|
+
writer.releaseLock();
|
|
7995
|
+
}
|
|
7996
|
+
}
|
|
7997
|
+
async function request(method, params) {
|
|
7998
|
+
const id = nextId2++;
|
|
7999
|
+
const meta = {
|
|
8000
|
+
protocolVersion: negotiatedVersion ?? PROTOCOL_VERSION,
|
|
8001
|
+
clientInfo: { name: "mcpcat", version }
|
|
8002
|
+
};
|
|
8003
|
+
const cleanParams = { ...params };
|
|
8004
|
+
if (cleanParams._inputResponses) {
|
|
8005
|
+
meta.inputResponses = cleanParams._inputResponses;
|
|
8006
|
+
delete cleanParams._inputResponses;
|
|
8007
|
+
}
|
|
8008
|
+
const body = JSON.stringify({
|
|
8009
|
+
jsonrpc: "2.0",
|
|
8010
|
+
id,
|
|
8011
|
+
method,
|
|
8012
|
+
params: { ...cleanParams, _meta: meta }
|
|
8013
|
+
});
|
|
8014
|
+
if (verbose?.enabled) verbose.request(method, body);
|
|
8015
|
+
await send(body);
|
|
8016
|
+
const deadline = Date.now() + timeoutMs;
|
|
8017
|
+
while (Date.now() < deadline) {
|
|
8018
|
+
const remaining = deadline - Date.now();
|
|
8019
|
+
if (externalSignal?.aborted) throw new McpTimeoutError2(method, timeoutMs);
|
|
8020
|
+
const linePromise = readLine2();
|
|
8021
|
+
const timeoutPromise = new Promise(
|
|
8022
|
+
(_, reject) => setTimeout(() => reject(new McpTimeoutError2(method, timeoutMs)), remaining)
|
|
8023
|
+
);
|
|
8024
|
+
const line = await Promise.race([linePromise, timeoutPromise]);
|
|
8025
|
+
const parsed = JSON.parse(line);
|
|
8026
|
+
if (verbose?.enabled) verbose.response(line);
|
|
8027
|
+
if (parsed.id === id) {
|
|
8028
|
+
if (parsed.error) {
|
|
8029
|
+
throw new StatelessRpcError(parsed.error.code, parsed.error.message, parsed.error.data);
|
|
8030
|
+
}
|
|
8031
|
+
return parsed.result;
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
throw new McpTimeoutError2(method, timeoutMs);
|
|
8035
|
+
}
|
|
8036
|
+
return {
|
|
8037
|
+
request,
|
|
8038
|
+
close() {
|
|
8039
|
+
try {
|
|
8040
|
+
reader.releaseLock();
|
|
8041
|
+
} catch {
|
|
8042
|
+
}
|
|
8043
|
+
}
|
|
8044
|
+
};
|
|
8045
|
+
}
|
|
8046
|
+
function buildServerInfo2(discoverResult2) {
|
|
8047
|
+
return {
|
|
8048
|
+
name: discoverResult2?.serverInfo?.name,
|
|
8049
|
+
version: discoverResult2?.serverInfo?.version,
|
|
8050
|
+
title: discoverResult2?.serverInfo?.title,
|
|
8051
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
8052
|
+
capabilities: discoverResult2?.capabilities ?? {},
|
|
8053
|
+
instructions: discoverResult2?.instructions
|
|
8054
|
+
};
|
|
8055
|
+
}
|
|
8056
|
+
function buildConnectionMethods(rpc, discoverResult2, proc) {
|
|
8057
|
+
return {
|
|
8058
|
+
async info() {
|
|
8059
|
+
return buildServerInfo2(discoverResult2);
|
|
8060
|
+
},
|
|
8061
|
+
async listTools() {
|
|
8062
|
+
return (await rpc.request("tools/list", {}))?.tools ?? [];
|
|
8063
|
+
},
|
|
8064
|
+
async callTool(name, args, callOptions) {
|
|
8065
|
+
const params = { name, arguments: args ?? {} };
|
|
8066
|
+
if (callOptions?.inputResponses) {
|
|
8067
|
+
params._inputResponses = callOptions.inputResponses;
|
|
8068
|
+
}
|
|
8069
|
+
const result = await rpc.request("tools/call", params);
|
|
8070
|
+
if ((result.resultType ?? "complete") === "input_required") {
|
|
8071
|
+
throw new InputRequiredError(
|
|
8072
|
+
JSON.stringify({ name, arguments: args ?? {}, result }, null, 2)
|
|
8073
|
+
);
|
|
8074
|
+
}
|
|
8075
|
+
return result;
|
|
8076
|
+
},
|
|
8077
|
+
async listResources() {
|
|
8078
|
+
return (await rpc.request("resources/list", {}))?.resources ?? [];
|
|
8079
|
+
},
|
|
8080
|
+
async listResourceTemplates() {
|
|
8081
|
+
return (await rpc.request("resources/templates/list", {}))?.resourceTemplates ?? [];
|
|
8082
|
+
},
|
|
8083
|
+
async readResource(uri) {
|
|
8084
|
+
return await rpc.request("resources/read", { uri });
|
|
8085
|
+
},
|
|
8086
|
+
async listPrompts() {
|
|
8087
|
+
return (await rpc.request("prompts/list", {}))?.prompts ?? [];
|
|
8088
|
+
},
|
|
8089
|
+
async getPrompt(name, args) {
|
|
8090
|
+
return await rpc.request("prompts/get", { name, arguments: args ?? {} });
|
|
8091
|
+
},
|
|
8092
|
+
async close() {
|
|
8093
|
+
rpc.close();
|
|
8094
|
+
try {
|
|
8095
|
+
proc.kill("SIGTERM");
|
|
8096
|
+
} catch {
|
|
8097
|
+
}
|
|
8098
|
+
}
|
|
8099
|
+
};
|
|
8100
|
+
}
|
|
8101
|
+
async function createStdioStatelessConnection(options) {
|
|
8102
|
+
const inheritedEnv = {
|
|
8103
|
+
...options.platform.env,
|
|
8104
|
+
...options.env
|
|
8105
|
+
};
|
|
8106
|
+
const proc = await options.platform.spawn({
|
|
8107
|
+
argv: [options.command, ...options.args ?? []],
|
|
8108
|
+
env: inheritedEnv,
|
|
8109
|
+
cwd: options.cwd
|
|
8110
|
+
});
|
|
8111
|
+
void readStderr(proc, options.verbose);
|
|
8112
|
+
const rpc = createStdioJsonRpc(proc, options.platform, {
|
|
8113
|
+
version: options.version,
|
|
8114
|
+
timeoutMs: options.timeoutMs,
|
|
8115
|
+
signal: options.signal,
|
|
8116
|
+
verbose: options.verbose
|
|
8117
|
+
});
|
|
8118
|
+
const discoverRaw = await rpc.request("server/discover", {
|
|
8119
|
+
protocolVersion: PROTOCOL_VERSION
|
|
8120
|
+
});
|
|
8121
|
+
const discoverResult2 = discoverRaw;
|
|
8122
|
+
const supported = (discoverResult2?.supportedVersions ?? []).filter(
|
|
8123
|
+
(v) => typeof v === "string"
|
|
8124
|
+
);
|
|
8125
|
+
if (!supported.includes(PROTOCOL_VERSION)) {
|
|
8126
|
+
proc.kill("SIGTERM");
|
|
8127
|
+
throw new Error(
|
|
8128
|
+
`Server does not support ${PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
8129
|
+
);
|
|
8130
|
+
}
|
|
8131
|
+
return buildConnectionMethods(rpc, discoverResult2, proc);
|
|
8132
|
+
}
|
|
8133
|
+
async function readStderr(proc, verbose) {
|
|
8134
|
+
if (!verbose?.enabled) return;
|
|
8135
|
+
const reader = proc.stderr.getReader();
|
|
8136
|
+
const decoder3 = new TextDecoder();
|
|
8137
|
+
try {
|
|
8138
|
+
while (true) {
|
|
8139
|
+
const { done, value } = await reader.read();
|
|
8140
|
+
if (done) break;
|
|
8141
|
+
const chunk = decoder3.decode(value, { stream: true });
|
|
8142
|
+
for (const line of chunk.split("\n")) {
|
|
8143
|
+
if (line.trim()) verbose.server(line);
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
} catch {
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
|
|
7728
8150
|
// src/client/connect.ts
|
|
7729
8151
|
async function connect(platform2, options) {
|
|
7730
8152
|
const shared = {
|
|
@@ -7735,11 +8157,32 @@ async function connect(platform2, options) {
|
|
|
7735
8157
|
platform: platform2
|
|
7736
8158
|
};
|
|
7737
8159
|
if (options.command) {
|
|
8160
|
+
if (!options.protocolVersion || options.protocolVersion.startsWith("2026")) {
|
|
8161
|
+
try {
|
|
8162
|
+
return await createStdioStatelessConnection({
|
|
8163
|
+
command: options.command,
|
|
8164
|
+
args: options.args,
|
|
8165
|
+
env: options.env,
|
|
8166
|
+
cwd: options.cwd,
|
|
8167
|
+
platform: platform2,
|
|
8168
|
+
version: options.version,
|
|
8169
|
+
timeoutMs: options.timeoutMs,
|
|
8170
|
+
signal: options.signal,
|
|
8171
|
+
verbose: options.verbose
|
|
8172
|
+
});
|
|
8173
|
+
} catch (err) {
|
|
8174
|
+
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601;
|
|
8175
|
+
const isUnsupported = err instanceof Error && err.message.includes("does not support");
|
|
8176
|
+
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
8177
|
+
if (!isMethodNotFound && !isUnsupported) throw err;
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
7738
8180
|
return createPiConnection({
|
|
7739
8181
|
command: options.command,
|
|
7740
8182
|
args: options.args,
|
|
7741
8183
|
env: options.env,
|
|
7742
8184
|
cwd: options.cwd,
|
|
8185
|
+
roots: options.roots,
|
|
7743
8186
|
...shared
|
|
7744
8187
|
});
|
|
7745
8188
|
}
|
|
@@ -7782,27 +8225,123 @@ async function connect(platform2, options) {
|
|
|
7782
8225
|
}
|
|
7783
8226
|
}
|
|
7784
8227
|
|
|
7785
|
-
// src/client/
|
|
7786
|
-
function
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
8228
|
+
// src/client/elicit.ts
|
|
8229
|
+
function readLine(platform2) {
|
|
8230
|
+
return new Promise((resolve2, reject) => {
|
|
8231
|
+
const reader = platform2.stdin.getReader();
|
|
8232
|
+
const chunks = [];
|
|
8233
|
+
function pump() {
|
|
8234
|
+
reader.read().then(({ done, value }) => {
|
|
8235
|
+
if (done) {
|
|
8236
|
+
reader.releaseLock();
|
|
8237
|
+
resolve2(new TextDecoder().decode(concatChunks(chunks)).split("\n")[0] ?? "");
|
|
8238
|
+
return;
|
|
8239
|
+
}
|
|
8240
|
+
chunks.push(value);
|
|
8241
|
+
const text = new TextDecoder().decode(concatChunks(chunks));
|
|
8242
|
+
if (text.includes("\n")) {
|
|
8243
|
+
reader.releaseLock();
|
|
8244
|
+
resolve2(text.split("\n")[0]);
|
|
8245
|
+
return;
|
|
8246
|
+
}
|
|
8247
|
+
pump();
|
|
8248
|
+
}, reject);
|
|
8249
|
+
}
|
|
8250
|
+
pump();
|
|
8251
|
+
});
|
|
8252
|
+
}
|
|
8253
|
+
function concatChunks(chunks) {
|
|
8254
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
8255
|
+
const merged = new Uint8Array(total);
|
|
8256
|
+
let offset = 0;
|
|
8257
|
+
for (const c of chunks) {
|
|
8258
|
+
merged.set(c, offset);
|
|
8259
|
+
offset += c.length;
|
|
8260
|
+
}
|
|
8261
|
+
return merged;
|
|
8262
|
+
}
|
|
8263
|
+
function coerceValue(raw, type) {
|
|
8264
|
+
if (!type || type === "string") return raw;
|
|
8265
|
+
if (type === "integer") return Number.parseInt(raw, 10);
|
|
8266
|
+
if (type === "number") return Number.parseFloat(raw);
|
|
8267
|
+
if (type === "boolean") return raw === "true" || raw === "1" || raw === "yes";
|
|
8268
|
+
return raw;
|
|
8269
|
+
}
|
|
8270
|
+
async function promptForSchema(platform2, schema) {
|
|
8271
|
+
const answers = {};
|
|
8272
|
+
const properties = schema.properties ?? {};
|
|
8273
|
+
const required = new Set(schema.required ?? []);
|
|
8274
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
8275
|
+
const req = required.has(key) ? " (required)" : "";
|
|
8276
|
+
const desc = prop.description ? ` - ${prop.description}` : "";
|
|
8277
|
+
const def = prop.default !== void 0 ? ` [${String(prop.default)}]` : "";
|
|
8278
|
+
const choices = prop.enum ? ` (${prop.enum.join("|")})` : "";
|
|
8279
|
+
await platform2.write(2, `${key}${req}${desc}${choices}${def}: `);
|
|
8280
|
+
const line = await readLine(platform2);
|
|
8281
|
+
const value = line.trim();
|
|
8282
|
+
if (value === "" && prop.default !== void 0) {
|
|
8283
|
+
answers[key] = prop.default;
|
|
8284
|
+
} else if (value !== "") {
|
|
8285
|
+
answers[key] = coerceValue(value, prop.type);
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
8288
|
+
return answers;
|
|
8289
|
+
}
|
|
8290
|
+
function parseInputRequest(result) {
|
|
8291
|
+
const ir = result.inputRequest;
|
|
8292
|
+
if (!ir && !result.inputRequests) return void 0;
|
|
8293
|
+
if (ir) return ir;
|
|
8294
|
+
const reqs = result.inputRequests;
|
|
8295
|
+
if (Array.isArray(reqs) && reqs.length > 0) return reqs[0];
|
|
8296
|
+
return void 0;
|
|
8297
|
+
}
|
|
8298
|
+
async function handleElicitation(platform2, inputRequest, isTty, respondData) {
|
|
8299
|
+
if (inputRequest.url) {
|
|
8300
|
+
if (isTty) {
|
|
8301
|
+
await platform2.write(2, `Open this URL to continue: ${inputRequest.url}
|
|
8302
|
+
`);
|
|
8303
|
+
await platform2.write(2, "Press Enter when done...");
|
|
8304
|
+
await readLine(platform2);
|
|
8305
|
+
return { answers: {} };
|
|
8306
|
+
}
|
|
8307
|
+
return { exitCode: 6 };
|
|
8308
|
+
}
|
|
8309
|
+
if (respondData) {
|
|
8310
|
+
return { answers: respondData };
|
|
8311
|
+
}
|
|
8312
|
+
if (!isTty) {
|
|
8313
|
+
return { exitCode: 6 };
|
|
8314
|
+
}
|
|
8315
|
+
const schema = inputRequest.schema ?? { type: "object", properties: {} };
|
|
8316
|
+
if (inputRequest.message) {
|
|
8317
|
+
await platform2.write(2, `${inputRequest.message}
|
|
8318
|
+
`);
|
|
8319
|
+
}
|
|
8320
|
+
const answers = await promptForSchema(platform2, schema);
|
|
8321
|
+
return { answers };
|
|
8322
|
+
}
|
|
8323
|
+
|
|
8324
|
+
// src/client/targets.ts
|
|
8325
|
+
function splitShellStyle(input) {
|
|
8326
|
+
const result = [];
|
|
8327
|
+
let current = "";
|
|
8328
|
+
let inSingle = false;
|
|
8329
|
+
let inDouble = false;
|
|
8330
|
+
for (let i = 0; i < input.length; i++) {
|
|
8331
|
+
const c = input[i];
|
|
8332
|
+
if (inSingle) {
|
|
8333
|
+
if (c === "'") {
|
|
8334
|
+
inSingle = false;
|
|
8335
|
+
} else {
|
|
8336
|
+
current += c;
|
|
8337
|
+
}
|
|
8338
|
+
} else if (inDouble) {
|
|
8339
|
+
if (c === '"') {
|
|
8340
|
+
inDouble = false;
|
|
8341
|
+
} else if (c === "\\" && i + 1 < input.length) {
|
|
8342
|
+
current += input[++i];
|
|
8343
|
+
} else {
|
|
8344
|
+
current += c;
|
|
7806
8345
|
}
|
|
7807
8346
|
} else if (c === "'") {
|
|
7808
8347
|
inSingle = true;
|
|
@@ -8191,10 +8730,19 @@ function buildShellGlobal(platform2) {
|
|
|
8191
8730
|
}
|
|
8192
8731
|
};
|
|
8193
8732
|
}
|
|
8733
|
+
var DEFAULT_MEMORY_LIMIT_MIB = 256;
|
|
8734
|
+
var MAX_MEMORY_LIMIT_MIB = 1024;
|
|
8194
8735
|
async function runCode(targets, source, options) {
|
|
8195
8736
|
const { platform: platform2, json } = options;
|
|
8196
8737
|
const { code, options: sourceOptions } = parseCodemodeSource(source);
|
|
8197
|
-
const
|
|
8738
|
+
const callerTimeoutMs = options.timeoutMs ?? 3e5;
|
|
8739
|
+
const scriptTimeoutMs = sourceOptions.timeoutMs;
|
|
8740
|
+
const timeoutMs = scriptTimeoutMs !== void 0 ? Math.min(callerTimeoutMs, scriptTimeoutMs) : callerTimeoutMs;
|
|
8741
|
+
const memoryLimitMiB = Math.min(
|
|
8742
|
+
options.memoryLimitMiB ?? DEFAULT_MEMORY_LIMIT_MIB,
|
|
8743
|
+
MAX_MEMORY_LIMIT_MIB
|
|
8744
|
+
);
|
|
8745
|
+
const memoryLimitBytes = memoryLimitMiB * 1024 * 1024;
|
|
8198
8746
|
const { connections, allDeclaredTools, namespaces } = await collectTools(targets);
|
|
8199
8747
|
const searchDocs = buildSearchDocs(allDeclaredTools, namespaces);
|
|
8200
8748
|
const stateDir2 = options.stateDir ?? `${platform2.env.XDG_STATE_HOME ?? `${platform2.env.HOME ?? "."}/.local/state`}/mcpcat`;
|
|
@@ -8204,7 +8752,7 @@ async function runCode(targets, source, options) {
|
|
|
8204
8752
|
...buildDiscoveryGlobals(allDeclaredTools, namespaces, searchDocs),
|
|
8205
8753
|
...options.allowShell ? [buildShellGlobal(platform2)] : []
|
|
8206
8754
|
];
|
|
8207
|
-
const sandbox = await createSandbox(platform2, toolImpls, globals, timeoutMs);
|
|
8755
|
+
const sandbox = await createSandbox(platform2, toolImpls, globals, timeoutMs, memoryLimitBytes);
|
|
8208
8756
|
try {
|
|
8209
8757
|
const result = await sandbox.execute(code, { store });
|
|
8210
8758
|
if (result.ok) {
|
|
@@ -8248,21 +8796,25 @@ async function formatOutput(platform2, result, json) {
|
|
|
8248
8796
|
const isTimeout = !result.ok && result.error && result.error.kind === "timeout";
|
|
8249
8797
|
return result.ok ? 0 : isTimeout ? 124 : 1;
|
|
8250
8798
|
}
|
|
8251
|
-
async function createSandbox(platform2, tools, globals, timeoutMs) {
|
|
8799
|
+
async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBytes) {
|
|
8252
8800
|
const isKernel2 = platform2.packageDir !== void 0;
|
|
8253
8801
|
if (isKernel2) {
|
|
8254
8802
|
const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
|
|
8255
8803
|
const wasm = await loadWasmKernel(platform2);
|
|
8256
|
-
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
|
|
8804
|
+
return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm, memoryLimitBytes });
|
|
8257
8805
|
}
|
|
8258
8806
|
const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
8259
|
-
const
|
|
8807
|
+
const getSeaDir = globalThis.__mcpcat_get_sea_asset_dir;
|
|
8808
|
+
const seaDir = getSeaDir?.();
|
|
8809
|
+
const wasmPath = seaDir ? `${seaDir}/quickjs.wasm` : await resolveWasmPath(platform2);
|
|
8810
|
+
const workerUrl = seaDir ? new URL(`file://${seaDir}/codemode-worker.mjs`) : new URL("./codemode-worker.mjs", import.meta.url);
|
|
8260
8811
|
return new CodemodeSandbox2({
|
|
8261
8812
|
tools,
|
|
8262
8813
|
globals,
|
|
8263
8814
|
timeoutMs,
|
|
8815
|
+
memoryLimitBytes,
|
|
8264
8816
|
wasm: loadQuickJSWasm2(wasmPath),
|
|
8265
|
-
workerUrl
|
|
8817
|
+
workerUrl
|
|
8266
8818
|
});
|
|
8267
8819
|
}
|
|
8268
8820
|
async function resolveWasmPath(platform2) {
|
|
@@ -8336,6 +8888,14 @@ function containsPlaceholders(value) {
|
|
|
8336
8888
|
function expandPlaceholders(value, env2) {
|
|
8337
8889
|
return value.replace(PLACEHOLDER, (_, name) => env2[name] ?? "");
|
|
8338
8890
|
}
|
|
8891
|
+
function missingPlaceholders(value, env2) {
|
|
8892
|
+
const missing = [];
|
|
8893
|
+
for (const m of value.matchAll(PLACEHOLDER)) {
|
|
8894
|
+
const name = m[1];
|
|
8895
|
+
if (env2[name] === void 0 && !missing.includes(name)) missing.push(name);
|
|
8896
|
+
}
|
|
8897
|
+
return missing;
|
|
8898
|
+
}
|
|
8339
8899
|
function expandPlaceholdersStrict(value, env2) {
|
|
8340
8900
|
let hasUndefined = false;
|
|
8341
8901
|
const result = value.replace(PLACEHOLDER, (_, name) => {
|
|
@@ -8348,6 +8908,20 @@ function expandPlaceholdersStrict(value, env2) {
|
|
|
8348
8908
|
if (hasUndefined) return void 0;
|
|
8349
8909
|
return result;
|
|
8350
8910
|
}
|
|
8911
|
+
function expandHeaders(headers, env2, warn) {
|
|
8912
|
+
if (!headers) return void 0;
|
|
8913
|
+
const result = {};
|
|
8914
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
8915
|
+
const missing = missingPlaceholders(v, env2);
|
|
8916
|
+
if (missing.length > 0) {
|
|
8917
|
+
if (warn)
|
|
8918
|
+
warn(`header "${k}": unset variable${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`);
|
|
8919
|
+
continue;
|
|
8920
|
+
}
|
|
8921
|
+
result[k] = expandPlaceholders(v, env2);
|
|
8922
|
+
}
|
|
8923
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
8924
|
+
}
|
|
8351
8925
|
|
|
8352
8926
|
// src/config/parse.ts
|
|
8353
8927
|
var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
@@ -8538,7 +9112,12 @@ async function readFile2(fs, path) {
|
|
|
8538
9112
|
try {
|
|
8539
9113
|
const data = await fs.readFile(path);
|
|
8540
9114
|
return new TextDecoder().decode(data);
|
|
8541
|
-
} catch {
|
|
9115
|
+
} catch (err) {
|
|
9116
|
+
if (err.code === "EACCES") {
|
|
9117
|
+
const e = new Error(`permission denied: ${path}`);
|
|
9118
|
+
e.code = "EACCES";
|
|
9119
|
+
throw e;
|
|
9120
|
+
}
|
|
8542
9121
|
return void 0;
|
|
8543
9122
|
}
|
|
8544
9123
|
}
|
|
@@ -8703,6 +9282,13 @@ async function removeStaleLock(fs, lockPath) {
|
|
|
8703
9282
|
async function acquireLock(fs, configPath) {
|
|
8704
9283
|
const lockPath = `${configPath}.lock`;
|
|
8705
9284
|
const start = Date.now();
|
|
9285
|
+
const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
|
|
9286
|
+
if (parentDir) {
|
|
9287
|
+
try {
|
|
9288
|
+
await fs.mkdir(parentDir);
|
|
9289
|
+
} catch {
|
|
9290
|
+
}
|
|
9291
|
+
}
|
|
8706
9292
|
while (true) {
|
|
8707
9293
|
try {
|
|
8708
9294
|
await fs.mkdirExclusive(lockPath);
|
|
@@ -8764,7 +9350,15 @@ async function editMcpServers(fs, path, edit) {
|
|
|
8764
9350
|
}
|
|
8765
9351
|
const tmpPath = `${path}.tmp.${Date.now()}`;
|
|
8766
9352
|
await fs.writeFile(tmpPath, output);
|
|
8767
|
-
|
|
9353
|
+
try {
|
|
9354
|
+
await fs.rename(tmpPath, path);
|
|
9355
|
+
} catch (renameErr) {
|
|
9356
|
+
try {
|
|
9357
|
+
if (fs.unlink) await fs.unlink(tmpPath);
|
|
9358
|
+
} catch {
|
|
9359
|
+
}
|
|
9360
|
+
throw renameErr;
|
|
9361
|
+
}
|
|
8768
9362
|
return { changed: true };
|
|
8769
9363
|
} finally {
|
|
8770
9364
|
await release();
|
|
@@ -9141,6 +9735,31 @@ async function getCandidate(fs, stateDir2, id) {
|
|
|
9141
9735
|
}
|
|
9142
9736
|
|
|
9143
9737
|
// src/discovery/probe.ts
|
|
9738
|
+
var LOOPBACK_RE = /^(127\.\d+\.\d+\.\d+|\[?::1\]?|localhost)$/i;
|
|
9739
|
+
var LINK_LOCAL_RE = /^(169\.254\.|fe80:)/i;
|
|
9740
|
+
var PRIVATE_RE = /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|fc|fd)/i;
|
|
9741
|
+
var LOCALHOST_SUFFIX_RE = /\.localhost$/i;
|
|
9742
|
+
var SLICC_INTERNAL_RE = /^host\.slicc\.internal$/i;
|
|
9743
|
+
function isPrivateOrLoopback(hostname) {
|
|
9744
|
+
const bare = hostname.replace(/^\[|\]$/g, "");
|
|
9745
|
+
return LOOPBACK_RE.test(bare) || LINK_LOCAL_RE.test(bare) || PRIVATE_RE.test(bare) || LOCALHOST_SUFFIX_RE.test(bare) || SLICC_INTERNAL_RE.test(bare);
|
|
9746
|
+
}
|
|
9747
|
+
function isSameOrigin(base, candidate) {
|
|
9748
|
+
try {
|
|
9749
|
+
const a = new URL(base);
|
|
9750
|
+
const b = new URL(candidate);
|
|
9751
|
+
return a.origin === b.origin;
|
|
9752
|
+
} catch {
|
|
9753
|
+
return false;
|
|
9754
|
+
}
|
|
9755
|
+
}
|
|
9756
|
+
function isUserUrlPrivate(userUrl) {
|
|
9757
|
+
try {
|
|
9758
|
+
return isPrivateOrLoopback(new URL(userUrl).hostname);
|
|
9759
|
+
} catch {
|
|
9760
|
+
return false;
|
|
9761
|
+
}
|
|
9762
|
+
}
|
|
9144
9763
|
function isJsonContentType(ct) {
|
|
9145
9764
|
if (!ct) return false;
|
|
9146
9765
|
const lower = ct.toLowerCase();
|
|
@@ -9275,13 +9894,44 @@ async function probeWellKnown(fetcher, origin, page) {
|
|
|
9275
9894
|
}
|
|
9276
9895
|
return events;
|
|
9277
9896
|
}
|
|
9278
|
-
async function probeLinkHeaders(fetcher, targetUrl, origin, page) {
|
|
9897
|
+
async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPrivate) {
|
|
9279
9898
|
const events = [];
|
|
9280
9899
|
const targetResult = await probeUrl(fetcher, targetUrl);
|
|
9281
9900
|
if (!targetResult) return events;
|
|
9282
9901
|
const links = parseLinkHeaders(targetResult.headers);
|
|
9283
9902
|
for (const link of links) {
|
|
9284
9903
|
const resolved = new URL(link.url, targetUrl).href;
|
|
9904
|
+
if (!isSameOrigin(targetUrl, resolved)) {
|
|
9905
|
+
events.push(
|
|
9906
|
+
makeEvent(
|
|
9907
|
+
link.rel,
|
|
9908
|
+
origin,
|
|
9909
|
+
resolved,
|
|
9910
|
+
"link-header",
|
|
9911
|
+
page,
|
|
9912
|
+
"invalid",
|
|
9913
|
+
null,
|
|
9914
|
+
`skipped: cross-origin Link target (${new URL(resolved).origin})`
|
|
9915
|
+
)
|
|
9916
|
+
);
|
|
9917
|
+
continue;
|
|
9918
|
+
}
|
|
9919
|
+
const resolvedHost = new URL(resolved).hostname;
|
|
9920
|
+
if (!userUrlIsPrivate && isPrivateOrLoopback(resolvedHost)) {
|
|
9921
|
+
events.push(
|
|
9922
|
+
makeEvent(
|
|
9923
|
+
link.rel,
|
|
9924
|
+
origin,
|
|
9925
|
+
resolved,
|
|
9926
|
+
"link-header",
|
|
9927
|
+
page,
|
|
9928
|
+
"invalid",
|
|
9929
|
+
null,
|
|
9930
|
+
`skipped: private/loopback target (${resolvedHost})`
|
|
9931
|
+
)
|
|
9932
|
+
);
|
|
9933
|
+
continue;
|
|
9934
|
+
}
|
|
9285
9935
|
const classifier = link.rel === "ard" ? classifyArd : link.rel === "ai-catalog" ? classifyAiCatalog : null;
|
|
9286
9936
|
if (!classifier) continue;
|
|
9287
9937
|
const linkResult = await probeUrl(fetcher, resolved, "application/json");
|
|
@@ -9378,9 +10028,10 @@ async function probeDiscover(platform2, targetUrl) {
|
|
|
9378
10028
|
}
|
|
9379
10029
|
const page = targetUrl;
|
|
9380
10030
|
const fetcher = platform2.fetch;
|
|
10031
|
+
const userPrivate = isUserUrlPrivate(targetUrl);
|
|
9381
10032
|
const events = [
|
|
9382
10033
|
...await probeWellKnown(fetcher, origin, page),
|
|
9383
|
-
...await probeLinkHeaders(fetcher, targetUrl, origin, page),
|
|
10034
|
+
...await probeLinkHeaders(fetcher, targetUrl, origin, page, userPrivate),
|
|
9384
10035
|
...await probeMcp(fetcher, origin, page, targetUrl)
|
|
9385
10036
|
];
|
|
9386
10037
|
return { events };
|
|
@@ -9637,13 +10288,27 @@ function renderCallResultJson(result) {
|
|
|
9637
10288
|
return JSON.stringify(result, null, 2);
|
|
9638
10289
|
}
|
|
9639
10290
|
async function renderCallResult(platform2, result, options) {
|
|
10291
|
+
const r = options?.redact ?? ((s) => s);
|
|
10292
|
+
if (options?.outputPath) {
|
|
10293
|
+
const content = options.json ? renderCallResultJson(result) : renderCallResultText(result, options);
|
|
10294
|
+
await platform2.fs.writeFile(options.outputPath, content);
|
|
10295
|
+
if (result.isError) {
|
|
10296
|
+
await platform2.write(
|
|
10297
|
+
2,
|
|
10298
|
+
r(`${result.isError ? "(tool reported error)" : ""}
|
|
10299
|
+
`)
|
|
10300
|
+
);
|
|
10301
|
+
return 1;
|
|
10302
|
+
}
|
|
10303
|
+
return 0;
|
|
10304
|
+
}
|
|
9640
10305
|
if (options?.json) {
|
|
9641
10306
|
const json = renderCallResultJson(result);
|
|
9642
10307
|
if (result.isError) {
|
|
9643
10308
|
await platform2.write(
|
|
9644
10309
|
2,
|
|
9645
|
-
`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
|
|
9646
|
-
`
|
|
10310
|
+
r(`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
|
|
10311
|
+
`)
|
|
9647
10312
|
);
|
|
9648
10313
|
return 1;
|
|
9649
10314
|
}
|
|
@@ -9653,8 +10318,8 @@ async function renderCallResult(platform2, result, options) {
|
|
|
9653
10318
|
}
|
|
9654
10319
|
const text = renderCallResultText(result, options);
|
|
9655
10320
|
if (result.isError) {
|
|
9656
|
-
await platform2.write(2, `${text || "(tool reported error)"}
|
|
9657
|
-
`);
|
|
10321
|
+
await platform2.write(2, r(`${text || "(tool reported error)"}
|
|
10322
|
+
`));
|
|
9658
10323
|
return 1;
|
|
9659
10324
|
}
|
|
9660
10325
|
if (text) {
|
|
@@ -9671,13 +10336,33 @@ async function renderInfoJson(platform2, info) {
|
|
|
9671
10336
|
await platform2.write(1, `${JSON.stringify(info, null, 2)}
|
|
9672
10337
|
`);
|
|
9673
10338
|
}
|
|
9674
|
-
async function renderErrorJson(platform2, kind, message, code, data) {
|
|
9675
|
-
const
|
|
10339
|
+
async function renderErrorJson(platform2, kind, message, code, data, redact2) {
|
|
10340
|
+
const r = redact2 ?? ((s) => s);
|
|
10341
|
+
const errorObj = { kind, message: r(message) };
|
|
9676
10342
|
if (code !== void 0) errorObj.code = code;
|
|
9677
10343
|
if (data !== void 0) errorObj.data = data;
|
|
9678
10344
|
await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
|
|
9679
10345
|
`);
|
|
9680
10346
|
}
|
|
10347
|
+
async function renderResourcesJson(platform2, resources, templates) {
|
|
10348
|
+
await platform2.write(
|
|
10349
|
+
1,
|
|
10350
|
+
`${JSON.stringify({ resources, resourceTemplates: templates }, null, 2)}
|
|
10351
|
+
`
|
|
10352
|
+
);
|
|
10353
|
+
}
|
|
10354
|
+
async function renderPromptsJson(platform2, prompts) {
|
|
10355
|
+
await platform2.write(1, `${JSON.stringify({ prompts }, null, 2)}
|
|
10356
|
+
`);
|
|
10357
|
+
}
|
|
10358
|
+
async function renderPromptResultJson(platform2, result) {
|
|
10359
|
+
await platform2.write(1, `${JSON.stringify(result, null, 2)}
|
|
10360
|
+
`);
|
|
10361
|
+
}
|
|
10362
|
+
async function renderReadResourceJson(platform2, result) {
|
|
10363
|
+
await platform2.write(1, `${JSON.stringify(result, null, 2)}
|
|
10364
|
+
`);
|
|
10365
|
+
}
|
|
9681
10366
|
|
|
9682
10367
|
// src/serve/catalog.ts
|
|
9683
10368
|
var NAME_RE = /^[A-Za-z][\w-]*$/;
|
|
@@ -10445,38 +11130,46 @@ function hasOwn(value, key) {
|
|
|
10445
11130
|
}
|
|
10446
11131
|
|
|
10447
11132
|
// src/serve/origin.ts
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
try {
|
|
10451
|
-
const url = new URL(origin);
|
|
10452
|
-
const hostname = url.hostname;
|
|
10453
|
-
if (LOOPBACK_HOSTS.has(hostname)) return true;
|
|
10454
|
-
if (hostname === "::1") return true;
|
|
10455
|
-
if (hostname.endsWith(".localhost")) return true;
|
|
10456
|
-
return false;
|
|
10457
|
-
} catch {
|
|
10458
|
-
return false;
|
|
10459
|
-
}
|
|
11133
|
+
function isLoopbackBind(host) {
|
|
11134
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]";
|
|
10460
11135
|
}
|
|
10461
|
-
function
|
|
10462
|
-
const
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
if (
|
|
10466
|
-
|
|
11136
|
+
function buildServerOrigins(host, port) {
|
|
11137
|
+
const origins = /* @__PURE__ */ new Set();
|
|
11138
|
+
origins.add(`http://${host}:${port}`);
|
|
11139
|
+
origins.add(`http://${port}.kernel.localhost`);
|
|
11140
|
+
if (isLoopbackBind(host)) {
|
|
11141
|
+
origins.add(`http://localhost:${port}`);
|
|
11142
|
+
origins.add(`http://127.0.0.1:${port}`);
|
|
11143
|
+
origins.add(`http://[::1]:${port}`);
|
|
11144
|
+
}
|
|
11145
|
+
return origins;
|
|
10467
11146
|
}
|
|
10468
|
-
function checkOrigin(origin,
|
|
11147
|
+
function checkOrigin(origin, serverOrigins, allowedOrigins) {
|
|
10469
11148
|
if (origin === void 0) return { ok: true };
|
|
11149
|
+
if (serverOrigins.has(origin)) return { ok: true };
|
|
10470
11150
|
if (allowedOrigins.has(origin)) return { ok: true };
|
|
10471
|
-
if (isLoopbackOrigin(origin)) return { ok: true };
|
|
10472
|
-
if (!boundLoopback) return { ok: true };
|
|
10473
11151
|
return { ok: false, reason: `origin ${origin} is not allowed` };
|
|
10474
11152
|
}
|
|
10475
|
-
function
|
|
10476
|
-
|
|
11153
|
+
function buildAllowedHosts(host, port, extraHosts) {
|
|
11154
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
11155
|
+
hosts.add(`${host}:${port}`);
|
|
11156
|
+
hosts.add(host);
|
|
11157
|
+
hosts.add(`${port}.kernel.localhost`);
|
|
11158
|
+
const loopback2 = ["localhost", "127.0.0.1", "[::1]"];
|
|
11159
|
+
for (const h of loopback2) {
|
|
11160
|
+
hosts.add(h);
|
|
11161
|
+
hosts.add(`${h}:${port}`);
|
|
11162
|
+
}
|
|
11163
|
+
for (const h of extraHosts) hosts.add(h);
|
|
11164
|
+
return hosts;
|
|
11165
|
+
}
|
|
11166
|
+
function checkHost(host, allowedHosts) {
|
|
10477
11167
|
if (!host) return { ok: false, reason: "missing Host header" };
|
|
10478
|
-
if (
|
|
10479
|
-
|
|
11168
|
+
if (allowedHosts.has(host)) return { ok: true };
|
|
11169
|
+
const hostname = host.replace(/:\d+$/, "");
|
|
11170
|
+
if (allowedHosts.has(hostname)) return { ok: true };
|
|
11171
|
+
if (hostname.endsWith(".localhost")) return { ok: true };
|
|
11172
|
+
return { ok: false, reason: `host ${host} is not allowed` };
|
|
10480
11173
|
}
|
|
10481
11174
|
|
|
10482
11175
|
// src/serve/proxy.ts
|
|
@@ -10713,6 +11406,8 @@ function parseSingleFlag(args, i, opts) {
|
|
|
10713
11406
|
return parsed.next;
|
|
10714
11407
|
} else if (arg === "--json") {
|
|
10715
11408
|
opts.json = true;
|
|
11409
|
+
} else if (arg === "--no-auth") {
|
|
11410
|
+
opts.noAuth = true;
|
|
10716
11411
|
} else if (!arg.startsWith("-")) {
|
|
10717
11412
|
opts.commands.push(parseCommandTarget(arg));
|
|
10718
11413
|
} else {
|
|
@@ -10733,6 +11428,9 @@ function parseValueFlag(args, i, opts) {
|
|
|
10733
11428
|
case "--allow-origin":
|
|
10734
11429
|
opts.allowOrigins.push(args[++i]);
|
|
10735
11430
|
break;
|
|
11431
|
+
case "--allow-host":
|
|
11432
|
+
opts.allowHosts.push(args[++i]);
|
|
11433
|
+
break;
|
|
10736
11434
|
case "--env":
|
|
10737
11435
|
opts.envAllow.push(args[++i]);
|
|
10738
11436
|
break;
|
|
@@ -10771,7 +11469,9 @@ function parseServeFlags(args) {
|
|
|
10771
11469
|
host: "127.0.0.1",
|
|
10772
11470
|
port: 0,
|
|
10773
11471
|
bearer: void 0,
|
|
11472
|
+
noAuth: false,
|
|
10774
11473
|
allowOrigins: [],
|
|
11474
|
+
allowHosts: [],
|
|
10775
11475
|
envAllow: [],
|
|
10776
11476
|
toolTemplates: [],
|
|
10777
11477
|
callTimeout: CALL_TIMEOUT_MS,
|
|
@@ -10800,6 +11500,20 @@ function constantTimeEqual(a, b) {
|
|
|
10800
11500
|
}
|
|
10801
11501
|
return mismatch === 0;
|
|
10802
11502
|
}
|
|
11503
|
+
function generateToken() {
|
|
11504
|
+
const bytes = new Uint8Array(32);
|
|
11505
|
+
crypto.getRandomValues(bytes);
|
|
11506
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
11507
|
+
}
|
|
11508
|
+
function checkContentType(ct) {
|
|
11509
|
+
if (!ct) return false;
|
|
11510
|
+
return ct.toLowerCase().startsWith("application/json");
|
|
11511
|
+
}
|
|
11512
|
+
function checkAcceptHeader(accept) {
|
|
11513
|
+
if (!accept) return false;
|
|
11514
|
+
const lower = accept.toLowerCase();
|
|
11515
|
+
return lower.includes("application/json") || lower.includes("text/event-stream") || lower.includes("*/*");
|
|
11516
|
+
}
|
|
10803
11517
|
function buildChildEnv(platform2, envAllow) {
|
|
10804
11518
|
const env2 = {};
|
|
10805
11519
|
for (const key of ALLOWED_ENV_KEYS) {
|
|
@@ -10848,7 +11562,7 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
10848
11562
|
}
|
|
10849
11563
|
const stdoutChunks = [];
|
|
10850
11564
|
const stderrChunks = [];
|
|
10851
|
-
const
|
|
11565
|
+
const readStream3 = async (stream, into) => {
|
|
10852
11566
|
const r = stream.getReader();
|
|
10853
11567
|
try {
|
|
10854
11568
|
for (; ; ) {
|
|
@@ -10864,8 +11578,8 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
10864
11578
|
(resolve2) => setTimeout(() => resolve2("timeout"), timeoutMs)
|
|
10865
11579
|
);
|
|
10866
11580
|
const work = Promise.all([
|
|
10867
|
-
|
|
10868
|
-
|
|
11581
|
+
readStream3(proc.stdout, stdoutChunks),
|
|
11582
|
+
readStream3(proc.stderr, stderrChunks),
|
|
10869
11583
|
proc.wait()
|
|
10870
11584
|
]);
|
|
10871
11585
|
const result = await Promise.race([work, timer]);
|
|
@@ -11097,7 +11811,10 @@ function createToolExecutor(platform2, clis, allTools, childEnv, options, serveC
|
|
|
11097
11811
|
}
|
|
11098
11812
|
async function runServe(platform2, options, version) {
|
|
11099
11813
|
let resolvedBearer;
|
|
11100
|
-
|
|
11814
|
+
let generatedBearer = false;
|
|
11815
|
+
if (options._resolvedBearer) {
|
|
11816
|
+
resolvedBearer = options._resolvedBearer;
|
|
11817
|
+
} else if (options.bearer) {
|
|
11101
11818
|
const expanded = expandBearerToken(options.bearer, platform2.env);
|
|
11102
11819
|
if (!expanded) {
|
|
11103
11820
|
await platform2.write(
|
|
@@ -11107,9 +11824,31 @@ async function runServe(platform2, options, version) {
|
|
|
11107
11824
|
return 2;
|
|
11108
11825
|
}
|
|
11109
11826
|
resolvedBearer = expanded;
|
|
11827
|
+
} else if (options.noAuth) {
|
|
11828
|
+
if (options.transport === "http" && !isLoopbackBind(options.host)) {
|
|
11829
|
+
await platform2.write(2, "mcpcat serve: --no-auth is only allowed on loopback\n");
|
|
11830
|
+
return 2;
|
|
11831
|
+
}
|
|
11832
|
+
if (options.transport === "http") {
|
|
11833
|
+
await platform2.write(
|
|
11834
|
+
2,
|
|
11835
|
+
"mcpcat serve: warning: running without authentication on loopback\n"
|
|
11836
|
+
);
|
|
11837
|
+
}
|
|
11838
|
+
resolvedBearer = void 0;
|
|
11839
|
+
} else if (options.transport === "http") {
|
|
11840
|
+
if (platform2.kind === "kernel") {
|
|
11841
|
+
await platform2.write(
|
|
11842
|
+
2,
|
|
11843
|
+
"mcpcat serve: kernel HTTP serve requires --bearer '${VAR}' or --no-auth\n"
|
|
11844
|
+
);
|
|
11845
|
+
return 2;
|
|
11846
|
+
}
|
|
11847
|
+
resolvedBearer = generateToken();
|
|
11848
|
+
generatedBearer = true;
|
|
11110
11849
|
}
|
|
11111
11850
|
if (options.proxy) {
|
|
11112
|
-
return runProxyServe(platform2, options, version, resolvedBearer);
|
|
11851
|
+
return runProxyServe(platform2, options, version, resolvedBearer, generatedBearer);
|
|
11113
11852
|
}
|
|
11114
11853
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
11115
11854
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
@@ -11146,12 +11885,35 @@ async function runServe(platform2, options, version) {
|
|
|
11146
11885
|
executor.invoke,
|
|
11147
11886
|
serverName,
|
|
11148
11887
|
version,
|
|
11149
|
-
resolvedBearer
|
|
11888
|
+
resolvedBearer,
|
|
11889
|
+
generatedBearer
|
|
11150
11890
|
);
|
|
11151
11891
|
}
|
|
11892
|
+
async function publishBearer(platform2, token, port, listenUrl, json) {
|
|
11893
|
+
if (!json && platform2.isatty(2)) {
|
|
11894
|
+
await platform2.write(2, `mcpcat serve: bearer ${token}
|
|
11895
|
+
`);
|
|
11896
|
+
return void 0;
|
|
11897
|
+
}
|
|
11898
|
+
const dir = stateDir(platform2);
|
|
11899
|
+
await platform2.fs.mkdir(dir, { mode: 448 });
|
|
11900
|
+
const tokenPath = `${dir}/serve-${port}.token`;
|
|
11901
|
+
try {
|
|
11902
|
+
await platform2.fs.unlink?.(tokenPath);
|
|
11903
|
+
} catch {
|
|
11904
|
+
}
|
|
11905
|
+
await platform2.fs.writeFile(tokenPath, token, { mode: 384 });
|
|
11906
|
+
await platform2.write(2, `mcpcat serve: bearer saved to ${tokenPath}
|
|
11907
|
+
`);
|
|
11908
|
+
if (json) {
|
|
11909
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl, bearerFile: tokenPath })}
|
|
11910
|
+
`);
|
|
11911
|
+
}
|
|
11912
|
+
return tokenPath;
|
|
11913
|
+
}
|
|
11152
11914
|
function resolveProxyStdio(target, version) {
|
|
11153
11915
|
const rest = target.slice(6);
|
|
11154
|
-
const parts = rest
|
|
11916
|
+
const parts = splitShellStyle(rest);
|
|
11155
11917
|
return { command: parts[0], args: parts.slice(1), version };
|
|
11156
11918
|
}
|
|
11157
11919
|
function resolveProxyUrl(target, headers, version) {
|
|
@@ -11221,7 +11983,7 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
11221
11983
|
}
|
|
11222
11984
|
return { connectOpts, hiddenValues };
|
|
11223
11985
|
}
|
|
11224
|
-
async function runProxyServe(platform2, options, version, resolvedBearer) {
|
|
11986
|
+
async function runProxyServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
11225
11987
|
const built = await buildProxyConnectOpts(platform2, options, version);
|
|
11226
11988
|
if ("error" in built) {
|
|
11227
11989
|
await platform2.write(2, `mcpcat serve --proxy: ${built.error}
|
|
@@ -11239,7 +12001,7 @@ async function runProxyServe(platform2, options, version, resolvedBearer) {
|
|
|
11239
12001
|
if (options.transport === "stdio") {
|
|
11240
12002
|
return runProxyStdio(platform2, proxy);
|
|
11241
12003
|
}
|
|
11242
|
-
return runProxyHttp(platform2, options, proxy, resolvedBearer);
|
|
12004
|
+
return runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer);
|
|
11243
12005
|
}
|
|
11244
12006
|
async function runProxyStdio(platform2, proxy) {
|
|
11245
12007
|
const reader = platform2.stdin.getReader();
|
|
@@ -11270,12 +12032,12 @@ async function runProxyStdio(platform2, proxy) {
|
|
|
11270
12032
|
}
|
|
11271
12033
|
return 0;
|
|
11272
12034
|
}
|
|
11273
|
-
async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
12035
|
+
async function runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer) {
|
|
11274
12036
|
if (!platform2.listen) {
|
|
11275
12037
|
await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
|
|
11276
12038
|
return 3;
|
|
11277
12039
|
}
|
|
11278
|
-
const boundLoopback = options.host
|
|
12040
|
+
const boundLoopback = isLoopbackBind(options.host);
|
|
11279
12041
|
if (!boundLoopback && !resolvedBearer) {
|
|
11280
12042
|
await platform2.write(
|
|
11281
12043
|
2,
|
|
@@ -11289,10 +12051,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
|
11289
12051
|
port: options.port
|
|
11290
12052
|
});
|
|
11291
12053
|
const actualPort = listener.port;
|
|
12054
|
+
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
12055
|
+
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
11292
12056
|
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
11293
|
-
|
|
11294
|
-
|
|
12057
|
+
let tokenFilePath;
|
|
12058
|
+
if (generatedBearer && resolvedBearer) {
|
|
12059
|
+
tokenFilePath = await publishBearer(
|
|
12060
|
+
platform2,
|
|
12061
|
+
resolvedBearer,
|
|
12062
|
+
actualPort,
|
|
12063
|
+
listenUrl,
|
|
12064
|
+
options.json
|
|
12065
|
+
);
|
|
12066
|
+
} else {
|
|
12067
|
+
if (options.json) {
|
|
12068
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
11295
12069
|
`);
|
|
12070
|
+
}
|
|
11296
12071
|
}
|
|
11297
12072
|
await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
|
|
11298
12073
|
`);
|
|
@@ -11305,12 +12080,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
|
11305
12080
|
shutdownRequested = true;
|
|
11306
12081
|
listener.close();
|
|
11307
12082
|
void proxy.shutdown();
|
|
12083
|
+
if (tokenFilePath) {
|
|
12084
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
12085
|
+
});
|
|
12086
|
+
}
|
|
11308
12087
|
};
|
|
11309
12088
|
if (platform2.onSignal) {
|
|
11310
12089
|
platform2.onSignal("SIGINT", shutdown);
|
|
11311
12090
|
platform2.onSignal("SIGTERM", shutdown);
|
|
11312
12091
|
}
|
|
11313
|
-
const handler = (request) => handleProxyMcpRequest(
|
|
12092
|
+
const handler = (request) => handleProxyMcpRequest(
|
|
12093
|
+
request,
|
|
12094
|
+
proxy,
|
|
12095
|
+
serverOrigins,
|
|
12096
|
+
allowedOrigins,
|
|
12097
|
+
allowedHosts,
|
|
12098
|
+
resolvedBearer
|
|
12099
|
+
);
|
|
11314
12100
|
try {
|
|
11315
12101
|
for await (const connection of listener) {
|
|
11316
12102
|
if (shutdownRequested) break;
|
|
@@ -11328,20 +12114,40 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
|
11328
12114
|
}
|
|
11329
12115
|
return 0;
|
|
11330
12116
|
}
|
|
11331
|
-
async function handleProxyMcpRequest(request, proxy, allowedOrigins,
|
|
11332
|
-
if (
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
12117
|
+
async function handleProxyMcpRequest(request, proxy, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
|
|
12118
|
+
if (request.target !== "/mcp") {
|
|
12119
|
+
return {
|
|
12120
|
+
status: 404,
|
|
12121
|
+
reason: "Not Found",
|
|
12122
|
+
headers: { "content-type": "text/plain" },
|
|
12123
|
+
body: "Not Found"
|
|
12124
|
+
};
|
|
12125
|
+
}
|
|
12126
|
+
if (request.method !== "POST") {
|
|
12127
|
+
return {
|
|
12128
|
+
status: 405,
|
|
12129
|
+
reason: "Method Not Allowed",
|
|
12130
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
12131
|
+
body: "Method Not Allowed"
|
|
12132
|
+
};
|
|
12133
|
+
}
|
|
12134
|
+
if (!checkContentType(request.headers["content-type"])) {
|
|
12135
|
+
return {
|
|
12136
|
+
status: 415,
|
|
12137
|
+
reason: "Unsupported Media Type",
|
|
12138
|
+
headers: { "content-type": "text/plain" },
|
|
12139
|
+
body: "Content-Type must be application/json"
|
|
12140
|
+
};
|
|
12141
|
+
}
|
|
12142
|
+
if (!checkAcceptHeader(request.headers.accept)) {
|
|
12143
|
+
return {
|
|
12144
|
+
status: 406,
|
|
12145
|
+
reason: "Not Acceptable",
|
|
12146
|
+
headers: { "content-type": "text/plain" },
|
|
12147
|
+
body: "Accept must include application/json or text/event-stream"
|
|
12148
|
+
};
|
|
11343
12149
|
}
|
|
11344
|
-
const originCheck = checkOrigin(request.headers.origin,
|
|
12150
|
+
const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
|
|
11345
12151
|
if (!originCheck.ok) {
|
|
11346
12152
|
return {
|
|
11347
12153
|
status: 403,
|
|
@@ -11350,7 +12156,7 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
|
|
|
11350
12156
|
body: originCheck.reason ?? "Forbidden"
|
|
11351
12157
|
};
|
|
11352
12158
|
}
|
|
11353
|
-
const hostCheck = checkHost(request.headers.host,
|
|
12159
|
+
const hostCheck = checkHost(request.headers.host, allowedHosts);
|
|
11354
12160
|
if (!hostCheck.ok) {
|
|
11355
12161
|
return {
|
|
11356
12162
|
status: 403,
|
|
@@ -11359,21 +12165,17 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
|
|
|
11359
12165
|
body: hostCheck.reason ?? "Forbidden"
|
|
11360
12166
|
};
|
|
11361
12167
|
}
|
|
11362
|
-
if (
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
reason: "Method Not Allowed",
|
|
11374
|
-
headers: { "content-type": "text/plain", allow: "POST" },
|
|
11375
|
-
body: "Method Not Allowed"
|
|
11376
|
-
};
|
|
12168
|
+
if (resolvedBearer) {
|
|
12169
|
+
const authHeader = request.headers.authorization;
|
|
12170
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
12171
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
12172
|
+
return {
|
|
12173
|
+
status: 401,
|
|
12174
|
+
reason: "Unauthorized",
|
|
12175
|
+
headers: { "content-type": "text/plain" },
|
|
12176
|
+
body: "Unauthorized"
|
|
12177
|
+
};
|
|
12178
|
+
}
|
|
11377
12179
|
}
|
|
11378
12180
|
const outcome = await proxy.handle(request.body);
|
|
11379
12181
|
return {
|
|
@@ -11411,12 +12213,12 @@ async function runStdioServer(platform2, tools, call, serverName, version) {
|
|
|
11411
12213
|
}
|
|
11412
12214
|
return 0;
|
|
11413
12215
|
}
|
|
11414
|
-
async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer) {
|
|
12216
|
+
async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer, generatedBearer) {
|
|
11415
12217
|
if (!platform2.listen) {
|
|
11416
12218
|
await platform2.write(2, "mcpcat serve: HTTP transport requires platform.listen\n");
|
|
11417
12219
|
return 3;
|
|
11418
12220
|
}
|
|
11419
|
-
const boundLoopback = options.host
|
|
12221
|
+
const boundLoopback = isLoopbackBind(options.host);
|
|
11420
12222
|
if (!boundLoopback && !resolvedBearer) {
|
|
11421
12223
|
await platform2.write(2, "mcpcat serve: --bearer is required when binding off loopback\n");
|
|
11422
12224
|
return 2;
|
|
@@ -11424,10 +12226,23 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11424
12226
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
11425
12227
|
const listener = await platform2.listen({ host: options.host, port: options.port });
|
|
11426
12228
|
const actualPort = listener.port;
|
|
12229
|
+
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
12230
|
+
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
11427
12231
|
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
11428
|
-
|
|
11429
|
-
|
|
12232
|
+
let tokenFilePath;
|
|
12233
|
+
if (generatedBearer && resolvedBearer) {
|
|
12234
|
+
tokenFilePath = await publishBearer(
|
|
12235
|
+
platform2,
|
|
12236
|
+
resolvedBearer,
|
|
12237
|
+
actualPort,
|
|
12238
|
+
listenUrl,
|
|
12239
|
+
options.json
|
|
12240
|
+
);
|
|
12241
|
+
} else {
|
|
12242
|
+
if (options.json) {
|
|
12243
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
11430
12244
|
`);
|
|
12245
|
+
}
|
|
11431
12246
|
}
|
|
11432
12247
|
await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
|
|
11433
12248
|
`);
|
|
@@ -11437,6 +12252,10 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11437
12252
|
shutdownRequested = true;
|
|
11438
12253
|
listener.close();
|
|
11439
12254
|
void killInflight();
|
|
12255
|
+
if (tokenFilePath) {
|
|
12256
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
12257
|
+
});
|
|
12258
|
+
}
|
|
11440
12259
|
};
|
|
11441
12260
|
if (platform2.onSignal) {
|
|
11442
12261
|
platform2.onSignal("SIGINT", shutdown);
|
|
@@ -11448,8 +12267,9 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11448
12267
|
call,
|
|
11449
12268
|
serverName,
|
|
11450
12269
|
version,
|
|
12270
|
+
serverOrigins,
|
|
11451
12271
|
allowedOrigins,
|
|
11452
|
-
|
|
12272
|
+
allowedHosts,
|
|
11453
12273
|
resolvedBearer
|
|
11454
12274
|
);
|
|
11455
12275
|
try {
|
|
@@ -11469,37 +12289,7 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11469
12289
|
}
|
|
11470
12290
|
return 0;
|
|
11471
12291
|
}
|
|
11472
|
-
async function handleMcpRequest(request, tools, call, serverName, version, allowedOrigins,
|
|
11473
|
-
if (resolvedBearer) {
|
|
11474
|
-
const authHeader = request.headers.authorization;
|
|
11475
|
-
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
11476
|
-
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
11477
|
-
return {
|
|
11478
|
-
status: 401,
|
|
11479
|
-
reason: "Unauthorized",
|
|
11480
|
-
headers: { "content-type": "text/plain" },
|
|
11481
|
-
body: "Unauthorized"
|
|
11482
|
-
};
|
|
11483
|
-
}
|
|
11484
|
-
}
|
|
11485
|
-
const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
|
|
11486
|
-
if (!originCheck.ok) {
|
|
11487
|
-
return {
|
|
11488
|
-
status: 403,
|
|
11489
|
-
reason: "Forbidden",
|
|
11490
|
-
headers: { "content-type": "text/plain" },
|
|
11491
|
-
body: originCheck.reason ?? "Forbidden"
|
|
11492
|
-
};
|
|
11493
|
-
}
|
|
11494
|
-
const hostCheck = checkHost(request.headers.host, boundLoopback);
|
|
11495
|
-
if (!hostCheck.ok) {
|
|
11496
|
-
return {
|
|
11497
|
-
status: 403,
|
|
11498
|
-
reason: "Forbidden",
|
|
11499
|
-
headers: { "content-type": "text/plain" },
|
|
11500
|
-
body: hostCheck.reason ?? "Forbidden"
|
|
11501
|
-
};
|
|
11502
|
-
}
|
|
12292
|
+
async function handleMcpRequest(request, tools, call, serverName, version, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
|
|
11503
12293
|
if (request.target !== "/mcp") {
|
|
11504
12294
|
return {
|
|
11505
12295
|
status: 404,
|
|
@@ -11516,8 +12306,54 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
|
|
|
11516
12306
|
body: "Method Not Allowed"
|
|
11517
12307
|
};
|
|
11518
12308
|
}
|
|
11519
|
-
|
|
11520
|
-
|
|
12309
|
+
if (!checkContentType(request.headers["content-type"])) {
|
|
12310
|
+
return {
|
|
12311
|
+
status: 415,
|
|
12312
|
+
reason: "Unsupported Media Type",
|
|
12313
|
+
headers: { "content-type": "text/plain" },
|
|
12314
|
+
body: "Content-Type must be application/json"
|
|
12315
|
+
};
|
|
12316
|
+
}
|
|
12317
|
+
if (!checkAcceptHeader(request.headers.accept)) {
|
|
12318
|
+
return {
|
|
12319
|
+
status: 406,
|
|
12320
|
+
reason: "Not Acceptable",
|
|
12321
|
+
headers: { "content-type": "text/plain" },
|
|
12322
|
+
body: "Accept must include application/json or text/event-stream"
|
|
12323
|
+
};
|
|
12324
|
+
}
|
|
12325
|
+
const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
|
|
12326
|
+
if (!originCheck.ok) {
|
|
12327
|
+
return {
|
|
12328
|
+
status: 403,
|
|
12329
|
+
reason: "Forbidden",
|
|
12330
|
+
headers: { "content-type": "text/plain" },
|
|
12331
|
+
body: originCheck.reason ?? "Forbidden"
|
|
12332
|
+
};
|
|
12333
|
+
}
|
|
12334
|
+
const hostCheck = checkHost(request.headers.host, allowedHosts);
|
|
12335
|
+
if (!hostCheck.ok) {
|
|
12336
|
+
return {
|
|
12337
|
+
status: 403,
|
|
12338
|
+
reason: "Forbidden",
|
|
12339
|
+
headers: { "content-type": "text/plain" },
|
|
12340
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
12341
|
+
};
|
|
12342
|
+
}
|
|
12343
|
+
if (resolvedBearer) {
|
|
12344
|
+
const authHeader = request.headers.authorization;
|
|
12345
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
12346
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
12347
|
+
return {
|
|
12348
|
+
status: 401,
|
|
12349
|
+
reason: "Unauthorized",
|
|
12350
|
+
headers: { "content-type": "text/plain" },
|
|
12351
|
+
body: "Unauthorized"
|
|
12352
|
+
};
|
|
12353
|
+
}
|
|
12354
|
+
}
|
|
12355
|
+
const outcome = await handleJsonRpc(request.body, tools, call, serverName, version);
|
|
12356
|
+
return {
|
|
11521
12357
|
status: outcome.status,
|
|
11522
12358
|
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
11523
12359
|
headers: { "content-type": outcome.contentType },
|
|
@@ -11841,8 +12677,16 @@ var PiFileTokenSource = class {
|
|
|
11841
12677
|
};
|
|
11842
12678
|
|
|
11843
12679
|
// src/cli/auth-verbs.ts
|
|
11844
|
-
|
|
11845
|
-
|
|
12680
|
+
var resolvedSource;
|
|
12681
|
+
async function createTokenSource(platform2) {
|
|
12682
|
+
if (isKernel(platform2)) return new PiFileTokenSource(platform2);
|
|
12683
|
+
if (resolvedSource) return resolvedSource;
|
|
12684
|
+
if (await jerkAvailable(platform2)) {
|
|
12685
|
+
resolvedSource = new JerkTokenSource(platform2);
|
|
12686
|
+
} else {
|
|
12687
|
+
resolvedSource = new PiFileTokenSource(platform2);
|
|
12688
|
+
}
|
|
12689
|
+
return resolvedSource;
|
|
11846
12690
|
}
|
|
11847
12691
|
function resolveServerName(target) {
|
|
11848
12692
|
try {
|
|
@@ -11872,6 +12716,79 @@ async function resolveLoginTarget(platform2, target) {
|
|
|
11872
12716
|
}
|
|
11873
12717
|
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
11874
12718
|
}
|
|
12719
|
+
function jerkMcpId(name) {
|
|
12720
|
+
if (name.startsWith("mcp:")) return name;
|
|
12721
|
+
if (name.startsWith("url:")) return `mcp:${name}`;
|
|
12722
|
+
return `mcp:${name}`;
|
|
12723
|
+
}
|
|
12724
|
+
async function readStream2(stream) {
|
|
12725
|
+
const reader = stream.getReader();
|
|
12726
|
+
const chunks = [];
|
|
12727
|
+
for (; ; ) {
|
|
12728
|
+
const { done, value } = await reader.read();
|
|
12729
|
+
if (done) break;
|
|
12730
|
+
chunks.push(value);
|
|
12731
|
+
}
|
|
12732
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
12733
|
+
const buf = new Uint8Array(total);
|
|
12734
|
+
let off = 0;
|
|
12735
|
+
for (const c of chunks) {
|
|
12736
|
+
buf.set(c, off);
|
|
12737
|
+
off += c.length;
|
|
12738
|
+
}
|
|
12739
|
+
return new TextDecoder().decode(buf);
|
|
12740
|
+
}
|
|
12741
|
+
async function jerkLogin(platform2, target, args) {
|
|
12742
|
+
const resolved = await resolveLoginTarget(platform2, target);
|
|
12743
|
+
const id = jerkMcpId(resolved.name);
|
|
12744
|
+
const argv = ["jerk", id, "create"];
|
|
12745
|
+
const scopeIdx = args.indexOf("--scope");
|
|
12746
|
+
if (scopeIdx !== -1 && args[scopeIdx + 1]) {
|
|
12747
|
+
argv.push("--scope", args[scopeIdx + 1]);
|
|
12748
|
+
}
|
|
12749
|
+
if (args.includes("--no-browser")) {
|
|
12750
|
+
argv.push("--no-browser");
|
|
12751
|
+
}
|
|
12752
|
+
const proc = await platform2.spawn({ argv });
|
|
12753
|
+
proc.stdin.close().catch(() => {
|
|
12754
|
+
});
|
|
12755
|
+
const [, errText, result] = await Promise.all([
|
|
12756
|
+
readStream2(proc.stdout),
|
|
12757
|
+
readStream2(proc.stderr),
|
|
12758
|
+
proc.wait()
|
|
12759
|
+
]);
|
|
12760
|
+
if (result.status === 0) {
|
|
12761
|
+
await platform2.write(1, `Signed in to ${target}
|
|
12762
|
+
`);
|
|
12763
|
+
return 0;
|
|
12764
|
+
}
|
|
12765
|
+
if (errText.trim()) {
|
|
12766
|
+
await platform2.write(2, errText);
|
|
12767
|
+
}
|
|
12768
|
+
if (result.status === JERK_EXIT_SIGN_IN) return 4;
|
|
12769
|
+
return result.status;
|
|
12770
|
+
}
|
|
12771
|
+
async function jerkLogout(platform2, target) {
|
|
12772
|
+
const resolved = await resolveLoginTarget(platform2, target);
|
|
12773
|
+
const id = jerkMcpId(resolved.name);
|
|
12774
|
+
const argv = ["jerk", id, "drop", "--yes"];
|
|
12775
|
+
const proc = await platform2.spawn({ argv });
|
|
12776
|
+
proc.stdin.close().catch(() => {
|
|
12777
|
+
});
|
|
12778
|
+
const [, , result] = await Promise.all([
|
|
12779
|
+
readStream2(proc.stdout),
|
|
12780
|
+
readStream2(proc.stderr),
|
|
12781
|
+
proc.wait()
|
|
12782
|
+
]);
|
|
12783
|
+
if (result.status === 0) {
|
|
12784
|
+
await platform2.write(1, `Signed out of ${target}
|
|
12785
|
+
`);
|
|
12786
|
+
} else {
|
|
12787
|
+
await platform2.write(1, `No credentials stored for ${target}
|
|
12788
|
+
`);
|
|
12789
|
+
}
|
|
12790
|
+
return 0;
|
|
12791
|
+
}
|
|
11875
12792
|
async function runLogin(platform2, args, source) {
|
|
11876
12793
|
if (isKernel(platform2)) {
|
|
11877
12794
|
await platform2.write(
|
|
@@ -11880,13 +12797,22 @@ async function runLogin(platform2, args, source) {
|
|
|
11880
12797
|
);
|
|
11881
12798
|
return 4;
|
|
11882
12799
|
}
|
|
11883
|
-
const noBrowser = args.includes("--no-browser");
|
|
11884
12800
|
const positional = args.filter((a) => !a.startsWith("-"));
|
|
11885
12801
|
if (positional.length === 0) {
|
|
11886
12802
|
await platform2.write(2, "mcpcat: login requires a target\n");
|
|
11887
12803
|
return 2;
|
|
11888
12804
|
}
|
|
11889
12805
|
const target = positional[0];
|
|
12806
|
+
if (source instanceof JerkTokenSource) {
|
|
12807
|
+
try {
|
|
12808
|
+
return await jerkLogin(platform2, target, args);
|
|
12809
|
+
} catch (err) {
|
|
12810
|
+
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
12811
|
+
`);
|
|
12812
|
+
return 4;
|
|
12813
|
+
}
|
|
12814
|
+
}
|
|
12815
|
+
const noBrowser = args.includes("--no-browser");
|
|
11890
12816
|
let resolved;
|
|
11891
12817
|
try {
|
|
11892
12818
|
resolved = await resolveLoginTarget(platform2, target);
|
|
@@ -11968,6 +12894,15 @@ async function runLogout(platform2, args, source) {
|
|
|
11968
12894
|
return 2;
|
|
11969
12895
|
}
|
|
11970
12896
|
const target = positional[0];
|
|
12897
|
+
if (source instanceof JerkTokenSource) {
|
|
12898
|
+
try {
|
|
12899
|
+
return await jerkLogout(platform2, target);
|
|
12900
|
+
} catch (err) {
|
|
12901
|
+
await platform2.write(2, `mcpcat: logout failed: ${err.message}
|
|
12902
|
+
`);
|
|
12903
|
+
return 1;
|
|
12904
|
+
}
|
|
12905
|
+
}
|
|
11971
12906
|
let resolved;
|
|
11972
12907
|
try {
|
|
11973
12908
|
resolved = await resolveLoginTarget(platform2, target);
|
|
@@ -12340,6 +13275,49 @@ function checkAgentWarnings(name, config) {
|
|
|
12340
13275
|
return warnings;
|
|
12341
13276
|
}
|
|
12342
13277
|
|
|
13278
|
+
// src/serve/seal.ts
|
|
13279
|
+
async function sealSecret(platform2, name, value, options) {
|
|
13280
|
+
if (!platform2.seal) {
|
|
13281
|
+
throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
|
|
13282
|
+
}
|
|
13283
|
+
const request = JSON.stringify({
|
|
13284
|
+
v: 1,
|
|
13285
|
+
op: "seal",
|
|
13286
|
+
kind: "mcp",
|
|
13287
|
+
name,
|
|
13288
|
+
value,
|
|
13289
|
+
server: options.server,
|
|
13290
|
+
url: options.url
|
|
13291
|
+
});
|
|
13292
|
+
let responseText;
|
|
13293
|
+
try {
|
|
13294
|
+
responseText = await platform2.seal(request);
|
|
13295
|
+
} catch (err) {
|
|
13296
|
+
const code = err.code;
|
|
13297
|
+
if (code === "ENOENT" || code === "ENOTCONN") {
|
|
13298
|
+
throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
|
|
13299
|
+
}
|
|
13300
|
+
throw Object.assign(new Error(`vault: ${err.message}`), {
|
|
13301
|
+
sealErrno: code ?? "EACCES"
|
|
13302
|
+
});
|
|
13303
|
+
}
|
|
13304
|
+
const response = JSON.parse(responseText);
|
|
13305
|
+
if (response.error) {
|
|
13306
|
+
throw Object.assign(new Error(response.error), { sealErrno: response.errno ?? "EACCES" });
|
|
13307
|
+
}
|
|
13308
|
+
return { placeholder: response.placeholder };
|
|
13309
|
+
}
|
|
13310
|
+
function sealExitCode(errno) {
|
|
13311
|
+
if (errno === "EEXIST") return 2;
|
|
13312
|
+
return 4;
|
|
13313
|
+
}
|
|
13314
|
+
function sealErrorMessage(errno, name) {
|
|
13315
|
+
if (errno === "EEXIST") return `a non-bridge secret named ${name} already exists`;
|
|
13316
|
+
if (errno === "ENOTCONN") return "no vault available; use --bearer '${VAR}' or --no-auth";
|
|
13317
|
+
if (errno === "EACCES") return "vault locked; use --bearer '${VAR}' or --no-auth";
|
|
13318
|
+
return `vault error: ${errno}`;
|
|
13319
|
+
}
|
|
13320
|
+
|
|
12343
13321
|
// src/cli/config-verbs.ts
|
|
12344
13322
|
function parseHeader(hdr) {
|
|
12345
13323
|
const colonIdx = hdr.indexOf(":");
|
|
@@ -12361,6 +13339,10 @@ function parseAddFlagAt(flagArgs, i, flags) {
|
|
|
12361
13339
|
flags.noProbe = true;
|
|
12362
13340
|
return { next: i + 1 };
|
|
12363
13341
|
}
|
|
13342
|
+
if (arg === "--no-auth") {
|
|
13343
|
+
flags.noAuth = true;
|
|
13344
|
+
return { next: i + 1 };
|
|
13345
|
+
}
|
|
12364
13346
|
if (arg === "--bridge") {
|
|
12365
13347
|
flags.bridge = true;
|
|
12366
13348
|
return { next: i + 1 };
|
|
@@ -12406,6 +13388,7 @@ function parseAddFlags(args) {
|
|
|
12406
13388
|
local: false,
|
|
12407
13389
|
noProbe: false,
|
|
12408
13390
|
bridge: false,
|
|
13391
|
+
noAuth: false,
|
|
12409
13392
|
headers: [],
|
|
12410
13393
|
envPairs: [],
|
|
12411
13394
|
positional: [],
|
|
@@ -12768,6 +13751,11 @@ async function runImport(platform2, args) {
|
|
|
12768
13751
|
}
|
|
12769
13752
|
return 0;
|
|
12770
13753
|
}
|
|
13754
|
+
function generateBridgeToken() {
|
|
13755
|
+
const bytes = new Uint8Array(32);
|
|
13756
|
+
crypto.getRandomValues(bytes);
|
|
13757
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
13758
|
+
}
|
|
12771
13759
|
async function runBridge(platform2, args, version) {
|
|
12772
13760
|
if (platform2.kind === "host") {
|
|
12773
13761
|
await platform2.write(
|
|
@@ -12794,16 +13782,56 @@ async function runBridge(platform2, args, version) {
|
|
|
12794
13782
|
if (flags.bearer) {
|
|
12795
13783
|
await platform2.write(
|
|
12796
13784
|
2,
|
|
12797
|
-
"mcpcat add --bridge: --bearer is not
|
|
13785
|
+
"mcpcat add --bridge: --bearer is not supported for bridge; authentication is automatic via the vault\n"
|
|
12798
13786
|
);
|
|
12799
13787
|
return 2;
|
|
12800
13788
|
}
|
|
12801
13789
|
const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
|
|
12802
13790
|
serveOptions.proxyCommand = flags.cmdArgs;
|
|
12803
13791
|
const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
|
|
13792
|
+
if (flags.noAuth) {
|
|
13793
|
+
await platform2.write(
|
|
13794
|
+
2,
|
|
13795
|
+
"mcpcat add --bridge: warning: running without authentication on loopback\n"
|
|
13796
|
+
);
|
|
13797
|
+
serveOptions.noAuth = true;
|
|
13798
|
+
serveOptions.onListening = async (actualPort) => {
|
|
13799
|
+
const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
|
|
13800
|
+
const httpConfig = { url: bridgeUrl };
|
|
13801
|
+
if (flags.description) httpConfig.description = flags.description;
|
|
13802
|
+
if (flags.exposure) httpConfig.exposure = flags.exposure;
|
|
13803
|
+
const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
|
|
13804
|
+
await platform2.write(
|
|
13805
|
+
1,
|
|
13806
|
+
`${replaced ? "replaced" : "added"} ${name} in ${path}
|
|
13807
|
+
bridge does not survive a reload; runs in the foreground (start with &)
|
|
13808
|
+
`
|
|
13809
|
+
);
|
|
13810
|
+
};
|
|
13811
|
+
return runServe(platform2, serveOptions, version);
|
|
13812
|
+
}
|
|
13813
|
+
const token = generateBridgeToken();
|
|
13814
|
+
const secretName = `MCP_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_TOKEN`;
|
|
13815
|
+
serveOptions._resolvedBearer = token;
|
|
12804
13816
|
serveOptions.onListening = async (actualPort) => {
|
|
13817
|
+
if (actualPort === 9222) {
|
|
13818
|
+
await platform2.write(2, "mcpcat add --bridge: port 9222 is reserved; please retry\n");
|
|
13819
|
+
return;
|
|
13820
|
+
}
|
|
12805
13821
|
const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
|
|
12806
|
-
|
|
13822
|
+
try {
|
|
13823
|
+
await sealSecret(platform2, secretName, token, { server: name, url: bridgeUrl });
|
|
13824
|
+
} catch (err) {
|
|
13825
|
+
const errno = err.sealErrno ?? "EACCES";
|
|
13826
|
+
const code = sealExitCode(errno);
|
|
13827
|
+
await platform2.write(2, `mcpcat add --bridge: ${sealErrorMessage(errno, secretName)}
|
|
13828
|
+
`);
|
|
13829
|
+
platform2.exit(code);
|
|
13830
|
+
}
|
|
13831
|
+
const httpConfig = {
|
|
13832
|
+
url: bridgeUrl,
|
|
13833
|
+
headers: { Authorization: "Bearer ${" + secretName + "}" }
|
|
13834
|
+
};
|
|
12807
13835
|
if (flags.description) httpConfig.description = flags.description;
|
|
12808
13836
|
if (flags.exposure) httpConfig.exposure = flags.exposure;
|
|
12809
13837
|
const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
|
|
@@ -12940,8 +13968,18 @@ var CONFIG_VERBS = {
|
|
|
12940
13968
|
exposure: true,
|
|
12941
13969
|
import: true
|
|
12942
13970
|
};
|
|
12943
|
-
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["
|
|
12944
|
-
var CLIENT_VERBS = /* @__PURE__ */ new Set([
|
|
13971
|
+
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["complete", "task"]);
|
|
13972
|
+
var CLIENT_VERBS = /* @__PURE__ */ new Set([
|
|
13973
|
+
"info",
|
|
13974
|
+
"tools",
|
|
13975
|
+
"call",
|
|
13976
|
+
"describe",
|
|
13977
|
+
"serve",
|
|
13978
|
+
"resources",
|
|
13979
|
+
"read",
|
|
13980
|
+
"prompts",
|
|
13981
|
+
"prompt"
|
|
13982
|
+
]);
|
|
12945
13983
|
function helpText(version) {
|
|
12946
13984
|
return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
|
|
12947
13985
|
|
|
@@ -12980,6 +14018,7 @@ Options:
|
|
|
12980
14018
|
-H, --header 'N: V' Extra header
|
|
12981
14019
|
-d <json> Tool arguments as JSON (or @file, @- for stdin)
|
|
12982
14020
|
-m, --timeout <s> Request timeout (default 60)
|
|
14021
|
+
--memory <MiB> Code mode memory limit (default 256, max 1024)
|
|
12983
14022
|
--protocol-version <v> Pin protocol version
|
|
12984
14023
|
--schema Print raw JSON Schema (describe)
|
|
12985
14024
|
--ts TypeScript declarations (describe)
|
|
@@ -12996,6 +14035,31 @@ Options:
|
|
|
12996
14035
|
-h, --help Show this help
|
|
12997
14036
|
`;
|
|
12998
14037
|
}
|
|
14038
|
+
var VERBS_WITH_TOOL = /* @__PURE__ */ new Set(["call", "prompt", "describe", "read"]);
|
|
14039
|
+
var VERBS_WITH_TARGET = /* @__PURE__ */ new Set([
|
|
14040
|
+
"info",
|
|
14041
|
+
"tools",
|
|
14042
|
+
"call",
|
|
14043
|
+
"describe",
|
|
14044
|
+
"serve",
|
|
14045
|
+
"grep",
|
|
14046
|
+
"code",
|
|
14047
|
+
"discover",
|
|
14048
|
+
"login",
|
|
14049
|
+
"logout",
|
|
14050
|
+
"resources",
|
|
14051
|
+
"read",
|
|
14052
|
+
"prompts",
|
|
14053
|
+
"prompt"
|
|
14054
|
+
]);
|
|
14055
|
+
function resolveMaxPositional(positional) {
|
|
14056
|
+
if (positional.length === 0) return 3;
|
|
14057
|
+
const first = positional[0];
|
|
14058
|
+
if (!VERBS.includes(first)) return 2;
|
|
14059
|
+
if (VERBS_WITH_TOOL.has(first)) return 3;
|
|
14060
|
+
if (VERBS_WITH_TARGET.has(first)) return 2;
|
|
14061
|
+
return 2;
|
|
14062
|
+
}
|
|
12999
14063
|
function parseFlags(args) {
|
|
13000
14064
|
const headers = {};
|
|
13001
14065
|
let dataArg;
|
|
@@ -13010,10 +14074,16 @@ function parseFlags(args) {
|
|
|
13010
14074
|
const serverTargets = [];
|
|
13011
14075
|
let allowShell = false;
|
|
13012
14076
|
let timeoutMs;
|
|
14077
|
+
let memoryLimitMiB;
|
|
13013
14078
|
let verbose = false;
|
|
13014
14079
|
let silent = false;
|
|
13015
14080
|
const positional = [];
|
|
13016
14081
|
let trailingCommand;
|
|
14082
|
+
let respondArg;
|
|
14083
|
+
const roots = [];
|
|
14084
|
+
let outputPath;
|
|
14085
|
+
let noValidate = false;
|
|
14086
|
+
const rest = [];
|
|
13017
14087
|
for (let i = 0; i < args.length; i++) {
|
|
13018
14088
|
const arg = args[i];
|
|
13019
14089
|
if (arg === "--") {
|
|
@@ -13054,8 +14124,41 @@ function parseFlags(args) {
|
|
|
13054
14124
|
silent = true;
|
|
13055
14125
|
} else if ((arg === "-m" || arg === "--timeout") && args[i + 1]) {
|
|
13056
14126
|
timeoutMs = Number(args[++i]) * 1e3;
|
|
14127
|
+
} else if (arg === "--respond" && args[i + 1]) {
|
|
14128
|
+
respondArg = args[++i];
|
|
14129
|
+
} else if (arg === "--root" && args[i + 1]) {
|
|
14130
|
+
roots.push(args[++i]);
|
|
14131
|
+
} else if ((arg === "-o" || arg === "--output") && args[i + 1]) {
|
|
14132
|
+
outputPath = args[++i];
|
|
14133
|
+
} else if (arg === "--memory" && args[i + 1]) {
|
|
14134
|
+
memoryLimitMiB = Number(args[++i]);
|
|
14135
|
+
} else if (arg === "--no-validate") {
|
|
14136
|
+
noValidate = true;
|
|
13057
14137
|
} else if (!arg.startsWith("-")) {
|
|
13058
14138
|
positional.push(arg);
|
|
14139
|
+
const maxPositional = resolveMaxPositional(positional);
|
|
14140
|
+
if (positional.length >= maxPositional) {
|
|
14141
|
+
rest.push(...args.slice(i + 1));
|
|
14142
|
+
break;
|
|
14143
|
+
}
|
|
14144
|
+
}
|
|
14145
|
+
}
|
|
14146
|
+
const toolArgs = [];
|
|
14147
|
+
for (let j = 0; j < rest.length; j++) {
|
|
14148
|
+
const ra = rest[j];
|
|
14149
|
+
if (ra === "-d" && rest[j + 1] !== void 0) {
|
|
14150
|
+
dataArg = rest[++j];
|
|
14151
|
+
} else if (ra === "--json") {
|
|
14152
|
+
jsonOutput = true;
|
|
14153
|
+
} else if (ra === "--no-validate") {
|
|
14154
|
+
noValidate = true;
|
|
14155
|
+
} else if ((ra === "-o" || ra === "--output") && rest[j + 1] !== void 0) {
|
|
14156
|
+
outputPath = rest[++j];
|
|
14157
|
+
} else if (ra === "--help" || ra === "-h") {
|
|
14158
|
+
toolArgs.length = 0;
|
|
14159
|
+
break;
|
|
14160
|
+
} else {
|
|
14161
|
+
toolArgs.push(ra);
|
|
13059
14162
|
}
|
|
13060
14163
|
}
|
|
13061
14164
|
return {
|
|
@@ -13072,10 +14175,16 @@ function parseFlags(args) {
|
|
|
13072
14175
|
serverTargets,
|
|
13073
14176
|
allowShell,
|
|
13074
14177
|
timeoutMs,
|
|
14178
|
+
memoryLimitMiB,
|
|
13075
14179
|
verbose,
|
|
13076
14180
|
silent,
|
|
13077
14181
|
positional,
|
|
13078
|
-
trailingCommand
|
|
14182
|
+
trailingCommand,
|
|
14183
|
+
respondArg,
|
|
14184
|
+
roots,
|
|
14185
|
+
outputPath,
|
|
14186
|
+
noValidate,
|
|
14187
|
+
rest: toolArgs
|
|
13079
14188
|
};
|
|
13080
14189
|
}
|
|
13081
14190
|
function resolveVerb(positional, trailingCommand) {
|
|
@@ -13110,6 +14219,7 @@ function parseArgs(args) {
|
|
|
13110
14219
|
const resolved = resolveVerb(flags.positional, flags.trailingCommand);
|
|
13111
14220
|
return {
|
|
13112
14221
|
...resolved,
|
|
14222
|
+
toolArgs: flags.rest,
|
|
13113
14223
|
headers: flags.headers,
|
|
13114
14224
|
dataArg: flags.dataArg,
|
|
13115
14225
|
protocolVersion: flags.protocolVersion,
|
|
@@ -13123,8 +14233,13 @@ function parseArgs(args) {
|
|
|
13123
14233
|
serverTargets: flags.serverTargets,
|
|
13124
14234
|
allowShell: flags.allowShell,
|
|
13125
14235
|
timeoutMs: flags.timeoutMs,
|
|
14236
|
+
memoryLimitMiB: flags.memoryLimitMiB,
|
|
13126
14237
|
verbose: flags.verbose,
|
|
13127
|
-
silent: flags.silent
|
|
14238
|
+
silent: flags.silent,
|
|
14239
|
+
noValidate: flags.noValidate,
|
|
14240
|
+
respondArg: flags.respondArg,
|
|
14241
|
+
roots: flags.roots,
|
|
14242
|
+
outputPath: flags.outputPath
|
|
13128
14243
|
};
|
|
13129
14244
|
}
|
|
13130
14245
|
function formatToolLine(tool) {
|
|
@@ -13247,7 +14362,8 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
13247
14362
|
const tools = await conn.listTools();
|
|
13248
14363
|
const tool = tools.find((t) => t.name === toolName);
|
|
13249
14364
|
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
13250
|
-
const
|
|
14365
|
+
const skipRequired = opts.noValidate || !!dataArg;
|
|
14366
|
+
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
13251
14367
|
if (!c.ok) {
|
|
13252
14368
|
await writeErr(platform2, opts.json ?? false, "usage", c.error);
|
|
13253
14369
|
return EXIT_USAGE;
|
|
@@ -13256,8 +14372,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
13256
14372
|
}
|
|
13257
14373
|
return renderCallResult(
|
|
13258
14374
|
platform2,
|
|
13259
|
-
await conn.callTool(toolName, fin, {
|
|
13260
|
-
|
|
14375
|
+
await conn.callTool(toolName, fin, {
|
|
14376
|
+
onProgress: opts.onProgress,
|
|
14377
|
+
inputResponses: opts.inputResponses
|
|
14378
|
+
}),
|
|
14379
|
+
{ json: opts.json, outputPath: opts.outputPath }
|
|
13261
14380
|
);
|
|
13262
14381
|
}
|
|
13263
14382
|
async function runDescribe(platform2, conn, toolName, showSchema, json) {
|
|
@@ -13294,19 +14413,168 @@ async function runDescribe(platform2, conn, toolName, showSchema, json) {
|
|
|
13294
14413
|
}
|
|
13295
14414
|
return EXIT_OK;
|
|
13296
14415
|
}
|
|
13297
|
-
async function
|
|
14416
|
+
async function runResources(platform2, conn, json) {
|
|
14417
|
+
const resources = await conn.listResources();
|
|
14418
|
+
const templates = await conn.listResourceTemplates();
|
|
14419
|
+
if (json) {
|
|
14420
|
+
await renderResourcesJson(platform2, resources, templates);
|
|
14421
|
+
return EXIT_OK;
|
|
14422
|
+
}
|
|
14423
|
+
if (resources.length === 0 && templates.length === 0) {
|
|
14424
|
+
await platform2.write(1, "(no resources)\n");
|
|
14425
|
+
return EXIT_OK;
|
|
14426
|
+
}
|
|
14427
|
+
for (const r of resources) {
|
|
14428
|
+
const desc = r.description ? ` ${r.description}` : "";
|
|
14429
|
+
const mime = r.mimeType ? ` [${r.mimeType}]` : "";
|
|
14430
|
+
await platform2.write(1, `${r.uri} ${r.name}${mime}${desc}
|
|
14431
|
+
`);
|
|
14432
|
+
}
|
|
14433
|
+
if (templates.length > 0) {
|
|
14434
|
+
if (resources.length > 0) await platform2.write(1, "\n");
|
|
14435
|
+
await platform2.write(1, "Templates:\n");
|
|
14436
|
+
for (const t of templates) {
|
|
14437
|
+
const desc = t.description ? ` ${t.description}` : "";
|
|
14438
|
+
await platform2.write(1, ` ${t.uriTemplate} ${t.name}${desc}
|
|
14439
|
+
`);
|
|
14440
|
+
}
|
|
14441
|
+
}
|
|
14442
|
+
return EXIT_OK;
|
|
14443
|
+
}
|
|
14444
|
+
async function runRead(platform2, conn, uri, json, outputPath) {
|
|
14445
|
+
const result = await conn.readResource(uri);
|
|
14446
|
+
if (json) {
|
|
14447
|
+
await renderReadResourceJson(platform2, result);
|
|
14448
|
+
return EXIT_OK;
|
|
14449
|
+
}
|
|
14450
|
+
for (const content of result.contents) {
|
|
14451
|
+
if ("text" in content) {
|
|
14452
|
+
if (outputPath) {
|
|
14453
|
+
await platform2.fs.writeFile(outputPath, content.text);
|
|
14454
|
+
await platform2.write(1, `${outputPath}
|
|
14455
|
+
`);
|
|
14456
|
+
} else {
|
|
14457
|
+
await platform2.write(1, `${content.text}
|
|
14458
|
+
`);
|
|
14459
|
+
}
|
|
14460
|
+
} else if ("blob" in content) {
|
|
14461
|
+
if (outputPath) {
|
|
14462
|
+
const bytes = Uint8Array.from(atob(content.blob), (c) => c.charCodeAt(0));
|
|
14463
|
+
await platform2.fs.writeFile(outputPath, bytes);
|
|
14464
|
+
await platform2.write(1, `${outputPath}
|
|
14465
|
+
`);
|
|
14466
|
+
} else {
|
|
14467
|
+
await platform2.write(1, `${content.blob}
|
|
14468
|
+
`);
|
|
14469
|
+
}
|
|
14470
|
+
}
|
|
14471
|
+
}
|
|
14472
|
+
return EXIT_OK;
|
|
14473
|
+
}
|
|
14474
|
+
async function runPrompts(platform2, conn, json) {
|
|
14475
|
+
const prompts = await conn.listPrompts();
|
|
14476
|
+
if (json) {
|
|
14477
|
+
await renderPromptsJson(platform2, prompts);
|
|
14478
|
+
return EXIT_OK;
|
|
14479
|
+
}
|
|
14480
|
+
if (prompts.length === 0) {
|
|
14481
|
+
await platform2.write(1, "(no prompts)\n");
|
|
14482
|
+
return EXIT_OK;
|
|
14483
|
+
}
|
|
14484
|
+
for (const p of prompts) {
|
|
14485
|
+
const desc = p.description ? ` ${p.description}` : "";
|
|
14486
|
+
await platform2.write(1, `${p.name}${desc}
|
|
14487
|
+
`);
|
|
14488
|
+
}
|
|
14489
|
+
return EXIT_OK;
|
|
14490
|
+
}
|
|
14491
|
+
async function runPrompt(platform2, conn, name, dataArg, toolArgs, json) {
|
|
14492
|
+
let args = {};
|
|
14493
|
+
if (dataArg) {
|
|
14494
|
+
args = await readDataArg(platform2, dataArg);
|
|
14495
|
+
}
|
|
14496
|
+
const prompts = await conn.listPrompts();
|
|
14497
|
+
const prompt = prompts.find((p) => p.name === name);
|
|
14498
|
+
const schema = {
|
|
14499
|
+
type: "object",
|
|
14500
|
+
properties: {},
|
|
14501
|
+
required: []
|
|
14502
|
+
};
|
|
14503
|
+
if (prompt?.arguments) {
|
|
14504
|
+
const props = {};
|
|
14505
|
+
const req = [];
|
|
14506
|
+
for (const a of prompt.arguments) {
|
|
14507
|
+
props[a.name] = { type: "string", description: a.description };
|
|
14508
|
+
if (a.required) req.push(a.name);
|
|
14509
|
+
}
|
|
14510
|
+
schema.properties = props;
|
|
14511
|
+
schema.required = req;
|
|
14512
|
+
}
|
|
14513
|
+
if (toolArgs.length > 0) {
|
|
14514
|
+
const skipRequired = !!dataArg;
|
|
14515
|
+
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
14516
|
+
if (!c.ok) {
|
|
14517
|
+
await writeErr(platform2, json, "usage", c.error);
|
|
14518
|
+
return EXIT_USAGE;
|
|
14519
|
+
}
|
|
14520
|
+
args = mergeArgs(args, c.value);
|
|
14521
|
+
} else if (!dataArg) {
|
|
14522
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
14523
|
+
for (const r of required) {
|
|
14524
|
+
if (!Object.hasOwn(args, r)) {
|
|
14525
|
+
await writeErr(platform2, json, "usage", `missing required flag --${r}`);
|
|
14526
|
+
return EXIT_USAGE;
|
|
14527
|
+
}
|
|
14528
|
+
}
|
|
14529
|
+
}
|
|
14530
|
+
const result = await conn.getPrompt(name, args);
|
|
14531
|
+
if (json) {
|
|
14532
|
+
await renderPromptResultJson(platform2, result);
|
|
14533
|
+
return EXIT_OK;
|
|
14534
|
+
}
|
|
14535
|
+
if (result.description) {
|
|
14536
|
+
await platform2.write(1, `${result.description}
|
|
14537
|
+
|
|
14538
|
+
`);
|
|
14539
|
+
}
|
|
14540
|
+
for (const msg of result.messages) {
|
|
14541
|
+
const text = msg.content?.text ?? (msg.content?.data ? `[${msg.content.mimeType ?? "binary"}]` : "");
|
|
14542
|
+
await platform2.write(1, `[${msg.role}] ${text}
|
|
14543
|
+
`);
|
|
14544
|
+
}
|
|
14545
|
+
return EXIT_OK;
|
|
14546
|
+
}
|
|
14547
|
+
async function runVerb(platform2, parsed, conn, onProgress, respondData) {
|
|
13298
14548
|
switch (parsed.verb) {
|
|
13299
14549
|
case "info":
|
|
13300
14550
|
return runInfo(platform2, conn, parsed.jsonOutput);
|
|
13301
14551
|
case "tools":
|
|
13302
14552
|
return runTools(platform2, conn, parsed.jsonOutput);
|
|
13303
14553
|
case "call":
|
|
13304
|
-
return runCall(platform2, conn, parsed.toolName, parsed.dataArg,
|
|
14554
|
+
return runCall(platform2, conn, parsed.toolName, parsed.dataArg, parsed.toolArgs, {
|
|
13305
14555
|
json: parsed.jsonOutput,
|
|
13306
|
-
|
|
14556
|
+
noValidate: parsed.noValidate,
|
|
14557
|
+
outputPath: parsed.outputPath,
|
|
14558
|
+
onProgress,
|
|
14559
|
+
inputResponses: respondData
|
|
13307
14560
|
});
|
|
13308
14561
|
case "describe":
|
|
13309
14562
|
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
|
|
14563
|
+
case "resources":
|
|
14564
|
+
return runResources(platform2, conn, parsed.jsonOutput);
|
|
14565
|
+
case "read":
|
|
14566
|
+
return runRead(platform2, conn, parsed.toolName, parsed.jsonOutput, parsed.outputPath);
|
|
14567
|
+
case "prompts":
|
|
14568
|
+
return runPrompts(platform2, conn, parsed.jsonOutput);
|
|
14569
|
+
case "prompt":
|
|
14570
|
+
return runPrompt(
|
|
14571
|
+
platform2,
|
|
14572
|
+
conn,
|
|
14573
|
+
parsed.toolName,
|
|
14574
|
+
parsed.dataArg,
|
|
14575
|
+
parsed.toolArgs,
|
|
14576
|
+
parsed.jsonOutput
|
|
14577
|
+
);
|
|
13310
14578
|
default:
|
|
13311
14579
|
await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
|
|
13312
14580
|
`);
|
|
@@ -13444,6 +14712,18 @@ async function resolveTargetNames(platform2, parsed) {
|
|
|
13444
14712
|
return config.list().filter((e) => e.config.enabled !== false).map((e) => e.name);
|
|
13445
14713
|
}
|
|
13446
14714
|
async function dispatchMultiTarget(platform2, parsed, version) {
|
|
14715
|
+
let activeCleanup;
|
|
14716
|
+
async function killTreeAndExit(exitCode) {
|
|
14717
|
+
if (activeCleanup) {
|
|
14718
|
+
try {
|
|
14719
|
+
await Promise.race([activeCleanup(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
14720
|
+
} catch {
|
|
14721
|
+
}
|
|
14722
|
+
}
|
|
14723
|
+
platform2.exit(exitCode);
|
|
14724
|
+
}
|
|
14725
|
+
platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
|
|
14726
|
+
platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
|
|
13447
14727
|
const config = await MergedConfigSource.load(platform2);
|
|
13448
14728
|
if (parsed.verb === "code") {
|
|
13449
14729
|
let source;
|
|
@@ -13475,10 +14755,12 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13475
14755
|
parsed,
|
|
13476
14756
|
version
|
|
13477
14757
|
);
|
|
14758
|
+
activeCleanup = cleanup;
|
|
13478
14759
|
try {
|
|
13479
14760
|
return await runCode(targets, source, {
|
|
13480
14761
|
json: parsed.jsonOutput,
|
|
13481
14762
|
timeoutMs: parsed.timeoutMs,
|
|
14763
|
+
memoryLimitMiB: parsed.memoryLimitMiB,
|
|
13482
14764
|
allowShell: parsed.allowShell,
|
|
13483
14765
|
platform: platform2
|
|
13484
14766
|
});
|
|
@@ -13504,6 +14786,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13504
14786
|
parsed,
|
|
13505
14787
|
version
|
|
13506
14788
|
);
|
|
14789
|
+
activeCleanup = cleanup;
|
|
13507
14790
|
try {
|
|
13508
14791
|
return await runGrep(platform2, pattern, targets, {
|
|
13509
14792
|
rank: parsed.rank,
|
|
@@ -13522,6 +14805,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13522
14805
|
parsed,
|
|
13523
14806
|
version
|
|
13524
14807
|
);
|
|
14808
|
+
activeCleanup = cleanup;
|
|
13525
14809
|
try {
|
|
13526
14810
|
return await runDescribeTs(platform2, targets, parsed.tsBudget);
|
|
13527
14811
|
} finally {
|
|
@@ -13531,6 +14815,18 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13531
14815
|
return 2;
|
|
13532
14816
|
}
|
|
13533
14817
|
async function run(platform2, version, args) {
|
|
14818
|
+
try {
|
|
14819
|
+
return await runInner(platform2, version, args);
|
|
14820
|
+
} catch (err) {
|
|
14821
|
+
if (err.code === "EACCES") {
|
|
14822
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
14823
|
+
`);
|
|
14824
|
+
return EXIT_USAGE;
|
|
14825
|
+
}
|
|
14826
|
+
throw err;
|
|
14827
|
+
}
|
|
14828
|
+
}
|
|
14829
|
+
async function runInner(platform2, version, args) {
|
|
13534
14830
|
if (args.includes("--version") || args.includes("-V")) {
|
|
13535
14831
|
await platform2.write(1, `${version}
|
|
13536
14832
|
`);
|
|
@@ -13557,11 +14853,11 @@ async function run(platform2, version, args) {
|
|
|
13557
14853
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args);
|
|
13558
14854
|
if (discResult !== void 0) return discResult;
|
|
13559
14855
|
if (parsed.verb === "login") {
|
|
13560
|
-
const source = createTokenSource(platform2);
|
|
14856
|
+
const source = await createTokenSource(platform2);
|
|
13561
14857
|
return runLogin(platform2, args.slice(1), source);
|
|
13562
14858
|
}
|
|
13563
14859
|
if (parsed.verb === "logout") {
|
|
13564
|
-
const source = createTokenSource(platform2);
|
|
14860
|
+
const source = await createTokenSource(platform2);
|
|
13565
14861
|
return runLogout(platform2, args.slice(1), source);
|
|
13566
14862
|
}
|
|
13567
14863
|
if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
|
|
@@ -13584,6 +14880,14 @@ async function run(platform2, version, args) {
|
|
|
13584
14880
|
await platform2.write(2, "mcpcat: call requires a tool name\n");
|
|
13585
14881
|
return 2;
|
|
13586
14882
|
}
|
|
14883
|
+
if (parsed.verb === "read" && !parsed.toolName) {
|
|
14884
|
+
await platform2.write(2, "mcpcat: read requires a URI\n");
|
|
14885
|
+
return 2;
|
|
14886
|
+
}
|
|
14887
|
+
if (parsed.verb === "prompt" && !parsed.toolName) {
|
|
14888
|
+
await platform2.write(2, "mcpcat: prompt requires a name\n");
|
|
14889
|
+
return 2;
|
|
14890
|
+
}
|
|
13587
14891
|
if (!parsed.target) {
|
|
13588
14892
|
await platform2.write(2, `mcpcat: ${parsed.verb} requires a target
|
|
13589
14893
|
`);
|
|
@@ -13594,13 +14898,19 @@ async function run(platform2, version, args) {
|
|
|
13594
14898
|
function expandAndCollect(headers, env2) {
|
|
13595
14899
|
const secrets = [];
|
|
13596
14900
|
const result = {};
|
|
14901
|
+
const unset = [];
|
|
13597
14902
|
for (const [k, v] of Object.entries(headers)) {
|
|
14903
|
+
const missing = missingPlaceholders(v, env2);
|
|
14904
|
+
if (missing.length > 0) {
|
|
14905
|
+
unset.push(...missing);
|
|
14906
|
+
continue;
|
|
14907
|
+
}
|
|
13598
14908
|
const e = expandPlaceholders(v, env2);
|
|
13599
14909
|
result[k] = e;
|
|
13600
14910
|
if (e !== v) secrets.push(e);
|
|
13601
14911
|
if (v.length > 0) secrets.push(v);
|
|
13602
14912
|
}
|
|
13603
|
-
return { expanded: result, secrets };
|
|
14913
|
+
return { expanded: result, secrets, unset };
|
|
13604
14914
|
}
|
|
13605
14915
|
function classifyError(err) {
|
|
13606
14916
|
if (err instanceof InputRequiredError) {
|
|
@@ -13612,6 +14922,9 @@ function classifyError(err) {
|
|
|
13612
14922
|
if (err instanceof McpAuthError) {
|
|
13613
14923
|
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
13614
14924
|
}
|
|
14925
|
+
if (err instanceof JerkSignInRequiredError) {
|
|
14926
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
14927
|
+
}
|
|
13615
14928
|
if (err instanceof AuthStepUpError) {
|
|
13616
14929
|
const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
|
|
13617
14930
|
return {
|
|
@@ -13633,25 +14946,112 @@ function classifyError(err) {
|
|
|
13633
14946
|
}
|
|
13634
14947
|
return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
|
|
13635
14948
|
}
|
|
13636
|
-
function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
14949
|
+
async function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
13637
14950
|
if (hasAuthHeader || !url) return void 0;
|
|
13638
|
-
const source = createTokenSource(platform2);
|
|
14951
|
+
const source = await createTokenSource(platform2);
|
|
13639
14952
|
const entry = config.entry(target);
|
|
13640
14953
|
const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
|
|
13641
14954
|
const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
|
|
13642
14955
|
return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
|
|
13643
14956
|
}
|
|
14957
|
+
function classifyConnectionError(err, target, interrupted) {
|
|
14958
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError || err instanceof JerkSignInRequiredError) {
|
|
14959
|
+
return classifyError(err);
|
|
14960
|
+
}
|
|
14961
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
14962
|
+
return { code: EXIT_AUTH, kind: "auth", msg: `unauthorized. Run: mcpcat login ${target}` };
|
|
14963
|
+
}
|
|
14964
|
+
if (interrupted) {
|
|
14965
|
+
return { code: EXIT_INTERRUPTED, kind: "interrupted", msg: "interrupted" };
|
|
14966
|
+
}
|
|
14967
|
+
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
14968
|
+
return { code: EXIT_CONNECTION, kind: "connection", msg };
|
|
14969
|
+
}
|
|
14970
|
+
function buildRoots(rootArgs, isStdio, cwd) {
|
|
14971
|
+
const roots = rootArgs.map((r) => ({
|
|
14972
|
+
uri: r.startsWith("file://") ? r : `file://${r}`
|
|
14973
|
+
}));
|
|
14974
|
+
if (roots.length === 0 && isStdio) {
|
|
14975
|
+
roots.push({ uri: `file://${cwd}` });
|
|
14976
|
+
}
|
|
14977
|
+
return roots.length > 0 ? roots : void 0;
|
|
14978
|
+
}
|
|
14979
|
+
async function loadRespondData(platform2, respondArg, json) {
|
|
14980
|
+
if (!respondArg) return { data: void 0 };
|
|
14981
|
+
try {
|
|
14982
|
+
return { data: await readDataArg(platform2, respondArg) };
|
|
14983
|
+
} catch (err) {
|
|
14984
|
+
await writeErr(
|
|
14985
|
+
platform2,
|
|
14986
|
+
json,
|
|
14987
|
+
"usage",
|
|
14988
|
+
`invalid --respond argument: ${err.message}`
|
|
14989
|
+
);
|
|
14990
|
+
return { exitCode: EXIT_USAGE };
|
|
14991
|
+
}
|
|
14992
|
+
}
|
|
14993
|
+
async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
14994
|
+
const parsed2 = JSON.parse(err.requestJson);
|
|
14995
|
+
const inputReq = parseInputRequest(parsed2.result ?? parsed2);
|
|
14996
|
+
if (!inputReq) {
|
|
14997
|
+
await platform2.write(2, `${err.requestJson}
|
|
14998
|
+
`);
|
|
14999
|
+
return EXIT_INPUT_REQUIRED;
|
|
15000
|
+
}
|
|
15001
|
+
const isTty = platform2.isatty(0);
|
|
15002
|
+
const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
|
|
15003
|
+
if ("exitCode" in elicitResult) {
|
|
15004
|
+
await platform2.write(2, `${err.requestJson}
|
|
15005
|
+
`);
|
|
15006
|
+
return EXIT_INPUT_REQUIRED;
|
|
15007
|
+
}
|
|
15008
|
+
try {
|
|
15009
|
+
const retryResult = await conn.callTool(parsed2.name, {
|
|
15010
|
+
...parsed2.arguments,
|
|
15011
|
+
_meta: { inputResponses: elicitResult.answers }
|
|
15012
|
+
});
|
|
15013
|
+
return renderCallResult(platform2, retryResult, { json });
|
|
15014
|
+
} catch (retryErr) {
|
|
15015
|
+
const e2 = classifyError(retryErr);
|
|
15016
|
+
await writeErr(platform2, json, e2.kind, e2.msg);
|
|
15017
|
+
return e2.code;
|
|
15018
|
+
}
|
|
15019
|
+
}
|
|
13644
15020
|
async function runClientVerb(platform2, version, parsed) {
|
|
13645
|
-
const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
|
|
15021
|
+
const { expanded, secrets, unset } = expandAndCollect(parsed.headers, platform2.env);
|
|
15022
|
+
if (unset.length > 0) {
|
|
15023
|
+
await platform2.write(
|
|
15024
|
+
2,
|
|
15025
|
+
`mcpcat: unset variable${unset.length > 1 ? "s" : ""} in -H: ${unset.join(", ")}
|
|
15026
|
+
`
|
|
15027
|
+
);
|
|
15028
|
+
return EXIT_USAGE;
|
|
15029
|
+
}
|
|
13646
15030
|
const verbose = createVerboseLogger(platform2, parsed.verbose);
|
|
13647
15031
|
for (const s of secrets) verbose.secrets.add(s);
|
|
13648
15032
|
const timeoutMs = parsed.timeoutMs;
|
|
13649
15033
|
const sigintController = new AbortController();
|
|
13650
15034
|
let interrupted = false;
|
|
15035
|
+
let termExitCode = EXIT_INTERRUPTED;
|
|
15036
|
+
let activeConn;
|
|
15037
|
+
async function killTreeAndExit(exitCode) {
|
|
15038
|
+
interrupted = true;
|
|
15039
|
+
termExitCode = exitCode;
|
|
15040
|
+
sigintController.abort();
|
|
15041
|
+
if (activeConn) {
|
|
15042
|
+
try {
|
|
15043
|
+
await Promise.race([activeConn.close(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
15044
|
+
} catch {
|
|
15045
|
+
}
|
|
15046
|
+
}
|
|
15047
|
+
platform2.exit(exitCode);
|
|
15048
|
+
}
|
|
13651
15049
|
platform2.onSignal?.("SIGINT", () => {
|
|
13652
15050
|
interrupted = true;
|
|
13653
15051
|
sigintController.abort();
|
|
13654
15052
|
});
|
|
15053
|
+
platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
|
|
15054
|
+
platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
|
|
13655
15055
|
const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
|
|
13656
15056
|
const onProgress = (progress, total, message) => {
|
|
13657
15057
|
if (!showProgress) return;
|
|
@@ -13670,50 +15070,91 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
13670
15070
|
await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
|
|
13671
15071
|
return EXIT_USAGE;
|
|
13672
15072
|
}
|
|
13673
|
-
const
|
|
13674
|
-
|
|
15073
|
+
const configHeaders = expandHeaders(resolved.headers, platform2.env, (msg) => {
|
|
15074
|
+
platform2.write(2, `mcpcat: warning: ${msg}
|
|
15075
|
+
`).catch(() => {
|
|
15076
|
+
});
|
|
15077
|
+
});
|
|
15078
|
+
const configSecrets = collectConfigSecrets(resolved.headers, platform2.env);
|
|
15079
|
+
for (const s of configSecrets) verbose.secrets.add(s);
|
|
15080
|
+
const allSecrets = /* @__PURE__ */ new Set([...secrets, ...configSecrets]);
|
|
15081
|
+
const redact2 = (text) => {
|
|
15082
|
+
let result = text;
|
|
15083
|
+
for (const secret of allSecrets) {
|
|
15084
|
+
if (secret.length > 0) {
|
|
15085
|
+
while (result.includes(secret)) {
|
|
15086
|
+
result = result.replace(secret, "[REDACTED]");
|
|
15087
|
+
}
|
|
15088
|
+
}
|
|
15089
|
+
}
|
|
15090
|
+
return result;
|
|
15091
|
+
};
|
|
15092
|
+
const mergedHeaders = { ...configHeaders, ...expanded };
|
|
15093
|
+
const hasAuthHeader = "Authorization" in mergedHeaders;
|
|
15094
|
+
const authProvider = await resolveAuthProvider(
|
|
15095
|
+
platform2,
|
|
15096
|
+
hasAuthHeader,
|
|
15097
|
+
resolved.url,
|
|
15098
|
+
target,
|
|
15099
|
+
config
|
|
15100
|
+
);
|
|
15101
|
+
const roots = buildRoots(parsed.roots, !!resolved.command, platform2.cwd);
|
|
15102
|
+
let respondData;
|
|
15103
|
+
if (parsed.respondArg) {
|
|
15104
|
+
const loaded = await loadRespondData(platform2, parsed.respondArg, parsed.jsonOutput);
|
|
15105
|
+
if ("exitCode" in loaded) return loaded.exitCode;
|
|
15106
|
+
respondData = loaded.data;
|
|
15107
|
+
}
|
|
13675
15108
|
let conn;
|
|
13676
15109
|
try {
|
|
13677
15110
|
conn = await connect(platform2, {
|
|
13678
15111
|
...resolved,
|
|
13679
|
-
headers:
|
|
15112
|
+
headers: mergedHeaders,
|
|
13680
15113
|
protocolVersion: parsed.protocolVersion,
|
|
13681
15114
|
version,
|
|
13682
15115
|
timeoutMs,
|
|
13683
15116
|
signal: sigintController.signal,
|
|
13684
15117
|
verbose,
|
|
13685
|
-
authProvider
|
|
15118
|
+
authProvider,
|
|
15119
|
+
roots
|
|
13686
15120
|
});
|
|
15121
|
+
activeConn = conn;
|
|
13687
15122
|
} catch (err) {
|
|
13688
|
-
if (err
|
|
13689
|
-
|
|
13690
|
-
await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
13691
|
-
return EXIT_AUTH;
|
|
13692
|
-
}
|
|
13693
|
-
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
13694
|
-
await writeErr(
|
|
15123
|
+
if (isSpawnNotFound(err)) {
|
|
15124
|
+
await writeErrRedacted(
|
|
13695
15125
|
platform2,
|
|
13696
15126
|
parsed.jsonOutput,
|
|
13697
|
-
"
|
|
13698
|
-
`
|
|
15127
|
+
"connection",
|
|
15128
|
+
`command not found: ${resolved.command}`,
|
|
15129
|
+
redact2
|
|
13699
15130
|
);
|
|
13700
|
-
return
|
|
15131
|
+
return EXIT_CONNECTION;
|
|
15132
|
+
}
|
|
15133
|
+
const e = classifyConnectionError(err, target, interrupted);
|
|
15134
|
+
if (e.code === EXIT_INTERRUPTED) {
|
|
15135
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
15136
|
+
return termExitCode;
|
|
13701
15137
|
}
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
return EXIT_CONNECTION;
|
|
15138
|
+
await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
|
|
15139
|
+
return e.code;
|
|
13705
15140
|
}
|
|
13706
15141
|
try {
|
|
13707
|
-
|
|
15142
|
+
const code = await runVerb(platform2, parsed, conn, onProgress, respondData);
|
|
15143
|
+
if (interrupted) {
|
|
15144
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
15145
|
+
return termExitCode;
|
|
15146
|
+
}
|
|
15147
|
+
return code;
|
|
13708
15148
|
} catch (err) {
|
|
13709
15149
|
if (interrupted) {
|
|
13710
15150
|
await platform2.write(2, "mcpcat: interrupted\n");
|
|
13711
|
-
return
|
|
15151
|
+
return termExitCode;
|
|
15152
|
+
}
|
|
15153
|
+
if (err instanceof InputRequiredError) {
|
|
15154
|
+
return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
|
|
13712
15155
|
}
|
|
13713
15156
|
const e = classifyError(err);
|
|
13714
|
-
|
|
13715
|
-
`);
|
|
13716
|
-
else await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
15157
|
+
await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
|
|
13717
15158
|
return e.code;
|
|
13718
15159
|
} finally {
|
|
13719
15160
|
try {
|
|
@@ -13727,6 +15168,28 @@ async function writeErr(p, json, kind, msg) {
|
|
|
13727
15168
|
else await p.write(2, `mcpcat: ${msg}
|
|
13728
15169
|
`);
|
|
13729
15170
|
}
|
|
15171
|
+
async function writeErrRedacted(p, json, kind, msg, redact2) {
|
|
15172
|
+
if (json) await renderErrorJson(p, kind, msg, void 0, void 0, redact2);
|
|
15173
|
+
else await p.write(2, `mcpcat: ${redact2(msg)}
|
|
15174
|
+
`);
|
|
15175
|
+
}
|
|
15176
|
+
function collectConfigSecrets(headers, env2) {
|
|
15177
|
+
if (!headers) return [];
|
|
15178
|
+
const secrets = [];
|
|
15179
|
+
for (const [, v] of Object.entries(headers)) {
|
|
15180
|
+
const expanded = expandPlaceholders(v, env2);
|
|
15181
|
+
if (expanded !== v && expanded.length > 0) secrets.push(expanded);
|
|
15182
|
+
if (v.length > 0 && v !== expanded) secrets.push(v);
|
|
15183
|
+
}
|
|
15184
|
+
return secrets;
|
|
15185
|
+
}
|
|
15186
|
+
function isSpawnNotFound(err) {
|
|
15187
|
+
if (!(err instanceof Error)) return false;
|
|
15188
|
+
const code = err.code;
|
|
15189
|
+
if (code === "ENOENT" || code === "EACCES") return true;
|
|
15190
|
+
const msg = err.message;
|
|
15191
|
+
return msg.includes("ENOENT") || msg.includes("EACCES");
|
|
15192
|
+
}
|
|
13730
15193
|
|
|
13731
15194
|
// src/platform/kernel.ts
|
|
13732
15195
|
function kernelFs(ctx) {
|
|
@@ -13738,15 +15201,20 @@ function kernelFs(ctx) {
|
|
|
13738
15201
|
stat: (path) => ctx.fs.stat(path),
|
|
13739
15202
|
exists: (path) => ctx.fs.exists(path),
|
|
13740
15203
|
async mkdirExclusive(path) {
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
err.
|
|
15204
|
+
try {
|
|
15205
|
+
await ctx.fs.mkdir(path);
|
|
15206
|
+
} catch (err) {
|
|
15207
|
+
const msg = err.message ?? "";
|
|
15208
|
+
if (msg.includes("EEXIST") || msg.includes("already exists")) {
|
|
15209
|
+
const wrapped = new Error(`EEXIST: directory already exists, mkdir '${path}'`);
|
|
15210
|
+
wrapped.code = "EEXIST";
|
|
15211
|
+
throw wrapped;
|
|
15212
|
+
}
|
|
13745
15213
|
throw err;
|
|
13746
15214
|
}
|
|
13747
|
-
await ctx.fs.mkdir(path);
|
|
13748
15215
|
},
|
|
13749
|
-
rmdir: (path) => ctx.fs.rm(path)
|
|
15216
|
+
rmdir: (path) => ctx.fs.rm(path),
|
|
15217
|
+
unlink: (path) => ctx.fs.unlink(path)
|
|
13750
15218
|
};
|
|
13751
15219
|
}
|
|
13752
15220
|
async function kernelSpawn(ctx, options) {
|
|
@@ -13810,11 +15278,149 @@ async function kernelListen(ctx, options) {
|
|
|
13810
15278
|
}
|
|
13811
15279
|
};
|
|
13812
15280
|
}
|
|
15281
|
+
var KERNEL_LOCALHOST_RE = /^(\d+)\.kernel\.localhost$/;
|
|
15282
|
+
function extractLoopbackPort(url) {
|
|
15283
|
+
const m = KERNEL_LOCALHOST_RE.exec(url.hostname);
|
|
15284
|
+
if (m) return Number(m[1]);
|
|
15285
|
+
return null;
|
|
15286
|
+
}
|
|
15287
|
+
function collectHeaders(init) {
|
|
15288
|
+
const entries = [];
|
|
15289
|
+
if (!init?.headers) return entries;
|
|
15290
|
+
if (init.headers instanceof Headers) {
|
|
15291
|
+
for (const [k, v] of init.headers.entries()) entries.push([k, v]);
|
|
15292
|
+
} else if (Array.isArray(init.headers)) {
|
|
15293
|
+
for (const pair of init.headers) entries.push(pair);
|
|
15294
|
+
} else {
|
|
15295
|
+
for (const [k, v] of Object.entries(init.headers)) {
|
|
15296
|
+
if (v != null) entries.push([k, String(v)]);
|
|
15297
|
+
}
|
|
15298
|
+
}
|
|
15299
|
+
return entries;
|
|
15300
|
+
}
|
|
15301
|
+
function buildHttpRequest(method, url, headerEntries, bodyBytes) {
|
|
15302
|
+
const enc = new TextEncoder();
|
|
15303
|
+
const has = (n) => headerEntries.some(([k]) => k.toLowerCase() === n.toLowerCase());
|
|
15304
|
+
if (!has("host")) headerEntries.push(["Host", url.host]);
|
|
15305
|
+
if (!has("connection")) headerEntries.push(["Connection", "close"]);
|
|
15306
|
+
if (bodyBytes.length > 0 && !has("content-length")) {
|
|
15307
|
+
headerEntries.push(["Content-Length", String(bodyBytes.length)]);
|
|
15308
|
+
}
|
|
15309
|
+
const target = url.pathname + (url.search || "");
|
|
15310
|
+
let reqText = `${method} ${target} HTTP/1.1\r
|
|
15311
|
+
`;
|
|
15312
|
+
for (const [k, v] of headerEntries) reqText += `${k}: ${v}\r
|
|
15313
|
+
`;
|
|
15314
|
+
reqText += "\r\n";
|
|
15315
|
+
const headBytes = enc.encode(reqText);
|
|
15316
|
+
if (bodyBytes.length === 0) return headBytes;
|
|
15317
|
+
const full = new Uint8Array(headBytes.length + bodyBytes.length);
|
|
15318
|
+
full.set(headBytes);
|
|
15319
|
+
full.set(bodyBytes, headBytes.length);
|
|
15320
|
+
return full;
|
|
15321
|
+
}
|
|
15322
|
+
function parseHttpResponse(buf, headerPos, bufLen) {
|
|
15323
|
+
const dec = new TextDecoder();
|
|
15324
|
+
const headerText = dec.decode(new Uint8Array(buf.buffer, buf.byteOffset, headerPos));
|
|
15325
|
+
const lines = headerText.split("\r\n");
|
|
15326
|
+
const statusMatch = /^HTTP\/1\.1\s+(\d+)\s*(.*)/.exec(lines[0] || "");
|
|
15327
|
+
const status = statusMatch ? Number(statusMatch[1]) : 500;
|
|
15328
|
+
const statusText = statusMatch ? statusMatch[2] : "Error";
|
|
15329
|
+
const respHeaders = {};
|
|
15330
|
+
for (let i = 1; i < lines.length; i++) {
|
|
15331
|
+
const colonIdx = lines[i].indexOf(":");
|
|
15332
|
+
if (colonIdx > 0) {
|
|
15333
|
+
respHeaders[lines[i].slice(0, colonIdx).trim().toLowerCase()] = lines[i].slice(colonIdx + 1).trim();
|
|
15334
|
+
}
|
|
15335
|
+
}
|
|
15336
|
+
const contentLength = Number(respHeaders["content-length"] ?? 0);
|
|
15337
|
+
const headerEndByte = headerPos + 4;
|
|
15338
|
+
const responseBody = dec.decode(buf.slice(headerEndByte, headerEndByte + contentLength));
|
|
15339
|
+
return { status, statusText, headers: respHeaders, body: responseBody };
|
|
15340
|
+
}
|
|
15341
|
+
async function kernelLoopbackFetch(ctx, url, port, init) {
|
|
15342
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
15343
|
+
const enc = new TextEncoder();
|
|
15344
|
+
const dec = new TextDecoder();
|
|
15345
|
+
let bodyStr = "";
|
|
15346
|
+
if (init?.body != null) {
|
|
15347
|
+
bodyStr = typeof init.body === "string" ? init.body : dec.decode(init.body);
|
|
15348
|
+
}
|
|
15349
|
+
const bodyBytes = enc.encode(bodyStr);
|
|
15350
|
+
const headerEntries = collectHeaders(init);
|
|
15351
|
+
const reqBytes = buildHttpRequest(method, url, headerEntries, bodyBytes);
|
|
15352
|
+
const conn = await ctx.net.connect({ host: "127.0.0.1", port });
|
|
15353
|
+
const writer = conn.writable.getWriter();
|
|
15354
|
+
await writer.write(reqBytes);
|
|
15355
|
+
const reader = conn.readable.getReader();
|
|
15356
|
+
let buf = new Uint8Array(4096);
|
|
15357
|
+
let bufLen = 0;
|
|
15358
|
+
const headerResult = await readUntil(reader, buf, bufLen, "\r\n\r\n", 16384);
|
|
15359
|
+
if (!headerResult) {
|
|
15360
|
+
reader.releaseLock();
|
|
15361
|
+
try {
|
|
15362
|
+
await writer.close();
|
|
15363
|
+
} catch {
|
|
15364
|
+
}
|
|
15365
|
+
conn.close();
|
|
15366
|
+
throw new Error("incomplete HTTP response headers");
|
|
15367
|
+
}
|
|
15368
|
+
buf = headerResult.buf;
|
|
15369
|
+
bufLen = headerResult.bufLen;
|
|
15370
|
+
const contentLength = Number(
|
|
15371
|
+
dec.decode(new Uint8Array(buf.buffer, buf.byteOffset, headerResult.pos)).split("\r\n").find((l) => l.toLowerCase().startsWith("content-length:"))?.split(":")[1]?.trim() ?? 0
|
|
15372
|
+
);
|
|
15373
|
+
const totalNeeded = headerResult.pos + 4 + contentLength;
|
|
15374
|
+
if (bufLen < totalNeeded && contentLength > 0) {
|
|
15375
|
+
const bodyResult = await readExact(reader, buf, bufLen, totalNeeded);
|
|
15376
|
+
if (bodyResult) {
|
|
15377
|
+
buf = bodyResult.buf;
|
|
15378
|
+
bufLen = bodyResult.bufLen;
|
|
15379
|
+
}
|
|
15380
|
+
}
|
|
15381
|
+
const parsed = parseHttpResponse(buf, headerResult.pos, bufLen);
|
|
15382
|
+
reader.releaseLock();
|
|
15383
|
+
try {
|
|
15384
|
+
await writer.close();
|
|
15385
|
+
} catch {
|
|
15386
|
+
}
|
|
15387
|
+
conn.close();
|
|
15388
|
+
return new Response(parsed.body, {
|
|
15389
|
+
status: parsed.status,
|
|
15390
|
+
statusText: parsed.statusText,
|
|
15391
|
+
headers: parsed.headers
|
|
15392
|
+
});
|
|
15393
|
+
}
|
|
15394
|
+
function createKernelFetch(ctx) {
|
|
15395
|
+
return async (input, init) => {
|
|
15396
|
+
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
15397
|
+
let url;
|
|
15398
|
+
try {
|
|
15399
|
+
url = new URL(urlStr);
|
|
15400
|
+
} catch {
|
|
15401
|
+
return ctx.fetch(input, init);
|
|
15402
|
+
}
|
|
15403
|
+
const port = extractLoopbackPort(url);
|
|
15404
|
+
if (port !== null) return kernelLoopbackFetch(ctx, url, port, init);
|
|
15405
|
+
return ctx.fetch(input, init);
|
|
15406
|
+
};
|
|
15407
|
+
}
|
|
15408
|
+
async function kernelSeal(ctx, request) {
|
|
15409
|
+
const f = await ctx.open("/dev/sealed", { read: true, write: true });
|
|
15410
|
+
try {
|
|
15411
|
+
const enc = new TextEncoder();
|
|
15412
|
+
await f.write(0, enc.encode(request));
|
|
15413
|
+
const responseBytes = await f.read(0, 65536);
|
|
15414
|
+
return new TextDecoder().decode(responseBytes);
|
|
15415
|
+
} finally {
|
|
15416
|
+
await f.close();
|
|
15417
|
+
}
|
|
15418
|
+
}
|
|
13813
15419
|
function createKernelPlatform(ctx, options) {
|
|
13814
15420
|
const encoder2 = new TextEncoder();
|
|
13815
15421
|
return {
|
|
13816
15422
|
kind: "kernel",
|
|
13817
|
-
fetch: ctx
|
|
15423
|
+
fetch: createKernelFetch(ctx),
|
|
13818
15424
|
spawn: (options2) => kernelSpawn(ctx, options2),
|
|
13819
15425
|
listen: (options2) => kernelListen(ctx, options2),
|
|
13820
15426
|
fs: kernelFs(ctx),
|
|
@@ -13833,17 +15439,26 @@ function createKernelPlatform(ctx, options) {
|
|
|
13833
15439
|
onSignal(signal, handler) {
|
|
13834
15440
|
ctx.signals.on(signal, handler);
|
|
13835
15441
|
},
|
|
15442
|
+
async seal(request) {
|
|
15443
|
+
return kernelSeal(ctx, request);
|
|
15444
|
+
},
|
|
13836
15445
|
packageDir: options?.packageDir
|
|
13837
15446
|
};
|
|
13838
15447
|
}
|
|
13839
15448
|
|
|
13840
15449
|
// src/kernel.ts
|
|
13841
15450
|
async function main(ctx) {
|
|
15451
|
+
if (ctx.signals?.ignore) {
|
|
15452
|
+
try {
|
|
15453
|
+
ctx.signals.ignore("SIGPIPE");
|
|
15454
|
+
} catch {
|
|
15455
|
+
}
|
|
15456
|
+
}
|
|
13842
15457
|
const platform2 = createKernelPlatform(ctx, {
|
|
13843
15458
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
13844
15459
|
});
|
|
13845
15460
|
const args = ctx.argv.slice(1);
|
|
13846
|
-
return run(platform2, "0.
|
|
15461
|
+
return run(platform2, "0.13.0", args);
|
|
13847
15462
|
}
|
|
13848
15463
|
export {
|
|
13849
15464
|
main as default
|