REST API
MCPProxy provides a REST API for server management and monitoring.
Interactive API documentation is available at http://127.0.0.1:8080/swagger/ when MCPProxy is running. The OpenAPI spec file is also available at oas/swagger.yaml.
Authentication
All /api/v1/* endpoints require authentication via API key:
# Using X-API-Key header (recommended)
curl -H "X-API-Key: your-api-key" http://127.0.0.1:8080/api/v1/servers
# Using query parameter
curl "http://127.0.0.1:8080/api/v1/servers?apikey=your-api-key"
Note: Unix socket connections bypass API key authentication (OS-level auth).
Base URL
http://127.0.0.1:8080/api/v1
Request ID Tracking
All API responses include an X-Request-Id header for request tracing and log correlation. This is useful for debugging issues and correlating errors with server logs.
Request Header
You can optionally provide your own request ID:
curl -H "X-API-Key: your-api-key" \
-H "X-Request-Id: my-custom-id-123" \
http://127.0.0.1:8080/api/v1/servers
Validation rules:
- Pattern:
^[a-zA-Z0-9_-]{1,256}$ - Max length: 256 characters
- If missing or invalid, MCPProxy generates a UUID v4
Response Header
Every response includes the request ID:
X-Request-Id: my-custom-id-123
Error Responses
Error responses include the request_id in the JSON body for easy correlation:
{
"success": false,
"error": "server 'nonexistent' not found",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Log Correlation
Use the request ID to find related activity logs:
# Via CLI
mcpproxy activity list --request-id a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Via API
curl "http://127.0.0.1:8080/api/v1/activity?request_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Endpoints
Status
GET /api/v1/status
Get server status and statistics.
Response:
{
"status": "running",
"version": "0.11.0",
"uptime": 3600,
"servers": {
"total": 5,
"connected": 4,
"quarantined": 1
},
"tools": {
"total": 42
}
}
Servers
GET /api/v1/servers
List all upstream servers with unified health status.
Header redaction and the mask format
By default, sensitive header values (Authorization, X-API-Key, Cookie,
Set-Cookie, etc.) are replaced with a length-preserving mask of the form
••••<last2> (<N> chars) before serialization. This applies to:
GET /api/v1/serversand its single-server children- The
/eventsSSEservers.changedpayloads - The
upstream_servers listMCP tool
The mask preserves enough information to identify which token is in use
(the last two characters + total length) while keeping the secret out of
the response. Values that are already secret references — ${keyring:NAME}
or ${env:VAR} — pass through unchanged because they're labels, not
secrets.
Setting reveal_secret_headers: true in
mcp_config.json disables redaction on
all three channels. This is not normally needed: the Web UI / macOS
tray / CLI can edit, delete, and convert-to-secret without ever seeing
the plaintext, because the PATCH endpoint deep-merges (omitted keys are
preserved) and the config-to-secret
endpoint reads the real value server-side. Flip the flag only if you
need to inspect a raw value through the API for debugging.
The MCP upstream_servers tool was the original motivator for redaction
(see PR #425) —
a prompt-injected agent could otherwise read another upstream's PAT via
upstream_servers list.
Response:
{
"success": true,
"data": {
"servers": [
{
"name": "github-server",
"protocol": "http",
"enabled": true,
"connected": true,
"quarantined": false,
"tool_count": 15,
"health": {
"level": "healthy",
"admin_state": "enabled",
"summary": "Connected (15 tools)",
"action": ""
}
},
{
"name": "oauth-server",
"protocol": "http",
"enabled": true,
"connected": false,
"quarantined": false,
"tool_count": 0,
"health": {
"level": "unhealthy",
"admin_state": "enabled",
"summary": "Token expired",
"detail": "OAuth access token has expired",
"action": "login"
}
}
]
}
}
Health Object Fields:
| Field | Type | Description |
|---|---|---|
level | string | Health level: healthy, degraded, or unhealthy |
admin_state | string | Admin state: enabled, disabled, or quarantined |
summary | string | Human-readable status message |
detail | string | Optional additional context about the status |
action | string | Suggested remediation: login, restart, enable, approve, view_logs, or empty |
PATCH /api/v1/servers/{name}
Partial update of an existing upstream server. All request fields are optional; omitted fields are preserved as-is.
The map-typed fields headers and env follow JSON Merge Patch
(RFC 7396) semantics:
| Value in patch body | Effect on stored map |
|---|---|
| key present with a non-null string value | upsert (add or replace that key) |
key present with JSON null | delete that key |
| key absent from the patch body | preserve as-is |
This is the same convention the MCP upstream_servers patch tool uses. It
lets the Web UI / macOS tray / CLI send a minimal diff — keys that match
the server's current masked view (••••<last2> (<N> chars) — see
Header redaction below) simply stay
out of the patch body, so the real stored value is never overwritten by the
mask string.
Request body (AddServerRequest — all fields optional):
{
"url": "https://api.example.com/mcp",
"command": "uvx",
"args": ["mcp-server-foo"],
"env": {"API_KEY": "new-value", "OLD_VAR": null},
"headers": {"X-Trace": "on", "X-Stale": null},
"working_dir": "/path/to/dir",
"protocol": "http",
"enabled": true,
"quarantined": false,
"isolation": {"enabled": true, "image": "node:20"}
}
Examples:
# Rotate a Bearer token without touching anything else on the server
curl -X PATCH -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"headers":{"Authorization":"Bearer new-token"}}' \
http://127.0.0.1:8080/api/v1/servers/synapbus
# Remove a stale header (the JSON null is the delete signal)
curl -X PATCH -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"headers":{"X-Stale":null}}' \
http://127.0.0.1:8080/api/v1/servers/synapbus
# Upsert one env var and delete another in a single round-trip
curl -X PATCH -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"env":{"LOG_LEVEL":"debug","OBSOLETE":null}}' \
http://127.0.0.1:8080/api/v1/servers/obsidian-pilot
Notes:
- Empty string
""is set-to-empty, NOT delete. JSON Merge Patch is explicit about this — only the JSONnulltoken deletes. - Boolean fields (
enabled,quarantined,reconnect_on_use) use pointer-style semantics: absent = preserve, present = explicit value.
POST /api/v1/servers/{name}/config-to-secret
Atomically move a header or env value out of mcp_config.json and into the
OS keyring. The backend reads the real value from the loaded config, stores
it in the keyring under secret_name, and rewrites the config field with
${keyring:<secret_name>}. The client never needs to possess the plaintext
— useful when the API redacts sensitive header values on the read path.
Request body:
{
"scope": "header",
"key": "Authorization",
"secret_name": "synapbus-auth"
}
| Field | Type | Description |
|---|---|---|
scope | string | header or env |
key | string | The key on the server's headers / env map |
secret_name | string | Name to store the value under in the OS keyring |
Response (200 OK):
{
"success": true,
"data": {
"message": "header \"Authorization\" on \"synapbus\" now references keyring secret \"synapbus-auth\"",
"reference": "${keyring:synapbus-auth}"
}
}
Failure cases:
| Status | Cause |
|---|---|
| 400 | Missing scope / key / secret_name, invalid scope, value is already a ${keyring:…} or ${env:…} reference, or value is empty |
| 404 | Server or key not found |
| 500 | Secret resolver unavailable, keyring store failed, or config update failed |
This endpoint is what the Web UI and macOS tray "Convert to secret" button calls. It works even for headers the API redacts (the backend has the real value on disk).
POST /api/v1/servers/{name}/enable
Enable a server.
POST /api/v1/servers/{name}/disable
Disable a server.
POST /api/v1/servers/{name}/quarantine
Place a server in quarantine to prevent tool execution. No request body required.
POST /api/v1/servers/{name}/unquarantine
Remove a server from quarantine to allow tool execution. No request body required.
POST /api/v1/servers/{name}/restart
Restart a server.
POST /api/v1/servers/{name}/login
Initiate OAuth authentication flow for a server.
Response (200 OK):
{
"success": true,
"data": {
"success": true,
"server_name": "github-server",
"correlation_id": "a1b2c3d4e5f6789012345678",
"browser_opened": true,
"message": "OAuth authentication started for server 'github-server'. Please complete authentication in browser."
}
}
OAuthStartResponse Fields:
| Field | Type | Description |
|---|---|---|
success | boolean | Always true for successful initiation |
server_name | string | Name of the server being authenticated |
correlation_id | string | Unique ID for tracking this OAuth flow |
auth_url | string | Authorization URL (for manual browser opening) |
browser_opened | boolean | Whether browser was automatically opened |
browser_error | string | Error message if browser opening failed |
message | string | Human-readable status message |
Error Response (400 Bad Request):
OAuth errors return structured error responses for better debugging:
{
"success": false,
"error_type": "dcr_failed",
"server_name": "github-server",
"message": "Dynamic Client Registration failed: 403 Forbidden",
"suggestion": "Check if the OAuth server requires pre-registered clients",
"correlation_id": "a1b2c3d4e5f6789012345678",
"request_id": "req-xyz-123",
"details": {
"metadata": {
"protected_resource_url": "https://api.example.com/.well-known/oauth-protected-resource",
"authorization_server_url": "https://auth.example.com/.well-known/oauth-authorization-server",
"status": "ok"
},
"dcr": {
"attempted": true,
"status": "failed",
"error": "403 Forbidden"
}
},
"debug_hint": "For logs: mcpproxy upstream logs github-server"
}
OAuthFlowError Fields:
| Field | Type | Description |
|---|---|---|
error_type | string | Error category: client_id_required, dcr_failed, metadata_discovery_failed, code_flow_failed |
server_name | string | Name of the server |
message | string | Human-readable error description |
suggestion | string | Actionable remediation hint |
correlation_id | string | Flow tracking ID |
request_id | string | HTTP request ID for log correlation |
details | object | Diagnostic details (metadata status, DCR status) |
debug_hint | string | CLI command for debugging |
POST /api/v1/servers/{name}/logout
Clear OAuth tokens and disconnect a server.
Tool Quarantine
POST /api/v1/servers/{name}/tools/approve
Approve pending or changed tools for a server. See Tool Quarantine for details.
Request Body:
{
"tools": ["create_issue", "delete_repo"]
}
Or approve all pending/changed tools:
{
"approve_all": true
}
Response:
{
"success": true,
"data": {
"approved": 2,
"tools": ["create_issue", "delete_repo"],
"message": "Approved 2 tools for server github-server"
}
}
GET /api/v1/servers/{name}/tools/{tool}/diff
Get the description/schema diff for a changed tool.
Response:
{
"success": true,
"data": {
"server_name": "github-server",
"tool_name": "delete_repo",
"status": "changed",
"approved_hash": "abc123...",
"current_hash": "def456...",
"previous_description": "Delete a repository",
"current_description": "Delete a repository (modified description)",
"previous_schema": "...",
"current_schema": "..."
}
}
GET /api/v1/servers/{name}/tools/export
Export all tool descriptions and schemas for a server. Useful for audit and compliance.
Query Parameters:
format- Export format:json(default) ortext
Routing
GET /api/v1/routing
Get the current routing mode and available MCP endpoints.
Response:
{
"success": true,
"data": {
"routing_mode": "retrieve_tools",
"description": "BM25 search via retrieve_tools + call_tool variants (default)",
"endpoints": {
"default": "/mcp",
"direct": "/mcp/all",
"code_execution": "/mcp/code",
"retrieve_tools": "/mcp/call"
},
"available_modes": ["retrieve_tools", "direct", "code_execution"]
}
}
See Routing Modes for details on each mode.
Tools
GET /api/v1/tools
Search tools across all servers.
Query Parameters:
q- Search query (optional)limit- Maximum results (default: 15)
Response:
{
"tools": [
{
"name": "github:create_issue",
"server": "github-server",
"description": "Create a new GitHub issue"
}
]
}
GET /api/v1/servers/{name}/tools
List tools for a specific server.
Real-time Updates
GET /events
Server-Sent Events (SSE) stream for live updates.
curl "http://127.0.0.1:8080/events?apikey=your-api-key"
Events include:
servers.changed- Server status changedconfig.reloaded- Configuration reloadedtools.indexed- Tool index updatedactivity.tool_call.started- Tool call initiatedactivity.tool_call.completed- Tool call finishedactivity.policy_decision- Tool call blocked by policy
Error Responses
{
"error": "error message",
"code": "ERROR_CODE"
}
| Code | Description |
|---|---|
| 401 | Unauthorized - Invalid or missing API key |
| 404 | Not Found - Server or resource not found |
| 500 | Internal Server Error |
Configuration
GET /api/v1/config
Get current configuration.
POST /api/v1/config/apply
Apply configuration changes.
POST /api/v1/config/validate
Validate configuration without applying.
Diagnostics
GET /api/v1/diagnostics
Get system diagnostics.
GET /api/v1/doctor
Run health checks (same as mcpproxy doctor CLI).
GET /api/v1/info
Get application info, version, and update availability.
Response:
{
"success": true,
"data": {
"version": "v1.2.3",
"web_ui_url": "http://127.0.0.1:8080/?apikey=xxx",
"listen_addr": "127.0.0.1:8080",
"endpoints": {
"http": "127.0.0.1:8080",
"socket": "/Users/user/.mcpproxy/mcpproxy.sock"
},
"update": {
"available": true,
"latest_version": "v1.3.0",
"release_url": "https://github.com/smart-mcp-proxy/mcpproxy-go/releases/tag/v1.3.0",
"checked_at": "2025-01-15T10:30:00Z",
"is_prerelease": false
}
}
}
Response Fields:
| Field | Type | Description |
|---|---|---|
version | string | Current MCPProxy version |
web_ui_url | string | URL to access the web control panel |
listen_addr | string | Server listen address |
endpoints.http | string | HTTP API endpoint address |
endpoints.socket | string | Unix socket path (empty if disabled) |
update | object | Update information (may be null if not checked yet) |
update.available | boolean | Whether a newer version is available |
update.latest_version | string | Latest version available on GitHub |
update.release_url | string | URL to the GitHub release page |
update.checked_at | string | ISO 8601 timestamp of last update check |
update.is_prerelease | boolean | Whether the latest version is a prerelease |
update.check_error | string | Error message if update check failed |
MCPProxy automatically checks for updates every 4 hours. The update information is exposed via this endpoint and used by the tray application and web UI to show update notifications.
Docker
GET /api/v1/docker/status
Get Docker isolation status.
Secrets
GET /api/v1/secrets
List stored secrets.
GET /api/v1/secrets/{name}
Get secret metadata (not the value).
Sessions
GET /api/v1/sessions
List active MCP sessions.
GET /api/v1/sessions/{id}
Get session details.
Activity
Track and audit AI agent tool calls. See Activity Log for detailed documentation.
GET /api/v1/activity
List activity records with filtering and pagination.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
type | string | Filter by type: tool_call, policy_decision, quarantine_change, server_change |
server | string | Filter by server name |
tool | string | Filter by tool name |
session_id | string | Filter by MCP session ID |
status | string | Filter by status: success, error, blocked |
start_time | string | Filter after this time (RFC3339) |
end_time | string | Filter before this time (RFC3339) |
limit | integer | Max records (1-100, default: 50) |
offset | integer | Pagination offset (default: 0) |
Response:
{
"success": true,
"data": {
"activities": [
{
"id": "01JFXYZ123ABC",
"type": "tool_call",
"server_name": "github-server",
"tool_name": "create_issue",
"status": "success",
"duration_ms": 245,
"timestamp": "2025-01-15T10:30:00Z"
}
],
"total": 150,
"limit": 50,
"offset": 0
}
}
GET /api/v1/activity/{id}
Get full activity record details including request arguments and response data.
GET /api/v1/activity/export
Export activity records for compliance and auditing.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
format | string | Export format: json (JSON Lines) or csv |
| (filters) | Same filters as list endpoint |
Example:
# Export as JSON Lines
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity/export?format=json"
# Export as CSV
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity/export?format=csv"
Bulk Operations
POST /api/v1/servers/enable_all
Enable all servers.
POST /api/v1/servers/disable_all
Disable all servers.
POST /api/v1/servers/restart_all
Restart all servers.
POST /api/v1/servers/reconnect
Reconnect all servers.
OpenAPI Specification
The complete OpenAPI 3.1 specification is available at:
/swagger/- Interactive Swagger UI/swagger/swagger.yaml- Raw specification
See oas/swagger.yaml in the repository for the complete API reference.