Skip to content

Commit 7c4bc64

Browse files
committed
migrate from supabase to docker images
1 parent 9de66eb commit 7c4bc64

27 files changed

Lines changed: 1465 additions & 1337 deletions

.envrc.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export SUPABASE_PROJECT_REF=your-project-ref-here
1+
export DATABASE_URL=postgres://splitcount:password@localhost:5432/splitcount
2+
export PORT=3000

.github/workflows/deploy.yml

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
1-
name: Deploy to Supabase
1+
name: Build and Push Docker Image
22

33
on:
44
push:
55
branches:
66
- main
77

8+
env:
9+
REGISTRY: ghcr.io
10+
IMAGE_NAME: ${{ github.repository }}
11+
812
jobs:
9-
deploy:
13+
build-and-push:
1014
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
packages: write
1118

1219
steps:
1320
- uses: actions/checkout@v4
1421

15-
- uses: supabase/setup-cli@v1
22+
- name: Log in to GitHub Container Registry
23+
uses: docker/login-action@v3
1624
with:
17-
version: latest
18-
19-
- name: Link project
20-
run: supabase link --project-ref "$SUPABASE_PROJECT_REF"
21-
env:
22-
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
23-
SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }}
24-
25-
- name: Push database migrations
26-
run: supabase db push --password "$SUPABASE_DB_PASSWORD"
27-
env:
28-
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
29-
SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }}
30-
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
25+
registry: ${{ env.REGISTRY }}
26+
username: ${{ github.actor }}
27+
password: ${{ secrets.GITHUB_TOKEN }}
3128

32-
- name: Reload PostgREST schema cache
33-
run: |
34-
supabase db execute --sql "SELECT pg_notify('pgrst', 'reload schema');" 2>/dev/null \
35-
|| echo "Schema cache reload skipped — run manually if needed."
36-
env:
37-
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
38-
SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }}
29+
- name: Extract metadata
30+
id: meta
31+
uses: docker/metadata-action@v5
32+
with:
33+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
34+
tags: |
35+
type=sha,prefix=
36+
type=raw,value=latest,enable={{is_default_branch}}
3937
40-
- name: Deploy mcp edge function
41-
run: supabase functions deploy mcp --no-verify-jwt
42-
env:
43-
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
44-
SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }}
38+
- name: Build and push
39+
uses: docker/build-push-action@v6
40+
with:
41+
context: .
42+
push: true
43+
tags: ${{ steps.meta.outputs.tags }}
44+
labels: ${{ steps.meta.outputs.labels }}

.github/workflows/test.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Test
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
lint:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: oven-sh/setup-bun@v2
17+
- run: bun install --frozen-lockfile
18+
- run: bun run check
19+
- run: bun run typecheck

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
.env.*
44
!.env.example
55
.envrc
6-
supabase/.temp/
76
node_modules/
7+
dist/
88
.DS_Store

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM oven/bun:1-alpine
2+
WORKDIR /app
3+
COPY package.json bun.lock* ./
4+
RUN bun install --frozen-lockfile --production
5+
COPY src/ ./src
6+
COPY migrations/ ./migrations
7+
ENV PORT=3000
8+
EXPOSE 3000
9+
CMD ["bun", "run", "src/index.ts"]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Dennis Falling
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1-
.PHONY: deploy
1+
.PHONY: install dev start typecheck docker-build docker-up docker-down
22

3-
# Deploy migrations and edge function.
4-
# Requires SUPABASE_PROJECT_REF to be set (via .envrc + direnv, or exported manually).
5-
deploy:
6-
./scripts/deploy.sh
3+
install:
4+
bun install
5+
6+
dev:
7+
bun run --watch src/index.ts
8+
9+
start:
10+
bun run src/index.ts
11+
12+
typecheck:
13+
bun run tsc --noEmit
14+
15+
docker-build:
16+
docker build -t splitcount .
17+
18+
docker-up:
19+
docker compose up --build -d
20+
21+
docker-down:
22+
docker compose down

README.md

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,85 @@ Expense splitting via MCP + Claude. Log expenses (including receipt photos), tra
44

55
## Architecture
66

7-
- **MCP server**: Supabase Edge Function (Deno) at `supabase/functions/mcp/`
8-
- **Database**: Supabase PostgreSQL (groups, members, expenses, splits, settlements)
7+
- **MCP server**: Bun HTTP server at `src/`
8+
- **Database**: PostgreSQL (self-hosted or any provider)
99
- **Client**: Any MCP-compatible client (Claude Desktop, Claude.ai, etc.)
1010

11-
## Setup
11+
Migrations run automatically on startup. No migration tooling required.
1212

13-
### 1. Create a Supabase project
13+
## Self-hosting with Docker
1414

15-
Go to [supabase.com](https://supabase.com), create a new project, and note your project ref (visible in the project URL or Settings → General).
15+
The easiest way to run SplitCount is with Docker Compose. Add it to your existing stack:
1616

17-
### 2. Set your project ref
18-
19-
Add to `.envrc` (used by [direnv](https://direnv.net/)):
20-
```bash
21-
export SUPABASE_PROJECT_REF=<your-ref>
17+
```yaml
18+
services:
19+
splitcount:
20+
image: ghcr.io/dfalling/splitcount:latest
21+
environment:
22+
DATABASE_URL: postgres://user:password@your-postgres-host:5432/splitcount
23+
ports:
24+
- "3000:3000"
25+
restart: unless-stopped
2226
```
2327
24-
Or export it manually:
28+
Or run the full stack including Postgres:
29+
2530
```bash
26-
export SUPABASE_PROJECT_REF=<your-ref>
31+
docker compose up -d
2732
```
2833

29-
### 3. Deploy
34+
The server will be available at `http://localhost:3000`.
3035

31-
```bash
32-
make deploy
33-
```
36+
### Connect to Claude
3437

35-
This links to your project, pushes database migrations, and deploys the MCP edge function. Your server URL will be printed at the end:
38+
In Claude Desktop or Claude.ai settings, add a custom MCP connector:
3639
```
37-
https://<your-ref>.supabase.co/functions/v1/mcp
40+
http://your-server:3000
3841
```
3942

40-
### 4. Add as a custom connector
43+
## Local development
44+
45+
**Prerequisites**: [mise](https://mise.jdx.dev/) for version management.
46+
47+
```bash
48+
mise install # installs bun
49+
bun install # install dependencies
50+
cp .envrc.example .envrc
51+
# edit .envrc with your DATABASE_URL
52+
bun run dev # start with hot reload
53+
```
4154

42-
In Claude Desktop or Claude.ai settings, add a custom MCP connector with your server URL:
55+
Test the server:
56+
```bash
57+
curl -X POST http://localhost:3000 \
58+
-H "Content-Type: application/json" \
59+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
4360
```
44-
https://<your-ref>.supabase.co/functions/v1/mcp
61+
62+
Health check:
63+
```bash
64+
curl http://localhost:3000?health
4565
```
4666

4767
## Usage
4868

4969
### Create a group
5070
> "Create a new expense group called 'Barcelona Trip' with my name as Alex"
5171
52-
Claude will return a **6-character join code** (e.g. `XK7M2P`) and your **member_id**. Share the join code with friends.
72+
Claude returns a **6-character join code** (e.g. `XK7M2P`) and your **member_id**. Share the join code with friends.
5373

5474
### Join a group
55-
> "Join group XK7M2P, my name is Jordan"
75+
> "Join group XK7M2P"
76+
77+
Claude will show you the existing member list so you can claim your slot or join as someone new.
5678

5779
### Log an expense
5880
> "Log a $45 dinner expense, I paid, split equally among everyone"
5981
60-
**From a receipt photo**: Share the image with Claude, then ask it to log it:
82+
**From a receipt photo**: share the image with Claude, then ask it to log it:
6183
> "Here's my receipt [image]. Add it as an expense split equally."
6284
63-
Claude will read the receipt, extract the amount and description, and call `add_expense` with the details.
85+
Claude reads the receipt, extracts the amount and description, and calls `add_expense`.
6486

6587
### Check balances
6688
> "Who owes what in our group?"
@@ -70,37 +92,31 @@ Claude will read the receipt, extract the amount and description, and call `add_
7092
7193
## Identity model
7294

73-
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.
95+
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.
7496

75-
If you switch devices or start a new conversation, use `get_member` to confirm your identity:
97+
If you start a new conversation, use `get_member` to confirm your identity:
7698
> "My member ID is abc123... — what group am I in?"
7799
78100
## Tools
79101

80102
| Tool | Description |
81103
|------|-------------|
82104
| `create_group` | Create a group, get a join code |
83-
| `join_group` | Join with a join code + display name |
105+
| `join_group` | Join with a join code; shows member list to claim or create |
84106
| `get_group` | Group info and member list |
85107
| `add_expense` | Log an expense (equal/exact/percent splits) |
86108
| `list_expenses` | View expenses with split details |
109+
| `update_expense` | Edit an expense you logged |
87110
| `delete_expense` | Soft-delete an expense you logged |
88111
| `get_balances` | Net balances + minimum payments to settle |
89112
| `record_settlement` | Record a payment between members |
90113
| `get_settlement_history` | Past settlements |
114+
| `add_member` | Pre-add a member before they join |
115+
| `claim_member` | Identify as an existing member |
116+
| `list_members` | List all members in a group |
91117
| `get_member` | Look up your member info |
92118
| `rename_member` | Change your display name |
93119

94-
## Local development
95-
96-
```bash
97-
supabase start # starts local Postgres + Edge Functions runtime
98-
supabase functions serve mcp --no-verify-jwt
99-
```
120+
## Database migrations
100121

101-
Test with curl:
102-
```bash
103-
curl -X POST http://localhost:54321/functions/v1/mcp \
104-
-H "Content-Type: application/json" \
105-
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
106-
```
122+
Migrations live in `migrations/` and are applied automatically at startup in order. To add a new migration, create a file named `NNN_description.sql` (e.g. `005_add_tags.sql`).

biome.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
8+
"files": {
9+
"ignoreUnknown": false
10+
},
11+
"formatter": {
12+
"enabled": true,
13+
"indentStyle": "space",
14+
"indentWidth": 2
15+
},
16+
"linter": {
17+
"enabled": true,
18+
"rules": {
19+
"recommended": true
20+
}
21+
},
22+
"javascript": {
23+
"formatter": {
24+
"quoteStyle": "double"
25+
}
26+
},
27+
"assist": {
28+
"enabled": true,
29+
"actions": {
30+
"source": {
31+
"organizeImports": "on"
32+
}
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)