MCP Passthrough · Documentation

API Reference

Authentication and contracts for MCP, customer, operator, and federation endpoints.

For plugin v0.29.3 on eiou-docker v0.1.21-alpha. Plugin & downloads · Alpha pilot

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

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.

  1. Send POST /p/mcp-passthrough/mcp with the bearer, Content-Type: application/json, and Accept: application/json, text/event-stream.

  2. 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"}
      }
    }
  3. Save the returned Mcp-Session-Id. Echo it on subsequent requests with the same bearer. Use the negotiated version in MCP-Protocol-Version.

  4. Send {"jsonrpc":"2.0","method":"notifications/initialized"}. Notifications normally return HTTP 202 without a JSON-RPC result.

  5. Call tools/list, then tools/call with 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": {}}
    }
  6. End the session with HTTP DELETE on the same endpoint, sending its bearer and session header; successful termination returns 204. The nonstandard session/end JSON-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 required id; optional preset, endpoint, label, token, clear_token, models. Existing provider fields are merged; an included models array replaces the model collection. Omitted or empty token preserves it; clear_token: true explicitly removes it and takes precedence over a replacement token.
  • active: object with required provider, optional model. Unlike CLI provider 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 pilot mode, a verified contact proof or accepted-contact-bound key is required.
  • In open mode, 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.