MCP server
The Model Context Protocol door at mcp.cuvo.co, the tools behind it, and how an agent authenticates.
The MCP server is the same API, addressed the way an agent addresses things. It is a thin client of
/v1: every tool is one published operation, made with your own credential, subject to the same
scopes, modes, grants and audit.
https://mcp.cuvo.co/mcp
Streamable HTTP, stateless, on GET, POST and DELETE. There is no separate SSE endpoint.
Authenticating
The door takes the same bearer credential as the REST API: an API key, or an OAuth access token. See Authentication.
{
"mcpServers": {
"cuvo": {
"url": "https://mcp.cuvo.co/mcp",
"headers": { "Authorization": "Bearer cuvo_sk_test_…" }
}
}
}
An MCP client that has never seen Cuvo can discover the rest. An unauthenticated request answers
401 with WWW-Authenticate: Bearer resource_metadata="https://mcp.cuvo.co/.well-known/oauth-protected-resource",
which points at the authorization server. Dynamic client registration is open and unauthenticated,
with PKCE required, because an MCP client has no other way in. A registered client holds nothing
until a portal member consents to it.
A user-consented token is always a test mode principal. An agent that signed a person in picks the integration and the scopes at the consent screen and reaches the sandbox. Live data needs a live key or a live client, exactly as it does through the API.
Every credential used here must hold organization:read. That is the scope that names which
organization the principal acts for, the door resolves one before any tool runs, and a credential
without it cannot open a session at all. Everything beyond that is admitted per tool: each tool's
own scope is checked against the credential's effective scopes, and its modes against the
credential's mode.
The tools
Forty-six, in three groups.
build, for writing the integration. docs_search searches these guides and spec_lookup reads
the published contract; both answer from this service, spend no credential and need no scope. The
other fourteen are the simulated clinician and pharmacy: test_approve_case, test_decline_case,
test_request_info, test_clinician_message, test_ship_order, test_deliver_order,
test_block_order, test_fail_order, test_start_visit, test_complete_visit,
test_no_show_visit, test_trigger_webhook, test_set_autopilot, test_reset.
All need test:manage and are refused to a live credential.
operate, for running it: usage_get, invoices_list (billing:read), webhook_endpoints_list,
webhook_deliveries_list, events_redeliver (webhooks:manage), events_list (events:read).
runtime, the consult itself: organization_get, catalog_medications, catalog_pharmacies,
catalog_states, patients_create, patients_list, patients_get, consents_create,
consents_list, cases_create, cases_list, cases_get, cases_cancel, cases_release_hold,
messages_list, messages_send, orders_list, orders_get, visits_slots, visits_create,
visits_get, visits_cancel, visits_reschedule, visits_join_link. Each carries the scope its
operation carries, and all of them work in both modes.
tools/list returns only the tools your credential's scopes and mode admit, so an agent is never
offered something it will be refused. A call outside scope is a JSON-RPC error, and it is audited
like any other refusal.
Every tool carries MCP annotations, derived from the operation it names rather than written beside
it. readOnlyHint is true for a tool that only reads, idempotentHint is true when calling a tool
twice is calling it once, destructiveHint is true for a tool that cancels, decides, deletes or
redelivers, and openWorldHint is false on all of them, because every tool talks to this one API.
They are hints, for a client deciding how much confirmation to ask a person for. What a credential
may actually do is still its scopes and its mode, checked here and again at the API door.
A list tool answers the same envelope the REST list does: data plus has_more, with limit and
starting_after to page it. See Pagination.
The resources
The guides and the contract are also MCP resources, so a client can attach one to a conversation rather than spend a tool call on it.
cuvo://docs/<slug>is one guide, as markdown, under the title and summary the developer portal gives it. This page iscuvo://docs/mcp.cuvo://openapi/v1.yamlis the published contract, the same bytes/docs/openapi.yamlserves.
resources/list returns every one of them to every session. They need no scope of their own: the
organization:read your credential already holds to open the door is the whole requirement. A read
is audited the way a tool call is, with the URI where the tool name goes, and a URI nothing is
published at is a JSON-RPC error and an audited refusal.
docs_search stays. A resource list is a table of contents, and an agent still has to find the
paragraph.
What is deliberately absent
No tool mints, rotates or reveals a credential. No tool creates or deletes a webhook endpoint or rotates a signing secret. Those stay in the developer portal behind a fresh multi-factor challenge, because an agent holding a bearer token should not be able to mint a second one.
test_decide_case is three tools rather than one, because the decision is a discriminated union and
a single tool would have meant a nested decision.decision.
Audit
Every request through the door writes the same audit row a REST call writes. A tool call writes a second row naming the tool, and a resource read writes one naming the URI. Nothing an agent does is invisible to the integration that owns the credential; the portal's audit log shows both.
A first session
Ask the agent to call spec_lookup with no arguments for the operation index, docs_search for the
guide it needs, then organization_get to confirm which organization and mode it is in. From there
patients_create, consents_create, cases_create and test_approve_case drive a whole consult.
The published skill at /skills/cuvo-api/SKILL.md says the same thing
in the form an agent loads directly.