MCP

Connect Claude, Cursor, Codex, VS Code and any other MCP-compatible client to the Stables API through the Model Context Protocol. An agent connected this way can onboard customers, price and execute USDT payouts, open payment routes for fiat deposits, and reconcile webhooks, all through the same API your code uses.

MCP (Model Context Protocol) is an open standard for connecting AI applications to external tools, like a USB-C port for AI: any client that speaks MCP can use any MCP server.

For agent-specific discovery and safety guidance, see AI agents and Agent safety.


Connect in one step

The Stables MCP server is hosted. There is nothing to install and nothing to run. Point your client at:

https://mcp.stables.money/mcp

There are two ways to authenticate:

  • Sign in with your Stables account. Clients that support MCP sign-in (OAuth), such as Claude.ai, Claude Desktop and ChatGPT, open a Stables login page and need no API key. See Claude.ai and Claude Desktop and ChatGPT.
  • Send an API key as a bearer token. Everything else, including Claude Code, Cursor, VS Code, Windsurf and Codex.

With an API key, the hosted server takes sandbox keys (sti_test_…), which reach api.sandbox.stables.money, where no real money moves.

Production only accepts calls from allowlisted addresses, and the hosted server's addresses are not on that list, so it refuses live keys (sti_live_…) with an explanation. For production, either connect by signing in (Claude, ChatGPT), or run the server locally from an allowlisted machine with your live key.

Start with a sandbox key. The hosted server stores no credentials of its own; every request carries yours, and a request without a valid key is refused before it reaches the API.

Claude Code

claude mcp add --transport http stables https://mcp.stables.money/mcp --header "Authorization: Bearer ${STABLES_API_KEY}"

Cursor, VS Code, Windsurf and other clients with a url field

Add this to the client's MCP configuration (~/.cursor/mcp.json, .vscode/mcp.json, and so on):

{
  "mcpServers": {
    "stables": {
      "url": "https://mcp.stables.money/mcp",
      "headers": {
        "Authorization": "Bearer sti_test_..."
      }
    }
  }
}

Codex

In ~/.codex/config.toml:

[mcp_servers.stables]
url = "https://mcp.stables.money/mcp"
bearer_token_env_var = "STABLES_API_KEY"

Claude.ai and Claude Desktop

These clients connect by signing in, so there is no key to copy.

  1. In Claude, open Settings > Connectors and choose Add custom connector.
  2. Name it Stables, enter https://mcp.stables.money/mcp as the URL and choose Add.
  3. Choose Connect on the new connector. A Stables page opens: pick Sandbox or Production, choose Read only or Read and make changes, then Allow and sign in.
  4. Sign in with the account you use for the Stables dashboard. If you belong to more than one organization, pick the one the agent should act for.

When you allow access, Stables creates an API key for the connection in that environment, named after the client and your email (for example Claude via MCP ([email protected])). It is listed with your other keys in the dashboard. Revoke it there, or use Disconnect on the AI agents page, to cut the connection off; removing the connector in Claude alone does not revoke the key. Connecting the same client again replaces its earlier key, which is revoked for you.

ChatGPT

ChatGPT connects by signing in too. Custom connectors need developer mode, which is available on paid plans; on a Business or Enterprise workspace an admin may need to allow it first.

  1. In ChatGPT on the web, open Settings and turn on Developer mode (under the apps or plugins settings, in Advanced settings).
  2. Create a new plugin: name it Stables, enter https://mcp.stables.money/mcp as the URL, choose OAuth for authentication, and create it.
  3. A Stables page opens: pick Sandbox or Production, choose the access, then Allow and sign in, and pick your organization.
  4. In a chat, turn on Stables from the + menu before asking for something.

The key Stables creates is named ChatGPT via MCP ([email protected]) and works exactly like Claude's: listed in the dashboard, revoked there.

Other clients that support MCP sign-in can connect the same way. Claude.ai, Claude Desktop and ChatGPT are the ones we have tested.

Try it

Start a new conversation and ask: "List my Stables customers". The agent calls list_customers and shows what is in the environment you connected to.


Run it locally instead

The same server is published to npm and runs as a local process over stdio. Use this for production API keys (from a machine whose address is on your production allowlist), when your client only supports local servers, when you work against a sti_local_… key and your own deployment, or when the client cannot reach the internet. The AI agents page in the production dashboard gives each coding tool this local setup with a key filled in.

Requirements: Node.js 18 or later.

For example, to run it locally in Claude Desktop instead of signing in, add this to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows), then quit and reopen Claude Desktop:

{
  "mcpServers": {
    "stables": {
      "command": "npx",
      "args": ["stables-mcp-server"],
      "env": {
        "STABLES_API_KEY": "sti_test_..."
      }
    }
  }
}
VariableRequiredDescription
STABLES_API_KEYYesYour Stables API key. The sti_test_ or sti_live_ prefix picks the environment.
STABLES_API_URLNoOverrides the environment the key implies. Needed only for a sti_local_… key or a non-standard deployment. Must be HTTPS.

Source and issues: github.com/stables-money/mcp-server.


Tools

The server exposes 26 tools. Each one mirrors a documented Stables API endpoint and nothing else; the reference below is generated from the server's own tool schemas, so it matches what your client sees.

Customers

create_customer

Create a new customer in Stables for KYC verification and transfers. Use 'individual' for personal accounts or 'business' for company accounts. Include entitlements like 'base_payout' to enable transactions.

ParameterTypeRequiredDescription
emailstringNoCustomer's email address
customerTypeindividual | businessYesType of customer - 'individual' for personal, 'business' for companies
firstNamestringNoFirst name (required for individuals)
lastNamestringNoLast name (required for individuals)
middleNamestringNoMiddle name
dobstringNoDate of birth in YYYY-MM-DD format (e.g., '1990-01-15')
nationalitystringNoTwo-letter country code (e.g., 'US', 'GB')
companyNamestringNoCompany name (required for businesses)
countrystringNoCountry code ISO 3166-1 alpha-2 (required for businesses, e.g., 'US')
registrationNumberstringNoBusiness registration number
incorporatedOnstringNoDate of incorporation in YYYY-MM-DD format
typestringNoCompany type (e.g., 'Private Company Limited by Shares')
taxIdstringNoTax ID (e.g., '12-3456789')
registrationLocationstringNoRegistration location (e.g., state for USA)
websitestringNoWebsite URL
describeBusinessstringNoDescription of the business
conductMoneyServicesbooleanNoWhether the company conducts money services
describeMoneyServicesstringNoDescribe money services (required if conductMoneyServices is true)
describeComplianceControlsstringNoDescription of compliance controls
mainSourceOfFundsBUSINESS_LOANS | GRANTS | INTER_COMPANY_FUNDS | INVESTMENT_PROCEEDS | LEGAL_SETTLEMENT | OWNERS_CAPITAL | PENSION_RETIREMENT | SALE_OF_ASSETS | SALES_OF_GOODS_AND_SERVICES | THIRD_PARTY_FUNDS | TREASURY_RESERVESNoMain source of funds
accountPurposeCHARITABLE_DONATIONS | ECOMMERCE_RETAIL_PAYMENTS | INVESTMENT_PURPOSES | PAYMENTS_TO_FRIENDS_OR_FAMILY_ABROAD | PAYROLL | PERSONAL_OR_LIVING_EXPENSES | PROTECT_WEALTH | PURCHASE_GOODS_AND_SERVICES | RECEIVE_PAYMENTS_FOR_GOODS_AND_SERVICES | TAX_OPTIMIZATION | THIRD_PARTY_MONEY_TRANSMISSION | TREASURY_MANAGEMENT | OTHERNoWhat will you use Stables for?
accountPurposeOtherstringNoExplain purpose (required if accountPurpose is OTHER)
isYourBusinessADaobooleanNoIs your business a DAO?
industrySelectionstringNoNAICS industry code (e.g., '5415')
expectedAnnualRevenue0_99999 | 100000_999999 | 1000000_9999999 | 10000000_49999999 | 50000000_249999999 | 250000000_plusNoEstimated annual revenue in USD
expectedMonthlyPaymentsstringNoExpected monthly payments in USD
sourceOfFundsBUSINESS_LOANS | GRANTS | INTER_COMPANY_FUNDS | INVESTMENT_PROCEEDS | LEGAL_SETTLEMENT | OWNERS_CAPITAL | PENSION_RETIREMENT | SALE_OF_ASSETS | SALES_OF_GOODS_AND_SERVICES | THIRD_PARTY_FUNDS | TREASURY_RESERVESNoSource of funds
sourceOfFundsDescriptionstringNoDescribe where your business funds come from
operateInProhibitedCountrybooleanNoDoes your business operate in any prohibited countries?
doesYourBusinessEngageInHighRiskActivitiesyes | noNoDoes your business engage in high risk activities?
acceptTermsbooleanNoAccept Stables' terms of service and privacy policy
howDidYouComeAcrossStablesstringNoHow did you come across Stables?
externalCustomerIdstringNoYour own reference ID for this customer
phonestringNoPhone number with country code (e.g., '+14155552671')
entitlementsarray of base_payout | virtual_accountNoList of entitlements to request
addressLine1stringNoStreet address line 1
addressLine2stringNoStreet address line 2
addressCitystringNoCity
addressStatestringNoState or region
addressPostalCodestringNoPostal/ZIP code
addressCountrystringNoTwo-letter country code (e.g., 'US')
legalAddressLine1stringNoLegal address line 1 (for businesses)
legalAddressLine2stringNoLegal address line 2 (for businesses)
legalAddressCitystringNoLegal address city (for businesses)
legalAddressStatestringNoLegal address state (for businesses)
legalAddressPostalCodestringNoLegal address postal code (for businesses)
legalAddressCountrystringNoLegal address country code (for businesses)

get_customer

Get details about a specific customer including their verification status

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID to look up

list_customers

List all customers for the authenticated tenant

No parameters.

Generate a KYC verification link for a customer. The customer must complete verification before they can make transfers.

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID to generate verification link for
ttlInSecsnumberNoTime-to-live for the KYC link in seconds (default: 1800)
successUrlstringNoURL to redirect to after successful verification
rejectUrlstringNoURL to redirect to after rejected verification

update_customer

Update customer details, entitlements, or verification information

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID to update
emailstringNoUpdated email address
phonestringNoUpdated phone number
firstNamestringNoUpdated first name
lastNamestringNoUpdated last name
middleNamestringNoUpdated middle name
dobstringNoUpdated date of birth (YYYY-MM-DD)
nationalitystringNoUpdated nationality (two-letter country code)
companyNamestringNoUpdated company name (for businesses)
entitlementsarray of base_payout | virtual_accountNoUpdated entitlements

update_customer_metadata

Update customer metadata key-value pairs

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID to update metadata for
metadataobjectYesMetadata key-value pairs to set

Quotes

create_quote

Get a quote for a currency exchange: the rate, the fees, and what the customer receives. Quotes are short-lived, so create one immediately before the transfer. Off-ramp converts a stablecoin to fiat (source network required, destination country required); on-ramp converts fiat to a stablecoin (destination address required). Set preview to price without committing.

ParameterTypeRequiredDescription
sourceCurrencystringYesSource currency: a stablecoin for off-ramp (e.g. 'USDT'), fiat for on-ramp
sourceAmountstringYesAmount to convert, in major units (e.g. '125.75')
sourceNetworkarbitrum | avalanche | base | ethereum | optimism | polygon | solana | tronNoBlockchain network of the source. Required for off-ramp, omitted for on-ramp
destinationCurrencystringYesDestination currency: fiat for off-ramp (e.g. 'EUR'), a stablecoin for on-ramp
destinationCountrystringNoDestination country, ISO 2-letter. Required for off-ramp
destinationNetworkstringNoOff-ramp: payment network, 'swift' or 'bank'. On-ramp: blockchain network. Replaces the old paymentMethodType
destinationAddressstringNoDestination wallet address. Required for on-ramp
previewbooleanNoPrice the quote without persisting it, to show an estimate
metadataobjectNoOptional metadata

get_quote

Get details about an existing quote including its current status

ParameterTypeRequiredDescription
quoteIdstringYesThe quote ID to look up

Transfers

create_transfer

Execute a transfer using an active quote. The quote must not be expired. This initiates real money movement, so get explicit human approval first. For payouts in AED, CAD, EUR, GBP, MXN, USD you must also supply recipientType, the full beneficiary address, and dateOfBirth when the recipient is an individual.

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID for this transfer
quoteIdstringYesThe quote ID to execute
destinationTypebank | cryptoNoPayout target: a bank account (off-ramp) or a wallet address. Default: "bank".
accountHolderNamestringNoAccount holder's full name, or the company name when recipientType is business
bankNamestringNoName of the destination bank
bankCountrystringNoBank country, ISO 2-letter (e.g. 'AU', 'US')
bankCurrencystringNoPayout currency, ISO 3-letter (e.g. 'EUR')
recipientTypeindividual | businessNoBeneficiary type. Required for AED, CAD, EUR, GBP, MXN, USD
dateOfBirthstringNoBeneficiary date of birth, YYYY-MM-DD. Required when recipientType is individual for AED, CAD, EUR, GBP, MXN, USD
addressStreetstringNoBeneficiary street address
addressCitystringNoBeneficiary city
addressStatestringNoBeneficiary state or region
addressPostalCodestringNoBeneficiary postal code
addressCountrystringNoBeneficiary country, ISO 2-letter. The complete address is required for AED, CAD, EUR, GBP, MXN, USD
ibanstringNoIBAN (EU/international)
accountNumberstringNoBank account number, if not using IBAN
accountTypesavings | checking | paymentNoAccount type. Required for CAD and USD bank payouts
payIdstringNoPayID alias (AUD only). Requires payIdType
payIdTypeemail | phone | abn | org_idNoPayID alias type. Required when payId is given
swiftCodestringNoSWIFT code
bicCodestringNoBIC (EUR/GBP international)
routingNumberstringNoABA routing number (US)
sortCodestringNoSort code (UK)
ifscCodestringNoIFSC code (India)
bsbCodestringNoBSB code (Australia)
bankCodestringNoBank code / institution number (CAD)
branchCodestringNoBranch code / transit number (CAD)
cnapsstringNoCNAPS (China)
destinationCurrencystringNoStablecoin to deliver when destinationType is crypto (e.g. 'USDT')
destinationNetworkstringNoBlockchain network when destinationType is crypto (e.g. 'polygon')
destinationAddressstringNoWallet address when destinationType is crypto
purposeCodestringNoPurpose of the transfer (e.g. 'SALARY'). Required on some corridors
metadataobjectNoOptional metadata

get_transfer

Get the current status and details of a transfer

ParameterTypeRequiredDescription
transferIdstringYesThe transfer ID to look up

list_transfers

List transfers with optional filters for status, type, or customer

ParameterTypeRequiredDescription
statuscreated | compliance_hold | awaiting_funds_collection | funds_collected | in_progress | payment_submitted | payment_processed | completed | failed | cancelled | expiredNoFilter by transfer status
typeonramp | offrampNoFilter by transfer type
customerIdstringNoFilter by customer ID
pageSizenumberNoNumber of transfers per page (default: 20)
pageTokenstringNoToken for the next page of results

Payment routes

create_virtual_account

Create a payment route (virtual bank account) so a customer can receive fiat deposits that convert to a stablecoin and pay out to a wallet. The payout destination is mandatory: the API refuses a fiat-to-crypto route without one. Deposit handling is set server-side and defaults to auto_payout; change it afterwards with update_virtual_account.

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID to create the payment route for
sourceCurrencystringYesFiat currency the route collects in (e.g. 'AUD', 'USD', 'EUR')
destinationAddressstringYesWallet address deposits are paid out to. Validated against the chosen network
destinationPaymentRailarbitrum | avalanche_c_chain | base | celo | ethereum | optimism | polygon | solana | stellar | tronYesBlockchain network the payout address belongs to
destinationCurrencyusdc | usdt | dai | pyusd | eurcNoStablecoin to receive. Default: "usdt".
developerFeePercentstringNoYour fee on each deposit, as a numeric string (e.g. '0.5')

list_virtual_accounts

List all virtual accounts for a customer

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID to list virtual accounts for
statusactivated | deactivated | pending | closedNoFilter by account status
limitnumberNoMaximum number of accounts to return

update_virtual_account

Update virtual account settings (e.g., deposit handling mode)

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID
virtualAccountIdstringYesThe virtual account ID to update
depositHandlingModeauto_payout | hold | manualYesNew deposit handling mode

get_virtual_account_history

Get the activity history for a virtual account (deposits, payouts, etc.)

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID
virtualAccountIdstringYesThe virtual account ID
limitnumberNoMaximum number of events to return (default: 10)

update_route_destination

Change the payout wallet on an existing payment route. Deposits after this point are paid out to the new address; use it when a customer rotates wallets rather than creating a second route.

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID
virtualAccountIdstringYesThe payment route (virtual account) ID
destinationAddressstringYesNew payout wallet address
destinationPaymentRailarbitrum | avalanche_c_chain | base | celo | ethereum | optimism | polygon | solana | stellar | tronYesBlockchain network the new address belongs to
destinationCurrencyusdc | usdt | dai | pyusd | eurcNoStablecoin to receive. Default: "usdt".

Sandbox

simulate_route_deposit

SANDBOX ONLY. Simulate a fiat deposit into a payment route so you can watch the conversion and payout complete without a real bank transfer. Fails on production.

ParameterTypeRequiredDescription
customerIdstringYesThe customer ID
virtualAccountIdstringYesThe payment route (virtual account) ID
amountstringYesDeposit amount in major units (e.g. '500.00')
scenariocreate_only | completed | failedNo'completed' runs the deposit through to payout, 'failed' exercises the failure path, 'create_only' stops at the deposit
senderNamestringNoName to attribute the deposit to
senderReferencestringNoPayment reference on the deposit
externalDepositIdstringNoYour own reference for this deposit

simulate_transfer_deposit

SANDBOX ONLY. Simulate the customer sending the crypto an off-ramp transfer is waiting on, so the transfer can progress to payout without an on-chain payment. Fails on production.

ParameterTypeRequiredDescription
transferIdstringYesThe transfer awaiting funds

API keys

create_api_key

Create a new API key for accessing the Stables API. The secret key is only shown once on creation - save it immediately.

ParameterTypeRequiredDescription
namestringYesA descriptive name for this API key (e.g., 'Production Bot', 'Agent Smith')
metadataobjectNoOptional metadata to attach to the key

list_api_keys

List all API keys for the current account

ParameterTypeRequiredDescription
pageSizenumberNoNumber of keys per page
pageTokenstringNoToken for the next page

get_api_key

Get details about a specific API key

ParameterTypeRequiredDescription
apiKeyIdstringYesThe API key ID to look up

revoke_api_key

Revoke an API key. This permanently disables the key and cannot be undone.

ParameterTypeRequiredDescription
apiKeyIdstringYesThe API key ID to revoke

Webhooks

create_webhook

Subscribe to Stables events via webhook. You'll receive POST requests to your URL when events occur.

Event types are dotted and lowercase. Use 'all' to receive everything.

Security: Set a secret to enable HMAC-SHA256 signature verification via X-Webhook-Signature header.

ParameterTypeRequiredDescription
namestringYesA descriptive name for this webhook (e.g., 'Transfer status notifications')
urlstringYesThe HTTPS URL to receive webhook POST requests
eventTypesarray of customer.created | customer.updated | kyc_link.updated.status_transitioned | transfer.created | transfer.updated.status_transitioned | quote.created | quote.updated.status_transitioned | virtual_account.created | virtual_account.activity.created | virtual_account.activity.updated.status_transitioned | travel_rule.wallet_verification_required | rfi.created | rfi.updated | rfi.resolved | allYesEvent types to subscribe to (e.g. ['transfer.updated.status_transitioned']), or ['all']
secretstringNoOptional signing secret for HMAC-SHA256 webhook signature verification

list_webhooks

List all webhook subscriptions for the current account

No parameters.

delete_webhook

Delete a webhook subscription. You will stop receiving events at this endpoint.

ParameterTypeRequiredDescription
webhookIdstringYesThe webhook subscription ID to delete

list_webhook_deliveries

List recent webhook delivery attempts with their HTTP status and retry state. This is the first place to look when an integration is not receiving events: it shows whether Stables sent them and what the endpoint answered.

ParameterTypeRequiredDescription
pageSizenumberNoHow many attempts to return (default 10, max 50)
statusPENDING | SUCCESS | FAILED | RETRYINGNoFilter by delivery status
eventTypestringNoFilter by event type (e.g. 'transfer.created')

Example workflows

Onboard a customer

"Create an individual customer for [email protected] with the base_payout entitlement and give me a KYC link"

The agent calls create_customer, then get_verification_link for the new customer, and returns a link to share. Links expire after 30 minutes by default.

Price and execute a payout

"Quote 500 USDT on Polygon to EUR for customer abc123"

The agent calls create_quote and shows the rate, the fees and what the beneficiary receives, along with when the quote expires. Quotes are short-lived, so create one immediately before the transfer.

"Execute it to IBAN GR16 0110 1250 0000 0001 2300 695 at Alpha Bank, account holder Maria Papadopoulou"

The agent calls create_transfer with the quote and the beneficiary details, and returns the transfer ID, its status and the wallet address the USDT must be sent to. Some currencies require more beneficiary detail than others; the tool description lists what each currency needs.

Open a payment route

"Create a USD payment route for customer abc123 that pays out USDT on Base to 0x…"

The agent calls create_virtual_account and returns the deposit instructions. Later, "change the payout wallet on that route" calls update_route_destination, and "show me the deposit history" calls get_virtual_account_history.

Test a deposit in sandbox

"Simulate a 250 USD deposit into that route"

The agent calls simulate_route_deposit, which drives the route to a terminal state without a real bank payment. Sandbox only.

Reconcile webhooks

"Set up a webhook at https://example.com/stables that fires when transfers change status"

The agent calls create_webhook with transfer.updated.status_transitioned. If events stop arriving, "show me recent webhook deliveries" calls list_webhook_deliveries, the first place to look.


Security

  • With an API key, the hosted server holds no credentials. Your key travels with each request and is only ever forwarded to the Stables environment it belongs to.
  • With sign-in, the key created for the connection is stored encrypted by the hosted server and used only for that connection's requests. It appears in your dashboard like any other key and can be revoked there.
  • A key is never accepted in the URL, only in the Authorization or X-Api-Key header.
  • Every write carries an idempotency key, built once per call, so a retried request cannot submit a payout twice.
  • Create a separate API key per agent (sign-in does this for you) and revoke it from the dashboard when the agent is retired. An agent holding a sti_live_ key can move real money on your behalf; read Agent safety before going live.
  • Keep human confirmation on for create_transfer and other money-moving tools in your client.

Troubleshooting

401 from mcp.stables.money with "error": "unauthorized"

The request reached the hosted server without a usable key. Check that the Authorization: Bearer header is set and that the key starts with sti_test_. A live key (sti_live_) is refused with "Production API keys can't be used through the hosted server": sign in instead, or run the server locally from an allowlisted machine. A sti_local_ key cannot be routed by the hosted server either; run it locally with STABLES_API_URL.

"Invalid or missing authorization credentials" inside a tool result

The key reached the Stables API and was refused there. It has been revoked, or it belongs to the other environment. Check the key in the dashboard.

A local server's calls to production are refused

Production only accepts calls from allowlisted addresses. Run the server from a machine whose address is on your production allowlist, or ask Stables to add it.

Quote expired before the transfer

Quotes have a short lifetime. Create a new quote and execute the transfer straight away.

Local server not appearing in Claude Desktop

Check that the config file is valid JSON, that STABLES_API_KEY is set in the env block, and that you fully quit Claude Desktop (Cmd+Q) before reopening it. Run npx stables-mcp-server@latest once in a terminal to confirm Node can download and start it.

On this page