Use the MCP server#

MCP, the Model Context Protocol, lets an AI agent call a service’s tools over HTTP; Streamable HTTP is its remote transport. The hosted TauOpt MCP server exposes the same experiment surface as the hosted REST API (create, suggest, observe, best, list, archive) as twelve tools over MCP Streamable HTTP, so an agent in Claude Code or any other MCP client can drive an optimization loop in conversation. The server is https://mcp.tauopt.com/mcp, and the tau_ token you mint at app.tauopt.com/settings/tokens is the credential (Hosted beta: getting started walks the sign-in and the mint). This page was verified on the validation date by reading the server source in the closed service repository and by running it: the health check, the four 401 rows, the twelve tools as served, and one experiment driven through create, ask, report, best, and archive from the Python SDK; the 403 for a browser Origin was checked against the live server on 2026-10-20. Where the MCP server sits relative to the REST API and the engine is drawn on Run against the hosted service.

With the hosted examples over plain HTTP, this page is the closed beta’s whole surface: the package is not distributed until the public release, neither path installs the package (five of the hosted scripts need their objective’s libraries), and the token you minted rides every request.

Before you start#

  • An MCP client that speaks Streamable HTTP and can send a static Authorization header. The server authenticates with a bearer token only; there is no OAuth flow, so a client that insists on an OAuth login cannot connect.

  • The MCP URL, https://mcp.tauopt.com/mcp: a separate host from the REST base URL (the service runs its MCP server beside the API), and the endpoint is the /mcp path on it.

  • Your token: tau_ followed by exactly 43 URL-safe characters (A-Z, a-z, 0-9, _, -). The server checks that shape before verifying the token.

  • The server answers with the engine its lane runs: the first suggestion’s provenance.engine_version names it, and on the validation date (2026-10-31) the beta ran engine 0.4.0, the version these pages describe (Run against the hosted service explains when the pages can lead the lane).

Add the server to Claude Code#

  1. With TAUOPT_TOKEN exported in your shell, register the server:

    claude mcp add --transport http tauopt https://mcp.tauopt.com/mcp --header 'Authorization: Bearer ${TAUOPT_TOKEN}'
    

    --transport http selects Streamable HTTP and --header attaches the bearer token to every request. The single quotes pass ${TAUOPT_TOKEN} as a literal, so the token never enters the command line (argv is world-readable on shared machines); Claude Code expands the reference from its own environment at connection time. The alternative is to register the server without --header and add the header by hand to the server’s entry in Claude Code’s config file (~/.claude.json for the default scope). The flag names are from claude mcp add --help. Keep the default scope (local): the project scope writes a .mcp.json into the repository; with the literal reference that file holds no token, but a server entry for your closed-beta host does not belong in a shared checkout.

  2. Confirm the entry with claude mcp get tauopt. It prints the header as the stored ${TAUOPT_TOKEN} reference, never the token (the status line carries the CLI’s check mark or cross):

    tauopt:
      Scope: Local config (private to you in this project)
      Status: Connected
      Type: http
      URL: https://mcp.tauopt.com/mcp
      Headers:
        Authorization: Bearer ${TAUOPT_TOKEN}
    

    The first probe after a quiet spell can report Failed to connect with Version negotiation probe timed out while the service wakes; run it again. A rejected token also shows as a failed connection: the two curl lines of the 401 table below tell the cases apart. In a session the twelve tools appear under the tauopt server name, for example mcp__tauopt__create_experiment.

Configure another MCP client#

For clients that take a servers map, the entry is an HTTP server with one static header (Cursor’s mcp.json, Claude’s custom connectors with a request header, and the Python SDK are shown on Hosted beta: getting started):

{
  "mcpServers": {
    "tauopt": {
      "type": "http",
      "url": "https://mcp.tauopt.com/mcp",
      "headers": {
        "Authorization": "Bearer tau_<your-token>"
      }
    }
  }
}

Whether a given client accepts a static header on a remote HTTP server is that client’s documentation to answer; the server needs exactly this header on every request, tools/list included.

Check the connection#

GET /health on the MCP host needs no token and answers {"status":"ok"}; it says the server is up and nothing about your token. Every request to /mcp is authenticated before it is served. The two lines that tell the cases apart:

curl -sS https://mcp.tauopt.com/health
curl -sS -i -X POST -H "Authorization: Bearer $TAUOPT_TOKEN" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}' https://mcp.tauopt.com/mcp

The first prints {"status":"ok"}. The second answers HTTP/2 200 with an mcp-session-id header and the server’s initialize result when the token is good (both lines were run against the service on the validation date), or the 401 of the table below with the error_description that names your case.

A client that opens a session this way carries the protocol version from then on; a client that discovers the modern protocol instead (the Python SDK’s default) sends the protocol’s per-request fields on every call for you. A request you send yourself on the modern stateless protocol – a tools/list from a script, say – must carry them: in the request’s params._meta, the protocol version under io.modelcontextprotocol/protocolVersion and the client’s capabilities (an empty object is fine) under io.modelcontextprotocol/clientCapabilities; and in headers beside the body: MCP-Protocol-Version with the same version, Mcp-Method with the request’s method, and, on a tools/call, Mcp-Name with the tool’s name (the server checks that the headers and the body agree). With the headers but without the envelope the server answers 400 with a JSON-RPC error -32602 whose message names the two keys; without the headers the request takes the legacy session path and answers a plain 400 for the missing session; either way it is not served. A call that carries the envelope and the headers:

curl -sS -X POST -H "Authorization: Bearer $TAUOPT_TOKEN" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "MCP-Protocol-Version: 2026-07-28" -H "Mcp-Method: tools/list" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' https://mcp.tauopt.com/mcp

The answer is a JSON-RPC result carrying the tool list (the twelve names below); the validation run’s, cut after the first tool’s opening:

{"jsonrpc":"2.0","id":1,"result":{"cacheScope":"private","resultType":"complete","tools":[{"annotations":{"destructiveHint":false,"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false},"description":"Create an optimization experiment ...

The same request without the _meta envelope answered 400 (the status line shown with -i):

HTTP/2 400
{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"params._meta must be an object carrying the required 'io.modelcontextprotocol/protocolVersion' and 'io.modelcontextprotocol/clientCapabilities' envelope keys"}}

The Python SDK sends the envelope and the headers for you; Claude Code, the custom connectors and Cursor carry the protocol’s fields themselves, so none of them needs the envelope by hand. A failed check answers 401 with a JSON body {"error": "invalid_token", "error_description": "..."} and a WWW-Authenticate: Bearer realm="tauopt" challenge (the challenge carries error="invalid_token" for the last two rows):

Request

Status

error_description

No Authorization header

401

Authorization header required

A scheme other than Bearer, or an empty token

401

Bearer scheme required

A token that fails the tau_ + 43-character shape

401

malformed token

A well-formed token that is unknown, revoked, or issued for another lane

401

unknown or revoked token

Two more answers are not verdicts on your token. 429 with {"error": "rate_limited"} and Retry-After: 10 means first-sight token verification is being rate-limited: retry after the delay. 503 with {"error": "verification_unavailable"} and Retry-After: 10 means the server could not reach its verifier. A 403 with {"error": "origin_forbidden"} appears only when a browser-style Origin header is present and not allowed; non-browser MCP clients send none. A verified token is cached at this boundary, so a token revoked or rotated on the tokens page keeps working on the MCP server for a short window: up to a minute on the server image the beta runs (the revoke answer’s mcp_revocation_lag_s reports the same figure, 60). The REST API refuses a revoked token at once.

The twelve tools#

Each tool maps to one route of the hosted REST API (the Client wraps every one of them). Experiment and suggestion ids are integers in tool arguments; they come back as strings in results, so pass them as integers in the next call (a numeric string is accepted). The arguments, the served descriptions, and the read-only, destructive, and idempotent annotations of every tool are on MCP tools.

Tool

One line

create_experiment

Create an experiment from parameters and one of metric or metrics

list_experiments

List active experiments, newest first, with limit and offset

get_experiment

Fetch one experiment, archived included, with its progress

update_experiment

Change the name, budget, bandwidth, metadata, or metric thresholds

archive_experiment

Soft-delete an experiment; there is no un-archive

next_suggestion

Ask for the next point; the open suggestion holds a bandwidth slot until observed or dismissed

list_suggestions

List suggestions, all or by state

close_suggestions

Dismiss every open suggestion

dismiss_suggestion

Dismiss one open suggestion

report_observation

Report a value, values, or a failure against a suggestion or raw assignments

list_observations

List observations in id order

best_now

The best assignments so far

Mind the wait budget on next_suggestion#

The server never retries next_suggestion, because a retried call would mint a second open suggestion: after a timeout or transport error, call list_suggestions with state="open" before asking again. The server allows max_wait_s plus 30 s (90 s when max_wait_s is unset) before it reports a timeout, and a failed call comes back as an isError tool result whose text is one JSON object carrying a code; the arithmetic and the shape are on MCP tools.

Drive one experiment from a conversation#

You speak; the agent calls the tools. The arguments are shown as the agent sends them.

  1. You: “Create a TauOpt experiment named lr-sweep: lr is a double from 1e-5 to 0.1 on a log scale, layers is an integer from 1 to 8; maximize val_score; budget 30.” The agent calls create_experiment:

    {
      "name": "lr-sweep",
      "parameters": [
        {"name": "lr", "type": "double", "bounds": [1e-05, 0.1], "log_scale": true},
        {"name": "layers", "type": "integer", "bounds": [1, 8]}
      ],
      "metric": {"name": "val_score", "objective": "maximize"},
      "observation_budget": 30
    }
    

    The result carries the experiment’s id; the steps below use 17.

  2. You: “Ask it for the next point.” The agent calls next_suggestion with {"experiment_id": 17} and gets back an open suggestion with its own id and the assignments for lr and layers.

  3. You train with those assignments and report: “val_score was 0.91 for that suggestion.” The agent calls report_observation:

    {"experiment_id": 17, "suggestion_id": 42, "value": 0.91}
    

    A run that crashed is evidence too; report it with {"experiment_id": 17, "suggestion_id": 42, "failed": true} instead of a value.

  4. Repeat steps 2 and 3. With the default parallel_bandwidth of 1, asking twice without reporting answers 429 bandwidth_full: report, or call dismiss_suggestion with {"experiment_id": 17, "suggestion_id": 42}, before the next ask.

  5. You: “What is the best so far?” The agent calls best_now with {"experiment_id": 17} and reads {count, data}.

  6. You: “Archive it.” The agent calls archive_experiment with {"experiment_id": 17}; the slot under the active-experiment cap is freed, and the experiment stays readable through get_experiment and best_now.

Run the stdio bridge against your own service#

This section is for operators running their own TauOpt service; the service is a separate component from this repository. The tauopt-mcp console script serves the same twelve tools over stdio, with no boundary authentication of its own, and forwards every call to the API you name:

Variable

Required

Meaning

TAUOPT_TOKEN

yes

The token sent on every call; the bridge refuses to start without it

TAUOPT_API_URL

yes

The service’s base URL, for example http://localhost:8080

TAUOPT_UPSTREAM

no

direct (the default: the token rides the X-Tauopt-Token header straight to the API) or iap (for operators of a lane fronted by an identity-aware proxy only: the bridge sends its own service account’s self-signed JWT in Authorization and the token in X-Tauopt-Token)

The bridge reads TAUOPT_API_URL; the Python snippets elsewhere use TAUOPT_BASE_URL, a name you choose – the client reads neither. A missing variable exits with tauopt-mcp: TAUOPT_TOKEN is required (refusing to start without a token) or tauopt-mcp: TAUOPT_API_URL is required. The bridge does no token checking itself: a bad token surfaces as an unauthorized tool error at the first call. It needs Python 3.12 or newer and is installed from the service repository (uv sync --frozen in its mcp-server directory puts tauopt-mcp in that environment’s bin). Registration in Claude Code, where stdio is the default transport:

claude mcp add tauopt -e TAUOPT_TOKEN='${TAUOPT_TOKEN}' -e TAUOPT_API_URL=http://localhost:8080 -- /path/to/mcp-server/.venv/bin/tauopt-mcp

As above, the single quotes pass the literal reference and Claude Code expands it from its own environment at connection time, so the token stays out of the command line; the hand-edit of the config file is the alternative.

Every tool, argument, and annotation is listed in MCP tools; the same loop from Python is Run against the hosted service.