MCP Server
Connect AI agents to your financial data using the Model Context Protocol.
What is the Cobalt MCP server?
A hosted Model Context Protocol (MCP) server that gives AI agents programmatic access to your financial data — accounts, transactions, investments, brokerage positions, tags, and market research.
Instead of one-tool-per-endpoint, the server exposes a single cobalt_execute_code tool. The agent writes a short JavaScript program that calls the cobalt.* SDK (accounts, transactions, brokerage, tags, research) and the program runs in a sandboxed V8 isolate. One turn replaces what used to take many round-trip tool calls.
- Authenticated via OAuth — the access token is bound to one Cobalt user.
- All
cobalt.*calls are auto-scoped to that user; sandboxed code cannot supply or overrideuserId. - Mostly read;
cobalt.transactions.updateand thecobalt.tags.*mutators are the only write paths. - Accessible from any MCP client that supports Streamable HTTP transport.
Server URL:
https://api.cobaltpf.com/api/mcpQuick start
Cursor
VS Code
Claude
ChatGPT
Claude Code
OpenCode
Codex CLI
Gemini CLI
Amp CLI
Zed
Pi
Other clients
Authentication
The MCP server uses OAuth via Better Auth. When you connect for the first time, your client will redirect you to sign in with your Cobalt account.
- Each client (Cursor, Claude Code, VS Code, etc.) registers as an OAuth application with Cobalt.
- After sign-in, the OAuth token is cached by your client.
- All queries are automatically scoped to your user via row-level security — you can only access your own financial data.
Security
- Code runs in a Cloudflare V8 isolate — no filesystem, no network, no access to host secrets. Only the
cobalt.*SDK is exposed. - The OAuth subject is captured server-side and injected into every
cobalt.*call. Sandboxed code cannot pass or spoof auserId. - Execution timeout is 180 seconds.
- Write surface is limited to
cobalt.transactions.update(patch only — cannot create) andcobalt.tags.*(create / update / archive / attach to transactions). Everything else is read-only.
cobalt.transactions.update and cobalt.tags.* can modify your data. Review
what your agent plans to run before approving destructive edits. Tag deletes
are soft (archived), so history is preserved.
Installation instructions
Claude
Works the same on both Claude.ai (Pro/Max) and Claude Desktop.
Fill in:
- Name:
Cobalt - Remote MCP Server URL:
https://api.cobaltpf.com/api/mcp
Click Add, then follow the connection steps to sign in with your Cobalt account
ChatGPT
Fill in:
- Name:
Cobalt - Description:
Tools for Financial Data - MCP Server URL:
https://api.cobaltpf.com/api/mcp
Cursor
Cursor — One-click install
Click to install the Cobalt MCP server in Cursor.
Manual installation:
Open the command palette and type "Cursor Settings"
Under "Tools & MCP" click "New MCP Server"
Paste the following JSON into the configuration file:
{
"mcpServers": {
"cobalt": {
"url": "https://api.cobaltpf.com/api/mcp"
}
}
}Save the file. Cursor will prompt you to authenticate — follow the browser flow to sign in with your Cobalt account.
You may need to restart Cursor to load the new configuration.
VS Code
VS Code — One-click install
Click to install the Cobalt MCP server in VS Code.
Manual installation:
Open the Command Palette (Ctrl+Shift+P on Windows/Linux, Cmd+Shift+P on macOS)
Type "MCP: Add Server" and select HTTP
Enter the URL https://api.cobaltpf.com/api/mcp and name it Cobalt
Start the server via "MCP: List Servers" > select Cobalt > "Start Server"
When prompted to authenticate, click "Allow" and complete the sign-in flow in your browser.
Claude Code
Ensure Claude Code is installed:
claude --versionAdd the Cobalt MCP server:
claude mcp add --transport http cobalt https://api.cobaltpf.com/api/mcpStart Claude Code and verify:
claude
/mcpYou should see cobalt listed. If it shows "needs authentication", select it and follow the browser flow.
OpenCode
Ensure OpenCode is installed:
opencode --versionAdd the server to your configuration file:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"cobalt": {
"type": "remote",
"url": "https://api.cobaltpf.com/api/mcp"
}
}
}Authenticate:
opencode mcp auth cobaltVerify connection:
opencode
/mcpCodex CLI
Ensure Codex CLI is installed:
codex --versionAdd the Cobalt MCP server:
codex mcp add cobalt --url https://api.cobaltpf.com/api/mcpYou will be prompted to authenticate immediately.
Verify:
codex
/mcpGemini CLI
Ensure Gemini CLI is installed:
gemini --versionAdd the Cobalt MCP server:
gemini mcp add --transport http cobalt https://api.cobaltpf.com/api/mcpAuthenticate:
gemini
/mcp auth cobaltVerify:
/mcp listAmp CLI
Ensure Amp CLI is installed:
amp --versionAdd the Cobalt MCP server:
amp mcp add cobalt https://api.cobaltpf.com/api/mcpStart Amp and authenticate when prompted:
ampVerify:
/mcp list toolsZed
Zed talks to remote MCP servers directly from settings.json — no Rust extension required. Rust is only needed when packaging a locally-spawned MCP server as a Zed extension; Cobalt's MCP is hosted and uses OAuth, so JSON config is enough.
Open the Command Palette (Cmd+Shift+P on macOS, Ctrl+Shift+P on Windows/Linux) and run zed: open settings.
Add Cobalt under context_servers:
{
"context_servers": {
"cobalt": {
"url": "https://api.cobaltpf.com/api/mcp"
}
}
}Save the file. Zed picks up the change immediately and opens the OAuth flow in your browser — sign in with your Cobalt account.
Open the Agent Panel and confirm cobalt appears under available context servers. Cobalt's tools are now usable from any Zed agent.
Zed forwards context_servers to external agents (Claude Agent, Codex) via
ACP. Remote OAuth-based MCP servers occasionally hit forwarding issues with
external agents — if a tool call fails there, run it from Zed's built-in
Agent Panel instead.
Pi
Pi is a minimal terminal coding agent from earendil-works. Pi does not speak MCP — tools are registered as TypeScript extensions, so there is no transport for Cobalt's hosted MCP server to plug into.
Support is planned via a dedicated cobalt-pi extension that runs the OAuth2 authorization-code + PKCE flow against https://api.cobaltpf.com/api/auth/oauth2/*, caches the resulting access token, and registers cobalt.* tools that call the Cobalt API on your behalf. No tokens to paste.
Track progress on the Cobalt
GitHub. Once cobalt-pi
ships, install instructions land here.
Other clients
Any MCP client that supports Streamable HTTP transport can connect to:
https://api.cobaltpf.com/api/mcpThe server uses OAuth — your client will need to handle the browser-based authentication flow.
Local development
For local development, point to your dev server:
claude mcp add --transport http cobalt http://localhost:3000/api/mcp{
"mcpServers": {
"cobalt": {
"url": "http://localhost:3000/api/mcp"
}
}
}Use http://localhost:3000/api/mcp as the server URL in your client's MCP configuration.
Available tools
The server exposes two tools. Almost all real work happens through cobalt_execute_code.
cobalt_execute_code
Runs a JavaScript program against the cobalt.* SDK inside a sandboxed V8 isolate. Top-level await is supported. cobalt is pre-injected — do not import it.
| Parameter | Type | Description |
|---|---|---|
code | string | JavaScript source (no TypeScript syntax). Pre-injected cobalt.* |
Return value of the program is serialized back as the tool result. Use console.log for intermediate output.
Example — savings rate this month:
const txns = await cobalt.transactions.list({
startDate: "2026-05-01",
endDate: "2026-05-31",
limit: 500,
});
let spent = 0, earned = 0;
for (const t of txns.transactions) {
if (t.amount > 0) spent += t.amount;
else earned += -t.amount;
}
return { spent, earned, savingsRate: (earned - spent) / earned };cobalt_get_session_subject
Returns the Cobalt user id (sub) for the current OAuth access token. Useful for debugging auth.
Input: None
The cobalt.* SDK
All calls below are auto-scoped to the authenticated user.
Accounts
cobalt.accounts.list({ type?, subtype? })
cobalt.accounts.getById({ accountId })type is a Plaid type ("depository" | "credit" | "loan" | "investment" | "other"). subtype is the Plaid subtype ("checking" | "savings" | "mortgage" | "401k" | ...). Omit both for everything. SnapTrade brokerage data lives under cobalt.brokerage.*.
Transactions
cobalt.transactions.list({
startDate?, endDate?, primaryCategory?, accountType?,
minAmount?, maxAmount?, searchQuery?, pendingFilter?,
limit?, cursor?,
})
// returns { transactions, nextCursor, hasMore }
cobalt.transactions.update({
transactionId,
patch: { name?, date?, notes?, categoryId?, tags?,
merchantName?, website?, location? },
})Cursor-paginate by passing nextCursor back as cursor. update is patch-only — pass null for name / date / notes / categoryId / merchantName / location to restore the original Plaid value. website accepts a bare domain or full URL and is normalized to bare lowercase. patch.tags is a full replace of the tag set — for single-tag add/remove use the tag helpers below. location is a composite object: { address, city, region, postal_code, country, lat, lon, store_number }.
Tags
cobalt.tags.list()
cobalt.tags.get({ tagId })
cobalt.tags.create({ name, color }) // returns { id }
cobalt.tags.update({ tagId, patch: { name?, color?, archived? } })
cobalt.tags.forTransaction({ transactionId })
cobalt.tags.addToTransaction({ transactionId, tagIds }) // merge
cobalt.tags.removeFromTransaction({ transactionId, tagIds })
cobalt.tags.setOnTransaction({ transactionId, tagIds }) // full replace; [] clearsarchived: true is a soft delete — hidden from the picker, history preserved. Hard delete is not exposed.
Brokerage (SnapTrade)
cobalt.brokerage.balances()
cobalt.brokerage.accounts()
cobalt.brokerage.userBrokerages()
cobalt.brokerage.userTickers()
cobalt.brokerage.positions({ accountId?, limit?, offset? })
cobalt.brokerage.activities({ accountId?, limit?, offset? })
cobalt.brokerage.portfolioSnapshots({ accountId?, startDate?, endDate? })Snapshots
cobalt.snapshots.balances({ accountId?, startDate?, endDate? })Research (global market data, not user-scoped)
cobalt.research.quote({ symbol })
cobalt.research.overview({ symbol })
cobalt.research.news({ symbol })Example workflows
Once connected, try asking your AI agent:
- "What are my top 10 largest transactions this month?"
- "Show me my total balance across all bank accounts."
- "Tag every Whole Foods transaction this year as
groceries." - "What recurring subscriptions do I have and how much do they cost?"
- "Break down my spending by category for the last 3 months."
- "What's my current brokerage allocation by ticker?"
- "Compare my spending this month vs last month."
- "Rename the merchant on transaction
txn_…toCostco Wholesale."
Help your agent target your data
Add a hint to your CLAUDE.md / .cursorrules:
## Cobalt MCP
- Use `cobalt_execute_code` — write one program, not many tool calls.
- The `cobalt.*` SDK is pre-injected; do not import it.
- `cobalt.transactions.list` returns `{ transactions, nextCursor, hasMore }`. Paginate with `cursor: nextCursor`.
- `cobalt.transactions.update.patch.tags` is a full replace — use `cobalt.tags.addToTransaction` / `removeFromTransaction` for single-tag edits.Troubleshooting
Restart your client — After installation, you may need to restart your client or CLI to load the new MCP configuration.
Check the server URL — https://api.cobaltpf.com/api/mcp.
Re-authenticate — If your token has expired, re-authenticate by restarting the MCP connection in your client.
Code errored — The tool result contains error (with name + message) and any stdout your program produced before the throw. console.log your intermediate values to debug.
Empty results — All cobalt.* calls are scoped to your user. Verify you actually have data linked to your Cobalt account.
Timeout — Execution is capped at 180 seconds. Narrow your startDate / endDate window, lower limit, or paginate with cursor.
userId rejected — Sandboxed code cannot pass a userId to cobalt.*. The subject is injected server-side.
TypeScript SDK
Use the @cobalt-money/sdk to call the Cobalt API from Node, Bun, Deno, or the browser.
List brokerage accounts GET
All brokerage-shaped accounts (SnapTrade, Plaid investment, manual investment). Inspect `source` on each item to distinguish. SnapTrade-only fields (`snaptradeAuthorizationId`, `needsReauth`) are null/false for non-SnapTrade rows.