Skip to main content
This page documents the stdio MCP server — the local subprocess your agent’s runtime launches (npx @tallyforagents/mcp-server), which discovers four tools (pay_x402_service, pay_direct, list_recent_payments, get_wallet_info) the LLM can use like any built-in tool. No SDK calls in your agent code.
For most agents the hosted endpoint is the recommended way to connect — it’s reached over the network with OAuth (no install, no API key in config) and exposes three more tools (get_wallet_balance, check_x402_price, list_x402_services). Use the stdio server documented here only for stdio-only hosts. The tool reference below applies to both.
For agents that don’t speak the Model Context Protocol, see the direct SDK instead.

Authentication

There are two ways to give the MCP server a credential:
OAuth (recommended)Static API key
SetupBrowser sign-in, no secret in configPaste tly_… into config
Multiple workspacesSwitch accounts without editing configOne key = one account
Credential lifetimeShort-lived, auto-refreshedLong-lived until rotated
Stored whereYour host’s secure token storePlaintext in config.yaml
HostsAny MCP host with OAuth 2.1 support (e.g. Hermes)Every host
Pick OAuth if your host supports it. Pick the API key for hosts that only support static env (Claude Desktop, Cursor) — see Quick start below.

OAuth (Hermes)

Tally is a standard OAuth 2.1 + PKCE authorization server with dynamic client registration and .well-known discovery, so any host that can do remote-MCP OAuth drives it the same way. With Hermes: 1. Configure the server for OAuth in ~/.hermes/config.yaml:
mcp_servers:
  tally:
    command: npx
    args: ["-y", "@tallyforagents/mcp-server"]
    auth:
      type: oauth
      provider: tally
      # Discovery base — Hermes fetches
      # https://app.tallyforagents.com/.well-known/oauth-authorization-server
      issuer: https://app.tallyforagents.com
      scopes:
        - wallet:read
        - wallet:transfer
        - x402:pay
    env:
      # Optional. Overrides the agent you pick on the consent screen.
      TALLY_AGENT_ID: hermes
The exact auth: keys follow your host’s MCP OAuth convention — match whatever Hermes uses for other OAuth MCP servers. The only requirements are that it points at Tally’s issuer (so discovery resolves the authorize/token endpoints) and that it injects the resulting access token into the server as TALLY_ACCESS_TOKEN. Notice there is no TALLY_API_KEY — that’s the point.
2. Connect:
hermes mcp auth tally
This opens your browser to Tally’s authorization page. Sign in, choose the workspace (account), mode (test/live), and agent to authorize, review the scopes, and click Authorize. Hermes receives a short-lived access token plus a refresh token, stores them in its MCP token store, injects TALLY_ACCESS_TOKEN, and reloads the server. 3. Verify:
hermes mcp test tally
Then ask the agent “what’s my wallet?” — it calls get_wallet_info, which now reflects the workspace, agent, wallet address, and spending caps you authorized.

Switching Tally accounts

The token is bound to the workspace you picked, so switching accounts is a re-auth — no config edits, no key juggling:
hermes mcp logout tally   # revokes the current token (server-side too)
hermes mcp auth tally     # sign in, pick a different workspace
Or just re-run hermes mcp auth tally and choose a different workspace on the consent screen. After reconnecting, run hermes mcp test tally and ask for the wallet again — get_wallet_info will show the new account’s agent, wallet, and caps.
hermes mcp logout tally revokes the token at Tally (RFC 7009), so the old credential stops working immediately — not just locally. This is what makes account switching safe: the previous workspace’s access can’t linger.

Copy-paste: the full Hermes happy path

hermes mcp add tally --url https://app.tallyforagents.com   # register the server
hermes mcp login tally                                      # browser → pick workspace → authorize
hermes mcp test tally                                       # confirms tools are discoverable
hermes gateway restart                                      # pick up new tools in messaging sessions
Exact command names are host-specific — these are the Hermes verbs. The sequence is universal: add server → log in (browser) → verify → reload tools. On Hermes, freshly authorized tools may not appear in an in-flight chat session until the gateway restarts or you start a new session.

Check which account / wallet is active

Ask the agent “what’s my wallet?” (it calls get_wallet_info), or use your host’s identity command — on Hermes, hermes mcp whoami tally. Both are backed by Tally’s GET /v1/me, which returns the connected workspace, mode, agent, wallet address + caps, granted scopes, and token expiry — all non-secret, so it’s safe to display or log.

Troubleshooting

Tally accepts https redirect URIs and any loopback http port (127.0.0.1 / localhost, RFC 8252) — so a host that grabs an ephemeral callback port like http://127.0.0.1:52363/callback is fine; the port doesn’t need to be pre-registered. If you still get invalid_redirect_uri, the mismatch is the host + path, not the port: the redirect URI the host sends must match (host + path) one it registered. Re-running the host’s registration (or hermes mcp login after logout) re-registers the current callback URL.
OAuth succeeded but the agent doesn’t see pay_x402_service etc.? The MCP tool list is cached per session. Reload it: hermes gateway restart (or start a new chat session; some hosts expose /reload-mcp). This is a host concern — the Tally server connects fine; the host just needs to re-list its tools.
You authorized a different workspace than intended. Switch: hermes mcp logout tally then hermes mcp login tally, and pick the right workspace on the consent screen. Confirm with hermes mcp whoami tally / get_wallet_info. Logout revokes the previous token server-side, so there’s no stale access.
The agent exists but has no wallet grant in the connected workspace + mode. Open the dashboard URL in the tool’s response, Grant permission, pick a funded wallet, set caps, approve via passkey — then reload tools. Note grants are mode-scoped: a test-mode grant won’t show in live mode.

Quick start

On an OAuth-capable host like Hermes, use OAuth instead of the static key shown here.

1. Install

You don’t — npx runs it on demand. Just configure it.

2. Configure your MCP client

{
  "mcpServers": {
    "tally": {
      "command": "npx",
      "args": ["-y", "@tallyforagents/mcp-server"],
      "env": {
        "TALLY_API_KEY": "tly_test_..."
      }
    }
  }
}
Get the API key from Dashboard → API keys → Create.

3. Restart your MCP client + grant a permission

On first launch, the server auto-creates a Tally agent named mcp-default with no permissions. The agent has the tools but every payment will fail until you grant a wallet. Ask the agent something like “What wallets can you spend from?” — it calls get_wallet_info, sees no permissions, and prints the dashboard URL. Open that URL, click Grant permission, pick a funded wallet, accept the default caps (10/tx,10/tx, 100/day), approve via passkey. Restart the MCP client one more time and you’re done.

Environment

Provide one credential — TALLY_ACCESS_TOKEN (OAuth) or TALLY_API_KEY. If both are set, the access token wins.
VariableRequiredDefaultNotes
TALLY_ACCESS_TOKENone oftly_oat_… OAuth access token. Normally injected by your host’s OAuth flow, not set by hand. The workspace/mode/agent are bound to the token; the server resolves them via GET /v1/me.
TALLY_API_KEYone oftly_test_… or tly_live_… static key.
TALLY_AGENT_IDnomcp-defaultOverrides the token-bound agent (OAuth) or names the agent (API key). The agent must already exist in the resolved account, unless it’s mcp-default (auto-created).
TALLY_BASE_URLnohttps://app.tallyforagents.comOverride for local dev.

Tools

pay_x402_service

Call any HTTP service. If the response is 402 Payment Required (x402 protocol), the server automatically pays via Tally and retries with proof.
One-off payments without an account or API key. This is the canonical pay-per-use pattern: instead of signing up for a vendor, creating an API key, and configuring billing, the agent calls the endpoint and pays a few cents from its Tally wallet — bounded by its on-chain caps. Example: generate one image from a pay-per-call model for $0.03 rather than onboarding a whole account.
pay_x402_service(
  url="https://image-api.example.com/generate",
  method="POST",
  body="{...}",
  max_amount_usdc="0.10"   # per-call ceiling, on top of the agent's policy caps
)
→ Paid $0.03 via x402 and returned the result.
Use when: the user wants data or a service from a paywalled URL — weather, search, AI models, data feeds, etc. Inputs:
ParamTypeRequiredNotes
urlstringyesFull URL with query string.
methodenumnoGET (default) / POST / PUT / PATCH / DELETE.
bodystringnoPre-serialized body for POST/PUT/PATCH.
headersobjectnoExtra request headers.
max_amount_usdcstringnoPer-call safety cap. Decimal USDC (e.g. "1.00").
Returns (JSON):
{
  "status": 200,
  "ok": true,
  "body": "{\"city\":\"Tokyo\",\"temp_c\":14,...}",
  "paid": {
    "amount_usdc": "0.05",
    "tx_hash": "0x...",
    "to": "0x...",
    "network": "base-sepolia"
  }
}
paid is null if the URL returned 200 on the first call (no payment needed).

pay_direct

Send USDC from the agent’s wallet to a known recipient address. Use when: the user explicitly names a recipient (a person, contract, or service) at a known address. For paywalled HTTP services, prefer pay_x402_service. Inputs:
ParamTypeRequiredNotes
tostringyesEVM address.
amount_usdcstringyesDecimal USDC, e.g. "0.50".
memostringnoUp to 200 chars.
Returns:
{
  "id": "pay_...",
  "status": "pending",
  "tx_hash": "0x...",
  "amount_usdc": "0.50",
  "to": "0x...",
  "from": "0x...",
  "memo": "Refund for #123"
}
status flips to confirmed once the chain confirms (a few seconds on Base Sepolia). Call list_recent_payments to check.

list_recent_payments

Recent transactions for this agent — both outbound payments and inbound deposits. Use when: the user asks “what have I spent?”, “show me my transactions”, or you need to check whether a recent payment confirmed. Inputs:
ParamTypeRequiredNotes
limitintegerno1–50. Default 10.
statusenumnopending / confirmed / failed.
directionenumnoinbound / outbound.
Returns an array of payment records under payments[].

get_wallet_info

The agent’s identity, its wallets, and per-wallet caps. Use when: the user asks “what’s my wallet?”, “what can I spend?”, “what are my limits?”, or before attempting a payment if you’re unsure permissions exist. No inputs. Returns (with permissions):
{
  "agent_id": "mcp-default",
  "status": "active",
  "wallets": [
    {
      "address": "0x...",
      "display_name": "Main Wallet",
      "role_label": "main",
      "max_per_tx_usdc": "10",
      "daily_cap_usdc": "100"
    }
  ],
  "dashboard_url": "https://app.tallyforagents.com/..."
}
Returns (no permissions):
{
  "agent_id": "mcp-default",
  "status": "no_permissions",
  "wallets": [],
  "next_step": "This agent has no spending permissions yet. Visit the dashboard to grant one: https://app.tallyforagents.com/...",
  "dashboard_url": "..."
}

get_wallet_balance

Available on the hosted endpoint (https://app.tallyforagents.com/mcp), not the stdio package. get_wallet_info (above) returns caps on every host; get_wallet_balance adds the live on-chain figure.
The live on-chain USDC balance of each wallet the agent can spend from. Use when: the user asks “what’s my balance?”, or to check there are enough funds before a payment. No inputs. Returns:
{
  "agent_id": "mcp-default",
  "mode": "test",
  "wallets": [
    {
      "address": "0x...",
      "display_name": "Main Wallet",
      "usdc_balance": "12.5",
      "eth_balance": "0.0",
      "max_per_tx_usdc": "10",
      "daily_cap_usdc": "100"
    }
  ]
}
Gas is sponsored by Tally, so eth_balance is typically 0 — it’s surfaced for transparency.

check_x402_price

Available on the hosted endpoint, not the stdio package.
Make a request to a URL and, if it returns 402 Payment Required, report the price, asset, network, and recipient without paying. No funds move. Use when: you want to preview a cost before calling pay_x402_service, or to confirm whether an endpoint is paywalled at all. Inputs:
ParamTypeRequiredNotes
urlstringyesFull URL to check.
methodenumnoGET (default) / POST / PUT / PATCH / DELETE.
headersobjectnoExtra request headers.
bodystringnoBody for POST/PUT/PATCH.
Returns (paywalled):
{
  "url": "https://api.example.com/data",
  "payment_required": true,
  "status": 402,
  "options": [
    {
      "amount_usdc": "0.05",
      "asset": "0x...",
      "network": "base-sepolia",
      "pay_to": "0x...",
      "description": "...",
      "payable_by_tally": true
    }
  ]
}
payment_required is false (with the real status) when the endpoint isn’t paywalled. payable_by_tally flags whether Tally can settle that option — Tally pays on base / base-sepolia only.

list_x402_services

Available on the hosted endpoint, not the stdio package.
Discover paid services from the public x402 Bazaar (the CDP facilitator’s catalog), filtered to ones Tally can pay on your current mode’s network. Pairs with check_x402_price (confirm a cost) and pay_x402_service (call it). Use when: the user asks for a kind of service (“find a crypto-news API”, “what weather services can I pay for?”) rather than naming a URL. Inputs:
ParamTypeRequiredNotes
querystringnoFree-text, e.g. “crypto news”. Omit to browse top-ranked services.
max_price_usdcstringnoOnly return services at or below this price per call (decimal USDC).
limitintegerno1–20. Default 10.
Returns:
{
  "query": "crypto news",
  "network": "base-sepolia",
  "count": 1,
  "services": [
    {
      "url": "https://api.example.com/news",
      "description": "Real-time crypto news feed",
      "price_usdc": "0.01",
      "network": "base-sepolia",
      "pay_to": "0x...",
      "last_updated": "2026-01-15T12:30:00.000Z"
    }
  ],
  "next_step": "To use one: check_x402_price(url) to confirm the cost, then pay_x402_service(url)."
}
Results are quality-ranked by the Bazaar (buyer reach, transaction volume, recency) and limited to services payable on your connected mode’s chain — a test-mode agent won’t see mainnet-only services it can’t pay. Cached briefly server-side.

Error shape

Every tool returns isError: true with a structured payload when something fails:
{
  "error_type": "policy_denied",
  "message": "amount 50 USDC exceeds per-transaction cap of 10",
  "details": { "cap_usdc": "10", "requested_usdc": "50" }
}
The LLM reads error_type + message to decide how to react (apologize, try a smaller amount, ask for a higher cap, etc.). Common types:
error_typeMeaning
policy_deniedThe agent’s permission caps rejected the request.
x402_protocolThe 402 response was malformed or specified an unsupported network.
x402_amount_exceeds_capThe 402 asked for more than the caller’s max_amount_usdc.
unauthenticatedAPI key is invalid or expired.
not_foundThe agent or wallet doesn’t exist.
validation_failedA required input was missing or malformed.
See SDK errors for the complete list.

For AI assistants helping a user set this up

If you’re an LLM helping the user configure this server:
  1. Confirm the user has a Tally account — direct them to tallyforagents.com if not.
  2. Choose the auth method. If the host supports OAuth (e.g. Hermes), prefer it — configure the auth: block and tell the user to run hermes mcp auth tally (no key to paste, and they can switch workspaces later). Otherwise ask for an API key — tly_test_… for testnet — and write the JSON/YAML block (you can edit the config file directly on Hermes/Claude Desktop).
  3. Tell them to restart the MCP client after editing the config.
  4. Walk through the first-run flow: the agent will exist but have no permissions. The user grants permission via the dashboard URL the agent will print on first tool call.
  5. Don’t auto-spend. When the user first connects, prefer reading tools (get_wallet_info, list_recent_payments) before suggesting any pay_* call. Confirm intent before money moves.

How it works

  • Local stdio MCP server, spawned by the host as a subprocess.
  • The credential stays on the user’s machine. With OAuth, the host holds the tokens in its own secure store and injects only TALLY_ACCESS_TOKEN; with an API key, the key is in the host config. No Tally infrastructure sits between the agent and the SDK.
  • On OAuth startup the server calls GET /v1/me to resolve the workspace, mode, granted scopes, and bound agent from the access token. A clear “re-authorize” error is surfaced if the token is expired/revoked.
  • Identity is TALLY_AGENT_ID if set, otherwise the token-bound agent, otherwise mcp-default (auto-upserted on first tool call).
  • Wallet defaults to the first wallet attached to the agent. Restart the MCP host to refresh the cache after granting a new permission.
  • Payments are subject to the agent’s permission policy (Tally server-side check) and Privy’s enclave check, same as direct SDK calls. OAuth tokens additionally need the wallet:transfer scope to move money; reads only need wallet:read.
  • Secrets are never logged — the server scrubs tly_… / Bearer … strings from its stderr output.

Source

github.com/pkohler95/tally-v2/tree/main/packages/mcp-server