Use the MCP server
Metergraph exposes a stdio MCP server and a stateless remote HTTP MCP endpoint. For deployment choices, credentials, client configuration, privacy and troubleshooting, start with Connect a coding agent.
This page is the tool reference. It keeps the tool names, arguments, response bounds, and content behavior in one place.
What it is
Section titled “What it is”A zero-dependency bridge speaks newline-delimited JSON-RPC over stdio and
forwards to the Agent Access HTTP API. The remote endpoint accepts the same
JSON-RPC messages at /v1/agent/mcp, one message per request, so clients that
support remote MCP do not need to run a local process. Both target MCP protocol
revision 2025-11-25 and also accept 2025-03-26 and 2025-06-18. The bridge
refuses redirects, so the bearer token cannot be walked to another origin, and
both cap protocol messages at 1 MiB and API responses at 5 MiB.
METERGRAPH_URL must be an absolute HTTP or HTTPS URL with only a scheme, host
and path. A query string, a fragment or embedded credentials are rejected at
startup with exit code 2. The token is read from the process environment, never
from a protocol message, and is never written to stdout or stderr.
Deployment capabilities
Section titled “Deployment capabilities”Use metergraph_get_capabilities to discover what your server supports. The table below is a reference for server editions. Stdio requires a separate metergraph-mcp installation on the client machine; a Docker bundle does not install it there.
| Local | Staging | Hosted | BYOC core | Self-hosted OSS | |
|---|---|---|---|---|---|
| Transport | stdio and remote HTTP | remote HTTP | remote HTTP | stdio and remote HTTP | stdio and remote HTTP |
| Credential | Keys page coding-agent key, or the operator CLI for a local development tenant | Keys page coding-agent key | Keys page coding-agent key | Keys page coding-agent key, or the operator CLI | Static MG_AGENT_TOKENS bearer tokens set by the operator |
| Workspace context | Yes | Yes | Yes | Yes | Yes |
| Capability discovery | Yes | Yes | Yes | Yes | Yes |
| Routes | Yes | Yes | Yes | Yes | Yes |
| Usage | Yes | Yes | Yes | Yes | Yes |
| Trace metadata | Yes | Yes | Yes | Yes | Yes |
| Ingestion health | Yes | Yes | Yes | Yes | No |
| Incidents | Yes | Yes | Yes | Yes | No |
| Reports | Yes | Yes | Yes | Yes | No |
| Trace content | Yes | Yes | Yes | No | No |
| Replay | Yes | Yes | Yes | No | No |
BYOC core includes the Agent Access API but withholds trace content and replay,
because that profile does not run the debugging and provider paths. The
self-hosted open-source server is content-blind by construction and has no
ingest batches, detectors, or imported reports, so it advertises ingestion
health, incidents, reports, trace content, and replay as unavailable. In every
case, calling an unavailable tool returns the stable unsupported_capability
error rather than an ambiguous failure.
The tools
Section titled “The tools”Investigation tools
Section titled “Investigation tools”These tools are the default content-free read surface:
metergraph_get_workspace_context: workspace identity, retention, scopes, and content policy.metergraph_get_capabilities: deployment profile, available capabilities, tool availability, and response bounds.metergraph_list_routes: route governance, constraints, and call counts.metergraph_get_usage: bounded route/day usage with cost, tokens, latency, and error counts, optionally filtered by exact metadata.metergraph_get_usage_aggregates: usage totals grouped by provider, model, route, UTC day or one metadata key, with whole-window totals.metergraph_get_ingestion_health: pending, processed, failed, and classified ingestion batches with the latest receive/process timestamps.metergraph_list_incidents: bounded tenant detector incidents.metergraph_query_traces: bounded trace metadata, including models, providers, tokens, cost, latency, status, and environment provenance, newest first or as a spread sample across the window.
Every investigation response declares content_included: false where content
could otherwise be ambiguous. Time windows are capped at 90 days and list
responses at 200 rows. A truncated field tells the client when it hit a row
bound.
Debugging tools
Section titled “Debugging tools”The existing Dropzone debugging tools remain available for clients whose key is authorized for that work:
metergraph_get_tracereturns the bounded trace debugging contract, including retained content when it exists, and requires the Debug role (agent:read).metergraph_replay_traceruns one bounded, non-persistent replay and requires the Replay role (agent:readandagent:replay).
Metadata-role keys (agent:metadata) do not see these tools or
metergraph_get_report_evidence in tools/list, and the server rejects calls
to them.
Use capability discovery before calling either tool. The investigation surface
is content-free by default; debugging content and replay are separate choices.
Capability discovery reports each tool’s privacy_class and required_scope,
and tool errors carry a stable error.code that clients can handle without
matching message text.
Report tools
Section titled “Report tools”Imported pipeline reports are available through four tools. The pipeline is never called live: the tools read the versioned report artifact that was imported into the workspace.
metergraph_list_reports: bounded report metadata, newest first, with the pipeline schema version, provenance, outcome, workload counts (including failed and ineligible workloads), opportunity count, evidence coverage, and the artifact’s SHA-256 and size.metergraph_get_report: one report’s metadata plus safe per-workload projections (state, warnings, numeric opportunity summaries, evidence coverage). Model-authored text is never included. On a workload an evaluation suite gated, each opportunity’sevaluation_gatenames the suite revision, the reasons with the checks they cite, and the candidate’s case counts.metergraph_get_suite_results: one workload’s suite results, as the app’s Suite results page shows them: for each candidate, its outcome and reasons, and for each check its kind, pass rate or mean score, scored and eligible cases, counts per outcome, and representative case ids. It is metadata-class and returns no case text; each case id can be read withmetergraph_get_report_evidence. A workload no suite gated returnsavailable: falsewithunavailable_reason: "no_suite_gate".metergraph_get_report_evidence: a separate content-class tool that pages the replay evidence for one workload. Each text field is bounded to 16 KiB. On a suite-gated workload, each case’ssuite_exampleslists the checks whose representative cases cite it.
HTTP clients can download the stored artifact from
GET /v1/agent/reports/{analysis_id}/artifact with a Debug-role key. No MCP
tool serves it. The
download is capped at 32 MiB, sets Cache-Control: no-store, and returns the
SHA-256 of the served body as the ETag. Larger artifacts return
response_too_large.
App links
Section titled “App links”Analysis-run and report responses carry links that open the same thing in the Metergraph app. Give these to a person as returned; do not build a link from an ID.
| Tool | Field | Opens |
|---|---|---|
metergraph_get_analysis_run, metergraph_list_analysis_runs, metergraph_start_analysis, metergraph_start_classification |
run.app_url |
The Analysis page, where the run’s progress and the latest report appear. The app has no page for a single run. |
| same | run.report.app_url |
The run’s report. null until report.available is true. |
metergraph_list_reports, metergraph_get_report |
app_url |
The Analysis page, which shows the workspace’s most recent report. For an older report, link its workloads and opportunities, which always open that report’s evidence. |
metergraph_get_report |
workloads[].app_url |
The workload’s model comparison when the report holds one, otherwise the workload’s page. |
metergraph_get_report |
workloads[].opportunities[].app_url |
The opportunity’s evidence. A switch withheld by the workload’s acceptance policy opens the workload’s suite results, where the app explains it. |
metergraph_get_report_evidence |
metergraph_links.workload |
The workload’s page for that report. |
metergraph_get_suite_results |
app_url |
The workload’s suite results in that report. null when no suite gated it. |
Each link is built from the deployment’s configured public address, so it is
correct on hosted, customer-local and customer-cloud installs alike. It also
names its workspace: opened while signed in to a different workspace, the app
says the link belongs to another workspace instead of showing that
workspace’s data. A field is null when the deployment has no usable public
address.
metergraph_list_routes
Section titled “metergraph_list_routes”No arguments. Returns every route in the workspace with its description, constraints, evaluation contract, last update, total call count, and how many of those calls are eligible for replay.
metergraph_get_usage
Section titled “metergraph_get_usage”The usage summary accepts days from 1 to 90, default 7, and limit from 1
to 200, default 100. It returns route/day aggregates only.
| Argument | Type | Range |
|---|---|---|
days |
integer | 1 to 90, default 7 |
limit |
integer | 1 to 200, default 100 |
metadata |
object of string to string | 1 to 5 keys; see Metadata filters |
items holds at most limit route/day rows. totals covers every matching
call in the window, whatever the limit. Before presenting a figure as the
whole answer, check two fields:
coverage.completenessiscomplete,partialorunknown, andcoverage.reasonssays why (for examplerow_limit,retention_clippedoringestion_pending).coverage.requested_windowandcoverage.effective_windowshow what was asked and what was read.pricing.costisexactonly when every call has a known price.zero_cost_callsare calls priced at zero;unpriced_callshave no price andunknown_callscannot be told apart, so the cost is a lower bound.
metergraph_get_usage_aggregates
Section titled “metergraph_get_usage_aggregates”Groups usage server-side by any of provider, model, route and day (UTC).
Each group and the window totals carry cost in USD, calls, errors, input,
output, cache and reasoning tokens, cost per call, and latency; each group also
carries its pricing. Cost is attributed per call, so a trace that used two
providers splits its cost between them. totals cover every matching call
whatever limit is. As with metergraph_get_usage, check
coverage.completeness and pricing.cost before presenting a figure as the
whole answer; coverage.rows.matched reports how many groups matched. The
latency p95 is estimated from a histogram of the group’s own calls, never a
maximum of daily values. Calls without a provider or model appear as
(unknown), and calls without a route as (unrouted).
| Argument | Type | Range |
|---|---|---|
group_by |
array | distinct provider, model, route, day and at most one metadata.<key>; default provider, model |
days |
integer | 1 to 90, default 7: today and the previous whole UTC days |
since, until |
ISO 8601 string | an exact window of at most 90 days; not with days |
route, provider, model |
array of strings | 1 to 20 exact values |
environment |
array of strings | 1 to 20 environments |
metadata |
object of string to string | 1 to 5 keys; see Metadata filters |
limit |
integer | 1 to 200 groups, default 100 |
The same read is available over HTTP at GET /v1/agent/usage/aggregates.
Grouping by a metadata key
Section titled “Grouping by a metadata key”group_by may include one metadata.<key> entry, for example
metadata.prompt_id. Each group then carries that key’s exact string value
under the same name. Everything after metadata. is the key, so a key may
contain dots or commas. Over HTTP, send a metadata entry as its own group_by
parameter, since only the built-in dimensions are split on commas.
Calls without an eligible value form the group whose value is null: the key
is missing, its value is not a string, or the value is longer than 1,000
characters. That group carries their calls, cost and pricing like any other,
so the groups always add up to totals. coverage.reasons adds
metadata_key_missing and metadata_value_ineligible when such calls exist.
These reasons explain the null group and never lower completeness.
When calls without a usable value hold at least half of the window’s calls, or
at least half of its cost, the response also carries a
metadata_key_mostly_missing warning. When no call carries the key, the
warning says so outright: usually the key is misspelled or the SDK does not set
it. completeness still says complete in that case, because every call was
read and counted in the null group, so totals are exact. The warning is
about how much of the window the per-value rows can explain, not about what
was read.
metadata_groups.unlisted says where every call that is not in a listed row
with a value went, from the same statement as the groups and totals. It has
four buckets, and they never overlap:
other_values: groups pastlimitthat do have a value, with agroupscount.missing_key: calls without the key.ineligible_value: calls with a non-string or oversize value.
The listed rows with a value, plus these three buckets, add up to
totals.calls.
Truncation works as for the other dimensions. coverage.rows.matched counts
the distinct values, including the null group. When groups were cut,
coverage.completeness is partial with reason row_limit, and the
truncated and coverage_incomplete warnings are set. Metadata reads always
scan the raw calls, because the hourly rollups do not keep metadata.
A route and a metadata value are different things. One route usually carries several prompt IDs, so grouping by route and prompt shows how a route splits:
{"name": "metergraph_get_usage_aggregates", "arguments": {"days": 7, "group_by": ["route", "metadata.prompt_id"]}}{"group_by": ["route", "metadata.prompt_id"], "items": [ {"route": "support-reply", "metadata.prompt_id": "reply-v3", "calls": 812, "cost_usd": 4.1}, {"route": "support-reply", "metadata.prompt_id": "reply-v2", "calls": 97, "cost_usd": 0.6}, {"route": "support-reply", "metadata.prompt_id": null, "calls": 40, "cost_usd": 0.2} ], "coverage": {"rows": {"shape": "top_n", "returned": 3, "limit": 100, "truncated": false, "matched": 3}, "totals_scope": "population", "completeness": "complete", "reasons": ["metadata_key_missing"]}, "metadata_groups": {"key": "prompt_id", "unlisted": { "other_values": {"groups": 0, "calls": 0, "cost_usd": 0}, "missing_key": {"calls": 40, "cost_usd": 0.2}, "ineligible_value": {"calls": 0, "cost_usd": 0}}}}metergraph_get_ingestion_health
Section titled “metergraph_get_ingestion_health”Returns bounded ingestion batch counts and the latest receive and process timestamps for the selected window.
| Argument | Type | Range |
|---|---|---|
days |
integer | 1 to 90, default 7 |
metergraph_list_incidents
Section titled “metergraph_list_incidents”Returns bounded detector incidents for the workspace.
| Argument | Type | Range |
|---|---|---|
limit |
integer | 1 to 200, default 50 |
metergraph_query_traces
Section titled “metergraph_query_traces”Recent traces, grouped by trace id, newest first. The optional workload filter
matches a route or a saved analysis workload. The optional environment array
selects one or more environments.
| Argument | Type | Range |
|---|---|---|
route |
string | 1 to 512 characters |
status |
string | 1 to 128 characters |
workload |
string | 1 to 200 characters |
environment |
string array | 1 to 20 unique values, each 1 to 128 characters |
metadata |
object of string to string | 1 to 5 keys; see Metadata filters |
cursor |
string | opaque continuation cursor, up to 512 characters |
days |
integer | 1 to 90, default 7 |
limit |
integer | 1 to 200, default 50 |
sample |
string | spread |
Anything else is rejected as an unknown argument. Each trace comes back with its name, first and last span times, input/output and cache token totals, cost, a success or error status, and the distinct routes, providers and models it touched.
A trace’s cost_usd is the total for the whole trace. It is null when no
call in the trace has a price, and mixed_providers marks a trace that used
more than one provider. A trace total cannot be split between providers, so
use metergraph_get_usage for per-provider or per-route cost. coverage
reports the unread next_cursor and any other reason a page is incomplete;
a final page without a cursor can still be partial.
A route, status or request_id filter selects calls, not whole traces.
Each returned trace then covers only its matching calls, and
attribution.cost_basis is matching_calls instead of trace_total.
Spread samples
Section titled “Spread samples”sample: "spread" returns traces drawn across the window instead of the
newest. The window is split into limit equal time slices, at most 10, and
each slice contributes the matching call nearest its middle, one trace per
call. The same window and filters return the same traces. A sample takes no
cursor, trace_id or request_id, and a limit above 10 is reduced to 10.
coverage.rows.shape is sample, coverage.reasons includes sampled, and
sample reports how many slices had a matching call.
To compare groups of traffic, draw one sample per group with that group’s
filters, for example one metadata value or route each, then read the
sampled traces’ prompts and responses with metergraph_get_trace (Debug role).
When a coding-agent key has an environment allowlist, broad queries are
automatically limited to that allowlist. Requesting any other environment
returns forbidden. The same boundary applies to metergraph_get_trace and
workload detail lookups, so a known identifier does not bypass the filter.
An environment that is NULL, empty, or whitespace-only in stored telemetry
is untagged. Restricted keys exclude untagged telemetry. Unrestricted keys keep
their existing visibility, and responses represent an untagged, mixed, or
unavailable environment as null. A filtered trace is returned only when all
of its spans belong to the selected environment set.
Metadata filters
Section titled “Metadata filters”Metadata is the string dictionary your code attaches with the SDK’s tags(),
context(tags=...), withTags() or setTags(). It is stored as sent and
read back as opaque strings. Metergraph does not split prefixes (a
queue: value is just a string), fold case, trim whitespace, or infer a
workflow or activity from a route name. metergraph_get_usage,
metergraph_get_usage_aggregates and metergraph_query_traces accept
metadata.
- A filter is an exact, case-sensitive match of a key to a string value.
p-1does not matchp-10,P-1orp-1. - Several keys are ANDed. A call must carry every pair.
- A call without the key never matches. An empty string
""matches only a key that is present with an empty value. - A stored value that is not a string (for example a number sent through the raw ingest API) never matches a filter.
- On traces,
metadataselects spans the wayrouteandstatusdo, so a returned trace’s totals cover its matching spans. - Every response echoes the applied pairs, sorted by key, in
coverage.filters.metadata. The list is empty when no filter was sent. - Filters compose with environment scope. A key restricted to some environments sees only matching calls inside them.
Keys and values travel as JSON strings, so =, :, /, quotes, commas and
non-ASCII text need no escaping. Over REST, send metadata as one URL-encoded
JSON object: GET /v1/agent/traces?metadata=%7B%22prompt_id%22%3A%22p-1%22%7D.
A malformed object, a duplicate key or a non-string value returns 422. The
bounds apply to the decoded keys and values, so escaped (\u4e2d) and
unescaped spellings of the same object are treated alike. Very long values can
still exceed a proxy’s URL length limit over HTTP; MCP has no such limit.
| Bound | Limit |
|---|---|
| Keys per request | 5 |
| Key length | 1 to 200 characters |
| Value length | 0 to 1,000 characters |
| Metadata group dimensions | 1 per aggregate read |
| Groups listed | limit, at most 200, with totals covering the rest |
Metadata keys and values are metadata-class fields: any key with
agent:metadata can filter and group by them, inside its environment scope.
Keys that look like credentials are not eligible for filtering or grouping,
because an exact match would let a caller test guesses and grouping would
print the values. A key is refused when, lowercased with everything except
letters and digits removed, it ends in accesskey, apikey, auth,
authorization, bearer, clientsecret, cookie, credential,
credentials, encryptionkey, jwt, passwd, password, privatekey,
secret, secretkey, sessionkey, signingkey or token. Error messages
never repeat a key or value you sent. Do not put secrets in tags.
Only native SDK capture fills metadata today. OTLP spans and provider imports do not map their own metadata dictionaries into it yet.
metergraph_list_reports
Section titled “metergraph_list_reports”| Argument | Type | Range |
|---|---|---|
limit |
integer | 1 to 200, default 50 |
metergraph_get_report
Section titled “metergraph_get_report”| Argument | Type | Range |
|---|---|---|
analysis_id |
string | 1 to 200 characters, required |
metergraph_get_suite_results
Section titled “metergraph_get_suite_results”| Argument | Type | Range |
|---|---|---|
analysis_id |
string | 1 to 200 characters, required |
workload_id |
string | 1 to 200 characters, required |
metergraph_get_report_evidence
Section titled “metergraph_get_report_evidence”| Argument | Type | Range |
|---|---|---|
analysis_id |
string | 1 to 200 characters, required |
workload_id |
string | 1 to 200 characters, required |
cursor |
string | opaque continuation cursor, up to 512 characters |
limit |
integer | 1 to 50, default 50 |
Content-free by construction
Section titled “Content-free by construction”The response payload carries content_included: false, and that is not a
promise, it is a description of the query: the endpoints behind these tools
never select prompt text, response text, tool arguments or tool results. The
debugging endpoint is intentionally separate from the investigation
surface. Capability discovery tells the client whether retained content or
replay is available for its current key and deployment.
So the investigation tools can reason about cost, latency, failure and coverage without reading your users’ data. Debugging content is a separate, explicit capability.
Availability by deployment
Section titled “Availability by deployment”The Agent Access API is available in managed, local, and byoc-core profiles.
The self-hosted open-source server exposes the same tool names with static
MG_AGENT_TOKENS bearer tokens and advertises trace content, replay, incidents,
ingestion health, and pipeline reports as unavailable, because it is
content-blind by construction and has no detectors, ingest batches, or
imported reports.
Capability discovery is authoritative for a particular deployment. See
Connect a coding agent for the full matrix.
See also
Section titled “See also”- Manage your workspace for what the dashboard can and cannot mint
- Ingest API