Skip to content

Put a third-party MCP server behind the platform

mcp-server-adapter is a sidecar for an MCP server whose code you do not control, such as an open source or vendor server. It reads the tool list of that server, serves it to the gateway as a manifest and forwards each call with the credential of the user who made it. One adapter serves one third-party server and one system.

The adapter does not turn a REST API into tools. It has no configuration file that describes endpoints. If your system has only a REST API, write a server from the template, see Write a server from the template.

  • Only tools classified READ_ONLY work. A tool that changes data stays hidden, because the tools of a third-party server name no resource that a write could be scoped to. The portal accepts only risk READ_ONLY and action read for an adapter.
  • The third-party server gets the credential of the calling user, never a shared one. Keep it on a network that only the adapter shares with it.
  • Tools whose input schema cannot be bounded are skipped: open maps, unions of two types, recursion, lists without items, depth above 8. The log line event=foreign_tool_skipped gives the reason.
  • Every error of the third-party server reaches the model as not_found_or_denied, except credential_required (HTTP 401) and upstream_unavailable. An error that the server returns as ordinary text reaches the model unchanged, so check how it reports errors.
  • The tool list is read at start. After a change, restart the adapter. The gateway pulls the new manifest within a minute and changed tools wait for approval again.
  • There are no policies and no scope mapping for an adapter. Access is by the group mcp-<system>-read.

You need Docker Engine with Compose v2.20 or newer and 1 GiB of memory. The platform must reach this host on port 8443, and this host must reach https://<platform> on port 443.

  1. In deploy/mcp-adapter.env, fill in the four required values:

    Terminal window
    MCP_SERVER_ID=mcp-<system>
    MCP_GATEWAY_JWKS_URI=https://<platform>/.well-known/mcp-jwks.json
    MCP_CONNECT_URL=https://<platform>/connections?system={system}
    MCP_FOREIGN_URL=https://<third-party server>

    <system> is the id the system has in the broker: 2 to 31 lower case letters and digits, starting with a letter.

  2. If the third-party server is not on the default Docker network, add its network under mcp-adapter and under networks: in compose.yaml. No other server may reach it.

  3. Start the adapter:

    Terminal window
    docker compose up -d

    The adapter waits up to 90 seconds for the third-party server to answer tools/list and exits when it does not.

  4. Read the certificate fingerprint:

    Terminal window
    docker compose logs mcp-adapter | grep event=tls_certificate

    Expected: event=tls_certificate source=generated fingerprint=sha256:...

  5. Give the platform administrator the address https://<this host>:8443/mcp and the fingerprint. They add the system and the server, see Servers and Systems. For a product that takes the user’s own token, the system uses the broker adapter generic-rest, type user-secret, scheme Bearer.

The adapter generates its certificate on first start and keeps it in the state volume. A new volume means a new fingerprint and a new approval; docker compose down -v deletes it.

Variable Required Description
MCP_SERVER_ID yes mcp-<system>
MCP_GATEWAY_JWKS_URI yes Key set of the gateway
MCP_CONNECT_URL yes Portal page where a user connects an account
MCP_FOREIGN_URL yes Address of the third-party server, https
MCP_FOREIGN_PATH no MCP path of the server, default /mcp
MCP_FOREIGN_AUTHORIZATION_FORMAT no How the credential becomes a header. Default Token {credential}; {credential} must appear exactly once
MCP_FOREIGN_DISCOVERY_AUTHORIZATION no Header for the startup tools/list, for servers that refuse a request without one
MCP_ADAPTER_TOOLS_INCLUDE no Tools to publish, comma-separated. Empty means all that can be bounded
MCP_ADAPTER_STRIP_PREFIX no Prefix to remove from tool names

Own certificate, Redis for several instances and limits are in the same file and in src/main/resources/application.yml.

The repository’s example puts sooperset/mcp-atlassian 0.23.1 (MIT) in front of Jira with four read tools. It runs on a test stack: the third-party server speaks plain http, which the adapter accepts only with the devstack profile. deploy/docker-compose.demo.yml has all settings.

  1. Pull the image, pinned by digest:

    Terminal window
    docker pull ghcr.io/sooperset/mcp-atlassian@sha256:5b7c9b64d4eb3210cab74be8bf3e6aeea9ed14f3042dc59ba5c5287bd4dbe466
  2. Merge deploy/docker-compose.demo.yml into your test stack and set ADAPTER_IMAGE and MCP_GATEWAY_JWKS_URI. If the stack already runs a native mcp-jira, stop it: the adapter takes its server id.

  3. Keep these settings of the third-party server:

    ENABLED_TOOLS: jira_get_issue,jira_search,jira_get_all_projects,jira_get_transitions
    JIRA_PERSONAL_TOKEN: placeholder-never-used
    ALLOW_GLOBAL_CRED_FALLBACK: "false"

    ENABLED_TOOLS lists read tools only. The token is a filler: without it the server does not list the Jira tools. With the fallback off, a call without a credential ends with 401.

  4. Keep these settings of the adapter:

    MCP_FOREIGN_URL: http://mcp-atlassian:8000
    MCP_FOREIGN_ALLOW_PLAIN_HTTP: "true"
    MCP_FOREIGN_AUTHORIZATION_FORMAT: "Token {credential}"
    MCP_FOREIGN_DISCOVERY_AUTHORIZATION: "Token discovery-only"
    MCP_ADAPTER_STRIP_PREFIX: jira_
  5. Start the stack with docker compose up -d.

  6. In the portal, add the server http://mcp-jira.internal:8443/mcp with “allow plain http”, approve the connection and approve the four tools: get_issue, search, get_all_projects and get_transitions.

The third-party container is the least trusted one in the stack. The fragment runs it read-only, without capabilities and with memory limits, on two internal networks (one shared with the adapter, one with Jira).

Symptom Cause Fix
The adapter exits right after start The third-party server did not answer tools/list within 90 s, or it refuses requests without Authorization Check MCP_FOREIGN_URL and the network; in the second case set MCP_FOREIGN_DISCOVERY_AUTHORIZATION
Server status certificate_changed The state volume was recreated Approve the new fingerprint in the portal
A tool is missing from the list It is not approved, not READ_ONLY, or its schema cannot be bounded Check the log for event=foreign_tool_skipped
A tool changed after a new image of the third-party server The tool list is read at start Restart the adapter; the changed tools wait for approval again