lazy-mcp 2.2.7 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -1
- package/dist/cli.js +274 -35
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +117 -0
- package/dist/config.js.map +1 -1
- package/dist/http-server.d.ts +20 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +578 -0
- package/dist/http-server.js.map +1 -0
- package/dist/logger.d.ts +40 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +238 -0
- package/dist/logger.js.map +1 -0
- package/dist/server-manager.d.ts +4 -1
- package/dist/server-manager.d.ts.map +1 -1
- package/dist/server-manager.js +279 -135
- package/dist/server-manager.js.map +1 -1
- package/dist/server.d.ts +11 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +276 -188
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,9 +12,11 @@ A client-agnostic proxy that converts normal MCP servers to use a lazy-loading p
|
|
|
12
12
|
- [How It Works](#how-it-works)
|
|
13
13
|
- [Installation](#installation)
|
|
14
14
|
- [Usage](#usage)
|
|
15
|
+
- [Streamable HTTP Transport](#streamable-http-transport)
|
|
15
16
|
- [Integration (Claude Desktop, OpenCode, Cursor, VS Code, and more)](#integration-claude-desktop-opencode-cursor-vs-code-and-more)
|
|
16
17
|
- [Example](#example)
|
|
17
18
|
- [Development](#development)
|
|
19
|
+
- [Running from Local Source](#running-from-local-source)
|
|
18
20
|
- [Releases](#releases)
|
|
19
21
|
- [How It Works](#how-it-works)
|
|
20
22
|
- [Required CI/CD Variables](#required-cicd-variables)
|
|
@@ -27,6 +29,8 @@ A client-agnostic proxy that converts normal MCP servers to use a lazy-loading p
|
|
|
27
29
|
- [OAuth 2.0 Authentication](#oauth-20-authentication)
|
|
28
30
|
- [Command Format](#command-format)
|
|
29
31
|
- [Environment Variables](#environment-variables)
|
|
32
|
+
- [Transport Configuration](#transport-configuration)
|
|
33
|
+
- [Logging Configuration](#logging-configuration)
|
|
30
34
|
- [Health Monitoring](#health-monitoring)
|
|
31
35
|
- [Config Reload (SIGHUP)](#config-reload-sighup)
|
|
32
36
|
- [Benefits](#benefits)
|
|
@@ -43,6 +47,7 @@ A client-agnostic proxy that converts normal MCP servers to use a lazy-loading p
|
|
|
43
47
|
- **Built-in OAuth 2.0 + PKCE**: Authenticate with OAuth-protected remote servers without a browser — works in sandboxed agent environments
|
|
44
48
|
- **Background Health Monitoring**: Probes all servers on startup and periodically; `list_servers` shows accurate health from the first call
|
|
45
49
|
- **Hot Config Reload**: Send `SIGHUP` to reload config without restarting — add, remove, or update servers on the fly
|
|
50
|
+
- **Streamable HTTP Transport**: Run as an HTTP server — expose lazy-mcp over the network so remote clients can connect via `POST /mcp`
|
|
46
51
|
|
|
47
52
|
## How It Works
|
|
48
53
|
|
|
@@ -86,6 +91,24 @@ Or install globally (locks to specific version):
|
|
|
86
91
|
npm install -g lazy-mcp
|
|
87
92
|
```
|
|
88
93
|
|
|
94
|
+
**Docker / Podman**:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
docker build -t lazy-mcp .
|
|
98
|
+
docker compose up
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The image compiles the TypeScript CLI during `docker build`, so this works from a clean checkout without a prebuilt `dist/` directory.
|
|
102
|
+
|
|
103
|
+
Or with Podman:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
podman build -t lazy-mcp .
|
|
107
|
+
podman compose up
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This runs lazy-mcp in HTTP mode on port 8080 with config mounted from `~/.config/lazy-mcp/servers.json`. See `docker-compose.yml` for configuration options.
|
|
111
|
+
|
|
89
112
|
## Usage
|
|
90
113
|
|
|
91
114
|
Create a configuration file at `~/.config/lazy-mcp/servers.json`:
|
|
@@ -133,6 +156,86 @@ LAZY_MCP_CONFIG=~/.config/lazy-mcp/servers.json npx lazy-mcp@latest
|
|
|
133
156
|
lazy-mcp --config ~/.config/lazy-mcp/servers.json
|
|
134
157
|
```
|
|
135
158
|
|
|
159
|
+
### Streamable HTTP Transport
|
|
160
|
+
|
|
161
|
+
By default, lazy-mcp communicates over **stdio** (standard MCP transport). You can also run it as an **HTTP server** using the Streamable HTTP transport, allowing remote clients to connect over the network:
|
|
162
|
+
|
|
163
|
+
**Via config file** — add a `transport` block to `servers.json`:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"servers": [
|
|
168
|
+
{ "name": "my-server", "description": "My MCP server", "command": ["python", "server.py"] }
|
|
169
|
+
],
|
|
170
|
+
"transport": {
|
|
171
|
+
"type": "http",
|
|
172
|
+
"port": 3000,
|
|
173
|
+
"host": "localhost",
|
|
174
|
+
"path": "/mcp",
|
|
175
|
+
"authToken": "${MY_API_KEY}"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Via CLI flags** (override config values):
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Start HTTP server on port 3000
|
|
184
|
+
lazy-mcp --config servers.json --transport http --port 3000
|
|
185
|
+
|
|
186
|
+
# With custom host, path, and auth
|
|
187
|
+
lazy-mcp --config servers.json --transport http --port 3000 --host 127.0.0.1 --path /mcp --auth-token "my-secret"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Security note:** `--host 0.0.0.0` exposes the HTTP server on all network interfaces. Use it only inside Docker or trusted networks.
|
|
191
|
+
|
|
192
|
+
> **Reverse proxy note:** The default Origin check uses the scheme visible to
|
|
193
|
+
> lazy-mcp's own socket plus the incoming `Host` header. If you run lazy-mcp
|
|
194
|
+
> behind a TLS-terminating reverse proxy, the backend hop is usually plain
|
|
195
|
+
> HTTP, so the implicit same-origin shortcut may reject public HTTPS origins.
|
|
196
|
+
> In that setup, configure `transport.allowedOrigins` explicitly. If your
|
|
197
|
+
> proxy preserves the public `Host` header, configure `transport.allowedHosts`
|
|
198
|
+
> too.
|
|
199
|
+
>
|
|
200
|
+
> Example:
|
|
201
|
+
> ```json
|
|
202
|
+
> {
|
|
203
|
+
> "transport": {
|
|
204
|
+
> "type": "http",
|
|
205
|
+
> "host": "127.0.0.1",
|
|
206
|
+
> "port": 8080,
|
|
207
|
+
> "path": "/mcp",
|
|
208
|
+
> "allowedHosts": ["mcp.example.com"],
|
|
209
|
+
> "allowedOrigins": ["https://mcp.example.com"]
|
|
210
|
+
> }
|
|
211
|
+
> }
|
|
212
|
+
> ```
|
|
213
|
+
>
|
|
214
|
+
> lazy-mcp intentionally does not trust `Forwarded` / `X-Forwarded-*` headers
|
|
215
|
+
> by default. If proxy-aware origin reconstruction is ever added, it should be
|
|
216
|
+
> behind an explicit trusted-proxy setting.
|
|
217
|
+
|
|
218
|
+
**Via environment variables** (lowest precedence):
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
LAZY_MCP_TRANSPORT=http LAZY_MCP_PORT=3000 LAZY_MCP_AUTH_TOKEN=my-secret lazy-mcp
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Precedence**: CLI flags > config file > environment variables > defaults.
|
|
225
|
+
|
|
226
|
+
Once running, clients connect as a remote MCP server:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Quick test with curl
|
|
230
|
+
curl -X POST http://localhost:3000/mcp \
|
|
231
|
+
-H "Content-Type: application/json" \
|
|
232
|
+
-H "Accept: application/json, text/event-stream" \
|
|
233
|
+
-H "Authorization: Bearer ${MY_API_KEY}" \
|
|
234
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
> **Note**: Server-initiated notifications via GET/SSE are not supported in stateless mode. Use POST for all requests.
|
|
238
|
+
|
|
136
239
|
## Integration (Claude Desktop, OpenCode, Cursor, VS Code, and more)
|
|
137
240
|
|
|
138
241
|
Replace multiple MCP server entries with one aggregated proxy:
|
|
@@ -163,6 +266,24 @@ Replace multiple MCP server entries with one aggregated proxy:
|
|
|
163
266
|
}
|
|
164
267
|
```
|
|
165
268
|
|
|
269
|
+
**HTTP mode** — for clients that support remote MCP servers:
|
|
270
|
+
|
|
271
|
+
Start lazy-mcp in HTTP mode (e.g. `lazy-mcp --config servers.json --transport http --port 3000`), then configure your client:
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"mcp": {
|
|
276
|
+
"lazy-mcp": {
|
|
277
|
+
"type": "remote",
|
|
278
|
+
"url": "http://localhost:3000/mcp",
|
|
279
|
+
"headers": {
|
|
280
|
+
"Authorization": "Bearer ${LAZY_MCP_AUTH_TOKEN}"
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
166
287
|
Where `~/.config/lazy-mcp/servers.json` contains all 5 servers:
|
|
167
288
|
```json
|
|
168
289
|
{
|
|
@@ -196,6 +317,51 @@ npm run build
|
|
|
196
317
|
npm test
|
|
197
318
|
```
|
|
198
319
|
|
|
320
|
+
### Running from Local Source
|
|
321
|
+
|
|
322
|
+
Instead of `npx lazy-mcp@latest` (which downloads the published package), you can run directly from the cloned repo:
|
|
323
|
+
|
|
324
|
+
**Without building** — using `ts-node` (picks up source changes immediately):
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npm run dev -- --config ~/.config/lazy-mcp/servers.json
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**After building** — run the compiled output:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
npm run build
|
|
334
|
+
node dist/cli.js --config ~/.config/lazy-mcp/servers.json
|
|
335
|
+
# or equivalently:
|
|
336
|
+
npm start -- --config ~/.config/lazy-mcp/servers.json
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**In an MCP client config** — point directly at the local build:
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"mcp": {
|
|
344
|
+
"lazy-mcp": {
|
|
345
|
+
"command": "node",
|
|
346
|
+
"args": ["/path/to/lazy-mcp/dist/cli.js", "--config", "~/.config/lazy-mcp/servers.json"]
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Or with `ts-node` (no build needed, always reflects latest source):
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"mcp": {
|
|
357
|
+
"lazy-mcp": {
|
|
358
|
+
"command": "npx",
|
|
359
|
+
"args": ["ts-node", "/path/to/lazy-mcp/src/cli.ts", "--config", "~/.config/lazy-mcp/servers.json"]
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
199
365
|
## Releases
|
|
200
366
|
|
|
201
367
|
Releases are fully automated via [semantic-release](https://semantic-release.gitbook.io/) on every push to `main`.
|
|
@@ -393,6 +559,80 @@ Use `${VAR_NAME}` to reference environment variables:
|
|
|
393
559
|
}
|
|
394
560
|
```
|
|
395
561
|
|
|
562
|
+
### Transport Configuration
|
|
563
|
+
|
|
564
|
+
Configure how lazy-mcp exposes its MCP endpoint. By default, it uses stdio (for subprocess-based clients). Set `type: "http"` to run as an HTTP server.
|
|
565
|
+
|
|
566
|
+
**Top-level configuration** (in `servers.json`):
|
|
567
|
+
|
|
568
|
+
| Field | Type | Default | Description |
|
|
569
|
+
|-------|------|---------|-------------|
|
|
570
|
+
| `transport.type` | `"stdio"` \| `"http"` | `"stdio"` | Transport mode |
|
|
571
|
+
| `transport.port` | number | `8080` | HTTP server port |
|
|
572
|
+
| `transport.host` | string | `"127.0.0.1"` | HTTP server bind address. For TLS-terminating reverse proxies, keep localhost binding and configure `allowedOrigins` explicitly. |
|
|
573
|
+
| `transport.path` | string | `"/mcp"` | MCP endpoint URL path |
|
|
574
|
+
| `transport.authToken` | string | — | Bearer token for HTTP auth (supports `${VAR}` expansion) |
|
|
575
|
+
| `transport.allowedOrigins` | string[] | — | List of allowed Origin header values for request-origin validation / CSRF protection (e.g. `["https://example.com"]`). Full origins including scheme and port. Recommended for TLS-terminating reverse-proxy deployments. Does not send CORS response headers. |
|
|
576
|
+
| `transport.allowedHosts` | string[] | — | List of explicitly allowed host domains for DNS rebinding protection. If a reverse proxy preserves the public `Host` header, add that host here. |
|
|
577
|
+
| `transport.maxPayloadSize` | number | `4194304` | Maximum request body size in bytes. Requests exceeding this limit return HTTP 413 |
|
|
578
|
+
|
|
579
|
+
**CLI flags** (override config values):
|
|
580
|
+
|
|
581
|
+
| Flag | Env Variable | Description |
|
|
582
|
+
|------|-------------|-------------|
|
|
583
|
+
| `--transport` | `LAZY_MCP_TRANSPORT` | Transport type (`stdio` or `http`) |
|
|
584
|
+
| `--port` | `LAZY_MCP_PORT` | HTTP server port |
|
|
585
|
+
| `--host` | `LAZY_MCP_HOST` | HTTP server bind address |
|
|
586
|
+
| `--path` | `LAZY_MCP_PATH` | MCP endpoint URL path |
|
|
587
|
+
| `--auth-token` | `LAZY_MCP_AUTH_TOKEN` | Bearer token for HTTP auth |
|
|
588
|
+
| `--request-timeout` | `LAZY_MCP_REQUEST_TIMEOUT` | Request timeout in ms for server calls, including MCP handshake requests and remote response parsing (default: 10000) |
|
|
589
|
+
| `--max-payload-size` | `LAZY_MCP_MAX_PAYLOAD_SIZE` | Maximum request body size in bytes (default: 4194304) |
|
|
590
|
+
|
|
591
|
+
When `authToken` is set, all HTTP requests must include `Authorization: Bearer <token>` — unauthenticated requests receive `401 Unauthorized`.
|
|
592
|
+
|
|
593
|
+
### Logging Configuration
|
|
594
|
+
|
|
595
|
+
lazy-mcp now emits structured logs to **stderr** (stdout remains reserved for MCP protocol traffic).
|
|
596
|
+
|
|
597
|
+
**Top-level configuration** (in `servers.json`):
|
|
598
|
+
|
|
599
|
+
| Field | Type | Default | Description |
|
|
600
|
+
|-------|------|---------|-------------|
|
|
601
|
+
| `logging.level` | `"error"` \| `"info"` \| `"debug"` | `"info"` | Minimum log level |
|
|
602
|
+
| `logging.format` | `"json"` \| `"plain"` | `"json"` | Log output format |
|
|
603
|
+
| `logging.dumpBodies` | boolean | `false` | Enable debug request/response body dumps |
|
|
604
|
+
| `logging.maxBodyLogBytes` | number | `8192` | Max body-dump size in bytes before truncation |
|
|
605
|
+
| `logging.redactKeys` | string[] | — | Additional case-insensitive keys to redact (merged with built-in defaults) |
|
|
606
|
+
|
|
607
|
+
Built-in redaction includes common secret keys like `authorization`, `token`, `access_token`, `refresh_token`, `client_secret`, and `headers.authorization`.
|
|
608
|
+
|
|
609
|
+
Example:
|
|
610
|
+
|
|
611
|
+
```json
|
|
612
|
+
{
|
|
613
|
+
"servers": [
|
|
614
|
+
{
|
|
615
|
+
"name": "my-server",
|
|
616
|
+
"description": "Example",
|
|
617
|
+
"command": ["npx", "-y", "my-mcp-server"]
|
|
618
|
+
}
|
|
619
|
+
],
|
|
620
|
+
"logging": {
|
|
621
|
+
"level": "debug",
|
|
622
|
+
"format": "json",
|
|
623
|
+
"dumpBodies": true,
|
|
624
|
+
"maxBodyLogBytes": 4096,
|
|
625
|
+
"redactKeys": ["my_custom_secret"]
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
Typical access log event fields include:
|
|
631
|
+
- client source (`clientIp`, optional `forwardedFor`)
|
|
632
|
+
- request shape (`httpMethod`, `path`, `mcpMethod`, `lazyTool`)
|
|
633
|
+
- downstream routing (`downstreamServer`, `downstreamCommand`)
|
|
634
|
+
- outcome (`status`, `reason`, `durationMs`)
|
|
635
|
+
|
|
396
636
|
### Health Monitoring
|
|
397
637
|
|
|
398
638
|
lazy-mcp includes a background health monitor that probes all servers periodically. The monitor is **activity-driven**: it sleeps on startup and only begins probing after the first user tool call (`list_servers`, `list_commands`, etc.). After a configurable idle timeout (default: 5 minutes) with no tool calls, the monitor goes back to sleep. This prevents OAuth-protected servers (e.g. GitLab via `mcp-remote`) from opening browser windows when no one is using the tools.
|
|
@@ -407,8 +647,9 @@ Successful probes populate the discovery cache, so subsequent `list_commands` ca
|
|
|
407
647
|
| `healthMonitor.interval` | number | `30000` | Interval between health checks (ms) |
|
|
408
648
|
| `healthMonitor.timeout` | number | `10000` | Timeout per server probe (ms) |
|
|
409
649
|
| `healthMonitor.idleTimeout` | number | `300000` | Stop probing after this much inactivity (ms). `0` = never sleep (legacy) |
|
|
650
|
+
| `requestTimeout` | number | `10000` | Timeout for individual server requests. For remote HTTP servers, this includes waiting for headers, reading response bodies, and SSE response parsing. Override via `--request-timeout` or `LAZY_MCP_REQUEST_TIMEOUT` |
|
|
410
651
|
|
|
411
|
-
To disable:
|
|
652
|
+
To disable health monitoring:
|
|
412
653
|
```json
|
|
413
654
|
{
|
|
414
655
|
"servers": [...],
|
|
@@ -416,6 +657,14 @@ To disable:
|
|
|
416
657
|
}
|
|
417
658
|
```
|
|
418
659
|
|
|
660
|
+
To increase the request timeout (e.g. for slow remote servers):
|
|
661
|
+
```json
|
|
662
|
+
{
|
|
663
|
+
"servers": [...],
|
|
664
|
+
"requestTimeout": 30000
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
419
668
|
### Config Reload (SIGHUP)
|
|
420
669
|
|
|
421
670
|
You can reload the configuration without restarting the process by sending a `SIGHUP` signal:
|
|
@@ -446,6 +695,7 @@ If the new config is invalid, the reload is rejected and the current config cont
|
|
|
446
695
|
- **Flexible configuration** - Enable/disable servers on demand
|
|
447
696
|
- **Environment variable support** - Secure credential management
|
|
448
697
|
- **Both local and remote** - Support for subprocess and HTTP servers
|
|
698
|
+
- **Streamable HTTP transport** - Run as an HTTP server for remote client access
|
|
449
699
|
- **Health monitoring** - Background probes detect broken servers before you hit them
|
|
450
700
|
|
|
451
701
|
## Documentation
|
|
@@ -454,4 +704,5 @@ If the new config is invalid, the reload is rejected and the current config cont
|
|
|
454
704
|
- **[AGENTS.md](./AGENTS.md)** - Development guide for AI coding agents (build commands, code style, testing patterns)
|
|
455
705
|
- **[doc/ARCHITECTURE.md](./doc/ARCHITECTURE.md)** - Architecture overview and design patterns
|
|
456
706
|
- **[doc/CONTRIBUTING.md](./doc/CONTRIBUTING.md)** - Contributing guide with common development tasks
|
|
707
|
+
- **[doc/requests/](./doc/requests/)** - [Bruno](https://www.usebruno.com/) API collection for testing the Streamable HTTP transport. Open the `doc/requests/` folder as a collection in Bruno, select the `local` or `local-with-auth` environment, and run requests against a locally running `lazy-mcp --transport http` instance.
|
|
457
708
|
- **[Configuration Reference](#configuration-reference)** - Server configuration options (above)
|