POST /mcp on the same service that serves the web UI and the REST API. It ships with the backend, so there is no separate server to install or version.
The tools read the project the token is bound to: its cases, its findings, the evidence behind them, and the traces and spans underneath.
The endpoint
The endpoint is always live. It has no environment variable of its own, and nothing turns it off.
The endpoint accepts batched requests: send an array of envelopes and receive an array of responses. Notifications, meaning envelopes with a null
id, are accepted and dropped. A batch made entirely of notifications answers 204 No Content.
initialize returns an instructions string that Tessary builds from the tools offered to this token, so the prose a client reads can never describe a tool its tools/list does not contain.
Authentication
/mcp is bearer only. It ignores cookies even when a valid browser session is present.
The credential is an admin-scoped project API key, prefix tsy_a_. Tessary rejects a write- or query-scoped key before any tool runs. See API keys for the scope model and how keys are minted.
The token binds the session to exactly one project. Every tool reads the project from the token, and no tool takes a project argument. To read a second project, mint a second token.
Read-only guarantee
No tool on this surface writes a row, spends a token, or starts an agent run.initialize states that to every client on connect, as a property of the surface rather than of any one tool.
The writes the product has stay on REST and in the UI, because each one either records a human judgment or spends money:
- Resolving, absorbing, and muting a case
- Triggering a root-cause analysis run
tools/list never returns a write-shaped tool name, and a name retired in an earlier release is never reused. A client that wants to check rather than assume reads the instructions string initialize returns, not this page.
What the surface withholds
Three tools strip the triage ruling before their response leaves the server. Root-cause analysis reaches this surface with a project-scoped admin key like any other caller, and it must receive a finding id and nothing else, because “nothing happened here” is a supported conclusion and the only check on the triage gate.
Two fields deliberately survive:
triageStatus, which says only whether a ruling exists and which the findings list needs to render, and a case’s inline rca, which is the analysis lane’s own prior work rather than the gate the redaction exists to protect. The redaction applies to every MCP caller, not only to keys minted for analysis. The full ruling is one click away in the UI, which reads these fields over REST and is untouched.
Conventions the tools share
- Read-only. Nothing writes, spends, or starts an agent run.
- Project-scoped by the token. No tool takes a project argument.
- Paging on
list_cases,list_traces,list_spans, andlist_sessions:limitin, default 50 and capped at 100, pluscursorin andnext_cursorout. An unreadable or stale cursor restarts at the newest page rather than returning an error.query_searchandget_finding_evidencepage at a wider bound:limitdefault 100, capped at 1,000. - Lists find, gets read. List rows carry typed columns plus the stored
input_previewandoutput_previewand apayload_availableflag, never the full payload. Raw text comes fromget_span, or fromget_traceandlist_spanswithfields: ["payload"]on a page already scoped to one trace or to a window of at most 24 hours. - Every input schema sets
additionalProperties: false. An argument that is not listed is rejected. - Timestamps are ISO-8601. The snake_case wire shape is identical to REST.
The tools
Tessary offers all 19 tools to every organization. The per-tool capability mechanism still exists in the code but no tool declares one today, sotools/list returns the whole catalog for any valid token.
tools/list is the authoritative schema. The tables below name each tool’s arguments so you can see the shape of the surface; they are not a schema copy.
Project and taxonomy
Start with
get_project. An empty case list is not an all-clear on its own: traces_last_day = 0 means nothing is arriving, which is a different answer from nothing being wrong.
Cases
get_case accepts either the stored id or the human reference a person would quote, such as C-118. rca is null while a report is still running, in which case rca_report_id names it so you can poll, and null when none has been run; rca_available says whether one could be.
Findings and evidence
A finding is the aggregated cause behind many classifier firings, distinct from a single firing in the
classifier_events dataset.
get_finding_evidence pages the population a detector actually measured, one ref per measured row, uncapped at write time. Refs are ids rather than bodies: follow one with get_trace, get_span, or list_spans. Call it with count_only=true first for the per-role sizes with no rows.
Two details worth knowing before you read the counts. counts is what survives and can still be opened; recorded_counts is what the detector wrote at finding-open, so counts below recorded_counts means substrate aged out rather than a lost write. A zero under a role is a real answer, not missing evidence: several detectors compare against a fitted model and therefore have no baseline rows to point at.
There is deliberately no server-side sampling mode. An agent that wants a stride or a random draw takes it and says so in its citation.
Traces, spans, and sessions
get_span needs both ids because a producer span id is unique only within its trace. A call with no trace_id returns a tool error naming the argument to add.
fields takes one value, ["payload"], and adds each row’s full input, output, attributes, and provided usage instead of the previews. On list_spans it is allowed only on a page already scoped to one trace_id or to a range at most 24 hours wide; an unscoped request is an error rather than a quietly compact page. get_trace is always scoped to one trace, so the rule is always satisfied there.
Read the null columns carefully. On a trace row, is_settled=false means the trace is still receiving spans, settled with a null token or cost column means no span reported usage, and unpriced_spans > 0 means the total is real but incomplete. On a session, unsettled_traces counts traces still receiving spans, so a non-zero value makes every total below it a lower bound.
payload_available on a span row says whether the full text still exists. Payloads age out ahead of spans, so a row with no preview and payload_available=false is text Tessary no longer holds, not a call that had no input.
Aggregate queries
The four query tools read four datasets:spans, tool_calls, classifier_events, and metric_rollups.
Call
describe_dataset before the other four. Facetable and filterable fields differ per dataset, and an unknown field returns an error naming the dataset rather than an empty result.
Four things constrain these tools:
- Both
rangebounds are required onquery_timeseries. An unbounded timeseries is a full scan. rangeis half-open,[from, to), on the dataset’s own time column:created_at, orbucket_startonmetric_rollups.metric_rollupsrows are already aggregated, so the answer is the sum of the rollup’svaluerather than a row count. Always filtergranularitythere, or the hour and day grains are summed together.query_searchdoes not accept thespansdataset.list_spanssearches the same rows with a richer filter set and returns full payloads on a scoped page, so spans are excluded fromquery_searchby decision rather than by capability.metric_rollupsis not searchable at all: a rollup row has no text.
filters is a flat map of allow-listed field to value, applied as equality.
Client configuration
1
Mint an admin-scoped token
In the UI, open the project you want, then Settings → MCP tokens → New token. Name it and select Issue token. Copy the
tsy_a_ string it shows once.The token appears in the list with its prefix and a Last used column reading
Never.2
Add the server to your client
The MCP config section on that same page renders the block below with your own origin already filled in. Copy it into Set
~/.claude/mcp.json:TESSARY_TOKEN to the secret you copied. On a default local install the URL is http://localhost/mcp.3
Restart the client
Restart Claude Code so it reads the new configuration.
tools/list returns 19 tools, and get_project names the project the token is bound to.Errors
Tessary reports tool failures and protocol failures differently, which matters when you write a client.
A successful
tools/call returns content as a single text block carrying the JSON-encoded payload, structuredContent with the same payload typed, and isError: false.
An unknown tool name and a tool withheld from your organization return the same message. That is deliberate: a client holding a stale tool list must not be able to tell the two apart.
Known limits
These are current behaviors, stated so you do not read around them.list_findingstruncates without saying so. Each triage source caps its own page at 200 rows and has nolimitorcursorargument, and the service concatenates every source’s page. Nothing in the response marks the list as partial, so a project with more confirmed findings than the effective cap gets that many back with no signal.get_tracereports one span count. The response carries the rollup’sspan_countas the true total even whenspans_truncatedis set. There is no separate total field. For a trace past the 200-span cap, page the rest withlist_spansand atrace_id.- A resolved case page sorts rather than seeks. Open and muted pages read an index;
state=resolvedsorts at query time. list_spanspages on a column with no matching index. Its keyset predicate runs onspan.created_at, and the only time index on the span table is onstarted_at.
Related pages
API keys
The scope model behind the admin key this endpoint requires, and its errors.
Ingestion contract
The fields the platform consumes, which is what these tools read back.
