mcp-pacemaker 2.1.1 → 2.1.2

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/CHANGELOG.md CHANGED
@@ -7,8 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.1.2] - 2026-10-26
11
+
12
+ ### Fixed
13
+
14
+ - The bridge no longer crashes when a server exits while a request is being written to it, for
15
+ example when the server restarts mid-request. The write failed with `EPIPE` and nothing
16
+ handled the error, so the whole bridge restarted and calls in flight on every server failed.
17
+ A Streamable HTTP request now gets a "server exited before answering" error, and the bridge
18
+ keeps running.
19
+ - A crash after startup now leaves its error and stack in `bridge.log`. They went only to
20
+ stderr, which nothing captures when the bridge runs as a Windows task.
21
+ - The offline release verifier (`npm run publication:verify-local`) rejected every 2.1.x local
22
+ gate run with "Supporting command vector changed". The gate binds each Git command to its
23
+ checkout with `safe.directory`, and the verifier rebuilt the commands without it. Both now
24
+ build it with the same function.
25
+
26
+ ### Security
27
+
28
+ - Web pages from other sites can no longer use the bridge. Host and Origin were checked only on
29
+ the admin and pooling routes, so a page open in the user's browser could call tools through
30
+ the bridge, including on HTTP servers the bridge adds credentials to. Every route now answers
31
+ 403 when the Host is a DNS name other than `localhost` (DNS rebinding), when the Origin is
32
+ neither the bridge nor a `localhost` or loopback page, or when the browser marks the request
33
+ as cross-site, except opening the dashboard from a link. MCP clients, the CLI and the
34
+ dashboard work as before through `localhost` or an IP address; a client that reaches the
35
+ bridge by another host name, such as `host.docker.internal`, now gets 403 and should use an
36
+ IP address.
37
+ - The dashboard is built with Vite 7.3.7. Vite's development server has advisories up to 7.3.6
38
+ (GHSA-9jrq-w75r-8gcw, GHSA-rq7h-c2jc-7f22, GHSA-698v-4r3x-3vcv, GHSA-r825-w8mm-5pxw,
39
+ GHSA-587h-7wmj-h5pm). The published package ships the prebuilt dashboard and never runs that
40
+ server; the built dashboard is unchanged.
41
+
42
+ ### Changed
43
+
44
+ - The README again compares mcp-pacemaker with Supergateway, mcp-proxy, MCP Hub and ToolHive.
45
+ Each cell was checked against the projects' October 2026 releases and is sourced in
46
+ `docs/comparison.md`, which now ships in the package.
47
+ - The npm publishing tools accept 1.x release candidates. CI validation expects the 1.x Windows
48
+ job names, and the runtime file check asks only for the pooling modules a 1.x package has. The
49
+ 1.x maintenance line now carries these same tools.
50
+
10
51
  ## [2.1.1] - 2026-10-15
11
52
 
53
+ Published to npm on 2026-10-17.
54
+
12
55
  ### Added
13
56
 
14
57
  - `upgrade --fix-legacy-access` is a one-time step for a Windows 1.3 install that was set up from
@@ -640,7 +683,8 @@ one is a trap for anyone building something similar.
640
683
  - Do not report a teardown the bridge initiated as a crash. `taskkill /F` exits non-zero, so
641
684
  every recycle, idle reap and session close was recorded as a failure, burying real ones.
642
685
 
643
- [Unreleased]: https://github.com/girishkvs/mcp-pacemaker/compare/v2.1.1...HEAD
686
+ [Unreleased]: https://github.com/girishkvs/mcp-pacemaker/compare/v2.1.2...HEAD
687
+ [2.1.2]: https://github.com/girishkvs/mcp-pacemaker/releases/tag/v2.1.2
644
688
  [2.1.1]: https://github.com/girishkvs/mcp-pacemaker/releases/tag/v2.1.1
645
689
  [2.1.0]: https://github.com/girishkvs/mcp-pacemaker/releases/tag/v2.1.0
646
690
  [2.0.2]: https://github.com/girishkvs/mcp-pacemaker/releases/tag/v2.0.2
package/CONTRIBUTING.md CHANGED
@@ -143,12 +143,12 @@ The same harness runs in both maintained branches:
143
143
 
144
144
  | Preparation | Selected legacy / current pair |
145
145
  |---|---|
146
- | Default in the selected 2.1.1 checkout | Immutable 1.3.0 / packed 2.1.1 |
147
- | Default in the 1.3.1 checkout | Packed 1.3.1 / immutable 2.0.0 |
146
+ | Default in the selected 2.1.2 checkout | Immutable 1.3.0 / packed 2.1.2 |
147
+ | Default in the 1.3.2 checkout | Packed 1.3.2 / immutable 2.0.0 |
148
148
  | `npm run compat:prepare:baseline` | Immutable 1.3.0 / immutable 2.0.0 |
149
- | `npm run compat:prepare -- --peer-root <opposite-patch-checkout>` | Packed explicit 1.x peer / packed 2.1.1 |
149
+ | `npm run compat:prepare -- --peer-root <opposite-patch-checkout>` | Packed explicit 1.x peer / packed 2.1.2 |
150
150
 
151
- These selections are not qualification evidence. The 2.1.1 candidate requires fresh
151
+ These selections are not qualification evidence. The 2.1.2 candidate requires fresh
152
152
  exact-byte gates; selecting it does not select an opposite-major patch pair.
153
153
  Earlier 1.3.1/2.0.2 and 1.3.1/2.0.1 results remain historical.
154
154
 
package/README.md CHANGED
@@ -14,7 +14,7 @@ mcp-pacemaker init # imports your servers, asks before rewiring any
14
14
  [Release notes](https://github.com/girishkvs/mcp-pacemaker/releases) ·
15
15
  [Demo and design write-up](https://girishkonda.com/projects/mcp-pacemaker/)
16
16
 
17
- > This README describes **2.1.1**.
17
+ > This README describes **2.1.2**.
18
18
  > See the [release changes](CHANGELOG.md).
19
19
 
20
20
  ## What it does
@@ -41,17 +41,17 @@ exits (every one-shot CLI run), **those servers die**.
41
41
 
42
42
  ## Quick start
43
43
 
44
- Install **mcp-pacemaker 2.1.1** from npm. Use a maintained **Node.js 22 or 24**
44
+ Install **mcp-pacemaker 2.1.2** from npm. Use a maintained **Node.js 22 or 24**
45
45
  release; Windows also needs PowerShell 7 and Windows Script Host.
46
46
  See [installation requirements](docs/install.md) before setup.
47
47
 
48
48
  ```bash
49
- npm view mcp-pacemaker@2.1.1 version
50
- npm install -g mcp-pacemaker@2.1.1
49
+ npm view mcp-pacemaker@2.1.2 version
50
+ npm install -g mcp-pacemaker@2.1.2
51
51
  mcp-pacemaker --version
52
52
  ```
53
53
 
54
- The version check prints `2.1.1` without running setup.
54
+ The version check prints `2.1.2` without running setup.
55
55
 
56
56
  > **Run it as an ordinary user.** From 2.1.0 the bridge, supervisor and default upgrades
57
57
  > refuse a privileged caller *at startup* — a non-administrator terminal on Windows and a
@@ -120,6 +120,32 @@ them in.
120
120
 
121
121
  </details>
122
122
 
123
+ ## How it compares
124
+
125
+ Checked against each project's latest release on October 16, 2026. Every cell is sourced in
126
+ [docs/comparison.md](docs/comparison.md). These projects overlap; `mcp-pacemaker` focuses on
127
+ keeping MCP servers running on a developer workstation.
128
+
129
+ | | [Supergateway](https://github.com/supercorp-ai/supergateway) | [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy) | [MCP Hub](https://github.com/ravitemer/mcp-hub) | [ToolHive](https://github.com/stacklok/toolhive) | **mcp-pacemaker** |
130
+ |---|---|---|---|---|---|
131
+ | Serves stdio servers over Streamable HTTP | ✅ | ✅ | ❌ | ✅ | ✅ |
132
+ | Serves stdio servers over SSE (legacy) | ✅ | ✅ | ✅ | ✅ (deprecated) | ✅ |
133
+ | What you install | npm, binary or Docker | PyPI or Docker | npm or Nix | Go binary + Docker or Podman | npm; the bridge has **zero** dependencies |
134
+ | Restarts crashed processes automatically | new child per connection | ❌ | manual restart (API) | ✅ | ✅ bridge and warm pools |
135
+ | Starts at OS logon | ❌ | ❌ | ❌ | desktop app only | ✅ Windows, macOS, Linux |
136
+ | Starts again after unlock or wake | ❌ | ❌ | ❌ | ❌ | ✅ Windows |
137
+ | Auth header from your own command, auto-refreshed | ❌ | ❌ | at connect only | ❌ | ✅ |
138
+ | Sessions survive a bridge restart without re-initializing | ❌ | ❌ | ❌ | Kubernetes with Redis only | ✅ |
139
+ | Timed restart to refresh an interactive login | ❌ | ❌ | ❌ | ❌ | ✅ |
140
+ | Reuses existing client MCP config files | ✅ | stdio entries only | ✅ | ❌ | ✅ |
141
+ | Wires editors and CLIs for you | ❌ | ❌ | ❌ | ✅ | ✅ |
142
+ | Web dashboard | ❌ | ❌ | ❌ | desktop app instead | ✅ |
143
+ | Terminal dashboard | ❌ | ❌ | ❌ | ✅ (experimental) | ✅ |
144
+ | HTTP control API | health checks only | `/status` only | ✅ | ✅ | ✅ |
145
+
146
+ A surviving session re-establishes the client's handshake against a fresh server; it does not
147
+ restore arbitrary tool state. A transport bridge is not a sandbox for untrusted tools.
148
+
123
149
  ## Everyday commands
124
150
 
125
151
  | Command | What it does |
@@ -167,19 +193,6 @@ loopback.
167
193
  **Topologies:** wire every host to one shared bridge (default), or give each host its own bridge
168
194
  (`install --client X --port N`) — or any mix. `status`/`doctor` report all bridges + wired hosts.
169
195
 
170
- ## Choosing a transport tool
171
-
172
- Documentation checked September 27, 2026; this is a use-case guide, not a feature benchmark.
173
-
174
- | Tool | Consider it when |
175
- |---|---|
176
- | [Supergateway](https://github.com/supercorp-ai/supergateway) | You want command-line conversion between stdio and HTTP/SSE/WebSocket transports |
177
- | [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy) | You want a Python-based adapter between local stdio clients/servers and remote MCP transports |
178
- | **mcp-pacemaker** | You want workstation process supervision, host wiring, auto-start, lifecycle diagnostics and explicit isolation/pooling/sharing policies |
179
-
180
- These projects have overlapping capabilities. Reconnecting to a fresh server is not recovery
181
- of arbitrary application state, and a transport bridge is not a sandbox for untrusted tools.
182
-
183
196
  ## Upgrades
184
197
 
185
198
  > Package installation alone does not upgrade a running backend. Use explicit `upgrade --to`
@@ -241,6 +254,7 @@ See [SECURITY.md](SECURITY.md) for the threat model and how to report a vulnerab
241
254
  - [HTTP API](docs/http-api.md) — endpoints, pooling batches and the admin nonce
242
255
  - [Upgrades and rollback](docs/upgrades.md) — managed upgrades, `--sxs`, recovery and the Windows 1.3 migration
243
256
  - [Version compatibility](docs/compatibility.md) — 1.3.x and 2.x pairs, and upgrading from 1.3.0
257
+ - [How it compares](docs/comparison.md) — sourced comparison with Supergateway, mcp-proxy, MCP Hub and ToolHive
244
258
  - [User-runtime contract](docs/execution-context.md) — what the ordinary-user check verifies
245
259
  - [Shared sessions](docs/shared-sessions.md) — the full shared-mode contract
246
260
  - [Account upgrades](docs/account-upgrades.md) — explicit original-account upgrades on Windows
package/SECURITY.md CHANGED
@@ -36,6 +36,13 @@ so a few properties matter more than usual:
36
36
  - **There is no user authentication on the proxy path.** Any local process that can open the
37
37
  port can use the bridged servers. Treat access to the port as equivalent to access to every
38
38
  credential the bridge holds.
39
+ - **Web pages from other sites are refused.** A page open in the browser can reach a loopback
40
+ port too, so every route refuses with 403 a `Host` that is a DNS name other than `localhost`
41
+ (DNS rebinding), an `Origin` that is neither the bridge itself nor a `localhost` or loopback
42
+ page, and a browser request marked `Sec-Fetch-Site: cross-site`, except opening the dashboard
43
+ from a link. This does not identify callers: with `--host 0.0.0.0`, a client that sends an IP
44
+ `Host` and no `Origin` is still served. MCP clients and the CLI send no `Origin`; have them
45
+ connect through `localhost` or an IP address.
39
46
  - **Administrative endpoints require a nonce.** `/admin/*` is guarded by a random nonce written
40
47
  to `~/.mcp-pacemaker/admin.nonce` on startup, readable only by the user running the bridge.
41
48
  This covers `/admin/reload`, which re-reads `servers.json` — see the note below on why that
@@ -219,7 +219,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
219
219
  SOFTWARE.
220
220
 
221
221
  ==============================================================================
222
- vite@7.3.6
222
+ vite@7.3.7
223
223
  Declared license: MIT
224
224
 
225
225
  --- LICENSE.md: # Vite core license ---
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mcp-pacemaker",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "mcp-pacemaker",
9
- "version": "2.1.1",
9
+ "version": "2.1.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@clack/prompts": "^1.7.0",
@@ -25,6 +25,7 @@
25
25
  */
26
26
  import http from 'node:http';
27
27
  import https from 'node:https';
28
+ import { isIP } from 'node:net';
28
29
  import { spawn, exec, execFile, execFileSync } from 'node:child_process';
29
30
  import { createHash, randomUUID } from 'node:crypto';
30
31
  import { readFileSync, existsSync, writeFileSync, appendFileSync, statSync, renameSync, watch } from 'node:fs';
@@ -188,6 +189,16 @@ const log = (m) => {
188
189
  for (const c of logClients) { try { c.write(`data: ${JSON.stringify(line)}\n\n`); } catch { /* gone */ } }
189
190
  };
190
191
 
192
+ // A crash left no trace: its stack went only to stderr, which nothing captures on Windows (see
193
+ // LOG_FILE). The monitor only records the error. The process still exits as before, and the
194
+ // supervisor restarts it.
195
+ process.on('uncaughtExceptionMonitor', (error, origin) => {
196
+ try {
197
+ const detail = error instanceof Error ? error.stack : String(error);
198
+ appendLog(`[mcp-bridge] ${new Date().toISOString()} fatal ${origin}: ${detail}`);
199
+ } catch { /* recording must never replace the original error */ }
200
+ });
201
+
191
202
  // Token expiry (seconds) for an http server's cached command/az token, if any — for the dashboard.
192
203
  //
193
204
  // A bare `audience` is shorthand for an Azure CLI token command. It is expanded in exactly one
@@ -651,6 +662,10 @@ function spawnServer(name, def) {
651
662
  child.once('error', () => {
652
663
  if (!spawned) serverMetrics.failedSpawn(name);
653
664
  });
665
+ // A child can exit between a write to it and the bridge handling that exit, for example when
666
+ // a server restarts mid-request. The write then fails with EPIPE, and a pipe 'error' with no
667
+ // listener crashes the whole bridge. The exit handler still fails the request that was waiting.
668
+ child.stdin.on('error', (error) => log(`[${name}] stdin write failed: ${error.message}`));
654
669
  child.__spawnedAt = Date.now(); // start of the cold-start clock; stopped when initialize lands
655
670
  return child;
656
671
  }
@@ -2092,7 +2107,69 @@ function proxyHttp(name, def, req, res) {
2092
2107
  }
2093
2108
 
2094
2109
  /* --------------------------------- server ---------------------------------- */
2110
+ // Browsers can reach a loopback server too. Without these checks any web page could use the
2111
+ // bridge, and the credentials it adds, through the user's browser: a cross-site POST runs a tool
2112
+ // without a CORS preflight, and DNS rebinding lets the page read the replies. The MCP spec
2113
+ // requires servers to check Origin for this reason.
2114
+ //
2115
+ // - Host: rebinding only works through a DNS name, so only `localhost` and IP addresses pass.
2116
+ // - Origin, when sent: the bridge itself (the dashboard) or another page on this machine.
2117
+ // - No Origin: clients that are not browsers send none. A browser also leaves it out of a
2118
+ // cross-site GET, such as an image, a frame or a link, which Sec-Fetch-Site still marks. Only
2119
+ // opening the dashboard itself from a link on another site is let through.
2120
+ function hostnameOf(urlText) {
2121
+ try {
2122
+ const { hostname } = new URL(urlText);
2123
+ return hostname.startsWith('[') ? hostname.slice(1, -1) : hostname;
2124
+ } catch { return ''; }
2125
+ }
2126
+
2127
+ function isLoopback(hostname) {
2128
+ const loopbackV4 = isIP(hostname) === 4 &&
2129
+ hostname.startsWith('127.');
2130
+ return hostname === 'localhost' ||
2131
+ hostname === '::1' ||
2132
+ loopbackV4;
2133
+ }
2134
+
2135
+ function isLocalRequest(req) {
2136
+ if (!req.headers.host) return false;
2137
+ const host = hostnameOf(`http://${req.headers.host}`);
2138
+ const hostIsSafe = host === 'localhost' ||
2139
+ isIP(host) !== 0;
2140
+ if (!hostIsSafe) return false;
2141
+ const origin = req.headers.origin;
2142
+ if (origin !== undefined) {
2143
+ const sameOrigin = origin === `http://${req.headers.host}`;
2144
+ const loopbackPage = isLoopback(hostnameOf(origin));
2145
+ return sameOrigin ||
2146
+ loopbackPage;
2147
+ }
2148
+ const path = req.url.split('?')[0];
2149
+ const dashboardPath = path === '/ui' ||
2150
+ path.startsWith('/ui/');
2151
+ const opensDashboard = dashboardPath &&
2152
+ req.headers['sec-fetch-mode'] === 'navigate' &&
2153
+ req.headers['sec-fetch-dest'] === 'document';
2154
+ const crossSiteFetch = req.headers['sec-fetch-site'] === 'cross-site' &&
2155
+ !opensDashboard;
2156
+ return !crossSiteFetch;
2157
+ }
2158
+
2159
+ // One line a minute explains a refusal; a page retrying in a loop must not flood the log.
2160
+ let lastRefusalLog = 0;
2161
+ function noteRefusal(req) {
2162
+ if (Date.now() - lastRefusalLog < 60000) return;
2163
+ lastRefusalLog = Date.now();
2164
+ log(`refused ${req.method} ${req.url} from Host ${req.headers.host ?? 'none'}, Origin ${req.headers.origin ?? 'none'}: not a local request`);
2165
+ }
2166
+
2095
2167
  const server = http.createServer((req, res) => {
2168
+ if (!isLocalRequest(req)) {
2169
+ noteRefusal(req);
2170
+ res.writeHead(403).end('forbidden: not a local request');
2171
+ return;
2172
+ }
2096
2173
  const url = new URL(req.url, `http://${req.headers.host}`);
2097
2174
  const [name, kind] = url.pathname.split('/').filter(Boolean);
2098
2175
  const readOnlyStatus = (name === 'status' && !kind) ||
@@ -0,0 +1,167 @@
1
+ # How mcp-pacemaker compares
2
+
3
+ Checked on October 16, 2026, from each project's documentation and source. We did not run the
4
+ other tools. Corrections are welcome: open an issue with a link.
5
+
6
+ | Project | Release checked |
7
+ |---|---|
8
+ | [Supergateway](https://github.com/supercorp-ai/supergateway) | v4.2.0 (2026-10-15) |
9
+ | [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy) | v0.13.0 (2026-10-10) |
10
+ | [MCP Hub](https://github.com/ravitemer/mcp-hub) | v4.2.1 (2025-08-22, no later commits) |
11
+ | [ToolHive](https://github.com/stacklok/toolhive) | v0.54.0 (2026-10-14), desktop app v0.40.2 |
12
+
13
+ ✅ means documented or implemented. ❌ means we did not find it in the README, docs, CLI options
14
+ or source; it does not mean the feature is impossible. Text in a cell describes partial support.
15
+ ToolHive's desktop app counts as part of ToolHive.
16
+
17
+ | | Supergateway | mcp-proxy | MCP Hub | ToolHive | **mcp-pacemaker** |
18
+ |---|---|---|---|---|---|
19
+ | Serves stdio servers over Streamable HTTP | ✅ | ✅ | ❌ | ✅ | ✅ |
20
+ | Serves stdio servers over SSE (legacy) | ✅ | ✅ | ✅ | ✅ (deprecated) | ✅ |
21
+ | What you install | npm, binary or Docker | PyPI or Docker | npm or Nix | Go binary + Docker or Podman | npm; the bridge has **zero** dependencies |
22
+ | Restarts crashed processes automatically | new child per connection | ❌ | manual restart (API) | ✅ | ✅ bridge and warm pools |
23
+ | Starts at OS logon | ❌ | ❌ | ❌ | desktop app only | ✅ Windows, macOS, Linux |
24
+ | Starts again after unlock or wake | ❌ | ❌ | ❌ | ❌ | ✅ Windows |
25
+ | Auth header from your own command, auto-refreshed | ❌ | ❌ | at connect only | ❌ | ✅ |
26
+ | Sessions survive a bridge restart without re-initializing | ❌ | ❌ | ❌ | Kubernetes with Redis only | ✅ |
27
+ | Timed restart to refresh an interactive login | ❌ | ❌ | ❌ | ❌ | ✅ |
28
+ | Reuses existing client MCP config files | ✅ | stdio entries only | ✅ | ❌ | ✅ |
29
+ | Wires editors and CLIs for you | ❌ | ❌ | ❌ | ✅ | ✅ |
30
+ | Web dashboard | ❌ | ❌ | ❌ | desktop app instead | ✅ |
31
+ | Terminal dashboard | ❌ | ❌ | ❌ | ✅ (experimental) | ✅ |
32
+ | HTTP control API | health checks only | `/status` only | ✅ | ✅ | ✅ |
33
+
34
+ ## Sources
35
+
36
+ ### Supergateway v4.2.0
37
+
38
+ - **Transports:** stdio → SSE is the default output
39
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L50-L63)); stdio →
40
+ Streamable HTTP in both directions
41
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L84-L130)).
42
+ - **Install:** npm, standalone executables
43
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L258-L294)) or Docker
44
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L334-L380)).
45
+ - **Crashed processes:** each SSE/WebSocket connection gets its own child and a reconnecting client
46
+ gets a fresh one ([CHANGELOG](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/CHANGELOG.md#L83));
47
+ when a child exits, its calls fail and the session ends
48
+ ([source](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/src/gateways/sseSession.ts#L120-L170)).
49
+ - **Logon start, unlock, timed restart:** not in the option list
50
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L13-L48),
51
+ [options](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/src/cliOptions.ts)); the only
52
+ related option, `--sessionTimeout`, expires idle sessions.
53
+ - **Auth:** static `--header` and `--oauth2Bearer`
54
+ ([options](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/src/cliOptions.ts#L106-L117));
55
+ `${VAR}` in the config file is expanded once at load
56
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L209)).
57
+ - **Sessions:** stateful sessions are kept in memory, and an unknown session ID after a restart gets
58
+ 404 ([source](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/src/gateways/statefulSessions.ts#L211-L232)).
59
+ Stateless mode has no session to lose
60
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L107-L129)).
61
+ - **Config reuse and wiring:** `--config` reads a Claude Desktop `mcpServers` file
62
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L179-L218)); client
63
+ entries are written by hand
64
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L382-L467)).
65
+ - **Dashboards and API:** health endpoints only
66
+ ([README](https://github.com/supercorp-ai/supergateway/blob/v4.2.0/README.md#L39-L40)).
67
+
68
+ ### mcp-proxy v0.13.0
69
+
70
+ - **Transports:** `/sse` and `/mcp` server routes
71
+ ([source](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/src/mcp_proxy/mcp_server.py#L89-L93));
72
+ stdio → SSE server mode
73
+ ([README](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/README.md#L98-L170)).
74
+ - **Install:** uv, pipx or Docker, Python 3.10 or newer
75
+ ([README](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/README.md#L222-L261)).
76
+ - **Crashed processes, logon start, timed restart:** not in the option list
77
+ ([README](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/README.md#L318-L398)). The Docker
78
+ Compose example relies on Docker's `restart: unless-stopped`
79
+ ([README](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/README.md#L297-L316)).
80
+ - **Auth:** static headers, `API_ACCESS_TOKEN`, OAuth2 client credentials and Basic/Digest
81
+ ([README](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/README.md#L56-L74)). OAuth2 tokens
82
+ refresh, but no user command produces the header.
83
+ - **Sessions:** kept in memory (`event_store=None`)
84
+ ([source](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/src/mcp_proxy/mcp_server.py#L89-L93));
85
+ `--stateless` avoids sessions.
86
+ - **Config reuse and wiring:** `--named-server-config` reads the stdio entries of an `mcpServers` file
87
+ ([source](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/src/mcp_proxy/config_loader.py#L15-L70));
88
+ client entries are written by hand
89
+ ([README](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/README.md#L76-L96)).
90
+ - **Dashboards and API:** a read-only `/status` JSON
91
+ ([source](https://github.com/sparfenyuk/mcp-proxy/blob/v0.13.0/src/mcp_proxy/mcp_server.py#L48-L73)).
92
+
93
+ ### MCP Hub v4.2.1
94
+
95
+ - **Transports:** its single `/mcp` endpoint is SSE
96
+ ([source](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/src/server.js#L345-L372)); Streamable HTTP
97
+ is only used to connect to remote servers
98
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L19-L20)).
99
+ - **Install:** npm (one bundled file) or a Nix flake
100
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L136-L140)).
101
+ - **Crashed processes:** restart is manual through `POST /api/restart`
102
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L791)); we did not find an
103
+ automatic restart of a local server in the source
104
+ ([source](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/src/MCPConnection.js#L626-L631)).
105
+ - **Logon start:** not documented; the 2.2.0 notes suggest running it under systemd yourself
106
+ ([CHANGELOG](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/CHANGELOG.md#L430-L436)).
107
+ - **Auth:** headers accept `${cmd: …}`, which runs when the server connects
108
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L255-L262),
109
+ [source](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/src/MCPConnection.js#L161-L164)).
110
+ - **Sessions:** SSE sessions are kept in memory
111
+ ([source](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/src/mcp/server.js#L473-L490)).
112
+ - **Config reuse and wiring:** existing `.vscode/mcp.json` and `mcpServers` files work directly
113
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L168-L253)); clients are
114
+ pointed at the hub by hand
115
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L51-L60)).
116
+ - **Dashboards and API:** REST API
117
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L422-L800)); a web UI and TUI
118
+ are listed as to-do items
119
+ ([README](https://github.com/ravitemer/mcp-hub/blob/v4.2.1/README.md#L1262-L1266)).
120
+
121
+ ### ToolHive v0.54.0 and desktop app v0.40.2
122
+
123
+ - **Transports:** `--proxy-mode` is Streamable HTTP by default; SSE is deprecated
124
+ ([CLI docs](https://docs.stacklok.com/toolhive/reference/cli/thv_run)).
125
+ - **Install:** a Go binary that needs a container runtime
126
+ ([install](https://docs.stacklok.com/toolhive/guides-cli/install#prerequisites)), or the desktop app
127
+ ([install](https://docs.stacklok.com/toolhive/guides-ui/install)).
128
+ - **Crashed processes:** exited containers restart with backoff
129
+ ([source](https://github.com/stacklok/toolhive/blob/v0.54.0/pkg/workloads/manager.go#L534-L640)).
130
+ - **Logon start:** the desktop app's "Start on login" setting
131
+ ([docs](https://docs.stacklok.com/toolhive/guides-ui/install#application-settings)); the CLI has no
132
+ autostart command.
133
+ - **Auth:** OAuth/OIDC with automatic refresh, a static bearer token read once, forwarded headers
134
+ and token exchange
135
+ ([CLI docs](https://docs.stacklok.com/toolhive/reference/cli/thv_run)).
136
+ - **Sessions:** with Redis session storage, clients can reconnect and resume; with local storage
137
+ they re-initialize
138
+ ([architecture](https://github.com/stacklok/toolhive/blob/v0.54.0/docs/arch/13-vmcp-scalability.md#L193-L210)).
139
+ - **Timed restart:** restarts are on demand only
140
+ ([docs](https://docs.stacklok.com/toolhive/guides-cli/manage-mcp-servers#lifecycle-management)).
141
+ - **Config reuse and wiring:** `thv client setup` and `thv client register` configure 26 clients
142
+ ([CLI docs](https://github.com/stacklok/toolhive/blob/v0.54.0/docs/cli/thv_client_register.md#L20-L46));
143
+ import and export cover only ToolHive's own run configuration
144
+ ([CLI docs](https://github.com/stacklok/toolhive/blob/v0.54.0/docs/cli/thv_export.md#L20-L24)).
145
+ - **Dashboards and API:** `thv tui` (experimental)
146
+ ([CLI docs](https://github.com/stacklok/toolhive/blob/v0.54.0/docs/cli/thv_tui.md#L14-L20)) and
147
+ `thv serve`
148
+ ([CLI docs](https://github.com/stacklok/toolhive/blob/v0.54.0/docs/cli/thv_serve.md#L18-L36)); the
149
+ web console is in the enterprise edition only
150
+ ([platform](https://docs.stacklok.com/platform)).
151
+
152
+ ### mcp-pacemaker
153
+
154
+ - **Transports and hosts:** see [Supported hosts](../README.md#supported-hosts).
155
+ - **Install:** the npm dependencies belong to the setup CLI and terminal dashboard; the bridge
156
+ imports only Node.js built-ins.
157
+ - **Crashed processes:** the supervisor restarts the bridge, and warm-pool children are replaced
158
+ with backoff. See [configuration](configuration.md).
159
+ - **Logon and unlock:** a Windows scheduled task starts at logon and on workstation unlock, which
160
+ also covers wake from sleep; macOS uses a launchd agent and Linux a systemd user unit.
161
+ - **Auth:** `auth.type: "command"` runs your command and refreshes the token on a schedule
162
+ ([configuration](configuration.md#serversjson)).
163
+ - **Sessions and timed restart:** session resume (`MCP_RESUME`) and scheduled recycle
164
+ (`recycleMinutes`) in [configuration](configuration.md).
165
+ - **Config reuse and wiring:** `import --from` and `install --client`; see the
166
+ [command reference](operations.md#command-reference).
167
+ - **Dashboards and API:** [dashboards](dashboards.md) and [HTTP API](http-api.md).
@@ -4,13 +4,13 @@ How the 1.3.x and 2.x lines work together. For npm channels and exact-version in
4
4
  [install details](install.md). To replace a running installation, see
5
5
  [upgrades and rollback](upgrades.md).
6
6
 
7
- This documentation describes **2.1.1**. For 1.3.x immediate-save behavior, see the
7
+ This documentation describes **2.1.2**. For 1.3.x immediate-save behavior, see the
8
8
  [legacy documentation](https://github.com/girishkvs/mcp-pacemaker/blob/v1.3.1/README.md)
9
9
  and [support policy](../SECURITY.md#supported-versions). Those documents do not establish
10
10
  that a legacy version is available on npm.
11
11
 
12
12
  The historical baselines remain **1.3.0 / 2.0.0**. The earlier **1.3.1 / 2.0.1** and
13
- **1.3.1 / 2.0.2** patch-pair evidence is not transferable to a new release. The **2.1.1**
13
+ **1.3.1 / 2.0.2** patch-pair evidence is not transferable to a new release. The **2.1.2**
14
14
  candidate requires fresh exact-byte compatibility and service-replacement gates.
15
15
  A 2.x client handles legacy immediate saves. A 1.x client cannot write pooling settings to a
16
16
  2.x bridge: update the CLI and refresh old dashboard tabs. Before downgrading, settle pending
package/docs/install.md CHANGED
@@ -21,8 +21,8 @@ Its repository link should point to
21
21
  Use an exact version for a repeatable installation:
22
22
 
23
23
  ```bash
24
- npm view mcp-pacemaker@2.1.1 version
25
- npm install -g mcp-pacemaker@2.1.1
24
+ npm view mcp-pacemaker@2.1.2 version
25
+ npm install -g mcp-pacemaker@2.1.2
26
26
  mcp-pacemaker --version
27
27
  ```
28
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-pacemaker",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Keeps your MCP servers alive across editor restarts, reboots, and token expiry — pluggable auth, cross-platform auto-start, one-command setup. Zero-dependency bridge; a small CLI for setup.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,8 @@
33
33
  "docs/http-api.md",
34
34
  "docs/install.md",
35
35
  "docs/operations.md",
36
- "docs/upgrades.md"
36
+ "docs/upgrades.md",
37
+ "docs/comparison.md"
37
38
  ],
38
39
  "engines": {
39
40
  "node": ">=20"
@@ -69,7 +70,7 @@
69
70
  "compat:clean": "node tools/compatibility/prepare.mjs --clean",
70
71
  "test:compat": "node --test --test-concurrency=1 test/compat/gates.test.mjs test/compat/cli-api.test.mjs",
71
72
  "test:compat:browser": "node --test --test-concurrency=1 test/compat/browser.test.mjs",
72
- "test": "node --test --test-concurrency=4 test/bridge.test.mjs test/bridge-controls.test.mjs test/kill-tree.test.mjs test/keepalive.test.mjs test/auth-token.test.mjs test/cold-start.test.mjs test/crash-report.test.mjs test/resume.test.mjs test/config-checks.test.mjs test/config-reload.test.mjs test/health.test.mjs test/cli.test.mjs test/logs.test.mjs test/prewarm.test.mjs test/pooling-batch-scheduler.test.mjs test/pooling-batch-api.test.mjs test/pooling-batch-store.test.mjs test/pooling-batches.test.mjs test/pooling-stage-publication.test.mjs test/pooling-batch-startup.test.mjs test/pooling-postcommit-recovery.test.mjs test/pooling-config.test.mjs test/pooling-writer.test.mjs test/pooling-api.test.mjs test/pooling-deadlines.test.mjs test/pooling-queue-budget.test.mjs test/windows-security-helper.test.mjs test/windows-batch-security.test.mjs test/tui.test.mjs test/warm-pool.test.mjs test/server-metrics.test.mjs test/admission.test.mjs test/shared-sessions.test.mjs test/shared-http.test.mjs test/cross-host.test.mjs test/suite-integrity.test.mjs test/consumer-package.test.mjs test/notices.test.mjs test/notices-runtime.test.mjs test/npm-publication.test.mjs test/npm-publication-proof.test.mjs test/update-check.test.mjs test/service-lifecycle.test.mjs test/service-supervisor.test.mjs test/npm-publication-gates.test.mjs test/npm-publication-matrix.test.mjs test/publication-scanners-advisories.test.mjs test/publication-scanners.test.mjs test/service-replacement.test.mjs test/consumer-environment.test.mjs test/npm-publication-external.test.mjs test/npm-publication-peer.test.mjs test/publication-scanners-diagnostics.test.mjs test/publication-scanners-publication.test.mjs test/publication-scanners-paths.test.mjs test/npm-publication-local-enforcement.test.mjs test/publication-scanners-admission.test.mjs test/windows-process-lifetime.test.mjs test/windows-lifetime-startup.test.mjs test/managed-upgrade.test.mjs test/windows-lifetime-identity.test.mjs test/windows-legacy-process.test.mjs test/legacy-upgrade.test.mjs test/legacy-access.test.mjs test/legacy-task-security.test.mjs test/windows-task-channel.test.mjs test/task-controller.test.mjs test/account-task-adapter.test.mjs test/account-upgrade.test.mjs test/account-rollback-launch.test.mjs test/account-recovery.test.mjs test/runtime-context.test.mjs test/ci-contracts.test.mjs",
73
+ "test": "node --test --test-concurrency=4 test/bridge.test.mjs test/bridge-controls.test.mjs test/kill-tree.test.mjs test/keepalive.test.mjs test/auth-token.test.mjs test/cold-start.test.mjs test/crash-report.test.mjs test/child-write-race.test.mjs test/resume.test.mjs test/config-checks.test.mjs test/config-reload.test.mjs test/health.test.mjs test/cli.test.mjs test/logs.test.mjs test/prewarm.test.mjs test/pooling-batch-scheduler.test.mjs test/pooling-batch-api.test.mjs test/pooling-batch-store.test.mjs test/pooling-batches.test.mjs test/pooling-stage-publication.test.mjs test/pooling-batch-startup.test.mjs test/pooling-postcommit-recovery.test.mjs test/pooling-config.test.mjs test/pooling-writer.test.mjs test/pooling-api.test.mjs test/pooling-deadlines.test.mjs test/pooling-queue-budget.test.mjs test/windows-security-helper.test.mjs test/windows-batch-security.test.mjs test/tui.test.mjs test/warm-pool.test.mjs test/server-metrics.test.mjs test/admission.test.mjs test/shared-sessions.test.mjs test/shared-http.test.mjs test/cross-host.test.mjs test/request-origin.test.mjs test/suite-integrity.test.mjs test/consumer-package.test.mjs test/notices.test.mjs test/notices-runtime.test.mjs test/npm-publication.test.mjs test/npm-publication-proof.test.mjs test/update-check.test.mjs test/service-lifecycle.test.mjs test/service-supervisor.test.mjs test/npm-publication-gates.test.mjs test/npm-publication-matrix.test.mjs test/publication-scanners-advisories.test.mjs test/publication-scanners.test.mjs test/service-replacement.test.mjs test/consumer-environment.test.mjs test/npm-publication-external.test.mjs test/npm-publication-peer.test.mjs test/publication-scanners-diagnostics.test.mjs test/publication-scanners-publication.test.mjs test/publication-scanners-paths.test.mjs test/npm-publication-local-enforcement.test.mjs test/publication-scanners-admission.test.mjs test/windows-process-lifetime.test.mjs test/windows-lifetime-startup.test.mjs test/managed-upgrade.test.mjs test/windows-lifetime-identity.test.mjs test/windows-legacy-process.test.mjs test/legacy-upgrade.test.mjs test/legacy-access.test.mjs test/legacy-task-security.test.mjs test/windows-task-channel.test.mjs test/task-controller.test.mjs test/account-task-adapter.test.mjs test/account-upgrade.test.mjs test/account-rollback-launch.test.mjs test/account-recovery.test.mjs test/runtime-context.test.mjs test/ci-contracts.test.mjs",
73
74
  "notices:check": "node tools/third-party-notices/check.mjs",
74
75
  "publication:source-gates": "node tools/npm-publication/source-gates.mjs",
75
76
  "publication:artifact-gates": "node tools/npm-publication/artifact-gates.mjs",
@@ -219,7 +219,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
219
219
  SOFTWARE.
220
220
 
221
221
  ==============================================================================
222
- vite@7.3.6
222
+ vite@7.3.7
223
223
  Declared license: MIT
224
224
 
225
225
  --- LICENSE.md: # Vite core license ---
@@ -32,12 +32,12 @@
32
32
  },
33
33
  {
34
34
  "name": "vite",
35
- "version": "7.3.6"
35
+ "version": "7.3.7"
36
36
  }
37
37
  ],
38
- "producerLockSha256": "c2162e5015b4d015058c6ba539d42dfd9afdc8c8c2ecd23a44acbebfbb6f0088",
38
+ "producerLockSha256": "f9176a69ff877b2596b7b00190ba5a9f88ae99f579aa06f947a6ba78f17ac6fa",
39
39
  "projectLicenseSha256": "590ad9c29ee5580f83b9c05e403ca259d89e8366d1ed4c7207aa5e0a07afbf79",
40
- "noticesSha256": "2843c26d27582cd029efa8d82408817ac82af0d5b9c4e7cd7962103b70244dfe",
40
+ "noticesSha256": "f114a23088813c16f46ac1b7d2749938b1c846afaa0a2a03349fddb9d51cdf90",
41
41
  "chunks": [
42
42
  {
43
43
  "file": "assets/index-CcwMo2sI.js",
@@ -116,7 +116,7 @@
116
116
  },
117
117
  {
118
118
  "id": "commonjsHelpers.js",
119
- "package": "vite@7.3.6",
119
+ "package": "vite@7.3.7",
120
120
  "generated": true,
121
121
  "renderedLength": 140
122
122
  },
@@ -282,7 +282,7 @@
282
282
  },
283
283
  {
284
284
  "id": "vite/modulepreload-polyfill.js",
285
- "package": "vite@7.3.6",
285
+ "package": "vite@7.3.7",
286
286
  "generated": true,
287
287
  "renderedLength": 1168
288
288
  }
@@ -533,7 +533,7 @@
533
533
  },
534
534
  {
535
535
  "name": "vite",
536
- "version": "7.3.6",
536
+ "version": "7.3.7",
537
537
  "license": "MIT",
538
538
  "lockPath": "node_modules/vite",
539
539
  "licenses": [