splitwise-mcp 1.3.0 → 2.0.1

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/dist/index.js CHANGED
@@ -1,54 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
4
  import { SplitwiseClient } from './client.js';
6
- import { toolDefinitions as userTools, handleTool as handleUser } from './tools/user.js';
7
- import { toolDefinitions as groupTools, handleTool as handleGroups } from './tools/groups.js';
8
- import { toolDefinitions as friendTools, handleTool as handleFriends } from './tools/friends.js';
9
- import { toolDefinitions as expenseTools, handleTool as handleExpenses } from './tools/expenses.js';
10
- import { toolDefinitions as utilityTools, handleTool as handleUtilities } from './tools/utilities.js';
5
+ import { registerUserTools } from './tools/user.js';
6
+ import { registerGroupTools } from './tools/groups.js';
7
+ import { registerFriendTools } from './tools/friends.js';
8
+ import { registerExpenseTools } from './tools/expenses.js';
9
+ import { registerUtilityTools } from './tools/utilities.js';
11
10
  const client = new SplitwiseClient();
12
- const allTools = [
13
- ...userTools,
14
- ...groupTools,
15
- ...friendTools,
16
- ...expenseTools,
17
- ...utilityTools,
18
- ];
19
- const handlers = {};
20
- for (const tool of userTools)
21
- handlers[tool.name] = (n, a) => handleUser(n, a, client);
22
- for (const tool of groupTools)
23
- handlers[tool.name] = (n, a) => handleGroups(n, a, client);
24
- for (const tool of friendTools)
25
- handlers[tool.name] = (n, a) => handleFriends(n, a, client);
26
- for (const tool of expenseTools)
27
- handlers[tool.name] = (n, a) => handleExpenses(n, a, client);
28
- for (const tool of utilityTools)
29
- handlers[tool.name] = (n, a) => handleUtilities(n, a, client);
30
- const server = new Server({ name: 'splitwise-mcp', version: '1.2.0' }, { capabilities: { tools: {} } });
31
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: allTools }));
32
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
33
- const { name, arguments: args = {} } = request.params;
34
- const handler = handlers[name];
35
- if (!handler) {
36
- return {
37
- content: [{ type: 'text', text: `Unknown tool: ${name}` }],
38
- isError: true,
39
- };
40
- }
41
- try {
42
- return await handler(name, args);
43
- }
44
- catch (err) {
45
- const message = err instanceof Error ? err.message : String(err);
46
- return {
47
- content: [{ type: 'text', text: `Error: ${message}` }],
48
- isError: true,
49
- };
50
- }
51
- });
11
+ const server = new McpServer({ name: 'splitwise-mcp', version: '2.0.2' });
12
+ registerUserTools(server, client);
13
+ registerGroupTools(server, client);
14
+ registerFriendTools(server, client);
15
+ registerExpenseTools(server, client);
16
+ registerUtilityTools(server, client);
52
17
  console.error('[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.');
53
18
  const transport = new StdioServerTransport();
54
19
  await server.connect(transport);
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  /** Flattens a users array into Splitwise's flat-param JSON format. */
2
3
  export function flattenUsers(users) {
3
4
  const flat = {};
@@ -22,164 +23,101 @@ function buildExpenseBody(args) {
22
23
  }
23
24
  return body;
24
25
  }
25
- export const toolDefinitions = [
26
- {
27
- name: 'sw_list_expenses',
26
+ const userShareSchema = z.object({
27
+ user_id: z.number(),
28
+ paid_share: z.string().describe('Amount this user paid, e.g. "25.00"'),
29
+ owed_share: z.string().describe('Amount this user owes, e.g. "12.50"'),
30
+ });
31
+ export function registerExpenseTools(server, client) {
32
+ server.registerTool('sw_list_expenses', {
28
33
  description: 'List or search Splitwise expenses. All filters are optional. Use group_id to filter by group, dated_after/dated_before for date ranges.',
29
34
  annotations: { readOnlyHint: true },
30
35
  inputSchema: {
31
- type: 'object',
32
- properties: {
33
- group_id: { type: 'integer', description: 'Only expenses in this group' },
34
- friend_id: { type: 'integer', description: 'Only expenses with this friend' },
35
- dated_after: { type: 'string', description: 'ISO 8601 date — only expenses on or after this date' },
36
- dated_before: { type: 'string', description: 'ISO 8601 date — only expenses on or before this date' },
37
- updated_after: { type: 'string', description: 'ISO 8601 datetime' },
38
- updated_before: { type: 'string', description: 'ISO 8601 datetime' },
39
- limit: { type: 'integer', description: 'Max results (API default: 20)' },
40
- offset: { type: 'integer', description: 'Pagination offset' },
41
- },
42
- required: [],
36
+ group_id: z.number().describe('Only expenses in this group').optional(),
37
+ friend_id: z.number().describe('Only expenses with this friend').optional(),
38
+ dated_after: z.string().describe('ISO 8601 date only expenses on or after this date').optional(),
39
+ dated_before: z.string().describe('ISO 8601 date only expenses on or before this date').optional(),
40
+ updated_after: z.string().describe('ISO 8601 datetime').optional(),
41
+ updated_before: z.string().describe('ISO 8601 datetime').optional(),
42
+ limit: z.number().describe('Max results (API default: 20)').optional(),
43
+ offset: z.number().describe('Pagination offset').optional(),
43
44
  },
44
- },
45
- {
46
- name: 'sw_get_expense',
45
+ }, async (args) => {
46
+ const params = new URLSearchParams();
47
+ const filters = ['group_id', 'friend_id', 'dated_after', 'dated_before', 'updated_after', 'updated_before', 'limit', 'offset'];
48
+ for (const key of filters) {
49
+ const val = args[key];
50
+ if (val !== undefined)
51
+ params.append(key, String(val));
52
+ }
53
+ const qs = params.toString();
54
+ const data = await client.request('GET', qs ? `/get_expenses?${qs}` : '/get_expenses');
55
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
56
+ });
57
+ server.registerTool('sw_get_expense', {
47
58
  description: 'Get full details of a single Splitwise expense by id.',
48
59
  annotations: { readOnlyHint: true },
49
60
  inputSchema: {
50
- type: 'object',
51
- properties: {
52
- id: { type: 'integer', description: 'Expense ID' },
53
- },
54
- required: ['id'],
61
+ id: z.number().describe('Expense ID'),
55
62
  },
56
- },
57
- {
58
- name: 'sw_create_expense',
63
+ }, async ({ id }) => {
64
+ const data = await client.request('GET', `/get_expense/${id}`);
65
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
66
+ });
67
+ server.registerTool('sw_create_expense', {
59
68
  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.',
60
69
  inputSchema: {
61
- type: 'object',
62
- properties: {
63
- group_id: { type: 'integer', description: 'Group to add expense to (use 0 for no group)' },
64
- description: { type: 'string', description: 'Short description of the expense' },
65
- cost: { type: 'string', description: 'Total cost as decimal string, e.g. "25.00"' },
66
- split_equally: { type: 'boolean', description: 'Split equally among group members (mutually exclusive with users)' },
67
- users: {
68
- type: 'array',
69
- description: 'Custom split (mutually exclusive with split_equally). Full list of participants required.',
70
- items: {
71
- type: 'object',
72
- properties: {
73
- user_id: { type: 'integer' },
74
- paid_share: { type: 'string', description: 'Amount this user paid, e.g. "25.00"' },
75
- owed_share: { type: 'string', description: 'Amount this user owes, e.g. "12.50"' },
76
- },
77
- required: ['user_id', 'paid_share', 'owed_share'],
78
- },
79
- },
80
- currency_code: { type: 'string', description: 'Currency code, e.g. "USD". Defaults to group/user default.' },
81
- date: { type: 'string', description: 'ISO 8601 datetime' },
82
- category_id: { type: 'integer', description: 'Category id from sw_get_categories' },
83
- details: { type: 'string', description: 'Notes' },
84
- },
85
- required: ['group_id', 'description', 'cost'],
70
+ group_id: z.number().describe('Group to add expense to (use 0 for no group)'),
71
+ description: z.string().describe('Short description of the expense'),
72
+ cost: z.string().describe('Total cost as decimal string, e.g. "25.00"'),
73
+ split_equally: z.boolean().describe('Split equally among group members (mutually exclusive with users)').optional(),
74
+ users: z.array(userShareSchema).describe('Custom split (mutually exclusive with split_equally). Full list of participants required.').optional(),
75
+ currency_code: z.string().describe('Currency code, e.g. "USD". Defaults to group/user default.').optional(),
76
+ date: z.string().describe('ISO 8601 datetime').optional(),
77
+ category_id: z.number().describe('Category id from sw_get_categories').optional(),
78
+ details: z.string().describe('Notes').optional(),
86
79
  },
87
- },
88
- {
89
- name: 'sw_update_expense',
80
+ }, async (args) => {
81
+ const body = buildExpenseBody(args);
82
+ const data = await client.request('POST', '/create_expense', body);
83
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
84
+ });
85
+ server.registerTool('sw_update_expense', {
90
86
  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).',
91
87
  inputSchema: {
92
- type: 'object',
93
- properties: {
94
- expense_id: { type: 'integer', description: 'ID of the expense to update' },
95
- description: { type: 'string' },
96
- cost: { type: 'string', description: 'Decimal string, e.g. "25.00"' },
97
- split_equally: { type: 'boolean', description: 'Mutually exclusive with users' },
98
- users: {
99
- type: 'array',
100
- description: 'Full replacement split — all users must be included. Mutually exclusive with split_equally.',
101
- items: {
102
- type: 'object',
103
- properties: {
104
- user_id: { type: 'integer' },
105
- paid_share: { type: 'string' },
106
- owed_share: { type: 'string' },
107
- },
108
- required: ['user_id', 'paid_share', 'owed_share'],
109
- },
110
- },
111
- currency_code: { type: 'string' },
112
- date: { type: 'string' },
113
- category_id: { type: 'integer' },
114
- details: { type: 'string' },
115
- },
116
- required: ['expense_id'],
88
+ expense_id: z.number().describe('ID of the expense to update'),
89
+ description: z.string().optional(),
90
+ cost: z.string().describe('Decimal string, e.g. "25.00"').optional(),
91
+ split_equally: z.boolean().describe('Mutually exclusive with users').optional(),
92
+ users: z.array(userShareSchema).describe('Full replacement split all users must be included. Mutually exclusive with split_equally.').optional(),
93
+ currency_code: z.string().optional(),
94
+ date: z.string().optional(),
95
+ category_id: z.number().optional(),
96
+ details: z.string().optional(),
117
97
  },
118
- },
119
- {
120
- name: 'sw_delete_expense',
98
+ }, async (args) => {
99
+ const { expense_id } = args;
100
+ const body = buildExpenseBody(args);
101
+ const data = await client.request('POST', `/update_expense/${expense_id}`, body);
102
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
103
+ });
104
+ server.registerTool('sw_delete_expense', {
121
105
  description: 'Soft-delete a Splitwise expense by id. Returns {success: true} on success. Use sw_undelete_expense to restore.',
122
106
  annotations: { destructiveHint: true },
123
107
  inputSchema: {
124
- type: 'object',
125
- properties: {
126
- id: { type: 'integer', description: 'Expense ID to delete' },
127
- },
128
- required: ['id'],
108
+ id: z.number().describe('Expense ID to delete'),
129
109
  },
130
- },
131
- {
132
- name: 'sw_undelete_expense',
110
+ }, async ({ id }) => {
111
+ const data = await client.request('POST', `/delete_expense/${id}`);
112
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
113
+ });
114
+ server.registerTool('sw_undelete_expense', {
133
115
  description: 'Restore a soft-deleted Splitwise expense.',
134
116
  inputSchema: {
135
- type: 'object',
136
- properties: {
137
- id: { type: 'integer', description: 'Expense ID to restore' },
138
- },
139
- required: ['id'],
117
+ id: z.number().describe('Expense ID to restore'),
140
118
  },
141
- },
142
- ];
143
- export async function handleTool(name, args, client) {
144
- switch (name) {
145
- case 'sw_list_expenses': {
146
- const params = new URLSearchParams();
147
- const filters = ['group_id', 'friend_id', 'dated_after', 'dated_before', 'updated_after', 'updated_before', 'limit', 'offset'];
148
- for (const key of filters) {
149
- if (args[key] !== undefined)
150
- params.append(key, String(args[key]));
151
- }
152
- const qs = params.toString();
153
- const data = await client.request('GET', qs ? `/get_expenses?${qs}` : '/get_expenses');
154
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
155
- }
156
- case 'sw_get_expense': {
157
- const { id } = args;
158
- const data = await client.request('GET', `/get_expense/${id}`);
159
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
160
- }
161
- case 'sw_create_expense': {
162
- const body = buildExpenseBody(args);
163
- const data = await client.request('POST', '/create_expense', body);
164
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
165
- }
166
- case 'sw_update_expense': {
167
- const { expense_id } = args;
168
- const body = buildExpenseBody(args);
169
- const data = await client.request('POST', `/update_expense/${expense_id}`, body);
170
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
171
- }
172
- case 'sw_delete_expense': {
173
- const { id } = args;
174
- const data = await client.request('POST', `/delete_expense/${id}`);
175
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
176
- }
177
- case 'sw_undelete_expense': {
178
- const { id } = args;
179
- const data = await client.request('POST', `/undelete_expense/${id}`);
180
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
181
- }
182
- default:
183
- throw new Error(`Unknown tool: ${name}`);
184
- }
119
+ }, async ({ id }) => {
120
+ const data = await client.request('POST', `/undelete_expense/${id}`);
121
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
122
+ });
185
123
  }
@@ -1,58 +1,36 @@
1
- export const toolDefinitions = [
2
- {
3
- name: 'sw_list_friends',
4
- 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.',
1
+ import { z } from 'zod';
2
+ export function registerFriendTools(server, client) {
3
+ server.registerTool('sw_list_friends', {
4
+ 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
5
  annotations: { readOnlyHint: true },
6
- inputSchema: { type: 'object', properties: {}, required: [] },
7
- },
8
- {
9
- name: 'sw_create_friend',
6
+ }, async () => {
7
+ const data = await client.request('GET', '/get_friends');
8
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
9
+ });
10
+ server.registerTool('sw_create_friend', {
10
11
  description: 'Add a Splitwise friend by email.',
11
12
  inputSchema: {
12
- type: 'object',
13
- properties: {
14
- user_email: { type: 'string', description: 'Email of the user to add as a friend' },
15
- user_first_name: { type: 'string', description: 'First name of the user' },
16
- user_last_name: { type: 'string', description: 'Last name of the user' },
17
- },
18
- required: ['user_email'],
13
+ user_email: z.string().describe('Email of the user to add as a friend'),
14
+ user_first_name: z.string().describe('First name of the user').optional(),
15
+ user_last_name: z.string().describe('Last name of the user').optional(),
19
16
  },
20
- },
21
- {
22
- name: 'sw_delete_friend',
17
+ }, async ({ user_email, user_first_name, user_last_name }) => {
18
+ const body = { user_email };
19
+ if (user_first_name !== undefined)
20
+ body.user_first_name = user_first_name;
21
+ if (user_last_name !== undefined)
22
+ body.user_last_name = user_last_name;
23
+ const data = await client.request('POST', '/create_friend', body);
24
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
25
+ });
26
+ server.registerTool('sw_delete_friend', {
23
27
  description: 'Remove a Splitwise friendship by user id.',
24
28
  annotations: { destructiveHint: true },
25
29
  inputSchema: {
26
- type: 'object',
27
- properties: {
28
- id: { type: 'integer', description: 'User ID of the friend to remove' },
29
- },
30
- required: ['id'],
30
+ id: z.number().describe('User ID of the friend to remove'),
31
31
  },
32
- },
33
- ];
34
- export async function handleTool(name, args, client) {
35
- switch (name) {
36
- case 'sw_list_friends': {
37
- const data = await client.request('GET', '/get_friends');
38
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
39
- }
40
- case 'sw_create_friend': {
41
- const { user_email, user_first_name, user_last_name } = args;
42
- const body = { user_email };
43
- if (user_first_name !== undefined)
44
- body.user_first_name = user_first_name;
45
- if (user_last_name !== undefined)
46
- body.user_last_name = user_last_name;
47
- const data = await client.request('POST', '/create_friend', body);
48
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
49
- }
50
- case 'sw_delete_friend': {
51
- const { id } = args;
52
- const data = await client.request('POST', `/delete_friend/${id}`);
53
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
54
- }
55
- default:
56
- throw new Error(`Unknown tool: ${name}`);
57
- }
32
+ }, async ({ id }) => {
33
+ const data = await client.request('POST', `/delete_friend/${id}`);
34
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
35
+ });
58
36
  }
@@ -1,142 +1,88 @@
1
- export const toolDefinitions = [
2
- {
3
- name: 'sw_list_groups',
1
+ import { z } from 'zod';
2
+ export function registerGroupTools(server, client) {
3
+ server.registerTool('sw_list_groups', {
4
4
  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
5
  annotations: { readOnlyHint: true },
6
- inputSchema: { type: 'object', properties: {}, required: [] },
7
- },
8
- {
9
- name: 'sw_get_group',
6
+ }, async () => {
7
+ const data = await client.request('GET', '/get_groups');
8
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
9
+ });
10
+ server.registerTool('sw_get_group', {
10
11
  description: 'Get details of a single Splitwise group including all members and balances.',
11
12
  annotations: { readOnlyHint: true },
12
13
  inputSchema: {
13
- type: 'object',
14
- properties: {
15
- id: { type: 'integer', description: 'Group ID' },
16
- },
17
- required: ['id'],
14
+ id: z.number().describe('Group ID'),
18
15
  },
19
- },
20
- {
21
- name: 'sw_create_group',
16
+ }, async ({ id }) => {
17
+ const data = await client.request('GET', `/get_group/${id}`);
18
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
19
+ });
20
+ server.registerTool('sw_create_group', {
22
21
  description: 'Create a new Splitwise group.',
23
22
  inputSchema: {
24
- type: 'object',
25
- properties: {
26
- name: { type: 'string', description: 'Group name' },
27
- group_type: {
28
- type: 'string',
29
- enum: ['apartment', 'house', 'trip', 'other'],
30
- description: 'Type of group',
31
- },
32
- simplify_by_default: { type: 'boolean', description: 'Whether to simplify debts by default' },
33
- },
34
- required: ['name'],
23
+ name: z.string().describe('Group name'),
24
+ group_type: z.enum(['apartment', 'house', 'trip', 'other']).describe('Type of group').optional(),
25
+ simplify_by_default: z.boolean().describe('Whether to simplify debts by default').optional(),
35
26
  },
36
- },
37
- {
38
- name: 'sw_add_user_to_group',
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.',
27
+ }, async ({ name, group_type, simplify_by_default }) => {
28
+ const body = { name };
29
+ if (group_type !== undefined)
30
+ body.group_type = group_type;
31
+ if (simplify_by_default !== undefined)
32
+ body.simplify_by_default = simplify_by_default;
33
+ const data = await client.request('POST', '/create_group', body);
34
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
35
+ });
36
+ server.registerTool('sw_add_user_to_group', {
37
+ 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.",
40
38
  inputSchema: {
41
- type: 'object',
42
- properties: {
43
- group_id: { type: 'integer', description: 'Group ID' },
44
- user_id: { type: 'integer', description: 'User ID (preferred)' },
45
- first_name: { type: 'string' },
46
- last_name: { type: 'string' },
47
- email: { type: 'string' },
48
- },
49
- required: ['group_id'],
39
+ group_id: z.number().describe('Group ID'),
40
+ user_id: z.number().describe('User ID (preferred)').optional(),
41
+ first_name: z.string().optional(),
42
+ last_name: z.string().optional(),
43
+ email: z.string().optional(),
50
44
  },
51
- },
52
- {
53
- name: 'sw_remove_user_from_group',
45
+ }, async ({ group_id, user_id, first_name, last_name, email }) => {
46
+ let body;
47
+ if (user_id !== undefined) {
48
+ body = { group_id, user_id };
49
+ }
50
+ else {
51
+ if (!first_name || !last_name || !email) {
52
+ throw new Error('first_name, last_name, and email are required when user_id is not provided');
53
+ }
54
+ body = { group_id, first_name, last_name, email };
55
+ }
56
+ const data = await client.request('POST', '/add_user_to_group', body);
57
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
58
+ });
59
+ server.registerTool('sw_remove_user_from_group', {
54
60
  description: 'Remove a user from a Splitwise group.',
55
61
  inputSchema: {
56
- type: 'object',
57
- properties: {
58
- group_id: { type: 'integer', description: 'Group ID' },
59
- user_id: { type: 'integer', description: 'User ID to remove' },
60
- },
61
- required: ['group_id', 'user_id'],
62
+ group_id: z.number().describe('Group ID'),
63
+ user_id: z.number().describe('User ID to remove'),
62
64
  },
63
- },
64
- {
65
- name: 'sw_delete_group',
65
+ }, async ({ group_id, user_id }) => {
66
+ const data = await client.request('POST', '/remove_user_from_group', { group_id, user_id });
67
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
68
+ });
69
+ server.registerTool('sw_delete_group', {
66
70
  description: 'Soft-delete a Splitwise group.',
67
71
  annotations: { destructiveHint: true },
68
72
  inputSchema: {
69
- type: 'object',
70
- properties: {
71
- id: { type: 'integer', description: 'Group ID to delete' },
72
- },
73
- required: ['id'],
73
+ id: z.number().describe('Group ID to delete'),
74
74
  },
75
- },
76
- {
77
- name: 'sw_undelete_group',
75
+ }, async ({ id }) => {
76
+ const data = await client.request('POST', `/delete_group/${id}`);
77
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
78
+ });
79
+ server.registerTool('sw_undelete_group', {
78
80
  description: 'Restore a soft-deleted Splitwise group.',
79
81
  inputSchema: {
80
- type: 'object',
81
- properties: {
82
- id: { type: 'integer', description: 'Group ID to restore' },
83
- },
84
- required: ['id'],
82
+ id: z.number().describe('Group ID to restore'),
85
83
  },
86
- },
87
- ];
88
- export async function handleTool(name, args, client) {
89
- switch (name) {
90
- case 'sw_list_groups': {
91
- const data = await client.request('GET', '/get_groups');
92
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
93
- }
94
- case 'sw_get_group': {
95
- const { id } = args;
96
- const data = await client.request('GET', `/get_group/${id}`);
97
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
98
- }
99
- case 'sw_create_group': {
100
- const { name, group_type, simplify_by_default } = args;
101
- const body = { name };
102
- if (group_type !== undefined)
103
- body.group_type = group_type;
104
- if (simplify_by_default !== undefined)
105
- body.simplify_by_default = simplify_by_default;
106
- const data = await client.request('POST', '/create_group', body);
107
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
108
- }
109
- case 'sw_add_user_to_group': {
110
- const { group_id, user_id, first_name, last_name, email } = args;
111
- let body;
112
- if (user_id !== undefined) {
113
- body = { group_id, user_id };
114
- }
115
- else {
116
- if (!first_name || !last_name || !email) {
117
- throw new Error('first_name, last_name, and email are required when user_id is not provided');
118
- }
119
- body = { group_id, first_name, last_name, email };
120
- }
121
- const data = await client.request('POST', '/add_user_to_group', body);
122
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
123
- }
124
- case 'sw_remove_user_from_group': {
125
- const { group_id, user_id } = args;
126
- const data = await client.request('POST', '/remove_user_from_group', { group_id, user_id });
127
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
128
- }
129
- case 'sw_delete_group': {
130
- const { id } = args;
131
- const data = await client.request('POST', `/delete_group/${id}`);
132
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
133
- }
134
- case 'sw_undelete_group': {
135
- const { id } = args;
136
- const data = await client.request('POST', `/undelete_group/${id}`);
137
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
138
- }
139
- default:
140
- throw new Error(`Unknown tool: ${name}`);
141
- }
84
+ }, async ({ id }) => {
85
+ const data = await client.request('POST', `/undelete_group/${id}`);
86
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
87
+ });
142
88
  }