@wenathlan/extension 1.1.54 → 1.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/agentstream.d.ts +155 -0
- package/dist/agentstream.d.ts.map +1 -0
- package/dist/approvalgate.d.ts +39 -0
- package/dist/approvalgate.d.ts.map +1 -0
- package/dist/clientauth.d.ts +114 -0
- package/dist/clientauth.d.ts.map +1 -0
- package/dist/httpstream.d.ts +92 -0
- package/dist/httpstream.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4892 -4304
- package/dist/index.js.map +4 -4
- package/dist/mcpserver.d.ts +29 -9
- package/dist/mcpserver.d.ts.map +1 -1
- package/dist/memory.d.ts +93 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +44 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +278 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/toolcatalog.d.ts +2 -2
- package/dist/types.d.ts +347 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1310 -155
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js.map +1 -1
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +4 -1
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.js +397 -7
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -2438,6 +2438,183 @@ var sessionmemory = class {
|
|
|
2438
2438
|
async listtoolcalls() {
|
|
2439
2439
|
return await this.adapter.get("mcptoolcalls") ?? [];
|
|
2440
2440
|
}
|
|
2441
|
+
/** Returns every stored session token of paired remote clients; the records carry only their tokenhash form so raw tokens never persist. */
|
|
2442
|
+
async getsessiontokens() {
|
|
2443
|
+
return await this.adapter.get("mcpsessiontokens") ?? [];
|
|
2444
|
+
}
|
|
2445
|
+
/** Replaces the stored session token set after one issue, revocation or expiry sweep. */
|
|
2446
|
+
async setsessiontokens(tokens) {
|
|
2447
|
+
return this.adapter.set("mcpsessiontokens", tokens);
|
|
2448
|
+
}
|
|
2449
|
+
/** Returns every stored pairing code with its single use state, newest first. */
|
|
2450
|
+
async getpairingcodes() {
|
|
2451
|
+
return await this.adapter.get("mcppairingcodes") ?? [];
|
|
2452
|
+
}
|
|
2453
|
+
/** Records one issued pairing code for the one time client pairing. */
|
|
2454
|
+
async addpairingcode(code) {
|
|
2455
|
+
return this.adapter.set("mcppairingcodes", [code, ...(await this.getpairingcodes()).filter((candidate) => candidate.code !== code.code)]);
|
|
2456
|
+
}
|
|
2457
|
+
/** Marks one pairing code used so it never pairs a second client; an unknown code stays untouched. */
|
|
2458
|
+
async usepairingcode(code, now) {
|
|
2459
|
+
await this.adapter.set("mcppairingcodes", (await this.getpairingcodes()).map((candidate) => candidate.code === code ? { ...candidate, usedat: now } : candidate));
|
|
2460
|
+
}
|
|
2461
|
+
/** Returns every client allowlist entry with its grant history. */
|
|
2462
|
+
async getallowlist() {
|
|
2463
|
+
return await this.adapter.get("mcpallowlist") ?? [];
|
|
2464
|
+
}
|
|
2465
|
+
/** Upserts one client allowlist entry by its fingerprint with the grant history riding the record. */
|
|
2466
|
+
async setallowlistentry(entry) {
|
|
2467
|
+
await this.adapter.set("mcpallowlist", [entry, ...(await this.getallowlist()).filter((candidate) => candidate.fingerprint !== entry.fingerprint)]);
|
|
2468
|
+
}
|
|
2469
|
+
/** Removes one client allowlist entry so its fingerprint stops passing the allowlist check. */
|
|
2470
|
+
async removeallowlistentry(fingerprint) {
|
|
2471
|
+
await this.adapter.set("mcpallowlist", (await this.getallowlist()).filter((entry) => entry.fingerprint !== fingerprint));
|
|
2472
|
+
}
|
|
2473
|
+
/** Returns every approval gate with its decision state — the pending and resolved gates of the approval view. */
|
|
2474
|
+
async listapprovals() {
|
|
2475
|
+
return await this.adapter.get("mcpapprovals") ?? [];
|
|
2476
|
+
}
|
|
2477
|
+
/** Records one raised approval gate or its resolved state, keyed by the gate id. */
|
|
2478
|
+
async setapproval(request) {
|
|
2479
|
+
await this.adapter.set("mcpapprovals", [request, ...(await this.listapprovals()).filter((candidate) => candidate.id !== request.id)]);
|
|
2480
|
+
}
|
|
2481
|
+
/** Records one approval execution — the decision, the actor, the time and the latency — beside its gate. */
|
|
2482
|
+
async addapprovalexec(exec) {
|
|
2483
|
+
return this.adapter.set("mcpapprovalexecs", [exec, ...await this.adapter.get("mcpapprovalexecs") ?? []]);
|
|
2484
|
+
}
|
|
2485
|
+
/** Returns every approval execution record, newest first. */
|
|
2486
|
+
async listapprovalexecs() {
|
|
2487
|
+
return await this.adapter.get("mcpapprovalexecs") ?? [];
|
|
2488
|
+
}
|
|
2489
|
+
/** Records one auth handshake event with its issued, verified or refused outcome. */
|
|
2490
|
+
async addauthhandshake(event) {
|
|
2491
|
+
return this.adapter.set("mcpauthhandshakes", [event, ...await this.adapter.get("mcpauthhandshakes") ?? []]);
|
|
2492
|
+
}
|
|
2493
|
+
/** Returns every auth handshake event with its outcome, newest first. */
|
|
2494
|
+
async listauthhandshakes() {
|
|
2495
|
+
return await this.adapter.get("mcpauthhandshakes") ?? [];
|
|
2496
|
+
}
|
|
2497
|
+
/** Returns every stored client identity with its fingerprint for allowlist matching. */
|
|
2498
|
+
async getclientidentities() {
|
|
2499
|
+
return await this.adapter.get("mcpidentities") ?? [];
|
|
2500
|
+
}
|
|
2501
|
+
/** Upserts one client identity by its fingerprint so the allowlist matches it. */
|
|
2502
|
+
async setclientidentity(identity) {
|
|
2503
|
+
await this.adapter.set("mcpidentities", [identity, ...(await this.getclientidentities()).filter((candidate) => candidate.fingerprint !== identity.fingerprint)]);
|
|
2504
|
+
}
|
|
2505
|
+
/** Returns every open and closed stream channel of the http stream transport. */
|
|
2506
|
+
async getstreamchannels() {
|
|
2507
|
+
return await this.adapter.get("mcpchannels") ?? [];
|
|
2508
|
+
}
|
|
2509
|
+
/** Replaces the stored stream channel set after one open, heartbeat or close sweep. */
|
|
2510
|
+
async setstreamchannels(channels) {
|
|
2511
|
+
return this.adapter.set("mcpchannels", channels);
|
|
2512
|
+
}
|
|
2513
|
+
/** Returns every client event subscription with its kinds and filters, newest first. */
|
|
2514
|
+
async geteventsubscriptions() {
|
|
2515
|
+
return await this.adapter.get("mcpeventsubscriptions") ?? [];
|
|
2516
|
+
}
|
|
2517
|
+
/** Replaces the stored event subscription set after one subscribe, unsubscribe or delivery sweep. */
|
|
2518
|
+
async seteventsubscriptions(subscriptions) {
|
|
2519
|
+
return this.adapter.set("mcpeventsubscriptions", subscriptions);
|
|
2520
|
+
}
|
|
2521
|
+
/** Returns every page state resource watcher with its baseline, newest first. */
|
|
2522
|
+
async getresourcewatches() {
|
|
2523
|
+
return await this.adapter.get("mcpresourcewatches") ?? [];
|
|
2524
|
+
}
|
|
2525
|
+
/** Replaces the stored resource watcher set after one watch, unwatch or delta push. */
|
|
2526
|
+
async setresourcewatches(watches) {
|
|
2527
|
+
return this.adapter.set("mcpresourcewatches", watches);
|
|
2528
|
+
}
|
|
2529
|
+
/** Returns every sampling request with its provenance, newest first. */
|
|
2530
|
+
async getsamplingrequests() {
|
|
2531
|
+
return await this.adapter.get("mcpsampling") ?? [];
|
|
2532
|
+
}
|
|
2533
|
+
/** Replaces the stored sampling request set after one request or answer. */
|
|
2534
|
+
async setsamplingrequests(requests) {
|
|
2535
|
+
return this.adapter.set("mcpsampling", requests);
|
|
2536
|
+
}
|
|
2537
|
+
/** Returns every stored idempotency record for replay, newest first. */
|
|
2538
|
+
async getidempotencyrecords() {
|
|
2539
|
+
return await this.adapter.get("mcpidempotency") ?? [];
|
|
2540
|
+
}
|
|
2541
|
+
/** Replaces the stored idempotency record set after one store or expiry sweep. */
|
|
2542
|
+
async setidempotencyrecords(records) {
|
|
2543
|
+
return this.adapter.set("mcpidempotency", records);
|
|
2544
|
+
}
|
|
2545
|
+
/** Returns every per client rate limit counter with its window and budget. */
|
|
2546
|
+
async getcallratelimits() {
|
|
2547
|
+
return await this.adapter.get("mcpcallratelimits") ?? [];
|
|
2548
|
+
}
|
|
2549
|
+
/** Replaces the stored per client rate limit set after one configuration or counted call. */
|
|
2550
|
+
async setcallratelimits(limits) {
|
|
2551
|
+
return this.adapter.set("mcpcallratelimits", limits);
|
|
2552
|
+
}
|
|
2553
|
+
/** Returns every stored batch call with its per item outcomes, newest first. */
|
|
2554
|
+
async getbatchcalls() {
|
|
2555
|
+
return await this.adapter.get("mcpbatchcalls") ?? [];
|
|
2556
|
+
}
|
|
2557
|
+
/** Upserts one batch call by its id with the per item outcomes riding the record. */
|
|
2558
|
+
async setbatchcall(batch) {
|
|
2559
|
+
await this.adapter.set("mcpbatchcalls", [batch, ...(await this.getbatchcalls()).filter((candidate) => candidate.id !== batch.id)]);
|
|
2560
|
+
}
|
|
2561
|
+
/** Returns every call context of the call runtime, newest first. */
|
|
2562
|
+
async getcallcontexts() {
|
|
2563
|
+
return await this.adapter.get("mcpcallcontexts") ?? [];
|
|
2564
|
+
}
|
|
2565
|
+
/** Replaces the stored call context set after one begin, end or cancellation. */
|
|
2566
|
+
async setcallcontexts(contexts) {
|
|
2567
|
+
return this.adapter.set("mcpcallcontexts", contexts);
|
|
2568
|
+
}
|
|
2569
|
+
/** Returns every stored tool mock for client testing. */
|
|
2570
|
+
async gettoolmocks() {
|
|
2571
|
+
return await this.adapter.get("mcptoolmocks") ?? [];
|
|
2572
|
+
}
|
|
2573
|
+
/** Upserts one tool mock by its tool name or removes it when the canned result is absent. */
|
|
2574
|
+
async settoolmock(mock) {
|
|
2575
|
+
await this.adapter.set("mcptoolmocks", [mock, ...(await this.gettoolmocks()).filter((candidate) => candidate.tool !== mock.tool)]);
|
|
2576
|
+
}
|
|
2577
|
+
/** Removes one tool mock so its tool returns to the real gates. */
|
|
2578
|
+
async removetoolmock(tool) {
|
|
2579
|
+
await this.adapter.set("mcptoolmocks", (await this.gettoolmocks()).filter((candidate) => candidate.tool !== tool));
|
|
2580
|
+
}
|
|
2581
|
+
/** Stores one stream chunk of a progressive tool result under the recent chunk window of 25 records. */
|
|
2582
|
+
async addstreamchunk(chunk) {
|
|
2583
|
+
await this.adapter.set("mcpstreamchunks", [chunk, ...(await this.getstreamchunks()).slice(0, 24)]);
|
|
2584
|
+
}
|
|
2585
|
+
/** Returns the recent stream chunks of progressive tool results, newest first. */
|
|
2586
|
+
async getstreamchunks() {
|
|
2587
|
+
return await this.adapter.get("mcpstreamchunks") ?? [];
|
|
2588
|
+
}
|
|
2589
|
+
/** Replaces the recent stream chunk window after one streaming sweep. */
|
|
2590
|
+
async setstreamchunks(chunks) {
|
|
2591
|
+
return this.adapter.set("mcpstreamchunks", chunks);
|
|
2592
|
+
}
|
|
2593
|
+
/** Returns the audited tool call log under the requested filters: the client, the tool, the outcome, the time floor and the newest bound, all optional. */
|
|
2594
|
+
async getcalllog(filters) {
|
|
2595
|
+
let records = await this.listtoolcalls();
|
|
2596
|
+
if (filters?.clientid !== void 0) records = records.filter((record2) => record2.clientid === filters.clientid);
|
|
2597
|
+
if (filters?.tool !== void 0) records = records.filter((record2) => record2.tool === filters.tool);
|
|
2598
|
+
if (filters?.ok !== void 0) records = records.filter((record2) => record2.ok === filters.ok);
|
|
2599
|
+
if (filters?.since !== void 0) records = records.filter((record2) => record2.at >= (filters.since ?? 0));
|
|
2600
|
+
return filters?.limit !== void 0 ? records.slice(0, filters.limit) : records;
|
|
2601
|
+
}
|
|
2602
|
+
/** Stores one progress notice of a long tool call under the recent notice window of 25 records. */
|
|
2603
|
+
async addprogressnotice(notice) {
|
|
2604
|
+
await this.adapter.set("mcpprogressnotices", [notice, ...(await this.getprogressnotices()).slice(0, 24)]);
|
|
2605
|
+
}
|
|
2606
|
+
/** Returns the recent progress notices of long tool calls, newest first. */
|
|
2607
|
+
async getprogressnotices() {
|
|
2608
|
+
return await this.adapter.get("mcpprogressnotices") ?? [];
|
|
2609
|
+
}
|
|
2610
|
+
/** Returns the tool dry run toggle of the next call: true once the user armed the dry run in the panel. */
|
|
2611
|
+
async getdryruntoggle() {
|
|
2612
|
+
return await this.adapter.get("mcpdryruntoggle") === true;
|
|
2613
|
+
}
|
|
2614
|
+
/** Arms or disarms the tool dry run of the next call. */
|
|
2615
|
+
async setdryruntoggle(enabled) {
|
|
2616
|
+
return this.adapter.set("mcpdryruntoggle", enabled);
|
|
2617
|
+
}
|
|
2441
2618
|
};
|
|
2442
2619
|
function mediakindof(record2) {
|
|
2443
2620
|
if ("pages" in record2) return "pdf";
|
|
@@ -2491,7 +2668,7 @@ function readtool(name, kind, description, inputs = {}) {
|
|
|
2491
2668
|
return { name, version: toolcatalogversion, description, inputschema: toolschemaof({ target: { type: "string", description: "Reviewed css selector the tool addresses." }, value: { type: "string", description: "Reviewed literal value the tool carries." }, options: { type: "object", description: "Reviewed json options of the wrapped action kind with the empty default.", default: {} }, ...inputs }), kind, risk: "read" };
|
|
2492
2669
|
}
|
|
2493
2670
|
function gatedtool(name, kind, risk, description, review) {
|
|
2494
|
-
return { name, version: toolcatalogversion, description, inputschema: toolschemaof({ stepid: { type: "string", description: "Id of the approved plan step this tool executes.", required: true } }), kind, risk, consentmeta: { review } };
|
|
2671
|
+
return { name, version: toolcatalogversion, description, inputschema: toolschemaof({ stepid: { type: "string", description: "Id of the approved plan step this tool executes.", required: true } }), kind, risk, consentmeta: { review, riskclass: risk, approvalrequired: true, originscope: "session" } };
|
|
2495
2672
|
}
|
|
2496
2673
|
function browserdomain() {
|
|
2497
2674
|
return {
|
|
@@ -2567,6 +2744,10 @@ function resolvetool(catalog, name) {
|
|
|
2567
2744
|
const matches = alltools(catalog).filter((tool) => tool.name.split(".")[1] === name);
|
|
2568
2745
|
return matches.length === 1 ? matches[0] : void 0;
|
|
2569
2746
|
}
|
|
2747
|
+
function namespaceof(name) {
|
|
2748
|
+
const head = name.split(".")[0];
|
|
2749
|
+
return toolnamespaces.includes(head) ? head : void 0;
|
|
2750
|
+
}
|
|
2570
2751
|
|
|
2571
2752
|
// socketbus.ts
|
|
2572
2753
|
var socketkinds = ["opensocket", "sendmessage", "waitmessage", "subscribesse", "longpoll"];
|
|
@@ -8276,6 +8457,8 @@ function serverenablementgate(config) {
|
|
|
8276
8457
|
if (typeof config.port !== "number" || !Number.isFinite(config.port) || config.port <= 0 || config.port > 65535) return { allowed: false, reason: "The http listener port must be a valid port number." };
|
|
8277
8458
|
if (config.framesize !== void 0 && (typeof config.framesize !== "number" || !Number.isFinite(config.framesize) || config.framesize <= 0)) return { allowed: false, reason: "The user configured frame size must stay a positive number with no code ceiling." };
|
|
8278
8459
|
if (config.queuedepth !== void 0 && (typeof config.queuedepth !== "number" || !Number.isFinite(config.queuedepth) || config.queuedepth <= 0)) return { allowed: false, reason: "The user configured queue depth must stay a positive number with no code ceiling." };
|
|
8460
|
+
const remote = remoteenablementgate(config);
|
|
8461
|
+
if (!remote.allowed) return remote;
|
|
8279
8462
|
return { allowed: true };
|
|
8280
8463
|
}
|
|
8281
8464
|
function tooldispatchgate(input) {
|
|
@@ -8292,6 +8475,77 @@ function tooldispatchgate(input) {
|
|
|
8292
8475
|
if (step.kind !== input.tool.kind) return { allowed: false, reason: `The tool call names the step ${input.stepid} whose kind ${String(step.kind)} does not match the ${input.tool.name} tool.` };
|
|
8293
8476
|
return { allowed: true };
|
|
8294
8477
|
}
|
|
8478
|
+
function allowlistentryvalid(entry, identities) {
|
|
8479
|
+
if (typeof entry.fingerprint !== "string" || entry.fingerprint.trim() === "") return { allowed: false, reason: "The allowlist entry needs the client fingerprint it grants." };
|
|
8480
|
+
if (!identities.some((identity) => identity.fingerprint === entry.fingerprint)) return { allowed: false, reason: `The allowlist entry ${entry.fingerprint} matches no known client identity.` };
|
|
8481
|
+
if (typeof entry.displayname !== "string" || entry.displayname.trim() === "") return { allowed: false, reason: `The allowlist entry ${entry.fingerprint} needs its display name.` };
|
|
8482
|
+
if (!Array.isArray(entry.namespaces) || entry.namespaces.length === 0) return { allowed: false, reason: `The allowlist entry ${entry.displayname} grants no tool namespace.` };
|
|
8483
|
+
if (!entry.namespaces.every((namespace) => toolnamespaces.includes(namespace))) return { allowed: false, reason: `The allowlist entry ${entry.displayname} grants an unreviewed namespace.` };
|
|
8484
|
+
return { allowed: true };
|
|
8485
|
+
}
|
|
8486
|
+
function tokenlifetimevalid(lifetime) {
|
|
8487
|
+
if (lifetime === void 0) return { allowed: true };
|
|
8488
|
+
if (typeof lifetime !== "number" || !Number.isFinite(lifetime) || lifetime <= 0) return { allowed: false, reason: "The token lifetime must stay a positive user value with no code ceiling." };
|
|
8489
|
+
return { allowed: true };
|
|
8490
|
+
}
|
|
8491
|
+
function remotetransporttls(config) {
|
|
8492
|
+
const bind = config.bind !== void 0 && config.bind.trim() !== "" ? config.bind.trim() : "127.0.0.1";
|
|
8493
|
+
const local = bind === "127.0.0.1" || bind === "localhost" || bind === "::1";
|
|
8494
|
+
const tls = config.remoteaccess?.tls ?? config.httpstream?.tls;
|
|
8495
|
+
if ((config.remoteaccess !== void 0 || !local) && (tls === void 0 || tls.mode === "off")) return { allowed: false, reason: `The ${config.remoteaccess !== void 0 ? "remote transport" : `bind ${bind}`} leaves localhost and every non localhost transport requires tls before any remote traffic.` };
|
|
8496
|
+
return { allowed: true };
|
|
8497
|
+
}
|
|
8498
|
+
function pairingreadinessgate(session, now) {
|
|
8499
|
+
if (!session || session.stoppedat || session.pausedat) return { allowed: false, reason: "The pairing flow needs the live browser session before any code issues." };
|
|
8500
|
+
if (session.expiresat <= now) return { allowed: false, reason: "The browser session has expired and the pairing flow is refused." };
|
|
8501
|
+
return { allowed: true };
|
|
8502
|
+
}
|
|
8503
|
+
function remoteenablementgate(config) {
|
|
8504
|
+
if (config.remoteaccess === void 0) return { allowed: true };
|
|
8505
|
+
if (config.remote !== true) return { allowed: false, reason: "The remote transport enablement is a sensitive user choice and needs the explicit remote review." };
|
|
8506
|
+
const tls = remotetransporttls(config);
|
|
8507
|
+
if (!tls.allowed) return tls;
|
|
8508
|
+
if (typeof config.remoteaccess.endpoint !== "string" || config.remoteaccess.endpoint.trim() === "") return { allowed: false, reason: "The remote access policy needs its user configured endpoint." };
|
|
8509
|
+
if (config.remoteaccess.maxclients !== void 0 && (typeof config.remoteaccess.maxclients !== "number" || !Number.isFinite(config.remoteaccess.maxclients) || config.remoteaccess.maxclients <= 0)) return { allowed: false, reason: "The user configured client ceiling must stay a positive value with no code ceiling." };
|
|
8510
|
+
const lifetime = tokenlifetimevalid(config.remoteaccess.tokenlifetimems);
|
|
8511
|
+
if (!lifetime.allowed) return lifetime;
|
|
8512
|
+
const timeout = approvaltimeoutvalid(config.remoteaccess.approvaltimeout);
|
|
8513
|
+
if (!timeout.allowed) return timeout;
|
|
8514
|
+
return { allowed: true };
|
|
8515
|
+
}
|
|
8516
|
+
function approvaltimeoutvalid(timeout) {
|
|
8517
|
+
if (timeout === void 0) return { allowed: true };
|
|
8518
|
+
if (typeof timeout.windowms !== "number" || !Number.isFinite(timeout.windowms) || timeout.windowms <= 0) return { allowed: false, reason: "The approval timeout must stay a positive user window with no code ceiling." };
|
|
8519
|
+
if (timeout.ontimeout !== "refuse") return { allowed: false, reason: "The documented disposition of an unanswered approval gate is refusal." };
|
|
8520
|
+
return { allowed: true };
|
|
8521
|
+
}
|
|
8522
|
+
function revocationgate() {
|
|
8523
|
+
return { allowed: true };
|
|
8524
|
+
}
|
|
8525
|
+
function subscriptiongrade(subscription) {
|
|
8526
|
+
if (!Array.isArray(subscription.kinds) || subscription.kinds.length === 0) return { allowed: false, reason: "An event subscription needs at least one protocol event kind." };
|
|
8527
|
+
if (subscription.kinds.includes("callstarted") && subscription.origin === void 0 && subscription.tool === void 0) return { allowed: false, reason: "An event subscription that mirrors the callstarted events of tools with side effects needs its origin or tool filter so it never widens what the session grants." };
|
|
8528
|
+
return { allowed: true };
|
|
8529
|
+
}
|
|
8530
|
+
function callratelimitvalid(limit) {
|
|
8531
|
+
if (limit === void 0) return { allowed: true };
|
|
8532
|
+
if (typeof limit.windowms !== "number" || !Number.isFinite(limit.windowms) || limit.windowms <= 0) return { allowed: false, reason: "The rate limit window must stay a positive user value with no code ceiling." };
|
|
8533
|
+
if (limit.budget !== void 0 && (typeof limit.budget !== "number" || !Number.isFinite(limit.budget) || limit.budget <= 0)) return { allowed: false, reason: "The rate limit budget must stay a positive user value with no code ceiling." };
|
|
8534
|
+
if (limit.clientid.trim() === "") return { allowed: false, reason: "A per client rate limit needs the client it counts." };
|
|
8535
|
+
return { allowed: true };
|
|
8536
|
+
}
|
|
8537
|
+
function batchgrade(input) {
|
|
8538
|
+
if (input.calls.length === 0) return { allowed: false, reason: "A batch call needs at least one ordered tool call." };
|
|
8539
|
+
const sensitive = input.calls.some((call) => call.risk === "sensitive");
|
|
8540
|
+
if (sensitive && !input.approved) return { allowed: false, reason: "The batch grades sensitive through its most sensitive member and runs only behind the approval gates." };
|
|
8541
|
+
return { allowed: true };
|
|
8542
|
+
}
|
|
8543
|
+
function mockusagevalid(mock) {
|
|
8544
|
+
if (mock.testcontext !== true) return { allowed: false, reason: `The ${mock.tool} mock stays outside a test context and is refused; tool mocks never answer real calls.` };
|
|
8545
|
+
if (mock.tool.trim() === "") return { allowed: false, reason: "A tool mock needs the namespaced tool it stands in for." };
|
|
8546
|
+
if (typeof mock.result.content !== "string") return { allowed: false, reason: "A tool mock needs its canned result content." };
|
|
8547
|
+
return { allowed: true };
|
|
8548
|
+
}
|
|
8295
8549
|
|
|
8296
8550
|
// progress.ts
|
|
8297
8551
|
function emptyprogress(planid, now) {
|
|
@@ -8471,11 +8725,432 @@ function recordtoolcall(progress, planid, stepid, entry, now) {
|
|
|
8471
8725
|
}
|
|
8472
8726
|
|
|
8473
8727
|
// version.ts
|
|
8474
|
-
var packageversion = "1.1.
|
|
8728
|
+
var packageversion = "1.1.56";
|
|
8475
8729
|
|
|
8476
8730
|
// types.ts
|
|
8477
8731
|
var protocolversion = packageversion;
|
|
8478
8732
|
|
|
8733
|
+
// clientauth.ts
|
|
8734
|
+
var tokenhashprefix = "sha256:";
|
|
8735
|
+
var defaulttokenlifetimems = 36e5;
|
|
8736
|
+
var defaultpairinglifetimems = 3e5;
|
|
8737
|
+
var defaultchallengelifetimems = 12e4;
|
|
8738
|
+
var authrefusedmessage = "The remote frame failed its authentication handshake.";
|
|
8739
|
+
async function tokenhashof(raw) {
|
|
8740
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(raw));
|
|
8741
|
+
return tokenhashprefix + [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8742
|
+
}
|
|
8743
|
+
function issuepairingcode(input) {
|
|
8744
|
+
const scopes = input.scopes.filter((scope) => toolnamespaces.includes(scope));
|
|
8745
|
+
return { code: input.code ?? `DT-${randomid().replace(/-/g, "").slice(0, 8).toUpperCase()}`, scopes, issuedat: input.now, expiresat: input.now + (input.lifetime ?? defaultpairinglifetimems) };
|
|
8746
|
+
}
|
|
8747
|
+
function redeempairingcode(input) {
|
|
8748
|
+
const match = input.codes.find((candidate) => candidate.code === input.code);
|
|
8749
|
+
if (match === void 0) return { reason: authrefusedmessage };
|
|
8750
|
+
if (match.usedat !== void 0) return { reason: "The pairing code was already used once and never pairs a second client." };
|
|
8751
|
+
if (input.now >= match.expiresat) return { reason: "The pairing code expired before the exchange completed." };
|
|
8752
|
+
return { code: { ...match, usedat: input.now } };
|
|
8753
|
+
}
|
|
8754
|
+
async function issuetoken(input) {
|
|
8755
|
+
const raw = input.raw ?? `${randomid()}.${randomid()}`;
|
|
8756
|
+
const token = { id: input.id ?? randomid(), clientid: input.clientid, hash: await tokenhashof(raw), scopes: input.scopes.filter((scope) => toolnamespaces.includes(scope)), issuedat: input.now, expiresat: input.now + (input.lifetime ?? defaulttokenlifetimems) };
|
|
8757
|
+
return { token, raw };
|
|
8758
|
+
}
|
|
8759
|
+
async function verifytoken(input) {
|
|
8760
|
+
const hash = await tokenhashof(input.raw);
|
|
8761
|
+
const match = input.tokens.find((candidate) => candidate.hash === hash);
|
|
8762
|
+
if (match === void 0) return { reason: authrefusedmessage };
|
|
8763
|
+
if (match.revokedat !== void 0) return { reason: authrefusedmessage };
|
|
8764
|
+
if (input.now >= match.expiresat) return { reason: authrefusedmessage };
|
|
8765
|
+
return { token: match };
|
|
8766
|
+
}
|
|
8767
|
+
function revokeclient(tokens, clientid, now) {
|
|
8768
|
+
return tokens.map((token) => token.clientid === clientid && token.revokedat === void 0 ? { ...token, revokedat: now } : token);
|
|
8769
|
+
}
|
|
8770
|
+
function checkallowlist(input) {
|
|
8771
|
+
const entry = input.entries.find((candidate) => candidate.fingerprint === input.fingerprint);
|
|
8772
|
+
if (entry === void 0) return { allowed: false, reason: `The client fingerprint ${input.fingerprint} is not on the allowlist and is refused.` };
|
|
8773
|
+
if (input.namespace !== void 0 && !entry.namespaces.includes(input.namespace)) return { allowed: false, reason: `The allowlist entry ${entry.displayname} grants no ${input.namespace} tools.` };
|
|
8774
|
+
return { allowed: true };
|
|
8775
|
+
}
|
|
8776
|
+
function grantallowlistentry(input) {
|
|
8777
|
+
const scopes = input.namespaces.filter((scope) => toolnamespaces.includes(scope));
|
|
8778
|
+
const existing = input.entries.find((entry) => entry.fingerprint === input.identity.fingerprint);
|
|
8779
|
+
if (existing === void 0) {
|
|
8780
|
+
return [{ fingerprint: input.identity.fingerprint, displayname: input.identity.displayname, namespaces: scopes, grantedat: input.now, history: [{ at: input.now, actor: input.actor, change: `Granted the ${scopes.length > 0 ? scopes.join(", ") : "no"} namespaces.` }] }, ...input.entries];
|
|
8781
|
+
}
|
|
8782
|
+
return input.entries.map((entry) => entry.fingerprint !== input.identity.fingerprint ? entry : { ...entry, displayname: input.identity.displayname, namespaces: scopes, history: [{ at: input.now, actor: input.actor, change: `Rescoped to ${scopes.length > 0 ? scopes.join(", ") : "no"} namespaces.` }, ...entry.history] });
|
|
8783
|
+
}
|
|
8784
|
+
function issuechallenge(input) {
|
|
8785
|
+
return { nonce: input.nonce ?? randomid(), method: input.method, issuedat: input.now, expiresat: input.now + (input.lifetime ?? defaultchallengelifetimems) };
|
|
8786
|
+
}
|
|
8787
|
+
function scopecheck(token, namespace) {
|
|
8788
|
+
if (namespace === void 0) return { allowed: false, fast: true, reason: "The tool call names no reviewed namespace." };
|
|
8789
|
+
if (token === void 0) return { allowed: false, reason: "The tool call carries no verified session token." };
|
|
8790
|
+
if (!token.scopes.includes(namespace)) return { allowed: false, reason: `The session token grants no ${namespace} tools.` };
|
|
8791
|
+
return { allowed: true };
|
|
8792
|
+
}
|
|
8793
|
+
function tlsstateof(tls) {
|
|
8794
|
+
return { mode: tls.mode, certificaterequired: tls.mode === "required" || tls.certificatefingerprint !== void 0, verified: tls.verifiedat !== void 0 };
|
|
8795
|
+
}
|
|
8796
|
+
|
|
8797
|
+
// agentstream.ts
|
|
8798
|
+
var protocoleventkinds = ["callstarted", "callresult", "streamchunk", "progress", "resourcedelta", "sampling", "cancellation"];
|
|
8799
|
+
var readonlyeventkinds = ["callresult", "streamchunk", "progress", "resourcedelta", "sampling", "cancellation"];
|
|
8800
|
+
function subscriberegister(input) {
|
|
8801
|
+
if (input.clientid.trim() === "") return { reason: "The event subscription needs the paired client it belongs to." };
|
|
8802
|
+
const kinds = input.kinds === void 0 || input.kinds.length === 0 ? [...readonlyeventkinds] : [...new Set(input.kinds)];
|
|
8803
|
+
for (const kind of kinds) {
|
|
8804
|
+
if (!protocoleventkinds.includes(kind)) return { reason: `The event kind ${kind} is not a protocol event kind.` };
|
|
8805
|
+
}
|
|
8806
|
+
if (input.origin !== void 0 && input.origin.trim() === "") return { reason: "The origin filter of an event subscription must name an origin or stay absent." };
|
|
8807
|
+
if (input.tool !== void 0 && input.tool.trim() === "") return { reason: "The tool filter of an event subscription must name a tool or stay absent." };
|
|
8808
|
+
return { subscription: { id: input.id ?? randomid(), clientid: input.clientid, kinds, ...input.origin !== void 0 ? { origin: input.origin } : {}, ...input.tool !== void 0 ? { tool: input.tool } : {}, createdat: input.now } };
|
|
8809
|
+
}
|
|
8810
|
+
function unsubscriberegister(subscriptions, id, now) {
|
|
8811
|
+
return subscriptions.map((subscription) => subscription.id === id && subscription.canceledat === void 0 ? { ...subscription, canceledat: now } : subscription);
|
|
8812
|
+
}
|
|
8813
|
+
function notifyevent(input) {
|
|
8814
|
+
const deliveries = [];
|
|
8815
|
+
const subscriptions = input.subscriptions.map((subscription) => {
|
|
8816
|
+
if (subscription.canceledat !== void 0) return subscription;
|
|
8817
|
+
if (!subscription.kinds.includes(input.kind)) return subscription;
|
|
8818
|
+
if (subscription.origin !== void 0 && input.origin !== void 0 && subscription.origin !== input.origin) return subscription;
|
|
8819
|
+
if (subscription.tool !== void 0 && input.tool !== void 0 && subscription.tool !== input.tool) return subscription;
|
|
8820
|
+
deliveries.push({ subscriptionid: subscription.id, clientid: subscription.clientid, frame: { jsonrpc: "2.0", method: "events/notify", params: { subscriptionid: subscription.id, kind: input.kind, ...input.origin !== void 0 ? { origin: input.origin } : {}, ...input.tool !== void 0 ? { tool: input.tool } : {}, ...input.payload !== void 0 ? { payload: input.payload } : {}, at: input.now } } });
|
|
8821
|
+
return { ...subscription, lastdeliveredat: input.now };
|
|
8822
|
+
});
|
|
8823
|
+
return { deliveries, subscriptions };
|
|
8824
|
+
}
|
|
8825
|
+
function watchresource(input) {
|
|
8826
|
+
if (input.clientid.trim() === "") return { reason: "The resource watcher needs the paired client it belongs to." };
|
|
8827
|
+
if (input.resource.trim() === "") return { reason: "The resource watcher needs the page state resource it watches." };
|
|
8828
|
+
return { watch: { id: input.id ?? randomid(), clientid: input.clientid, resource: input.resource, baseline: input.state ?? {}, createdat: input.now } };
|
|
8829
|
+
}
|
|
8830
|
+
function unwatchresource(watches, id, now) {
|
|
8831
|
+
return watches.map((watch) => watch.id === id && watch.canceledat === void 0 ? { ...watch, canceledat: now } : watch);
|
|
8832
|
+
}
|
|
8833
|
+
function notifyresource(input) {
|
|
8834
|
+
const deliveries = [];
|
|
8835
|
+
const watches = input.watches.map((watch) => {
|
|
8836
|
+
if (watch.canceledat !== void 0 || watch.resource !== input.resource) return watch;
|
|
8837
|
+
const delta = {};
|
|
8838
|
+
for (const [key, value] of Object.entries(input.state)) {
|
|
8839
|
+
if (!(key in watch.baseline) || watch.baseline[key] !== value) delta[key] = value;
|
|
8840
|
+
}
|
|
8841
|
+
if (Object.keys(delta).length === 0) return watch;
|
|
8842
|
+
deliveries.push({ watchid: watch.id, clientid: watch.clientid, delta });
|
|
8843
|
+
return { ...watch, baseline: { ...input.state }, lastdeliveredat: input.now };
|
|
8844
|
+
});
|
|
8845
|
+
return { deliveries, watches };
|
|
8846
|
+
}
|
|
8847
|
+
function requestsampling(input) {
|
|
8848
|
+
if (input.clientid.trim() === "") return { reason: "The sampling callback needs the paired client it addresses." };
|
|
8849
|
+
if (input.prompt.trim() === "") return { reason: "The sampling callback needs its prompt." };
|
|
8850
|
+
if (input.capabilities?.sampling === false) return { reason: "The client declared no sampling capability and the callback is refused." };
|
|
8851
|
+
if (input.maxtokens !== void 0 && (!Number.isFinite(input.maxtokens) || input.maxtokens <= 0)) return { reason: "The granted maximum tokens of a sampling callback must stay a positive user value." };
|
|
8852
|
+
const granted = input.pagegrant === true;
|
|
8853
|
+
const pagecontent = granted ? input.pagecontent : void 0;
|
|
8854
|
+
const prompt = granted || input.pagecontent === void 0 ? input.prompt : `${input.prompt}
|
|
8855
|
+
The page content stays stripped because the user granted none.`;
|
|
8856
|
+
return { request: { id: input.id ?? randomid(), clientid: input.clientid, prompt, ...input.system !== void 0 ? { system: input.system } : {}, ...pagecontent !== void 0 ? { pagecontent } : {}, ...input.maxtokens !== void 0 ? { maxtokens: input.maxtokens } : {}, state: "pending", requestedat: input.now } };
|
|
8857
|
+
}
|
|
8858
|
+
function answersampling(input) {
|
|
8859
|
+
const match = input.requests.find((request2) => request2.id === input.id);
|
|
8860
|
+
if (match === void 0) return { requests: input.requests, reason: "The sampling answer names no stored request." };
|
|
8861
|
+
if (match.state !== "pending") return { requests: input.requests, reason: "The sampling request already closed its round trip." };
|
|
8862
|
+
const request = { ...match, state: input.refused === true ? "refused" : "answered", answeredat: input.now, ...input.refused !== true && input.answer !== void 0 ? { answer: input.answer } : {} };
|
|
8863
|
+
return { requests: input.requests.map((candidate) => candidate.id === input.id ? request : candidate), request };
|
|
8864
|
+
}
|
|
8865
|
+
function listprompts() {
|
|
8866
|
+
return [
|
|
8867
|
+
{ name: "runreview", description: "Renders the run review prompt that asks the user model to summarize the executed steps of the approved plan behind the consent gates.", arguments: [{ name: "objective", description: "The objective of the approved plan under review.", required: true }, { name: "steps", description: "The executed step summaries the review covers.", required: true }, { name: "tone", description: "The tone of the summary.", default: "plain" }], template: "Review the run of the objective {{objective}}. Summarize the executed steps: {{steps}}. Keep the tone {{tone}} and state every refusal the consent gates raised." },
|
|
8868
|
+
{ name: "pagesummary", description: "Renders the page summary prompt that condenses the observed page state of the session tab into the summary the client model asked for.", arguments: [{ name: "url", description: "The url of the observed page.", required: true }, { name: "observations", description: "The observed page state sections the summary condenses.", required: true }], template: "Summarize the page at {{url}} from the observations: {{observations}}. Name nothing the observations leave out." },
|
|
8869
|
+
{ name: "failuretriage", description: "Renders the failure triage prompt that classifies a failed tool call through the retry hints of the structured error it produced.", arguments: [{ name: "tool", description: "The namespaced tool that failed.", required: true }, { name: "error", description: "The structured error of the failed call.", required: true }], template: "Triage the failure of the {{tool}} tool: {{error}}. Classify it as retryable, a busy window or a consent refusal and propose the next reviewed step." }
|
|
8870
|
+
];
|
|
8871
|
+
}
|
|
8872
|
+
function renderprompt(prompt, args) {
|
|
8873
|
+
return prompt.template.replace(/\{\{\s*([a-z0-9]+)\s*\}\}/g, (whole, name) => {
|
|
8874
|
+
const value = args[name];
|
|
8875
|
+
if (value === void 0 || value === null) return whole;
|
|
8876
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
8877
|
+
});
|
|
8878
|
+
}
|
|
8879
|
+
function callprompt(input) {
|
|
8880
|
+
const prompt = listprompts().find((candidate) => candidate.name === input.name);
|
|
8881
|
+
if (prompt === void 0) return { reason: `The server exposes no prompt named ${input.name}.` };
|
|
8882
|
+
const args = input.args ?? {};
|
|
8883
|
+
const findings = [];
|
|
8884
|
+
const resolved = {};
|
|
8885
|
+
for (const argument of prompt.arguments) {
|
|
8886
|
+
const value = args[argument.name];
|
|
8887
|
+
if (value === void 0 || value === null || typeof value === "string" && value.trim() === "") {
|
|
8888
|
+
if (argument.default !== void 0) resolved[argument.name] = argument.default;
|
|
8889
|
+
else if (argument.required === true) findings.push(`The prompt argument ${argument.name} is required and stays empty.`);
|
|
8890
|
+
else resolved[argument.name] = "";
|
|
8891
|
+
} else {
|
|
8892
|
+
resolved[argument.name] = value;
|
|
8893
|
+
}
|
|
8894
|
+
}
|
|
8895
|
+
if (findings.length > 0) return { reason: findings.join(" ") };
|
|
8896
|
+
return { rendered: renderprompt(prompt, resolved), toolcall: { name: `prompts.${prompt.name}`, params: { prompt: prompt.name, arguments: resolved, rendered: renderprompt(prompt, resolved) } } };
|
|
8897
|
+
}
|
|
8898
|
+
function streamchunkof(input) {
|
|
8899
|
+
return { callid: input.callid, seq: input.seq, content: input.content, done: input.done === true, at: input.now };
|
|
8900
|
+
}
|
|
8901
|
+
function chunkcontent(input) {
|
|
8902
|
+
const size = input.size !== void 0 && Number.isFinite(input.size) && input.size > 0 ? Math.floor(input.size) : 80;
|
|
8903
|
+
const parts = [];
|
|
8904
|
+
for (let index = 0; index < input.content.length; index += size) parts.push(input.content.slice(index, index + size));
|
|
8905
|
+
const slices = parts.length > 0 ? parts : [""];
|
|
8906
|
+
return slices.map((content, index) => streamchunkof({ callid: input.callid, seq: index + 1, content, done: index === slices.length - 1, now: input.now + index }));
|
|
8907
|
+
}
|
|
8908
|
+
function notifyprogress(input) {
|
|
8909
|
+
return { callid: input.callid, ...input.percent !== void 0 ? { percent: input.percent } : {}, message: input.message, cancellable: input.cancellable !== false, at: input.now };
|
|
8910
|
+
}
|
|
8911
|
+
function canceltool(input) {
|
|
8912
|
+
const match = input.contexts.find((context2) => context2.callid === input.callid);
|
|
8913
|
+
if (match === void 0) return { contexts: input.contexts, reason: `The cancellation frame names no call context ${input.callid}.` };
|
|
8914
|
+
if (match.state !== "inflight") return { contexts: input.contexts, reason: `The call ${input.callid} already left the in flight state.` };
|
|
8915
|
+
const context = { ...match, state: "cancelled", endedat: input.now, ...input.partial !== void 0 ? { partial: input.partial } : {} };
|
|
8916
|
+
return { contexts: input.contexts.map((candidate) => candidate.callid === input.callid ? context : candidate), context };
|
|
8917
|
+
}
|
|
8918
|
+
|
|
8919
|
+
// mcpserver.ts
|
|
8920
|
+
var localhostbind = "127.0.0.1";
|
|
8921
|
+
var defaultmcpport = 7436;
|
|
8922
|
+
function rpcerrorof(code, message, data) {
|
|
8923
|
+
return { code, message, ...data !== void 0 ? { data } : {} };
|
|
8924
|
+
}
|
|
8925
|
+
function defaultmcpconfig() {
|
|
8926
|
+
return { port: defaultmcpport, transports: ["stdio", "http"], enabled: false };
|
|
8927
|
+
}
|
|
8928
|
+
function unwraphttppost(value) {
|
|
8929
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
8930
|
+
const candidate = value;
|
|
8931
|
+
if (candidate.transport === "http" && candidate.frame && typeof candidate.frame === "object" && !Array.isArray(candidate.frame)) return candidate.frame;
|
|
8932
|
+
}
|
|
8933
|
+
return value;
|
|
8934
|
+
}
|
|
8935
|
+
function parseframe(raw) {
|
|
8936
|
+
const parsed = unwraphttppost(JSON.parse(raw));
|
|
8937
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("A json rpc frame must be an object.");
|
|
8938
|
+
return parsed;
|
|
8939
|
+
}
|
|
8940
|
+
function serializeframe(frame) {
|
|
8941
|
+
return JSON.stringify(frame);
|
|
8942
|
+
}
|
|
8943
|
+
function validateframe(frame, methods, config) {
|
|
8944
|
+
if (frame.jsonrpc !== "2.0") return rpcerrorof("parse", "The frame must carry the jsonrpc 2.0 tag.");
|
|
8945
|
+
if (frame.id !== void 0 && typeof frame.id !== "number" && typeof frame.id !== "string" && frame.id !== null) return rpcerrorof("parse", "The frame id must be a number, a string or null.");
|
|
8946
|
+
if (frame.method === void 0 || frame.method.trim() === "") return rpcerrorof("method", "The frame carries no method to route.");
|
|
8947
|
+
if (!methods.some((entry) => entry.method === frame.method)) return rpcerrorof("method", `The server routes no method named ${frame.method}.`);
|
|
8948
|
+
if (frame.params !== void 0 && (typeof frame.params !== "object" || Array.isArray(frame.params))) return rpcerrorof("params", "The frame params must be an object.");
|
|
8949
|
+
if (config?.framesize !== void 0 && serializeframe(frame).length > config.framesize) return rpcerrorof("params", `The serialized frame exceeds the user configured frame size of ${config.framesize} characters.`);
|
|
8950
|
+
return void 0;
|
|
8951
|
+
}
|
|
8952
|
+
function respond(input) {
|
|
8953
|
+
return { jsonrpc: "2.0", ...input.id === void 0 ? input.error !== void 0 ? { id: null } : {} : { id: input.id }, ...input.error !== void 0 ? { error: input.error } : { result: input.result } };
|
|
8954
|
+
}
|
|
8955
|
+
function servermethods() {
|
|
8956
|
+
return [
|
|
8957
|
+
{ method: "initialize", handler: "initialize", description: "Completes the mcp handshake and returns the server info." },
|
|
8958
|
+
{ method: "ping", handler: "ping", description: "Answers keepalive frames with pong." },
|
|
8959
|
+
{ method: "tools/list", handler: "listtools", description: "Returns every tool with its version and json schema inputs." },
|
|
8960
|
+
{ method: "negotiate", handler: "negotiate", description: "Exchanges capability sets with the client." },
|
|
8961
|
+
{ method: "tools/call", handler: "dispatch", description: "Invokes one tool behind the consent gates." },
|
|
8962
|
+
{ method: "prompts/list", handler: "listprompts", description: "Lists the prompt defs the server exposes as callable tools." },
|
|
8963
|
+
{ method: "prompts/call", handler: "callprompt", description: "Renders one prompt and returns its arguments as a tool call." },
|
|
8964
|
+
{ method: "calls/cancel", handler: "cancel", description: "Aborts one in flight tool call and preserves its partial result." }
|
|
8965
|
+
];
|
|
8966
|
+
}
|
|
8967
|
+
function servercapabilities(input) {
|
|
8968
|
+
return { protocolversion, name: "devthink", version: protocolversion, toolversion: input.catalog.version, tools: alltools(input.catalog).length, namespaces: toolnamespaces, transports: input.config.transports };
|
|
8969
|
+
}
|
|
8970
|
+
function initialize(input) {
|
|
8971
|
+
void input.params;
|
|
8972
|
+
return { serverinfo: servercapabilities({ config: input.config, catalog: input.catalog }), protocolversion, instructions: "Devthink serves browser tools behind the human review gates: read only tools run once a session is approved while every tool with side effects executes exactly the approved plan step it names. No endpoint, provider or key is hardcoded; the user pairs every client." };
|
|
8973
|
+
}
|
|
8974
|
+
function ping(input) {
|
|
8975
|
+
return { pong: true, at: input.now };
|
|
8976
|
+
}
|
|
8977
|
+
function listtools(catalog) {
|
|
8978
|
+
return { tools: alltools(catalog).map((tool) => ({ name: tool.name, version: tool.version, description: tool.description, inputschema: tool.inputschema, risk: tool.risk, ...tool.consentmeta !== void 0 ? { consentmeta: { review: tool.consentmeta.review, riskclass: tool.consentmeta.riskclass ?? tool.risk, approvalrequired: tool.consentmeta.approvalrequired ?? true, originscope: tool.consentmeta.originscope ?? "session" } } : {} })) };
|
|
8979
|
+
}
|
|
8980
|
+
function negotiate(input) {
|
|
8981
|
+
const client = input.client;
|
|
8982
|
+
if (client?.protocolversion !== void 0 && client.protocolversion !== input.server.protocolversion) return { agreed: false, mismatch: `The client speaks protocol version ${String(client.protocolversion)} while the server offers ${input.server.protocolversion}.` };
|
|
8983
|
+
if (client?.toolversion !== void 0 && client.toolversion > input.server.toolversion) return { agreed: false, mismatch: `The client requires tool version ${String(client.toolversion)} while the server offers ${String(input.server.toolversion)}.` };
|
|
8984
|
+
if (client?.transports !== void 0 && client.transports.some((transport) => !input.server.transports.includes(transport))) return { agreed: false, mismatch: "The client requires a transport the server configuration does not allow." };
|
|
8985
|
+
return { agreed: true, capabilities: input.server };
|
|
8986
|
+
}
|
|
8987
|
+
function connectclient(input) {
|
|
8988
|
+
return { id: input.id ?? `client-${input.now}`, transport: input.transport, paired: false, connectedat: input.now };
|
|
8989
|
+
}
|
|
8990
|
+
function disconnectclient(clients, id, now) {
|
|
8991
|
+
return clients.map((client) => client.id === id && client.disconnectedat === void 0 ? { ...client, disconnectedat: now } : client);
|
|
8992
|
+
}
|
|
8993
|
+
function negotiatetoolfloor(clientfloor, catalogversion) {
|
|
8994
|
+
if (clientfloor === void 0) return { floor: catalogversion };
|
|
8995
|
+
if (clientfloor > catalogversion) return { mismatch: `The client requires the tool version floor ${clientfloor} while the catalog serves version ${catalogversion}.` };
|
|
8996
|
+
return { floor: clientfloor };
|
|
8997
|
+
}
|
|
8998
|
+
async function dispatchtool(input) {
|
|
8999
|
+
const params = input.params;
|
|
9000
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) return { error: rpcerrorof("params", "The tool call needs its params object.") };
|
|
9001
|
+
if (typeof params.name !== "string" || !params.name.trim()) return { error: rpcerrorof("params", "The tool call needs the namespaced name of the tool it invokes.") };
|
|
9002
|
+
const tool = resolvetool(input.catalog, params.name.trim());
|
|
9003
|
+
if (tool === void 0) return { error: rpcerrorof("params", `The catalog holds no unambiguous tool named ${params.name.trim()}.`) };
|
|
9004
|
+
const namespace = namespaceof(tool.name);
|
|
9005
|
+
if (namespace === void 0) return { error: rpcerrorof("params", `The tool ${tool.name} carries no reviewed namespace.`) };
|
|
9006
|
+
if (input.scopes !== void 0 && !input.scopes.includes(namespace)) return { error: rpcerrorof("consentrefused", `The session token grants no ${namespace} tools.`) };
|
|
9007
|
+
const floor = input.client.toolfloor ?? input.client.capabilities?.toolversion ?? input.catalog.version;
|
|
9008
|
+
if (tool.version < floor) return { error: rpcerrorof("params", `The tool ${tool.name} of version ${tool.version} stays below the negotiated compatibility floor of ${floor}.`) };
|
|
9009
|
+
const stepid = typeof params.stepid === "string" ? params.stepid : void 0;
|
|
9010
|
+
const gate = tooldispatchgate({ client: input.client, tool, session: input.session, plan: input.plan, origin: input.origin, ...stepid !== void 0 ? { stepid } : {}, now: input.now });
|
|
9011
|
+
if (!gate.allowed) return { error: rpcerrorof("consentrefused", gate.reason ?? "The consent gates refused the tool call.") };
|
|
9012
|
+
const step = tool.risk === "read" ? { id: `mcp-${input.client.id}-${input.now}`, kind: tool.kind, summary: tool.description.split(".")[0] ?? tool.description, risk: "read", ...typeof params.target === "string" ? { target: params.target } : {}, ...typeof params.value === "string" ? { value: params.value } : {}, ...params.options !== void 0 && typeof params.options === "object" && !Array.isArray(params.options) ? { options: JSON.stringify(params.options) } : {} } : input.plan?.steps.find((candidate) => candidate.id === stepid);
|
|
9013
|
+
if (step === void 0) return { error: rpcerrorof("consentrefused", "The tool call names a step the approved plan does not carry.") };
|
|
9014
|
+
try {
|
|
9015
|
+
const result = await input.execute(step);
|
|
9016
|
+
return { result, step };
|
|
9017
|
+
} catch (error) {
|
|
9018
|
+
return { error: rpcerrorof("internal", error instanceof Error ? error.message : String(error)) };
|
|
9019
|
+
}
|
|
9020
|
+
}
|
|
9021
|
+
async function handleframe(input) {
|
|
9022
|
+
if (input.raw !== void 0 && input.config.framesize !== void 0 && input.raw.length > input.config.framesize) return respond({ id: null, error: rpcerrorof("params", `The wire frame exceeds the user configured frame size of ${input.config.framesize} characters.`) });
|
|
9023
|
+
let frame;
|
|
9024
|
+
if (input.raw !== void 0) {
|
|
9025
|
+
try {
|
|
9026
|
+
frame = parseframe(input.raw);
|
|
9027
|
+
} catch {
|
|
9028
|
+
return respond({ id: null, error: rpcerrorof("parse", "The wire frame does not parse as json.") });
|
|
9029
|
+
}
|
|
9030
|
+
} else if (input.frame !== void 0) {
|
|
9031
|
+
frame = input.frame;
|
|
9032
|
+
} else {
|
|
9033
|
+
return respond({ id: null, error: rpcerrorof("parse", "The server received no frame to route.") });
|
|
9034
|
+
}
|
|
9035
|
+
const invalid = validateframe(frame, servermethods(), input.config);
|
|
9036
|
+
if (invalid !== void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: invalid });
|
|
9037
|
+
const entry = servermethods().find((candidate) => candidate.method === frame.method);
|
|
9038
|
+
if (entry === void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("method", `The server routes no method named ${String(frame.method)}.`) });
|
|
9039
|
+
const params = frame.params;
|
|
9040
|
+
if (entry.handler === "initialize") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: initialize({ ...params !== void 0 ? { params } : {}, config: input.config, catalog: input.catalog }) });
|
|
9041
|
+
if (entry.handler === "ping") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: ping({ now: input.now }) });
|
|
9042
|
+
if (entry.handler === "listtools") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: listtools(input.catalog) });
|
|
9043
|
+
if (entry.handler === "negotiate") {
|
|
9044
|
+
const server = servercapabilities({ config: input.config, catalog: input.catalog });
|
|
9045
|
+
const clientcaps = params?.capabilities && typeof params.capabilities === "object" && !Array.isArray(params.capabilities) ? params.capabilities : void 0;
|
|
9046
|
+
const outcome = negotiate({ ...clientcaps !== void 0 ? { client: clientcaps } : {}, server });
|
|
9047
|
+
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, ...outcome.agreed ? { result: outcome.capabilities } : { error: rpcerrorof("params", outcome.mismatch ?? "The capability negotiation did not agree.") } });
|
|
9048
|
+
}
|
|
9049
|
+
if (entry.handler === "listprompts") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: { prompts: listprompts() } });
|
|
9050
|
+
if (entry.handler === "callprompt") {
|
|
9051
|
+
const name = typeof params?.name === "string" ? params.name : "";
|
|
9052
|
+
const args = params?.arguments && typeof params.arguments === "object" && !Array.isArray(params.arguments) ? params.arguments : void 0;
|
|
9053
|
+
const called = name === "" ? { reason: "The prompt call needs the prompt name." } : callprompt({ name, ...args !== void 0 ? { args } : {} });
|
|
9054
|
+
if (called.toolcall === void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("params", called.reason ?? "The prompt call did not render.") });
|
|
9055
|
+
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: { toolcall: called.toolcall, rendered: called.rendered } });
|
|
9056
|
+
}
|
|
9057
|
+
if (entry.handler === "cancel") {
|
|
9058
|
+
const callid = typeof params?.callid === "string" ? params.callid : "";
|
|
9059
|
+
const reason = typeof params?.reason === "string" ? params.reason : void 0;
|
|
9060
|
+
if (callid.trim() === "") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("params", "The cancellation frame needs the call id it aborts.") });
|
|
9061
|
+
const aborted = canceltool({ contexts: input.contexts ?? [], callid, ...reason !== void 0 ? { reason } : {}, now: input.now });
|
|
9062
|
+
if (aborted.context === void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("params", aborted.reason ?? "The cancellation frame named no in flight tool call.") });
|
|
9063
|
+
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: { cancelled: true, callid, ...reason !== void 0 ? { reason } : {}, ...aborted.context.partial !== void 0 ? { partial: aborted.context.partial } : {} } });
|
|
9064
|
+
}
|
|
9065
|
+
const dispatched = await dispatchtool({ ...params !== void 0 ? { params } : {}, client: input.client, catalog: input.catalog, ...input.session !== void 0 ? { session: input.session } : {}, ...input.plan !== void 0 ? { plan: input.plan } : {}, ...input.scopes !== void 0 ? { scopes: input.scopes } : {}, origin: input.origin, now: input.now, execute: input.execute });
|
|
9066
|
+
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, ...dispatched.error !== void 0 ? { error: dispatched.error } : { result: dispatched.result } });
|
|
9067
|
+
}
|
|
9068
|
+
function bindlocalhost(config) {
|
|
9069
|
+
const bind = config.bind !== void 0 && config.bind.trim() !== "" ? config.bind.trim() : localhostbind;
|
|
9070
|
+
return { bind, port: config.port, localhost: bind === localhostbind || bind === "localhost" || bind === "::1" };
|
|
9071
|
+
}
|
|
9072
|
+
function launchbridge(input) {
|
|
9073
|
+
return { id: input.id ?? `bridge-${input.now}`, host: input.host, connected: true, ...input.pid !== void 0 ? { pid: input.pid } : {}, startedat: input.now, restarts: 0, received: 0, sent: 0 };
|
|
9074
|
+
}
|
|
9075
|
+
function relayframe(input) {
|
|
9076
|
+
return { ...input.bridge, connected: true, received: input.bridge.received + (input.direction === "inbound" ? 1 : 0), sent: input.bridge.sent + (input.direction === "outbound" ? 1 : 0), lastframeat: input.now };
|
|
9077
|
+
}
|
|
9078
|
+
function toolcallevent(input) {
|
|
9079
|
+
return { id: input.id, clientid: input.clientid, tool: input.tool, origin: input.origin, ok: input.ok, ...input.code !== void 0 ? { code: input.code } : {}, at: input.now, ...input.callid !== void 0 ? { callid: input.callid } : {}, ...input.idempotencykey !== void 0 && input.idempotencykey.trim() !== "" ? { idempotencykey: input.idempotencykey } : {}, ...input.dryrun === true ? { dryrun: true } : {}, ...input.mocked === true ? { mocked: true } : {}, ...input.batchid !== void 0 ? { batchid: input.batchid } : {}, ...input.replayed === true ? { replayed: true } : {} };
|
|
9080
|
+
}
|
|
9081
|
+
|
|
9082
|
+
// httpstream.ts
|
|
9083
|
+
var defaultheartbeatms = 3e4;
|
|
9084
|
+
var defaultidlewindowms = 9e4;
|
|
9085
|
+
function defaulthttpstream() {
|
|
9086
|
+
return { endpoint: "/mcp", streampath: "/mcp/stream", tls: { mode: "off" }, heartbeatms: defaultheartbeatms, idlewindowms: defaultidlewindowms };
|
|
9087
|
+
}
|
|
9088
|
+
function openstreamchannel(input) {
|
|
9089
|
+
return { id: input.id ?? `channel-${randomchannelid()}`, clientid: input.clientid, openedat: input.now, lastbeatat: input.now };
|
|
9090
|
+
}
|
|
9091
|
+
function randomchannelid() {
|
|
9092
|
+
return crypto.randomUUID();
|
|
9093
|
+
}
|
|
9094
|
+
function heartbeat(input) {
|
|
9095
|
+
return input.channels.map((channel) => channel.clientid === input.clientid && channel.closedat === void 0 ? { ...channel, lastbeatat: input.now } : channel);
|
|
9096
|
+
}
|
|
9097
|
+
function channellive(channel, now, idlewindow) {
|
|
9098
|
+
if (channel.closedat !== void 0) return false;
|
|
9099
|
+
return now - channel.lastbeatat < (idlewindow ?? defaultidlewindowms);
|
|
9100
|
+
}
|
|
9101
|
+
function closeidlechannels(input) {
|
|
9102
|
+
return input.channels.map((channel) => channel.closedat === void 0 && !channellive(channel, input.now, input.idlewindow) ? { ...channel, closedat: input.now } : channel);
|
|
9103
|
+
}
|
|
9104
|
+
function starttls(input) {
|
|
9105
|
+
if (input.config.mode === "off") return { tls: false, verified: false };
|
|
9106
|
+
if (input.config.mode === "required" && input.presented?.fingerprint === void 0) return { tls: false, verified: false, reason: "The remote transport requires tls and the peer presented no certificate." };
|
|
9107
|
+
if (input.config.certificatefingerprint !== void 0 && input.presented?.fingerprint !== input.config.certificatefingerprint) return { tls: false, verified: false, reason: "The peer certificate does not match the user configured fingerprint and the remote traffic is refused." };
|
|
9108
|
+
return { tls: true, verified: true };
|
|
9109
|
+
}
|
|
9110
|
+
function enforcemaxclients(input) {
|
|
9111
|
+
if (input.maxclients === void 0) return { allowed: true };
|
|
9112
|
+
const connected = input.clients.filter((client) => client.disconnectedat === void 0).length;
|
|
9113
|
+
if (connected >= input.maxclients) return { allowed: false, reason: `The user configured maximum of ${input.maxclients} remote clients is reached and the connection is refused.` };
|
|
9114
|
+
return { allowed: true };
|
|
9115
|
+
}
|
|
9116
|
+
function listremotestatus(input) {
|
|
9117
|
+
const stream = input.config.httpstream ?? defaulthttpstream();
|
|
9118
|
+
const remote = input.config.remoteaccess;
|
|
9119
|
+
const idlewindow = stream.idlewindowms;
|
|
9120
|
+
const open = input.channels.filter((channel) => channellive(channel, input.now, idlewindow));
|
|
9121
|
+
return { endpoint: remote?.endpoint ?? stream.endpoint, tls: tlsstateof(remote?.tls ?? stream.tls), clients: input.clients.filter((client) => client.disconnectedat === void 0).length, paired: input.clients.filter((client) => client.paired && client.disconnectedat === void 0).length, channelsopen: open.length, channelsdead: input.channels.length - open.length, tokenslive: input.tokens.filter((token) => token.revokedat === void 0 && input.now < token.expiresat).length };
|
|
9122
|
+
}
|
|
9123
|
+
async function httpframepipeline(input) {
|
|
9124
|
+
const stream = input.config.httpstream ?? defaulthttpstream();
|
|
9125
|
+
const tls = starttls({ config: input.config.remoteaccess?.tls ?? stream.tls, ...input.presented !== void 0 ? { presented: input.presented } : {}, now: input.now });
|
|
9126
|
+
if (tls.reason !== void 0) return { error: rpcerrorof("consentrefused", tls.reason) };
|
|
9127
|
+
if (input.rawtoken === void 0) return { error: rpcerrorof("consentrefused", authrefusedmessage) };
|
|
9128
|
+
const verified = await verifytoken({ tokens: input.tokens, raw: input.rawtoken, now: input.now });
|
|
9129
|
+
if (verified.token === void 0) return { error: rpcerrorof("consentrefused", verified.reason ?? authrefusedmessage) };
|
|
9130
|
+
const namespace = input.toolname !== void 0 ? namespaceof(input.toolname) : void 0;
|
|
9131
|
+
const listed = checkallowlist({ entries: input.allowlist, fingerprint: input.fingerprint, ...namespace !== void 0 ? { namespace } : {} });
|
|
9132
|
+
if (!listed.allowed) return { error: rpcerrorof("consentrefused", listed.reason ?? "The allowlist refused the client.") };
|
|
9133
|
+
const scoped = scopecheck(verified.token, namespace);
|
|
9134
|
+
if (!scoped.allowed) return { error: rpcerrorof(scoped.fast === true ? "params" : "consentrefused", scoped.reason ?? "The tool call stayed outside the granted scopes.") };
|
|
9135
|
+
return { token: verified.token };
|
|
9136
|
+
}
|
|
9137
|
+
|
|
9138
|
+
// approvalgate.ts
|
|
9139
|
+
var defaultapprovalwindowms = 12e4;
|
|
9140
|
+
function requireapproval(input) {
|
|
9141
|
+
return { id: input.id ?? randomid(), clientid: input.clientid, tool: input.tool, reason: input.reason, params: input.params, ...input.secretfields !== void 0 && input.secretfields.length > 0 ? { secretfields: input.secretfields } : {}, state: "pending", raisedat: input.now, ...input.timeout !== void 0 ? { timeoutat: input.now + input.timeout } : {} };
|
|
9142
|
+
}
|
|
9143
|
+
function resolveapproval(input) {
|
|
9144
|
+
const gate = input.requests.find((request) => request.id === input.id);
|
|
9145
|
+
if (gate === void 0 || gate.state !== "pending") return { requests: input.requests };
|
|
9146
|
+
const decision = input.decision;
|
|
9147
|
+
const requests = input.requests.map((request) => request.id === input.id ? { ...request, state: decision, decidedat: input.now, actor: input.actor } : request);
|
|
9148
|
+
return { requests, exec: { requestid: input.id, decision, actor: input.actor, at: input.now, latencyms: input.now - gate.raisedat } };
|
|
9149
|
+
}
|
|
9150
|
+
function expireapprovals(requests, now) {
|
|
9151
|
+
return requests.map((request) => request.state === "pending" && request.timeoutat !== void 0 && now >= request.timeoutat ? { ...request, state: "expired" } : request);
|
|
9152
|
+
}
|
|
9153
|
+
|
|
8479
9154
|
// protocol.ts
|
|
8480
9155
|
function record(value) {
|
|
8481
9156
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Protocol message must be an object.");
|
|
@@ -10072,140 +10747,88 @@ function yamlscalarvalue(text2) {
|
|
|
10072
10747
|
return text2;
|
|
10073
10748
|
}
|
|
10074
10749
|
|
|
10075
|
-
//
|
|
10076
|
-
var
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
}
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
return
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
return
|
|
10098
|
-
}
|
|
10099
|
-
function
|
|
10100
|
-
|
|
10101
|
-
if (
|
|
10102
|
-
if (
|
|
10103
|
-
if (
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
}
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
}
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
}
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
}
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
}
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
}
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
const
|
|
10153
|
-
if (
|
|
10154
|
-
|
|
10155
|
-
const
|
|
10156
|
-
|
|
10157
|
-
const step = tool.risk === "read" ? { id: `mcp-${input.client.id}-${input.now}`, kind: tool.kind, summary: tool.description.split(".")[0] ?? tool.description, risk: "read", ...typeof params.target === "string" ? { target: params.target } : {}, ...typeof params.value === "string" ? { value: params.value } : {}, ...params.options !== void 0 && typeof params.options === "object" && !Array.isArray(params.options) ? { options: JSON.stringify(params.options) } : {} } : input.plan?.steps.find((candidate) => candidate.id === stepid);
|
|
10158
|
-
if (step === void 0) return { error: rpcerrorof("consentrefused", "The tool call names a step the approved plan does not carry.") };
|
|
10159
|
-
try {
|
|
10160
|
-
const result = await input.execute(step);
|
|
10161
|
-
return { result, step };
|
|
10162
|
-
} catch (error) {
|
|
10163
|
-
return { error: rpcerrorof("internal", error instanceof Error ? error.message : String(error)) };
|
|
10164
|
-
}
|
|
10165
|
-
}
|
|
10166
|
-
async function handleframe(input) {
|
|
10167
|
-
if (input.raw !== void 0 && input.config.framesize !== void 0 && input.raw.length > input.config.framesize) return respond({ id: null, error: rpcerrorof("params", `The wire frame exceeds the user configured frame size of ${input.config.framesize} characters.`) });
|
|
10168
|
-
let frame;
|
|
10169
|
-
if (input.raw !== void 0) {
|
|
10170
|
-
try {
|
|
10171
|
-
frame = parseframe(input.raw);
|
|
10172
|
-
} catch {
|
|
10173
|
-
return respond({ id: null, error: rpcerrorof("parse", "The wire frame does not parse as json.") });
|
|
10174
|
-
}
|
|
10175
|
-
} else if (input.frame !== void 0) {
|
|
10176
|
-
frame = input.frame;
|
|
10177
|
-
} else {
|
|
10178
|
-
return respond({ id: null, error: rpcerrorof("parse", "The server received no frame to route.") });
|
|
10179
|
-
}
|
|
10180
|
-
const invalid = validateframe(frame, servermethods(), input.config);
|
|
10181
|
-
if (invalid !== void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: invalid });
|
|
10182
|
-
const entry = servermethods().find((candidate) => candidate.method === frame.method);
|
|
10183
|
-
if (entry === void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("method", `The server routes no method named ${String(frame.method)}.`) });
|
|
10184
|
-
const params = frame.params;
|
|
10185
|
-
if (entry.handler === "initialize") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: initialize({ ...params !== void 0 ? { params } : {}, config: input.config, catalog: input.catalog }) });
|
|
10186
|
-
if (entry.handler === "ping") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: ping({ now: input.now }) });
|
|
10187
|
-
if (entry.handler === "listtools") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: listtools(input.catalog) });
|
|
10188
|
-
if (entry.handler === "negotiate") {
|
|
10189
|
-
const server = servercapabilities({ config: input.config, catalog: input.catalog });
|
|
10190
|
-
const clientcaps = params?.capabilities && typeof params.capabilities === "object" && !Array.isArray(params.capabilities) ? params.capabilities : void 0;
|
|
10191
|
-
const outcome = negotiate({ ...clientcaps !== void 0 ? { client: clientcaps } : {}, server });
|
|
10192
|
-
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, ...outcome.agreed ? { result: outcome.capabilities } : { error: rpcerrorof("params", outcome.mismatch ?? "The capability negotiation did not agree.") } });
|
|
10193
|
-
}
|
|
10194
|
-
const dispatched = await dispatchtool({ ...params !== void 0 ? { params } : {}, client: input.client, catalog: input.catalog, ...input.session !== void 0 ? { session: input.session } : {}, ...input.plan !== void 0 ? { plan: input.plan } : {}, origin: input.origin, now: input.now, execute: input.execute });
|
|
10195
|
-
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, ...dispatched.error !== void 0 ? { error: dispatched.error } : { result: dispatched.result } });
|
|
10196
|
-
}
|
|
10197
|
-
function bindlocalhost(config) {
|
|
10198
|
-
const bind = config.bind !== void 0 && config.bind.trim() !== "" ? config.bind.trim() : localhostbind;
|
|
10199
|
-
return { bind, port: config.port, localhost: bind === localhostbind || bind === "localhost" || bind === "::1" };
|
|
10200
|
-
}
|
|
10201
|
-
function launchbridge(input) {
|
|
10202
|
-
return { id: input.id ?? `bridge-${input.now}`, host: input.host, connected: true, ...input.pid !== void 0 ? { pid: input.pid } : {}, startedat: input.now, restarts: 0, received: 0, sent: 0 };
|
|
10203
|
-
}
|
|
10204
|
-
function relayframe(input) {
|
|
10205
|
-
return { ...input.bridge, connected: true, received: input.bridge.received + (input.direction === "inbound" ? 1 : 0), sent: input.bridge.sent + (input.direction === "outbound" ? 1 : 0), lastframeat: input.now };
|
|
10206
|
-
}
|
|
10207
|
-
function toolcallevent(input) {
|
|
10208
|
-
return { id: input.id, clientid: input.clientid, tool: input.tool, origin: input.origin, ok: input.ok, ...input.code !== void 0 ? { code: input.code } : {}, at: input.now };
|
|
10750
|
+
// toolcalls.ts
|
|
10751
|
+
var defaultidempotencywindowms = 3e5;
|
|
10752
|
+
function applyratelimit(input) {
|
|
10753
|
+
const existing = input.limits.find((limit2) => limit2.clientid === input.clientid);
|
|
10754
|
+
if (existing === void 0) return { allowed: true, used: 0, limits: input.limits };
|
|
10755
|
+
const elapsed = input.now - existing.windowstartedat;
|
|
10756
|
+
const limit = elapsed >= existing.windowms ? { ...existing, windowstartedat: input.now, used: 0 } : existing;
|
|
10757
|
+
if (limit.budget !== void 0 && limit.used >= limit.budget) {
|
|
10758
|
+
const retryafter = Math.max(0, limit.windowms - (input.now - limit.windowstartedat));
|
|
10759
|
+
return { allowed: false, used: limit.used, budget: limit.budget, retryafter, limits: input.limits.map((candidate) => candidate.clientid === input.clientid ? limit : candidate) };
|
|
10760
|
+
}
|
|
10761
|
+
const counted = { ...limit, used: limit.used + 1 };
|
|
10762
|
+
return { allowed: true, used: counted.used, ...counted.budget !== void 0 ? { budget: counted.budget } : {}, limit: counted, limits: input.limits.map((candidate) => candidate.clientid === input.clientid ? counted : candidate) };
|
|
10763
|
+
}
|
|
10764
|
+
function structurederrorof(input) {
|
|
10765
|
+
return { code: input.code, message: input.message, retryhint: input.retryhint, ...input.retryafter !== void 0 ? { retryafter: input.retryafter } : {} };
|
|
10766
|
+
}
|
|
10767
|
+
function retryhintof(failure) {
|
|
10768
|
+
const text2 = `${failure.code ?? ""} ${failure.message ?? ""}`.toLowerCase();
|
|
10769
|
+
if (text2.includes("consent") || text2.includes("refus") || text2.includes("unpaired") || text2.includes("unapproved") || text2.includes("grant")) return "none";
|
|
10770
|
+
if (text2.includes("rate") || text2.includes("busy") || text2.includes("queue") || text2.includes("window")) return "wait";
|
|
10771
|
+
if (text2.includes("timeout") || text2.includes("timed out") || text2.includes("internal") || text2.includes("network")) return "retry";
|
|
10772
|
+
return "none";
|
|
10773
|
+
}
|
|
10774
|
+
function checkidempotency(input) {
|
|
10775
|
+
const match = input.records.find((record2) => record2.key === input.key);
|
|
10776
|
+
if (match === void 0) return { reason: "The idempotency key names no stored record." };
|
|
10777
|
+
if (match.clientid !== input.clientid) return { reason: "The idempotency key belongs to another client and never replays across clients." };
|
|
10778
|
+
if (input.now >= match.expiresat) return { reason: "The idempotency record expired past its window and the call runs again." };
|
|
10779
|
+
return { replay: match.result, record: match };
|
|
10780
|
+
}
|
|
10781
|
+
function recordidempotency(input) {
|
|
10782
|
+
const record2 = { key: input.key, clientid: input.clientid, tool: input.tool, result: input.result, createdat: input.now, expiresat: input.now + (input.window ?? defaultidempotencywindowms) };
|
|
10783
|
+
return [record2, ...input.records.filter((candidate) => !(candidate.key === input.key && candidate.clientid === input.clientid))];
|
|
10784
|
+
}
|
|
10785
|
+
function expireidempotency(records, now) {
|
|
10786
|
+
return records.filter((record2) => now < record2.expiresat);
|
|
10787
|
+
}
|
|
10788
|
+
async function runbatch(input) {
|
|
10789
|
+
const outcomes = [];
|
|
10790
|
+
for (let index = 0; index < input.calls.length; index += 1) {
|
|
10791
|
+
const call = input.calls[index];
|
|
10792
|
+
if (call === void 0) continue;
|
|
10793
|
+
const outcome = await input.execute(call, index);
|
|
10794
|
+
outcomes.push({ callid: call.id, tool: call.name, ok: outcome.ok, ...outcome.result !== void 0 ? { result: outcome.result } : {}, ...outcome.error !== void 0 ? { error: outcome.error } : {}, at: input.now + index });
|
|
10795
|
+
if (!outcome.ok && input.stoponerror) return { outcomes, stoppedat: call.id };
|
|
10796
|
+
}
|
|
10797
|
+
return { outcomes };
|
|
10798
|
+
}
|
|
10799
|
+
function dryruntool(input) {
|
|
10800
|
+
const findings = [];
|
|
10801
|
+
for (const required of input.tool.inputschema.required) {
|
|
10802
|
+
const value = input.params[required];
|
|
10803
|
+
if (value === void 0 || value === null || typeof value === "string" && value.trim() === "") findings.push(`The required argument ${required} of ${input.tool.name} stays empty.`);
|
|
10804
|
+
}
|
|
10805
|
+
for (const [name, property] of Object.entries(input.tool.inputschema.properties)) {
|
|
10806
|
+
const value = input.params[name];
|
|
10807
|
+
if (value === void 0 || value === null) continue;
|
|
10808
|
+
const expected = property.type;
|
|
10809
|
+
const actual = Array.isArray(value) ? "array" : typeof value;
|
|
10810
|
+
if (actual !== expected) findings.push(`The argument ${name} of ${input.tool.name} carries a ${actual} value where the schema asks a ${expected}.`);
|
|
10811
|
+
}
|
|
10812
|
+
const stepid = input.stepid !== void 0 ? input.stepid : typeof input.params.stepid === "string" ? input.params.stepid : void 0;
|
|
10813
|
+
const gate = tooldispatchgate({ client: input.client, tool: input.tool, session: input.session, plan: input.plan, origin: input.origin, ...stepid !== void 0 ? { stepid } : {}, now: input.now });
|
|
10814
|
+
if (!gate.allowed) findings.push(gate.reason ?? `The consent gates refused the ${input.tool.name} dry run.`);
|
|
10815
|
+
return { callid: input.callid ?? randomid(), tool: input.tool.name, argsvalid: findings.length === 0, consentok: gate.allowed, findings, executed: false, mutations: [], at: input.now };
|
|
10816
|
+
}
|
|
10817
|
+
function applymock(input) {
|
|
10818
|
+
const mock = input.mocks.find((candidate) => candidate.tool === input.tool);
|
|
10819
|
+
if (mock === void 0) return {};
|
|
10820
|
+
if (mock.testcontext !== true) return { reason: `The ${input.tool} mock stays outside a test context and is refused; mocks never answer real calls.` };
|
|
10821
|
+
return { result: mock.result };
|
|
10822
|
+
}
|
|
10823
|
+
function begincall(input) {
|
|
10824
|
+
return { callid: input.callid ?? randomid(), clientid: input.clientid, tool: input.tool, state: "inflight", startedat: input.now, ...input.idempotencykey !== void 0 && input.idempotencykey.trim() !== "" ? { idempotencykey: input.idempotencykey } : {}, ...input.dryrun === true ? { dryrun: true } : {}, ...input.batchid !== void 0 ? { batchid: input.batchid } : {}, chunks: 0 };
|
|
10825
|
+
}
|
|
10826
|
+
function endcall(input) {
|
|
10827
|
+
const match = input.contexts.find((context2) => context2.callid === input.callid);
|
|
10828
|
+
if (match === void 0) return { contexts: input.contexts, reason: `The call context ${input.callid} never opened.` };
|
|
10829
|
+
if (match.state !== "inflight") return { contexts: input.contexts, context: match };
|
|
10830
|
+
const context = { ...match, state: input.ok ? "done" : "failed", endedat: input.now, ...input.errorcode !== void 0 ? { errorcode: input.errorcode } : {}, ...input.partial !== void 0 ? { partial: input.partial } : {} };
|
|
10831
|
+
return { contexts: input.contexts.map((candidate) => candidate.callid === input.callid ? context : candidate), context };
|
|
10209
10832
|
}
|
|
10210
10833
|
|
|
10211
10834
|
// extension/pagesession.ts
|
|
@@ -18272,7 +18895,270 @@ async function handlerequest(message, sender) {
|
|
|
18272
18895
|
const inputframe = message;
|
|
18273
18896
|
if (typeof inputframe.raw !== "string" || inputframe.raw.trim() === "") throw new Error("The mcp frame intake needs the raw wire frame.");
|
|
18274
18897
|
const transport = inputframe.transport === "http" ? "http" : "stdio";
|
|
18275
|
-
return processmcpframe(inputframe.raw, inputframe.clientid ?? "", transport);
|
|
18898
|
+
return processmcpframe(inputframe.raw, inputframe.clientid ?? "", transport, inputframe.token, inputframe.fingerprint);
|
|
18899
|
+
}
|
|
18900
|
+
case "mcppairing": {
|
|
18901
|
+
const inputpairing = message;
|
|
18902
|
+
const scopes = (Array.isArray(inputpairing.scopes) ? inputpairing.scopes : []).filter((scope) => ["browser", "workflow", "memory", "system"].includes(scope));
|
|
18903
|
+
const code = await issuepairingcodehandler(scopes);
|
|
18904
|
+
return { code: code.code, scopes: code.scopes, issuedat: code.issuedat, expiresat: code.expiresat };
|
|
18905
|
+
}
|
|
18906
|
+
case "mcpchallenge": {
|
|
18907
|
+
const state = await memory.getmcpstate();
|
|
18908
|
+
if (state?.state !== "running") throw new Error("The auth challenge needs the running mcp server.");
|
|
18909
|
+
const now = Date.now();
|
|
18910
|
+
const challenge = issuechallenge({ method: "pairingcode", now });
|
|
18911
|
+
await memory.setmcpstate({ ...state, challenge });
|
|
18912
|
+
await memory.addauthhandshake({ id: randomid(), clientid: "new", method: "pairingcode", outcome: "issued", at: now });
|
|
18913
|
+
await audit("protocol", "The server issued one auth challenge to a new remote client; the handshake verifies the single use nonce before any pairing exchange.", {});
|
|
18914
|
+
return { nonce: challenge.nonce, method: challenge.method, expiresat: challenge.expiresat };
|
|
18915
|
+
}
|
|
18916
|
+
case "mcpexchange": {
|
|
18917
|
+
const inputexchange = message;
|
|
18918
|
+
const state = await memory.getmcpstate();
|
|
18919
|
+
if (state?.state !== "running") throw new Error("The pairing exchange needs the running mcp server.");
|
|
18920
|
+
const now = Date.now();
|
|
18921
|
+
const challenge = state.challenge;
|
|
18922
|
+
const fingerprint = typeof inputexchange.fingerprint === "string" && inputexchange.fingerprint.trim() !== "" ? inputexchange.fingerprint.trim() : "";
|
|
18923
|
+
const refuseexchange = async () => {
|
|
18924
|
+
await memory.addauthhandshake({ id: randomid(), clientid: "new", method: "pairingcode", outcome: "refused", at: now });
|
|
18925
|
+
await audit("protocol", "The pairing exchange failed its auth handshake and was refused; the fixed refusal carries no pairing state.", {});
|
|
18926
|
+
throw new Error("The remote frame failed its authentication handshake.");
|
|
18927
|
+
};
|
|
18928
|
+
if (fingerprint === "" || challenge === void 0 || inputexchange.nonce !== challenge.nonce || now >= challenge.expiresat) await refuseexchange();
|
|
18929
|
+
const redeemed = redeempairingcode({ codes: await memory.getpairingcodes(), code: inputexchange.code ?? "", now });
|
|
18930
|
+
if (redeemed.code === void 0) {
|
|
18931
|
+
await memory.addauthhandshake({ id: randomid(), clientid: "new", method: "pairingcode", outcome: "refused", at: now });
|
|
18932
|
+
await audit("protocol", "The pairing exchange presented a code that does not pair and was refused; a used or expired code never pairs a second client.", {});
|
|
18933
|
+
throw new Error(redeemed.reason ?? "The remote frame failed its authentication handshake.");
|
|
18934
|
+
}
|
|
18935
|
+
await memory.usepairingcode(redeemed.code.code, now);
|
|
18936
|
+
const identity = { fingerprint, displayname: typeof inputexchange.displayname === "string" && inputexchange.displayname.trim() !== "" ? inputexchange.displayname.trim() : `Client ${fingerprint.slice(0, 8)}` };
|
|
18937
|
+
await memory.setclientidentity(identity);
|
|
18938
|
+
await memory.setallowlistentry(grantallowlistentry({ entries: await memory.getallowlist(), identity, namespaces: redeemed.code.scopes, actor: "pairing", now })[0]);
|
|
18939
|
+
const config = await mcpconfigof();
|
|
18940
|
+
const clientid = `client-${now}`;
|
|
18941
|
+
const issued = await issuetoken({ clientid, scopes: redeemed.code.scopes, now, ...config.remoteaccess?.tokenlifetimems !== void 0 ? { lifetime: config.remoteaccess.tokenlifetimems } : { lifetime: defaulttokenlifetimems } });
|
|
18942
|
+
const client = { ...connectclient({ transport: "http", now, id: clientid }), fingerprint, paired: true, pairedat: now };
|
|
18943
|
+
await memory.setclient(client);
|
|
18944
|
+
await memory.setsessiontokens([...await memory.getsessiontokens(), issued.token]);
|
|
18945
|
+
await memory.addauthhandshake({ id: randomid(), clientid, method: "pairingcode", outcome: "verified", at: now });
|
|
18946
|
+
await audit("protocol", `The client ${identity.displayname} (${fingerprint}) exchanged its single use pairing code for a session token of the ${redeemed.code.scopes.join(", ") || "no"} namespace${redeemed.code.scopes.length === 1 ? "" : "s"}; the raw token leaves exactly once and only its digest persists.`, {});
|
|
18947
|
+
return { clientid, token: issued.raw, scopes: issued.token.scopes, expiresat: issued.token.expiresat };
|
|
18948
|
+
}
|
|
18949
|
+
case "mcpremoteconfig": {
|
|
18950
|
+
const inputremote = message;
|
|
18951
|
+
const current = await mcpconfigof();
|
|
18952
|
+
const endpoint = typeof inputremote.endpoint === "string" && inputremote.endpoint.trim() !== "" ? inputremote.endpoint.trim() : current.remoteaccess?.endpoint ?? "https://127.0.0.1:7436";
|
|
18953
|
+
const tlsmode = inputremote.tlsmode === "off" || inputremote.tlsmode === "on" || inputremote.tlsmode === "required" ? inputremote.tlsmode : current.remoteaccess?.tls.mode ?? "off";
|
|
18954
|
+
const certificatefingerprint = typeof inputremote.certificatefingerprint === "string" && inputremote.certificatefingerprint.trim() !== "" ? inputremote.certificatefingerprint.trim() : current.remoteaccess?.tls.certificatefingerprint;
|
|
18955
|
+
const config = {
|
|
18956
|
+
...current,
|
|
18957
|
+
...inputremote.reviewed === true || current.remote === true ? { remote: true } : {},
|
|
18958
|
+
httpstream: {
|
|
18959
|
+
endpoint: current.httpstream?.endpoint ?? "/mcp",
|
|
18960
|
+
streampath: typeof inputremote.streampath === "string" && inputremote.streampath.trim() !== "" ? inputremote.streampath.trim() : current.httpstream?.streampath ?? "/mcp/stream",
|
|
18961
|
+
tls: { mode: tlsmode, ...certificatefingerprint !== void 0 ? { certificatefingerprint } : {}, ...current.httpstream?.tls.verifiedat !== void 0 ? { verifiedat: current.httpstream.tls.verifiedat } : {} },
|
|
18962
|
+
...inputremote.heartbeatms !== void 0 ? { heartbeatms: inputremote.heartbeatms } : current.httpstream?.heartbeatms !== void 0 ? { heartbeatms: current.httpstream.heartbeatms } : {},
|
|
18963
|
+
...inputremote.idlewindowms !== void 0 ? { idlewindowms: inputremote.idlewindowms } : current.httpstream?.idlewindowms !== void 0 ? { idlewindowms: current.httpstream.idlewindowms } : {}
|
|
18964
|
+
},
|
|
18965
|
+
remoteaccess: {
|
|
18966
|
+
endpoint,
|
|
18967
|
+
tls: { mode: tlsmode, ...certificatefingerprint !== void 0 ? { certificatefingerprint } : {} },
|
|
18968
|
+
...inputremote.maxclients !== void 0 ? { maxclients: inputremote.maxclients } : current.remoteaccess?.maxclients !== void 0 ? { maxclients: current.remoteaccess.maxclients } : {},
|
|
18969
|
+
...inputremote.tokenlifetime !== void 0 ? { tokenlifetimems: inputremote.tokenlifetime } : current.remoteaccess?.tokenlifetimems !== void 0 ? { tokenlifetimems: current.remoteaccess.tokenlifetimems } : {},
|
|
18970
|
+
...inputremote.approvaltimeoutms !== void 0 ? { approvaltimeout: { windowms: inputremote.approvaltimeoutms, ontimeout: "refuse" } } : current.remoteaccess?.approvaltimeout !== void 0 ? { approvaltimeout: current.remoteaccess.approvaltimeout } : {}
|
|
18971
|
+
}
|
|
18972
|
+
};
|
|
18973
|
+
const gate = remoteenablementgate(config);
|
|
18974
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The remote transport config failed its gate.");
|
|
18975
|
+
const enablement = serverenablementgate(config);
|
|
18976
|
+
if (!enablement.allowed) throw new Error(enablement.reason ?? "The remote transport config failed the enablement gate.");
|
|
18977
|
+
await memory.setmcpconfig(config);
|
|
18978
|
+
await audit("protocol", `The user configured the remote transport for the endpoint ${endpoint} with the ${tlsmode} tls mode${certificatefingerprint !== void 0 ? " and the reviewed certificate fingerprint" : ""}${config.remoteaccess?.maxclients !== void 0 ? `, a client ceiling of ${config.remoteaccess.maxclients}` : " and no client ceiling"}${config.remoteaccess?.tokenlifetimems !== void 0 ? `, a token lifetime of ${config.remoteaccess.tokenlifetimems} milliseconds` : ""}${config.remoteaccess?.approvaltimeout !== void 0 ? ` and an approval window of ${config.remoteaccess.approvaltimeout.windowms} milliseconds` : ""}; every value stays the user choice.`, {});
|
|
18979
|
+
return mcpstateof();
|
|
18980
|
+
}
|
|
18981
|
+
case "mcpallowlist": {
|
|
18982
|
+
const inputallow = message;
|
|
18983
|
+
const fingerprint = typeof inputallow.fingerprint === "string" && inputallow.fingerprint.trim() !== "" ? inputallow.fingerprint.trim() : "";
|
|
18984
|
+
if (fingerprint === "") throw new Error("The allowlist edit needs the client fingerprint.");
|
|
18985
|
+
const now = Date.now();
|
|
18986
|
+
if (inputallow.remove === true) {
|
|
18987
|
+
const entries2 = await memory.getallowlist();
|
|
18988
|
+
const removed = entries2.find((entry) => entry.fingerprint === fingerprint);
|
|
18989
|
+
await memory.removeallowlistentry(fingerprint);
|
|
18990
|
+
await audit("protocol", `The user refused the client ${removed?.displayname ?? fingerprint} its allowlist entry; its fingerprint stops passing the allowlist check.`, {});
|
|
18991
|
+
return mcpstateof();
|
|
18992
|
+
}
|
|
18993
|
+
const namespaces = (Array.isArray(inputallow.namespaces) ? inputallow.namespaces : []).filter((scope) => ["browser", "workflow", "memory", "system"].includes(scope));
|
|
18994
|
+
const identities = await memory.getclientidentities();
|
|
18995
|
+
const known = identities.find((identity2) => identity2.fingerprint === fingerprint);
|
|
18996
|
+
const identity = { fingerprint, displayname: typeof inputallow.displayname === "string" && inputallow.displayname.trim() !== "" ? inputallow.displayname.trim() : known?.displayname ?? `Client ${fingerprint.slice(0, 8)}` };
|
|
18997
|
+
await memory.setclientidentity(identity);
|
|
18998
|
+
const entries = grantallowlistentry({ entries: await memory.getallowlist(), identity, namespaces, actor: "user", now });
|
|
18999
|
+
const granted = entries[0];
|
|
19000
|
+
if (granted === void 0) throw new Error("The allowlist grant failed.");
|
|
19001
|
+
const valid = allowlistentryvalid(granted, await memory.getclientidentities());
|
|
19002
|
+
if (!valid.allowed) throw new Error(valid.reason ?? "The allowlist entry failed its validation.");
|
|
19003
|
+
await memory.setallowlistentry(granted);
|
|
19004
|
+
await audit("protocol", `The user allowed the client ${identity.displayname} (${fingerprint}) the ${namespaces.join(", ") || "no"} namespace${namespaces.length === 1 ? "" : "s"}; the grant history rides the record.`, {});
|
|
19005
|
+
return mcpstateof();
|
|
19006
|
+
}
|
|
19007
|
+
case "mcprevokeclient": {
|
|
19008
|
+
const inputrevoke = message;
|
|
19009
|
+
const clientid = inputrevoke.clientid ?? "";
|
|
19010
|
+
if (clientid === "") throw new Error("The revocation needs the client id.");
|
|
19011
|
+
const gate = revocationgate();
|
|
19012
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The revocation failed.");
|
|
19013
|
+
const now = Date.now();
|
|
19014
|
+
const tokens = revokeclient(await memory.getsessiontokens(), clientid, now);
|
|
19015
|
+
await memory.setsessiontokens(tokens);
|
|
19016
|
+
const client = (await memory.getclients()).find((entry) => entry.id === clientid);
|
|
19017
|
+
if (client !== void 0 && client.disconnectedat === void 0) await memory.setclient(disconnectclient(await memory.getclients(), clientid, now).find((entry) => entry.id === clientid));
|
|
19018
|
+
const revoked = tokens.filter((token) => token.clientid === clientid && token.revokedat === now).length;
|
|
19019
|
+
await audit("protocol", `The user revoked the paired client ${clientid}; ${revoked} session token${revoked === 1 ? "" : "s"} stopped verifying and every further frame of the client refuses \u2014 the revocation stays available at any time.`, {});
|
|
19020
|
+
return mcpstateof();
|
|
19021
|
+
}
|
|
19022
|
+
case "mcpapprovaldecision": {
|
|
19023
|
+
const inputapproval = message;
|
|
19024
|
+
const approvalid = inputapproval.approvalid ?? "";
|
|
19025
|
+
if (approvalid === "" || typeof inputapproval.approved !== "boolean") throw new Error("The approval decision needs the gate id and the reviewed approved flag.");
|
|
19026
|
+
const now = Date.now();
|
|
19027
|
+
const stored = await memory.listapprovals();
|
|
19028
|
+
const outcome = resolveapproval({ requests: stored, id: approvalid, decision: inputapproval.approved ? "approved" : "refused", actor: "user", now });
|
|
19029
|
+
if (outcome.exec === void 0) throw new Error(`No pending approval gate matches ${approvalid}.`);
|
|
19030
|
+
for (const request of outcome.requests) await memory.setapproval(request);
|
|
19031
|
+
await memory.addapprovalexec(outcome.exec);
|
|
19032
|
+
const gate = stored.find((request) => request.id === approvalid);
|
|
19033
|
+
if (gate !== void 0 && inputapproval.approved) {
|
|
19034
|
+
const tool = resolvetool(buildtoolcatalog(), gate.tool);
|
|
19035
|
+
const stepid = typeof gate.params.stepid === "string" ? gate.params.stepid : void 0;
|
|
19036
|
+
const plan = await memory.getplan();
|
|
19037
|
+
const step = tool !== void 0 && tool.risk !== "read" && plan !== void 0 && stepid !== void 0 ? plan.steps.find((candidate) => candidate.id === stepid) : void 0;
|
|
19038
|
+
if (step === void 0) throw new Error("The approved gate names no step the approved plan carries; the call refuses.");
|
|
19039
|
+
const result = await executemcpstep(step);
|
|
19040
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: gate.clientid, tool: gate.tool, origin: (await memory.getsession())?.origin ?? "", ok: !result.iserror, now }));
|
|
19041
|
+
await audit("tool", `The approval gate ${approvalid} executed the ${gate.tool} call of the client ${gate.clientid} after the user approved it in ${outcome.exec.latencyms} milliseconds; no payload rides the record.`, {});
|
|
19042
|
+
} else {
|
|
19043
|
+
await audit("protocol", `The user refused the approval gate ${approvalid} for the ${gate?.tool ?? "tool"} call of the client ${gate?.clientid ?? "unknown"}; the pending call never executes.`, {});
|
|
19044
|
+
}
|
|
19045
|
+
return mcpstateof();
|
|
19046
|
+
}
|
|
19047
|
+
case "mcpsubscribe": {
|
|
19048
|
+
const inputsub = message;
|
|
19049
|
+
const clientid = inputsub.clientid?.trim() ?? "";
|
|
19050
|
+
if (clientid === "") throw new Error("The event subscription needs the paired client.");
|
|
19051
|
+
const now = Date.now();
|
|
19052
|
+
if (inputsub.unsubscribe === true) {
|
|
19053
|
+
const subscriptionid = inputsub.subscriptionid?.trim() ?? "";
|
|
19054
|
+
if (subscriptionid === "") throw new Error("The unsubscribe needs the subscription id.");
|
|
19055
|
+
await memory.seteventsubscriptions(unsubscriberegister(await memory.geteventsubscriptions(), subscriptionid, now));
|
|
19056
|
+
await audit("protocol", `The user cancelled the event subscription ${subscriptionid} of the client ${clientid}; the record stays for the audit trail.`, {});
|
|
19057
|
+
return mcpstateof();
|
|
19058
|
+
}
|
|
19059
|
+
const registered = subscriberegister({ clientid, ...Array.isArray(inputsub.kinds) ? { kinds: inputsub.kinds } : {}, ...inputsub.origin !== void 0 && inputsub.origin.trim() !== "" ? { origin: inputsub.origin.trim() } : {}, ...inputsub.tool !== void 0 && inputsub.tool.trim() !== "" ? { tool: inputsub.tool.trim() } : {}, now });
|
|
19060
|
+
if (registered.subscription === void 0) throw new Error(registered.reason ?? "The event subscription did not register.");
|
|
19061
|
+
const grade = subscriptiongrade(registered.subscription);
|
|
19062
|
+
if (!grade.allowed) throw new Error(grade.reason ?? "The event subscription failed its gate.");
|
|
19063
|
+
await memory.seteventsubscriptions([registered.subscription, ...await memory.geteventsubscriptions()]);
|
|
19064
|
+
await audit("protocol", `The user subscribed the client ${clientid} to the ${registered.subscription.kinds.join(", ")} event kinds${registered.subscription.origin !== void 0 ? ` of the origin ${registered.subscription.origin}` : ""}${registered.subscription.tool !== void 0 ? ` for the tool ${registered.subscription.tool}` : ""}.`, {});
|
|
19065
|
+
return mcpstateof();
|
|
19066
|
+
}
|
|
19067
|
+
case "mcpresource": {
|
|
19068
|
+
const inputwatch = message;
|
|
19069
|
+
const clientid = inputwatch.clientid?.trim() ?? "";
|
|
19070
|
+
if (clientid === "") throw new Error("The resource watcher needs the paired client.");
|
|
19071
|
+
const now = Date.now();
|
|
19072
|
+
if (inputwatch.unwatch === true) {
|
|
19073
|
+
const watchid = inputwatch.watchid?.trim() ?? "";
|
|
19074
|
+
if (watchid === "") throw new Error("The unwatch needs the watcher id.");
|
|
19075
|
+
await memory.setresourcewatches(unwatchresource(await memory.getresourcewatches(), watchid, now));
|
|
19076
|
+
await audit("protocol", `The user cancelled the resource watcher ${watchid} of the client ${clientid}; the record stays for the audit trail.`, {});
|
|
19077
|
+
return mcpstateof();
|
|
19078
|
+
}
|
|
19079
|
+
const watched = watchresource({ clientid, resource: inputwatch.resource?.trim() ?? "", ...inputwatch.state !== void 0 ? { state: inputwatch.state } : {}, now });
|
|
19080
|
+
if (watched.watch === void 0) throw new Error(watched.reason ?? "The resource watcher did not start.");
|
|
19081
|
+
await memory.setresourcewatches([watched.watch, ...await memory.getresourcewatches()]);
|
|
19082
|
+
await audit("protocol", `The user started the ${watched.watch.resource} resource watcher for the client ${clientid} with its page state baseline; the deltas compare against it.`, {});
|
|
19083
|
+
return mcpstateof();
|
|
19084
|
+
}
|
|
19085
|
+
case "mcpsampling": {
|
|
19086
|
+
const inputsample = message;
|
|
19087
|
+
const now = Date.now();
|
|
19088
|
+
if (inputsample.respond === true) {
|
|
19089
|
+
const samplingid = inputsample.samplingid?.trim() ?? "";
|
|
19090
|
+
if (samplingid === "") throw new Error("The sampling answer needs the request id.");
|
|
19091
|
+
const outcome = answersampling({ requests: await memory.getsamplingrequests(), id: samplingid, ...inputsample.answer !== void 0 ? { answer: inputsample.answer } : {}, ...inputsample.refused === true ? { refused: true } : {}, now });
|
|
19092
|
+
if (outcome.request === void 0) throw new Error(outcome.reason ?? "The sampling answer closed no request.");
|
|
19093
|
+
await memory.setsamplingrequests(outcome.requests);
|
|
19094
|
+
await audit("protocol", `The sampling callback ${samplingid} of the client ${outcome.request.clientid} closed its round trip with the client answer; the provenance times ride the record.`, {});
|
|
19095
|
+
return mcpstateof();
|
|
19096
|
+
}
|
|
19097
|
+
const clientid = inputsample.clientid?.trim() ?? "";
|
|
19098
|
+
if (clientid === "") throw new Error("The sampling callback needs the paired client it addresses.");
|
|
19099
|
+
const client = (await memory.getclients()).find((entry) => entry.id === clientid);
|
|
19100
|
+
const requested = requestsampling({ clientid, ...client?.capabilities !== void 0 ? { capabilities: client.capabilities } : {}, prompt: inputsample.prompt ?? "", ...inputsample.system !== void 0 ? { system: inputsample.system } : {}, ...inputsample.pagecontent !== void 0 ? { pagecontent: inputsample.pagecontent } : {}, pagegrant: inputsample.pagegrant === true, ...inputsample.maxtokens !== void 0 ? { maxtokens: inputsample.maxtokens } : {}, now });
|
|
19101
|
+
if (requested.request === void 0) throw new Error(requested.reason ?? "The sampling callback was refused.");
|
|
19102
|
+
await memory.setsamplingrequests([requested.request, ...await memory.getsamplingrequests()]);
|
|
19103
|
+
await audit("protocol", `The user sent one sampling callback to the client ${clientid}: the prompt leaves the browser${requested.request.pagecontent !== void 0 ? " with the page content the user granted" : " with the page content stripped because the user granted none"}, and the exact payload rides the panel.`, {});
|
|
19104
|
+
return mcpstateof();
|
|
19105
|
+
}
|
|
19106
|
+
case "mcpcancelcall": {
|
|
19107
|
+
const inputcancel = message;
|
|
19108
|
+
const callid = inputcancel.callid?.trim() ?? "";
|
|
19109
|
+
if (callid === "") throw new Error("The cancellation needs the call id.");
|
|
19110
|
+
const now = Date.now();
|
|
19111
|
+
const contexts = await memory.getcallcontexts();
|
|
19112
|
+
const chunks = (await memory.getstreamchunks()).filter((chunk) => chunk.callid === callid);
|
|
19113
|
+
const partial = chunks.length > 0 ? { content: chunks.map((chunk) => chunk.content).join(""), iserror: false } : void 0;
|
|
19114
|
+
const aborted = canceltool({ contexts, callid, ...inputcancel.reason !== void 0 ? { reason: inputcancel.reason } : {}, ...partial !== void 0 ? { partial } : {}, now });
|
|
19115
|
+
if (aborted.context === void 0) throw new Error(aborted.reason ?? "The cancellation named no in flight call.");
|
|
19116
|
+
await memory.setcallcontexts(aborted.contexts);
|
|
19117
|
+
await audit("protocol", `The user cancelled the in flight ${aborted.context.tool} call ${callid}${partial !== void 0 ? " with its partial result preserved" : ""}; the cooperative flag stops the page executor and the workflow engine keeps its own pause machinery.`, {});
|
|
19118
|
+
return mcpstateof();
|
|
19119
|
+
}
|
|
19120
|
+
case "mcpmock": {
|
|
19121
|
+
const inputmock = message;
|
|
19122
|
+
const toolname = inputmock.tool?.trim() ?? "";
|
|
19123
|
+
if (toolname === "") throw new Error("The tool mock needs the namespaced tool it stands in for.");
|
|
19124
|
+
const now = Date.now();
|
|
19125
|
+
if (inputmock.remove === true) {
|
|
19126
|
+
await memory.removetoolmock(toolname);
|
|
19127
|
+
await audit("protocol", `The user removed the ${toolname} tool mock; the tool returns to the real gates.`, {});
|
|
19128
|
+
return mcpstateof();
|
|
19129
|
+
}
|
|
19130
|
+
if (resolvetool(buildtoolcatalog(), toolname) === void 0) throw new Error(`The catalog holds no unambiguous tool named ${toolname}.`);
|
|
19131
|
+
const mock = { tool: toolname, result: { content: inputmock.content ?? `The ${toolname} mock answered from the test context.`, iserror: false }, testcontext: true, createdat: now };
|
|
19132
|
+
const valid = mockusagevalid(mock);
|
|
19133
|
+
if (!valid.allowed) throw new Error(valid.reason ?? "The tool mock failed its validation.");
|
|
19134
|
+
await memory.settoolmock(mock);
|
|
19135
|
+
await audit("protocol", `The user registered the ${toolname} tool mock of a test context; mocks answer with canned results and never touch the browser.`, {});
|
|
19136
|
+
return mcpstateof();
|
|
19137
|
+
}
|
|
19138
|
+
case "mcpratelimit": {
|
|
19139
|
+
const inputlimit = message;
|
|
19140
|
+
const clientid = inputlimit.clientid?.trim() ?? "";
|
|
19141
|
+
if (clientid === "") throw new Error("The per client rate limit needs the client it counts.");
|
|
19142
|
+
const now = Date.now();
|
|
19143
|
+
const limits = (await memory.getcallratelimits()).filter((limit2) => limit2.clientid !== clientid);
|
|
19144
|
+
if (inputlimit.remove === true) {
|
|
19145
|
+
await memory.setcallratelimits(limits);
|
|
19146
|
+
await audit("protocol", `The user removed the rate limit of the client ${clientid}; the client stays unbounded because no silent default applies.`, {});
|
|
19147
|
+
return mcpstateof();
|
|
19148
|
+
}
|
|
19149
|
+
const limit = { clientid, windowms: inputlimit.windowms ?? 6e4, ...inputlimit.budget !== void 0 ? { budget: inputlimit.budget } : {}, windowstartedat: now, used: 0 };
|
|
19150
|
+
const valid = callratelimitvalid(limit);
|
|
19151
|
+
if (!valid.allowed) throw new Error(valid.reason ?? "The rate limit failed its validation.");
|
|
19152
|
+
await memory.setcallratelimits([limit, ...limits]);
|
|
19153
|
+
await audit("protocol", `The user limited the client ${clientid} to ${limit.budget !== void 0 ? `${limit.budget} call${limit.budget === 1 ? "" : "s"} per ${limit.windowms} millisecond window` : `a ${limit.windowms} millisecond window with no call budget`}; every value stays the user choice.`, {});
|
|
19154
|
+
return mcpstateof();
|
|
19155
|
+
}
|
|
19156
|
+
case "mcpdryrun": {
|
|
19157
|
+
const inputdry = message;
|
|
19158
|
+
const enabled = inputdry.enabled === true;
|
|
19159
|
+
await memory.setdryruntoggle(enabled);
|
|
19160
|
+
await audit("protocol", `The user ${enabled ? "armed the tool dry run of the next call: it evaluates arguments and consent with no side effects" : "disarmed the tool dry run; the next call executes behind the gates"}.`, {});
|
|
19161
|
+
return mcpstateof();
|
|
18276
19162
|
}
|
|
18277
19163
|
case "runtobreakpoint": {
|
|
18278
19164
|
const inputdebug = message;
|
|
@@ -18644,7 +19530,70 @@ async function mcpstateof() {
|
|
|
18644
19530
|
const config = await mcpconfigof();
|
|
18645
19531
|
const state = await memory.getmcpstate();
|
|
18646
19532
|
const binding = bindlocalhost(config);
|
|
18647
|
-
|
|
19533
|
+
const now = Date.now();
|
|
19534
|
+
await mcpmaintenance(now);
|
|
19535
|
+
const tokens = await memory.getsessiontokens();
|
|
19536
|
+
const clients = await memory.listclients();
|
|
19537
|
+
const channels = closeidlechannels({ channels: await memory.getstreamchannels(), now, ...config.httpstream?.idlewindowms !== void 0 ? { idlewindow: config.httpstream.idlewindowms } : {} });
|
|
19538
|
+
await memory.setstreamchannels(channels);
|
|
19539
|
+
const streamstatus = listremotestatus;
|
|
19540
|
+
const watches = await pushresourcestate(now);
|
|
19541
|
+
const contexts = await memory.getcallcontexts();
|
|
19542
|
+
const dryruntoggle = await memory.getdryruntoggle();
|
|
19543
|
+
return { state: state?.state ?? "stopped", config, bind: binding.bind, port: binding.port, localhost: binding.localhost, clients, ...state?.bridge !== void 0 ? { bridge: state.bridge } : {}, calls: (await memory.listtoolcalls()).slice(0, 25), calllog: await memory.getcalllog({ limit: 25 }), catalog: listtools(buildtoolcatalog()), launches: (await memory.listbridgelaunches()).slice(0, 10), remote: streamstatus({ config, channels, clients, tokens, now }), pairing: (await memory.getpairingcodes()).filter((code) => code.usedat === void 0 && now < code.expiresat), allowlist: await memory.getallowlist(), tokens, identities: await memory.getclientidentities(), handshakes: (await memory.listauthhandshakes()).slice(0, 10), channels, approvals: await memory.listapprovals(), subscriptions: (await memory.geteventsubscriptions()).filter((subscription) => subscription.canceledat === void 0), watches: watches.filter((watch) => watch.canceledat === void 0), sampling: (await memory.getsamplingrequests()).slice(0, 10), limits: await memory.getcallratelimits(), contexts: contexts.slice(0, 25), inflight: contexts.filter((context) => context.state === "inflight"), batches: (await memory.getbatchcalls()).slice(0, 10), chunks: (await memory.getstreamchunks()).slice(0, 25), progressnotices: (await memory.getprogressnotices()).slice(0, 25), mocks: await memory.gettoolmocks(), dryruntoggle, idempotency: expireidempotency(await memory.getidempotencyrecords(), now) };
|
|
19544
|
+
}
|
|
19545
|
+
async function pushresourcestate(now) {
|
|
19546
|
+
const watches = await memory.getresourcewatches();
|
|
19547
|
+
const active = watches.filter((watch) => watch.canceledat === void 0);
|
|
19548
|
+
if (active.length === 0) return watches;
|
|
19549
|
+
const session = await memory.getsession();
|
|
19550
|
+
const state = { origin: session?.origin ?? "", mutations: (await memory.getmutationevents()).length, diffs: (await memory.getdiffs()).length };
|
|
19551
|
+
const pushed = notifyresource({ watches, resource: "page", state, now });
|
|
19552
|
+
if (pushed.deliveries.length > 0) {
|
|
19553
|
+
await memory.setresourcewatches(pushed.watches);
|
|
19554
|
+
await audit("protocol", `${pushed.deliveries.length} page state delta${pushed.deliveries.length === 1 ? "" : "s"} reached the resource watcher${pushed.deliveries.length === 1 ? "" : "s"} of ${[...new Set(pushed.deliveries.map((delivery) => delivery.clientid))].join(", ")}; the baselines absorbed the new page state.`, {});
|
|
19555
|
+
}
|
|
19556
|
+
return pushed.watches;
|
|
19557
|
+
}
|
|
19558
|
+
async function mcpmaintenance(now) {
|
|
19559
|
+
const tokens = await memory.getsessiontokens();
|
|
19560
|
+
const expired = tokens.filter((token) => token.revokedat === void 0 && now >= token.expiresat);
|
|
19561
|
+
if (expired.length > 0) {
|
|
19562
|
+
await memory.setsessiontokens(tokens.map((token) => expired.includes(token) ? { ...token, revokedat: now } : token));
|
|
19563
|
+
await audit("protocol", `${expired.length} session token${expired.length === 1 ? "" : "s"} reached the user configured lifetime and the server refused ${expired.length === 1 ? "it" : "them"}; the records stay for the audit trail.`, {});
|
|
19564
|
+
}
|
|
19565
|
+
const storedrecords = await memory.getidempotencyrecords();
|
|
19566
|
+
const liverecords = expireidempotency(storedrecords, now);
|
|
19567
|
+
if (liverecords.length !== storedrecords.length) {
|
|
19568
|
+
await memory.setidempotencyrecords(liverecords);
|
|
19569
|
+
await audit("protocol", `${storedrecords.length - liverecords.length} idempotency record${storedrecords.length - liverecords.length === 1 ? "" : "s"} expired past the user configured window and the key${storedrecords.length - liverecords.length === 1 ? " never replays" : "s never replay"}.`, {});
|
|
19570
|
+
}
|
|
19571
|
+
const stored = await memory.listapprovals();
|
|
19572
|
+
const approvals = expireapprovals(stored, now);
|
|
19573
|
+
for (let index = 0; index < approvals.length; index += 1) {
|
|
19574
|
+
const request = approvals[index];
|
|
19575
|
+
if (request !== void 0 && request.state === "expired" && stored[index]?.state === "pending") {
|
|
19576
|
+
await memory.setapproval(request);
|
|
19577
|
+
await audit("protocol", `The approval gate ${request.id} for the ${request.tool} call of the client ${request.clientid} expired unanswered and refused by default; the call never executes.`, {});
|
|
19578
|
+
}
|
|
19579
|
+
}
|
|
19580
|
+
}
|
|
19581
|
+
async function issuepairingcodehandler(scopes) {
|
|
19582
|
+
const session = await memory.getsession();
|
|
19583
|
+
const gate = pairingreadinessgate(session, Date.now());
|
|
19584
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The pairing flow is refused.");
|
|
19585
|
+
const code = issuepairingcode({ now: Date.now(), scopes });
|
|
19586
|
+
await memory.addpairingcode(code);
|
|
19587
|
+
await audit("protocol", `The user issued the pairing code for the ${code.scopes.join(", ") || "no"} namespace${code.scopes.length === 1 ? "" : "s"}; the code pairs one client once and expires in ${Math.round((code.expiresat - code.issuedat) / 1e3)} seconds.`, {});
|
|
19588
|
+
return code;
|
|
19589
|
+
}
|
|
19590
|
+
async function raiseremoteapproval(clientid, toolname, params, step) {
|
|
19591
|
+
const config = await mcpconfigof();
|
|
19592
|
+
const tool = resolvetool(buildtoolcatalog(), toolname);
|
|
19593
|
+
const request = requireapproval({ clientid, tool: toolname, reason: tool?.consentmeta?.review ?? `The ${toolname} tool has side effects and needs the approval gate.`, params: { ...params, stepid: step.id }, now: Date.now(), ...config.remoteaccess?.approvaltimeout !== void 0 ? { timeout: config.remoteaccess.approvaltimeout.windowms } : { timeout: defaultapprovalwindowms } });
|
|
19594
|
+
await memory.setapproval(request);
|
|
19595
|
+
await audit("protocol", `Raised the approval gate ${request.id} for the ${toolname} call of the remote client ${clientid}; the gate refuses by default after its window and no payload rides the audit.`, {});
|
|
19596
|
+
return { content: `The approval gate ${request.id} holds the ${toolname} call; it executes once the user approves it in the panel.`, payload: { approvalid: request.id, state: "pending", ...request.timeoutat !== void 0 ? { timeoutat: request.timeoutat } : {} }, iserror: false };
|
|
18648
19597
|
}
|
|
18649
19598
|
async function executelistruns(step, session) {
|
|
18650
19599
|
const options = stepoptions2(step);
|
|
@@ -18691,11 +19640,13 @@ async function trybridgelaunch(restart) {
|
|
|
18691
19640
|
function restartbridgeof(bridge) {
|
|
18692
19641
|
return { ...bridge, connected: true, restarts: bridge.restarts + 1, startedat: Date.now() };
|
|
18693
19642
|
}
|
|
18694
|
-
|
|
19643
|
+
var mcpcancelledcalls = /* @__PURE__ */ new Set();
|
|
19644
|
+
async function routemcpframe(client, frame, config, scopes) {
|
|
18695
19645
|
const session = await memory.getsession();
|
|
18696
19646
|
const plan = await memory.getplan();
|
|
18697
19647
|
const catalog = buildtoolcatalog();
|
|
18698
|
-
|
|
19648
|
+
if (frame.method === "tools/call") return await routetoolcall(client, frame, config, scopes, session, plan, catalog);
|
|
19649
|
+
const response = await handleframe({ frame, client, catalog, config, ...session !== void 0 ? { session } : {}, ...plan !== void 0 ? { plan } : {}, ...scopes !== void 0 ? { scopes } : {}, origin: session?.origin ?? "", tabid: session?.tabid ?? 0, now: Date.now(), execute: executemcpstep });
|
|
18699
19650
|
const now = Date.now();
|
|
18700
19651
|
if (frame.method === "initialize") {
|
|
18701
19652
|
const clientinfo = frame.params?.clientinfo && typeof frame.params.clientinfo === "object" && !Array.isArray(frame.params.clientinfo) ? frame.params.clientinfo : void 0;
|
|
@@ -18709,23 +19660,200 @@ async function routemcpframe(client, frame, config) {
|
|
|
18709
19660
|
if (frame.method === "tools/list") await audit("protocol", `The mcp client ${client.id} listed the tool catalog of ${listtools(catalog).tools.length} tools with their json schema inputs; the listing carries no page data.`, {});
|
|
18710
19661
|
if (frame.method === "negotiate") {
|
|
18711
19662
|
const agreed = response.error === void 0;
|
|
18712
|
-
if (agreed && response.result !== void 0)
|
|
18713
|
-
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
|
|
18720
|
-
|
|
18721
|
-
const stepid = typeof frame.params?.stepid === "string" ? frame.params.stepid : "mcp";
|
|
18722
|
-
await memory.setprogress(recordtoolcall(await memory.getprogress(), plan.id, stepid, { clientid: client.id, tool: name, ok, ...code !== void 0 ? { code } : {} }, now));
|
|
19663
|
+
if (agreed && response.result !== void 0) {
|
|
19664
|
+
await memory.setclientcapabilities(client.id, response.result);
|
|
19665
|
+
const clientcaps = frame.params?.capabilities && typeof frame.params.capabilities === "object" && !Array.isArray(frame.params.capabilities) ? frame.params.capabilities : void 0;
|
|
19666
|
+
const clientversion = typeof clientcaps?.toolversion === "number" && Number.isFinite(clientcaps.toolversion) ? clientcaps.toolversion : void 0;
|
|
19667
|
+
const floor = negotiatetoolfloor(clientversion, catalog.version);
|
|
19668
|
+
if ("floor" in floor) {
|
|
19669
|
+
const stored = (await memory.getclients()).find((entry) => entry.id === client.id);
|
|
19670
|
+
if (stored !== void 0) await memory.setclient({ ...stored, toolfloor: floor.floor });
|
|
19671
|
+
}
|
|
18723
19672
|
}
|
|
18724
|
-
await audit("
|
|
19673
|
+
await audit("protocol", `The mcp client ${client.id} ${agreed ? "negotiated its capability set with the server and the negotiated floor stays stored on its record" : `failed the capability negotiation: ${response.error?.message ?? "the sets did not agree"}`}.`, {});
|
|
18725
19674
|
}
|
|
18726
19675
|
return response;
|
|
18727
19676
|
}
|
|
18728
|
-
async function
|
|
19677
|
+
async function routetoolcall(client, frame, config, scopes, session, plan, catalog) {
|
|
19678
|
+
const now = Date.now();
|
|
19679
|
+
const params = frame.params ?? {};
|
|
19680
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
19681
|
+
const origin = session?.origin ?? "";
|
|
19682
|
+
const answerframe = (result2, error) => ({ jsonrpc: "2.0", ...frame.id !== void 0 ? { id: frame.id } : error !== void 0 ? { id: null } : {}, ...error !== void 0 ? { error: { code: error.code, message: error.message, ...error.data !== void 0 ? { data: error.data } : {} } } : { result: result2 } });
|
|
19683
|
+
const refuse = async (code, message, data) => {
|
|
19684
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: false, now: Date.now(), code }));
|
|
19685
|
+
await audit("tool", `The mcp client ${client.id} called the ${name} tool on ${origin || "no origin"} and was refused with the ${code} error; no payload rides the record.`, {});
|
|
19686
|
+
return answerframe(void 0, { code, message, ...data !== void 0 ? { data } : {} });
|
|
19687
|
+
};
|
|
19688
|
+
const counted = applyratelimit({ limits: await memory.getcallratelimits(), clientid: client.id, now });
|
|
19689
|
+
await memory.setcallratelimits(counted.limits);
|
|
19690
|
+
if (!counted.allowed) {
|
|
19691
|
+
const error = structurederrorof({ code: "ratelimited", message: `The client ${client.id} spent its user configured call budget of ${counted.budget ?? 0} inside the window.`, retryhint: "wait", ...counted.retryafter !== void 0 ? { retryafter: counted.retryafter } : {} });
|
|
19692
|
+
await audit("protocol", `The per client rate limit refused the ${name} call of the client ${client.id}: the budget of ${counted.budget ?? 0} calls is spent and the retry hint waits ${counted.retryafter ?? 0} milliseconds.`, {});
|
|
19693
|
+
return refuse("consentrefused", `${error.message} Retry: wait${error.retryafter !== void 0 ? ` after ${error.retryafter} milliseconds` : ""}.`, { retryhint: error.retryhint, ...error.retryafter !== void 0 ? { retryafter: error.retryafter } : {} });
|
|
19694
|
+
}
|
|
19695
|
+
const key = typeof params.idempotencykey === "string" && params.idempotencykey.trim() !== "" ? params.idempotencykey.trim() : void 0;
|
|
19696
|
+
if (key !== void 0) {
|
|
19697
|
+
const replay = checkidempotency({ records: await memory.getidempotencyrecords(), key, clientid: client.id, now });
|
|
19698
|
+
if (replay.replay !== void 0 && replay.record !== void 0) {
|
|
19699
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: !replay.replay.iserror, now: Date.now(), idempotencykey: key, replayed: true }));
|
|
19700
|
+
await audit("tool", `The mcp client ${client.id} repeated the idempotency key of the ${name} call and the server replayed the stored result instead of executing; no payload rides the record.`, {});
|
|
19701
|
+
return answerframe({ ...replay.replay, replayed: true, idempotencykey: key, originalat: replay.record.createdat });
|
|
19702
|
+
}
|
|
19703
|
+
}
|
|
19704
|
+
const tool = name !== "" ? resolvetool(catalog, name) : void 0;
|
|
19705
|
+
const mock = tool !== void 0 ? applymock({ mocks: await memory.gettoolmocks(), tool: tool.name }) : void 0;
|
|
19706
|
+
if (mock?.result !== void 0) {
|
|
19707
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: !mock.result.iserror, now: Date.now(), mocked: true }));
|
|
19708
|
+
await audit("tool", `A tool mock of a test context answered the ${name} call of the client ${client.id} with its canned result; the browser was never touched.`, {});
|
|
19709
|
+
return answerframe({ ...mock.result, mocked: true });
|
|
19710
|
+
}
|
|
19711
|
+
if (mock?.reason !== void 0) return await refuse("consentrefused", mock.reason);
|
|
19712
|
+
let dryrun = params.dryrun === true;
|
|
19713
|
+
if (!dryrun && await memory.getdryruntoggle()) {
|
|
19714
|
+
dryrun = true;
|
|
19715
|
+
await memory.setdryruntoggle(false);
|
|
19716
|
+
}
|
|
19717
|
+
if (dryrun && tool !== void 0) {
|
|
19718
|
+
const dry = dryruntool({ tool, params, client, ...session !== void 0 ? { session } : {}, ...plan !== void 0 ? { plan } : {}, origin, now });
|
|
19719
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: dry.argsvalid && dry.consentok, now: Date.now(), dryrun: true }));
|
|
19720
|
+
await audit("tool", `The mcp client ${client.id} dry ran the ${name} tool: ${dry.findings.length} finding${dry.findings.length === 1 ? "" : "s"} and no page mutation because a dry run never executes anything.`, {});
|
|
19721
|
+
return answerframe({ content: `The ${name} dry run found ${dry.findings.length} issue${dry.findings.length === 1 ? "" : "s"}.`, payload: { dryrun: dry }, iserror: false });
|
|
19722
|
+
}
|
|
19723
|
+
const context = begincall({ clientid: client.id, tool: name, ...key !== void 0 ? { idempotencykey: key } : {}, now });
|
|
19724
|
+
await memory.setcallcontexts([context, ...await memory.getcallcontexts()]);
|
|
19725
|
+
const started = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "callstarted", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: context.callid, clientid: client.id }, now });
|
|
19726
|
+
await memory.seteventsubscriptions(started.subscriptions);
|
|
19727
|
+
const notice = notifyprogress({ callid: context.callid, percent: 10, message: `The ${name} call started behind the consent gates.`, now });
|
|
19728
|
+
await memory.addprogressnotice(notice);
|
|
19729
|
+
const progressed = notifyevent({ subscriptions: started.subscriptions, kind: "progress", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: notice.callid, percent: notice.percent, message: notice.message, cancellable: notice.cancellable }, now });
|
|
19730
|
+
await memory.seteventsubscriptions(progressed.subscriptions);
|
|
19731
|
+
const gatedname = client.transport === "http" && tool !== void 0 && tool.risk !== "read" ? tool.name : void 0;
|
|
19732
|
+
const execute = gatedname !== void 0 ? (step) => raiseremoteapproval(client.id, gatedname, params, step) : async (step) => {
|
|
19733
|
+
if (mcpcancelledcalls.has(context.callid)) throw new Error(`The ${name} call was cancelled before the page executor ran.`);
|
|
19734
|
+
const result2 = await executemcpstep(step);
|
|
19735
|
+
if (mcpcancelledcalls.has(context.callid)) throw new Error(`The ${name} call was cancelled in flight; the partial result stays preserved.`);
|
|
19736
|
+
return result2;
|
|
19737
|
+
};
|
|
19738
|
+
const response = await handleframe({ frame, client, catalog, config, ...session !== void 0 ? { session } : {}, ...plan !== void 0 ? { plan } : {}, ...scopes !== void 0 ? { scopes } : {}, origin, tabid: session?.tabid ?? 0, now: Date.now(), contexts: await memory.getcallcontexts(), execute });
|
|
19739
|
+
const after = Date.now();
|
|
19740
|
+
const ok = response.error === void 0 && (response.result?.iserror ?? true) !== true;
|
|
19741
|
+
const result = response.error === void 0 ? response.result : void 0;
|
|
19742
|
+
const closed = endcall({ contexts: await memory.getcallcontexts(), callid: context.callid, ok, ...response.error !== void 0 ? { errorcode: response.error.code } : {}, ...result !== void 0 ? { partial: result } : {}, now: after });
|
|
19743
|
+
if (closed.contexts !== void 0) await memory.setcallcontexts(closed.contexts);
|
|
19744
|
+
if (result !== void 0 && typeof result.content === "string" && result.content.length > 0) {
|
|
19745
|
+
const chunks = chunkcontent({ callid: context.callid, content: result.content, now: after });
|
|
19746
|
+
for (const chunk of chunks) await memory.addstreamchunk(chunk);
|
|
19747
|
+
const streamed = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "streamchunk", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: context.callid, chunks: chunks.length, done: true }, now: after });
|
|
19748
|
+
await memory.seteventsubscriptions(streamed.subscriptions);
|
|
19749
|
+
}
|
|
19750
|
+
const finished = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "callresult", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: context.callid, ok }, now: after });
|
|
19751
|
+
await memory.seteventsubscriptions(finished.subscriptions);
|
|
19752
|
+
if (key !== void 0 && result !== void 0 && !result.iserror) await memory.setidempotencyrecords(recordidempotency({ records: await memory.getidempotencyrecords(), key, clientid: client.id, tool: name, result, now: after }));
|
|
19753
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok, now: after, ...response.error !== void 0 ? { code: response.error.code } : {}, callid: context.callid, ...key !== void 0 ? { idempotencykey: key } : {} }));
|
|
19754
|
+
if (plan !== void 0) {
|
|
19755
|
+
const stepid = typeof params.stepid === "string" ? params.stepid : "mcp";
|
|
19756
|
+
await memory.setprogress(recordtoolcall(await memory.getprogress(), plan.id, stepid, { clientid: client.id, tool: name, ok, ...response.error !== void 0 ? { code: response.error.code } : {} }, after));
|
|
19757
|
+
}
|
|
19758
|
+
await audit("tool", `The mcp client ${client.id} called the ${name} tool on ${origin || "no origin"} and ${ok ? gatedname !== void 0 ? "its approval gate waits for the user decision" : `it ran behind the consent gates as the call ${context.callid}` : `it was refused${response.error !== void 0 ? ` with the ${response.error.code} error` : ""}`}${key !== void 0 ? " under its idempotency key" : ""}; no payload rides the record.`, {});
|
|
19759
|
+
return response;
|
|
19760
|
+
}
|
|
19761
|
+
async function routeprotocolframe(client, frame, config, scopes) {
|
|
19762
|
+
void config;
|
|
19763
|
+
void scopes;
|
|
19764
|
+
const params = frame.params ?? {};
|
|
19765
|
+
const now = Date.now();
|
|
19766
|
+
const answerframe = (result, error) => ({ jsonrpc: "2.0", ...frame.id !== void 0 ? { id: frame.id } : error !== void 0 ? { id: null } : {}, ...error !== void 0 ? { error: { code: error.code, message: error.message } } : { result } });
|
|
19767
|
+
if (frame.method === "events/subscribe") {
|
|
19768
|
+
const kinds = Array.isArray(params.kinds) ? params.kinds.filter((kind) => typeof kind === "string") : void 0;
|
|
19769
|
+
const registered = subscriberegister({ clientid: client.id, ...kinds !== void 0 ? { kinds } : {}, ...typeof params.origin === "string" && params.origin.trim() !== "" ? { origin: params.origin.trim() } : {}, ...typeof params.tool === "string" && params.tool.trim() !== "" ? { tool: params.tool.trim() } : {}, now });
|
|
19770
|
+
if (registered.subscription === void 0) return answerframe(void 0, { code: "params", message: registered.reason ?? "The event subscription did not register." });
|
|
19771
|
+
const grade = subscriptiongrade(registered.subscription);
|
|
19772
|
+
if (!grade.allowed) return answerframe(void 0, { code: "consentrefused", message: grade.reason ?? "The event subscription failed its gate." });
|
|
19773
|
+
await memory.seteventsubscriptions([registered.subscription, ...await memory.geteventsubscriptions()]);
|
|
19774
|
+
await audit("protocol", `The client ${client.id} subscribed to the ${registered.subscription.kinds.join(", ")} event kinds${registered.subscription.origin !== void 0 ? ` of the origin ${registered.subscription.origin}` : ""}${registered.subscription.tool !== void 0 ? ` for the tool ${registered.subscription.tool}` : ""}; the subscription grades read only.`, {});
|
|
19775
|
+
return answerframe({ subscriptionid: registered.subscription.id, kinds: registered.subscription.kinds });
|
|
19776
|
+
}
|
|
19777
|
+
if (frame.method === "events/unsubscribe") {
|
|
19778
|
+
const subscriptionid = typeof params.subscriptionid === "string" ? params.subscriptionid : "";
|
|
19779
|
+
await memory.seteventsubscriptions(unsubscriberegister(await memory.geteventsubscriptions(), subscriptionid, now));
|
|
19780
|
+
await audit("protocol", `The client ${client.id} cancelled its event subscription ${subscriptionid}; the record stays for the audit trail.`, {});
|
|
19781
|
+
return answerframe({ unsubscribed: true, subscriptionid });
|
|
19782
|
+
}
|
|
19783
|
+
if (frame.method === "resources/watch") {
|
|
19784
|
+
const resource = typeof params.resource === "string" ? params.resource.trim() : "";
|
|
19785
|
+
const state = params.state !== void 0 && typeof params.state === "object" && !Array.isArray(params.state) ? params.state : void 0;
|
|
19786
|
+
const watched = watchresource({ clientid: client.id, resource, ...state !== void 0 ? { state } : {}, now });
|
|
19787
|
+
if (watched.watch === void 0) return answerframe(void 0, { code: "params", message: watched.reason ?? "The resource watcher did not start." });
|
|
19788
|
+
await memory.setresourcewatches([watched.watch, ...await memory.getresourcewatches()]);
|
|
19789
|
+
await audit("protocol", `The client ${client.id} started the ${watched.watch.resource} resource watcher with its page state baseline; the deltas compare against it.`, {});
|
|
19790
|
+
return answerframe({ watchid: watched.watch.id, resource: watched.watch.resource });
|
|
19791
|
+
}
|
|
19792
|
+
if (frame.method === "resources/unwatch") {
|
|
19793
|
+
const watchid = typeof params.watchid === "string" ? params.watchid : "";
|
|
19794
|
+
await memory.setresourcewatches(unwatchresource(await memory.getresourcewatches(), watchid, now));
|
|
19795
|
+
await audit("protocol", `The client ${client.id} cancelled its resource watcher ${watchid}; the record stays for the audit trail.`, {});
|
|
19796
|
+
return answerframe({ unwatched: true, watchid });
|
|
19797
|
+
}
|
|
19798
|
+
if (frame.method === "sampling/answer") {
|
|
19799
|
+
const samplingid = typeof params.samplingid === "string" ? params.samplingid : "";
|
|
19800
|
+
const clientanswer = typeof params.answer === "string" ? params.answer : void 0;
|
|
19801
|
+
const outcome = answersampling({ requests: await memory.getsamplingrequests(), id: samplingid, ...clientanswer !== void 0 ? { answer: clientanswer } : {}, ...params.refused === true ? { refused: true } : {}, now });
|
|
19802
|
+
if (outcome.request === void 0) return answerframe(void 0, { code: "params", message: outcome.reason ?? "The sampling answer closed no request." });
|
|
19803
|
+
await memory.setsamplingrequests(outcome.requests);
|
|
19804
|
+
await audit("protocol", `The sampling callback ${samplingid} of the client ${client.id} closed its round trip with the client answer; the provenance times ride the record.`, {});
|
|
19805
|
+
return answerframe({ samplingid, state: outcome.request.state });
|
|
19806
|
+
}
|
|
19807
|
+
if (frame.method === "calls/cancel") {
|
|
19808
|
+
const callid = typeof params.callid === "string" ? params.callid : "";
|
|
19809
|
+
const reason = typeof params.reason === "string" ? params.reason : void 0;
|
|
19810
|
+
if (callid.trim() === "") return answerframe(void 0, { code: "params", message: "The cancellation frame needs the call id it aborts." });
|
|
19811
|
+
const chunks = (await memory.getstreamchunks()).filter((chunk) => chunk.callid === callid);
|
|
19812
|
+
const partial = chunks.length > 0 ? { content: chunks.map((chunk) => chunk.content).join(""), iserror: false } : void 0;
|
|
19813
|
+
const aborted = canceltool({ contexts: await memory.getcallcontexts(), callid, ...reason !== void 0 ? { reason } : {}, ...partial !== void 0 ? { partial } : {}, now });
|
|
19814
|
+
if (aborted.context === void 0) return answerframe(void 0, { code: "params", message: aborted.reason ?? "The cancellation frame named no in flight tool call." });
|
|
19815
|
+
await memory.setcallcontexts(aborted.contexts);
|
|
19816
|
+
mcpcancelledcalls.add(callid);
|
|
19817
|
+
const cancelled = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "cancellation", payload: { callid, reason: reason ?? "cancelled by the client" }, now });
|
|
19818
|
+
await memory.seteventsubscriptions(cancelled.subscriptions);
|
|
19819
|
+
await audit("protocol", `The client ${client.id} cancelled the in flight ${aborted.context.tool} call ${callid}${partial !== void 0 ? " with its partial result preserved" : ""}; the cooperative flag stops the page executor between chunks and the workflow engine keeps its own pause machinery.`, {});
|
|
19820
|
+
return answerframe({ cancelled: true, callid, ...aborted.context.partial !== void 0 ? { partial: aborted.context.partial } : {} });
|
|
19821
|
+
}
|
|
19822
|
+
if (frame.method === "calls/batch") {
|
|
19823
|
+
const members = (Array.isArray(params.calls) ? params.calls : []).filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry)).map((entry, index) => ({ id: typeof entry.id === "string" && entry.id.trim() !== "" ? entry.id.trim() : `member-${index + 1}`, name: typeof entry.name === "string" ? entry.name : "", params: entry.params !== void 0 && typeof entry.params === "object" && !Array.isArray(entry.params) ? entry.params : {} }));
|
|
19824
|
+
const stoponerror = params.stoponerror === true;
|
|
19825
|
+
const session = await memory.getsession();
|
|
19826
|
+
const plan = await memory.getplan();
|
|
19827
|
+
const catalog = buildtoolcatalog();
|
|
19828
|
+
const risks = members.map((member) => {
|
|
19829
|
+
const tool = resolvetool(catalog, member.name);
|
|
19830
|
+
return { risk: tool?.risk ?? "read" };
|
|
19831
|
+
});
|
|
19832
|
+
const approved = plan?.state === "approved" && members.every((member) => {
|
|
19833
|
+
const tool = resolvetool(catalog, member.name);
|
|
19834
|
+
return tool === void 0 || tool.risk === "read" || typeof member.params.stepid === "string" && plan.steps.some((step) => step.id === member.params.stepid);
|
|
19835
|
+
});
|
|
19836
|
+
const gate = batchgrade({ calls: risks, approved });
|
|
19837
|
+
const batch = { id: randomid(), clientid: client.id, calls: members, stoponerror, state: gate.allowed ? "running" : "stopped", createdat: now, outcomes: [] };
|
|
19838
|
+
await memory.setbatchcall(batch);
|
|
19839
|
+
if (!gate.allowed) {
|
|
19840
|
+
await audit("protocol", `The batch call of the client ${client.id} was refused: ${gate.reason ?? "the batch failed its grade."}`, {});
|
|
19841
|
+
return answerframe(void 0, { code: "consentrefused", message: gate.reason ?? "The batch call failed its grade." });
|
|
19842
|
+
}
|
|
19843
|
+
const outcome = await runbatch({ calls: members, stoponerror, now, execute: async (member) => {
|
|
19844
|
+
const response = await routemcpframe(client, { jsonrpc: "2.0", ...frame.id !== void 0 ? { id: frame.id } : {}, method: "tools/call", params: { ...member.params, name: member.name } }, config, scopes);
|
|
19845
|
+
const ok = response.error === void 0 && (response.result?.iserror ?? true) !== true;
|
|
19846
|
+
return { ok, ...response.result !== void 0 ? { result: response.result } : {}, ...response.error !== void 0 ? { error: structurederrorof({ code: response.error.code, message: response.error.message, retryhint: retryhintof({ code: response.error.code, message: response.error.message }) }) } : {} };
|
|
19847
|
+
} });
|
|
19848
|
+
const storedbatch = { ...batch, state: outcome.stoppedat !== void 0 ? "stopped" : "done", outcomes: outcome.outcomes, finishedat: Date.now() };
|
|
19849
|
+
await memory.setbatchcall(storedbatch);
|
|
19850
|
+
const ran = outcome.outcomes.filter((entry) => entry.ok).length;
|
|
19851
|
+
await audit("protocol", `The client ${client.id} ran the batch ${batch.id} of ${members.length} ordered call${members.length === 1 ? "" : "s"}${outcome.stoppedat !== void 0 ? ` and stopped at the member ${outcome.stoppedat} on its first error as the flag requested` : ""}; ${ran} of ${members.length} members ran behind the gates.`, {});
|
|
19852
|
+
return answerframe({ batchid: batch.id, state: storedbatch.state, stoponerror, outcomes: storedbatch.outcomes });
|
|
19853
|
+
}
|
|
19854
|
+
return void 0;
|
|
19855
|
+
}
|
|
19856
|
+
async function processmcpframe(raw, clientid, transport, rawtoken, fingerprint) {
|
|
18729
19857
|
const state = await memory.getmcpstate();
|
|
18730
19858
|
if (state?.state !== "running") return { jsonrpc: "2.0", id: null, error: rpcerrorof("consentrefused", "The mcp server is not running and no frame is routed.") };
|
|
18731
19859
|
const config = await mcpconfigof();
|
|
@@ -18737,14 +19865,41 @@ async function processmcpframe(raw, clientid, transport) {
|
|
|
18737
19865
|
await audit("protocol", "The mcp server refused a wire frame that does not parse as json; the parse error answered the client.", {});
|
|
18738
19866
|
return { jsonrpc: "2.0", id: null, error: rpcerrorof("parse", "The wire frame does not parse as json.") };
|
|
18739
19867
|
}
|
|
18740
|
-
let
|
|
19868
|
+
let scopes;
|
|
19869
|
+
let routedclient = clientid;
|
|
19870
|
+
if (transport === "http") {
|
|
19871
|
+
const now = Date.now();
|
|
19872
|
+
const ceiling = enforcemaxclients({ clients: await memory.getclients(), ...config.remoteaccess?.maxclients !== void 0 ? { maxclients: config.remoteaccess.maxclients } : {} });
|
|
19873
|
+
if (!ceiling.allowed) {
|
|
19874
|
+
await audit("protocol", `The remote connection of the client ${clientid || "unknown"} was refused because the user configured client ceiling is reached.`, {});
|
|
19875
|
+
return { jsonrpc: "2.0", id: frame.id ?? null, error: rpcerrorof("consentrefused", ceiling.reason ?? "The remote connection was refused.") };
|
|
19876
|
+
}
|
|
19877
|
+
const toolname = frame.method === "tools/call" && typeof frame.params?.name === "string" ? frame.params.name : void 0;
|
|
19878
|
+
const pipeline = await httpframepipeline({ config, ...fingerprint !== void 0 ? { presented: { fingerprint } } : {}, tokens: await memory.getsessiontokens(), ...rawtoken !== void 0 ? { rawtoken } : {}, allowlist: await memory.getallowlist(), fingerprint: fingerprint ?? "", ...toolname !== void 0 ? { toolname } : {}, now });
|
|
19879
|
+
if (pipeline.error !== void 0) {
|
|
19880
|
+
await memory.addauthhandshake({ id: randomid(), clientid: clientid || "unknown", method: "token", outcome: "refused", at: now });
|
|
19881
|
+
await audit("protocol", `The remote frame of the client ${clientid || "unknown"} failed the ordered intake pipeline and was refused with the ${pipeline.error.code} error; no pairing state rides the record.`, {});
|
|
19882
|
+
return { jsonrpc: "2.0", id: frame.id ?? null, error: pipeline.error };
|
|
19883
|
+
}
|
|
19884
|
+
scopes = pipeline.token?.scopes;
|
|
19885
|
+
routedclient = pipeline.token?.clientid ?? clientid;
|
|
19886
|
+
const channels = await memory.getstreamchannels();
|
|
19887
|
+
const open = channels.filter((channel) => channel.clientid === routedclient && channel.closedat === void 0);
|
|
19888
|
+
await memory.setstreamchannels(open.length > 0 ? heartbeat({ channels, clientid: routedclient, now }) : [...channels, openstreamchannel({ clientid: routedclient, now })]);
|
|
19889
|
+
}
|
|
19890
|
+
let client = (await memory.getclients()).find((entry) => entry.id === routedclient && entry.disconnectedat === void 0);
|
|
18741
19891
|
if (client === void 0) {
|
|
19892
|
+
if (transport === "http") return { jsonrpc: "2.0", id: frame.id ?? null, error: rpcerrorof("consentrefused", "The remote frame names no paired client; pair the client through the pairing exchange first.") };
|
|
18742
19893
|
client = connectclient({ transport, now: Date.now(), ...clientid !== "" ? { id: clientid } : {} });
|
|
18743
19894
|
await memory.setclient(client);
|
|
18744
19895
|
await audit("protocol", `A new mcp client ${client.id} connected on the ${transport} transport and waits for the pairing approval; unpaired clients never dispatch tools.`, {});
|
|
18745
19896
|
}
|
|
18746
19897
|
const previous = mcpclientchains.get(client.id) ?? Promise.resolve();
|
|
18747
|
-
const task = previous.catch(() => void 0).then(async () =>
|
|
19898
|
+
const task = previous.catch(() => void 0).then(async () => {
|
|
19899
|
+
const protocol = await routeprotocolframe(client, frame, config, scopes);
|
|
19900
|
+
if (protocol !== void 0) return protocol;
|
|
19901
|
+
return await routemcpframe(client, frame, config, scopes);
|
|
19902
|
+
});
|
|
18748
19903
|
mcpclientchains.set(client.id, task);
|
|
18749
19904
|
return task;
|
|
18750
19905
|
}
|