iicp-client · Rust SDK
Official Rust client library for the IICP protocol — route AI agent tasks by intent across a self-organising mesh of provider nodes. No central broker. No hardcoded endpoints.
urn:iicp:intent:llm:chat:v1 → discover → select → submit
Install
Or add to Cargo.toml directly:
[]
= "0.7.35"
To run a provider node from the command line, install the iicp-node binary:
Or for the latest unreleased code:
[]
= { = "https://github.com/RobLe3/iicp-client-rust" }
Architecture — consumer or provider?
This SDK covers both sides of the IICP protocol:
| Role | What you do | Type |
|---|---|---|
| Consumer | Send AI tasks to the mesh; discover and submit | IicpClient |
| Provider | Run a node, register with the directory, serve tasks | IicpNode |
Consumer and provider can run in the same process. For production provider nodes backed by Ollama/vLLM, see iicp.network/docs/node-setup.
Quickstart
chat() discovers the best node and submits the task internally (SDK-01) — no
manual node selection needed.
use ;
async
Need the discovered nodes directly? Call discover yourself — the third
argument is an optional W3C traceparent for trace propagation:
let nodes = client.discover.await?;
let node = nodes.nodes.into_iter.next.expect;
Configuration
use ClientConfig;
let config = ClientConfig ;
| Field | Default | Description |
|---|---|---|
directory_url |
"https://iicp.network/api" |
IICP directory endpoint |
timeout_ms |
30000 |
Request timeout — max 120 000 ms |
region |
None |
Preferred node region |
node_token |
None |
Bearer token for authenticated nodes |
Discover options
use DiscoverOptions;
let nodes = client.discover.await?;
Error handling
use IicpError;
match client.submit.await
Error codes match the IICP error reference.
Serving as a node — handler contract
When you run a serving node (IicpNode::serve), your handler returns the inner result
value; serve() wraps it in the TaskResponse.result envelope for you. Do not return
an already-wrapped {"result": ...} value — that double-nests the response and breaks
cross-flavour interop with the Python/TS SDKs (response shape must be {"result": {...}}).
The backends::invoke_backend / openai_compat::invoke helpers return a
{"result": ...} consumer envelope, so when using them as a serve handler, unwrap the
inner value first:
let v = invoke_backend
.await
.unwrap_or_else;
// serve() re-wraps in TaskResponse.result — return the inner value to stay single-level.
Ok
Backends — pick an inference engine
iicp-node serve (and the backends::invoke_backend dispatch) supports four
backend engines, selected with --backend-type / IICP_BACKEND_TYPE
(default openai_compat):
--backend-type |
Speaks | Typical backend |
|---|---|---|
openai_compat |
OpenAI /v1/* |
Ollama, LM Studio, any OpenAI-compatible server |
vllm |
OpenAI /v1/* |
vLLM OpenAI server (default port 8000) |
llamacpp |
OpenAI /v1/* |
llama.cpp llama-server (default port 8080) |
anthropic |
Anthropic Messages API (POST /v1/messages) |
Anthropic API → first-class Claude |
The anthropic backend translates the IICP llm:chat:v1 task into an Anthropic
Messages request and translates the reply back to the OpenAI chat-completion
shape — so a Claude-backed node looks identical to any other node to IICP
clients. It hoists system-role messages into the top-level system param, sends
x-api-key + anthropic-version headers, and defaults max_tokens (Anthropic
requires it). With no --backend-url override it targets https://api.anthropic.com/v1.
# Serve Claude as an IICP node
# or set IICP_BACKEND_TYPE / IICP_BACKEND_API_KEY in the environment
The API key comes from --backend-api-key (env IICP_BACKEND_API_KEY). For the
OpenAI-compatible backends this is sent as a Bearer token; for anthropic it is
sent as the x-api-key header.
Input modalities — text, image, audio
A node advertises the input modalities each model accepts under
capabilities[].input_modalities, detected from the model name (conservative
name-pattern matching, ADR-046 / #408 / #414):
| Model name contains | Advertised modalities |
|---|---|
vl / vision / llava |
["text", "image"] |
audio / voxtral |
["text", "audio"] |
omni |
["text", "image", "audio"] |
| anything else | ["text"] |
Each modality is a modality of chat, not a separate intent. A single node hosting
several models advertises one capability object per (intent, input_modalities)
group, so a text model and a vision model on the same node surface as distinct
capabilities. Image and audio are passed through OpenAI-style content parts
(text and image_url blocks); the anthropic backend maps image_url parts
(data-URL or remote URL) into native Anthropic image content blocks.
Listen port — default 9484, auto-increment (v0.7.5+)
The official IICP port 9484 is the default listen port (IICP_PORT, --port).
The iicp-node binary auto-increments to the next free port when 9484 is already
in use, so several nodes on one host don't need hand-picked ports — first binds
9484, second 9485, third 9486, etc. Each node gets its own port (hence its own NAT
pinhole); multiple models on one node share that single port. Auto-increment is
skipped when you pass an explicit --public-endpoint.
NAT traversal — automatic (v0.7.3+)
Since v0.7.3, NAT detection runs automatically on every iicp-node serve startup — no flags
needed. Requires the nat feature (UPnP detection):
[]
= { = "0.7", = ["nat"] }
# For relay substrate (CGNAT fallback): add "iicp-tcp"
= { = "0.7", = ["nat", "iicp-tcp"] }
| Tier | When | What happens |
|---|---|---|
| 0 | VPS/cloud (public IP on NIC) or IICP_PUBLIC_ENDPOINT set |
Registers directly |
| 1a | Home router with UPnP, no CGNAT | Port-forward via UPnP → register WAN IP |
| 1b | CGNAT + IPv6 + AddPinhole works | Registers IPv6 with firewall rule |
| 1c | CGNAT + IPv6 + AddPinhole fails (FRITZ!Box error 606) | Registers IPv6 + logs guidance |
| 3 | CGNAT + no usable IPv6 | Auto-elects relay from directory |
| 4 | Nothing worked | Serves locally with operator guidance |
Environment-specific behaviour
Docker bridge (-p 8020:8020) — UPnP is skipped (reaches Docker NAT, not home router).
Set IICP_PUBLIC_ENDPOINT:
CGNAT + no IPv6 → automatic relay:
[iicp-node] NAT tier=3: auto-electing relay from directory...
[iicp-node] auto-elected relay: relay.example.com:9485
Running a relay-capable node (relay operator)
use ;
let node = new;
Opt-out / override
IICP_AUTO_DETECT_NAT=false # disable detection entirely
IICP_PUBLIC_ENDPOINT=http://x.x.x.x:8020 # trust this endpoint
IICP_RELAY_WORKER_ENDPOINT=host:9485 # specific relay instead of auto-elect
SDK conformance
| Rule | Description | Status |
|---|---|---|
| SDK-01 | discover → select → submit pipeline | ✓ |
| SDK-02 | task_id auto-generated (UUID v4) |
✓ |
| SDK-03 | Intent URN pattern validation (regex) | ✓ |
| SDK-04 | timeout_ms capped at 120 000 ms |
✓ |
| SDK-05 | Retry on transient errors (429 / 502 / 503 / 504) | ✓ |
| SDK-06 | W3C traceparent propagation (shared across discover + submit) |
✓ |
Conformance tier: iicp:sdk:v1 (spec S.14) · Request a badge
Development
Links
- Protocol spec — full IICP specification
- Node setup guide — run your own node
- Error reference — all error codes
- iicp-client-python — Python SDK
- iicp-client-typescript — TypeScript SDK
Apache 2.0 · iicp.network