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.Authentication
There are two ways to give the MCP server a credential:| OAuth (recommended) | Static API key | |
|---|---|---|
| Setup | Browser sign-in, no secret in config | Paste tly_… into config |
| Multiple workspaces | Switch accounts without editing config | One key = one account |
| Credential lifetime | Short-lived, auto-refreshed | Long-lived until rotated |
| Stored where | Your host’s secure token store | Plaintext in config.yaml |
| Hosts | Any MCP host with OAuth 2.1 support (e.g. Hermes) | Every host |
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:
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.TALLY_ACCESS_TOKEN, and reloads the server.
3. Verify:
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 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.
Copy-paste: the full Hermes happy path
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 callsget_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
invalid_redirect_uri
invalid_redirect_uri
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.Tools don't appear after login
Tools don't appear after login
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.Wrong Tally account connected
Wrong Tally account connected
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.get_wallet_info says no_permissions
get_wallet_info says no_permissions
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
3. Restart your MCP client + grant a permission
On first launch, the server auto-creates a Tally agent namedmcp-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 (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.
| Variable | Required | Default | Notes |
|---|---|---|---|
TALLY_ACCESS_TOKEN | one of | — | tly_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_KEY | one of | — | tly_test_… or tly_live_… static key. |
TALLY_AGENT_ID | no | mcp-default | Overrides 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_URL | no | https://app.tallyforagents.com | Override 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.
Use when: the user wants data or a service from a paywalled URL — weather, search, AI models, data feeds, etc.
Inputs:
| Param | Type | Required | Notes |
|---|---|---|---|
url | string | yes | Full URL with query string. |
method | enum | no | GET (default) / POST / PUT / PATCH / DELETE. |
body | string | no | Pre-serialized body for POST/PUT/PATCH. |
headers | object | no | Extra request headers. |
max_amount_usdc | string | no | Per-call safety cap. Decimal USDC (e.g. "1.00"). |
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:
| Param | Type | Required | Notes |
|---|---|---|---|
to | string | yes | EVM address. |
amount_usdc | string | yes | Decimal USDC, e.g. "0.50". |
memo | string | no | Up to 200 chars. |
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:
| Param | Type | Required | Notes |
|---|---|---|---|
limit | integer | no | 1–50. Default 10. |
status | enum | no | pending / confirmed / failed. |
direction | enum | no | inbound / outbound. |
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):
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.eth_balance is typically 0 — it’s surfaced for transparency.
check_x402_price
Available on the hosted endpoint, not the stdio package.
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:
| Param | Type | Required | Notes |
|---|---|---|---|
url | string | yes | Full URL to check. |
method | enum | no | GET (default) / POST / PUT / PATCH / DELETE. |
headers | object | no | Extra request headers. |
body | string | no | Body for POST/PUT/PATCH. |
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.
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:
| Param | Type | Required | Notes |
|---|---|---|---|
query | string | no | Free-text, e.g. “crypto news”. Omit to browse top-ranked services. |
max_price_usdc | string | no | Only return services at or below this price per call (decimal USDC). |
limit | integer | no | 1–20. Default 10. |
Error shape
Every tool returnsisError: true with a structured payload when something fails:
error_type + message to decide how to react (apologize, try a smaller amount, ask for a higher cap, etc.). Common types:
error_type | Meaning |
|---|---|
policy_denied | The agent’s permission caps rejected the request. |
x402_protocol | The 402 response was malformed or specified an unsupported network. |
x402_amount_exceeds_cap | The 402 asked for more than the caller’s max_amount_usdc. |
unauthenticated | API key is invalid or expired. |
not_found | The agent or wallet doesn’t exist. |
validation_failed | A required input was missing or malformed. |
For AI assistants helping a user set this up
If you’re an LLM helping the user configure this server:- Confirm the user has a Tally account — direct them to tallyforagents.com if not.
- Choose the auth method. If the host supports OAuth (e.g. Hermes), prefer it — configure the
auth:block and tell the user to runhermes 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). - Tell them to restart the MCP client after editing the config.
- 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.
- Don’t auto-spend. When the user first connects, prefer reading tools (
get_wallet_info,list_recent_payments) before suggesting anypay_*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/meto 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_IDif set, otherwise the token-bound agent, otherwisemcp-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:transferscope to move money; reads only needwallet:read. - Secrets are never logged — the server scrubs
tly_…/Bearer …strings from its stderr output.