splitwise-mcp 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/splitwise-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "3.5.0",
9
+ "version": "3.6.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "splitwise-mcp",
14
- "version": "3.5.0",
14
+ "version": "3.6.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -111,9 +111,31 @@ API key auth — no login flow or token rotation. The key is attached to every r
111
111
  | `sw_get_categories` | Hierarchical list of expense categories (use `id` as `category_id`) |
112
112
  | `sw_get_currencies` | List of supported currency codes |
113
113
 
114
+ ## In context groups
115
+
116
+ On an mcp-host gateway, splitwise joins the **money** group (`_meta.mint` on every
117
+ tool but `sw_healthcheck`). Its canonical reads, each answering one line per item
118
+ with `view: "brief"` inside the untrusted-data envelope (`data: string[]`):
119
+
120
+ - **what's due** — `sw_list_friends`: one line per friend with an outstanding
121
+ balance, `name — no date — owes you USD 12.50` / `you owe EUR 3.00`, then a
122
+ `-- N settled up` note; `-- none` when nothing is owed either way.
123
+ - **what's new** — `sw_get_notifications`: recent activity, `text — <ISO date> —
124
+ <kind>` (expense added, comment added, …), at most 50 lines with a
125
+ `-- 50 of M shown` note past that; `-- none` for no activity.
126
+
127
+ Neither names a subject (one API key is one account; friends and groups are who
128
+ you split with, not whose question it is) and neither takes a date range. Nothing
129
+ here answers what's on. Every other read is a `lookup`; the writes are `change`
130
+ (`sw_create_comment` is `send`). A compound ask looks like "what do I owe on
131
+ Splitwise, and is anything due at school?" — the money group reads the balances
132
+ while the school group answers the rest. Splitwise is a direct API with a key,
133
+ not a browser bridge, so it never answers "needs your browser open".
134
+
114
135
  ## Response shape (`view`)
115
136
 
116
- Nine read tools take `view: "compact" | "full"`, and **`compact` is the
137
+ Nine read tools take `view: "compact" | "full"` (`sw_list_friends` and
138
+ `sw_get_notifications` also take `"brief"`, above), and **`compact` is the
117
139
  default** — you get the slim rung without asking. Two of them are not in the
118
140
  table above: `sw_get_user(id, view?)` and `sw_get_comments(expense_id, view?)`.
119
141
 
@@ -1,40 +0,0 @@
1
- import { createHmac } from 'crypto';
2
- import { readEnvVar } from '@chrischall/mcp-utils';
3
- export function blobConfig() {
4
- const baseUrl = readEnvVar('MCP_BLOB_BASE_URL');
5
- const signingKey = readEnvVar('MCP_BLOB_SIGNING_KEY');
6
- if (!baseUrl || !signingKey)
7
- return undefined;
8
- let parsed;
9
- try {
10
- parsed = new URL(baseUrl);
11
- }
12
- catch {
13
- throw new Error(`MCP_BLOB_BASE_URL is not a valid URL — it should look like https://<host>/b/<registrationId>`);
14
- }
15
- const path = parsed.pathname.replace(/^\/+|\/+$/g, '');
16
- const prefix = path.startsWith('b/') ? path.slice(2) : path;
17
- return { base: baseUrl.replace(/\/+$/, ''), prefix, signingKey };
18
- }
19
- const sign = (cfg, payload) => createHmac('sha256', cfg.signingKey).update(payload).digest('base64url');
20
- function url(cfg, key, exp, sig) {
21
- return `${cfg.base}/${key}?exp=${exp}&sig=${encodeURIComponent(sig)}`;
22
- }
23
- export function signedPutUrl(cfg, key, contentType, exp) {
24
- return url(cfg, key, exp, sign(cfg, `put\0${cfg.prefix}/${key}\0${contentType}\0${exp}`));
25
- }
26
- export function signedGetUrl(cfg, key, exp) {
27
- return url(cfg, key, exp, sign(cfg, `${cfg.prefix}/${key}\n${exp}`));
28
- }
29
- /**
30
- * The only keys `receipt.upload_id` may name: `up/<uuid>/<safe name>`, exactly
31
- * what `sw_get_upload_url` mints. Anything else in this registration's space
32
- * is not the caller's to read through a receipt.
33
- */
34
- export const UPLOAD_ID_PATTERN = /^up\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/[A-Za-z0-9_-][A-Za-z0-9._-]{0,99}$/;
35
- /** A filename made safe for a key segment: no separators, no dot-dot, no surprises. */
36
- export function safeKeyName(filename) {
37
- const base = filename.split(/[\\/]/).pop() ?? '';
38
- const cleaned = base.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^[._]+/, '').slice(0, 100);
39
- return cleaned || 'receipt';
40
- }