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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -2
- package/dist/bundle.js +119 -33
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/server.json +2 -2
- package/skills/splitwise-mcp/SKILL.md +1 -1
|
@@ -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.
|
|
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.
|
|
18
|
+
"version": "3.3.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
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
|
|
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-
|
|
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-
|
|
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 `
|
|
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 ?
|
|
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
|
|
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
|
|
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
|
|
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 }, {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
49
|
-
"@modelcontextprotocol/server": "^2.
|
|
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.
|
|
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.
|
|
9
|
+
"version": "3.3.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "splitwise-mcp",
|
|
14
|
-
"version": "3.
|
|
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
|