ofw-mcp 2.17.0 → 2.18.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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "OurFamilyWizard tools for Claude Code",
9
- "version": "2.17.0"
9
+ "version": "2.18.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.0",
17
+ "version": "2.18.0",
18
18
  "author": {
19
19
  "name": "Chris Chall"
20
20
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ofw",
3
3
  "displayName": "OurFamilyWizard",
4
- "version": "2.17.0",
4
+ "version": "2.18.0",
5
5
  "description": "OurFamilyWizard co-parenting tools for Claude — messages, calendar, expenses, and journal via MCP",
6
6
  "author": {
7
7
  "name": "Chris Chall"
package/dist/bundle.js CHANGED
@@ -24550,6 +24550,7 @@ function missingClientCapabilities(required2, declared) {
24550
24550
  return Object.keys(missing).length > 0 ? missing : void 0;
24551
24551
  }
24552
24552
  var FIRST_MODERN_PROTOCOL_VERSION = "2026-07-28";
24553
+ var SUPPORTED_MODERN_PROTOCOL_VERSIONS = [FIRST_MODERN_PROTOCOL_VERSION];
24553
24554
  function isModernProtocolVersion(version2) {
24554
24555
  return version2 >= FIRST_MODERN_PROTOCOL_VERSION;
24555
24556
  }
@@ -27108,6 +27109,25 @@ function isSpecNotificationMethod(method) {
27108
27109
  return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
27109
27110
  }
27110
27111
  var ALL_CODECS = [rev2025Codec, rev2026Codec];
27112
+ function isPlainObject$3(value) {
27113
+ return value !== null && typeof value === "object" && !Array.isArray(value);
27114
+ }
27115
+ function requestMetaOf(params) {
27116
+ if (!isPlainObject$3(params)) return void 0;
27117
+ const meta3 = params["_meta"];
27118
+ return isPlainObject$3(meta3) ? meta3 : void 0;
27119
+ }
27120
+ function hasEnvelopeClaim(params) {
27121
+ const meta3 = requestMetaOf(params);
27122
+ return meta3 !== void 0 && PROTOCOL_VERSION_META_KEY in meta3;
27123
+ }
27124
+ function envelopeClaimVersion(params) {
27125
+ const value = requestMetaOf(params)?.[PROTOCOL_VERSION_META_KEY];
27126
+ return typeof value === "string" ? value : void 0;
27127
+ }
27128
+ function validateEnvelopeMeta(meta3) {
27129
+ return codecForVersion(MODERN_WIRE_REVISION).validateEnvelopeMeta(meta3);
27130
+ }
27111
27131
  var schemas_exports3 = /* @__PURE__ */ __exportAll({
27112
27132
  AnnotationsSchema: () => AnnotationsSchema,
27113
27133
  AudioContentSchema: () => AudioContentSchema,
@@ -27458,6 +27478,45 @@ var LADDER_ERROR_HTTP_STATUS = {
27458
27478
  [ProtocolErrorCode.MissingRequiredClientCapability]: 400,
27459
27479
  [HEADER_MISMATCH_ERROR_CODE]: 400
27460
27480
  };
27481
+ function rejection(rung, cell, httpStatus, error62, settled) {
27482
+ return {
27483
+ kind: "reject",
27484
+ rung,
27485
+ cell,
27486
+ httpStatus,
27487
+ code: error62.code,
27488
+ message: error62.message,
27489
+ ...error62.data !== void 0 && { data: error62.data },
27490
+ settled
27491
+ };
27492
+ }
27493
+ function carriesValidModernEnvelopeClaim(params) {
27494
+ if (!hasEnvelopeClaim(params)) return false;
27495
+ const claimedVersion = envelopeClaimVersion(params);
27496
+ if (claimedVersion === void 0 || !isModernProtocolVersion(claimedVersion)) return false;
27497
+ const meta3 = requestMetaOf(params);
27498
+ return meta3 !== void 0 && validateEnvelopeMeta(meta3).length === 0;
27499
+ }
27500
+ function modernOnlyStrictRejection(route, supportedVersions) {
27501
+ switch (route.reason) {
27502
+ case "http-method":
27503
+ return rejection("http-method", "modern-only-method-not-allowed", 405, new ProtocolError(-32e3, "Method not allowed."), true);
27504
+ case "batch":
27505
+ 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);
27506
+ case "response":
27507
+ 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);
27508
+ case "notification":
27509
+ return;
27510
+ case "initialize":
27511
+ case "no-claim": {
27512
+ const requested = route.requestedVersion;
27513
+ 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({
27514
+ supported: [...supportedVersions],
27515
+ requested
27516
+ }), true);
27517
+ }
27518
+ }
27519
+ }
27461
27520
  function parseSchema(schema, data) {
27462
27521
  return safeParse2(schema, data);
27463
27522
  }
@@ -28019,6 +28078,9 @@ function withRequestStateValue(ctx, value) {
28019
28078
  };
28020
28079
  }
28021
28080
  var writeNegotiatedProtocolVersion;
28081
+ function setNegotiatedProtocolVersion(instance, version2) {
28082
+ writeNegotiatedProtocolVersion(instance, version2);
28083
+ }
28022
28084
  var Protocol = class {
28023
28085
  _transport;
28024
28086
  _requestMessageId = 0;
@@ -35962,6 +36024,190 @@ function getCompleter(schema) {
35962
36024
  return schema[COMPLETABLE_SYMBOL]?.complete;
35963
36025
  }
35964
36026
  var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
36027
+ function listenFilterAccepts(filter, event) {
36028
+ switch (event.kind) {
36029
+ case "tools_list_changed":
36030
+ return filter.toolsListChanged === true;
36031
+ case "prompts_list_changed":
36032
+ return filter.promptsListChanged === true;
36033
+ case "resources_list_changed":
36034
+ return filter.resourcesListChanged === true;
36035
+ case "resource_updated":
36036
+ return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
36037
+ }
36038
+ }
36039
+ function honoredSubset(requested, capabilities) {
36040
+ const honored = {};
36041
+ const allow = (bit) => capabilities === void 0 || bit === true;
36042
+ if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
36043
+ if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
36044
+ if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
36045
+ if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
36046
+ return honored;
36047
+ }
36048
+ var DEFAULT_MAX_SUBSCRIPTIONS = 1024;
36049
+ function stampSubscriptionId(notification, subscriptionId) {
36050
+ return {
36051
+ method: notification.method,
36052
+ params: {
36053
+ ...notification.params,
36054
+ _meta: {
36055
+ ...notification.params?._meta,
36056
+ [SUBSCRIPTION_ID_META_KEY]: subscriptionId
36057
+ }
36058
+ }
36059
+ };
36060
+ }
36061
+ function parseListenFilter(message) {
36062
+ const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
36063
+ return outcome.ok ? outcome.value.params?.notifications : void 0;
36064
+ }
36065
+ var CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
36066
+ "notifications/tools/list_changed",
36067
+ "notifications/prompts/list_changed",
36068
+ "notifications/resources/list_changed",
36069
+ "notifications/resources/updated"
36070
+ ]);
36071
+ var StdioListenRouter = class {
36072
+ /** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
36073
+ _subs = /* @__PURE__ */ new Map();
36074
+ /**
36075
+ * The serving instance's declared capabilities. Filled in by the entry
36076
+ * once the modern instance is constructed (the router is created before
36077
+ * the instance exists), so the acknowledged filter is narrowed against
36078
+ * what the server can actually deliver.
36079
+ */
36080
+ _serverCapabilities;
36081
+ /**
36082
+ * The serving instance's identity, stamped onto the graceful-close
36083
+ * results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
36084
+ * `ResultMetaObject`). Handed over together with the capabilities.
36085
+ */
36086
+ _serverInfo;
36087
+ constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
36088
+ this._maxSubscriptions = _maxSubscriptions;
36089
+ this._serverCapabilities = serverCapabilities;
36090
+ this._serverInfo = serverInfo;
36091
+ }
36092
+ /**
36093
+ * Record the serving instance's declared capabilities and identity once
36094
+ * it has been constructed. Called by `serveStdio`'s connect path;
36095
+ * subsequent `serve()` calls narrow the honored filter against the
36096
+ * capabilities, and `teardownAll()` stamps the identity.
36097
+ */
36098
+ setServerCapabilities(capabilities, serverInfo) {
36099
+ this._serverCapabilities = capabilities;
36100
+ if (serverInfo !== void 0) this._serverInfo = serverInfo;
36101
+ }
36102
+ /** Whether `id` is an active listen subscription on this connection. */
36103
+ has(id) {
36104
+ return this._subs.has(id);
36105
+ }
36106
+ /**
36107
+ * Serve one inbound `subscriptions/listen` request: registers the
36108
+ * subscription and returns the stamped acknowledged notification (or, on
36109
+ * capacity / params rejection, the in-band JSON-RPC error response).
36110
+ *
36111
+ * @throws when called before {@linkcode setServerCapabilities} (or the
36112
+ * constructor) has supplied the serving instance's capabilities. Honoring a
36113
+ * filter without knowing the server's advertised capabilities would fail
36114
+ * open (deliver unadvertised types); the entry guarantees capabilities are
36115
+ * set before any listen request is routed here.
36116
+ */
36117
+ serve(message) {
36118
+ if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
36119
+ if (this._subs.size >= this._maxSubscriptions) return {
36120
+ jsonrpc: "2.0",
36121
+ id: message.id,
36122
+ error: {
36123
+ code: -32603,
36124
+ message: "Subscription limit reached"
36125
+ }
36126
+ };
36127
+ const filter = parseListenFilter(message);
36128
+ if (filter === void 0) return {
36129
+ jsonrpc: "2.0",
36130
+ id: message.id,
36131
+ error: {
36132
+ code: -32602,
36133
+ message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
36134
+ }
36135
+ };
36136
+ const honored = honoredSubset(filter, this._serverCapabilities);
36137
+ this._subs.set(message.id, honored);
36138
+ return stampSubscriptionId({
36139
+ method: "notifications/subscriptions/acknowledged",
36140
+ params: { notifications: honored }
36141
+ }, message.id);
36142
+ }
36143
+ /**
36144
+ * Tear down one subscription (inbound `notifications/cancelled`). Returns
36145
+ * `true` when a subscription was removed. After this call NOTHING further
36146
+ * is delivered for that subscription id (the post-cancel hardening).
36147
+ */
36148
+ cancel(id) {
36149
+ return this._subs.delete(id);
36150
+ }
36151
+ /**
36152
+ * Route an outbound notification through the active subscriptions.
36153
+ *
36154
+ * - For a subscription-gated change notification, returns one stamped copy
36155
+ * per subscription that opted in to it (an empty array means it is
36156
+ * dropped — the modern era never delivers an un-requested change type).
36157
+ * - For any other outbound message, returns `'passthrough'` (the entry
36158
+ * forwards it as-is).
36159
+ */
36160
+ routeOutbound(message) {
36161
+ if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
36162
+ const uriParam = message.params?.["uri"];
36163
+ const uri = typeof uriParam === "string" ? uriParam : void 0;
36164
+ const event = notificationToServerEvent(message.method, uri);
36165
+ const out = [];
36166
+ for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
36167
+ method: message.method,
36168
+ params: message.params ?? {}
36169
+ }, subscriptionId));
36170
+ return out;
36171
+ }
36172
+ /**
36173
+ * Server-side graceful teardown of every active subscription: returns the
36174
+ * empty `subscriptions/listen` JSON-RPC result for each subscription id —
36175
+ * the spec's graceful-close signal, `_meta` carrying the subscription id
36176
+ * and the serving instance's identity — for the entry to emit before
36177
+ * closing the wire. Clears the set so nothing further is delivered.
36178
+ */
36179
+ teardownAll() {
36180
+ const out = [];
36181
+ for (const id of this._subs.keys()) out.push({
36182
+ jsonrpc: "2.0",
36183
+ id,
36184
+ result: {
36185
+ resultType: "complete",
36186
+ _meta: {
36187
+ [SUBSCRIPTION_ID_META_KEY]: id,
36188
+ ...this._serverInfo !== void 0 && { [SERVER_INFO_META_KEY]: this._serverInfo }
36189
+ }
36190
+ }
36191
+ });
36192
+ this._subs.clear();
36193
+ return out;
36194
+ }
36195
+ };
36196
+ function notificationToServerEvent(method, uri) {
36197
+ switch (method) {
36198
+ case "notifications/tools/list_changed":
36199
+ return { kind: "tools_list_changed" };
36200
+ case "notifications/prompts/list_changed":
36201
+ return { kind: "prompts_list_changed" };
36202
+ case "notifications/resources/list_changed":
36203
+ return { kind: "resources_list_changed" };
36204
+ default:
36205
+ return {
36206
+ kind: "resource_updated",
36207
+ uri: uri ?? ""
36208
+ };
36209
+ }
36210
+ }
35965
36211
  var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
35966
36212
  var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
35967
36213
  function resolveLegacyShimOptions(options) {
@@ -36099,6 +36345,12 @@ var INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
36099
36345
  var writeClientIdentity;
36100
36346
  var installDiscoverHandler;
36101
36347
  var readServerIdentity;
36348
+ function installModernOnlyHandlers(server, servedModernVersions) {
36349
+ installDiscoverHandler(server, servedModernVersions);
36350
+ }
36351
+ function serverIdentityOf(server) {
36352
+ return readServerIdentity(server);
36353
+ }
36102
36354
  var Server = class extends Protocol {
36103
36355
  _clientCapabilities;
36104
36356
  _clientVersion;
@@ -37373,6 +37625,400 @@ var StdioServerTransport = class {
37373
37625
  });
37374
37626
  }
37375
37627
  };
37628
+ var DISCARD_ANSWER_TIMEOUT_MS = 3e3;
37629
+ var StdioConnectionChannel = class {
37630
+ onclose;
37631
+ onerror;
37632
+ onmessage;
37633
+ _closed = false;
37634
+ /** Request ids the entry delivered to the instance that the instance has not yet answered. */
37635
+ _pendingRequests = /* @__PURE__ */ new Set();
37636
+ _drainWaiters = [];
37637
+ constructor(_wire, _onInstanceClose, _outboundIntercept) {
37638
+ this._wire = _wire;
37639
+ this._onInstanceClose = _onInstanceClose;
37640
+ this._outboundIntercept = _outboundIntercept;
37641
+ }
37642
+ async start() {
37643
+ }
37644
+ async send(message, options) {
37645
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
37646
+ const { id } = message;
37647
+ if (id !== void 0) this._settle(id);
37648
+ }
37649
+ if (this._closed) return;
37650
+ if (this._outboundIntercept?.(message) === "handled") return;
37651
+ return this._wire.send(message, options);
37652
+ }
37653
+ setProtocolVersion = (version2) => {
37654
+ this._wire.setProtocolVersion?.(version2);
37655
+ };
37656
+ /** Forwards one inbound message to the connected instance. */
37657
+ deliver(message, extra) {
37658
+ if (this._closed) return;
37659
+ if (isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
37660
+ else if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
37661
+ const cancelledId = message.params?.requestId;
37662
+ if (cancelledId !== void 0) this._settle(cancelledId);
37663
+ }
37664
+ this.onmessage?.(message, extra);
37665
+ }
37666
+ /**
37667
+ * Resolves once every request delivered to the instance has been answered
37668
+ * through {@linkcode send}, settled by a delivered cancellation, or the
37669
+ * channel has been closed and nothing further can be answered. The wait is
37670
+ * bounded by `timeoutMs` as a backstop so no edge can hold the caller
37671
+ * indefinitely; resolves `false` only when the bound elapsed with requests
37672
+ * still unanswered. Used by the probe-discard path so a probe request the
37673
+ * entry accepted is never silently dropped.
37674
+ */
37675
+ async whenRequestsAnswered(timeoutMs) {
37676
+ if (this._closed || this._pendingRequests.size === 0) return true;
37677
+ return await new Promise((resolve3) => {
37678
+ const waiter = () => {
37679
+ clearTimeout(timer);
37680
+ resolve3(true);
37681
+ };
37682
+ const timer = setTimeout(() => {
37683
+ this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
37684
+ resolve3(false);
37685
+ }, timeoutMs);
37686
+ this._drainWaiters.push(waiter);
37687
+ });
37688
+ }
37689
+ async close() {
37690
+ if (this._closed) return;
37691
+ this._closed = true;
37692
+ this._pendingRequests.clear();
37693
+ this._releaseDrainWaiters();
37694
+ try {
37695
+ this._onInstanceClose();
37696
+ } finally {
37697
+ this.onclose?.();
37698
+ }
37699
+ }
37700
+ _settle(id) {
37701
+ this._pendingRequests.delete(id);
37702
+ if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
37703
+ }
37704
+ _releaseDrainWaiters() {
37705
+ const waiters = this._drainWaiters;
37706
+ this._drainWaiters = [];
37707
+ for (const waiter of waiters) waiter();
37708
+ }
37709
+ };
37710
+ function classifyOpeningMessage(message) {
37711
+ const params = message.params;
37712
+ if (message.method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
37713
+ const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
37714
+ return {
37715
+ kind: "legacy",
37716
+ reason: "initialize",
37717
+ ...requestedVersion !== void 0 && { requestedVersion }
37718
+ };
37719
+ }
37720
+ if (!hasEnvelopeClaim(params)) return {
37721
+ kind: "legacy",
37722
+ reason: "no-claim"
37723
+ };
37724
+ const meta3 = requestMetaOf(params);
37725
+ const firstIssue = (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0];
37726
+ if (firstIssue !== void 0) return {
37727
+ kind: "invalid-envelope",
37728
+ issue: firstIssue
37729
+ };
37730
+ const claimedVersion = envelopeClaimVersion(params);
37731
+ if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
37732
+ kind: "unsupported-revision",
37733
+ requested: claimedVersion ?? "unknown"
37734
+ };
37735
+ return {
37736
+ kind: "modern",
37737
+ revision: claimedVersion,
37738
+ classification: {
37739
+ era: "modern",
37740
+ revision: claimedVersion
37741
+ }
37742
+ };
37743
+ }
37744
+ function serveStdio(factory, options = {}) {
37745
+ const legacyMode = options.legacy ?? "serve";
37746
+ const wire = options.transport ?? new StdioServerTransport();
37747
+ let state = { phase: "opening" };
37748
+ let discarding;
37749
+ let closing = false;
37750
+ const isTornDown = () => closing || state.phase === "closed";
37751
+ const reportError = (error62) => {
37752
+ try {
37753
+ options.onerror?.(error62);
37754
+ } catch {
37755
+ }
37756
+ };
37757
+ const writeErrorResponse = (id, code, message, data) => wire.send({
37758
+ jsonrpc: "2.0",
37759
+ id,
37760
+ error: {
37761
+ code,
37762
+ message,
37763
+ ...data !== void 0 && { data }
37764
+ }
37765
+ }).catch((error62) => reportError(toError(error62)));
37766
+ const listenRouter = new StdioListenRouter(options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS);
37767
+ const modernOutboundIntercept = (message) => {
37768
+ if (!isJSONRPCNotification(message)) return void 0;
37769
+ const routed = listenRouter.routeOutbound(message);
37770
+ if (routed === "passthrough") return void 0;
37771
+ for (const stamped of routed) wire.send({
37772
+ jsonrpc: "2.0",
37773
+ ...stamped
37774
+ }).catch((error62) => reportError(toError(error62)));
37775
+ return "handled";
37776
+ };
37777
+ const tryServeListen = async (message) => {
37778
+ if (isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
37779
+ const meta3 = requestMetaOf(message.params);
37780
+ const issue2 = hasEnvelopeClaim(message.params) ? (meta3 === void 0 ? [] : validateEnvelopeMeta(meta3))[0] : {
37781
+ key: "_meta",
37782
+ problem: "the per-request envelope is required on protocol revision 2026-07-28"
37783
+ };
37784
+ const claimedVersion = envelopeClaimVersion(message.params);
37785
+ let reply;
37786
+ if (issue2 !== void 0) reply = {
37787
+ jsonrpc: "2.0",
37788
+ id: message.id,
37789
+ error: {
37790
+ code: -32602,
37791
+ message: `Invalid _meta envelope: ${issue2.key}: ${issue2.problem}`
37792
+ }
37793
+ };
37794
+ else if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
37795
+ const error62 = new UnsupportedProtocolVersionError({
37796
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
37797
+ requested: claimedVersion ?? "unknown"
37798
+ });
37799
+ reply = {
37800
+ jsonrpc: "2.0",
37801
+ id: message.id,
37802
+ error: {
37803
+ code: error62.code,
37804
+ message: error62.message,
37805
+ data: error62.data
37806
+ }
37807
+ };
37808
+ } else reply = listenRouter.serve(message);
37809
+ await wire.send("error" in reply ? reply : {
37810
+ jsonrpc: "2.0",
37811
+ method: reply.method,
37812
+ params: reply.params
37813
+ }).catch((error62) => reportError(toError(error62)));
37814
+ return true;
37815
+ }
37816
+ if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
37817
+ const cancelledId = message.params?.requestId;
37818
+ if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
37819
+ }
37820
+ return false;
37821
+ };
37822
+ const answerLegacyRejection = (request, reason, requestedVersion) => {
37823
+ const rejection2 = modernOnlyStrictRejection({
37824
+ kind: "legacy",
37825
+ reason,
37826
+ ...requestedVersion !== void 0 && { requestedVersion }
37827
+ }, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
37828
+ if (rejection2 === void 0) return Promise.resolve();
37829
+ reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection2.cell}): ${rejection2.message}`));
37830
+ return writeErrorResponse(request.id, rejection2.code, rejection2.message, rejection2.data);
37831
+ };
37832
+ const onInstanceClosed = (channel) => {
37833
+ if (closing || channel === discarding) return;
37834
+ closeAll();
37835
+ };
37836
+ const connectInstance = async (era, revision) => {
37837
+ const product = await factory({ era });
37838
+ const server = product instanceof McpServer ? product.server : product;
37839
+ if (era === "modern") {
37840
+ setNegotiatedProtocolVersion(server, revision);
37841
+ installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
37842
+ listenRouter.setServerCapabilities(server.getCapabilities(), serverIdentityOf(server));
37843
+ }
37844
+ const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
37845
+ await product.connect(channel);
37846
+ return {
37847
+ product,
37848
+ channel
37849
+ };
37850
+ };
37851
+ const disposeLateInstance = (instance) => instance.product.close().catch((error62) => reportError(toError(error62)));
37852
+ const discardProbeInstance = async (instance) => {
37853
+ discarding = instance.channel;
37854
+ try {
37855
+ 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`));
37856
+ await instance.product.close();
37857
+ } catch (error62) {
37858
+ reportError(toError(error62));
37859
+ } finally {
37860
+ discarding = void 0;
37861
+ }
37862
+ };
37863
+ const processMessage = async (message) => {
37864
+ if (state.phase === "closed") return;
37865
+ if (state.phase === "pinned") {
37866
+ if (state.era === "modern" && isJSONRPCRequest(message) && message.method === "initialize" && !carriesValidModernEnvelopeClaim(message.params)) {
37867
+ await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
37868
+ return;
37869
+ }
37870
+ if (state.era === "modern" && await tryServeListen(message)) return;
37871
+ state.instance.channel.deliver(message);
37872
+ return;
37873
+ }
37874
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message)) {
37875
+ reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
37876
+ return;
37877
+ }
37878
+ const opening = classifyOpeningMessage(message);
37879
+ switch (opening.kind) {
37880
+ case "invalid-envelope": {
37881
+ const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
37882
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
37883
+ else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
37884
+ return;
37885
+ }
37886
+ case "unsupported-revision":
37887
+ if (isJSONRPCRequest(message)) {
37888
+ const error62 = new UnsupportedProtocolVersionError({
37889
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
37890
+ requested: opening.requested
37891
+ });
37892
+ reportError(error62);
37893
+ await writeErrorResponse(message.id, error62.code, error62.message, error62.data);
37894
+ } else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
37895
+ return;
37896
+ case "modern":
37897
+ if (isJSONRPCRequest(message) && message.method === "server/discover") {
37898
+ if (state.phase === "probe") {
37899
+ state.instance.channel.deliver(message, { classification: opening.classification });
37900
+ return;
37901
+ }
37902
+ const instance = await connectInstance("modern", opening.revision);
37903
+ if (isTornDown()) {
37904
+ await disposeLateInstance(instance);
37905
+ return;
37906
+ }
37907
+ state = {
37908
+ phase: "probe",
37909
+ instance
37910
+ };
37911
+ instance.channel.deliver(message, { classification: opening.classification });
37912
+ return;
37913
+ }
37914
+ if (state.phase === "probe") {
37915
+ if (isJSONRPCNotification(message)) {
37916
+ state.instance.channel.deliver(message, { classification: opening.classification });
37917
+ return;
37918
+ }
37919
+ state = {
37920
+ phase: "pinned",
37921
+ era: "modern",
37922
+ instance: state.instance
37923
+ };
37924
+ } else {
37925
+ const instance = await connectInstance("modern", opening.revision);
37926
+ if (isTornDown()) {
37927
+ await disposeLateInstance(instance);
37928
+ return;
37929
+ }
37930
+ state = {
37931
+ phase: "pinned",
37932
+ era: "modern",
37933
+ instance
37934
+ };
37935
+ }
37936
+ if (await tryServeListen(message)) return;
37937
+ state.instance.channel.deliver(message, { classification: opening.classification });
37938
+ return;
37939
+ case "legacy": {
37940
+ if (legacyMode === "reject") {
37941
+ if (isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
37942
+ return;
37943
+ }
37944
+ if (state.phase === "probe") {
37945
+ await discardProbeInstance(state.instance);
37946
+ if (isTornDown()) return;
37947
+ state = { phase: "opening" };
37948
+ }
37949
+ const instance = await connectInstance("legacy");
37950
+ if (isTornDown()) {
37951
+ await disposeLateInstance(instance);
37952
+ return;
37953
+ }
37954
+ state = {
37955
+ phase: "pinned",
37956
+ era: "legacy",
37957
+ instance
37958
+ };
37959
+ state.instance.channel.deliver(message);
37960
+ return;
37961
+ }
37962
+ }
37963
+ };
37964
+ const queue = [];
37965
+ let pumping = false;
37966
+ const pump = async () => {
37967
+ if (pumping) return;
37968
+ pumping = true;
37969
+ try {
37970
+ while (queue.length > 0) {
37971
+ const message = queue.shift();
37972
+ try {
37973
+ await processMessage(message);
37974
+ } catch (error62) {
37975
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InternalError, "Internal server error");
37976
+ reportError(toError(error62));
37977
+ }
37978
+ }
37979
+ } finally {
37980
+ pumping = false;
37981
+ }
37982
+ };
37983
+ const closeAll = async () => {
37984
+ if (closing || state.phase === "closed") return;
37985
+ closing = true;
37986
+ const current = state;
37987
+ state = { phase: "closed" };
37988
+ for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error62) => reportError(toError(error62)));
37989
+ if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error62) => reportError(toError(error62)));
37990
+ await wire.close().catch((error62) => reportError(toError(error62)));
37991
+ };
37992
+ wire.onmessage = (message) => {
37993
+ queue.push(message);
37994
+ pump();
37995
+ };
37996
+ wire.onerror = (error62) => {
37997
+ reportError(error62);
37998
+ if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error62);
37999
+ };
38000
+ wire.onclose = () => {
38001
+ if (closing || state.phase === "closed") return;
38002
+ closing = true;
38003
+ const current = state;
38004
+ state = { phase: "closed" };
38005
+ if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error62) => reportError(toError(error62)));
38006
+ };
38007
+ const started = wire.start().catch((error62) => {
38008
+ reportError(toError(error62));
38009
+ throw error62;
38010
+ });
38011
+ started.catch(() => {
38012
+ });
38013
+ return { close: async () => {
38014
+ await started.catch(() => {
38015
+ });
38016
+ await closeAll();
38017
+ } };
38018
+ }
38019
+ function toError(value) {
38020
+ return value instanceof Error ? value : new Error(String(value));
38021
+ }
37376
38022
 
37377
38023
  // node_modules/@chrischall/mcp-utils/dist/errors/index.js
37378
38024
  var DEFAULT_ERROR_MESSAGE_MAX = 500;
@@ -37534,7 +38180,7 @@ async function createMcpServer(opts) {
37534
38180
  }
37535
38181
  return server;
37536
38182
  }
37537
- function withGracefulShutdown(server, opts = {}) {
38183
+ function withGracefulShutdown(target, opts = {}) {
37538
38184
  const shouldExit = opts.exit ?? true;
37539
38185
  let shuttingDown = false;
37540
38186
  const handler = (signal) => {
@@ -37545,7 +38191,7 @@ function withGracefulShutdown(server, opts = {}) {
37545
38191
  try {
37546
38192
  if (opts.onSignal)
37547
38193
  await opts.onSignal(signal);
37548
- await server.close();
38194
+ await target.close();
37549
38195
  } catch (err) {
37550
38196
  console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
37551
38197
  } finally {
@@ -37557,16 +38203,26 @@ function withGracefulShutdown(server, opts = {}) {
37557
38203
  process.on("SIGINT", () => handler("SIGINT"));
37558
38204
  process.on("SIGTERM", () => handler("SIGTERM"));
37559
38205
  }
37560
- async function runMcp(opts) {
37561
- const server = await createMcpServer(opts);
38206
+ function runMcp(opts) {
38207
+ if (opts.banner !== void 0) {
38208
+ console.error(opts.banner);
38209
+ }
38210
+ const spec = opts.transport ?? "stdio";
38211
+ const handle = serveStdio(() => createMcpServer({ ...opts, banner: void 0 }), {
38212
+ legacy: opts.legacy ?? "serve",
38213
+ onerror: opts.onerror ?? ((error62) => {
38214
+ console.error(`[mcp-utils] stdio server error: ${redactSecrets(error62.message)}`);
38215
+ }),
38216
+ // `'stdio'` means "let the entry make its own StdioServerTransport over
38217
+ // this process"; anything else is the caller's transport, which the entry
38218
+ // then owns (it starts it, pumps it, and closes it with the handle).
38219
+ ...spec === "stdio" ? {} : { transport: spec }
38220
+ });
37562
38221
  const shutdown = opts.shutdown ?? true;
37563
38222
  if (shutdown !== false) {
37564
- withGracefulShutdown(server, shutdown === true ? {} : shutdown);
38223
+ withGracefulShutdown(handle, shutdown === true ? {} : shutdown);
37565
38224
  }
37566
- const spec = opts.transport ?? "stdio";
37567
- const transport = spec === "stdio" ? new StdioServerTransport() : spec;
37568
- await server.connect(transport);
37569
- return server;
38225
+ return handle;
37570
38226
  }
37571
38227
 
37572
38228
  // node_modules/@chrischall/mcp-utils/dist/config/index.js
@@ -43309,7 +43965,7 @@ async function loginWithPassword(username, password) {
43309
43965
  // package.json
43310
43966
  var package_default = {
43311
43967
  name: "ofw-mcp",
43312
- version: "2.17.0",
43968
+ version: "2.18.0",
43313
43969
  license: "MIT",
43314
43970
  mcpName: "io.github.chrischall/ofw-mcp",
43315
43971
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -43343,7 +43999,7 @@ var package_default = {
43343
43999
  typecheck: "tsc -p tsconfig.json --noEmit"
43344
44000
  },
43345
44001
  dependencies: {
43346
- "@chrischall/mcp-utils": "^0.28.0",
44002
+ "@chrischall/mcp-utils": "^1.0.0",
43347
44003
  "@fetchproxy/bootstrap": "^3.0.1",
43348
44004
  "@modelcontextprotocol/server": "^2.0.0",
43349
44005
  dotenv: "^17.4.2",
@@ -48296,7 +48952,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
48296
48952
  var nodeAttachmentIO = new NodeAttachmentIO();
48297
48953
  await runMcp({
48298
48954
  name: "ofw",
48299
- version: "2.17.0",
48955
+ version: "2.18.0",
48300
48956
  // x-release-please-version
48301
48957
  deps: client,
48302
48958
  tools: [
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.17.0', // x-release-please-version
39
+ version: '2.18.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.17.0",
3
+ "version": "2.18.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.28.0",
37
+ "@chrischall/mcp-utils": "^1.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.17.0",
9
+ "version": "2.18.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ofw-mcp",
14
- "version": "2.17.0",
14
+ "version": "2.18.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },