If you are an AI agent — or a person asking one — writing code against our MCP server, this page is the part that is not in the catalog. It is short on purpose.
Read schema from tools/list. Read quantities from a response field.
These are different kinds of claim with different lifetimes, and treating them
as one source is how integrations break. A tool's name, parameters, types and
units are a contract — versioned, stable, and authoritative at
tools/list. A count of facilities or markets is a measurement,
taken at a moment, and it moves daily.
Tool descriptions are prose, and prose carries measurements that go
stale — including ours. Do not lift a number out of a description, this page,
a comparison table, or a blog post and cite it as current. Take it from the
provenance block or the count field of an actual data response,
where it arrives with an as_of stamp telling you when it was true.
"Read it from the response" is right but not precise enough on its own, and the
imprecision has a specific failure mode: an agent asks one narrow question, reads
the provenance on that answer, and generalises its population to the
whole platform. A response is authoritative for what that call established,
not for what DC Hub holds. The levels do not compete — each answers a different
question:
| Level | Authoritative for |
|---|---|
tools/list |
what a capability accepts and exposes — names, parameters, types, units |
a response + its provenance |
what that specific call established, over the population it names, at its as_of |
the canonical endpoints —/api/v1/stats/canonical,/api/v1/canon/phrases |
platform-wide counts and the definitions behind them. This is the level to cite for "how much does DC Hub cover", and the one a narrow response cannot stand in for. |
| instruction prose and descriptions | routing and context — which tool to reach for. Never a quantity. |
This distinction came from ChatGPT, reviewing an earlier version of this page that collapsed the middle two levels into "read it from a response field". It is a better rule than the one it replaced: "never trust descriptions" is blunt enough to be wrong, because descriptions are exactly right for deciding which tool to call.
Most generated integrations fail on the same three things. All three are
visible in tools/list; none is guessable from a description.
A suffix is not decoration — it is the unit, and there is no coercion. A latency
budget is max_latency_us, in microseconds; passing a
millisecond figure asks for a budget a thousand times tighter than you meant, and
for sub-millisecond values it asks for something no fiber path can satisfy at any
distance. Power is _mw (megawatts). Horizons are _months.
When a name carries no suffix, the description states the unit.
We have no generic list_* family. Inventory lookups are
search_*; ranking is rank_*; single-record reads are
get_*. If you reached for list_facilities, the tool you
want is search_facilities.
Tools that take a location take lat + lon (with
latitude/longitude/lng accepted as aliases),
not a site_id or facility_id you invented. Where an
identifier is accepted it is a candidate_id minted by a prior
call — get_refined_queue hands you cand_… tokens that
resolve to frozen coordinates, which is the transposition-proof path.
Every row below was produced by a frontier model writing confident, well-structured integration code. None of these names or parameters has ever existed. They are here because the plausible reconstruction is the dangerous one — it reviews cleanly and fails at runtime.
| Generated | Actual | Note |
|---|---|---|
| list_facilities(…) | search_facilities(…) | no list_* family exists |
| get_retirement_headroom( min_capacity_mw, timeline_horizon_months, iso_regions) |
get_retirement_headroom( target_mw, horizon_months, region_iso) |
both required params renamed — the call cannot succeed |
| cluster_sites_by_latency( facility_ids, max_round_trip_time_ms, prefer_dark_fiber_routes) |
cluster_sites_by_latency( sites | candidate_ids, max_latency_us, min_confidence) |
and the unit changes: µs, not ms |
| save_site(site_id, project_id, metadata) |
save_site(lat, lon, name?, market?, target_mw?) |
no project/metadata model; writes need a free key |
| get_changes(project=…) | get_changes(since?, limit?) | scoping is by your account's saved sites, not a project tag |
Grouping saved work by project is a reasonable thing to want and we do not have it.
Saved sites belong to your key; get_changes returns a
portfolio block for keyed callers covering the sites you saved. If you
need per-project separation today, separate the keys.
Our facility figures differ across responses, and most of that difference is real rather than sloppy. Three distinct populations are published, and a number is only interpretable with its population attached:
A response's provenance block names which population it is reporting
and stamps as_of. Quote the population along with the figure — "N
analyst-verified of M tracked" is honest and citable; a bare figure with no
population is not, because the reader cannot tell which of the three you took.
Where this has gone wrong for us, stated plainly: populations explain most of the spread but they have not explained all of it. We have shipped the same population at two magnitudes in a single payload, because one field resolved the count at request time and another had frozen it at process start. If you find two of our numbers that disagree and cannot be told apart by population, that is a bug rather than a subtlety — tell us and cite the response.
Several tools return an explicit statement of what the answer does not
cover. rank_sites, site_selection_canvas and
get_power_availability_timeline return
constraint_coverage; get_composite_site_score returns
coverage and coverage_ratio.
Its shape differs per tool — a list of caveat strings on the
timeline tool, an object keyed by field on the others — so read the shape from the
response rather than from this sentence. On
site_selection_canvas it reports per-argument disposition: an argument
you passed can come back applied:false with a reason and
an instead. An argument being accepted is not the same as it being
applied, and only the coverage block tells you which happened.
Everything above is checkable, and you should check it. None of these require a key:
| Endpoint | What it answers |
|---|---|
POST /mcp → tools/list |
the authoritative catalog: every name, parameter, type and unit. If this page and tools/list disagree, tools/list is right and this page is a bug. |
/mcp-selftest |
runs the full handshake against our own edge and reports what it got, so a directory or client can tell a cold start from an outage. |
/api/v1/ops/deadman |
per-source ingest freshness, keyless. Liveness you can audit instead of a liveness claim you have to believe. |
/api/v1/agent/cookbook |
ordered, copy-paste tool chains for the questions humans actually ask. |
Connect at dchub.cloud/mcp (streamable HTTP). The free tier needs
no signup; claim_free_key mints a key in one call for the tools that
write. Data is CC-BY-4.0 — cite as “DC Hub, dchub.cloud”.