/mcp with a bearer token and parse the reply. This is supported, not a workaround.
Compatibility notes
Compatibility notes
Verified 2026-09-08 against the deployed service on
@modelcontextprotocol/server 2.0.0, and re-measured 2026-10-13 on 2.2.0, the version the service now runs. Two shapes this guide never uses are refused with HTTP 400:- A request that claims protocol revision
2026-07-28without theMCP-Protocol-Versionheader is refused with-32020(since@modelcontextprotocol/server2.2.0). - A JSON-RPC batch is refused at any size, a batch of one included. Send one request per POST.
It works, and you do not need initialize
/mcp answers a request that carries no protocol-revision claim statelessly, one request at a time. A bare tools/call returns a result with no handshake, no session ID, and nothing to keep alive between calls.
Every call is independent. There is no session to establish, resume or tear down.
Two things that will break your first request
Send both media types in
Accept, or you get a 406. Accept: application/json on its own, and a missing Accept header, are both rejected before your request is read:406 response
A 200 comes back SSE-framed, not as a JSON body. The response is
content-type: text/event-stream and looks like this, so JSON.parse on the raw body fails:200 response body
- Take the line beginning
data:. - Strip that prefix.
- Parse what is left.
Despite the framing this is a single-shot request/response: one frame, then the connection closes.
GET /mcp is a 404: there is no stream to subscribe to.A working call
curl
tools/list works over the same bare POST and returns each tool’s input schema. Read argument names from there rather than guessing them, and read the required array as well as the properties, because on the write surface it is load-bearing.
Read this before you trust a result
Skippinginitialize means you never receive the server’s instructions string, which is where the arbitration semantics are written down. An MCP client is handed it on connect; you are not. Fetch it yourself:
Skill document
Unauthenticated. Required reading, not background.
Pass chain explicitly
Pass chain explicitly
dispute_get, dispute_list and evidence_list all fan out when you omit it: across ethereum, then gnosis, then arbitrum-one, drained in that order. Page one is entirely ethereum, so an absent result means “not on ethereum yet”, not “does not exist”.arbitrum-one is Kleros v2 Beta, the live production deployment
arbitrum-one is Kleros v2 Beta, the live production deployment
chain: "arbitrum-one" is Kleros v2 Beta, and Beta is the live production deployment: real disputes, real arbitration fees, real money. The name describes the release, not the network. The public testing deployment with test data is chain: "arbitrum-sepolia-testnet". A client that read “Beta” as a testnet has already answered a question about live disputes with test data.arbitrum-sepolia is retired
arbitrum-sepolia is retired
arbitrum-sepolia is retired and is now refused. It used to name the Devnet, which is now arbitrum-sepolia-devnet, so a find-and-replace onto the advertised arbitrum-sepolia-testnet succeeds and silently reads a different deployment. Relatedly, any unresolvable chain is now an error: it previously fell through to a live three-chain production fan-out reported as success.Failed tool calls carry no error code
Failed tool calls carry no error code
The result is
isError: true plus rendered text: no code field, no structuredContent. AgentKit raises codes such as CHAIN_REQUIRED and CHAIN_NOT_SUPPORTED internally and they are real on its command line, but the MCP layer emits only the message, so they never reach you. Branch on isError; treat the text as prose that moves between releases.ruled means on each Kleros generation, why dispute_get and dispute_list can disagree about it, and which tools refuse to fan out at all.
One request per POST
Send each JSON-RPC message in a POST of its own. A JSON-RPC array (a batch, of any length, 1 included) is refused with HTTP 400 before any tool runs:400 batch refusal
The refusal is decided after your key is checked and metered, so a refused batch still spends one request of your limit.
One write at a time per key
dispute_create, evidence_pin and evidence_submit run one at a time per key (strictly, per signer your key’s Preset names), so your Beta and Testnet keys do not block each other.
Limits and health
Your key is metered at 30 requests per minute, per key. One request is one POST, and since a POST carries exactly one call, one request is one tool call./health is unauthenticated so the platform can probe it. A green /health means the HTTP process is up; it does not mean the arbitration backend is reachable, which is only exercised by a real tool call.