dynamic-mcp
by mcpland
README.md
# dynamic-mcp

[](https://www.npmjs.com/package/dynamic-mcp)

A production-grade **dynamic MCP server** for Node.js that enables runtime tool creation, management, and execution in isolated execution sandboxes (`docker` or `node`).
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 with full lifecycle management.
## Key Features
- **Runtime tool management** — Create, update, delete, enable/disable tools without restarts via the `dynamic.tool.*` control plane
- **Execution backend selection** — `auto` (Docker preferred, Node fallback), or force `docker` / `node`
- **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
- **Experimental upstream attach** — Optional feature-flagged `upstream.mcp.attach` for lazy discovery of existing MCP servers
- **Two profiles** — `mvp` (default) for core functionality, `enterprise` for long-lived sandbox sessions, metrics, and ops tools
- **Production-ready** — Health probes, Prometheus metrics, graceful shutdown, Kubernetes manifests, Docker Compose baselines
## Quick Start
**Prerequisites:** Node.js >= 20 (Docker recommended)
Fastest way to run `stdio` (no clone/build):
```bash
npx -y dynamic-mcp --transport stdio --profile mvp
# optional: pin version for reproducibility
npx -y dynamic-mcp@<version> --transport stdio --profile mvp
```
From source (local development):
```bash
# 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:enterprise
```
HTTP mode default endpoint: `http://127.0.0.1:8788/mcp`
## Recommended Operating Modes
- Development / PoC: `mvp` profile + `stdio` transport + file backend (`.env.example`)
- Production: `enterprise` profile + `http` transport + JWT auth + PostgreSQL backend (`.env.prod.example`)
## MCP Server Configuration
This project supports both MCP standard transports:
- `stdio` (recommended for local development/CLI clients)
- Streamable HTTP (recommended for remote/network deployment)
### 1. Stdio Runtime Options
Most MCP clients launch your server as a child process in `stdio` mode.
Option A (recommended for quick setup): run from npm with `npx`:
```bash
npx -y dynamic-mcp --transport stdio --profile mvp
```
Option B (recommended when developing this repo): build local runtime first:
```bash
pnpm install
pnpm build
```
Then use an absolute path to `dist/index.js` in client config. Example:
```bash
node /ABS/PATH/TO/dynamic-mcp/dist/index.js --transport stdio --profile mvp
```
Dynamic code execution features use the selected execution backend (`docker` or `node`).
Note: `sandbox.*` tools remain Docker-based; in environments without Docker, use `mvp` profile or avoid `sandbox.*`.
Execution backend can be controlled with `MCP_EXECUTION_ENGINE` / `--execution-engine`:
- `auto` (default): use Docker when available, fallback to Node sandbox when Docker is unavailable
- `docker`: force Docker
- `node`: force Node sandbox (no dynamic dependency installation)
If Docker is not installed and you want the MCP server to default to Node immediately, set `MCP_EXECUTION_ENGINE=node` in the client config or append `--execution-engine node` to the launch command:
```bash
npx -y dynamic-mcp --transport stdio --profile mvp --execution-engine node
```
### 2. Claude Desktop (Local stdio)
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:
```json
{
"mcpServers": {
"dynamic-mcp": {
"command": "npx",
"args": [
"-y",
"dynamic-mcp",
"--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"
}
}
}
}
```
If Docker is not installed, configure the server to use Node explicitly:
```json
{
"mcpServers": {
"dynamic-mcp": {
"command": "npx",
"args": [
"-y",
"dynamic-mcp",
"--transport",
"stdio",
"--profile",
"mvp"
],
"env": {
"MCP_DYNAMIC_BACKEND": "file",
"MCP_DYNAMIC_STORE": "/ABS/PATH/TO/dynamic-mcp/.dynamic-mcp/tools.json",
"MCP_EXECUTION_ENGINE": "node"
}
}
}
}
```
Note: Claude Desktop remote MCP server management is done in app settings (`Settings -> Connectors`), not in `claude_desktop_config.json`.
### 3. Claude Code
Add local stdio server:
```bash
claude mcp add dynamic-mcp -- npx -y dynamic-mcp --transport stdio --profile mvp
```
If Docker is not installed:
```bash
claude mcp add dynamic-mcp -- npx -y dynamic-mcp --transport stdio --profile mvp --execution-engine node
```
Add remote HTTP server:
```bash
claude mcp add --transport http dynamic-mcp-http http://127.0.0.1:8788/mcp
```
Project-level `.mcp.json` example (supports both local and remote server definitions):
```json
{
"mcpServers": {
"dynamic-mcp-local": {
"command": "npx",
"args": [
"-y",
"dynamic-mcp",
"--transport",
"stdio",
"--profile",
"enterprise"
]
},
"dynamic-mcp-http": {
"type": "http",
"url": "http://127.0.0.1:8788/mcp",
"authorization_token": "${DYNAMIC_MCP_JWT_TOKEN}"
}
}
}
```
If Docker is not installed, add `"env": { "MCP_EXECUTION_ENGINE": "node" }` to the local server entry or append `"--execution-engine", "node"` to its `args`.
Claude Code supports environment variable expansion in config values, including `${VAR}` and `${VAR:-default}`.
### 4. VS Code
Use workspace config file: `.vscode/mcp.json`.
Local stdio example:
```json
{
"servers": {
"dynamic-mcp": {
"command": "npx",
"args": [
"-y",
"dynamic-mcp",
"--transport",
"stdio",
"--profile",
"mvp"
],
"env": {
"MCP_DYNAMIC_BACKEND": "file",
"MCP_SANDBOX_DOCKER_BIN": "docker"
}
}
}
}
```
If Docker is not installed, set the execution engine to Node:
```json
{
"servers": {
"dynamic-mcp": {
"command": "npx",
"args": [
"-y",
"dynamic-mcp",
"--transport",
"stdio",
"--profile",
"mvp"
],
"env": {
"MCP_DYNAMIC_BACKEND": "file",
"MCP_EXECUTION_ENGINE": "node"
}
}
}
}
```
Remote HTTP + JWT header example:
```json
{
"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"
}
]
}
```
### 5. HTTP Mode Details for This Repo
Server startup example:
```bash
pnpm run dev:http
# or:
node /ABS/PATH/TO/dynamic-mcp/dist/index.js --transport http --host 127.0.0.1 --port 8788 --path /mcp
# or:
npx -y dynamic-mcp --transport http --host 127.0.0.1 --port 8788 --path /mcp
```
In HTTP mode, the server runs as an independent process/container, and MCP clients connect to the configured URL.
HTTP endpoints:
- `POST /mcp` initialize/continue MCP session
- `GET /mcp` session stream
- `DELETE /mcp` close session
- `GET /livez` liveness
- `GET /readyz` readiness
- `GET /metrics` Prometheus metrics
JWT behavior in this repo:
- When `MCP_AUTH_MODE=jwt`, authentication is enforced on MCP endpoint requests (`${MCP_PATH}`, default `/mcp`).
- `/livez`, `/readyz`, `/metrics` remain anonymous by default.
Production recommendation: keep `/livez`, `/readyz`, `/metrics` behind private networking, ingress allowlists, or a gateway even when JWT is enabled.
### 6. Minimal Secure Baseline (Recommended)
```bash
MCP_TRANSPORT=http
MCP_PROFILE=enterprise
MCP_HOST=0.0.0.0
MCP_PORT=8788
MCP_PATH=/mcp
MCP_EXECUTION_ENGINE=auto
MCP_DYNAMIC_BACKEND=postgres
MCP_REQUIRE_ADMIN_TOKEN=true
MCP_ADMIN_TOKEN=change-me
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.invoke
# Optional experimental feature (enterprise only)
MCP_EXPERIMENTAL_UPSTREAM_MCP_ATTACH=false
MCP_EXPERIMENTAL_UPSTREAM_MCP_ATTACH_MAX=8
```
Full variable reference: [docs/configuration.md](docs/configuration.md)
Production baseline assets:
- [`.env.prod.example`](.env.prod.example)
- [`docs/production-runbook.md`](docs/production-runbook.md)
## Documentation
| Document | Description |
| ------------------------------------------------ | ----------------------------------------------------- |
| [Architecture](docs/architecture.md) | System design, module structure, and data flow |
| [Configuration](docs/configuration.md) | All environment variables and CLI arguments |
| [API Reference](docs/api-reference.md) | Complete tool, resource, and prompt reference |
| [Dynamic Tools Guide](docs/dynamic-tools.md) | How to author and manage dynamic tools |
| [Security](docs/security.md) | Security model, sandbox isolation, and authentication |
| [Deployment](docs/deployment.md) | Docker, Compose, and Kubernetes deployment guides |
| [Production Runbook](docs/production-runbook.md) | Production rollout, verification, and rollback steps |
## Profiles
### MVP (default)
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 |
### Enterprise
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 |
| `upstream.mcp.attach` | Experimental upstream MCP attach* |
| `upstream.mcp.detach` | Experimental upstream MCP detach* |
\* Registered only when `MCP_EXPERIMENTAL_UPSTREAM_MCP_ATTACH=true`.
## Experimental Upstream MCP Attach
Enable with feature flag (enterprise profile only):
```bash
MCP_PROFILE=enterprise
MCP_EXPERIMENTAL_UPSTREAM_MCP_ATTACH=true
MCP_EXPERIMENTAL_UPSTREAM_MCP_ATTACH_MAX=8
MCP_ADMIN_TOKEN=change-me
```
This registers `upstream.mcp.attach` and `upstream.mcp.detach`. `attach` can connect to an existing MCP server (`stdio` or `http`) and return its current `listTools` output.
Current scope is intentionally narrow:
- Supported: attach + tool discovery
- Supported: attach + detach + tool discovery
- Not yet supported: runtime mount/unmount/proxy of upstream tools into dynamic-mcp tool namespace
Security boundary:
- `transport=stdio` can spawn local processes; treat it as privileged
- `MCP_ADMIN_TOKEN` is required when this feature flag is enabled
- Pair with `MCP_REQUIRE_ADMIN_TOKEN=true` and `MCP_ADMIN_TOKEN=...`
## Example: Creating a Dynamic Tool
```json
{
"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:
```json
{
"args": { "text": "hello world" }
}
```
See the [Dynamic Tools Guide](docs/dynamic-tools.md) for full details.
## Docker
```bash
docker build -t dynamic-mcp:latest .
docker run --rm -p 8788:8788 dynamic-mcp:latest
```
When `MCP_EXECUTION_ENGINE=auto` (default), dynamic tool execution (`dynamic.*`, `run_js_ephemeral`) uses Docker when available and falls back to Node sandbox when Docker is unavailable.
`sandbox.*` tools remain Docker-based. For those tools 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 Docker access, `sandbox.*` calls 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 (with PostgreSQL)
```bash
docker compose -f deploy/docker-compose.postgres.yml up -d --build
```
## Kubernetes
```bash
kubectl 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.yaml
```
## Development
```bash
pnpm 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 TypeScript
```
## License
MIT
TDQS
B3.2/5.0
Scored across 8 tools
Disambiguation5/5
Each tool has a clearly distinct purpose: CRUD operations for dynamic tools, one-off code execution, and system health. No functional overlap.
Naming Consistency4/5
Six tools follow a consistent 'dynamic.tool.<action>' pattern, but 'run_js_ephemeral' uses snake_case and 'system.health' uses a different namespace, creating minor inconsistency.
Tool Count5/5
8 tools is well-scoped for managing dynamic tools and executing ephemeral code, with no unnecessary bloat or deficiency.
Completeness4/5
CRUD operations and enable/disable cover the full lifecycle of dynamic tools. The ephemeral runner adds useful functionality. Missing a validation or test tool, but core needs are met.
Maintenance
ActivityInactive
ResponsivenessNo issues