> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tallyforagents.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP server

> @tallyforagents/mcp-server — give any MCP-aware AI agent (Claude Desktop, Cursor, Hermes, Cline, Goose) a USDC wallet with scoped spending permissions, no code required.

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.

<Note>
  For most agents the [hosted endpoint](/guides/connect-remote-mcp) 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.
</Note>

For agents that don't speak the [Model Context Protocol](https://modelcontextprotocol.io), see the direct [SDK](/sdk/installation) instead.

## 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                 |

Pick **OAuth** if your host supports it. Pick the **API key** for hosts that only support static env (Claude Desktop, Cursor) — see [Quick start](#quick-start) below.

### OAuth (Hermes)

Tally is a standard OAuth 2.1 + PKCE authorization server with dynamic client
registration and [`.well-known` discovery](/api/oauth), 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`:

```yaml theme={null}
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
```

<Note>
  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.
</Note>

**2. Connect:**

```bash theme={null}
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:**

```bash theme={null}
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:

```bash theme={null}
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.

<Warning>
  `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.
</Warning>

### Copy-paste: the full Hermes happy path

```bash theme={null}
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
```

<Note>
  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.
</Note>

### 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`](/api/oauth), 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

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## Quick start

<Note>
  On an OAuth-capable host like Hermes, use [OAuth](#oauth-hermes) instead of the
  static key shown here.
</Note>

### 1. Install

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

### 2. Configure your MCP client

<CodeGroup>
  ```json Claude Desktop theme={null}
  {
    "mcpServers": {
      "tally": {
        "command": "npx",
        "args": ["-y", "@tallyforagents/mcp-server"],
        "env": {
          "TALLY_API_KEY": "tly_test_..."
        }
      }
    }
  }
  ```

  ```yaml Hermes theme={null}
  mcp_servers:
    tally:
      command: "npx"
      args: ["-y", "@tallyforagents/mcp-server"]
      env:
        TALLY_API_KEY: "tly_test_..."
  ```

  ```json Cursor theme={null}
  {
    "mcpServers": {
      "tally": {
        "command": "npx",
        "args": ["-y", "@tallyforagents/mcp-server"],
        "env": {
          "TALLY_API_KEY": "tly_test_..."
        }
      }
    }
  }
  ```

  ```json Cline / Goose / generic theme={null}
  {
    "mcpServers": {
      "tally": {
        "command": "npx",
        "args": ["-y", "@tallyforagents/mcp-server"],
        "env": {
          "TALLY_API_KEY": "tly_test_..."
        }
      }
    }
  }
  ```
</CodeGroup>

Get the API key from [Dashboard → API keys → Create](https://www.tallyforagents.com).

### 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, $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](https://www.x402.org)), the server automatically pays via Tally and retries with proof.

<Tip>
  **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.

  ```text theme={null}
  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.
  ```
</Tip>

**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"`).     |

**Returns** (JSON):

```json theme={null}
{
  "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**:

| 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.             |

**Returns**:

```json theme={null}
{
  "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**:

| Param       | Type    | Required | Notes                               |
| ----------- | ------- | -------- | ----------------------------------- |
| `limit`     | integer | no       | 1–50. Default 10.                   |
| `status`    | enum    | no       | `pending` / `confirmed` / `failed`. |
| `direction` | enum    | no       | `inbound` / `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):

```json theme={null}
{
  "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):

```json theme={null}
{
  "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`

<Note>
  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.
</Note>

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**:

```json theme={null}
{
  "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`

<Note>Available on the **hosted endpoint**, not the stdio package.</Note>

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**:

| 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.                               |

**Returns** (paywalled):

```json theme={null}
{
  "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`

<Note>Available on the **hosted endpoint**, not the stdio package.</Note>

Discover paid services from the public [x402 Bazaar](https://docs.cdp.coinbase.com/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**:

| 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.                                                    |

**Returns**:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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_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.                          |

See [SDK errors](/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](https://www.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](https://github.com/pkohler95/tally-v2/tree/main/packages/mcp-server)
