lazy-mcp 2.5.0 → 2.6.6

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 CHANGED
@@ -14,28 +14,14 @@ A client-agnostic proxy that converts normal MCP servers to use a lazy-loading p
14
14
  - [Known Issue](#known-issue)
15
15
  - [Installation](#installation)
16
16
  - [Usage](#usage)
17
+ - [Config directory location (XDG Base Directory)](#config-directory-location-xdg-base-directory)
17
18
  - [Streamable HTTP Transport](#streamable-http-transport)
18
19
  - [Integration (Claude Desktop, OpenCode, Cursor, VS Code, and more)](#integration-claude-desktop-opencode-cursor-vs-code-and-more)
19
20
  - [Example](#example)
20
21
  - [Development](#development)
21
22
  - [Running from Local Source](#running-from-local-source)
22
23
  - [Releases](#releases)
23
- - [How It Works](#how-it-works)
24
- - [Required CI/CD Variables](#required-cicd-variables)
25
- - [`GITLAB_RELEASE_TOKEN`](#gitlabreleasetoken)
26
- - [`NPM_TOKEN`](#npmtoken)
27
- - [`PYPI_TOKEN`](#pypitoken)
28
- - [`CARGO_TOKEN`](#cargotoken)
29
24
  - [Configuration Reference](#configuration-reference)
30
- - [Server Configuration Fields](#server-configuration-fields)
31
- - [OAuth 2.0 Authentication](#oauth-20-authentication)
32
- - [Command Format](#command-format)
33
- - [Environment Variables](#environment-variables)
34
- - [Embed Server Summaries](#embed-server-summaries)
35
- - [Transport Configuration](#transport-configuration)
36
- - [Logging Configuration](#logging-configuration)
37
- - [Health Monitoring](#health-monitoring)
38
- - [Config Reload (SIGHUP)](#config-reload-sighup)
39
25
  - [Benefits](#benefits)
40
26
  - [Documentation](#documentation)
41
27
  <!-- TOC_END -->
@@ -200,104 +186,43 @@ LAZY_MCP_CONFIG=~/.config/lazy-mcp/servers.json npx lazy-mcp@latest
200
186
  lazy-mcp --config ~/.config/lazy-mcp/servers.json
201
187
  ```
202
188
 
203
- ### Streamable HTTP Transport
189
+ ### Config directory location (XDG Base Directory)
204
190
 
205
- 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:
191
+ The config directory defaults to `~/.config/lazy-mcp/`, but lazy-mcp honours the
192
+ [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html):
193
+ if `$XDG_CONFIG_HOME` is set, lazy-mcp uses `$XDG_CONFIG_HOME/lazy-mcp/`
194
+ instead. This applies to:
206
195
 
207
- **Via config file** — add a `transport` block to `servers.json`:
196
+ - the default `servers.json` location,
197
+ - the OAuth token store (`tokens.json`, `client-info.json`, …),
198
+ - the OAuth callback PID lock files.
208
199
 
209
- ```json
210
- {
211
- "servers": [
212
- { "name": "my-server", "description": "My MCP server", "command": ["python", "server.py"] }
213
- ],
214
- "transport": {
215
- "type": "http",
216
- "port": 3000,
217
- "host": "localhost",
218
- "path": "/mcp",
219
- "authToken": "${MY_API_KEY}"
220
- }
221
- }
222
- ```
223
-
224
- **Via CLI flags** (override config values):
200
+ This makes it easy to scope tokens per project (e.g. via `direnv` or `mise`)
201
+ without having to override `$HOME`:
225
202
 
226
203
  ```bash
227
- # Start HTTP server on port 3000
228
- lazy-mcp --config servers.json --transport http --port 3000
229
-
230
- # With custom host, path, and auth
231
- lazy-mcp --config servers.json --transport http --port 3000 --host 127.0.0.1 --path /mcp --auth-token "my-secret"
204
+ # In a project's .envrc / mise.toml:
205
+ export XDG_CONFIG_HOME="$PWD/.config"
232
206
  ```
233
207
 
234
- **Security note:** `--host 0.0.0.0` exposes the HTTP server on all network interfaces. Use it only inside Docker or trusted networks.
235
-
236
- > **Reverse proxy note:** The default Origin check uses the scheme visible to
237
- > lazy-mcp's own socket plus the incoming `Host` header. If you run lazy-mcp
238
- > behind a TLS-terminating reverse proxy, the backend hop is usually plain
239
- > HTTP, so the implicit same-origin shortcut may reject public HTTPS origins.
240
- > In that setup, configure `transport.allowedOrigins` explicitly. If your
241
- > proxy preserves the public `Host` header, configure `transport.allowedHosts`
242
- > too.
243
- >
244
- > Example:
245
- > ```json
246
- > {
247
- > "transport": {
248
- > "type": "http",
249
- > "host": "127.0.0.1",
250
- > "port": 8080,
251
- > "path": "/mcp",
252
- > "allowedHosts": ["mcp.example.com"],
253
- > "allowedOrigins": ["https://mcp.example.com"]
254
- > }
255
- > }
256
- > ```
257
- >
258
- > lazy-mcp intentionally does not trust `Forwarded` / `X-Forwarded-*` headers
259
- > by default. If proxy-aware origin reconstruction is ever added, it should be
260
- > behind an explicit trusted-proxy setting.
261
-
262
- **Via environment variables** (lowest precedence):
263
-
264
- ```bash
265
- LAZY_MCP_TRANSPORT=http LAZY_MCP_PORT=3000 LAZY_MCP_AUTH_TOKEN=my-secret lazy-mcp
266
- ```
208
+ With that set, lazy-mcp will read `./.config/lazy-mcp/servers.json` and store
209
+ tokens under `./.config/lazy-mcp/` — completely isolated from your global
210
+ lazy-mcp state.
267
211
 
268
- **Precedence**: CLI flags > config file > environment variables > defaults.
212
+ ### Streamable HTTP Transport
269
213
 
270
- Once running, clients connect as a remote MCP server:
214
+ By default, lazy-mcp communicates over **stdio**. You can also run it as an **HTTP server** so remote clients can connect over the network:
271
215
 
272
216
  ```bash
273
- # Quick test with curl
274
- curl -X POST http://localhost:3000/mcp \
275
- -H "Content-Type: application/json" \
276
- -H "Accept: application/json, text/event-stream" \
277
- -H "Authorization: Bearer ${MY_API_KEY}" \
278
- -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
217
+ lazy-mcp --config servers.json --transport http --port 3000 --auth-token "my-secret"
279
218
  ```
280
219
 
281
- > **Note**: Server-initiated notifications via GET/SSE are not supported in stateless mode. Use POST for all requests.
220
+ See [doc/HTTP_TRANSPORT.md](./doc/HTTP_TRANSPORT.md) for full configuration, security guidance (DNS rebinding protection, payload limits, bearer auth), and reverse-proxy setup.
282
221
 
283
222
  ## Integration (Claude Desktop, OpenCode, Cursor, VS Code, and more)
284
223
 
285
- Replace multiple MCP server entries with one aggregated proxy:
224
+ Replace multiple MCP server entries in your client with one aggregated lazy-mcp proxy:
286
225
 
287
- **Before** (5 separate MCP servers):
288
- ```json
289
- {
290
- "mcp": {
291
- "chrome-devtools": { "command": ["npx", "lazy-mcp@latest", "npx", "-y", "chrome-devtools-mcp@latest"] },
292
- "gitlab": { "command": ["npx", "lazy-mcp@latest", "npx", "mcp-remote@latest", "https://..."] },
293
- "grepai": { "command": ["npx", "lazy-mcp@latest", "grepai", "mcp-serve"] },
294
- "context7": { "command": ["npx", "-y", "@upstash/context7-mcp"] },
295
- "perplexity": { "command": ["npx", "-y", "@perplexity-ai/mcp-server"] }
296
- }
297
- }
298
- ```
299
-
300
- **After** (Consolidated into 1 multi-server proxy):
301
226
  ```json
302
227
  {
303
228
  "mcp": {
@@ -310,38 +235,9 @@ Replace multiple MCP server entries with one aggregated proxy:
310
235
  }
311
236
  ```
312
237
 
313
- **HTTP mode** for clients that support remote MCP servers:
314
-
315
- Start lazy-mcp in HTTP mode (e.g. `lazy-mcp --config servers.json --transport http --port 3000`), then configure your client:
316
-
317
- ```json
318
- {
319
- "mcp": {
320
- "lazy-mcp": {
321
- "type": "remote",
322
- "url": "http://localhost:3000/mcp",
323
- "headers": {
324
- "Authorization": "Bearer ${LAZY_MCP_AUTH_TOKEN}"
325
- }
326
- }
327
- }
328
- }
329
- ```
330
-
331
- Where `~/.config/lazy-mcp/servers.json` contains all 5 servers:
332
- ```json
333
- {
334
- "servers": [
335
- { "name": "chrome-devtools", "description": "Chrome DevTools automation", "command": ["npx", "-y", "chrome-devtools-mcp@latest"] },
336
- { "name": "gitlab", "description": "GitLab API integration", "command": ["npx", "mcp-remote@latest", "https://..."] },
337
- { "name": "grepai", "description": "Search codebase", "command": ["grepai", "mcp-serve"] },
338
- { "name": "context7", "description": "Library documentation", "command": ["npx", "-y", "@upstash/context7-mcp"] },
339
- { "name": "perplexity", "description": "Web search", "command": ["npx", "-y", "@perplexity-ai/mcp-server"] }
340
- ]
341
- }
342
- ```
238
+ All downstream MCP servers live in `~/.config/lazy-mcp/servers.json`. **Result**: ~90% context reduction (from ~16K to ~1.5K tokens initially).
343
239
 
344
- **Result**: ~90% context reduction (from ~16K to ~1.5K tokens initially)
240
+ See [doc/INTEGRATION.md](./doc/INTEGRATION.md) for before/after examples and HTTP-mode client configuration.
345
241
 
346
242
  ## Example
347
243
 
@@ -408,384 +304,29 @@ Or with `ts-node` (no build needed, always reflects latest source):
408
304
 
409
305
  ## Releases
410
306
 
411
- Releases are fully automated via [semantic-release](https://semantic-release.gitbook.io/) on every push to `main`.
412
-
413
- ### How It Works
414
-
415
- 1. CI analyzes all commits since the last tag using [Conventional Commits](https://www.conventionalcommits.org/)
416
- 2. Determines the next version (`feat` → minor bump, `fix`/`perf`/`chore`/`refactor`/`test` → patch bump)
417
- 3. Updates `package.json`, `CHANGELOG.md`, `VERSION`, and `RELEASE_NOTES.md`
418
- 4. Commits those files as `chore(release): X.Y.Z` and pushes a `vX.Y.Z` tag
419
- 5. Creates a GitLab Release with the generated release notes
420
- 6. The `vX.Y.Z` tag triggers a separate pipeline that publishes the package to npm automatically
421
-
422
- No manual version bumping or tagging needed — just merge to `main` with conventional commit messages.
423
-
424
- ### Required CI/CD Variables
425
-
426
- Four CI/CD variables must be configured (GitLab → Project → Settings → CI/CD → Variables):
307
+ Releases are fully automated via [semantic-release](https://semantic-release.gitbook.io/) on every push to `main`. CI analyzes [Conventional Commits](https://www.conventionalcommits.org/), bumps the version, tags, and publishes to npm, PyPI, and crates.io.
427
308
 
428
- | Variable | Description |
429
- |----------|-------------|
430
- | `GITLAB_RELEASE_TOKEN` | Project access token — pushes the release commit + tag to `main` and creates the GitLab Release |
431
- | `NPM_TOKEN` | npm automation token — publishes the package to the npm registry on tag pipelines |
432
- | `PYPI_TOKEN` | PyPI API token — publishes the package to PyPI on tag pipelines |
433
- | `CARGO_TOKEN` | crates.io API token — publishes the crate to crates.io on tag pipelines |
434
-
435
- #### `GITLAB_RELEASE_TOKEN`
436
-
437
- **Creating the token** (GitLab → Project → Settings → Access Tokens):
438
-
439
- | Setting | Value |
440
- |---------|-------|
441
- | Token name | `semantic-release-bot` (or any name) |
442
- | Role | **Developer** (push to a protected branch should be configured separately) |
443
- | Scopes | `api`, `write_repository` |
444
-
445
- **Adding the variable:**
446
-
447
- | Setting | Value |
448
- |---------|-------|
449
- | Key | `GITLAB_RELEASE_TOKEN` |
450
- | Masked | ✅ Yes |
451
- | Protected | ❌ No (must be available on the unprotected `main` pipeline) |
452
-
453
- > **Note**: If `main` is a protected branch with push restrictions, the token's bot user must be added to the "Allowed to push" list under GitLab → Project → Settings → Repository → Protected branches.
454
-
455
- #### `NPM_TOKEN`
456
-
457
- **Creating the token** (npmjs.com → Account → Access Tokens → Generate New Token):
458
-
459
- | Setting | Value |
460
- |---------|-------|
461
- | Token type | **Automation** (bypasses 2FA, suitable for CI) |
462
-
463
- **Adding the variable:**
464
-
465
- | Setting | Value |
466
- |---------|-------|
467
- | Key | `NPM_TOKEN` |
468
- | Masked | ✅ Yes |
469
- | Protected | ✅ Yes (only needed on tag pipelines, which are protected) |
470
-
471
- #### `PYPI_TOKEN`
472
-
473
- **Creating the token** (pypi.org → Account Settings → API tokens → Add API token):
474
-
475
- | Setting | Value |
476
- |---------|-------|
477
- | Token name | `lazy-mcp-ci` (or any name) |
478
- | Scope | **Project: lazy-mcp** (restrict to this project after first publish; use "Entire account" for the very first publish) |
479
-
480
- **Adding the variable:**
481
-
482
- | Setting | Value |
483
- |---------|-------|
484
- | Key | `PYPI_TOKEN` |
485
- | Masked | ✅ Yes |
486
- | Protected | ✅ Yes (only needed on tag pipelines, which are protected) |
487
-
488
- #### `CARGO_TOKEN`
489
-
490
- **Creating the token** (crates.io → Account Settings → API Tokens → New Token):
491
-
492
- | Setting | Value |
493
- |---------|-------|
494
- | Token name | `lazy-mcp-ci` (or any name) |
495
- | Scopes | `publish-new`, `publish-update` |
496
-
497
- **Adding the variable:**
498
-
499
- | Setting | Value |
500
- |---------|-------|
501
- | Key | `CARGO_TOKEN` |
502
- | Masked | ✅ Yes |
503
- | Protected | ✅ Yes (only needed on tag pipelines, which are protected) |
309
+ See [doc/RELEASES.md](./doc/RELEASES.md) for the full release pipeline and the required CI/CD variables (`GITLAB_RELEASE_TOKEN`, `NPM_TOKEN`, `PYPI_TOKEN`, `CARGO_TOKEN`).
504
310
 
505
311
  ## Configuration Reference
506
312
 
507
- ### Server Configuration Fields
508
-
509
- | Field | Type | Required | Description |
510
- |-------|------|----------|-------------|
511
- | `name` | string | ✅ | Unique server identifier |
512
- | `description` | string | ✅ | Human-readable description |
513
- | `type` | "local" \| "remote" | Optional | Inferred from `url` (remote) or `command` (local) if omitted |
514
- | `command` | string[] \| string | For local | Command to execute (array format recommended) |
515
- | `args` | string[] | Optional | Arguments (only if command is string) |
516
- | `url` | string | For remote | HTTP/HTTPS URL |
517
- | `headers` | object | Optional | Static HTTP headers for remote servers |
518
- | `oauth` | object | Optional | OAuth 2.0 config for remote servers (see below) |
519
- | `env` | object | Optional | Environment variables (supports `${VAR}` and `{file:...}` expansion) |
520
- | `enabled` | boolean | Optional | Enable/disable server (default: true) |
521
- | `examples` | object[] | Optional | Usage examples shown in `list_servers` output |
522
- | `tags` | string[] | Optional | Capability tags for filtering (e.g. `"api"`, `"browser"`) |
523
-
524
- ### OAuth 2.0 Authentication
525
-
526
- lazy-mcp has built-in OAuth 2.0 + PKCE support for remote servers that require user authorization. It works without opening a browser automatically, making it suitable for sandboxed agent environments: when authentication is needed, lazy-mcp returns the authorization URL in the error message so the agent can present it to the user.
527
-
528
- OAuth server endpoints are **discovered automatically** via RFC 8414 (`/.well-known/oauth-authorization-server`). Dynamic client registration (RFC 7591) is used when no `clientId` is provided.
529
-
530
- Tokens are persisted to `~/.config/lazy-mcp/tokens.json` (mode `0600`) and refreshed automatically via `refresh_token` when available.
531
-
532
- **Minimal config** (fully automatic — discovery + dynamic registration):
533
- ```json
534
- {
535
- "name": "glean",
536
- "description": "Glean enterprise search",
537
- "url": "https://your-company.glean.com/mcp/default",
538
- "oauth": {}
539
- }
540
- ```
541
-
542
- **With a pre-registered client ID**:
543
- ```json
544
- {
545
- "name": "glean",
546
- "description": "Glean enterprise search",
547
- "url": "https://your-company.glean.com/mcp/default",
548
- "oauth": {
549
- "clientId": "${GLEAN_CLIENT_ID}",
550
- "extraHeaders": { "X-Glean-Auth-Type": "OAUTH" }
551
- }
552
- }
553
- ```
554
-
555
- **`oauth` object fields:**
556
-
557
- | Field | Type | Default | Description |
558
- |-------|------|---------|-------------|
559
- | `clientId` | string | — | OAuth client ID. If omitted, dynamic registration (RFC 7591) is attempted |
560
- | `clientSecret` | string | — | Client secret (omit for public-client / PKCE-only flows) |
561
- | `callbackPort` | number | `8947` | Local port for the OAuth redirect callback server |
562
- | `extraHeaders` | object | — | Additional headers added to every authenticated request (e.g. `X-Glean-Auth-Type`) |
563
-
564
- **How it works:**
565
-
566
- 1. Agent calls `invoke_command` (or `list_commands`) on an OAuth-protected server
567
- 2. lazy-mcp returns an `isError: true` response with the authorization URL
568
- 3. Agent presents the URL to the user: _"Open this URL to authorize Glean: https://..."_
569
- 4. User opens the URL in a browser and completes authorization
570
- 5. Browser redirects to `http://localhost:8947/callback` — lazy-mcp captures the token
571
- 6. Agent retries the original command — now succeeds transparently
572
-
573
- ### Command Format
574
-
575
- **Recommended** (OpenCode-compatible):
576
- ```json
577
- {
578
- "command": ["npx", "-y", "my-mcp-server", "--port", "3000"]
579
- }
580
- ```
581
-
582
- **Legacy** (still supported):
583
- ```json
584
- {
585
- "command": "npx",
586
- "args": ["-y", "my-mcp-server", "--port", "3000"]
587
- }
588
- ```
589
-
590
- ### Environment Variables
591
-
592
- Use `${VAR_NAME}` to reference environment variables:
593
-
594
- ```json
595
- {
596
- "env": {
597
- "API_KEY": "${MY_API_KEY}",
598
- "DEBUG": "true"
599
- },
600
- "headers": {
601
- "Authorization": "Bearer ${AUTH_TOKEN}"
602
- }
603
- }
604
- ```
605
-
606
- Use `{file:/path/to/secret}` to read a secret directly from a file (trailing newline is stripped automatically). `~/` is expanded to the home directory. On Windows, `%VAR_NAME%` references inside the path are expanded from environment variables (e.g. `%USERPROFILE%`):
607
-
608
- ```json
609
- {
610
- "env": {
611
- "GITLAB_API_TOKEN": "{file:~/.secrets/gl-pat-token}",
612
- "OTHER_SECRET": "{file:/run/secrets/other-token}"
613
- },
614
- "headers": {
615
- "Authorization": "Bearer {file:~/.secrets/api-token}"
616
- }
617
- }
618
- ```
619
-
620
- On Windows you can use `%USERPROFILE%` or any other environment variable in the path:
621
-
622
- ```json
623
- {
624
- "env": {
625
- "API_TOKEN": "{file:%USERPROFILE%\\.secrets\\token}"
626
- }
627
- }
628
- ```
629
-
630
- Only absolute paths and `~/` paths are accepted. Relative paths are left unchanged. Both notations can be combined in the same value and work in `env`, `headers`, and `transport.authToken` fields. `${VAR}` references inside a `{file:...}` path are not expanded; use an absolute path, `~/`, or Windows-style `%VAR_NAME%` path expansion instead. If the referenced file cannot be read (missing or permission error), the app exits at startup with an error message — e.g. `Cannot read secret file '/run/secrets/token' (from {file:/run/secrets/token}): ENOENT: no such file or directory`.
631
-
632
- **Security best practices for secret files:**
633
-
634
- - Set permissions to `0600` (owner read/write only): `chmod 0600 ~/.secrets/my-token`
635
- - Store secret files in a user-specific directory (e.g. `~/.secrets/`)
636
- - Never commit secret files to version control — add them to `.gitignore`
637
-
638
- ### Embed Server Summaries
639
-
640
- By default, the AI must call `list_servers` to discover which MCP servers are available.
641
- You can opt in to embedding server names and descriptions directly in the `list_servers` tool description so the
642
- AI sees them upfront — useful for routing requests like "check my email" to the correct server without an extra tool call.
643
-
644
- ```json
645
- {
646
- "servers": [...],
647
- "embedServerSummaries": {
648
- "enabled": true,
649
- "maxServers": 5
650
- }
651
- }
652
- ```
653
-
654
- | Field | Type | Default | Description |
655
- |-------|------|---------|-------------|
656
- | `embedServerSummaries.enabled` | boolean | — | Enable/disable embedding |
657
- | `embedServerSummaries.maxServers` | number | `10` | Maximum number of server summaries to embed |
658
-
659
- > **Note:** Tool descriptions are included in every request context window.
660
- > If your system prompt already instructs the LLM to call `list_servers`, this adds tokens with no benefit.
661
- > Per-server descriptions are unbounded, so one verbose description can significantly increase tool description size.
662
- > Making this opt-in lets you consciously accept that trade-off.
663
-
664
- ### Transport Configuration
313
+ lazy-mcp reads its configuration from `~/.config/lazy-mcp/servers.json` (or `--config <path>`). At minimum each server needs `name`, `description`, and either `command` (local) or `url` (remote).
665
314
 
666
- 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.
315
+ Common top-level blocks:
667
316
 
668
- **Top-level configuration** (in `servers.json`):
317
+ - **`servers[]`** list of MCP servers to aggregate (required)
318
+ - **`permissions`** — global and per-server allow/deny rules for `invoke_command` (experimental)
319
+ - **`transport`** — switch from stdio to HTTP, set port, bind host, bearer auth, etc.
320
+ - **`logging`** — structured stderr logging (level, format, body dumps, redaction)
321
+ - **`healthMonitor`** — background health probes (activity-driven by default)
322
+ - **`embedServerSummaries`** — opt-in: embed configured server names/descriptions in `list_servers` description
323
+ - **`requestTimeout`** — per-server request timeout in ms
669
324
 
670
- | Field | Type | Default | Description |
671
- |-------|------|---------|-------------|
672
- | `transport.type` | `"stdio"` \| `"http"` | `"stdio"` | Transport mode |
673
- | `transport.port` | number | `8080` | HTTP server port |
674
- | `transport.host` | string | `"127.0.0.1"` | HTTP server bind address. For TLS-terminating reverse proxies, keep localhost binding and configure `allowedOrigins` explicitly. |
675
- | `transport.path` | string | `"/mcp"` | MCP endpoint URL path |
676
- | `transport.authToken` | string | — | Bearer token for HTTP auth (supports `${VAR}` and `{file:...}` expansion) |
677
- | `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. |
678
- | `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. |
679
- | `transport.maxPayloadSize` | number | `4194304` | Maximum request body size in bytes. Requests exceeding this limit return HTTP 413 |
325
+ You can also expand secrets in any string value with `${VAR}` (env var) or `{file:/path/to/secret}` (file-based, owner-only `0600` recommended).
680
326
 
681
- **CLI flags** (override config values):
327
+ Send `SIGHUP` to reload the config without restarting (`kill -HUP <pid>` — the PID is in `list_servers`).
682
328
 
683
- | Flag | Env Variable | Description |
684
- |------|-------------|-------------|
685
- | `--transport` | `LAZY_MCP_TRANSPORT` | Transport type (`stdio` or `http`) |
686
- | `--port` | `LAZY_MCP_PORT` | HTTP server port |
687
- | `--host` | `LAZY_MCP_HOST` | HTTP server bind address |
688
- | `--path` | `LAZY_MCP_PATH` | MCP endpoint URL path |
689
- | `--auth-token` | `LAZY_MCP_AUTH_TOKEN` | Bearer token for HTTP auth |
690
- | `--request-timeout` | `LAZY_MCP_REQUEST_TIMEOUT` | Request timeout in ms for server calls, including MCP handshake requests and remote response parsing (default: 10000) |
691
- | `--max-payload-size` | `LAZY_MCP_MAX_PAYLOAD_SIZE` | Maximum request body size in bytes (default: 4194304) |
692
-
693
- When `authToken` is set, all HTTP requests must include `Authorization: Bearer <token>` — unauthenticated requests receive `401 Unauthorized`.
694
-
695
- ### Logging Configuration
696
-
697
- lazy-mcp now emits structured logs to **stderr** (stdout remains reserved for MCP protocol traffic).
698
-
699
- **Top-level configuration** (in `servers.json`):
700
-
701
- | Field | Type | Default | Description |
702
- |-------|------|---------|-------------|
703
- | `logging.level` | `"error"` \| `"info"` \| `"debug"` | `"info"` | Minimum log level |
704
- | `logging.format` | `"json"` \| `"plain"` | `"json"` | Log output format |
705
- | `logging.dumpBodies` | boolean | `false` | Enable debug request/response body dumps |
706
- | `logging.maxBodyLogBytes` | number | `8192` | Max body-dump size in bytes before truncation |
707
- | `logging.redactKeys` | string[] | — | Additional case-insensitive keys to redact (merged with built-in defaults) |
708
-
709
- Built-in redaction includes common secret keys like `authorization`, `token`, `access_token`, `refresh_token`, `client_secret`, and `headers.authorization`.
710
-
711
- Example:
712
-
713
- ```json
714
- {
715
- "servers": [
716
- {
717
- "name": "my-server",
718
- "description": "Example",
719
- "command": ["npx", "-y", "my-mcp-server"]
720
- }
721
- ],
722
- "logging": {
723
- "level": "debug",
724
- "format": "json",
725
- "dumpBodies": true,
726
- "maxBodyLogBytes": 4096,
727
- "redactKeys": ["my_custom_secret"]
728
- }
729
- }
730
- ```
731
-
732
- Typical access log event fields include:
733
- - client source (`clientIp`, optional `forwardedFor`)
734
- - request shape (`httpMethod`, `path`, `mcpMethod`, `lazyTool`)
735
- - downstream routing (`downstreamServer`, `downstreamCommand`)
736
- - outcome (`status`, `reason`, `durationMs`)
737
-
738
- ### Health Monitoring
739
-
740
- 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.
741
-
742
- Successful probes populate the discovery cache, so subsequent `list_commands` calls return instantly from cache.
743
-
744
- **Top-level configuration** (in `servers.json`):
745
-
746
- | Field | Type | Default | Description |
747
- |-------|------|---------|-------------|
748
- | `healthMonitor.enabled` | boolean | `true` | Enable/disable background health monitoring |
749
- | `healthMonitor.interval` | number | `30000` | Interval between health checks (ms) |
750
- | `healthMonitor.timeout` | number | `10000` | Timeout per server probe (ms) |
751
- | `healthMonitor.idleTimeout` | number | `300000` | Stop probing after this much inactivity (ms). `0` = never sleep (legacy) |
752
- | `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` |
753
-
754
- To disable health monitoring:
755
- ```json
756
- {
757
- "servers": [...],
758
- "healthMonitor": { "enabled": false }
759
- }
760
- ```
761
-
762
- To increase the request timeout (e.g. for slow remote servers):
763
- ```json
764
- {
765
- "servers": [...],
766
- "requestTimeout": 30000
767
- }
768
- ```
769
-
770
- ### Config Reload (SIGHUP)
771
-
772
- You can reload the configuration without restarting the process by sending a `SIGHUP` signal:
773
-
774
- ```bash
775
- kill -HUP $(pgrep -f lazy-mcp)
776
- ```
777
-
778
- This will:
779
- - Re-read and validate the config file
780
- - Add newly configured servers (lazy connection on first use)
781
- - Remove servers no longer in config (closes connections)
782
- - Reconnect servers whose config changed (updated URL, env, etc.)
783
- - Preserve unchanged servers (keeps existing connections and caches)
784
- - Restart the health monitor and probe all servers
785
-
786
- If the new config is invalid, the reload is rejected and the current config continues running. All reload activity is logged to stderr.
787
-
788
- > **Note**: SIGHUP is not available on Windows.
329
+ For the full reference every field, OAuth flow, permission rule semantics, glob syntax, HTTP transport security, logging knobs, health-monitor tuning, and SIGHUP reload semantics — see **[doc/CONFIGURATION.md](./doc/CONFIGURATION.md)**.
789
330
 
790
331
  ## Benefits
791
332
 
@@ -802,9 +343,12 @@ If the new config is invalid, the reload is rejected and the current config cont
802
343
 
803
344
  ## Documentation
804
345
 
805
- - **[CHANGELOG.md](./CHANGELOG.md)** - Version history and release notes
806
- - **[AGENTS.md](./AGENTS.md)** - Development guide for AI coding agents (build commands, code style, testing patterns)
346
+ - **[doc/CONFIGURATION.md](./doc/CONFIGURATION.md)** - Full configuration reference (servers, permissions, OAuth, transport, logging, health monitoring, SIGHUP reload)
347
+ - **[doc/HTTP_TRANSPORT.md](./doc/HTTP_TRANSPORT.md)** - Streamable HTTP transport setup, security, and reverse-proxy guidance
348
+ - **[doc/INTEGRATION.md](./doc/INTEGRATION.md)** - Client integration examples (Claude Desktop, OpenCode, Cursor, VS Code) for stdio and HTTP modes
349
+ - **[doc/RELEASES.md](./doc/RELEASES.md)** - Release pipeline and required CI/CD variables
807
350
  - **[doc/ARCHITECTURE.md](./doc/ARCHITECTURE.md)** - Architecture overview and design patterns
808
351
  - **[doc/CONTRIBUTING.md](./doc/CONTRIBUTING.md)** - Contributing guide with common development tasks
809
352
  - **[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.
810
- - **[Configuration Reference](#configuration-reference)** - Server configuration options (above)
353
+ - **[CHANGELOG.md](./CHANGELOG.md)** - Version history and release notes
354
+ - **[AGENTS.md](./AGENTS.md)** - Development guide for AI coding agents (build commands, code style, testing patterns)