An MCP server that scores a real charge
Assistants are good at recalling that a card earns 4x on dining and bad at knowing whether that beats 2% flat once caps, foreign fees and point values are applied. This server does that arithmetic — over 289 cards, with the same scoring function the app runs on real statements.
Read-only. No API key, no account, no user data reachable from it.
Add it to a client
Any MCP client that speaks Streamable HTTP. In Claude Code:
claude mcp add --transport http credit-upside https://creditupside.com/mcpOr, in a client configured by JSON:
{
"mcpServers": {
"credit-upside": {
"type": "http",
"url": "https://creditupside.com/mcp"
}
}
}The four tools
score_charge
The one that matters. Rank cards by what a single charge actually earns, net of the foreign‑transaction fee. Category umbrellas, rotating quarters, choose‑your‑category slots, spend caps and brand‑gated lines all behave exactly as they do in the product.
best_card_for_category
Rank the whole catalog for one spend category — the “what should I get for groceries” question — with an optional annual‑fee ceiling.
catalog.search
Find cards by name, issuer, network, reward currency or annual fee, and get the card ids the scoring tools take.
point_value
The cents‑per‑point valuations behind every dollar figure the server returns. How we set them.
Try it without a client
It is one HTTP POST. This is the legacy handshake shape, which is what most clients still send:
curl -s https://creditupside.com/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"name":"score_charge",
"arguments":{"amount":180,"category":"dining",
"cardIds":["amex_gold","citi_double_cash","capone_savor"]}}}'Three things worth knowing
Pass the merchant when you have one
A co‑brand’s headline rate — “14x at Hilton”, “5% at Amazon” — is not a hotel rate or a shopping rate. It is a rate at one merchant. Without a merchant argument those lines are skipped, so a co‑brand card is under‑counted rather than flattered. Pass one and they apply — and every gated rate comes back flagged gated: true with the reason, so an assistant can repeat the condition instead of quoting a number the user can’t actually get.
The dollar figures are estimates
Points are converted at our own published valuations. They are deliberately conservative, and they are estimates, not prices. Spend caps assume nothing has been spent against them yet this period, so a capped bonus rate is a best case. Every response says so in its assumptions field.
Nothing here is paid placement
No link in this data pays us anything today, and the ranking code has no access to which cards would. The server returns what the earn rules say, in the order the arithmetic puts them. Our disclosure.
Protocol notes
Streamable HTTP on a single endpoint. The server is dual‑era: it serves the current 2026‑07‑28 revision — stateless, with the protocol version, client identity and capabilities carried in per‑request _meta and mirrored into headers — and it still answers the initialize handshake used by 2025‑11‑25 and earlier, which is what most shipping clients speak today. server/discover lists everything it supports.
Responses are application/json; there is no SSE stream, because none of these tools is slow enough to need one. GET on this URL is the page you are reading. Rate limit: 120 calls a minute per IP.
The card data behind it is the same file the app downloads — card-benefits.master.json — so you can also just read it yourself if a tool call is more ceremony than you need. Found a wrong rate? Tell us; corrections ship without a deploy.