iicp-client · Rust SDK
Use the open AI mesh from your Rust app. Install the client, send an intent, and get a routed response from an IICP node.
You do not need to run a node to try the client path. Consume first, provide later.
urn:iicp:intent:llm:chat:v1 → discover → select → submit
Install
Or add to Cargo.toml directly:
[]
= "0.7.83"
One-line test
Install the CLI and ask the mesh:
What good looks like:
The query command contacts the public directory, discovers a matching live node, routes your prompt, and prints the response. No account, API key, or local node is required for this consumer path.
Privacy note: the selected remote node can read the prompt it executes. IICP-CX keeps key-ready transport/relay paths confidential, but it is not executor-blind inference. For sensitive data, use local/browser inference or a fail-closed routing profile.
Use from Rust
use ;
async
Do I need to run a node?
No. Running a node is only needed when you want to provide compute or tools to the mesh. Start as a client; run a node later when you want to contribute.
Routing policy profiles
The client applies routing policy after prompt-free discovery and before the prompt is sent. Defaults stay adoption-friendly but keyless plaintext is still refused.
use ;
let reply = client.chat.await?;
For stricter deployments, require a minimum policy-manifest identity level before any prompt leaves the client. This keeps the default open mesh behavior unchanged, but lets controllers fail closed on self-attested or rotated/revoked providers.
let reply = client.chat.await?;
Migrate from existing AI tools
Direct call:
// Before: call one vendor endpoint directly.
// After: ask IICP to discover and route by capability.
let reply = client.chat.await?;
Existing OpenAI-compatible tools:
Then point LangChain, Cursor, liteLLM or another OpenAI-compatible tool at that base URL. Full guide: https://iicp.network/docs/proxy
Provider upgrade note
Upgrade note (0.7.80) — clients now support remote-routing policy profiles that can refuse unsafe remote dispatch before any prompt leaves the caller. Use
--routing-profile sensitivefor fail-closed no-remote behavior,eu-restrictedfor EU/EEA node filtering, orstrict-policywhen a no-retention node policy manifest is required.Existing provider reachability fixes from 0.7.79 remain intact.
Keeping provider nodes current
Provider nodes run an hourly official-registry check by default
(IICP_AUTO_UPDATE=1, IICP_AUTO_UPDATE_INTERVAL_S=3600; minimum 300s).
When crates.io publishes a newer stable release, serve installs it with
cargo install iicp-client --force --features nat,iicp-tcp and re-execs the
node so identity and cached node tokens are preserved.
If a node is older than 0.7.67, perform one manual upgrade/restart first,
especially for Dockerized Python or TypeScript providers: early updater wiring
did not reliably cover every normal serve path. For Docker, use a restart
policy such as --restart unless-stopped so 0.7.80 can intentionally exit from
a confirmed tunnel-dead state and let Docker bring it back cleanly.
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.
Library 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;
Use as a local API proxy (OpenAI / Ollama / Anthropic compat)
Run a local gateway that speaks the OpenAI, Ollama, and Anthropic HTTP APIs and routes every request across the IICP mesh — point any tool you already use at it, no code changes.
# OpenAI SDK / LangChain / Cursor / liteLLM
# Open WebUI / Continue.dev / aider / Jan
Loopback-only consumer (never registers with the directory). The proxy is behind the
proxy Cargo feature (kept out of default so library builds stay lean). Override the
port with --port / IICP_PROXY_PORT; co-host next to a node with
iicp-node serve --with-proxy. Every response carries Server: iicp-proxy. Full guide:
https://iicp.network/docs/proxy
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 |
routing_policy |
RoutingPolicy::default() |
Pre-dispatch remote-routing gate; use Sensitive, EuRestricted, StrictPolicy, or an explicit debug override for special cases |
node_token |
None |
Bearer token for authenticated nodes |
routing_epsilon |
0.05 |
ε-greedy exploration probability — with this probability a random node is selected instead of the top-ranked one, promoting discovery of new providers; 0.0 disables; override with IICP_ROUTING_EPSILON |
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 | Opens a Quick Tunnel if available → otherwise auto-elects relay |
| 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).
The official image includes cloudflared, so without a public endpoint it first tries a
zero-account Quick Tunnel, then relay. The image also sets IICP_SUPERVISED=1, so
with Docker restart policy enabled a confirmed tunnel-dead state exits visibly and lets
Docker restart the node. For stable direct hosting, set IICP_PUBLIC_ENDPOINT:
CGNAT + no IPv6 → Quick Tunnel, then relay:
[iicp-node] NAT tier=3: opening Quick Tunnel...
[iicp-node] no tunnel available — 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;
Security note: relay bind authentication is pending (#510) — only run a relay accept port on networks you trust until the signed-bind mechanism ships.
Opt-out / override
IICP_AUTO_DETECT_NAT=false # disable detection entirely
IICP_PUBLIC_ENDPOINT=http://x.x.x.x:8020 # trust this endpoint
IICP_TUNNEL=0 # opt out of Quick Tunnel fallback
IICP_TUNNEL_CREATE_MIN_INTERVAL_S=120 # host-wide Quick Tunnel create pacing
IICP_TUNNEL_DEAD_POLICY=auto # auto|retry|exit|log-only (auto = supervised exit, manual retry)
IICP_SUPERVISED=1 # set by generated services/Docker so supervisors can restart
IICP_AUTO_UPDATE=1 # hourly provider self-update; set 0 to disable
IICP_AUTO_UPDATE_INTERVAL_S=3600 # update cadence in seconds; minimum 300
IICP_RELAY_WORKER_ENDPOINT=host:9485 # specific relay instead of auto-elect
Operator identity
Your operator identity is an ed25519 keypair — its public key is your operator_id (the
directory stores it as operator_pubkey). One identity spans every node you run: it binds them to
you (nodes show Operated by <your name> ✓), earns a
founder ordinal, and rolls each node's credits into one operator
wallet. Your display_name is the public, mutable handle; your contact stays local.
The key is the identity — whoever holds ~/.iicp/operator.json controls it (its nodes, ordinal,
and wallet); there is no central recovery. Back it up (encrypted), never commit or share it; lose it
and the identity, with its founder ordinal, is gone.
Full guide: iicp.network/docs/operator-identity
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