Finature MCP server
Overview
Finature runs a remote MCP server so an AI agent can ask which of your cards to use and, with your approval, make a purchase. The agent never sees card numbers and cannot approve its own purchases. Every decision records its inputs and versions, so it can be reproduced later.
Endpoint. /mcp/ on the server that serves this page, Streamable HTTP.
Status. Team preview. Only people with the team access code can connect an agent. Purchases run on Stripe Issuing test mode or the simulator; no real money moves.
The demo wallet uses five real cards and their published earning rules as of October 2026. Merchants, card numbers and offers are fictional. Rates change, so check with the issuer.
Prefer a short walkthrough? See the Quickstart.
Connect a client
Every client needs the endpoint URL and your personal token, sent as Authorization: Bearer <token>. Get the token in the dashboard under Agents, Connect your own agent; see Authentication. The dashboard also shows each snippet below with your token filled in.
Claude Code
claude mcp add --transport http finature /mcp/ --header "Authorization: Bearer YOUR_TOKEN"
Claude Desktop
Claude Desktop reaches remote servers with a header through mcp-remote. Add this to claude_desktop_config.json, then restart Claude Desktop. Custom connectors in claude.ai need OAuth sign-in, which isn't available yet.
{
"mcpServers": {
"finature": {
"command": "npx",
"args": ["-y", "mcp-remote", "/mcp/",
"--header", "Authorization:${FINATURE_AUTH}"],
"env": { "FINATURE_AUTH": "Bearer YOUR_TOKEN" }
}
}
}
Cursor
Add this to ~/.cursor/mcp.json:
{
"mcpServers": {
"finature": {
"url": "/mcp/",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
VS Code
Add this to .vscode/mcp.json in your workspace:
{
"servers": {
"finature": {
"type": "http",
"url": "/mcp/",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
Strands Agents (Python)
Pass your token as a bearer header. The agent lists the server's tools and calls them like any other tool.
from strands import Agent
from strands.tools.mcp import MCPClient
from mcp.client.streamable_http import streamablehttp_client
finature = MCPClient(lambda: streamablehttp_client(
"/mcp/",
headers={"Authorization": f"Bearer {token}"},
))
with finature:
agent = Agent(tools=finature.list_tools_sync())
agent("Buy Studio Buds from Demo Electronics. Ask before paying.")
Any other client
Any MCP client that supports Streamable HTTP and custom headers can connect. Point it at the endpoint above and send your token in the Authorization header.
Authentication
Team preview: agents connect with a personal token. A signed-in user who has the team's access code creates one in the dashboard (Agents, Connect your own agent) and names it after the agent. The token is shown once. OAuth sign-in, for claude.ai connectors and clients without custom headers, comes later.
- A token acts only on the account that created it, lasts 30 days, and can be revoked in the dashboard at any time.
- Purchases prepared with a token always wait for approval in the dashboard. The server's own agent has a small standing allowance; tokens have none.
- A token can make up to 30 tool calls a minute.
- Tokens are issued for this server. Every request validates the token's audience.
- The client's access token is never forwarded to a payment provider. Finature uses its own separate provider credentials.
- The user identity comes from the token. No tool accepts a user ID argument, so an agent cannot act for a different user by changing a parameter.
- Each token is bound to one user. Revoked and expired tokens are refused on the next request.
Scopes
Each token carries scopes. A tool call returns SCOPE_DENIED when the token lacks the scope that the tool needs.
| Scope | Allows |
|---|---|
| wallet:read | Read the card summaries and policy summary from wallet_summary. Cards appear as opaque references. |
| decisions:read | Run recommend_payment and read stored decisions with explain_decision. Reads only; nothing is reserved. |
| purchases:prepare | Prepare purchase intents, read their status and receipts, and cancel an intent before submission. Preparing reserves limits for a short time and charges nothing. |
| purchases:execute | Call execute_purchase for an intent that is approved or covered by a valid mandate. |
| catalog:read | Search the demo store with search_catalog and get a server quote with get_quote. |
| purchases:refund | Refund an approved purchase with refund_purchase, once. Refunds return to the original card. |
| dining:reserve | Find restaurants near the user and book or cancel a table (demo reservations). |
| cards:freeze | List the user's Finature cards and freeze one. |
purchases:execute lets the agent call execute_purchase. It does not approve anything by itself. Execution still needs an approval record from the trusted app, or a valid mandate.
Approvals and mandates
By default, every purchase an agent prepares needs your approval. You give it in the Finature app, which is the trusted surface. An approval cannot be passed as a tool argument, and an agent cannot approve its own purchase. The app shows the merchant, item, final total, delivery, selected card and estimated reward. Nothing is charged until you approve.
A mandate is an optional standing permission that you create in the app. A purchase that falls inside a valid mandate can proceed without a separate approval. Each mandate is bound to one user and one client, and you can revoke it at any time.
Mandate fields
| Field | Meaning |
|---|---|
| Merchant allowlist | The merchants the mandate covers. Any other merchant needs approval. |
| Category | The purchase categories the mandate covers. |
| Per-purchase amount and currency | The largest single purchase allowed, in one currency. |
| Daily and total spend | Limits across all purchases under the mandate, per day and overall. |
| Expiry | The date and time after which the mandate no longer applies. |
| Permitted cards | The set of cards the mandate may use. |
| Recurring purchases | Allowed or not allowed. Recurring purchases run only when this is set to allowed. |
| Bound user and client | The user and the client the mandate applies to. |
Revocation takes effect before any later submission. It does not reverse a payment that was already submitted. That payment is reconciled and, where the rail supports it, cancelled.
What an approval binds
An approval covers one exact purchase. It binds:
- the merchant
- the quote and cart hash
- the exact amount and currency
- the selected card
- the delivery terms
- the policy version
- the expiry. An intent expires two minutes after it is prepared, or earlier if the merchant quote expires first. The approval expires with the intent.
If the price or the card changes, the approval no longer applies. The agent must prepare a new intent, and you must approve that one. Finature never changes the card or the price after approval.
Tools
The server exposes twenty tools; your client lists them after it connects. The ones below with a section of their own are the purchase loop. Each is a thin wrapper over the same service operations the REST API uses, so both paths share permission checks, error codes and idempotency rules. Tools take no user ID. Unknown tools and unknown fields are rejected, not treated as alternative instructions.
| Tool | Scope | Side effect |
|---|---|---|
wallet_summary | wallet:read | Read only |
search_catalog | catalog:read | Read only |
get_quote | catalog:read | Read only |
recommend_payment | decisions:read | Read only |
explain_decision | decisions:read | Read only |
prepare_purchase | purchases:prepare | Reserves limits |
get_purchase_status | purchases:prepare | Read only |
execute_purchase | purchases:execute | Charges once |
cancel_purchase | purchases:prepare | Cancels |
get_receipt | purchases:prepare | Read only |
list_open_orders | purchases:prepare | Read only |
list_purchases | purchases:prepare | Read only |
refund_purchase | purchases:refund | Starts refund |
list_finature_cards | cards:freeze | Read only |
freeze_finature_card | cards:freeze | Freezes card |
find_restaurants | dining:reserve | Read only |
reserve_table | dining:reserve | Books (demo) |
list_reservations | dining:reserve | Read only |
cancel_reservation | dining:reserve | Cancels |
search_card_database | wallet:read | Read only |
wallet_summary
Returns the cards the user can pay with, as opaque references, and the current policy version. Read only.
Takes no arguments. The user comes from the token.
The response lists each card with its reference, nickname, whether it is the default, and its status. Card numbers never appear.
{
"name": "wallet_summary",
"arguments": {}
}
{
"cards": [
{ "card_ref": "card_amex_gold", "nickname": "Amex Gold", "is_default": false, "status": "ACTIVE" },
{ "card_ref": "card_savor", "nickname": "Capital One Savor", "is_default": false, "status": "PAUSED" },
{ "card_ref": "card_bce", "nickname": "Amex Blue Cash Everyday", "is_default": false, "status": "ACTIVE" },
{ "card_ref": "card_quicksilver", "nickname": "Capital One Quicksilver", "is_default": true, "status": "ACTIVE" },
{ "card_ref": "card_chrome", "nickname": "Discover it Chrome", "is_default": false, "status": "ACTIVE" }
],
"policy_version": 3
}
recommend_payment
Ranks the user's cards for a verified checkout quote and returns the best one with its estimated value. Read only. It reserves no spending capacity, and its result is not permission to pay.
| Name | Type | Required | Description |
|---|---|---|---|
| quote_id | string | Yes | A checkout quote from the merchant, verified by the server. |
| decision_id | string | No | Links this evaluation to an earlier decision. |
The response is a decision. The full object is described in Decision object.
{
"name": "recommend_payment",
"arguments": { "quote_id": "q_7Hk2" }
}
{
"decision_id": "dec_9f31",
"selected_card_ref": "card_bce",
"amount_minor": 16499,
"currency": "USD",
"estimated_reward_minor": 1495,
"estimated_fee_minor": 0,
"estimated_net_value_minor": 1495,
"alternative": { "card_ref": "card_quicksilver", "estimated_net_value_minor": 247 },
"reason_codes": ["CATEGORY_BONUS", "OFFER_APPLIED"],
"is_payment_authorization": false
}
explain_decision
Returns a stored decision and the reasons behind it. Read only.
| Name | Type | Required | Description |
|---|---|---|---|
| decision_id | string | Yes | A decision owned by the same user and client. |
A decision that belongs to another user is denied, and its contents are not returned.
{
"name": "explain_decision",
"arguments": { "decision_id": "dec_9f31" }
}
{
"decision_id": "dec_9f31",
"selected_card_ref": "card_bce",
"reason_codes": ["CATEGORY_BONUS", "OFFER_APPLIED"],
"explanation": "Blue Cash Everyday earns 3% on U.S. online retail, and your activated $10 Amex Offer at Demo Electronics adds to it. Capital One Quicksilver would earn $2.47.",
"catalog_version": "demo-1",
"policy_version": 3,
"engine_version": "1.0.0"
}
prepare_purchase
Creates a purchase intent from a verified quote, using the decision that chose the card. It reserves limits for the intent's short lifetime and charges nothing. The status is AWAITING_APPROVAL, or APPROVED when a valid mandate already covers the purchase.
| Name | Type | Required | Description |
|---|---|---|---|
| quote_id | string | Yes | A checkout quote, verified by the server. |
| decision_id | string | No | A decision from recommend_payment. If omitted, the server evaluates the quote itself. Either way the intent binds the selected card and amount. |
The response includes expires_at. After that time the intent cannot be approved or executed, and a new one must be prepared. The app shows the purchase to the user. The tool never returns an approval.
{
"name": "prepare_purchase",
"arguments": {
"quote_id": "q_7Hk2",
"decision_id": "dec_9f31"
}
}
{
"intent_id": "pi_4Ld8",
"status": "AWAITING_APPROVAL",
"merchant": "Demo Electronics",
"total_minor": 16499,
"currency": "USD",
"selected_card_ref": "card_bce",
"rail": "simulator",
"expires_at": "2026-10-10T18:02:00Z"
}
get_purchase_status
Returns the authoritative state of an intent, its payment attempt and its order. The three states are reported separately. Read only. Poll this tool after PAYMENT_PENDING or SUBMISSION_UNKNOWN.
| Name | Type | Required | Description |
|---|---|---|---|
| intent_id | string | Yes | The intent to check. |
{
"name": "get_purchase_status",
"arguments": { "intent_id": "pi_4Ld8" }
}
{
"intent_id": "pi_4Ld8",
"intent_status": "COMPLETED",
"payment_status": "CAPTURED",
"order_status": "ACCEPTED",
"payment_attempts": 1,
"rail": "simulator"
}
execute_purchase
Executes an approved intent. It checks authority, then makes one payment attempt and returns the resulting status. This is the only tool that charges a card. Before submitting, the server rechecks the policy, amount, card status, budgets, reservations and approval, and any change stops the call with an error and no charge.
| Name | Type | Required | Description |
|---|---|---|---|
| intent_id | string | Yes | The approved intent to execute. |
| idempotency_key | string | Yes | Unique per operation. Reuse the same key when retrying the same request. See Idempotency and retries. |
The status is COMPLETED, PROCESSING or REQUIRES_ACTION, or the call returns an error code from Errors.
{
"name": "execute_purchase",
"arguments": {
"intent_id": "pi_4Ld8",
"idempotency_key": "k_6f0c2a"
}
}
{
"intent_id": "pi_4Ld8",
"status": "COMPLETED",
"payment_status": "CAPTURED",
"order_id": "ord_5521",
"order_status": "ACCEPTED",
"selected_card_ref": "card_bce",
"total_minor": 16499,
"currency": "USD",
"rail": "simulator"
}
cancel_purchase
Cancels an intent. Before submission, the cancellation is complete and nothing is charged. After submission, the server requests the rail's void where the rail supports one. A void cannot be promised, so check get_purchase_status for the outcome.
| Name | Type | Required | Description |
|---|---|---|---|
| intent_id | string | Yes | The intent to cancel. |
| idempotency_key | string | Yes | Unique per operation. Reuse it when retrying the same cancellation. |
{
"name": "cancel_purchase",
"arguments": {
"intent_id": "pi_4Ld8",
"idempotency_key": "k_c41d09"
}
}
{
"intent_id": "pi_4Ld8",
"status": "CANCELLED",
"rail": "simulator"
}
get_receipt
Returns the confirmed order and payment details for an intent: the selected card, the exact total, the approval status, and both the estimated and the observed reward. Read only. The observed reward is null until it has been observed.
| Name | Type | Required | Description |
|---|---|---|---|
| intent_id | string | Yes | The intent whose receipt to return. |
{
"name": "get_receipt",
"arguments": { "intent_id": "pi_4Ld8" }
}
{
"intent_id": "pi_4Ld8",
"order_id": "ord_5521",
"merchant": "Demo Electronics",
"selected_card_ref": "card_bce",
"total_minor": 16499,
"currency": "USD",
"approval_status": "APPROVED",
"estimated_reward_minor": 1495,
"observed_reward_minor": null,
"rail": "simulator"
}
refund_purchase
Refunds an approved purchase in full, back to the card it was paid with. Call it only when the user asked for that refund. Safe to repeat: a purchase is refunded at most once. Find the purchase with list_purchases.
| Name | Type | Required | Description |
|---|---|---|---|
| purchase_id | string | Yes | The purchase to refund, from list_purchases. |
{
"name": "refund_purchase",
"arguments": { "purchase_id": "pur_7f3a91c2" }
}
Purchase lifecycle
Three state machines are kept apart: the intent (the purchase and your approval), the payment attempt (the charge on the card), and the order (the merchant's acceptance). Each has its own states, and each is reported separately.
Intent:
PREPARED -> AWAITING_APPROVAL -> APPROVED -> EXECUTING -> COMPLETED
PREPARED -> APPROVED (valid existing mandate)
PREPARED / AWAITING_APPROVAL / APPROVED -> EXPIRED or CANCELLED
EXECUTING -> REQUIRES_ACTION or PROCESSING or FAILED
Payment attempt:
CREATED -> SUBMITTING -> AUTHORIZED -> CAPTURED
SUBMITTING -> SUBMISSION_UNKNOWN -> reconciled rail state
SUBMITTING -> DECLINED
AUTHORIZED -> VOIDED or PARTIALLY_CAPTURED or CAPTURED
CAPTURED -> PARTIALLY_REFUNDED -> REFUNDED
Order:
QUOTED -> SUBMITTING -> ACCEPTED -> FULFILLED
SUBMITTING -> UNKNOWN or REJECTED
COMPLETED means the payment was captured and the merchant accepted the order. Shipping can still be pending after that. A payment can succeed while the order fails, and the server shows both facts so they can be reconciled.
Unknown states are first-class. SUBMISSION_UNKNOWN and UNKNOWN are not shown as success or failure until the rail or merchant confirms what happened.
Idempotency and retries
execute_purchase and cancel_purchase take an idempotency_key. The server stores each key with a hash of its request, scoped to the user, client and operation.
- Same key, same payload: the server returns the original operation. Nothing new starts.
- Same key, different payload: the call fails with
IDEMPOTENCY_CONFLICT. Reuse the original payload with that key, or start a new intent with a new key. - One execution attempt per intent: changing the key does not create a second charge for an intent that has already been executed.
- No double reservation: execution uses the limits reserved at preparation instead of reserving them again.
After SUBMISSION_UNKNOWN or a timeout, call get_purchase_status and wait for the rail's answer. Never retry an ambiguous charge on a different card. If a merchant may have accepted an order before a timeout, reconcile that order before submitting again.
For read tools, follow the retry guidance in the error response. For anything that changes state, reconcile first, then decide whether a retry is needed.
Errors
Errors return a stable code, a safe message, and any structured fields that explain the failure. The table gives the action the caller should take for each code.
| Code | What the caller should do |
|---|---|
| AUTH_REQUIRED | Reauthenticate or request the permitted scope. |
| SCOPE_DENIED | Reauthenticate or request the permitted scope. |
| QUOTE_EXPIRED | Obtain a new quote, then prepare again. |
| POLICY_CHANGED | Obtain a new quote, then prepare again under the current policy. |
| APPROVAL_REQUIRED | Open the trusted approval surface in the app and wait for the user. |
| NO_ELIGIBLE_CARD | Explain which cards were excluded and why. Do not make a charge. |
| BUDGET_EXCEEDED | Stop, or ask the user for an explicit policy change. |
| USER_ACTION_REQUIRED | The user completes provider authentication outside the agent. |
| PAYMENT_PENDING | Poll get_purchase_status. Do not retry on a new card. |
| SUBMISSION_UNKNOWN | Poll get_purchase_status. Do not retry on a new card. |
| PAYMENT_DECLINED | Follow the decline type and the configured fallback policy. A new card needs a new intent and approval. |
| IDEMPOTENCY_CONFLICT | Reuse the original payload with its key, or start a new intent with a new key. |
| PROVIDER_UNAVAILABLE | For reads, follow the retry guidance. For changes, reconcile before any retry. |
| RATE_LIMITED | For reads, follow the retry guidance. For changes, reconcile before any retry. |
An example error body:
{
"error": {
"code": "BUDGET_EXCEEDED",
"message": "This purchase is over the $200.00 per-purchase limit set for this agent.",
"limit_minor": 20000,
"requested_minor": 24900
}
}
Errors never include stack traces or credentials. Messages are safe to show to the user.
Decision object
Every choice of card is returned as a decision. This is the shape returned by recommend_payment and explain_decision:
{
"decision_id": "dec_042",
"mode": "simulator",
"selected_card_ref": "card_chrome",
"amount_minor": 6000,
"currency": "USD",
"estimated_reward_minor": 120,
"estimated_fee_minor": 0,
"estimated_net_value_minor": 120,
"alternative": {
"card_ref": "card_bce",
"estimated_net_value_minor": 100
},
"reason_codes": ["CATEGORY_CAP_NEAR_EXHAUSTION"],
"category_provenance": "fixture_confirmed",
"catalog_version": "demo-1",
"policy_version": 3,
"wallet_snapshot_id": "snapshot_17",
"engine_version": "1.0.0",
"is_payment_authorization": false
}
| Field | Meaning |
|---|---|
| decision_id | Identifier of this decision. Read it back with explain_decision. |
| mode | The rail the decision was made for, such as simulator. |
| selected_card_ref | Opaque reference to the card to use, or NO_ELIGIBLE_CARD. |
| amount_minor | The purchase amount in integer minor units. For USD, 6000 is $60.00. |
| currency | The ISO 4217 code that gives amount_minor its unit. |
| estimated_reward_minor | Estimated reward for the selected card, in minor units. An estimate, not a guarantee. |
| estimated_fee_minor | Estimated fees for the selected card. |
| estimated_net_value_minor | Estimated reward minus estimated fees. |
| alternative | The next-best eligible card and its estimated net value. |
| reason_codes | Machine-readable reasons for the choice. |
| category_provenance | Where the purchase category came from. |
| catalog_version | The offer catalog version used. |
| policy_version | The version of the user's policy used. |
| wallet_snapshot_id | The snapshot of the card set that was evaluated. |
| engine_version | The version of the decision engine that produced the result. |
| is_payment_authorization | Always false for a decision. A recommendation is never permission to pay. |
Amounts are integer minor units with an explicit ISO currency. Do not round or convert them. A stored decision also keeps the ranked eligible cards, the rejected cards with reasons, and any uncertainty flags.
Rails and test mode
A rail is the path a payment takes. Every purchase response includes a rail field, so you can see which path handled it.
| Rail | Money | Availability |
|---|---|---|
| simulator | Fictional money. | Default for every purchase. Models pending, partial and failed outcomes, so agents must handle them. |
| provider test mode | Test money through a payment provider's test environment. | Only when enabled for your account. Uses the provider's published test cards and test credentials. |
| live | Real money. | Not available. |
Security
- Card numbers stay with the payment partner. The partner tokenizes card details. Raw card numbers never reach Finature's application database, the model, MCP outputs or logs. Cards are referred to by opaque references.
- Page and tool text is data. Text from web pages and tool results is never treated as instructions. A page that says a purchase is pre-approved changes nothing. Approval comes only from the trusted app.
- The server enforces every rule. A client that skips its own agent logic, or a model that produces malicious arguments, still meets the same checks, including spending limits, approval and revocation.
- HTTPS only. The MCP endpoint, the app and provider webhooks all require HTTPS.
- Provider credentials stay server-side. They are separate from your access token, limited in scope, and never returned to a client.
Changelog
Oct 11, 2026 · Team preview
- Connect your own agent with a personal token from the dashboard. Team access code required.
- Twenty tools, adding the store catalog, open orders, Finature card freeze and restaurant booking.
refund_purchasereplacesrequest_refund. - Purchases run on Stripe Issuing test mode or the simulator.
Oct 10, 2026 · Preview
- Nine tools for wallet, decisions, purchases, receipts and refunds.
- Simulator rail, with fictional money.
- Approval required for every agent purchase by default.
- Mandates in preview.