MCP API
Complete API reference for the MCP Gateway: servers, tools, policies, approval requests, proxy.
All MCP endpoints require API-key authentication (Authorization: Bearer {project_api_key}) and additionally enforce a role floor. Editor: register, scan, create a stricter policy. Admin: approve / resume / pause / reject / delete a server, admit tools, emergency-stop, set a tool rule, create an allowing policy, delete a policy.
A refusal has the shape {"error": {"code", "message", "request_id"}}; message is English, code is what to act on (see Refusal codes).
Servers
List servers
GET /api/v1/mcp/serversReturns all registered MCP servers for the project. Per server:
| Field | Meaning |
|---|---|
status | set only by humans: PENDING_APPROVAL (waiting for approval), ACTIVE, BLOCKED (paused). A scan never changes it. |
scan_state | what the last scan says: never · failed · not_run (could not run because of the address or sign-in) · unreadable (result outdated, scan again) · succeeded. Only succeeded can be approved. |
scan_reason | reason of the last attempt that did not succeed: for failed unreachable · bad_status · rpc_error · invalid_response; for not_run upstream_url_forbidden · auth_method_unsupported · auth_token_missing; otherwise null |
is_healthy | whether the server answered at the last scan |
tool_count, tools_unreleased | number of tools and of those not admitted (changed tools are not counted) |
last_scan_at | time of the last successful scan |
Register a server (Editor)
POST /api/v1/mcp/servers{
"name": "GitHub MCP Server",
"server_url": "https://api.githubcopilot.com/mcp/",
"connector_type": "custom",
"description": "Optional description",
"is_proxied": true
}connector_type is an optional free-form label (defaults to custom). New servers start in PENDING_APPROVAL; registering does not trigger a scan.
Update a server (Editor)
PATCH /api/v1/mcp/servers/{id}Fields: name, description, auth_method, auth_config. This route is metadata-only — it cannot change status. Status transitions go exclusively through the audited lifecycle actions below.
Delete a server (Admin)
DELETE /api/v1/mcp/servers/{id}Cascades to this server's tools and policies. Audited.
Lifecycle actions (Admin, audited)
Status changes only through these dedicated, admin-audited routes:
POST /api/v1/mcp/servers/{id}/activate # PENDING_APPROVAL → ACTIVE (approve) or BLOCKED → ACTIVE (resume)
POST /api/v1/mcp/servers/{id}/pause # ACTIVE → BLOCKED
POST /api/v1/mcp/servers/{id}/reject # PENDING_APPROVAL → removedApproving is a decision about every tool and possible only after a successful scan. The body names every tool of the last successful scan exactly once, with a rule and the current_fingerprint from the tool list; a server without tools is approved with "tools": []:
{ "tools": [ { "tool_id": "clxyz...", "rule": "REQUIRE_APPROVAL", "fingerprint": "5f0c7e…" } ] }rule ∈ ALLOW · LOG_ONLY · REQUIRE_APPROVAL · DENY. In one step the server becomes active and every tool is admitted with its rule: the rule counts as confirmed by this Admin (is_auto_generated: false), and the tool's state (name, description, input schema) is recorded. Response: {"id", "status": "ACTIVE", "release": {"release_id", "released": [{"tool_id", "tool_name", "rule", "approved_hash"}]}}.
Resuming takes no tools ({}) and admits nothing: a tool changed since its admission stays blocked. Each action writes an entry to the tamper-evident log of admin actions (mapped to OCSF for SIEM export).
Admit tools (Admin)
POST /api/v1/mcp/servers/{id}/tools/releaseOnly on an ACTIVE server. The body has the same shape as for the approval and names at least one tool that is not admitted (matches_approved: null) or changed since its admission (matches_approved: false). This is also how a changed tool is admitted again. Response: {"release_id", "released": […]}. Each tool gets a log entry MCP_TOOL_RELEASED.
Scan tools (Editor)
POST /api/v1/mcp/servers/{id}/scanConnects to the MCP server, discovers tools, determines each tool's action and risk, and records the current state of every tool. The scan sets no rule, admits no tool and never changes the status.
The response mirrors the outcome honestly:
// reached and scanned
{ "scanned": true, "tool_count": 12, "tools_new": 2, "tools_vanished": 0, "poisoning_suspects": 0, "policies_bound": 0, "tools_unreleased": 12, "server_status": "PENDING_APPROVAL" }
// target could not be scanned (still HTTP 200 — not a Palveron 5xx)
{ "scanned": false, "reason": "unreachable", "detail": "refused", "target": "https://…", "server_status": "PENDING_APPROVAL" }tools_unreleased is the number of tools that serve nothing until an Admin admits them; server_status is the unchanged status. reason ∈ unreachable · bad_status · rpc_error · invalid_response; the server list then shows scan_state: failed. When the scan cannot run because of the configuration (private or internal address, unsupported sign-in method, missing token), the route answers 400 (validation) and records scan_state: not_run with its reason.
List server tools
GET /api/v1/mcp/servers/{id}/toolsEach tool carries risk_level, category, description_hash, action, the effective server-wide rule policy_action (null = no rule, calls are held for approval) and is_auto_generated (true = created by the system, false = confirmed by an Admin), plus the admission:
| Field | Meaning |
|---|---|
matches_approved | true = admitted and unchanged · false = changed since the admission (blocked) · null = not admitted (blocked) |
approved_hash, approved_at | recorded state and time of the admission |
current_fingerprint | fingerprint of the current state; an admission names it back |
proposed_rule | proposed rule for a waiting tool, otherwise null; decides no call |
proposed_rule_source | action (from the action) or template (from a stricter template rule, the only way to DENY); null exactly when proposed_rule is null |
"Accept proposals" is exactly tools = [{tool_id: id, rule: proposed_rule, fingerprint: current_fingerprint}].
Set a tool rule (Admin)
POST /api/v1/mcp/servers/{id}/tools/{tool_id}/verdict{ "action": "LOG_ONLY", "reason": "Reviewed — safe to log only" }Sets (or loosens) the tool's server-wide rule, replacing its server-wide rules with the new one so loosening actually takes effect. It admits nothing: for a tool that is not admitted or changed, the way is …/tools/release. Logged. There is no update endpoint for policies; use this route, or delete and create again.
Templates
GET /api/v1/mcp/templatesLists the templates Palveron maintains with id, name, description, category, connector_type, auth_method, default_policies, plus available and unavailable_reason. All templates are locked at the moment (available: false, unavailable_reason: "auth_method_unsupported"): they sign in with a method the gateway does not support yet. Locked templates stay listed; POST /api/v1/mcp/servers/from-template refuses them with 409 mcp_template_unavailable and creates nothing.
Registry (catalog)
GET /api/v1/mcp/registry?q={search}&frontable=true&cursor={registry_name}&limit=50Returns Palveron's mirror of the official MCP registry — the catalog behind the one-click connect flow. Each item: registry_name, version, description, remote_url, frontable (has a usable HTTP remote), website_url, registry_status, and the trust signals registry_updated_at, published_at, repository_url. The response carries items, an optional keyset next_cursor (over registry_name), and total.
Proxy
Forward a tool call
POST /api/v1/mcp/proxy/{server_id}Accepts any JSON-RPC 2.0 request. Optionally send an X-Agent-Id header with a registered agent id (plain CUID) so calls are attributed to that agent — an unknown id is treated as anonymous. For the tools/call method, the following checks run in order:
- Agent suspended: a suspended agent is refused first (
-32003,AGENT_EMERGENCY_STOP) - Server active:
PENDING_APPROVAL→-32002, paused (BLOCKED) →-32001 - Admission: a tool changed since its admission (
-32002,APPROVED_FINGERPRINT_MISMATCH) and a tool that is not admitted (-32002,TOOL_NOT_RELEASED) are blocked; a tool that is not admitted and whose description changed since the last scan carries the legacy poisoning reason (-32002) - Rules: the strictest rule wins (see Tool Policies):
DENY→-32003,REQUIRE_APPROVAL→-32004, no rule →-32004(NO_TOOL_RULE_REQUIRE_APPROVAL); the agent's permission model can tighten further - Upstream auth attach: if the server's auth cannot be attached, the call is refused fail-closed (
-32008) - Forward to the MCP server, then record the call with
traceType: MCP_TOOL_CALL
Non-tool-call methods (initialize, tools/list) pass through without policy evaluation.
Error codes:
| Code | Meaning |
|---|---|
-32001 | Server paused or not ACTIVE |
-32002 | Call blocked: server awaiting approval (PENDING_APPROVAL), tool not admitted (TOOL_NOT_RELEASED), changed since the admission (APPROVED_FINGERPRINT_MISMATCH), or a poisoning suspect |
-32003 | Tool denied by a rule, or the agent is suspended (AGENT_EMERGENCY_STOP) |
-32004 | Call held for approval (queued; carries the request id), by a rule or because no rule applies |
-32005 | Limit exceeded — returned when too many approvals are already pending for the agent (resolve or wait before requesting more) |
-32006 | Invalid response / protocol error from the MCP server |
-32007 | MCP server unreachable |
-32008 | Server authentication could not be attached (fail-closed; HTTP 403) |
Policies
List policies
GET /api/v1/mcp/policiesCreate a policy (Editor, allowing: Admin)
POST /api/v1/mcp/policies{
"action": "DENY",
"mcp_tool_id": "clxyz...",
"agent_id": "ckagent...",
"reason": "Shell execution not allowed"
}action is required, plus at least mcp_tool_id or agent_id; reason and conditions are optional. Valid actions: ALLOW, LOG_ONLY, REQUIRE_APPROVAL, DENY; any other value returns 400 listing the allowed set. An allowing policy (ALLOW, LOG_ONLY) needs an Admin. With agent_id only REQUIRE_APPROVAL and DENY are accepted, because an agent rule can only tighten the server-wide rule; ALLOW or LOG_ONLY returns 400 mcp_agent_rule_ineffective and writes nothing. There is no update endpoint; to change a tool's rule use the set-rule route above.
Delete a policy (Admin)
DELETE /api/v1/mcp/policies/{id}Approval requests
List pending
GET /api/v1/mcp/approvalsReturns only open approval requests (PENDING) that have not yet expired.
Decide
POST /api/v1/mcp/approvals/{id}/decide{ "approved": true, "comment": "Reviewed — one-time access granted" }After an approval, send the call again and carry the approval id, either in the X-Palveron-Approval-Id header or in the _palveron_approval_id argument. The approval is single use and bound to the exact arguments it was requested for (a canonical hash of the tool arguments); a call with different arguments opens a new approval request.
Targeted MCP stop (Admin)
POST /api/v1/mcp/emergency-stop{ "scope": "server", "target_id": "<server_id>", "reason": "Security incident" }Scopes: server (block one specific server, target_id required; a server that is not ACTIVE stays unchanged) and agent (end the open approvals of one agent, target_id required). Each ends only the open approvals of its target. Admin only, and recorded as a Flare-attested log entry. The former scope: "all" has been removed (mcp_scope_all_removed); to stop the whole project at once, use the project emergency stop.
Refusal codes
With every one of these codes nothing is written.
| Code | Status | Meaning | Route |
|---|---|---|---|
mcp_scan_missing | 409 | server never scanned | approve |
mcp_scan_failed | 409 | last scan failed | approve |
mcp_scan_not_run | 409 | last scan could not run because of the address or sign-in | approve |
mcp_scan_unreadable | 409 | stored scan result does not name the tools: scan again | approve |
mcp_release_tools_required | 400 | tools missing, or empty when admitting | approve, admit |
mcp_release_tool_missing | 400 | a tool of the last scan is missing; the message names them | approve |
mcp_release_tool_unknown | 400 | a named tool is not in the decidable set | approve, admit |
mcp_release_tool_duplicate | 400 | tool named more than once | approve, admit |
mcp_release_rule_invalid | 400 | rule outside the four values | approve, admit |
mcp_release_surface_changed | 409 | name, description or parameters changed since the list was read: reload | approve, admit |
mcp_release_already_released | 409 | tool is admitted and unchanged; change its rule via …/verdict | admit |
mcp_release_server_not_active | 409 | admitting only on an active server | admit |
mcp_resume_takes_no_tools | 400 | resume with tools | resume |
mcp_release_author_unresolved | 403 | the Admin's identity could not be resolved; a confirmed rule needs its human | approve, admit |
mcp_agent_rule_ineffective | 400 | agent rule with ALLOW or LOG_ONLY | create policy |
mcp_template_unavailable | 409 | template locked; the message names the sign-in method | create from template |
mcp_scope_all_removed | 400 | the MCP stop no longer stops all servers at once; use a targeted stop or the project emergency stop | emergency-stop |
mcp_server_name_conflict | 409 | a server with this name already exists in the project (ignoring case and outer spaces) | register |