ofw-mcp 2.19.4 → 2.19.5
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/README.md +3 -3
- package/dist/auth.js +4 -4
- package/dist/bundle.js +722 -133
- package/dist/index.js +1 -1
- package/dist/tools/delivery.js +42 -2
- package/dist/tools/healthcheck.js +2 -2
- package/dist/tools/messages.js +2 -1
- package/package.json +3 -3
- package/server.json +2 -2
- package/skills/ofw/SKILL.md +1 -1
- package/skills/ofw-fpx/SKILL.md +5 -2
package/dist/bundle.js
CHANGED
|
@@ -23487,7 +23487,7 @@ function date4(params) {
|
|
|
23487
23487
|
return _coercedDate(ZodDate, params);
|
|
23488
23488
|
}
|
|
23489
23489
|
|
|
23490
|
-
// node_modules/@modelcontextprotocol/core/dist/auth-
|
|
23490
|
+
// node_modules/@modelcontextprotocol/core/dist/auth-CGP0BDVq.mjs
|
|
23491
23491
|
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
23492
23492
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
23493
23493
|
LATEST_PROTOCOL_VERSION,
|
|
@@ -24324,7 +24324,8 @@ var OAuthMetadataSchema = looseObject({
|
|
|
24324
24324
|
introspection_endpoint_auth_signing_alg_values_supported: array(string2()).optional(),
|
|
24325
24325
|
code_challenge_methods_supported: array(string2()).optional(),
|
|
24326
24326
|
client_id_metadata_document_supported: boolean2().optional(),
|
|
24327
|
-
authorization_response_iss_parameter_supported: boolean2().optional().catch(void 0)
|
|
24327
|
+
authorization_response_iss_parameter_supported: boolean2().optional().catch(void 0),
|
|
24328
|
+
dpop_signing_alg_values_supported: array(string2()).optional()
|
|
24328
24329
|
});
|
|
24329
24330
|
var OpenIdProviderMetadataSchema = looseObject({
|
|
24330
24331
|
issuer: string2(),
|
|
@@ -24425,7 +24426,7 @@ var OAuthTokenRevocationRequestSchema = object({
|
|
|
24425
24426
|
token_type_hint: string2().optional()
|
|
24426
24427
|
}).strip();
|
|
24427
24428
|
|
|
24428
|
-
// node_modules/@modelcontextprotocol/server/dist/src-
|
|
24429
|
+
// node_modules/@modelcontextprotocol/server/dist/src-D-y6h4N7.mjs
|
|
24429
24430
|
var BRANDS = /* @__PURE__ */ Symbol.for("mcp.sdk.errorBrands");
|
|
24430
24431
|
function stampErrorBrands(instance, ctor) {
|
|
24431
24432
|
const brands = /* @__PURE__ */ new Set();
|
|
@@ -24539,8 +24540,18 @@ var SdkError = class extends Error {
|
|
|
24539
24540
|
if (typeof this !== "function") throw new TypeError("isInstance must be called on the class (e.g. `SdkError.isInstance(value)`); for callbacks use `v => SdkError.isInstance(v)`");
|
|
24540
24541
|
return brandedHasInstance(this, value);
|
|
24541
24542
|
}
|
|
24542
|
-
|
|
24543
|
-
|
|
24543
|
+
/**
|
|
24544
|
+
* @param code - Stable string code identifying the failure ({@linkcode SdkErrorCode}).
|
|
24545
|
+
* @param message - Human-readable description.
|
|
24546
|
+
* @param data - Optional structured payload (for example the HTTP status carried by
|
|
24547
|
+
* {@linkcode SdkHttpError}). Opaque to the SDK: a `cause` key inside `data` is not
|
|
24548
|
+
* promoted to `Error.cause`.
|
|
24549
|
+
* @param options - Standard `ErrorOptions`, forwarded to `Error`. Pass the underlying
|
|
24550
|
+
* failure as `{ cause }` so it is reachable through the `Error.cause` chain that
|
|
24551
|
+
* loggers and error trackers walk.
|
|
24552
|
+
*/
|
|
24553
|
+
constructor(code, message, data, options) {
|
|
24554
|
+
super(message, options);
|
|
24544
24555
|
this.code = code;
|
|
24545
24556
|
this.data = data;
|
|
24546
24557
|
this.name = "SdkError";
|
|
@@ -24551,8 +24562,11 @@ var SdkHttpError = class extends SdkError {
|
|
|
24551
24562
|
static {
|
|
24552
24563
|
Object.defineProperty(this, "mcpBrand", { value: "mcp.SdkHttpError" });
|
|
24553
24564
|
}
|
|
24554
|
-
|
|
24555
|
-
|
|
24565
|
+
/**
|
|
24566
|
+
* @param options - Standard `ErrorOptions`, forwarded to `Error` (see {@linkcode SdkError}).
|
|
24567
|
+
*/
|
|
24568
|
+
constructor(code, message, data, options) {
|
|
24569
|
+
super(code, message, data, options);
|
|
24556
24570
|
this.name = "SdkHttpError";
|
|
24557
24571
|
}
|
|
24558
24572
|
get status() {
|
|
@@ -27503,7 +27517,7 @@ var INBOUND_VALIDATION_LADDER = [
|
|
|
27503
27517
|
evaluatedAt: "pre-dispatch",
|
|
27504
27518
|
codes: [HEADER_MISMATCH_ERROR_CODE],
|
|
27505
27519
|
conformance: ["http-header-validation"],
|
|
27506
|
-
rationale: "SEP-2243 standard `Mcp-Method` / `Mcp-Name` headers \u2014 presence, sentinel decoding, and `Mcp-Name` \u2194 body cross-check \u2014 are validated by the HTTP entry on a modern-classified request after the supported-revision gate and before dispatch. The classifier\u2019s own header-mismatch cells (protocol-version, `Mcp-Method` mismatch) stay on the edge `era-classification` rung; this rung carries the entry-layer presence/`Mcp-Name` half. Evaluated before the capability gate, the factory call, and the `Mcp-Param-*` rung so a request that fails several rungs is answered by the standard-header rung first. The documented order (after method-registry 5 and request-params 6) is NOT the observed precedence: serveModern evaluates this rung immediately after the supported-revision gate, so a request that also fails a dispatch rung is answered here before the dispatch rungs (5\u20136) are consulted."
|
|
27520
|
+
rationale: "SEP-2243 standard `MCP-Protocol-Version` / `Mcp-Method` / `Mcp-Name` headers \u2014 presence, sentinel decoding, and `Mcp-Name` \u2194 body cross-check \u2014 are validated by the HTTP entry on a modern-classified request after the supported-revision gate and before dispatch. The spec requires `MCP-Protocol-Version` and `Mcp-Method` on every modern *request* POST (`Mcp-Name` only for the methods that mirror `params.name` / `params.uri` / `params.taskId`, see `MCP_NAME_HEADER_SOURCE`) and names them in that order, so a request missing several is answered by the earliest. Notification POSTs are exempt: the presence half runs on requests only, so a modern-enveloped notification is dispatched even with no standard headers at all. The classifier\u2019s own header-mismatch cells (protocol-version, `Mcp-Method` mismatch) stay on the edge `era-classification` rung; this rung carries the entry-layer presence/`Mcp-Name` half \u2014 including the missing `MCP-Protocol-Version` cell, which cannot live on the edge rung without breaking body-primary classification. Evaluated before the capability gate, the factory call, and the `Mcp-Param-*` rung so a request that fails several rungs is answered by the standard-header rung first. The documented order (after method-registry 5 and request-params 6) is NOT the observed precedence: serveModern evaluates this rung immediately after the supported-revision gate, so a request that also fails a dispatch rung is answered here before the dispatch rungs (5\u20136) are consulted."
|
|
27507
27521
|
},
|
|
27508
27522
|
{
|
|
27509
27523
|
rung: "client-capabilities",
|
|
@@ -28304,7 +28318,7 @@ var Protocol = class {
|
|
|
28304
28318
|
return this._requestHandlers.get(method);
|
|
28305
28319
|
}
|
|
28306
28320
|
async _oncancel(notification) {
|
|
28307
|
-
if (
|
|
28321
|
+
if (notification.params.requestId === void 0) return;
|
|
28308
28322
|
this._requestHandlerAbortControllers.get(notification.params.requestId)?.abort(notification.params.reason);
|
|
28309
28323
|
}
|
|
28310
28324
|
_setupTimeout(messageId, timeout, maxTotalTimeout, onTimeout, resetTimeoutOnProgress = false) {
|
|
@@ -28703,19 +28717,20 @@ var Protocol = class {
|
|
|
28703
28717
|
const cancel = (reason) => {
|
|
28704
28718
|
if (responseReceived) return;
|
|
28705
28719
|
this._progressHandlers.delete(messageId);
|
|
28706
|
-
if (requestAbort === void 0)
|
|
28707
|
-
|
|
28708
|
-
|
|
28709
|
-
|
|
28710
|
-
|
|
28711
|
-
|
|
28712
|
-
|
|
28713
|
-
|
|
28714
|
-
|
|
28715
|
-
|
|
28716
|
-
|
|
28717
|
-
|
|
28718
|
-
|
|
28720
|
+
if (requestAbort === void 0) {
|
|
28721
|
+
if (request.method !== "initialize") this._transport?.send(this._envelopeOutbound({
|
|
28722
|
+
jsonrpc: "2.0",
|
|
28723
|
+
method: "notifications/cancelled",
|
|
28724
|
+
params: {
|
|
28725
|
+
requestId: messageId,
|
|
28726
|
+
reason: String(reason)
|
|
28727
|
+
}
|
|
28728
|
+
}), {
|
|
28729
|
+
relatedRequestId,
|
|
28730
|
+
resumptionToken,
|
|
28731
|
+
onresumptiontoken
|
|
28732
|
+
}).catch((error62) => this._onerror(/* @__PURE__ */ new Error(`Failed to send cancellation: ${error62}`)));
|
|
28733
|
+
} else requestAbort.abort();
|
|
28719
28734
|
reject(reason instanceof SdkError ? reason : new SdkError(SdkErrorCode.RequestTimeout, String(reason)));
|
|
28720
28735
|
};
|
|
28721
28736
|
this._responseHandlers.set(messageId, (response) => {
|
|
@@ -28795,7 +28810,7 @@ var Protocol = class {
|
|
|
28795
28810
|
jsonrpc: "2.0",
|
|
28796
28811
|
...notification
|
|
28797
28812
|
});
|
|
28798
|
-
if ((this._options?.debouncedNotificationMethods ?? []).includes(notification.method) && !notification.params &&
|
|
28813
|
+
if ((this._options?.debouncedNotificationMethods ?? []).includes(notification.method) && !notification.params && options?.relatedRequestId === void 0) {
|
|
28799
28814
|
if (this._pendingDebouncedNotifications.has(notification.method)) return;
|
|
28800
28815
|
this._pendingDebouncedNotifications.add(notification.method);
|
|
28801
28816
|
Promise.resolve().then(() => {
|
|
@@ -36098,7 +36113,7 @@ var Ajv = import_ajv.Ajv;
|
|
|
36098
36113
|
// node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
|
|
36099
36114
|
import process2 from "node:process";
|
|
36100
36115
|
|
|
36101
|
-
// node_modules/@modelcontextprotocol/server/dist/mcp-
|
|
36116
|
+
// node_modules/@modelcontextprotocol/server/dist/mcp-Dw2OlZ1f.mjs
|
|
36102
36117
|
var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
|
|
36103
36118
|
function isCompletable(schema) {
|
|
36104
36119
|
return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
|
|
@@ -36291,6 +36306,9 @@ function notificationToServerEvent(method, uri) {
|
|
|
36291
36306
|
};
|
|
36292
36307
|
}
|
|
36293
36308
|
}
|
|
36309
|
+
function supportsScopeChallengeResolver(transport) {
|
|
36310
|
+
return typeof transport === "object" && transport !== null && "setScopeChallengeResolver" in transport && typeof transport.setScopeChallengeResolver === "function";
|
|
36311
|
+
}
|
|
36294
36312
|
var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
|
|
36295
36313
|
var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
|
|
36296
36314
|
function resolveLegacyShimOptions(options) {
|
|
@@ -37101,6 +37119,7 @@ var McpServer = class {
|
|
|
37101
37119
|
* ```
|
|
37102
37120
|
*/
|
|
37103
37121
|
async connect(transport) {
|
|
37122
|
+
if (supportsScopeChallengeResolver(transport)) transport.setScopeChallengeResolver((context) => this.resolveScopeChallenge(context));
|
|
37104
37123
|
return await this.server.connect(transport);
|
|
37105
37124
|
}
|
|
37106
37125
|
/**
|
|
@@ -37109,6 +37128,41 @@ var McpServer = class {
|
|
|
37109
37128
|
async close() {
|
|
37110
37129
|
await this.server.close();
|
|
37111
37130
|
}
|
|
37131
|
+
/** @internal */
|
|
37132
|
+
resolveScopeChallenge = (context) => {
|
|
37133
|
+
switch (context.request.method) {
|
|
37134
|
+
case "tools/call": {
|
|
37135
|
+
const toolName = context.request.params?.name;
|
|
37136
|
+
if (typeof toolName !== "string") return;
|
|
37137
|
+
const tool = this._registeredTools[toolName];
|
|
37138
|
+
if (tool === void 0 || !tool.enabled) return;
|
|
37139
|
+
return tool.scopeChallenge?.(context);
|
|
37140
|
+
}
|
|
37141
|
+
case "resources/read": {
|
|
37142
|
+
const resourceUri = context.request.params?.uri;
|
|
37143
|
+
if (typeof resourceUri !== "string") return;
|
|
37144
|
+
let uri;
|
|
37145
|
+
try {
|
|
37146
|
+
uri = new URL(resourceUri);
|
|
37147
|
+
} catch {
|
|
37148
|
+
return;
|
|
37149
|
+
}
|
|
37150
|
+
const resource = this._registeredResources[uri.toString()];
|
|
37151
|
+
if (resource !== void 0) return resource.enabled ? resource.scopeChallenge?.(context) : void 0;
|
|
37152
|
+
for (const template of Object.values(this._registeredResourceTemplates)) if (template.resourceTemplate.uriTemplate.match(uri.toString())) return template.enabled ? template.scopeChallenge?.(context) : void 0;
|
|
37153
|
+
return;
|
|
37154
|
+
}
|
|
37155
|
+
case "prompts/get": {
|
|
37156
|
+
const promptName = context.request.params?.name;
|
|
37157
|
+
if (typeof promptName !== "string") return;
|
|
37158
|
+
const prompt = this._registeredPrompts[promptName];
|
|
37159
|
+
if (prompt === void 0 || !prompt.enabled) return;
|
|
37160
|
+
return prompt.scopeChallenge?.(context);
|
|
37161
|
+
}
|
|
37162
|
+
default:
|
|
37163
|
+
return;
|
|
37164
|
+
}
|
|
37165
|
+
};
|
|
37112
37166
|
_toolHandlersInitialized = false;
|
|
37113
37167
|
setToolRequestHandlers() {
|
|
37114
37168
|
if (this._toolHandlersInitialized) return;
|
|
@@ -37275,7 +37329,10 @@ var McpServer = class {
|
|
|
37275
37329
|
}
|
|
37276
37330
|
for (const template of Object.values(this._registeredResourceTemplates)) {
|
|
37277
37331
|
const variables = template.resourceTemplate.uriTemplate.match(uri.toString());
|
|
37278
|
-
if (variables)
|
|
37332
|
+
if (variables) {
|
|
37333
|
+
if (!template.enabled) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource template ${template.resourceTemplate.uriTemplate} disabled`);
|
|
37334
|
+
return attachCacheHintFallback(await template.readCallback(uri, variables, ctx), template.cacheHint);
|
|
37335
|
+
}
|
|
37279
37336
|
}
|
|
37280
37337
|
throw new ResourceNotFoundError(request.params.uri);
|
|
37281
37338
|
});
|
|
@@ -37306,36 +37363,32 @@ var McpServer = class {
|
|
|
37306
37363
|
this._promptHandlersInitialized = true;
|
|
37307
37364
|
}
|
|
37308
37365
|
registerResource(name, uriOrTemplate, config2, readCallback) {
|
|
37309
|
-
const cacheHint = config2
|
|
37310
|
-
|
|
37311
|
-
if (cacheHint !== void 0) {
|
|
37312
|
-
assertValidCacheHint(cacheHint, `resource ${name}`);
|
|
37313
|
-
const rest = { ...config2 };
|
|
37314
|
-
delete rest.cacheHint;
|
|
37315
|
-
metadata = rest;
|
|
37316
|
-
}
|
|
37366
|
+
const { cacheHint, scopeChallenge, ...resourceMetadata } = config2;
|
|
37367
|
+
const metadata = resourceMetadata;
|
|
37368
|
+
if (cacheHint !== void 0) assertValidCacheHint(cacheHint, `resource ${name}`);
|
|
37317
37369
|
if (typeof uriOrTemplate === "string") {
|
|
37318
37370
|
if (this._registeredResources[uriOrTemplate]) throw new Error(`Resource ${uriOrTemplate} is already registered`);
|
|
37319
|
-
const registeredResource = this._createRegisteredResource(name, config2.title, uriOrTemplate, metadata, readCallback);
|
|
37371
|
+
const registeredResource = this._createRegisteredResource(name, config2.title, uriOrTemplate, metadata, scopeChallenge, readCallback);
|
|
37320
37372
|
if (cacheHint !== void 0) registeredResource.cacheHint = cacheHint;
|
|
37321
37373
|
this.setResourceRequestHandlers();
|
|
37322
37374
|
this.sendResourceListChanged();
|
|
37323
37375
|
return registeredResource;
|
|
37324
37376
|
} else {
|
|
37325
37377
|
if (this._registeredResourceTemplates[name]) throw new Error(`Resource template ${name} is already registered`);
|
|
37326
|
-
const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config2.title, uriOrTemplate, metadata, readCallback);
|
|
37378
|
+
const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config2.title, uriOrTemplate, metadata, scopeChallenge, readCallback);
|
|
37327
37379
|
if (cacheHint !== void 0) registeredResourceTemplate.cacheHint = cacheHint;
|
|
37328
37380
|
this.setResourceRequestHandlers();
|
|
37329
37381
|
this.sendResourceListChanged();
|
|
37330
37382
|
return registeredResourceTemplate;
|
|
37331
37383
|
}
|
|
37332
37384
|
}
|
|
37333
|
-
_createRegisteredResource(name, title, uri, metadata, readCallback) {
|
|
37385
|
+
_createRegisteredResource(name, title, uri, metadata, scopeChallenge, readCallback) {
|
|
37334
37386
|
const registeredResource = {
|
|
37335
37387
|
name,
|
|
37336
37388
|
title,
|
|
37337
37389
|
metadata,
|
|
37338
37390
|
readCallback,
|
|
37391
|
+
scopeChallenge,
|
|
37339
37392
|
enabled: true,
|
|
37340
37393
|
disable: () => registeredResource.update({ enabled: false }),
|
|
37341
37394
|
enable: () => registeredResource.update({ enabled: true }),
|
|
@@ -37349,6 +37402,7 @@ var McpServer = class {
|
|
|
37349
37402
|
if (updates.title !== void 0) registeredResource.title = updates.title;
|
|
37350
37403
|
if (updates.metadata !== void 0) registeredResource.metadata = updates.metadata;
|
|
37351
37404
|
if (updates.callback !== void 0) registeredResource.readCallback = updates.callback;
|
|
37405
|
+
if (updates.scopeChallenge !== void 0) registeredResource.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
37352
37406
|
if (updates.enabled !== void 0) registeredResource.enabled = updates.enabled;
|
|
37353
37407
|
this.sendResourceListChanged();
|
|
37354
37408
|
}
|
|
@@ -37356,12 +37410,13 @@ var McpServer = class {
|
|
|
37356
37410
|
this._registeredResources[uri] = registeredResource;
|
|
37357
37411
|
return registeredResource;
|
|
37358
37412
|
}
|
|
37359
|
-
_createRegisteredResourceTemplate(name, title, template, metadata, readCallback) {
|
|
37413
|
+
_createRegisteredResourceTemplate(name, title, template, metadata, scopeChallenge, readCallback) {
|
|
37360
37414
|
const registeredResourceTemplate = {
|
|
37361
37415
|
resourceTemplate: template,
|
|
37362
37416
|
title,
|
|
37363
37417
|
metadata,
|
|
37364
37418
|
readCallback,
|
|
37419
|
+
scopeChallenge,
|
|
37365
37420
|
enabled: true,
|
|
37366
37421
|
disable: () => registeredResourceTemplate.update({ enabled: false }),
|
|
37367
37422
|
enable: () => registeredResourceTemplate.update({ enabled: true }),
|
|
@@ -37375,6 +37430,7 @@ var McpServer = class {
|
|
|
37375
37430
|
if (updates.template !== void 0) registeredResourceTemplate.resourceTemplate = updates.template;
|
|
37376
37431
|
if (updates.metadata !== void 0) registeredResourceTemplate.metadata = updates.metadata;
|
|
37377
37432
|
if (updates.callback !== void 0) registeredResourceTemplate.readCallback = updates.callback;
|
|
37433
|
+
if (updates.scopeChallenge !== void 0) registeredResourceTemplate.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
37378
37434
|
if (updates.enabled !== void 0) registeredResourceTemplate.enabled = updates.enabled;
|
|
37379
37435
|
this.sendResourceListChanged();
|
|
37380
37436
|
}
|
|
@@ -37384,7 +37440,7 @@ var McpServer = class {
|
|
|
37384
37440
|
if (Array.isArray(variableNames) && variableNames.some((v) => !!template.completeCallback(v))) this.setCompletionRequestHandler();
|
|
37385
37441
|
return registeredResourceTemplate;
|
|
37386
37442
|
}
|
|
37387
|
-
_createRegisteredPrompt(name, title, description, argsSchema, callback, icons, _meta) {
|
|
37443
|
+
_createRegisteredPrompt(name, title, description, argsSchema, callback, icons, scopeChallenge, _meta) {
|
|
37388
37444
|
let currentArgsSchema = argsSchema;
|
|
37389
37445
|
let currentCallback = callback;
|
|
37390
37446
|
const registeredPrompt = {
|
|
@@ -37392,6 +37448,7 @@ var McpServer = class {
|
|
|
37392
37448
|
description,
|
|
37393
37449
|
argsSchema,
|
|
37394
37450
|
icons,
|
|
37451
|
+
scopeChallenge,
|
|
37395
37452
|
_meta,
|
|
37396
37453
|
handler: createPromptHandler(name, argsSchema, callback),
|
|
37397
37454
|
enabled: true,
|
|
@@ -37406,6 +37463,7 @@ var McpServer = class {
|
|
|
37406
37463
|
if (updates.title !== void 0) registeredPrompt.title = updates.title;
|
|
37407
37464
|
if (updates.description !== void 0) registeredPrompt.description = updates.description;
|
|
37408
37465
|
if (updates.icons !== void 0) registeredPrompt.icons = updates.icons;
|
|
37466
|
+
if (updates.scopeChallenge !== void 0) registeredPrompt.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
37409
37467
|
if (updates._meta !== void 0) registeredPrompt._meta = updates._meta;
|
|
37410
37468
|
let needsHandlerRegen = false;
|
|
37411
37469
|
if (updates.argsSchema !== void 0) {
|
|
@@ -37433,7 +37491,7 @@ var McpServer = class {
|
|
|
37433
37491
|
}
|
|
37434
37492
|
return registeredPrompt;
|
|
37435
37493
|
}
|
|
37436
|
-
_createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, _meta, handler) {
|
|
37494
|
+
_createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
|
|
37437
37495
|
validateAndWarnToolName(name);
|
|
37438
37496
|
if (inputSchema !== void 0) try {
|
|
37439
37497
|
const json2 = standardSchemaToJsonSchema(inputSchema, "input");
|
|
@@ -37452,6 +37510,7 @@ var McpServer = class {
|
|
|
37452
37510
|
annotations,
|
|
37453
37511
|
icons,
|
|
37454
37512
|
execution,
|
|
37513
|
+
scopeChallenge,
|
|
37455
37514
|
_meta,
|
|
37456
37515
|
handler,
|
|
37457
37516
|
executor: createToolExecutor(inputSchema, handler),
|
|
@@ -37490,6 +37549,7 @@ var McpServer = class {
|
|
|
37490
37549
|
}
|
|
37491
37550
|
if (updates.annotations !== void 0) registeredTool.annotations = updates.annotations;
|
|
37492
37551
|
if (updates.icons !== void 0) registeredTool.icons = updates.icons;
|
|
37552
|
+
if (updates.scopeChallenge !== void 0) registeredTool.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
37493
37553
|
if (updates._meta !== void 0) registeredTool._meta = updates._meta;
|
|
37494
37554
|
if (updates.enabled !== void 0) registeredTool.enabled = updates.enabled;
|
|
37495
37555
|
this.sendToolListChanged();
|
|
@@ -37502,13 +37562,13 @@ var McpServer = class {
|
|
|
37502
37562
|
}
|
|
37503
37563
|
registerTool(name, config2, cb) {
|
|
37504
37564
|
if (this._registeredTools[name]) throw new Error(`Tool ${name} is already registered`);
|
|
37505
|
-
const { title, description, inputSchema, outputSchema, annotations, icons, _meta } = config2;
|
|
37506
|
-
return this._createRegisteredTool(name, title, description, normalizeRawShapeSchema(inputSchema), normalizeRawShapeSchema(outputSchema), annotations, icons, void 0, _meta, cb);
|
|
37565
|
+
const { title, description, inputSchema, outputSchema, annotations, icons, scopeChallenge, _meta } = config2;
|
|
37566
|
+
return this._createRegisteredTool(name, title, description, normalizeRawShapeSchema(inputSchema), normalizeRawShapeSchema(outputSchema), annotations, icons, void 0, scopeChallenge, _meta, cb);
|
|
37507
37567
|
}
|
|
37508
37568
|
registerPrompt(name, config2, cb) {
|
|
37509
37569
|
if (this._registeredPrompts[name]) throw new Error(`Prompt ${name} is already registered`);
|
|
37510
|
-
const { title, description, argsSchema, icons, _meta } = config2;
|
|
37511
|
-
const registeredPrompt = this._createRegisteredPrompt(name, title, description, normalizeRawShapeSchema(argsSchema), cb, icons, _meta);
|
|
37570
|
+
const { title, description, argsSchema, icons, scopeChallenge, _meta } = config2;
|
|
37571
|
+
const registeredPrompt = this._createRegisteredPrompt(name, title, description, normalizeRawShapeSchema(argsSchema), cb, icons, scopeChallenge, _meta);
|
|
37512
37572
|
this.setPromptRequestHandlers();
|
|
37513
37573
|
this.sendPromptListChanged();
|
|
37514
37574
|
return registeredPrompt;
|
|
@@ -37620,6 +37680,7 @@ function unwrapOptionalSchema(schema) {
|
|
|
37620
37680
|
}
|
|
37621
37681
|
|
|
37622
37682
|
// node_modules/@modelcontextprotocol/server/dist/stdio.mjs
|
|
37683
|
+
var swallowsErrorsAfterClose = /* @__PURE__ */ Symbol("swallowsErrorsAfterClose");
|
|
37623
37684
|
var StdioServerTransport = class {
|
|
37624
37685
|
_readBuffer;
|
|
37625
37686
|
_started = false;
|
|
@@ -37646,18 +37707,27 @@ var StdioServerTransport = class {
|
|
|
37646
37707
|
this.onerror?.(error62);
|
|
37647
37708
|
};
|
|
37648
37709
|
_onstdouterror = (error62) => {
|
|
37710
|
+
if (this._closed) return;
|
|
37649
37711
|
this.onerror?.(error62);
|
|
37650
37712
|
this.close().catch(() => {
|
|
37651
37713
|
});
|
|
37652
37714
|
};
|
|
37715
|
+
_onstdinclose = () => {
|
|
37716
|
+
this.close().catch(() => {
|
|
37717
|
+
});
|
|
37718
|
+
};
|
|
37653
37719
|
/**
|
|
37654
37720
|
* Starts listening for messages on `stdin`.
|
|
37655
37721
|
*/
|
|
37656
37722
|
async start() {
|
|
37657
37723
|
if (this._started) throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
37658
37724
|
this._started = true;
|
|
37725
|
+
for (const listener of this._stdout.listeners("error")) if (listener[swallowsErrorsAfterClose]) this._stdout.off("error", listener);
|
|
37726
|
+
if (this._stdin.readableEnded || this._stdin.destroyed) setImmediate(this._onstdinclose);
|
|
37659
37727
|
this._stdin.on("data", this._ondata);
|
|
37660
37728
|
this._stdin.on("error", this._onerror);
|
|
37729
|
+
this._stdin.on("end", this._onstdinclose);
|
|
37730
|
+
this._stdin.on("close", this._onstdinclose);
|
|
37661
37731
|
this._stdout.on("error", this._onstdouterror);
|
|
37662
37732
|
}
|
|
37663
37733
|
processReadBuffer() {
|
|
@@ -37674,7 +37744,9 @@ var StdioServerTransport = class {
|
|
|
37674
37744
|
this._closed = true;
|
|
37675
37745
|
this._stdin.off("data", this._ondata);
|
|
37676
37746
|
this._stdin.off("error", this._onerror);
|
|
37677
|
-
this.
|
|
37747
|
+
this._stdin.off("end", this._onstdinclose);
|
|
37748
|
+
this._stdin.off("close", this._onstdinclose);
|
|
37749
|
+
this._onstdouterror[swallowsErrorsAfterClose] = true;
|
|
37678
37750
|
if (this._stdin.listenerCount("data") === 0) this._stdin.pause();
|
|
37679
37751
|
this._readBuffer.clear();
|
|
37680
37752
|
this.onclose?.();
|
|
@@ -37951,6 +38023,7 @@ function serveStdio(factory, options = {}) {
|
|
|
37951
38023
|
return;
|
|
37952
38024
|
}
|
|
37953
38025
|
if (state.era === "modern" && await tryServeListen(message)) return;
|
|
38026
|
+
if (isTornDown()) return;
|
|
37954
38027
|
state.instance.channel.deliver(message);
|
|
37955
38028
|
return;
|
|
37956
38029
|
}
|
|
@@ -38017,6 +38090,7 @@ function serveStdio(factory, options = {}) {
|
|
|
38017
38090
|
};
|
|
38018
38091
|
}
|
|
38019
38092
|
if (await tryServeListen(message)) return;
|
|
38093
|
+
if (isTornDown()) return;
|
|
38020
38094
|
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
38021
38095
|
return;
|
|
38022
38096
|
case "legacy": {
|
|
@@ -38103,6 +38177,9 @@ function toError(value) {
|
|
|
38103
38177
|
return value instanceof Error ? value : new Error(String(value));
|
|
38104
38178
|
}
|
|
38105
38179
|
|
|
38180
|
+
// node_modules/@modelcontextprotocol/server/dist/index.mjs
|
|
38181
|
+
var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
38182
|
+
|
|
38106
38183
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
38107
38184
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
38108
38185
|
var McpToolError = class extends Error {
|
|
@@ -38119,11 +38196,12 @@ var McpToolError = class extends Error {
|
|
|
38119
38196
|
function createHelpfulError(message, opts) {
|
|
38120
38197
|
return new McpToolError(message, opts);
|
|
38121
38198
|
}
|
|
38199
|
+
var HEADER_NAME_RUN = "[a-z0-9_-]{0,40}";
|
|
38122
38200
|
var BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
38123
|
-
var JWT_RE =
|
|
38201
|
+
var JWT_RE = /(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}(?![A-Za-z0-9_-])/g;
|
|
38124
38202
|
var BASIC_AUTH_RE = /(authorization\s*:\s*basic\s+)[A-Za-z0-9+/=_-]{6,}/gi;
|
|
38125
|
-
var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([
|
|
38126
|
-
var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[
|
|
38203
|
+
var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([^=;,:\s]+)=[^;,\s]*/gi;
|
|
38204
|
+
var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[^=;,:\s]+=[^;,\s]*)(?:;\s*[^=;,:\s]+=[^;,\s]*)*)/gi;
|
|
38127
38205
|
var API_KEY_RE = new RegExp([
|
|
38128
38206
|
"sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])",
|
|
38129
38207
|
// OpenAI / Anthropic (incl. sk-ant-…)
|
|
@@ -38138,10 +38216,10 @@ var API_KEY_RE = new RegExp([
|
|
|
38138
38216
|
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
38139
38217
|
// webhook signing secret (Stripe-style)
|
|
38140
38218
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
38141
|
-
var QUERY_SECRET_RE =
|
|
38219
|
+
var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
38142
38220
|
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
38143
38221
|
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
38144
|
-
var HEADER_SECRET_RE =
|
|
38222
|
+
var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
38145
38223
|
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
38146
38224
|
var JSON_SECRET_KEYS = [
|
|
38147
38225
|
"(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
|
|
@@ -38149,7 +38227,8 @@ var JSON_SECRET_KEYS = [
|
|
|
38149
38227
|
"api[_-]?secret",
|
|
38150
38228
|
"(?:x[_-])?api[_-]?key",
|
|
38151
38229
|
"private[_-]?key",
|
|
38152
|
-
|
|
38230
|
+
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
38231
|
+
// bounded like HEADER_SECRET_RE
|
|
38153
38232
|
"(?:proxy-)?authorization",
|
|
38154
38233
|
"password",
|
|
38155
38234
|
"passwd",
|
|
@@ -38158,12 +38237,30 @@ var JSON_SECRET_KEYS = [
|
|
|
38158
38237
|
].join("|");
|
|
38159
38238
|
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
|
|
38160
38239
|
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
|
|
38240
|
+
var JSON_SECRET_NUM_RE = new RegExp(`(["'](?:${JSON_SECRET_KEYS})["']\\s*:\\s*)-?\\d+(?:\\.\\d+)?(?![\\d.])`, "gi");
|
|
38241
|
+
var JSON_COOKIE_DQ_RE = /("((?:set-)?cookie)"\s*:\s*)("(?:[^"\\]|\\.)*"|\[\s*"(?:[^"\\]|\\.)*"(?:\s*,\s*"(?:[^"\\]|\\.)*")*)/gi;
|
|
38242
|
+
var JSON_COOKIE_SQ_RE = /('((?:set-)?cookie)'\s*:\s*)('(?:[^'\\]|\\.)*'|\[\s*'(?:[^'\\]|\\.)*'(?:\s*,\s*'(?:[^'\\]|\\.)*')*)/gi;
|
|
38243
|
+
var DQ_STRING_RE = /"((?:[^"\\]|\\.)*)"/g;
|
|
38244
|
+
var SQ_STRING_RE = /'((?:[^'\\]|\\.)*)'/g;
|
|
38245
|
+
function redactJsonCookie(quote) {
|
|
38246
|
+
const stringRe = quote === '"' ? DQ_STRING_RE : SQ_STRING_RE;
|
|
38247
|
+
return (_m, prefix, key, value) => {
|
|
38248
|
+
const setCookie = key.toLowerCase() === "set-cookie";
|
|
38249
|
+
const redacted = value.replace(stringRe, (_s, body) => {
|
|
38250
|
+
const out = setCookie ? body.replace(/^(\s*[^=;]+)=[^;]*/, "$1=[REDACTED]") : body.replace(/=[^;]*/g, "=[REDACTED]");
|
|
38251
|
+
return `${quote}${out}${quote}`;
|
|
38252
|
+
});
|
|
38253
|
+
return `${prefix}${redacted}`;
|
|
38254
|
+
};
|
|
38255
|
+
}
|
|
38161
38256
|
function redactSecrets(text) {
|
|
38162
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
38257
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_COOKIE_DQ_RE, redactJsonCookie('"')).replace(JSON_COOKIE_SQ_RE, redactJsonCookie("'")).replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_NUM_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
|
|
38163
38258
|
}
|
|
38259
|
+
var ERROR_REDACTION_INPUT_MAX = 64 * 1024;
|
|
38164
38260
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
38165
38261
|
const str = text === null || text === void 0 ? "" : String(text);
|
|
38166
|
-
const
|
|
38262
|
+
const cap = Math.max(ERROR_REDACTION_INPUT_MAX, max);
|
|
38263
|
+
const redacted = redactSecrets(str.length > cap ? str.slice(0, cap) : str);
|
|
38167
38264
|
if (redacted.length <= max)
|
|
38168
38265
|
return redacted;
|
|
38169
38266
|
return `${redacted.slice(0, max)}\u2026 [truncated]`;
|
|
@@ -38516,6 +38613,9 @@ async function tokenConfirmation(action, fb) {
|
|
|
38516
38613
|
const subject = await fb.subject();
|
|
38517
38614
|
if (isToolResult(subject))
|
|
38518
38615
|
return subject;
|
|
38616
|
+
if (subject.payload === void 0) {
|
|
38617
|
+
throw new TypeError(`confirm token: ${fb.tool}'s subject() returned no payload. Bind what the call will do (the arguments or the request body), or pass \`args\` to confirmationFromEnv.`);
|
|
38618
|
+
}
|
|
38519
38619
|
const binding = {
|
|
38520
38620
|
tool: fb.tool,
|
|
38521
38621
|
...fb.account === void 0 ? {} : { account: fb.account },
|
|
@@ -38633,9 +38733,19 @@ function readConfirmMode(env = process.env) {
|
|
|
38633
38733
|
}
|
|
38634
38734
|
return "refuse";
|
|
38635
38735
|
}
|
|
38636
|
-
function
|
|
38736
|
+
function readConfirmTtl(env) {
|
|
38637
38737
|
const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
|
|
38638
|
-
|
|
38738
|
+
if (!raw)
|
|
38739
|
+
return DEFAULT_TTL_SECONDS2;
|
|
38740
|
+
if (/^[1-9]\d*$/.test(raw))
|
|
38741
|
+
return Number(raw);
|
|
38742
|
+
const key = `ttl:${raw}`;
|
|
38743
|
+
if (!warned.has(key)) {
|
|
38744
|
+
warned.add(key);
|
|
38745
|
+
process.stderr.write(`MCP_CONFIRM_TTL_SECONDS="${raw}" is not a positive whole number of seconds; gated writes will refuse on clients that cannot show a confirmation prompt until it is fixed.
|
|
38746
|
+
`);
|
|
38747
|
+
}
|
|
38748
|
+
return void 0;
|
|
38639
38749
|
}
|
|
38640
38750
|
var processKey;
|
|
38641
38751
|
function confirmKeyFromEnv(env = process.env) {
|
|
@@ -38647,15 +38757,33 @@ function confirmKeyFromEnv(env = process.env) {
|
|
|
38647
38757
|
}
|
|
38648
38758
|
var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
|
|
38649
38759
|
var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
|
|
38760
|
+
var BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
|
|
38761
|
+
function boundArgs(args) {
|
|
38762
|
+
if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
|
|
38763
|
+
return args;
|
|
38764
|
+
const { confirmToken, ...rest } = args;
|
|
38765
|
+
void confirmToken;
|
|
38766
|
+
return rest;
|
|
38767
|
+
}
|
|
38650
38768
|
function confirmationFromEnv(options) {
|
|
38651
|
-
const { tool, account, confirmToken, subject, instruction, spent, env = process.env, ...
|
|
38769
|
+
const { tool, account, confirmToken, subject, instruction, spent, args, env = process.env, ...rest } = options;
|
|
38652
38770
|
const mode = readConfirmMode(env);
|
|
38653
|
-
|
|
38771
|
+
const ttlSeconds = readConfirmTtl(env);
|
|
38772
|
+
const bound = args === void 0 ? void 0 : boundArgs(args);
|
|
38773
|
+
const confirmation = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
|
|
38774
|
+
if (mode === "refuse" || ttlSeconds === void 0) {
|
|
38775
|
+
const hint = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
|
|
38654
38776
|
return {
|
|
38655
38777
|
...confirmation,
|
|
38656
|
-
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${
|
|
38778
|
+
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
|
|
38657
38779
|
};
|
|
38658
38780
|
}
|
|
38781
|
+
const boundSubject = bound === void 0 ? subject : async () => {
|
|
38782
|
+
const read = await subject();
|
|
38783
|
+
if ("content" in read)
|
|
38784
|
+
return read;
|
|
38785
|
+
return { ...read, payload: { payload: read.payload, args: bound } };
|
|
38786
|
+
};
|
|
38659
38787
|
return {
|
|
38660
38788
|
...confirmation,
|
|
38661
38789
|
tokenFallback: {
|
|
@@ -38663,8 +38791,8 @@ function confirmationFromEnv(options) {
|
|
|
38663
38791
|
tool,
|
|
38664
38792
|
...account === void 0 ? {} : { account },
|
|
38665
38793
|
...confirmToken === void 0 ? {} : { confirmToken },
|
|
38666
|
-
subject,
|
|
38667
|
-
ttlSeconds
|
|
38794
|
+
subject: boundSubject,
|
|
38795
|
+
ttlSeconds,
|
|
38668
38796
|
instruction: mode === "auto" ? CONFIRM_TOKEN_AUTO_INSTRUCTION : instruction ?? CONFIRM_TOKEN_INSTRUCTION,
|
|
38669
38797
|
...spent === void 0 ? {} : { spent }
|
|
38670
38798
|
}
|
|
@@ -38788,6 +38916,8 @@ function withGracefulShutdown(target, opts = {}) {
|
|
|
38788
38916
|
};
|
|
38789
38917
|
const handler = (signal) => {
|
|
38790
38918
|
if (shuttingDown) {
|
|
38919
|
+
if (signal === "stdin-eof")
|
|
38920
|
+
return;
|
|
38791
38921
|
if (Date.now() - firstSignalAt < repeatGraceMs)
|
|
38792
38922
|
return;
|
|
38793
38923
|
if (shouldExit) {
|
|
@@ -38823,6 +38953,10 @@ function withGracefulShutdown(target, opts = {}) {
|
|
|
38823
38953
|
};
|
|
38824
38954
|
process.on("SIGINT", () => handler("SIGINT"));
|
|
38825
38955
|
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
38956
|
+
if (opts.stdin) {
|
|
38957
|
+
opts.stdin.once("end", () => handler("stdin-eof"));
|
|
38958
|
+
opts.stdin.once("close", () => handler("stdin-eof"));
|
|
38959
|
+
}
|
|
38826
38960
|
}
|
|
38827
38961
|
function runMcp(opts) {
|
|
38828
38962
|
if (opts.banner !== void 0) {
|
|
@@ -38841,7 +38975,8 @@ function runMcp(opts) {
|
|
|
38841
38975
|
});
|
|
38842
38976
|
const shutdown = opts.shutdown ?? true;
|
|
38843
38977
|
if (shutdown !== false) {
|
|
38844
|
-
|
|
38978
|
+
const stdinDefault = spec === "stdio" ? { stdin: process.stdin } : {};
|
|
38979
|
+
withGracefulShutdown(handle, { ...stdinDefault, ...shutdown === true ? {} : shutdown });
|
|
38845
38980
|
}
|
|
38846
38981
|
return handle;
|
|
38847
38982
|
}
|
|
@@ -38973,7 +39108,7 @@ async function resolveAuthPattern(pattern) {
|
|
|
38973
39108
|
if (resolver)
|
|
38974
39109
|
return resolver();
|
|
38975
39110
|
}
|
|
38976
|
-
throw createHelpfulError("No auth configured. Provide a token, OAuth credentials, login credentials, or sign in in your browser (
|
|
39111
|
+
throw createHelpfulError("No auth configured. Provide a token, OAuth credentials, login credentials, or sign in in your browser (read via ContextMint Bridge).", { hint: "Set one of the supported credential env vars, or sign in in your browser." });
|
|
38977
39112
|
}
|
|
38978
39113
|
|
|
38979
39114
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
@@ -39353,7 +39488,7 @@ var TokenManager = class {
|
|
|
39353
39488
|
};
|
|
39354
39489
|
|
|
39355
39490
|
// src/client.ts
|
|
39356
|
-
import { dirname as dirname3, join as
|
|
39491
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
39357
39492
|
import { fileURLToPath } from "url";
|
|
39358
39493
|
|
|
39359
39494
|
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
@@ -39559,6 +39694,30 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
39559
39694
|
"write_cookies"
|
|
39560
39695
|
]);
|
|
39561
39696
|
|
|
39697
|
+
// node_modules/@fetchproxy/protocol/dist/capability-availability.js
|
|
39698
|
+
var CAPABILITY_UNAVAILABLE_CODE = "capability_unavailable";
|
|
39699
|
+
var MAX_UNAVAILABLE_CAPABILITIES = 32;
|
|
39700
|
+
var MAX_CAPABILITY_NAME_LENGTH = 64;
|
|
39701
|
+
var UNSUPPORTED_CAPABILITY_REASON_PREFIX = "unsupported-capability:";
|
|
39702
|
+
function capabilityUnavailableMessage(capability, platform) {
|
|
39703
|
+
return `capability ${JSON.stringify(capability)} is not available in this browser (${platform})`;
|
|
39704
|
+
}
|
|
39705
|
+
var UNAVAILABLE_RE = /^capability "([^"]{1,64})" is not available in this browser(?: \(([^)]{1,32})\))?/;
|
|
39706
|
+
function parseCapabilityUnavailable(error62) {
|
|
39707
|
+
const m = UNAVAILABLE_RE.exec(error62);
|
|
39708
|
+
if (!m)
|
|
39709
|
+
return null;
|
|
39710
|
+
return { capability: m[1], platform: m[2] ?? null };
|
|
39711
|
+
}
|
|
39712
|
+
function parseUnsupportedCapabilityReason(reason) {
|
|
39713
|
+
if (!reason.startsWith(UNSUPPORTED_CAPABILITY_REASON_PREFIX))
|
|
39714
|
+
return null;
|
|
39715
|
+
const rest = reason.slice(UNSUPPORTED_CAPABILITY_REASON_PREFIX.length).replace(/\s*\(not available in this browser\)\s*$/, "").trim();
|
|
39716
|
+
if (rest === "")
|
|
39717
|
+
return [];
|
|
39718
|
+
return rest.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
39719
|
+
}
|
|
39720
|
+
|
|
39562
39721
|
// node_modules/@fetchproxy/protocol/dist/mcp-id.js
|
|
39563
39722
|
var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
|
|
39564
39723
|
function generateMcpId(serverName, version2) {
|
|
@@ -40273,6 +40432,9 @@ function validateHello(raw) {
|
|
|
40273
40432
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
40274
40433
|
if (raw.accepts !== void 0)
|
|
40275
40434
|
assertAcceptsList(raw.accepts);
|
|
40435
|
+
if (raw.unavailableCapabilities !== void 0) {
|
|
40436
|
+
assertUnavailableCapabilities(raw.unavailableCapabilities);
|
|
40437
|
+
}
|
|
40276
40438
|
return raw;
|
|
40277
40439
|
}
|
|
40278
40440
|
throw new ProtocolError3(`hello.role: must be 'server' or 'extension', got ${String(role)}`);
|
|
@@ -40338,6 +40500,22 @@ function assertAcceptsList(accepts) {
|
|
|
40338
40500
|
}
|
|
40339
40501
|
}
|
|
40340
40502
|
}
|
|
40503
|
+
function assertUnavailableCapabilities(list) {
|
|
40504
|
+
const label = "hello.unavailableCapabilities";
|
|
40505
|
+
if (!Array.isArray(list))
|
|
40506
|
+
throw new ProtocolError3(`${label}: expected array`);
|
|
40507
|
+
if (list.length > MAX_UNAVAILABLE_CAPABILITIES) {
|
|
40508
|
+
throw new ProtocolError3(`${label}: at most ${MAX_UNAVAILABLE_CAPABILITIES} entries, got ${list.length}`);
|
|
40509
|
+
}
|
|
40510
|
+
for (const c of list) {
|
|
40511
|
+
if (typeof c !== "string") {
|
|
40512
|
+
throw new ProtocolError3(`${label}: entry must be string, got ${typeof c}`);
|
|
40513
|
+
}
|
|
40514
|
+
if (c.length < 1 || c.length > MAX_CAPABILITY_NAME_LENGTH) {
|
|
40515
|
+
throw new ProtocolError3(`${label}: entry must be 1-${MAX_CAPABILITY_NAME_LENGTH} characters, got ${c.length}`);
|
|
40516
|
+
}
|
|
40517
|
+
}
|
|
40518
|
+
}
|
|
40341
40519
|
function validatePeerGone(raw) {
|
|
40342
40520
|
assertString(raw.mcpId, "peer-gone.mcpId");
|
|
40343
40521
|
if (!isValidMcpId(raw.mcpId))
|
|
@@ -40813,6 +40991,8 @@ function validateInnerResponse(raw) {
|
|
|
40813
40991
|
}
|
|
40814
40992
|
if (raw.ok === false) {
|
|
40815
40993
|
assertString(raw.error, "inner.error");
|
|
40994
|
+
if (raw.code !== void 0)
|
|
40995
|
+
assertString(raw.code, "inner.code");
|
|
40816
40996
|
if (raw.op !== void 0) {
|
|
40817
40997
|
if (typeof raw.op !== "string" || !KNOWN_RESPONSE_OPS.has(raw.op)) {
|
|
40818
40998
|
throw new ProtocolError3(`inner.op: unknown response op ${JSON.stringify(raw.op)}`);
|
|
@@ -40908,6 +41088,38 @@ async function openEncryptedFrameDetailed(sessionKey, frame, direction) {
|
|
|
40908
41088
|
}
|
|
40909
41089
|
}
|
|
40910
41090
|
|
|
41091
|
+
// node_modules/@fetchproxy/server/dist/session-info.js
|
|
41092
|
+
function sessionInfoFromHello(hello) {
|
|
41093
|
+
const raw = hello.unavailableCapabilities;
|
|
41094
|
+
const known = /* @__PURE__ */ new Set();
|
|
41095
|
+
if (Array.isArray(raw)) {
|
|
41096
|
+
for (const c of raw) {
|
|
41097
|
+
if (typeof c === "string" && KNOWN_CAPABILITIES.has(c))
|
|
41098
|
+
known.add(c);
|
|
41099
|
+
}
|
|
41100
|
+
}
|
|
41101
|
+
return { platform: hello.platform, unavailableCapabilities: [...known].sort() };
|
|
41102
|
+
}
|
|
41103
|
+
function capabilitiesForRequest(inner) {
|
|
41104
|
+
if (inner.type !== "request")
|
|
41105
|
+
return [];
|
|
41106
|
+
if (inner.op === "graphql_query")
|
|
41107
|
+
return ["graphql"];
|
|
41108
|
+
if (inner.op === "fetch") {
|
|
41109
|
+
return inner.init.inPage === true ? ["fetch", "fetch_in_page"] : ["fetch"];
|
|
41110
|
+
}
|
|
41111
|
+
return [inner.op];
|
|
41112
|
+
}
|
|
41113
|
+
function unavailableCapabilityFor(inner, info) {
|
|
41114
|
+
if (!info || info.unavailableCapabilities.length === 0)
|
|
41115
|
+
return null;
|
|
41116
|
+
for (const c of capabilitiesForRequest(inner)) {
|
|
41117
|
+
if (info.unavailableCapabilities.includes(c))
|
|
41118
|
+
return c;
|
|
41119
|
+
}
|
|
41120
|
+
return null;
|
|
41121
|
+
}
|
|
41122
|
+
|
|
40911
41123
|
// node_modules/@fetchproxy/server/dist/election.js
|
|
40912
41124
|
import { createServer, Server as HttpServer } from "node:http";
|
|
40913
41125
|
var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
@@ -41153,7 +41365,7 @@ var FetchproxySessionNotReadyError = class extends Error {
|
|
|
41153
41365
|
hint;
|
|
41154
41366
|
constructor(info) {
|
|
41155
41367
|
const pairing = info.pairCode !== null && info.pairCode !== "";
|
|
41156
|
-
const hint = pairing ? `Open the
|
|
41368
|
+
const hint = pairing ? `Open the ContextMint Bridge extension popup and approve pair code ${info.pairCode} for "${info.mcpId}", then retry.` : `The extension is connected but hasn't confirmed a session for "${info.mcpId}" \u2014 sign in to the target site in that browser (and approve the requested scope if it changed), then retry.`;
|
|
41157
41369
|
super(`fetchproxy: ${pairing ? "pairing not yet approved" : "no confirmed browser session"} for "${info.mcpId}". ${hint}`);
|
|
41158
41370
|
this.name = "FetchproxySessionNotReadyError";
|
|
41159
41371
|
this.reason = pairing ? "pair-required" : "not-ready";
|
|
@@ -41166,15 +41378,35 @@ var FetchproxySessionNotReadyError = class extends Error {
|
|
|
41166
41378
|
var FetchproxyHelloRejectedError = class extends Error {
|
|
41167
41379
|
mcpId;
|
|
41168
41380
|
reason;
|
|
41381
|
+
/**
|
|
41382
|
+
* #418: when the reason is `unsupported-capability: a, b (not available in
|
|
41383
|
+
* this browser)` — every capability this MCP declared is one the browser
|
|
41384
|
+
* cannot serve — those capabilities, parsed out of it. Empty otherwise.
|
|
41385
|
+
*/
|
|
41386
|
+
unavailableCapabilities;
|
|
41387
|
+
/** #418: the refusing browser's platform, when this bridge knew it. */
|
|
41388
|
+
platform;
|
|
41389
|
+
/**
|
|
41390
|
+
* #418: the remedy, when the reason is one this package knows how to
|
|
41391
|
+
* explain — today only `unsupported-capability`, which blames the browser
|
|
41392
|
+
* rather than the MCP. `null` otherwise.
|
|
41393
|
+
*/
|
|
41394
|
+
hint;
|
|
41169
41395
|
constructor(info) {
|
|
41170
|
-
|
|
41396
|
+
const unavailable = parseUnsupportedCapabilityReason(info.reason);
|
|
41397
|
+
const platform = info.platform ?? null;
|
|
41398
|
+
const hint = unavailable !== null ? `${platform ? `this browser (${platform})` : "this browser"} cannot serve any of the capabilities this MCP needs (${unavailable.join(", ")}) \u2014 ContextMint Bridge checked for the browser APIs and they are missing. Nothing is wrong with the MCP or your pairing, and updating will not change it: use this MCP from a browser that provides them (for example Chrome).` : null;
|
|
41399
|
+
super(`fetchproxy: the extension refused the connection for "${info.mcpId}": ${info.reason}. ` + (hint !== null ? hint : `This is the extension's own reason \u2014 it is not a timeout, and retrying unchanged will be refused the same way.`));
|
|
41171
41400
|
this.name = "FetchproxyHelloRejectedError";
|
|
41172
41401
|
this.mcpId = info.mcpId;
|
|
41173
41402
|
this.reason = info.reason;
|
|
41403
|
+
this.unavailableCapabilities = unavailable ?? [];
|
|
41404
|
+
this.platform = platform;
|
|
41405
|
+
this.hint = hint;
|
|
41174
41406
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
41175
41407
|
}
|
|
41176
41408
|
};
|
|
41177
|
-
var
|
|
41409
|
+
var MIN_SERVER_VERSION = "3.0.0";
|
|
41178
41410
|
var FetchproxyProtocolVersionError = class extends Error {
|
|
41179
41411
|
/** The protocol version this process speaks. */
|
|
41180
41412
|
ourVersion;
|
|
@@ -41182,7 +41414,7 @@ var FetchproxyProtocolVersionError = class extends Error {
|
|
|
41182
41414
|
theirVersion;
|
|
41183
41415
|
peer;
|
|
41184
41416
|
constructor(info) {
|
|
41185
|
-
const far = info.peer === "extension" ? `the attached browser extension speaks ${info.theirVersion} \u2014 update
|
|
41417
|
+
const far = info.peer === "extension" ? `the attached browser extension speaks ${info.theirVersion} \u2014 update ContextMint Bridge to a release that speaks fetchproxy protocol ${info.ourVersion}` : `the MCP holding the bridge port speaks ${info.theirVersion} \u2014 upgrade @fetchproxy/server to ${MIN_SERVER_VERSION} or later in that MCP`;
|
|
41186
41418
|
super(`protocol version mismatch: this MCP speaks fetchproxy protocol ${info.ourVersion}, ${far}`);
|
|
41187
41419
|
this.name = "FetchproxyProtocolVersionError";
|
|
41188
41420
|
this.ourVersion = info.ourVersion;
|
|
@@ -41215,9 +41447,9 @@ async function awaitSessionReady(ready, opts) {
|
|
|
41215
41447
|
}
|
|
41216
41448
|
|
|
41217
41449
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
41218
|
-
import { readFile as
|
|
41450
|
+
import { readFile as readFile3, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2, rm as rm2 } from "node:fs/promises";
|
|
41219
41451
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
41220
|
-
import { isAbsolute as isAbsolute4, join as
|
|
41452
|
+
import { isAbsolute as isAbsolute4, join as join5 } from "node:path";
|
|
41221
41453
|
|
|
41222
41454
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
41223
41455
|
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink, link, lstat } from "node:fs/promises";
|
|
@@ -41318,6 +41550,7 @@ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
|
41318
41550
|
throw e;
|
|
41319
41551
|
}
|
|
41320
41552
|
let unreadable = null;
|
|
41553
|
+
let recovered = false;
|
|
41321
41554
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
41322
41555
|
const id = await generateIdentity();
|
|
41323
41556
|
if (await publishIdentityIfAbsent(dir, serverName, id))
|
|
@@ -41331,6 +41564,11 @@ async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
|
41331
41564
|
if (!(e instanceof SyntaxError))
|
|
41332
41565
|
throw e;
|
|
41333
41566
|
unreadable = e;
|
|
41567
|
+
if (!recovered && await removeAbandonedIdentity(path)) {
|
|
41568
|
+
recovered = true;
|
|
41569
|
+
attempt--;
|
|
41570
|
+
continue;
|
|
41571
|
+
}
|
|
41334
41572
|
await new Promise((r) => setTimeout(r, 20 * (attempt + 1)));
|
|
41335
41573
|
}
|
|
41336
41574
|
}
|
|
@@ -41372,6 +41610,192 @@ async function publishIdentityIfAbsent(dir, serverName, id) {
|
|
|
41372
41610
|
await rm(tmp, { force: true });
|
|
41373
41611
|
}
|
|
41374
41612
|
}
|
|
41613
|
+
var ABANDONED_IDENTITY_MS = 5e3;
|
|
41614
|
+
var RECOVERY_LOCK_STALE_MS = 3e4;
|
|
41615
|
+
function looksAbandoned(text) {
|
|
41616
|
+
const t = text.trim();
|
|
41617
|
+
return t === "" || t.startsWith("{") && !t.endsWith("}");
|
|
41618
|
+
}
|
|
41619
|
+
function sameFile(a, b) {
|
|
41620
|
+
return a.dev === b.dev && a.ino === b.ino && a.size === b.size && a.mtimeMs === b.mtimeMs;
|
|
41621
|
+
}
|
|
41622
|
+
async function takeRecoveryLock(lock) {
|
|
41623
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
41624
|
+
try {
|
|
41625
|
+
await writeFile(lock, `${process.pid}
|
|
41626
|
+
`, { mode: 384, flag: "wx" });
|
|
41627
|
+
return await lstat(lock);
|
|
41628
|
+
} catch (e) {
|
|
41629
|
+
if (e.code !== "EEXIST")
|
|
41630
|
+
return null;
|
|
41631
|
+
const st = await lstat(lock).catch(() => null);
|
|
41632
|
+
if (st === null)
|
|
41633
|
+
continue;
|
|
41634
|
+
if (Date.now() - st.mtimeMs < RECOVERY_LOCK_STALE_MS)
|
|
41635
|
+
return null;
|
|
41636
|
+
const again = await lstat(lock).catch(() => null);
|
|
41637
|
+
if (again === null || !sameFile(st, again))
|
|
41638
|
+
continue;
|
|
41639
|
+
await unlink(lock).catch(() => void 0);
|
|
41640
|
+
}
|
|
41641
|
+
}
|
|
41642
|
+
return null;
|
|
41643
|
+
}
|
|
41644
|
+
async function removeAbandonedIdentity(path) {
|
|
41645
|
+
const lock = `${path}.recover.lock`;
|
|
41646
|
+
const held = await takeRecoveryLock(lock);
|
|
41647
|
+
if (held === null)
|
|
41648
|
+
return false;
|
|
41649
|
+
try {
|
|
41650
|
+
const before = await lstat(path);
|
|
41651
|
+
if (!before.isFile() || Date.now() - before.mtimeMs < ABANDONED_IDENTITY_MS)
|
|
41652
|
+
return false;
|
|
41653
|
+
if (!looksAbandoned(await readFile(path, "utf8")))
|
|
41654
|
+
return false;
|
|
41655
|
+
const now = await lstat(path);
|
|
41656
|
+
if (!sameFile(before, now))
|
|
41657
|
+
return false;
|
|
41658
|
+
await unlink(path);
|
|
41659
|
+
return true;
|
|
41660
|
+
} catch {
|
|
41661
|
+
return false;
|
|
41662
|
+
} finally {
|
|
41663
|
+
const st = await lstat(lock).catch(() => null);
|
|
41664
|
+
if (st !== null && sameFile(held, st))
|
|
41665
|
+
await unlink(lock).catch(() => void 0);
|
|
41666
|
+
}
|
|
41667
|
+
}
|
|
41668
|
+
|
|
41669
|
+
// node_modules/@fetchproxy/server/dist/extension-pins.js
|
|
41670
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
41671
|
+
import { join as join4 } from "node:path";
|
|
41672
|
+
var EXTENSION_PINS_FILE_VERSION = 1;
|
|
41673
|
+
var EXTENSION_PINS_ENV = "FETCHPROXY_EXTENSION_PINS";
|
|
41674
|
+
var ExtensionPinsError = class extends Error {
|
|
41675
|
+
code;
|
|
41676
|
+
constructor(code, message) {
|
|
41677
|
+
super(message);
|
|
41678
|
+
this.name = "ExtensionPinsError";
|
|
41679
|
+
this.code = code;
|
|
41680
|
+
}
|
|
41681
|
+
};
|
|
41682
|
+
function malformed(why) {
|
|
41683
|
+
return new ExtensionPinsError("EXTENSION_PINS_MALFORMED", `malformed extension pin set: ${why}`);
|
|
41684
|
+
}
|
|
41685
|
+
function isKey(x) {
|
|
41686
|
+
if (typeof x !== "string" || x === "")
|
|
41687
|
+
return false;
|
|
41688
|
+
let raw;
|
|
41689
|
+
try {
|
|
41690
|
+
raw = fromB64(x);
|
|
41691
|
+
} catch {
|
|
41692
|
+
return false;
|
|
41693
|
+
}
|
|
41694
|
+
return raw.length === 32 && toB64(raw) === x;
|
|
41695
|
+
}
|
|
41696
|
+
function parseExtensionPins(text) {
|
|
41697
|
+
let parsed;
|
|
41698
|
+
try {
|
|
41699
|
+
parsed = JSON.parse(text);
|
|
41700
|
+
} catch {
|
|
41701
|
+
throw malformed("not JSON");
|
|
41702
|
+
}
|
|
41703
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41704
|
+
throw malformed("not an object");
|
|
41705
|
+
}
|
|
41706
|
+
const r = parsed;
|
|
41707
|
+
if (r.v !== EXTENSION_PINS_FILE_VERSION) {
|
|
41708
|
+
throw malformed(`"v" is not ${EXTENSION_PINS_FILE_VERSION}`);
|
|
41709
|
+
}
|
|
41710
|
+
if (r.managed !== true)
|
|
41711
|
+
throw malformed('"managed" is not true');
|
|
41712
|
+
if (r.managedBy !== void 0 && typeof r.managedBy !== "string") {
|
|
41713
|
+
throw malformed('"managedBy" is not a string');
|
|
41714
|
+
}
|
|
41715
|
+
if (!Array.isArray(r.extensions))
|
|
41716
|
+
throw malformed('"extensions" is not an array');
|
|
41717
|
+
const extensions = r.extensions.map((e, i) => {
|
|
41718
|
+
if (!e || typeof e !== "object" || Array.isArray(e)) {
|
|
41719
|
+
throw malformed(`extensions[${i}] is not an object`);
|
|
41720
|
+
}
|
|
41721
|
+
const x = e;
|
|
41722
|
+
if (!isKey(x.x25519Pub)) {
|
|
41723
|
+
throw malformed(`extensions[${i}].x25519Pub is not canonical base64 of 32 bytes`);
|
|
41724
|
+
}
|
|
41725
|
+
if (!isKey(x.ed25519Pub)) {
|
|
41726
|
+
throw malformed(`extensions[${i}].ed25519Pub is not canonical base64 of 32 bytes`);
|
|
41727
|
+
}
|
|
41728
|
+
if (x.label !== void 0 && typeof x.label !== "string") {
|
|
41729
|
+
throw malformed(`extensions[${i}].label is not a string`);
|
|
41730
|
+
}
|
|
41731
|
+
return {
|
|
41732
|
+
x25519Pub: x.x25519Pub,
|
|
41733
|
+
ed25519Pub: x.ed25519Pub,
|
|
41734
|
+
...x.label !== void 0 ? { label: x.label } : {}
|
|
41735
|
+
};
|
|
41736
|
+
});
|
|
41737
|
+
return {
|
|
41738
|
+
v: EXTENSION_PINS_FILE_VERSION,
|
|
41739
|
+
managed: true,
|
|
41740
|
+
...r.managedBy !== void 0 ? { managedBy: r.managedBy } : {},
|
|
41741
|
+
extensions
|
|
41742
|
+
};
|
|
41743
|
+
}
|
|
41744
|
+
function extensionPinsPath(serverName, trustDir) {
|
|
41745
|
+
return join4(trustDir, `${safeIdentityFileBase(serverName)}.extension-pins.json`);
|
|
41746
|
+
}
|
|
41747
|
+
async function readExtensionPinsAt(path) {
|
|
41748
|
+
let text;
|
|
41749
|
+
try {
|
|
41750
|
+
text = await readFile2(path, "utf8");
|
|
41751
|
+
} catch (e) {
|
|
41752
|
+
const code = e.code ?? "read failed";
|
|
41753
|
+
throw new ExtensionPinsError("EXTENSION_PINS_MISSING", `no managed extension pin set at ${path} (${code})`);
|
|
41754
|
+
}
|
|
41755
|
+
try {
|
|
41756
|
+
return parseExtensionPins(text);
|
|
41757
|
+
} catch (e) {
|
|
41758
|
+
const why = e instanceof ExtensionPinsError ? e.message : "malformed extension pin set";
|
|
41759
|
+
throw new ExtensionPinsError("EXTENSION_PINS_MALFORMED", `${why} at ${path}`);
|
|
41760
|
+
}
|
|
41761
|
+
}
|
|
41762
|
+
function decideManagedExtensionTrust(args) {
|
|
41763
|
+
const { set: set2, hello, serverName } = args;
|
|
41764
|
+
const listed = set2.extensions.some((e) => e.x25519Pub === hello.identityX25519Pub && e.ed25519Pub === hello.identityEd25519Pub);
|
|
41765
|
+
if (listed)
|
|
41766
|
+
return { decision: "pinned" };
|
|
41767
|
+
return {
|
|
41768
|
+
decision: "refused",
|
|
41769
|
+
code: "EXTENSION_NOT_PINNED",
|
|
41770
|
+
message: `[fetchproxy] ${serverName}: refusing an extension that is not in the host-managed pin set${args.location ? ` (${args.location})` : ""}. The host that runs this MCP decides which browsers it accepts; confirm this browser there.`
|
|
41771
|
+
};
|
|
41772
|
+
}
|
|
41773
|
+
async function evaluateManagedExtensionTrust(port, hello, serverName) {
|
|
41774
|
+
let set2;
|
|
41775
|
+
try {
|
|
41776
|
+
set2 = await port.read();
|
|
41777
|
+
} catch (e) {
|
|
41778
|
+
const code = e instanceof ExtensionPinsError ? e.code : "EXTENSION_PINS_MISSING";
|
|
41779
|
+
const detail = e instanceof ExtensionPinsError ? e.message : `could not read ${port.location}`;
|
|
41780
|
+
return {
|
|
41781
|
+
decision: "refused",
|
|
41782
|
+
code,
|
|
41783
|
+
message: `[fetchproxy] ${serverName}: ${code}: ${detail} \u2014 refusing every extension until the host that manages this MCP's pins writes a valid set.`
|
|
41784
|
+
};
|
|
41785
|
+
}
|
|
41786
|
+
return decideManagedExtensionTrust({ set: set2, hello, serverName, location: port.location });
|
|
41787
|
+
}
|
|
41788
|
+
function resolveExtensionPinsMode(explicit, env = process.env) {
|
|
41789
|
+
if (explicit === "managed")
|
|
41790
|
+
return "managed";
|
|
41791
|
+
const raw = env[EXTENSION_PINS_ENV];
|
|
41792
|
+
if (raw === void 0 || raw.trim() === "")
|
|
41793
|
+
return "first-use";
|
|
41794
|
+
if (raw === "managed")
|
|
41795
|
+
return "managed";
|
|
41796
|
+
console.warn(`[fetchproxy] ${EXTENSION_PINS_ENV}=${JSON.stringify(raw)} is not "managed" \u2014 treating it as managed anyway, because a set variable means a host manages this MCP's extension pins. Unset it for trust on first use.`);
|
|
41797
|
+
return "managed";
|
|
41798
|
+
}
|
|
41375
41799
|
|
|
41376
41800
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
41377
41801
|
var TRUST_DIR_ENV = "FETCHPROXY_TRUST_DIR";
|
|
@@ -41395,6 +41819,22 @@ function defaultTrustDir() {
|
|
|
41395
41819
|
}
|
|
41396
41820
|
function fileExtensionTrust(args) {
|
|
41397
41821
|
const dir = resolveTrustDir(args.trustDir, args.dir);
|
|
41822
|
+
if (args.extensionPins === "managed") {
|
|
41823
|
+
const location = extensionPinsPath(args.serverName, dir);
|
|
41824
|
+
return {
|
|
41825
|
+
// Managed mode has no first-use pin: nothing to read, nothing to write,
|
|
41826
|
+
// and no operator override. `write` REJECTS rather than no-ops, so a
|
|
41827
|
+
// path that ever reached it would log the attempt instead of silently
|
|
41828
|
+
// pretending to have pinned.
|
|
41829
|
+
allowNew: false,
|
|
41830
|
+
location,
|
|
41831
|
+
read: async () => null,
|
|
41832
|
+
write: async () => {
|
|
41833
|
+
throw new Error(`refusing to write an extension pin: ${location} is host-managed and this MCP never pins on first use`);
|
|
41834
|
+
},
|
|
41835
|
+
managed: { location, read: () => readExtensionPinsAt(location) }
|
|
41836
|
+
};
|
|
41837
|
+
}
|
|
41398
41838
|
return {
|
|
41399
41839
|
allowNew: args.allowNew,
|
|
41400
41840
|
location: extensionTrustPath(args.serverName, dir),
|
|
@@ -41428,13 +41868,13 @@ function decideExtensionTrust(args) {
|
|
|
41428
41868
|
};
|
|
41429
41869
|
}
|
|
41430
41870
|
function extensionTrustPath(serverName, dir = defaultTrustDir()) {
|
|
41431
|
-
return
|
|
41871
|
+
return join5(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
41432
41872
|
}
|
|
41433
41873
|
function extensionTrustPathHint(serverName) {
|
|
41434
41874
|
try {
|
|
41435
41875
|
return extensionTrustPath(serverName);
|
|
41436
41876
|
} catch {
|
|
41437
|
-
return
|
|
41877
|
+
return join5(defaultTrustDir(), "<server-name>.extension-trust.json");
|
|
41438
41878
|
}
|
|
41439
41879
|
}
|
|
41440
41880
|
function isPin(x) {
|
|
@@ -41447,7 +41887,7 @@ async function readExtensionPin(serverName, dir = defaultTrustDir()) {
|
|
|
41447
41887
|
const path = extensionTrustPath(serverName, dir);
|
|
41448
41888
|
let raw;
|
|
41449
41889
|
try {
|
|
41450
|
-
raw = await
|
|
41890
|
+
raw = await readFile3(path, "utf8");
|
|
41451
41891
|
} catch (e) {
|
|
41452
41892
|
if (e.code === "ENOENT")
|
|
41453
41893
|
return null;
|
|
@@ -41620,6 +42060,7 @@ async function startHost(opts) {
|
|
|
41620
42060
|
return true;
|
|
41621
42061
|
}
|
|
41622
42062
|
let extensionHello = null;
|
|
42063
|
+
let ownSessionInfo = null;
|
|
41623
42064
|
let extensionClaim = null;
|
|
41624
42065
|
const pairCodeFor = async (hello, mint) => {
|
|
41625
42066
|
if (!mint)
|
|
@@ -41638,6 +42079,7 @@ async function startHost(opts) {
|
|
|
41638
42079
|
return;
|
|
41639
42080
|
console.warn(`[fetchproxy] ${opts.ownServerName}: ${OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE} frames in a row from the extension failed authentication under this MCP's session key \u2014 re-handshaking this MCP's session (the extension stays connected).`);
|
|
41640
42081
|
ownSession = dropOwnSessionAndEphemeral();
|
|
42082
|
+
ownSessionInfo = null;
|
|
41641
42083
|
resetSessionPromise();
|
|
41642
42084
|
const mintedKeypair = await generateSessionKeypair();
|
|
41643
42085
|
const mintedHello = await buildServerHello({
|
|
@@ -41696,28 +42138,39 @@ async function startHost(opts) {
|
|
|
41696
42138
|
return;
|
|
41697
42139
|
}
|
|
41698
42140
|
extensionClaim = ws;
|
|
41699
|
-
|
|
41700
|
-
|
|
41701
|
-
|
|
41702
|
-
|
|
41703
|
-
|
|
41704
|
-
|
|
41705
|
-
|
|
41706
|
-
|
|
41707
|
-
|
|
42141
|
+
const managed = opts.extensionTrust.managed;
|
|
42142
|
+
let outcome;
|
|
42143
|
+
let refusalReason = "extension identity is not the pinned one";
|
|
42144
|
+
if (managed) {
|
|
42145
|
+
const decided = await evaluateManagedExtensionTrust(managed, frame, opts.ownServerName);
|
|
42146
|
+
outcome = decided;
|
|
42147
|
+
if (decided.decision === "refused") {
|
|
42148
|
+
refusalReason = decided.code === "EXTENSION_NOT_PINNED" ? "extension identity is not in the managed pin set" : "extension pin set unavailable";
|
|
42149
|
+
}
|
|
42150
|
+
} else {
|
|
42151
|
+
let pin;
|
|
42152
|
+
try {
|
|
42153
|
+
pin = await opts.extensionTrust.read();
|
|
42154
|
+
} catch (e) {
|
|
42155
|
+
console.error(`[fetchproxy] ${String(e)}`);
|
|
42156
|
+
if (extensionClaim === ws)
|
|
42157
|
+
extensionClaim = null;
|
|
42158
|
+
ws.close(1008, "extension pin unreadable");
|
|
42159
|
+
return;
|
|
42160
|
+
}
|
|
42161
|
+
outcome = decideExtensionTrust({
|
|
42162
|
+
pin,
|
|
42163
|
+
hello: frame,
|
|
42164
|
+
allowNew: opts.extensionTrust.allowNew,
|
|
42165
|
+
serverName: opts.ownServerName,
|
|
42166
|
+
location: opts.extensionTrust.location
|
|
42167
|
+
});
|
|
41708
42168
|
}
|
|
41709
|
-
const outcome = decideExtensionTrust({
|
|
41710
|
-
pin,
|
|
41711
|
-
hello: frame,
|
|
41712
|
-
allowNew: opts.extensionTrust.allowNew,
|
|
41713
|
-
serverName: opts.ownServerName,
|
|
41714
|
-
location: opts.extensionTrust.location
|
|
41715
|
-
});
|
|
41716
42169
|
if (outcome.decision === "refused") {
|
|
41717
42170
|
console.warn(outcome.message);
|
|
41718
42171
|
if (extensionClaim === ws)
|
|
41719
42172
|
extensionClaim = null;
|
|
41720
|
-
ws.close(1008,
|
|
42173
|
+
ws.close(1008, refusalReason);
|
|
41721
42174
|
return;
|
|
41722
42175
|
}
|
|
41723
42176
|
if (outcome.decision === "replace")
|
|
@@ -41812,8 +42265,9 @@ async function startHost(opts) {
|
|
|
41812
42265
|
console.warn("[fetchproxy] discarding a ready for a session ephemeral this host no longer holds (the extension reconnected while a hello was in flight)");
|
|
41813
42266
|
return;
|
|
41814
42267
|
}
|
|
41815
|
-
const
|
|
41816
|
-
const
|
|
42268
|
+
const verifiedHello = extensionHello;
|
|
42269
|
+
const extEdPub = fromB64(verifiedHello.identityEd25519Pub);
|
|
42270
|
+
const extNonce = fromB64(verifiedHello.sessionNonce);
|
|
41817
42271
|
const msg = readySignaturePayload(held.nonce, extNonce, fromB64(frame.extensionSessionPub), held.pub);
|
|
41818
42272
|
const sig = fromB64(frame.sessionSig);
|
|
41819
42273
|
let sigOk = false;
|
|
@@ -41846,6 +42300,7 @@ async function startHost(opts) {
|
|
|
41846
42300
|
if (extensionWs !== ws)
|
|
41847
42301
|
return;
|
|
41848
42302
|
ownSession = new SessionState(key);
|
|
42303
|
+
ownSessionInfo = sessionInfoFromHello(verifiedHello);
|
|
41849
42304
|
ownDecryptFailures = 0;
|
|
41850
42305
|
ownPendingPairCode = null;
|
|
41851
42306
|
resolveOwnSession(ownSession);
|
|
@@ -41916,7 +42371,11 @@ async function startHost(opts) {
|
|
|
41916
42371
|
}
|
|
41917
42372
|
if (frame.type === "hello-rejected" && identified === "extension") {
|
|
41918
42373
|
if (frame.mcpId === opts.ownMcpId) {
|
|
41919
|
-
rejectOwnSession(new FetchproxyHelloRejectedError({
|
|
42374
|
+
rejectOwnSession(new FetchproxyHelloRejectedError({
|
|
42375
|
+
mcpId: frame.mcpId,
|
|
42376
|
+
reason: frame.reason,
|
|
42377
|
+
platform: extensionHello?.platform ?? null
|
|
42378
|
+
}));
|
|
41920
42379
|
} else {
|
|
41921
42380
|
const slot = peers.get(frame.mcpId);
|
|
41922
42381
|
if (slot?.helloFrame.accepts?.includes("hello-rejected")) {
|
|
@@ -41962,6 +42421,7 @@ async function startHost(opts) {
|
|
|
41962
42421
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
41963
42422
|
}
|
|
41964
42423
|
ownSession = dropOwnSessionAndEphemeral();
|
|
42424
|
+
ownSessionInfo = null;
|
|
41965
42425
|
ownDecryptFailures = 0;
|
|
41966
42426
|
ownPendingPairCode = null;
|
|
41967
42427
|
resetSessionPromise();
|
|
@@ -42008,6 +42468,7 @@ async function startHost(opts) {
|
|
|
42008
42468
|
});
|
|
42009
42469
|
if (!extensionWs)
|
|
42010
42470
|
throw new Error("host: no extension connected");
|
|
42471
|
+
opts.refuseOutbound?.(inner, ownSessionInfo);
|
|
42011
42472
|
const plaintext = encodeOutboundInnerFrame(opts.ownMcpId, inner);
|
|
42012
42473
|
const sealed = await sealInnerFrame(session.sessionKey, opts.ownMcpId, session.nextOutboundSeq(), plaintext, "s2e");
|
|
42013
42474
|
extensionWs.send(JSON.stringify(sealed));
|
|
@@ -42023,7 +42484,8 @@ async function startHost(opts) {
|
|
|
42023
42484
|
},
|
|
42024
42485
|
pendingPairCode: () => ownPendingPairCode,
|
|
42025
42486
|
extensionConnected: () => extensionWs !== null,
|
|
42026
|
-
sessionLinked: () => ownSession !== null
|
|
42487
|
+
sessionLinked: () => ownSession !== null,
|
|
42488
|
+
sessionInfo: () => ownSession !== null ? ownSessionInfo : null
|
|
42027
42489
|
};
|
|
42028
42490
|
}
|
|
42029
42491
|
|
|
@@ -42108,6 +42570,7 @@ async function startPeer(opts) {
|
|
|
42108
42570
|
const closeListeners = [];
|
|
42109
42571
|
const extensionDisconnectListeners = [];
|
|
42110
42572
|
let session = null;
|
|
42573
|
+
let sessionInfo = null;
|
|
42111
42574
|
let sessionEphemeral = null;
|
|
42112
42575
|
function dropSessionEphemeral() {
|
|
42113
42576
|
if (sessionEphemeral)
|
|
@@ -42173,6 +42636,15 @@ async function startPeer(opts) {
|
|
|
42173
42636
|
console.warn(`[fetchproxy] ${opts.serverName}: extension session signature invalid \u2014 refusing (the concentrator may be answering in the browser's place)`);
|
|
42174
42637
|
return null;
|
|
42175
42638
|
}
|
|
42639
|
+
const managed = opts.extensionTrust.managed;
|
|
42640
|
+
if (managed) {
|
|
42641
|
+
const decided = await evaluateManagedExtensionTrust(managed, hello2, opts.serverName);
|
|
42642
|
+
if (decided.decision === "refused") {
|
|
42643
|
+
console.warn(decided.message);
|
|
42644
|
+
return null;
|
|
42645
|
+
}
|
|
42646
|
+
return hello2;
|
|
42647
|
+
}
|
|
42176
42648
|
if (cachedPin === void 0) {
|
|
42177
42649
|
try {
|
|
42178
42650
|
cachedPin = await opts.extensionTrust.read();
|
|
@@ -42240,6 +42712,7 @@ async function startPeer(opts) {
|
|
|
42240
42712
|
extensionGone = true;
|
|
42241
42713
|
dropSessionEphemeral();
|
|
42242
42714
|
pendingPairCode = null;
|
|
42715
|
+
sessionInfo = null;
|
|
42243
42716
|
if (session !== null) {
|
|
42244
42717
|
session = null;
|
|
42245
42718
|
if (sessionPromiseSettled)
|
|
@@ -42272,6 +42745,7 @@ async function startPeer(opts) {
|
|
|
42272
42745
|
}
|
|
42273
42746
|
const isRenegotiation = session !== null;
|
|
42274
42747
|
session = new SessionState(sessionKey);
|
|
42748
|
+
sessionInfo = sessionInfoFromHello(authenticated);
|
|
42275
42749
|
extensionGone = false;
|
|
42276
42750
|
pendingPairCode = null;
|
|
42277
42751
|
if (isRenegotiation) {
|
|
@@ -42282,7 +42756,11 @@ async function startPeer(opts) {
|
|
|
42282
42756
|
return;
|
|
42283
42757
|
}
|
|
42284
42758
|
if (frame.type === "hello-rejected" && frame.mcpId === opts.mcpId) {
|
|
42285
|
-
rejectFirstReady(new FetchproxyHelloRejectedError({
|
|
42759
|
+
rejectFirstReady(new FetchproxyHelloRejectedError({
|
|
42760
|
+
mcpId: frame.mcpId,
|
|
42761
|
+
reason: frame.reason,
|
|
42762
|
+
platform: extensionHello?.platform ?? null
|
|
42763
|
+
}));
|
|
42286
42764
|
}
|
|
42287
42765
|
if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
|
|
42288
42766
|
const hello2 = extensionHello;
|
|
@@ -42382,6 +42860,7 @@ async function startPeer(opts) {
|
|
|
42382
42860
|
const s = session;
|
|
42383
42861
|
if (s === null)
|
|
42384
42862
|
throw new Error("peer: extension disconnected");
|
|
42863
|
+
opts.refuseOutbound?.(inner, sessionInfo);
|
|
42385
42864
|
const plaintext = encodeOutboundInnerFrame(opts.mcpId, inner);
|
|
42386
42865
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), plaintext, "s2e");
|
|
42387
42866
|
if (ws.readyState !== import_websocket.default.OPEN)
|
|
@@ -42400,6 +42879,7 @@ async function startPeer(opts) {
|
|
|
42400
42879
|
pendingPairCode: () => pendingPairCode,
|
|
42401
42880
|
extensionConnected: () => extensionHello !== null,
|
|
42402
42881
|
sessionLinked: () => session !== null && !extensionGone,
|
|
42882
|
+
sessionInfo: () => session !== null && !extensionGone ? sessionInfo : null,
|
|
42403
42883
|
onClose: (cb) => {
|
|
42404
42884
|
closeListeners.push(cb);
|
|
42405
42885
|
},
|
|
@@ -42412,7 +42892,9 @@ async function startPeer(opts) {
|
|
|
42412
42892
|
}
|
|
42413
42893
|
|
|
42414
42894
|
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
42415
|
-
function classifyFetchError(error62) {
|
|
42895
|
+
function classifyFetchError(error62, code) {
|
|
42896
|
+
if (code === CAPABILITY_UNAVAILABLE_CODE)
|
|
42897
|
+
return "capability_unavailable";
|
|
42416
42898
|
if (/^(in-page )?fetch threw:/.test(error62)) {
|
|
42417
42899
|
return "tab_fetch_failed";
|
|
42418
42900
|
}
|
|
@@ -42428,6 +42910,9 @@ function classifyFetchError(error62) {
|
|
|
42428
42910
|
if (/not in domains \[/.test(error62)) {
|
|
42429
42911
|
return "domain_denied";
|
|
42430
42912
|
}
|
|
42913
|
+
if (parseCapabilityUnavailable(error62) !== null) {
|
|
42914
|
+
return "capability_unavailable";
|
|
42915
|
+
}
|
|
42431
42916
|
if (/^capability .+ not granted/.test(error62)) {
|
|
42432
42917
|
return "capability_denied";
|
|
42433
42918
|
}
|
|
@@ -42506,7 +42991,7 @@ var FetchproxyBridgeDownError = class extends FetchproxyProtocolError {
|
|
|
42506
42991
|
const retryAttempted = args.retryAttempted ?? false;
|
|
42507
42992
|
const op = args.op ?? "fetch";
|
|
42508
42993
|
const retryClause = retryAttempted ? `Server already burned a one-shot lazy-revive retry; SW is still down. ` : `Server lazy-revive retry was disabled (bridgeReviveDelayMs unset/0). `;
|
|
42509
|
-
const hint = `
|
|
42994
|
+
const hint = `ContextMint Bridge's service worker is not responding ("${args.originalError}"). Chrome evicts extension service workers after ~30s idle by default. ${retryClause}Make sure a tab for this domain is open, fully loaded, and signed in (the bridge fetches through that tab) \u2014 then retry. If it keeps happening, reload the extension from chrome://extensions and reload the tab.`;
|
|
42510
42995
|
super(`fetchproxy bridge down during ${op}${args.url ? ` (${args.url})` : ""}. ${hint}`);
|
|
42511
42996
|
this.name = "FetchproxyBridgeDownError";
|
|
42512
42997
|
this.originalError = args.originalError;
|
|
@@ -42533,7 +43018,7 @@ var FetchproxyHintedError = class extends FetchproxyProtocolError {
|
|
|
42533
43018
|
};
|
|
42534
43019
|
var FetchproxyScopeError = class extends FetchproxyHintedError {
|
|
42535
43020
|
constructor(originalError) {
|
|
42536
|
-
super(originalError, "the declared scope changed since you paired, so the extension is refusing the request. Revoke this MCP in the
|
|
43021
|
+
super(originalError, "the declared scope changed since you paired, so the extension is refusing the request. Revoke this MCP in the ContextMint Bridge extension popup, then re-run \u2014 you will be asked to approve the new scope. This is not a version problem and does not need an update.");
|
|
42537
43022
|
this.name = "FetchproxyScopeError";
|
|
42538
43023
|
}
|
|
42539
43024
|
};
|
|
@@ -42555,6 +43040,19 @@ var FetchproxyWaitedError = class extends FetchproxyHintedError {
|
|
|
42555
43040
|
this.name = "FetchproxyWaitedError";
|
|
42556
43041
|
}
|
|
42557
43042
|
};
|
|
43043
|
+
var FetchproxyCapabilityUnavailableError = class extends FetchproxyHintedError {
|
|
43044
|
+
/** The capability the browser cannot serve (`graphql` for a `graphql_query`). */
|
|
43045
|
+
capability;
|
|
43046
|
+
/** The browser, when known (`'safari'`); `null` when the extension did not say. */
|
|
43047
|
+
platform;
|
|
43048
|
+
constructor(originalError, info) {
|
|
43049
|
+
const where = info.platform ? `this browser (${info.platform})` : "this browser";
|
|
43050
|
+
super(originalError, `${where} cannot serve the "${info.capability}" capability \u2014 ContextMint Bridge checked for the browser API it needs and it is missing. The rest of this MCP still works here. Nothing is wrong with the MCP's code or your pairing, and neither updating nor re-approving will change it: use this tool from a browser that provides the API (for example Chrome).`);
|
|
43051
|
+
this.name = "FetchproxyCapabilityUnavailableError";
|
|
43052
|
+
this.capability = info.capability;
|
|
43053
|
+
this.platform = info.platform;
|
|
43054
|
+
}
|
|
43055
|
+
};
|
|
42558
43056
|
function waitedHint(op) {
|
|
42559
43057
|
const tail = " This is not a version problem and does not need an update.";
|
|
42560
43058
|
if (op === "capture" || op === "capture_redirect") {
|
|
@@ -42568,7 +43066,15 @@ function waitedHint(op) {
|
|
|
42568
43066
|
var SCOPE_REJECTION = /not in declared/;
|
|
42569
43067
|
var NO_TAB_REJECTION = /no tab matching (?!.*(content script loaded|one is still opening))/;
|
|
42570
43068
|
var TAB_OPENING_REJECTION = /no tab matching .*one is still opening/;
|
|
42571
|
-
function protocolErrorFrom(error62, op) {
|
|
43069
|
+
function protocolErrorFrom(error62, op, response) {
|
|
43070
|
+
const unavailable = parseCapabilityUnavailable(error62);
|
|
43071
|
+
if (unavailable !== null || response?.code === CAPABILITY_UNAVAILABLE_CODE) {
|
|
43072
|
+
const fromOp = response?.op === "graphql_query" ? "graphql" : response?.op ?? "unknown";
|
|
43073
|
+
return new FetchproxyCapabilityUnavailableError(error62, {
|
|
43074
|
+
capability: unavailable?.capability ?? fromOp,
|
|
43075
|
+
platform: unavailable?.platform ?? null
|
|
43076
|
+
});
|
|
43077
|
+
}
|
|
42572
43078
|
if (error62 === "timeout")
|
|
42573
43079
|
return new FetchproxyWaitedError(error62, op);
|
|
42574
43080
|
if (SCOPE_REJECTION.test(error62))
|
|
@@ -42627,7 +43133,8 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
42627
43133
|
constructor(args) {
|
|
42628
43134
|
const graceMs = args.graceMs ?? VERB_DEADLINE_GRACE_MS;
|
|
42629
43135
|
const callerChose = args.requestedTimeoutMs !== void 0 && args.timeoutMs === args.requestedTimeoutMs + graceMs;
|
|
42630
|
-
|
|
43136
|
+
const base = `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.` : "");
|
|
43137
|
+
super(args.retrySafe === false ? withNotResentNote(base) : base);
|
|
42631
43138
|
this.name = "FetchproxyTimeoutError";
|
|
42632
43139
|
this.url = args.url;
|
|
42633
43140
|
this.timeoutMs = args.timeoutMs;
|
|
@@ -42639,6 +43146,10 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
42639
43146
|
this.retrySafe = args.retrySafe ?? true;
|
|
42640
43147
|
}
|
|
42641
43148
|
};
|
|
43149
|
+
var TIMEOUT_NOT_RESENT_NOTE = "It was not re-sent because it may already have run in the browser \u2014 check before retrying.";
|
|
43150
|
+
function withNotResentNote(message) {
|
|
43151
|
+
return `${message}${message.endsWith(".") ? " " : ". "}${TIMEOUT_NOT_RESENT_NOTE}`;
|
|
43152
|
+
}
|
|
42642
43153
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
42643
43154
|
function normalizeCookiePath(path) {
|
|
42644
43155
|
if (path === void 0 || path === "")
|
|
@@ -42898,6 +43409,7 @@ var FetchproxyServer = class {
|
|
|
42898
43409
|
identityDir: opts.identityDir,
|
|
42899
43410
|
trustDir: opts.trustDir,
|
|
42900
43411
|
allowNewExtensionIdentity: opts.allowNewExtensionIdentity,
|
|
43412
|
+
extensionPins: opts.extensionPins,
|
|
42901
43413
|
requireExtensionIdentity: opts.requireExtensionIdentity,
|
|
42902
43414
|
onPairCode: opts.onPairCode
|
|
42903
43415
|
};
|
|
@@ -42983,6 +43495,7 @@ var FetchproxyServer = class {
|
|
|
42983
43495
|
this.role = "host";
|
|
42984
43496
|
this.hostHandle = await startHost({
|
|
42985
43497
|
httpServer: el.server,
|
|
43498
|
+
refuseOutbound: (inner, info) => this.refuseIfUnavailable(inner, info),
|
|
42986
43499
|
ownIdentity: identity,
|
|
42987
43500
|
ownMcpId: mcpId,
|
|
42988
43501
|
ownServerName: this.opts.serverName,
|
|
@@ -43013,6 +43526,7 @@ var FetchproxyServer = class {
|
|
|
43013
43526
|
} else {
|
|
43014
43527
|
this.role = "peer";
|
|
43015
43528
|
this.peerHandle = await startPeer({
|
|
43529
|
+
refuseOutbound: (inner, info) => this.refuseIfUnavailable(inner, info),
|
|
43016
43530
|
host: this.opts.host,
|
|
43017
43531
|
port: this.opts.port,
|
|
43018
43532
|
identity,
|
|
@@ -43061,7 +43575,7 @@ var FetchproxyServer = class {
|
|
|
43061
43575
|
}
|
|
43062
43576
|
}
|
|
43063
43577
|
pairingErrorMessage(code) {
|
|
43064
|
-
return `fetchproxy transport error: pairing required for ${this.opts.serverName}. Tell the user to open the
|
|
43578
|
+
return `fetchproxy transport error: pairing required for ${this.opts.serverName}. Tell the user to open the ContextMint Bridge browser extension popup and approve the pair request. The pair code is: ${code} \u2014 display this code to the user so they can verify it matches.`;
|
|
43065
43579
|
}
|
|
43066
43580
|
/**
|
|
43067
43581
|
* Raw single-shot fetch through the bridge. Most callers should prefer
|
|
@@ -43191,13 +43705,28 @@ var FetchproxyServer = class {
|
|
|
43191
43705
|
* every one of them.
|
|
43192
43706
|
*/
|
|
43193
43707
|
extensionTrust() {
|
|
43708
|
+
const allowNew = allowNewExtensionIdentity(this.opts.allowNewExtensionIdentity);
|
|
43709
|
+
if (resolveExtensionPinsMode(this.opts.extensionPins) === "managed") {
|
|
43710
|
+
if (allowNew && !this.warnedAllowNewIgnored) {
|
|
43711
|
+
this.warnedAllowNewIgnored = true;
|
|
43712
|
+
console.warn(`[fetchproxy] ${this.opts.serverName}: ignoring ${TRUST_NEW_EXTENSION_ENV} / allowNewExtensionIdentity \u2014 this MCP's extension pins are host-managed (${EXTENSION_PINS_ENV}=managed), so only the host's pin set decides which browsers it accepts.`);
|
|
43713
|
+
}
|
|
43714
|
+
return fileExtensionTrust({
|
|
43715
|
+
serverName: this.opts.serverName,
|
|
43716
|
+
dir: this.opts.identityDir,
|
|
43717
|
+
trustDir: this.opts.trustDir,
|
|
43718
|
+
allowNew: false,
|
|
43719
|
+
extensionPins: "managed"
|
|
43720
|
+
});
|
|
43721
|
+
}
|
|
43194
43722
|
return fileExtensionTrust({
|
|
43195
43723
|
serverName: this.opts.serverName,
|
|
43196
43724
|
dir: this.opts.identityDir,
|
|
43197
43725
|
trustDir: this.opts.trustDir,
|
|
43198
|
-
allowNew
|
|
43726
|
+
allowNew
|
|
43199
43727
|
});
|
|
43200
43728
|
}
|
|
43729
|
+
warnedAllowNewIgnored = false;
|
|
43201
43730
|
noteActivityForKeepalive() {
|
|
43202
43731
|
const intervalMs = this.opts.keepAliveIntervalMs;
|
|
43203
43732
|
if (intervalMs <= 0)
|
|
@@ -43291,6 +43820,15 @@ var FetchproxyServer = class {
|
|
|
43291
43820
|
resendable: isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout)
|
|
43292
43821
|
});
|
|
43293
43822
|
} catch (err) {
|
|
43823
|
+
if (err instanceof FetchproxyCapabilityUnavailableError) {
|
|
43824
|
+
return {
|
|
43825
|
+
ok: false,
|
|
43826
|
+
error: err.originalError,
|
|
43827
|
+
kind: "capability_unavailable",
|
|
43828
|
+
retryAttempted: false,
|
|
43829
|
+
cause: err
|
|
43830
|
+
};
|
|
43831
|
+
}
|
|
43294
43832
|
return {
|
|
43295
43833
|
ok: false,
|
|
43296
43834
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -43311,7 +43849,8 @@ var FetchproxyServer = class {
|
|
|
43311
43849
|
timer = setTimeout(() => {
|
|
43312
43850
|
this.pending.delete(id);
|
|
43313
43851
|
const elapsedMs = Date.now() - start;
|
|
43314
|
-
const
|
|
43852
|
+
const base = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
|
|
43853
|
+
const error62 = isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout) ? base : withNotResentNote(base);
|
|
43315
43854
|
resolve6({
|
|
43316
43855
|
ok: false,
|
|
43317
43856
|
error: error62,
|
|
@@ -43433,6 +43972,9 @@ var FetchproxyServer = class {
|
|
|
43433
43972
|
port: this.opts.port
|
|
43434
43973
|
});
|
|
43435
43974
|
}
|
|
43975
|
+
if (result.kind === "capability_unavailable") {
|
|
43976
|
+
return protocolErrorFrom(result.error, void 0, { code: CAPABILITY_UNAVAILABLE_CODE, op });
|
|
43977
|
+
}
|
|
43436
43978
|
return protocolErrorFrom(result.error);
|
|
43437
43979
|
}
|
|
43438
43980
|
/**
|
|
@@ -43499,6 +44041,9 @@ var FetchproxyServer = class {
|
|
|
43499
44041
|
if (result.kind === "session_not_ready" && result.cause instanceof Error) {
|
|
43500
44042
|
throw result.cause;
|
|
43501
44043
|
}
|
|
44044
|
+
if (result.kind === "capability_unavailable" && result.cause instanceof FetchproxyCapabilityUnavailableError) {
|
|
44045
|
+
throw result.cause;
|
|
44046
|
+
}
|
|
43502
44047
|
throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false, isRetrySafeOnTimeout(method, opts.retryOnTimeout));
|
|
43503
44048
|
}
|
|
43504
44049
|
const response = {
|
|
@@ -43681,7 +44226,9 @@ var FetchproxyServer = class {
|
|
|
43681
44226
|
session_state: health.session.state,
|
|
43682
44227
|
pending_pair_code: health.session.pairCode,
|
|
43683
44228
|
extension_connected: health.session.extensionConnected,
|
|
43684
|
-
last_extension_message_at: health.lastExtensionMessageAt
|
|
44229
|
+
last_extension_message_at: health.lastExtensionMessageAt,
|
|
44230
|
+
unavailable_capabilities: health.session.unavailableCapabilities,
|
|
44231
|
+
platform: health.session.platform
|
|
43685
44232
|
},
|
|
43686
44233
|
...error62 ? { error: error62 } : {}
|
|
43687
44234
|
};
|
|
@@ -44327,7 +44874,7 @@ var FetchproxyServer = class {
|
|
|
44327
44874
|
fetchCb({
|
|
44328
44875
|
ok: false,
|
|
44329
44876
|
error: inner.error,
|
|
44330
|
-
kind: classifyFetchError(inner.error),
|
|
44877
|
+
kind: classifyFetchError(inner.error, inner.code),
|
|
44331
44878
|
retryAttempted: false
|
|
44332
44879
|
});
|
|
44333
44880
|
}
|
|
@@ -44343,7 +44890,7 @@ var FetchproxyServer = class {
|
|
|
44343
44890
|
storageCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on storage awaiter`));
|
|
44344
44891
|
}
|
|
44345
44892
|
} else {
|
|
44346
|
-
storageCb.reject(protocolErrorFrom(inner.error));
|
|
44893
|
+
storageCb.reject(protocolErrorFrom(inner.error, void 0, inner));
|
|
44347
44894
|
}
|
|
44348
44895
|
return;
|
|
44349
44896
|
}
|
|
@@ -44357,7 +44904,7 @@ var FetchproxyServer = class {
|
|
|
44357
44904
|
captureCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture awaiter`));
|
|
44358
44905
|
}
|
|
44359
44906
|
} else {
|
|
44360
|
-
captureCb.reject(protocolErrorFrom(inner.error, "capture"));
|
|
44907
|
+
captureCb.reject(protocolErrorFrom(inner.error, "capture", inner));
|
|
44361
44908
|
}
|
|
44362
44909
|
return;
|
|
44363
44910
|
}
|
|
@@ -44371,7 +44918,7 @@ var FetchproxyServer = class {
|
|
|
44371
44918
|
redirectCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on capture_redirect awaiter`));
|
|
44372
44919
|
}
|
|
44373
44920
|
} else {
|
|
44374
|
-
redirectCb.reject(protocolErrorFrom(inner.error, "capture_redirect"));
|
|
44921
|
+
redirectCb.reject(protocolErrorFrom(inner.error, "capture_redirect", inner));
|
|
44375
44922
|
}
|
|
44376
44923
|
return;
|
|
44377
44924
|
}
|
|
@@ -44385,7 +44932,7 @@ var FetchproxyServer = class {
|
|
|
44385
44932
|
idbCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_indexed_db awaiter`));
|
|
44386
44933
|
}
|
|
44387
44934
|
} else {
|
|
44388
|
-
idbCb.reject(protocolErrorFrom(inner.error));
|
|
44935
|
+
idbCb.reject(protocolErrorFrom(inner.error, void 0, inner));
|
|
44389
44936
|
}
|
|
44390
44937
|
return;
|
|
44391
44938
|
}
|
|
@@ -44399,7 +44946,7 @@ var FetchproxyServer = class {
|
|
|
44399
44946
|
domListCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_dom_list awaiter`));
|
|
44400
44947
|
}
|
|
44401
44948
|
} else {
|
|
44402
|
-
domListCb.reject(protocolErrorFrom(inner.error));
|
|
44949
|
+
domListCb.reject(protocolErrorFrom(inner.error, void 0, inner));
|
|
44403
44950
|
}
|
|
44404
44951
|
return;
|
|
44405
44952
|
}
|
|
@@ -44413,7 +44960,7 @@ var FetchproxyServer = class {
|
|
|
44413
44960
|
downloadCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on download awaiter`));
|
|
44414
44961
|
}
|
|
44415
44962
|
} else {
|
|
44416
|
-
downloadCb.reject(protocolErrorFrom(inner.error, "download"));
|
|
44963
|
+
downloadCb.reject(protocolErrorFrom(inner.error, "download", inner));
|
|
44417
44964
|
}
|
|
44418
44965
|
return;
|
|
44419
44966
|
}
|
|
@@ -44427,7 +44974,7 @@ var FetchproxyServer = class {
|
|
|
44427
44974
|
graphqlCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on graphql_query awaiter`));
|
|
44428
44975
|
}
|
|
44429
44976
|
} else {
|
|
44430
|
-
graphqlCb.reject(protocolErrorFrom(inner.error));
|
|
44977
|
+
graphqlCb.reject(protocolErrorFrom(inner.error, void 0, inner));
|
|
44431
44978
|
}
|
|
44432
44979
|
return;
|
|
44433
44980
|
}
|
|
@@ -44437,7 +44984,7 @@ var FetchproxyServer = class {
|
|
|
44437
44984
|
if (inner.ok && inner.op === "write_cookies") {
|
|
44438
44985
|
writeCookiesCb.resolve([...inner.written]);
|
|
44439
44986
|
} else {
|
|
44440
|
-
writeCookiesCb.reject(
|
|
44987
|
+
writeCookiesCb.reject(inner.ok ? protocolErrorFrom("write_cookies response had the wrong op") : protocolErrorFrom(inner.error, void 0, inner));
|
|
44441
44988
|
}
|
|
44442
44989
|
return;
|
|
44443
44990
|
}
|
|
@@ -44627,11 +45174,38 @@ var FetchproxyServer = class {
|
|
|
44627
45174
|
const handle = this.hostHandle ?? this.peerHandle;
|
|
44628
45175
|
const pairCode = this.currentPendingPairCode();
|
|
44629
45176
|
if (!handle || this.role === null) {
|
|
44630
|
-
return {
|
|
45177
|
+
return {
|
|
45178
|
+
state: "not_listening",
|
|
45179
|
+
pairCode,
|
|
45180
|
+
extensionConnected: false,
|
|
45181
|
+
unavailableCapabilities: [],
|
|
45182
|
+
platform: null
|
|
45183
|
+
};
|
|
44631
45184
|
}
|
|
45185
|
+
const info = handle.sessionInfo?.() ?? null;
|
|
44632
45186
|
const extensionConnected = handle.extensionConnected();
|
|
44633
45187
|
const state = handle.sessionLinked() ? "linked" : pairCode !== null ? "pair_pending" : !extensionConnected ? "extension_disconnected" : "no_session";
|
|
44634
|
-
return {
|
|
45188
|
+
return {
|
|
45189
|
+
state,
|
|
45190
|
+
pairCode,
|
|
45191
|
+
extensionConnected,
|
|
45192
|
+
unavailableCapabilities: info ? [...info.unavailableCapabilities] : [],
|
|
45193
|
+
platform: info?.platform ?? null
|
|
45194
|
+
};
|
|
45195
|
+
}
|
|
45196
|
+
/**
|
|
45197
|
+
* #418: refuse, before a seq is claimed, a request whose capability the
|
|
45198
|
+
* extension said this browser cannot serve. Called by the host / peer
|
|
45199
|
+
* handle once the session is ready, so it judges against the list from the
|
|
45200
|
+
* hello that session was verified against. Advisory: the list is unsigned,
|
|
45201
|
+
* so it may only ever make this MCP refuse MORE — the extension still
|
|
45202
|
+
* enforces from its own runtime check for anything that gets through.
|
|
45203
|
+
*/
|
|
45204
|
+
refuseIfUnavailable(inner, info) {
|
|
45205
|
+
const cap = unavailableCapabilityFor(inner, info);
|
|
45206
|
+
if (cap === null || info === null)
|
|
45207
|
+
return;
|
|
45208
|
+
throw new FetchproxyCapabilityUnavailableError(capabilityUnavailableMessage(cap, info.platform), { capability: cap, platform: info.platform });
|
|
44635
45209
|
}
|
|
44636
45210
|
/**
|
|
44637
45211
|
* 0.5.2+: throw `FetchproxyProtocolError` with the actionable pair-code
|
|
@@ -45105,7 +45679,7 @@ async function loginWithPassword(username, password) {
|
|
|
45105
45679
|
// package.json
|
|
45106
45680
|
var package_default = {
|
|
45107
45681
|
name: "ofw-mcp",
|
|
45108
|
-
version: "2.19.
|
|
45682
|
+
version: "2.19.5",
|
|
45109
45683
|
license: "MIT",
|
|
45110
45684
|
mcpName: "io.github.chrischall/ofw-mcp",
|
|
45111
45685
|
description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
|
|
@@ -45139,8 +45713,8 @@ var package_default = {
|
|
|
45139
45713
|
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
45140
45714
|
},
|
|
45141
45715
|
dependencies: {
|
|
45142
|
-
"@chrischall/mcp-utils": "^2.
|
|
45143
|
-
"@fetchproxy/bootstrap": "^3.
|
|
45716
|
+
"@chrischall/mcp-utils": "^2.8.0",
|
|
45717
|
+
"@fetchproxy/bootstrap": "^3.4.1",
|
|
45144
45718
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
45145
45719
|
dotenv: "^18.0.0",
|
|
45146
45720
|
zod: "^4.6.5"
|
|
@@ -45159,8 +45733,8 @@ var package_default = {
|
|
|
45159
45733
|
function fetchproxyDisabled() {
|
|
45160
45734
|
return parseBoolEnv("OFW_DISABLE_FETCHPROXY");
|
|
45161
45735
|
}
|
|
45162
|
-
var NO_AUTH_CONFIGURED = "OFW auth: set OFW_USERNAME + OFW_PASSWORD, or install
|
|
45163
|
-
var BRIDGE_DOWN_PREFIX = "OFW auth:
|
|
45736
|
+
var NO_AUTH_CONFIGURED = "OFW auth: set OFW_USERNAME + OFW_PASSWORD, or install ContextMint Bridge and sign into ourfamilywizard.com (unset OFW_DISABLE_FETCHPROXY if it is set).";
|
|
45737
|
+
var BRIDGE_DOWN_PREFIX = "OFW auth: ContextMint Bridge is down";
|
|
45164
45738
|
function isBridgeDown(e) {
|
|
45165
45739
|
return e instanceof Error && e.message.startsWith(BRIDGE_DOWN_PREFIX);
|
|
45166
45740
|
}
|
|
@@ -45202,7 +45776,7 @@ async function resolveAuth() {
|
|
|
45202
45776
|
const expiryRaw = session.localStorage["tokenExpiry"];
|
|
45203
45777
|
if (!token) {
|
|
45204
45778
|
throw new Error(
|
|
45205
|
-
'localStorage["auth"] missing on ourfamilywizard.com. Sign into OFW in your browser (with
|
|
45779
|
+
'localStorage["auth"] missing on ourfamilywizard.com. Sign into OFW in your browser (with ContextMint Bridge installed) and retry.'
|
|
45206
45780
|
);
|
|
45207
45781
|
}
|
|
45208
45782
|
return {
|
|
@@ -45277,7 +45851,7 @@ function reportCacheWriteFailure(err) {
|
|
|
45277
45851
|
// src/client.ts
|
|
45278
45852
|
try {
|
|
45279
45853
|
const dir = dirname3(fileURLToPath(import.meta.url));
|
|
45280
|
-
await loadDotenvSafely({ path:
|
|
45854
|
+
await loadDotenvSafely({ path: join6(dir, "..", ".env") });
|
|
45281
45855
|
} catch {
|
|
45282
45856
|
}
|
|
45283
45857
|
function parseContentDispositionFilename(cd) {
|
|
@@ -45861,12 +46435,12 @@ function registerHealthcheckTools(server, client2, resolve6 = resolveAuth) {
|
|
|
45861
46435
|
kind: "transport",
|
|
45862
46436
|
// The upstream `.hint` rides along in `error.message` — it carries
|
|
45863
46437
|
// the actionable "click the toolbar icon" copy this cannot know.
|
|
45864
|
-
hint: "
|
|
46438
|
+
hint: "ContextMint Bridge is down, so the browser path could not be tried. This is not a credential problem: OFW_USERNAME/OFW_PASSWORD, if set, were not reached either. See error.message for the extension-specific fix."
|
|
45865
46439
|
} : void 0,
|
|
45866
46440
|
hints: {
|
|
45867
46441
|
// Now means exactly what it says: nothing is set up. A configured path
|
|
45868
46442
|
// that was tried and failed no longer lands here.
|
|
45869
|
-
no_credential: "No OFW credential is configured. Either set OFW_USERNAME + OFW_PASSWORD, or install
|
|
46443
|
+
no_credential: "No OFW credential is configured. Either set OFW_USERNAME + OFW_PASSWORD, or install ContextMint Bridge and sign in to ourfamilywizard.com in a tab (unsetting OFW_DISABLE_FETCHPROXY if you set it).",
|
|
45870
46444
|
credential_rejected: "OurFamilyWizard rejected the credential. If it came from `env`, the password changed or the account is locked; if from `fetchproxy`, the browser session expired \u2014 sign in again in the tab. Retrying will not fix either."
|
|
45871
46445
|
}
|
|
45872
46446
|
});
|
|
@@ -46293,19 +46867,19 @@ async function syncAll(client2, opts, store) {
|
|
|
46293
46867
|
// src/config.ts
|
|
46294
46868
|
import { createHash as createHash5 } from "node:crypto";
|
|
46295
46869
|
import { homedir as homedir4 } from "node:os";
|
|
46296
|
-
import { join as
|
|
46870
|
+
import { join as join7 } from "node:path";
|
|
46297
46871
|
function readCacheIdentity() {
|
|
46298
46872
|
return readEnvVar("OFW_CACHE_IDENTITY") ?? readEnvVar("OFW_USERNAME") ?? "_default";
|
|
46299
46873
|
}
|
|
46300
46874
|
function getCacheDir() {
|
|
46301
46875
|
const override = process.env.OFW_CACHE_DIR;
|
|
46302
46876
|
if (override && override.trim().length > 0) return override.trim();
|
|
46303
|
-
return
|
|
46877
|
+
return join7(homedir4(), ".cache", "ofw-mcp");
|
|
46304
46878
|
}
|
|
46305
46879
|
function getCacheDbPath() {
|
|
46306
46880
|
const identity = readCacheIdentity();
|
|
46307
46881
|
const hash2 = createHash5("sha256").update(identity).digest("hex").slice(0, 16);
|
|
46308
|
-
return
|
|
46882
|
+
return join7(getCacheDir(), `${hash2}.db`);
|
|
46309
46883
|
}
|
|
46310
46884
|
function getAttachmentsDir() {
|
|
46311
46885
|
const override = process.env.OFW_ATTACHMENTS_DIR;
|
|
@@ -46313,7 +46887,7 @@ function getAttachmentsDir() {
|
|
|
46313
46887
|
return getDefaultAttachmentsDir();
|
|
46314
46888
|
}
|
|
46315
46889
|
function getDefaultAttachmentsDir() {
|
|
46316
|
-
return
|
|
46890
|
+
return join7(homedir4(), "Downloads", "ofw-mcp");
|
|
46317
46891
|
}
|
|
46318
46892
|
function getUploadDir() {
|
|
46319
46893
|
const override = process.env.OFW_UPLOAD_DIR;
|
|
@@ -47856,6 +48430,7 @@ var NodeAttachmentIO = class {
|
|
|
47856
48430
|
};
|
|
47857
48431
|
|
|
47858
48432
|
// src/tools/delivery.ts
|
|
48433
|
+
var MAX_INLINE_BYTES = 10 * 1024 * 1024;
|
|
47859
48434
|
async function tryExtract(bytes, mimeType, fileName, opts) {
|
|
47860
48435
|
try {
|
|
47861
48436
|
const extracted = await extractAttachment(bytes, mimeType, fileName, {
|
|
@@ -47870,6 +48445,17 @@ async function tryExtract(bytes, mimeType, fileName, opts) {
|
|
|
47870
48445
|
return { reason: `extraction failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
47871
48446
|
}
|
|
47872
48447
|
}
|
|
48448
|
+
function assertInlineSize(input2, rawRequested) {
|
|
48449
|
+
const { bytes, fileName, fileId, diskAvailable } = input2;
|
|
48450
|
+
if (bytes.length <= MAX_INLINE_BYTES) return;
|
|
48451
|
+
const hints = [];
|
|
48452
|
+
if (rawRequested) hints.push("omit extract:false to get the file's extracted text instead, if it is a readable document type");
|
|
48453
|
+
if (diskAvailable) hints.push("pass inline:false to save it to disk and get the path");
|
|
48454
|
+
hints.push("or open it directly in OurFamilyWizard");
|
|
48455
|
+
throw new Error(
|
|
48456
|
+
`Attachment ${fileId} (${fileName}) is ${bytes.length} bytes, over the ${MAX_INLINE_BYTES / (1024 * 1024)} MiB limit for returning a file inline (a larger result would exceed the host's response size limit). To get it: ${hints.join("; ")}.`
|
|
48457
|
+
);
|
|
48458
|
+
}
|
|
47873
48459
|
async function buildInlineDelivery(input2) {
|
|
47874
48460
|
const { fileId, fileName, mimeType, bytes, forcedInline, options } = input2;
|
|
47875
48461
|
const meta3 = {
|
|
@@ -47882,6 +48468,7 @@ async function buildInlineDelivery(input2) {
|
|
|
47882
48468
|
if (forcedInline) meta3.forcedInline = true;
|
|
47883
48469
|
const block = () => ({ type: "text", text: JSON.stringify(meta3, null, 2) });
|
|
47884
48470
|
if (isHostRenderableImage(mimeType)) {
|
|
48471
|
+
assertInlineSize(input2, false);
|
|
47885
48472
|
meta3.deliveredVia = "image";
|
|
47886
48473
|
return { content: [block(), { type: "image", data: bytes.toString("base64"), mimeType }] };
|
|
47887
48474
|
}
|
|
@@ -47899,6 +48486,7 @@ async function buildInlineDelivery(input2) {
|
|
|
47899
48486
|
}
|
|
47900
48487
|
attempts.push(outcome.reason ?? "extraction produced no content");
|
|
47901
48488
|
}
|
|
48489
|
+
assertInlineSize(input2, options.extract === false);
|
|
47902
48490
|
meta3.deliveredVia = "blob";
|
|
47903
48491
|
meta3.deliveryAttempts = attempts;
|
|
47904
48492
|
meta3.note = "Returned as raw bytes. Some hosts cannot render an embedded resource of this type; if it came back unreadable, the file has no text extractor here (see deliveryAttempts).";
|
|
@@ -47915,7 +48503,7 @@ async function buildInlineDelivery(input2) {
|
|
|
47915
48503
|
}
|
|
47916
48504
|
|
|
47917
48505
|
// src/tools/messages.ts
|
|
47918
|
-
import { basename as basename2, join as
|
|
48506
|
+
import { basename as basename2, join as join8, resolve as resolve5 } from "node:path";
|
|
47919
48507
|
|
|
47920
48508
|
// src/tools/pagination.ts
|
|
47921
48509
|
function pageState(input2) {
|
|
@@ -49186,7 +49774,7 @@ ${text}` : text);
|
|
|
49186
49774
|
});
|
|
49187
49775
|
});
|
|
49188
49776
|
server.registerTool("ofw_download_attachment", {
|
|
49189
|
-
description: "Download an OFW message attachment by fileId and return content you can actually read. Inline delivery walks a ladder and returns the first rung that works: (1) host-renderable images (PNG/JPEG/GIF/WEBP) come back as ImageContent; (2) .xlsx/.csv/.tsv, .pdf, .docx, .pptx and text files come back as EXTRACTED CONTENT \u2014 per-sheet CSV, per-page/slide text, document text \u2014 in the response JSON under `extracted`; (3) anything else comes back as an EmbeddedResource blob of the raw bytes. The meta block names the rung as `deliveredVia` and, when it falls through to bytes, lists what was tried in `deliveryAttempts`. Reported mime types are always normalized to a bare media type (no charset/name parameters). In disk mode the bytes are saved to ~/Downloads/ofw-mcp/ and the response carries the absolute path; pass extract:true to ALSO get the extracted content in that response. The default for `inline` can be flipped server-side via the OFW_INLINE_ATTACHMENTS env var. On a hosted deployment with no filesystem, disk mode is unavailable, so inline is forced (forcedInline:true) rather than failing \u2014 a saveTo path never costs you the content. fileId comes from attachments[].fileId on ofw_get_message. Override disk destination with OFW_ATTACHMENTS_DIR or saveTo; saveTo must stay inside the attachments directory, and an existing file is never overwritten unless force:true. Re-downloading to the same path is a no-op (disk mode only).",
|
|
49777
|
+
description: "Download an OFW message attachment by fileId and return content you can actually read. Inline delivery walks a ladder and returns the first rung that works: (1) host-renderable images (PNG/JPEG/GIF/WEBP) come back as ImageContent; (2) .xlsx/.csv/.tsv, .pdf, .docx, .pptx and text files come back as EXTRACTED CONTENT \u2014 per-sheet CSV, per-page/slide text, document text \u2014 in the response JSON under `extracted`; (3) anything else comes back as an EmbeddedResource blob of the raw bytes. Images and raw bytes are only returned inline up to 10 MiB; a larger file fails with a tool error that says how to get it instead (extracted text is unaffected \u2014 it is bounded by maxChars). The meta block names the rung as `deliveredVia` and, when it falls through to bytes, lists what was tried in `deliveryAttempts`. Reported mime types are always normalized to a bare media type (no charset/name parameters). In disk mode the bytes are saved to ~/Downloads/ofw-mcp/ and the response carries the absolute path; pass extract:true to ALSO get the extracted content in that response. The default for `inline` can be flipped server-side via the OFW_INLINE_ATTACHMENTS env var. On a hosted deployment with no filesystem, disk mode is unavailable, so inline is forced (forcedInline:true) rather than failing \u2014 a saveTo path never costs you the content. fileId comes from attachments[].fileId on ofw_get_message. Override disk destination with OFW_ATTACHMENTS_DIR or saveTo; saveTo must stay inside the attachments directory, and an existing file is never overwritten unless force:true. Re-downloading to the same path is a no-op (disk mode only).",
|
|
49190
49778
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
49191
49779
|
inputSchema: external_exports.object({
|
|
49192
49780
|
fileId: external_exports.number().describe("Attachment file id (from ofw_get_message \u2192 attachments[].fileId)"),
|
|
@@ -49230,7 +49818,8 @@ ${text}` : text);
|
|
|
49230
49818
|
mimeType: mimeType2,
|
|
49231
49819
|
bytes,
|
|
49232
49820
|
forcedInline,
|
|
49233
|
-
options: deliveryOptions
|
|
49821
|
+
options: deliveryOptions,
|
|
49822
|
+
diskAvailable: attachmentIO.supportsDisk
|
|
49234
49823
|
});
|
|
49235
49824
|
}
|
|
49236
49825
|
let dest;
|
|
@@ -49239,12 +49828,12 @@ ${text}` : text);
|
|
|
49239
49828
|
if (args.saveTo) {
|
|
49240
49829
|
const isDirArg = args.saveTo.endsWith("/") || args.saveTo.endsWith("\\");
|
|
49241
49830
|
const abs = resolve5(root, args.saveTo.startsWith("~") ? expandPath2(args.saveTo) : args.saveTo);
|
|
49242
|
-
dest = isDirArg ?
|
|
49831
|
+
dest = isDirArg ? join8(abs, `${fileId}-${safeName}`) : abs;
|
|
49243
49832
|
if (!isWithin(root, dest)) {
|
|
49244
49833
|
throw new Error(`Refusing to save to ${dest}: it is outside the attachments directory (${root}). Downloads can only be written inside it \u2014 pass a path or subdirectory under it, or set OFW_ATTACHMENTS_DIR to move it.`);
|
|
49245
49834
|
}
|
|
49246
49835
|
} else {
|
|
49247
|
-
dest =
|
|
49836
|
+
dest = join8(root, `${fileId}-${safeName}`);
|
|
49248
49837
|
}
|
|
49249
49838
|
const extractOnDisk = args.extract === true;
|
|
49250
49839
|
if (!args.force && cached2.downloadedPath === dest) {
|
|
@@ -50515,7 +51104,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
|
|
|
50515
51104
|
var nodeAttachmentIO = new NodeAttachmentIO();
|
|
50516
51105
|
await runMcp({
|
|
50517
51106
|
name: "ofw",
|
|
50518
|
-
version: "2.19.
|
|
51107
|
+
version: "2.19.5",
|
|
50519
51108
|
// x-release-please-version
|
|
50520
51109
|
deps: client,
|
|
50521
51110
|
tools: [
|
|
@@ -50530,7 +51119,7 @@ await runMcp({
|
|
|
50530
51119
|
});
|
|
50531
51120
|
/*! Bundled license information:
|
|
50532
51121
|
|
|
50533
|
-
@modelcontextprotocol/server/dist/src-
|
|
51122
|
+
@modelcontextprotocol/server/dist/src-D-y6h4N7.mjs:
|
|
50534
51123
|
(*!
|
|
50535
51124
|
* content-type
|
|
50536
51125
|
* Copyright(c) 2015 Douglas Christopher Wilson
|