MCP tools reference#
The TauOpt MCP server exposes twelve /v1 routes of the hosted service as tools. It lives in the service repository as the tauopt_mcp package: module manifest holds the table and descriptions below, module server the tool inputs, module upstream the error results, and module stdio the local entry point. This page was rendered by hand from that source – the closed service repository at commit 816acbe, read 2026-10-09; the server source is unchanged at commit e76b64f, the commit the beta lane’s server images were built from, and on 2026-10-20 the twelve served descriptions and input schemas were checked against the live server’s tool list (the titles and annotations are from the source read); on 2026-10-22 the create_experiment and update_experiment descriptions and inputs were re-read from the service source at commit 6d05e6d, which carries suggestion_ttl_s; on 2026-10-23 all twelve served descriptions were re-read from the service’s worker-tag lane (which carries tag) and the next_suggestion, list_suggestions and list_observations inputs with them – a stack-free pin in the service repository now holds every quote on this page to the served text, reading the page from the installed engine checkout; on 2026-10-25 the next_suggestion description’s wait-budget and admission paragraphs were re-read from the service’s queue lane at the integration review’s stage 2. Descriptions are quoted as served to MCP clients, with em dashes written as --. Routes are the hosted REST routes (REST API); the input object shapes are the REST request-body schemas.
Tool table#
Tool |
Method and route |
Read-only |
Destructive |
Idempotent |
|---|---|---|---|---|
|
|
no |
no |
no |
|
|
yes |
no |
yes |
|
|
yes |
no |
yes |
|
|
no |
no |
no |
|
|
no |
yes |
no |
|
|
no |
no |
no |
|
|
yes |
no |
yes |
|
|
no |
yes |
no |
|
|
no |
yes |
no |
|
|
no |
no |
no |
|
|
yes |
no |
yes |
|
|
yes |
no |
yes |
Every tool carries openWorldHint: false (a closed system). idempotentHint is true for exactly the five read-only GET tools; the other seven are false, matching the server’s no-retry stance. A successful call returns the route’s JSON response; a 204 answer returns {}.
Tools#
create_experiment#
Title: Create experiment. POST /v1/experiments.
Inputs: name (string, length 1..255), parameters (array of parameter objects, at least 1), metric (metric object or null), metrics (array of metric objects, at least 1 when given, or null), linear_constraints (array of linear-constraint objects or null), num_solutions (integer >= 1, default 1), observation_budget (integer >= 1 or null), parallel_bandwidth (integer >= 1, default 1), suggestion_ttl_s (integer >= 0 or null: the open-suggestion TTL in seconds, null = the service default of 86400 s, 0 = never), metadata (object or null).
Create an optimization experiment (SigOpt-lineage semantics). Define the search space via parameters (double/integer/categorical; optional bounds, log_scale, grid, conditions), and EXACTLY ONE of: metric (single-objective) or metrics (multimetric – strategy optimize|store|constraint; thresholds are the Pareto-frontier reference point). Optional linear_constraints, num_solutions (diverse top-k, single-objective only), observation_budget, parallel_bandwidth (max concurrently open suggestions), suggestion_ttl_s (seconds an unobserved open suggestion may stay open before the next suggest expires it and frees its slot; unset = the service default of 24 h, 0 = never), and metadata. The engine validates the full space/metric shape and answers 400 with the offending field on any violation.
list_experiments#
Title: List experiments. GET /v1/experiments.
Inputs: limit (integer, default 50), offset (integer, default 0). The service clamps limit to 1..200.
List active experiments, newest first: {count, data:[experiment]}. Pagination via limit (default 50, max 200) and offset.
get_experiment#
Title: Get experiment. GET /v1/experiments/{experiment_id}.
Inputs: experiment_id (integer).
Fetch one experiment (archived included) with progress state and the expected next-suggest latency estimate.
update_experiment#
Title: Update experiment. PATCH /v1/experiments/{experiment_id}.
Inputs: experiment_id (integer), name (string, length 1..255, or null), observation_budget (integer >= 1 or null), parallel_bandwidth (integer >= 1 or null), suggestion_ttl_s (integer >= 0, the literal "default", or null), metadata (object or null), metric_thresholds (object of metric name to number or null, or null). Null inputs are omitted from the request, so the field stays unchanged – suggestion_ttl_s included; the REST PATCH’s explicit-null revert to the service default TTL is the tool’s literal "default", which the server sends as that explicit null.
Update an experiment in place: name, observation_budget, parallel_bandwidth, suggestion_ttl_s, metadata, and metric_thresholds (multimetric mode only; SigOpt semantics – thresholds move mid-experiment, space and metric STRUCTURE are immutable). Omitted fields stay unchanged; the engine re-validates thresholds before commit. suggestion_ttl_s takes an integer (seconds) or the string “default”, which reverts the experiment to the service default TTL; null (or omitting it) leaves the TTL unchanged.
archive_experiment#
Title: Archive experiment. DELETE /v1/experiments/{experiment_id}.
Inputs: experiment_id (integer).
Archive (soft-delete) an experiment. It leaves list_experiments but stays readable by id; new suggestions are refused. There is no un-archive endpoint at v1 – treat as destructive.
next_suggestion#
Title: Get next suggestion. POST /v1/experiments/{experiment_id}/suggestions.
Inputs: experiment_id (integer), config (object or null), max_wait_s (number > 0 and <= 300, or null), on_timeout (ladder, the default, or fail), intents (array of objects or null: typed authorized-duplicate requests {kind, source_id, ordinal} the engine validates; refusals ride provenance.dedup), tag (string of 1 to 128 printable characters, or null: the worker label served back on the suggestion and filterable on list_suggestions).
Ask the optimizer for the next point to evaluate. Returns an OPEN suggestion with assignments; it occupies one parallel_bandwidth slot until observed or dismissed (a full bandwidth answers 429 with Retry-After). Optional intents: typed authorized-duplicate requests ({kind, source_id, ordinal}) the engine validates (refusals ride provenance.dedup). Optional config selects the public strategy stratum ({strategy, params, seed}); max_wait_s and on_timeout are the user-owned wait budget. Optional tag: a worker label (1..128 printable characters) stored on the suggestion and served back on every read; list_suggestions filters by it, so a worker that restarts finds its own open suggestions (never a key for quota or admission).
The served description continues with the wait-budget paragraph quoted in the next section, and then with the admission block – the queue’s state since the service’s queue lane, 2026-10-24:
The result carries an
admissionblock – the hosted service’s queue state for this ask (the service’s X-Tauopt-* headers): outcome (direct | queued | refused_queue | refused_parked), queue_ms (arrival to lease), inflight (your asks admitted or running, across the service), inflight_limit (your in-flight bound), queued (your own queue); size parallel asks to inflight_limit. The ask parks until a worker serves it or its bound B(max_wait_s) = max(1.25 W, W + 3) s arrives: a 429 too_many_queued (your queue is full; retry after one completes) or a 503 ask_deadline_exceeded / queue_full refusal carries the same block beside its error. A 429 suggest_rate_limited says more than ~10 suggests a second came from this user (a burst of 20 is allowed; Retry-After). Every tenant rides the queue (deploy 2, 2026-10-28).
list_suggestions#
Title: List suggestions. GET /v1/experiments/{experiment_id}/suggestions.
Inputs: experiment_id (integer), state (open, closed, or null for all), tag (string of 1 to 128 printable characters, or null: the exact worker label to narrow the list to), limit (integer or null: absent = every row; the service clamps a present value to 1..200 and refuses a negative one, 400), offset (integer, default 0; the service refuses a negative one, 400).
List an experiment’s suggestions in id order: {count, data:[suggestion]}; count is the total under the filters. Filter with state=’open’ (awaiting observation, holding a bandwidth slot) or state=’closed’, and with tag=
close_suggestions#
Title: Close all open suggestions. DELETE /v1/experiments/{experiment_id}/suggestions.
Inputs: experiment_id (integer).
Bulk-dismiss EVERY open suggestion, freeing all bandwidth slots: returns {closed: n}. Dismissed points count as dismissals on state rebuild (never reissued by sequence strategies) – destructive to in-flight evaluations.
Dismissed points leave the dedup universe and may be suggested again: sobol advances its cursor past them, while random can re-issue the identical point and the GP rungs are not barred from it.
dismiss_suggestion#
Title: Dismiss suggestion. DELETE /v1/experiments/{experiment_id}/suggestions/{suggestion_id}.
Inputs: experiment_id (integer), suggestion_id (integer).
Dismiss one open suggestion without an observation, freeing its bandwidth slot. The point is recorded as dismissed and leaves the dedup universe, so it may be suggested again – destructive to that evaluation.
As with a bulk dismissal, sobol moves past the point and random can re-issue it.
report_observation#
Title: Report observation. POST /v1/experiments/{experiment_id}/observations.
Inputs: experiment_id (integer), suggestion_id (integer or null), assignments (object of parameter name to number, integer, or string; or null), value (number or null), values (object of metric name to number, or null), value_stddev (number or null), values_stddev (object of metric name to number, or null), failed (boolean, default false), metadata (object or null).
Report an evaluation result. Provide EXACTLY ONE of suggestion_id (closes that suggestion, freeing its slot) or raw assignments. Provide value (single-metric) or values (multimetric) XOR failed=true – a failure is real evidence that shapes the search away from that region. Optional value_stddev/values_stddev report noise; a suggestion accepts exactly one observation (409 on repeat). Bodies are bounded at 32 KiB serialized (the service answers 400 past it).
list_observations#
Title: List observations. GET /v1/experiments/{experiment_id}/observations.
Inputs: experiment_id (integer), limit (integer or null: absent = every row; the service clamps a present value to 1..200 and refuses a negative one, 400), offset (integer, default 0; the service refuses a negative one, 400).
List an experiment’s observations in id order: {count, data:[observation]}; count is the total. Optional window: limit (1..200; absent = every row) and offset.
Id order is report order (ids are serial).
best_now#
Title: Best assignments now. GET /v1/experiments/{experiment_id}/best_assignments.
Inputs: experiment_id (integer).
The best assignments so far. Single-metric: the best observation (or the diverse top-k when num_solutions > 1). Multimetric: the threshold-filtered Pareto frontier. Answers {count: 0, data: []} when nothing has succeeded yet – an empty set, not an error.
Wait budget#
The paragraph appended to the served next_suggestion description:
End-to-end wait budget: the optimizer computes for up to max_wait_s (<= 300 s, default engine ladder) and this server allows B(max_wait_s) + 40 s – B(W) = max(1.25 W, W + 3), the hosted service’s own bound, plus transport and the cold-start allowance (415 s when unset) – before reporting a timeout error. on_timeout=’ladder’ degrades the strategy with provenance receipts; ‘fail’ returns a 503 with retry_after. This call is NEVER retried by the server: a retried call would mint a second open suggestion – on a timeout or transport error, list open suggestions before asking again.
The served sentence’s allowance (B(max_wait_s) + 40 s, 415 s unset) is the Python client’s listen bound, written into the manifest by the queue lane on 2026-10-24, and is corrected here: every other tool runs under the 90-second route budget; a next_suggestion runs under max_wait_s plus 30 s (at most 330 s, and 90 s when max_wait_s is unset), the server’s own allowance for the ask. In caps mode the same max_wait_s is a refusal rather than a ladder step: a config carrying timebox_mode: "caps" whose max_wait_s expires raises the engine’s AskDeadlineExceeded in either on_timeout mode, which the service maps to 503 ask_deadline_exceeded with no Retry-After (a caps re-ask repeats deterministic work), so the tool result’s code is ask_deadline_exceeded with no retry_after.
Error results#
A failed call returns an isError tool result whose text is one JSON object:
{"code": "<code>", "message": "<text>", "retry_after": 30, "timeout": true}
retry_after (integer seconds) is present only on 429 and 503 answers that carried a Retry-After header; timeout is present, and true, only on the server’s own timeout. The codes:
the service’s
error_type– for exampleunauthorized,not_found,bandwidth_full,space_exhausted,suggest_timeout– when the service answered 400, 401, 403, 404, 409, 429, 500, or 503 in its JSON error shape;messageis the service’s messageupstream_<status>for any other non-2xx status, and for a 4xx or 5xx without the service’s JSON shapeupstream_<status>_malformedwhen a 2xx body is not JSONtimeoutwhen no response arrived within the route budget; the call is not retriedtransport_errorwhen the connection to the service failed; the call is not retried
A 401 or 403 that is not in the service’s JSON shape, or a redirect, is reported as a protocol-level error (upstream auth transport failure (...)) rather than a tool result. No call is retried by the server.
Running the server#
Local (stdio): the
tauopt-mcpentry point. Environment:TAUOPT_TOKEN(required; the process refuses to start without it),TAUOPT_API_URL(required),TAUOPT_UPSTREAM(directby default). The token is sent to the service asX-Tauopt-Tokenon every call; a bad token surfaces as anunauthorizedtool error at the first call.Hosted: the same tools over streamable HTTP at
/mcp. The caller sends thetau_token asAuthorization: Bearer <token>; a missing header or a non-Bearer scheme answers 401 with aWWW-Authenticate: Bearer realm="tauopt"challenge, and a malformed or unknown token answers 401 witherror="invalid_token"added to it; every 401 body is{"error": "invalid_token", "error_description": "..."}.GET /healthanswers{"status": "ok"}.