Skip to content

Commit 30ea543

Browse files
committed
mcp + functions and unnecessary cloudflare proxy
0 parents  commit 30ea543

18 files changed

Lines changed: 2193 additions & 0 deletions

.envrc.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export SUPABASE_PROJECT_REF=your-project-ref-here

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.env
2+
.env.local
3+
.env.*
4+
!.env.example
5+
.envrc
6+
supabase/.temp/
7+
node_modules/
8+
.DS_Store

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.PHONY: deploy deploy-proxy
2+
3+
# Deploy migrations, edge function, and proxy worker.
4+
# Requires SUPABASE_PROJECT_REF to be set (via .envrc + direnv, or exported manually).
5+
deploy:
6+
./scripts/deploy.sh
7+
8+
# Deploy only the Cloudflare Worker proxy.
9+
deploy-proxy:
10+
cd proxy && npx wrangler deploy

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# SplitCount
2+
3+
Expense splitting via MCP + Claude. Log expenses (including receipt photos), track who paid, and settle up conversationally.
4+
5+
## Architecture
6+
7+
- **MCP server**: Supabase Edge Function (Deno) at `supabase/functions/mcp/`
8+
- **Database**: Supabase PostgreSQL (groups, members, expenses, splits, settlements)
9+
- **Client**: Any MCP-compatible client (Claude Desktop, Claude.ai, etc.)
10+
11+
## Setup
12+
13+
### 1. Create a Supabase project
14+
15+
Go to [supabase.com](https://supabase.com), create a new project, and note your:
16+
- Project URL: `https://<ref>.supabase.co`
17+
- Service role key (Settings → API)
18+
- Anon key (Settings → API)
19+
20+
### 2. Run the migration
21+
22+
In the Supabase dashboard → SQL Editor, run the contents of:
23+
```
24+
supabase/migrations/20260315000000_initial_schema.sql
25+
```
26+
27+
Or with the Supabase CLI:
28+
```bash
29+
supabase link --project-ref <your-ref>
30+
supabase db push
31+
```
32+
33+
### 3. Deploy the Edge Function
34+
35+
```bash
36+
supabase functions deploy mcp --no-verify-jwt
37+
```
38+
39+
The `--no-verify-jwt` flag is required since MCP clients don't send Supabase JWTs.
40+
41+
Your MCP server URL will be:
42+
```
43+
https://<your-ref>.supabase.co/functions/v1/mcp
44+
```
45+
46+
### 4. Configure your MCP client
47+
48+
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
49+
50+
```json
51+
{
52+
"mcpServers": {
53+
"splitcount": {
54+
"command": "npx",
55+
"args": ["-y", "mcp-remote", "https://<your-ref>.supabase.co/functions/v1/mcp"]
56+
}
57+
}
58+
}
59+
```
60+
61+
> Uses [`mcp-remote`](https://github.com/geelen/mcp-remote) to bridge HTTP MCP servers to the Claude Desktop stdio transport.
62+
63+
**Claude.ai** (MCP integrations):
64+
Add the server URL directly: `https://<your-ref>.supabase.co/functions/v1/mcp`
65+
66+
## Usage
67+
68+
### Create a group
69+
> "Create a new expense group called 'Barcelona Trip' with my name as Alex"
70+
71+
Claude will return a **6-character join code** (e.g. `XK7M2P`) and your **member_id**. Share the join code with friends.
72+
73+
### Join a group
74+
> "Join group XK7M2P, my name is Jordan"
75+
76+
### Log an expense
77+
> "Log a $45 dinner expense, I paid, split equally among everyone"
78+
79+
**From a receipt photo**: Share the image with Claude, then ask it to log it:
80+
> "Here's my receipt [image]. Add it as an expense split equally."
81+
82+
Claude will read the receipt, extract the amount and description, and call `add_expense` with the details.
83+
84+
### Check balances
85+
> "Who owes what in our group?"
86+
87+
### Settle up
88+
> "Jordan just paid me back $22.50 — record that"
89+
90+
## Identity model
91+
92+
There is no login. When you create or join a group, you receive a **member_id** (UUID). This is your permanent identity — save it. Claude stores it in conversation context and will remind you to note it down.
93+
94+
If you switch devices or start a new conversation, use `get_member` to confirm your identity:
95+
> "My member ID is abc123... — what group am I in?"
96+
97+
## Tools
98+
99+
| Tool | Description |
100+
|------|-------------|
101+
| `create_group` | Create a group, get a join code |
102+
| `join_group` | Join with a join code + display name |
103+
| `get_group` | Group info and member list |
104+
| `add_expense` | Log an expense (equal/exact/percent splits) |
105+
| `list_expenses` | View expenses with split details |
106+
| `delete_expense` | Soft-delete an expense you logged |
107+
| `get_balances` | Net balances + minimum payments to settle |
108+
| `record_settlement` | Record a payment between members |
109+
| `get_settlement_history` | Past settlements |
110+
| `get_member` | Look up your member info |
111+
| `rename_member` | Change your display name |
112+
113+
## Local development
114+
115+
```bash
116+
supabase start # starts local Postgres + Edge Functions runtime
117+
supabase functions serve mcp --no-verify-jwt
118+
```
119+
120+
Test with curl:
121+
```bash
122+
curl -X POST http://localhost:54321/functions/v1/mcp \
123+
-H "Content-Type: application/json" \
124+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
125+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"account": {
3+
"id": "9a39f1620584a1dd58e885a8f68fb45d",
4+
"name": "Dfalling@gmail.com's Account"
5+
}
6+
}

proxy/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "splitcount-mcp-proxy",
3+
"version": "1.0.0",
4+
"private": true,
5+
"devDependencies": {
6+
"wrangler": "^3"
7+
},
8+
"scripts": {
9+
"deploy": "wrangler deploy"
10+
}
11+
}

proxy/src/index.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const CORS = {
2+
"Access-Control-Allow-Origin": "*",
3+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
4+
"Access-Control-Allow-Headers":
5+
"Content-Type, Authorization, x-client-info, apikey, MCP-Protocol-Version",
6+
};
7+
8+
interface Env {
9+
UPSTREAM: string;
10+
}
11+
12+
export default {
13+
async fetch(req: Request, env: Env): Promise<Response> {
14+
const url = new URL(req.url);
15+
16+
// Tell Claude "no auth needed" — Supabase would return 401 here
17+
if (url.pathname.startsWith("/.well-known/")) {
18+
return new Response(JSON.stringify({ error: "Not Found" }), {
19+
status: 404,
20+
headers: { ...CORS, "Content-Type": "application/json" },
21+
});
22+
}
23+
24+
// CORS preflight
25+
if (req.method === "OPTIONS") {
26+
return new Response(null, { headers: CORS });
27+
}
28+
29+
// Proxy everything else to Supabase
30+
const upstream = new Request(env.UPSTREAM, {
31+
method: req.method,
32+
headers: req.headers,
33+
body: req.body,
34+
});
35+
36+
const res = await fetch(upstream);
37+
const resHeaders = new Headers(res.headers);
38+
Object.entries(CORS).forEach(([k, v]) => resHeaders.set(k, v));
39+
return new Response(res.body, { status: res.status, headers: resHeaders });
40+
},
41+
};

proxy/wrangler.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = "splitcount-mcp"
2+
main = "src/index.ts"
3+
compatibility_date = "2025-01-01"
4+
5+
[vars]
6+
UPSTREAM = "https://jjwhkljwhzacodjkfyyf.supabase.co/functions/v1/mcp"

scripts/deploy.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Deploy SplitCount to Supabase.
5+
# Reads SUPABASE_PROJECT_REF from the environment (set it in .envrc).
6+
# Usage: ./scripts/deploy.sh
7+
8+
if ! command -v supabase &>/dev/null; then
9+
echo "Error: supabase CLI not found."
10+
echo "Install it: https://supabase.com/docs/guides/cli/getting-started"
11+
exit 1
12+
fi
13+
14+
if [[ -z "${SUPABASE_PROJECT_REF:-}" ]]; then
15+
echo "Error: SUPABASE_PROJECT_REF is not set."
16+
echo "Add it to .envrc: export SUPABASE_PROJECT_REF=<your-ref>"
17+
exit 1
18+
fi
19+
20+
echo "Linking to project: $SUPABASE_PROJECT_REF"
21+
supabase link --project-ref "$SUPABASE_PROJECT_REF"
22+
23+
echo "Pushing database migrations..."
24+
supabase db push
25+
26+
echo "Reloading PostgREST schema cache..."
27+
supabase db execute --sql "SELECT pg_notify('pgrst', 'reload schema');" 2>/dev/null \
28+
|| echo " (schema cache reload skipped — run manually in SQL Editor if queries fail: SELECT pg_notify('pgrst', 'reload schema');)"
29+
30+
echo "Deploying mcp edge function..."
31+
supabase functions deploy mcp --no-verify-jwt
32+
33+
echo "Deploying proxy worker..."
34+
cd proxy && npx wrangler deploy && cd ..
35+
36+
echo ""
37+
echo "Deploy complete."
38+
echo "Direct Supabase URL (API calls): https://${SUPABASE_PROJECT_REF}.supabase.co/functions/v1/mcp"
39+
echo "Connector URL (Claude Desktop/web/mobile): see worker URL printed by wrangler above"

supabase/config.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
project_id = "splitcount"
2+
3+
[api]
4+
port = 54321
5+
schemas = ["public", "storage", "graphql_public"]
6+
extra_search_path = ["public", "extensions"]
7+
max_rows = 1000
8+
9+
[db]
10+
port = 54322
11+
shadow_port = 54320
12+
major_version = 17
13+
14+
[studio]
15+
port = 54323
16+
17+
[inbucket]
18+
port = 54324
19+
20+
[storage]
21+
file_size_limit = "50MiB"
22+
23+
[functions.mcp]
24+
verify_jwt = false

0 commit comments

Comments
 (0)