MCP Server¶
This page describes how to run the OctoPerf MCP server alongside an OctoPerf Enterprise installation. For an overview of what the MCP server is and how AI agents connect to it, see the MCP Server overview.
Architecture¶
The MCP server is a separate, stateless Spring Boot service shipped as the octoperf/mcp-server Docker image. It does not own any data: every tool call resolves to a REST call against the regular OctoPerf backend, carrying the same OAuth JWT the agent received during the OAuth flow.
+-----------------+ +-------------------+ +---------------------+
| AI agent | --1--> | octoperf-mcp | --3--> | OctoPerf backend |
| (Claude.ai, | <-2--- | (JWT validation, | <-4--- | (enterprise-edition |
| Claude Code...)| | stateless) | | + nginx) |
+-----------------+ +-------------------+ +---------------------+
^
| 5. JWT signed / discovered
v
+-------------------+
| OctoPerf IdP |
| (bundled in |
| enterprise- |
| edition) |
+-------------------+
- The agent hits
/mcpwith a JWT inAuthorization: Bearer ..., - The MCP server validates signature / issuer / audience / expiry against the IdP (OctoPerf backend), then forwards the call,
- OctoPerf backend resolves the
subclaim to the same user the SPA login lands on, - Response goes back through the MCP server (no body mutation),
- JWTs are signed by the IdP, which is bundled in the OctoPerf backend (no separate process); see IdP configuration for how to enable and persist signing keys.
Prerequisites¶
Before deploying the MCP server, make sure the bundled OAuth Identity Provider on the OctoPerf backend advertises itself at its public origin. The IdP issuer and the audiences it stamps on JWTs are derived from the backend's server.scheme / server.hostname / server.public.port — the public host clients reach through the reverse proxy, not the container bind address. The full reference (including persisting the JWK signing key across restarts and graceful key rotation) lives in Advanced Configuration → MCP Server / OAuth Identity Provider.
Configuration¶
The MCP server reads its settings from application.yml, mounted into the container at /home/octoperf/config/application.yml. The pre-built Enterprise zips ship with a working config/mcp/application.yml you can edit in place.
The MCP server derives the JWT issuer (the backend URL it discovers JWKS from), the accepted audiences, and its own server-to-server REST calls from a single public origin — the same one the backend advertises. Point both services at the same origin and front them with one reverse proxy (/ → backend, /mcp → MCP):
server:
# Public origin shared with the OctoPerf backend — the host clients
# reach through the reverse proxy, NOT the container bind address.
# Must match the backend's server.scheme / hostname / public.port.
scheme: https
hostname: YOUR-OCTOPERF-HOST
public:
port: 443
port: 8091 # internal container listen port (proxied by the /mcp location)
| Property | Required | Default | Description |
|---|---|---|---|
server.hostname |
✅ | local IP | Public host clients reach through the proxy. Must equal the backend's server.hostname. |
server.scheme |
http |
Public scheme (http / https). Must equal the backend's server.scheme. |
|
server.public.port |
server.port |
Public port advertised to clients. Omitted from URLs when it is the scheme default (80 / 443). Must equal the backend's server.public.port. |
|
server.port |
8091 |
Internal container HTTP listen port (the port the /mcp reverse-proxy location forwards to). |
|
server.mcp-context-path |
/mcp |
Path the MCP API is exposed at on the public origin. |
Warning
The issuer and accepted audiences are derived from server.scheme / server.hostname / server.public.port and validated against every inbound JWT. They must resolve to the same public origin the backend mints tokens with, or every call returns 401.
Warning
The public origin must be reachable from outside: the same host is embedded in presigned URLs handed to Claude.ai's sandbox / the user's browser, and the MCP server routes its own server-to-server calls back through it. Pointing it at an internal Docker name (http://enterprise-edition:8090, http://nginx) breaks both.
The JVM also reads two environment variables: JAVA_OPTS (default -Xmx512m) and OCTOPERF_MCP_FILE_SOURCE_MAX_BYTES (default 1073741824 — 1 GiB hard cap on any file resolved by import_*_virtual_user tools).
Docker compose¶
The standard Enterprise zips (HTTP, SSL, offline, k8s) bundle the MCP server out of the box from version 16.2.0 onwards. A minimal mcp service entry looks like this:
services:
mcp:
image: docker.io/octoperf/mcp-server:16.2.0
container_name: octoperf-mcp
restart: unless-stopped
depends_on:
- enterprise-edition
volumes:
- ./config/mcp:/home/octoperf/config
And on the nginx reverse-proxy, a single location /mcp block exposes the MCP API alongside the backend:
upstream mcp-server {
server mcp:8091;
}
# MCP API — Streamable HTTP transport keeps the connection open
# for SSE responses; disable buffering, extend the read timeout.
location /mcp {
proxy_pass http://mcp-server;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_buffering off;
proxy_read_timeout 86400s;
}
Public download routes¶
The MCP server exposes a small, unauthenticated route family under /mcp/public/** so any agent host can bootstrap a project before it has IdP credentials. These are the only paths on the server that don't require a JWT — see MCP Server overview → Public HTTP downloads for the full list.
Unknown paths under /mcp/public/** return 404; every other path is gated by the OAuth filter chain. If your reverse-proxy enforces additional authentication, make sure to whitelist /mcp/public/** or your users won't be able to bootstrap.
Verifying the deployment¶
After starting the MCP server:
- Hit
https://YOUR-MCP-HOST/mcp/public/AGENTS.md- should return the markdown file. If this fails, your reverse-proxy is probably stripping/mcp/public/. - Connect with the MCP Inspector for an interactive sanity check.
- Make sure your UI exposes the Connected Apps page by setting
connectedApps: truein the UI configuration; your users won't be able to review or revoke connectors otherwise.
Telling your Claude.ai users about the sandbox domain allowlist¶
Your users connecting from Claude.ai web (or Desktop) hit the same sandbox egress constraint as SaaS users: file upload / download tools return presigned URLs on https://YOUR-MCP-HOST that Claude's sandbox can only fetch once a workspace admin has added the host to Claude.ai → Organization Settings → Capabilities → Domain allowlist. There is no operator-side way to pre-authorize the host on your users' behalf — see MCP Server overview → Claude.ai web file uploads for the full step-by-step and screenshot.
Practical recommendation:
- Surface the requirement in your own onboarding - list
YOUR-MCP-HOST(or*.YOUR-DOMAIN) and the exact settings path alongside the connector URL in the doc you give your users, and flag that it needs the Claude.ai workspace admin. - Steer file-heavy users to Claude Code if the allowlist is out of reach (personal Claude.ai accounts, locked-down orgs). Claude Code fetches presigned URLs through its local shell, no sandbox allowlist applies, uploads / downloads work out of the box. Cursor, Continue.dev, and the MCP Inspector behave the same way.
The upstream MCP spec is tracking a server-declared trusted-hosts mechanism that would remove this manual step (modelcontextprotocol#1541); until Claude.ai honours it, the org allowlist is the only knob.
Related topics¶
- MCP Server overview: what the server is and why use it
- Connect a client: point an agent at your self-hosted MCP server
- IdP configuration: enable the bundled OAuth IdP, persist the JWK signing key, rotate keys gracefully
- UI configuration: expose the Connected Apps page to your users
- OAuth Clients (admin): audit registered OAuth clients on your instance