A production-grade dynamic MCP server for Node.js that enables runtime tool creation, management, and execution in isolated Docker sandboxes.
Unlike static MCP servers that define tools at compile time, dynamic-mcp lets AI agents and operators create, update, and delete tools on the fly — each running in a hardened Docker container with full lifecycle management.
- Runtime tool management — Create, update, delete, enable/disable tools without restarts via the
dynamic.tool.*control plane - Docker sandbox isolation — Every tool execution runs in a locked-down container (read-only FS, dropped capabilities, PID/memory/CPU limits)
- Dual transport — Stdio for local/CLI use, Streamable HTTP for networked deployments with per-session MCP servers
- Dual registry backend — File-based (single node) or PostgreSQL (multi-instance) with optimistic concurrency control
- Execution guard — Global concurrency and per-scope rate limiting to prevent abuse
- JWT authentication — Optional JWKS-based token verification for HTTP mode
- Audit logging — Structured JSONL logs with rotation, redaction of sensitive fields, and shutdown flush
- Two profiles —
mvp(default) for core functionality,enterprisefor long-lived sandbox sessions, metrics, and ops tools - Production-ready — Health probes, Prometheus metrics, graceful shutdown, Kubernetes manifests, Docker Compose baselines
Prerequisites: Node.js >= 20, pnpm, Docker
# Install dependencies
pnpm install
# Run in stdio mode (default, mvp profile)
pnpm run dev
# Run in HTTP mode
pnpm run dev:http
# Run with enterprise profile
pnpm run dev:enterpriseHTTP mode default endpoint: http://127.0.0.1:8788/mcp
- Development / PoC:
mvpprofile +stdiotransport + file backend (.env.example) - Production:
enterpriseprofile +httptransport + JWT auth + PostgreSQL backend (.env.prod.example)
This project supports both MCP standard transports:
stdio(recommended for local development/CLI clients)- Streamable HTTP (recommended for remote/network deployment)
Most MCP clients launch your server as a child process in stdio mode, so build once before wiring client config:
pnpm install
pnpm buildUse an absolute path to dist/index.js in client config. Example:
node /ABS/PATH/TO/dynamic-mcp/dist/index.js --transport stdio --profile mvpDynamic code execution features (registered dynamic tools, run_js_ephemeral, sandbox.*) require a reachable Docker daemon.
Claude Desktop uses a local claude_desktop_config.json file with mcpServers.
macOS path:
~/Library/Application Support/Claude/claude_desktop_config.json
Windows path:
%APPDATA%\Claude\claude_desktop_config.json
Example:
{
"mcpServers": {
"dynamic-mcp": {
"command": "node",
"args": [
"/ABS/PATH/TO/dynamic-mcp/dist/index.js",
"--transport",
"stdio",
"--profile",
"mvp"
],
"env": {
"MCP_DYNAMIC_BACKEND": "file",
"MCP_DYNAMIC_STORE": "/ABS/PATH/TO/dynamic-mcp/.dynamic-mcp/tools.json",
"MCP_SANDBOX_DOCKER_BIN": "docker"
}
}
}
}Note: Claude Desktop remote MCP server management is done in app settings (Settings -> Connectors), not in claude_desktop_config.json.
Add local stdio server:
claude mcp add dynamic-mcp -- node /ABS/PATH/TO/dynamic-mcp/dist/index.js --transport stdio --profile mvpAdd remote HTTP server:
claude mcp add --transport http dynamic-mcp-http http://127.0.0.1:8788/mcpProject-level .mcp.json example (supports both local and remote server definitions):
{
"mcpServers": {
"dynamic-mcp-local": {
"command": "node",
"args": [
"/ABS/PATH/TO/dynamic-mcp/dist/index.js",
"--transport",
"stdio",
"--profile",
"enterprise"
]
},
"dynamic-mcp-http": {
"type": "http",
"url": "http://127.0.0.1:8788/mcp",
"authorization_token": "${DYNAMIC_MCP_JWT_TOKEN}"
}
}
}Claude Code supports environment variable expansion in config values, including ${VAR} and ${VAR:-default}.
Use workspace config file: .vscode/mcp.json.
Local stdio example:
{
"servers": {
"dynamic-mcp": {
"command": "node",
"args": [
"/ABS/PATH/TO/dynamic-mcp/dist/index.js",
"--transport",
"stdio",
"--profile",
"mvp"
],
"env": {
"MCP_DYNAMIC_BACKEND": "file",
"MCP_SANDBOX_DOCKER_BIN": "docker"
}
}
}
}Remote HTTP + JWT header example:
{
"servers": {
"dynamic-mcp-http": {
"url": "http://127.0.0.1:8788/mcp",
"headers": {
"Authorization": "Bearer ${input:dynamic_mcp_jwt}"
}
}
},
"inputs": [
{
"type": "promptString",
"id": "dynamic_mcp_jwt",
"description": "JWT Bearer token for dynamic-mcp"
}
]
}Server startup example:
pnpm run dev:http
# or:
node /ABS/PATH/TO/dynamic-mcp/dist/index.js --transport http --host 127.0.0.1 --port 8788 --path /mcpIn HTTP mode, the server runs as an independent process/container, and MCP clients connect to the configured URL.
HTTP endpoints:
POST /mcpinitialize/continue MCP sessionGET /mcpsession streamDELETE /mcpclose sessionGET /livezlivenessGET /readyzreadinessGET /metricsPrometheus metrics
JWT behavior in this repo:
- When
MCP_AUTH_MODE=jwt, authentication is enforced on MCP endpoint requests (${MCP_PATH}, default/mcp). /livez,/readyz,/metricsremain anonymous by default.
Production recommendation: keep /livez, /readyz, /metrics behind private networking, ingress allowlists, or a gateway even when JWT is enabled.
MCP_TRANSPORT=http
MCP_PROFILE=enterprise
MCP_HOST=0.0.0.0
MCP_PORT=8788
MCP_PATH=/mcp
MCP_DYNAMIC_BACKEND=postgres
MCP_AUTH_MODE=jwt
MCP_AUTH_JWKS_URL=https://your-idp.example.com/.well-known/jwks.json
MCP_AUTH_ISSUER=https://your-idp.example.com/
MCP_AUTH_AUDIENCE=dynamic-mcp
MCP_AUTH_REQUIRED_SCOPES=mcp.invokeFull variable reference: docs/configuration.md
Production baseline assets:
| Document | Description |
|---|---|
| Architecture | System design, module structure, and data flow |
| Configuration | All environment variables and CLI arguments |
| API Reference | Complete tool, resource, and prompt reference |
| Dynamic Tools Guide | How to author and manage dynamic tools |
| Security | Security model, sandbox isolation, and authentication |
| Deployment | Docker, Compose, and Kubernetes deployment guides |
| Production Runbook | Production rollout, verification, and rollback steps |
Core dynamic tool engine:
| Tool | Description |
|---|---|
dynamic.tool.create |
Register a new dynamic tool |
dynamic.tool.update |
Modify an existing tool definition |
dynamic.tool.delete |
Remove a tool |
dynamic.tool.list |
List all registered tools |
dynamic.tool.get |
Get a single tool definition |
dynamic.tool.enable |
Enable or disable a tool |
run_js_ephemeral |
One-off JavaScript execution in a sandbox |
system.health |
Server liveness and uptime |
Everything in MVP, plus:
| Tool / Resource | Description |
|---|---|
sandbox.initialize |
Create a reusable container session |
sandbox.exec |
Run shell commands in a session |
sandbox.run_js |
Run JavaScript in a session |
sandbox.stop |
Stop a session container |
sandbox.session.list |
List active sessions |
system.guard_metrics |
Concurrency/rate-limit counters |
system.runtime_config |
Sanitized config snapshot |
dynamic://metrics/guard |
Guard metrics resource |
dynamic://service/runtime-config |
Config snapshot resource |
dynamic://service/meta |
Service metadata resource |
tool-call-checklist |
Reusable pre-call checklist prompt |
{
"tool": {
"name": "text.uppercase",
"description": "Convert text to uppercase",
"code": "const { text } = args;\nreturn { upper: String(text).toUpperCase() };",
"dependencies": [],
"image": "node:lts-slim",
"timeoutMs": 10000
}
}Then invoke it:
{
"args": { "text": "hello world" }
}See the Dynamic Tools Guide for full details.
docker build -t dynamic-mcp:latest .
docker run --rm -p 8788:8788 dynamic-mcp:latestFor dynamic tool execution (registered dynamic tools, run_js_ephemeral, sandbox.*) in containerized deployments, the running dynamic-mcp process must have:
- A Docker CLI binary available in the container (
docker) - Connectivity and authorization to a Docker daemon (local socket or remote daemon)
Without that access, dynamic tool execution will fail at runtime.
Security note: exposing the host Docker socket gives the container high privilege over the host. Prefer a dedicated remote Docker daemon with network isolation and TLS for production.
docker compose -f deploy/docker-compose.postgres.yml up -d --buildkubectl apply -f deploy/k8s/dynamic-mcp-postgres.yaml
# Optional: HPA + PDB
kubectl apply -f deploy/k8s/dynamic-mcp-scalability.yaml
# Optional: Network policy
kubectl apply -f deploy/k8s/dynamic-mcp-networkpolicy.yamlpnpm run dev # stdio mode, mvp profile
pnpm run dev:mvp # explicit mvp profile
pnpm run dev:http # HTTP mode
pnpm run dev:enterprise # enterprise profile
pnpm run test # run tests
pnpm run lint # lint
pnpm run typecheck # type check
pnpm run build # compile TypeScriptMIT