ofw-mcp 2.16.2 → 2.16.3
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +1000 -300
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -9444,7 +9444,7 @@ var require_websocket = __commonJS({
|
|
|
9444
9444
|
var http = __require("http");
|
|
9445
9445
|
var net = __require("net");
|
|
9446
9446
|
var tls = __require("tls");
|
|
9447
|
-
var { randomBytes:
|
|
9447
|
+
var { randomBytes: randomBytes3, createHash: createHash2 } = __require("crypto");
|
|
9448
9448
|
var { Duplex, Readable } = __require("stream");
|
|
9449
9449
|
var { URL: URL2 } = __require("url");
|
|
9450
9450
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -9982,7 +9982,7 @@ var require_websocket = __commonJS({
|
|
|
9982
9982
|
}
|
|
9983
9983
|
}
|
|
9984
9984
|
const defaultPort = isSecure ? 443 : 80;
|
|
9985
|
-
const key =
|
|
9985
|
+
const key = randomBytes3(16).toString("base64");
|
|
9986
9986
|
const request = isSecure ? https.request : http.request;
|
|
9987
9987
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
9988
9988
|
let perMessageDeflate;
|
|
@@ -17513,12 +17513,12 @@ var $ZodE164 = /* @__PURE__ */ $constructor("$ZodE164", (inst, def) => {
|
|
|
17513
17513
|
$ZodStringFormat.init(inst, def);
|
|
17514
17514
|
});
|
|
17515
17515
|
var CC_SANITIZE = /[- ]/g;
|
|
17516
|
-
function isLuhnAlgo(
|
|
17517
|
-
let length =
|
|
17516
|
+
function isLuhnAlgo(digits2) {
|
|
17517
|
+
let length = digits2.length;
|
|
17518
17518
|
let bit = 1;
|
|
17519
17519
|
let sum = 0;
|
|
17520
17520
|
while (length) {
|
|
17521
|
-
const value =
|
|
17521
|
+
const value = digits2.charCodeAt(--length) - 48;
|
|
17522
17522
|
bit ^= 1;
|
|
17523
17523
|
sum += bit ? [0, 2, 4, 6, 8, 1, 3, 5, 7, 9][value] : value;
|
|
17524
17524
|
}
|
|
@@ -32880,8 +32880,8 @@ function hex2(_params) {
|
|
|
32880
32880
|
return _stringFormat(ZodCustomStringFormat, "hex", regexes_exports.hex, _params);
|
|
32881
32881
|
}
|
|
32882
32882
|
function hash(alg, params) {
|
|
32883
|
-
const
|
|
32884
|
-
const format = `${alg}_${
|
|
32883
|
+
const enc6 = params?.enc ?? "hex";
|
|
32884
|
+
const format = `${alg}_${enc6}`;
|
|
32885
32885
|
const regex = regexes_exports[format];
|
|
32886
32886
|
if (!regex)
|
|
32887
32887
|
throw new Error(`Unrecognized hash format: ${format}`);
|
|
@@ -40837,16 +40837,193 @@ var TokenManager = class {
|
|
|
40837
40837
|
import { dirname as dirname2, join as join5 } from "path";
|
|
40838
40838
|
import { fileURLToPath } from "url";
|
|
40839
40839
|
|
|
40840
|
+
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
40841
|
+
var subtle = globalThis.crypto.subtle;
|
|
40842
|
+
async function exportRaw(key, format) {
|
|
40843
|
+
const buf = await subtle.exportKey(format, key);
|
|
40844
|
+
return new Uint8Array(buf);
|
|
40845
|
+
}
|
|
40846
|
+
async function generateX25519() {
|
|
40847
|
+
const kp = await subtle.generateKey({ name: "X25519" }, true, [
|
|
40848
|
+
"deriveBits"
|
|
40849
|
+
]);
|
|
40850
|
+
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
40851
|
+
const pub = await exportRaw(kp.publicKey, "raw");
|
|
40852
|
+
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
40853
|
+
}
|
|
40854
|
+
async function generateEd25519() {
|
|
40855
|
+
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
40856
|
+
"sign",
|
|
40857
|
+
"verify"
|
|
40858
|
+
]);
|
|
40859
|
+
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
40860
|
+
const pub = await exportRaw(kp.publicKey, "raw");
|
|
40861
|
+
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
40862
|
+
}
|
|
40863
|
+
async function importX25519Priv(raw) {
|
|
40864
|
+
const prefix = new Uint8Array([
|
|
40865
|
+
48,
|
|
40866
|
+
46,
|
|
40867
|
+
2,
|
|
40868
|
+
1,
|
|
40869
|
+
0,
|
|
40870
|
+
48,
|
|
40871
|
+
5,
|
|
40872
|
+
6,
|
|
40873
|
+
3,
|
|
40874
|
+
43,
|
|
40875
|
+
101,
|
|
40876
|
+
110,
|
|
40877
|
+
4,
|
|
40878
|
+
34,
|
|
40879
|
+
4,
|
|
40880
|
+
32
|
|
40881
|
+
]);
|
|
40882
|
+
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
40883
|
+
pkcs8.set(prefix, 0);
|
|
40884
|
+
pkcs8.set(raw, prefix.length);
|
|
40885
|
+
return subtle.importKey("pkcs8", pkcs8, { name: "X25519" }, false, [
|
|
40886
|
+
"deriveBits"
|
|
40887
|
+
]);
|
|
40888
|
+
}
|
|
40889
|
+
async function importX25519Pub(raw) {
|
|
40890
|
+
return subtle.importKey("raw", raw, { name: "X25519" }, false, []);
|
|
40891
|
+
}
|
|
40892
|
+
async function importEd25519Priv(raw) {
|
|
40893
|
+
const prefix = new Uint8Array([
|
|
40894
|
+
48,
|
|
40895
|
+
46,
|
|
40896
|
+
2,
|
|
40897
|
+
1,
|
|
40898
|
+
0,
|
|
40899
|
+
48,
|
|
40900
|
+
5,
|
|
40901
|
+
6,
|
|
40902
|
+
3,
|
|
40903
|
+
43,
|
|
40904
|
+
101,
|
|
40905
|
+
112,
|
|
40906
|
+
4,
|
|
40907
|
+
34,
|
|
40908
|
+
4,
|
|
40909
|
+
32
|
|
40910
|
+
]);
|
|
40911
|
+
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
40912
|
+
pkcs8.set(prefix, 0);
|
|
40913
|
+
pkcs8.set(raw, prefix.length);
|
|
40914
|
+
return subtle.importKey("pkcs8", pkcs8, { name: "Ed25519" }, false, ["sign"]);
|
|
40915
|
+
}
|
|
40916
|
+
async function importEd25519Pub(raw) {
|
|
40917
|
+
return subtle.importKey("raw", raw, { name: "Ed25519" }, false, ["verify"]);
|
|
40918
|
+
}
|
|
40919
|
+
async function ecdhX25519(privRaw, pubRaw) {
|
|
40920
|
+
const priv = await importX25519Priv(privRaw);
|
|
40921
|
+
const pub = await importX25519Pub(pubRaw);
|
|
40922
|
+
const bits = await subtle.deriveBits({ name: "X25519", public: pub }, priv, 256);
|
|
40923
|
+
return new Uint8Array(bits);
|
|
40924
|
+
}
|
|
40925
|
+
async function ed25519Sign(privRaw, msg) {
|
|
40926
|
+
const priv = await importEd25519Priv(privRaw);
|
|
40927
|
+
const sig = await subtle.sign({ name: "Ed25519" }, priv, msg);
|
|
40928
|
+
return new Uint8Array(sig);
|
|
40929
|
+
}
|
|
40930
|
+
async function ed25519Verify(pubRaw, msg, sig) {
|
|
40931
|
+
const pub = await importEd25519Pub(pubRaw);
|
|
40932
|
+
return subtle.verify({ name: "Ed25519" }, pub, sig, msg);
|
|
40933
|
+
}
|
|
40934
|
+
async function hkdfSha256(ikm, salt, info, lenBytes) {
|
|
40935
|
+
const baseKey = await subtle.importKey("raw", ikm, { name: "HKDF" }, false, [
|
|
40936
|
+
"deriveBits"
|
|
40937
|
+
]);
|
|
40938
|
+
const bits = await subtle.deriveBits({
|
|
40939
|
+
name: "HKDF",
|
|
40940
|
+
hash: "SHA-256",
|
|
40941
|
+
salt,
|
|
40942
|
+
info
|
|
40943
|
+
}, baseKey, lenBytes * 8);
|
|
40944
|
+
return new Uint8Array(bits);
|
|
40945
|
+
}
|
|
40946
|
+
async function aesGcmSeal(key, iv, plaintext, aad) {
|
|
40947
|
+
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
40948
|
+
"encrypt"
|
|
40949
|
+
]);
|
|
40950
|
+
const ct = await subtle.encrypt({ name: "AES-GCM", iv, additionalData: aad }, k, plaintext);
|
|
40951
|
+
return new Uint8Array(ct);
|
|
40952
|
+
}
|
|
40953
|
+
async function aesGcmOpen(key, iv, ciphertext, aad) {
|
|
40954
|
+
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
40955
|
+
"decrypt"
|
|
40956
|
+
]);
|
|
40957
|
+
const pt = await subtle.decrypt({ name: "AES-GCM", iv, additionalData: aad }, k, ciphertext);
|
|
40958
|
+
return new Uint8Array(pt);
|
|
40959
|
+
}
|
|
40960
|
+
async function sha256(data) {
|
|
40961
|
+
const h = await subtle.digest("SHA-256", data);
|
|
40962
|
+
return new Uint8Array(h);
|
|
40963
|
+
}
|
|
40964
|
+
|
|
40965
|
+
// node_modules/@fetchproxy/protocol/dist/encoding.js
|
|
40966
|
+
function toB64(bytes) {
|
|
40967
|
+
let s = "";
|
|
40968
|
+
for (let i = 0; i < bytes.length; i++)
|
|
40969
|
+
s += String.fromCharCode(bytes[i]);
|
|
40970
|
+
return btoa(s);
|
|
40971
|
+
}
|
|
40972
|
+
function fromB64(s) {
|
|
40973
|
+
const bin = atob(s);
|
|
40974
|
+
const out = new Uint8Array(bin.length);
|
|
40975
|
+
for (let i = 0; i < bin.length; i++)
|
|
40976
|
+
out[i] = bin.charCodeAt(i);
|
|
40977
|
+
return out;
|
|
40978
|
+
}
|
|
40979
|
+
|
|
40840
40980
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
40841
|
-
var PROTOCOL_VERSION =
|
|
40842
|
-
var HKDF_SESSION_INFO = "fetchproxy/
|
|
40843
|
-
|
|
40844
|
-
|
|
40981
|
+
var PROTOCOL_VERSION = 4;
|
|
40982
|
+
var HKDF_SESSION_INFO = "fetchproxy/4.0.0/session";
|
|
40983
|
+
var enc = new TextEncoder();
|
|
40984
|
+
function helloSignaturePayload(mcpId, sessionNonce, sessionPub, answersExtNonce) {
|
|
40985
|
+
const id = enc.encode(mcpId);
|
|
40986
|
+
const out = new Uint8Array(id.length + sessionNonce.length + sessionPub.length + answersExtNonce.length);
|
|
40987
|
+
out.set(id, 0);
|
|
40988
|
+
out.set(sessionNonce, id.length);
|
|
40989
|
+
out.set(sessionPub, id.length + sessionNonce.length);
|
|
40990
|
+
out.set(answersExtNonce, id.length + sessionNonce.length + sessionPub.length);
|
|
40991
|
+
return out;
|
|
40992
|
+
}
|
|
40993
|
+
var ANSWERS_NO_EXT_SESSION = new Uint8Array(32);
|
|
40994
|
+
function answersNoExtSession(answersExtNonce) {
|
|
40995
|
+
let bytes;
|
|
40996
|
+
try {
|
|
40997
|
+
bytes = fromB64(answersExtNonce);
|
|
40998
|
+
} catch {
|
|
40999
|
+
return false;
|
|
41000
|
+
}
|
|
41001
|
+
if (bytes.length !== ANSWERS_NO_EXT_SESSION.length)
|
|
41002
|
+
return false;
|
|
41003
|
+
for (const b of bytes)
|
|
41004
|
+
if (b !== 0)
|
|
41005
|
+
return false;
|
|
41006
|
+
return true;
|
|
41007
|
+
}
|
|
41008
|
+
function readySignaturePayload(mcpHelloNonce, extHelloNonce, extensionSessionPub, mcpSessionPub) {
|
|
41009
|
+
const out = new Uint8Array(mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length + mcpSessionPub.length);
|
|
40845
41010
|
out.set(mcpHelloNonce, 0);
|
|
40846
41011
|
out.set(extHelloNonce, mcpHelloNonce.length);
|
|
40847
41012
|
out.set(extensionSessionPub, mcpHelloNonce.length + extHelloNonce.length);
|
|
41013
|
+
out.set(mcpSessionPub, mcpHelloNonce.length + extHelloNonce.length + extensionSessionPub.length);
|
|
40848
41014
|
return out;
|
|
40849
41015
|
}
|
|
41016
|
+
async function transcriptHash(mcpNonce, extNonce, mcpSessionPub, extSessionPub) {
|
|
41017
|
+
const out = new Uint8Array(mcpNonce.length + extNonce.length + mcpSessionPub.length + extSessionPub.length);
|
|
41018
|
+
out.set(mcpNonce, 0);
|
|
41019
|
+
out.set(extNonce, mcpNonce.length);
|
|
41020
|
+
out.set(mcpSessionPub, mcpNonce.length + extNonce.length);
|
|
41021
|
+
out.set(extSessionPub, mcpNonce.length + extNonce.length + mcpSessionPub.length);
|
|
41022
|
+
return sha256(out);
|
|
41023
|
+
}
|
|
41024
|
+
function frameAad(mcpId, seq, direction) {
|
|
41025
|
+
return enc.encode(`fetchproxy/4/frame\0${mcpId}\0${seq}\0${direction}`);
|
|
41026
|
+
}
|
|
40850
41027
|
var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
40851
41028
|
"fetch",
|
|
40852
41029
|
"fetch_in_page",
|
|
@@ -40917,6 +41094,138 @@ function isValidJsonPointer(pointer) {
|
|
|
40917
41094
|
return true;
|
|
40918
41095
|
}
|
|
40919
41096
|
|
|
41097
|
+
// node_modules/@fetchproxy/protocol/dist/public-suffix.js
|
|
41098
|
+
var CCTLD_ADMIN_LABELS = /* @__PURE__ */ new Set([
|
|
41099
|
+
"ac",
|
|
41100
|
+
"co",
|
|
41101
|
+
"com",
|
|
41102
|
+
"edu",
|
|
41103
|
+
"gob",
|
|
41104
|
+
"gov",
|
|
41105
|
+
"mil",
|
|
41106
|
+
"net",
|
|
41107
|
+
"nom",
|
|
41108
|
+
"org",
|
|
41109
|
+
"sch"
|
|
41110
|
+
]);
|
|
41111
|
+
var LISTED_PUBLIC_SUFFIXES = /* @__PURE__ */ new Set([
|
|
41112
|
+
// --- ICANN, under ccTLDs, outside CCTLD_ADMIN_LABELS ---
|
|
41113
|
+
"ad.jp",
|
|
41114
|
+
"ed.jp",
|
|
41115
|
+
"gr.jp",
|
|
41116
|
+
"lg.jp",
|
|
41117
|
+
"ne.jp",
|
|
41118
|
+
"or.jp",
|
|
41119
|
+
"go.kr",
|
|
41120
|
+
"ne.kr",
|
|
41121
|
+
"or.kr",
|
|
41122
|
+
"pe.kr",
|
|
41123
|
+
"re.kr",
|
|
41124
|
+
"go.id",
|
|
41125
|
+
"my.id",
|
|
41126
|
+
"or.id",
|
|
41127
|
+
"web.id",
|
|
41128
|
+
"go.th",
|
|
41129
|
+
"in.th",
|
|
41130
|
+
"or.th",
|
|
41131
|
+
"me.uk",
|
|
41132
|
+
"ltd.uk",
|
|
41133
|
+
"plc.uk",
|
|
41134
|
+
"nhs.uk",
|
|
41135
|
+
"mod.uk",
|
|
41136
|
+
"police.uk",
|
|
41137
|
+
"parliament.uk",
|
|
41138
|
+
"asn.au",
|
|
41139
|
+
"id.au",
|
|
41140
|
+
"gen.in",
|
|
41141
|
+
"ind.in",
|
|
41142
|
+
"firm.in",
|
|
41143
|
+
"eu.org",
|
|
41144
|
+
// --- Vendor ("private") suffixes: one entry = every customer's site ---
|
|
41145
|
+
"github.io",
|
|
41146
|
+
"githubusercontent.com",
|
|
41147
|
+
"gitlab.io",
|
|
41148
|
+
"bitbucket.io",
|
|
41149
|
+
"pages.dev",
|
|
41150
|
+
"workers.dev",
|
|
41151
|
+
"r2.dev",
|
|
41152
|
+
"trycloudflare.com",
|
|
41153
|
+
"vercel.app",
|
|
41154
|
+
"now.sh",
|
|
41155
|
+
"netlify.app",
|
|
41156
|
+
"netlify.live",
|
|
41157
|
+
"herokuapp.com",
|
|
41158
|
+
"herokussl.com",
|
|
41159
|
+
"appspot.com",
|
|
41160
|
+
"web.app",
|
|
41161
|
+
"firebaseapp.com",
|
|
41162
|
+
"cloudfunctions.net",
|
|
41163
|
+
"blogspot.com",
|
|
41164
|
+
"azurewebsites.net",
|
|
41165
|
+
"azurestaticapps.net",
|
|
41166
|
+
"azureedge.net",
|
|
41167
|
+
"azurecontainer.io",
|
|
41168
|
+
"cloudapp.azure.com",
|
|
41169
|
+
"trafficmanager.net",
|
|
41170
|
+
"s3.amazonaws.com",
|
|
41171
|
+
"compute.amazonaws.com",
|
|
41172
|
+
"compute-1.amazonaws.com",
|
|
41173
|
+
"elasticbeanstalk.com",
|
|
41174
|
+
"cloudfront.net",
|
|
41175
|
+
"awsapprunner.com",
|
|
41176
|
+
"amplifyapp.com",
|
|
41177
|
+
"fly.dev",
|
|
41178
|
+
"onrender.com",
|
|
41179
|
+
"railway.app",
|
|
41180
|
+
"koyeb.app",
|
|
41181
|
+
"deno.dev",
|
|
41182
|
+
"replit.dev",
|
|
41183
|
+
"repl.co",
|
|
41184
|
+
"glitch.me",
|
|
41185
|
+
"surge.sh",
|
|
41186
|
+
"neocities.org",
|
|
41187
|
+
"pythonanywhere.com",
|
|
41188
|
+
"readthedocs.io",
|
|
41189
|
+
"gitbook.io",
|
|
41190
|
+
"myshopify.com",
|
|
41191
|
+
"wixsite.com",
|
|
41192
|
+
"editorx.io",
|
|
41193
|
+
"webflow.io",
|
|
41194
|
+
"bubbleapps.io",
|
|
41195
|
+
"notion.site",
|
|
41196
|
+
"framer.app",
|
|
41197
|
+
"framer.website",
|
|
41198
|
+
"ngrok.io",
|
|
41199
|
+
"ngrok.app",
|
|
41200
|
+
"ngrok-free.app",
|
|
41201
|
+
"loca.lt",
|
|
41202
|
+
"duckdns.org",
|
|
41203
|
+
"ddns.net",
|
|
41204
|
+
"no-ip.org",
|
|
41205
|
+
"dyndns.org",
|
|
41206
|
+
"hopto.org",
|
|
41207
|
+
"zapto.org"
|
|
41208
|
+
]);
|
|
41209
|
+
function isPublicSuffix(host) {
|
|
41210
|
+
const normalised = host.trim().toLowerCase().replace(/\.$/, "");
|
|
41211
|
+
if (normalised === "")
|
|
41212
|
+
return false;
|
|
41213
|
+
const labels = normalised.split(".");
|
|
41214
|
+
if (labels.some((l) => l === ""))
|
|
41215
|
+
return false;
|
|
41216
|
+
if (labels.length === 1)
|
|
41217
|
+
return true;
|
|
41218
|
+
if (LISTED_PUBLIC_SUFFIXES.has(normalised))
|
|
41219
|
+
return true;
|
|
41220
|
+
if (labels.length === 2) {
|
|
41221
|
+
const [secondLevel, tld] = labels;
|
|
41222
|
+
if (secondLevel && tld && /^[a-z]{2}$/.test(tld) && CCTLD_ADMIN_LABELS.has(secondLevel)) {
|
|
41223
|
+
return true;
|
|
41224
|
+
}
|
|
41225
|
+
}
|
|
41226
|
+
return false;
|
|
41227
|
+
}
|
|
41228
|
+
|
|
40920
41229
|
// node_modules/@fetchproxy/protocol/dist/validate.js
|
|
40921
41230
|
var ProtocolError = class extends Error {
|
|
40922
41231
|
constructor(message) {
|
|
@@ -40953,6 +41262,18 @@ function assertBase64(x, label) {
|
|
|
40953
41262
|
if (!BASE64_RE.test(x))
|
|
40954
41263
|
throw new ProtocolError(`${label}: invalid base64`);
|
|
40955
41264
|
}
|
|
41265
|
+
function base64ByteLength(s) {
|
|
41266
|
+
if (s.length % 4 !== 0)
|
|
41267
|
+
return null;
|
|
41268
|
+
const pad2 = s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0;
|
|
41269
|
+
return s.length / 4 * 3 - pad2;
|
|
41270
|
+
}
|
|
41271
|
+
function assertBase64Bytes(x, label, bytes) {
|
|
41272
|
+
assertBase64(x, label);
|
|
41273
|
+
if (base64ByteLength(x) !== bytes) {
|
|
41274
|
+
throw new ProtocolError(`${label}: expected base64 of ${bytes} raw bytes`);
|
|
41275
|
+
}
|
|
41276
|
+
}
|
|
40956
41277
|
function assertBoolean(x, label) {
|
|
40957
41278
|
if (typeof x !== "boolean") {
|
|
40958
41279
|
throw new ProtocolError(`${label}: must be boolean`);
|
|
@@ -41174,7 +41495,7 @@ function assertIndexedDbScopesArray(value, label) {
|
|
|
41174
41495
|
}
|
|
41175
41496
|
}
|
|
41176
41497
|
}
|
|
41177
|
-
var DOM_SELECTOR_RE = /^[
|
|
41498
|
+
var DOM_SELECTOR_RE = /^[^\x00-\x1f\x7f]{1,512}$/;
|
|
41178
41499
|
var DOM_ATTRIBUTE_RE = /^[A-Za-z_:][A-Za-z0-9_:.\-]{0,127}$/;
|
|
41179
41500
|
var GRAPHQL_OP_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]{0,127}$/;
|
|
41180
41501
|
function assertDomSelectorsArray(value, label) {
|
|
@@ -41285,6 +41606,9 @@ function validateHello(raw) {
|
|
|
41285
41606
|
if (!HOSTNAME_RE.test(d)) {
|
|
41286
41607
|
throw new ProtocolError(`hello.domains: invalid hostname ${JSON.stringify(d)}`);
|
|
41287
41608
|
}
|
|
41609
|
+
if (isPublicSuffix(d)) {
|
|
41610
|
+
throw new ProtocolError(`hello.domains: ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
41611
|
+
}
|
|
41288
41612
|
}
|
|
41289
41613
|
if (raw.capabilities !== void 0) {
|
|
41290
41614
|
if (!Array.isArray(raw.capabilities)) {
|
|
@@ -41342,6 +41666,8 @@ function validateHello(raw) {
|
|
|
41342
41666
|
assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
|
|
41343
41667
|
assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
|
|
41344
41668
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
41669
|
+
assertBase64Bytes(raw.sessionPub, "hello.sessionPub", 32);
|
|
41670
|
+
assertBase64Bytes(raw.answersExtNonce, "hello.answersExtNonce", 32);
|
|
41345
41671
|
assertBase64(raw.sessionSig, "hello.sessionSig");
|
|
41346
41672
|
return raw;
|
|
41347
41673
|
}
|
|
@@ -41359,11 +41685,34 @@ function validateHello(raw) {
|
|
|
41359
41685
|
}
|
|
41360
41686
|
throw new ProtocolError(`hello.role: must be 'server' or 'extension', got ${String(role)}`);
|
|
41361
41687
|
}
|
|
41688
|
+
function peekHelloVersion(raw) {
|
|
41689
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
41690
|
+
return null;
|
|
41691
|
+
const frame = raw;
|
|
41692
|
+
if (frame.type !== "hello")
|
|
41693
|
+
return null;
|
|
41694
|
+
const protocolVersion = frame.protocolVersion;
|
|
41695
|
+
if (typeof protocolVersion !== "number" || !Number.isInteger(protocolVersion))
|
|
41696
|
+
return null;
|
|
41697
|
+
const accepts = [];
|
|
41698
|
+
if (Array.isArray(frame.accepts)) {
|
|
41699
|
+
for (const a of frame.accepts) {
|
|
41700
|
+
if (typeof a === "string")
|
|
41701
|
+
accepts.push(a);
|
|
41702
|
+
}
|
|
41703
|
+
}
|
|
41704
|
+
return {
|
|
41705
|
+
protocolVersion,
|
|
41706
|
+
mcpId: isValidMcpId(frame.mcpId) ? frame.mcpId : null,
|
|
41707
|
+
accepts
|
|
41708
|
+
};
|
|
41709
|
+
}
|
|
41362
41710
|
function validateReady(raw) {
|
|
41363
41711
|
assertString(raw.mcpId, "ready.mcpId");
|
|
41364
41712
|
if (!isValidMcpId(raw.mcpId))
|
|
41365
41713
|
throw new ProtocolError("ready.mcpId: invalid format");
|
|
41366
41714
|
assertBase64(raw.extensionSessionPub, "ready.extensionSessionPub");
|
|
41715
|
+
assertBase64Bytes(raw.mcpSessionPub, "ready.mcpSessionPub", 32);
|
|
41367
41716
|
assertBase64(raw.sessionSig, "ready.sessionSig");
|
|
41368
41717
|
return raw;
|
|
41369
41718
|
}
|
|
@@ -41376,14 +41725,14 @@ function validateEncrypted(raw) {
|
|
|
41376
41725
|
assertBase64(raw.ciphertext, "frame.ciphertext");
|
|
41377
41726
|
return raw;
|
|
41378
41727
|
}
|
|
41379
|
-
var PAIR_CODE_RE = /^\d{
|
|
41728
|
+
var PAIR_CODE_RE = /^\d{4}-\d{4}$/;
|
|
41380
41729
|
function validatePairPending(raw) {
|
|
41381
41730
|
assertString(raw.mcpId, "pair-pending.mcpId");
|
|
41382
41731
|
if (!isValidMcpId(raw.mcpId))
|
|
41383
41732
|
throw new ProtocolError("pair-pending.mcpId: invalid format");
|
|
41384
41733
|
assertString(raw.pairCode, "pair-pending.pairCode");
|
|
41385
41734
|
if (!PAIR_CODE_RE.test(raw.pairCode)) {
|
|
41386
|
-
throw new ProtocolError(`pair-pending.pairCode: must match
|
|
41735
|
+
throw new ProtocolError(`pair-pending.pairCode: must match XXXX-XXXX, got ${String(raw.pairCode)}`);
|
|
41387
41736
|
}
|
|
41388
41737
|
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
41389
41738
|
}
|
|
@@ -41831,180 +42180,59 @@ function validateInnerResponse(raw) {
|
|
|
41831
42180
|
throw new ProtocolError(`inner.ok: must be boolean, got ${typeof raw.ok}`);
|
|
41832
42181
|
}
|
|
41833
42182
|
|
|
41834
|
-
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
41835
|
-
var subtle = globalThis.crypto.subtle;
|
|
41836
|
-
async function exportRaw(key, format) {
|
|
41837
|
-
const buf = await subtle.exportKey(format, key);
|
|
41838
|
-
return new Uint8Array(buf);
|
|
41839
|
-
}
|
|
41840
|
-
async function generateX25519() {
|
|
41841
|
-
const kp = await subtle.generateKey({ name: "X25519" }, true, [
|
|
41842
|
-
"deriveBits"
|
|
41843
|
-
]);
|
|
41844
|
-
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
41845
|
-
const pub = await exportRaw(kp.publicKey, "raw");
|
|
41846
|
-
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
41847
|
-
}
|
|
41848
|
-
async function generateEd25519() {
|
|
41849
|
-
const kp = await subtle.generateKey({ name: "Ed25519" }, true, [
|
|
41850
|
-
"sign",
|
|
41851
|
-
"verify"
|
|
41852
|
-
]);
|
|
41853
|
-
const priv = await exportRaw(kp.privateKey, "pkcs8");
|
|
41854
|
-
const pub = await exportRaw(kp.publicKey, "raw");
|
|
41855
|
-
return { privateKey: priv.slice(-32), publicKey: pub };
|
|
41856
|
-
}
|
|
41857
|
-
async function importX25519Priv(raw) {
|
|
41858
|
-
const prefix = new Uint8Array([
|
|
41859
|
-
48,
|
|
41860
|
-
46,
|
|
41861
|
-
2,
|
|
41862
|
-
1,
|
|
41863
|
-
0,
|
|
41864
|
-
48,
|
|
41865
|
-
5,
|
|
41866
|
-
6,
|
|
41867
|
-
3,
|
|
41868
|
-
43,
|
|
41869
|
-
101,
|
|
41870
|
-
110,
|
|
41871
|
-
4,
|
|
41872
|
-
34,
|
|
41873
|
-
4,
|
|
41874
|
-
32
|
|
41875
|
-
]);
|
|
41876
|
-
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
41877
|
-
pkcs8.set(prefix, 0);
|
|
41878
|
-
pkcs8.set(raw, prefix.length);
|
|
41879
|
-
return subtle.importKey("pkcs8", pkcs8, { name: "X25519" }, false, [
|
|
41880
|
-
"deriveBits"
|
|
41881
|
-
]);
|
|
41882
|
-
}
|
|
41883
|
-
async function importX25519Pub(raw) {
|
|
41884
|
-
return subtle.importKey("raw", raw, { name: "X25519" }, false, []);
|
|
41885
|
-
}
|
|
41886
|
-
async function importEd25519Priv(raw) {
|
|
41887
|
-
const prefix = new Uint8Array([
|
|
41888
|
-
48,
|
|
41889
|
-
46,
|
|
41890
|
-
2,
|
|
41891
|
-
1,
|
|
41892
|
-
0,
|
|
41893
|
-
48,
|
|
41894
|
-
5,
|
|
41895
|
-
6,
|
|
41896
|
-
3,
|
|
41897
|
-
43,
|
|
41898
|
-
101,
|
|
41899
|
-
112,
|
|
41900
|
-
4,
|
|
41901
|
-
34,
|
|
41902
|
-
4,
|
|
41903
|
-
32
|
|
41904
|
-
]);
|
|
41905
|
-
const pkcs8 = new Uint8Array(prefix.length + raw.length);
|
|
41906
|
-
pkcs8.set(prefix, 0);
|
|
41907
|
-
pkcs8.set(raw, prefix.length);
|
|
41908
|
-
return subtle.importKey("pkcs8", pkcs8, { name: "Ed25519" }, false, ["sign"]);
|
|
41909
|
-
}
|
|
41910
|
-
async function importEd25519Pub(raw) {
|
|
41911
|
-
return subtle.importKey("raw", raw, { name: "Ed25519" }, false, ["verify"]);
|
|
41912
|
-
}
|
|
41913
|
-
async function ecdhX25519(privRaw, pubRaw) {
|
|
41914
|
-
const priv = await importX25519Priv(privRaw);
|
|
41915
|
-
const pub = await importX25519Pub(pubRaw);
|
|
41916
|
-
const bits = await subtle.deriveBits({ name: "X25519", public: pub }, priv, 256);
|
|
41917
|
-
return new Uint8Array(bits);
|
|
41918
|
-
}
|
|
41919
|
-
async function ed25519Sign(privRaw, msg) {
|
|
41920
|
-
const priv = await importEd25519Priv(privRaw);
|
|
41921
|
-
const sig = await subtle.sign({ name: "Ed25519" }, priv, msg);
|
|
41922
|
-
return new Uint8Array(sig);
|
|
41923
|
-
}
|
|
41924
|
-
async function ed25519Verify(pubRaw, msg, sig) {
|
|
41925
|
-
const pub = await importEd25519Pub(pubRaw);
|
|
41926
|
-
return subtle.verify({ name: "Ed25519" }, pub, sig, msg);
|
|
41927
|
-
}
|
|
41928
|
-
async function hkdfSha256(ikm, salt, info, lenBytes) {
|
|
41929
|
-
const baseKey = await subtle.importKey("raw", ikm, { name: "HKDF" }, false, [
|
|
41930
|
-
"deriveBits"
|
|
41931
|
-
]);
|
|
41932
|
-
const bits = await subtle.deriveBits({
|
|
41933
|
-
name: "HKDF",
|
|
41934
|
-
hash: "SHA-256",
|
|
41935
|
-
salt,
|
|
41936
|
-
info
|
|
41937
|
-
}, baseKey, lenBytes * 8);
|
|
41938
|
-
return new Uint8Array(bits);
|
|
41939
|
-
}
|
|
41940
|
-
async function aesGcmSeal(key, iv, plaintext) {
|
|
41941
|
-
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
41942
|
-
"encrypt"
|
|
41943
|
-
]);
|
|
41944
|
-
const ct = await subtle.encrypt({ name: "AES-GCM", iv }, k, plaintext);
|
|
41945
|
-
return new Uint8Array(ct);
|
|
41946
|
-
}
|
|
41947
|
-
async function aesGcmOpen(key, iv, ciphertext) {
|
|
41948
|
-
const k = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
|
|
41949
|
-
"decrypt"
|
|
41950
|
-
]);
|
|
41951
|
-
const pt = await subtle.decrypt({ name: "AES-GCM", iv }, k, ciphertext);
|
|
41952
|
-
return new Uint8Array(pt);
|
|
41953
|
-
}
|
|
41954
|
-
async function sha256(data) {
|
|
41955
|
-
const h = await subtle.digest("SHA-256", data);
|
|
41956
|
-
return new Uint8Array(h);
|
|
41957
|
-
}
|
|
41958
|
-
|
|
41959
|
-
// node_modules/@fetchproxy/protocol/dist/encoding.js
|
|
41960
|
-
function toB64(bytes) {
|
|
41961
|
-
let s = "";
|
|
41962
|
-
for (let i = 0; i < bytes.length; i++)
|
|
41963
|
-
s += String.fromCharCode(bytes[i]);
|
|
41964
|
-
return btoa(s);
|
|
41965
|
-
}
|
|
41966
|
-
function fromB64(s) {
|
|
41967
|
-
const bin = atob(s);
|
|
41968
|
-
const out = new Uint8Array(bin.length);
|
|
41969
|
-
for (let i = 0; i < bin.length; i++)
|
|
41970
|
-
out[i] = bin.charCodeAt(i);
|
|
41971
|
-
return out;
|
|
41972
|
-
}
|
|
41973
|
-
function concatBytes(a, b) {
|
|
41974
|
-
const out = new Uint8Array(a.length + b.length);
|
|
41975
|
-
out.set(a, 0);
|
|
41976
|
-
out.set(b, a.length);
|
|
41977
|
-
return out;
|
|
41978
|
-
}
|
|
41979
|
-
|
|
41980
42183
|
// node_modules/@fetchproxy/protocol/dist/pair-code.js
|
|
41981
|
-
|
|
41982
|
-
|
|
41983
|
-
|
|
41984
|
-
const
|
|
41985
|
-
const
|
|
41986
|
-
const
|
|
41987
|
-
|
|
41988
|
-
|
|
41989
|
-
|
|
41990
|
-
|
|
42184
|
+
var enc2 = new TextEncoder();
|
|
42185
|
+
var PAIR_LABEL = "fetchproxy/4/pair";
|
|
42186
|
+
async function pairTranscript(mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub) {
|
|
42187
|
+
const label = enc2.encode(PAIR_LABEL);
|
|
42188
|
+
const parts = [mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub];
|
|
42189
|
+
const out = new Uint8Array(label.length + 1 + parts.reduce((n, p) => n + p.length, 0));
|
|
42190
|
+
out.set(label, 0);
|
|
42191
|
+
out[label.length] = 0;
|
|
42192
|
+
let at = label.length + 1;
|
|
42193
|
+
for (const p of parts) {
|
|
42194
|
+
out.set(p, at);
|
|
42195
|
+
at += p.length;
|
|
42196
|
+
}
|
|
42197
|
+
return digits(await sha256(out));
|
|
41991
42198
|
}
|
|
41992
|
-
|
|
41993
|
-
|
|
42199
|
+
function digits(hash2) {
|
|
42200
|
+
let n = 0n;
|
|
42201
|
+
for (let i = 0; i < 8; i++)
|
|
42202
|
+
n = n << 8n | BigInt(hash2[i] ?? 0);
|
|
42203
|
+
const s = (n % 100000000n).toString().padStart(8, "0");
|
|
42204
|
+
return `${s.slice(0, 4)}-${s.slice(4)}`;
|
|
41994
42205
|
}
|
|
41995
42206
|
|
|
41996
42207
|
// node_modules/@fetchproxy/protocol/dist/seal.js
|
|
41997
|
-
var
|
|
42208
|
+
var enc3 = new TextEncoder();
|
|
41998
42209
|
var dec = new TextDecoder();
|
|
41999
42210
|
function randomIv() {
|
|
42000
42211
|
const iv = new Uint8Array(12);
|
|
42001
42212
|
globalThis.crypto.getRandomValues(iv);
|
|
42002
42213
|
return iv;
|
|
42003
42214
|
}
|
|
42004
|
-
|
|
42215
|
+
var AES_GCM_TAG_BYTES = 16;
|
|
42216
|
+
var IV_B64_BYTES = 16;
|
|
42217
|
+
function base64Length(byteLength) {
|
|
42218
|
+
return Math.ceil(byteLength / 3) * 4;
|
|
42219
|
+
}
|
|
42220
|
+
function encodeInnerFrame(inner) {
|
|
42221
|
+
return enc3.encode(JSON.stringify(inner));
|
|
42222
|
+
}
|
|
42223
|
+
function plaintextOf(inner) {
|
|
42224
|
+
return inner instanceof Uint8Array ? inner : encodeInnerFrame(inner);
|
|
42225
|
+
}
|
|
42226
|
+
function sealedFrameWireBytes(mcpId, seq, inner) {
|
|
42227
|
+
const plaintext = plaintextOf(inner).length;
|
|
42228
|
+
const envelope = enc3.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
|
|
42229
|
+
return envelope + IV_B64_BYTES + base64Length(plaintext + AES_GCM_TAG_BYTES);
|
|
42230
|
+
}
|
|
42231
|
+
var MAX_FRAME_BYTES = 42 * 1024 * 1024;
|
|
42232
|
+
async function sealInnerFrame(sessionKey, mcpId, seq, inner, direction) {
|
|
42005
42233
|
const iv = randomIv();
|
|
42006
|
-
const pt =
|
|
42007
|
-
const ct = await aesGcmSeal(sessionKey, iv, pt);
|
|
42234
|
+
const pt = plaintextOf(inner);
|
|
42235
|
+
const ct = await aesGcmSeal(sessionKey, iv, pt, frameAad(mcpId, seq, direction));
|
|
42008
42236
|
return {
|
|
42009
42237
|
type: "frame",
|
|
42010
42238
|
mcpId,
|
|
@@ -42013,18 +42241,18 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner) {
|
|
|
42013
42241
|
ciphertext: toB64(ct)
|
|
42014
42242
|
};
|
|
42015
42243
|
}
|
|
42016
|
-
async function openEncryptedFrame(sessionKey, frame) {
|
|
42017
|
-
const result = await openEncryptedFrameDetailed(sessionKey, frame);
|
|
42244
|
+
async function openEncryptedFrame(sessionKey, frame, direction) {
|
|
42245
|
+
const result = await openEncryptedFrameDetailed(sessionKey, frame, direction);
|
|
42018
42246
|
if (result.stage === "ok")
|
|
42019
42247
|
return result.inner;
|
|
42020
42248
|
throw result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
42021
42249
|
}
|
|
42022
|
-
async function openEncryptedFrameDetailed(sessionKey, frame) {
|
|
42250
|
+
async function openEncryptedFrameDetailed(sessionKey, frame, direction) {
|
|
42023
42251
|
let pt;
|
|
42024
42252
|
try {
|
|
42025
42253
|
const iv = fromB64(frame.iv);
|
|
42026
42254
|
const ct = fromB64(frame.ciphertext);
|
|
42027
|
-
pt = await aesGcmOpen(sessionKey, iv, ct);
|
|
42255
|
+
pt = await aesGcmOpen(sessionKey, iv, ct, frameAad(frame.mcpId, frame.seq, direction));
|
|
42028
42256
|
} catch (error62) {
|
|
42029
42257
|
return { stage: "decrypt-failed", error: error62 };
|
|
42030
42258
|
}
|
|
@@ -42108,7 +42336,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
42108
42336
|
async function buildServerHello(opts) {
|
|
42109
42337
|
const sessionNonce = new Uint8Array(32);
|
|
42110
42338
|
globalThis.crypto.getRandomValues(sessionNonce);
|
|
42111
|
-
const sig = await ed25519Sign(opts.identity.ed25519Priv,
|
|
42339
|
+
const sig = await ed25519Sign(opts.identity.ed25519Priv, helloSignaturePayload(opts.mcpId, sessionNonce, opts.sessionPub, opts.answersExtNonce));
|
|
42112
42340
|
const hello = {
|
|
42113
42341
|
type: "hello",
|
|
42114
42342
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -42121,6 +42349,8 @@ async function buildServerHello(opts) {
|
|
|
42121
42349
|
identityX25519Pub: toB64(opts.identity.x25519Pub),
|
|
42122
42350
|
identityEd25519Pub: toB64(opts.identity.ed25519Pub),
|
|
42123
42351
|
sessionNonce: toB64(sessionNonce),
|
|
42352
|
+
sessionPub: toB64(opts.sessionPub),
|
|
42353
|
+
answersExtNonce: toB64(opts.answersExtNonce),
|
|
42124
42354
|
sessionSig: toB64(sig)
|
|
42125
42355
|
};
|
|
42126
42356
|
if (opts.accepts && opts.accepts.length > 0)
|
|
@@ -42177,23 +42407,91 @@ async function buildServerHello(opts) {
|
|
|
42177
42407
|
return hello;
|
|
42178
42408
|
}
|
|
42179
42409
|
|
|
42410
|
+
// node_modules/@fetchproxy/server/dist/frame-size.js
|
|
42411
|
+
function encodeOutboundInnerFrame(mcpId, inner) {
|
|
42412
|
+
const plaintext = encodeInnerFrame(inner);
|
|
42413
|
+
const wireBytes = sealedFrameWireBytes(mcpId, Number.MAX_SAFE_INTEGER, plaintext);
|
|
42414
|
+
if (wireBytes > MAX_FRAME_BYTES) {
|
|
42415
|
+
throw new Error(`fetchproxy: this request is too large for one bridge frame: ${wireBytes} bytes on the wire, cap ${MAX_FRAME_BYTES}. The request is refused; the bridge is not closed, so other calls and other MCPs on it are unaffected.`);
|
|
42416
|
+
}
|
|
42417
|
+
return plaintext;
|
|
42418
|
+
}
|
|
42419
|
+
|
|
42180
42420
|
// node_modules/@fetchproxy/server/dist/session.js
|
|
42421
|
+
var MAX_INFLIGHT_INBOUND_SEQS = 1024;
|
|
42181
42422
|
var SessionState = class {
|
|
42182
42423
|
sessionKey;
|
|
42183
42424
|
outboundSeq = 0;
|
|
42184
42425
|
lastInboundSeq = 0;
|
|
42426
|
+
inflightInbound = /* @__PURE__ */ new Set();
|
|
42427
|
+
saturationWarned = false;
|
|
42185
42428
|
constructor(sessionKey) {
|
|
42186
42429
|
this.sessionKey = sessionKey;
|
|
42187
42430
|
}
|
|
42431
|
+
/**
|
|
42432
|
+
* Whether the caller holding this claim verdict should log saturation now.
|
|
42433
|
+
* True only on the TRANSITION into it: a line per dropped frame turned a
|
|
42434
|
+
* flood — or a set that never drains — into a log flood amplifying the very
|
|
42435
|
+
* condition it reported (#376). An `'ok'` claim re-arms the latch, since it
|
|
42436
|
+
* is the one signal every caller sees that the set has drained below the
|
|
42437
|
+
* bound; a replay needs no capacity and leaves it alone. Latched per
|
|
42438
|
+
* session, so a renegotiated one starts armed. A set hovering at the bound
|
|
42439
|
+
* can still alternate ok/saturated, but that logs at the rate frames finish
|
|
42440
|
+
* opening, not the rate they arrive.
|
|
42441
|
+
*/
|
|
42442
|
+
saturationWarningDue(claim2) {
|
|
42443
|
+
if (claim2 === "ok")
|
|
42444
|
+
this.saturationWarned = false;
|
|
42445
|
+
if (claim2 !== "saturated" || this.saturationWarned)
|
|
42446
|
+
return false;
|
|
42447
|
+
this.saturationWarned = true;
|
|
42448
|
+
return true;
|
|
42449
|
+
}
|
|
42188
42450
|
nextOutboundSeq() {
|
|
42189
42451
|
this.outboundSeq += 1;
|
|
42190
42452
|
return this.outboundSeq;
|
|
42191
42453
|
}
|
|
42192
|
-
|
|
42454
|
+
/**
|
|
42455
|
+
* Take this seq out of circulation for the frame about to be opened, and
|
|
42456
|
+
* say whether it was available. SYNCHRONOUS and single-shot: call it before
|
|
42457
|
+
* the first `await` of the receive path, or a duplicate frame read in the
|
|
42458
|
+
* same pass will be judged against a counter neither frame has moved yet.
|
|
42459
|
+
*
|
|
42460
|
+
* Anything but `'ok'` means "not this frame's to process": `'replay'` for
|
|
42461
|
+
* a spent seq or a duplicate of one still in flight, `'saturated'` when the
|
|
42462
|
+
* in-flight bound is full. Replay is checked first, since refusing a stale
|
|
42463
|
+
* seq needs no capacity. Every `'ok'` MUST be answered by exactly one
|
|
42464
|
+
* {@link commitInboundSeq} or {@link releaseInboundSeq}.
|
|
42465
|
+
*/
|
|
42466
|
+
claimInboundSeq(seq) {
|
|
42193
42467
|
if (seq <= this.lastInboundSeq)
|
|
42194
|
-
return
|
|
42195
|
-
this.
|
|
42196
|
-
|
|
42468
|
+
return "replay";
|
|
42469
|
+
if (this.inflightInbound.has(seq))
|
|
42470
|
+
return "replay";
|
|
42471
|
+
if (this.inflightInbound.size >= MAX_INFLIGHT_INBOUND_SEQS)
|
|
42472
|
+
return "saturated";
|
|
42473
|
+
this.inflightInbound.add(seq);
|
|
42474
|
+
return "ok";
|
|
42475
|
+
}
|
|
42476
|
+
/**
|
|
42477
|
+
* Give a claim back without spending the seq — for a frame that did not
|
|
42478
|
+
* authenticate, which is a frame that never happened. The counter does not
|
|
42479
|
+
* move, so the genuine frames behind it, carrying lower numbers, are still
|
|
42480
|
+
* accepted. Idempotent, so a caller may release in a `catch` after a commit
|
|
42481
|
+
* it is not sure ran.
|
|
42482
|
+
*/
|
|
42483
|
+
releaseInboundSeq(seq) {
|
|
42484
|
+
this.inflightInbound.delete(seq);
|
|
42485
|
+
}
|
|
42486
|
+
/**
|
|
42487
|
+
* Record a seq as spent. Call only for a frame that authenticated. Never
|
|
42488
|
+
* moves the counter backwards, so an out-of-order commit cannot reopen a
|
|
42489
|
+
* seq an earlier one already closed.
|
|
42490
|
+
*/
|
|
42491
|
+
commitInboundSeq(seq) {
|
|
42492
|
+
this.inflightInbound.delete(seq);
|
|
42493
|
+
if (seq > this.lastInboundSeq)
|
|
42494
|
+
this.lastInboundSeq = seq;
|
|
42197
42495
|
}
|
|
42198
42496
|
};
|
|
42199
42497
|
|
|
@@ -42227,6 +42525,27 @@ var FetchproxyHelloRejectedError = class extends Error {
|
|
|
42227
42525
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
42228
42526
|
}
|
|
42229
42527
|
};
|
|
42528
|
+
var MIN_VERSION = "3.0.0";
|
|
42529
|
+
var FetchproxyProtocolVersionError = class extends Error {
|
|
42530
|
+
/** The protocol version this process speaks. */
|
|
42531
|
+
ourVersion;
|
|
42532
|
+
/** The protocol version the far end announced in the hello we refused. */
|
|
42533
|
+
theirVersion;
|
|
42534
|
+
peer;
|
|
42535
|
+
constructor(info) {
|
|
42536
|
+
const far = info.peer === "extension" ? `the attached browser extension speaks ${info.theirVersion} \u2014 update Transporter (the fetchproxy extension) to ${MIN_VERSION} or later` : `the MCP holding the bridge port speaks ${info.theirVersion} \u2014 upgrade @fetchproxy/server to ${MIN_VERSION} or later in that MCP`;
|
|
42537
|
+
super(`protocol version mismatch: this MCP speaks fetchproxy protocol ${info.ourVersion}, ${far}`);
|
|
42538
|
+
this.name = "FetchproxyProtocolVersionError";
|
|
42539
|
+
this.ourVersion = info.ourVersion;
|
|
42540
|
+
this.theirVersion = info.theirVersion;
|
|
42541
|
+
this.peer = info.peer;
|
|
42542
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
42543
|
+
}
|
|
42544
|
+
};
|
|
42545
|
+
function protocolVersionCloseReason(info) {
|
|
42546
|
+
const far = info.peer === "extension" ? "the extension" : "the other MCP";
|
|
42547
|
+
return `protocol version mismatch: this MCP speaks ${info.ourVersion}, ${far} speaks ${info.theirVersion}`;
|
|
42548
|
+
}
|
|
42230
42549
|
async function awaitSessionReady(ready, opts) {
|
|
42231
42550
|
const ms = opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS;
|
|
42232
42551
|
if (ms <= 0)
|
|
@@ -42247,11 +42566,12 @@ async function awaitSessionReady(ready, opts) {
|
|
|
42247
42566
|
}
|
|
42248
42567
|
|
|
42249
42568
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
42250
|
-
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
42251
|
-
import { join as join4 } from "node:path";
|
|
42569
|
+
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
42570
|
+
import { isAbsolute as isAbsolute3, join as join4 } from "node:path";
|
|
42252
42571
|
|
|
42253
42572
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
42254
|
-
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
42573
|
+
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
|
|
42574
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
42255
42575
|
import { isAbsolute as isAbsolute2, join as join3 } from "node:path";
|
|
42256
42576
|
import { homedir as homedir3 } from "node:os";
|
|
42257
42577
|
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
@@ -42307,16 +42627,46 @@ function parseIdentity(text) {
|
|
|
42307
42627
|
createdAt: j.createdAt
|
|
42308
42628
|
};
|
|
42309
42629
|
}
|
|
42630
|
+
async function openIdentityDir(dir) {
|
|
42631
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
42632
|
+
try {
|
|
42633
|
+
await chmod(dir, 448);
|
|
42634
|
+
} catch (e) {
|
|
42635
|
+
const code = e.code;
|
|
42636
|
+
if (code !== "EROFS" && code !== "EPERM")
|
|
42637
|
+
throw e;
|
|
42638
|
+
}
|
|
42639
|
+
}
|
|
42640
|
+
var STAGING_BYTES = 6;
|
|
42641
|
+
async function sweepStagingFiles(dir, serverName) {
|
|
42642
|
+
const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
|
|
42643
|
+
const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
|
|
42644
|
+
let names;
|
|
42645
|
+
try {
|
|
42646
|
+
names = await readdir(dir);
|
|
42647
|
+
} catch {
|
|
42648
|
+
return;
|
|
42649
|
+
}
|
|
42650
|
+
await Promise.all(names.filter((name) => staging.test(name)).map((name) => unlink(join3(dir, name)).catch(() => void 0)));
|
|
42651
|
+
}
|
|
42310
42652
|
async function writeIdentityFile(dir, serverName, id) {
|
|
42311
42653
|
const path = identityFilePath(dir, serverName);
|
|
42312
|
-
await
|
|
42313
|
-
await
|
|
42314
|
-
|
|
42654
|
+
await openIdentityDir(dir);
|
|
42655
|
+
await sweepStagingFiles(dir, serverName);
|
|
42656
|
+
const tmp = `${path}.${randomBytes2(STAGING_BYTES).toString("hex")}.tmp`;
|
|
42657
|
+
try {
|
|
42658
|
+
await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
42659
|
+
await chmod(tmp, 384);
|
|
42660
|
+
await rename(tmp, path);
|
|
42661
|
+
} catch (e) {
|
|
42662
|
+
await rm(tmp, { force: true });
|
|
42663
|
+
throw e;
|
|
42664
|
+
}
|
|
42315
42665
|
return path;
|
|
42316
42666
|
}
|
|
42317
42667
|
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
42318
42668
|
const path = identityFilePath(dir, serverName);
|
|
42319
|
-
await
|
|
42669
|
+
await openIdentityDir(dir);
|
|
42320
42670
|
try {
|
|
42321
42671
|
return parseIdentity(await readFile(path, "utf8"));
|
|
42322
42672
|
} catch (e) {
|
|
@@ -42329,12 +42679,32 @@ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
|
42329
42679
|
}
|
|
42330
42680
|
|
|
42331
42681
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
42682
|
+
var TRUST_DIR_ENV = "FETCHPROXY_TRUST_DIR";
|
|
42683
|
+
function envTrustDir(env = process.env) {
|
|
42684
|
+
const raw = env[TRUST_DIR_ENV];
|
|
42685
|
+
if (raw === void 0)
|
|
42686
|
+
return void 0;
|
|
42687
|
+
const dir = raw.trim();
|
|
42688
|
+
if (dir !== "" && isAbsolute3(dir))
|
|
42689
|
+
return dir;
|
|
42690
|
+
console.warn(`[fetchproxy] ignoring ${TRUST_DIR_ENV}=${JSON.stringify(raw)}: it is not an absolute path. The extension pin will be written beside the identity instead, which is not where you pointed it \u2014 if that directory is read-only, no pin is ever kept.`);
|
|
42691
|
+
return void 0;
|
|
42692
|
+
}
|
|
42693
|
+
function resolveTrustDir(explicit, identityDir) {
|
|
42694
|
+
if (explicit !== void 0)
|
|
42695
|
+
return explicit;
|
|
42696
|
+
return envTrustDir() ?? identityDir ?? defaultIdentityDir();
|
|
42697
|
+
}
|
|
42698
|
+
function defaultTrustDir() {
|
|
42699
|
+
return resolveTrustDir();
|
|
42700
|
+
}
|
|
42332
42701
|
function fileExtensionTrust(args) {
|
|
42702
|
+
const dir = resolveTrustDir(args.trustDir, args.dir);
|
|
42333
42703
|
return {
|
|
42334
42704
|
allowNew: args.allowNew,
|
|
42335
|
-
location: extensionTrustPath(args.serverName,
|
|
42336
|
-
read: () => readExtensionPin(args.serverName,
|
|
42337
|
-
write: (pin) => writeExtensionPin(args.serverName, pin,
|
|
42705
|
+
location: extensionTrustPath(args.serverName, dir),
|
|
42706
|
+
read: () => readExtensionPin(args.serverName, dir),
|
|
42707
|
+
write: (pin) => writeExtensionPin(args.serverName, pin, dir)
|
|
42338
42708
|
};
|
|
42339
42709
|
}
|
|
42340
42710
|
var TRUST_NEW_EXTENSION_ENV = "FETCHPROXY_TRUST_NEW_EXTENSION";
|
|
@@ -42362,14 +42732,14 @@ function decideExtensionTrust(args) {
|
|
|
42362
42732
|
message: `[fetchproxy] ${serverName}: refusing an extension whose identity is not the one this MCP paired with. If you re-installed the extension or moved to another browser, re-pair deliberately: run this MCP once with ${TRUST_NEW_EXTENSION_ENV}=1, or delete ${trustPath}. If you did neither, something else is answering as your browser.`
|
|
42363
42733
|
};
|
|
42364
42734
|
}
|
|
42365
|
-
function extensionTrustPath(serverName, dir =
|
|
42735
|
+
function extensionTrustPath(serverName, dir = defaultTrustDir()) {
|
|
42366
42736
|
return join4(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
42367
42737
|
}
|
|
42368
42738
|
function extensionTrustPathHint(serverName) {
|
|
42369
42739
|
try {
|
|
42370
42740
|
return extensionTrustPath(serverName);
|
|
42371
42741
|
} catch {
|
|
42372
|
-
return join4(
|
|
42742
|
+
return join4(defaultTrustDir(), "<server-name>.extension-trust.json");
|
|
42373
42743
|
}
|
|
42374
42744
|
}
|
|
42375
42745
|
function isPin(x) {
|
|
@@ -42378,7 +42748,7 @@ function isPin(x) {
|
|
|
42378
42748
|
const r = x;
|
|
42379
42749
|
return typeof r.identityX25519Pub === "string" && typeof r.identityEd25519Pub === "string" && typeof r.pinnedAt === "number";
|
|
42380
42750
|
}
|
|
42381
|
-
async function readExtensionPin(serverName, dir =
|
|
42751
|
+
async function readExtensionPin(serverName, dir = defaultTrustDir()) {
|
|
42382
42752
|
const path = extensionTrustPath(serverName, dir);
|
|
42383
42753
|
let raw;
|
|
42384
42754
|
try {
|
|
@@ -42403,31 +42773,75 @@ async function readExtensionPin(serverName, dir = defaultIdentityDir()) {
|
|
|
42403
42773
|
pinnedAt: parsed.pinnedAt
|
|
42404
42774
|
};
|
|
42405
42775
|
}
|
|
42406
|
-
async function writeExtensionPin(serverName, pin, dir =
|
|
42776
|
+
async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
|
|
42407
42777
|
const path = extensionTrustPath(serverName, dir);
|
|
42408
42778
|
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
42409
42779
|
const tmp = `${path}.tmp`;
|
|
42410
42780
|
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
|
|
42411
42781
|
await chmod2(tmp, 384);
|
|
42412
|
-
await
|
|
42782
|
+
await rename2(tmp, path);
|
|
42413
42783
|
}
|
|
42414
42784
|
|
|
42415
42785
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
42416
|
-
var
|
|
42417
|
-
var
|
|
42786
|
+
var HTTP_ORIGIN_RE = /^https?:\/\//i;
|
|
42787
|
+
var PUBLIC_ORIGIN_RE = /^https?:\/\/(?!(127\.0\.0\.1|localhost|\[::1\])(:|$))/i;
|
|
42788
|
+
var EXTENSION_ORIGIN_RE = /^(chrome-extension|moz-extension|safari-web-extension):\/\//i;
|
|
42789
|
+
var OPAQUE_ORIGIN = "null";
|
|
42790
|
+
var ALLOW_LOCAL_ORIGINS_ENV = "FETCHPROXY_ALLOW_LOCAL_ORIGINS";
|
|
42791
|
+
function envAllowsLocalOrigins(env = process.env) {
|
|
42792
|
+
const raw = env[ALLOW_LOCAL_ORIGINS_ENV];
|
|
42793
|
+
if (raw === void 0 || raw.trim() === "")
|
|
42794
|
+
return false;
|
|
42795
|
+
if (raw.trim() === "1")
|
|
42796
|
+
return true;
|
|
42797
|
+
console.warn(`[fetchproxy] ignoring ${ALLOW_LOCAL_ORIGINS_ENV}=${JSON.stringify(raw)}: the only value that turns it on is 1. Upgrades from a null or localhost origin stay refused.`);
|
|
42798
|
+
return false;
|
|
42799
|
+
}
|
|
42800
|
+
function originVerdict(origin, allowLocal) {
|
|
42801
|
+
if (origin === void 0)
|
|
42802
|
+
return { allow: true };
|
|
42803
|
+
if (HTTP_ORIGIN_RE.test(origin)) {
|
|
42804
|
+
if (PUBLIC_ORIGIN_RE.test(origin)) {
|
|
42805
|
+
return { allow: false, reason: "origin not allowed", escapable: false };
|
|
42806
|
+
}
|
|
42807
|
+
return allowLocal ? { allow: true } : { allow: false, reason: "localhost page origin not allowed", escapable: true };
|
|
42808
|
+
}
|
|
42809
|
+
if (origin.toLowerCase() === OPAQUE_ORIGIN) {
|
|
42810
|
+
return allowLocal ? { allow: true } : { allow: false, reason: "opaque (null) origin not allowed", escapable: true };
|
|
42811
|
+
}
|
|
42812
|
+
if (EXTENSION_ORIGIN_RE.test(origin))
|
|
42813
|
+
return { allow: true };
|
|
42814
|
+
return { allow: false, reason: "origin not allowed", escapable: false };
|
|
42815
|
+
}
|
|
42816
|
+
var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
42817
|
+
var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
|
|
42818
|
+
var enc4 = new TextEncoder();
|
|
42418
42819
|
async function startHost(opts) {
|
|
42820
|
+
const allowLocalOrigins = envAllowsLocalOrigins();
|
|
42821
|
+
if (allowLocalOrigins) {
|
|
42822
|
+
console.warn(`[fetchproxy] ${ALLOW_LOCAL_ORIGINS_ENV}=1: accepting WebSocket upgrades from null and localhost page origins. This is a development escape \u2014 any local page the browser has open can reach this concentrator while it is set.`);
|
|
42823
|
+
}
|
|
42824
|
+
const warnedOrigins = /* @__PURE__ */ new Set();
|
|
42419
42825
|
const wss = new import_websocket_server.default({
|
|
42420
42826
|
server: opts.httpServer,
|
|
42827
|
+
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES,
|
|
42421
42828
|
verifyClient: (info, cb) => {
|
|
42422
42829
|
const origin = info.req.headers.origin;
|
|
42423
|
-
|
|
42424
|
-
|
|
42830
|
+
const verdict = originVerdict(origin, allowLocalOrigins);
|
|
42831
|
+
if (verdict.allow) {
|
|
42832
|
+
cb(true);
|
|
42425
42833
|
return;
|
|
42426
42834
|
}
|
|
42427
|
-
|
|
42835
|
+
if (verdict.escapable && origin !== void 0 && warnedOrigins.size < 8) {
|
|
42836
|
+
if (!warnedOrigins.has(origin)) {
|
|
42837
|
+
warnedOrigins.add(origin);
|
|
42838
|
+
console.warn(`[fetchproxy] refused a WebSocket upgrade from ${JSON.stringify(origin)}: ${verdict.reason}. If that is you, developing against the bridge, set ${ALLOW_LOCAL_ORIGINS_ENV}=1 on this MCP \u2014 never on a deployed one.`);
|
|
42839
|
+
}
|
|
42840
|
+
}
|
|
42841
|
+
cb(false, 403, verdict.reason);
|
|
42428
42842
|
}
|
|
42429
42843
|
});
|
|
42430
|
-
const
|
|
42844
|
+
const ownHelloBase = {
|
|
42431
42845
|
// 2.6.0: tell the extension it can say WHY it refused a hello, instead of
|
|
42432
42846
|
// leaving us to time out and guess.
|
|
42433
42847
|
accepts: ["hello-rejected"],
|
|
@@ -42446,14 +42860,26 @@ async function startHost(opts) {
|
|
|
42446
42860
|
sessionStoragePointers: opts.ownSessionStoragePointers,
|
|
42447
42861
|
domSelectors: opts.ownDomSelectors,
|
|
42448
42862
|
graphqlOps: opts.ownGraphqlOps
|
|
42449
|
-
}
|
|
42450
|
-
const
|
|
42863
|
+
};
|
|
42864
|
+
const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
|
|
42451
42865
|
let extensionWs = null;
|
|
42452
42866
|
const peers = /* @__PURE__ */ new Map();
|
|
42453
42867
|
const ownInnerListeners = [];
|
|
42454
42868
|
const disconnectListeners = [];
|
|
42455
42869
|
const pendingPairListeners = [];
|
|
42456
42870
|
let ownSession = null;
|
|
42871
|
+
let ownEphemeral = null;
|
|
42872
|
+
function dropOwnSessionAndEphemeral() {
|
|
42873
|
+
if (ownEphemeral)
|
|
42874
|
+
ownEphemeral.priv.fill(0);
|
|
42875
|
+
ownEphemeral = null;
|
|
42876
|
+
return null;
|
|
42877
|
+
}
|
|
42878
|
+
function installOwnEphemeral(next) {
|
|
42879
|
+
if (ownEphemeral)
|
|
42880
|
+
ownEphemeral.priv.fill(0);
|
|
42881
|
+
ownEphemeral = next;
|
|
42882
|
+
}
|
|
42457
42883
|
let ownPendingPairCode = null;
|
|
42458
42884
|
let resolveOwnSession;
|
|
42459
42885
|
let rejectOwnSession;
|
|
@@ -42467,21 +42893,74 @@ async function startHost(opts) {
|
|
|
42467
42893
|
});
|
|
42468
42894
|
}
|
|
42469
42895
|
resetSessionPromise();
|
|
42896
|
+
let ownSessionRefusal = null;
|
|
42897
|
+
function refuseVersionMismatch(ws, raw, identified) {
|
|
42898
|
+
const peek = peekHelloVersion(raw);
|
|
42899
|
+
if (!peek || peek.protocolVersion === PROTOCOL_VERSION)
|
|
42900
|
+
return false;
|
|
42901
|
+
const err = new FetchproxyProtocolVersionError({
|
|
42902
|
+
ourVersion: PROTOCOL_VERSION,
|
|
42903
|
+
theirVersion: peek.protocolVersion,
|
|
42904
|
+
peer: peek.mcpId === null ? "extension" : "mcp"
|
|
42905
|
+
});
|
|
42906
|
+
console.warn(`[fetchproxy] host: ${err.message}`);
|
|
42907
|
+
const nothingBetterAttached = extensionWs === null && extensionClaim === null && ownSession === null;
|
|
42908
|
+
if (err.peer === "extension" && identified !== "peer" && nothingBetterAttached) {
|
|
42909
|
+
ownSessionRefusal = err;
|
|
42910
|
+
rejectOwnSession(err);
|
|
42911
|
+
resetSessionPromise();
|
|
42912
|
+
rejectOwnSession(err);
|
|
42913
|
+
}
|
|
42914
|
+
try {
|
|
42915
|
+
ws.close(1002, protocolVersionCloseReason(err));
|
|
42916
|
+
} catch {
|
|
42917
|
+
}
|
|
42918
|
+
return true;
|
|
42919
|
+
}
|
|
42470
42920
|
let extensionHello = null;
|
|
42471
42921
|
let extensionClaim = null;
|
|
42922
|
+
const pairCodeFor = async (hello, mint) => {
|
|
42923
|
+
if (!mint)
|
|
42924
|
+
return null;
|
|
42925
|
+
try {
|
|
42926
|
+
return await pairTranscript(opts.ownIdentity.x25519Pub, fromB64(hello.identityX25519Pub), mint.nonce, fromB64(hello.sessionNonce), mint.pub);
|
|
42927
|
+
} catch (e) {
|
|
42928
|
+
console.error("[fetchproxy] could not derive the pair code:", e);
|
|
42929
|
+
return null;
|
|
42930
|
+
}
|
|
42931
|
+
};
|
|
42472
42932
|
wss.on("connection", (ws) => {
|
|
42473
42933
|
let identified = null;
|
|
42474
42934
|
let peerMcpId = null;
|
|
42475
42935
|
let closed = false;
|
|
42476
42936
|
let pinOnReady = false;
|
|
42937
|
+
const handshakeTimer = setTimeout(() => {
|
|
42938
|
+
if (identified || closed)
|
|
42939
|
+
return;
|
|
42940
|
+
try {
|
|
42941
|
+
ws.close(1008, "handshake timeout");
|
|
42942
|
+
} catch {
|
|
42943
|
+
}
|
|
42944
|
+
}, opts.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS);
|
|
42945
|
+
handshakeTimer.unref?.();
|
|
42946
|
+
ws.on("error", (e) => {
|
|
42947
|
+
console.warn(`[fetchproxy] host: socket error: ${String(e)}`);
|
|
42948
|
+
});
|
|
42477
42949
|
ws.on("message", async (data) => {
|
|
42478
42950
|
try {
|
|
42951
|
+
let raw;
|
|
42952
|
+
try {
|
|
42953
|
+
raw = JSON.parse(data.toString());
|
|
42954
|
+
} catch {
|
|
42955
|
+
ws.close(1002, "protocol error");
|
|
42956
|
+
return;
|
|
42957
|
+
}
|
|
42479
42958
|
let frame;
|
|
42480
42959
|
try {
|
|
42481
|
-
const raw = JSON.parse(data.toString());
|
|
42482
42960
|
frame = validateFrame(raw);
|
|
42483
42961
|
} catch {
|
|
42484
|
-
ws
|
|
42962
|
+
if (!refuseVersionMismatch(ws, raw, identified))
|
|
42963
|
+
ws.close(1002, "protocol error");
|
|
42485
42964
|
return;
|
|
42486
42965
|
}
|
|
42487
42966
|
if (frame.type === "hello" && frame.role === "extension") {
|
|
@@ -42525,25 +43004,43 @@ async function startHost(opts) {
|
|
|
42525
43004
|
identified = "extension";
|
|
42526
43005
|
extensionWs = ws;
|
|
42527
43006
|
extensionHello = frame;
|
|
42528
|
-
if (
|
|
43007
|
+
if (ownSessionRefusal) {
|
|
43008
|
+
ownSessionRefusal = null;
|
|
43009
|
+
resetSessionPromise();
|
|
43010
|
+
}
|
|
43011
|
+
const mintedKeypair = await generateSessionKeypair();
|
|
43012
|
+
const mintedHello = await buildServerHello({
|
|
43013
|
+
...ownHelloBase,
|
|
43014
|
+
sessionPub: mintedKeypair.publicKey,
|
|
43015
|
+
// The echo Rule B's gate reads, inside the signed payload: this
|
|
43016
|
+
// hello answers the extension session whose hello triggered it.
|
|
43017
|
+
answersExtNonce: fromB64(frame.sessionNonce)
|
|
43018
|
+
});
|
|
43019
|
+
if (extensionWs !== ws) {
|
|
43020
|
+
mintedKeypair.privateKey.fill(0);
|
|
43021
|
+
return;
|
|
43022
|
+
}
|
|
43023
|
+
const mint = {
|
|
43024
|
+
nonce: fromB64(mintedHello.sessionNonce),
|
|
43025
|
+
pub: mintedKeypair.publicKey
|
|
43026
|
+
};
|
|
43027
|
+
installOwnEphemeral({ ...mint, priv: mintedKeypair.privateKey });
|
|
43028
|
+
for (const slot of peers.values())
|
|
43029
|
+
slot.ws.send(JSON.stringify(frame));
|
|
43030
|
+
ws.send(JSON.stringify(mintedHello));
|
|
43031
|
+
const helloPairCode = await pairCodeFor(frame, mint);
|
|
43032
|
+
if (opts.onPairCode && helloPairCode !== null) {
|
|
42529
43033
|
try {
|
|
42530
|
-
|
|
42531
|
-
opts.onPairCode(code);
|
|
43034
|
+
opts.onPairCode(helloPairCode);
|
|
42532
43035
|
} catch (e) {
|
|
42533
43036
|
console.error("[fetchproxy] onPairCode threw:", e);
|
|
42534
43037
|
}
|
|
42535
43038
|
}
|
|
42536
|
-
for (const slot of peers.values())
|
|
42537
|
-
slot.ws.send(JSON.stringify(frame));
|
|
42538
|
-
ws.send(JSON.stringify(ownHello));
|
|
42539
|
-
for (const slot of peers.values()) {
|
|
42540
|
-
ws.send(JSON.stringify(slot.helloFrame));
|
|
42541
|
-
}
|
|
42542
43039
|
return;
|
|
42543
43040
|
}
|
|
42544
43041
|
if (frame.type === "hello" && frame.role === "server") {
|
|
42545
43042
|
const peerEdPub = fromB64(frame.identityEd25519Pub);
|
|
42546
|
-
const peerSigMsg =
|
|
43043
|
+
const peerSigMsg = helloSignaturePayload(frame.mcpId, fromB64(frame.sessionNonce), fromB64(frame.sessionPub), fromB64(frame.answersExtNonce));
|
|
42547
43044
|
const peerSig = fromB64(frame.sessionSig);
|
|
42548
43045
|
let peerSigOk = false;
|
|
42549
43046
|
try {
|
|
@@ -42568,10 +43065,12 @@ async function startHost(opts) {
|
|
|
42568
43065
|
identified = "peer";
|
|
42569
43066
|
peerMcpId = frame.mcpId;
|
|
42570
43067
|
peers.set(frame.mcpId, { ws, helloFrame: frame });
|
|
42571
|
-
if (
|
|
42572
|
-
extensionWs.send(JSON.stringify(frame));
|
|
42573
|
-
if (extensionHello)
|
|
43068
|
+
if (extensionHello && answersNoExtSession(frame.answersExtNonce)) {
|
|
42574
43069
|
ws.send(JSON.stringify(extensionHello));
|
|
43070
|
+
}
|
|
43071
|
+
if (extensionWs && extensionHello && frame.answersExtNonce === extensionHello.sessionNonce) {
|
|
43072
|
+
extensionWs.send(JSON.stringify(frame));
|
|
43073
|
+
}
|
|
42575
43074
|
return;
|
|
42576
43075
|
}
|
|
42577
43076
|
if (frame.type === "ready") {
|
|
@@ -42581,9 +43080,14 @@ async function startHost(opts) {
|
|
|
42581
43080
|
ws.close(1002, "ready before extension hello");
|
|
42582
43081
|
return;
|
|
42583
43082
|
}
|
|
43083
|
+
const held = ownEphemeral;
|
|
43084
|
+
if (!held || frame.mcpSessionPub !== toB64(held.pub)) {
|
|
43085
|
+
console.warn("[fetchproxy] discarding a ready for a session ephemeral this host no longer holds (the extension reconnected while a hello was in flight)");
|
|
43086
|
+
return;
|
|
43087
|
+
}
|
|
42584
43088
|
const extEdPub = fromB64(extensionHello.identityEd25519Pub);
|
|
42585
43089
|
const extNonce = fromB64(extensionHello.sessionNonce);
|
|
42586
|
-
const msg = readySignaturePayload(
|
|
43090
|
+
const msg = readySignaturePayload(held.nonce, extNonce, fromB64(frame.extensionSessionPub), held.pub);
|
|
42587
43091
|
const sig = fromB64(frame.sessionSig);
|
|
42588
43092
|
let sigOk = false;
|
|
42589
43093
|
try {
|
|
@@ -42609,8 +43113,9 @@ async function startHost(opts) {
|
|
|
42609
43113
|
}
|
|
42610
43114
|
}
|
|
42611
43115
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
42612
|
-
const shared = await ecdhX25519(
|
|
42613
|
-
const
|
|
43116
|
+
const shared = await ecdhX25519(held.priv, extPub);
|
|
43117
|
+
const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
|
|
43118
|
+
const key = await hkdfSha256(shared, salt, enc4.encode(HKDF_SESSION_INFO), 32);
|
|
42614
43119
|
if (extensionWs !== ws)
|
|
42615
43120
|
return;
|
|
42616
43121
|
ownSession = new SessionState(key);
|
|
@@ -42626,11 +43131,23 @@ async function startHost(opts) {
|
|
|
42626
43131
|
if (frame.type === "frame") {
|
|
42627
43132
|
if (identified === "extension") {
|
|
42628
43133
|
if (frame.mcpId === opts.ownMcpId) {
|
|
42629
|
-
|
|
43134
|
+
const session = ownSession;
|
|
43135
|
+
if (!session)
|
|
42630
43136
|
return;
|
|
42631
|
-
|
|
43137
|
+
const claim2 = session.claimInboundSeq(frame.seq);
|
|
43138
|
+
if (session.saturationWarningDue(claim2)) {
|
|
43139
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
43140
|
+
}
|
|
43141
|
+
if (claim2 !== "ok")
|
|
42632
43142
|
return;
|
|
42633
|
-
|
|
43143
|
+
let inner;
|
|
43144
|
+
try {
|
|
43145
|
+
inner = await openEncryptedFrame(session.sessionKey, frame, "e2s");
|
|
43146
|
+
} catch (e) {
|
|
43147
|
+
session.releaseInboundSeq(frame.seq);
|
|
43148
|
+
throw e;
|
|
43149
|
+
}
|
|
43150
|
+
session.commitInboundSeq(frame.seq);
|
|
42634
43151
|
ownInnerListeners.forEach((cb) => cb(inner));
|
|
42635
43152
|
} else {
|
|
42636
43153
|
const slot = peers.get(frame.mcpId);
|
|
@@ -42654,8 +43171,16 @@ async function startHost(opts) {
|
|
|
42654
43171
|
}
|
|
42655
43172
|
if (frame.type === "pair-pending" && identified === "extension") {
|
|
42656
43173
|
if (frame.mcpId === opts.ownMcpId) {
|
|
42657
|
-
|
|
42658
|
-
|
|
43174
|
+
const hello = extensionHello;
|
|
43175
|
+
const mint = ownEphemeral;
|
|
43176
|
+
const derived2 = hello === null ? null : await pairCodeFor(hello, mint);
|
|
43177
|
+
if (derived2 === null || frame.pairCode !== derived2) {
|
|
43178
|
+
console.error(`[fetchproxy] ${opts.ownServerName}: the extension's pair code (${frame.pairCode}) does not match the one this MCP derived from the pair transcript` + (derived2 === null ? " (none \u2014 this MCP could not derive its own)" : ` (${derived2})`) + " \u2014 refusing to pair (possible MITM between this MCP and the extension)");
|
|
43179
|
+
ws.close(1008, "pair code mismatch");
|
|
43180
|
+
return;
|
|
43181
|
+
}
|
|
43182
|
+
ownPendingPairCode = derived2;
|
|
43183
|
+
pendingPairListeners.forEach((cb) => cb(derived2));
|
|
42659
43184
|
} else {
|
|
42660
43185
|
const slot = peers.get(frame.mcpId);
|
|
42661
43186
|
if (slot)
|
|
@@ -42672,6 +43197,7 @@ async function startHost(opts) {
|
|
|
42672
43197
|
});
|
|
42673
43198
|
ws.on("close", () => {
|
|
42674
43199
|
closed = true;
|
|
43200
|
+
clearTimeout(handshakeTimer);
|
|
42675
43201
|
if (extensionClaim === ws)
|
|
42676
43202
|
extensionClaim = null;
|
|
42677
43203
|
if (identified === "extension" && extensionWs === ws) {
|
|
@@ -42680,7 +43206,7 @@ async function startHost(opts) {
|
|
|
42680
43206
|
if (!ownSession) {
|
|
42681
43207
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
42682
43208
|
}
|
|
42683
|
-
ownSession =
|
|
43209
|
+
ownSession = dropOwnSessionAndEphemeral();
|
|
42684
43210
|
ownPendingPairCode = null;
|
|
42685
43211
|
resetSessionPromise();
|
|
42686
43212
|
disconnectListeners.forEach((cb) => cb());
|
|
@@ -42719,7 +43245,8 @@ async function startHost(opts) {
|
|
|
42719
43245
|
});
|
|
42720
43246
|
if (!extensionWs)
|
|
42721
43247
|
throw new Error("host: no extension connected");
|
|
42722
|
-
const
|
|
43248
|
+
const plaintext = encodeOutboundInnerFrame(opts.ownMcpId, inner);
|
|
43249
|
+
const sealed = await sealInnerFrame(session.sessionKey, opts.ownMcpId, session.nextOutboundSeq(), plaintext, "s2e");
|
|
42723
43250
|
extensionWs.send(JSON.stringify(sealed));
|
|
42724
43251
|
},
|
|
42725
43252
|
onOwnInner: (cb) => {
|
|
@@ -42738,14 +43265,32 @@ async function startHost(opts) {
|
|
|
42738
43265
|
}
|
|
42739
43266
|
|
|
42740
43267
|
// node_modules/@fetchproxy/server/dist/peer.js
|
|
42741
|
-
var
|
|
43268
|
+
var enc5 = new TextEncoder();
|
|
42742
43269
|
async function startPeer(opts) {
|
|
42743
|
-
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}
|
|
43270
|
+
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
|
|
43271
|
+
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
|
|
43272
|
+
});
|
|
42744
43273
|
await new Promise((resolve3, reject) => {
|
|
42745
|
-
|
|
42746
|
-
|
|
43274
|
+
const onOpen = () => {
|
|
43275
|
+
cleanup();
|
|
43276
|
+
resolve3();
|
|
43277
|
+
};
|
|
43278
|
+
const onError = (e) => {
|
|
43279
|
+
cleanup();
|
|
43280
|
+
reject(e);
|
|
43281
|
+
};
|
|
43282
|
+
const cleanup = () => {
|
|
43283
|
+
ws.off("open", onOpen);
|
|
43284
|
+
ws.off("error", onError);
|
|
43285
|
+
};
|
|
43286
|
+
ws.once("open", onOpen);
|
|
43287
|
+
ws.once("error", onError);
|
|
42747
43288
|
});
|
|
42748
|
-
|
|
43289
|
+
ws.on("error", (e) => {
|
|
43290
|
+
console.warn(`[fetchproxy] peer: socket error: ${String(e)}`);
|
|
43291
|
+
});
|
|
43292
|
+
const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
|
|
43293
|
+
const helloBase = {
|
|
42749
43294
|
identity: opts.identity,
|
|
42750
43295
|
mcpId: opts.mcpId,
|
|
42751
43296
|
serverName: opts.serverName,
|
|
@@ -42765,15 +43310,39 @@ async function startPeer(opts) {
|
|
|
42765
43310
|
// `extensionConnected()` / `sessionLinked()` can go back to false.
|
|
42766
43311
|
// 2.6.0: `hello-rejected` for the same reason, from the extension.
|
|
42767
43312
|
accepts: ["extension-disconnected", "hello-rejected"]
|
|
43313
|
+
};
|
|
43314
|
+
const bootstrapKeypair = await generateSessionKeypair();
|
|
43315
|
+
let bootstrapPriv = bootstrapKeypair.privateKey;
|
|
43316
|
+
const hello = await buildServerHello({
|
|
43317
|
+
...helloBase,
|
|
43318
|
+
sessionPub: bootstrapKeypair.publicKey,
|
|
43319
|
+
// At dial this peer has been told of no extension session, and saying so
|
|
43320
|
+
// is what keeps the host from forwarding a hello whose ephemeral is not
|
|
43321
|
+
// one a session may be opened from.
|
|
43322
|
+
answersExtNonce: ANSWERS_NO_EXT_SESSION
|
|
42768
43323
|
});
|
|
42769
|
-
const sessionNonce = fromB64(hello.sessionNonce);
|
|
42770
43324
|
ws.send(JSON.stringify(hello));
|
|
42771
43325
|
const innerListeners = [];
|
|
42772
43326
|
const renegotiateListeners = [];
|
|
42773
43327
|
const pendingPairListeners = [];
|
|
42774
43328
|
const closeListeners = [];
|
|
42775
43329
|
let session = null;
|
|
43330
|
+
let sessionEphemeral = null;
|
|
43331
|
+
function dropSessionEphemeral() {
|
|
43332
|
+
if (sessionEphemeral)
|
|
43333
|
+
sessionEphemeral.priv.fill(0);
|
|
43334
|
+
sessionEphemeral = null;
|
|
43335
|
+
}
|
|
43336
|
+
function installSessionEphemeral(next) {
|
|
43337
|
+
dropSessionEphemeral();
|
|
43338
|
+
if (bootstrapPriv) {
|
|
43339
|
+
bootstrapPriv.fill(0);
|
|
43340
|
+
bootstrapPriv = null;
|
|
43341
|
+
}
|
|
43342
|
+
sessionEphemeral = next;
|
|
43343
|
+
}
|
|
42776
43344
|
let pendingPairCode = null;
|
|
43345
|
+
let warnedUnverifiablePairCode = false;
|
|
42777
43346
|
let resolveFirstReady;
|
|
42778
43347
|
let rejectFirstReady;
|
|
42779
43348
|
const sessionPromise = new Promise((resolve3, reject) => {
|
|
@@ -42782,58 +43351,60 @@ async function startPeer(opts) {
|
|
|
42782
43351
|
});
|
|
42783
43352
|
let extensionHello = null;
|
|
42784
43353
|
let extensionGone = false;
|
|
42785
|
-
let warnedUnverifiable = false;
|
|
42786
43354
|
let cachedPin = void 0;
|
|
42787
|
-
const
|
|
42788
|
-
if (!
|
|
42789
|
-
|
|
42790
|
-
|
|
42791
|
-
|
|
42792
|
-
|
|
42793
|
-
|
|
42794
|
-
|
|
42795
|
-
|
|
42796
|
-
|
|
42797
|
-
|
|
43355
|
+
const pairCodeFor = async (hello2, mint) => {
|
|
43356
|
+
if (!mint)
|
|
43357
|
+
return null;
|
|
43358
|
+
try {
|
|
43359
|
+
return await pairTranscript(opts.identity.x25519Pub, fromB64(hello2.identityX25519Pub), mint.nonce, fromB64(hello2.sessionNonce), mint.pub);
|
|
43360
|
+
} catch (e) {
|
|
43361
|
+
console.error("[fetchproxy] could not derive the pair code:", e);
|
|
43362
|
+
return null;
|
|
43363
|
+
}
|
|
43364
|
+
};
|
|
43365
|
+
const authenticateExtension = async (sessionSig, extensionSessionPub, held, hello2) => {
|
|
43366
|
+
if (!hello2) {
|
|
43367
|
+
console.error(`[fetchproxy] ${opts.serverName}: no extension hello has been relayed to this peer, so there is nothing to derive a v4 session from \u2014 refusing. Upgrade the MCP holding the bridge port to 3.0.0 or later.`);
|
|
43368
|
+
return null;
|
|
42798
43369
|
}
|
|
42799
|
-
const payload = readySignaturePayload(
|
|
43370
|
+
const payload = readySignaturePayload(held.nonce, fromB64(hello2.sessionNonce), fromB64(extensionSessionPub), held.pub);
|
|
42800
43371
|
let sigOk = false;
|
|
42801
43372
|
try {
|
|
42802
|
-
sigOk = await ed25519Verify(fromB64(
|
|
43373
|
+
sigOk = await ed25519Verify(fromB64(hello2.identityEd25519Pub), payload, fromB64(sessionSig));
|
|
42803
43374
|
} catch {
|
|
42804
43375
|
sigOk = false;
|
|
42805
43376
|
}
|
|
42806
43377
|
if (!sigOk) {
|
|
42807
43378
|
console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
|
|
42808
|
-
return
|
|
43379
|
+
return null;
|
|
42809
43380
|
}
|
|
42810
43381
|
if (cachedPin === void 0) {
|
|
42811
43382
|
try {
|
|
42812
43383
|
cachedPin = await opts.extensionTrust.read();
|
|
42813
43384
|
} catch (e) {
|
|
42814
43385
|
console.error(`[fetchproxy] ${String(e)}`);
|
|
42815
|
-
return
|
|
43386
|
+
return null;
|
|
42816
43387
|
}
|
|
42817
43388
|
}
|
|
42818
43389
|
const pin = cachedPin;
|
|
42819
43390
|
const outcome = decideExtensionTrust({
|
|
42820
43391
|
pin,
|
|
42821
|
-
hello:
|
|
43392
|
+
hello: hello2,
|
|
42822
43393
|
allowNew: opts.extensionTrust.allowNew,
|
|
42823
43394
|
serverName: opts.serverName,
|
|
42824
43395
|
location: opts.extensionTrust.location
|
|
42825
43396
|
});
|
|
42826
43397
|
if (outcome.decision === "refused") {
|
|
42827
43398
|
console.warn(outcome.message);
|
|
42828
|
-
return
|
|
43399
|
+
return null;
|
|
42829
43400
|
}
|
|
42830
43401
|
if (outcome.decision === "replace")
|
|
42831
43402
|
console.warn(outcome.message);
|
|
42832
43403
|
if (outcome.decision !== "pinned") {
|
|
42833
43404
|
try {
|
|
42834
43405
|
const written = {
|
|
42835
|
-
identityX25519Pub:
|
|
42836
|
-
identityEd25519Pub:
|
|
43406
|
+
identityX25519Pub: hello2.identityX25519Pub,
|
|
43407
|
+
identityEd25519Pub: hello2.identityEd25519Pub,
|
|
42837
43408
|
pinnedAt: Date.now()
|
|
42838
43409
|
};
|
|
42839
43410
|
await opts.extensionTrust.write(written);
|
|
@@ -42842,32 +43413,62 @@ async function startPeer(opts) {
|
|
|
42842
43413
|
console.error(`[fetchproxy] could not persist the extension pin: ${String(e)}`);
|
|
42843
43414
|
}
|
|
42844
43415
|
}
|
|
42845
|
-
return
|
|
43416
|
+
return hello2;
|
|
42846
43417
|
};
|
|
42847
43418
|
const onMessage = async (data) => {
|
|
43419
|
+
let raw;
|
|
42848
43420
|
try {
|
|
42849
|
-
|
|
43421
|
+
raw = JSON.parse(data.toString());
|
|
42850
43422
|
const frame = validateFrame(raw);
|
|
42851
43423
|
if (frame.type === "hello" && frame.role === "extension") {
|
|
42852
43424
|
extensionHello = frame;
|
|
43425
|
+
const mintedKeypair = await generateSessionKeypair();
|
|
43426
|
+
const mintedHello = await buildServerHello({
|
|
43427
|
+
...helloBase,
|
|
43428
|
+
sessionPub: mintedKeypair.publicKey,
|
|
43429
|
+
answersExtNonce: fromB64(frame.sessionNonce)
|
|
43430
|
+
});
|
|
43431
|
+
if (extensionHello !== frame) {
|
|
43432
|
+
mintedKeypair.privateKey.fill(0);
|
|
43433
|
+
return;
|
|
43434
|
+
}
|
|
43435
|
+
installSessionEphemeral({
|
|
43436
|
+
nonce: fromB64(mintedHello.sessionNonce),
|
|
43437
|
+
pub: mintedKeypair.publicKey,
|
|
43438
|
+
priv: mintedKeypair.privateKey
|
|
43439
|
+
});
|
|
43440
|
+
ws.send(JSON.stringify(mintedHello));
|
|
42853
43441
|
return;
|
|
42854
43442
|
}
|
|
42855
43443
|
if (frame.type === "extension-disconnected") {
|
|
42856
43444
|
extensionHello = null;
|
|
42857
43445
|
extensionGone = true;
|
|
43446
|
+
dropSessionEphemeral();
|
|
42858
43447
|
pendingPairCode = null;
|
|
42859
43448
|
return;
|
|
42860
43449
|
}
|
|
42861
43450
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
42862
|
-
const
|
|
42863
|
-
|
|
43451
|
+
const held = sessionEphemeral;
|
|
43452
|
+
const heldExtHello = extensionHello;
|
|
43453
|
+
if (!held || frame.mcpSessionPub !== toB64(held.pub)) {
|
|
43454
|
+
console.warn(`[fetchproxy] ${opts.serverName}: discarding a ready for a session ephemeral this peer no longer holds (the extension reconnected while a hello was in flight)`);
|
|
43455
|
+
return;
|
|
43456
|
+
}
|
|
43457
|
+
const authenticated = await authenticateExtension(frame.sessionSig, frame.extensionSessionPub, held, heldExtHello);
|
|
43458
|
+
if (!authenticated) {
|
|
42864
43459
|
ws.close(1008, "extension identity refused");
|
|
42865
43460
|
rejectFirstReady(new Error("peer: extension identity refused"));
|
|
42866
43461
|
return;
|
|
42867
43462
|
}
|
|
42868
43463
|
const extPub = fromB64(frame.extensionSessionPub);
|
|
42869
|
-
const
|
|
42870
|
-
const
|
|
43464
|
+
const extNonce = fromB64(authenticated.sessionNonce);
|
|
43465
|
+
const shared = await ecdhX25519(held.priv, extPub);
|
|
43466
|
+
const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
|
|
43467
|
+
const sessionKey = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
|
|
43468
|
+
if (sessionEphemeral !== held) {
|
|
43469
|
+
console.warn(`[fetchproxy] ${opts.serverName}: dropping a session derived against an ephemeral that was superseded while it was being derived`);
|
|
43470
|
+
return;
|
|
43471
|
+
}
|
|
42871
43472
|
const isRenegotiation = session !== null;
|
|
42872
43473
|
session = new SessionState(sessionKey);
|
|
42873
43474
|
extensionGone = false;
|
|
@@ -42883,16 +43484,46 @@ async function startPeer(opts) {
|
|
|
42883
43484
|
rejectFirstReady(new FetchproxyHelloRejectedError({ mcpId: frame.mcpId, reason: frame.reason }));
|
|
42884
43485
|
}
|
|
42885
43486
|
if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
|
|
42886
|
-
|
|
42887
|
-
|
|
43487
|
+
const hello2 = extensionHello;
|
|
43488
|
+
const mint = sessionEphemeral;
|
|
43489
|
+
const derived2 = hello2 === null ? null : await pairCodeFor(hello2, mint);
|
|
43490
|
+
if (derived2 === null) {
|
|
43491
|
+
if (!warnedUnverifiablePairCode) {
|
|
43492
|
+
warnedUnverifiablePairCode = true;
|
|
43493
|
+
console.warn(`[fetchproxy] ${opts.serverName}: a pair code arrived that this peer cannot derive for itself (no extension identity has been relayed to it, or no session ephemeral has been minted in answer to one), so it is not being shown. If the MCP holding the bridge port is older than 1.12.0, upgrading it closes this.`);
|
|
43494
|
+
}
|
|
43495
|
+
return;
|
|
43496
|
+
}
|
|
43497
|
+
if (frame.pairCode !== derived2) {
|
|
43498
|
+
console.error(`[fetchproxy] ${opts.serverName}: the extension's pair code (${frame.pairCode}) does not match the one this peer derived from the pair transcript (${derived2}) \u2014 refusing to pair (possible MITM between this MCP and the extension)`);
|
|
43499
|
+
ws.close(1008, "pair code mismatch");
|
|
43500
|
+
return;
|
|
43501
|
+
}
|
|
43502
|
+
pendingPairCode = derived2;
|
|
43503
|
+
pendingPairListeners.forEach((cb) => cb(derived2));
|
|
42888
43504
|
return;
|
|
42889
43505
|
}
|
|
42890
43506
|
if (frame.type === "frame" && frame.mcpId === opts.mcpId) {
|
|
42891
|
-
|
|
43507
|
+
const inboundSession = session;
|
|
43508
|
+
if (!inboundSession)
|
|
42892
43509
|
return;
|
|
42893
|
-
|
|
43510
|
+
const claim2 = inboundSession.claimInboundSeq(frame.seq);
|
|
43511
|
+
if (inboundSession.saturationWarningDue(claim2)) {
|
|
43512
|
+
console.warn(`[fetchproxy] ${opts.serverName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
|
|
43513
|
+
}
|
|
43514
|
+
if (claim2 !== "ok")
|
|
42894
43515
|
return;
|
|
42895
|
-
|
|
43516
|
+
let result;
|
|
43517
|
+
try {
|
|
43518
|
+
result = await openEncryptedFrameDetailed(inboundSession.sessionKey, frame, "e2s");
|
|
43519
|
+
} catch (e) {
|
|
43520
|
+
inboundSession.releaseInboundSeq(frame.seq);
|
|
43521
|
+
throw e;
|
|
43522
|
+
}
|
|
43523
|
+
if (result.stage !== "decrypt-failed")
|
|
43524
|
+
inboundSession.commitInboundSeq(frame.seq);
|
|
43525
|
+
else
|
|
43526
|
+
inboundSession.releaseInboundSeq(frame.seq);
|
|
42896
43527
|
if (result.stage === "ok") {
|
|
42897
43528
|
innerListeners.forEach((cb) => cb(result.inner));
|
|
42898
43529
|
} else if (result.stage === "decrypt-failed") {
|
|
@@ -42909,12 +43540,25 @@ async function startPeer(opts) {
|
|
|
42909
43540
|
}
|
|
42910
43541
|
}
|
|
42911
43542
|
} catch (e) {
|
|
42912
|
-
|
|
43543
|
+
const peek = peekHelloVersion(raw);
|
|
43544
|
+
const mismatch = peek && peek.protocolVersion !== PROTOCOL_VERSION ? new FetchproxyProtocolVersionError({
|
|
43545
|
+
ourVersion: PROTOCOL_VERSION,
|
|
43546
|
+
theirVersion: peek.protocolVersion,
|
|
43547
|
+
peer: peek.mcpId === null ? "extension" : "mcp"
|
|
43548
|
+
}) : null;
|
|
43549
|
+
if (mismatch)
|
|
43550
|
+
console.warn(`[fetchproxy] ${opts.serverName}: ${mismatch.message}`);
|
|
43551
|
+
rejectFirstReady(mismatch ?? (e instanceof Error ? e : new Error(String(e))));
|
|
42913
43552
|
}
|
|
42914
43553
|
};
|
|
42915
43554
|
ws.on("message", onMessage);
|
|
42916
43555
|
ws.once("close", () => {
|
|
42917
43556
|
rejectFirstReady(new Error("peer WS closed before ready"));
|
|
43557
|
+
dropSessionEphemeral();
|
|
43558
|
+
if (bootstrapPriv) {
|
|
43559
|
+
bootstrapPriv.fill(0);
|
|
43560
|
+
bootstrapPriv = null;
|
|
43561
|
+
}
|
|
42918
43562
|
closeListeners.forEach((cb) => cb());
|
|
42919
43563
|
});
|
|
42920
43564
|
sessionPromise.catch(() => {
|
|
@@ -42928,7 +43572,8 @@ async function startPeer(opts) {
|
|
|
42928
43572
|
pendingPairCode: () => pendingPairCode
|
|
42929
43573
|
});
|
|
42930
43574
|
const s = session;
|
|
42931
|
-
const
|
|
43575
|
+
const plaintext = encodeOutboundInnerFrame(opts.mcpId, inner);
|
|
43576
|
+
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), plaintext, "s2e");
|
|
42932
43577
|
ws.send(JSON.stringify(sealed));
|
|
42933
43578
|
},
|
|
42934
43579
|
onInner: (cb) => {
|
|
@@ -43119,6 +43764,12 @@ function protocolErrorFrom(error62, op) {
|
|
|
43119
43764
|
return new FetchproxyNoTabError(error62);
|
|
43120
43765
|
return new FetchproxyProtocolError(error62);
|
|
43121
43766
|
}
|
|
43767
|
+
var VERB_DEADLINE_GRACE_MS = 15e3;
|
|
43768
|
+
function verbDeadlineMs(transportMs, requestedMs, graceMs = VERB_DEADLINE_GRACE_MS) {
|
|
43769
|
+
if (requestedMs === void 0)
|
|
43770
|
+
return transportMs;
|
|
43771
|
+
return Math.max(requestedMs + graceMs, transportMs);
|
|
43772
|
+
}
|
|
43122
43773
|
var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
43123
43774
|
url;
|
|
43124
43775
|
timeoutMs;
|
|
@@ -43137,16 +43788,20 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
43137
43788
|
*/
|
|
43138
43789
|
retryAttempted;
|
|
43139
43790
|
/**
|
|
43140
|
-
* 2.4.2+ (#277): what the CALL asked for,
|
|
43141
|
-
*
|
|
43142
|
-
*
|
|
43143
|
-
*
|
|
43144
|
-
*
|
|
43791
|
+
* 2.4.2+ (#277): what the CALL asked for, whenever it asked for anything.
|
|
43792
|
+
*
|
|
43793
|
+
* Until #237 this was recorded only when the call asked for MORE than the
|
|
43794
|
+
* deadline, because asking for more is what the cap silently ignored. The
|
|
43795
|
+
* cap is gone — the call's own `timeoutMs` now GOVERNS its deadline
|
|
43796
|
+
* (`verbDeadlineMs`) — so the interesting case inverted: a timeout here
|
|
43797
|
+
* means the bridge did not answer within the window the CALLER chose, and
|
|
43798
|
+
* the message says which number that was and where it came from.
|
|
43145
43799
|
*/
|
|
43146
43800
|
requestedTimeoutMs;
|
|
43147
43801
|
constructor(args) {
|
|
43148
|
-
const
|
|
43149
|
-
|
|
43802
|
+
const graceMs = args.graceMs ?? VERB_DEADLINE_GRACE_MS;
|
|
43803
|
+
const callerChose = args.requestedTimeoutMs !== void 0 && args.timeoutMs === args.requestedTimeoutMs + graceMs;
|
|
43804
|
+
super(`fetchproxy: ${args.url} did not respond within ${args.timeoutMs}ms` + (callerChose ? ` \u2014 the ${args.requestedTimeoutMs}ms this call asked for, plus ${graceMs}ms for the reply to reach this server. The extension runs its own timer on the value you passed, so a window that is genuinely too short reports what did not happen rather than this.` : ""));
|
|
43150
43805
|
this.name = "FetchproxyTimeoutError";
|
|
43151
43806
|
this.url = args.url;
|
|
43152
43807
|
this.timeoutMs = args.timeoutMs;
|
|
@@ -43279,6 +43934,11 @@ var FetchproxyServer = class {
|
|
|
43279
43934
|
if (!Array.isArray(opts.domains) || opts.domains.length === 0) {
|
|
43280
43935
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
43281
43936
|
}
|
|
43937
|
+
for (const d of opts.domains) {
|
|
43938
|
+
if (typeof d === "string" && isPublicSuffix(d)) {
|
|
43939
|
+
throw new Error(`FetchproxyServer: opts.domains entry ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
43940
|
+
}
|
|
43941
|
+
}
|
|
43282
43942
|
let capabilities;
|
|
43283
43943
|
if (opts.capabilities === void 0) {
|
|
43284
43944
|
capabilities = ["fetch"];
|
|
@@ -43344,6 +44004,7 @@ var FetchproxyServer = class {
|
|
|
43344
44004
|
// back-door is `0` (explicit opt-out) if a caller genuinely wants
|
|
43345
44005
|
// the legacy hang-forever / fail-once-on-SW-eviction behavior.
|
|
43346
44006
|
fetchTimeoutMs: opts.fetchTimeoutMs ?? 3e4,
|
|
44007
|
+
verbDeadlineGraceMs: opts.verbDeadlineGraceMs ?? VERB_DEADLINE_GRACE_MS,
|
|
43347
44008
|
bridgeReviveDelayMs: opts.bridgeReviveDelayMs ?? 2e3,
|
|
43348
44009
|
// 0.10.0+ (#72): keep-alive defaults to 25s — round-3 #71 cohort
|
|
43349
44010
|
// wave showed every Pattern A consumer was opting into this same
|
|
@@ -43362,6 +44023,7 @@ var FetchproxyServer = class {
|
|
|
43362
44023
|
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? 2e4,
|
|
43363
44024
|
keepAliveMaxIdleMs: opts.keepAliveMaxIdleMs ?? 5 * 60 * 1e3,
|
|
43364
44025
|
identityDir: opts.identityDir,
|
|
44026
|
+
trustDir: opts.trustDir,
|
|
43365
44027
|
allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
|
|
43366
44028
|
requireExtensionIdentity: opts.requireExtensionIdentity,
|
|
43367
44029
|
onPairCode: opts.onPairCode
|
|
@@ -43638,7 +44300,9 @@ var FetchproxyServer = class {
|
|
|
43638
44300
|
}
|
|
43639
44301
|
/**
|
|
43640
44302
|
* #208: this MCP's pin on the extension's identity, stored beside its own
|
|
43641
|
-
* identity key and so following `identityDir` wherever the caller put it
|
|
44303
|
+
* identity key and so following `identityDir` wherever the caller put it —
|
|
44304
|
+
* unless `trustDir` (or `FETCHPROXY_TRUST_DIR`) says otherwise, which is
|
|
44305
|
+
* what a host that provisions the identity read-only has to say.
|
|
43642
44306
|
*
|
|
43643
44307
|
* `allowNewExtensionIdentity` falls back to an environment variable when the
|
|
43644
44308
|
* caller expressed no opinion, because the thirteen MCPs that construct this
|
|
@@ -43650,6 +44314,7 @@ var FetchproxyServer = class {
|
|
|
43650
44314
|
return fileExtensionTrust({
|
|
43651
44315
|
serverName: this.opts.serverName,
|
|
43652
44316
|
dir: this.opts.identityDir,
|
|
44317
|
+
trustDir: this.opts.trustDir,
|
|
43653
44318
|
allowNew: allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity)
|
|
43654
44319
|
});
|
|
43655
44320
|
}
|
|
@@ -43812,9 +44477,11 @@ var FetchproxyServer = class {
|
|
|
43812
44477
|
return pending;
|
|
43813
44478
|
}
|
|
43814
44479
|
async _withVerbTimeout(pending, pendingMap, id, url2, requestedTimeoutMs) {
|
|
43815
|
-
const
|
|
43816
|
-
if (
|
|
44480
|
+
const transportMs = this.opts.fetchTimeoutMs;
|
|
44481
|
+
if (transportMs === void 0 || transportMs <= 0)
|
|
43817
44482
|
return pending;
|
|
44483
|
+
const graceMs = this.opts.verbDeadlineGraceMs;
|
|
44484
|
+
const timeoutMs = verbDeadlineMs(transportMs, requestedTimeoutMs, graceMs);
|
|
43818
44485
|
let timer;
|
|
43819
44486
|
const start = Date.now();
|
|
43820
44487
|
try {
|
|
@@ -43830,6 +44497,7 @@ var FetchproxyServer = class {
|
|
|
43830
44497
|
port: this.opts.port,
|
|
43831
44498
|
elapsedMs: Date.now() - start,
|
|
43832
44499
|
retryAttempted: false,
|
|
44500
|
+
graceMs,
|
|
43833
44501
|
...requestedTimeoutMs !== void 0 ? { requestedTimeoutMs } : {}
|
|
43834
44502
|
}));
|
|
43835
44503
|
}, timeoutMs);
|
|
@@ -43890,9 +44558,9 @@ var FetchproxyServer = class {
|
|
|
43890
44558
|
if (opts.subdomain !== void 0)
|
|
43891
44559
|
assertSubdomainLabel(opts.subdomain);
|
|
43892
44560
|
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
43893
|
-
const
|
|
44561
|
+
const isAbsolute4 = path.startsWith("http://") || path.startsWith("https://");
|
|
43894
44562
|
let host;
|
|
43895
|
-
if (
|
|
44563
|
+
if (isAbsolute4) {
|
|
43896
44564
|
try {
|
|
43897
44565
|
host = new URL(path).host;
|
|
43898
44566
|
} catch {
|
|
@@ -43901,7 +44569,7 @@ var FetchproxyServer = class {
|
|
|
43901
44569
|
} else {
|
|
43902
44570
|
host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
43903
44571
|
}
|
|
43904
|
-
const url2 =
|
|
44572
|
+
const url2 = isAbsolute4 ? path : `https://${host}${path}`;
|
|
43905
44573
|
assertUrlInDomains("request url", url2, this.opts.domains);
|
|
43906
44574
|
let tabUrl = `https://${host}/`;
|
|
43907
44575
|
if (opts.viaTab !== void 0) {
|
|
@@ -44390,8 +45058,9 @@ var FetchproxyServer = class {
|
|
|
44390
45058
|
this.pendingCapture,
|
|
44391
45059
|
id,
|
|
44392
45060
|
`https://${opts.host}${opts.path ?? "/*"}`,
|
|
44393
|
-
// #
|
|
44394
|
-
//
|
|
45061
|
+
// #237: this GOVERNS the deadline (window + reply grace, floored at the
|
|
45062
|
+
// transport bound). Under #277 it did not, and was passed only so the
|
|
45063
|
+
// timeout could name the number that actually fired.
|
|
44395
45064
|
opts.timeoutMs
|
|
44396
45065
|
);
|
|
44397
45066
|
}
|
|
@@ -44483,8 +45152,9 @@ var FetchproxyServer = class {
|
|
|
44483
45152
|
this.pendingRedirect,
|
|
44484
45153
|
id,
|
|
44485
45154
|
`https://${opts.host}${opts.path ?? "/*"}`,
|
|
44486
|
-
// #
|
|
44487
|
-
//
|
|
45155
|
+
// #237: this GOVERNS the deadline (window + reply grace, floored at the
|
|
45156
|
+
// transport bound). Under #277 it did not, and was passed only so the
|
|
45157
|
+
// timeout could name the number that actually fired.
|
|
44488
45158
|
opts.timeoutMs
|
|
44489
45159
|
);
|
|
44490
45160
|
}
|
|
@@ -44573,10 +45243,11 @@ var FetchproxyServer = class {
|
|
|
44573
45243
|
this.pendingDownload,
|
|
44574
45244
|
id,
|
|
44575
45245
|
opts.url,
|
|
44576
|
-
//
|
|
44577
|
-
//
|
|
44578
|
-
//
|
|
44579
|
-
// counted off the two call sites in view
|
|
45246
|
+
// `download` takes a per-call timeoutMs like the two capture verbs, so
|
|
45247
|
+
// it is governed by it like them (#237). It used to hit the identical
|
|
45248
|
+
// CAP and need the identical explanation; missing it then was the point
|
|
45249
|
+
// of #279 — "both verbs" was counted off the two call sites in view
|
|
45250
|
+
// rather than off the type. Three is still the number.
|
|
44580
45251
|
opts.timeoutMs
|
|
44581
45252
|
);
|
|
44582
45253
|
}
|
|
@@ -44950,11 +45621,21 @@ var FetchproxyServer = class {
|
|
|
44950
45621
|
/**
|
|
44951
45622
|
* 2.5.0: the extension link as {@link BridgeSessionState}, read off
|
|
44952
45623
|
* whichever handle is live. A pending pair code outranks "extension not
|
|
44953
|
-
* seen" because
|
|
44954
|
-
*
|
|
44955
|
-
* pair-pending frames. Where the extension is KNOWN to be gone (a host's
|
|
45624
|
+
* seen" because a code could only have come from a `pair-pending` frame
|
|
45625
|
+
* the extension sent. Where the extension is KNOWN to be gone (a host's
|
|
44956
45626
|
* socket closed, a peer told by a 2.5.0+ host) the handle has already
|
|
44957
45627
|
* dropped the code, so the precedence never lies there (#283).
|
|
45628
|
+
*
|
|
45629
|
+
* M1 (bridge review 2026-09-10): a code is now only ever present when the
|
|
45630
|
+
* handle DERIVED it from both identity pubs, and both handles set and clear
|
|
45631
|
+
* that derivation in lockstep with the extension hello — so the precedence
|
|
45632
|
+
* has nothing left to decide: a pair code implies the hello, which implies
|
|
45633
|
+
* `extensionConnected`. A peer behind a pre-1.12.0 host is relayed no
|
|
45634
|
+
* extension hello, cannot derive, and therefore reports
|
|
45635
|
+
* `extension_disconnected` with a null code where it used to report the
|
|
45636
|
+
* wire's number as `pair_pending` — the honest state for a peer that has
|
|
45637
|
+
* never been told an extension is there. That is the trade: a number nobody
|
|
45638
|
+
* can check is worse than no number.
|
|
44958
45639
|
*/
|
|
44959
45640
|
sessionSnapshot() {
|
|
44960
45641
|
const handle = this.hostHandle ?? this.peerHandle;
|
|
@@ -45004,6 +45685,7 @@ var FetchproxyServer = class {
|
|
|
45004
45685
|
|
|
45005
45686
|
// node_modules/@fetchproxy/bootstrap/dist/index.js
|
|
45006
45687
|
var defaultFactory = (opts) => new FetchproxyServer(opts);
|
|
45688
|
+
var CAPTURE_BOOTSTRAP_DEADLINE_MS = 45e3;
|
|
45007
45689
|
async function bootstrap(opts) {
|
|
45008
45690
|
return runOneLift(opts);
|
|
45009
45691
|
}
|
|
@@ -45062,7 +45744,25 @@ async function runOneLift(opts) {
|
|
|
45062
45744
|
onPairCode: opts.onPairCode,
|
|
45063
45745
|
// 0.8.0+ pass-through. Only forwarded when the caller set them;
|
|
45064
45746
|
// unset → server defaults apply (30000 / 2000 in 0.8.0).
|
|
45065
|
-
|
|
45747
|
+
//
|
|
45748
|
+
// EXCEPT when captures are declared and the caller named no deadline of
|
|
45749
|
+
// their own, where the default is a tie this side loses. `bootstrap` asks
|
|
45750
|
+
// for no per-call `timeoutMs` below, so the capture runs for the
|
|
45751
|
+
// EXTENSION's 30 s default — against a transport deadline that is also
|
|
45752
|
+
// 30 s. Two timers armed for the same instant, and this one starts first,
|
|
45753
|
+
// because its frame has yet to travel.
|
|
45754
|
+
//
|
|
45755
|
+
// The timing was never the problem; the MESSAGE was. Winning that race
|
|
45756
|
+
// replaces the extension's rejection — which explains that a capture
|
|
45757
|
+
// resolves on the next request the PAGE makes, so an idle tab times out —
|
|
45758
|
+
// with a bare `did not respond within 30000ms`, naming a number the caller
|
|
45759
|
+
// never chose about a mechanism it never mentions. Twelve MCPs reach
|
|
45760
|
+
// captures only through here, so they all inherited it.
|
|
45761
|
+
//
|
|
45762
|
+
// Only when the caller set nothing: an explicit `fetchTimeoutMs` is a
|
|
45763
|
+
// deliberate bound and stays exactly as given, even though it re-loses the
|
|
45764
|
+
// race. Bounding is theirs to decide; this only fills a default.
|
|
45765
|
+
...opts.fetchTimeoutMs !== void 0 ? { fetchTimeoutMs: opts.fetchTimeoutMs } : opts.declare.captureHeaders.length > 0 ? { fetchTimeoutMs: CAPTURE_BOOTSTRAP_DEADLINE_MS } : {},
|
|
45066
45766
|
...opts.bridgeReviveDelayMs !== void 0 ? { bridgeReviveDelayMs: opts.bridgeReviveDelayMs } : {}
|
|
45067
45767
|
});
|
|
45068
45768
|
const storageDomainOpts = {};
|
|
@@ -45388,7 +46088,7 @@ async function loginWithPassword(username, password) {
|
|
|
45388
46088
|
// package.json
|
|
45389
46089
|
var package_default = {
|
|
45390
46090
|
name: "ofw-mcp",
|
|
45391
|
-
version: "2.16.
|
|
46091
|
+
version: "2.16.3",
|
|
45392
46092
|
license: "MIT",
|
|
45393
46093
|
mcpName: "io.github.chrischall/ofw-mcp",
|
|
45394
46094
|
description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
|
|
@@ -45423,7 +46123,7 @@ var package_default = {
|
|
|
45423
46123
|
},
|
|
45424
46124
|
dependencies: {
|
|
45425
46125
|
"@chrischall/mcp-utils": "^0.27.1",
|
|
45426
|
-
"@fetchproxy/bootstrap": "^
|
|
46126
|
+
"@fetchproxy/bootstrap": "^3.0.1",
|
|
45427
46127
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
45428
46128
|
dotenv: "^17.4.2",
|
|
45429
46129
|
zod: "^4.5.4"
|
|
@@ -50374,7 +51074,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
|
|
|
50374
51074
|
var nodeAttachmentIO = new NodeAttachmentIO();
|
|
50375
51075
|
await runMcp({
|
|
50376
51076
|
name: "ofw",
|
|
50377
|
-
version: "2.16.
|
|
51077
|
+
version: "2.16.3",
|
|
50378
51078
|
// x-release-please-version
|
|
50379
51079
|
deps: client,
|
|
50380
51080
|
tools: [
|