Skip to content

MCP Server Reference

BRIDGEPORT can expose a curated subset of its HTTP API as Model Context Protocol (MCP) tools, so an MCP-capable agent (Claude Desktop, Claude Code, Cursor, etc.) can list environments, inspect services, read logs, and — for operators — trigger deploys and backups, all through BRIDGEPORT’s existing authentication and authorization.

The MCP server is disabled by default and is a thin projection of the REST API: every tool replays a real internal API request carrying the caller’s bearer token, so auth, role/scope enforcement, validation, idempotency, and audit logging behave exactly as they do for a REST call. There is no separate permission model and no new business logic.

The Model Context Protocol is an open standard that lets an AI model call external tools over a well-defined transport. BRIDGEPORT implements the server side: it advertises a set of tools and runs them on request.

Key properties:

  • Bring-your-own-model. BRIDGEPORT does no inference on the host. It never calls an LLM and never ships a model. The model runs wherever your MCP client runs (your laptop, Anthropic’s API, etc.); BRIDGEPORT only answers tool calls.
  • A projection of the API. Each tool and resource maps to one existing REST endpoint. The handler issues an internal request with your bearer token, so it can never do anything your token couldn’t already do via the REST API.
  • Tools and resources (no prompts/subscriptions). The server exposes tools (callable actions) and read-only resources (browseable content — see Resources) — but no MCP prompts or subscriptions. The transport is stateless: each request is self-contained, with no server-side session, so resource list-change notifications are not pushed (re-list on demand).

Set the environment variable and restart BRIDGEPORT:

Terminal window
MCP_ENABLED=true
VariableTypeDefaultDescription
MCP_ENABLEDbooleanfalseMaster switch for the MCP server. Strictly parsed: only true or 1 (case-insensitive, whitespace-trimmed) enable it; anything else — including false, 0, an empty string, or leaving it unset — keeps it off. When off, the /mcp route is not registered at all (requests return 404).
MCP_ALLOWED_HOSTSstring (comma-separated)(unset)Public Host header value(s) MCP clients use to reach /mcp (e.g. mcp.example.com). When set, enables DNS-rebinding protection limited to these hosts; off by default. See Transport and Networking.

Why MCP_ENABLED is parsed strictly. It’s a network-exposed, default-off security feature, so it must fail closed: a literal MCP_ENABLED=false (or =0) keeps the endpoint disabled. This differs from BRIDGEPORT’s other boolean env flags by design.

When enabled, the endpoint is:

POST {BRIDGEPORT_URL}/mcp

MCP_ENABLED is a deployment-level kill switch (an environment variable, not a database setting): flipping it off and restarting removes the endpoint entirely.


An admin-only page at /admin/mcp (in the UI, Admin Settings → MCP Server) is the in-app home for everything in this reference:

  • Status — an enabled/disabled badge (derived from the MCP_ENABLED env var; there is no UI toggle — enable/disable stays a deployment-level env-var decision), the live endpoint URL (window.location.origin + /mcp), and the DNS-rebinding-protection state (configured hosts, or a prompt to set MCP_ALLOWED_HOSTS for remote/proxied clients).
  • Connect a client — ready-to-copy config snippets for Claude Desktop, Cursor, and Claude Code, with a pointer to the Integrations page to mint an API / service-account token.
  • Exposed surface — the full tool inventory (name, description, read/write, required scope, destructive, env-scoped) and the resource inventory (name, description, URI template, scope, env-scoped), read from the in-process registries so it is accurate whether or not MCP is currently enabled.
  • Safety / data egress — a summary of the redaction boundary and write-tool semantics (mirrors Data Egress).

It is backed by the admin-only GET /api/admin/mcp endpoint and is read-only — it is operational guidance and status, not a runtime switch.


The endpoint speaks Streamable HTTP (the modern MCP HTTP transport). Point your client at POST {BRIDGEPORT_URL}/mcp and supply an Authorization: Bearer <token> header. Use an API token (admin-managed, see the API Reference) rather than a session JWT — API tokens are long-lived and can be scoped to specific environments and a capped role.

Most desktop clients expect an MCP server entry with a URL and headers. A typical configuration looks like:

{
"mcpServers": {
"bridgeport": {
"url": "https://bridgeport.example.com/mcp",
"headers": {
"Authorization": "Bearer <your-api-token>"
}
}
}
}

If your client only supports stdio MCP servers, run a Streamable-HTTP bridge (e.g. mcp-remote) in front of the URL.

Terminal window
claude mcp add --transport http bridgeport https://bridgeport.example.com/mcp \
--header "Authorization: Bearer <your-api-token>"

Then, in a session, the BRIDGEPORT tools appear alongside Claude Code’s built-in tools. Start by calling get_capabilities to confirm which tools your token unlocked.


The /mcp route is protected by the same authentication layer as the REST API. The bearer token you present is validated on connect, and the tools registered for that session depend on the token’s role-derived scopes:

RoleRead toolsWrite tools
viewer
operator
admin
  • Read tools are available to any valid token (every role has *:read).
  • Write tools are registered only when the token’s scopes include services:write — which BRIDGEPORT grants to operator and admin only. A viewer simply doesn’t see the write tools in tools/list.
  • Environment scoping narrows the tool list. An env-scoped token can connect, but it sees only the tools it can actually use — every tool backed by a global route (all write tools and global read tools like get_server or query_audit_log) is hidden, because such routes always return FORBIDDEN_SCOPE for an env-scoped token. See Environment-scoped tokens see only environment-scoped tools.
  • The admin-only secret-reveal endpoint is not exposed as any tool. There is no way to decrypt a secret value through MCP.

Call get_capabilities to see the exact scope set and tool list your token resolved to.

Environment-scoped tokens see only environment-scoped tools

Section titled “Environment-scoped tokens see only environment-scoped tools”

An environment-scoped API token (one whose scope is not “all environments”) gets a deliberately narrower — and truthful — MCP surface: tools/list (and get_capabilities) advertise only the tools it can actually use. A tool is listed for an env-scoped token only when its backing route is reachable by such a token:

  • Listed — environment-scoped routes (/api/environments/:envId/...), the scope-exempt environment list/get (GET /api/environments, GET /api/environments/:id), and no-scope routes (/health), plus the locally-synthesized get_capabilities. These are the per-environment read toolslist_servers, list_services, list_secrets, list_vars, get_server_health, list_config_files, list_config_fragments, get_metrics_history, list_health_checks, list_deployment_plans, list_environments, get_environment, list_databases, list_registries, list_external_entities, list_server_clusters, get_dependency_graph, list_container_images, list_webhook_subscriptions — plus get_version and get_capabilities. (An env-scoped admin token additionally sees the admin-gated env read get_environment_settings, whose route is also under /api/environments/:id/....)
  • Hidden — every tool backed by a global route (no environment in the path), because BRIDGEPORT’s token-scope check rejects an env-scoped token on those routes with FORBIDDEN_SCOPE, so listing them would only advertise guaranteed failures. This covers:
    • All write tools (deploy_service, execute_deployment_plan, restart_deployment, rollback_deployment_plan, run_database_backup, sync_config_file, refresh_server_health, execute_sync_batch) — e.g. POST /api/services/:id/deploy, POST /api/servers/:id/health, POST /api/sync/batch.
    • The global read tools get_server, get_service, get_service_logs, get_service_compose, get_service_dependencies, get_config_file, get_server_metrics, get_service_metrics, get_deployments, get_deployment_plan, get_drift, query_audit_log, get_database, list_database_backups, list_notifications, get_registry, get_container_image, list_image_digests, get_topology, list_connections, list_service_types, list_database_types, get_system_settings, and the admin-only list_service_accounts / list_api_tokens — each hits a global /api/<resource>/... route.

Per-resource scope enforcement still runs on every call regardless; this just stops the env-scoped tool list from advertising tools that could never succeed.

Recommendation: for the full MCP surface (all write tools + every read tool), use an all-environments API token with the role you intend — or a user session. Use env-scoped tokens when you specifically want to limit a session to read access within particular environments.


Tool outputs are passthrough JSON of the API response, with deliberate exceptions for safety: get_capabilities is synthesized locally, and tools touching secret material expose metadata only (see the secret-stripping summary below and Data Egress). IDs are BRIDGEPORT cuids — get them from the corresponding list_* / get_* tools.

Backed by side-effect-free GET routes. Available to every role unless marked admin-only (the last three rows mirror their requireAdmin / tokens:manage routes, so a non-admin token simply doesn’t see them in tools/list).

ToolArgumentsBacking route
list_environmentsGET /api/environments
get_environmentidGET /api/environments/:id
list_serversenvIdGET /api/environments/:envId/servers
get_serverid, includeServices?GET /api/servers/:id
get_server_healthenvIdGET /api/environments/:envId/health-status (cached health for all servers/services/databases — never triggers a live SSH check)
list_servicesenvIdGET /api/environments/:envId/services
get_serviceidGET /api/services/:id
get_service_logsid, depId, tail?GET /api/services/:id/deployments/:depId/logs
get_service_composeidGET /api/services/:id/compose/preview (rendered compose + env artifacts; resolved secret values are redacted)
get_service_dependenciesidGET /api/services/:id/dependencies
get_dependency_graphenvIdGET /api/environments/:envId/dependency-graph (nodes, edges, computed deployment order)
list_config_filesenvIdGET /api/environments/:envId/config-files
get_config_fileidGET /api/config-files/:id
list_config_fragmentsenvIdGET /api/environments/:envId/config-fragments
list_secretsenvIdGET /api/environments/:envId/secrets (keys + metadata + usage only)
list_varsenvIdGET /api/environments/:envId/varsthe plaintext value field is stripped from the tool output (keys, descriptions, usage, timestamps only)
list_databasesenvIdGET /api/environments/:envId/databases (metadata + backup/monitoring state; credentials never returned — only a hasCredentials flag)
get_databaseidGET /api/databases/:id (same credential-free shape as list_databases)
list_database_backupsid, limit?, offset?GET /api/databases/:id/backups
list_container_imagesenvId, limit?, offset?GET /api/environments/:envId/container-images
get_container_imageidGET /api/container-images/:id (digests + linked services)
list_image_digestsid, limit?, offset?GET /api/container-images/:id/digests
list_registriesenvIdGET /api/environments/:envId/registriesmetadata only: type/URL/prefix/defaults + hasToken/hasPassword booleans + the (non-secret) username. Credentials are never returned.
get_registryidGET /api/registries/:id (same credential-free shape as list_registries)
get_topologyenvironmentIdGET /api/diagram-export?format=mermaid (servers/services/databases/external entities + connections as a Mermaid graph)
list_connectionsenvironmentIdGET /api/connections (topology edges)
list_external_entitiesenvIdGET /api/environments/:envId/external-entities
list_server_clustersenvIdGET /api/environments/:envId/server-clusters
list_service_typesGET /api/settings/service-types (plugin-defined types + commands)
list_database_typesGET /api/settings/database-types (connection-field definitions describe shape only — no credential values)
list_notificationslimit?, offset?, unreadOnly?, environmentId?, category?GET /api/notifications
list_webhook_subscriptionsenvIdGET /api/environments/:envId/webhooksthe signing secret is never returned, only a hasSecret boolean
get_server_metricsidGET /api/servers/:id/metrics
get_service_metricsidGET /api/services/:id/metrics
get_metrics_historyenvIdGET /api/environments/:envId/metrics/history
list_health_checksenvIdGET /api/environments/:envId/health-logs
get_deploymentsidGET /api/services/:id/deployments-history
list_deployment_plansenvIdGET /api/environments/:envId/deployment-plans
get_deployment_planidGET /api/deployment-plans/:id
get_driftid (server)GET /api/servers/:id/drift
get_system_settingsGET /api/settings/system — the only secret field (the DO registry token) is masked by the route (****-suffixed)
query_audit_logenvironmentId?, resourceType?, resourceId?, action?, limit?, offset?GET /api/audit-logs
get_versionGET /health (app / bundled agent / CLI versions)
get_environment_settings 🔒id, moduleGET /api/environments/:id/settings/:moduleadmin-only (requireAdmin). modulegeneral | monitoring | operations | data | configuration
list_service_accounts 🔒GET /api/admin/service-accountsadmin-only. Metadata only (name, role, disabled, token count); no token values or hashes
list_api_tokens 🔒ownerUserId?, ownerServiceAccountId?GET /api/admin/tokens — requires tokens:manage (admin). Returns the non-secret tokenPrefix only; the token value/hash is never returned

🔒 = admin-gated (the tool is only registered for a token whose scopes include admin:* / tokens:manage).

get_service_logs and get_deployments operate at the deployment level (per-server runtime). Get the depId from get_service, which lists the service’s deployments.

Secret-stripping summary. Several read tools touch resources with secret material; in every case the tool exposes metadata only and the secret never leaves the host: list_secrets/list_vars (no values), list_registries/get_registry (hasToken/hasPassword booleans, no credential), list_databases/get_database (hasCredentials flag, no encrypted blob), list_webhook_subscriptions (hasSecret boolean, no signing secret), get_system_settings (DO registry token masked), and list_api_tokens (non-secret prefix, no value/hash). These are properties of the backing routes’ service-layer projections — the MCP tools add no new exposure.

Require a write scope (operator/admin) and are hidden from environment-scoped tokens (see above). Each carries the MCP destructiveHint: true annotation. Each mutating call injects a time-bucketed Idempotency-Key; dryRun=true previews are not cached (see Idempotency).

ToolArgumentsBacking route
deploy_serviceid, imageTag?, pullImage?, generateArtifacts?, strategy?, idempotencyKey?POST /api/services/:id/deploy
execute_deployment_planid, dryRun?, idempotencyKey?POST /api/deployment-plans/:id/execute (dryRun=true → non-mutating preview)
restart_deploymentid, depId, idempotencyKey?POST /api/services/:id/deployments/:depId/restart
rollback_deployment_planid, idempotencyKey?POST /api/deployment-plans/:id/rollback
run_database_backupid, idempotencyKey?POST /api/databases/:id/backups (operator)
sync_config_fileid, dryRun?, idempotencyKey?POST /api/config-files/:id/sync-all (operator; dryRun=true → diff preview)
refresh_server_healthid, idempotencyKey?POST /api/servers/:id/health (operator) — performs a LIVE SSH health check against the host and updates the stored health columns
execute_sync_batchoperations[] ({ configFileId }), rollbackOnFailure?, idempotencyKey?POST /api/sync/batch (operator) — atomic multi-file sync; no dry-run (preview individual files with sync_config_file(dryRun=true))

For execute_deployment_plan and sync_config_file, prefer a dryRun=true call first to preview the effect before running the real mutation.

refresh_server_health is the only read-adjacent write: it triggers a live host query rather than reading cached health (use get_server_health for the cached view). execute_sync_batch is all-or-nothing by default (rollbackOnFailure=true rolls back already-applied ops if a later one fails); all files in a batch must live in the same environment.

Conservative by design. The write surface is deliberately limited to operational actions (deploy / restart / rollback / backup / config-sync / live health-check). MCP does not expose create/update/delete for servers, services, secrets, vars, config files, databases, environments, registries, etc. — declarative resource management is the Terraform provider’s domain. MCP is observe + safe-operate only.

ToolArgumentsBehavior
get_capabilitiesReturns { version, scopes, tools } for the current session — the BRIDGEPORT version, your token’s derived scopes, and the names of the tools you can call. Synthesized locally (no API call).

In addition to tools, the MCP server exposes a small set of read-only resources — content an MCP client can browse and attach to a conversation. Like tools, every resource is a thin projection of the REST API: its content is fetched by replaying a real internal request with your bearer token, so the same authentication, role/scope enforcement, and audit logging apply. Resources never mutate anything.

A note on data egress applies here too: resource content is sent to whatever model your MCP client uses. See Data Egress.

Resources are addressed by a custom bridgeport:///… URI scheme:

ResourceURIKindBacking route (read)
Config filesbridgeport:///config-files/{id}templateGET /api/config-files/:id
Config fragmentsbridgeport:///config-fragments/{id}templateGET /api/config-fragments/:id
Capabilitiesbridgeport:///capabilitiesstatic— (synthesized locally)
  • Config files and config fragments are resource templates. The resources/list (and resources/templates/list) request enumerates them lazily — nothing heavy runs at connect. When a client lists, the server walks your accessible environments (via GET /api/environments, which is already filtered to your token’s environment allowlist) and, for each, the env-scoped list route (GET /api/environments/:envId/config-files / …/config-fragments). Listed descriptors are labelled "<environment>: <name>". Reading a specific URI replays the per-id GET route, which runs the full scope/role check.
    • Enumeration cap. Each per-environment list inject is issued with an explicit ?limit=200, so up to 200 config files / fragments per environment are discoverable as resources (the underlying list routes default to a page of 25 — without the cap, items beyond the first 25 in an environment would be invisible to resources/list). This also bounds the enumeration so a single oversized environment can’t fan out unboundedly. The per-environment injects run concurrently.
  • Capabilities is a single static resource returning { version, scopes, tools, resources } for the current session — the same shape as the get_capabilities tool, extended with the registered resource names. It is synthesized locally (no API call).

Content is the templated (non-secret) form

Section titled “Content is the templated (non-secret) form”

The config-file and config-fragment read routes return the content exactly as stored — the templated form, with ${KEY} placeholders left intact. They do not resolve placeholders or decrypt secrets. (Placeholder resolution only happens on the separate preview / compose routes, which additionally redact resolved secret values; those are not exposed as resources.) So a resource read can surface a config file that references ${DB_PASSWORD}, but never the decrypted value of DB_PASSWORD. This is the same content the get_config_file tool already returns. (As with that tool, a literal secret you hard-coded inline into a config file’s text will appear — use ${KEY} placeholders + BRIDGEPORT secrets rather than inline literals.)

  • Read scope. Every valid token can list/read these resources (every role has *:read) — there is no write resource.
  • Environment-scoped tokens. An environment-scoped token does not see the config-file/fragment resources at all. Their read replays a global per-id route (GET /api/config-files/:id, GET /api/config-fragments/:id) that returns FORBIDDEN_SCOPE for an env-scoped token — so, exactly like the get_config_file tool, the resource is withheld to keep the advertised list truthful (it would only ever fail to read). The capabilities resource needs no API call and remains available. For browseable config resources, use an all-environments token.
  • The resources capability is advertised automatically when any resource is registered, so MCP-capable clients discover resource support on connect.

Each mutating write call injects an Idempotency-Key header so BRIDGEPORT’s idempotency middleware engages and a duplicated/retried identical call is deduplicated (the original result is replayed instead of running the mutation twice).

  • Short dedup window (default). The derived key folds in a ~60-second time bucket: sha256(toolName + ":" + timeBucket + ":" + canonicalJSON(args)). So identical calls within ~60s dedupe as retries (the original result replays), while an intended repeat of the same operation later executes normally rather than silently replaying a stale success. For example, run_database_backup({ id }) called twice within a minute returns the first result both times; called again ten minutes later it runs a new backup.
  • Dry-run previews are never cached. A call with dryRun=true (execute_deployment_plan, sync_config_file) attaches no Idempotency-Key, so re-running an identical preview always recomputes a fresh diff instead of replaying a stale one.
  • Override. Pass an explicit idempotencyKey string argument on any write tool to set the key yourself — to force dedup across the 60s windows (e.g. tie an operation to an external job id) or to extend the safety net. The override is excluded from the derived-key hash and is ignored for dry-run previews.

The outer POST /mcp envelope itself is not idempotency-managed — only the injected sub-calls are. (The transport hijacks the response, which would wedge a key applied to the envelope; meaningful idempotency lives on the real mutating sub-calls.)

See the API Reference and issue #126 for the underlying idempotency contract (24-hour retention, conflict on same-key/different-body, etc.).


When you connect an MCP client, tool outputs are sent to whatever model that client uses. Treat anything a tool can return as data that may leave your infrastructure:

  • Logs (get_service_logs) and audit entries (query_audit_log) can contain sensitive application output — request payloads, stack traces, tokens an app happened to log. They are returned verbatim. Only enable MCP, and only mint tokens, for operators you trust to route that data to their model.
  • Secret, variable, and credential values are never returned. list_secrets exposes keys and metadata only; list_vars strips the value field; list_registries/get_registry return hasToken/hasPassword flags (no credential); list_databases/get_database return a hasCredentials flag (no encrypted blob); list_webhook_subscriptions returns a hasSecret flag (no signing secret); get_system_settings masks the DO registry token; list_api_tokens returns only the non-secret token prefix; and the admin-only secret-reveal endpoint is not exposed as a tool.
  • A boundary redactor enforces this independent of route behavior. As defense-in-depth, every tool output and every resource read is passed through a recursive redactor before it leaves the MCP server. It strips, by key name (recursing into nested objects/arrays), all encrypted/nonce/credential columns from the schema (encrypted*, *Nonce, plus sshPrivateKey, agentToken, tokenHash, etc.). The non-secret presence metadata (hasToken, hasPassword, hasSecret, hasCredentials, tokenPrefix) is deliberately preserved. So even if an underlying route regressed and returned a raw row, the secret-named fields could not reach the client.
  • Config and compose content is returned. get_config_file (and the equivalent bridgeport:///config-files/{id} / bridgeport:///config-fragments/{id} resources) return config content in its templated form (${KEY} placeholders intact, never resolved/decrypted secret values); get_service_compose returns the rendered compose/env artifacts with resolved secret values redacted. A config file you wrote with an inline literal will still show that literal. get_topology / list_connections expose your infrastructure layout (hostnames, ports, service names).
  • refresh_server_health performs a live host query. Unlike the cached get_server_health read, it opens an SSH connection to the target host. It’s operator-gated and idempotency-keyed, but it is not a free/cached call.
  • Scope your tokens. Use an environment-scoped, role-capped API token so an MCP session can only read/act on the environments you intend.

  • Transport: Streamable HTTP, stateless (POST /mcp only). GET/DELETE /mcp return 405 — there is no server-side session or SSE notification stream to resume.
  • Rate limiting: MCP requests — and every API sub-call a tool replays internally — are subject to the same global per-IP rate limit as the rest of the API. Each injected sub-call is attributed to the calling client’s real IP (the same IP its direct API calls would use), so one caller’s tool-call flood is throttled under that caller’s bucket and can’t starve others. There is intentionally no bypass.
  • DNS-rebinding / Origin protection: off by default, controlled by the explicit MCP_ALLOWED_HOSTS env var (a comma-separated list of the public Host header value(s) clients use, e.g. mcp.example.com). When set, the transport validates the request Host header against that allowlist; when unset/empty, host validation is left off. This is intentionally decoupled from HOST (the socket bind address): a public hostname behind a reverse proxy differs from the bind address, and the common HOST=0.0.0.0 would otherwise either reject every proxied client or silently disable protection. The endpoint is bearer-authenticated regardless; setting MCP_ALLOWED_HOSTS (plus TLS) is recommended when exposing MCP to remote clients.
  • TLS: terminate TLS at your reverse proxy, exactly as for the REST API. Bearer tokens must only travel over HTTPS in production.