ofw-mcp 2.17.0 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/auth-password.js +6 -0
- package/dist/bundle.js +734 -23
- package/dist/client.js +10 -2
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "OurFamilyWizard tools for Claude Code",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.19.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"displayName": "OurFamilyWizard",
|
|
15
15
|
"source": "./",
|
|
16
16
|
"description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
|
|
17
|
-
"version": "2.
|
|
17
|
+
"version": "2.19.0",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Chris Chall"
|
|
20
20
|
},
|
package/dist/auth-password.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// This file exists as a standalone helper (not a method on `OFWClient`) so
|
|
10
10
|
// `resolveAuth()` in `./auth.ts` can call it without a Client instance, and
|
|
11
11
|
// so tests can mock it at the module boundary.
|
|
12
|
+
import { currentCallSignal } from '@chrischall/mcp-utils';
|
|
12
13
|
import { BASE_URL, OFW_PROTOCOL_HEADERS, OFW_TOKEN_TTL_MS, assertOfwUrl } from './protocol.js';
|
|
13
14
|
export async function loginWithPassword(username, password) {
|
|
14
15
|
// Step 1: get a SESSION cookie (Spring Security refuses the POST without it).
|
|
@@ -17,6 +18,10 @@ export async function loginWithPassword(username, password) {
|
|
|
17
18
|
const initResponse = await fetch(initUrl, {
|
|
18
19
|
headers: { ...OFW_PROTOCOL_HEADERS },
|
|
19
20
|
redirect: 'manual',
|
|
21
|
+
// Honour a caller who has given up (mcp-utils `cancel`). A sign-in
|
|
22
|
+
// nobody is waiting for should not keep hitting OFW, which counts
|
|
23
|
+
// failed attempts against the account.
|
|
24
|
+
signal: currentCallSignal(),
|
|
20
25
|
});
|
|
21
26
|
// headers.get('set-cookie') folds multiple Set-Cookie headers into one
|
|
22
27
|
// comma-joined string; getSetCookie() preserves them individually. Echo
|
|
@@ -30,6 +35,7 @@ export async function loginWithPassword(username, password) {
|
|
|
30
35
|
assertOfwUrl(loginUrl);
|
|
31
36
|
const response = await fetch(loginUrl, {
|
|
32
37
|
method: 'POST',
|
|
38
|
+
signal: currentCallSignal(),
|
|
33
39
|
headers: {
|
|
34
40
|
...OFW_PROTOCOL_HEADERS,
|
|
35
41
|
Accept: 'application/json',
|
package/dist/bundle.js
CHANGED
|
@@ -3710,6 +3710,24 @@ var require_websocket_server = __commonJS({
|
|
|
3710
3710
|
}
|
|
3711
3711
|
});
|
|
3712
3712
|
|
|
3713
|
+
// node_modules/@chrischall/mcp-utils/dist/cancel/index.js
|
|
3714
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3715
|
+
var storage = new AsyncLocalStorage();
|
|
3716
|
+
function withCallSignal(signal, fn) {
|
|
3717
|
+
return signal ? storage.run({ signal }, fn) : fn();
|
|
3718
|
+
}
|
|
3719
|
+
function currentCallSignal() {
|
|
3720
|
+
return storage.getStore()?.signal;
|
|
3721
|
+
}
|
|
3722
|
+
function withAmbientCancellation(own2) {
|
|
3723
|
+
const ambient = currentCallSignal();
|
|
3724
|
+
if (!ambient)
|
|
3725
|
+
return own2;
|
|
3726
|
+
if (!own2)
|
|
3727
|
+
return ambient;
|
|
3728
|
+
return AbortSignal.any([own2, ambient]);
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3713
3731
|
// node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
3714
3732
|
var __create2 = Object.create;
|
|
3715
3733
|
var __defProp2 = Object.defineProperty;
|
|
@@ -24550,6 +24568,7 @@ function missingClientCapabilities(required2, declared) {
|
|
|
24550
24568
|
return Object.keys(missing).length > 0 ? missing : void 0;
|
|
24551
24569
|
}
|
|
24552
24570
|
var FIRST_MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
24571
|
+
var SUPPORTED_MODERN_PROTOCOL_VERSIONS = [FIRST_MODERN_PROTOCOL_VERSION];
|
|
24553
24572
|
function isModernProtocolVersion(version2) {
|
|
24554
24573
|
return version2 >= FIRST_MODERN_PROTOCOL_VERSION;
|
|
24555
24574
|
}
|
|
@@ -27108,6 +27127,25 @@ function isSpecNotificationMethod(method) {
|
|
|
27108
27127
|
return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
|
|
27109
27128
|
}
|
|
27110
27129
|
var ALL_CODECS = [rev2025Codec, rev2026Codec];
|
|
27130
|
+
function isPlainObject$3(value) {
|
|
27131
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
27132
|
+
}
|
|
27133
|
+
function requestMetaOf(params) {
|
|
27134
|
+
if (!isPlainObject$3(params)) return void 0;
|
|
27135
|
+
const meta3 = params["_meta"];
|
|
27136
|
+
return isPlainObject$3(meta3) ? meta3 : void 0;
|
|
27137
|
+
}
|
|
27138
|
+
function hasEnvelopeClaim(params) {
|
|
27139
|
+
const meta3 = requestMetaOf(params);
|
|
27140
|
+
return meta3 !== void 0 && PROTOCOL_VERSION_META_KEY in meta3;
|
|
27141
|
+
}
|
|
27142
|
+
function envelopeClaimVersion(params) {
|
|
27143
|
+
const value = requestMetaOf(params)?.[PROTOCOL_VERSION_META_KEY];
|
|
27144
|
+
return typeof value === "string" ? value : void 0;
|
|
27145
|
+
}
|
|
27146
|
+
function validateEnvelopeMeta(meta3) {
|
|
27147
|
+
return codecForVersion(MODERN_WIRE_REVISION).validateEnvelopeMeta(meta3);
|
|
27148
|
+
}
|
|
27111
27149
|
var schemas_exports3 = /* @__PURE__ */ __exportAll({
|
|
27112
27150
|
AnnotationsSchema: () => AnnotationsSchema,
|
|
27113
27151
|
AudioContentSchema: () => AudioContentSchema,
|
|
@@ -27458,6 +27496,45 @@ var LADDER_ERROR_HTTP_STATUS = {
|
|
|
27458
27496
|
[ProtocolErrorCode.MissingRequiredClientCapability]: 400,
|
|
27459
27497
|
[HEADER_MISMATCH_ERROR_CODE]: 400
|
|
27460
27498
|
};
|
|
27499
|
+
function rejection(rung, cell, httpStatus, error62, settled) {
|
|
27500
|
+
return {
|
|
27501
|
+
kind: "reject",
|
|
27502
|
+
rung,
|
|
27503
|
+
cell,
|
|
27504
|
+
httpStatus,
|
|
27505
|
+
code: error62.code,
|
|
27506
|
+
message: error62.message,
|
|
27507
|
+
...error62.data !== void 0 && { data: error62.data },
|
|
27508
|
+
settled
|
|
27509
|
+
};
|
|
27510
|
+
}
|
|
27511
|
+
function carriesValidModernEnvelopeClaim(params) {
|
|
27512
|
+
if (!hasEnvelopeClaim(params)) return false;
|
|
27513
|
+
const claimedVersion = envelopeClaimVersion(params);
|
|
27514
|
+
if (claimedVersion === void 0 || !isModernProtocolVersion(claimedVersion)) return false;
|
|
27515
|
+
const meta3 = requestMetaOf(params);
|
|
27516
|
+
return meta3 !== void 0 && validateEnvelopeMeta(meta3).length === 0;
|
|
27517
|
+
}
|
|
27518
|
+
function modernOnlyStrictRejection(route, supportedVersions) {
|
|
27519
|
+
switch (route.reason) {
|
|
27520
|
+
case "http-method":
|
|
27521
|
+
return rejection("http-method", "modern-only-method-not-allowed", 405, new ProtocolError(-32e3, "Method not allowed."), true);
|
|
27522
|
+
case "batch":
|
|
27523
|
+
return rejection("jsonrpc-shape", "modern-only-batch-not-supported", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batches are not supported by this endpoint"), true);
|
|
27524
|
+
case "response":
|
|
27525
|
+
return rejection("jsonrpc-shape", "modern-only-response-post", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC responses cannot be posted to this endpoint"), true);
|
|
27526
|
+
case "notification":
|
|
27527
|
+
return;
|
|
27528
|
+
case "initialize":
|
|
27529
|
+
case "no-claim": {
|
|
27530
|
+
const requested = route.requestedVersion;
|
|
27531
|
+
return rejection("era-classification", "modern-only-missing-envelope", 400, requested === void 0 ? new ProtocolError(ProtocolErrorCode.UnsupportedProtocolVersion, "Unsupported protocol version: the request did not name a protocol version", { supported: [...supportedVersions] }) : new UnsupportedProtocolVersionError({
|
|
27532
|
+
supported: [...supportedVersions],
|
|
27533
|
+
requested
|
|
27534
|
+
}), true);
|
|
27535
|
+
}
|
|
27536
|
+
}
|
|
27537
|
+
}
|
|
27461
27538
|
function parseSchema(schema, data) {
|
|
27462
27539
|
return safeParse2(schema, data);
|
|
27463
27540
|
}
|
|
@@ -28019,6 +28096,9 @@ function withRequestStateValue(ctx, value) {
|
|
|
28019
28096
|
};
|
|
28020
28097
|
}
|
|
28021
28098
|
var writeNegotiatedProtocolVersion;
|
|
28099
|
+
function setNegotiatedProtocolVersion(instance, version2) {
|
|
28100
|
+
writeNegotiatedProtocolVersion(instance, version2);
|
|
28101
|
+
}
|
|
28022
28102
|
var Protocol = class {
|
|
28023
28103
|
_transport;
|
|
28024
28104
|
_requestMessageId = 0;
|
|
@@ -35962,6 +36042,190 @@ function getCompleter(schema) {
|
|
|
35962
36042
|
return schema[COMPLETABLE_SYMBOL]?.complete;
|
|
35963
36043
|
}
|
|
35964
36044
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
36045
|
+
function listenFilterAccepts(filter, event) {
|
|
36046
|
+
switch (event.kind) {
|
|
36047
|
+
case "tools_list_changed":
|
|
36048
|
+
return filter.toolsListChanged === true;
|
|
36049
|
+
case "prompts_list_changed":
|
|
36050
|
+
return filter.promptsListChanged === true;
|
|
36051
|
+
case "resources_list_changed":
|
|
36052
|
+
return filter.resourcesListChanged === true;
|
|
36053
|
+
case "resource_updated":
|
|
36054
|
+
return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
|
|
36055
|
+
}
|
|
36056
|
+
}
|
|
36057
|
+
function honoredSubset(requested, capabilities) {
|
|
36058
|
+
const honored = {};
|
|
36059
|
+
const allow = (bit) => capabilities === void 0 || bit === true;
|
|
36060
|
+
if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
|
|
36061
|
+
if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
|
|
36062
|
+
if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
|
|
36063
|
+
if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
|
|
36064
|
+
return honored;
|
|
36065
|
+
}
|
|
36066
|
+
var DEFAULT_MAX_SUBSCRIPTIONS = 1024;
|
|
36067
|
+
function stampSubscriptionId(notification, subscriptionId) {
|
|
36068
|
+
return {
|
|
36069
|
+
method: notification.method,
|
|
36070
|
+
params: {
|
|
36071
|
+
...notification.params,
|
|
36072
|
+
_meta: {
|
|
36073
|
+
...notification.params?._meta,
|
|
36074
|
+
[SUBSCRIPTION_ID_META_KEY]: subscriptionId
|
|
36075
|
+
}
|
|
36076
|
+
}
|
|
36077
|
+
};
|
|
36078
|
+
}
|
|
36079
|
+
function parseListenFilter(message) {
|
|
36080
|
+
const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
|
|
36081
|
+
return outcome.ok ? outcome.value.params?.notifications : void 0;
|
|
36082
|
+
}
|
|
36083
|
+
var CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
|
|
36084
|
+
"notifications/tools/list_changed",
|
|
36085
|
+
"notifications/prompts/list_changed",
|
|
36086
|
+
"notifications/resources/list_changed",
|
|
36087
|
+
"notifications/resources/updated"
|
|
36088
|
+
]);
|
|
36089
|
+
var StdioListenRouter = class {
|
|
36090
|
+
/** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
|
|
36091
|
+
_subs = /* @__PURE__ */ new Map();
|
|
36092
|
+
/**
|
|
36093
|
+
* The serving instance's declared capabilities. Filled in by the entry
|
|
36094
|
+
* once the modern instance is constructed (the router is created before
|
|
36095
|
+
* the instance exists), so the acknowledged filter is narrowed against
|
|
36096
|
+
* what the server can actually deliver.
|
|
36097
|
+
*/
|
|
36098
|
+
_serverCapabilities;
|
|
36099
|
+
/**
|
|
36100
|
+
* The serving instance's identity, stamped onto the graceful-close
|
|
36101
|
+
* results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
|
|
36102
|
+
* `ResultMetaObject`). Handed over together with the capabilities.
|
|
36103
|
+
*/
|
|
36104
|
+
_serverInfo;
|
|
36105
|
+
constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
|
|
36106
|
+
this._maxSubscriptions = _maxSubscriptions;
|
|
36107
|
+
this._serverCapabilities = serverCapabilities;
|
|
36108
|
+
this._serverInfo = serverInfo;
|
|
36109
|
+
}
|
|
36110
|
+
/**
|
|
36111
|
+
* Record the serving instance's declared capabilities and identity once
|
|
36112
|
+
* it has been constructed. Called by `serveStdio`'s connect path;
|
|
36113
|
+
* subsequent `serve()` calls narrow the honored filter against the
|
|
36114
|
+
* capabilities, and `teardownAll()` stamps the identity.
|
|
36115
|
+
*/
|
|
36116
|
+
setServerCapabilities(capabilities, serverInfo) {
|
|
36117
|
+
this._serverCapabilities = capabilities;
|
|
36118
|
+
if (serverInfo !== void 0) this._serverInfo = serverInfo;
|
|
36119
|
+
}
|
|
36120
|
+
/** Whether `id` is an active listen subscription on this connection. */
|
|
36121
|
+
has(id) {
|
|
36122
|
+
return this._subs.has(id);
|
|
36123
|
+
}
|
|
36124
|
+
/**
|
|
36125
|
+
* Serve one inbound `subscriptions/listen` request: registers the
|
|
36126
|
+
* subscription and returns the stamped acknowledged notification (or, on
|
|
36127
|
+
* capacity / params rejection, the in-band JSON-RPC error response).
|
|
36128
|
+
*
|
|
36129
|
+
* @throws when called before {@linkcode setServerCapabilities} (or the
|
|
36130
|
+
* constructor) has supplied the serving instance's capabilities. Honoring a
|
|
36131
|
+
* filter without knowing the server's advertised capabilities would fail
|
|
36132
|
+
* open (deliver unadvertised types); the entry guarantees capabilities are
|
|
36133
|
+
* set before any listen request is routed here.
|
|
36134
|
+
*/
|
|
36135
|
+
serve(message) {
|
|
36136
|
+
if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
|
|
36137
|
+
if (this._subs.size >= this._maxSubscriptions) return {
|
|
36138
|
+
jsonrpc: "2.0",
|
|
36139
|
+
id: message.id,
|
|
36140
|
+
error: {
|
|
36141
|
+
code: -32603,
|
|
36142
|
+
message: "Subscription limit reached"
|
|
36143
|
+
}
|
|
36144
|
+
};
|
|
36145
|
+
const filter = parseListenFilter(message);
|
|
36146
|
+
if (filter === void 0) return {
|
|
36147
|
+
jsonrpc: "2.0",
|
|
36148
|
+
id: message.id,
|
|
36149
|
+
error: {
|
|
36150
|
+
code: -32602,
|
|
36151
|
+
message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
|
|
36152
|
+
}
|
|
36153
|
+
};
|
|
36154
|
+
const honored = honoredSubset(filter, this._serverCapabilities);
|
|
36155
|
+
this._subs.set(message.id, honored);
|
|
36156
|
+
return stampSubscriptionId({
|
|
36157
|
+
method: "notifications/subscriptions/acknowledged",
|
|
36158
|
+
params: { notifications: honored }
|
|
36159
|
+
}, message.id);
|
|
36160
|
+
}
|
|
36161
|
+
/**
|
|
36162
|
+
* Tear down one subscription (inbound `notifications/cancelled`). Returns
|
|
36163
|
+
* `true` when a subscription was removed. After this call NOTHING further
|
|
36164
|
+
* is delivered for that subscription id (the post-cancel hardening).
|
|
36165
|
+
*/
|
|
36166
|
+
cancel(id) {
|
|
36167
|
+
return this._subs.delete(id);
|
|
36168
|
+
}
|
|
36169
|
+
/**
|
|
36170
|
+
* Route an outbound notification through the active subscriptions.
|
|
36171
|
+
*
|
|
36172
|
+
* - For a subscription-gated change notification, returns one stamped copy
|
|
36173
|
+
* per subscription that opted in to it (an empty array means it is
|
|
36174
|
+
* dropped — the modern era never delivers an un-requested change type).
|
|
36175
|
+
* - For any other outbound message, returns `'passthrough'` (the entry
|
|
36176
|
+
* forwards it as-is).
|
|
36177
|
+
*/
|
|
36178
|
+
routeOutbound(message) {
|
|
36179
|
+
if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
|
|
36180
|
+
const uriParam = message.params?.["uri"];
|
|
36181
|
+
const uri = typeof uriParam === "string" ? uriParam : void 0;
|
|
36182
|
+
const event = notificationToServerEvent(message.method, uri);
|
|
36183
|
+
const out = [];
|
|
36184
|
+
for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
|
|
36185
|
+
method: message.method,
|
|
36186
|
+
params: message.params ?? {}
|
|
36187
|
+
}, subscriptionId));
|
|
36188
|
+
return out;
|
|
36189
|
+
}
|
|
36190
|
+
/**
|
|
36191
|
+
* Server-side graceful teardown of every active subscription: returns the
|
|
36192
|
+
* empty `subscriptions/listen` JSON-RPC result for each subscription id —
|
|
36193
|
+
* the spec's graceful-close signal, `_meta` carrying the subscription id
|
|
36194
|
+
* and the serving instance's identity — for the entry to emit before
|
|
36195
|
+
* closing the wire. Clears the set so nothing further is delivered.
|
|
36196
|
+
*/
|
|
36197
|
+
teardownAll() {
|
|
36198
|
+
const out = [];
|
|
36199
|
+
for (const id of this._subs.keys()) out.push({
|
|
36200
|
+
jsonrpc: "2.0",
|
|
36201
|
+
id,
|
|
36202
|
+
result: {
|
|
36203
|
+
resultType: "complete",
|
|
36204
|
+
_meta: {
|
|
36205
|
+
[SUBSCRIPTION_ID_META_KEY]: id,
|
|
36206
|
+
...this._serverInfo !== void 0 && { [SERVER_INFO_META_KEY]: this._serverInfo }
|
|
36207
|
+
}
|
|
36208
|
+
}
|
|
36209
|
+
});
|
|
36210
|
+
this._subs.clear();
|
|
36211
|
+
return out;
|
|
36212
|
+
}
|
|
36213
|
+
};
|
|
36214
|
+
function notificationToServerEvent(method, uri) {
|
|
36215
|
+
switch (method) {
|
|
36216
|
+
case "notifications/tools/list_changed":
|
|
36217
|
+
return { kind: "tools_list_changed" };
|
|
36218
|
+
case "notifications/prompts/list_changed":
|
|
36219
|
+
return { kind: "prompts_list_changed" };
|
|
36220
|
+
case "notifications/resources/list_changed":
|
|
36221
|
+
return { kind: "resources_list_changed" };
|
|
36222
|
+
default:
|
|
36223
|
+
return {
|
|
36224
|
+
kind: "resource_updated",
|
|
36225
|
+
uri: uri ?? ""
|
|
36226
|
+
};
|
|
36227
|
+
}
|
|
36228
|
+
}
|
|
35965
36229
|
var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
|
|
35966
36230
|
var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
|
|
35967
36231
|
function resolveLegacyShimOptions(options) {
|
|
@@ -36099,6 +36363,12 @@ var INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
|
|
|
36099
36363
|
var writeClientIdentity;
|
|
36100
36364
|
var installDiscoverHandler;
|
|
36101
36365
|
var readServerIdentity;
|
|
36366
|
+
function installModernOnlyHandlers(server, servedModernVersions) {
|
|
36367
|
+
installDiscoverHandler(server, servedModernVersions);
|
|
36368
|
+
}
|
|
36369
|
+
function serverIdentityOf(server) {
|
|
36370
|
+
return readServerIdentity(server);
|
|
36371
|
+
}
|
|
36102
36372
|
var Server = class extends Protocol {
|
|
36103
36373
|
_clientCapabilities;
|
|
36104
36374
|
_clientVersion;
|
|
@@ -37373,6 +37643,400 @@ var StdioServerTransport = class {
|
|
|
37373
37643
|
});
|
|
37374
37644
|
}
|
|
37375
37645
|
};
|
|
37646
|
+
var DISCARD_ANSWER_TIMEOUT_MS = 3e3;
|
|
37647
|
+
var StdioConnectionChannel = class {
|
|
37648
|
+
onclose;
|
|
37649
|
+
onerror;
|
|
37650
|
+
onmessage;
|
|
37651
|
+
_closed = false;
|
|
37652
|
+
/** Request ids the entry delivered to the instance that the instance has not yet answered. */
|
|
37653
|
+
_pendingRequests = /* @__PURE__ */ new Set();
|
|
37654
|
+
_drainWaiters = [];
|
|
37655
|
+
constructor(_wire, _onInstanceClose, _outboundIntercept) {
|
|
37656
|
+
this._wire = _wire;
|
|
37657
|
+
this._onInstanceClose = _onInstanceClose;
|
|
37658
|
+
this._outboundIntercept = _outboundIntercept;
|
|
37659
|
+
}
|
|
37660
|
+
async start() {
|
|
37661
|
+
}
|
|
37662
|
+
async send(message, options) {
|
|
37663
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
37664
|
+
const { id } = message;
|
|
37665
|
+
if (id !== void 0) this._settle(id);
|
|
37666
|
+
}
|
|
37667
|
+
if (this._closed) return;
|
|
37668
|
+
if (this._outboundIntercept?.(message) === "handled") return;
|
|
37669
|
+
return this._wire.send(message, options);
|
|
37670
|
+
}
|
|
37671
|
+
setProtocolVersion = (version2) => {
|
|
37672
|
+
this._wire.setProtocolVersion?.(version2);
|
|
37673
|
+
};
|
|
37674
|
+
/** Forwards one inbound message to the connected instance. */
|
|
37675
|
+
deliver(message, extra) {
|
|
37676
|
+
if (this._closed) return;
|
|
37677
|
+
if (isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
|
|
37678
|
+
else if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
|
|
37679
|
+
const cancelledId = message.params?.requestId;
|
|
37680
|
+
if (cancelledId !== void 0) this._settle(cancelledId);
|
|
37681
|
+
}
|
|
37682
|
+
this.onmessage?.(message, extra);
|
|
37683
|
+
}
|
|
37684
|
+
/**
|
|
37685
|
+
* Resolves once every request delivered to the instance has been answered
|
|
37686
|
+
* through {@linkcode send}, settled by a delivered cancellation, or the
|
|
37687
|
+
* channel has been closed and nothing further can be answered. The wait is
|
|
37688
|
+
* bounded by `timeoutMs` as a backstop so no edge can hold the caller
|
|
37689
|
+
* indefinitely; resolves `false` only when the bound elapsed with requests
|
|
37690
|
+
* still unanswered. Used by the probe-discard path so a probe request the
|
|
37691
|
+
* entry accepted is never silently dropped.
|
|
37692
|
+
*/
|
|
37693
|
+
async whenRequestsAnswered(timeoutMs) {
|
|
37694
|
+
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
37695
|
+
return await new Promise((resolve3) => {
|
|
37696
|
+
const waiter = () => {
|
|
37697
|
+
clearTimeout(timer);
|
|
37698
|
+
resolve3(true);
|
|
37699
|
+
};
|
|
37700
|
+
const timer = setTimeout(() => {
|
|
37701
|
+
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
37702
|
+
resolve3(false);
|
|
37703
|
+
}, timeoutMs);
|
|
37704
|
+
this._drainWaiters.push(waiter);
|
|
37705
|
+
});
|
|
37706
|
+
}
|
|
37707
|
+
async close() {
|
|
37708
|
+
if (this._closed) return;
|
|
37709
|
+
this._closed = true;
|
|
37710
|
+
this._pendingRequests.clear();
|
|
37711
|
+
this._releaseDrainWaiters();
|
|
37712
|
+
try {
|
|
37713
|
+
this._onInstanceClose();
|
|
37714
|
+
} finally {
|
|
37715
|
+
this.onclose?.();
|
|
37716
|
+
}
|
|
37717
|
+
}
|
|
37718
|
+
_settle(id) {
|
|
37719
|
+
this._pendingRequests.delete(id);
|
|
37720
|
+
if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
|
|
37721
|
+
}
|
|
37722
|
+
_releaseDrainWaiters() {
|
|
37723
|
+
const waiters = this._drainWaiters;
|
|
37724
|
+
this._drainWaiters = [];
|
|
37725
|
+
for (const waiter of waiters) waiter();
|
|
37726
|
+
}
|
|
37727
|
+
};
|
|
37728
|
+
function classifyOpeningMessage(message) {
|
|
37729
|
+
const params = message.params;
|
|
37730
|
+
if (message.method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
|
|
37731
|
+
const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
|
|
37732
|
+
return {
|
|
37733
|
+
kind: "legacy",
|
|
37734
|
+
reason: "initialize",
|
|
37735
|
+
...requestedVersion !== void 0 && { requestedVersion }
|
|
37736
|
+
};
|
|
37737
|
+
}
|
|
37738
|
+
if (!hasEnvelopeClaim(params)) return {
|
|
37739
|
+
kind: "legacy",
|
|
37740
|
+
reason: "no-claim"
|
|
37741
|
+
};
|
|
37742
|
+
const meta3 = requestMetaOf(params);
|
|
37743
|
+
const firstIssue = (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0];
|
|
37744
|
+
if (firstIssue !== void 0) return {
|
|
37745
|
+
kind: "invalid-envelope",
|
|
37746
|
+
issue: firstIssue
|
|
37747
|
+
};
|
|
37748
|
+
const claimedVersion = envelopeClaimVersion(params);
|
|
37749
|
+
if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
|
|
37750
|
+
kind: "unsupported-revision",
|
|
37751
|
+
requested: claimedVersion ?? "unknown"
|
|
37752
|
+
};
|
|
37753
|
+
return {
|
|
37754
|
+
kind: "modern",
|
|
37755
|
+
revision: claimedVersion,
|
|
37756
|
+
classification: {
|
|
37757
|
+
era: "modern",
|
|
37758
|
+
revision: claimedVersion
|
|
37759
|
+
}
|
|
37760
|
+
};
|
|
37761
|
+
}
|
|
37762
|
+
function serveStdio(factory, options = {}) {
|
|
37763
|
+
const legacyMode = options.legacy ?? "serve";
|
|
37764
|
+
const wire = options.transport ?? new StdioServerTransport();
|
|
37765
|
+
let state = { phase: "opening" };
|
|
37766
|
+
let discarding;
|
|
37767
|
+
let closing = false;
|
|
37768
|
+
const isTornDown = () => closing || state.phase === "closed";
|
|
37769
|
+
const reportError = (error62) => {
|
|
37770
|
+
try {
|
|
37771
|
+
options.onerror?.(error62);
|
|
37772
|
+
} catch {
|
|
37773
|
+
}
|
|
37774
|
+
};
|
|
37775
|
+
const writeErrorResponse = (id, code, message, data) => wire.send({
|
|
37776
|
+
jsonrpc: "2.0",
|
|
37777
|
+
id,
|
|
37778
|
+
error: {
|
|
37779
|
+
code,
|
|
37780
|
+
message,
|
|
37781
|
+
...data !== void 0 && { data }
|
|
37782
|
+
}
|
|
37783
|
+
}).catch((error62) => reportError(toError(error62)));
|
|
37784
|
+
const listenRouter = new StdioListenRouter(options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS);
|
|
37785
|
+
const modernOutboundIntercept = (message) => {
|
|
37786
|
+
if (!isJSONRPCNotification(message)) return void 0;
|
|
37787
|
+
const routed = listenRouter.routeOutbound(message);
|
|
37788
|
+
if (routed === "passthrough") return void 0;
|
|
37789
|
+
for (const stamped of routed) wire.send({
|
|
37790
|
+
jsonrpc: "2.0",
|
|
37791
|
+
...stamped
|
|
37792
|
+
}).catch((error62) => reportError(toError(error62)));
|
|
37793
|
+
return "handled";
|
|
37794
|
+
};
|
|
37795
|
+
const tryServeListen = async (message) => {
|
|
37796
|
+
if (isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
|
|
37797
|
+
const meta3 = requestMetaOf(message.params);
|
|
37798
|
+
const issue2 = hasEnvelopeClaim(message.params) ? (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0] : {
|
|
37799
|
+
key: "_meta",
|
|
37800
|
+
problem: "the per-request envelope is required on protocol revision 2026-07-28"
|
|
37801
|
+
};
|
|
37802
|
+
const claimedVersion = envelopeClaimVersion(message.params);
|
|
37803
|
+
let reply;
|
|
37804
|
+
if (issue2 !== void 0) reply = {
|
|
37805
|
+
jsonrpc: "2.0",
|
|
37806
|
+
id: message.id,
|
|
37807
|
+
error: {
|
|
37808
|
+
code: -32602,
|
|
37809
|
+
message: `Invalid _meta envelope: ${issue2.key}: ${issue2.problem}`
|
|
37810
|
+
}
|
|
37811
|
+
};
|
|
37812
|
+
else if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
|
|
37813
|
+
const error62 = new UnsupportedProtocolVersionError({
|
|
37814
|
+
supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
|
|
37815
|
+
requested: claimedVersion ?? "unknown"
|
|
37816
|
+
});
|
|
37817
|
+
reply = {
|
|
37818
|
+
jsonrpc: "2.0",
|
|
37819
|
+
id: message.id,
|
|
37820
|
+
error: {
|
|
37821
|
+
code: error62.code,
|
|
37822
|
+
message: error62.message,
|
|
37823
|
+
data: error62.data
|
|
37824
|
+
}
|
|
37825
|
+
};
|
|
37826
|
+
} else reply = listenRouter.serve(message);
|
|
37827
|
+
await wire.send("error" in reply ? reply : {
|
|
37828
|
+
jsonrpc: "2.0",
|
|
37829
|
+
method: reply.method,
|
|
37830
|
+
params: reply.params
|
|
37831
|
+
}).catch((error62) => reportError(toError(error62)));
|
|
37832
|
+
return true;
|
|
37833
|
+
}
|
|
37834
|
+
if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
|
|
37835
|
+
const cancelledId = message.params?.requestId;
|
|
37836
|
+
if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
|
|
37837
|
+
}
|
|
37838
|
+
return false;
|
|
37839
|
+
};
|
|
37840
|
+
const answerLegacyRejection = (request, reason, requestedVersion) => {
|
|
37841
|
+
const rejection2 = modernOnlyStrictRejection({
|
|
37842
|
+
kind: "legacy",
|
|
37843
|
+
reason,
|
|
37844
|
+
...requestedVersion !== void 0 && { requestedVersion }
|
|
37845
|
+
}, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
37846
|
+
if (rejection2 === void 0) return Promise.resolve();
|
|
37847
|
+
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection2.cell}): ${rejection2.message}`));
|
|
37848
|
+
return writeErrorResponse(request.id, rejection2.code, rejection2.message, rejection2.data);
|
|
37849
|
+
};
|
|
37850
|
+
const onInstanceClosed = (channel) => {
|
|
37851
|
+
if (closing || channel === discarding) return;
|
|
37852
|
+
closeAll();
|
|
37853
|
+
};
|
|
37854
|
+
const connectInstance = async (era, revision) => {
|
|
37855
|
+
const product = await factory({ era });
|
|
37856
|
+
const server = product instanceof McpServer ? product.server : product;
|
|
37857
|
+
if (era === "modern") {
|
|
37858
|
+
setNegotiatedProtocolVersion(server, revision);
|
|
37859
|
+
installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
37860
|
+
listenRouter.setServerCapabilities(server.getCapabilities(), serverIdentityOf(server));
|
|
37861
|
+
}
|
|
37862
|
+
const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
|
|
37863
|
+
await product.connect(channel);
|
|
37864
|
+
return {
|
|
37865
|
+
product,
|
|
37866
|
+
channel
|
|
37867
|
+
};
|
|
37868
|
+
};
|
|
37869
|
+
const disposeLateInstance = (instance) => instance.product.close().catch((error62) => reportError(toError(error62)));
|
|
37870
|
+
const discardProbeInstance = async (instance) => {
|
|
37871
|
+
discarding = instance.channel;
|
|
37872
|
+
try {
|
|
37873
|
+
if (!await instance.channel.whenRequestsAnswered(DISCARD_ANSWER_TIMEOUT_MS)) reportError(/* @__PURE__ */ new Error(`Discarded the probe instance with requests still unanswered after ${DISCARD_ANSWER_TIMEOUT_MS}ms; continuing with the fallback`));
|
|
37874
|
+
await instance.product.close();
|
|
37875
|
+
} catch (error62) {
|
|
37876
|
+
reportError(toError(error62));
|
|
37877
|
+
} finally {
|
|
37878
|
+
discarding = void 0;
|
|
37879
|
+
}
|
|
37880
|
+
};
|
|
37881
|
+
const processMessage = async (message) => {
|
|
37882
|
+
if (state.phase === "closed") return;
|
|
37883
|
+
if (state.phase === "pinned") {
|
|
37884
|
+
if (state.era === "modern" && isJSONRPCRequest(message) && message.method === "initialize" && !carriesValidModernEnvelopeClaim(message.params)) {
|
|
37885
|
+
await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
|
|
37886
|
+
return;
|
|
37887
|
+
}
|
|
37888
|
+
if (state.era === "modern" && await tryServeListen(message)) return;
|
|
37889
|
+
state.instance.channel.deliver(message);
|
|
37890
|
+
return;
|
|
37891
|
+
}
|
|
37892
|
+
if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message)) {
|
|
37893
|
+
reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
|
|
37894
|
+
return;
|
|
37895
|
+
}
|
|
37896
|
+
const opening = classifyOpeningMessage(message);
|
|
37897
|
+
switch (opening.kind) {
|
|
37898
|
+
case "invalid-envelope": {
|
|
37899
|
+
const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
|
|
37900
|
+
if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
|
|
37901
|
+
else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
|
|
37902
|
+
return;
|
|
37903
|
+
}
|
|
37904
|
+
case "unsupported-revision":
|
|
37905
|
+
if (isJSONRPCRequest(message)) {
|
|
37906
|
+
const error62 = new UnsupportedProtocolVersionError({
|
|
37907
|
+
supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
|
|
37908
|
+
requested: opening.requested
|
|
37909
|
+
});
|
|
37910
|
+
reportError(error62);
|
|
37911
|
+
await writeErrorResponse(message.id, error62.code, error62.message, error62.data);
|
|
37912
|
+
} else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
|
|
37913
|
+
return;
|
|
37914
|
+
case "modern":
|
|
37915
|
+
if (isJSONRPCRequest(message) && message.method === "server/discover") {
|
|
37916
|
+
if (state.phase === "probe") {
|
|
37917
|
+
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
37918
|
+
return;
|
|
37919
|
+
}
|
|
37920
|
+
const instance = await connectInstance("modern", opening.revision);
|
|
37921
|
+
if (isTornDown()) {
|
|
37922
|
+
await disposeLateInstance(instance);
|
|
37923
|
+
return;
|
|
37924
|
+
}
|
|
37925
|
+
state = {
|
|
37926
|
+
phase: "probe",
|
|
37927
|
+
instance
|
|
37928
|
+
};
|
|
37929
|
+
instance.channel.deliver(message, { classification: opening.classification });
|
|
37930
|
+
return;
|
|
37931
|
+
}
|
|
37932
|
+
if (state.phase === "probe") {
|
|
37933
|
+
if (isJSONRPCNotification(message)) {
|
|
37934
|
+
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
37935
|
+
return;
|
|
37936
|
+
}
|
|
37937
|
+
state = {
|
|
37938
|
+
phase: "pinned",
|
|
37939
|
+
era: "modern",
|
|
37940
|
+
instance: state.instance
|
|
37941
|
+
};
|
|
37942
|
+
} else {
|
|
37943
|
+
const instance = await connectInstance("modern", opening.revision);
|
|
37944
|
+
if (isTornDown()) {
|
|
37945
|
+
await disposeLateInstance(instance);
|
|
37946
|
+
return;
|
|
37947
|
+
}
|
|
37948
|
+
state = {
|
|
37949
|
+
phase: "pinned",
|
|
37950
|
+
era: "modern",
|
|
37951
|
+
instance
|
|
37952
|
+
};
|
|
37953
|
+
}
|
|
37954
|
+
if (await tryServeListen(message)) return;
|
|
37955
|
+
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
37956
|
+
return;
|
|
37957
|
+
case "legacy": {
|
|
37958
|
+
if (legacyMode === "reject") {
|
|
37959
|
+
if (isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
|
|
37960
|
+
return;
|
|
37961
|
+
}
|
|
37962
|
+
if (state.phase === "probe") {
|
|
37963
|
+
await discardProbeInstance(state.instance);
|
|
37964
|
+
if (isTornDown()) return;
|
|
37965
|
+
state = { phase: "opening" };
|
|
37966
|
+
}
|
|
37967
|
+
const instance = await connectInstance("legacy");
|
|
37968
|
+
if (isTornDown()) {
|
|
37969
|
+
await disposeLateInstance(instance);
|
|
37970
|
+
return;
|
|
37971
|
+
}
|
|
37972
|
+
state = {
|
|
37973
|
+
phase: "pinned",
|
|
37974
|
+
era: "legacy",
|
|
37975
|
+
instance
|
|
37976
|
+
};
|
|
37977
|
+
state.instance.channel.deliver(message);
|
|
37978
|
+
return;
|
|
37979
|
+
}
|
|
37980
|
+
}
|
|
37981
|
+
};
|
|
37982
|
+
const queue = [];
|
|
37983
|
+
let pumping = false;
|
|
37984
|
+
const pump = async () => {
|
|
37985
|
+
if (pumping) return;
|
|
37986
|
+
pumping = true;
|
|
37987
|
+
try {
|
|
37988
|
+
while (queue.length > 0) {
|
|
37989
|
+
const message = queue.shift();
|
|
37990
|
+
try {
|
|
37991
|
+
await processMessage(message);
|
|
37992
|
+
} catch (error62) {
|
|
37993
|
+
if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InternalError, "Internal server error");
|
|
37994
|
+
reportError(toError(error62));
|
|
37995
|
+
}
|
|
37996
|
+
}
|
|
37997
|
+
} finally {
|
|
37998
|
+
pumping = false;
|
|
37999
|
+
}
|
|
38000
|
+
};
|
|
38001
|
+
const closeAll = async () => {
|
|
38002
|
+
if (closing || state.phase === "closed") return;
|
|
38003
|
+
closing = true;
|
|
38004
|
+
const current = state;
|
|
38005
|
+
state = { phase: "closed" };
|
|
38006
|
+
for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error62) => reportError(toError(error62)));
|
|
38007
|
+
if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error62) => reportError(toError(error62)));
|
|
38008
|
+
await wire.close().catch((error62) => reportError(toError(error62)));
|
|
38009
|
+
};
|
|
38010
|
+
wire.onmessage = (message) => {
|
|
38011
|
+
queue.push(message);
|
|
38012
|
+
pump();
|
|
38013
|
+
};
|
|
38014
|
+
wire.onerror = (error62) => {
|
|
38015
|
+
reportError(error62);
|
|
38016
|
+
if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error62);
|
|
38017
|
+
};
|
|
38018
|
+
wire.onclose = () => {
|
|
38019
|
+
if (closing || state.phase === "closed") return;
|
|
38020
|
+
closing = true;
|
|
38021
|
+
const current = state;
|
|
38022
|
+
state = { phase: "closed" };
|
|
38023
|
+
if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error62) => reportError(toError(error62)));
|
|
38024
|
+
};
|
|
38025
|
+
const started = wire.start().catch((error62) => {
|
|
38026
|
+
reportError(toError(error62));
|
|
38027
|
+
throw error62;
|
|
38028
|
+
});
|
|
38029
|
+
started.catch(() => {
|
|
38030
|
+
});
|
|
38031
|
+
return { close: async () => {
|
|
38032
|
+
await started.catch(() => {
|
|
38033
|
+
});
|
|
38034
|
+
await closeAll();
|
|
38035
|
+
} };
|
|
38036
|
+
}
|
|
38037
|
+
function toError(value) {
|
|
38038
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
38039
|
+
}
|
|
37376
38040
|
|
|
37377
38041
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
37378
38042
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
@@ -37509,17 +38173,41 @@ Hint: ${err.hint}`);
|
|
|
37509
38173
|
}
|
|
37510
38174
|
throw err;
|
|
37511
38175
|
}
|
|
38176
|
+
function callSignalFrom(args) {
|
|
38177
|
+
const ctx = args.at(-1);
|
|
38178
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
38179
|
+
return void 0;
|
|
38180
|
+
const req = ctx.mcpReq;
|
|
38181
|
+
if (typeof req !== "object" || req === null)
|
|
38182
|
+
return void 0;
|
|
38183
|
+
const signal = req.signal;
|
|
38184
|
+
return signal instanceof AbortSignal ? signal : void 0;
|
|
38185
|
+
}
|
|
37512
38186
|
function surfaceToolHints(server) {
|
|
37513
38187
|
const register2 = server.registerTool.bind(server);
|
|
37514
|
-
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) =>
|
|
37515
|
-
|
|
37516
|
-
|
|
37517
|
-
|
|
37518
|
-
|
|
37519
|
-
|
|
37520
|
-
|
|
37521
|
-
|
|
37522
|
-
|
|
38188
|
+
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
|
|
38189
|
+
// THE CALLER'S CANCELLATION, made ambient for the whole handler
|
|
38190
|
+
// (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
|
|
38191
|
+
// measured against @modelcontextprotocol/server 2.0.0, a cancelled
|
|
38192
|
+
// call aborts `ctx.mcpReq.signal` with the caller's reason and the
|
|
38193
|
+
// handler runs to completion regardless — so the HTTP request stays
|
|
38194
|
+
// in flight, the child keeps burning metered CPU, and the upstream
|
|
38195
|
+
// keeps being hit for somebody who has gone. claude.ai sent 101
|
|
38196
|
+
// cancellations in the week to 2026-09-20.
|
|
38197
|
+
//
|
|
38198
|
+
// Here because this is the one wrapper every tool already passes
|
|
38199
|
+
// through: threading the signal by hand would mean editing several
|
|
38200
|
+
// hundred handlers and missing exactly the ones nobody edits.
|
|
38201
|
+
withCallSignal(callSignalFrom(args), () => {
|
|
38202
|
+
let result;
|
|
38203
|
+
try {
|
|
38204
|
+
result = cb(...args);
|
|
38205
|
+
} catch (err) {
|
|
38206
|
+
return hintResultOrRethrow(err);
|
|
38207
|
+
}
|
|
38208
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
38209
|
+
})
|
|
38210
|
+
));
|
|
37523
38211
|
}
|
|
37524
38212
|
async function createMcpServer(opts) {
|
|
37525
38213
|
const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
|
|
@@ -37534,7 +38222,7 @@ async function createMcpServer(opts) {
|
|
|
37534
38222
|
}
|
|
37535
38223
|
return server;
|
|
37536
38224
|
}
|
|
37537
|
-
function withGracefulShutdown(
|
|
38225
|
+
function withGracefulShutdown(target, opts = {}) {
|
|
37538
38226
|
const shouldExit = opts.exit ?? true;
|
|
37539
38227
|
let shuttingDown = false;
|
|
37540
38228
|
const handler = (signal) => {
|
|
@@ -37545,7 +38233,7 @@ function withGracefulShutdown(server, opts = {}) {
|
|
|
37545
38233
|
try {
|
|
37546
38234
|
if (opts.onSignal)
|
|
37547
38235
|
await opts.onSignal(signal);
|
|
37548
|
-
await
|
|
38236
|
+
await target.close();
|
|
37549
38237
|
} catch (err) {
|
|
37550
38238
|
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
37551
38239
|
} finally {
|
|
@@ -37557,16 +38245,26 @@ function withGracefulShutdown(server, opts = {}) {
|
|
|
37557
38245
|
process.on("SIGINT", () => handler("SIGINT"));
|
|
37558
38246
|
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
37559
38247
|
}
|
|
37560
|
-
|
|
37561
|
-
|
|
38248
|
+
function runMcp(opts) {
|
|
38249
|
+
if (opts.banner !== void 0) {
|
|
38250
|
+
console.error(opts.banner);
|
|
38251
|
+
}
|
|
38252
|
+
const spec = opts.transport ?? "stdio";
|
|
38253
|
+
const handle = serveStdio(() => createMcpServer({ ...opts, banner: void 0 }), {
|
|
38254
|
+
legacy: opts.legacy ?? "serve",
|
|
38255
|
+
onerror: opts.onerror ?? ((error62) => {
|
|
38256
|
+
console.error(`[mcp-utils] stdio server error: ${redactSecrets(error62.message)}`);
|
|
38257
|
+
}),
|
|
38258
|
+
// `'stdio'` means "let the entry make its own StdioServerTransport over
|
|
38259
|
+
// this process"; anything else is the caller's transport, which the entry
|
|
38260
|
+
// then owns (it starts it, pumps it, and closes it with the handle).
|
|
38261
|
+
...spec === "stdio" ? {} : { transport: spec }
|
|
38262
|
+
});
|
|
37562
38263
|
const shutdown = opts.shutdown ?? true;
|
|
37563
38264
|
if (shutdown !== false) {
|
|
37564
|
-
withGracefulShutdown(
|
|
38265
|
+
withGracefulShutdown(handle, shutdown === true ? {} : shutdown);
|
|
37565
38266
|
}
|
|
37566
|
-
|
|
37567
|
-
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
37568
|
-
await server.connect(transport);
|
|
37569
|
-
return server;
|
|
38267
|
+
return handle;
|
|
37570
38268
|
}
|
|
37571
38269
|
|
|
37572
38270
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
@@ -43266,13 +43964,18 @@ async function loginWithPassword(username, password) {
|
|
|
43266
43964
|
assertOfwUrl(initUrl);
|
|
43267
43965
|
const initResponse = await fetch(initUrl, {
|
|
43268
43966
|
headers: { ...OFW_PROTOCOL_HEADERS },
|
|
43269
|
-
redirect: "manual"
|
|
43967
|
+
redirect: "manual",
|
|
43968
|
+
// Honour a caller who has given up (mcp-utils `cancel`). A sign-in
|
|
43969
|
+
// nobody is waiting for should not keep hitting OFW, which counts
|
|
43970
|
+
// failed attempts against the account.
|
|
43971
|
+
signal: currentCallSignal()
|
|
43270
43972
|
});
|
|
43271
43973
|
const sessionCookie = initResponse.headers.getSetCookie().map((c) => c.split(";")[0]).join("; ");
|
|
43272
43974
|
const loginUrl = `${BASE_URL}/ofw/login`;
|
|
43273
43975
|
assertOfwUrl(loginUrl);
|
|
43274
43976
|
const response = await fetch(loginUrl, {
|
|
43275
43977
|
method: "POST",
|
|
43978
|
+
signal: currentCallSignal(),
|
|
43276
43979
|
headers: {
|
|
43277
43980
|
...OFW_PROTOCOL_HEADERS,
|
|
43278
43981
|
Accept: "application/json",
|
|
@@ -43309,7 +44012,7 @@ async function loginWithPassword(username, password) {
|
|
|
43309
44012
|
// package.json
|
|
43310
44013
|
var package_default = {
|
|
43311
44014
|
name: "ofw-mcp",
|
|
43312
|
-
version: "2.
|
|
44015
|
+
version: "2.19.0",
|
|
43313
44016
|
license: "MIT",
|
|
43314
44017
|
mcpName: "io.github.chrischall/ofw-mcp",
|
|
43315
44018
|
description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
|
|
@@ -43343,7 +44046,7 @@ var package_default = {
|
|
|
43343
44046
|
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
43344
44047
|
},
|
|
43345
44048
|
dependencies: {
|
|
43346
|
-
"@chrischall/mcp-utils": "^0.
|
|
44049
|
+
"@chrischall/mcp-utils": "^2.0.0",
|
|
43347
44050
|
"@fetchproxy/bootstrap": "^3.0.1",
|
|
43348
44051
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
43349
44052
|
dotenv: "^17.4.2",
|
|
@@ -43622,7 +44325,15 @@ var OFWClient = class {
|
|
|
43622
44325
|
response = await fetch(url2, {
|
|
43623
44326
|
method,
|
|
43624
44327
|
headers,
|
|
43625
|
-
|
|
44328
|
+
// THE CALLER'S CANCELLATION, folded in with our timeout (mcp-utils
|
|
44329
|
+
// `cancel`). Until this the only thing that could stop an OFW
|
|
44330
|
+
// request was the timeout below, so a cancelled tool call held it
|
|
44331
|
+
// open for the full budget while the child burned the CPU
|
|
44332
|
+
// mcp-host meters it on. The timeout diagnosis below is unaffected
|
|
44333
|
+
// because it asks `ac.signal`, OUR controller — a caller's abort
|
|
44334
|
+
// falls through to the generic path rather than being reported as
|
|
44335
|
+
// OFW being slow.
|
|
44336
|
+
signal: withAmbientCancellation(ac.signal),
|
|
43626
44337
|
...body !== void 0 ? { body: isFormData ? body : JSON.stringify(body) } : {}
|
|
43627
44338
|
});
|
|
43628
44339
|
} catch (err) {
|
|
@@ -48296,7 +49007,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
|
|
|
48296
49007
|
var nodeAttachmentIO = new NodeAttachmentIO();
|
|
48297
49008
|
await runMcp({
|
|
48298
49009
|
name: "ofw",
|
|
48299
|
-
version: "2.
|
|
49010
|
+
version: "2.19.0",
|
|
48300
49011
|
// x-release-please-version
|
|
48301
49012
|
deps: client,
|
|
48302
49013
|
tools: [
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadDotenvSafely, parseBoolEnv, redactSecrets } from '@chrischall/mcp-utils';
|
|
1
|
+
import { loadDotenvSafely, parseBoolEnv, redactSecrets, withAmbientCancellation } from '@chrischall/mcp-utils';
|
|
2
2
|
import { TokenManager } from '@chrischall/mcp-utils/session';
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
@@ -190,7 +190,15 @@ export class OFWClient {
|
|
|
190
190
|
response = await fetch(url, {
|
|
191
191
|
method,
|
|
192
192
|
headers,
|
|
193
|
-
|
|
193
|
+
// THE CALLER'S CANCELLATION, folded in with our timeout (mcp-utils
|
|
194
|
+
// `cancel`). Until this the only thing that could stop an OFW
|
|
195
|
+
// request was the timeout below, so a cancelled tool call held it
|
|
196
|
+
// open for the full budget while the child burned the CPU
|
|
197
|
+
// mcp-host meters it on. The timeout diagnosis below is unaffected
|
|
198
|
+
// because it asks `ac.signal`, OUR controller — a caller's abort
|
|
199
|
+
// falls through to the generic path rather than being reported as
|
|
200
|
+
// OFW being slow.
|
|
201
|
+
signal: withAmbientCancellation(ac.signal),
|
|
194
202
|
...(body !== undefined ? { body: isFormData ? body : JSON.stringify(body) } : {}),
|
|
195
203
|
});
|
|
196
204
|
}
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ const nodeAttachmentIO = new NodeAttachmentIO();
|
|
|
36
36
|
// always succeeds before any credential check runs.
|
|
37
37
|
await runMcp({
|
|
38
38
|
name: 'ofw',
|
|
39
|
-
version: '2.
|
|
39
|
+
version: '2.19.0', // x-release-please-version
|
|
40
40
|
deps: client,
|
|
41
41
|
tools: [
|
|
42
42
|
registerHealthcheckTools,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ofw-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"mcpName": "io.github.chrischall/ofw-mcp",
|
|
6
6
|
"description": "OurFamilyWizard MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@chrischall/mcp-utils": "^0.
|
|
37
|
+
"@chrischall/mcp-utils": "^2.0.0",
|
|
38
38
|
"@fetchproxy/bootstrap": "^3.0.1",
|
|
39
39
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
40
40
|
"dotenv": "^17.4.2",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/ofw-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.19.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ofw-mcp",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.19.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|