MCP API
The MCP API has two route groups: a project-scoped management route set and the streamable-HTTP tool gateway an MCP client speaks. All management routes live under /v1/projects/:projectId/mcp/...; there is deliberately no global (non-project-scoped) route.
| Route group | Base path | Runtime | Auth |
|---|---|---|---|
| Management | <api-origin>/v1/projects/:projectId/mcp/... |
Node + API Worker | Bearer key with mcp:manage, or session with project read/project:mcp:manage |
| Tool gateway | <mcp-origin>/v1?mcp=<target> |
Node + MCP Worker | Project API key with mcp:invoke only (sessions rejected) |
?mcp=<target> is required on every tool-gateway request (otherwise 400 MCP_ID_REQUIRED); see
Tool gateway for the provon / duckduckgo / <serverId>
values. On Node the shared-origin path is /mcp/v1?mcp=<target>.
Local Node: http://127.0.0.1:3000 (both groups on one origin; the gateway at /mcp/v1).
Hosted: management under https://api.provon.dev/v1, the tool gateway under
https://mcp.provon.dev/v1. When the management service is not wired on a runtime, management
routes return 501 MCP_MANAGEMENT_UNAVAILABLE. On the edge, the API Worker hosts the management
routes and the MCP Worker hosts the HTTP tool gateway.
Servers#
List servers#
GET /v1/projects/:projectId/mcp/serversManagement read. Returns { "servers": McpServer[] }.
Create a server#
POST /v1/projects/:projectId/mcp/serversManagement write. 201. Returns { "server": McpServer }.
{
"displayName": "Acme internal tools",
"sourceKind": "mcp",
"baseUrl": "https://mcp.acme.example.com",
"openapiSpec": null,
"enabled": true
}| Field | Required | Notes |
|---|---|---|
displayName |
yes | non-empty string |
sourceKind |
yes | "mcp" or "openapi" |
baseUrl |
yes for MCP | HTTP upstream endpoint |
openapiSpec |
when sourceKind: "openapi" |
the raw OpenAPI object |
enabled |
no | defaults true |
The server record carries connectionStatus (unknown | reachable | unreachable | disabled)
separately from enabled; connection health and authentication posture are distinct states and are
never collapsed into one status.
Update a server#
PATCH /v1/projects/:projectId/mcp/servers/:sidManagement write. Body may include displayName, baseUrl, openapiSpec, and/or enabled.
Returns { "server": McpServer }. Unknown server → 404 MCP_SERVER_NOT_FOUND.
Delete a server#
DELETE /v1/projects/:projectId/mcp/servers/:sidManagement write. 204 on success.
Test connection#
POST /v1/projects/:projectId/mcp/servers/:sid/testManagement write. Opens an outbound HTTP connection and records status. Returns
{ "ok": bool, "connectionStatus": "unknown"|"reachable"|"unreachable"|"disabled", "message": string|null, "toolCount": number|null }.
Discover (read-only)#
POST /v1/projects/:projectId/mcp/servers/:sid/discoverManagement write. Lists the upstream's tools without writing the catalog, publishing, or
changing the stored connection status. Returns { "reachable": bool, "classification": string, "message": string|null, "tools": Tool[] }; the provisional tools become catalog entries only after
an explicit sync.
Sync catalog#
POST /v1/projects/:projectId/mcp/servers/:sid/syncManagement write. Works on Node and Worker for OpenAPI servers (pure normalization) and native
HTTP MCP servers (edge outbound fetch). Returns
{ "catalogVersion": number, "tools": Tool[], "archivedTools": Tool[] }.
Credentials#
Sealed upstream secrets. Credentials are typed by kind (static_secret today; oauth2 reserved)
and by the whitelisted outbound authMethod.
List credentials#
GET /v1/projects/:projectId/mcp/servers/:sid/credentialsManagement read. Returns { "credentials": Credential[] } where each credential exposes
id, serverId, label, kind (static_secret | oauth2), authMethod
(authorization_bearer | x_api_key), role (primary | backup), enabled,
lastVerifiedAt, and createdAt — never the secret. Exactly one credential is the active
primary; every other credential is a backup held for cutover. There is no numeric selection
priority.
Create a credential#
POST /v1/projects/:projectId/mcp/servers/:sid/credentialsManagement write. 201. Returns { "credential": Credential }.
{
"label": "acme-robot",
"secret": "<upstream token>",
"authMethod": "authorization_bearer",
"enabled": true
}The secret is sealed at rest. authMethod selects the outbound header whitelist
(Authorization: Bearer or X-Api-Key). New credentials are backup until an isolation test
promotes them.
Test a credential#
POST /v1/projects/:projectId/mcp/servers/:sid/credentials/:cid/testManagement write. Isolation-tests the sealed secret against the upstream WITHOUT making it the
primary. Returns { "ok": bool, "classification": string, "message": string|null }.
Promote a credential#
POST /v1/projects/:projectId/mcp/servers/:sid/credentials/:cid/promoteManagement write. Atomically cuts over an isolation-verified credential as the server's primary —
the store demotes the current primary and promotes this one in one batch. Returns
{ "credential": Credential }.
Pause / resume a credential#
POST /v1/projects/:projectId/mcp/servers/:sid/credentials/:cid/pause
POST /v1/projects/:projectId/mcp/servers/:sid/credentials/:cid/resumeManagement write. Holds a credential (never used on the outbound path) or re-enables it without revoking.
Revoke a credential#
DELETE /v1/projects/:projectId/mcp/servers/:sid/credentials/:cidManagement write. 204; unknown → 404 MCP_CREDENTIAL_NOT_FOUND.
Tool catalog#
Project-wide catalog (Workbench)#
GET /v1/projects/:projectId/mcp/toolsManagement read. Merges code-defined hosted tools with persisted federation tools into one read-only
view. Returns { "tools": CatalogToolView[], "catalogVersion": number }. Each view carries
origin (hosted | federation), serverId (null for hosted), normalizedToolName,
title, description, inputSchema, publishState, catalogVersion, lastSyncedAt,
httpMethod/httpPath (OpenAPI only), connection display facts (never the upstream URL),
defaultAllowed (only hosted tools are pre-approved), and availability
({ state: "available" } or { state: "unavailable", reason: "draft"|"archived"|"server_disabled"|"server_unreachable" }).
A federation row stays on the page for management even when its upstream is unreachable; callers
cannot invoke it while the auth posture is missing, invalid, or revoked.
List tools for a server#
GET /v1/projects/:projectId/mcp/servers/:sid/toolsManagement read. Returns { "tools": Tool[] } — every cataloged tool for the server, scoped to the
project and server regardless of publish state.
Set publish state#
PATCH /v1/projects/:projectId/mcp/servers/:sid/tools/:tidManagement write. Body { "publishState": "draft"|"published"|"deprecated"|"archived" }. Only
published and deprecated are listable and invocable. Returns { "tool": Tool }. Unknown →
404 MCP_TOOL_NOT_FOUND.
OpenAPI import#
POST /v1/projects/:projectId/mcp/openapi/importManagement write. 201. The body supplies the OpenAPI document by reference or inline, but
exactly one of:
{ "displayName": "Optional name", "specUrl": "https://example.com/openapi.json" }{ "displayName": "Optional name", "specText": "<uploaded OpenAPI document contents>" }Omitting both or sending both returns 400 INVALID_FIELD. There is no inline spec object field.
Returns { "server": Server, "tools": Tool[] }. Each imported operation becomes a draft tool;
it is not discoverable or callable until explicitly published with the PATCH .../tools/:tid route.
See OpenAPI import for operation-to-tool mapping.
Access policies#
Explicit principal allow/deny. Default-deny, deny-overrides, narrowing-only. The built-in
hosted tools (the four provon tools plus duckduckgo web_search) are pre-approved and need
no policy; federation tools stay closed until allowed.
List policies#
GET /v1/projects/:projectId/mcp/policiesManagement read. { "policies": Policy[] }.
Create a policy#
POST /v1/projects/:projectId/mcp/policiesManagement write. 201. Returns { "policy": Policy }.
{
"effect": "allow",
"priority": 100,
"principalKind": "api_key",
"principalRef": "<invoking api key id>",
"serverIds": null,
"toolNames": null,
"actions": ["read", "invoke"]
}| Field | Notes |
|---|---|
effect |
"allow" or "deny" |
priority |
integer; higher evaluated first under deny-overrides (default 100) |
principalKind |
user | api_key |
principalRef |
an opaque id (user id or API key id) |
actions |
one or more of read, invoke (there is no manage tool action) |
serverIds |
null = all, or an explicit array |
toolNames |
null = all, or an explicit array |
Delete a policy#
DELETE /v1/projects/:projectId/mcp/policies/:pidManagement write. 204; unknown → 404 MCP_POLICY_NOT_FOUND.
Gateway enablement#
The project MCP tool gateway can be paused or resumed as a whole:
GET /v1/projects/:projectId/mcp/settings
POST /v1/projects/:projectId/mcp/pause
POST /v1/projects/:projectId/mcp/resumeGET requires a management read; pause and resume require mcp:manage. Each returns the
current { "settings": ... }.
Tool gateway (streamable HTTP MCP)#
POST /v1?mcp=<target> # Hosted MCP Worker
POST /mcp/v1?mcp=<target> # Node, shared originThe ?mcp= query parameter is required on every request; omitting it returns 400 MCP_ID_REQUIRED. Its value selects which tools are exposed:
?mcp=provon— the hosted Provon tools (search_traces,get_trace,list_findings,get_finding);?mcp=duckduckgo— the hostedweb_searchtool;?mcp=<serverId>— a federated MCP server's published tools.
On Node (shared origin) the endpoint is POST /mcp/v1; the Worker also answers GET/DELETE
session probes on the same path.
Runs on Node and the MCP Worker. Speak JSON-RPC 2.0 (stateless) with an mcp:invoke-bearing key.
This ingress accepts project API keys only: a Workspace session (or any non-API-key caller)
receives 401 UNAUTHORIZED; a key without mcp:invoke is rejected. A paused project gateway
returns 503 MCP_PAUSED. Supported methods:
| Method | Purpose |
|---|---|
initialize |
handshake; advertises the server name/version (provon-mcp) |
notifications/initialized |
post-init notification |
ping |
liveness |
tools/list |
discover hosted + published tools inside the principal's scope |
tools/call |
invoke a tool by name |
Errors use the reserved JSON-RPC range: -32601 method not found, -32000 session expired,
-32001 permission denied, -32002 rate limited, -32003 upstream failure, -32004 gateway
paused, -32603 internal. (On the HTTP ingress a paused gateway returns 503 MCP_PAUSED before
the JSON-RPC body; -32004 is the JSON-RPC code surfaced on bypass paths such as the Workbench
playground.)
Upstream HTTP-status → JSON-RPC mapping differs by transport:
- Native MCP (both runtimes):
401/403→-32001,404→-32601,429→-32002, other HTTP →-32003, transport/timeout →-32003. - OpenAPI direct-fetch on Node: every non-2xx (including
401/403/404/500) →-32001; fetch abort / network error →-32000. - OpenAPI direct-fetch on the Worker: every non-2xx and every transport / blocked-host /
over-size error →
-32003.
See Security for guardrail and audit behavior.