wallet-watch-mcp
by ykchethan
README.md
# wallet-watch-mcp
An [MCP](https://modelcontextprotocol.io) server that exposes the **wallet-watch**
subscription-tracking REST API (a Spring Boot service) as tools an AI model can call.
Ask an assistant *"what's renewing this week?"*, *"where am I wasting money?"*, or
*"did anything get more expensive lately?"* and it calls these tools against your
wallet-watch backend — reading, reasoning over, and acting on your subscriptions.
## What it does
Exposes the wallet-watch subscription API as tools an AI assistant can call, so you can
manage subscriptions in plain language. Through it, an assistant can:
- List your subscriptions and filter them by category.
- Tell you what's renewing soon and forecast spend for the months ahead.
- Summarize monthly spend by category.
- Surface unused subscriptions and overlapping (duplicate) services.
- Report subscriptions whose price recently went up.
- Snooze, resume, or downgrade a subscription on your behalf.
It talks to the [wallet-watch backend](https://github.com/yourorg/wallet-watch) over
HTTP, so that service (or your own API at the same routes) needs to be running.
## Files
Three files of code — everything configurable lives in `config.py`.
```
wallet-watch-mcp/
├── config.py ← ALL config: connection settings + endpoint paths
├── client.py ← HTTP helpers (get_json / patch_json): auth, timeout, errors
├── server.py ← the MCP server + the tools
├── requirements.txt
├── .env.example
├── Dockerfile
├── docker-compose.yml
└── .gitignore
```
## Tools
### Read
| Tool | What it does |
|------|--------------|
| `list_subscriptions(category?)` | All subscriptions, optionally filtered by category. |
| `get_upcoming_renewals(days=30)` | Renewing within N days, soonest first. |
| `get_spend_summary()` | Total monthly spend + per-category breakdown. |
| `find_unused_subscriptions(days=30)` | Unused subscriptions + potential savings. |
| `find_duplicate_services()` | Overlapping subscriptions in the same category. |
| `forecast_spend(months=6)` | Month-by-month spend forecast (annual bills as lumps). |
| `get_recent_price_increases(since_days=90)` | Subscriptions whose price went up. |
### Act
| Tool | What it does |
|------|--------------|
| `snooze_or_downgrade(id, action, tier?)` | Pause, resume, or downgrade a subscription. |
Every tool returns clean JSON and degrades gracefully: if the backend is down, it
returns `{"error": true, "message": "..."}` instead of crashing.
## Run it (local)
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then set WALLET_WATCH_BASE_URL
python server.py
```
Serves at `http://localhost:8000/mcp` (streamable HTTP transport). Make sure the
wallet-watch backend is running (default `http://localhost:8080`).
## Test it with the MCP Inspector
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) lists the tools
and lets you call them by hand — no AI client needed.
```bash
npx @modelcontextprotocol/inspector
```
It opens a UI at `http://localhost:6274` (if the terminal prints a URL with a session
token, open that one — you don't need to copy the token manually). Then:
1. **Transport Type:** `Streamable HTTP`
2. **URL:** `http://localhost:8000/mcp`
3. **Connect** → open **Tools** → **List Tools** → all eight should appear.
4. Run `get_spend_summary` (no args) → expect real JSON from your backend.
5. Try `snooze_or_downgrade` with `id=1, action="snooze"`, then `list_subscriptions`
to confirm the status changed.
6. Stop the backend and re-run any tool → you get a graceful `{"error": true, ...}`.
> **Troubleshooting:** a 404 on **connect** usually means the URL needs `/mcp` (try a
> trailing slash too). A `"...returned HTTP 404"` *inside* a tool result means the
> connection is fine but an endpoint path in `config.py` doesn't match your backend.
> An `AttributeError` for a missing config attribute means a path constant is missing
> from `config.py` — add it there.
## Connect a real AI client
Point an HTTP MCP client at the URL. In Claude Desktop's config:
```json
{
"mcpServers": {
"wallet-watch": { "url": "http://localhost:8000/mcp" }
}
}
```
Restart the client, then ask: *"What am I paying for?"*, *"What renews this week?"*,
*"Where am I wasting money?"*, *"Snooze my gym membership."*
## Run both services together
```bash
docker compose up
```
Set the `api` service in `docker-compose.yml` to your wallet-watch image or `build:`
path first. The MCP container reaches the API at `http://api:8080`.
## Config reference
| Variable | Default | Purpose |
|----------|---------|---------|
| `WALLET_WATCH_BASE_URL` | `http://localhost:8080` | Base URL of the wallet-watch API. |
| `WALLET_WATCH_API_KEY` | _(none)_ | Optional bearer token. |
| `WALLET_WATCH_TIMEOUT` | `10` | Per-request timeout (seconds). |
| `MCP_SERVER_NAME` | `wallet-watch-mcp` | Server identity. |
| `MCP_TRANSPORT` | `streamable-http` | Use `stdio` for local desktop clients. |
## Backend endpoints used
All defined in `config.py` — remap there if your backend differs.
- `GET /subscriptions?category=`
- `GET /subscriptions/upcoming?days=`
- `GET /subscriptions/spend-summary`
- `GET /subscriptions/unused?days=`
- `GET /subscriptions/price-changes?sinceDays=`
- `GET /subscriptions/forecast?months=`
- `PATCH /subscriptions/{id}/status`
- `PATCH /subscriptions/{id}/plan`
`find_duplicate_services` needs no dedicated endpoint — it reasons over
`GET /subscriptions`.
## Requirements
### Functional
- Expose subscription reads as MCP tools: list/filter, upcoming renewals, spend summary, unused, duplicates, forecast, recent price increases.
- Expose a write/action tool to snooze, resume, or downgrade a subscription.
- Each tool returns clean, structured JSON — not raw passthrough or verbose text.
- Each tool carries a clear docstring describing when to use it (this is what the model reads to decide).
- Validate arguments (e.g. positive `days`/`months`, required `tier` on downgrade) before calling the backend.
- `find_duplicate_services` reasons over existing subscription data without a dedicated backend endpoint.
### Non-functional
- **Graceful degradation:** if the backend is unreachable, times out, errors, or returns non-JSON, tools return `{"error": true, "message": "..."}` — never crash the server.
- **Single configuration point:** all connection settings and endpoint paths live in `config.py`.
- **Small and readable:** three code files; logic easy to follow top-to-bottom.
- **Transport-flexible:** streamable HTTP by default; `stdio` via config for local desktop clients.
- **Portable:** points at any wallet-watch deployment (local, Docker, remote) via one env var.
## Design decisions
**All configuration in one place.** Connection settings and endpoint paths live in
`config.py` and nowhere else, so pointing at a different backend or remapping a route is
a single-file change.
**One HTTP helper, centralized error handling.** Every request goes through `get_json` /
`patch_json` in `client.py`, which own auth headers, timeout, and translating any failure
into a readable `BackendError`. Tools never touch transport concerns and never crash on a
dead backend — they return a structured error object instead.
**Docstrings are the interface.** Each tool leads with what it's *for* in plain language,
because that text is what the model reads to decide when to call it. Clear descriptions
matter more here than in ordinary code.
**Structured JSON, shaped for a model.** Tools return tidy dicts and do light work the
model shouldn't — sorting renewals by urgency, summing potential savings, grouping
duplicates — rather than passing raw backend output straight through.
**Reads and actions, not just reads.** The server includes a write tool
(`snooze_or_downgrade`) so an assistant can act, not only report — with argument
validation and a returned updated state to confirm the change.
**Scheduling/notifying is out of scope — by design.** MCP is request/response; it has no
background loop. Proactive notifications (e.g. price-increase alerts) belong in the
backend as a scheduled job that shares the same detection logic. The MCP server exposes
the *reactive* view (`get_recent_price_increases`) only.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues