mcp-pacemaker 1.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/CONTRIBUTING.md +156 -0
  3. package/LICENSE +21 -0
  4. package/README.md +620 -0
  5. package/SECURITY.md +110 -0
  6. package/THIRD_PARTY_NOTICES.txt +366 -0
  7. package/autostart/windows/launcher.vbs +11 -0
  8. package/autostart/windows/register-task.ps1 +63 -0
  9. package/bin/cli.mjs +880 -0
  10. package/bin/config-checks.mjs +62 -0
  11. package/bin/logs.mjs +465 -0
  12. package/bin/mcp-bridge.mjs +2113 -0
  13. package/bin/pooling-config.mjs +553 -0
  14. package/bin/pooling-execution.mjs +91 -0
  15. package/bin/pooling-writer.mjs +169 -0
  16. package/bin/prewarm.mjs +152 -0
  17. package/bin/server-metrics.mjs +69 -0
  18. package/bin/service-control.mjs +124 -0
  19. package/bin/shared-sessions.mjs +1083 -0
  20. package/bin/top.mjs +115 -0
  21. package/bin/update-check.mjs +153 -0
  22. package/bin/windows/PoolingSecurityHelper.build.json +39 -0
  23. package/bin/windows/PoolingSecurityHelper.exe +0 -0
  24. package/bin/windows/src/AssemblyInfo.cs +11 -0
  25. package/bin/windows/src/PoolingSecurityHelper.cs +165 -0
  26. package/bin/windows/src/PoolingSecurityReader.cs +50 -0
  27. package/docs/shared-sessions.md +181 -0
  28. package/examples/servers.example.json +31 -0
  29. package/package.json +77 -0
  30. package/supervisor/bridge-child.mjs +14 -0
  31. package/supervisor/supervise.mjs +172 -0
  32. package/supervisor/supervise.ps1 +23 -0
  33. package/supervisor/supervise.sh +11 -0
  34. package/tools/windows-security-helper/README.md +95 -0
  35. package/tools/windows-security-helper/build.ps1 +262 -0
  36. package/ui/dist/THIRD_PARTY_NOTICES.txt +366 -0
  37. package/ui/dist/assets/index-CIy7uxfd.css +1 -0
  38. package/ui/dist/assets/index-CtaaQsB9.js +8 -0
  39. package/ui/dist/index.html +14 -0
  40. package/ui/dist/third-party-manifest.json +579 -0
package/README.md ADDED
@@ -0,0 +1,620 @@
1
+ # mcp-pacemaker
2
+
3
+ **A local process supervisor and transport bridge for your MCP servers.** Manage server
4
+ lifetime outside your editor or CLI, with opt-in pre-warming and compatible session sharing.
5
+ The long-running bridge has no third-party runtime dependencies; the setup CLI and dashboards
6
+ have their own dependencies.
7
+
8
+ [Install from npm](https://www.npmjs.com/package/mcp-pacemaker) ·
9
+ [Report a problem](https://github.com/girishkvs/mcp-pacemaker/issues) ·
10
+ [Release notes](https://github.com/girishkvs/mcp-pacemaker/releases)
11
+
12
+ MCP hosts — editors (VS Code, Cursor, Claude Desktop) **and** CLIs (Claude Code, Copilot CLI,
13
+ Codex, Gemini) — run stdio MCP servers as child processes. When the host restarts, crashes, or
14
+ exits (every one-shot CLI run), **those servers die**. `mcp-pacemaker` runs your servers as
15
+ children of a small, long-lived bridge and exposes each over the MCP HTTP+SSE / Streamable HTTP
16
+ transport. Multiple hosts use one bridge, with an isolated child per session by default.
17
+ Opt into pre-warming or reuse one initialized child for compatible stateless tools sessions
18
+ with [shared mode](#shared-mode). Wire hosts to one bridge, or give each its own.
19
+ A cross-platform supervisor + OS auto-start keep the bridge itself alive across reboots and
20
+ sleep/wake, and a pluggable auth layer injects a fresh token per request for HTTP servers.
21
+
22
+ After a restart, a returning client can reuse a recorded session ID to re-establish its handshake
23
+ against a fresh server. This does not restore arbitrary tool state or preserve TCP connections.
24
+ Clients that stop retrying after a dropped connection may still need a reload; socket handoff
25
+ is separate planned work.
26
+
27
+ ## Quick start
28
+
29
+ This README describes **1.3.x legacy behavior**. New installations should use the current
30
+ [2.0.x documentation](https://github.com/girishkvs/mcp-pacemaker/blob/v2.0.1/README.md).
31
+ See the [support policy](SECURITY.md#supported-versions) for the legacy maintenance window.
32
+
33
+ Use a maintained **Node.js 22 or 24** release; see [installation requirements](#install)
34
+ before setup. `2.0.1` is available from the official npm package linked above.
35
+
36
+ The compatibility checks cover **1.3.0 / 2.0.0** and the **1.3.1 / 2.0.1** patch pair.
37
+ This describes tested version pairs, not npm publication status.
38
+ A 2.x client handles legacy immediate saves. A 1.x client cannot write pooling settings to a
39
+ 2.x bridge: update the CLI and refresh old dashboard tabs. Before downgrading, settle pending
40
+ or interrupted transactions with 2.x. See the [real-version gates](CONTRIBUTING.md#real-version-compatibility-gates)
41
+ for exact pairs; other minor versions are not covered.
42
+
43
+ ```bash
44
+ # Current line for new installations:
45
+ npm install -g mcp-pacemaker@2.0.1
46
+ mcp-pacemaker --version
47
+ ```
48
+
49
+ The version check prints `2.0.1` and does not run setup. Before setup, have a readable
50
+ JSON host configuration containing server definitions, or prepare
51
+ [`~/.mcp-pacemaker/servers.json`](#serversjson). Import sources are `vscode`, `cursor`,
52
+ `claude` (Claude Desktop), `copilot-cli` and `gemini`. Codex and Claude Code are wiring
53
+ destinations, not supported import sources.
54
+
55
+ Then start interactive setup:
56
+
57
+ ```bash
58
+ mcp-pacemaker init
59
+ ```
60
+
61
+ `init` does the setup:
62
+
63
+ 1. **detects** every MCP host you have installed — editors and CLIs,
64
+ 2. **imports one source host** into `~/.mcp-pacemaker/servers.json`, or reuses that file if it already exists,
65
+ 3. asks for confirmation, then **wires** the selected hosts to the bridge,
66
+ 4. **registers** OS auto-start so the bridge returns after a reboot,
67
+ 5. **starts** the bridge and prints what it wired.
68
+
69
+ **Setup changes local configuration and auto-start.** Importing can create or update
70
+ `~/.mcp-pacemaker/servers.json` before the wiring confirmation. To inspect host changes,
71
+ use the manual import/plan steps below before running `install`. Existing JSON/TOML host
72
+ files get a `.bak` before replacement; Claude Code is configured through its native CLI.
73
+ Keep your own protected configuration backup, especially before repeating setup.
74
+
75
+ `--from` selects the import source; `--client` selects wiring destinations. For example,
76
+ `mcp-pacemaker init --from vscode --client codex` imports VS Code's server definitions and
77
+ wires Codex. Without `--from`, setup prefers the first detected JSON host when importing;
78
+ it does not merge every detected host. Omit `--yes` when you want the confirmation prompt.
79
+
80
+ After setup, bare `mcp-pacemaker` reports status:
81
+
82
+ ```bash
83
+ mcp-pacemaker status # status without entering first-run setup
84
+ mcp-pacemaker top # live terminal dashboard
85
+ mcp-pacemaker doctor # diagnose config, reachability, wiring
86
+ ```
87
+
88
+ <details>
89
+ <summary>Prefer to drive it yourself? The same steps, one at a time</summary>
90
+
91
+ `plan`, `install` and `emit` accept any [supported host](#supported-hosts) as a wiring target.
92
+ `import --from` accepts the five JSON import sources listed above. Run import first unless
93
+ you already have `servers.json`; `plan` needs that file.
94
+
95
+ ```bash
96
+ mcp-pacemaker import --from <source> # write servers.json from one JSON host config
97
+ mcp-pacemaker plan --client <host> # dry run: show what install would change
98
+ mcp-pacemaker install --client <host> # wire one host (--port N gives it its own bridge)
99
+ mcp-pacemaker start # start the bridge
100
+ ```
101
+
102
+ ```bash
103
+ # e.g. import from Cursor, then wire three hosts to the same bridge
104
+ mcp-pacemaker import --from cursor
105
+ mcp-pacemaker install --client cursor
106
+ mcp-pacemaker install --client claude-code
107
+ mcp-pacemaker install --client gemini
108
+ ```
109
+
110
+ Run `install` once per host you want wired; repeat with `--port N` instead to give a host its
111
+ own bridge. `emit` prints the config entries without writing anything, if you'd rather paste
112
+ them in.
113
+
114
+ </details>
115
+
116
+ ## Which process-sharing mode?
117
+
118
+ Sharing a bridge does not automatically share a server process.
119
+
120
+ | Mode | Process behavior | Use when |
121
+ |---|---|---|
122
+ | `isolated` (default) | One child per session | The server needs per-client workspace, account or conversation state |
123
+ | `pool` (opt-in) | Pre-started, uninitialized children; each is assigned exclusively to one session | Startup is slow but sessions must remain separate |
124
+ | `shared` (opt-in) | One initialized child for compatible sessions | Tools are stateless, inputs are explicit and the credential context is common |
125
+
126
+ Shared clients must send identical initialization parameters, including `clientInfo`, and
127
+ empty client capabilities. It is not automatic cross-editor sharing. See
128
+ [shared-mode limits](#shared-mode) before enabling it.
129
+
130
+ ## Choosing a transport tool
131
+
132
+ Documentation checked September 27, 2026; this is a use-case guide, not a feature benchmark.
133
+
134
+ | Tool | Consider it when |
135
+ |---|---|
136
+ | [Supergateway](https://github.com/supercorp-ai/supergateway) | You want command-line conversion between stdio and HTTP/SSE/WebSocket transports |
137
+ | [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy) | You want a Python-based adapter between local stdio clients/servers and remote MCP transports |
138
+ | **mcp-pacemaker** | You want workstation process supervision, host wiring, auto-start, lifecycle diagnostics and explicit isolation/pooling/sharing policies |
139
+
140
+ These projects have overlapping capabilities. Reconnecting to a fresh server is not recovery
141
+ of arbitrary application state, and a transport bridge is not a sandbox for untrusted tools.
142
+
143
+ ## Supported hosts
144
+
145
+ Editors and CLIs are auto-detected and wired to the bridge. Most hosts get Streamable HTTP
146
+ (`/<name>/mcp`); Cursor and Claude Desktop are wired over SSE (`/<name>/sse`) until Streamable
147
+ HTTP is verified there. The bridge centralizes auth, so hosts talk to it unauthenticated over
148
+ loopback.
149
+
150
+ | Host | Type | Config | Wiring | Transport |
151
+ |---|---|---|---|---|
152
+ | VS Code | editor | `Code/User/mcp.json` | direct edit | Streamable HTTP |
153
+ | Cursor | editor | `~/.cursor/mcp.json` | direct edit | SSE |
154
+ | Claude Desktop | editor | `claude_desktop_config.json` | direct edit | SSE |
155
+ | Claude Code | CLI | `~/.claude.json` | native `claude mcp add` | Streamable HTTP |
156
+ | Copilot CLI | CLI | `~/.copilot/mcp-config.json` | direct edit | Streamable HTTP |
157
+ | Codex CLI | CLI | `~/.codex/config.toml` | TOML (comment-preserving) | Streamable HTTP |
158
+ | Gemini CLI | CLI | `~/.gemini/settings.json` | direct edit | Streamable HTTP |
159
+
160
+ **Topologies:** wire every host to one shared bridge (default), or give each host its own bridge
161
+ (`install --client X --port N`) — or any mix. `status`/`doctor` report all bridges + wired hosts.
162
+
163
+ ## Install
164
+
165
+ Use a maintained **Node.js 22 or 24** release. Node 20 remains a compatibility target but is
166
+ end-of-life, not a recommended secure runtime. The bridge has no npm runtime dependencies;
167
+ the setup CLI and dashboard have their own dependencies.
168
+
169
+ Install the released package from [npm](https://www.npmjs.com/package/mcp-pacemaker).
170
+ Its repository link should point to
171
+ [`girishkvs/mcp-pacemaker`](https://github.com/girishkvs/mcp-pacemaker).
172
+ For new installations, use the current line rather than this branch's legacy save behavior:
173
+
174
+ ```bash
175
+ npm install -g mcp-pacemaker@2.0.1
176
+ mcp-pacemaker --version
177
+ ```
178
+
179
+ The current line uses the `latest` npm channel; the maintenance line uses `legacy` when that
180
+ channel has been published. Before deliberately choosing legacy behavior, check that channel
181
+ and choose its exact published version:
182
+
183
+ ```bash
184
+ npm view mcp-pacemaker dist-tags --json
185
+ ```
186
+
187
+ GitHub tags and npm publication are separate: a tag does not establish that the same
188
+ version or channel is available on npm, or that its package bytes are identical.
189
+ `@1` and `@2` are version ranges, not the maintained channels. They can select a version
190
+ withdrawn from a channel. `upgrade --self` resolves the installed major's channel and prints
191
+ an exact-version command; it never installs or downgrades anything.
192
+
193
+ Use a durable installation directory for a long-running service. Do not register autostart
194
+ from an `npx` cache: that cache may be removed later. Bare CLI invocation can enter the setup
195
+ wizard; use `--version` for a read-only installation check.
196
+
197
+ Windows setup also needs PowerShell 7 (`pwsh`) and Windows Script Host for its launcher.
198
+ Automatic configuration edits need .NET Framework 4.6.2 or newer. The bundled helper is
199
+ unsigned; npm provenance is not Authenticode signing or an application-control allowlist.
200
+ Windows ARM64 is not validated. Legacy automatic edits still fail closed when the caller
201
+ cannot read the audit policy; this patch does not adopt 2.x Windows save behavior.
202
+
203
+ ### Command reference
204
+
205
+ | Command | What it does |
206
+ |---|---|
207
+ | `init [--client a,b] [--yes]` | **The one you want.** Detect hosts, import, wire, auto-start, launch |
208
+ | `status` | Bridges, wired hosts, servers, and any server currently failing |
209
+ | `reload` | Re-read `servers.json` into the running bridge without restarting it |
210
+ | `doctor` | Diagnose config, bridge reachability, host wiring |
211
+ | `prewarm [--port N] [--json]` | One view of stdio candidates, pools, shared-child state and measured process starts |
212
+ | `prewarm --enable <server> [--count N]` | Explicitly enable warm slots; `--disable <server>` turns them off |
213
+ | `logs [--follow] [--since 2h] [--server name] [--grep text]` | Read and filter durable logs, including retained rotation |
214
+ | `top` / `dashboard` | Live terminal UI / web dashboard |
215
+ | `plan --client <host>` | Dry run — show exactly what `install` would change |
216
+ | `import --from <source>` | Build `~/.mcp-pacemaker/servers.json` from one supported JSON host config |
217
+ | `install --client <host> [--port N]` | Wire one host (its own `--port` gives it its own bridge) |
218
+ | `emit --client <host>` | Print the config entries without writing anything |
219
+ | `start` / `stop` | Start or stop bridges (`--port` for one, else all) |
220
+ | `upgrade [--self]` | Re-wire hosts; `--self` only prints same-major, exact-version installation guidance |
221
+ | `update-check [--json]` | Compare the installed version with its maintained npm channel |
222
+ | `uninstall` | Stop managed bridges, remove auto-start, restore available host-file `.bak` copies; native entries are not restored |
223
+
224
+ > Package installation does not upgrade a running backend. `upgrade` rewires host configuration;
225
+ > it does not replace package files, restart an adopted backend, or update its autostart path.
226
+
227
+ > On start, pacemaker **probes the port**: it *adopts* an existing pacemaker bridge and *refuses to collide* with a foreign service (the wizard offers another port).
228
+
229
+ For a supervisor created by these patch releases, `stop --port N` verifies the matching
230
+ installation/configuration identity, waits for shutdown, and holds managed autostart until an
231
+ explicit `start --port N`. Normal OS shutdown does not create that hold. Use the matching
232
+ installation's CLI. Older or directly launched bridges have no managed stop record and are
233
+ refused rather than reported as stopped; disable their OS autostart and verify shutdown first.
234
+
235
+ On Linux/macOS the control socket lives beside the configuration, independent of `TMPDIR`.
236
+ Its complete path must fit within 103 UTF-8 bytes. `install` and `init` check this before
237
+ changing configuration or autostart; use a shorter configuration location rather than
238
+ relying on an implicit socket-location fallback.
239
+
240
+ Isolated and pooled stdio servers support HTTP+SSE (`/<name>/sse`) and Streamable HTTP
241
+ (`/<name>/mcp`). Shared mode requires Streamable HTTP.
242
+
243
+ `install` backs up an existing JSON/TOML host file to `.bak`; repeating it replaces that backup.
244
+ `uninstall` restores available host-file backups, leaves files without backups as-is, and
245
+ keeps `servers.json`. Native Claude Code registrations are not backed up or automatically
246
+ restored; record their original definitions separately and restore them through Claude Code.
247
+
248
+ ### Replacing or rolling back a running installation
249
+
250
+ Do not overwrite a package directory while its supervisor or bridge is running. Workers and
251
+ native helpers can load later, and dashboard assets are read from disk: replacement can mix
252
+ old and new code even when the original process still reports its old version.
253
+
254
+ Identify the selected port, installation directory, configuration directory and OS autostart
255
+ entry first. Settle or cancel 2.x batches, recover interrupted transactions with 2.x, and keep
256
+ a protected configuration backup. Disable the selected autostart entry and stop its supervisor
257
+ and bridge before replacing files; prove they cannot restart during replacement. Keep the
258
+ known-good installation until the new backend's version and instance identity are confirmed.
259
+ Re-register the intended autostart path, refresh dashboard tabs, and confirm host wiring.
260
+
261
+ One global npm prefix holds one package version and its shared command names. Installing the
262
+ other major there replaces it. Separate ports do not isolate credentials, nonces, logs or
263
+ session files: manual side-by-side operation needs separate durable code roots and separate
264
+ configuration directories, plus separate host/HOME state for CLI-managed setup.
265
+ `init/import --config` selects a source host configuration, not an isolated runtime profile.
266
+ There is no package `--profile` or `MCP_HOME` switch. Do not use an unscoped stop/uninstall
267
+ operation when another instance must remain running.
268
+
269
+ ## Dashboards
270
+
271
+ Two views of the same live state. Neither is required — the bridge runs headless — but both are
272
+ useful for answering "is this server actually up, and who is using it?"
273
+
274
+ ### Web dashboard
275
+
276
+ `mcp-pacemaker dashboard`, or open `http://127.0.0.1:<port>/ui`. It streams over SSE, so it
277
+ updates on its own without polling or a refresh.
278
+
279
+ ![Web dashboard: one card per server, showing sessions, warm pool, pids and connected agents](docs/images/dashboard.png)
280
+
281
+ Per server it shows the transport (`stdio` / `http`), the sharing policy (`pool` when pre-warming),
282
+ live sessions against the cap, request count, the actual OS pids, the warm-pool count, how long
283
+ ago it was last used, and **which clients are connected** — above, one server is shared by
284
+ VS Code and Claude Code while others are held by Copilot, Cursor and Codex. `Recycle` restarts
285
+ that server's processes; it is disabled for servers with no session to restart. The live log at
286
+ the bottom is the bridge's own log, filterable.
287
+
288
+ ### Pre-warming controls
289
+
290
+ The **Pre-Warming** tab puts every stdio candidate, current pool and shared server in one table.
291
+ It shows cold-start p50/p95, observed peak sessions, process starts, warm/target counts,
292
+ shared-child state and pending work, and an action.
293
+ HTTP proxies are excluded because the bridge does not launch their processes.
294
+
295
+ Choose **Enable pre-warming (N)** on a slow-server card or in the table. The button shows
296
+ the exact warm count and the extra resident-process cost before you click. Nothing enables
297
+ it automatically. **Disable pre-warming** removes idle warm children without killing active
298
+ sessions; **Undo** restores the prior config only if no intervening edit has occurred.
299
+
300
+ The same controls are available without a browser:
301
+
302
+ ```sh
303
+ mcp-pacemaker prewarm --port 8791
304
+ mcp-pacemaker prewarm --port 8791 --enable filesystem --count 2
305
+ mcp-pacemaker prewarm --port 8791 --disable filesystem
306
+ # An applied change prints its own --server / --undo command.
307
+ ```
308
+
309
+ Use `--config <path>` when the bridge's config and admin nonce are outside the default
310
+ directory. `--json` returns the measured snapshot. The CLI and UI use the same server
311
+ eligibility and recommendation data; a missing latency sample is not reported as a zero-cost
312
+ startup. Config writes preserve unrelated JSON and permissions, create `.bak`, and require
313
+ a matching content revision. Conflicts are shown rather than retried over someone else's edit.
314
+ Config remains strict JSON, not JSONC.
315
+
316
+ On Windows, automatic edits require readable audit policy as well as ordinary file access.
317
+ If security cannot be verified or preserved, the action is refused before config data is written;
318
+ the bridge does not elevate itself. Existing sessions keep running. Configuration writes run in
319
+ a serialized worker so permission inspection does not block MCP traffic.
320
+
321
+ Windows security inspection uses the bundled, own-source .NET Framework helper rather than
322
+ starting PowerShell or compiling code on each write. Automatic edits require .NET Framework
323
+ 4.6.2 or later; modern Windows includes it. The helper's source, build instructions and
324
+ integrity metadata are [included in the package](tools/windows-security-helper/README.md).
325
+ Windows ARM64 execution is not yet validated.
326
+
327
+ Pooling writes have a nine-second budget, including queueing and permission inspection.
328
+ Expired or cancelled work cannot start a file replacement. A replacement already in progress
329
+ can finish after the deadline: the action reports an unknown or late-committed outcome, and
330
+ the bridge reloads the saved configuration. It never automatically retries or claims that a
331
+ timed-out write was rolled back. Reread the settings before taking another action.
332
+ If the writer worker exits, further automatic edits are refused until the bridge restarts;
333
+ existing MCP sessions continue.
334
+
335
+ ### Health
336
+
337
+ The `Health` tab runs the same checks as `mcp-pacemaker doctor`, so you can see them without a
338
+ terminal.
339
+
340
+ ![Health tab: Node version, config validity, per-server status and the admin nonce path](docs/images/health.png)
341
+
342
+ It checks the Node version, that the config parses, every server definition (including a stdio
343
+ server whose relative path will not resolve from the directory it would run in — a failure that
344
+ is otherwise silent until a client first calls it, and a server named after one of the bridge's
345
+ own routes, which is unreachable), any server currently failing, and that the admin nonce file
346
+ exists.
347
+
348
+ ### Terminal dashboard
349
+
350
+ `mcp-pacemaker top` is the same data as an Ink TUI, for when a browser is inconvenient:
351
+
352
+ ```
353
+ 🫀 mcp-pacemaker top ● :8850 · up 176s · v1.1.0
354
+ SERVER TYPE HEALTH SESS WARM REQ PID TOKEN ERROR / CLIENTS
355
+ filesystem stdio ok 2 1/1 8 186276,12 - ⇄ Visual Studio Code,Claude Code
356
+ github stdio ok 1 - 2 146436 - ⇄ GitHub Copilot
357
+ sqlite stdio ok 1 - 2 102108 - ⇄ Cursor
358
+ slow-tool stdio ok 1 - 2 103776 - ⇄ Codex CLI
359
+ search-api http FAIL×3 0 - 4 - 42m upstream rejected the crede
360
+ docs-api http ? 0 - 0 - -
361
+ ↑↓ select · r recycle · q quit
362
+ ```
363
+
364
+ `HEALTH` is `ok` when the last request to that server succeeded, `FAIL×n` after n consecutive
365
+ failures, and `?` when nothing has called it yet — deliberately not `ok`, since no evidence is not
366
+ the same as working. `WARM` shows `warm/minWarm` for pooled servers and `-` for the rest, `TOKEN`
367
+ the time left on a cached credential, and the last column either the connected clients or the last
368
+ error. `↑↓` selects a row and `r` recycles it.
369
+
370
+ ## Reading durable logs
371
+
372
+ ```sh
373
+ mcp-pacemaker logs --since 2h --server filesystem
374
+ mcp-pacemaker logs --follow --grep timeout
375
+ mcp-pacemaker logs --config /path/to/servers.json --since 2026-09-07T12:00:00Z
376
+ ```
377
+
378
+ `logs` reads `bridge.log.1` then `bridge.log` beside the selected config, including when
379
+ the bridge is stopped. `--since` accepts durations in seconds/minutes/hours/days or an ISO
380
+ timestamp with timezone. `--server` matches the exact log-header server name; `--grep` is
381
+ a case-insensitive literal match, not a regular expression. Matching multiline records retain
382
+ their header and context.
383
+
384
+ `--follow` handles complete new lines, split UTF-8 writes, truncation and rollover until
385
+ interrupted. It warns if rotation has already discarded an unread generation. Only retained
386
+ history is available; it cannot recover logs the bridge has deleted. Individual lines over
387
+ 1 MiB fail explicitly rather than being silently truncated.
388
+
389
+ ## Editing servers.json while it runs
390
+
391
+ Save the file and the bridge picks it up. Servers whose definition did not change are left
392
+ completely alone — same processes, same live sessions — so adding one server does not cost you the
393
+ fourteen that were already working:
394
+
395
+ ```bash
396
+ $ mcp-pacemaker reload # or just save the file; the bridge watches it
397
+ ✓ reloaded /Users/you/.mcp-pacemaker/servers.json on :8850
398
+ · added: postgres
399
+ · changed: github
400
+ · 2 session(s) restarted; untouched servers kept theirs
401
+ ```
402
+
403
+ A file that does not parse, or a server with neither `command` nor `url`, is **rejected whole** —
404
+ the bridge logs why and keeps serving the last good config, so a half-written save cannot take your
405
+ servers down. Set `MCP_CONFIG_WATCH=0` to require an explicit `reload` instead of watching.
406
+
407
+ ## Server health
408
+
409
+ Every server carries a health verdict in `/api/status`, `doctor`, `top` and the dashboard:
410
+
411
+ | State | Meaning |
412
+ |---|---|
413
+ | `ok` | The last request to this server succeeded |
414
+ | `failing` | Recent requests are failing, with a count of how many in a row |
415
+ | `unknown` | Nothing has called it yet |
416
+
417
+ This is passive and free — it is derived from traffic the bridge is already proxying. A JSON-RPC
418
+ error *from* the server counts as healthy (it answered); a 401/403, a 5xx, a spawn failure or a
419
+ non-zero exit counts as failing, and the state clears the moment a request succeeds again.
420
+
421
+ A server nobody calls stays `unknown` forever, which is honest but not much help if its credential
422
+ quietly expires overnight. `MCP_HEALTH_INTERVAL_MS` adds a periodic probe for HTTP servers so they
423
+ get a verdict without waiting for a client:
424
+
425
+ ```bash
426
+ MCP_HEALTH_INTERVAL_MS=300000 mcp-pacemaker start # probe every 5 minutes
427
+ ```
428
+
429
+ It is off by default because a probe is a real request to somebody else's service. Set
430
+ `"healthIntervalMinutes": 0` on a server to exclude just that one, or a number to give it its own
431
+ interval. stdio servers are not probed: a probe would mean spawning a process, which costs more
432
+ than the request it is meant to pre-empt.
433
+
434
+ ## HTTP API
435
+
436
+ Everything the dashboards show is plain HTTP on the same loopback port, so you can script it.
437
+ Read endpoints need no auth; the one state-changing endpoint requires a nonce.
438
+
439
+ | Endpoint | Method | Returns |
440
+ |---|---|---|
441
+ | `/status` | GET | Tiny liveness probe: `ok`, `service`, `version`, `port` and the server names. Used to tell a pacemaker bridge apart from a foreign service on the same port |
442
+ | `/api/status` | GET | Full snapshot — every server with sessions, pids, warm count, request count, `health`, `lastError`, connected clients |
443
+ | `/api/doctor` | GET | The health checks, as JSON |
444
+ | `/api/events` | GET (SSE) | The same snapshot pushed every 2s — what the dashboard consumes |
445
+ | `/api/logs` | GET (SSE) | Bridge log: replays the last 100 lines, then streams. For anything older, read `bridge.log` next to your config |
446
+ | `/admin/recycle/<name>` | POST | Restart a server's processes. Omit `<name>` to recycle everything |
447
+ | `/admin/reload` | POST | Re-read `servers.json`. Only servers whose definition changed are restarted |
448
+ | `/admin/servers/<name>/pooling` | POST | Explicit enable/disable/undo with admin nonce, bounded input and a matching config revision |
449
+ | `/<name>/mcp` | POST/GET/DELETE | Streamable HTTP transport for that server |
450
+ | `/<name>/sse` | GET | HTTP+SSE transport for that server |
451
+ | `/.well-known/oauth-protected-resource/<name>` | GET | OAuth discovery relayed from the upstream, for HTTP servers where the client authenticates |
452
+
453
+ ```bash
454
+ # is a server actually running, and who is using it?
455
+ curl -s http://127.0.0.1:8850/api/status | jq '.servers[] | {name, sessions, pids, clients}'
456
+
457
+ # watch the bridge log
458
+ curl -N http://127.0.0.1:8850/api/logs
459
+
460
+ # force a restart of one server (nonce is written next to servers.json)
461
+ curl -X POST http://127.0.0.1:8850/admin/recycle/filesystem \
462
+ -H "x-mcp-nonce: $(cat ~/.mcp-pacemaker/admin.nonce)"
463
+ ```
464
+
465
+ `/admin/*` is guarded twice: the request must arrive on loopback, **and** carry the nonce that the
466
+ bridge writes to `admin.nonce` next to your config at startup. The nonce is new on every start, so
467
+ only same-box tooling that can read that file — the CLI, and the dashboard the bridge itself
468
+ served — can change anything.
469
+
470
+ ## servers.json
471
+
472
+ Config lives at `~/.mcp-pacemaker/servers.json`. `import` fills it from your existing client
473
+ config and **carries over `audience`/`auth`/`headers`**, so HTTP servers usually need no hand-editing.
474
+ See [`examples/servers.example.json`](examples/servers.example.json).
475
+
476
+ ```jsonc
477
+ {
478
+ "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
479
+
480
+ "my-api": {
481
+ "type": "http",
482
+ "url": "https://my-service.example.com/mcp",
483
+ "auth": { "type": "command", "command": "my-cli print-token", "refreshMinutes": 50 }
484
+ }
485
+ }
486
+ ```
487
+
488
+ **Auth options** (per HTTP server):
489
+
490
+ | `auth.type` | Behavior |
491
+ |---|---|
492
+ | `command` | Runs any command that prints a token on stdout; the result is cached and auto-refreshed (`refreshMinutes`). Provider-agnostic — see below. |
493
+ | `env` | Uses `process.env[var]`. |
494
+ | `static` | Fixed `header` + `value`. |
495
+ | `none` | No header is added — the **client** authenticates, and the bridge relays OAuth discovery and challenges for it. |
496
+
497
+ `auth.type: "command"` is deliberately just "run this and use what it prints", so it works with
498
+ whatever issues tokens in your environment. Some examples — none of these are special-cased:
499
+
500
+ ```jsonc
501
+ // Azure
502
+ { "type": "command", "command": "az account get-access-token --resource api://<id> --query accessToken -o tsv" }
503
+ // Google Cloud
504
+ { "type": "command", "command": "gcloud auth print-access-token" }
505
+ // AWS (e.g. a signed token from your own helper)
506
+ { "type": "command", "command": "aws-token-helper --profile prod" }
507
+ // HashiCorp Vault
508
+ { "type": "command", "command": "vault read -field=token secret/my-api" }
509
+ // 1Password / any secret manager
510
+ { "type": "command", "command": "op read op://vault/my-api/token" }
511
+ // A plain script
512
+ { "type": "command", "command": "./scripts/get-token.sh" }
513
+ ```
514
+
515
+ By default the token is sent as `Authorization: Bearer <token>`; set `header` to send it
516
+ somewhere else (for example `{ "header": "X-Api-Key" }`).
517
+
518
+ **How long a credential is cached.** If the command returns a JWT, its `exp` claim decides —
519
+ `refreshMinutes` is only an upper bound and a fallback for opaque credentials. This matters
520
+ because token CLIs serve from their own cache: `az account get-access-token` will hand back a
521
+ token it minted earlier that may be nearly spent, and trusting a flat window would keep a dead
522
+ token in play. If the upstream rejects a credential the bridge supplied, that 401 is reported on
523
+ the server (`doctor`, `/api/status` → `lastError`) and the credential is discarded rather than
524
+ reused — the client is never asked to log in for a server the bridge authenticates.
525
+
526
+ > **Azure shorthand.** A bare `"audience": "<res>"` expands to
527
+ > `az account get-access-token --resource <res> …`. That is a convenience for Azure users only —
528
+ > every other provider uses `auth.command` above.
529
+
530
+ ## Security
531
+
532
+ - The bridge binds **`127.0.0.1` only** (local loopback) — not reachable off-box.
533
+ - Tokens are **minted from your own already-authenticated CLI** and cached in memory; **never written to disk**.
534
+ - The **bridge** (the process that runs 24/7) has **zero third-party dependencies**; the setup CLI uses `commander` / `@clack/prompts` / `picocolors` / `smol-toml`, and the TUI adds `ink` / `react`.
535
+
536
+ ## Multi-agent & resource controls
537
+
538
+ Multiple hosts/agents can share one bridge — each gets its own isolated session by default,
539
+ and HTTP servers share the cached token across all of them. The dashboard, `top`, and
540
+ `mcp-pacemaker status` show **which agents are connected** to each server.
541
+
542
+ | Control | Env / config | Default | Effect |
543
+ |---|---|---|---|
544
+ | Idle reaper | `MCP_IDLE_TIMEOUT_MS` | `1800000` (30 min) | free Streamable HTTP children idle past the timeout. Safe by default because a returning client's session is transparently re-established. `0` disables |
545
+ | Concurrency cap | `MCP_MAX_SESSIONS_PER_SERVER`, or per-server `maxSessions` | `0` (unlimited) | reject new sessions past the cap (HTTP 503) |
546
+ | Queue at the cap | `MCP_QUEUE_TIMEOUT_MS` | `10000` (10s) | at the cap, wait this long for a slot before returning 503, so a burst is absorbed rather than failed. `0` rejects immediately |
547
+ | Sharing policy | per-server `sharing` (+ `minWarm`) | `isolated` | `isolated` = one child per session; `pool` pre-starts exclusive children; `shared` reuses one initialized child for compatible stateless tools sessions. Both alternatives require explicit activation |
548
+ | Cold-start gate | `MCP_MAX_CONCURRENT_SPAWNS`, `MCP_SPAWN_GATE_WAIT_MS` | `2`, `15000` | how many package-manager-backed servers may cold-start at once. `npx`, `uvx`, `dnx` and friends share one cache, and starting several together can corrupt it — this queues them. Detected from the command, or forced either way with per-server `sharedPackageCache`. Servers that use no package manager are never gated. If no slot frees within the wait, the spawn proceeds ungated rather than hold a client request. `0` disables |
549
+ | Pooling advice | `MCP_POOL_ADVICE_MS`, `MCP_WARM_MAX` | 2s, 8 | recommend pooling once a server's measured cold start passes this, and cap the suggested (and auto-sized) pool here. Advice only — the bridge never enables pooling on its own |
550
+ | Session resume | `MCP_RESUME`, `MCP_RESUME_TTL_MS` | on, 24h | re-establish a session id the bridge has not seen — after a restart, recycle or idle reap. `MCP_RESUME=0` disables |
551
+ | Scheduled recycle | per-server `recycleMinutes`, or `MCP_RECYCLE_MINUTES` | off | restart a server periodically, for servers holding a credential they can only refresh interactively |
552
+ | Request timeouts | `MCP_REQUEST_TIMEOUT_MS` / `MCP_INIT_TIMEOUT_MS` | 30s / 180s | `initialize` also pays for spawning the server, so it gets a longer budget than ordinary calls |
553
+ | Credential refresh | `MCP_TOKEN_REFRESH_LEAD_MS` | `300000` (5 min) | renew a cached token before it expires instead of discovering the expiry on a request. A JWT's own `exp` still caps how long it is cached |
554
+ | Durable log | `MCP_LOG_MAX_BYTES` | `5242880` (5 MB) | `bridge.log` next to your config, rolled to `bridge.log.1` at this size. `0` disables. The dashboard only keeps the last few hundred lines in memory, which a chatty client fills in minutes |
555
+ | Config watching | `MCP_CONFIG_WATCH` | on | reload `servers.json` when it is saved. `0` requires an explicit `mcp-pacemaker reload`. Either way, only servers whose definition changed are restarted, and an unparseable file is rejected without disturbing anything |
556
+ | Health probing | `MCP_HEALTH_INTERVAL_MS`, or per-server `healthIntervalMinutes` | off | periodically call an HTTP server so it has a health verdict before a client needs it. Passive health from real traffic is always on and costs nothing |
557
+
558
+ ### Measuring process churn
559
+
560
+ For stdio servers, `/api/status` reports `spawn.total` (successful process launches),
561
+ `spawn.attempts`, `spawn.failures`, `spawn.warmAdoptions`, `spawn.sessionStarts`, and
562
+ `spawn.sessionResumes`. Launches include classic SSE, Streamable HTTP, warm-pool refill,
563
+ and replacements after recycle or resume. Adopting an existing warm child does not count
564
+ as another launch. The unit is one bridge-launched process, not all of its descendants.
565
+
566
+ These cumulative counters do not share the 50-sample latency limit. They reset when the
567
+ bridge process restarts; compare snapshots only within the same `instanceId`. `startedAt`
568
+ identifies that interval. Save the final snapshot before a planned restart for a before/after
569
+ comparison. HTTP-proxied servers have no local process counters (`spawn: null`).
570
+
571
+ `spawn.samples` and p50/p95/max still describe the latest 50 directly observed, successful
572
+ cold initializations, not all launches. Latencies are `null` until observed; a warm child's
573
+ idle lifetime is not a cold-start sample. The existing `requests` count covers routed HTTP
574
+ requests, not just `tools/call`; do not label it as a tool-call count.
575
+
576
+ ### Shared mode
577
+
578
+ Set `"sharing": "shared"` on a stdio server only when its tools are stateless, operate on
579
+ explicit inputs and use one common credential context. This is an operator assertion, not
580
+ something latency or matching capabilities can prove. Leave workspace-, conversation- or
581
+ client-account-dependent servers isolated.
582
+
583
+ Compatible sessions have identical full initialization parameters, including `clientInfo`,
584
+ and empty client capabilities. One child receives one real initialization; each virtual session
585
+ gets its own IDs, progress, cancellation and tools-list cursors. Only the upstream's actual
586
+ tools capability is exposed. Roots, sampling, elicitation, tasks, resource subscriptions and
587
+ other stateful methods are not supported. Incompatible clients fail explicitly, without
588
+ silently falling back to a different mode.
589
+
590
+ Deleting one session leaves the others running. The initialized child is retained between
591
+ compatible reconnects for up to `sharedLingerMs` (30 minutes by default). Recycle drains work,
592
+ then replaces the generation on later use. A timeout or child exit never triggers automatic
593
+ replay of a potentially executed tool call.
594
+
595
+ The server keeps its existing credential provider and configured `recycleMinutes`. Shared
596
+ mode does not promise silent credential renewal. The dashboard and `prewarm` CLI show the
597
+ actual shared-child state, members, queued work and cumulative process launches.
598
+ See [the shared-session contract](docs/shared-sessions.md) for limits and failure semantics.
599
+
600
+ ## Uninstall
601
+
602
+ ```bash
603
+ mcp-pacemaker uninstall # use the CLI from the matching installed version
604
+ ```
605
+
606
+ This stops managed bridges, removes auto-start entries and restores available host-file
607
+ backups. It leaves files without backups and native Claude Code registrations unchanged.
608
+ It is not scoped to one port; review [replacement and rollback](#replacing-or-rolling-back-a-running-installation)
609
+ first if another instance must remain running. Do not fetch a different CLI with `npx`
610
+ to remove an existing installation.
611
+
612
+ ## Project
613
+
614
+ - [CHANGELOG.md](CHANGELOG.md) — release notes
615
+ - [CONTRIBUTING.md](CONTRIBUTING.md) — development setup and how regression tests are expected to be proven
616
+ - [SECURITY.md](SECURITY.md) — threat model and how to report a vulnerability
617
+
618
+ ## License
619
+
620
+ MIT © Girish Konda