Ledgewise MCP reference
A read-only Model Context Protocol server exposing a user's Ledgewise budget, transactions, spending insights, cash-flow forecast, debt accounts, payoff plan and what-if scenarios to AI assistants such as Meta Muse, with the user's explicit OAuth consent.
Overview
| Item | Value |
|---|---|
| MCP endpoint | POST https://mcp.ledgewise.app/mcp |
| Transport | Streamable HTTP, single JSON response per POST (no SSE stream). GET returns 405. |
| Protocol versions | 2026-07-28, 2025-06-18, 2025-03-26, 2024-11-05 (the requested version is echoed if supported) |
| Capabilities | tools only. No resources, prompts or sampling. |
| Authorization server | https://auth.ledgewise.app |
| Access | Read-only, with one exception: ledgewise_request_payment_instructions records a request that the user must confirm in the Ledgewise app. No tool changes the user's budget or debts, and no tool moves money. Reading a tool never writes to the user's data. |
| Tiers | 4 tools are free; 10 require a Ledgewise Premium subscription (see Tools). |
| Currency / units | USD. All amounts are integer cents. Positive transaction amounts are spend; negative are income. |
Authentication
OAuth 2.1 authorization code flow with PKCE (S256 only) and RFC 8707 resource indicators. API keys are not supported. Every request to /mcp needs Authorization: Bearer <access_token>.
Discovery
| Document | URL |
|---|---|
| Protected resource metadata (RFC 9728) | https://mcp.ledgewise.app/.well-known/oauth-protected-resource |
| Authorization server metadata (RFC 8414) | https://auth.ledgewise.app/.well-known/oauth-authorization-server |
An unauthenticated or invalid request returns 401 with WWW-Authenticate: Bearer resource_metadata="https://mcp.ledgewise.app/.well-known/oauth-protected-resource".
Endpoints
| Purpose | URL |
|---|---|
| Authorization | GET https://auth.ledgewise.app/authorize |
| Token | POST https://auth.ledgewise.app/token (form-encoded) |
| Revocation | POST https://auth.ledgewise.app/revoke |
Flow
- Client sends the user to
/authorizewithresponse_type=code,client_id, a pre-registeredredirect_uri,scope,state,code_challenge(S256),code_challenge_method=S256andresource=https://mcp.ledgewise.app. - The user signs in to Ledgewise and approves the consent screen, which lists each requested permission.
- Ledgewise redirects to
redirect_uriwithcodeandstate(orerror=access_denied). - Client POSTs to
/tokenwithgrant_type=authorization_code,code,redirect_uri,client_id,code_verifierand the sameresource.
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "budget:read forecast:read transactions:read insights:read debt:read"
}
Token lifetimes and rules
- Access tokens last 1 hour; refresh tokens last 30 days (
grant_type=refresh_token). Reuse of a revoked refresh token is detected. - Strict audience: a token must have been issued for
resource=https://mcp.ledgewise.app. Tokens without that resource are rejected by/mcpwith 401. - Public clients authenticate with PKCE only (
token_endpoint_auth_method: none). Confidential clients may useclient_secret_basicorclient_secret_post. - Redirect URIs are matched exactly against the client's registered list. Dynamic client registration is not offered; clients are pre-registered.
- Users can revoke access at any time in Ledgewise under Settings → Accounts.
Scopes
| Scope | Grants | Tool(s) |
|---|---|---|
budget:read | Current month budget and safe-to-spend figure | ledgewise_get_budget_summary, ledgewise_get_safe_to_spend |
transactions:read | Recent transactions | ledgewise_get_transactions |
insights:read | Month-over-month spending insights and spending patterns (pay-cycle timing) | ledgewise_get_insights, ledgewise_get_spending_patterns |
forecast:read | Projected balance | ledgewise_get_forecast |
debt:read | Debt list, payoff plan, what-if scenarios, payoff changes (including income changes), pace and monthly commitment totals | ledgewise_get_debt_accounts, ledgewise_get_debt_payoff_plan, ledgewise_simulate_scenario, ledgewise_get_payoff_changes, ledgewise_get_payoff_pace, ledgewise_get_monthly_commitment |
payments:instruction | Ask to see this month's payment instructions; the user confirms each request in the Ledgewise app first and can stop sharing. Held only by clients Ledgewise has approved: never a generic custom connector or a client that identifies itself by metadata URL. | ledgewise_request_payment_instructions, ledgewise_get_payment_instructions |
tools/list returns only the tools whose scope the token holds. The consent screen requests the client's full registered scope set; users approve or deny it as a whole.
Protocol
JSON-RPC 2.0 over POST /mcp. Supported methods: initialize, tools/list, tools/call.
POST /mcp
Authorization: Bearer <access_token>
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}
{"jsonrpc":"2.0","id":1,"result":{
"protocolVersion":"2025-06-18",
"capabilities":{"tools":{}},
"serverInfo":{"name":"ledgewise-mcp","version":"1.0.0","url":"https://mcp.ledgewise.app"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
"name":"ledgewise_get_transactions","arguments":{"limit":5}}}
Tool results are returned as a single text content item containing a JSON string, with isError: false.
Tools
Amounts are integer cents. "Premium" tools return a tool error (see Errors) for free accounts.
ledgewise_get_budget_summary Free
Current month's budget: income, total assigned, ready-to-assign, and a per-envelope breakdown. Scope: budget:read. Works with manual data; no bank link required.
Input: none.
{"month":"September 2026","income_cents":480000,"total_assigned_cents":253000,
"ready_to_assign_cents":227000,"total_spent_cents":61200,"currency":"USD",
"envelopes":[{"name":"Groceries","budgeted_cents":45000,"spent_cents":18400,
"available_cents":26600,"children":[]}]}
ledgewise_get_transactions Free
The user's most recent transactions. Scope: transactions:read.
Input: limit (integer 1–100, default 20).
{"transactions":[{"date":"2026-09-28","merchant_name":"Trader Joe's","description":"Trader Joe's",
"amount_cents":8420,"category_id":"…","is_reviewed":true}],
"unreviewed_count":7,"currency":"USD"}
ledgewise_get_insights Free
Month-over-month spending comparison and a pace alert when spending is off-trend. Scope: insights:read.
Input: none.
{"current_month":{"year":2026,"month":9,"spent_cents":61200},
"previous_month":{"year":2026,"month":8,"spent_cents":224000},
"delta_pct":-72.7,"pace_alert":null,"currency":"USD"}
ledgewise_get_forecast Premium
Projects the user's checking and savings balance to a target date, up to 90 days out, using recurring income, bills and debt minimums plus recent net spending. Scope: forecast:read.
Input: target_date (string, YYYY-MM-DD, today to +90 days; default +30 days).
{"requested_date":"2026-10-30","projected_balance_cents":298500,"days_until":30,
"starting_balance_cents":474000,"currency":"USD","status":"ok"}
If no checking or savings balance has synced yet, projected_balance_cents and starting_balance_cents are null and status is "no_balance_data".
ledgewise_get_safe_to_spend Premium
The amount the user can spend today without running short over the next 60 days, after upcoming bills and debt minimums. Scope: budget:read.
Input: none.
{"safe_to_spend_cents":112000,"per_day_cents":1866,"current_balance_cents":474000,
"debt_minimums_cents":29500,"is_danger":false,"shortfall_cents":0,
"horizon_days":60,"status":"ok","has_income_data":true}
ledgewise_get_debt_accounts Free
The user's active debts (credit cards, loans, mortgages), largest balance first. Use the ids with the payoff and scenario tools. Scope: debt:read.
Input: none.
{"accounts":[{"id":"…","name":"Visa Platinum","creditor":"Chase","account_type":"credit_card",
"balance_cents":500000,"apr_bps":2400,"minimum_payment_cents":15000,"due_day":15,
"imported_from_bank":true,"apr_needs_review":false}],
"count":1,"total_balance_cents":500000,"total_minimum_payment_cents":15000,"currency":"USD"}
apr_bps is the annual rate in basis points (2400 = 24%). apr_needs_review is true for a bank-imported debt at 0% that the user has not confirmed, since banks that report no rate import as 0% and that makes payoff look faster than it is. Bank account and provider ids are never returned.
ledgewise_get_debt_payoff_plan Premium
A payoff plan over the user's active debts using avalanche (highest APR first) or snowball (lowest balance first). Scope: debt:read.
Input:
| Field | Type | Notes |
|---|---|---|
strategy | string | avalanche (default) or snowball |
monthly_extra_cents | integer | 0–100,000,000. Extra monthly payment above minimums. Default 0. |
{"strategy":"avalanche","monthly_extra_cents":20000,
"ordered_debt_ids":["…","…","…"],"payoff_date":"2029-04-01",
"total_interest_cents":318400,"interest_saved_cents":204100,
"monthly_schedule":[…first 36 months…],"never_pays_off":false,"problem_debt_ids":[]}
If a debt can't be paid off at its minimum, never_pays_off is true and payoff_date is null.
ledgewise_simulate_scenario Premium
A what-if for debt payoff. Compares the user's current plan with a different monthly extra and/or a one-time lump sum, and reports months sooner and interest saved. Read-only: it never moves money or changes a plan. Scope: debt:read.
Input: at least one of monthly_extra_cents (different from the baseline) or lump_sum_cents (above 0) is required.
| Field | Type | Notes |
|---|---|---|
strategy | string | avalanche (default) or snowball |
baseline_extra_cents | integer | 0–100,000,000. Extra per month the user pays today. Default 0. |
monthly_extra_cents | integer | 0–100,000,000. Extra per month in the scenario. Defaults to the baseline. |
lump_sum_cents | integer | 0–100,000,000,000. One-time payment now, applied in payoff order. Default 0. Anything above the total owed is not applied. |
{"strategy":"avalanche","ordered_debt_ids":["…","…"],
"baseline":{"monthly_extra_cents":0,"never_pays_off":false,"payoff_date":"2031-06-01","months":56,"total_interest_cents":507829},
"scenario":{"monthly_extra_cents":0,"lump_sum_cents":300000,"lump_sum_applied_cents":300000,
"never_pays_off":false,"payoff_date":"2030-02-01","months":40,"total_interest_cents":173832},
"months_sooner":16,"interest_saved_cents":333997,"scenario_makes_payoff_possible":false,
"has_debts":true,"focus_order":[{"id":"…","name":"Visa Platinum"},{"id":"…","name":"Car loan"}],"currency":"USD"}
months_sooner and interest_saved_cents are baseline minus scenario (negative means the scenario is worse) and are null unless both plans pay everything off. scenario_makes_payoff_possible is true when the current plan never pays off but the scenario does. A debt cleared by the lump sum still rolls its minimum onto the next debt. If the lump sum clears every debt, months is 0, total_interest_cents is 0 and payoff_date is today. With no active debts the result is {"strategy":…,"has_debts":false,"currency":"USD"}.
ledgewise_get_payoff_changes Premium
What changed since the user last looked at their payoff plan (a new debt, rising balances, a rate or income change) and what it did to the debt-free date, with ready-made explanations. Totals only, no debt names. Scope: debt:read.
Input: none.
{"has_baseline":true,"changed":true,"months_delta":3,
"baseline":{"date":"2026-09-01","payoff_date":"2029-03-01","never_pays_off":false,"total_balance_cents":500000},
"current":{"payoff_date":"2029-06-01","never_pays_off":false,"total_balance_cents":650000,"strategy":"avalanche","monthly_extra_cents":10000},
"events":[{"type":"balances_rose","amount_cents":150000}],
"explanations":["Your debt-free date moved 3 months later, to Jun 2029.","Balances rose by $1,500 (new charges or interest)."],"currency":"USD"}
has_baseline is false until the user viewed a payoff plan 7 to 120 days ago.
ledgewise_get_payoff_pace Premium
How fast the user is paying down debt compared with other Ledgewise users. Only available once the comparison group is large enough to be anonymous. Scope: debt:read.
Input: none.
{"available":true,"your_pct_per_90d":12.0,"cohort_median_pct_per_90d":9.0,"percentile":70,"cohort_min":50}
{"available":false,"reason":"cohort_too_small"}
ledgewise_get_spending_patterns Premium
When the user tends to overspend (the week after payday, weekends), roughly how much extra, and whether today is inside such a window. Pattern types and amounts only; no merchants or categories. Scope: insights:read.
Input: none.
{"has_enough_data":true,"patterns":[{"type":"payday_spike","ratio":2.7,"extra_cents":26688,"window_days":7,"cycles":8}],
"risk":{"type":"payday_spike","days_left":5},"analyzed_days":120,"pay_interval_days":14,"currency":"USD"}
ledgewise_get_monthly_commitment Premium
The user's standing monthly payoff commitment and where this month stands: the reminder day, the status and the totals. Read-only, and it saves nothing. The per-debt lines (who to pay, how much, by when) are payment instructions and are not included: ask for them with ledgewise_request_payment_instructions. Scope: debt:read.
Input: none.
{"committed":true,"month":"2026-10","day_of_month":5,"reminders":true,"strategy":"avalanche","monthly_extra_cents":20000,
"due_date":"2026-10-05","status":"due","total_cents":40000,"remaining_cents":40000,"debts_on_checklist":2,"currency":"USD"}
status is upcoming, due, done, skipped or nothing_due. {"committed":false} when there is no commitment.
ledgewise_request_payment_instructions Premium
Ask to see the user's payment instructions (who to pay, how much, by when). The user must confirm the request in the Ledgewise app first, so this returns input_required. Asking again while a request is open returns the same one (and does not notify the user again); at most 5 new requests per hour, and none for an hour after the user declines or stops sharing. A request waits 30 minutes for the user's answer. Ledgewise never moves money. Scope: payments:instruction (held only by clients Ledgewise has approved).
Input: none. The calling app and, if sent, the Mcp-Session-Id header are recorded with the request, along with when the user answered and how many times the answer was read. Records are removed after 90 days. The confirmation gates the payment instructions (what to pay each debt this month and by when); it does not hide the debt list or payoff plan from a client that holds debt:read.
{"status":"input_required","request_id":"6f1c…","expires_at":"2026-10-06T15:30:00+00:00",
"message":"Waiting for the user to confirm this request in the Ledgewise app. …"}
ledgewise_get_payment_instructions Premium
Read the answer to a request. Returns input_required until the user confirms, then the instructions for their approved plan, readable for 10 minutes from the confirmation (the user can stop sharing sooner); a tool error if they declined, stopped sharing or the request expired. Only the app that made the request can read it. Scope: payments:instruction.
Input: request_id (string, required).
{"status":"confirmed","strategy":"avalanche","monthly_extra_cents":20000,"never_pays_off":false,"total_cents":40000,
"instructions":[{"debt_id":"…","name":"Visa","creditor":"Chase","amount_cents":35000,"minimum_cents":15000,"extra_cents":20000,
"due_date":"2026-10-15","no_payment_planned":false,"paid_this_month_cents":0}],
"currency":"USD","note":"Instruction only. …"}
Errors
| Condition | Response |
|---|---|
| Missing/invalid/expired token, or wrong audience | HTTP 401 + WWW-Authenticate header |
| Rate limit exceeded | HTTP 429 |
Malformed JSON, non-object body, bad jsonrpc or method | HTTP 400, JSON-RPC error -32700 / -32600 |
| Unknown method or tool | JSON-RPC error -32601 |
Bad params shape | JSON-RPC error -32602 |
| Token lacks the tool's scope | Tool error: Missing required scope: <scope> |
| Free account calling a Premium tool | Tool error: Ledgewise Premium subscription required for this tool |
Invalid tool arguments (e.g. limit: 500) | Tool error describing the constraint |
| Unexpected server error | Tool error: An unexpected error occurred. Please try again. |
A "tool error" is a normal JSON-RPC result with isError: true and a text content item:
{"jsonrpc":"2.0","id":3,"result":{
"content":[{"type":"text","text":"Ledgewise Premium subscription required for this tool"}],
"isError":true}}
Limits
- 60 requests per minute per access token; additionally 120 requests per minute per client IP on
/mcp. /authorize: 30/min per IP./token: 20/min per IP.ledgewise_get_transactionsreturns at most 100 rows; the forecast horizon is at most 90 days; the payoff schedule returns at most 36 months.
Testing
Reviewer accounts with sample data (one Free, one Premium) are provided in the submission notes. Sign in with them on the Ledgewise consent page when prompted. To verify the server without OAuth, an unauthenticated call should return 401:
curl -i -X POST https://mcp.ledgewise.app/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
With a valid token, tools/list returns the tools in the table above. A Premium account sees and can call all eight; a Free account sees all eight but the four Premium tools return the subscription error.
Data & privacy
- Tools read only the authenticated user's own rows; every query is scoped to the user ID bound to the token.
- Returned data: budget figures and envelope names, transaction date/merchant/description/amount/category id, balances (cash accounts), debt names, creditors, account types, balances, APRs, minimum payments and due days, and payoff figures (ordered debt ids, interest, dates). Bank credentials, account numbers and access tokens are never exposed.
- The connector never changes your budget data and never moves money. It keeps an activity log of each tool call (which app, which tool, when, how long it took, and whether it succeeded or was blocked) with no arguments and no figures; you can see it under Settings, and entries are deleted after 90 days. Nothing is shared with third parties by the connector. See the privacy policy and terms.
- Support and security contact: founder@tenderlab.studio.