> For the complete documentation index, see [llms.txt](https://docs.trover.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trover.tech/engineering/customer-mcp.md).

# Trover customer MCP

Trover exposes an OAuth-protected Streamable HTTP MCP at:

```
https://api.trover.tech/mcp
```

The production endpoint is available only after the `ENABLE_CUSTOMER_MCP` rollout flag is enabled. The signing handoff and contextual X buy have separate flags.

## Connect from an AI client

1. Add `https://api.trover.tech/mcp` as a remote MCP server.
2. Let the client discover Trover's protected-resource and authorization-server metadata.
3. Sign in on Trover's Privy consent page.
4. Review the requested scopes and approve only those you want the client to use.

Public clients use OAuth authorization code with PKCE (`S256`). Trover supports OAuth dynamic client registration and HTTPS Client ID Metadata Documents, including ChatGPT client metadata. Clients must send a `resource` indicator for the exact MCP URL.

In ChatGPT, turn on **Settings → Security and login → Developer mode**. Then open **Settings → Plugins**, select the plus button, and add `https://api.trover.tech/mcp`. This customer MCP includes transaction tools and is therefore intended for developer-mode or direct remote-MCP connections. Current OpenAI public plugin guidelines prohibit directory plugins from executing investment trades, crypto transfers, or money transfers.

## Scopes

| Scope             | Access                                                                                                                                                                                                                                                            |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trover:read`     | Account, portfolio, balances, history/status, token, contract, market, stock, claims, fee analysis, and the indexed NFT market: collection detail, price history, activity, orderbook, traits, holders, drops, trending, discovery, snipe status; token economics |
| `trover:profile`  | Private address-book and profile-tool access                                                                                                                                                                                                                      |
| `trover:trade`    | Policy-checked buys and sells, NFT actions, and arming or cancelling an unattended NFT snipe plan (`nft_snipe_manage`)                                                                                                                                            |
| `trover:bridge`   | Policy-checked bridges                                                                                                                                                                                                                                            |
| `trover:transfer` | Policy-checked transfers, burns, fee claims, and contract actions                                                                                                                                                                                                 |
| `trover:launch`   | Policy-checked token launches                                                                                                                                                                                                                                     |

## Tools

Nine fixed tools plus one tool per registered capability, named by the capability id (`packages/domain/src/agent-capabilities.ts`; fifty-four today, listed in `docs/agent-orchestration.md`). `get_capability_catalog` returns the live list with risk, requirements and the scope each one needs.

| Tool                                                                         | Scope                          | Purpose                                                                         |
| ---------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------- |
| `get_connected_account`                                                      | `trover:read`                  | canonical wallet, linked wallets, X identity, delegation, confirmation settings |
| `get_wallet_snapshot`                                                        | `trover:read`                  | short-cached live balances and market context                                   |
| `get_address_book`                                                           | `trover:profile`               | private aliases and linked X recipients                                         |
| `get_capability_catalog`                                                     | `trover:read`                  | the registry as the planner sees it                                             |
| `get_action_status`                                                          | `trover:read`                  | poll an action id                                                               |
| `get_run_status`                                                             | `trover:read`                  | poll an asynchronous run id                                                     |
| `get_paper_agent_league`, `configure_paper_agent`, `place_paper_stock_order` | `trover:read` / `trover:trade` | the paper league                                                                |
| `<capability id>`                                                            | per capability                 | one tool for every registered capability                                        |

A capability tool takes `{ query }` for reads, `{ command, client_request_id }` for transactions, or the typed object below for the NFT capabilities. Every NFT result carries `_meta["ui/resourceUri"]` pointing at the NFT market preview (reads) or the NFT action resource (actions).

## Execution modes

`capabilityExecutionModes` in `apps/api/src/mcp-routes.ts` gives every capability one of four modes:

| Mode            | Behaviour                                                                                                                  | Result shape                                                                                            |
| --------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `direct`        | answered inside the API process, no agent run                                                                              | `{ request_id, status: "completed", terminal: true, result }`                                           |
| `provider_read` | same call path, reads a provider or the wallet (`nft_search`, `nft_portfolio`, `nft_market_snapshot`, balances, portfolio) | as `direct`; a failure is `{ status: "failed", stage: "provider_read", error: "provider_read_failed" }` |
| `agent_run`     | queues an `AgentRun` with a deterministic message and returns immediately                                                  | `{ run_id, status, terminal, retry_after_ms: 750, result? }`; poll `get_run_status`                     |
| `action_run`    | an `agent_run` that creates one idempotent action                                                                          | the action result with `action_id`, `signing_url`, `run_id`                                             |

Indexed NFT reads (`nft_collection_detail`, `nft_price_history`, `nft_activity`, `nft_orderbook`, `nft_traits`, `nft_holders`, `nft_drops`, `nft_trending`, `nft_discovery`, `nft_snipe_status`, `nft_trover`) are `direct`: `localNftApi` calls the API's own `/v1/nft-market/*` routes through `app.inject`, in-process, so the MCP answer is byte-for-byte what the terminal would get from the same route, with the same caches, lifecycle rule and freshness block. There is no second HTTP hop and no OpenSea call on that path. `nft_snipe_manage` is `agent_run` under `trover:trade`: the typed input is turned into a deterministic sentence and the worker's `manageNftSnipe` applies the route's rules (delegated embedded wallet, per-trade confirmation off, rank ceiling or traits, budget covering one item).

## NFT tool inputs

`chain` defaults to `robinhood` everywhere. A collection is resolved from `collection_slug`, else `contract_address` (our index), else `name` (index search, exact folded match or a single hit).

| Tool                                                                  | Input                                                                                                                                                                                                                                                                                                                         |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nft_search`                                                          | `query` (1 to 240), `chain`, `limit` (1 to 50, default 20)                                                                                                                                                                                                                                                                    |
| `nft_portfolio`                                                       | `chain`                                                                                                                                                                                                                                                                                                                       |
| `nft_market_snapshot`                                                 | `collection_slug`, `chain`                                                                                                                                                                                                                                                                                                    |
| `nft_collection_detail`, `nft_orderbook`, `nft_traits`, `nft_holders` | `collection_slug?`, `contract_address?`, `name?`, `chain`                                                                                                                                                                                                                                                                     |
| `nft_price_history`                                                   | collection ref plus `interval` (`1m`, `15m`, `30m`, `1h`; default `1h`), `window` (`1h`, `6h`, `12h`, `24h`, `7d`; default `24h`)                                                                                                                                                                                             |
| `nft_activity`                                                        | collection ref plus `window` (`1m` to `7d`; default `24h`), `event_types[]` (`sale`, `mint`, `listing`, `offer`, `transfer`, `cancel`)                                                                                                                                                                                        |
| `nft_drops`                                                           | `chain`, `collection_slug?`, `lifecycle` (`minting`, `upcoming`, `ended`; default `minting`)                                                                                                                                                                                                                                  |
| `nft_trending`                                                        | `chain`, `window` (`1m` to `24h`; default `15m`), `lifecycle` (`all`, `minting`, `upcoming`, `ended`), `limit` (1 to 40, default 15)                                                                                                                                                                                          |
| `nft_discovery`                                                       | `chain`, `mode` (`trending`, `top`, `new`)                                                                                                                                                                                                                                                                                    |
| `nft_snipe_status`                                                    | `chain?`, `collection_slug?`                                                                                                                                                                                                                                                                                                  |
| `nft_trover`                                                          | none                                                                                                                                                                                                                                                                                                                          |
| `nft_snipe_manage`                                                    | `operation` (`create`, `cancel`), `chain`, `collection_slug?`, `snipe_id?`, `trigger` (`on_reveal` default, `on_listing`), `max_rank?`, `trait_criteria?` (`{ trait: [values] }`), `max_price_per_item_eth?`, `budget_eth?`, `max_items` (1 to 50, default 1), `expires_in_hours` (1 to 720, default 72), `client_request_id` |
| `nft_mint`                                                            | `chain`, `collection_slug`, `quantity` (1 to 20), `max_total_eth?`, `client_request_id`                                                                                                                                                                                                                                       |
| `nft_buy`                                                             | `chain`, `contract_address`, `token_id`, `max_price_eth`, `collection_slug?`, `source_chain?`, `payment_token` (default `ETH`), `client_request_id`                                                                                                                                                                           |
| `nft_sweep`                                                           | `chain`, `collection_slug`, `max_items` (1 to 50), `max_price_per_item_eth`, `source_chain?`, `payment_token`, `client_request_id`                                                                                                                                                                                            |
| `nft_list`                                                            | `chain`, `contract_address`, `token_ids[]` (1 to 50), `price_eth`, `expiry_seconds?`, `client_request_id`                                                                                                                                                                                                                     |
| `nft_offer`                                                           | `chain`, `collection_slug`, `contract_address?`, `token_id?`, `price_eth`, `expiry_seconds?`, `client_request_id`                                                                                                                                                                                                             |
| `nft_accept_offer`                                                    | `chain`, `contract_address`, `token_id`, `order_hash`, `protocol_address`, `client_request_id`                                                                                                                                                                                                                                |
| `nft_cancel_order`                                                    | `chain`, `order_hash`, `protocol_address`, `client_request_id`                                                                                                                                                                                                                                                                |
| `nft_transfer`                                                        | `chain`, `contract_address`, `token_ids[]`, `recipient`, `client_request_id`                                                                                                                                                                                                                                                  |

Every indexed read result includes `freshness` (`chain`, `headBlock`, `indexedBlock`, `lagBlocks`, `indexedAt`, `servedAt`) taken from the live sale lane; a client should show the age rather than present the numbers as current.

Every transaction tool requires a stable `client_request_id`. Retrying with the same ID for the same customer and capability returns the existing action instead of creating another.

Tool results use this shape:

```json
{
  "action_id": "…",
  "status": "awaiting_wallet",
  "terminal": true,
  "stage": "awaiting_wallet",
  "retry_after_ms": null,
  "signing_url": "https://trover.tech/sign/…",
  "warnings": []
}
```

Longer operations also return a `run_id`. Poll `get_run_status`, or poll `get_action_status` after an action ID is available.

`get_connected_account` returns the exact canonical wallet, all active linked wallets, X identity, delegation capability, and confirmation settings directly from the authenticated Trover profile. `get_wallet_snapshot` adds short-cached live balances and market context. These private tools are authoritative and a client must not replace an unavailable result with public web search.

Robinhood Chain (`4663`) is the default for every MCP read and action. BNB Chain (`56`) is selected only when the caller explicitly supplies `chain: "bnb"` or the natural-language request says `bnb`, `bsc`, `bnb chain`, or `binance smart chain`. BNB spot swaps use PancakeSwap and BNB transfers reuse the same profile-owned Privy wallet and policy checks. The BNB wallet snapshot returns native BNB and its USD context; ERC-20 inventory is reported as a partial unavailable source until a server-side BNB account-indexing provider is configured, rather than silently substituting Robinhood balances.

Eligible delegated actions execute according to the same customer settings as web and X. Confirmation-enabled profiles stop at an action-specific confirmation/signing handoff. Trover's identity checks, limits, simulation, approval policy, safe mode, and dry-run settings remain authoritative for every client.

One-call MCP execution has its own production gate: `MCP_DELEGATED_TRADING_ENABLED`. Set it to `false` to force every MCP action through an authenticated signing handoff, regardless of the customer's normal delegation settings.

Signing links expire after 15 minutes. A link is only a locator: the customer must sign in, own the exact bound wallet, and connect that wallet in the browser. Only the explicitly scoped advanced contract-action tool accepts caller-supplied calldata; it still requires ownership validation, simulation, policy checks, and either delegated authority or the signing handoff.

## OAuth discovery

```
https://api.trover.tech/.well-known/oauth-protected-resource
https://api.trover.tech/.well-known/oauth-protected-resource/mcp
https://api.trover.tech/.well-known/oauth-authorization-server
```

Access and refresh tokens, authorization codes, and consent request tokens are stored only as SHA-256 hashes. Access tokens are audience-bound to the MCP resource. Refresh tokens rotate, and detected reuse revokes the token family. Connections can be revoked through the advertised revocation endpoint.

## Local development

Set:

```dotenv
WEB_APP_URL=http://localhost:3000
MCP_RESOURCE_URL=http://localhost:3001/mcp
MCP_ISSUER_URL=http://localhost:3001
ENABLE_CUSTOMER_MCP=true
ENABLE_SIGNING_HANDOFFS=true
MCP_DELEGATED_TRADING_ENABLED=true
```

The production defaults remain disabled until migrations are deployed and dry-run monitoring is healthy.

## OpenAI directory policy

The full customer MCP must not be submitted to the public Plugins Directory while the current prohibition on transaction execution remains in force. A separate read-only server exposing account, portfolio, balances, history, token, contract, market, stock, claims, and fee analysis can be prepared for directory review without exposing trade, bridge, transfer, or launch tools.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.trover.tech/engineering/customer-mcp.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
