@ai-ecoverse/mcpcat 0.14.6 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/mcpcat.mjs +500 -21
- package/dist/kernel/mcpcat.mjs +500 -21
- package/package.json +2 -2
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -6793,7 +6793,13 @@ var CLIENT_NAME = "mcpcat";
|
|
|
6793
6793
|
var CALLBACK_HOST = "127.0.0.1";
|
|
6794
6794
|
var CALLBACK_PATH = "/callback";
|
|
6795
6795
|
var REQUEST_TIMEOUT_MS = 15e3;
|
|
6796
|
-
var
|
|
6796
|
+
var MAX_REFRESH_SKEW_MS = 3e4;
|
|
6797
|
+
function computeSkew(state) {
|
|
6798
|
+
const expiresIn = state?.tokens?.expires_in;
|
|
6799
|
+
if (typeof expiresIn !== "number" || expiresIn <= 0) return MAX_REFRESH_SKEW_MS;
|
|
6800
|
+
const ttlMs = expiresIn * 1e3;
|
|
6801
|
+
return Math.min(MAX_REFRESH_SKEW_MS, Math.floor(ttlMs / 2));
|
|
6802
|
+
}
|
|
6797
6803
|
var FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
6798
6804
|
function timedFetch(fetchImpl) {
|
|
6799
6805
|
return (input, init) => {
|
|
@@ -6941,7 +6947,8 @@ function createPiAuthProvider(options) {
|
|
|
6941
6947
|
cachedToken = state?.tokens?.access_token;
|
|
6942
6948
|
registerCredential(cachedToken);
|
|
6943
6949
|
if (state?.tokens?.refresh_token) registerCredential(state.tokens.refresh_token);
|
|
6944
|
-
const
|
|
6950
|
+
const skew = computeSkew(state);
|
|
6951
|
+
const expired = state?.tokensExpireAt !== void 0 && state.tokensExpireAt - skew <= Date.now();
|
|
6945
6952
|
if (expired && state?.tokens?.refresh_token) {
|
|
6946
6953
|
await refresh(cachedToken).catch(() => void 0);
|
|
6947
6954
|
const refreshedState = await store.load();
|
|
@@ -11796,7 +11803,8 @@ ${stderr}` : stderr : stdout;
|
|
|
11796
11803
|
return {
|
|
11797
11804
|
content: [{ type: "text", text }],
|
|
11798
11805
|
isError: result.exitCode !== 0,
|
|
11799
|
-
structuredContent: { stdout, stderr, exitCode: result.exitCode }
|
|
11806
|
+
structuredContent: { stdout, stderr, exitCode: result.exitCode },
|
|
11807
|
+
...result.meta ? { _meta: result.meta } : {}
|
|
11800
11808
|
};
|
|
11801
11809
|
}
|
|
11802
11810
|
function initializeResult(params, serverName, serverVersion) {
|
|
@@ -12096,6 +12104,219 @@ function createProxyHandler(options) {
|
|
|
12096
12104
|
return { handle, shutdown };
|
|
12097
12105
|
}
|
|
12098
12106
|
|
|
12107
|
+
// src/serve/webmcp.ts
|
|
12108
|
+
function buildBaseArgs(options) {
|
|
12109
|
+
const args2 = [];
|
|
12110
|
+
if (options.cdp) {
|
|
12111
|
+
args2.push("--cdp", options.cdp);
|
|
12112
|
+
}
|
|
12113
|
+
if (options.tab) {
|
|
12114
|
+
args2.push("--tab", options.tab);
|
|
12115
|
+
}
|
|
12116
|
+
return args2;
|
|
12117
|
+
}
|
|
12118
|
+
function generateNonce() {
|
|
12119
|
+
const bytes = new Uint8Array(8);
|
|
12120
|
+
crypto.getRandomValues(bytes);
|
|
12121
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
12122
|
+
}
|
|
12123
|
+
function wrapUntrusted(content) {
|
|
12124
|
+
const nonce = generateNonce();
|
|
12125
|
+
const openTag = `<<untrusted-page-content id=${nonce}>>`;
|
|
12126
|
+
const closeTag = `<<end id=${nonce}>>`;
|
|
12127
|
+
const neutralised = content.replaceAll("<<untrusted-page-content", "< <untrusted-page-content");
|
|
12128
|
+
return `${openTag}
|
|
12129
|
+
${neutralised}
|
|
12130
|
+
${closeTag}`;
|
|
12131
|
+
}
|
|
12132
|
+
async function runProcess(platform2, argv, stdin, timeoutMs) {
|
|
12133
|
+
let proc;
|
|
12134
|
+
try {
|
|
12135
|
+
proc = await platform2.spawn({ argv });
|
|
12136
|
+
} catch (err) {
|
|
12137
|
+
const code2 = err.code;
|
|
12138
|
+
if (code2 === "ENOENT" || code2 === "EACCES") {
|
|
12139
|
+
return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1 };
|
|
12140
|
+
}
|
|
12141
|
+
throw err;
|
|
12142
|
+
}
|
|
12143
|
+
if (stdin !== void 0) {
|
|
12144
|
+
const writer = proc.stdin.getWriter();
|
|
12145
|
+
await writer.write(new TextEncoder().encode(stdin));
|
|
12146
|
+
await writer.close();
|
|
12147
|
+
} else {
|
|
12148
|
+
try {
|
|
12149
|
+
await proc.stdin.getWriter().close();
|
|
12150
|
+
} catch {
|
|
12151
|
+
}
|
|
12152
|
+
}
|
|
12153
|
+
const stdoutChunks = [];
|
|
12154
|
+
const stderrChunks = [];
|
|
12155
|
+
const readStream3 = async (stream, into) => {
|
|
12156
|
+
const r = stream.getReader();
|
|
12157
|
+
try {
|
|
12158
|
+
for (; ; ) {
|
|
12159
|
+
const { done, value } = await r.read();
|
|
12160
|
+
if (done) break;
|
|
12161
|
+
into.push(value);
|
|
12162
|
+
}
|
|
12163
|
+
} finally {
|
|
12164
|
+
r.releaseLock();
|
|
12165
|
+
}
|
|
12166
|
+
};
|
|
12167
|
+
const timer = new Promise(
|
|
12168
|
+
(resolve) => setTimeout(() => resolve("timeout"), timeoutMs ?? 3e4)
|
|
12169
|
+
);
|
|
12170
|
+
const work = Promise.all([
|
|
12171
|
+
readStream3(proc.stdout, stdoutChunks),
|
|
12172
|
+
readStream3(proc.stderr, stderrChunks),
|
|
12173
|
+
proc.wait()
|
|
12174
|
+
]);
|
|
12175
|
+
const result = await Promise.race([work, timer]);
|
|
12176
|
+
if (result === "timeout") {
|
|
12177
|
+
await proc.kill("SIGTERM");
|
|
12178
|
+
return { stdout: "", stderr: "timed out", exitCode: 124 };
|
|
12179
|
+
}
|
|
12180
|
+
const [, , exitResult] = result;
|
|
12181
|
+
const dec = new TextDecoder();
|
|
12182
|
+
const concat2 = (chunks) => {
|
|
12183
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
12184
|
+
const out = new Uint8Array(total);
|
|
12185
|
+
let offset = 0;
|
|
12186
|
+
for (const c of chunks) {
|
|
12187
|
+
out.set(c, offset);
|
|
12188
|
+
offset += c.length;
|
|
12189
|
+
}
|
|
12190
|
+
return out;
|
|
12191
|
+
};
|
|
12192
|
+
return {
|
|
12193
|
+
stdout: dec.decode(concat2(stdoutChunks)),
|
|
12194
|
+
stderr: dec.decode(concat2(stderrChunks)),
|
|
12195
|
+
exitCode: exitResult.status
|
|
12196
|
+
};
|
|
12197
|
+
}
|
|
12198
|
+
function createWebMcpHandler(platform2, options) {
|
|
12199
|
+
let cache;
|
|
12200
|
+
const baseArgs = buildBaseArgs(options);
|
|
12201
|
+
async function listRaw() {
|
|
12202
|
+
const now = Date.now();
|
|
12203
|
+
if (cache && now < cache.expiresAt) {
|
|
12204
|
+
return { tools: cache.tools };
|
|
12205
|
+
}
|
|
12206
|
+
const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
|
|
12207
|
+
const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs);
|
|
12208
|
+
if (result.exitCode === -1) {
|
|
12209
|
+
return { tools: [], error: result.stderr, notFound: true };
|
|
12210
|
+
}
|
|
12211
|
+
if (result.exitCode !== 0) {
|
|
12212
|
+
return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
|
|
12213
|
+
}
|
|
12214
|
+
let parsed;
|
|
12215
|
+
try {
|
|
12216
|
+
parsed = JSON.parse(result.stdout);
|
|
12217
|
+
} catch {
|
|
12218
|
+
return { tools: [], error: "webmcp-list returned invalid JSON" };
|
|
12219
|
+
}
|
|
12220
|
+
if (!Array.isArray(parsed)) {
|
|
12221
|
+
return { tools: [], error: "webmcp-list did not return an array" };
|
|
12222
|
+
}
|
|
12223
|
+
const tools = parsed;
|
|
12224
|
+
cache = { tools, expiresAt: now + options.ttlMs };
|
|
12225
|
+
return { tools };
|
|
12226
|
+
}
|
|
12227
|
+
function mapDescriptionAnnotations(ann) {
|
|
12228
|
+
const mapped = { openWorldHint: true };
|
|
12229
|
+
if (!ann) return mapped;
|
|
12230
|
+
if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
|
|
12231
|
+
if (ann.consequentialHint) mapped.destructiveHint = true;
|
|
12232
|
+
return mapped;
|
|
12233
|
+
}
|
|
12234
|
+
function originPrefix(tools) {
|
|
12235
|
+
const origins = new Set(tools.map((t) => t.origin));
|
|
12236
|
+
return origins.size > 1;
|
|
12237
|
+
}
|
|
12238
|
+
function hostFromOrigin(origin) {
|
|
12239
|
+
try {
|
|
12240
|
+
return new URL(origin).host;
|
|
12241
|
+
} catch {
|
|
12242
|
+
return origin;
|
|
12243
|
+
}
|
|
12244
|
+
}
|
|
12245
|
+
function buildDescription(tool) {
|
|
12246
|
+
const parts = [];
|
|
12247
|
+
if (tool.description) parts.push(tool.description);
|
|
12248
|
+
parts.push(`[origin: ${tool.origin}]`);
|
|
12249
|
+
return parts.join("\n");
|
|
12250
|
+
}
|
|
12251
|
+
function buildToolName(tool, prefix) {
|
|
12252
|
+
const base = tool.name;
|
|
12253
|
+
if (!prefix) return base;
|
|
12254
|
+
const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
|
|
12255
|
+
return `${host}__${base}`;
|
|
12256
|
+
}
|
|
12257
|
+
async function getRpcTools() {
|
|
12258
|
+
const { tools, error, notFound } = await listRaw();
|
|
12259
|
+
if (notFound) return null;
|
|
12260
|
+
if (error) return [];
|
|
12261
|
+
const needPrefix = originPrefix(tools);
|
|
12262
|
+
const rpcTools = [];
|
|
12263
|
+
for (const tool of tools) {
|
|
12264
|
+
if (tool.annotations?.consequentialHint && !options.allowConsequential) {
|
|
12265
|
+
continue;
|
|
12266
|
+
}
|
|
12267
|
+
const mapped = mapDescriptionAnnotations(tool.annotations);
|
|
12268
|
+
rpcTools.push({
|
|
12269
|
+
name: buildToolName(tool, needPrefix),
|
|
12270
|
+
description: buildDescription(tool),
|
|
12271
|
+
inputSchema: tool.inputSchema,
|
|
12272
|
+
annotations: mapped
|
|
12273
|
+
});
|
|
12274
|
+
}
|
|
12275
|
+
return rpcTools;
|
|
12276
|
+
}
|
|
12277
|
+
async function callTool(name, args2) {
|
|
12278
|
+
const { tools, error } = await listRaw();
|
|
12279
|
+
if (error) {
|
|
12280
|
+
return { stdout: "", stderr: error, exitCode: 2 };
|
|
12281
|
+
}
|
|
12282
|
+
const needPrefix = originPrefix(tools);
|
|
12283
|
+
const tool = tools.find((t) => buildToolName(t, needPrefix) === name);
|
|
12284
|
+
if (!tool) {
|
|
12285
|
+
return { stdout: "", stderr: `unknown tool: ${name}`, exitCode: 2 };
|
|
12286
|
+
}
|
|
12287
|
+
const argv = [options.playwrightCli, "webmcp-call", tool.name, "-", ...baseArgs];
|
|
12288
|
+
if (options.allowConsequential) {
|
|
12289
|
+
argv.push("--allow-consequential");
|
|
12290
|
+
}
|
|
12291
|
+
const stdinData = JSON.stringify(args2 ?? {});
|
|
12292
|
+
const result = await runProcess(platform2, argv, stdinData, options.callTimeoutMs);
|
|
12293
|
+
if (result.exitCode === -1) {
|
|
12294
|
+
throw new Error(result.stderr || "playwright-cli not found");
|
|
12295
|
+
}
|
|
12296
|
+
if (result.exitCode === 2) {
|
|
12297
|
+
throw new Error(result.stderr || "webmcp-call error");
|
|
12298
|
+
}
|
|
12299
|
+
const meta = {
|
|
12300
|
+
"io.mcpcat/untrusted": true,
|
|
12301
|
+
origin: tool.origin
|
|
12302
|
+
};
|
|
12303
|
+
if (result.exitCode === 1) {
|
|
12304
|
+
return {
|
|
12305
|
+
stdout: "",
|
|
12306
|
+
stderr: wrapUntrusted(result.stderr || result.stdout),
|
|
12307
|
+
exitCode: 1,
|
|
12308
|
+
meta
|
|
12309
|
+
};
|
|
12310
|
+
}
|
|
12311
|
+
const text = wrapUntrusted(result.stdout);
|
|
12312
|
+
return { stdout: text, stderr: result.stderr, exitCode: 0, meta };
|
|
12313
|
+
}
|
|
12314
|
+
function invalidateCache() {
|
|
12315
|
+
cache = void 0;
|
|
12316
|
+
}
|
|
12317
|
+
return { getRpcTools, callTool, invalidateCache };
|
|
12318
|
+
}
|
|
12319
|
+
|
|
12099
12320
|
// src/serve/runtime.ts
|
|
12100
12321
|
var CALL_TIMEOUT_MS = 12e4;
|
|
12101
12322
|
var MAX_OUTPUT_DEFAULT = 1048576;
|
|
@@ -12130,6 +12351,10 @@ function parseSingleFlag(args2, i, opts) {
|
|
|
12130
12351
|
opts.json = true;
|
|
12131
12352
|
} else if (arg === "--no-auth") {
|
|
12132
12353
|
opts.noAuth = true;
|
|
12354
|
+
} else if (arg === "--webmcp") {
|
|
12355
|
+
opts.webmcp = true;
|
|
12356
|
+
} else if (arg === "--allow-consequential") {
|
|
12357
|
+
opts.webmcpAllowConsequential = true;
|
|
12133
12358
|
} else if (!arg.startsWith("-")) {
|
|
12134
12359
|
opts.commands.push(parseCommandTarget(arg));
|
|
12135
12360
|
} else {
|
|
@@ -12171,6 +12396,18 @@ function parseValueFlag(args2, i, opts) {
|
|
|
12171
12396
|
case "--cwd":
|
|
12172
12397
|
opts.cwd = args2[++i];
|
|
12173
12398
|
break;
|
|
12399
|
+
case "--tab":
|
|
12400
|
+
opts.webmcpTab = args2[++i];
|
|
12401
|
+
break;
|
|
12402
|
+
case "--webmcp-ttl":
|
|
12403
|
+
opts.webmcpTtl = Number(args2[++i]) * 1e3;
|
|
12404
|
+
break;
|
|
12405
|
+
case "--playwright-cli":
|
|
12406
|
+
opts.webmcpPlaywrightCli = args2[++i];
|
|
12407
|
+
break;
|
|
12408
|
+
case "--cdp":
|
|
12409
|
+
opts.webmcpCdp = args2[++i];
|
|
12410
|
+
break;
|
|
12174
12411
|
case "-H":
|
|
12175
12412
|
case "--header": {
|
|
12176
12413
|
const hdr = args2[++i];
|
|
@@ -12203,7 +12440,13 @@ function parseServeFlags(args2) {
|
|
|
12203
12440
|
json: false,
|
|
12204
12441
|
commands: [],
|
|
12205
12442
|
proxy: void 0,
|
|
12206
|
-
proxyHeaders: []
|
|
12443
|
+
proxyHeaders: [],
|
|
12444
|
+
webmcp: false,
|
|
12445
|
+
webmcpTab: void 0,
|
|
12446
|
+
webmcpTtl: 5e3,
|
|
12447
|
+
webmcpAllowConsequential: false,
|
|
12448
|
+
webmcpPlaywrightCli: void 0,
|
|
12449
|
+
webmcpCdp: void 0
|
|
12207
12450
|
};
|
|
12208
12451
|
for (let i = 0; i < args2.length; i++) {
|
|
12209
12452
|
i = parseSingleFlag(args2, i, opts);
|
|
@@ -12585,25 +12828,25 @@ async function discoverWithSignal(platform2, commands, childEnv, cwd) {
|
|
|
12585
12828
|
if (earlyShutdown) return { exitCode: 143, inflightDone };
|
|
12586
12829
|
return { clis };
|
|
12587
12830
|
}
|
|
12588
|
-
async function
|
|
12589
|
-
let resolvedBearer;
|
|
12590
|
-
let generatedBearer = false;
|
|
12831
|
+
async function resolveBearer(platform2, options) {
|
|
12591
12832
|
if (options._resolvedBearer) {
|
|
12592
|
-
|
|
12593
|
-
}
|
|
12833
|
+
return { resolved: options._resolvedBearer, generated: false };
|
|
12834
|
+
}
|
|
12835
|
+
if (options.bearer) {
|
|
12594
12836
|
const expanded = expandBearerToken(options.bearer, platform2.env);
|
|
12595
12837
|
if (!expanded) {
|
|
12596
12838
|
await platform2.write(
|
|
12597
12839
|
2,
|
|
12598
12840
|
"mcpcat serve: --bearer requires a ${NAME} variable reference, not a literal token. Use --bearer '${MY_TOKEN}' and set MY_TOKEN in the environment.\n"
|
|
12599
12841
|
);
|
|
12600
|
-
return 2;
|
|
12842
|
+
return { exitCode: 2 };
|
|
12601
12843
|
}
|
|
12602
|
-
|
|
12603
|
-
}
|
|
12844
|
+
return { resolved: expanded, generated: false };
|
|
12845
|
+
}
|
|
12846
|
+
if (options.noAuth) {
|
|
12604
12847
|
if (options.transport === "http" && !isLoopbackBind(options.host)) {
|
|
12605
12848
|
await platform2.write(2, "mcpcat serve: --no-auth is only allowed on loopback\n");
|
|
12606
|
-
return 2;
|
|
12849
|
+
return { exitCode: 2 };
|
|
12607
12850
|
}
|
|
12608
12851
|
if (options.transport === "http") {
|
|
12609
12852
|
await platform2.write(
|
|
@@ -12611,21 +12854,30 @@ async function runServe(platform2, options, version) {
|
|
|
12611
12854
|
"mcpcat serve: warning: running without authentication on loopback\n"
|
|
12612
12855
|
);
|
|
12613
12856
|
}
|
|
12614
|
-
|
|
12615
|
-
}
|
|
12857
|
+
return { resolved: void 0, generated: false };
|
|
12858
|
+
}
|
|
12859
|
+
if (options.transport === "http") {
|
|
12616
12860
|
if (platform2.kind === "kernel") {
|
|
12617
12861
|
await platform2.write(
|
|
12618
12862
|
2,
|
|
12619
12863
|
"mcpcat serve: kernel HTTP serve requires --bearer '${VAR}' or --no-auth\n"
|
|
12620
12864
|
);
|
|
12621
|
-
return 2;
|
|
12865
|
+
return { exitCode: 2 };
|
|
12622
12866
|
}
|
|
12623
|
-
|
|
12624
|
-
generatedBearer = true;
|
|
12867
|
+
return { resolved: generateToken(), generated: true };
|
|
12625
12868
|
}
|
|
12869
|
+
return { resolved: void 0, generated: false };
|
|
12870
|
+
}
|
|
12871
|
+
async function runServe(platform2, options, version) {
|
|
12872
|
+
const bearerResult = await resolveBearer(platform2, options);
|
|
12873
|
+
if ("exitCode" in bearerResult) return bearerResult.exitCode;
|
|
12874
|
+
const { resolved: resolvedBearer, generated: generatedBearer } = bearerResult;
|
|
12626
12875
|
if (options.proxy) {
|
|
12627
12876
|
return runProxyServe(platform2, options, version, resolvedBearer, generatedBearer);
|
|
12628
12877
|
}
|
|
12878
|
+
if (options.webmcp) {
|
|
12879
|
+
return runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer);
|
|
12880
|
+
}
|
|
12629
12881
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
12630
12882
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
12631
12883
|
const discovery = await discoverWithSignal(platform2, options.commands, childEnv, serveCwd);
|
|
@@ -12759,6 +13011,233 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
12759
13011
|
}
|
|
12760
13012
|
return { connectOpts, hiddenValues };
|
|
12761
13013
|
}
|
|
13014
|
+
function resolvePlaywrightCli(platform2, explicit) {
|
|
13015
|
+
if (explicit) return explicit;
|
|
13016
|
+
if (platform2.kind === "kernel") return "playwright-cli";
|
|
13017
|
+
const pathEnv = platform2.env.PATH ?? "";
|
|
13018
|
+
const sep = pathEnv.includes(";") ? ";" : ":";
|
|
13019
|
+
for (const dir of pathEnv.split(sep)) {
|
|
13020
|
+
if (!dir) continue;
|
|
13021
|
+
return "playwright-cli";
|
|
13022
|
+
}
|
|
13023
|
+
return void 0;
|
|
13024
|
+
}
|
|
13025
|
+
async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
13026
|
+
const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13027
|
+
if (!cliPath) {
|
|
13028
|
+
await platform2.write(
|
|
13029
|
+
2,
|
|
13030
|
+
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13031
|
+
);
|
|
13032
|
+
return 3;
|
|
13033
|
+
}
|
|
13034
|
+
const cdp = options.webmcpCdp ?? platform2.env.SLICC_CDP_URL;
|
|
13035
|
+
const handler = createWebMcpHandler(platform2, {
|
|
13036
|
+
playwrightCli: cliPath,
|
|
13037
|
+
tab: options.webmcpTab,
|
|
13038
|
+
cdp,
|
|
13039
|
+
allowConsequential: options.webmcpAllowConsequential,
|
|
13040
|
+
ttlMs: options.webmcpTtl,
|
|
13041
|
+
callTimeoutMs: options.callTimeout,
|
|
13042
|
+
maxOutput: options.maxOutput
|
|
13043
|
+
});
|
|
13044
|
+
const probe = await handler.getRpcTools();
|
|
13045
|
+
if (probe === null) {
|
|
13046
|
+
await platform2.write(
|
|
13047
|
+
2,
|
|
13048
|
+
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13049
|
+
);
|
|
13050
|
+
return 3;
|
|
13051
|
+
}
|
|
13052
|
+
const serverName = "mcpcat-webmcp";
|
|
13053
|
+
const getTools = async () => await handler.getRpcTools() ?? [];
|
|
13054
|
+
const callTool = (name, args2) => handler.callTool(name, args2);
|
|
13055
|
+
if (options.transport === "stdio") {
|
|
13056
|
+
return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
|
|
13057
|
+
}
|
|
13058
|
+
return runWebMcpHttp(
|
|
13059
|
+
platform2,
|
|
13060
|
+
options,
|
|
13061
|
+
getTools,
|
|
13062
|
+
callTool,
|
|
13063
|
+
serverName,
|
|
13064
|
+
version,
|
|
13065
|
+
resolvedBearer,
|
|
13066
|
+
generatedBearer
|
|
13067
|
+
);
|
|
13068
|
+
}
|
|
13069
|
+
async function runWebMcpStdio(platform2, getTools, callTool, serverName, version) {
|
|
13070
|
+
const reader = platform2.stdin.getReader();
|
|
13071
|
+
let buffer = "";
|
|
13072
|
+
const dec = new TextDecoder();
|
|
13073
|
+
const enc = new TextEncoder();
|
|
13074
|
+
try {
|
|
13075
|
+
for (; ; ) {
|
|
13076
|
+
const { done, value } = await reader.read();
|
|
13077
|
+
if (done) break;
|
|
13078
|
+
buffer += dec.decode(value, { stream: true });
|
|
13079
|
+
let nlIdx;
|
|
13080
|
+
while ((nlIdx = buffer.indexOf("\n")) >= 0) {
|
|
13081
|
+
const line = buffer.slice(0, nlIdx).trim();
|
|
13082
|
+
buffer = buffer.slice(nlIdx + 1);
|
|
13083
|
+
if (!line) continue;
|
|
13084
|
+
const tools = await getTools();
|
|
13085
|
+
const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
|
|
13086
|
+
if (outcome.status === 202) continue;
|
|
13087
|
+
const writer = platform2.stdout.getWriter();
|
|
13088
|
+
await writer.write(enc.encode(`${outcome.body}
|
|
13089
|
+
`));
|
|
13090
|
+
writer.releaseLock();
|
|
13091
|
+
}
|
|
13092
|
+
}
|
|
13093
|
+
} finally {
|
|
13094
|
+
reader.releaseLock();
|
|
13095
|
+
}
|
|
13096
|
+
return 0;
|
|
13097
|
+
}
|
|
13098
|
+
async function runWebMcpHttp(platform2, options, getTools, callTool, serverName, version, resolvedBearer, generatedBearer) {
|
|
13099
|
+
if (!platform2.listen) {
|
|
13100
|
+
await platform2.write(2, "mcpcat serve --webmcp: HTTP transport requires platform.listen\n");
|
|
13101
|
+
return 3;
|
|
13102
|
+
}
|
|
13103
|
+
const boundLoopback = isLoopbackBind(options.host);
|
|
13104
|
+
if (!boundLoopback && !resolvedBearer) {
|
|
13105
|
+
await platform2.write(
|
|
13106
|
+
2,
|
|
13107
|
+
"mcpcat serve --webmcp: --bearer is required when binding off loopback\n"
|
|
13108
|
+
);
|
|
13109
|
+
return 2;
|
|
13110
|
+
}
|
|
13111
|
+
const allowedOrigins = new Set(options.allowOrigins);
|
|
13112
|
+
const listener = await platform2.listen({ host: options.host, port: options.port });
|
|
13113
|
+
const actualPort = listener.port;
|
|
13114
|
+
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13115
|
+
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
13116
|
+
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
13117
|
+
let tokenFilePath;
|
|
13118
|
+
if (generatedBearer && resolvedBearer) {
|
|
13119
|
+
tokenFilePath = await publishBearer(
|
|
13120
|
+
platform2,
|
|
13121
|
+
resolvedBearer,
|
|
13122
|
+
actualPort,
|
|
13123
|
+
listenUrl,
|
|
13124
|
+
options.json
|
|
13125
|
+
);
|
|
13126
|
+
} else {
|
|
13127
|
+
if (options.json) {
|
|
13128
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
13129
|
+
`);
|
|
13130
|
+
}
|
|
13131
|
+
}
|
|
13132
|
+
await platform2.write(2, `mcpcat serve --webmcp: listening on ${listenUrl}
|
|
13133
|
+
`);
|
|
13134
|
+
if (options.onListening) {
|
|
13135
|
+
await options.onListening(actualPort);
|
|
13136
|
+
}
|
|
13137
|
+
let shutdownRequested = false;
|
|
13138
|
+
const shutdown = () => {
|
|
13139
|
+
if (shutdownRequested) return;
|
|
13140
|
+
shutdownRequested = true;
|
|
13141
|
+
listener.close();
|
|
13142
|
+
if (tokenFilePath) {
|
|
13143
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13144
|
+
});
|
|
13145
|
+
}
|
|
13146
|
+
};
|
|
13147
|
+
if (platform2.onSignal) {
|
|
13148
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
13149
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
13150
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
13151
|
+
}
|
|
13152
|
+
const handler = async (request) => {
|
|
13153
|
+
if (request.target !== "/mcp") {
|
|
13154
|
+
return {
|
|
13155
|
+
status: 404,
|
|
13156
|
+
reason: "Not Found",
|
|
13157
|
+
headers: { "content-type": "text/plain" },
|
|
13158
|
+
body: "Not Found"
|
|
13159
|
+
};
|
|
13160
|
+
}
|
|
13161
|
+
if (request.method !== "POST") {
|
|
13162
|
+
return {
|
|
13163
|
+
status: 405,
|
|
13164
|
+
reason: "Method Not Allowed",
|
|
13165
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
13166
|
+
body: "Method Not Allowed"
|
|
13167
|
+
};
|
|
13168
|
+
}
|
|
13169
|
+
if (!checkContentType(request.headers["content-type"])) {
|
|
13170
|
+
return {
|
|
13171
|
+
status: 415,
|
|
13172
|
+
reason: "Unsupported Media Type",
|
|
13173
|
+
headers: { "content-type": "text/plain" },
|
|
13174
|
+
body: "Content-Type must be application/json"
|
|
13175
|
+
};
|
|
13176
|
+
}
|
|
13177
|
+
if (!checkAcceptHeader(request.headers.accept)) {
|
|
13178
|
+
return {
|
|
13179
|
+
status: 406,
|
|
13180
|
+
reason: "Not Acceptable",
|
|
13181
|
+
headers: { "content-type": "text/plain" },
|
|
13182
|
+
body: "Accept must include application/json or text/event-stream"
|
|
13183
|
+
};
|
|
13184
|
+
}
|
|
13185
|
+
const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
|
|
13186
|
+
if (!originCheck.ok) {
|
|
13187
|
+
return {
|
|
13188
|
+
status: 403,
|
|
13189
|
+
reason: "Forbidden",
|
|
13190
|
+
headers: { "content-type": "text/plain" },
|
|
13191
|
+
body: originCheck.reason ?? "Forbidden"
|
|
13192
|
+
};
|
|
13193
|
+
}
|
|
13194
|
+
const hostCheck = checkHost(request.headers.host, allowedHosts);
|
|
13195
|
+
if (!hostCheck.ok) {
|
|
13196
|
+
return {
|
|
13197
|
+
status: 403,
|
|
13198
|
+
reason: "Forbidden",
|
|
13199
|
+
headers: { "content-type": "text/plain" },
|
|
13200
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
13201
|
+
};
|
|
13202
|
+
}
|
|
13203
|
+
if (resolvedBearer) {
|
|
13204
|
+
const authHeader = request.headers.authorization;
|
|
13205
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
13206
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
13207
|
+
return {
|
|
13208
|
+
status: 401,
|
|
13209
|
+
reason: "Unauthorized",
|
|
13210
|
+
headers: { "content-type": "text/plain" },
|
|
13211
|
+
body: "Unauthorized"
|
|
13212
|
+
};
|
|
13213
|
+
}
|
|
13214
|
+
}
|
|
13215
|
+
const tools = await getTools();
|
|
13216
|
+
const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
|
|
13217
|
+
return {
|
|
13218
|
+
status: outcome.status,
|
|
13219
|
+
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
13220
|
+
headers: { "content-type": outcome.contentType },
|
|
13221
|
+
body: outcome.body
|
|
13222
|
+
};
|
|
13223
|
+
};
|
|
13224
|
+
try {
|
|
13225
|
+
for await (const connection of listener) {
|
|
13226
|
+
if (shutdownRequested) break;
|
|
13227
|
+
const conn = connection;
|
|
13228
|
+
handleConnection(conn.readable, conn.writable, handler).catch(() => {
|
|
13229
|
+
}).finally(() => {
|
|
13230
|
+
try {
|
|
13231
|
+
conn.close();
|
|
13232
|
+
} catch {
|
|
13233
|
+
}
|
|
13234
|
+
});
|
|
13235
|
+
}
|
|
13236
|
+
} catch {
|
|
13237
|
+
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
13238
|
+
}
|
|
13239
|
+
return 0;
|
|
13240
|
+
}
|
|
12762
13241
|
async function runProxyServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
12763
13242
|
const built = await buildProxyConnectOpts(platform2, options, version);
|
|
12764
13243
|
if ("error" in built) {
|
|
@@ -15337,7 +15816,7 @@ async function dispatchDiscoveryVerb(platform2, parsed, args2) {
|
|
|
15337
15816
|
async function dispatchServeVerb(platform2, args2, version) {
|
|
15338
15817
|
const serveArgs = args2.slice(args2.indexOf("serve") + 1);
|
|
15339
15818
|
const serveOptions = parseServeFlags(serveArgs);
|
|
15340
|
-
if (!serveOptions.proxy && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
15819
|
+
if (!serveOptions.proxy && !serveOptions.webmcp && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
15341
15820
|
await platform2.write(2, "mcpcat serve: no commands or --tool templates given\n");
|
|
15342
15821
|
return 2;
|
|
15343
15822
|
}
|
|
@@ -15708,7 +16187,7 @@ async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
|
15708
16187
|
`);
|
|
15709
16188
|
return EXIT_INPUT_REQUIRED;
|
|
15710
16189
|
}
|
|
15711
|
-
const isTty = platform2.isatty(0) && platform2.isatty(2);
|
|
16190
|
+
const isTty = platform2.kind === "host" && platform2.isatty(0) && platform2.isatty(2);
|
|
15712
16191
|
const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
|
|
15713
16192
|
if ("exitCode" in elicitResult) {
|
|
15714
16193
|
await platform2.write(2, `${err.requestJson}
|
|
@@ -16170,5 +16649,5 @@ try {
|
|
|
16170
16649
|
});
|
|
16171
16650
|
} catch {
|
|
16172
16651
|
}
|
|
16173
|
-
var code = await run(platform, "0.
|
|
16652
|
+
var code = await run(platform, "0.15.0", args);
|
|
16174
16653
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -6414,7 +6414,13 @@ var CLIENT_NAME = "mcpcat";
|
|
|
6414
6414
|
var CALLBACK_HOST = "127.0.0.1";
|
|
6415
6415
|
var CALLBACK_PATH = "/callback";
|
|
6416
6416
|
var REQUEST_TIMEOUT_MS = 15e3;
|
|
6417
|
-
var
|
|
6417
|
+
var MAX_REFRESH_SKEW_MS = 3e4;
|
|
6418
|
+
function computeSkew(state) {
|
|
6419
|
+
const expiresIn = state?.tokens?.expires_in;
|
|
6420
|
+
if (typeof expiresIn !== "number" || expiresIn <= 0) return MAX_REFRESH_SKEW_MS;
|
|
6421
|
+
const ttlMs = expiresIn * 1e3;
|
|
6422
|
+
return Math.min(MAX_REFRESH_SKEW_MS, Math.floor(ttlMs / 2));
|
|
6423
|
+
}
|
|
6418
6424
|
var FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
6419
6425
|
function timedFetch(fetchImpl) {
|
|
6420
6426
|
return (input, init) => {
|
|
@@ -6562,7 +6568,8 @@ function createPiAuthProvider(options) {
|
|
|
6562
6568
|
cachedToken = state?.tokens?.access_token;
|
|
6563
6569
|
registerCredential(cachedToken);
|
|
6564
6570
|
if (state?.tokens?.refresh_token) registerCredential(state.tokens.refresh_token);
|
|
6565
|
-
const
|
|
6571
|
+
const skew = computeSkew(state);
|
|
6572
|
+
const expired = state?.tokensExpireAt !== void 0 && state.tokensExpireAt - skew <= Date.now();
|
|
6566
6573
|
if (expired && state?.tokens?.refresh_token) {
|
|
6567
6574
|
await refresh(cachedToken).catch(() => void 0);
|
|
6568
6575
|
const refreshedState = await store.load();
|
|
@@ -11417,7 +11424,8 @@ ${stderr}` : stderr : stdout;
|
|
|
11417
11424
|
return {
|
|
11418
11425
|
content: [{ type: "text", text }],
|
|
11419
11426
|
isError: result.exitCode !== 0,
|
|
11420
|
-
structuredContent: { stdout, stderr, exitCode: result.exitCode }
|
|
11427
|
+
structuredContent: { stdout, stderr, exitCode: result.exitCode },
|
|
11428
|
+
...result.meta ? { _meta: result.meta } : {}
|
|
11421
11429
|
};
|
|
11422
11430
|
}
|
|
11423
11431
|
function initializeResult(params, serverName, serverVersion) {
|
|
@@ -11717,6 +11725,219 @@ function createProxyHandler(options) {
|
|
|
11717
11725
|
return { handle, shutdown };
|
|
11718
11726
|
}
|
|
11719
11727
|
|
|
11728
|
+
// src/serve/webmcp.ts
|
|
11729
|
+
function buildBaseArgs(options) {
|
|
11730
|
+
const args = [];
|
|
11731
|
+
if (options.cdp) {
|
|
11732
|
+
args.push("--cdp", options.cdp);
|
|
11733
|
+
}
|
|
11734
|
+
if (options.tab) {
|
|
11735
|
+
args.push("--tab", options.tab);
|
|
11736
|
+
}
|
|
11737
|
+
return args;
|
|
11738
|
+
}
|
|
11739
|
+
function generateNonce() {
|
|
11740
|
+
const bytes = new Uint8Array(8);
|
|
11741
|
+
crypto.getRandomValues(bytes);
|
|
11742
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
11743
|
+
}
|
|
11744
|
+
function wrapUntrusted(content) {
|
|
11745
|
+
const nonce = generateNonce();
|
|
11746
|
+
const openTag = `<<untrusted-page-content id=${nonce}>>`;
|
|
11747
|
+
const closeTag = `<<end id=${nonce}>>`;
|
|
11748
|
+
const neutralised = content.replaceAll("<<untrusted-page-content", "< <untrusted-page-content");
|
|
11749
|
+
return `${openTag}
|
|
11750
|
+
${neutralised}
|
|
11751
|
+
${closeTag}`;
|
|
11752
|
+
}
|
|
11753
|
+
async function runProcess(platform2, argv, stdin, timeoutMs) {
|
|
11754
|
+
let proc;
|
|
11755
|
+
try {
|
|
11756
|
+
proc = await platform2.spawn({ argv });
|
|
11757
|
+
} catch (err) {
|
|
11758
|
+
const code = err.code;
|
|
11759
|
+
if (code === "ENOENT" || code === "EACCES") {
|
|
11760
|
+
return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1 };
|
|
11761
|
+
}
|
|
11762
|
+
throw err;
|
|
11763
|
+
}
|
|
11764
|
+
if (stdin !== void 0) {
|
|
11765
|
+
const writer = proc.stdin.getWriter();
|
|
11766
|
+
await writer.write(new TextEncoder().encode(stdin));
|
|
11767
|
+
await writer.close();
|
|
11768
|
+
} else {
|
|
11769
|
+
try {
|
|
11770
|
+
await proc.stdin.getWriter().close();
|
|
11771
|
+
} catch {
|
|
11772
|
+
}
|
|
11773
|
+
}
|
|
11774
|
+
const stdoutChunks = [];
|
|
11775
|
+
const stderrChunks = [];
|
|
11776
|
+
const readStream3 = async (stream, into) => {
|
|
11777
|
+
const r = stream.getReader();
|
|
11778
|
+
try {
|
|
11779
|
+
for (; ; ) {
|
|
11780
|
+
const { done, value } = await r.read();
|
|
11781
|
+
if (done) break;
|
|
11782
|
+
into.push(value);
|
|
11783
|
+
}
|
|
11784
|
+
} finally {
|
|
11785
|
+
r.releaseLock();
|
|
11786
|
+
}
|
|
11787
|
+
};
|
|
11788
|
+
const timer = new Promise(
|
|
11789
|
+
(resolve2) => setTimeout(() => resolve2("timeout"), timeoutMs ?? 3e4)
|
|
11790
|
+
);
|
|
11791
|
+
const work = Promise.all([
|
|
11792
|
+
readStream3(proc.stdout, stdoutChunks),
|
|
11793
|
+
readStream3(proc.stderr, stderrChunks),
|
|
11794
|
+
proc.wait()
|
|
11795
|
+
]);
|
|
11796
|
+
const result = await Promise.race([work, timer]);
|
|
11797
|
+
if (result === "timeout") {
|
|
11798
|
+
await proc.kill("SIGTERM");
|
|
11799
|
+
return { stdout: "", stderr: "timed out", exitCode: 124 };
|
|
11800
|
+
}
|
|
11801
|
+
const [, , exitResult] = result;
|
|
11802
|
+
const dec = new TextDecoder();
|
|
11803
|
+
const concat2 = (chunks) => {
|
|
11804
|
+
const total = chunks.reduce((s, c) => s + c.length, 0);
|
|
11805
|
+
const out = new Uint8Array(total);
|
|
11806
|
+
let offset = 0;
|
|
11807
|
+
for (const c of chunks) {
|
|
11808
|
+
out.set(c, offset);
|
|
11809
|
+
offset += c.length;
|
|
11810
|
+
}
|
|
11811
|
+
return out;
|
|
11812
|
+
};
|
|
11813
|
+
return {
|
|
11814
|
+
stdout: dec.decode(concat2(stdoutChunks)),
|
|
11815
|
+
stderr: dec.decode(concat2(stderrChunks)),
|
|
11816
|
+
exitCode: exitResult.status
|
|
11817
|
+
};
|
|
11818
|
+
}
|
|
11819
|
+
function createWebMcpHandler(platform2, options) {
|
|
11820
|
+
let cache;
|
|
11821
|
+
const baseArgs = buildBaseArgs(options);
|
|
11822
|
+
async function listRaw() {
|
|
11823
|
+
const now = Date.now();
|
|
11824
|
+
if (cache && now < cache.expiresAt) {
|
|
11825
|
+
return { tools: cache.tools };
|
|
11826
|
+
}
|
|
11827
|
+
const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
|
|
11828
|
+
const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs);
|
|
11829
|
+
if (result.exitCode === -1) {
|
|
11830
|
+
return { tools: [], error: result.stderr, notFound: true };
|
|
11831
|
+
}
|
|
11832
|
+
if (result.exitCode !== 0) {
|
|
11833
|
+
return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
|
|
11834
|
+
}
|
|
11835
|
+
let parsed;
|
|
11836
|
+
try {
|
|
11837
|
+
parsed = JSON.parse(result.stdout);
|
|
11838
|
+
} catch {
|
|
11839
|
+
return { tools: [], error: "webmcp-list returned invalid JSON" };
|
|
11840
|
+
}
|
|
11841
|
+
if (!Array.isArray(parsed)) {
|
|
11842
|
+
return { tools: [], error: "webmcp-list did not return an array" };
|
|
11843
|
+
}
|
|
11844
|
+
const tools = parsed;
|
|
11845
|
+
cache = { tools, expiresAt: now + options.ttlMs };
|
|
11846
|
+
return { tools };
|
|
11847
|
+
}
|
|
11848
|
+
function mapDescriptionAnnotations(ann) {
|
|
11849
|
+
const mapped = { openWorldHint: true };
|
|
11850
|
+
if (!ann) return mapped;
|
|
11851
|
+
if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
|
|
11852
|
+
if (ann.consequentialHint) mapped.destructiveHint = true;
|
|
11853
|
+
return mapped;
|
|
11854
|
+
}
|
|
11855
|
+
function originPrefix(tools) {
|
|
11856
|
+
const origins = new Set(tools.map((t) => t.origin));
|
|
11857
|
+
return origins.size > 1;
|
|
11858
|
+
}
|
|
11859
|
+
function hostFromOrigin(origin) {
|
|
11860
|
+
try {
|
|
11861
|
+
return new URL(origin).host;
|
|
11862
|
+
} catch {
|
|
11863
|
+
return origin;
|
|
11864
|
+
}
|
|
11865
|
+
}
|
|
11866
|
+
function buildDescription(tool) {
|
|
11867
|
+
const parts = [];
|
|
11868
|
+
if (tool.description) parts.push(tool.description);
|
|
11869
|
+
parts.push(`[origin: ${tool.origin}]`);
|
|
11870
|
+
return parts.join("\n");
|
|
11871
|
+
}
|
|
11872
|
+
function buildToolName(tool, prefix) {
|
|
11873
|
+
const base = tool.name;
|
|
11874
|
+
if (!prefix) return base;
|
|
11875
|
+
const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
|
|
11876
|
+
return `${host}__${base}`;
|
|
11877
|
+
}
|
|
11878
|
+
async function getRpcTools() {
|
|
11879
|
+
const { tools, error, notFound } = await listRaw();
|
|
11880
|
+
if (notFound) return null;
|
|
11881
|
+
if (error) return [];
|
|
11882
|
+
const needPrefix = originPrefix(tools);
|
|
11883
|
+
const rpcTools = [];
|
|
11884
|
+
for (const tool of tools) {
|
|
11885
|
+
if (tool.annotations?.consequentialHint && !options.allowConsequential) {
|
|
11886
|
+
continue;
|
|
11887
|
+
}
|
|
11888
|
+
const mapped = mapDescriptionAnnotations(tool.annotations);
|
|
11889
|
+
rpcTools.push({
|
|
11890
|
+
name: buildToolName(tool, needPrefix),
|
|
11891
|
+
description: buildDescription(tool),
|
|
11892
|
+
inputSchema: tool.inputSchema,
|
|
11893
|
+
annotations: mapped
|
|
11894
|
+
});
|
|
11895
|
+
}
|
|
11896
|
+
return rpcTools;
|
|
11897
|
+
}
|
|
11898
|
+
async function callTool(name, args) {
|
|
11899
|
+
const { tools, error } = await listRaw();
|
|
11900
|
+
if (error) {
|
|
11901
|
+
return { stdout: "", stderr: error, exitCode: 2 };
|
|
11902
|
+
}
|
|
11903
|
+
const needPrefix = originPrefix(tools);
|
|
11904
|
+
const tool = tools.find((t) => buildToolName(t, needPrefix) === name);
|
|
11905
|
+
if (!tool) {
|
|
11906
|
+
return { stdout: "", stderr: `unknown tool: ${name}`, exitCode: 2 };
|
|
11907
|
+
}
|
|
11908
|
+
const argv = [options.playwrightCli, "webmcp-call", tool.name, "-", ...baseArgs];
|
|
11909
|
+
if (options.allowConsequential) {
|
|
11910
|
+
argv.push("--allow-consequential");
|
|
11911
|
+
}
|
|
11912
|
+
const stdinData = JSON.stringify(args ?? {});
|
|
11913
|
+
const result = await runProcess(platform2, argv, stdinData, options.callTimeoutMs);
|
|
11914
|
+
if (result.exitCode === -1) {
|
|
11915
|
+
throw new Error(result.stderr || "playwright-cli not found");
|
|
11916
|
+
}
|
|
11917
|
+
if (result.exitCode === 2) {
|
|
11918
|
+
throw new Error(result.stderr || "webmcp-call error");
|
|
11919
|
+
}
|
|
11920
|
+
const meta = {
|
|
11921
|
+
"io.mcpcat/untrusted": true,
|
|
11922
|
+
origin: tool.origin
|
|
11923
|
+
};
|
|
11924
|
+
if (result.exitCode === 1) {
|
|
11925
|
+
return {
|
|
11926
|
+
stdout: "",
|
|
11927
|
+
stderr: wrapUntrusted(result.stderr || result.stdout),
|
|
11928
|
+
exitCode: 1,
|
|
11929
|
+
meta
|
|
11930
|
+
};
|
|
11931
|
+
}
|
|
11932
|
+
const text = wrapUntrusted(result.stdout);
|
|
11933
|
+
return { stdout: text, stderr: result.stderr, exitCode: 0, meta };
|
|
11934
|
+
}
|
|
11935
|
+
function invalidateCache() {
|
|
11936
|
+
cache = void 0;
|
|
11937
|
+
}
|
|
11938
|
+
return { getRpcTools, callTool, invalidateCache };
|
|
11939
|
+
}
|
|
11940
|
+
|
|
11720
11941
|
// src/serve/runtime.ts
|
|
11721
11942
|
var CALL_TIMEOUT_MS = 12e4;
|
|
11722
11943
|
var MAX_OUTPUT_DEFAULT = 1048576;
|
|
@@ -11751,6 +11972,10 @@ function parseSingleFlag(args, i, opts) {
|
|
|
11751
11972
|
opts.json = true;
|
|
11752
11973
|
} else if (arg === "--no-auth") {
|
|
11753
11974
|
opts.noAuth = true;
|
|
11975
|
+
} else if (arg === "--webmcp") {
|
|
11976
|
+
opts.webmcp = true;
|
|
11977
|
+
} else if (arg === "--allow-consequential") {
|
|
11978
|
+
opts.webmcpAllowConsequential = true;
|
|
11754
11979
|
} else if (!arg.startsWith("-")) {
|
|
11755
11980
|
opts.commands.push(parseCommandTarget(arg));
|
|
11756
11981
|
} else {
|
|
@@ -11792,6 +12017,18 @@ function parseValueFlag(args, i, opts) {
|
|
|
11792
12017
|
case "--cwd":
|
|
11793
12018
|
opts.cwd = args[++i];
|
|
11794
12019
|
break;
|
|
12020
|
+
case "--tab":
|
|
12021
|
+
opts.webmcpTab = args[++i];
|
|
12022
|
+
break;
|
|
12023
|
+
case "--webmcp-ttl":
|
|
12024
|
+
opts.webmcpTtl = Number(args[++i]) * 1e3;
|
|
12025
|
+
break;
|
|
12026
|
+
case "--playwright-cli":
|
|
12027
|
+
opts.webmcpPlaywrightCli = args[++i];
|
|
12028
|
+
break;
|
|
12029
|
+
case "--cdp":
|
|
12030
|
+
opts.webmcpCdp = args[++i];
|
|
12031
|
+
break;
|
|
11795
12032
|
case "-H":
|
|
11796
12033
|
case "--header": {
|
|
11797
12034
|
const hdr = args[++i];
|
|
@@ -11824,7 +12061,13 @@ function parseServeFlags(args) {
|
|
|
11824
12061
|
json: false,
|
|
11825
12062
|
commands: [],
|
|
11826
12063
|
proxy: void 0,
|
|
11827
|
-
proxyHeaders: []
|
|
12064
|
+
proxyHeaders: [],
|
|
12065
|
+
webmcp: false,
|
|
12066
|
+
webmcpTab: void 0,
|
|
12067
|
+
webmcpTtl: 5e3,
|
|
12068
|
+
webmcpAllowConsequential: false,
|
|
12069
|
+
webmcpPlaywrightCli: void 0,
|
|
12070
|
+
webmcpCdp: void 0
|
|
11828
12071
|
};
|
|
11829
12072
|
for (let i = 0; i < args.length; i++) {
|
|
11830
12073
|
i = parseSingleFlag(args, i, opts);
|
|
@@ -12206,25 +12449,25 @@ async function discoverWithSignal(platform2, commands, childEnv, cwd) {
|
|
|
12206
12449
|
if (earlyShutdown) return { exitCode: 143, inflightDone };
|
|
12207
12450
|
return { clis };
|
|
12208
12451
|
}
|
|
12209
|
-
async function
|
|
12210
|
-
let resolvedBearer;
|
|
12211
|
-
let generatedBearer = false;
|
|
12452
|
+
async function resolveBearer(platform2, options) {
|
|
12212
12453
|
if (options._resolvedBearer) {
|
|
12213
|
-
|
|
12214
|
-
}
|
|
12454
|
+
return { resolved: options._resolvedBearer, generated: false };
|
|
12455
|
+
}
|
|
12456
|
+
if (options.bearer) {
|
|
12215
12457
|
const expanded = expandBearerToken(options.bearer, platform2.env);
|
|
12216
12458
|
if (!expanded) {
|
|
12217
12459
|
await platform2.write(
|
|
12218
12460
|
2,
|
|
12219
12461
|
"mcpcat serve: --bearer requires a ${NAME} variable reference, not a literal token. Use --bearer '${MY_TOKEN}' and set MY_TOKEN in the environment.\n"
|
|
12220
12462
|
);
|
|
12221
|
-
return 2;
|
|
12463
|
+
return { exitCode: 2 };
|
|
12222
12464
|
}
|
|
12223
|
-
|
|
12224
|
-
}
|
|
12465
|
+
return { resolved: expanded, generated: false };
|
|
12466
|
+
}
|
|
12467
|
+
if (options.noAuth) {
|
|
12225
12468
|
if (options.transport === "http" && !isLoopbackBind(options.host)) {
|
|
12226
12469
|
await platform2.write(2, "mcpcat serve: --no-auth is only allowed on loopback\n");
|
|
12227
|
-
return 2;
|
|
12470
|
+
return { exitCode: 2 };
|
|
12228
12471
|
}
|
|
12229
12472
|
if (options.transport === "http") {
|
|
12230
12473
|
await platform2.write(
|
|
@@ -12232,21 +12475,30 @@ async function runServe(platform2, options, version) {
|
|
|
12232
12475
|
"mcpcat serve: warning: running without authentication on loopback\n"
|
|
12233
12476
|
);
|
|
12234
12477
|
}
|
|
12235
|
-
|
|
12236
|
-
}
|
|
12478
|
+
return { resolved: void 0, generated: false };
|
|
12479
|
+
}
|
|
12480
|
+
if (options.transport === "http") {
|
|
12237
12481
|
if (platform2.kind === "kernel") {
|
|
12238
12482
|
await platform2.write(
|
|
12239
12483
|
2,
|
|
12240
12484
|
"mcpcat serve: kernel HTTP serve requires --bearer '${VAR}' or --no-auth\n"
|
|
12241
12485
|
);
|
|
12242
|
-
return 2;
|
|
12486
|
+
return { exitCode: 2 };
|
|
12243
12487
|
}
|
|
12244
|
-
|
|
12245
|
-
generatedBearer = true;
|
|
12488
|
+
return { resolved: generateToken(), generated: true };
|
|
12246
12489
|
}
|
|
12490
|
+
return { resolved: void 0, generated: false };
|
|
12491
|
+
}
|
|
12492
|
+
async function runServe(platform2, options, version) {
|
|
12493
|
+
const bearerResult = await resolveBearer(platform2, options);
|
|
12494
|
+
if ("exitCode" in bearerResult) return bearerResult.exitCode;
|
|
12495
|
+
const { resolved: resolvedBearer, generated: generatedBearer } = bearerResult;
|
|
12247
12496
|
if (options.proxy) {
|
|
12248
12497
|
return runProxyServe(platform2, options, version, resolvedBearer, generatedBearer);
|
|
12249
12498
|
}
|
|
12499
|
+
if (options.webmcp) {
|
|
12500
|
+
return runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer);
|
|
12501
|
+
}
|
|
12250
12502
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
12251
12503
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
12252
12504
|
const discovery = await discoverWithSignal(platform2, options.commands, childEnv, serveCwd);
|
|
@@ -12380,6 +12632,233 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
12380
12632
|
}
|
|
12381
12633
|
return { connectOpts, hiddenValues };
|
|
12382
12634
|
}
|
|
12635
|
+
function resolvePlaywrightCli(platform2, explicit) {
|
|
12636
|
+
if (explicit) return explicit;
|
|
12637
|
+
if (platform2.kind === "kernel") return "playwright-cli";
|
|
12638
|
+
const pathEnv = platform2.env.PATH ?? "";
|
|
12639
|
+
const sep = pathEnv.includes(";") ? ";" : ":";
|
|
12640
|
+
for (const dir of pathEnv.split(sep)) {
|
|
12641
|
+
if (!dir) continue;
|
|
12642
|
+
return "playwright-cli";
|
|
12643
|
+
}
|
|
12644
|
+
return void 0;
|
|
12645
|
+
}
|
|
12646
|
+
async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
12647
|
+
const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
12648
|
+
if (!cliPath) {
|
|
12649
|
+
await platform2.write(
|
|
12650
|
+
2,
|
|
12651
|
+
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
12652
|
+
);
|
|
12653
|
+
return 3;
|
|
12654
|
+
}
|
|
12655
|
+
const cdp = options.webmcpCdp ?? platform2.env.SLICC_CDP_URL;
|
|
12656
|
+
const handler = createWebMcpHandler(platform2, {
|
|
12657
|
+
playwrightCli: cliPath,
|
|
12658
|
+
tab: options.webmcpTab,
|
|
12659
|
+
cdp,
|
|
12660
|
+
allowConsequential: options.webmcpAllowConsequential,
|
|
12661
|
+
ttlMs: options.webmcpTtl,
|
|
12662
|
+
callTimeoutMs: options.callTimeout,
|
|
12663
|
+
maxOutput: options.maxOutput
|
|
12664
|
+
});
|
|
12665
|
+
const probe = await handler.getRpcTools();
|
|
12666
|
+
if (probe === null) {
|
|
12667
|
+
await platform2.write(
|
|
12668
|
+
2,
|
|
12669
|
+
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
12670
|
+
);
|
|
12671
|
+
return 3;
|
|
12672
|
+
}
|
|
12673
|
+
const serverName = "mcpcat-webmcp";
|
|
12674
|
+
const getTools = async () => await handler.getRpcTools() ?? [];
|
|
12675
|
+
const callTool = (name, args) => handler.callTool(name, args);
|
|
12676
|
+
if (options.transport === "stdio") {
|
|
12677
|
+
return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
|
|
12678
|
+
}
|
|
12679
|
+
return runWebMcpHttp(
|
|
12680
|
+
platform2,
|
|
12681
|
+
options,
|
|
12682
|
+
getTools,
|
|
12683
|
+
callTool,
|
|
12684
|
+
serverName,
|
|
12685
|
+
version,
|
|
12686
|
+
resolvedBearer,
|
|
12687
|
+
generatedBearer
|
|
12688
|
+
);
|
|
12689
|
+
}
|
|
12690
|
+
async function runWebMcpStdio(platform2, getTools, callTool, serverName, version) {
|
|
12691
|
+
const reader = platform2.stdin.getReader();
|
|
12692
|
+
let buffer = "";
|
|
12693
|
+
const dec = new TextDecoder();
|
|
12694
|
+
const enc = new TextEncoder();
|
|
12695
|
+
try {
|
|
12696
|
+
for (; ; ) {
|
|
12697
|
+
const { done, value } = await reader.read();
|
|
12698
|
+
if (done) break;
|
|
12699
|
+
buffer += dec.decode(value, { stream: true });
|
|
12700
|
+
let nlIdx;
|
|
12701
|
+
while ((nlIdx = buffer.indexOf("\n")) >= 0) {
|
|
12702
|
+
const line = buffer.slice(0, nlIdx).trim();
|
|
12703
|
+
buffer = buffer.slice(nlIdx + 1);
|
|
12704
|
+
if (!line) continue;
|
|
12705
|
+
const tools = await getTools();
|
|
12706
|
+
const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
|
|
12707
|
+
if (outcome.status === 202) continue;
|
|
12708
|
+
const writer = platform2.stdout.getWriter();
|
|
12709
|
+
await writer.write(enc.encode(`${outcome.body}
|
|
12710
|
+
`));
|
|
12711
|
+
writer.releaseLock();
|
|
12712
|
+
}
|
|
12713
|
+
}
|
|
12714
|
+
} finally {
|
|
12715
|
+
reader.releaseLock();
|
|
12716
|
+
}
|
|
12717
|
+
return 0;
|
|
12718
|
+
}
|
|
12719
|
+
async function runWebMcpHttp(platform2, options, getTools, callTool, serverName, version, resolvedBearer, generatedBearer) {
|
|
12720
|
+
if (!platform2.listen) {
|
|
12721
|
+
await platform2.write(2, "mcpcat serve --webmcp: HTTP transport requires platform.listen\n");
|
|
12722
|
+
return 3;
|
|
12723
|
+
}
|
|
12724
|
+
const boundLoopback = isLoopbackBind(options.host);
|
|
12725
|
+
if (!boundLoopback && !resolvedBearer) {
|
|
12726
|
+
await platform2.write(
|
|
12727
|
+
2,
|
|
12728
|
+
"mcpcat serve --webmcp: --bearer is required when binding off loopback\n"
|
|
12729
|
+
);
|
|
12730
|
+
return 2;
|
|
12731
|
+
}
|
|
12732
|
+
const allowedOrigins = new Set(options.allowOrigins);
|
|
12733
|
+
const listener = await platform2.listen({ host: options.host, port: options.port });
|
|
12734
|
+
const actualPort = listener.port;
|
|
12735
|
+
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
12736
|
+
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
12737
|
+
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
12738
|
+
let tokenFilePath;
|
|
12739
|
+
if (generatedBearer && resolvedBearer) {
|
|
12740
|
+
tokenFilePath = await publishBearer(
|
|
12741
|
+
platform2,
|
|
12742
|
+
resolvedBearer,
|
|
12743
|
+
actualPort,
|
|
12744
|
+
listenUrl,
|
|
12745
|
+
options.json
|
|
12746
|
+
);
|
|
12747
|
+
} else {
|
|
12748
|
+
if (options.json) {
|
|
12749
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
12750
|
+
`);
|
|
12751
|
+
}
|
|
12752
|
+
}
|
|
12753
|
+
await platform2.write(2, `mcpcat serve --webmcp: listening on ${listenUrl}
|
|
12754
|
+
`);
|
|
12755
|
+
if (options.onListening) {
|
|
12756
|
+
await options.onListening(actualPort);
|
|
12757
|
+
}
|
|
12758
|
+
let shutdownRequested = false;
|
|
12759
|
+
const shutdown = () => {
|
|
12760
|
+
if (shutdownRequested) return;
|
|
12761
|
+
shutdownRequested = true;
|
|
12762
|
+
listener.close();
|
|
12763
|
+
if (tokenFilePath) {
|
|
12764
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
12765
|
+
});
|
|
12766
|
+
}
|
|
12767
|
+
};
|
|
12768
|
+
if (platform2.onSignal) {
|
|
12769
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
12770
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
12771
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
12772
|
+
}
|
|
12773
|
+
const handler = async (request) => {
|
|
12774
|
+
if (request.target !== "/mcp") {
|
|
12775
|
+
return {
|
|
12776
|
+
status: 404,
|
|
12777
|
+
reason: "Not Found",
|
|
12778
|
+
headers: { "content-type": "text/plain" },
|
|
12779
|
+
body: "Not Found"
|
|
12780
|
+
};
|
|
12781
|
+
}
|
|
12782
|
+
if (request.method !== "POST") {
|
|
12783
|
+
return {
|
|
12784
|
+
status: 405,
|
|
12785
|
+
reason: "Method Not Allowed",
|
|
12786
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
12787
|
+
body: "Method Not Allowed"
|
|
12788
|
+
};
|
|
12789
|
+
}
|
|
12790
|
+
if (!checkContentType(request.headers["content-type"])) {
|
|
12791
|
+
return {
|
|
12792
|
+
status: 415,
|
|
12793
|
+
reason: "Unsupported Media Type",
|
|
12794
|
+
headers: { "content-type": "text/plain" },
|
|
12795
|
+
body: "Content-Type must be application/json"
|
|
12796
|
+
};
|
|
12797
|
+
}
|
|
12798
|
+
if (!checkAcceptHeader(request.headers.accept)) {
|
|
12799
|
+
return {
|
|
12800
|
+
status: 406,
|
|
12801
|
+
reason: "Not Acceptable",
|
|
12802
|
+
headers: { "content-type": "text/plain" },
|
|
12803
|
+
body: "Accept must include application/json or text/event-stream"
|
|
12804
|
+
};
|
|
12805
|
+
}
|
|
12806
|
+
const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
|
|
12807
|
+
if (!originCheck.ok) {
|
|
12808
|
+
return {
|
|
12809
|
+
status: 403,
|
|
12810
|
+
reason: "Forbidden",
|
|
12811
|
+
headers: { "content-type": "text/plain" },
|
|
12812
|
+
body: originCheck.reason ?? "Forbidden"
|
|
12813
|
+
};
|
|
12814
|
+
}
|
|
12815
|
+
const hostCheck = checkHost(request.headers.host, allowedHosts);
|
|
12816
|
+
if (!hostCheck.ok) {
|
|
12817
|
+
return {
|
|
12818
|
+
status: 403,
|
|
12819
|
+
reason: "Forbidden",
|
|
12820
|
+
headers: { "content-type": "text/plain" },
|
|
12821
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
12822
|
+
};
|
|
12823
|
+
}
|
|
12824
|
+
if (resolvedBearer) {
|
|
12825
|
+
const authHeader = request.headers.authorization;
|
|
12826
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
12827
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
12828
|
+
return {
|
|
12829
|
+
status: 401,
|
|
12830
|
+
reason: "Unauthorized",
|
|
12831
|
+
headers: { "content-type": "text/plain" },
|
|
12832
|
+
body: "Unauthorized"
|
|
12833
|
+
};
|
|
12834
|
+
}
|
|
12835
|
+
}
|
|
12836
|
+
const tools = await getTools();
|
|
12837
|
+
const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
|
|
12838
|
+
return {
|
|
12839
|
+
status: outcome.status,
|
|
12840
|
+
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
12841
|
+
headers: { "content-type": outcome.contentType },
|
|
12842
|
+
body: outcome.body
|
|
12843
|
+
};
|
|
12844
|
+
};
|
|
12845
|
+
try {
|
|
12846
|
+
for await (const connection of listener) {
|
|
12847
|
+
if (shutdownRequested) break;
|
|
12848
|
+
const conn = connection;
|
|
12849
|
+
handleConnection(conn.readable, conn.writable, handler).catch(() => {
|
|
12850
|
+
}).finally(() => {
|
|
12851
|
+
try {
|
|
12852
|
+
conn.close();
|
|
12853
|
+
} catch {
|
|
12854
|
+
}
|
|
12855
|
+
});
|
|
12856
|
+
}
|
|
12857
|
+
} catch {
|
|
12858
|
+
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
12859
|
+
}
|
|
12860
|
+
return 0;
|
|
12861
|
+
}
|
|
12383
12862
|
async function runProxyServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
12384
12863
|
const built = await buildProxyConnectOpts(platform2, options, version);
|
|
12385
12864
|
if ("error" in built) {
|
|
@@ -14958,7 +15437,7 @@ async function dispatchDiscoveryVerb(platform2, parsed, args) {
|
|
|
14958
15437
|
async function dispatchServeVerb(platform2, args, version) {
|
|
14959
15438
|
const serveArgs = args.slice(args.indexOf("serve") + 1);
|
|
14960
15439
|
const serveOptions = parseServeFlags(serveArgs);
|
|
14961
|
-
if (!serveOptions.proxy && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
15440
|
+
if (!serveOptions.proxy && !serveOptions.webmcp && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
14962
15441
|
await platform2.write(2, "mcpcat serve: no commands or --tool templates given\n");
|
|
14963
15442
|
return 2;
|
|
14964
15443
|
}
|
|
@@ -15329,7 +15808,7 @@ async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
|
15329
15808
|
`);
|
|
15330
15809
|
return EXIT_INPUT_REQUIRED;
|
|
15331
15810
|
}
|
|
15332
|
-
const isTty = platform2.isatty(0) && platform2.isatty(2);
|
|
15811
|
+
const isTty = platform2.kind === "host" && platform2.isatty(0) && platform2.isatty(2);
|
|
15333
15812
|
const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
|
|
15334
15813
|
if ("exitCode" in elicitResult) {
|
|
15335
15814
|
await platform2.write(2, `${err.requestJson}
|
|
@@ -15834,7 +16313,7 @@ async function main(ctx) {
|
|
|
15834
16313
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
15835
16314
|
});
|
|
15836
16315
|
const args = ctx.argv.slice(1);
|
|
15837
|
-
return run(platform2, "0.
|
|
16316
|
+
return run(platform2, "0.15.0", args);
|
|
15838
16317
|
}
|
|
15839
16318
|
export {
|
|
15840
16319
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "A curl for the Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
59
|
+
"@ai-ecoverse/slicc-kernel": "1.83.0",
|
|
60
60
|
"@ai-ecoverse/slicc-shared-web": "1.13.1",
|
|
61
61
|
"@biomejs/biome": "2.5.15",
|
|
62
62
|
"@earendil-works/pi-codemode": "1.2.0",
|