dynamic-mcp 1.5.0

MCP proxy server that reduces LLM context overhead with on-demand tool loading from multiple upstream servers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# dynamic-mcp

MCP proxy server that reduces LLM context overhead by grouping tools from multiple upstream MCP servers and loading tool schemas on-demand.

Instead of requiring you to expose all MCP servers upfront (which can consume thousands of tokens), dynamic-mcp exposes only two MCP tools initially.

It supports tools, resources, and prompts from upstream MCP servers with stdio, HTTP, and SSE transports, handles OAuth, and automatically retries failed connections.

## Quick Start

### Installation

### Option 1: Python package

Use `uvx` to run the [PyPI package](https://pypi.org/project/dmcp/) in your agent's MCP settings:

```json
{
  "mcpServers": {
    "dynamic-mcp": {
      "command": "uvx",
      "args": ["dmcp", "/path/to/your/dynamic-mcp.json"]
    }
  }
}
```

You can set the `DYNAMIC_MCP_CONFIG` environment variable and omit the config path.

### Option 2: Native binary

Download a [release](https://github.com/asyrjasalo/dynamic-mcp/releases) for
your operating system and put `dmcp` in your `PATH`:

```json
{
  "mcpServers": {
    "dynamic-mcp": {
      "command": "dmcp"
    }
  }
}
```

Set the `DYNAMIC_MCP_CONFIG` environment variable and omit the `args` altogether.

### Option 3: Compile from source

Install from [crates.io](https://crates.io/crates/dynamic-mcp):

```text
cargo install dynamic-mcp
```

The binary is then available at `~/.cargo/bin/dmcp` (`$CARGO_HOME/bin/dmcp`).

### Import from AI Coding Tools

Dynamic-mcp can automatically import MCP server configurations from popular AI coding tools.

**Supported Tools** (`<tool-name>`):

- Cursor (`cursor`)
- OpenCode (`opencode`)
- Claude Desktop (`claude-desktop`)
- Claude Code CLI (`claude`)
- Visual Studio Code (`vscode`)
- Cline (`cline`)
- KiloCode (`kilocode`)
- Codex CLI (`codex`)
- Gemini CLI (`gemini`)
- Google Antigravity (`antigravity`)

#### Quick Start

**Import from project config** (run in project directory):

```bash
dmcp import <tool-name>
```

**Import from global/user config**:

```bash
dmcp import --global <tool-name>
```

**Force overwrite** (skip confirmation prompt):

```bash
dmcp import <tool-name> --force
```

The command will:

1. Detect your tool's config location
2. Parse the existing MCP servers
3. Interactively prompt for descriptions
4. Interactively prompt for feature selection (tools, resources, prompts)
5. Normalize environment variable formats
6. Generate `dynamic-mcp.json`

#### Example Import

```bash
$ dmcp import cursor

🔄 Starting import from cursor to dynamic-mcp format
📖 Reading config from: .cursor/mcp.json

✅ Found 2 MCP server(s) to import

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Server: filesystem
Type: stdio

Config details:
  command: "npx"
  args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]

💬 Enter description for 'filesystem' (what this server does): File operations on /tmp directory

🔧 Keep all features (tools, resources, prompts) for 'filesystem'? [Y/n]:
(press Enter to keep all features, or 'n' to customize)

[... prompts for other servers ...]

✅ Import complete!
📝 Output saved to: dynamic-mcp.json
```

**Feature Selection**: During import, you can customize which MCP features are enabled per server:

- Press Enter (or Y) to keep all features (tools, resources, prompts)
- Type 'n' to selectively enable/disable individual features
- This allows fine-grained control without manually editing the config file

Example of custom feature selection:

```bash
🔧 Keep all features (tools, resources, prompts) for 'server'? [Y/n]: n

  Select features to enable (press Enter to accept default):
  Enable tools? [Y/n]: y
  Enable resources? [Y/n]: n
  Enable prompts? [Y/n]: n
```

#### Tool-Specific Notes

- **Cursor**: Supports both `.cursor/mcp.json` (project) and `~/.cursor/mcp.json` (global)
- **Claude Desktop**: Global config only, location varies by OS:
  - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
  - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
  - Linux: `~/.config/Claude/claude_desktop_config.json`
- **Claude Code CLI**: Supports both `.mcp.json` (project root) and `~/.claude.json` (user/global)
- **Gemini CLI**: Supports both `.gemini/settings.json` (project) and `~/.gemini/settings.json` (global)
- **VS Code**: Supports both `.vscode/mcp.json` (project) and user-level config (OS-specific paths)
- **OpenCode**: Supports both JSON and JSONC formats (JSON with comments)
- **Codex CLI**: Global only - uses TOML format (`~/.codex/config.toml`)
- **Antigravity**: Global only - `~/.gemini/antigravity/mcp_config.json`

#### Environment Variable Conversion

The import command automatically normalizes environment variables to dynamic-mcp's `${VAR}` format:

| Tool            | Original Format       | Converted To      |
| --------------- | --------------------- | ----------------- |
| Cursor          | `${env:GITHUB_TOKEN}` | `${GITHUB_TOKEN}` |
| Claude Desktop  | `${GITHUB_TOKEN}`     | `${GITHUB_TOKEN}` |
| Claude Code CLI | `${GITHUB_TOKEN}`     | `${GITHUB_TOKEN}` |
| VS Code         | `${env:GITHUB_TOKEN}` | `${GITHUB_TOKEN}` |
| Codex           | `"${GITHUB_TOKEN}"`   | `${GITHUB_TOKEN}` |

**Note**: VS Code's `${input:ID}` secure prompts cannot be automatically converted. You'll need to manually configure these after import.

See [docs/IMPORT.md](docs/IMPORT.md) for detailed tool-specific import guides.

## Dynamic MCP format

### Calling upstream servers on demand

Create a `dynamic-mcp.json` file with a `description` field for each server:

```json
{
  "mcpServers": {
    "filesystem": {
      "description": "Use when you need to read, write, or search files.",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    }
  }
}
```

### Environment Variables

It supports the `${VAR}` syntax for environment variable interpolation:

```json
{
  "mcpServers": {
    "example": {
      "description": "Example with env vars",
      "command": "node",
      "args": ["${HOME}/.local/bin/server.js"],
      "env": {
        "API_KEY": "${MY_API_KEY}"
      }
    }
  }
}
```

### Server Types

It supports all [standard MCP transport mechanisms](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports).

**Note**: The `type` field is **optional** when `url` is present. If omitted, the server automatically uses HTTP transport with SSE detection per the MCP spec. This maintains backwards compatibility with tools like [OpenCode](https://opencode.ai/docs/mcp-servers/).

#### stdio (Default)

```json
{
  "description": "Server description for LLM",
  "command": "npx",
  "args": ["-y", "package-name"],
  "env": {
    "KEY": "value"
  }
}
```

#### http

```json
{
  "description": "HTTP server (type is optional)",
  "url": "https://api.example.com",
  "headers": {
    "Authorization": "Bearer ${TOKEN}"
  }
}
```

Or with explicit type:

```json
{
  "type": "http",
  "description": "HTTP server with explicit type",
  "url": "https://api.example.com",
  "headers": {
    "Authorization": "Bearer ${TOKEN}"
  }
}
```

#### sse

SSE servers are automatically detected when the server responds with `Content-Type: text/event-stream`. You can also explicitly specify `type: "sse"` if the server only supports SSE:

```json
{
  "type": "sse",
  "description": "SSE server (explicit type required only if server doesn't auto-detect)",
  "url": "https://api.example.com/sse",
  "headers": {
    "Authorization": "Bearer ${TOKEN}"
  }
}
```

#### OAuth Authentication (HTTP/SSE)

```json
{
  "description": "OAuth-protected MCP server (type is optional)",
  "url": "https://api.example.com/mcp",
  "oauth_client_id": "your-client-id",
  "oauth_scopes": ["read", "write"]
}
```

**OAuth Flow:**

- On first connection, a browser opens for authorization
- Access tokens are stored in `~/.dynamic-mcp/oauth-servers/<server-name>.json`
- Automatic token refresh before expiry (with RFC 6749 token rotation support)
- The token is injected as an `Authorization: Bearer <token>` header

### Feature Flags

Control which MCP features are exposed per server using the optional `features` field. By default, all features (`tools`, `resources`, `prompts`) are enabled. You can selectively disable features:

```json
{
  "mcpServers": {
    "server-with-tools-only": {
      "description": "Server that only exposes tools",
      "command": "npx",
      "args": ["-y", "some-mcp-server"],
      "features": {
        "resources": false,
        "prompts": false
      }
    },
    "server-without-prompts": {
      "description": "HTTP server without prompt templates (type is optional)",
      "url": "https://api.example.com",
      "features": {
        "prompts": false
      }
    }
  }
}
```

**Behavior:**

- If `features` is omitted, all features are enabled (opt-out design)
- If `features` is specified, unmentioned features default to `true` (enabled)
- Disabled features return an error if accessed via the proxy
- Example: If `resources: false`, calling `resources/list` returns an error

### Disabling Servers

Use the optional `enabled` field to disable a specific server without removing it from the config:

```json
{
  "mcpServers": {
    "filesystem": {
      "description": "File operations",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    },
    "disabled-server": {
      "description": "This server won't connect",
      "command": "some-command",
      "enabled": false
    }
  }
}
```

**Behavior:**

- If `enabled` is omitted, the server is enabled (default behavior)
- If `enabled: false`, the server is skipped during connection and won't appear in available groups
- Useful for temporarily disabling servers during testing or maintenance without editing config structure
- See `examples/config.features.example.json` for a complete example

### Timeout Configuration

Configure custom timeouts for tool, resource, and prompt calls per server using the optional `timeout` field. By default:

- Tool calls: 30 seconds
- Resource calls: 10 seconds
- Prompt calls: 10 seconds

You can customize these for servers that need more time:

```json
{
  "mcpServers": {
    "slow-server": {
      "description": "Server with slow operations",
      "command": "npx",
      "args": ["-y", "some-slow-mcp-server"],
      "timeout": {
        "tools": "1min",
        "resources": "30s",
        "prompts": "30s"
      }
    }
  }
}
```

**Supported duration formats:**

| Format       | Example               | Description                   |
| ------------ | --------------------- | ----------------------------- |
| Seconds      | `"30s"`, `"5s"`       | Simple seconds                |
| Minutes      | `"1min"`, `"2m"`      | Minutes (abbreviated or full) |
| Milliseconds | `"3000ms"`, `"500ms"` | Milliseconds                  |
| Plain number | `30`                  | Seconds (plain number)        |

**Behavior:**

- If `timeout` is omitted, defaults are used (tools: 30s, resources: 10s, prompts: 10s)
- Individual timeout fields default to their respective defaults if not specified
- Applies only to tool/resource/prompt call operations, not to connection or initialization
- Useful for servers with long-running operations (database queries, file processing, etc.)

## Troubleshooting

### Server Connection Issues

**Problem**: `❌ Failed to connect to <server>`

**Solutions**:

- **Connection timeout**: Each server has 10-second timeout for transport creation, initialization, and tool listing
- **Automatic retry**: Failed servers are retried up to 3 times with exponential backoff (2s, 4s, 8s)
- **Periodic retry**: Failed servers are retried every 30 seconds in the background
- **Slow HTTP servers**: If remote HTTP/SSE servers are slow, they'll timeout and be retried automatically
- **Stdio servers**: Verify command exists (`which <command>`)
- **HTTP/SSE servers**: Check that the server is running and the URL is correct
- **Environment variables**: Ensure all `${VAR}` references are defined
- **OAuth servers**: Complete OAuth flow when prompted

**Logging**:

By default, errors and warnings are logged to the terminal. For more verbose output:

```bash
# Debug mode (all logs including debug-level details)
RUST_LOG=debug uvx dmcp config.json

# Info mode (includes informational messages)
RUST_LOG=info uvx dmcp config.json

# Default mode (errors and warnings only, no RUST_LOG needed)
uvx dmcp config.json
```

### OAuth Authentication Problems

**Problem**: The browser doesn't open for OAuth

**Solutions**:

- Manually open the URL shown in the console
- Check that the firewall allows localhost connections
- Verify `oauth_client_id` is correct for the server

**Problem**: Token refresh fails

**Solutions**:

- Delete cached token: `rm ~/.dynamic-mcp/oauth-servers/<server-name>.json`
- Re-authenticate on next connection

### Environment Variable Not Substituted

**Problem**: Config shows `${VAR}` instead of value

**Solutions**:

- Use `${VAR}` syntax, not `$VAR`
- Export variable: `export VAR=value`
- Variable names are case-sensitive
- Check for typos in variable name

### Configuration Errors

**Problem**: `Server missing 'description' field`

**Solutions**:

- Every MCP server in your config must have a `description` field
- The description explains what the server does to the LLM
- Example:

  ```json
  {
    "description": "File system access - read, write, and search files",
    "command": "npx",
    "args": ["@modelcontextprotocol/server-filesystem"]
  }
  ```

**Problem**: `Invalid JSON in config file`

**Solutions**:

- Validate JSON syntax (use `jq . config.json`)
- Check for trailing commas
- Ensure all required fields are present (`description` is always required; `type` is required only for http/sse servers)

**Problem**: Unknown field in config (e.g., `unknown field \`typo_field\`\`)

**Solutions**:

- dynamic-mcp uses strict JSON schema validation that only allows defined fields
- Check for typos in field names: `description`, `command`, `url`, `type`, `args`, `env`, `headers`, `oauth_client_id`, `oauth_scopes`, `features`, `enabled`, `timeout`
- Remove any extra or misspelled fields from your config
- Refer to the schema examples above to see valid fields for each server type

**Problem**: `Failed to resolve config path`

**Solutions**:

- Use an absolute path or a path relative to the working directory
- Check that the file exists and has read permissions
- Try: `ls -la <config-path>`

### Tool Call Failures

**Problem**: Tool call returns error

**Debugging**:

1. Test the tool directly with the upstream server
2. Check that the tool name and arguments match the schema
3. Verify the group name is correct
4. Enable debug logging to see JSON-RPC messages

### Performance Issues

**Problem**: Slow startup

**Solutions**:

- Parallel connections already enabled
- Check network latency for HTTP/SSE servers
- Some servers may be slow to initialize (normal)

**Problem**: High memory usage

**Solutions**:

- Tools are cached in memory (expected)
- Failed groups use minimal memory
- Large tool schemas contribute to memory usage

## Building from source

### Rust Binary

To build the Rust binary directly:

```bash
git clone https://github.com/asyrjasalo/dynamic-mcp.git
cd dynamic-mcp
cargo build --release
```

The binary is then available at `./target/release/dmcp`.

### Python Package

To build the Python package (wheel):

```bash
# Build wheel
uvx maturin build --release

# Install locally
pip install target/wheels/dmcp-*.whl
```

The Python package uses **maturin** with `bindings = "bin"` to compile the Rust binary directly into the wheel.

## Contributing

For instructions on development setup, testing, and contributing, see [CONTRIBUTING.md](CONTRIBUTING.md).

## Release History

See [CHANGELOG.md](CHANGELOG.md) for version history and release notes.

## Acknowledgments

- TypeScript implementation: [modular-mcp]https://github.com/d-kimuson/modular-mcp
- MCP Specification: [Model Context Protocol]https://modelcontextprotocol.io/
- Rust MCP Ecosystem: [rust-mcp-stack]https://github.com/rust-mcp-stack