@wenathlan/extension 1.1.55 → 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 +3 -3
- package/dist/agentstream.d.ts +155 -0
- package/dist/agentstream.d.ts.map +1 -0
- package/dist/index.js +247 -3
- package/dist/index.js.map +4 -4
- package/dist/mcpserver.d.ts +11 -4
- package/dist/mcpserver.d.ts.map +1 -1
- package/dist/memory.d.ts +57 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +26 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +202 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +198 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +683 -20
- 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 +3 -1
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.js +234 -5
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -2510,6 +2510,111 @@ var sessionmemory = class {
|
|
|
2510
2510
|
async setstreamchannels(channels) {
|
|
2511
2511
|
return this.adapter.set("mcpchannels", channels);
|
|
2512
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
|
+
}
|
|
2513
2618
|
};
|
|
2514
2619
|
function mediakindof(record2) {
|
|
2515
2620
|
if ("pages" in record2) return "pdf";
|
|
@@ -8417,6 +8522,30 @@ function approvaltimeoutvalid(timeout) {
|
|
|
8417
8522
|
function revocationgate() {
|
|
8418
8523
|
return { allowed: true };
|
|
8419
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
|
+
}
|
|
8420
8549
|
|
|
8421
8550
|
// progress.ts
|
|
8422
8551
|
function emptyprogress(planid, now) {
|
|
@@ -8596,7 +8725,7 @@ function recordtoolcall(progress, planid, stepid, entry, now) {
|
|
|
8596
8725
|
}
|
|
8597
8726
|
|
|
8598
8727
|
// version.ts
|
|
8599
|
-
var packageversion = "1.1.
|
|
8728
|
+
var packageversion = "1.1.56";
|
|
8600
8729
|
|
|
8601
8730
|
// types.ts
|
|
8602
8731
|
var protocolversion = packageversion;
|
|
@@ -8665,6 +8794,128 @@ function tlsstateof(tls) {
|
|
|
8665
8794
|
return { mode: tls.mode, certificaterequired: tls.mode === "required" || tls.certificatefingerprint !== void 0, verified: tls.verifiedat !== void 0 };
|
|
8666
8795
|
}
|
|
8667
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
|
+
|
|
8668
8919
|
// mcpserver.ts
|
|
8669
8920
|
var localhostbind = "127.0.0.1";
|
|
8670
8921
|
var defaultmcpport = 7436;
|
|
@@ -8707,7 +8958,10 @@ function servermethods() {
|
|
|
8707
8958
|
{ method: "ping", handler: "ping", description: "Answers keepalive frames with pong." },
|
|
8708
8959
|
{ method: "tools/list", handler: "listtools", description: "Returns every tool with its version and json schema inputs." },
|
|
8709
8960
|
{ method: "negotiate", handler: "negotiate", description: "Exchanges capability sets with the client." },
|
|
8710
|
-
{ method: "tools/call", handler: "dispatch", description: "Invokes one tool behind the consent gates." }
|
|
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." }
|
|
8711
8965
|
];
|
|
8712
8966
|
}
|
|
8713
8967
|
function servercapabilities(input) {
|
|
@@ -8792,6 +9046,22 @@ async function handleframe(input) {
|
|
|
8792
9046
|
const outcome = negotiate({ ...clientcaps !== void 0 ? { client: clientcaps } : {}, server });
|
|
8793
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.") } });
|
|
8794
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
|
+
}
|
|
8795
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 });
|
|
8796
9066
|
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, ...dispatched.error !== void 0 ? { error: dispatched.error } : { result: dispatched.result } });
|
|
8797
9067
|
}
|
|
@@ -8806,7 +9076,7 @@ function relayframe(input) {
|
|
|
8806
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 };
|
|
8807
9077
|
}
|
|
8808
9078
|
function toolcallevent(input) {
|
|
8809
|
-
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 };
|
|
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 } : {} };
|
|
8810
9080
|
}
|
|
8811
9081
|
|
|
8812
9082
|
// httpstream.ts
|
|
@@ -10477,6 +10747,90 @@ function yamlscalarvalue(text2) {
|
|
|
10477
10747
|
return text2;
|
|
10478
10748
|
}
|
|
10479
10749
|
|
|
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 };
|
|
10832
|
+
}
|
|
10833
|
+
|
|
10480
10834
|
// extension/pagesession.ts
|
|
10481
10835
|
function capturepagestate(sections) {
|
|
10482
10836
|
const wants = (section) => sections.includes(section);
|
|
@@ -18690,6 +19044,122 @@ async function handlerequest(message, sender) {
|
|
|
18690
19044
|
}
|
|
18691
19045
|
return mcpstateof();
|
|
18692
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();
|
|
19162
|
+
}
|
|
18693
19163
|
case "runtobreakpoint": {
|
|
18694
19164
|
const inputdebug = message;
|
|
18695
19165
|
const session = await memory.getsession();
|
|
@@ -19067,7 +19537,23 @@ async function mcpstateof() {
|
|
|
19067
19537
|
const channels = closeidlechannels({ channels: await memory.getstreamchannels(), now, ...config.httpstream?.idlewindowms !== void 0 ? { idlewindow: config.httpstream.idlewindowms } : {} });
|
|
19068
19538
|
await memory.setstreamchannels(channels);
|
|
19069
19539
|
const streamstatus = listremotestatus;
|
|
19070
|
-
|
|
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;
|
|
19071
19557
|
}
|
|
19072
19558
|
async function mcpmaintenance(now) {
|
|
19073
19559
|
const tokens = await memory.getsessiontokens();
|
|
@@ -19076,6 +19562,12 @@ async function mcpmaintenance(now) {
|
|
|
19076
19562
|
await memory.setsessiontokens(tokens.map((token) => expired.includes(token) ? { ...token, revokedat: now } : token));
|
|
19077
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.`, {});
|
|
19078
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
|
+
}
|
|
19079
19571
|
const stored = await memory.listapprovals();
|
|
19080
19572
|
const approvals = expireapprovals(stored, now);
|
|
19081
19573
|
for (let index = 0; index < approvals.length; index += 1) {
|
|
@@ -19148,14 +19640,13 @@ async function trybridgelaunch(restart) {
|
|
|
19148
19640
|
function restartbridgeof(bridge) {
|
|
19149
19641
|
return { ...bridge, connected: true, restarts: bridge.restarts + 1, startedat: Date.now() };
|
|
19150
19642
|
}
|
|
19643
|
+
var mcpcancelledcalls = /* @__PURE__ */ new Set();
|
|
19151
19644
|
async function routemcpframe(client, frame, config, scopes) {
|
|
19152
19645
|
const session = await memory.getsession();
|
|
19153
19646
|
const plan = await memory.getplan();
|
|
19154
19647
|
const catalog = buildtoolcatalog();
|
|
19155
|
-
|
|
19156
|
-
const
|
|
19157
|
-
const callparams = frame.params ?? {};
|
|
19158
|
-
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: gatedname !== void 0 ? (step) => raiseremoteapproval(client.id, gatedname, callparams, step) : executemcpstep });
|
|
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 });
|
|
19159
19650
|
const now = Date.now();
|
|
19160
19651
|
if (frame.method === "initialize") {
|
|
19161
19652
|
const clientinfo = frame.params?.clientinfo && typeof frame.params.clientinfo === "object" && !Array.isArray(frame.params.clientinfo) ? frame.params.clientinfo : void 0;
|
|
@@ -19181,19 +19672,187 @@ async function routemcpframe(client, frame, config, scopes) {
|
|
|
19181
19672
|
}
|
|
19182
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"}`}.`, {});
|
|
19183
19674
|
}
|
|
19184
|
-
if (frame.method === "tools/call") {
|
|
19185
|
-
const name = typeof frame.params?.name === "string" ? frame.params.name : "";
|
|
19186
|
-
const code = response.error?.code;
|
|
19187
|
-
const ok = response.error === void 0 && response.result?.iserror !== true;
|
|
19188
|
-
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin: session?.origin ?? "", ok, now, ...code !== void 0 ? { code } : {} }));
|
|
19189
|
-
if (plan !== void 0) {
|
|
19190
|
-
const stepid = typeof frame.params?.stepid === "string" ? frame.params.stepid : "mcp";
|
|
19191
|
-
await memory.setprogress(recordtoolcall(await memory.getprogress(), plan.id, stepid, { clientid: client.id, tool: name, ok, ...code !== void 0 ? { code } : {} }, now));
|
|
19192
|
-
}
|
|
19193
|
-
await audit("tool", `The mcp client ${client.id} called the ${name} tool on ${session?.origin ?? "no origin"} and ${ok ? gatedname !== void 0 ? "its approval gate waits for the user decision" : "it ran behind the consent gates" : `it was refused${code !== void 0 ? ` with the ${code} error` : ""}`}; no payload rides the record.`, {});
|
|
19194
|
-
}
|
|
19195
19675
|
return response;
|
|
19196
19676
|
}
|
|
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
|
+
}
|
|
19197
19856
|
async function processmcpframe(raw, clientid, transport, rawtoken, fingerprint) {
|
|
19198
19857
|
const state = await memory.getmcpstate();
|
|
19199
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.") };
|
|
@@ -19236,7 +19895,11 @@ async function processmcpframe(raw, clientid, transport, rawtoken, fingerprint)
|
|
|
19236
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.`, {});
|
|
19237
19896
|
}
|
|
19238
19897
|
const previous = mcpclientchains.get(client.id) ?? Promise.resolve();
|
|
19239
|
-
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
|
+
});
|
|
19240
19903
|
mcpclientchains.set(client.id, task);
|
|
19241
19904
|
return task;
|
|
19242
19905
|
}
|