@ai-ecoverse/mcpcat 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD_PARTY_NOTICES +173 -0
- package/dist/host/mcpcat.mjs +1695 -230
- package/dist/kernel/mcpcat.mjs +1824 -224
- package/package.json +2 -2
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,24 +8796,22 @@ 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
|
|
8260
|
-
const seaDir = getSeaDir?.();
|
|
8261
|
-
const wasmPath = seaDir ? `${seaDir}/quickjs.wasm` : await resolveWasmPath(platform2);
|
|
8262
|
-
const workerUrl = seaDir ? new URL(`file://${seaDir}/codemode-worker.mjs`) : new URL("./codemode-worker.mjs", import.meta.url);
|
|
8807
|
+
const wasmPath = await resolveWasmPath(platform2);
|
|
8263
8808
|
return new CodemodeSandbox2({
|
|
8264
8809
|
tools,
|
|
8265
8810
|
globals,
|
|
8266
8811
|
timeoutMs,
|
|
8812
|
+
memoryLimitBytes,
|
|
8267
8813
|
wasm: loadQuickJSWasm2(wasmPath),
|
|
8268
|
-
workerUrl
|
|
8814
|
+
workerUrl: new URL("./codemode-worker.mjs", import.meta.url)
|
|
8269
8815
|
});
|
|
8270
8816
|
}
|
|
8271
8817
|
async function resolveWasmPath(platform2) {
|
|
@@ -8339,6 +8885,14 @@ function containsPlaceholders(value) {
|
|
|
8339
8885
|
function expandPlaceholders(value, env2) {
|
|
8340
8886
|
return value.replace(PLACEHOLDER, (_, name) => env2[name] ?? "");
|
|
8341
8887
|
}
|
|
8888
|
+
function missingPlaceholders(value, env2) {
|
|
8889
|
+
const missing = [];
|
|
8890
|
+
for (const m of value.matchAll(PLACEHOLDER)) {
|
|
8891
|
+
const name = m[1];
|
|
8892
|
+
if (env2[name] === void 0 && !missing.includes(name)) missing.push(name);
|
|
8893
|
+
}
|
|
8894
|
+
return missing;
|
|
8895
|
+
}
|
|
8342
8896
|
function expandPlaceholdersStrict(value, env2) {
|
|
8343
8897
|
let hasUndefined = false;
|
|
8344
8898
|
const result = value.replace(PLACEHOLDER, (_, name) => {
|
|
@@ -8351,6 +8905,20 @@ function expandPlaceholdersStrict(value, env2) {
|
|
|
8351
8905
|
if (hasUndefined) return void 0;
|
|
8352
8906
|
return result;
|
|
8353
8907
|
}
|
|
8908
|
+
function expandHeaders(headers, env2, warn) {
|
|
8909
|
+
if (!headers) return void 0;
|
|
8910
|
+
const result = {};
|
|
8911
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
8912
|
+
const missing = missingPlaceholders(v, env2);
|
|
8913
|
+
if (missing.length > 0) {
|
|
8914
|
+
if (warn)
|
|
8915
|
+
warn(`header "${k}": unset variable${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`);
|
|
8916
|
+
continue;
|
|
8917
|
+
}
|
|
8918
|
+
result[k] = expandPlaceholders(v, env2);
|
|
8919
|
+
}
|
|
8920
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
8921
|
+
}
|
|
8354
8922
|
|
|
8355
8923
|
// src/config/parse.ts
|
|
8356
8924
|
var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
@@ -8541,7 +9109,12 @@ async function readFile2(fs, path) {
|
|
|
8541
9109
|
try {
|
|
8542
9110
|
const data = await fs.readFile(path);
|
|
8543
9111
|
return new TextDecoder().decode(data);
|
|
8544
|
-
} catch {
|
|
9112
|
+
} catch (err) {
|
|
9113
|
+
if (err.code === "EACCES") {
|
|
9114
|
+
const e = new Error(`permission denied: ${path}`);
|
|
9115
|
+
e.code = "EACCES";
|
|
9116
|
+
throw e;
|
|
9117
|
+
}
|
|
8545
9118
|
return void 0;
|
|
8546
9119
|
}
|
|
8547
9120
|
}
|
|
@@ -8706,6 +9279,13 @@ async function removeStaleLock(fs, lockPath) {
|
|
|
8706
9279
|
async function acquireLock(fs, configPath) {
|
|
8707
9280
|
const lockPath = `${configPath}.lock`;
|
|
8708
9281
|
const start = Date.now();
|
|
9282
|
+
const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
|
|
9283
|
+
if (parentDir) {
|
|
9284
|
+
try {
|
|
9285
|
+
await fs.mkdir(parentDir);
|
|
9286
|
+
} catch {
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
8709
9289
|
while (true) {
|
|
8710
9290
|
try {
|
|
8711
9291
|
await fs.mkdirExclusive(lockPath);
|
|
@@ -8767,7 +9347,15 @@ async function editMcpServers(fs, path, edit) {
|
|
|
8767
9347
|
}
|
|
8768
9348
|
const tmpPath = `${path}.tmp.${Date.now()}`;
|
|
8769
9349
|
await fs.writeFile(tmpPath, output);
|
|
8770
|
-
|
|
9350
|
+
try {
|
|
9351
|
+
await fs.rename(tmpPath, path);
|
|
9352
|
+
} catch (renameErr) {
|
|
9353
|
+
try {
|
|
9354
|
+
if (fs.unlink) await fs.unlink(tmpPath);
|
|
9355
|
+
} catch {
|
|
9356
|
+
}
|
|
9357
|
+
throw renameErr;
|
|
9358
|
+
}
|
|
8771
9359
|
return { changed: true };
|
|
8772
9360
|
} finally {
|
|
8773
9361
|
await release();
|
|
@@ -9144,6 +9732,31 @@ async function getCandidate(fs, stateDir2, id) {
|
|
|
9144
9732
|
}
|
|
9145
9733
|
|
|
9146
9734
|
// src/discovery/probe.ts
|
|
9735
|
+
var LOOPBACK_RE = /^(127\.\d+\.\d+\.\d+|\[?::1\]?|localhost)$/i;
|
|
9736
|
+
var LINK_LOCAL_RE = /^(169\.254\.|fe80:)/i;
|
|
9737
|
+
var PRIVATE_RE = /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|fc|fd)/i;
|
|
9738
|
+
var LOCALHOST_SUFFIX_RE = /\.localhost$/i;
|
|
9739
|
+
var SLICC_INTERNAL_RE = /^host\.slicc\.internal$/i;
|
|
9740
|
+
function isPrivateOrLoopback(hostname) {
|
|
9741
|
+
const bare = hostname.replace(/^\[|\]$/g, "");
|
|
9742
|
+
return LOOPBACK_RE.test(bare) || LINK_LOCAL_RE.test(bare) || PRIVATE_RE.test(bare) || LOCALHOST_SUFFIX_RE.test(bare) || SLICC_INTERNAL_RE.test(bare);
|
|
9743
|
+
}
|
|
9744
|
+
function isSameOrigin(base, candidate) {
|
|
9745
|
+
try {
|
|
9746
|
+
const a = new URL(base);
|
|
9747
|
+
const b = new URL(candidate);
|
|
9748
|
+
return a.origin === b.origin;
|
|
9749
|
+
} catch {
|
|
9750
|
+
return false;
|
|
9751
|
+
}
|
|
9752
|
+
}
|
|
9753
|
+
function isUserUrlPrivate(userUrl) {
|
|
9754
|
+
try {
|
|
9755
|
+
return isPrivateOrLoopback(new URL(userUrl).hostname);
|
|
9756
|
+
} catch {
|
|
9757
|
+
return false;
|
|
9758
|
+
}
|
|
9759
|
+
}
|
|
9147
9760
|
function isJsonContentType(ct) {
|
|
9148
9761
|
if (!ct) return false;
|
|
9149
9762
|
const lower = ct.toLowerCase();
|
|
@@ -9278,13 +9891,44 @@ async function probeWellKnown(fetcher, origin, page) {
|
|
|
9278
9891
|
}
|
|
9279
9892
|
return events;
|
|
9280
9893
|
}
|
|
9281
|
-
async function probeLinkHeaders(fetcher, targetUrl, origin, page) {
|
|
9894
|
+
async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPrivate) {
|
|
9282
9895
|
const events = [];
|
|
9283
9896
|
const targetResult = await probeUrl(fetcher, targetUrl);
|
|
9284
9897
|
if (!targetResult) return events;
|
|
9285
9898
|
const links = parseLinkHeaders(targetResult.headers);
|
|
9286
9899
|
for (const link of links) {
|
|
9287
9900
|
const resolved = new URL(link.url, targetUrl).href;
|
|
9901
|
+
if (!isSameOrigin(targetUrl, resolved)) {
|
|
9902
|
+
events.push(
|
|
9903
|
+
makeEvent(
|
|
9904
|
+
link.rel,
|
|
9905
|
+
origin,
|
|
9906
|
+
resolved,
|
|
9907
|
+
"link-header",
|
|
9908
|
+
page,
|
|
9909
|
+
"invalid",
|
|
9910
|
+
null,
|
|
9911
|
+
`skipped: cross-origin Link target (${new URL(resolved).origin})`
|
|
9912
|
+
)
|
|
9913
|
+
);
|
|
9914
|
+
continue;
|
|
9915
|
+
}
|
|
9916
|
+
const resolvedHost = new URL(resolved).hostname;
|
|
9917
|
+
if (!userUrlIsPrivate && isPrivateOrLoopback(resolvedHost)) {
|
|
9918
|
+
events.push(
|
|
9919
|
+
makeEvent(
|
|
9920
|
+
link.rel,
|
|
9921
|
+
origin,
|
|
9922
|
+
resolved,
|
|
9923
|
+
"link-header",
|
|
9924
|
+
page,
|
|
9925
|
+
"invalid",
|
|
9926
|
+
null,
|
|
9927
|
+
`skipped: private/loopback target (${resolvedHost})`
|
|
9928
|
+
)
|
|
9929
|
+
);
|
|
9930
|
+
continue;
|
|
9931
|
+
}
|
|
9288
9932
|
const classifier = link.rel === "ard" ? classifyArd : link.rel === "ai-catalog" ? classifyAiCatalog : null;
|
|
9289
9933
|
if (!classifier) continue;
|
|
9290
9934
|
const linkResult = await probeUrl(fetcher, resolved, "application/json");
|
|
@@ -9381,9 +10025,10 @@ async function probeDiscover(platform2, targetUrl) {
|
|
|
9381
10025
|
}
|
|
9382
10026
|
const page = targetUrl;
|
|
9383
10027
|
const fetcher = platform2.fetch;
|
|
10028
|
+
const userPrivate = isUserUrlPrivate(targetUrl);
|
|
9384
10029
|
const events = [
|
|
9385
10030
|
...await probeWellKnown(fetcher, origin, page),
|
|
9386
|
-
...await probeLinkHeaders(fetcher, targetUrl, origin, page),
|
|
10031
|
+
...await probeLinkHeaders(fetcher, targetUrl, origin, page, userPrivate),
|
|
9387
10032
|
...await probeMcp(fetcher, origin, page, targetUrl)
|
|
9388
10033
|
];
|
|
9389
10034
|
return { events };
|
|
@@ -9640,13 +10285,27 @@ function renderCallResultJson(result) {
|
|
|
9640
10285
|
return JSON.stringify(result, null, 2);
|
|
9641
10286
|
}
|
|
9642
10287
|
async function renderCallResult(platform2, result, options) {
|
|
10288
|
+
const r = options?.redact ?? ((s) => s);
|
|
10289
|
+
if (options?.outputPath) {
|
|
10290
|
+
const content = options.json ? renderCallResultJson(result) : renderCallResultText(result, options);
|
|
10291
|
+
await platform2.fs.writeFile(options.outputPath, content);
|
|
10292
|
+
if (result.isError) {
|
|
10293
|
+
await platform2.write(
|
|
10294
|
+
2,
|
|
10295
|
+
r(`${result.isError ? "(tool reported error)" : ""}
|
|
10296
|
+
`)
|
|
10297
|
+
);
|
|
10298
|
+
return 1;
|
|
10299
|
+
}
|
|
10300
|
+
return 0;
|
|
10301
|
+
}
|
|
9643
10302
|
if (options?.json) {
|
|
9644
10303
|
const json = renderCallResultJson(result);
|
|
9645
10304
|
if (result.isError) {
|
|
9646
10305
|
await platform2.write(
|
|
9647
10306
|
2,
|
|
9648
|
-
`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
|
|
9649
|
-
`
|
|
10307
|
+
r(`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
|
|
10308
|
+
`)
|
|
9650
10309
|
);
|
|
9651
10310
|
return 1;
|
|
9652
10311
|
}
|
|
@@ -9656,8 +10315,8 @@ async function renderCallResult(platform2, result, options) {
|
|
|
9656
10315
|
}
|
|
9657
10316
|
const text = renderCallResultText(result, options);
|
|
9658
10317
|
if (result.isError) {
|
|
9659
|
-
await platform2.write(2, `${text || "(tool reported error)"}
|
|
9660
|
-
`);
|
|
10318
|
+
await platform2.write(2, r(`${text || "(tool reported error)"}
|
|
10319
|
+
`));
|
|
9661
10320
|
return 1;
|
|
9662
10321
|
}
|
|
9663
10322
|
if (text) {
|
|
@@ -9674,13 +10333,33 @@ async function renderInfoJson(platform2, info) {
|
|
|
9674
10333
|
await platform2.write(1, `${JSON.stringify(info, null, 2)}
|
|
9675
10334
|
`);
|
|
9676
10335
|
}
|
|
9677
|
-
async function renderErrorJson(platform2, kind, message, code, data) {
|
|
9678
|
-
const
|
|
10336
|
+
async function renderErrorJson(platform2, kind, message, code, data, redact2) {
|
|
10337
|
+
const r = redact2 ?? ((s) => s);
|
|
10338
|
+
const errorObj = { kind, message: r(message) };
|
|
9679
10339
|
if (code !== void 0) errorObj.code = code;
|
|
9680
10340
|
if (data !== void 0) errorObj.data = data;
|
|
9681
10341
|
await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
|
|
9682
10342
|
`);
|
|
9683
10343
|
}
|
|
10344
|
+
async function renderResourcesJson(platform2, resources, templates) {
|
|
10345
|
+
await platform2.write(
|
|
10346
|
+
1,
|
|
10347
|
+
`${JSON.stringify({ resources, resourceTemplates: templates }, null, 2)}
|
|
10348
|
+
`
|
|
10349
|
+
);
|
|
10350
|
+
}
|
|
10351
|
+
async function renderPromptsJson(platform2, prompts) {
|
|
10352
|
+
await platform2.write(1, `${JSON.stringify({ prompts }, null, 2)}
|
|
10353
|
+
`);
|
|
10354
|
+
}
|
|
10355
|
+
async function renderPromptResultJson(platform2, result) {
|
|
10356
|
+
await platform2.write(1, `${JSON.stringify(result, null, 2)}
|
|
10357
|
+
`);
|
|
10358
|
+
}
|
|
10359
|
+
async function renderReadResourceJson(platform2, result) {
|
|
10360
|
+
await platform2.write(1, `${JSON.stringify(result, null, 2)}
|
|
10361
|
+
`);
|
|
10362
|
+
}
|
|
9684
10363
|
|
|
9685
10364
|
// src/serve/catalog.ts
|
|
9686
10365
|
var NAME_RE = /^[A-Za-z][\w-]*$/;
|
|
@@ -10448,38 +11127,46 @@ function hasOwn(value, key) {
|
|
|
10448
11127
|
}
|
|
10449
11128
|
|
|
10450
11129
|
// src/serve/origin.ts
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
try {
|
|
10454
|
-
const url = new URL(origin);
|
|
10455
|
-
const hostname = url.hostname;
|
|
10456
|
-
if (LOOPBACK_HOSTS.has(hostname)) return true;
|
|
10457
|
-
if (hostname === "::1") return true;
|
|
10458
|
-
if (hostname.endsWith(".localhost")) return true;
|
|
10459
|
-
return false;
|
|
10460
|
-
} catch {
|
|
10461
|
-
return false;
|
|
10462
|
-
}
|
|
11130
|
+
function isLoopbackBind(host) {
|
|
11131
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]";
|
|
10463
11132
|
}
|
|
10464
|
-
function
|
|
10465
|
-
const
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
if (
|
|
10469
|
-
|
|
11133
|
+
function buildServerOrigins(host, port) {
|
|
11134
|
+
const origins = /* @__PURE__ */ new Set();
|
|
11135
|
+
origins.add(`http://${host}:${port}`);
|
|
11136
|
+
origins.add(`http://${port}.kernel.localhost`);
|
|
11137
|
+
if (isLoopbackBind(host)) {
|
|
11138
|
+
origins.add(`http://localhost:${port}`);
|
|
11139
|
+
origins.add(`http://127.0.0.1:${port}`);
|
|
11140
|
+
origins.add(`http://[::1]:${port}`);
|
|
11141
|
+
}
|
|
11142
|
+
return origins;
|
|
10470
11143
|
}
|
|
10471
|
-
function checkOrigin(origin,
|
|
11144
|
+
function checkOrigin(origin, serverOrigins, allowedOrigins) {
|
|
10472
11145
|
if (origin === void 0) return { ok: true };
|
|
11146
|
+
if (serverOrigins.has(origin)) return { ok: true };
|
|
10473
11147
|
if (allowedOrigins.has(origin)) return { ok: true };
|
|
10474
|
-
if (isLoopbackOrigin(origin)) return { ok: true };
|
|
10475
|
-
if (!boundLoopback) return { ok: true };
|
|
10476
11148
|
return { ok: false, reason: `origin ${origin} is not allowed` };
|
|
10477
11149
|
}
|
|
10478
|
-
function
|
|
10479
|
-
|
|
11150
|
+
function buildAllowedHosts(host, port, extraHosts) {
|
|
11151
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
11152
|
+
hosts.add(`${host}:${port}`);
|
|
11153
|
+
hosts.add(host);
|
|
11154
|
+
hosts.add(`${port}.kernel.localhost`);
|
|
11155
|
+
const loopback2 = ["localhost", "127.0.0.1", "[::1]"];
|
|
11156
|
+
for (const h of loopback2) {
|
|
11157
|
+
hosts.add(h);
|
|
11158
|
+
hosts.add(`${h}:${port}`);
|
|
11159
|
+
}
|
|
11160
|
+
for (const h of extraHosts) hosts.add(h);
|
|
11161
|
+
return hosts;
|
|
11162
|
+
}
|
|
11163
|
+
function checkHost(host, allowedHosts) {
|
|
10480
11164
|
if (!host) return { ok: false, reason: "missing Host header" };
|
|
10481
|
-
if (
|
|
10482
|
-
|
|
11165
|
+
if (allowedHosts.has(host)) return { ok: true };
|
|
11166
|
+
const hostname = host.replace(/:\d+$/, "");
|
|
11167
|
+
if (allowedHosts.has(hostname)) return { ok: true };
|
|
11168
|
+
if (hostname.endsWith(".localhost")) return { ok: true };
|
|
11169
|
+
return { ok: false, reason: `host ${host} is not allowed` };
|
|
10483
11170
|
}
|
|
10484
11171
|
|
|
10485
11172
|
// src/serve/proxy.ts
|
|
@@ -10716,6 +11403,8 @@ function parseSingleFlag(args, i, opts) {
|
|
|
10716
11403
|
return parsed.next;
|
|
10717
11404
|
} else if (arg === "--json") {
|
|
10718
11405
|
opts.json = true;
|
|
11406
|
+
} else if (arg === "--no-auth") {
|
|
11407
|
+
opts.noAuth = true;
|
|
10719
11408
|
} else if (!arg.startsWith("-")) {
|
|
10720
11409
|
opts.commands.push(parseCommandTarget(arg));
|
|
10721
11410
|
} else {
|
|
@@ -10736,6 +11425,9 @@ function parseValueFlag(args, i, opts) {
|
|
|
10736
11425
|
case "--allow-origin":
|
|
10737
11426
|
opts.allowOrigins.push(args[++i]);
|
|
10738
11427
|
break;
|
|
11428
|
+
case "--allow-host":
|
|
11429
|
+
opts.allowHosts.push(args[++i]);
|
|
11430
|
+
break;
|
|
10739
11431
|
case "--env":
|
|
10740
11432
|
opts.envAllow.push(args[++i]);
|
|
10741
11433
|
break;
|
|
@@ -10774,7 +11466,9 @@ function parseServeFlags(args) {
|
|
|
10774
11466
|
host: "127.0.0.1",
|
|
10775
11467
|
port: 0,
|
|
10776
11468
|
bearer: void 0,
|
|
11469
|
+
noAuth: false,
|
|
10777
11470
|
allowOrigins: [],
|
|
11471
|
+
allowHosts: [],
|
|
10778
11472
|
envAllow: [],
|
|
10779
11473
|
toolTemplates: [],
|
|
10780
11474
|
callTimeout: CALL_TIMEOUT_MS,
|
|
@@ -10803,6 +11497,20 @@ function constantTimeEqual(a, b) {
|
|
|
10803
11497
|
}
|
|
10804
11498
|
return mismatch === 0;
|
|
10805
11499
|
}
|
|
11500
|
+
function generateToken() {
|
|
11501
|
+
const bytes = new Uint8Array(32);
|
|
11502
|
+
crypto.getRandomValues(bytes);
|
|
11503
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
11504
|
+
}
|
|
11505
|
+
function checkContentType(ct) {
|
|
11506
|
+
if (!ct) return false;
|
|
11507
|
+
return ct.toLowerCase().startsWith("application/json");
|
|
11508
|
+
}
|
|
11509
|
+
function checkAcceptHeader(accept) {
|
|
11510
|
+
if (!accept) return false;
|
|
11511
|
+
const lower = accept.toLowerCase();
|
|
11512
|
+
return lower.includes("application/json") || lower.includes("text/event-stream") || lower.includes("*/*");
|
|
11513
|
+
}
|
|
10806
11514
|
function buildChildEnv(platform2, envAllow) {
|
|
10807
11515
|
const env2 = {};
|
|
10808
11516
|
for (const key of ALLOWED_ENV_KEYS) {
|
|
@@ -10851,7 +11559,7 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
10851
11559
|
}
|
|
10852
11560
|
const stdoutChunks = [];
|
|
10853
11561
|
const stderrChunks = [];
|
|
10854
|
-
const
|
|
11562
|
+
const readStream3 = async (stream, into) => {
|
|
10855
11563
|
const r = stream.getReader();
|
|
10856
11564
|
try {
|
|
10857
11565
|
for (; ; ) {
|
|
@@ -10867,8 +11575,8 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
10867
11575
|
(resolve2) => setTimeout(() => resolve2("timeout"), timeoutMs)
|
|
10868
11576
|
);
|
|
10869
11577
|
const work = Promise.all([
|
|
10870
|
-
|
|
10871
|
-
|
|
11578
|
+
readStream3(proc.stdout, stdoutChunks),
|
|
11579
|
+
readStream3(proc.stderr, stderrChunks),
|
|
10872
11580
|
proc.wait()
|
|
10873
11581
|
]);
|
|
10874
11582
|
const result = await Promise.race([work, timer]);
|
|
@@ -11100,7 +11808,10 @@ function createToolExecutor(platform2, clis, allTools, childEnv, options, serveC
|
|
|
11100
11808
|
}
|
|
11101
11809
|
async function runServe(platform2, options, version) {
|
|
11102
11810
|
let resolvedBearer;
|
|
11103
|
-
|
|
11811
|
+
let generatedBearer = false;
|
|
11812
|
+
if (options._resolvedBearer) {
|
|
11813
|
+
resolvedBearer = options._resolvedBearer;
|
|
11814
|
+
} else if (options.bearer) {
|
|
11104
11815
|
const expanded = expandBearerToken(options.bearer, platform2.env);
|
|
11105
11816
|
if (!expanded) {
|
|
11106
11817
|
await platform2.write(
|
|
@@ -11110,9 +11821,31 @@ async function runServe(platform2, options, version) {
|
|
|
11110
11821
|
return 2;
|
|
11111
11822
|
}
|
|
11112
11823
|
resolvedBearer = expanded;
|
|
11824
|
+
} else if (options.noAuth) {
|
|
11825
|
+
if (options.transport === "http" && !isLoopbackBind(options.host)) {
|
|
11826
|
+
await platform2.write(2, "mcpcat serve: --no-auth is only allowed on loopback\n");
|
|
11827
|
+
return 2;
|
|
11828
|
+
}
|
|
11829
|
+
if (options.transport === "http") {
|
|
11830
|
+
await platform2.write(
|
|
11831
|
+
2,
|
|
11832
|
+
"mcpcat serve: warning: running without authentication on loopback\n"
|
|
11833
|
+
);
|
|
11834
|
+
}
|
|
11835
|
+
resolvedBearer = void 0;
|
|
11836
|
+
} else if (options.transport === "http") {
|
|
11837
|
+
if (platform2.kind === "kernel") {
|
|
11838
|
+
await platform2.write(
|
|
11839
|
+
2,
|
|
11840
|
+
"mcpcat serve: kernel HTTP serve requires --bearer '${VAR}' or --no-auth\n"
|
|
11841
|
+
);
|
|
11842
|
+
return 2;
|
|
11843
|
+
}
|
|
11844
|
+
resolvedBearer = generateToken();
|
|
11845
|
+
generatedBearer = true;
|
|
11113
11846
|
}
|
|
11114
11847
|
if (options.proxy) {
|
|
11115
|
-
return runProxyServe(platform2, options, version, resolvedBearer);
|
|
11848
|
+
return runProxyServe(platform2, options, version, resolvedBearer, generatedBearer);
|
|
11116
11849
|
}
|
|
11117
11850
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
11118
11851
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
@@ -11149,12 +11882,35 @@ async function runServe(platform2, options, version) {
|
|
|
11149
11882
|
executor.invoke,
|
|
11150
11883
|
serverName,
|
|
11151
11884
|
version,
|
|
11152
|
-
resolvedBearer
|
|
11885
|
+
resolvedBearer,
|
|
11886
|
+
generatedBearer
|
|
11153
11887
|
);
|
|
11154
11888
|
}
|
|
11889
|
+
async function publishBearer(platform2, token, port, listenUrl, json) {
|
|
11890
|
+
if (!json && platform2.isatty(2)) {
|
|
11891
|
+
await platform2.write(2, `mcpcat serve: bearer ${token}
|
|
11892
|
+
`);
|
|
11893
|
+
return void 0;
|
|
11894
|
+
}
|
|
11895
|
+
const dir = stateDir(platform2);
|
|
11896
|
+
await platform2.fs.mkdir(dir, { mode: 448 });
|
|
11897
|
+
const tokenPath = `${dir}/serve-${port}.token`;
|
|
11898
|
+
try {
|
|
11899
|
+
await platform2.fs.unlink?.(tokenPath);
|
|
11900
|
+
} catch {
|
|
11901
|
+
}
|
|
11902
|
+
await platform2.fs.writeFile(tokenPath, token, { mode: 384 });
|
|
11903
|
+
await platform2.write(2, `mcpcat serve: bearer saved to ${tokenPath}
|
|
11904
|
+
`);
|
|
11905
|
+
if (json) {
|
|
11906
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl, bearerFile: tokenPath })}
|
|
11907
|
+
`);
|
|
11908
|
+
}
|
|
11909
|
+
return tokenPath;
|
|
11910
|
+
}
|
|
11155
11911
|
function resolveProxyStdio(target, version) {
|
|
11156
11912
|
const rest = target.slice(6);
|
|
11157
|
-
const parts = rest
|
|
11913
|
+
const parts = splitShellStyle(rest);
|
|
11158
11914
|
return { command: parts[0], args: parts.slice(1), version };
|
|
11159
11915
|
}
|
|
11160
11916
|
function resolveProxyUrl(target, headers, version) {
|
|
@@ -11224,7 +11980,7 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
11224
11980
|
}
|
|
11225
11981
|
return { connectOpts, hiddenValues };
|
|
11226
11982
|
}
|
|
11227
|
-
async function runProxyServe(platform2, options, version, resolvedBearer) {
|
|
11983
|
+
async function runProxyServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
11228
11984
|
const built = await buildProxyConnectOpts(platform2, options, version);
|
|
11229
11985
|
if ("error" in built) {
|
|
11230
11986
|
await platform2.write(2, `mcpcat serve --proxy: ${built.error}
|
|
@@ -11242,7 +11998,7 @@ async function runProxyServe(platform2, options, version, resolvedBearer) {
|
|
|
11242
11998
|
if (options.transport === "stdio") {
|
|
11243
11999
|
return runProxyStdio(platform2, proxy);
|
|
11244
12000
|
}
|
|
11245
|
-
return runProxyHttp(platform2, options, proxy, resolvedBearer);
|
|
12001
|
+
return runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer);
|
|
11246
12002
|
}
|
|
11247
12003
|
async function runProxyStdio(platform2, proxy) {
|
|
11248
12004
|
const reader = platform2.stdin.getReader();
|
|
@@ -11273,12 +12029,12 @@ async function runProxyStdio(platform2, proxy) {
|
|
|
11273
12029
|
}
|
|
11274
12030
|
return 0;
|
|
11275
12031
|
}
|
|
11276
|
-
async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
12032
|
+
async function runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer) {
|
|
11277
12033
|
if (!platform2.listen) {
|
|
11278
12034
|
await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
|
|
11279
12035
|
return 3;
|
|
11280
12036
|
}
|
|
11281
|
-
const boundLoopback = options.host
|
|
12037
|
+
const boundLoopback = isLoopbackBind(options.host);
|
|
11282
12038
|
if (!boundLoopback && !resolvedBearer) {
|
|
11283
12039
|
await platform2.write(
|
|
11284
12040
|
2,
|
|
@@ -11292,10 +12048,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
|
11292
12048
|
port: options.port
|
|
11293
12049
|
});
|
|
11294
12050
|
const actualPort = listener.port;
|
|
12051
|
+
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
12052
|
+
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
11295
12053
|
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
11296
|
-
|
|
11297
|
-
|
|
12054
|
+
let tokenFilePath;
|
|
12055
|
+
if (generatedBearer && resolvedBearer) {
|
|
12056
|
+
tokenFilePath = await publishBearer(
|
|
12057
|
+
platform2,
|
|
12058
|
+
resolvedBearer,
|
|
12059
|
+
actualPort,
|
|
12060
|
+
listenUrl,
|
|
12061
|
+
options.json
|
|
12062
|
+
);
|
|
12063
|
+
} else {
|
|
12064
|
+
if (options.json) {
|
|
12065
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
11298
12066
|
`);
|
|
12067
|
+
}
|
|
11299
12068
|
}
|
|
11300
12069
|
await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
|
|
11301
12070
|
`);
|
|
@@ -11308,12 +12077,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
|
11308
12077
|
shutdownRequested = true;
|
|
11309
12078
|
listener.close();
|
|
11310
12079
|
void proxy.shutdown();
|
|
12080
|
+
if (tokenFilePath) {
|
|
12081
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
12082
|
+
});
|
|
12083
|
+
}
|
|
11311
12084
|
};
|
|
11312
12085
|
if (platform2.onSignal) {
|
|
11313
12086
|
platform2.onSignal("SIGINT", shutdown);
|
|
11314
12087
|
platform2.onSignal("SIGTERM", shutdown);
|
|
11315
12088
|
}
|
|
11316
|
-
const handler = (request) => handleProxyMcpRequest(
|
|
12089
|
+
const handler = (request) => handleProxyMcpRequest(
|
|
12090
|
+
request,
|
|
12091
|
+
proxy,
|
|
12092
|
+
serverOrigins,
|
|
12093
|
+
allowedOrigins,
|
|
12094
|
+
allowedHosts,
|
|
12095
|
+
resolvedBearer
|
|
12096
|
+
);
|
|
11317
12097
|
try {
|
|
11318
12098
|
for await (const connection of listener) {
|
|
11319
12099
|
if (shutdownRequested) break;
|
|
@@ -11331,20 +12111,40 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
|
11331
12111
|
}
|
|
11332
12112
|
return 0;
|
|
11333
12113
|
}
|
|
11334
|
-
async function handleProxyMcpRequest(request, proxy, allowedOrigins,
|
|
11335
|
-
if (
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
12114
|
+
async function handleProxyMcpRequest(request, proxy, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
|
|
12115
|
+
if (request.target !== "/mcp") {
|
|
12116
|
+
return {
|
|
12117
|
+
status: 404,
|
|
12118
|
+
reason: "Not Found",
|
|
12119
|
+
headers: { "content-type": "text/plain" },
|
|
12120
|
+
body: "Not Found"
|
|
12121
|
+
};
|
|
12122
|
+
}
|
|
12123
|
+
if (request.method !== "POST") {
|
|
12124
|
+
return {
|
|
12125
|
+
status: 405,
|
|
12126
|
+
reason: "Method Not Allowed",
|
|
12127
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
12128
|
+
body: "Method Not Allowed"
|
|
12129
|
+
};
|
|
12130
|
+
}
|
|
12131
|
+
if (!checkContentType(request.headers["content-type"])) {
|
|
12132
|
+
return {
|
|
12133
|
+
status: 415,
|
|
12134
|
+
reason: "Unsupported Media Type",
|
|
12135
|
+
headers: { "content-type": "text/plain" },
|
|
12136
|
+
body: "Content-Type must be application/json"
|
|
12137
|
+
};
|
|
12138
|
+
}
|
|
12139
|
+
if (!checkAcceptHeader(request.headers.accept)) {
|
|
12140
|
+
return {
|
|
12141
|
+
status: 406,
|
|
12142
|
+
reason: "Not Acceptable",
|
|
12143
|
+
headers: { "content-type": "text/plain" },
|
|
12144
|
+
body: "Accept must include application/json or text/event-stream"
|
|
12145
|
+
};
|
|
11346
12146
|
}
|
|
11347
|
-
const originCheck = checkOrigin(request.headers.origin,
|
|
12147
|
+
const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
|
|
11348
12148
|
if (!originCheck.ok) {
|
|
11349
12149
|
return {
|
|
11350
12150
|
status: 403,
|
|
@@ -11353,7 +12153,7 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
|
|
|
11353
12153
|
body: originCheck.reason ?? "Forbidden"
|
|
11354
12154
|
};
|
|
11355
12155
|
}
|
|
11356
|
-
const hostCheck = checkHost(request.headers.host,
|
|
12156
|
+
const hostCheck = checkHost(request.headers.host, allowedHosts);
|
|
11357
12157
|
if (!hostCheck.ok) {
|
|
11358
12158
|
return {
|
|
11359
12159
|
status: 403,
|
|
@@ -11362,21 +12162,17 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
|
|
|
11362
12162
|
body: hostCheck.reason ?? "Forbidden"
|
|
11363
12163
|
};
|
|
11364
12164
|
}
|
|
11365
|
-
if (
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
reason: "Method Not Allowed",
|
|
11377
|
-
headers: { "content-type": "text/plain", allow: "POST" },
|
|
11378
|
-
body: "Method Not Allowed"
|
|
11379
|
-
};
|
|
12165
|
+
if (resolvedBearer) {
|
|
12166
|
+
const authHeader = request.headers.authorization;
|
|
12167
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
12168
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
12169
|
+
return {
|
|
12170
|
+
status: 401,
|
|
12171
|
+
reason: "Unauthorized",
|
|
12172
|
+
headers: { "content-type": "text/plain" },
|
|
12173
|
+
body: "Unauthorized"
|
|
12174
|
+
};
|
|
12175
|
+
}
|
|
11380
12176
|
}
|
|
11381
12177
|
const outcome = await proxy.handle(request.body);
|
|
11382
12178
|
return {
|
|
@@ -11414,12 +12210,12 @@ async function runStdioServer(platform2, tools, call, serverName, version) {
|
|
|
11414
12210
|
}
|
|
11415
12211
|
return 0;
|
|
11416
12212
|
}
|
|
11417
|
-
async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer) {
|
|
12213
|
+
async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer, generatedBearer) {
|
|
11418
12214
|
if (!platform2.listen) {
|
|
11419
12215
|
await platform2.write(2, "mcpcat serve: HTTP transport requires platform.listen\n");
|
|
11420
12216
|
return 3;
|
|
11421
12217
|
}
|
|
11422
|
-
const boundLoopback = options.host
|
|
12218
|
+
const boundLoopback = isLoopbackBind(options.host);
|
|
11423
12219
|
if (!boundLoopback && !resolvedBearer) {
|
|
11424
12220
|
await platform2.write(2, "mcpcat serve: --bearer is required when binding off loopback\n");
|
|
11425
12221
|
return 2;
|
|
@@ -11427,10 +12223,23 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11427
12223
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
11428
12224
|
const listener = await platform2.listen({ host: options.host, port: options.port });
|
|
11429
12225
|
const actualPort = listener.port;
|
|
12226
|
+
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
12227
|
+
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
11430
12228
|
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
11431
|
-
|
|
11432
|
-
|
|
12229
|
+
let tokenFilePath;
|
|
12230
|
+
if (generatedBearer && resolvedBearer) {
|
|
12231
|
+
tokenFilePath = await publishBearer(
|
|
12232
|
+
platform2,
|
|
12233
|
+
resolvedBearer,
|
|
12234
|
+
actualPort,
|
|
12235
|
+
listenUrl,
|
|
12236
|
+
options.json
|
|
12237
|
+
);
|
|
12238
|
+
} else {
|
|
12239
|
+
if (options.json) {
|
|
12240
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
11433
12241
|
`);
|
|
12242
|
+
}
|
|
11434
12243
|
}
|
|
11435
12244
|
await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
|
|
11436
12245
|
`);
|
|
@@ -11440,6 +12249,10 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11440
12249
|
shutdownRequested = true;
|
|
11441
12250
|
listener.close();
|
|
11442
12251
|
void killInflight();
|
|
12252
|
+
if (tokenFilePath) {
|
|
12253
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
12254
|
+
});
|
|
12255
|
+
}
|
|
11443
12256
|
};
|
|
11444
12257
|
if (platform2.onSignal) {
|
|
11445
12258
|
platform2.onSignal("SIGINT", shutdown);
|
|
@@ -11451,8 +12264,9 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11451
12264
|
call,
|
|
11452
12265
|
serverName,
|
|
11453
12266
|
version,
|
|
12267
|
+
serverOrigins,
|
|
11454
12268
|
allowedOrigins,
|
|
11455
|
-
|
|
12269
|
+
allowedHosts,
|
|
11456
12270
|
resolvedBearer
|
|
11457
12271
|
);
|
|
11458
12272
|
try {
|
|
@@ -11472,37 +12286,7 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
11472
12286
|
}
|
|
11473
12287
|
return 0;
|
|
11474
12288
|
}
|
|
11475
|
-
async function handleMcpRequest(request, tools, call, serverName, version, allowedOrigins,
|
|
11476
|
-
if (resolvedBearer) {
|
|
11477
|
-
const authHeader = request.headers.authorization;
|
|
11478
|
-
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
11479
|
-
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
11480
|
-
return {
|
|
11481
|
-
status: 401,
|
|
11482
|
-
reason: "Unauthorized",
|
|
11483
|
-
headers: { "content-type": "text/plain" },
|
|
11484
|
-
body: "Unauthorized"
|
|
11485
|
-
};
|
|
11486
|
-
}
|
|
11487
|
-
}
|
|
11488
|
-
const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
|
|
11489
|
-
if (!originCheck.ok) {
|
|
11490
|
-
return {
|
|
11491
|
-
status: 403,
|
|
11492
|
-
reason: "Forbidden",
|
|
11493
|
-
headers: { "content-type": "text/plain" },
|
|
11494
|
-
body: originCheck.reason ?? "Forbidden"
|
|
11495
|
-
};
|
|
11496
|
-
}
|
|
11497
|
-
const hostCheck = checkHost(request.headers.host, boundLoopback);
|
|
11498
|
-
if (!hostCheck.ok) {
|
|
11499
|
-
return {
|
|
11500
|
-
status: 403,
|
|
11501
|
-
reason: "Forbidden",
|
|
11502
|
-
headers: { "content-type": "text/plain" },
|
|
11503
|
-
body: hostCheck.reason ?? "Forbidden"
|
|
11504
|
-
};
|
|
11505
|
-
}
|
|
12289
|
+
async function handleMcpRequest(request, tools, call, serverName, version, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
|
|
11506
12290
|
if (request.target !== "/mcp") {
|
|
11507
12291
|
return {
|
|
11508
12292
|
status: 404,
|
|
@@ -11519,8 +12303,54 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
|
|
|
11519
12303
|
body: "Method Not Allowed"
|
|
11520
12304
|
};
|
|
11521
12305
|
}
|
|
11522
|
-
|
|
11523
|
-
|
|
12306
|
+
if (!checkContentType(request.headers["content-type"])) {
|
|
12307
|
+
return {
|
|
12308
|
+
status: 415,
|
|
12309
|
+
reason: "Unsupported Media Type",
|
|
12310
|
+
headers: { "content-type": "text/plain" },
|
|
12311
|
+
body: "Content-Type must be application/json"
|
|
12312
|
+
};
|
|
12313
|
+
}
|
|
12314
|
+
if (!checkAcceptHeader(request.headers.accept)) {
|
|
12315
|
+
return {
|
|
12316
|
+
status: 406,
|
|
12317
|
+
reason: "Not Acceptable",
|
|
12318
|
+
headers: { "content-type": "text/plain" },
|
|
12319
|
+
body: "Accept must include application/json or text/event-stream"
|
|
12320
|
+
};
|
|
12321
|
+
}
|
|
12322
|
+
const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
|
|
12323
|
+
if (!originCheck.ok) {
|
|
12324
|
+
return {
|
|
12325
|
+
status: 403,
|
|
12326
|
+
reason: "Forbidden",
|
|
12327
|
+
headers: { "content-type": "text/plain" },
|
|
12328
|
+
body: originCheck.reason ?? "Forbidden"
|
|
12329
|
+
};
|
|
12330
|
+
}
|
|
12331
|
+
const hostCheck = checkHost(request.headers.host, allowedHosts);
|
|
12332
|
+
if (!hostCheck.ok) {
|
|
12333
|
+
return {
|
|
12334
|
+
status: 403,
|
|
12335
|
+
reason: "Forbidden",
|
|
12336
|
+
headers: { "content-type": "text/plain" },
|
|
12337
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
12338
|
+
};
|
|
12339
|
+
}
|
|
12340
|
+
if (resolvedBearer) {
|
|
12341
|
+
const authHeader = request.headers.authorization;
|
|
12342
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
12343
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
12344
|
+
return {
|
|
12345
|
+
status: 401,
|
|
12346
|
+
reason: "Unauthorized",
|
|
12347
|
+
headers: { "content-type": "text/plain" },
|
|
12348
|
+
body: "Unauthorized"
|
|
12349
|
+
};
|
|
12350
|
+
}
|
|
12351
|
+
}
|
|
12352
|
+
const outcome = await handleJsonRpc(request.body, tools, call, serverName, version);
|
|
12353
|
+
return {
|
|
11524
12354
|
status: outcome.status,
|
|
11525
12355
|
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
11526
12356
|
headers: { "content-type": outcome.contentType },
|
|
@@ -11844,8 +12674,16 @@ var PiFileTokenSource = class {
|
|
|
11844
12674
|
};
|
|
11845
12675
|
|
|
11846
12676
|
// src/cli/auth-verbs.ts
|
|
11847
|
-
|
|
11848
|
-
|
|
12677
|
+
var resolvedSource;
|
|
12678
|
+
async function createTokenSource(platform2) {
|
|
12679
|
+
if (isKernel(platform2)) return new PiFileTokenSource(platform2);
|
|
12680
|
+
if (resolvedSource) return resolvedSource;
|
|
12681
|
+
if (await jerkAvailable(platform2)) {
|
|
12682
|
+
resolvedSource = new JerkTokenSource(platform2);
|
|
12683
|
+
} else {
|
|
12684
|
+
resolvedSource = new PiFileTokenSource(platform2);
|
|
12685
|
+
}
|
|
12686
|
+
return resolvedSource;
|
|
11849
12687
|
}
|
|
11850
12688
|
function resolveServerName(target) {
|
|
11851
12689
|
try {
|
|
@@ -11875,6 +12713,79 @@ async function resolveLoginTarget(platform2, target) {
|
|
|
11875
12713
|
}
|
|
11876
12714
|
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
11877
12715
|
}
|
|
12716
|
+
function jerkMcpId(name) {
|
|
12717
|
+
if (name.startsWith("mcp:")) return name;
|
|
12718
|
+
if (name.startsWith("url:")) return `mcp:${name}`;
|
|
12719
|
+
return `mcp:${name}`;
|
|
12720
|
+
}
|
|
12721
|
+
async function readStream2(stream) {
|
|
12722
|
+
const reader = stream.getReader();
|
|
12723
|
+
const chunks = [];
|
|
12724
|
+
for (; ; ) {
|
|
12725
|
+
const { done, value } = await reader.read();
|
|
12726
|
+
if (done) break;
|
|
12727
|
+
chunks.push(value);
|
|
12728
|
+
}
|
|
12729
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
12730
|
+
const buf = new Uint8Array(total);
|
|
12731
|
+
let off = 0;
|
|
12732
|
+
for (const c of chunks) {
|
|
12733
|
+
buf.set(c, off);
|
|
12734
|
+
off += c.length;
|
|
12735
|
+
}
|
|
12736
|
+
return new TextDecoder().decode(buf);
|
|
12737
|
+
}
|
|
12738
|
+
async function jerkLogin(platform2, target, args) {
|
|
12739
|
+
const resolved = await resolveLoginTarget(platform2, target);
|
|
12740
|
+
const id = jerkMcpId(resolved.name);
|
|
12741
|
+
const argv = ["jerk", id, "create"];
|
|
12742
|
+
const scopeIdx = args.indexOf("--scope");
|
|
12743
|
+
if (scopeIdx !== -1 && args[scopeIdx + 1]) {
|
|
12744
|
+
argv.push("--scope", args[scopeIdx + 1]);
|
|
12745
|
+
}
|
|
12746
|
+
if (args.includes("--no-browser")) {
|
|
12747
|
+
argv.push("--no-browser");
|
|
12748
|
+
}
|
|
12749
|
+
const proc = await platform2.spawn({ argv });
|
|
12750
|
+
proc.stdin.close().catch(() => {
|
|
12751
|
+
});
|
|
12752
|
+
const [, errText, result] = await Promise.all([
|
|
12753
|
+
readStream2(proc.stdout),
|
|
12754
|
+
readStream2(proc.stderr),
|
|
12755
|
+
proc.wait()
|
|
12756
|
+
]);
|
|
12757
|
+
if (result.status === 0) {
|
|
12758
|
+
await platform2.write(1, `Signed in to ${target}
|
|
12759
|
+
`);
|
|
12760
|
+
return 0;
|
|
12761
|
+
}
|
|
12762
|
+
if (errText.trim()) {
|
|
12763
|
+
await platform2.write(2, errText);
|
|
12764
|
+
}
|
|
12765
|
+
if (result.status === JERK_EXIT_SIGN_IN) return 4;
|
|
12766
|
+
return result.status;
|
|
12767
|
+
}
|
|
12768
|
+
async function jerkLogout(platform2, target) {
|
|
12769
|
+
const resolved = await resolveLoginTarget(platform2, target);
|
|
12770
|
+
const id = jerkMcpId(resolved.name);
|
|
12771
|
+
const argv = ["jerk", id, "drop", "--yes"];
|
|
12772
|
+
const proc = await platform2.spawn({ argv });
|
|
12773
|
+
proc.stdin.close().catch(() => {
|
|
12774
|
+
});
|
|
12775
|
+
const [, , result] = await Promise.all([
|
|
12776
|
+
readStream2(proc.stdout),
|
|
12777
|
+
readStream2(proc.stderr),
|
|
12778
|
+
proc.wait()
|
|
12779
|
+
]);
|
|
12780
|
+
if (result.status === 0) {
|
|
12781
|
+
await platform2.write(1, `Signed out of ${target}
|
|
12782
|
+
`);
|
|
12783
|
+
} else {
|
|
12784
|
+
await platform2.write(1, `No credentials stored for ${target}
|
|
12785
|
+
`);
|
|
12786
|
+
}
|
|
12787
|
+
return 0;
|
|
12788
|
+
}
|
|
11878
12789
|
async function runLogin(platform2, args, source) {
|
|
11879
12790
|
if (isKernel(platform2)) {
|
|
11880
12791
|
await platform2.write(
|
|
@@ -11883,13 +12794,22 @@ async function runLogin(platform2, args, source) {
|
|
|
11883
12794
|
);
|
|
11884
12795
|
return 4;
|
|
11885
12796
|
}
|
|
11886
|
-
const noBrowser = args.includes("--no-browser");
|
|
11887
12797
|
const positional = args.filter((a) => !a.startsWith("-"));
|
|
11888
12798
|
if (positional.length === 0) {
|
|
11889
12799
|
await platform2.write(2, "mcpcat: login requires a target\n");
|
|
11890
12800
|
return 2;
|
|
11891
12801
|
}
|
|
11892
12802
|
const target = positional[0];
|
|
12803
|
+
if (source instanceof JerkTokenSource) {
|
|
12804
|
+
try {
|
|
12805
|
+
return await jerkLogin(platform2, target, args);
|
|
12806
|
+
} catch (err) {
|
|
12807
|
+
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
12808
|
+
`);
|
|
12809
|
+
return 4;
|
|
12810
|
+
}
|
|
12811
|
+
}
|
|
12812
|
+
const noBrowser = args.includes("--no-browser");
|
|
11893
12813
|
let resolved;
|
|
11894
12814
|
try {
|
|
11895
12815
|
resolved = await resolveLoginTarget(platform2, target);
|
|
@@ -11971,6 +12891,15 @@ async function runLogout(platform2, args, source) {
|
|
|
11971
12891
|
return 2;
|
|
11972
12892
|
}
|
|
11973
12893
|
const target = positional[0];
|
|
12894
|
+
if (source instanceof JerkTokenSource) {
|
|
12895
|
+
try {
|
|
12896
|
+
return await jerkLogout(platform2, target);
|
|
12897
|
+
} catch (err) {
|
|
12898
|
+
await platform2.write(2, `mcpcat: logout failed: ${err.message}
|
|
12899
|
+
`);
|
|
12900
|
+
return 1;
|
|
12901
|
+
}
|
|
12902
|
+
}
|
|
11974
12903
|
let resolved;
|
|
11975
12904
|
try {
|
|
11976
12905
|
resolved = await resolveLoginTarget(platform2, target);
|
|
@@ -12343,6 +13272,49 @@ function checkAgentWarnings(name, config) {
|
|
|
12343
13272
|
return warnings;
|
|
12344
13273
|
}
|
|
12345
13274
|
|
|
13275
|
+
// src/serve/seal.ts
|
|
13276
|
+
async function sealSecret(platform2, name, value, options) {
|
|
13277
|
+
if (!platform2.seal) {
|
|
13278
|
+
throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
|
|
13279
|
+
}
|
|
13280
|
+
const request = JSON.stringify({
|
|
13281
|
+
v: 1,
|
|
13282
|
+
op: "seal",
|
|
13283
|
+
kind: "mcp",
|
|
13284
|
+
name,
|
|
13285
|
+
value,
|
|
13286
|
+
server: options.server,
|
|
13287
|
+
url: options.url
|
|
13288
|
+
});
|
|
13289
|
+
let responseText;
|
|
13290
|
+
try {
|
|
13291
|
+
responseText = await platform2.seal(request);
|
|
13292
|
+
} catch (err) {
|
|
13293
|
+
const code = err.code;
|
|
13294
|
+
if (code === "ENOENT" || code === "ENOTCONN") {
|
|
13295
|
+
throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
|
|
13296
|
+
}
|
|
13297
|
+
throw Object.assign(new Error(`vault: ${err.message}`), {
|
|
13298
|
+
sealErrno: code ?? "EACCES"
|
|
13299
|
+
});
|
|
13300
|
+
}
|
|
13301
|
+
const response = JSON.parse(responseText);
|
|
13302
|
+
if (response.error) {
|
|
13303
|
+
throw Object.assign(new Error(response.error), { sealErrno: response.errno ?? "EACCES" });
|
|
13304
|
+
}
|
|
13305
|
+
return { placeholder: response.placeholder };
|
|
13306
|
+
}
|
|
13307
|
+
function sealExitCode(errno) {
|
|
13308
|
+
if (errno === "EEXIST") return 2;
|
|
13309
|
+
return 4;
|
|
13310
|
+
}
|
|
13311
|
+
function sealErrorMessage(errno, name) {
|
|
13312
|
+
if (errno === "EEXIST") return `a non-bridge secret named ${name} already exists`;
|
|
13313
|
+
if (errno === "ENOTCONN") return "no vault available; use --bearer '${VAR}' or --no-auth";
|
|
13314
|
+
if (errno === "EACCES") return "vault locked; use --bearer '${VAR}' or --no-auth";
|
|
13315
|
+
return `vault error: ${errno}`;
|
|
13316
|
+
}
|
|
13317
|
+
|
|
12346
13318
|
// src/cli/config-verbs.ts
|
|
12347
13319
|
function parseHeader(hdr) {
|
|
12348
13320
|
const colonIdx = hdr.indexOf(":");
|
|
@@ -12364,6 +13336,10 @@ function parseAddFlagAt(flagArgs, i, flags) {
|
|
|
12364
13336
|
flags.noProbe = true;
|
|
12365
13337
|
return { next: i + 1 };
|
|
12366
13338
|
}
|
|
13339
|
+
if (arg === "--no-auth") {
|
|
13340
|
+
flags.noAuth = true;
|
|
13341
|
+
return { next: i + 1 };
|
|
13342
|
+
}
|
|
12367
13343
|
if (arg === "--bridge") {
|
|
12368
13344
|
flags.bridge = true;
|
|
12369
13345
|
return { next: i + 1 };
|
|
@@ -12409,6 +13385,7 @@ function parseAddFlags(args) {
|
|
|
12409
13385
|
local: false,
|
|
12410
13386
|
noProbe: false,
|
|
12411
13387
|
bridge: false,
|
|
13388
|
+
noAuth: false,
|
|
12412
13389
|
headers: [],
|
|
12413
13390
|
envPairs: [],
|
|
12414
13391
|
positional: [],
|
|
@@ -12771,6 +13748,11 @@ async function runImport(platform2, args) {
|
|
|
12771
13748
|
}
|
|
12772
13749
|
return 0;
|
|
12773
13750
|
}
|
|
13751
|
+
function generateBridgeToken() {
|
|
13752
|
+
const bytes = new Uint8Array(32);
|
|
13753
|
+
crypto.getRandomValues(bytes);
|
|
13754
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
13755
|
+
}
|
|
12774
13756
|
async function runBridge(platform2, args, version) {
|
|
12775
13757
|
if (platform2.kind === "host") {
|
|
12776
13758
|
await platform2.write(
|
|
@@ -12797,16 +13779,56 @@ async function runBridge(platform2, args, version) {
|
|
|
12797
13779
|
if (flags.bearer) {
|
|
12798
13780
|
await platform2.write(
|
|
12799
13781
|
2,
|
|
12800
|
-
"mcpcat add --bridge: --bearer is not
|
|
13782
|
+
"mcpcat add --bridge: --bearer is not supported for bridge; authentication is automatic via the vault\n"
|
|
12801
13783
|
);
|
|
12802
13784
|
return 2;
|
|
12803
13785
|
}
|
|
12804
13786
|
const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
|
|
12805
13787
|
serveOptions.proxyCommand = flags.cmdArgs;
|
|
12806
13788
|
const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
|
|
13789
|
+
if (flags.noAuth) {
|
|
13790
|
+
await platform2.write(
|
|
13791
|
+
2,
|
|
13792
|
+
"mcpcat add --bridge: warning: running without authentication on loopback\n"
|
|
13793
|
+
);
|
|
13794
|
+
serveOptions.noAuth = true;
|
|
13795
|
+
serveOptions.onListening = async (actualPort) => {
|
|
13796
|
+
const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
|
|
13797
|
+
const httpConfig = { url: bridgeUrl };
|
|
13798
|
+
if (flags.description) httpConfig.description = flags.description;
|
|
13799
|
+
if (flags.exposure) httpConfig.exposure = flags.exposure;
|
|
13800
|
+
const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
|
|
13801
|
+
await platform2.write(
|
|
13802
|
+
1,
|
|
13803
|
+
`${replaced ? "replaced" : "added"} ${name} in ${path}
|
|
13804
|
+
bridge does not survive a reload; runs in the foreground (start with &)
|
|
13805
|
+
`
|
|
13806
|
+
);
|
|
13807
|
+
};
|
|
13808
|
+
return runServe(platform2, serveOptions, version);
|
|
13809
|
+
}
|
|
13810
|
+
const token = generateBridgeToken();
|
|
13811
|
+
const secretName = `MCP_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_TOKEN`;
|
|
13812
|
+
serveOptions._resolvedBearer = token;
|
|
12807
13813
|
serveOptions.onListening = async (actualPort) => {
|
|
13814
|
+
if (actualPort === 9222) {
|
|
13815
|
+
await platform2.write(2, "mcpcat add --bridge: port 9222 is reserved; please retry\n");
|
|
13816
|
+
return;
|
|
13817
|
+
}
|
|
12808
13818
|
const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
|
|
12809
|
-
|
|
13819
|
+
try {
|
|
13820
|
+
await sealSecret(platform2, secretName, token, { server: name, url: bridgeUrl });
|
|
13821
|
+
} catch (err) {
|
|
13822
|
+
const errno = err.sealErrno ?? "EACCES";
|
|
13823
|
+
const code = sealExitCode(errno);
|
|
13824
|
+
await platform2.write(2, `mcpcat add --bridge: ${sealErrorMessage(errno, secretName)}
|
|
13825
|
+
`);
|
|
13826
|
+
platform2.exit(code);
|
|
13827
|
+
}
|
|
13828
|
+
const httpConfig = {
|
|
13829
|
+
url: bridgeUrl,
|
|
13830
|
+
headers: { Authorization: "Bearer ${" + secretName + "}" }
|
|
13831
|
+
};
|
|
12810
13832
|
if (flags.description) httpConfig.description = flags.description;
|
|
12811
13833
|
if (flags.exposure) httpConfig.exposure = flags.exposure;
|
|
12812
13834
|
const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
|
|
@@ -12943,8 +13965,18 @@ var CONFIG_VERBS = {
|
|
|
12943
13965
|
exposure: true,
|
|
12944
13966
|
import: true
|
|
12945
13967
|
};
|
|
12946
|
-
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["
|
|
12947
|
-
var CLIENT_VERBS = /* @__PURE__ */ new Set([
|
|
13968
|
+
var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["complete", "task"]);
|
|
13969
|
+
var CLIENT_VERBS = /* @__PURE__ */ new Set([
|
|
13970
|
+
"info",
|
|
13971
|
+
"tools",
|
|
13972
|
+
"call",
|
|
13973
|
+
"describe",
|
|
13974
|
+
"serve",
|
|
13975
|
+
"resources",
|
|
13976
|
+
"read",
|
|
13977
|
+
"prompts",
|
|
13978
|
+
"prompt"
|
|
13979
|
+
]);
|
|
12948
13980
|
function helpText(version) {
|
|
12949
13981
|
return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
|
|
12950
13982
|
|
|
@@ -12983,6 +14015,7 @@ Options:
|
|
|
12983
14015
|
-H, --header 'N: V' Extra header
|
|
12984
14016
|
-d <json> Tool arguments as JSON (or @file, @- for stdin)
|
|
12985
14017
|
-m, --timeout <s> Request timeout (default 60)
|
|
14018
|
+
--memory <MiB> Code mode memory limit (default 256, max 1024)
|
|
12986
14019
|
--protocol-version <v> Pin protocol version
|
|
12987
14020
|
--schema Print raw JSON Schema (describe)
|
|
12988
14021
|
--ts TypeScript declarations (describe)
|
|
@@ -12999,6 +14032,31 @@ Options:
|
|
|
12999
14032
|
-h, --help Show this help
|
|
13000
14033
|
`;
|
|
13001
14034
|
}
|
|
14035
|
+
var VERBS_WITH_TOOL = /* @__PURE__ */ new Set(["call", "prompt", "describe", "read"]);
|
|
14036
|
+
var VERBS_WITH_TARGET = /* @__PURE__ */ new Set([
|
|
14037
|
+
"info",
|
|
14038
|
+
"tools",
|
|
14039
|
+
"call",
|
|
14040
|
+
"describe",
|
|
14041
|
+
"serve",
|
|
14042
|
+
"grep",
|
|
14043
|
+
"code",
|
|
14044
|
+
"discover",
|
|
14045
|
+
"login",
|
|
14046
|
+
"logout",
|
|
14047
|
+
"resources",
|
|
14048
|
+
"read",
|
|
14049
|
+
"prompts",
|
|
14050
|
+
"prompt"
|
|
14051
|
+
]);
|
|
14052
|
+
function resolveMaxPositional(positional) {
|
|
14053
|
+
if (positional.length === 0) return 3;
|
|
14054
|
+
const first = positional[0];
|
|
14055
|
+
if (!VERBS.includes(first)) return 2;
|
|
14056
|
+
if (VERBS_WITH_TOOL.has(first)) return 3;
|
|
14057
|
+
if (VERBS_WITH_TARGET.has(first)) return 2;
|
|
14058
|
+
return 2;
|
|
14059
|
+
}
|
|
13002
14060
|
function parseFlags(args) {
|
|
13003
14061
|
const headers = {};
|
|
13004
14062
|
let dataArg;
|
|
@@ -13013,10 +14071,16 @@ function parseFlags(args) {
|
|
|
13013
14071
|
const serverTargets = [];
|
|
13014
14072
|
let allowShell = false;
|
|
13015
14073
|
let timeoutMs;
|
|
14074
|
+
let memoryLimitMiB;
|
|
13016
14075
|
let verbose = false;
|
|
13017
14076
|
let silent = false;
|
|
13018
14077
|
const positional = [];
|
|
13019
14078
|
let trailingCommand;
|
|
14079
|
+
let respondArg;
|
|
14080
|
+
const roots = [];
|
|
14081
|
+
let outputPath;
|
|
14082
|
+
let noValidate = false;
|
|
14083
|
+
const rest = [];
|
|
13020
14084
|
for (let i = 0; i < args.length; i++) {
|
|
13021
14085
|
const arg = args[i];
|
|
13022
14086
|
if (arg === "--") {
|
|
@@ -13057,8 +14121,41 @@ function parseFlags(args) {
|
|
|
13057
14121
|
silent = true;
|
|
13058
14122
|
} else if ((arg === "-m" || arg === "--timeout") && args[i + 1]) {
|
|
13059
14123
|
timeoutMs = Number(args[++i]) * 1e3;
|
|
14124
|
+
} else if (arg === "--respond" && args[i + 1]) {
|
|
14125
|
+
respondArg = args[++i];
|
|
14126
|
+
} else if (arg === "--root" && args[i + 1]) {
|
|
14127
|
+
roots.push(args[++i]);
|
|
14128
|
+
} else if ((arg === "-o" || arg === "--output") && args[i + 1]) {
|
|
14129
|
+
outputPath = args[++i];
|
|
14130
|
+
} else if (arg === "--memory" && args[i + 1]) {
|
|
14131
|
+
memoryLimitMiB = Number(args[++i]);
|
|
14132
|
+
} else if (arg === "--no-validate") {
|
|
14133
|
+
noValidate = true;
|
|
13060
14134
|
} else if (!arg.startsWith("-")) {
|
|
13061
14135
|
positional.push(arg);
|
|
14136
|
+
const maxPositional = resolveMaxPositional(positional);
|
|
14137
|
+
if (positional.length >= maxPositional) {
|
|
14138
|
+
rest.push(...args.slice(i + 1));
|
|
14139
|
+
break;
|
|
14140
|
+
}
|
|
14141
|
+
}
|
|
14142
|
+
}
|
|
14143
|
+
const toolArgs = [];
|
|
14144
|
+
for (let j = 0; j < rest.length; j++) {
|
|
14145
|
+
const ra = rest[j];
|
|
14146
|
+
if (ra === "-d" && rest[j + 1] !== void 0) {
|
|
14147
|
+
dataArg = rest[++j];
|
|
14148
|
+
} else if (ra === "--json") {
|
|
14149
|
+
jsonOutput = true;
|
|
14150
|
+
} else if (ra === "--no-validate") {
|
|
14151
|
+
noValidate = true;
|
|
14152
|
+
} else if ((ra === "-o" || ra === "--output") && rest[j + 1] !== void 0) {
|
|
14153
|
+
outputPath = rest[++j];
|
|
14154
|
+
} else if (ra === "--help" || ra === "-h") {
|
|
14155
|
+
toolArgs.length = 0;
|
|
14156
|
+
break;
|
|
14157
|
+
} else {
|
|
14158
|
+
toolArgs.push(ra);
|
|
13062
14159
|
}
|
|
13063
14160
|
}
|
|
13064
14161
|
return {
|
|
@@ -13075,10 +14172,16 @@ function parseFlags(args) {
|
|
|
13075
14172
|
serverTargets,
|
|
13076
14173
|
allowShell,
|
|
13077
14174
|
timeoutMs,
|
|
14175
|
+
memoryLimitMiB,
|
|
13078
14176
|
verbose,
|
|
13079
14177
|
silent,
|
|
13080
14178
|
positional,
|
|
13081
|
-
trailingCommand
|
|
14179
|
+
trailingCommand,
|
|
14180
|
+
respondArg,
|
|
14181
|
+
roots,
|
|
14182
|
+
outputPath,
|
|
14183
|
+
noValidate,
|
|
14184
|
+
rest: toolArgs
|
|
13082
14185
|
};
|
|
13083
14186
|
}
|
|
13084
14187
|
function resolveVerb(positional, trailingCommand) {
|
|
@@ -13113,6 +14216,7 @@ function parseArgs(args) {
|
|
|
13113
14216
|
const resolved = resolveVerb(flags.positional, flags.trailingCommand);
|
|
13114
14217
|
return {
|
|
13115
14218
|
...resolved,
|
|
14219
|
+
toolArgs: flags.rest,
|
|
13116
14220
|
headers: flags.headers,
|
|
13117
14221
|
dataArg: flags.dataArg,
|
|
13118
14222
|
protocolVersion: flags.protocolVersion,
|
|
@@ -13126,8 +14230,13 @@ function parseArgs(args) {
|
|
|
13126
14230
|
serverTargets: flags.serverTargets,
|
|
13127
14231
|
allowShell: flags.allowShell,
|
|
13128
14232
|
timeoutMs: flags.timeoutMs,
|
|
14233
|
+
memoryLimitMiB: flags.memoryLimitMiB,
|
|
13129
14234
|
verbose: flags.verbose,
|
|
13130
|
-
silent: flags.silent
|
|
14235
|
+
silent: flags.silent,
|
|
14236
|
+
noValidate: flags.noValidate,
|
|
14237
|
+
respondArg: flags.respondArg,
|
|
14238
|
+
roots: flags.roots,
|
|
14239
|
+
outputPath: flags.outputPath
|
|
13131
14240
|
};
|
|
13132
14241
|
}
|
|
13133
14242
|
function formatToolLine(tool) {
|
|
@@ -13250,7 +14359,8 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
13250
14359
|
const tools = await conn.listTools();
|
|
13251
14360
|
const tool = tools.find((t) => t.name === toolName);
|
|
13252
14361
|
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
13253
|
-
const
|
|
14362
|
+
const skipRequired = opts.noValidate || !!dataArg;
|
|
14363
|
+
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
13254
14364
|
if (!c.ok) {
|
|
13255
14365
|
await writeErr(platform2, opts.json ?? false, "usage", c.error);
|
|
13256
14366
|
return EXIT_USAGE;
|
|
@@ -13259,8 +14369,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
13259
14369
|
}
|
|
13260
14370
|
return renderCallResult(
|
|
13261
14371
|
platform2,
|
|
13262
|
-
await conn.callTool(toolName, fin, {
|
|
13263
|
-
|
|
14372
|
+
await conn.callTool(toolName, fin, {
|
|
14373
|
+
onProgress: opts.onProgress,
|
|
14374
|
+
inputResponses: opts.inputResponses
|
|
14375
|
+
}),
|
|
14376
|
+
{ json: opts.json, outputPath: opts.outputPath }
|
|
13264
14377
|
);
|
|
13265
14378
|
}
|
|
13266
14379
|
async function runDescribe(platform2, conn, toolName, showSchema, json) {
|
|
@@ -13297,19 +14410,168 @@ async function runDescribe(platform2, conn, toolName, showSchema, json) {
|
|
|
13297
14410
|
}
|
|
13298
14411
|
return EXIT_OK;
|
|
13299
14412
|
}
|
|
13300
|
-
async function
|
|
14413
|
+
async function runResources(platform2, conn, json) {
|
|
14414
|
+
const resources = await conn.listResources();
|
|
14415
|
+
const templates = await conn.listResourceTemplates();
|
|
14416
|
+
if (json) {
|
|
14417
|
+
await renderResourcesJson(platform2, resources, templates);
|
|
14418
|
+
return EXIT_OK;
|
|
14419
|
+
}
|
|
14420
|
+
if (resources.length === 0 && templates.length === 0) {
|
|
14421
|
+
await platform2.write(1, "(no resources)\n");
|
|
14422
|
+
return EXIT_OK;
|
|
14423
|
+
}
|
|
14424
|
+
for (const r of resources) {
|
|
14425
|
+
const desc = r.description ? ` ${r.description}` : "";
|
|
14426
|
+
const mime = r.mimeType ? ` [${r.mimeType}]` : "";
|
|
14427
|
+
await platform2.write(1, `${r.uri} ${r.name}${mime}${desc}
|
|
14428
|
+
`);
|
|
14429
|
+
}
|
|
14430
|
+
if (templates.length > 0) {
|
|
14431
|
+
if (resources.length > 0) await platform2.write(1, "\n");
|
|
14432
|
+
await platform2.write(1, "Templates:\n");
|
|
14433
|
+
for (const t of templates) {
|
|
14434
|
+
const desc = t.description ? ` ${t.description}` : "";
|
|
14435
|
+
await platform2.write(1, ` ${t.uriTemplate} ${t.name}${desc}
|
|
14436
|
+
`);
|
|
14437
|
+
}
|
|
14438
|
+
}
|
|
14439
|
+
return EXIT_OK;
|
|
14440
|
+
}
|
|
14441
|
+
async function runRead(platform2, conn, uri, json, outputPath) {
|
|
14442
|
+
const result = await conn.readResource(uri);
|
|
14443
|
+
if (json) {
|
|
14444
|
+
await renderReadResourceJson(platform2, result);
|
|
14445
|
+
return EXIT_OK;
|
|
14446
|
+
}
|
|
14447
|
+
for (const content of result.contents) {
|
|
14448
|
+
if ("text" in content) {
|
|
14449
|
+
if (outputPath) {
|
|
14450
|
+
await platform2.fs.writeFile(outputPath, content.text);
|
|
14451
|
+
await platform2.write(1, `${outputPath}
|
|
14452
|
+
`);
|
|
14453
|
+
} else {
|
|
14454
|
+
await platform2.write(1, `${content.text}
|
|
14455
|
+
`);
|
|
14456
|
+
}
|
|
14457
|
+
} else if ("blob" in content) {
|
|
14458
|
+
if (outputPath) {
|
|
14459
|
+
const bytes = Uint8Array.from(atob(content.blob), (c) => c.charCodeAt(0));
|
|
14460
|
+
await platform2.fs.writeFile(outputPath, bytes);
|
|
14461
|
+
await platform2.write(1, `${outputPath}
|
|
14462
|
+
`);
|
|
14463
|
+
} else {
|
|
14464
|
+
await platform2.write(1, `${content.blob}
|
|
14465
|
+
`);
|
|
14466
|
+
}
|
|
14467
|
+
}
|
|
14468
|
+
}
|
|
14469
|
+
return EXIT_OK;
|
|
14470
|
+
}
|
|
14471
|
+
async function runPrompts(platform2, conn, json) {
|
|
14472
|
+
const prompts = await conn.listPrompts();
|
|
14473
|
+
if (json) {
|
|
14474
|
+
await renderPromptsJson(platform2, prompts);
|
|
14475
|
+
return EXIT_OK;
|
|
14476
|
+
}
|
|
14477
|
+
if (prompts.length === 0) {
|
|
14478
|
+
await platform2.write(1, "(no prompts)\n");
|
|
14479
|
+
return EXIT_OK;
|
|
14480
|
+
}
|
|
14481
|
+
for (const p of prompts) {
|
|
14482
|
+
const desc = p.description ? ` ${p.description}` : "";
|
|
14483
|
+
await platform2.write(1, `${p.name}${desc}
|
|
14484
|
+
`);
|
|
14485
|
+
}
|
|
14486
|
+
return EXIT_OK;
|
|
14487
|
+
}
|
|
14488
|
+
async function runPrompt(platform2, conn, name, dataArg, toolArgs, json) {
|
|
14489
|
+
let args = {};
|
|
14490
|
+
if (dataArg) {
|
|
14491
|
+
args = await readDataArg(platform2, dataArg);
|
|
14492
|
+
}
|
|
14493
|
+
const prompts = await conn.listPrompts();
|
|
14494
|
+
const prompt = prompts.find((p) => p.name === name);
|
|
14495
|
+
const schema = {
|
|
14496
|
+
type: "object",
|
|
14497
|
+
properties: {},
|
|
14498
|
+
required: []
|
|
14499
|
+
};
|
|
14500
|
+
if (prompt?.arguments) {
|
|
14501
|
+
const props = {};
|
|
14502
|
+
const req = [];
|
|
14503
|
+
for (const a of prompt.arguments) {
|
|
14504
|
+
props[a.name] = { type: "string", description: a.description };
|
|
14505
|
+
if (a.required) req.push(a.name);
|
|
14506
|
+
}
|
|
14507
|
+
schema.properties = props;
|
|
14508
|
+
schema.required = req;
|
|
14509
|
+
}
|
|
14510
|
+
if (toolArgs.length > 0) {
|
|
14511
|
+
const skipRequired = !!dataArg;
|
|
14512
|
+
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
14513
|
+
if (!c.ok) {
|
|
14514
|
+
await writeErr(platform2, json, "usage", c.error);
|
|
14515
|
+
return EXIT_USAGE;
|
|
14516
|
+
}
|
|
14517
|
+
args = mergeArgs(args, c.value);
|
|
14518
|
+
} else if (!dataArg) {
|
|
14519
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
14520
|
+
for (const r of required) {
|
|
14521
|
+
if (!Object.hasOwn(args, r)) {
|
|
14522
|
+
await writeErr(platform2, json, "usage", `missing required flag --${r}`);
|
|
14523
|
+
return EXIT_USAGE;
|
|
14524
|
+
}
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14527
|
+
const result = await conn.getPrompt(name, args);
|
|
14528
|
+
if (json) {
|
|
14529
|
+
await renderPromptResultJson(platform2, result);
|
|
14530
|
+
return EXIT_OK;
|
|
14531
|
+
}
|
|
14532
|
+
if (result.description) {
|
|
14533
|
+
await platform2.write(1, `${result.description}
|
|
14534
|
+
|
|
14535
|
+
`);
|
|
14536
|
+
}
|
|
14537
|
+
for (const msg of result.messages) {
|
|
14538
|
+
const text = msg.content?.text ?? (msg.content?.data ? `[${msg.content.mimeType ?? "binary"}]` : "");
|
|
14539
|
+
await platform2.write(1, `[${msg.role}] ${text}
|
|
14540
|
+
`);
|
|
14541
|
+
}
|
|
14542
|
+
return EXIT_OK;
|
|
14543
|
+
}
|
|
14544
|
+
async function runVerb(platform2, parsed, conn, onProgress, respondData) {
|
|
13301
14545
|
switch (parsed.verb) {
|
|
13302
14546
|
case "info":
|
|
13303
14547
|
return runInfo(platform2, conn, parsed.jsonOutput);
|
|
13304
14548
|
case "tools":
|
|
13305
14549
|
return runTools(platform2, conn, parsed.jsonOutput);
|
|
13306
14550
|
case "call":
|
|
13307
|
-
return runCall(platform2, conn, parsed.toolName, parsed.dataArg,
|
|
14551
|
+
return runCall(platform2, conn, parsed.toolName, parsed.dataArg, parsed.toolArgs, {
|
|
13308
14552
|
json: parsed.jsonOutput,
|
|
13309
|
-
|
|
14553
|
+
noValidate: parsed.noValidate,
|
|
14554
|
+
outputPath: parsed.outputPath,
|
|
14555
|
+
onProgress,
|
|
14556
|
+
inputResponses: respondData
|
|
13310
14557
|
});
|
|
13311
14558
|
case "describe":
|
|
13312
14559
|
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
|
|
14560
|
+
case "resources":
|
|
14561
|
+
return runResources(platform2, conn, parsed.jsonOutput);
|
|
14562
|
+
case "read":
|
|
14563
|
+
return runRead(platform2, conn, parsed.toolName, parsed.jsonOutput, parsed.outputPath);
|
|
14564
|
+
case "prompts":
|
|
14565
|
+
return runPrompts(platform2, conn, parsed.jsonOutput);
|
|
14566
|
+
case "prompt":
|
|
14567
|
+
return runPrompt(
|
|
14568
|
+
platform2,
|
|
14569
|
+
conn,
|
|
14570
|
+
parsed.toolName,
|
|
14571
|
+
parsed.dataArg,
|
|
14572
|
+
parsed.toolArgs,
|
|
14573
|
+
parsed.jsonOutput
|
|
14574
|
+
);
|
|
13313
14575
|
default:
|
|
13314
14576
|
await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
|
|
13315
14577
|
`);
|
|
@@ -13447,6 +14709,18 @@ async function resolveTargetNames(platform2, parsed) {
|
|
|
13447
14709
|
return config.list().filter((e) => e.config.enabled !== false).map((e) => e.name);
|
|
13448
14710
|
}
|
|
13449
14711
|
async function dispatchMultiTarget(platform2, parsed, version) {
|
|
14712
|
+
let activeCleanup;
|
|
14713
|
+
async function killTreeAndExit(exitCode) {
|
|
14714
|
+
if (activeCleanup) {
|
|
14715
|
+
try {
|
|
14716
|
+
await Promise.race([activeCleanup(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
14717
|
+
} catch {
|
|
14718
|
+
}
|
|
14719
|
+
}
|
|
14720
|
+
platform2.exit(exitCode);
|
|
14721
|
+
}
|
|
14722
|
+
platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
|
|
14723
|
+
platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
|
|
13450
14724
|
const config = await MergedConfigSource.load(platform2);
|
|
13451
14725
|
if (parsed.verb === "code") {
|
|
13452
14726
|
let source;
|
|
@@ -13478,10 +14752,12 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13478
14752
|
parsed,
|
|
13479
14753
|
version
|
|
13480
14754
|
);
|
|
14755
|
+
activeCleanup = cleanup;
|
|
13481
14756
|
try {
|
|
13482
14757
|
return await runCode(targets, source, {
|
|
13483
14758
|
json: parsed.jsonOutput,
|
|
13484
14759
|
timeoutMs: parsed.timeoutMs,
|
|
14760
|
+
memoryLimitMiB: parsed.memoryLimitMiB,
|
|
13485
14761
|
allowShell: parsed.allowShell,
|
|
13486
14762
|
platform: platform2
|
|
13487
14763
|
});
|
|
@@ -13507,6 +14783,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13507
14783
|
parsed,
|
|
13508
14784
|
version
|
|
13509
14785
|
);
|
|
14786
|
+
activeCleanup = cleanup;
|
|
13510
14787
|
try {
|
|
13511
14788
|
return await runGrep(platform2, pattern, targets, {
|
|
13512
14789
|
rank: parsed.rank,
|
|
@@ -13525,6 +14802,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13525
14802
|
parsed,
|
|
13526
14803
|
version
|
|
13527
14804
|
);
|
|
14805
|
+
activeCleanup = cleanup;
|
|
13528
14806
|
try {
|
|
13529
14807
|
return await runDescribeTs(platform2, targets, parsed.tsBudget);
|
|
13530
14808
|
} finally {
|
|
@@ -13534,6 +14812,18 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
13534
14812
|
return 2;
|
|
13535
14813
|
}
|
|
13536
14814
|
async function run(platform2, version, args) {
|
|
14815
|
+
try {
|
|
14816
|
+
return await runInner(platform2, version, args);
|
|
14817
|
+
} catch (err) {
|
|
14818
|
+
if (err.code === "EACCES") {
|
|
14819
|
+
await platform2.write(2, `mcpcat: ${err.message}
|
|
14820
|
+
`);
|
|
14821
|
+
return EXIT_USAGE;
|
|
14822
|
+
}
|
|
14823
|
+
throw err;
|
|
14824
|
+
}
|
|
14825
|
+
}
|
|
14826
|
+
async function runInner(platform2, version, args) {
|
|
13537
14827
|
if (args.includes("--version") || args.includes("-V")) {
|
|
13538
14828
|
await platform2.write(1, `${version}
|
|
13539
14829
|
`);
|
|
@@ -13560,11 +14850,11 @@ async function run(platform2, version, args) {
|
|
|
13560
14850
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args);
|
|
13561
14851
|
if (discResult !== void 0) return discResult;
|
|
13562
14852
|
if (parsed.verb === "login") {
|
|
13563
|
-
const source = createTokenSource(platform2);
|
|
14853
|
+
const source = await createTokenSource(platform2);
|
|
13564
14854
|
return runLogin(platform2, args.slice(1), source);
|
|
13565
14855
|
}
|
|
13566
14856
|
if (parsed.verb === "logout") {
|
|
13567
|
-
const source = createTokenSource(platform2);
|
|
14857
|
+
const source = await createTokenSource(platform2);
|
|
13568
14858
|
return runLogout(platform2, args.slice(1), source);
|
|
13569
14859
|
}
|
|
13570
14860
|
if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
|
|
@@ -13587,6 +14877,14 @@ async function run(platform2, version, args) {
|
|
|
13587
14877
|
await platform2.write(2, "mcpcat: call requires a tool name\n");
|
|
13588
14878
|
return 2;
|
|
13589
14879
|
}
|
|
14880
|
+
if (parsed.verb === "read" && !parsed.toolName) {
|
|
14881
|
+
await platform2.write(2, "mcpcat: read requires a URI\n");
|
|
14882
|
+
return 2;
|
|
14883
|
+
}
|
|
14884
|
+
if (parsed.verb === "prompt" && !parsed.toolName) {
|
|
14885
|
+
await platform2.write(2, "mcpcat: prompt requires a name\n");
|
|
14886
|
+
return 2;
|
|
14887
|
+
}
|
|
13590
14888
|
if (!parsed.target) {
|
|
13591
14889
|
await platform2.write(2, `mcpcat: ${parsed.verb} requires a target
|
|
13592
14890
|
`);
|
|
@@ -13597,13 +14895,19 @@ async function run(platform2, version, args) {
|
|
|
13597
14895
|
function expandAndCollect(headers, env2) {
|
|
13598
14896
|
const secrets = [];
|
|
13599
14897
|
const result = {};
|
|
14898
|
+
const unset = [];
|
|
13600
14899
|
for (const [k, v] of Object.entries(headers)) {
|
|
14900
|
+
const missing = missingPlaceholders(v, env2);
|
|
14901
|
+
if (missing.length > 0) {
|
|
14902
|
+
unset.push(...missing);
|
|
14903
|
+
continue;
|
|
14904
|
+
}
|
|
13601
14905
|
const e = expandPlaceholders(v, env2);
|
|
13602
14906
|
result[k] = e;
|
|
13603
14907
|
if (e !== v) secrets.push(e);
|
|
13604
14908
|
if (v.length > 0) secrets.push(v);
|
|
13605
14909
|
}
|
|
13606
|
-
return { expanded: result, secrets };
|
|
14910
|
+
return { expanded: result, secrets, unset };
|
|
13607
14911
|
}
|
|
13608
14912
|
function classifyError(err) {
|
|
13609
14913
|
if (err instanceof InputRequiredError) {
|
|
@@ -13615,6 +14919,9 @@ function classifyError(err) {
|
|
|
13615
14919
|
if (err instanceof McpAuthError) {
|
|
13616
14920
|
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
13617
14921
|
}
|
|
14922
|
+
if (err instanceof JerkSignInRequiredError) {
|
|
14923
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
14924
|
+
}
|
|
13618
14925
|
if (err instanceof AuthStepUpError) {
|
|
13619
14926
|
const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
|
|
13620
14927
|
return {
|
|
@@ -13636,25 +14943,112 @@ function classifyError(err) {
|
|
|
13636
14943
|
}
|
|
13637
14944
|
return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
|
|
13638
14945
|
}
|
|
13639
|
-
function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
14946
|
+
async function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
|
|
13640
14947
|
if (hasAuthHeader || !url) return void 0;
|
|
13641
|
-
const source = createTokenSource(platform2);
|
|
14948
|
+
const source = await createTokenSource(platform2);
|
|
13642
14949
|
const entry = config.entry(target);
|
|
13643
14950
|
const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
|
|
13644
14951
|
const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
|
|
13645
14952
|
return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
|
|
13646
14953
|
}
|
|
14954
|
+
function classifyConnectionError(err, target, interrupted) {
|
|
14955
|
+
if (err instanceof McpAuthError || err instanceof AuthStepUpError || err instanceof JerkSignInRequiredError) {
|
|
14956
|
+
return classifyError(err);
|
|
14957
|
+
}
|
|
14958
|
+
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
14959
|
+
return { code: EXIT_AUTH, kind: "auth", msg: `unauthorized. Run: mcpcat login ${target}` };
|
|
14960
|
+
}
|
|
14961
|
+
if (interrupted) {
|
|
14962
|
+
return { code: EXIT_INTERRUPTED, kind: "interrupted", msg: "interrupted" };
|
|
14963
|
+
}
|
|
14964
|
+
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
14965
|
+
return { code: EXIT_CONNECTION, kind: "connection", msg };
|
|
14966
|
+
}
|
|
14967
|
+
function buildRoots(rootArgs, isStdio, cwd) {
|
|
14968
|
+
const roots = rootArgs.map((r) => ({
|
|
14969
|
+
uri: r.startsWith("file://") ? r : `file://${r}`
|
|
14970
|
+
}));
|
|
14971
|
+
if (roots.length === 0 && isStdio) {
|
|
14972
|
+
roots.push({ uri: `file://${cwd}` });
|
|
14973
|
+
}
|
|
14974
|
+
return roots.length > 0 ? roots : void 0;
|
|
14975
|
+
}
|
|
14976
|
+
async function loadRespondData(platform2, respondArg, json) {
|
|
14977
|
+
if (!respondArg) return { data: void 0 };
|
|
14978
|
+
try {
|
|
14979
|
+
return { data: await readDataArg(platform2, respondArg) };
|
|
14980
|
+
} catch (err) {
|
|
14981
|
+
await writeErr(
|
|
14982
|
+
platform2,
|
|
14983
|
+
json,
|
|
14984
|
+
"usage",
|
|
14985
|
+
`invalid --respond argument: ${err.message}`
|
|
14986
|
+
);
|
|
14987
|
+
return { exitCode: EXIT_USAGE };
|
|
14988
|
+
}
|
|
14989
|
+
}
|
|
14990
|
+
async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
14991
|
+
const parsed2 = JSON.parse(err.requestJson);
|
|
14992
|
+
const inputReq = parseInputRequest(parsed2.result ?? parsed2);
|
|
14993
|
+
if (!inputReq) {
|
|
14994
|
+
await platform2.write(2, `${err.requestJson}
|
|
14995
|
+
`);
|
|
14996
|
+
return EXIT_INPUT_REQUIRED;
|
|
14997
|
+
}
|
|
14998
|
+
const isTty = platform2.isatty(0);
|
|
14999
|
+
const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
|
|
15000
|
+
if ("exitCode" in elicitResult) {
|
|
15001
|
+
await platform2.write(2, `${err.requestJson}
|
|
15002
|
+
`);
|
|
15003
|
+
return EXIT_INPUT_REQUIRED;
|
|
15004
|
+
}
|
|
15005
|
+
try {
|
|
15006
|
+
const retryResult = await conn.callTool(parsed2.name, {
|
|
15007
|
+
...parsed2.arguments,
|
|
15008
|
+
_meta: { inputResponses: elicitResult.answers }
|
|
15009
|
+
});
|
|
15010
|
+
return renderCallResult(platform2, retryResult, { json });
|
|
15011
|
+
} catch (retryErr) {
|
|
15012
|
+
const e2 = classifyError(retryErr);
|
|
15013
|
+
await writeErr(platform2, json, e2.kind, e2.msg);
|
|
15014
|
+
return e2.code;
|
|
15015
|
+
}
|
|
15016
|
+
}
|
|
13647
15017
|
async function runClientVerb(platform2, version, parsed) {
|
|
13648
|
-
const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
|
|
15018
|
+
const { expanded, secrets, unset } = expandAndCollect(parsed.headers, platform2.env);
|
|
15019
|
+
if (unset.length > 0) {
|
|
15020
|
+
await platform2.write(
|
|
15021
|
+
2,
|
|
15022
|
+
`mcpcat: unset variable${unset.length > 1 ? "s" : ""} in -H: ${unset.join(", ")}
|
|
15023
|
+
`
|
|
15024
|
+
);
|
|
15025
|
+
return EXIT_USAGE;
|
|
15026
|
+
}
|
|
13649
15027
|
const verbose = createVerboseLogger(platform2, parsed.verbose);
|
|
13650
15028
|
for (const s of secrets) verbose.secrets.add(s);
|
|
13651
15029
|
const timeoutMs = parsed.timeoutMs;
|
|
13652
15030
|
const sigintController = new AbortController();
|
|
13653
15031
|
let interrupted = false;
|
|
15032
|
+
let termExitCode = EXIT_INTERRUPTED;
|
|
15033
|
+
let activeConn;
|
|
15034
|
+
async function killTreeAndExit(exitCode) {
|
|
15035
|
+
interrupted = true;
|
|
15036
|
+
termExitCode = exitCode;
|
|
15037
|
+
sigintController.abort();
|
|
15038
|
+
if (activeConn) {
|
|
15039
|
+
try {
|
|
15040
|
+
await Promise.race([activeConn.close(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
15041
|
+
} catch {
|
|
15042
|
+
}
|
|
15043
|
+
}
|
|
15044
|
+
platform2.exit(exitCode);
|
|
15045
|
+
}
|
|
13654
15046
|
platform2.onSignal?.("SIGINT", () => {
|
|
13655
15047
|
interrupted = true;
|
|
13656
15048
|
sigintController.abort();
|
|
13657
15049
|
});
|
|
15050
|
+
platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
|
|
15051
|
+
platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
|
|
13658
15052
|
const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
|
|
13659
15053
|
const onProgress = (progress, total, message) => {
|
|
13660
15054
|
if (!showProgress) return;
|
|
@@ -13673,59 +15067,91 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
13673
15067
|
await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
|
|
13674
15068
|
return EXIT_USAGE;
|
|
13675
15069
|
}
|
|
13676
|
-
const
|
|
13677
|
-
|
|
15070
|
+
const configHeaders = expandHeaders(resolved.headers, platform2.env, (msg) => {
|
|
15071
|
+
platform2.write(2, `mcpcat: warning: ${msg}
|
|
15072
|
+
`).catch(() => {
|
|
15073
|
+
});
|
|
15074
|
+
});
|
|
15075
|
+
const configSecrets = collectConfigSecrets(resolved.headers, platform2.env);
|
|
15076
|
+
for (const s of configSecrets) verbose.secrets.add(s);
|
|
15077
|
+
const allSecrets = /* @__PURE__ */ new Set([...secrets, ...configSecrets]);
|
|
15078
|
+
const redact2 = (text) => {
|
|
15079
|
+
let result = text;
|
|
15080
|
+
for (const secret of allSecrets) {
|
|
15081
|
+
if (secret.length > 0) {
|
|
15082
|
+
while (result.includes(secret)) {
|
|
15083
|
+
result = result.replace(secret, "[REDACTED]");
|
|
15084
|
+
}
|
|
15085
|
+
}
|
|
15086
|
+
}
|
|
15087
|
+
return result;
|
|
15088
|
+
};
|
|
15089
|
+
const mergedHeaders = { ...configHeaders, ...expanded };
|
|
15090
|
+
const hasAuthHeader = "Authorization" in mergedHeaders;
|
|
15091
|
+
const authProvider = await resolveAuthProvider(
|
|
15092
|
+
platform2,
|
|
15093
|
+
hasAuthHeader,
|
|
15094
|
+
resolved.url,
|
|
15095
|
+
target,
|
|
15096
|
+
config
|
|
15097
|
+
);
|
|
15098
|
+
const roots = buildRoots(parsed.roots, !!resolved.command, platform2.cwd);
|
|
15099
|
+
let respondData;
|
|
15100
|
+
if (parsed.respondArg) {
|
|
15101
|
+
const loaded = await loadRespondData(platform2, parsed.respondArg, parsed.jsonOutput);
|
|
15102
|
+
if ("exitCode" in loaded) return loaded.exitCode;
|
|
15103
|
+
respondData = loaded.data;
|
|
15104
|
+
}
|
|
13678
15105
|
let conn;
|
|
13679
15106
|
try {
|
|
13680
15107
|
conn = await connect(platform2, {
|
|
13681
15108
|
...resolved,
|
|
13682
|
-
headers:
|
|
15109
|
+
headers: mergedHeaders,
|
|
13683
15110
|
protocolVersion: parsed.protocolVersion,
|
|
13684
15111
|
version,
|
|
13685
15112
|
timeoutMs,
|
|
13686
15113
|
signal: sigintController.signal,
|
|
13687
15114
|
verbose,
|
|
13688
|
-
authProvider
|
|
15115
|
+
authProvider,
|
|
15116
|
+
roots
|
|
13689
15117
|
});
|
|
15118
|
+
activeConn = conn;
|
|
13690
15119
|
} catch (err) {
|
|
13691
|
-
if (err
|
|
13692
|
-
|
|
13693
|
-
await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
13694
|
-
return EXIT_AUTH;
|
|
13695
|
-
}
|
|
13696
|
-
if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
|
|
13697
|
-
await writeErr(
|
|
15120
|
+
if (isSpawnNotFound(err)) {
|
|
15121
|
+
await writeErrRedacted(
|
|
13698
15122
|
platform2,
|
|
13699
15123
|
parsed.jsonOutput,
|
|
13700
|
-
"
|
|
13701
|
-
`
|
|
15124
|
+
"connection",
|
|
15125
|
+
`command not found: ${resolved.command}`,
|
|
15126
|
+
redact2
|
|
13702
15127
|
);
|
|
13703
|
-
return
|
|
15128
|
+
return EXIT_CONNECTION;
|
|
13704
15129
|
}
|
|
13705
|
-
const
|
|
13706
|
-
if (
|
|
15130
|
+
const e = classifyConnectionError(err, target, interrupted);
|
|
15131
|
+
if (e.code === EXIT_INTERRUPTED) {
|
|
13707
15132
|
await platform2.write(2, "mcpcat: interrupted\n");
|
|
13708
|
-
return
|
|
15133
|
+
return termExitCode;
|
|
13709
15134
|
}
|
|
13710
|
-
await
|
|
13711
|
-
return
|
|
15135
|
+
await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
|
|
15136
|
+
return e.code;
|
|
13712
15137
|
}
|
|
13713
15138
|
try {
|
|
13714
|
-
const code = await runVerb(platform2, parsed, conn, onProgress);
|
|
15139
|
+
const code = await runVerb(platform2, parsed, conn, onProgress, respondData);
|
|
13715
15140
|
if (interrupted) {
|
|
13716
15141
|
await platform2.write(2, "mcpcat: interrupted\n");
|
|
13717
|
-
return
|
|
15142
|
+
return termExitCode;
|
|
13718
15143
|
}
|
|
13719
15144
|
return code;
|
|
13720
15145
|
} catch (err) {
|
|
13721
15146
|
if (interrupted) {
|
|
13722
15147
|
await platform2.write(2, "mcpcat: interrupted\n");
|
|
13723
|
-
return
|
|
15148
|
+
return termExitCode;
|
|
15149
|
+
}
|
|
15150
|
+
if (err instanceof InputRequiredError) {
|
|
15151
|
+
return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
|
|
13724
15152
|
}
|
|
13725
15153
|
const e = classifyError(err);
|
|
13726
|
-
|
|
13727
|
-
`);
|
|
13728
|
-
else await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
15154
|
+
await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
|
|
13729
15155
|
return e.code;
|
|
13730
15156
|
} finally {
|
|
13731
15157
|
try {
|
|
@@ -13739,6 +15165,28 @@ async function writeErr(p, json, kind, msg) {
|
|
|
13739
15165
|
else await p.write(2, `mcpcat: ${msg}
|
|
13740
15166
|
`);
|
|
13741
15167
|
}
|
|
15168
|
+
async function writeErrRedacted(p, json, kind, msg, redact2) {
|
|
15169
|
+
if (json) await renderErrorJson(p, kind, msg, void 0, void 0, redact2);
|
|
15170
|
+
else await p.write(2, `mcpcat: ${redact2(msg)}
|
|
15171
|
+
`);
|
|
15172
|
+
}
|
|
15173
|
+
function collectConfigSecrets(headers, env2) {
|
|
15174
|
+
if (!headers) return [];
|
|
15175
|
+
const secrets = [];
|
|
15176
|
+
for (const [, v] of Object.entries(headers)) {
|
|
15177
|
+
const expanded = expandPlaceholders(v, env2);
|
|
15178
|
+
if (expanded !== v && expanded.length > 0) secrets.push(expanded);
|
|
15179
|
+
if (v.length > 0 && v !== expanded) secrets.push(v);
|
|
15180
|
+
}
|
|
15181
|
+
return secrets;
|
|
15182
|
+
}
|
|
15183
|
+
function isSpawnNotFound(err) {
|
|
15184
|
+
if (!(err instanceof Error)) return false;
|
|
15185
|
+
const code = err.code;
|
|
15186
|
+
if (code === "ENOENT" || code === "EACCES") return true;
|
|
15187
|
+
const msg = err.message;
|
|
15188
|
+
return msg.includes("ENOENT") || msg.includes("EACCES");
|
|
15189
|
+
}
|
|
13742
15190
|
|
|
13743
15191
|
// src/platform/kernel.ts
|
|
13744
15192
|
function kernelFs(ctx) {
|
|
@@ -13750,15 +15198,20 @@ function kernelFs(ctx) {
|
|
|
13750
15198
|
stat: (path) => ctx.fs.stat(path),
|
|
13751
15199
|
exists: (path) => ctx.fs.exists(path),
|
|
13752
15200
|
async mkdirExclusive(path) {
|
|
13753
|
-
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
err.
|
|
15201
|
+
try {
|
|
15202
|
+
await ctx.fs.mkdir(path);
|
|
15203
|
+
} catch (err) {
|
|
15204
|
+
const msg = err.message ?? "";
|
|
15205
|
+
if (msg.includes("EEXIST") || msg.includes("already exists")) {
|
|
15206
|
+
const wrapped = new Error(`EEXIST: directory already exists, mkdir '${path}'`);
|
|
15207
|
+
wrapped.code = "EEXIST";
|
|
15208
|
+
throw wrapped;
|
|
15209
|
+
}
|
|
13757
15210
|
throw err;
|
|
13758
15211
|
}
|
|
13759
|
-
await ctx.fs.mkdir(path);
|
|
13760
15212
|
},
|
|
13761
|
-
rmdir: (path) => ctx.fs.rm(path)
|
|
15213
|
+
rmdir: (path) => ctx.fs.rm(path),
|
|
15214
|
+
unlink: (path) => ctx.fs.unlink(path)
|
|
13762
15215
|
};
|
|
13763
15216
|
}
|
|
13764
15217
|
async function kernelSpawn(ctx, options) {
|
|
@@ -13822,11 +15275,149 @@ async function kernelListen(ctx, options) {
|
|
|
13822
15275
|
}
|
|
13823
15276
|
};
|
|
13824
15277
|
}
|
|
15278
|
+
var KERNEL_LOCALHOST_RE = /^(\d+)\.kernel\.localhost$/;
|
|
15279
|
+
function extractLoopbackPort(url) {
|
|
15280
|
+
const m = KERNEL_LOCALHOST_RE.exec(url.hostname);
|
|
15281
|
+
if (m) return Number(m[1]);
|
|
15282
|
+
return null;
|
|
15283
|
+
}
|
|
15284
|
+
function collectHeaders(init) {
|
|
15285
|
+
const entries = [];
|
|
15286
|
+
if (!init?.headers) return entries;
|
|
15287
|
+
if (init.headers instanceof Headers) {
|
|
15288
|
+
for (const [k, v] of init.headers.entries()) entries.push([k, v]);
|
|
15289
|
+
} else if (Array.isArray(init.headers)) {
|
|
15290
|
+
for (const pair of init.headers) entries.push(pair);
|
|
15291
|
+
} else {
|
|
15292
|
+
for (const [k, v] of Object.entries(init.headers)) {
|
|
15293
|
+
if (v != null) entries.push([k, String(v)]);
|
|
15294
|
+
}
|
|
15295
|
+
}
|
|
15296
|
+
return entries;
|
|
15297
|
+
}
|
|
15298
|
+
function buildHttpRequest(method, url, headerEntries, bodyBytes) {
|
|
15299
|
+
const enc = new TextEncoder();
|
|
15300
|
+
const has = (n) => headerEntries.some(([k]) => k.toLowerCase() === n.toLowerCase());
|
|
15301
|
+
if (!has("host")) headerEntries.push(["Host", url.host]);
|
|
15302
|
+
if (!has("connection")) headerEntries.push(["Connection", "close"]);
|
|
15303
|
+
if (bodyBytes.length > 0 && !has("content-length")) {
|
|
15304
|
+
headerEntries.push(["Content-Length", String(bodyBytes.length)]);
|
|
15305
|
+
}
|
|
15306
|
+
const target = url.pathname + (url.search || "");
|
|
15307
|
+
let reqText = `${method} ${target} HTTP/1.1\r
|
|
15308
|
+
`;
|
|
15309
|
+
for (const [k, v] of headerEntries) reqText += `${k}: ${v}\r
|
|
15310
|
+
`;
|
|
15311
|
+
reqText += "\r\n";
|
|
15312
|
+
const headBytes = enc.encode(reqText);
|
|
15313
|
+
if (bodyBytes.length === 0) return headBytes;
|
|
15314
|
+
const full = new Uint8Array(headBytes.length + bodyBytes.length);
|
|
15315
|
+
full.set(headBytes);
|
|
15316
|
+
full.set(bodyBytes, headBytes.length);
|
|
15317
|
+
return full;
|
|
15318
|
+
}
|
|
15319
|
+
function parseHttpResponse(buf, headerPos, bufLen) {
|
|
15320
|
+
const dec = new TextDecoder();
|
|
15321
|
+
const headerText = dec.decode(new Uint8Array(buf.buffer, buf.byteOffset, headerPos));
|
|
15322
|
+
const lines = headerText.split("\r\n");
|
|
15323
|
+
const statusMatch = /^HTTP\/1\.1\s+(\d+)\s*(.*)/.exec(lines[0] || "");
|
|
15324
|
+
const status = statusMatch ? Number(statusMatch[1]) : 500;
|
|
15325
|
+
const statusText = statusMatch ? statusMatch[2] : "Error";
|
|
15326
|
+
const respHeaders = {};
|
|
15327
|
+
for (let i = 1; i < lines.length; i++) {
|
|
15328
|
+
const colonIdx = lines[i].indexOf(":");
|
|
15329
|
+
if (colonIdx > 0) {
|
|
15330
|
+
respHeaders[lines[i].slice(0, colonIdx).trim().toLowerCase()] = lines[i].slice(colonIdx + 1).trim();
|
|
15331
|
+
}
|
|
15332
|
+
}
|
|
15333
|
+
const contentLength = Number(respHeaders["content-length"] ?? 0);
|
|
15334
|
+
const headerEndByte = headerPos + 4;
|
|
15335
|
+
const responseBody = dec.decode(buf.slice(headerEndByte, headerEndByte + contentLength));
|
|
15336
|
+
return { status, statusText, headers: respHeaders, body: responseBody };
|
|
15337
|
+
}
|
|
15338
|
+
async function kernelLoopbackFetch(ctx, url, port, init) {
|
|
15339
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
15340
|
+
const enc = new TextEncoder();
|
|
15341
|
+
const dec = new TextDecoder();
|
|
15342
|
+
let bodyStr = "";
|
|
15343
|
+
if (init?.body != null) {
|
|
15344
|
+
bodyStr = typeof init.body === "string" ? init.body : dec.decode(init.body);
|
|
15345
|
+
}
|
|
15346
|
+
const bodyBytes = enc.encode(bodyStr);
|
|
15347
|
+
const headerEntries = collectHeaders(init);
|
|
15348
|
+
const reqBytes = buildHttpRequest(method, url, headerEntries, bodyBytes);
|
|
15349
|
+
const conn = await ctx.net.connect({ host: "127.0.0.1", port });
|
|
15350
|
+
const writer = conn.writable.getWriter();
|
|
15351
|
+
await writer.write(reqBytes);
|
|
15352
|
+
const reader = conn.readable.getReader();
|
|
15353
|
+
let buf = new Uint8Array(4096);
|
|
15354
|
+
let bufLen = 0;
|
|
15355
|
+
const headerResult = await readUntil(reader, buf, bufLen, "\r\n\r\n", 16384);
|
|
15356
|
+
if (!headerResult) {
|
|
15357
|
+
reader.releaseLock();
|
|
15358
|
+
try {
|
|
15359
|
+
await writer.close();
|
|
15360
|
+
} catch {
|
|
15361
|
+
}
|
|
15362
|
+
conn.close();
|
|
15363
|
+
throw new Error("incomplete HTTP response headers");
|
|
15364
|
+
}
|
|
15365
|
+
buf = headerResult.buf;
|
|
15366
|
+
bufLen = headerResult.bufLen;
|
|
15367
|
+
const contentLength = Number(
|
|
15368
|
+
dec.decode(new Uint8Array(buf.buffer, buf.byteOffset, headerResult.pos)).split("\r\n").find((l) => l.toLowerCase().startsWith("content-length:"))?.split(":")[1]?.trim() ?? 0
|
|
15369
|
+
);
|
|
15370
|
+
const totalNeeded = headerResult.pos + 4 + contentLength;
|
|
15371
|
+
if (bufLen < totalNeeded && contentLength > 0) {
|
|
15372
|
+
const bodyResult = await readExact(reader, buf, bufLen, totalNeeded);
|
|
15373
|
+
if (bodyResult) {
|
|
15374
|
+
buf = bodyResult.buf;
|
|
15375
|
+
bufLen = bodyResult.bufLen;
|
|
15376
|
+
}
|
|
15377
|
+
}
|
|
15378
|
+
const parsed = parseHttpResponse(buf, headerResult.pos, bufLen);
|
|
15379
|
+
reader.releaseLock();
|
|
15380
|
+
try {
|
|
15381
|
+
await writer.close();
|
|
15382
|
+
} catch {
|
|
15383
|
+
}
|
|
15384
|
+
conn.close();
|
|
15385
|
+
return new Response(parsed.body, {
|
|
15386
|
+
status: parsed.status,
|
|
15387
|
+
statusText: parsed.statusText,
|
|
15388
|
+
headers: parsed.headers
|
|
15389
|
+
});
|
|
15390
|
+
}
|
|
15391
|
+
function createKernelFetch(ctx) {
|
|
15392
|
+
return async (input, init) => {
|
|
15393
|
+
const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
15394
|
+
let url;
|
|
15395
|
+
try {
|
|
15396
|
+
url = new URL(urlStr);
|
|
15397
|
+
} catch {
|
|
15398
|
+
return ctx.fetch(input, init);
|
|
15399
|
+
}
|
|
15400
|
+
const port = extractLoopbackPort(url);
|
|
15401
|
+
if (port !== null) return kernelLoopbackFetch(ctx, url, port, init);
|
|
15402
|
+
return ctx.fetch(input, init);
|
|
15403
|
+
};
|
|
15404
|
+
}
|
|
15405
|
+
async function kernelSeal(ctx, request) {
|
|
15406
|
+
const f = await ctx.open("/dev/sealed", { read: true, write: true });
|
|
15407
|
+
try {
|
|
15408
|
+
const enc = new TextEncoder();
|
|
15409
|
+
await f.write(0, enc.encode(request));
|
|
15410
|
+
const responseBytes = await f.read(0, 65536);
|
|
15411
|
+
return new TextDecoder().decode(responseBytes);
|
|
15412
|
+
} finally {
|
|
15413
|
+
await f.close();
|
|
15414
|
+
}
|
|
15415
|
+
}
|
|
13825
15416
|
function createKernelPlatform(ctx, options) {
|
|
13826
15417
|
const encoder2 = new TextEncoder();
|
|
13827
15418
|
return {
|
|
13828
15419
|
kind: "kernel",
|
|
13829
|
-
fetch: ctx
|
|
15420
|
+
fetch: createKernelFetch(ctx),
|
|
13830
15421
|
spawn: (options2) => kernelSpawn(ctx, options2),
|
|
13831
15422
|
listen: (options2) => kernelListen(ctx, options2),
|
|
13832
15423
|
fs: kernelFs(ctx),
|
|
@@ -13845,17 +15436,26 @@ function createKernelPlatform(ctx, options) {
|
|
|
13845
15436
|
onSignal(signal, handler) {
|
|
13846
15437
|
ctx.signals.on(signal, handler);
|
|
13847
15438
|
},
|
|
15439
|
+
async seal(request) {
|
|
15440
|
+
return kernelSeal(ctx, request);
|
|
15441
|
+
},
|
|
13848
15442
|
packageDir: options?.packageDir
|
|
13849
15443
|
};
|
|
13850
15444
|
}
|
|
13851
15445
|
|
|
13852
15446
|
// src/kernel.ts
|
|
13853
15447
|
async function main(ctx) {
|
|
15448
|
+
if (ctx.signals?.ignore) {
|
|
15449
|
+
try {
|
|
15450
|
+
ctx.signals.ignore("SIGPIPE");
|
|
15451
|
+
} catch {
|
|
15452
|
+
}
|
|
15453
|
+
}
|
|
13854
15454
|
const platform2 = createKernelPlatform(ctx, {
|
|
13855
15455
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
13856
15456
|
});
|
|
13857
15457
|
const args = ctx.argv.slice(1);
|
|
13858
|
-
return run(platform2, "0.
|
|
15458
|
+
return run(platform2, "0.14.0", args);
|
|
13859
15459
|
}
|
|
13860
15460
|
export {
|
|
13861
15461
|
main as default
|