splitwise-mcp 3.2.2 → 3.3.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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "MCP server for Splitwise — natural-language expense and group management via the Splitwise API",
10
- "version": "3.2.2"
10
+ "version": "3.3.0"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "Splitwise",
16
16
  "source": "./",
17
17
  "description": "MCP server for Splitwise — manage expenses, groups, and friends via natural language",
18
- "version": "3.2.2",
18
+ "version": "3.3.0",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "splitwise-mcp",
3
3
  "displayName": "Splitwise",
4
- "version": "3.2.2",
4
+ "version": "3.3.0",
5
5
  "description": "MCP server for Splitwise — natural-language expense and group management via the Splitwise API",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/README.md CHANGED
@@ -117,11 +117,12 @@ Add to Claude Desktop config:
117
117
 
118
118
  ## Confirmations
119
119
 
120
- Every Splitwise write (creating, editing or deleting expenses, groups, friends, comments, or your profile) notifies other people, so it asks you to confirm first. A client that can show a confirmation prompt (Claude Code) shows one. On a client that cannot (claude.ai, Claude Desktop), the first call changes nothing and returns a preview of exactly what will be sent plus a `confirmToken`; only a repeat call with that token goes through, and the token is refused if anything in the request changed in between.
120
+ Every Splitwise write (creating, editing or deleting expenses, groups, friends, comments, or your profile) notifies other people, so it asks you to confirm first. A client that can show a confirmation prompt (Claude Code) shows one (unless `MCP_CONFIRM_ELICITATION=off`). On a client that cannot (claude.ai, Claude Desktop), the first call changes nothing and returns a preview of exactly what will be sent plus a `confirmToken`; only a repeat call with that token goes through, and the token is refused if anything in the request changed in between.
121
121
 
122
122
  | variable | default | |
123
123
  |---|---|---|
124
- | `MCP_CONFIRM_MODE` | `ask-user` | What a write does on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop). `ask-user`: two steps — the first call does nothing and returns a preview plus a token, and the model must get your approval in chat before calling again with it. `auto`: the same two steps, but the model may use the token after reviewing the preview itself. `refuse`: writes are refused on such clients. A client that can show prompts (Claude Code) always gets the real prompt. An unrecognised value is treated as `refuse`. |
124
+ | `MCP_CONFIRM_MODE` | `ask-user` | What a write does on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop). `ask-user`: two steps — the first call does nothing and returns a preview plus a token, and the model must get your approval in chat before calling again with it. `auto`: the same two steps, but the model may use the token after reviewing the preview itself. `refuse`: writes are refused on such clients. A client that can show prompts (Claude Code) always gets the real prompt, unless `MCP_CONFIRM_ELICITATION=off`. An unrecognised value is treated as `refuse`. |
125
+ | `MCP_CONFIRM_ELICITATION` | `on` | `off` never shows a confirmation prompt, so every client gets the `MCP_CONFIRM_MODE` behaviour. Set it for a client that says it can show prompts but never does (the write hangs — opencode 2.0.x). Any other value is treated as `on`, with a warning on stderr. |
125
126
  | `MCP_CONFIRM_TTL_SECONDS` | `600` | How long a token stays valid. |
126
127
  | `MCP_CONFIRM_SECRET` | random per process | Signing key; set it only if tokens must survive a server restart. |
127
128
 
package/dist/bundle.js CHANGED
@@ -64250,7 +64250,7 @@ var OAuthTokenRevocationRequestSchema = z.object({
64250
64250
  token_type_hint: z.string().optional()
64251
64251
  }).strip();
64252
64252
 
64253
- // node_modules/@modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs
64253
+ // node_modules/@modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs
64254
64254
  var z2 = __toESM(require_v4(), 1);
64255
64255
  var BRANDS = /* @__PURE__ */ Symbol.for("mcp.sdk.errorBrands");
64256
64256
  function stampErrorBrands(instance, ctor) {
@@ -66494,7 +66494,8 @@ function build() {
66494
66494
  ListResourceTemplatesResultResponseSchema: wireResultResponse(ListResourceTemplatesResultSchema$1),
66495
66495
  ReadResourceResultResponseSchema: wireResultResponse(z2.union([ReadResourceResultSchema$1, InputRequiredResultSchema])),
66496
66496
  CompleteResultResponseSchema: wireResultResponse(CompleteResultSchema$1),
66497
- DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1)
66497
+ DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1),
66498
+ SubscriptionsListenResultResponseSchema: wireResultResponse(SubscriptionsListenResultSchema$1)
66498
66499
  };
66499
66500
  }
66500
66501
  var memo;
@@ -67002,6 +67003,9 @@ function isSpecRequestMethod(method) {
67002
67003
  function isSpecNotificationMethod(method) {
67003
67004
  return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
67004
67005
  }
67006
+ function isExtensionReusedRequestMethod(method) {
67007
+ return method === "tasks/get" || method === "tasks/cancel";
67008
+ }
67005
67009
  var ALL_CODECS = [rev2025Codec, rev2026Codec];
67006
67010
  function isPlainObject$3(value) {
67007
67011
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -68010,6 +68014,8 @@ var Protocol = class {
68010
68014
  _transport;
68011
68015
  _requestMessageId = 0;
68012
68016
  _requestHandlers = /* @__PURE__ */ new Map();
68017
+ /** Methods registered with an explicit schema; the era gate in `_onrequest` reads it for the Tasks extension names. */
68018
+ _customSchemaRequestMethods = /* @__PURE__ */ new Set();
68013
68019
  _requestHandlerAbortControllers = /* @__PURE__ */ new Map();
68014
68020
  _notificationHandlers = /* @__PURE__ */ new Map();
68015
68021
  _responseHandlers = /* @__PURE__ */ new Map();
@@ -68293,7 +68299,7 @@ var Protocol = class {
68293
68299
  return;
68294
68300
  }
68295
68301
  }
68296
- if (isSpecRequestMethod(request.method) && !codec2.hasRequestMethod(request.method)) {
68302
+ if (isSpecRequestMethod(request.method) && !codec2.hasRequestMethod(request.method) && !(isExtensionReusedRequestMethod(request.method) && this._customSchemaRequestMethods.has(request.method))) {
68297
68303
  sendErrorResponse(ProtocolErrorCode.MethodNotFound, "Method not found");
68298
68304
  return;
68299
68305
  }
@@ -68426,8 +68432,11 @@ var Protocol = class {
68426
68432
  }
68427
68433
  request(request, schemaOrOptions, maybeOptions) {
68428
68434
  const codec2 = this._resolveOutboundCodec(request.method);
68435
+ if (isStandardSchema(schemaOrOptions)) {
68436
+ if (!isExtensionReusedRequestMethod(request.method)) this._assertOutboundRequestInEra(codec2, request.method);
68437
+ return this._requestWithSchemaViaCodec(codec2, request, schemaOrOptions, maybeOptions);
68438
+ }
68429
68439
  this._assertOutboundRequestInEra(codec2, request.method);
68430
- if (isStandardSchema(schemaOrOptions)) return this._requestWithSchemaViaCodec(codec2, request, schemaOrOptions, maybeOptions);
68431
68440
  const validate2 = codecResultValidator(codec2, request.method);
68432
68441
  if (validate2 === void 0) throw new TypeError(`'${request.method}' is not a spec method; pass a result schema as the second argument to request().`);
68433
68442
  return this._requestWithSchemaViaCodec(codec2, request, validate2, schemaOrOptions);
@@ -68671,6 +68680,8 @@ var Protocol = class {
68671
68680
  return maybeHandler(parsed.data, ctx);
68672
68681
  };
68673
68682
  else throw new TypeError("setRequestHandler: handler is required");
68683
+ if (typeof schemasOrHandler === "function") this._customSchemaRequestMethods.delete(method);
68684
+ else this._customSchemaRequestMethods.add(method);
68674
68685
  this._requestHandlers.set(method, this._wrapHandler(method, stored));
68675
68686
  }
68676
68687
  /**
@@ -68700,6 +68711,7 @@ var Protocol = class {
68700
68711
  */
68701
68712
  removeRequestHandler(method) {
68702
68713
  this._requestHandlers.delete(method);
68714
+ this._customSchemaRequestMethods.delete(method);
68703
68715
  }
68704
68716
  /**
68705
68717
  * Asserts that a request handler has not already been set for the given method, in preparation for a new one being automatically installed.
@@ -75942,7 +75954,7 @@ var Ajv = import_ajv.Ajv;
75942
75954
  // node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
75943
75955
  import process2 from "node:process";
75944
75956
 
75945
- // node_modules/@modelcontextprotocol/server/dist/mcp-DYuW2ZSs.mjs
75957
+ // node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs
75946
75958
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
75947
75959
  function isCompletable(schema) {
75948
75960
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -76007,7 +76019,7 @@ var StdioListenRouter = class {
76007
76019
  _serverCapabilities;
76008
76020
  /**
76009
76021
  * The serving instance's identity, stamped onto the graceful-close
76010
- * results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
76022
+ * results' `_meta` (the spec's `SubscriptionsListenResultMetaObject` extends
76011
76023
  * `ResultMetaObject`). Handed over together with the capabilities.
76012
76024
  */
76013
76025
  _serverInfo;
@@ -76339,6 +76351,11 @@ var Server = class extends Protocol {
76339
76351
  if (modernProtocolVersions(this._supportedProtocolVersions).length > 0) this.setRequestHandler("server/discover", () => this._ondiscover());
76340
76352
  if (this._capabilities.logging) this._registerLoggingHandler();
76341
76353
  }
76354
+ /** Attaches to the given transport; rejects while this instance is connected to another one. */
76355
+ async connect(transport) {
76356
+ if (this.transport !== void 0) throw new SdkError(SdkErrorCode.AlreadyConnected, "Use a separate Server instance per connection: this instance is already connected to a transport. To connect it to a new transport, call close() first.");
76357
+ await super.connect(transport);
76358
+ }
76342
76359
  /**
76343
76360
  * Registers the built-in `logging/setLevel` request handler.
76344
76361
  *
@@ -76892,21 +76909,41 @@ var Server = class extends Protocol {
76892
76909
  function discoverAdvertisedCapabilities(capabilities) {
76893
76910
  return { ...capabilities };
76894
76911
  }
76912
+ function toolInputElementCount(value, max) {
76913
+ let count = 0;
76914
+ const stack = [value];
76915
+ while (stack.length > 0) {
76916
+ const node2 = stack.pop();
76917
+ if (node2 === null || typeof node2 !== "object") continue;
76918
+ if (Array.isArray(node2)) for (const child of node2) {
76919
+ if (++count > max) return count;
76920
+ if (child !== null && typeof child === "object") stack.push(child);
76921
+ }
76922
+ else for (const key in node2) {
76923
+ if (!Object.prototype.hasOwnProperty.call(node2, key)) continue;
76924
+ if (++count > max) return count;
76925
+ const child = node2[key];
76926
+ if (child !== null && typeof child === "object") stack.push(child);
76927
+ }
76928
+ }
76929
+ return count;
76930
+ }
76931
+ function resolveMaxToolInputElements(value) {
76932
+ if (value === void 0 || value === Infinity) return void 0;
76933
+ if (typeof value !== "number" || Number.isNaN(value) || value < 1) throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
76934
+ return value;
76935
+ }
76895
76936
  var McpServer = class {
76896
76937
  /**
76897
76938
  * The underlying {@linkcode Server} instance, useful for advanced operations like sending notifications.
76898
76939
  */
76899
76940
  server;
76941
+ _maxToolInputElements;
76900
76942
  _registeredResources = {};
76901
76943
  _registeredResourceTemplates = {};
76902
76944
  _registeredTools = {};
76903
76945
  _registeredPrompts = {};
76904
- /**
76905
- * Per-tool JSON-converted `inputSchema`, memoized so the SEP-2243
76906
- * registration-time scan and the pre-dispatch validation step share one
76907
- * conversion instead of paying it twice per request under the
76908
- * per-request-factory `createMcpHandler` model.
76909
- */
76946
+ /** Per-tool JSON-converted `inputSchema`, filled on first use by `toolInputSchemaJson()`. */
76910
76947
  _toolInputSchemaJson = {};
76911
76948
  /**
76912
76949
  * The JSON-serialized `inputSchema` of a registered tool, or `undefined`
@@ -76931,6 +76968,7 @@ var McpServer = class {
76931
76968
  }
76932
76969
  constructor(serverInfo, options) {
76933
76970
  this.server = new Server(serverInfo, options);
76971
+ this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
76934
76972
  if (options?.capabilities?.tools) this.setToolRequestHandlers();
76935
76973
  if (options?.capabilities?.resources) this.setResourceRequestHandlers();
76936
76974
  if (options?.capabilities?.prompts) this.setPromptRequestHandlers();
@@ -77003,7 +77041,7 @@ var McpServer = class {
77003
77041
  name,
77004
77042
  title: tool.title,
77005
77043
  description: tool.description,
77006
- inputSchema: tool.inputSchema ? standardSchemaToJsonSchema(tool.inputSchema, "input") : EMPTY_OBJECT_JSON_SCHEMA,
77044
+ inputSchema: tool.inputSchema ? convertListedInputSchema(name, tool.inputSchema) : EMPTY_OBJECT_JSON_SCHEMA,
77007
77045
  annotations: tool.annotations,
77008
77046
  icons: tool.icons,
77009
77047
  execution: tool.execution,
@@ -77048,6 +77086,7 @@ var McpServer = class {
77048
77086
  * Validates tool input arguments against the tool's input schema.
77049
77087
  */
77050
77088
  async validateToolInput(tool, args, toolName) {
77089
+ if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
77051
77090
  if (!tool.inputSchema) return;
77052
77091
  const parseResult = await validateStandardSchema(tool.inputSchema, args ?? {});
77053
77092
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${parseResult.error}`);
@@ -77322,20 +77361,19 @@ var McpServer = class {
77322
77361
  }
77323
77362
  _createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
77324
77363
  validateAndWarnToolName(name);
77325
- if (inputSchema !== void 0) try {
77326
- const json2 = standardSchemaToJsonSchema(inputSchema, "input");
77327
- this._toolInputSchemaJson[name] = json2;
77328
- const scan = scanXMcpHeaderDeclarations(json2);
77329
- if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
77330
- } catch {
77331
- }
77332
77364
  let currentHandler = handler;
77365
+ let outputSchemaJson;
77333
77366
  const registeredTool = {
77334
77367
  title,
77335
77368
  description,
77336
77369
  inputSchema,
77337
77370
  outputSchema,
77338
- outputSchemaJson: convertOutputSchemaJson(outputSchema),
77371
+ get outputSchemaJson() {
77372
+ return outputSchemaJson ??= convertOutputSchemaJson(registeredTool.outputSchema);
77373
+ },
77374
+ set outputSchemaJson(value) {
77375
+ outputSchemaJson = value;
77376
+ },
77339
77377
  annotations,
77340
77378
  icons,
77341
77379
  execution,
@@ -77462,6 +77500,12 @@ var EMPTY_OBJECT_JSON_SCHEMA = {
77462
77500
  type: "object",
77463
77501
  properties: {}
77464
77502
  };
77503
+ function convertListedInputSchema(name, inputSchema) {
77504
+ const json2 = standardSchemaToJsonSchema(inputSchema, "input");
77505
+ const scan = scanXMcpHeaderDeclarations(json2);
77506
+ if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
77507
+ return json2;
77508
+ }
77465
77509
  function convertOutputSchemaJson(outputSchema) {
77466
77510
  if (outputSchema === void 0) return void 0;
77467
77511
  try {
@@ -77474,7 +77518,7 @@ function createPromptHandler(name, argsSchema, callback) {
77474
77518
  if (argsSchema) {
77475
77519
  const typedCallback = callback;
77476
77520
  return async (args, ctx) => {
77477
- const parseResult = await validateStandardSchema(argsSchema, args);
77521
+ const parseResult = await validateStandardSchema(argsSchema, args ?? {});
77478
77522
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
77479
77523
  return typedCallback(parseResult.data, ctx);
77480
77524
  };
@@ -97936,6 +97980,7 @@ function escapeKey(key) {
97936
97980
  var DEFAULT_REQUEST_KEY = "confirmation";
97937
97981
  var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
97938
97982
  var UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
97983
+ var DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
97939
97984
  var STATE_PREFIX = "mcpu.confirm.v1.";
97940
97985
  function bindingKey(binding) {
97941
97986
  if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
@@ -98008,6 +98053,15 @@ ${JSON.stringify(preview, null, 2)}`,
98008
98053
  };
98009
98054
  const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
98010
98055
  const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
98056
+ if (options.elicitation === false) {
98057
+ return textResult({
98058
+ confirmed: false,
98059
+ dispatched: false,
98060
+ action: options.action,
98061
+ reason: "confirmation-unsupported",
98062
+ note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
98063
+ });
98064
+ }
98011
98065
  if (response.kind === "missing") {
98012
98066
  if (callerAcceptsFormElicitation(ctx) === false) {
98013
98067
  return textResult({
@@ -98202,7 +98256,7 @@ async function tokenConfirmation(action, fb) {
98202
98256
  }
98203
98257
  async function requireConfirmationWithFallback(ctx, options) {
98204
98258
  const { tokenFallback, ...confirmation } = options;
98205
- if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
98259
+ if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
98206
98260
  return tokenConfirmation(options.action, tokenFallback);
98207
98261
  }
98208
98262
  return requireConfirmation(ctx, confirmation);
@@ -98350,6 +98404,20 @@ function readConfirmMode(env = process.env) {
98350
98404
  }
98351
98405
  return "refuse";
98352
98406
  }
98407
+ function readConfirmElicitation(env = process.env) {
98408
+ const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
98409
+ if (!raw || raw === "on")
98410
+ return "on";
98411
+ if (raw === "off")
98412
+ return "off";
98413
+ const key = `elicitation:${raw}`;
98414
+ if (!warned.has(key)) {
98415
+ warned.add(key);
98416
+ process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
98417
+ `);
98418
+ }
98419
+ return "on";
98420
+ }
98353
98421
  function readConfirmTtl(env) {
98354
98422
  const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
98355
98423
  if (!raw)
@@ -98394,6 +98462,7 @@ function spentTokenStoreFromEnv(env = process.env) {
98394
98462
  }
98395
98463
  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.)";
98396
98464
  var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
98465
+ var ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
98397
98466
  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.";
98398
98467
  function boundArgs(args) {
98399
98468
  if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
@@ -98407,10 +98476,13 @@ function confirmationFromEnv(options) {
98407
98476
  const spent = callerSpent ?? spentTokenStoreFromEnv(env);
98408
98477
  const mode = readConfirmMode(env);
98409
98478
  const ttlSeconds = readConfirmTtl(env);
98479
+ const elicitationOff = readConfirmElicitation(env) === "off";
98410
98480
  const bound = args === void 0 ? void 0 : boundArgs(args);
98411
- const confirmation = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
98481
+ const bindingAdded = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
98482
+ const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
98412
98483
  if (mode === "refuse" || ttlSeconds === void 0) {
98413
- const hint = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
98484
+ const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
98485
+ const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
98414
98486
  return {
98415
98487
  ...confirmation,
98416
98488
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
@@ -98519,7 +98591,10 @@ function surfaceToolHints(server) {
98519
98591
  ));
98520
98592
  }
98521
98593
  async function createMcpServer(opts) {
98522
- const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
98594
+ const server = new McpServer({ name: opts.name, version: opts.version }, {
98595
+ supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS],
98596
+ ...opts.maxToolInputElements !== void 0 ? { maxToolInputElements: opts.maxToolInputElements } : {}
98597
+ });
98523
98598
  if (opts.surfaceHints !== false)
98524
98599
  surfaceToolHints(server);
98525
98600
  if (opts.banner !== void 0) {
@@ -98815,6 +98890,17 @@ import { constants as fsConstants2, realpathSync as realpathSync2 } from "node:f
98815
98890
  var O_NOFOLLOW2 = fsConstants2.O_NOFOLLOW ?? 0;
98816
98891
  var O_NONBLOCK = fsConstants2.O_NONBLOCK ?? 0;
98817
98892
 
98893
+ // node_modules/@chrischall/mcp-utils/dist/internal/headers.js
98894
+ function responseHeader(res, name) {
98895
+ return getHeader(res?.headers, name);
98896
+ }
98897
+ function getHeader(headers, name) {
98898
+ if (!headers || typeof headers.get !== "function")
98899
+ return null;
98900
+ const value = headers.get.call(headers, name);
98901
+ return typeof value === "string" ? value : null;
98902
+ }
98903
+
98818
98904
  // node_modules/@chrischall/mcp-utils/dist/scrape/index.js
98819
98905
  var NEXT_DATA_MAX_CHARS = 16 * 1024 * 1024;
98820
98906
  function isCloudflareChallenge(html) {
@@ -99103,7 +99189,7 @@ function createApiClient(opts) {
99103
99189
  if (retryStatuses.includes(res.status) && attempt < retry.count) {
99104
99190
  discard(current);
99105
99191
  attempt += 1;
99106
- const delay = retry.honorRetryAfter ? parseRetryAfterMs(res.headers.get("retry-after"), {
99192
+ const delay = retry.honorRetryAfter ? parseRetryAfterMs(responseHeader(res, "retry-after"), {
99107
99193
  defaultMs: retry.delayMs,
99108
99194
  capMs: retry.maxRetryAfterMs ?? 3e4
99109
99195
  }) : retry.delayMs;
@@ -99116,7 +99202,7 @@ function createApiClient(opts) {
99116
99202
  async function unauthorizedOrEdge(attempt, method, path) {
99117
99203
  const res = attempt.res;
99118
99204
  let text = "";
99119
- if (/json/i.test(res.headers.get("content-type") ?? "")) {
99205
+ if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
99120
99206
  discard(attempt);
99121
99207
  } else {
99122
99208
  text = await readBody(attempt, (r2) => r2.text()).catch(() => "");
@@ -99128,14 +99214,14 @@ function createApiClient(opts) {
99128
99214
  }
99129
99215
  async function rateLimitedError(attempt, method, path) {
99130
99216
  const res = attempt.res;
99131
- const retryAfter = res.headers.get("retry-after");
99217
+ const retryAfter = responseHeader(res, "retry-after");
99132
99218
  const retryAfterMs = retryAfterToMs(retryAfter);
99133
99219
  if (!opts.onRateLimited) {
99134
99220
  discard(attempt);
99135
99221
  return new RateLimitedError(service, retryAfterMs);
99136
99222
  }
99137
99223
  let text = "";
99138
- if (/json/i.test(res.headers.get("content-type") ?? "")) {
99224
+ if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
99139
99225
  discard(attempt);
99140
99226
  } else {
99141
99227
  text = await readBody(attempt, (r2) => r2.text()).catch(() => "");
@@ -99195,7 +99281,7 @@ function createApiClient(opts) {
99195
99281
  const bytes = new Uint8Array(await readBody(attempt, (r2) => r2.arrayBuffer()));
99196
99282
  return {
99197
99283
  status: res.status,
99198
- contentType: res.headers.get("content-type"),
99284
+ contentType: responseHeader(res, "content-type"),
99199
99285
  headers: res.headers,
99200
99286
  bytes
99201
99287
  };
@@ -100528,7 +100614,7 @@ function registerUtilityTools(server, client2) {
100528
100614
  // src/index.ts
100529
100615
  await runMcp({
100530
100616
  name: "splitwise-mcp",
100531
- version: "3.2.2",
100617
+ version: "3.3.0",
100532
100618
  // x-release-please-version
100533
100619
  deps: client,
100534
100620
  banner: "[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.",
@@ -100551,7 +100637,7 @@ unpdf/dist/index.mjs:
100551
100637
  * @license Apache-2.0
100552
100638
  *)
100553
100639
 
100554
- @modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs:
100640
+ @modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs:
100555
100641
  (*!
100556
100642
  * content-type
100557
100643
  * Copyright(c) 2015 Douglas Christopher Wilson
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import { registerUtilityTools } from './tools/utilities.js';
19
19
  // client into the same registrars instead of this singleton.
20
20
  await runMcp({
21
21
  name: 'splitwise-mcp',
22
- version: '3.2.2', // x-release-please-version
22
+ version: '3.3.0', // x-release-please-version
23
23
  deps: client,
24
24
  banner: '[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.',
25
25
  tools: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "splitwise-mcp",
3
- "version": "3.2.2",
3
+ "version": "3.3.0",
4
4
  "mcpName": "io.github.chrischall/splitwise-mcp",
5
5
  "description": "Splitwise MCP server for Claude — developed and maintained by AI (Claude Code)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -45,14 +45,14 @@
45
45
  "typecheck": "tsc -p tsconfig.json --noEmit"
46
46
  },
47
47
  "dependencies": {
48
- "@chrischall/mcp-utils": "^2.13.0",
49
- "@modelcontextprotocol/server": "^2.2.0",
48
+ "@chrischall/mcp-utils": "^2.15.0",
49
+ "@modelcontextprotocol/server": "^2.3.0",
50
50
  "dotenv": "^18.0.2",
51
51
  "unpdf": "^1.8.1",
52
52
  "zod": "^4.6.5"
53
53
  },
54
54
  "devDependencies": {
55
- "@modelcontextprotocol/client": "^2.2.0",
55
+ "@modelcontextprotocol/client": "^2.3.0",
56
56
  "@types/node": "^26.0.0",
57
57
  "@vitest/coverage-v8": "^5.0.0",
58
58
  "esbuild": "^0.28.0",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/splitwise-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "3.2.2",
9
+ "version": "3.3.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "splitwise-mcp",
14
- "version": "3.2.2",
14
+ "version": "3.3.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -221,7 +221,7 @@ sw_update_expense(expense_id, description: "Corrected description", cost: "95.00
221
221
 
222
222
  ## Notes
223
223
 
224
- - Every write except `sw_undelete_expense` / `sw_undelete_group` asks the user to confirm first. Where the client can show a prompt, it does. Otherwise the first call changes nothing and returns `status: "confirmation-required"` with a `preview` (method, path, `willSend`) and a `confirmToken`: show the preview to the user, get their approval in chat, then repeat the same call with the same arguments plus `confirmToken` (see `MCP_CONFIRM_MODE`). A token acts once, expires, and is refused (`DRAFT_CHANGED`) if any argument changed
224
+ - Every write except `sw_undelete_expense` / `sw_undelete_group` asks the user to confirm first. Where the client can show a prompt, it does (unless the server sets `MCP_CONFIRM_ELICITATION=off`). Otherwise the first call changes nothing and returns `status: "confirmation-required"` with a `preview` (method, path, `willSend`) and a `confirmToken`: show the preview to the user, get their approval in chat, then repeat the same call with the same arguments plus `confirmToken` (see `MCP_CONFIRM_MODE`). A token acts once, expires, and is refused (`DRAFT_CHANGED`) if any argument changed
225
225
  - `cost` is always a decimal string (e.g. `"25.00"`)
226
226
  - `split_equally: true` and `users` array are mutually exclusive
227
227
  - For custom split updates, the **full `users` array is required** — the API replaces the entire split