Use this guide to deploy and manage ToolPlane with Docker and Docker Compose.
Make sure that you have Docker and Docker Compose installed.
# Clone the repository
git clone https://github.com/nmdra/ERPBridge.git
cd ERPBridge
# Start the local development stack safely
make dev-up
# Direct Compose use requires MOCK_ERP_CREDENTIALS_JSON or
# MOCK_ERP_CREDENTIALS_FILE to be set first.
docker compose up --build --force-recreate -dThe stack includes:
- ToolPlane Server (
:8080): The core MCP middleware. This development bootstrap enables the twosystem.*_testdemonstration tools. Direct Compose use defaults to false. Production deployments must leaveMCP_ENABLE_TEST_TOOLSunset or false. - Mock ERP (
:8081): Simulates legacy ERP endpoints. - Redis (
:6379): Provides the exact-match cache.
RedisInsight binds to 127.0.0.1 by default. Keep this setting unless you
explicitly need local-network access. Never expose RedisInsight publicly.
For a disposable end-to-end onboarding check, run:
./scripts/test-onboarding.shThe check uses an isolated Compose project, temporary CLI home, dynamic loopback ports, synthetic fixture data, and ephemeral credentials. On exit, it removes its containers and temporary files. It does not use caller credentials or print ERP response bodies.
Set the server environment variables in docker-compose.yml.
| Variable | Description | Default (compose) |
|---|---|---|
BASE_URL |
Public URL of the MCP server. | http://localhost:8080 |
ERPBRIDGE_HOST_PORT |
Host port for the ToolPlane container. | 8080 |
ERP_BASE_URL |
Base URL of the underlying ERP system. | http://mock-erp:8081 |
MOCK_ERP_HOST_PORT |
Host port for the MockERP container. | 8081 |
MOCK_ERP_IMAGE |
MockERP image used by Compose. | ghcr.io/nmdra/mockerp:0.2.1 |
MOCK_ERP_VERSION |
MockERP release used for OpenAPI generation. | 0.2.1 |
MOCK_ERP_OPENAPI_URL |
Versioned MockERP OpenAPI URL. | https://raw.githubusercontent.com/nmdra/mockerp/v0.2.1/openapi.yaml |
MOCK_ERP_DB_PATH |
SQLite database path inside MockERP. | /data/mockerp.db |
MOCK_ERP_CREDENTIALS_JSON |
JSON credential configuration for local/container use. | (required) |
MOCK_ERP_CREDENTIALS_FILE |
Mounted JSON credential file path. | (required alternative) |
REDIS_URL |
URL for the Redis cache. | redis://redis:6379 |
REDIS_HOST_PORT |
Host port for Redis. | 6379 |
REDIS_INSIGHT_BIND_ADDRESS |
Address bound by RedisInsight. | 127.0.0.1 |
REDIS_INSIGHT_HOST_PORT |
Host port for RedisInsight. | 8001 |
DATABASE_PATH |
Path of the SQLite tool registry inside the container. | /app/data/erpbridge.db |
RATE_LIMIT_RPS |
Tool-execution requests per second, keyed by token principal, MCP session, or process fallback. | 10 |
RATE_LIMIT_BURST |
Token bucket burst size. | 20 |
INSECURE_AUTH_ALLOWED_HOSTS |
Development-only exact host:port exceptions for credentialed HTTP calls. |
mock-erp:8081 |
MCP_ENABLE_TEST_TOOLS |
Development-only registration of system.*_test demo tools. |
true with make dev-up; false with direct Compose |
MCP_ADVERTISE_REVISION_ALIASES |
Lists exact-revision names in MCP discovery. The default hides them so clients see one entry per tool. | unset |
API_AUTH_TOKEN |
Bearer token for protected admin, MCP, and direct-invoke routes. | unset |
ERPBRIDGE_CREDENTIALS_DIR |
Optional mounted directory used only by resources with credentialSource: file. |
unset |
PLUGIN_ENDPOINT_ALLOWLIST |
Exact host:port values allowed for credentialed plugin resources. |
unset |
PLUGIN_MOCK_API_KEY |
Environment-backed API key reference used by the plugin integration fixture. | unset |
MOCK_PLUGIN_API_KEY |
API key accepted by the separately deployed mock plugin fixture. | unset |
For the full list of server environment variables, see the Environment Variables Reference.
MockERP fails closed when neither MOCK_ERP_CREDENTIALS_JSON nor
MOCK_ERP_CREDENTIALS_FILE is configured. make dev-up generates an ephemeral
JSON credential pair only when neither source is set. It makes sure that the rendered
Compose configuration is valid. It then force recreates the stack and polls both
service health endpoints. It does not write or print the generated values. Use the JSON
variable for local development, or mount a Docker secret and set the file path.
Do not commit credential values to this repository.
The bootstrap does not source .env; Compose reads that file and preserves its
quoted values. Direct docker compose commands do not run the bootstrap
preflight. Set a credential source first. Then run docker compose config --quiet. Run docker compose up --build --force-recreate -d. RedisInsight binds
to 127.0.0.1 by default. Change REDIS_INSIGHT_BIND_ADDRESS only when you
explicitly need another interface.
Environment variables remain the default credential source. For a credential
that must rotate without recreating the ToolPlane container, mount a directory
and set ERPBRIDGE_CREDENTIALS_DIR in the server container. Add
credentialSource: file beside the logical credentialRef for only the tools,
APIs, resources, or plugins that use that mount:
services:
erpbridge-server:
volumes:
- ./operator-secrets:/run/secrets/erpbridge:ro
environment:
ERPBRIDGE_CREDENTIALS_DIR: /run/secrets/erpbridgeThe mounted file name is the validated credentialRef. ToolPlane reads it on
each request. It accepts complete projected-file generations. Missing, empty,
control-character, invalid UTF-8, non-regular, or oversized (over 64 KiB)
content causes the request to fail closed. It does not retain the previous value or fall back to an
environment value. For a writable local directory, replace files by writing a
complete temporary file and atomically renaming it over the live name.
For Kubernetes, use a normal directory volume rather than subPath. Projected
Secret updates do not update subPath mounts. AWS EKS ASCP, AKS Key Vault CSI,
and GKE Secret Manager CSI can populate this directory. Use workload identity.
Apply least privilege. Configure provider rotation. Provider updates are eventual
and replicas can observe them at different times. Adding a new cloud secret
may require changing the provider's workload mapping and rolling the workload
before the new filename is mounted. ToolPlane does not access AWS, Azure, or
Google Cloud APIs directly. Environment-variable changes still require a
container recreation.
Credentialed ERP and plugin endpoints must use HTTPS. The bundled MockERP
fixture is HTTP-only, so Compose explicitly sets
INSECURE_AUTH_ALLOWED_HOSTS=mock-erp:8081. The opt-in plugin integration
fixture adds mock-plugin:8080 to this exact host-and-port list and sets
PLUGIN_ENDPOINT_ALLOWLIST=mock-plugin:8080. These exceptions are for local
development only. Do not set a broad or production allowlist.
The server stores tool definitions in a SQLite database (DATABASE_PATH). The schemas/ directory is not mounted into the container. Git does not track schemas/. The server has no file-system watcher.
To load tools into the registry, generate and apply the manifests from your host:
-
For manual generation, register the ERP API. If you use the default generation task in the next step, skip this registration command:
./bridgectl api register --name erp --url http://localhost:8081 --module erp --description "Mock ERP" -
If
erpis not already registered in the selected context, fetch the pinned OpenAPI contract and generate and apply the tool schemas. This task registerserpitself. Iferpalready exists, use the manual generation workflow in the Onboarding Guide instead:make generate-tools
Within 10 seconds, the server detects new registry entries and exposes them over MCP. You do not need to restart it.
Use the local bridgectl binary to manage the server running in Docker.
-
Build bridgectl:
go build -o bridgectl tools/bridgectl/main.go
-
Verify Connection:
./bridgectl tool get
-
Generate a new tool:
MOCK_ERP_VERSION=0.2.1 make generate-tools
The target fetches the matching tagged OpenAPI contract. It applies one temporary YAML stream and removes the temporary files on exit. It does not write generated files to
schemas/. -
Test an API through the server:
./bridgectl api test erpThis is the default mode. ToolPlane resolves the API
credentialReffrom its environment and returns only a status summary. Use./bridgectl api test erp --localonly for a legacy host-side diagnostic.CLI control-plane requests use the configured MCP host root. The exact
/mcpor/mcp/suffix is removed automatically. Other non-empty paths fail withCONTROL_PLANE_URL_INVALID./mcp/remains the MCP client transport endpoint and must not be used as a REST path.
-
View Container Logs:
docker compose logs -f erpbridge-server
-
Live Stream Logs via CLI:
./bridgectl log tail
The REST /api/logs/stream endpoint flushes authenticated SSE headers before
its first event and sends data: <JSON>\n\n frames. Closing the client stops
its subscription. Each subscriber has a bounded 100-event channel. If a slow subscriber's channel
is full, the server drops new events. This raw stream is not
the redacted Console BFF projection.
- Metrics:
Prometheus metrics are available at
http://localhost:8080/metrics.
When the container receives SIGTERM or SIGINT, ToolPlane stops accepting new
HTTP connections. It shuts down the listener before the process exits.
ToolPlane does not install, start, update, or schedule plugin code. Deploy each
plugin process separately, then apply a Plugin resource with its reachable
endpoint and an exact-version PluginBinding. See the External Plugin
Resource Schema for the manifest and /v1/process JSON
contract.
For a deployment that uses a published plugin image, pin the image tag in the operator-owned deployment configuration:
services:
response-transformer:
image: ghcr.io/nmdra/erpbridge-plugins/mock-plugin:0.1.0The image is not part of the ToolPlane server image. An after-response plugin receives a normalized result and binding configuration. A raw-response plugin can process ERP response bytes before normalization. Plugins do not receive ERP credentials, inbound request headers, or caller identity. A credentialed plugin resource stores an environment-variable reference, not the key value:
metadata:
type: docker
spec:
endpoint: http://mock-plugin:8080
auth:
type: api-key
credentialRef: PLUGIN_MOCK_API_KEY
header: X-API-KeyThe server resolves the reference at invocation time and sends one API-key
header. The control plane must have API_AUTH_TOKEN enabled, and the endpoint
must match PLUGIN_ENDPOINT_ALLOWLIST exactly.
The repository includes an opt-in black-box test. It builds the deterministic
fixture from the sibling ../ERPBridge-Plugins polyrepo, starts it with the
pinned MockERP image, and removes only its isolated containers and volumes:
make test-plugin-integrationThe test uses the Compose project name erpbridge-plugin-test and ports
18080, 18081, 18090, and 16379 so it does not reuse the normal local
stack. It generates separate admin, ERP, and plugin credentials at runtime,
passes them only to the services that need them, and checks missing, wrong, and
correct plugin API keys at /v1/process. The plugin /health endpoint remains
unprotected for readiness checks. Do not run it when those ports are already
in use.
ToolPlane supports the Stdio and Streamable HTTP transports.
Claude Desktop connects to MCP servers via standard input and output. The server binary supports the --stdio flag.
-
Locate Configuration:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
- macOS:
-
Add ToolPlane Server: Add the following to the
mcpServerssection. This runs the server in stdio mode inside a container.{ "mcpServers": { "erpbridge": { "command": "docker", "args": [ "run", "-i", "--rm", "ghcr.io/nmdra/erpbridge-server:latest", "--stdio" ] } } }Note: The tool registry uses the SQLite database. In this disposable container, tools persist only while the container runs. To keep tools across restarts, run the full
docker composestack. Then connect via Streamable HTTP. -
Restart Claude: Fully quit and restart Claude Desktop. Look for the tool icon in the chat input.
Cursor connects to remote MCP servers via HTTP. Use this method when the ToolPlane stack is already running via docker compose up.
-
Make Sure the Server Is Running: Make sure that the stack is running and the server is reachable at
http://localhost:8080. -
Configure Cursor:
- Open Cursor Settings (
Cmd+,orCtrl+,). - Navigate to Features > MCP.
- Click + Add New MCP Server.
- Name:
ERPBridge - Type:
streamable-http(orhttp, depending on your Cursor version) - URL:
http://localhost:8080/mcp/
- Open Cursor Settings (
-
Verify: You see a green status indicator. You can now use the ERP tools in Cursor Chat or Composer.
-
Connection Refused: Make sure that
ERP_BASE_URLindocker-compose.ymluses the service namehttp://mock-erp:8081instead oflocalhost. Compose pullsghcr.io/nmdra/mockerp:0.2.1; overrideMOCK_ERP_IMAGEonly with a compatible image. -
Claude Stdio Timeout: If Claude fails to connect, build the server binary first and run it directly. This shows any startup errors.
-
Schema Errors: Before you apply the tool definition, make sure that it passes local validation:
./bridgectl tool validate -f schemas/erp/list_employees.json