splitwise-mcp 2.0.11 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "MCP server for Splitwise — natural-language expense and group management via the Splitwise API",
10
- "version": "2.0.11"
10
+ "version": "2.1.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": "2.0.11",
18
+ "version": "2.1.0",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "splitwise-mcp",
3
3
  "displayName": "Splitwise",
4
- "version": "2.0.11",
4
+ "version": "2.1.0",
5
5
  "description": "MCP server for Splitwise — natural-language expense and group management via the Splitwise API",
6
6
  "author": {
7
7
  "name": "Chris Hall",
@@ -16,6 +16,6 @@
16
16
  "mcp",
17
17
  "finance"
18
18
  ],
19
- "skills": "./SKILL.md",
19
+ "skills": "./skills/",
20
20
  "mcp": "./.mcp.json"
21
21
  }
package/dist/bundle.js CHANGED
@@ -31282,8 +31282,11 @@ var pageSchema = {
31282
31282
  // src/client.ts
31283
31283
  import { dirname, join } from "path";
31284
31284
  import { fileURLToPath } from "url";
31285
- var __dirname = dirname(fileURLToPath(import.meta.url));
31286
- await loadDotenvSafely({ path: join(__dirname, "..", ".env"), override: false });
31285
+ try {
31286
+ const dir = dirname(fileURLToPath(import.meta.url));
31287
+ await loadDotenvSafely({ path: join(dir, "..", ".env"), override: false });
31288
+ } catch {
31289
+ }
31287
31290
  var BASE_URL = "https://secure.splitwise.com/api/v3.0";
31288
31291
  var SERVICE_NAME = "Splitwise";
31289
31292
  var SplitwiseClient = class {
@@ -31294,9 +31297,14 @@ var SplitwiseClient = class {
31294
31297
  * Defer the config error so the server can still start (and respond to the
31295
31298
  * host's install-time smoke test) when SPLITWISE_API_KEY isn't set yet.
31296
31299
  * Tool calls re-raise the error at request time.
31300
+ *
31301
+ * Optional constructor seam: a hosted per-user deployment (e.g. a Cloudflare
31302
+ * Worker "remote connector") builds one client per request with that user's
31303
+ * `apiKey` injected. The stdio path passes no options, so the key resolves
31304
+ * from the environment exactly as before — byte-for-byte identical behaviour.
31297
31305
  */
31298
- constructor() {
31299
- const key = readEnvVar("SPLITWISE_API_KEY");
31306
+ constructor(opts) {
31307
+ const key = opts?.apiKey ?? readEnvVar("SPLITWISE_API_KEY");
31300
31308
  if (!key) {
31301
31309
  this.apiKey = null;
31302
31310
  this.configError = new Error("SPLITWISE_API_KEY environment variable is required");
@@ -31338,12 +31346,12 @@ function previewUnlessConfirmed(confirm, action, method, path, body) {
31338
31346
  }
31339
31347
 
31340
31348
  // src/tools/user.ts
31341
- function registerUserTools(server) {
31349
+ function registerUserTools(server, client2) {
31342
31350
  server.registerTool("sw_get_current_user", {
31343
31351
  description: "Get the authenticated Splitwise user's profile (id, first_name, last_name, email). Use the returned id when building custom expense splits.",
31344
31352
  annotations: { readOnlyHint: true }
31345
31353
  }, async () => {
31346
- const data = await client.request("GET", "/get_current_user");
31354
+ const data = await client2.request("GET", "/get_current_user");
31347
31355
  return textResult(data);
31348
31356
  });
31349
31357
  server.registerTool("sw_get_user", {
@@ -31353,7 +31361,7 @@ function registerUserTools(server) {
31353
31361
  id: external_exports.number().describe("User ID")
31354
31362
  }
31355
31363
  }, async ({ id }) => {
31356
- const data = await client.request("GET", `/get_user/${id}`);
31364
+ const data = await client2.request("GET", `/get_user/${id}`);
31357
31365
  return textResult(data);
31358
31366
  });
31359
31367
  server.registerTool("sw_update_user", {
@@ -31379,18 +31387,18 @@ function registerUserTools(server) {
31379
31387
  const previewBody = { ...body, ...password !== void 0 ? { password: "[hidden]" } : {} };
31380
31388
  const gate = previewUnlessConfirmed(confirm, `Update current Splitwise user ${id} profile`, "POST", `/update_user/${id}`, previewBody);
31381
31389
  if (gate) return gate;
31382
- const data = await client.request("POST", `/update_user/${id}`, body);
31390
+ const data = await client2.request("POST", `/update_user/${id}`, body);
31383
31391
  return textResult(data);
31384
31392
  });
31385
31393
  }
31386
31394
 
31387
31395
  // src/tools/groups.ts
31388
- function registerGroupTools(server) {
31396
+ function registerGroupTools(server, client2) {
31389
31397
  server.registerTool("sw_list_groups", {
31390
31398
  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.",
31391
31399
  annotations: { readOnlyHint: true }
31392
31400
  }, async () => {
31393
- const data = await client.request("GET", "/get_groups");
31401
+ const data = await client2.request("GET", "/get_groups");
31394
31402
  return textResult(data);
31395
31403
  });
31396
31404
  server.registerTool("sw_get_group", {
@@ -31400,7 +31408,7 @@ function registerGroupTools(server) {
31400
31408
  id: external_exports.number().describe("Group ID")
31401
31409
  }
31402
31410
  }, async ({ id }) => {
31403
- const data = await client.request("GET", `/get_group/${id}`);
31411
+ const data = await client2.request("GET", `/get_group/${id}`);
31404
31412
  return textResult(data);
31405
31413
  });
31406
31414
  server.registerTool("sw_create_group", {
@@ -31418,7 +31426,7 @@ function registerGroupTools(server) {
31418
31426
  if (simplify_by_default !== void 0) body.simplify_by_default = simplify_by_default;
31419
31427
  const gate = previewUnlessConfirmed(confirm, `Create Splitwise group "${name}"`, "POST", "/create_group", body);
31420
31428
  if (gate) return gate;
31421
- const data = await client.request("POST", "/create_group", body);
31429
+ const data = await client2.request("POST", "/create_group", body);
31422
31430
  return textResult(data);
31423
31431
  });
31424
31432
  server.registerTool("sw_add_user_to_group", {
@@ -31443,7 +31451,7 @@ function registerGroupTools(server) {
31443
31451
  }
31444
31452
  const gate = previewUnlessConfirmed(confirm, `Add a user to Splitwise group ${group_id} (may send an invite email)`, "POST", "/add_user_to_group", body);
31445
31453
  if (gate) return gate;
31446
- const data = await client.request("POST", "/add_user_to_group", body);
31454
+ const data = await client2.request("POST", "/add_user_to_group", body);
31447
31455
  return textResult(data);
31448
31456
  });
31449
31457
  server.registerTool("sw_remove_user_from_group", {
@@ -31457,7 +31465,7 @@ function registerGroupTools(server) {
31457
31465
  }, async ({ group_id, user_id, confirm }) => {
31458
31466
  const gate = previewUnlessConfirmed(confirm, `Remove user ${user_id} from Splitwise group ${group_id}`, "POST", "/remove_user_from_group", { group_id, user_id });
31459
31467
  if (gate) return gate;
31460
- const data = await client.request("POST", "/remove_user_from_group", { group_id, user_id });
31468
+ const data = await client2.request("POST", "/remove_user_from_group", { group_id, user_id });
31461
31469
  return textResult(data);
31462
31470
  });
31463
31471
  server.registerTool("sw_delete_group", {
@@ -31470,7 +31478,7 @@ function registerGroupTools(server) {
31470
31478
  }, async ({ id, confirm }) => {
31471
31479
  const gate = previewUnlessConfirmed(confirm, `Soft-delete Splitwise group ${id}`, "POST", `/delete_group/${id}`);
31472
31480
  if (gate) return gate;
31473
- const data = await client.request("POST", `/delete_group/${id}`);
31481
+ const data = await client2.request("POST", `/delete_group/${id}`);
31474
31482
  return textResult(data);
31475
31483
  });
31476
31484
  server.registerTool("sw_undelete_group", {
@@ -31479,18 +31487,18 @@ function registerGroupTools(server) {
31479
31487
  id: external_exports.number().describe("Group ID to restore")
31480
31488
  }
31481
31489
  }, async ({ id }) => {
31482
- const data = await client.request("POST", `/undelete_group/${id}`);
31490
+ const data = await client2.request("POST", `/undelete_group/${id}`);
31483
31491
  return textResult(data);
31484
31492
  });
31485
31493
  }
31486
31494
 
31487
31495
  // src/tools/friends.ts
31488
- function registerFriendTools(server) {
31496
+ function registerFriendTools(server, client2) {
31489
31497
  server.registerTool("sw_list_friends", {
31490
31498
  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.",
31491
31499
  annotations: { readOnlyHint: true }
31492
31500
  }, async () => {
31493
- const data = await client.request("GET", "/get_friends");
31501
+ const data = await client2.request("GET", "/get_friends");
31494
31502
  return textResult(data);
31495
31503
  });
31496
31504
  server.registerTool("sw_create_friend", {
@@ -31508,7 +31516,7 @@ function registerFriendTools(server) {
31508
31516
  if (user_last_name !== void 0) body.user_last_name = user_last_name;
31509
31517
  const gate = previewUnlessConfirmed(confirm, `Add ${user_email} as a Splitwise friend`, "POST", "/create_friend", body);
31510
31518
  if (gate) return gate;
31511
- const data = await client.request("POST", "/create_friend", body);
31519
+ const data = await client2.request("POST", "/create_friend", body);
31512
31520
  return textResult(data);
31513
31521
  });
31514
31522
  server.registerTool("sw_delete_friend", {
@@ -31521,7 +31529,7 @@ function registerFriendTools(server) {
31521
31529
  }, async ({ id, confirm }) => {
31522
31530
  const gate = previewUnlessConfirmed(confirm, `Remove Splitwise friendship with user ${id}`, "POST", `/delete_friend/${id}`);
31523
31531
  if (gate) return gate;
31524
- const data = await client.request("POST", `/delete_friend/${id}`);
31532
+ const data = await client2.request("POST", `/delete_friend/${id}`);
31525
31533
  return textResult(data);
31526
31534
  });
31527
31535
  }
@@ -31554,7 +31562,7 @@ var userShareSchema = external_exports.object({
31554
31562
  paid_share: external_exports.string().describe('Amount this user paid, e.g. "25.00"'),
31555
31563
  owed_share: external_exports.string().describe('Amount this user owes, e.g. "12.50"')
31556
31564
  });
31557
- function registerExpenseTools(server) {
31565
+ function registerExpenseTools(server, client2) {
31558
31566
  server.registerTool("sw_list_expenses", {
31559
31567
  description: "List or search Splitwise expenses. All filters are optional. Use group_id to filter by group, dated_after/dated_before for date ranges.",
31560
31568
  annotations: { readOnlyHint: true },
@@ -31579,7 +31587,7 @@ function registerExpenseTools(server) {
31579
31587
  limit: args.limit,
31580
31588
  offset: args.offset
31581
31589
  });
31582
- const data = await client.request("GET", `/get_expenses${qs}`);
31590
+ const data = await client2.request("GET", `/get_expenses${qs}`);
31583
31591
  return textResult(data);
31584
31592
  });
31585
31593
  server.registerTool("sw_get_expense", {
@@ -31589,7 +31597,7 @@ function registerExpenseTools(server) {
31589
31597
  id: external_exports.number().describe("Expense ID")
31590
31598
  }
31591
31599
  }, async ({ id }) => {
31592
- const data = await client.request("GET", `/get_expense/${id}`);
31600
+ const data = await client2.request("GET", `/get_expense/${id}`);
31593
31601
  return textResult(data);
31594
31602
  });
31595
31603
  server.registerTool("sw_create_expense", {
@@ -31610,7 +31618,7 @@ function registerExpenseTools(server) {
31610
31618
  const body = buildExpenseBody(args);
31611
31619
  const gate = previewUnlessConfirmed(args.confirm, `Create a Splitwise expense "${args.description}" (${args.cost}) \u2014 notifies group members`, "POST", "/create_expense", body);
31612
31620
  if (gate) return gate;
31613
- const data = await client.request("POST", "/create_expense", body);
31621
+ const data = await client2.request("POST", "/create_expense", body);
31614
31622
  return textResult(data);
31615
31623
  });
31616
31624
  server.registerTool("sw_update_expense", {
@@ -31632,7 +31640,7 @@ function registerExpenseTools(server) {
31632
31640
  const body = buildExpenseBody(args);
31633
31641
  const gate = previewUnlessConfirmed(args.confirm, `Update Splitwise expense ${expense_id} \u2014 notifies group members`, "POST", `/update_expense/${expense_id}`, body);
31634
31642
  if (gate) return gate;
31635
- const data = await client.request("POST", `/update_expense/${expense_id}`, body);
31643
+ const data = await client2.request("POST", `/update_expense/${expense_id}`, body);
31636
31644
  return textResult(data);
31637
31645
  });
31638
31646
  server.registerTool("sw_delete_expense", {
@@ -31645,7 +31653,7 @@ function registerExpenseTools(server) {
31645
31653
  }, async ({ id, confirm }) => {
31646
31654
  const gate = previewUnlessConfirmed(confirm, `Soft-delete Splitwise expense ${id}`, "POST", `/delete_expense/${id}`);
31647
31655
  if (gate) return gate;
31648
- const data = await client.request("POST", `/delete_expense/${id}`);
31656
+ const data = await client2.request("POST", `/delete_expense/${id}`);
31649
31657
  return textResult(data);
31650
31658
  });
31651
31659
  server.registerTool("sw_undelete_expense", {
@@ -31654,32 +31662,32 @@ function registerExpenseTools(server) {
31654
31662
  id: external_exports.number().describe("Expense ID to restore")
31655
31663
  }
31656
31664
  }, async ({ id }) => {
31657
- const data = await client.request("POST", `/undelete_expense/${id}`);
31665
+ const data = await client2.request("POST", `/undelete_expense/${id}`);
31658
31666
  return textResult(data);
31659
31667
  });
31660
31668
  }
31661
31669
 
31662
31670
  // src/tools/utilities.ts
31663
- function registerUtilityTools(server) {
31671
+ function registerUtilityTools(server, client2) {
31664
31672
  server.registerTool("sw_get_notifications", {
31665
31673
  description: "Get recent Splitwise activity notifications for the current user.",
31666
31674
  annotations: { readOnlyHint: true }
31667
31675
  }, async () => {
31668
- const data = await client.request("GET", "/get_notifications");
31676
+ const data = await client2.request("GET", "/get_notifications");
31669
31677
  return textResult(data);
31670
31678
  });
31671
31679
  server.registerTool("sw_get_categories", {
31672
31680
  description: "Get the hierarchical list of Splitwise expense categories. Use the returned id as category_id when creating expenses.",
31673
31681
  annotations: { readOnlyHint: true }
31674
31682
  }, async () => {
31675
- const data = await client.request("GET", "/get_categories");
31683
+ const data = await client2.request("GET", "/get_categories");
31676
31684
  return textResult(data);
31677
31685
  });
31678
31686
  server.registerTool("sw_get_currencies", {
31679
31687
  description: "Get all Splitwise-supported currency codes and units. Use the currency_code value when creating expenses in non-default currencies.",
31680
31688
  annotations: { readOnlyHint: true }
31681
31689
  }, async () => {
31682
- const data = await client.request("GET", "/get_currencies");
31690
+ const data = await client2.request("GET", "/get_currencies");
31683
31691
  return textResult(data);
31684
31692
  });
31685
31693
  server.registerTool("sw_get_comments", {
@@ -31689,7 +31697,7 @@ function registerUtilityTools(server) {
31689
31697
  expense_id: external_exports.number().describe("Expense ID to get comments for")
31690
31698
  }
31691
31699
  }, async ({ expense_id }) => {
31692
- const data = await client.request("GET", `/get_comments${buildQueryString({ expense_id })}`);
31700
+ const data = await client2.request("GET", `/get_comments${buildQueryString({ expense_id })}`);
31693
31701
  return textResult(data);
31694
31702
  });
31695
31703
  server.registerTool("sw_create_comment", {
@@ -31703,7 +31711,7 @@ function registerUtilityTools(server) {
31703
31711
  }, async ({ expense_id, content, confirm }) => {
31704
31712
  const gate = previewUnlessConfirmed(confirm, `Comment on Splitwise expense ${expense_id} (visible to participants)`, "POST", "/create_comment", { expense_id, content });
31705
31713
  if (gate) return gate;
31706
- const data = await client.request("POST", "/create_comment", { expense_id, content });
31714
+ const data = await client2.request("POST", "/create_comment", { expense_id, content });
31707
31715
  return textResult(data);
31708
31716
  });
31709
31717
  server.registerTool("sw_delete_comment", {
@@ -31716,7 +31724,7 @@ function registerUtilityTools(server) {
31716
31724
  }, async ({ id, confirm }) => {
31717
31725
  const gate = previewUnlessConfirmed(confirm, `Delete Splitwise comment ${id}`, "POST", `/delete_comment/${id}`);
31718
31726
  if (gate) return gate;
31719
- const data = await client.request("POST", `/delete_comment/${id}`);
31727
+ const data = await client2.request("POST", `/delete_comment/${id}`);
31720
31728
  return textResult(data);
31721
31729
  });
31722
31730
  }
@@ -31724,8 +31732,9 @@ function registerUtilityTools(server) {
31724
31732
  // src/index.ts
31725
31733
  await runMcp({
31726
31734
  name: "splitwise-mcp",
31727
- version: "2.0.11",
31735
+ version: "2.1.0",
31728
31736
  // x-release-please-version
31737
+ deps: client,
31729
31738
  banner: "[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.",
31730
31739
  tools: [
31731
31740
  registerUserTools,
package/dist/client.js CHANGED
@@ -4,8 +4,16 @@ 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
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- await loadDotenvSafely({ path: join(__dirname, '..', '.env'), override: false });
7
+ // The try/catch guards the Cloudflare Worker runtime, where `import.meta.url`
8
+ // is undefined and `fileURLToPath(undefined)` would throw at module init
9
+ // (Worker startup validation) — there is no filesystem / .env to load there.
10
+ try {
11
+ const dir = dirname(fileURLToPath(import.meta.url));
12
+ await loadDotenvSafely({ path: join(dir, '..', '.env'), override: false });
13
+ }
14
+ catch {
15
+ /* non-Node runtime (Workers): no .env to load */
16
+ }
9
17
  const BASE_URL = 'https://secure.splitwise.com/api/v3.0';
10
18
  const SERVICE_NAME = 'Splitwise';
11
19
  export class SplitwiseClient {
@@ -16,12 +24,18 @@ export class SplitwiseClient {
16
24
  * Defer the config error so the server can still start (and respond to the
17
25
  * host's install-time smoke test) when SPLITWISE_API_KEY isn't set yet.
18
26
  * Tool calls re-raise the error at request time.
27
+ *
28
+ * Optional constructor seam: a hosted per-user deployment (e.g. a Cloudflare
29
+ * Worker "remote connector") builds one client per request with that user's
30
+ * `apiKey` injected. The stdio path passes no options, so the key resolves
31
+ * from the environment exactly as before — byte-for-byte identical behaviour.
19
32
  */
20
- constructor() {
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');
33
+ constructor(opts) {
34
+ // Injected apiKey (hosted per-user path) takes precedence; otherwise fall
35
+ // back to the env var. readEnvVar trims whitespace and treats
36
+ // blank/`undefined`/`null`/`${...}` placeholder values as unset — defends
37
+ // against MCP hosts that pass .mcp.json env blocks through unexpanded.
38
+ const key = opts?.apiKey ?? readEnvVar('SPLITWISE_API_KEY');
25
39
  if (!key) {
26
40
  this.apiKey = null;
27
41
  this.configError = new Error('SPLITWISE_API_KEY environment variable is required');
package/dist/index.js CHANGED
@@ -1,18 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import { runMcp } from '@chrischall/mcp-utils';
3
+ import { client } from './client.js';
3
4
  import { registerUserTools } from './tools/user.js';
4
5
  import { registerGroupTools } from './tools/groups.js';
5
6
  import { registerFriendTools } from './tools/friends.js';
6
7
  import { registerExpenseTools } from './tools/expenses.js';
7
8
  import { registerUtilityTools } from './tools/utilities.js';
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.
9
+ // runMcp builds the McpServer, applies the registrars (threading `client`
10
+ // through as deps), prints the banner to stderr, wires graceful shutdown, and
11
+ // connects the stdio transport. The Splitwise client is a module-level
12
+ // singleton constructed in ./client.js that defers its config error to the
13
+ // first request. That preserves the deferred-config-error pattern: the server
14
+ // boots and answers the host's install-time tools/list smoke test even when
15
+ // SPLITWISE_API_KEY is absent — the configuration error only surfaces on the
16
+ // first tool call. A hosted per-user connector (a later task) injects its own
17
+ // per-request client into the same registrars instead of this singleton.
13
18
  await runMcp({
14
19
  name: 'splitwise-mcp',
15
- version: '2.0.11', // x-release-please-version
20
+ version: '2.1.0', // x-release-please-version
21
+ deps: client,
16
22
  banner: '[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.',
17
23
  tools: [
18
24
  registerUserTools,
@@ -0,0 +1,23 @@
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
+ };
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  import { textResult, buildQueryString } from '@chrischall/mcp-utils';
3
- import { client } from '../client.js';
4
3
  import { previewUnlessConfirmed, schemaConfirm } from './_confirm.js';
5
4
  /** Flattens a users array into Splitwise's flat-param JSON format. */
6
5
  export function flattenUsers(users) {
@@ -32,7 +31,7 @@ const userShareSchema = z.object({
32
31
  paid_share: z.string().describe('Amount this user paid, e.g. "25.00"'),
33
32
  owed_share: z.string().describe('Amount this user owes, e.g. "12.50"'),
34
33
  });
35
- export function registerExpenseTools(server) {
34
+ export function registerExpenseTools(server, client) {
36
35
  server.registerTool('sw_list_expenses', {
37
36
  description: 'List or search Splitwise expenses. All filters are optional. Use group_id to filter by group, dated_after/dated_before for date ranges.',
38
37
  annotations: { readOnlyHint: true },
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { textResult } from '@chrischall/mcp-utils';
3
- import { client } from '../client.js';
4
3
  import { previewUnlessConfirmed, schemaConfirm } from './_confirm.js';
5
- export function registerFriendTools(server) {
4
+ export function registerFriendTools(server, client) {
6
5
  server.registerTool('sw_list_friends', {
7
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.",
8
7
  annotations: { readOnlyHint: true },
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { textResult } from '@chrischall/mcp-utils';
3
- import { client } from '../client.js';
4
3
  import { previewUnlessConfirmed, schemaConfirm } from './_confirm.js';
5
- export function registerGroupTools(server) {
4
+ export function registerGroupTools(server, client) {
6
5
  server.registerTool('sw_list_groups', {
7
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.',
8
7
  annotations: { readOnlyHint: true },
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { textResult } from '@chrischall/mcp-utils';
3
- import { client } from '../client.js';
4
3
  import { previewUnlessConfirmed, schemaConfirm } from './_confirm.js';
5
- export function registerUserTools(server) {
4
+ export function registerUserTools(server, client) {
6
5
  server.registerTool('sw_get_current_user', {
7
6
  description: "Get the authenticated Splitwise user's profile (id, first_name, last_name, email). Use the returned id when building custom expense splits.",
8
7
  annotations: { readOnlyHint: true },
@@ -1,8 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { textResult, buildQueryString } from '@chrischall/mcp-utils';
3
- import { client } from '../client.js';
4
3
  import { previewUnlessConfirmed, schemaConfirm } from './_confirm.js';
5
- export function registerUtilityTools(server) {
4
+ export function registerUtilityTools(server, client) {
6
5
  server.registerTool('sw_get_notifications', {
7
6
  description: 'Get recent Splitwise activity notifications for the current user.',
8
7
  annotations: { readOnlyHint: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "splitwise-mcp",
3
- "version": "2.0.11",
3
+ "version": "2.1.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>",
@@ -30,7 +30,7 @@
30
30
  "files": [
31
31
  "dist",
32
32
  ".claude-plugin",
33
- "SKILL.md",
33
+ "skills/",
34
34
  ".mcp.json",
35
35
  "server.json"
36
36
  ],
@@ -40,19 +40,28 @@
40
40
  "dev": "node dist/index.js",
41
41
  "test": "vitest run",
42
42
  "test:watch": "vitest",
43
- "test:coverage": "vitest run --coverage"
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"
44
47
  },
45
48
  "dependencies": {
46
- "@chrischall/mcp-utils": "^0.12.0",
49
+ "@chrischall/mcp-utils": "^0.13.0",
47
50
  "@modelcontextprotocol/sdk": "^1.29.0",
48
51
  "dotenv": "^17.4.0",
49
52
  "zod": "^4.4.2"
50
53
  },
51
54
  "devDependencies": {
55
+ "@chrischall/mcp-connector": "^0.1.0",
56
+ "@cloudflare/vitest-pool-workers": "^0.18.4",
57
+ "@cloudflare/workers-oauth-provider": "^0.0.11",
58
+ "@cloudflare/workers-types": "^5.20260708.1",
52
59
  "@types/node": "^26.0.0",
53
60
  "@vitest/coverage-v8": "^4.1.2",
61
+ "agents": "^0.17.3",
54
62
  "esbuild": "^0.28.0",
55
- "typescript": "^6.0.2",
56
- "vitest": "^4.1.2"
63
+ "typescript": "^7.0.2",
64
+ "vitest": "^4.1.2",
65
+ "wrangler": "^4.110.0"
57
66
  }
58
67
  }
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.11",
9
+ "version": "2.1.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "splitwise-mcp",
14
- "version": "2.0.11",
14
+ "version": "2.1.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
File without changes