upbank-mcp

upbank-mcp

A Model Context Protocol server that exposes the Up Banking API to LLM clients, providing 19 tools and 2 resources for accounts, transactions, categories, tags, attachments, and webhooks.

Category
访问服务器

README

upbank-mcp

A Model Context Protocol server that exposes the Up Banking API to LLM clients, built with FastMCP and packaged for Docker.

It provides 19 tools and 2 resources covering the complete public surface of the Up API — accounts, transactions, categories, tags, attachments and webhooks — with responses reshaped for token efficiency, cursor pagination preserved end to end, and automatic retry on rate limits.


Contents


Requirements

Up account A personal access token from https://api.up.com.au/getting_started. Tokens look like up:yeah:….
Docker Docker Engine 20.10+ with Compose v2 (docker compose, not docker-compose).
Python 3.11+ — only if running outside Docker.

The Up API is available to Up customers in Australia. A token grants access to the issuing customer's own data only.


Quick start

git clone git@github.com:uiux-me/upbank-mcp.git
cd upbank-mcp
cp .env.example .env          # paste your token into UP_API_TOKEN
docker compose up --build

The server listens on http://127.0.0.1:8000/mcp. Verify it:

docker compose exec upbank-mcp python -c "
import asyncio, upbank_mcp
from fastmcp import Client
async def main():
    async with Client(upbank_mcp.mcp) as c:
        print((await c.call_tool('ping')).data)
asyncio.run(main())"

A healthy response is your customer id and a status emoji:

{'ok': True, 'id': 'eb59f467-…', 'status_emoji': '⚡️'}

Configuration

All configuration is by environment variable. Compose reads .env from the project directory automatically.

Variable Default Description
UP_API_TOKEN (required) Personal access token. The only variable the server reads for credentials. Compose refuses to start without it; run directly, the server starts and fails on the first tool call.
UP_API_BASE https://api.up.com.au/api/v1 API base URL. Override only for testing against a mock.
MCP_TRANSPORT stdio stdio for local MCP clients, http for a network-addressable server. Compose sets http.
MCP_HOST 0.0.0.0 Bind address for the HTTP transport, inside the container.
MCP_PORT 8000 Listen port for the HTTP transport, inside the container.
MCP_HOST_PORT 8000 Compose only. Host port published on 127.0.0.1. Change this if 8000 is already in use.

Running the server

HTTP, via Compose

Best for a long-running server shared by several clients on your machine.

docker compose up --build          # foreground
docker compose up -d --build       # detached
docker compose logs -f             # follow logs
docker compose down                # stop and remove

The port is published on 127.0.0.1 only. See Security.

stdio, via Docker

Best for MCP clients that spawn the server as a subprocess. Build the image once:

docker build -t upbank-mcp:latest .

The image defaults to MCP_TRANSPORT=stdio, so no transport override is needed.

Without Docker

pip install -e .
export UP_API_TOKEN=up:yeah:...
upbank-mcp

Set MCP_TRANSPORT=http to serve over HTTP instead of stdio.


Connecting an MCP client

Claude Code

claude mcp add upbank \
  -e UP_API_TOKEN=up:yeah:... \
  -- docker run -i --rm -e UP_API_TOKEN upbank-mcp:latest

Claude Desktop, or any client using mcpServers config

{
  "mcpServers": {
    "upbank": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "UP_API_TOKEN", "upbank-mcp:latest"],
      "env": { "UP_API_TOKEN": "up:yeah:..." }
    }
  }
}

-i is required — the server communicates over stdin/stdout. --rm cleans up the container when the client disconnects.

Over HTTP

Point the client at http://127.0.0.1:8000/mcp while the Compose stack is running.


Tool reference

Required parameters are bold. Every list tool accepts cursor; see Pagination.

Utility

Tool Parameters Returns
ping — {ok, id, status_emoji}. Verifies the token and API reachability.

Accounts

Tool Parameters Returns
list_accounts account_type (SAVER | TRANSACTIONAL | HOME_LOAN), ownership_type (INDIVIDUAL | JOINT), page_size (1–100, default 30), cursor Page of accounts with balances.
get_account account_id One account.

Transactions

Tool Parameters Returns
list_transactions account_id, status (HELD | SETTLED), since, until, category, tag, page_size (1–100, default 30), cursor Page of transactions, newest first. Omit account_id to search across all accounts.
get_transaction transaction_id One transaction, including hold, round-up and cashback detail.

since and until bound createdAt inclusively. category accepts a parent id, which matches all of its children.

Categories

Tool Parameters Returns
list_categories parent The category tree, or one parent's children. Not paginated.
get_category category_id One category with its parent and child ids.
categorize_transaction transaction_id, category_id Sets the category, or clears it when category_id is null.

Categories are fixed by Up and cannot be created. Ids are slugs such as restaurants-and-cafes. Only transactions with is_categorizable: true can be changed, and only leaf categories are accepted — passing a parent such as good-life returns HTTP 403.

Tags

Tool Parameters Returns
list_tags page_size (1–100, default 50), cursor Page of tags. A tag's id is its label.
add_tags_to_transaction transaction_id, tags (list) Adds tags, creating any that do not exist.
remove_tags_from_transaction transaction_id, tags (list) Removes tags from the transaction.

A transaction holds at most 6 tags. Tags with no remaining transactions disappear from list_tags.

Attachments

Tool Parameters Returns
list_attachments page_size (1–100, default 30), cursor Page of attachments.
get_attachment attachment_id One attachment.

file_url is a signed URL that expires at file_url_expires_at. Fetch it promptly or re-request the attachment.

Webhooks

Tool Parameters Returns
list_webhooks page_size (1–100, default 30), cursor Page of webhooks.
get_webhook webhook_id One webhook.
create_webhook url, description (≤64 chars) The new webhook, including secret_key.
delete_webhook webhook_id {ok, deleted}. Permanent.
ping_webhook webhook_id Sends a test PING event.
list_webhook_logs webhook_id, page_size (1–100, default 30), cursor Recent delivery attempts with response codes and bodies.

secret_key is returned only at creation and never again. Store it to verify the X-Up-Authenticity-Signature header (SHA-256 HMAC) on incoming deliveries.


Resources

URI Contents
up://accounts Every account and its current balance, as a single JSON snapshot.
up://categories The full category tree, for resolving valid category filter values.

Both are read on demand and reflect state at read time.


Response conventions

Shape

Up returns JSON:API, which nests every field under attributes/relationships and repeats self-links on each resource. This server flattens each resource into a compact dict and omits optional fields when absent, which materially reduces token cost without losing information a caller needs.

{
  "id": "45b83097-c97d-40da-9790-254056f03d40",
  "status": "SETTLED",
  "description": "Google One",
  "amount": { "value": "-2.49", "currency": "AUD", "base_units": -249 },
  "created_at": "2026-08-20T06:53:17+10:00",
  "settled_at": "2026-08-20T06:53:17+10:00",
  "account_id": "90c0fffc-bed6-4214-9450-6a76cd39957b",
  "category_id": "games-and-software",
  "parent_category_id": "good-life",
  "tags": [],
  "is_categorizable": true
}

Fields such as foreign_amount, hold_info, round_up, cashback, card_purchase_method, note and message appear only when the transaction has them.

Money

Every amount is an object:

{ "value": "-2.49", "currency": "AUD", "base_units": -249 }

value is a decimal string, base_units is the integer minor unit (cents for AUD). Debits are negative. Prefer base_units for arithmetic to avoid float error.

Pagination

List tools return:

{ "items": [ ... ], "next_cursor": "https://api.up.com.au/...", "prev_cursor": null }

To page, pass a returned cursor back as the cursor argument of the same tool. Cursors are Up's own opaque URLs and already encode the filters and page size, so all other arguments are ignored when cursor is set. A null cursor means no further page in that direction.

Cursors are validated against the configured API host before being followed, so a cursor cannot redirect the client to another server.

Dates

since and until accept either YYYY-MM-DD or a full RFC-3339 timestamp. Bare dates and naive datetimes are anchored to Australia/Sydney, matching how Up presents times in the app; the correct offset is applied for the date in question, so daylight saving is handled. Unparseable input is rejected before the request is made, rather than surfacing as an opaque HTTP 400.


Error handling and rate limits

  • API errors are raised as ToolError with the HTTP status and Up's own error title and detail, for example: HTTP 403 — Forbidden: Top-level categories cannot be set directly on transactions.
  • 429 and 5xx responses are retried up to 3 times with exponential backoff, honouring the Retry-After header when present.
  • Network failures are retried on the same schedule before surfacing.
  • 4xx responses other than 429 are not retried — they indicate a bad request.

Project layout

src/upbank_mcp/
├── client.py     Async HTTP client: auth, retry/backoff, date normalisation,
│                 cursor host validation
├── shapes.py     JSON:API → flat dict transforms, one per resource type
├── server.py     FastMCP instance, tool and resource definitions, entrypoint
├── __init__.py   Exports `mcp` and `main`
└── __main__.py   Enables `python -m upbank_mcp`

The separation is deliberate: client.py knows about HTTP and nothing about MCP, shapes.py is pure data transformation, and server.py holds the tool contracts. Each is independently testable.


Development

pip install -e .
export UP_API_TOKEN=up:yeah:...
upbank-mcp                              # stdio
MCP_TRANSPORT=http upbank-mcp           # http on :8000

Drive the server in-process with the FastMCP client:

import asyncio
from fastmcp import Client
import upbank_mcp

async def main():
    async with Client(upbank_mcp.mcp) as client:
        print(await client.list_tools())
        result = await client.call_tool("list_accounts", {"account_type": "TRANSACTIONAL"})
        print(result.data)

asyncio.run(main())

Rebuild the image after changes:

docker compose up -d --build

Security

The token is powerful. Up personal access tokens cannot move money — the API has no payment or transfer endpoint — but they can read your complete transaction history and mutate categories, tags and webhooks. Treat one like a password.

  • The HTTP transport has no authentication of its own. Anything that can reach the port can read your banking data. Compose therefore publishes to 127.0.0.1 only. Do not bind it to 0.0.0.0 or expose it through a tunnel or reverse proxy without putting authentication in front of it.
  • The token is never baked into an image. .env is listed in .dockerignore, and the token is supplied at runtime. It does not appear in any image layer, so the image is safe to push to a registry.
  • .env is gitignored, and .env.example carries a placeholder only.
  • The container runs as a non-root user (uid 10001).
  • Rotate immediately at https://api.up.com.au/getting_started if a token is ever exposed. Tokens do not expire on their own.

Troubleshooting

Symptom Cause and fix
No Up API token configured UP_API_TOKEN is unset or empty. Check .env, and that Compose is run from the project directory.
Compose exits with set UP_API_TOKEN in .env Same cause, caught at container start rather than first call.
Bind for 127.0.0.1:8000 failed: port is already allocated Another process holds port 8000. Set MCP_HOST_PORT in .env.
HTTP 401 — Unauthorized Token is invalid or revoked. Reissue it.
HTTP 403 — Top-level categories cannot be set… categorize_transaction was given a parent category. Use a leaf id from list_categories.
HTTP 404 on a valid-looking id Ids are per-customer. Confirm the id came from this token's own data.
Repeated HTTP 429 Sustained rate limiting. Reduce page_size and request frequency; retries are already automatic.
Client shows no tools The client must run the container with -i. Without it, stdio is closed immediately.

Reference

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选