Skip to content

MCP server

Badlen ships an MCP server exposing eight tools over the /api/v1 surface. It is how a whole statement gets into the product at once.

Your statements ──▶ Claude Code / Desktop ──▶ MCP tools ──▶ Badlen /api/v1
reads and extracts import_transactions dedup, write
(your subscription)

Your Claude client reads the files, on your machine, on your subscription. The server is a stdio bridge and holds no model, so Badlen never calls the Anthropic API, no model key is ever stored on the server, and nothing from a statement reaches Badlen except operations that have already been extracted.

  1. Have a running Badlen instance, and know the address you reach it on. An instance installed from Docker Compose answers at http://localhost:8080: that number is WEB_PORT in your .env, and changing it there changes this address. Behind a reverse proxy, use the address the proxy serves. The examples below carry the default, which is also what the server falls back to when BADLEN_URL is unset.

  2. Have Node and pnpm on the machine the client runs on. The server is a Node script your Claude client starts directly, outside Docker, so a machine that only followed Install is short of both: node 22.12 or newer and pnpm 12, the versions the repository pins in its root package.json.

  3. Run pnpm install at the repository root, which pulls the MCP SDK.

  4. Issue an API key carrying exactly five permissions: accounts:read, dashboard:read, positions:read, accounts:write and transactions:write.

  5. Register the server with your client. The path to server.mjs must be absolute: the client does not start it from inside the repository.

    Terminal window
    claude mcp add badlen \
    -e BADLEN_URL=http://localhost:8080 \
    -e BADLEN_API_KEY=sk_your_key \
    -- node /absolute/path/to/packages/mcp/src/server.mjs
Tool What it does
list_accounts The accounts, with id, name, type and value, so Claude knows where to import
get_net_worth Net worth and allocation by asset class
get_positions Holdings by security, with latent and realised gains
create_account Create an account to import into
set_balance Set a manual account’s balance on a given day
add_holding Add a fund or ETF line by ISIN, price resolved automatically
reconcile_transactions Dry run: compare a batch against the account without writing
import_transactions Write the batch, deduplicated

None of them deletes anything.

Point your client at the files and ask it, in plain words, to import them into Badlen. It normalises the operations, calls reconcile_transactions to see the duplicates and raise its questions with you, then calls import_transactions once you have answered. Re-running the same statement is safe: duplicates are skipped. What that second call does with the batch is on the import page.

What you see What it means
401 on every tool The key is absent, unknown, revoked or expired
403 on one tool The key was issued without the permission that tool needs
400 on an amount Amounts travel as decimal strings; a JSON number is refused outright
400 on a date A date is ten characters, YYYY-MM-DD, not a timestamp

Every route the tools reach is written out in the REST API reference, generated from the controller rather than typed by hand.

Privacy