splitwise-mcp 2.1.5 → 2.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +68 -26
- package/dist/client.js +8 -8
- package/dist/index.js +3 -3
- package/package.json +4 -13
- package/server.json +2 -2
- package/dist/splitwise-auth.js +0 -23
|
@@ -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": "2.1.
|
|
10
|
+
"version": "2.1.6"
|
|
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": "2.1.
|
|
18
|
+
"version": "2.1.6",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/bundle.js
CHANGED
|
@@ -3644,7 +3644,12 @@ var require_fast_uri = __commonJS({
|
|
|
3644
3644
|
}
|
|
3645
3645
|
function resolve(baseURI, relativeURI, options) {
|
|
3646
3646
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3647
|
-
const
|
|
3647
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3648
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3649
|
+
if (baseMalformed || relativeMalformed) {
|
|
3650
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3651
|
+
}
|
|
3652
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3648
3653
|
schemelessOptions.skipEscape = true;
|
|
3649
3654
|
return serialize(resolved, schemelessOptions);
|
|
3650
3655
|
}
|
|
@@ -3770,6 +3775,7 @@ var require_fast_uri = __commonJS({
|
|
|
3770
3775
|
}
|
|
3771
3776
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3772
3777
|
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3778
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3773
3779
|
function getParseError(parsed, matches) {
|
|
3774
3780
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3775
3781
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3804,6 +3810,20 @@ var require_fast_uri = __commonJS({
|
|
|
3804
3810
|
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3805
3811
|
malformedAuthorityOrPort = true;
|
|
3806
3812
|
}
|
|
3813
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3814
|
+
if (introducerMatch !== null) {
|
|
3815
|
+
const region = introducerMatch[1];
|
|
3816
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3817
|
+
if (normalizedRegion.length >= 2) {
|
|
3818
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3819
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3820
|
+
malformedAuthorityOrPort = true;
|
|
3821
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3822
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3823
|
+
malformedAuthorityOrPort = true;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3807
3827
|
const matches = uri.match(URI_PARSE);
|
|
3808
3828
|
if (matches) {
|
|
3809
3829
|
parsed.scheme = matches[1];
|
|
@@ -23048,17 +23068,33 @@ function normalizeObjectSchema(schema) {
|
|
|
23048
23068
|
}
|
|
23049
23069
|
return void 0;
|
|
23050
23070
|
}
|
|
23071
|
+
function getDotPath(path) {
|
|
23072
|
+
if (path.length === 0) {
|
|
23073
|
+
return "object root";
|
|
23074
|
+
}
|
|
23075
|
+
return path.reduce((acc, seg, index) => {
|
|
23076
|
+
if (index === 0) {
|
|
23077
|
+
return String(seg);
|
|
23078
|
+
}
|
|
23079
|
+
if (typeof seg === "number") {
|
|
23080
|
+
return `${acc}[${seg}]`;
|
|
23081
|
+
}
|
|
23082
|
+
return `${acc}.${seg}`;
|
|
23083
|
+
}, "");
|
|
23084
|
+
}
|
|
23051
23085
|
function getParseErrorMessage(error51) {
|
|
23052
23086
|
if (error51 && typeof error51 === "object") {
|
|
23087
|
+
if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
|
|
23088
|
+
return error51.issues.map((i) => {
|
|
23089
|
+
if (!i.path?.length) {
|
|
23090
|
+
return i.message;
|
|
23091
|
+
}
|
|
23092
|
+
return `${i.message} at ${getDotPath(i.path)}`;
|
|
23093
|
+
}).join("\n");
|
|
23094
|
+
}
|
|
23053
23095
|
if ("message" in error51 && typeof error51.message === "string") {
|
|
23054
23096
|
return error51.message;
|
|
23055
23097
|
}
|
|
23056
|
-
if ("issues" in error51 && Array.isArray(error51.issues) && error51.issues.length > 0) {
|
|
23057
|
-
const firstIssue = error51.issues[0];
|
|
23058
|
-
if (firstIssue && typeof firstIssue === "object" && "message" in firstIssue) {
|
|
23059
|
-
return String(firstIssue.message);
|
|
23060
|
-
}
|
|
23061
|
-
}
|
|
23062
23098
|
try {
|
|
23063
23099
|
return JSON.stringify(error51);
|
|
23064
23100
|
} catch {
|
|
@@ -29673,16 +29709,7 @@ var Server = class extends Protocol {
|
|
|
29673
29709
|
if (!methodSchema) {
|
|
29674
29710
|
throw new Error("Schema is missing a method literal");
|
|
29675
29711
|
}
|
|
29676
|
-
|
|
29677
|
-
if (isZ4Schema(methodSchema)) {
|
|
29678
|
-
const v4Schema = methodSchema;
|
|
29679
|
-
const v4Def = v4Schema._zod?.def;
|
|
29680
|
-
methodValue = v4Def?.value ?? v4Schema.value;
|
|
29681
|
-
} else {
|
|
29682
|
-
const v3Schema = methodSchema;
|
|
29683
|
-
const legacyDef = v3Schema._def;
|
|
29684
|
-
methodValue = legacyDef?.value ?? v3Schema.value;
|
|
29685
|
-
}
|
|
29712
|
+
const methodValue = getLiteralValue(methodSchema);
|
|
29686
29713
|
if (typeof methodValue !== "string") {
|
|
29687
29714
|
throw new Error("Schema method literal must be a string");
|
|
29688
29715
|
}
|
|
@@ -30870,8 +30897,17 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
30870
30897
|
import process3 from "node:process";
|
|
30871
30898
|
|
|
30872
30899
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
|
|
30900
|
+
var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
|
|
30873
30901
|
var ReadBuffer = class {
|
|
30902
|
+
constructor(options) {
|
|
30903
|
+
this._maxBufferSize = options?.maxBufferSize ?? STDIO_DEFAULT_MAX_BUFFER_SIZE;
|
|
30904
|
+
}
|
|
30874
30905
|
append(chunk) {
|
|
30906
|
+
const newSize = (this._buffer?.length ?? 0) + chunk.length;
|
|
30907
|
+
if (newSize > this._maxBufferSize) {
|
|
30908
|
+
this.clear();
|
|
30909
|
+
throw new Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);
|
|
30910
|
+
}
|
|
30875
30911
|
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
30876
30912
|
}
|
|
30877
30913
|
readMessage() {
|
|
@@ -30899,18 +30935,24 @@ function serializeMessage(message) {
|
|
|
30899
30935
|
|
|
30900
30936
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
30901
30937
|
var StdioServerTransport = class {
|
|
30902
|
-
constructor(_stdin = process3.stdin, _stdout = process3.stdout) {
|
|
30938
|
+
constructor(_stdin = process3.stdin, _stdout = process3.stdout, options) {
|
|
30903
30939
|
this._stdin = _stdin;
|
|
30904
30940
|
this._stdout = _stdout;
|
|
30905
|
-
this._readBuffer = new ReadBuffer();
|
|
30906
30941
|
this._started = false;
|
|
30907
30942
|
this._ondata = (chunk) => {
|
|
30908
|
-
|
|
30909
|
-
|
|
30943
|
+
try {
|
|
30944
|
+
this._readBuffer.append(chunk);
|
|
30945
|
+
this.processReadBuffer();
|
|
30946
|
+
} catch (error51) {
|
|
30947
|
+
this.onerror?.(error51);
|
|
30948
|
+
this.close().catch(() => {
|
|
30949
|
+
});
|
|
30950
|
+
}
|
|
30910
30951
|
};
|
|
30911
30952
|
this._onerror = (error51) => {
|
|
30912
30953
|
this.onerror?.(error51);
|
|
30913
30954
|
};
|
|
30955
|
+
this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
|
|
30914
30956
|
}
|
|
30915
30957
|
/**
|
|
30916
30958
|
* Starts listening for messages on stdin.
|
|
@@ -31304,10 +31346,10 @@ var SplitwiseClient = class {
|
|
|
31304
31346
|
* host's install-time smoke test) when SPLITWISE_API_KEY isn't set yet.
|
|
31305
31347
|
* Tool calls re-raise the error at request time.
|
|
31306
31348
|
*
|
|
31307
|
-
* Optional constructor seam: a hosted per-user deployment
|
|
31308
|
-
*
|
|
31309
|
-
*
|
|
31310
|
-
*
|
|
31349
|
+
* Optional constructor seam: a hosted per-user deployment builds one client
|
|
31350
|
+
* per request with that user's `apiKey` injected. The stdio path passes no
|
|
31351
|
+
* options, so the key resolves from the environment exactly as before —
|
|
31352
|
+
* byte-for-byte identical behaviour.
|
|
31311
31353
|
*/
|
|
31312
31354
|
constructor(opts) {
|
|
31313
31355
|
const key = opts?.apiKey ?? readEnvVar("SPLITWISE_API_KEY");
|
|
@@ -31738,7 +31780,7 @@ function registerUtilityTools(server, client2) {
|
|
|
31738
31780
|
// src/index.ts
|
|
31739
31781
|
await runMcp({
|
|
31740
31782
|
name: "splitwise-mcp",
|
|
31741
|
-
version: "2.1.
|
|
31783
|
+
version: "2.1.6",
|
|
31742
31784
|
// x-release-please-version
|
|
31743
31785
|
deps: client,
|
|
31744
31786
|
banner: "[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.",
|
package/dist/client.js
CHANGED
|
@@ -4,15 +4,15 @@ import { loadDotenvSafely, readEnvVar, createApiClient } from '@chrischall/mcp-u
|
|
|
4
4
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. mcpb
|
|
5
5
|
// bundle). `loadDotenvSafely` swallows a missing dotenv module and never lets
|
|
6
6
|
// .env override a host-provided value.
|
|
7
|
-
// The try/catch guards
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// The try/catch guards a runtime where `import.meta.url` is undefined and
|
|
8
|
+
// `fileURLToPath(undefined)` would throw at module init — a sandboxed one has
|
|
9
|
+
// no filesystem or .env to load anyway.
|
|
10
10
|
try {
|
|
11
11
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
await loadDotenvSafely({ path: join(dir, '..', '.env'), override: false });
|
|
13
13
|
}
|
|
14
14
|
catch {
|
|
15
|
-
/* non-Node runtime
|
|
15
|
+
/* non-Node runtime: no .env to load */
|
|
16
16
|
}
|
|
17
17
|
const BASE_URL = 'https://secure.splitwise.com/api/v3.0';
|
|
18
18
|
const SERVICE_NAME = 'Splitwise';
|
|
@@ -25,10 +25,10 @@ export class SplitwiseClient {
|
|
|
25
25
|
* host's install-time smoke test) when SPLITWISE_API_KEY isn't set yet.
|
|
26
26
|
* Tool calls re-raise the error at request time.
|
|
27
27
|
*
|
|
28
|
-
* Optional constructor seam: a hosted per-user deployment
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* Optional constructor seam: a hosted per-user deployment builds one client
|
|
29
|
+
* per request with that user's `apiKey` injected. The stdio path passes no
|
|
30
|
+
* options, so the key resolves from the environment exactly as before —
|
|
31
|
+
* byte-for-byte identical behaviour.
|
|
32
32
|
*/
|
|
33
33
|
constructor(opts) {
|
|
34
34
|
// Injected apiKey (hosted per-user path) takes precedence; otherwise fall
|
package/dist/index.js
CHANGED
|
@@ -13,11 +13,11 @@ import { registerUtilityTools } from './tools/utilities.js';
|
|
|
13
13
|
// first request. That preserves the deferred-config-error pattern: the server
|
|
14
14
|
// boots and answers the host's install-time tools/list smoke test even when
|
|
15
15
|
// SPLITWISE_API_KEY is absent — the configuration error only surfaces on the
|
|
16
|
-
// first tool call. A hosted per-user
|
|
17
|
-
//
|
|
16
|
+
// first tool call. A hosted per-user deployment injects its own per-request
|
|
17
|
+
// client into the same registrars instead of this singleton.
|
|
18
18
|
await runMcp({
|
|
19
19
|
name: 'splitwise-mcp',
|
|
20
|
-
version: '2.1.
|
|
20
|
+
version: '2.1.6', // x-release-please-version
|
|
21
21
|
deps: client,
|
|
22
22
|
banner: '[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.',
|
|
23
23
|
tools: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "splitwise-mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
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>",
|
|
@@ -40,28 +40,19 @@
|
|
|
40
40
|
"dev": "node dist/index.js",
|
|
41
41
|
"test": "vitest run",
|
|
42
42
|
"test:watch": "vitest",
|
|
43
|
-
"test:coverage": "vitest run --coverage"
|
|
44
|
-
"worker:dev": "wrangler dev",
|
|
45
|
-
"worker:deploy": "wrangler deploy",
|
|
46
|
-
"worker:test": "vitest run --config vitest.workers.config.ts"
|
|
43
|
+
"test:coverage": "vitest run --coverage"
|
|
47
44
|
},
|
|
48
45
|
"dependencies": {
|
|
49
|
-
"@chrischall/mcp-utils": "^0.
|
|
46
|
+
"@chrischall/mcp-utils": "^0.14.0",
|
|
50
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
48
|
"dotenv": "^17.4.0",
|
|
52
49
|
"zod": "^4.4.2"
|
|
53
50
|
},
|
|
54
51
|
"devDependencies": {
|
|
55
|
-
"@chrischall/mcp-connector": "^1.1.1",
|
|
56
|
-
"@cloudflare/vitest-pool-workers": "^0.18.4",
|
|
57
|
-
"@cloudflare/workers-oauth-provider": "^0.8.1",
|
|
58
|
-
"@cloudflare/workers-types": "^5.20260708.1",
|
|
59
52
|
"@types/node": "^26.0.0",
|
|
60
53
|
"@vitest/coverage-v8": "^4.1.2",
|
|
61
|
-
"agents": "^0.19.0",
|
|
62
54
|
"esbuild": "^0.28.0",
|
|
63
55
|
"typescript": "^7.0.2",
|
|
64
|
-
"vitest": "^4.1.2"
|
|
65
|
-
"wrangler": "^4.110.0"
|
|
56
|
+
"vitest": "^4.1.2"
|
|
66
57
|
}
|
|
67
58
|
}
|
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": "2.1.
|
|
9
|
+
"version": "2.1.6",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "splitwise-mcp",
|
|
14
|
-
"version": "2.1.
|
|
14
|
+
"version": "2.1.6",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/dist/splitwise-auth.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { SplitwiseClient } from './client.js';
|
|
2
|
-
/**
|
|
3
|
-
* `ConnectorAuth` for the Splitwise remote connector: the login page collects
|
|
4
|
-
* the user's own Splitwise API key (from https://secure.splitwise.com/apps/register),
|
|
5
|
-
* verifies it by constructing a `SplitwiseClient` with the key and calling the
|
|
6
|
-
* current-user endpoint (a bad key throws, which the connector surfaces back on
|
|
7
|
-
* the login page), and stores `{ apiKey }` as the OAuth props that `worker.ts`'s
|
|
8
|
-
* `buildClient` turns into a per-user client.
|
|
9
|
-
*/
|
|
10
|
-
export const splitwiseAuth = {
|
|
11
|
-
service: 'Splitwise',
|
|
12
|
-
accent: '#1CC29F',
|
|
13
|
-
privacyNote: 'Your Splitwise API key is stored encrypted and used only to call Splitwise on your behalf.',
|
|
14
|
-
fields: [{ name: 'apiKey', label: 'Splitwise API key', type: 'password' }],
|
|
15
|
-
async login(fields) {
|
|
16
|
-
// Verify the key up front — an invalid key makes /get_current_user throw
|
|
17
|
-
// here, which the connector surfaces back on the login page. We discard the
|
|
18
|
-
// response: the per-user client is built fresh from the stored key.
|
|
19
|
-
const client = new SplitwiseClient({ apiKey: fields.apiKey });
|
|
20
|
-
await client.request('GET', '/get_current_user');
|
|
21
|
-
return { apiKey: fields.apiKey };
|
|
22
|
-
},
|
|
23
|
-
};
|