splitwise-mcp 2.0.7 → 2.0.9
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/LICENSE +21 -0
- package/README.md +4 -0
- package/dist/bundle.js +537 -187
- package/dist/client.js +30 -55
- package/dist/index.js +18 -13
- package/dist/tools/expenses.js +20 -16
- package/dist/tools/friends.js +6 -4
- package/dist/tools/groups.js +10 -8
- package/dist/tools/user.js +6 -4
- package/dist/tools/utilities.js +12 -8
- package/package.json +2 -1
- package/server.json +2 -2
package/dist/client.js
CHANGED
|
@@ -1,43 +1,27 @@
|
|
|
1
1
|
import { dirname, join } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
// not available — rely on process.env (mcpb sets credentials via mcp_config.env)
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Read an env var, trim whitespace, and treat as unset if blank or if the value
|
|
14
|
-
* looks like an unsubstituted shell placeholder (e.g. `${FOO}`) — defends
|
|
15
|
-
* against MCP hosts that pass .mcp.json env blocks through unexpanded.
|
|
16
|
-
*/
|
|
17
|
-
function readVar(key) {
|
|
18
|
-
const raw = process.env[key];
|
|
19
|
-
if (typeof raw !== 'string')
|
|
20
|
-
return undefined;
|
|
21
|
-
const trimmed = raw.trim();
|
|
22
|
-
if (trimmed.length === 0)
|
|
23
|
-
return undefined;
|
|
24
|
-
if (trimmed === 'undefined' || trimmed === 'null')
|
|
25
|
-
return undefined;
|
|
26
|
-
if (/^\$\{[^}]*\}$/.test(trimmed))
|
|
27
|
-
return undefined;
|
|
28
|
-
return trimmed;
|
|
29
|
-
}
|
|
3
|
+
import { loadDotenvSafely, readEnvVar, createApiClient } from '@chrischall/mcp-utils';
|
|
4
|
+
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. mcpb
|
|
5
|
+
// bundle). `loadDotenvSafely` swallows a missing dotenv module and never lets
|
|
6
|
+
// .env override a host-provided value.
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
await loadDotenvSafely({ path: join(__dirname, '..', '.env'), override: false });
|
|
30
9
|
const BASE_URL = 'https://secure.splitwise.com/api/v3.0';
|
|
10
|
+
const SERVICE_NAME = 'Splitwise';
|
|
31
11
|
export class SplitwiseClient {
|
|
32
12
|
apiKey;
|
|
33
13
|
configError;
|
|
14
|
+
api;
|
|
34
15
|
/**
|
|
35
16
|
* Defer the config error so the server can still start (and respond to the
|
|
36
17
|
* host's install-time smoke test) when SPLITWISE_API_KEY isn't set yet.
|
|
37
18
|
* Tool calls re-raise the error at request time.
|
|
38
19
|
*/
|
|
39
20
|
constructor() {
|
|
40
|
-
|
|
21
|
+
// readEnvVar trims whitespace and treats blank/`undefined`/`null`/`${...}`
|
|
22
|
+
// placeholder values as unset — defends against MCP hosts that pass
|
|
23
|
+
// .mcp.json env blocks through unexpanded.
|
|
24
|
+
const key = readEnvVar('SPLITWISE_API_KEY');
|
|
41
25
|
if (!key) {
|
|
42
26
|
this.apiKey = null;
|
|
43
27
|
this.configError = new Error('SPLITWISE_API_KEY environment variable is required');
|
|
@@ -46,6 +30,16 @@ export class SplitwiseClient {
|
|
|
46
30
|
this.apiKey = key;
|
|
47
31
|
this.configError = null;
|
|
48
32
|
}
|
|
33
|
+
// `getToken` defers config errors to request time; on* handlers preserve Splitwise's documented 401/429 messages.
|
|
34
|
+
this.api = createApiClient({
|
|
35
|
+
baseUrl: BASE_URL,
|
|
36
|
+
getToken: () => this.requireKey(),
|
|
37
|
+
serviceName: SERVICE_NAME,
|
|
38
|
+
retry: { count: 1, delayMs: 2000 },
|
|
39
|
+
timeout: 30_000,
|
|
40
|
+
onUnauthorized: () => new Error('SPLITWISE_API_KEY is invalid or missing'),
|
|
41
|
+
onRateLimited: () => new Error('Rate limited by Splitwise API'),
|
|
42
|
+
});
|
|
49
43
|
}
|
|
50
44
|
requireKey() {
|
|
51
45
|
if (this.configError)
|
|
@@ -53,32 +47,13 @@ export class SplitwiseClient {
|
|
|
53
47
|
return this.apiKey;
|
|
54
48
|
}
|
|
55
49
|
async request(method, path, body) {
|
|
56
|
-
return this.
|
|
57
|
-
}
|
|
58
|
-
async doRequest(method, path, body, isRetry) {
|
|
59
|
-
const headers = {
|
|
60
|
-
Authorization: `Bearer ${this.requireKey()}`,
|
|
61
|
-
'Content-Type': 'application/json',
|
|
62
|
-
Accept: 'application/json',
|
|
63
|
-
};
|
|
64
|
-
const response = await fetch(`${BASE_URL}${path}`, {
|
|
65
|
-
method,
|
|
66
|
-
headers,
|
|
67
|
-
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
68
|
-
});
|
|
69
|
-
if (response.status === 401) {
|
|
70
|
-
throw new Error('SPLITWISE_API_KEY is invalid or missing');
|
|
71
|
-
}
|
|
72
|
-
if (response.status === 429) {
|
|
73
|
-
if (!isRetry) {
|
|
74
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
75
|
-
return this.doRequest(method, path, body, true);
|
|
76
|
-
}
|
|
77
|
-
throw new Error('Rate limited by Splitwise API');
|
|
78
|
-
}
|
|
79
|
-
if (!response.ok) {
|
|
80
|
-
throw new Error(`Splitwise API error: ${response.status} ${response.statusText} for ${method} ${path}`);
|
|
81
|
-
}
|
|
82
|
-
return response.json();
|
|
50
|
+
return this.api.fetchJson(method, path, body !== undefined ? { body } : {});
|
|
83
51
|
}
|
|
84
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Module-level singleton shared by every tool module. Constructing it here (not
|
|
55
|
+
* in `index.ts`) keeps the deferred-config-error pattern: the server boots and
|
|
56
|
+
* answers the host's install-time tools/list smoke test even when the API key is
|
|
57
|
+
* absent — the error only surfaces on the first request.
|
|
58
|
+
*/
|
|
59
|
+
export const client = new SplitwiseClient();
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { SplitwiseClient } from './client.js';
|
|
2
|
+
import { runMcp } from '@chrischall/mcp-utils';
|
|
5
3
|
import { registerUserTools } from './tools/user.js';
|
|
6
4
|
import { registerGroupTools } from './tools/groups.js';
|
|
7
5
|
import { registerFriendTools } from './tools/friends.js';
|
|
8
6
|
import { registerExpenseTools } from './tools/expenses.js';
|
|
9
7
|
import { registerUtilityTools } from './tools/utilities.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
// The Splitwise client is a module-level singleton (imported by each tool
|
|
9
|
+
// module) that defers its config error to the first request. That preserves the
|
|
10
|
+
// deferred-config-error pattern: the server boots and answers the host's
|
|
11
|
+
// install-time tools/list smoke test even when SPLITWISE_API_KEY is absent —
|
|
12
|
+
// the configuration error only surfaces on the first tool call.
|
|
13
|
+
await runMcp({
|
|
14
|
+
name: 'splitwise-mcp',
|
|
15
|
+
version: '2.0.9', // x-release-please-version
|
|
16
|
+
banner: '[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.',
|
|
17
|
+
tools: [
|
|
18
|
+
registerUserTools,
|
|
19
|
+
registerGroupTools,
|
|
20
|
+
registerFriendTools,
|
|
21
|
+
registerExpenseTools,
|
|
22
|
+
registerUtilityTools,
|
|
23
|
+
],
|
|
24
|
+
});
|
package/dist/tools/expenses.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { textResult, buildQueryString } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
2
4
|
/** Flattens a users array into Splitwise's flat-param JSON format. */
|
|
3
5
|
export function flattenUsers(users) {
|
|
4
6
|
const flat = {};
|
|
@@ -28,7 +30,7 @@ const userShareSchema = z.object({
|
|
|
28
30
|
paid_share: z.string().describe('Amount this user paid, e.g. "25.00"'),
|
|
29
31
|
owed_share: z.string().describe('Amount this user owes, e.g. "12.50"'),
|
|
30
32
|
});
|
|
31
|
-
export function registerExpenseTools(server
|
|
33
|
+
export function registerExpenseTools(server) {
|
|
32
34
|
server.registerTool('sw_list_expenses', {
|
|
33
35
|
description: 'List or search Splitwise expenses. All filters are optional. Use group_id to filter by group, dated_after/dated_before for date ranges.',
|
|
34
36
|
annotations: { readOnlyHint: true },
|
|
@@ -43,16 +45,18 @@ export function registerExpenseTools(server, client) {
|
|
|
43
45
|
offset: z.number().describe('Pagination offset').optional(),
|
|
44
46
|
},
|
|
45
47
|
}, async (args) => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
const qs = buildQueryString({
|
|
49
|
+
group_id: args.group_id,
|
|
50
|
+
friend_id: args.friend_id,
|
|
51
|
+
dated_after: args.dated_after,
|
|
52
|
+
dated_before: args.dated_before,
|
|
53
|
+
updated_after: args.updated_after,
|
|
54
|
+
updated_before: args.updated_before,
|
|
55
|
+
limit: args.limit,
|
|
56
|
+
offset: args.offset,
|
|
57
|
+
});
|
|
58
|
+
const data = await client.request('GET', `/get_expenses${qs}`);
|
|
59
|
+
return textResult(data);
|
|
56
60
|
});
|
|
57
61
|
server.registerTool('sw_get_expense', {
|
|
58
62
|
description: 'Get full details of a single Splitwise expense by id.',
|
|
@@ -62,7 +66,7 @@ export function registerExpenseTools(server, client) {
|
|
|
62
66
|
},
|
|
63
67
|
}, async ({ id }) => {
|
|
64
68
|
const data = await client.request('GET', `/get_expense/${id}`);
|
|
65
|
-
return
|
|
69
|
+
return textResult(data);
|
|
66
70
|
});
|
|
67
71
|
server.registerTool('sw_create_expense', {
|
|
68
72
|
description: 'Create a Splitwise expense. Use split_equally:true to split evenly among group members, or provide a users array for custom per-person splits (paid_share and owed_share as decimal strings like "25.00"). cost must be a decimal string.',
|
|
@@ -80,7 +84,7 @@ export function registerExpenseTools(server, client) {
|
|
|
80
84
|
}, async (args) => {
|
|
81
85
|
const body = buildExpenseBody(args);
|
|
82
86
|
const data = await client.request('POST', '/create_expense', body);
|
|
83
|
-
return
|
|
87
|
+
return textResult(data);
|
|
84
88
|
});
|
|
85
89
|
server.registerTool('sw_update_expense', {
|
|
86
90
|
description: 'Edit an existing Splitwise expense. Provide expense_id and any fields to change. For custom split updates, the full users array must be provided (the API replaces the entire split).',
|
|
@@ -99,7 +103,7 @@ export function registerExpenseTools(server, client) {
|
|
|
99
103
|
const { expense_id } = args;
|
|
100
104
|
const body = buildExpenseBody(args);
|
|
101
105
|
const data = await client.request('POST', `/update_expense/${expense_id}`, body);
|
|
102
|
-
return
|
|
106
|
+
return textResult(data);
|
|
103
107
|
});
|
|
104
108
|
server.registerTool('sw_delete_expense', {
|
|
105
109
|
description: 'Soft-delete a Splitwise expense by id. Returns {success: true} on success. Use sw_undelete_expense to restore.',
|
|
@@ -109,7 +113,7 @@ export function registerExpenseTools(server, client) {
|
|
|
109
113
|
},
|
|
110
114
|
}, async ({ id }) => {
|
|
111
115
|
const data = await client.request('POST', `/delete_expense/${id}`);
|
|
112
|
-
return
|
|
116
|
+
return textResult(data);
|
|
113
117
|
});
|
|
114
118
|
server.registerTool('sw_undelete_expense', {
|
|
115
119
|
description: 'Restore a soft-deleted Splitwise expense.',
|
|
@@ -118,6 +122,6 @@ export function registerExpenseTools(server, client) {
|
|
|
118
122
|
},
|
|
119
123
|
}, async ({ id }) => {
|
|
120
124
|
const data = await client.request('POST', `/undelete_expense/${id}`);
|
|
121
|
-
return
|
|
125
|
+
return textResult(data);
|
|
122
126
|
});
|
|
123
127
|
}
|
package/dist/tools/friends.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
import { textResult } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
export function registerFriendTools(server) {
|
|
3
5
|
server.registerTool('sw_list_friends', {
|
|
4
6
|
description: "List all Splitwise friends with their id, first_name, last_name, and email. Use this to resolve a friend's name to a user_id before adding them to a group or building a custom expense split.",
|
|
5
7
|
annotations: { readOnlyHint: true },
|
|
6
8
|
}, async () => {
|
|
7
9
|
const data = await client.request('GET', '/get_friends');
|
|
8
|
-
return
|
|
10
|
+
return textResult(data);
|
|
9
11
|
});
|
|
10
12
|
server.registerTool('sw_create_friend', {
|
|
11
13
|
description: 'Add a Splitwise friend by email.',
|
|
@@ -21,7 +23,7 @@ export function registerFriendTools(server, client) {
|
|
|
21
23
|
if (user_last_name !== undefined)
|
|
22
24
|
body.user_last_name = user_last_name;
|
|
23
25
|
const data = await client.request('POST', '/create_friend', body);
|
|
24
|
-
return
|
|
26
|
+
return textResult(data);
|
|
25
27
|
});
|
|
26
28
|
server.registerTool('sw_delete_friend', {
|
|
27
29
|
description: 'Remove a Splitwise friendship by user id.',
|
|
@@ -31,6 +33,6 @@ export function registerFriendTools(server, client) {
|
|
|
31
33
|
},
|
|
32
34
|
}, async ({ id }) => {
|
|
33
35
|
const data = await client.request('POST', `/delete_friend/${id}`);
|
|
34
|
-
return
|
|
36
|
+
return textResult(data);
|
|
35
37
|
});
|
|
36
38
|
}
|
package/dist/tools/groups.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
import { textResult } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
export function registerGroupTools(server) {
|
|
3
5
|
server.registerTool('sw_list_groups', {
|
|
4
6
|
description: 'List all Splitwise groups the current user belongs to. Returns id, name, and members for each group. Use this to resolve a group name to its id.',
|
|
5
7
|
annotations: { readOnlyHint: true },
|
|
6
8
|
}, async () => {
|
|
7
9
|
const data = await client.request('GET', '/get_groups');
|
|
8
|
-
return
|
|
10
|
+
return textResult(data);
|
|
9
11
|
});
|
|
10
12
|
server.registerTool('sw_get_group', {
|
|
11
13
|
description: 'Get details of a single Splitwise group including all members and balances.',
|
|
@@ -15,7 +17,7 @@ export function registerGroupTools(server, client) {
|
|
|
15
17
|
},
|
|
16
18
|
}, async ({ id }) => {
|
|
17
19
|
const data = await client.request('GET', `/get_group/${id}`);
|
|
18
|
-
return
|
|
20
|
+
return textResult(data);
|
|
19
21
|
});
|
|
20
22
|
server.registerTool('sw_create_group', {
|
|
21
23
|
description: 'Create a new Splitwise group.',
|
|
@@ -31,7 +33,7 @@ export function registerGroupTools(server, client) {
|
|
|
31
33
|
if (simplify_by_default !== undefined)
|
|
32
34
|
body.simplify_by_default = simplify_by_default;
|
|
33
35
|
const data = await client.request('POST', '/create_group', body);
|
|
34
|
-
return
|
|
36
|
+
return textResult(data);
|
|
35
37
|
});
|
|
36
38
|
server.registerTool('sw_add_user_to_group', {
|
|
37
39
|
description: "Add a user to a Splitwise group. Provide user_id (preferred, use sw_list_friends to resolve a name) or first_name + last_name + email to invite by email.",
|
|
@@ -54,7 +56,7 @@ export function registerGroupTools(server, client) {
|
|
|
54
56
|
body = { group_id, first_name, last_name, email };
|
|
55
57
|
}
|
|
56
58
|
const data = await client.request('POST', '/add_user_to_group', body);
|
|
57
|
-
return
|
|
59
|
+
return textResult(data);
|
|
58
60
|
});
|
|
59
61
|
server.registerTool('sw_remove_user_from_group', {
|
|
60
62
|
description: 'Remove a user from a Splitwise group.',
|
|
@@ -64,7 +66,7 @@ export function registerGroupTools(server, client) {
|
|
|
64
66
|
},
|
|
65
67
|
}, async ({ group_id, user_id }) => {
|
|
66
68
|
const data = await client.request('POST', '/remove_user_from_group', { group_id, user_id });
|
|
67
|
-
return
|
|
69
|
+
return textResult(data);
|
|
68
70
|
});
|
|
69
71
|
server.registerTool('sw_delete_group', {
|
|
70
72
|
description: 'Soft-delete a Splitwise group.',
|
|
@@ -74,7 +76,7 @@ export function registerGroupTools(server, client) {
|
|
|
74
76
|
},
|
|
75
77
|
}, async ({ id }) => {
|
|
76
78
|
const data = await client.request('POST', `/delete_group/${id}`);
|
|
77
|
-
return
|
|
79
|
+
return textResult(data);
|
|
78
80
|
});
|
|
79
81
|
server.registerTool('sw_undelete_group', {
|
|
80
82
|
description: 'Restore a soft-deleted Splitwise group.',
|
|
@@ -83,6 +85,6 @@ export function registerGroupTools(server, client) {
|
|
|
83
85
|
},
|
|
84
86
|
}, async ({ id }) => {
|
|
85
87
|
const data = await client.request('POST', `/undelete_group/${id}`);
|
|
86
|
-
return
|
|
88
|
+
return textResult(data);
|
|
87
89
|
});
|
|
88
90
|
}
|
package/dist/tools/user.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
import { textResult } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
export function registerUserTools(server) {
|
|
3
5
|
server.registerTool('sw_get_current_user', {
|
|
4
6
|
description: "Get the authenticated Splitwise user's profile (id, first_name, last_name, email). Use the returned id when building custom expense splits.",
|
|
5
7
|
annotations: { readOnlyHint: true },
|
|
6
8
|
}, async () => {
|
|
7
9
|
const data = await client.request('GET', '/get_current_user');
|
|
8
|
-
return
|
|
10
|
+
return textResult(data);
|
|
9
11
|
});
|
|
10
12
|
server.registerTool('sw_get_user', {
|
|
11
13
|
description: "Get another Splitwise user's profile by id.",
|
|
@@ -15,7 +17,7 @@ export function registerUserTools(server, client) {
|
|
|
15
17
|
},
|
|
16
18
|
}, async ({ id }) => {
|
|
17
19
|
const data = await client.request('GET', `/get_user/${id}`);
|
|
18
|
-
return
|
|
20
|
+
return textResult(data);
|
|
19
21
|
});
|
|
20
22
|
server.registerTool('sw_update_user', {
|
|
21
23
|
description: "Update the current user's profile fields. id must be the current user's id.",
|
|
@@ -43,6 +45,6 @@ export function registerUserTools(server, client) {
|
|
|
43
45
|
if (default_currency !== undefined)
|
|
44
46
|
body.default_currency = default_currency;
|
|
45
47
|
const data = await client.request('POST', `/update_user/${id}`, body);
|
|
46
|
-
return
|
|
48
|
+
return textResult(data);
|
|
47
49
|
});
|
|
48
50
|
}
|
package/dist/tools/utilities.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
import { textResult, buildQueryString } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
export function registerUtilityTools(server) {
|
|
3
5
|
server.registerTool('sw_get_notifications', {
|
|
4
6
|
description: 'Get recent Splitwise activity notifications for the current user.',
|
|
5
7
|
annotations: { readOnlyHint: true },
|
|
6
8
|
}, async () => {
|
|
7
9
|
const data = await client.request('GET', '/get_notifications');
|
|
8
|
-
return
|
|
10
|
+
return textResult(data);
|
|
9
11
|
});
|
|
10
12
|
server.registerTool('sw_get_categories', {
|
|
11
13
|
description: 'Get the hierarchical list of Splitwise expense categories. Use the returned id as category_id when creating expenses.',
|
|
12
14
|
annotations: { readOnlyHint: true },
|
|
13
15
|
}, async () => {
|
|
14
16
|
const data = await client.request('GET', '/get_categories');
|
|
15
|
-
return
|
|
17
|
+
return textResult(data);
|
|
16
18
|
});
|
|
17
19
|
server.registerTool('sw_get_currencies', {
|
|
18
20
|
description: 'Get all Splitwise-supported currency codes and units. Use the currency_code value when creating expenses in non-default currencies.',
|
|
19
21
|
annotations: { readOnlyHint: true },
|
|
20
22
|
}, async () => {
|
|
21
23
|
const data = await client.request('GET', '/get_currencies');
|
|
22
|
-
return
|
|
24
|
+
return textResult(data);
|
|
23
25
|
});
|
|
24
26
|
server.registerTool('sw_get_comments', {
|
|
25
27
|
description: 'Get all comments on a Splitwise expense.',
|
|
@@ -28,8 +30,10 @@ export function registerUtilityTools(server, client) {
|
|
|
28
30
|
expense_id: z.number().describe('Expense ID to get comments for'),
|
|
29
31
|
},
|
|
30
32
|
}, async ({ expense_id }) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
// buildQueryString percent-encodes the value — defense-in-depth against
|
|
34
|
+
// query-param injection (already constrained to a number by the schema).
|
|
35
|
+
const data = await client.request('GET', `/get_comments${buildQueryString({ expense_id })}`);
|
|
36
|
+
return textResult(data);
|
|
33
37
|
});
|
|
34
38
|
server.registerTool('sw_create_comment', {
|
|
35
39
|
description: 'Add a comment to a Splitwise expense.',
|
|
@@ -39,7 +43,7 @@ export function registerUtilityTools(server, client) {
|
|
|
39
43
|
},
|
|
40
44
|
}, async ({ expense_id, content }) => {
|
|
41
45
|
const data = await client.request('POST', '/create_comment', { expense_id, content });
|
|
42
|
-
return
|
|
46
|
+
return textResult(data);
|
|
43
47
|
});
|
|
44
48
|
server.registerTool('sw_delete_comment', {
|
|
45
49
|
description: 'Delete a comment by id.',
|
|
@@ -49,6 +53,6 @@ export function registerUtilityTools(server, client) {
|
|
|
49
53
|
},
|
|
50
54
|
}, async ({ id }) => {
|
|
51
55
|
const data = await client.request('POST', `/delete_comment/${id}`);
|
|
52
|
-
return
|
|
56
|
+
return textResult(data);
|
|
53
57
|
});
|
|
54
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "splitwise-mcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
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>",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"test:coverage": "vitest run --coverage"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@chrischall/mcp-utils": "^0.7.0",
|
|
46
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
48
|
"dotenv": "^17.4.0",
|
|
48
49
|
"zod": "^4.4.2"
|
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.0.
|
|
9
|
+
"version": "2.0.9",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "splitwise-mcp",
|
|
14
|
-
"version": "2.0.
|
|
14
|
+
"version": "2.0.9",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|