MCP tools over a channel
Once you’re serving a channel (see
Serve a callable service over a channel),
your peer talks to you over real Model Context Protocol — JSON-RPC 2.0
initialize / tools/list / tools/call, protocol version 2024-11-05 — not just a raw payload pipe.
This page is what’s actually reachable, driven entirely by which env vars you set.
What’s live
| Tool | On by default? | Enabled by |
|---|---|---|
ping |
Yes | Nothing — every served channel has it. |
agent/card |
No | CT_AGENT_CARD_* (see Publish an agent card). |
auction/offer, auction/bid |
No | CT_AGENT_OFFER_* (see Environment variables (channels, cards, offers)). |
service/<slug> |
No | CT_AGENT_SERVICE_HANDLER_CMD + CT_AGENT_SERVICES — fixed {input: string} -> {output: string} shape. |
Call tools/list first. It only ever lists what your own env actually turned on, so it’s the
authoritative answer for “what can I call on this peer” — no need to guess from the table above.
ct_common::mcp also defines chat, propose,
and settlement/* tools (real, tested code — checked their own test coverage directly). But
no shipped ct-agent binary ever calls the functions that would register them
(register_chat_tool/register_propose_tool/register_settlement_tools)
— confirmed by grepping channel_run.rs's non-test code for every call site. Don't expect
them in a live tools/list; only the four rows above are reachable today.
Verified live: tools/list
Started a real serve process exposing two services, then queried it from a separate process:
CT_CHANNEL_CALL=tools/list ./ct-agent channel # + the CT_CHANNEL_* join env for your channel
Real response:
{"jsonrpc":"2.0","id":1,"result":{"tools":[
{"description":"liveness check → returns pong","name":"ping"},
{"description":"typed safety_check service — fixed {input:string} -> {output:string} shape","name":"service/safety_check"},
{"description":"typed text_generation service — fixed {input:string} -> {output:string} shape","name":"service/text_generation"}
]}}
No agent/card (no CT_AGENT_CARD_* set for this run) and no auction/* (no offer configured) —
exactly matching the table above.
Verified live: tools/call
CT_CHANNEL_CALL=tools/call \
CT_CHANNEL_CALL_PARAMS='{"name":"ping"}' \
./ct-agent channel
Real response:
{"jsonrpc":"2.0","id":1,"result":{"reply":"pong"}}
CT_CHANNEL_CALL_SERVICE=<slug> (covered in
Serve a callable service over a channel) is a
convenience wrapper around exactly this — tools/call with {"name": "service/<slug>", ...} — for the
one shape most callers actually need.
A signed offer caps what CT_AGENT_SERVICES can register
Found reading the source for this page, not previously documented: if you configure both an offer
(CT_AGENT_OFFER_*, which includes CT_AGENT_OFFER_SERVICES) and CT_AGENT_SERVICES, the offer’s
declared service catalog becomes a hard ceiling, not just a separate advertisement. Any
CT_AGENT_SERVICES entry outside the offer’s declared services is refused, not silently registered
— ct-agent logs exactly which ones and why:
ct-agent channel: REFUSING 1 service tool(s) not in the signed offer's declared catalog (#167): [...]
The reasoning: a buyer can cryptographically verify your signed offer’s claims, so what you actually
serve should never be able to exceed what that signature promises. With no offer configured,
CT_AGENT_SERVICES stands alone — the unchanged self-asserted regime described in
Environment variables (channels, cards, offers).