MCP Passthrough v0.29.3 exposes a customer MCP server, customer self-service routes, an operator REST API and node-to-node federation routes. Their credentials and privileges are different. This reference describes the released plugin with eiou-docker v0.1.21-alpha; start with Setup and Client setup for an interactive installation.
Contents
- Endpoints and credentials
- MCP session lifecycle
- MCP tools
- Resources and prompts
- Customer HTTP routes
- Operator REST API
- Federation routes
- Limits and failures
Endpoints and credentials
| Surface | Base path | Authentication |
|---|---|---|
| Customer MCP | /p/mcp-passthrough/mcp |
Authorization: Bearer <customer-token>; a plugin-issued token beginning mcp_. |
| Customer HTTP | /p/mcp-passthrough/ |
The customer’s own plugin token. |
| Operator REST | /api/v1/plugins/mcp-passthrough/ |
Host eIOU API key and HMAC signature, with admin scope. |
| Federation | /p/mcp-passthrough/directory, /request-key |
Accepted-contact proof or the route-specific admission rules below. A bearer-shaped header alone is not proof of a contact. |
Public paths require the plugin enabled, its host Public routes switch enabled, and a reachable node address. Use HTTPS with a trusted certificate for internet client connections. The plugin’s upstream provider token, wallet password, customer key and host API secret are not interchangeable.
In default pilot mode, a customer key must be bound to a currently accepted contact to run inference. A valid key can still use MCP diagnostics and inspect its own balance when inference admission is refused. It must also have sufficient prepaid balance or available credit extended to its billing contact. Separate funds/credit are needed when the node buys inference from a seller; see Provider access.
MCP session lifecycle
The endpoint implements JSON-RPC 2.0 over Streamable HTTP. Supported protocol versions are 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05; an unrecognized requested version negotiates the first supported version.
-
Send
POST /p/mcp-passthrough/mcpwith the bearer,Content-Type: application/json, andAccept: application/json, text/event-stream. -
Initialize with a request such as the following:
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": {"name": "your-client", "version": "your-version"} } } -
Save the returned
Mcp-Session-Id. Echo it on subsequent requests with the same bearer. Use the negotiated version inMCP-Protocol-Version. -
Send
{"jsonrpc":"2.0","method":"notifications/initialized"}. Notifications normally return HTTP 202 without a JSON-RPC result. -
Call
tools/list, thentools/callwith a tool name and arguments. For example, this reads a balance and does not buy inference:{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "balance.check", "arguments": {}} } -
End the session with HTTP
DELETEon the same endpoint, sending its bearer and session header; successful termination returns 204. The nonstandardsession/endJSON-RPC method is also implemented.
Sessions idle more than 30 minutes are swept. An unknown/expired session or one belonging to a different bearer returns 404; initialize again. Tool/resource/prompt use requires the initialized session. ping is supported. There is no standalone GET event stream on this route.
For incremental output from chat.completion, send Accept: text/event-stream and a params._meta.progressToken on the tools/call request. The SSE response contains notifications/progress messages for text deltas, followed by the final result. Without the progress token, an SSE response carries only the final result. Providers without streaming support also return only a final result. Other calls can return ordinary JSON; clients must parse both forms. Cancellation uses notifications/cancelled with params.requestId; do not assume cancellation reverses output already generated or its charge.
MCP tools
Use tools/list for the complete machine-readable schemas and annotations. Tool results contain content for presentation and structuredContent for programmatic fields. A tool failure can be returned as result.isError: true even when HTTP and the JSON-RPC exchange succeeded.
| Tool | Arguments | Effect / useful returned fields |
|---|---|---|
chat.list_models |
{} |
Available enabled models, customer rates, currency, active model and routing mode. Includes eligible learned network offers when routing permits them. Discovery is not a guarantee of credit/admission or readiness. |
balance.check |
{} |
Calling key’s key_id, prepaid balance, currency, last_used_at. A prepaid balance is not the full contact-credit arrangement. |
topup.instructions |
{} |
Node Tor/HTTPS locators, verification public key, settlement currency and memo such as mcp-topup:<actual-key-id>. It does not send a payment. |
usage.recent |
Optional limit integer 1–200; default 25 |
Calling key’s recent model/provider, token counts, charged amount, currency and timestamps. |
route.explain |
Optional model, estimated_input_tokens (positive integer, default 500), max_output_tokens (positive integer) |
Dry-run candidates, estimated charges, readiness and reasons for rejected routes. No prompt, credential mint or payment. |
route.status |
Same arguments as route.explain |
Readiness counts, best route and rejections. Reports execution_mode: "synchronous" and pending_requests_supported: false. |
chat.completion |
Required messages; optional model, max_output_tokens, routing |
Runs and bills inference. Returns output, actual model/provider, tokens, charge, currency, balance and route. |
Inference arguments
messages is a nonempty array of {role, content}, where role is system, user or assistant and content is a string. This is a text chat surface; the advertised schema does not offer image blocks, a caller-supplied upstream provider key or arbitrary provider options.
Select a model ID from chat.list_models, or omit it to let the configured routing/default selection apply. max_output_tokens must be positive. The server clamps it down to the operator/model allowance, itself capped at 32768. The default allowance is 1024 unless configured otherwise. Balance preflight reserves against the full allowed output, so a smaller requested cap can reduce the balance required to start.
Optional per-call routing fields:
| Field | Type | Effect |
|---|---|---|
policy |
local_first, cheapest_advertised, manual |
Choose ranking without enabling automatic routing. Advertised cost does not include all payment-route fees. |
remote_allowed |
Boolean | false excludes network sellers; true cannot override an operator prohibition. Locally configured online APIs are still possible. |
max_hops |
Positive integer | Reduce the operator’s discovery-hop horizon. |
max_price |
Decimal string | Reduce the estimated charge ceiling in settlement currency. |
Requests cannot raise an operator spending limit or enable remote routing. A failover may send the prompt to another provider. Your node, the selected provider and any upstream inference service can process prompts and output; their retention policies apply. See Architecture.
Ordinary MCP requests are synchronous: they do not become queued background inference jobs when a seller is not ready. The operator’s explicit CLI deal flow can record/resume a funded job; it is a different surface.
Resources and prompts
resources/list and resources/read expose JSON resources:
| URI | Contents |
|---|---|
mcp-passthrough://provider/active |
Active provider ID/preset, token-presence boolean, model count and active model. Does not return the upstream endpoint or token. |
mcp-passthrough://provider/models |
Available models from configured providers and their customer rates. Use chat.list_models for the catalog that also considers network offers. |
mcp-passthrough://usage/recent |
Last 50 usage records for the calling key. |
resources/templates/list returns no templates. There is no balance resource; use balance.check.
prompts/list and prompts/get expose workflow templates:
| Prompt | Arguments |
|---|---|
concise_summary |
Required content; optional max_output_tokens. |
model_picker |
Required task_description; optional budget, quality. |
balance_check_workflow |
Optional low_threshold. |
Retrieving a prompt supplies instructions to the client; it does not itself run a paid model request. logging/setLevel is accepted as a no-op; logs are not streamed through it. completion/complete supplies model-name, prompt-argument and resource-URI suggestions, not generated model output.
Customer HTTP routes
These use the same customer bearer, without an MCP session.
| Method / route | Request | Response / effect |
|---|---|---|
GET /p/mcp-passthrough/admin-keyinfo |
No body | {ok, key, usage, max_allowed_origins}. Own key metadata and last 25 usage rows only. Despite “admin” in the path, this is customer self-service. |
POST /p/mcp-passthrough/admin-origins |
{"allowed_origins":["https://your-client.example"]} |
Replace this key’s browser-origin override. null or [] clears it and restores the operator default. At most 3 entries are stored. |
POST /p/mcp-passthrough/chat |
{"messages":[{"role":"user","content":"Your prompt"}],"model":"MODEL_ID"} |
Legacy billed chat call; optional model. Returns {ok, result, _log} on its plain JSON path. |
The legacy chat route also accepts a JSON-RPC tools/call envelope with params.arguments.messages and optional model; its legacy tools/list catalog names the tool chat. It does not provide the full MCP session/catalog interface or the MCP tool’s per-call routing and output-cap arguments. Prefer /mcp for new integrations.
Browser Origin checks apply to MCP and legacy chat. Absent Origin is allowed for non-browser clients; loopback is implicit, and other origins must match configured defaults or the key override. Matching the request Host does not implicitly allow an origin. In v0.29.3, matching uses the scheme and hostname and does not restrict the port: an entry with a port also permits other ports on that scheme and host. Origin checks do not replace possession of the bearer. The self-service routes are scoped by the bearer and never accept another customer’s key ID.
Operator REST API
All seven plugin-owned routes below require the host’s HMAC API authentication with admin scope, not a customer bearer or just a wallet browser session.
Authentication
Use a host key ID beginning eiou_ and its API secret. Send:
X-API-Key: <host-key-id>
X-API-Timestamp: <unix-seconds>
X-API-Nonce: <fresh-random-value>
X-API-Signature: <hex-hmac-sha256>
Calculate the hex HMAC-SHA256 with the API secret over the exact string:
UPPERCASE_METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY
Here \n means a literal newline. PATH is the URL path without origin or query string; BODY is the exact transmitted JSON bytes, or empty for GET. Use a unique nonce of 8–64 characters and a timestamp within 300 seconds of the host. Never transmit the API secret itself. See the host authentication implementation and host API reference.
Routes and payloads
All paths below follow /api/v1/plugins/mcp-passthrough/:
| Method / action | Body or query | Result |
|---|---|---|
GET status |
None | Active provider/model, currency, provider summaries and key count. |
GET config |
None | Stored operator config, including currency. Effective routing defaults are resolved when used; absent stored values need not appear here. |
POST configure |
Optional objects set, provider, active |
Validate and atomically apply settings/provider/active-selection changes. |
GET keys |
None | {keys: [...]} metadata; no plaintext customer tokens. |
POST key-create |
Required label; optional contact, registered_pubkey, ttl, currency |
ID and once-only token, contact/funding binding, expiry and warnings. Pilot mode requires accepted contact; currency must match settlement currency. |
POST key-revoke |
{"id": <numeric-key-id>} |
Revoke the key; returns ID, revoked state and any warnings. Does not refund balance. |
GET usage |
Optional limit, key_id; default 50 |
{usage: [...], topups: [...]}; list limits clamp to 1–500. |
Successful results pass through the host envelope: {"success":true,"data":{...},"error":null,...}. The data object is the plugin result listed above. Do not expect the raw plugin IPC {ok,result} wrapper on the external host API.
The v0.1.21-alpha host bridge can collapse a plugin validation failure into data.success: false / data.error: "plugin_unavailable" under an outer HTTP 200 success envelope. Check both layers; inspect node logs when a rejected configuration does not expose its plugin error. Do not treat every HTTP 200 as proof that a change was applied.
configure sections:
set: map of setting names to values. Use string values matching the CLI settings reference.provider: object with requiredid; optionalpreset,endpoint,label,token,clear_token,models. Existing provider fields are merged; an includedmodelsarray replaces the model collection. Omitted or emptytokenpreserves it;clear_token: trueexplicitly removes it and takes precedence over a replacement token.active: object with requiredprovider, optionalmodel. Unlike CLIprovider use, it does not create a missing model. It can select a model supplied in the same request’s provider object.
Model rows use id, label, enabled, pricing_mode (markup or manual), markup_pct, cost_in_per_1m, cost_out_per_1m, cost_xfer_per_gb, cost_currency, manual_in_per_1m, manual_out_per_1m and manual_xfer_per_gb. Monetary rates are decimal strings. New rows default disabled; set actual prices deliberately. cost_currency identifies the denomination of recorded costs, not an exchange-rate instruction. Ollama availability also depends on capability checks. The provider guide explains discovery and pricing.
This REST API does not expose every panel/CLI operation: there are no plugin-owned REST routes for refunds, provider probes, quota changes, directory sync or peer warming. Use the CLI or GUI where documented. The host’s plugin installation, enable/disable and public-route APIs are separate host management routes.
Federation routes
These are used by nodes, normally through the plugin’s directory and managed-credential workflow. MCP clients connect to their own node instead of implementing this handshake.
Directory
POST /p/mcp-passthrough/directory accepts a JSON object, optionally carrying an assert contact-proof object. A valid customer key bound to a currently accepted contact is another admission path.
The response is {v, entries: [...]}. Each entry contains tor_address, models, currency, and hops; model offers contain name, in_price_per_1m and out_price_per_1m. The node’s own offers have hop count zero in its published view; a receiving contact increments it.
- In
pilotmode, a verified contact proof or accepted-contact-bound key is required. - In
openmode, an unverified caller can receive the node’s own advertised models; learned contact topology is still withheld. - Verified callers can receive eligible learned rows too, within the three-hop directory horizon. Discovery does not create a credit agreement or grant inference access to every discovered seller.
Key bootstrap
POST /p/mcp-passthrough/request-key accepts optional label and assert. It creates a zero-balance key and returns {ok, key_id, token, currency, deposit_memo, deposit_pubkey, deposit_onion, note}. Store the returned token securely.
An accepted contact’s valid proof can mint a contact-bound key in either mode, with the proved sender hash also bound for top-up matching. Without proof, this works only in open mode and creates an unbound key: a caller-supplied public key is not trusted as proof of ownership. Fund an unbound key using the returned deposit_memo.
The default key TTL applies. Bootstrap has a global cap of 500 unfunded keys and a cap of 5 unfunded bootstrap keys per proved contact. Unused, unbound, zero-balance bootstrap keys can be reaped after 24 hours; funded/used/bound keys are not erased by that cleanup.
Contact proof
The node sends a nonempty bearer-shaped header for host routing even when bootstrapping without an issued credential. The meaningful identity proof is the JSON assert object:
{
"assert": {
"v": 1,
"pubkey_hash": "ACCEPTED_CONTACT_PUBLIC_KEY_HASH",
"nonce": "32_HEX_CHARACTERS_FROM_16_RANDOM_BYTES",
"ts": 0,
"sig": "HOST_GENERATED_SIGNATURE"
}
}
The placeholders above describe a schema, not a usable proof; ts must be the current Unix timestamp. The signed challenge binds the recipient’s onion address, nonce and timestamp under mcp-passthrough:directory-auth:v1. The host wraps it in the plugin assertion domain and signs with the node identity. The verifier checks the accepted contact, signature, audience, ±120-second clock window and replay nonce. Use the implemented node workflow rather than copying a proof between sellers. See ContactAssertion.php.
Limits and failures
The plugin manifest requests these host-enforced per-route limits:
| Route | Requests/minute | Maximum body bytes |
|---|---|---|
POST mcp, DELETE mcp |
600 | 1048576 |
POST chat |
60 | 1048576 |
GET admin-keyinfo |
120 | 4096 |
POST admin-origins |
60 | 16384 |
POST directory |
60 | 4096 |
POST request-key |
10 | 4096 |
The host enforces route limits before plugin execution. Oversize requests can be rejected without an MCP-shaped error. The plugin pool request timeout is 125 seconds; individual network/provider limits can be shorter. Split large work into bounded calls.
For MCP, distinguish authentication (401), browser origin/admission restrictions, session errors (400/404), JSON-RPC protocol errors, and tool-level isError results. Quota or unavailable-capacity tool results may give retry guidance. Customer calls do not wait in a persistent queue. A failed or interrupted stream may still have delivered output that is billable; check usage.recent before retrying a request whose outcome is uncertain.
Verified against plugin.json, the dispatcher, McpServer.php, tool implementations, RestRouter.php, PeerAdmission.php, and the pinned host API controller/forwarder. Return to the documentation introduction for the setup sequence.
Source: docs/API.md at revision 07476eedee57.