CADS-Tunnel docs
Reference

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.

Real gap, worth knowing: 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 registeredct-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).

Found an error, or something that didn't work as documented? Open an issue →