Skip to content

MCP Server

Portwing's built-in read-only Model Context Protocol endpoint lets MCP-aware AI agents query live Docker container and host state.

Portwing ships a built-in Model Context Protocol server at POST /_portwing/mcp. AI assistants — Claude Desktop, Cursor, Windsurf, or any MCP-compatible client — can call its tools to inspect containers and host metrics using their standard tool-call flow. No extra process or sidecar is needed; the endpoint is part of the main Portwing HTTP server.

The MCP server is supported in Portwing v0.9.x. Its tool names, input schemas, and result shapes follow the repository's published pre-v1 stability policy.


Protocol

PropertyValue
EndpointPOST /_portwing/mcp
Protocol revisionMCP 2025-11-25
TransportStreamable HTTP — stateless single-request mode
Content-Typeapplication/json (request and response)
Session managementNone — no Mcp-Session-Id is issued or required

Every POST returns one JSON-RPC 2.0 response object. GET /_portwing/mcp returns 405 Method Not Allowed to signal that no SSE stream is offered at this endpoint.

Authentication follows the same bearer-token scheme used by all other Portwing endpoints. See Authentication.


Enabling the endpoint

The MCP endpoint is always active when Portwing is running — it is not gated by a separate flag or environment variable. Configure access using the standard token variables (TOKEN, TOKEN_FILE, or TOKEN_HASH). See Configuration for the full variable reference.

# Minimal example: run Portwing with a token and connect an MCP client
docker run -e TOKEN=mysecrettoken -p 3000:3000 ghcr.io/codeswhat/portwing:latest

Read-only tools, not a read-only credential

The MCP tools themselves are strictly read-only. None of the five tools listed below can start, stop, restart, remove, or mutate any container or resource. There is no write tool, and none is planned. Destructive operations must go through the authenticated REST API directly.

That boundary is at the protocol level, not the credential level. /_portwing/mcp is authenticated by the same middleware, and the same shared token or Ed25519 key, as every other Portwing route, including the Docker proxy catch-all at /. See Authentication and Security Model. Whatever credential can call tools/call can also call the full Docker Engine API through the proxy: start, stop, remove containers, mount host paths, and anything else that API exposes. Portwing has no read-only role, scope, or permission tier for tokens or keys today, so every credential is all-or-nothing. A compromised MCP credential is a compromised Portwing credential, not a compromised read-only viewer.

If you want the AI agent on a credential you can reason about independently, generate it its own Ed25519 keypair instead of handing it an operator's shared token. Token-based auth (TOKEN, TOKEN_HASH) is a single shared secret for the whole agent, so it can't be split this way. A dedicated Ed25519 key does not narrow what the key can do; it still has full proxy access. But it lets you revoke that one key without affecting other integrations, and its calls appear under their own key ID in the audit log (see Audit Logging).

Additionally, inspect_container returns only the count of environment variables — values are never transmitted. This prevents accidental leakage of secrets, API keys, or credentials that are commonly passed to containers via environment variables. See Security Model for more detail.


Tools

The server advertises five tools via tools/list. All are read-only.

list_containers

Returns all Docker containers (running and stopped).

Parameters: none

Response fields per container:

FieldTypeDescription
idstringFull container ID
namesstring[]Container name(s)
imagestringImage reference
statestringRuntime state (e.g. running, exited)
statusstringHuman-readable status string
labelsobjectContainer labels (omitted if empty)

inspect_container

Returns detailed state for a single container.

Parameters:

NameTypeRequiredDescription
idstringyesContainer ID or name

Response fields:

FieldTypeDescription
idstringFull container ID
namestringContainer name
stateobjectRuntime state struct
imagestringImage reference
envCountintegerNumber of environment variables (values never returned)
mountsarrayEach mount: source, destination, readOnly
networksstring[]Network names the container is attached to
restartPolicystringRestart policy name

container_logs

Returns the last N lines of stdout and stderr from a container. Each line is prefixed with stdout: or stderr:.

Parameters:

NameTypeRequiredDescription
idstringyesContainer ID or name
tailintegernoLines to return (1–500, default 100)

Response fields:

FieldTypeDescription
idstringContainer ID echoed back
linesstring[]Log lines, each prefixed stdout: or stderr:

host_metrics

Returns a point-in-time snapshot of host-level resource metrics. No parameters.

Parameters: none

Response: a metrics snapshot including CPU, memory, disk, network I/O, and uptime. The exact shape matches the Prometheus collector used internally by Portwing.

Linux only. Every field except cpuCores and the disk fields is read from /proc, so this tool works on the container, .deb and .rpm builds and nowhere else. On a native macOS install it returns an MCP error naming the missing procfs instead of a snapshot of zeros, because a zero-filled snapshot is indistinguishable from a real reading of an idle host. The Prometheus endpoint reports the same thing as portwing_host_metrics_supported 0.

Disk usage (diskTotal, diskUsed, diskFree) is measured with statfs against the Docker daemon's actual data root, resolved from /info rather than assumed to be /var/lib/docker. That read can fail independently of the /proc fields — for example if the data root is unreadable — in which case diskMetricsAvailable is false, the disk fields stay 0, and diskError names the failure instead of the response being an MCP error. /metrics mirrors this as portwing_host_disk_metrics_available 0 with the disk byte series omitted.


container_stats

Returns a one-shot CPU, memory, and network stats snapshot for a single container.

Parameters:

NameTypeRequiredDescription
idstringyesContainer ID or name

Response fields:

FieldTypeDescription
idstringContainer ID echoed back
cpuTotalUsageintegerCumulative CPU usage in nanoseconds
memUsageintegerCurrent memory usage in bytes
memLimitintegerMemory limit in bytes
networksobjectPer-interface map of rxBytes and txBytes

Client configuration

Replace YOUR_PORTWING_TOKEN with the value you set in TOKEN, TOKEN_FILE, or TOKEN_HASH (see Configuration).

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "portwing": {
      "type": "http",
      "url": "http://your-host:3000/_portwing/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_PORTWING_TOKEN"
      }
    }
  }
}

Claude Code CLI

claude mcp add --transport http \
  --header "Authorization: Bearer YOUR_PORTWING_TOKEN" \
  portwing http://your-host:3000/_portwing/mcp

Project-level .mcp.json (Cursor, Windsurf, or any MCP client)

{
  "mcpServers": {
    "portwing": {
      "type": "http",
      "url": "http://your-host:3000/_portwing/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_PORTWING_TOKEN"
      }
    }
  }
}