Learn / Exchange & Broker MCP

Read-only vs trade permission in an MCP setup

Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs before acting on a number.

“Read-only” in an MCP setup can mean two completely different things: a label the server attaches to its own tools, or a permission the venue enforces on your key. Only one of them survives the server being wrong.

This distinction is easy to miss because both are called read-only, both appear in configuration, and in the normal case they agree. They come apart exactly when it matters.

The annotation is a hint, and the spec says so

MCP lets a server attach annotations to each tool it exposes. One of them is readOnlyHint. The schema defines it plainly:

readOnlyHint?: boolean — “If true, the tool does not modify its environment.” Default: false.

So far so good. Now the note directly above it in the same schema file:

“NOTE: all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like title).

Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers.”

And the tools specification repeats it normatively:

“For trust & safety and security, clients MUST consider tool annotations to be untrusted unless they come from trusted servers.”

The implication for trading is blunt. Nothing stops a server from declaring a place_order tool with readOnlyHint: true. The annotation is not verified by anything; it is the server describing itself. A client that decides what to auto-approve by reading annotations is asking the component it is supposed to be guarding for permission to stop guarding it.

This is not a flaw in MCP. Annotations exist to improve UI and model behaviour, and the spec is honest about what they are. It is a flaw in using them as a security boundary.

The defaults are better than people assume

Worth knowing, because it changes how you read a server that annotates nothing:

FieldDefaultWhat the default means
readOnlyHintfalseAssume the tool modifies state
destructiveHinttrueAssume the modification is destructive
idempotentHintfalseAssume calling twice does it twice
openWorldHinttrueAssume it touches external systems

An unannotated tool is, by the specification’s own defaults, treated as destructive and non-idempotent. That is the right default, and it means a server that annotates nothing is easier to reason about than one that annotates optimistically. Absence of annotations is not a red flag. Confident annotations from a server you have not audited are.

Note idempotentHint defaulting to false in particular. For an order-placement tool that default is correct and load-bearing: calling it twice places two orders.

Where read-only can actually be enforced

Three layers, in increasing order of how much they are worth.

1. The server’s own configuration

Some servers implement real read-only modes, and the good ones are quite thorough. Among the Bybit community servers, one ships with trading disabled by default, requires an explicit TRADING_ENABLED=true to enable mutating tools, offers a READONLY_MODE=true that hard-blocks mutations, and caps estimated order notional with a MAX_ORDER_SIZE_USDT setting. Another is read-only by construction and recommends only ever pointing a read-only API key at it.

This is genuinely good engineering and you should prefer servers that do it. But it is a control inside the component you are bounding. It protects you from the model; it does not protect you from the server being buggy, being updated, or being something other than what you thought you installed.

2. The venue’s key permissions

This is the layer that holds regardless of what the software does, because it is enforced on the other side of the network by a party with no stake in your configuration being right.

On a centralised exchange this is the API key’s permission set. The detail worth knowing on Binance: withdrawals cannot be enabled at all on a key without IP access restriction — the filter is a precondition, not an option alongside it. And a key with no IP whitelist has its spot trading permission automatically switched off after a period, by design, as a standing hygiene mechanism.

The practical shape: a key that can read and nothing else cannot be talked into placing an order by any prompt, any server bug, or any annotation. There is no argument to be had with the exchange.

Binance also exposes the key’s current state programmatically via GET /sapi/v1/account/apiRestrictions, which returns fields including ipRestrict, enableReading, enableWithdrawals and enableFutures. If you want to verify what a key can do rather than remember what you ticked, ask.

3. Local code between the model and the venue

The layer the venue cannot provide, because the venue does not know your rules. Per-trade size caps, a daily-loss breaker, a cooldown after consecutive losses — these are yours, and they only bind if they are evaluated as functions rather than read as instructions.

The case that breaks the mental model

Everything above assumes a key with permissions attached to it. Hyperliquid does not work that way, and it is worth knowing before you carry CEX advice across.

There is no API key. A master account approves an API wallet (also called an agent wallet) to sign on its behalf. The documentation is explicit that “API wallets are only used to sign”, and that to query account data you must pass the actual address of the master or sub-account — the agent’s own address owns nothing.

Can that agent wallet move funds? Hyperliquid’s documentation does not say either way; there is no prohibition to quote. What its official Python SDK shows is structural: the fund-moving actions (withdraw3, usdSend, spotSend) are user-signed and take no account or vault address, whereas trading actions go through the L1 signing path that does carry one. That address field is the mechanism by which an agent’s signature is attributed to the master. Without it, there is no way to express “move the master account’s funds” in the signed payload at all.

So the practical answer is that the signing scheme gives an agent wallet no way to express a withdrawal of the master’s funds — a structural property rather than a permission flag. That is a meaningfully different kind of guarantee from a checkbox, and arguably a stronger one. But Hyperliquid has not stated it, and you should verify it against your own setup before relying on it as a security boundary.

What to actually do

  • Treat readOnlyHint as a UI affordance. It is useful for making a client’s interface sensible. It is not a permission.
  • Set the permission at the venue, and prefer a key that cannot trade until you have decided you want it to.
  • Prefer servers with explicit default-off trading and a real read-only mode — then still set the key permission, because the two protect against different failures.
  • Re-check after updates. A server’s tool list can change under you; the protocol has a notifications/tools/list_changed message precisely because this is expected behaviour.

FAQ

If a tool says readOnlyHint: true, can it still place an order?

Yes. The annotation is a claim the server makes about its own tool, and the MCP schema states outright that annotations “are not guaranteed to provide a faithful description of tool behavior”. The specification further requires clients to treat annotations from untrusted servers as untrusted. Use the annotation to build a sensible interface; use the venue’s key permissions to decide what is actually possible.

Is a server that provides no annotations less safe?

No, and slightly the opposite. The schema defaults assume the worst — readOnlyHint false, destructiveHint true, idempotentHint false — so an unannotated tool is treated as destructive and non-idempotent. A server that annotates nothing gives you conservative defaults. A server that annotates confidently gives you claims you would have to audit to trust.

Can I use one API key for both reading and trading if I am careful?

You can, and the question is what “careful” is protecting against. A single key with trade permission means every path to that key — the server, the client, the model, anything that can read your environment variables — is a path to an order. Separate keys collapse that to one path. The operational cost is configuring two credentials once.

How do I check what a key is actually allowed to do?

Ask the venue rather than your memory. On Binance, GET /sapi/v1/account/apiRestrictions returns the current permission flags including ipRestrict, enableReading and enableWithdrawals. Other venues expose equivalents to varying degrees. Checking takes one request and catches the case where a key is not configured the way you believe.