mcp-pacemaker 2.0.2 → 2.1.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 (90) hide show
  1. package/CHANGELOG.md +171 -1
  2. package/CONTRIBUTING.md +46 -13
  3. package/README.md +109 -524
  4. package/THIRD_PARTY_NOTICES.txt +1 -28
  5. package/autostart/windows/account-task.ps1 +167 -0
  6. package/autostart/windows/inspect-task.ps1 +42 -0
  7. package/autostart/windows/inspect-worker-process-sessions.ps1 +155 -0
  8. package/autostart/windows/legacy-access.ps1 +359 -0
  9. package/autostart/windows/legacy-task.ps1 +301 -0
  10. package/autostart/windows/register-task.ps1 +11 -2
  11. package/bin/account-task-adapter.mjs +47 -0
  12. package/bin/account-upgrade-controller.mjs +198 -0
  13. package/bin/account-upgrade-worker.mjs +141 -0
  14. package/bin/account-worker-session.mjs +215 -0
  15. package/bin/cli-dispatch.mjs +154 -0
  16. package/bin/cli-main.mjs +1063 -0
  17. package/bin/cli-selection.mjs +43 -0
  18. package/bin/cli.mjs +7 -876
  19. package/bin/dependency-lock.json +642 -0
  20. package/bin/legacy-1.3.json +32 -0
  21. package/bin/legacy-access.mjs +73 -0
  22. package/bin/legacy-installation.mjs +273 -0
  23. package/bin/legacy-task.mjs +76 -0
  24. package/bin/legacy-upgrade.mjs +527 -0
  25. package/bin/managed-runtime.mjs +170 -0
  26. package/bin/managed-state.mjs +257 -0
  27. package/bin/managed-upgrade.mjs +581 -0
  28. package/bin/mcp-bridge.mjs +150 -4
  29. package/bin/pooling-batches.mjs +13 -0
  30. package/bin/pooling-writer.mjs +4 -0
  31. package/bin/prewarm.mjs +59 -25
  32. package/bin/runtime-context.mjs +100 -0
  33. package/bin/service-control.mjs +32 -4
  34. package/bin/stable-launcher.mjs +54 -0
  35. package/bin/task-only-controller.mjs +226 -0
  36. package/bin/task-scope.mjs +65 -0
  37. package/bin/task-transaction-protocol.mjs +173 -0
  38. package/bin/top.mjs +22 -10
  39. package/bin/update-check.mjs +13 -2
  40. package/bin/upgrade-autostart.mjs +47 -0
  41. package/bin/upgrade-capability.json +9 -0
  42. package/bin/upgrade-dependencies.mjs +183 -0
  43. package/bin/upgrade-package.mjs +205 -0
  44. package/bin/windows-legacy/LegacyProcessBroker.build.json +42 -0
  45. package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
  46. package/bin/windows-legacy/src/AssemblyInfo.cs +8 -0
  47. package/bin/windows-legacy/src/LegacyNative.cs +832 -0
  48. package/bin/windows-legacy/src/LegacyProcessBroker.cs +961 -0
  49. package/bin/windows-legacy-process.mjs +248 -0
  50. package/bin/windows-lifetime/ProcessLifetimeHelper.build.json +38 -0
  51. package/bin/windows-lifetime/ProcessLifetimeHelper.exe +0 -0
  52. package/bin/windows-lifetime/src/AssemblyInfo.cs +8 -0
  53. package/bin/windows-lifetime/src/ProcessLifetimeHelper.cs +468 -0
  54. package/bin/windows-process-lifetime.mjs +218 -0
  55. package/bin/windows-task-channel/TaskChannelGuard.build.json +45 -0
  56. package/bin/windows-task-channel/TaskChannelGuard.exe +0 -0
  57. package/bin/windows-task-channel/src/AssemblyInfo.cs +8 -0
  58. package/bin/windows-task-channel/src/ChannelCliContext.cs +210 -0
  59. package/bin/windows-task-channel/src/ChannelFiles.cs +231 -0
  60. package/bin/windows-task-channel/src/ChannelNative.cs +477 -0
  61. package/bin/windows-task-channel/src/ChannelSession.cs +203 -0
  62. package/bin/windows-task-channel/src/TaskChannelGuard.cs +686 -0
  63. package/bin/windows-task-channel.mjs +335 -0
  64. package/docs/account-upgrades.md +218 -0
  65. package/docs/compatibility.md +39 -0
  66. package/docs/configuration.md +146 -0
  67. package/docs/dashboards.md +125 -0
  68. package/docs/execution-context.md +119 -0
  69. package/docs/http-api.md +47 -0
  70. package/docs/install.md +52 -0
  71. package/docs/operations.md +99 -0
  72. package/docs/upgrades.md +324 -0
  73. package/package.json +19 -5
  74. package/supervisor/supervise.mjs +226 -40
  75. package/tools/account-upgrade/qualify-two-account.ps1 +66 -0
  76. package/tools/windows-legacy/README.md +251 -0
  77. package/tools/windows-legacy/build.ps1 +122 -0
  78. package/tools/windows-legacy/inventory.mjs +92 -0
  79. package/tools/windows-process-lifetime/README.md +119 -0
  80. package/tools/windows-process-lifetime/build.ps1 +122 -0
  81. package/tools/windows-process-lifetime/inventory.mjs +86 -0
  82. package/tools/windows-task-channel/README.md +329 -0
  83. package/tools/windows-task-channel/build.ps1 +107 -0
  84. package/tools/windows-task-channel/inventory.mjs +117 -0
  85. package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -28
  86. package/ui/dist/assets/{index-CgPruWYi.js → index-CcwMo2sI.js} +1 -1
  87. package/ui/dist/assets/index-PVcuDNrN.css +1 -0
  88. package/ui/dist/index.html +2 -2
  89. package/ui/dist/third-party-manifest.json +37 -25
  90. package/ui/dist/assets/index-CaLPg0c-.css +0 -1
@@ -0,0 +1,146 @@
1
+ # Configuration
2
+
3
+ How to define servers in `servers.json`, how live edits are applied, and the controls for
4
+ sessions, resources and sharing.
5
+
6
+ ## servers.json
7
+
8
+ Config lives at `~/.mcp-pacemaker/servers.json`. `import` fills it from your existing client
9
+ config and **carries over `audience`/`auth`/`headers`**, so HTTP servers usually need no hand-editing.
10
+ See [`examples/servers.example.json`](../examples/servers.example.json).
11
+
12
+ ```jsonc
13
+ {
14
+ "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
15
+
16
+ "my-api": {
17
+ "type": "http",
18
+ "url": "https://my-service.example.com/mcp",
19
+ "auth": { "type": "command", "command": "my-cli print-token", "refreshMinutes": 50 }
20
+ }
21
+ }
22
+ ```
23
+
24
+ **Auth options** (per HTTP server):
25
+
26
+ | `auth.type` | Behavior |
27
+ |---|---|
28
+ | `command` | Runs any command that prints a token on stdout; the result is cached and auto-refreshed (`refreshMinutes`). Provider-agnostic — see below. |
29
+ | `env` | Uses `process.env[var]`. |
30
+ | `static` | Fixed `header` + `value`. |
31
+ | `none` | No header is added — the **client** authenticates, and the bridge relays OAuth discovery and challenges for it. |
32
+
33
+ `auth.type: "command"` is deliberately just "run this and use what it prints", so it works with
34
+ whatever issues tokens in your environment. Some examples — none of these are special-cased:
35
+
36
+ ```jsonc
37
+ // Azure
38
+ { "type": "command", "command": "az account get-access-token --resource api://<id> --query accessToken -o tsv" }
39
+ // Google Cloud
40
+ { "type": "command", "command": "gcloud auth print-access-token" }
41
+ // AWS (e.g. a signed token from your own helper)
42
+ { "type": "command", "command": "aws-token-helper --profile prod" }
43
+ // HashiCorp Vault
44
+ { "type": "command", "command": "vault read -field=token secret/my-api" }
45
+ // 1Password / any secret manager
46
+ { "type": "command", "command": "op read op://vault/my-api/token" }
47
+ // A plain script
48
+ { "type": "command", "command": "./scripts/get-token.sh" }
49
+ ```
50
+
51
+ By default the token is sent as `Authorization: Bearer <token>`; set `header` to send it
52
+ somewhere else (for example `{ "header": "X-Api-Key" }`).
53
+
54
+ **How long a credential is cached.** If the command returns a JWT, its `exp` claim decides —
55
+ `refreshMinutes` is only an upper bound and a fallback for opaque credentials. This matters
56
+ because token CLIs serve from their own cache: `az account get-access-token` will hand back a
57
+ token it minted earlier that may be nearly spent, and trusting a flat window would keep a dead
58
+ token in play. If the upstream rejects a credential the bridge supplied, that 401 is reported on
59
+ the server (`doctor`, `/api/status` → `lastError`) and the credential is discarded rather than
60
+ reused — the client is never asked to log in for a server the bridge authenticates.
61
+
62
+ > **Azure shorthand.** A bare `"audience": "<res>"` expands to
63
+ > `az account get-access-token --resource <res> …`. That is a convenience for Azure users only —
64
+ > every other provider uses `auth.command` above.
65
+
66
+ ## Editing servers.json while it runs
67
+
68
+ Save the file and the bridge picks it up. Servers whose definition did not change are left
69
+ completely alone — same processes, same live sessions — so adding one server does not cost you the
70
+ fourteen that were already working:
71
+
72
+ ```bash
73
+ $ mcp-pacemaker reload # or just save the file; the bridge watches it
74
+ ✓ reloaded /Users/you/.mcp-pacemaker/servers.json on :8850
75
+ · added: postgres
76
+ · changed: github
77
+ · 2 session(s) restarted; untouched servers kept theirs
78
+ ```
79
+
80
+ A file that does not parse, or a server with neither `command` nor `url`, is **rejected whole** —
81
+ the bridge logs why and keeps serving the last good config, so a half-written save cannot take your
82
+ servers down. Set `MCP_CONFIG_WATCH=0` to require an explicit `reload` instead of watching.
83
+
84
+ ## Multi-agent & resource controls
85
+
86
+ Multiple hosts/agents can share one bridge — each gets its own isolated session by default,
87
+ and HTTP servers share the cached token across all of them. The dashboard, `top`, and
88
+ `mcp-pacemaker status` show **which agents are connected** to each server.
89
+
90
+ | Control | Env / config | Default | Effect |
91
+ |---|---|---|---|
92
+ | 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 |
93
+ | Concurrency cap | `MCP_MAX_SESSIONS_PER_SERVER`, or per-server `maxSessions` | `0` (unlimited) | reject new sessions past the cap (HTTP 503) |
94
+ | 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 |
95
+ | 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 |
96
+ | 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 |
97
+ | 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 |
98
+ | 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 |
99
+ | Scheduled recycle | per-server `recycleMinutes`, or `MCP_RECYCLE_MINUTES` | off | restart a server periodically, for servers holding a credential they can only refresh interactively |
100
+ | 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 |
101
+ | 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 |
102
+ | 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 |
103
+ | 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 |
104
+ | 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 |
105
+
106
+ ### Measuring process churn
107
+
108
+ For stdio servers, `/api/status` reports `spawn.total` (successful process launches),
109
+ `spawn.attempts`, `spawn.failures`, `spawn.warmAdoptions`, `spawn.sessionStarts`, and
110
+ `spawn.sessionResumes`. Launches include classic SSE, Streamable HTTP, warm-pool refill,
111
+ and replacements after recycle or resume. Adopting an existing warm child does not count
112
+ as another launch. The unit is one bridge-launched process, not all of its descendants.
113
+
114
+ These cumulative counters do not share the 50-sample latency limit. They reset when the
115
+ bridge process restarts; compare snapshots only within the same `instanceId`. `startedAt`
116
+ identifies that interval. Save the final snapshot before a planned restart for a before/after
117
+ comparison. HTTP-proxied servers have no local process counters (`spawn: null`).
118
+
119
+ `spawn.samples` and p50/p95/max still describe the latest 50 directly observed, successful
120
+ cold initializations, not all launches. Latencies are `null` until observed; a warm child's
121
+ idle lifetime is not a cold-start sample. The existing `requests` count covers routed HTTP
122
+ requests, not just `tools/call`; do not label it as a tool-call count.
123
+
124
+ ### Shared mode
125
+
126
+ Set `"sharing": "shared"` on a stdio server only when its tools are stateless, operate on
127
+ explicit inputs and use one common credential context. This is an operator assertion, not
128
+ something latency or matching capabilities can prove. Leave workspace-, conversation- or
129
+ client-account-dependent servers isolated.
130
+
131
+ Compatible sessions have identical full initialization parameters, including `clientInfo`,
132
+ and empty client capabilities. One child receives one real initialization; each virtual session
133
+ gets its own IDs, progress, cancellation and tools-list cursors. Only the upstream's actual
134
+ tools capability is exposed. Roots, sampling, elicitation, tasks, resource subscriptions and
135
+ other stateful methods are not supported. Incompatible clients fail explicitly, without
136
+ silently falling back to a different mode.
137
+
138
+ Deleting one session leaves the others running. The initialized child is retained between
139
+ compatible reconnects for up to `sharedLingerMs` (30 minutes by default). Recycle drains work,
140
+ then replaces the generation on later use. A timeout or child exit never triggers automatic
141
+ replay of a potentially executed tool call.
142
+
143
+ The server keeps its existing credential provider and configured `recycleMinutes`. Shared
144
+ mode does not promise silent credential renewal. The dashboard and `prewarm` CLI show the
145
+ actual shared-child state, members, queued work and cumulative process launches.
146
+ See [the shared-session contract](shared-sessions.md) for limits and failure semantics.
@@ -0,0 +1,125 @@
1
+ # Dashboards
2
+
3
+ Two views of the same live state. Neither is required — the bridge runs headless — but both are
4
+ useful for answering "is this server actually up, and who is using it?"
5
+
6
+ ## Web dashboard
7
+
8
+ `mcp-pacemaker dashboard`, or open `http://127.0.0.1:<port>/ui`. It streams over SSE, so it
9
+ updates on its own without polling or a refresh.
10
+
11
+ ![Web dashboard: one card per server, showing sessions, warm pool, pids and connected agents](images/dashboard.png)
12
+
13
+ Per server it shows the transport (`stdio` / `http`), the sharing policy (`pool` when pre-warming),
14
+ live sessions against the cap, request count, the actual OS pids, the warm-pool count, how long
15
+ ago it was last used, and **which clients are connected** — above, one server is shared by
16
+ VS Code and Claude Code while others are held by Copilot, Cursor and Codex. `Recycle` restarts
17
+ that server's processes; it is disabled for servers with no session to restart. The live log at
18
+ the bottom is the bridge's own log, filterable.
19
+
20
+ ## Pre-warming controls
21
+
22
+ The **Pre-Warming** tab puts every stdio candidate, current pool and shared server in one table.
23
+ It shows cold-start p50/p95, observed peak sessions, process starts, warm/target counts,
24
+ shared-child state and pending work, and an action.
25
+ HTTP proxies are excluded because the bridge does not launch their processes.
26
+
27
+ Choose **Enable pre-warming (N)** on a slow-server card or in the table. The button shows
28
+ the exact warm count and the extra resident-process cost before you click. Nothing enables
29
+ it automatically. **Disable pre-warming** removes idle warm children without killing active
30
+ sessions after the change is applied.
31
+
32
+ Changes are batched in a **complete pending copy** of `servers.json`. Every accepted change
33
+ resets a five-second countdown. When editing stops, reload validates the pending copy, retains
34
+ the original as `servers.previous.json`, flips the pending file into place, and activates the
35
+ new settings. The dashboard shows **Pending** or **Applying** until that succeeds; active
36
+ mode and warm counts are not changed early. **Reload now** flushes the same batch immediately.
37
+
38
+ **Cancel batch** discards a pending batch without changing active settings. **Undo batch**
39
+ stages the full previous configuration for another five-second batch, only if no intervening
40
+ active-file edit has occurred. These actions affect the entire batch; the dashboard names all
41
+ affected servers. An unrelated pending batch must be cancelled or applied before Undo.
42
+
43
+ The same controls are available without a browser:
44
+
45
+ ```sh
46
+ mcp-pacemaker prewarm --port 8791
47
+ mcp-pacemaker prewarm --port 8791 --enable filesystem --count 2
48
+ mcp-pacemaker prewarm --port 8791 --disable filesystem
49
+ # Changes are queued; the printed command cancels a pending batch or undoes it after application.
50
+ ```
51
+
52
+ Use `--config <path>` when the bridge's config and admin nonce are outside the default
53
+ directory. `--json` returns the measured snapshot. The CLI and UI use the same server
54
+ eligibility and recommendation data; a missing latency sample is not reported as a zero-cost
55
+ startup. The CLI returns after staging, so consecutive commands can join the same batch.
56
+ `prewarm` shows pending settings separately from active values. Config remains strict JSON,
57
+ not JSONC; the pending copy preserves unrelated JSON and requires a matching active revision.
58
+ Conflicts are reported rather than overwriting someone else's edit.
59
+
60
+ On Windows, saves run with the normal bridge account and use **directory-inherited auditing**.
61
+ The dashboard gives a non-blocking notice: custom per-file audit rules may not carry forward
62
+ to the new active copy. The retained original keeps its original metadata. No audit-read
63
+ privilege, UAC prompt, or permanently elevated bridge is required.
64
+
65
+ This does not permit broader access to configuration data. Candidates must have appropriate
66
+ access restrictions before any configuration bytes are written, and supported integrity
67
+ protections must be retained. Actual source write denial or unsupported protection remains
68
+ an error. Configuration operations run in a serialized worker so file inspection does not
69
+ block MCP traffic.
70
+
71
+ Windows security inspection uses the bundled, own-source .NET Framework helper rather than
72
+ starting PowerShell or compiling code on each write. Automatic edits require .NET Framework
73
+ 4.6.2 or later; modern Windows includes it. The helper's source, build instructions and
74
+ integrity metadata are [included in the package](../tools/windows-security-helper/README.md).
75
+ Windows ARM64 execution is not yet validated.
76
+
77
+ Staging requests and file commits each have a bounded execution budget; the acknowledged
78
+ five-second batching delay does not keep the original HTTP request open. A commit already in
79
+ progress can finish after its deadline. The result distinguishes a pre-commit failure from
80
+ a committed or unknown outcome; it never automatically retries or claims a timed-out write
81
+ was rolled back.
82
+
83
+ The two file moves are not an atomic exchange. Reload coordinates them with validated recovery
84
+ state and non-replacing destination placement. Existing MCP traffic continues using the
85
+ previous in-memory configuration during the flip; an external reader can briefly find the
86
+ active path absent. Recovery must not overwrite a file created by an external editor.
87
+ Reread the settings after an uncertain result.
88
+
89
+ If you are upgrading from 1.3.0, see [upgrading from 1.3.0](compatibility.md#upgrading-from-130)
90
+ for the configuration-save change and the reload that open dashboard tabs need.
91
+
92
+ ## Health
93
+
94
+ The `Health` tab runs the same checks as `mcp-pacemaker doctor`, so you can see them without a
95
+ terminal.
96
+
97
+ ![Health tab: Node version, config validity, per-server status and the admin nonce path](images/health.png)
98
+
99
+ It checks the Node version, that the config parses, every server definition (including a stdio
100
+ server whose relative path will not resolve from the directory it would run in — a failure that
101
+ is otherwise silent until a client first calls it, and a server named after one of the bridge's
102
+ own routes, which is unreachable), any server currently failing, and that the admin nonce file
103
+ exists.
104
+
105
+ ## Terminal dashboard
106
+
107
+ `mcp-pacemaker top` is the same data as an Ink TUI, for when a browser is inconvenient:
108
+
109
+ ```
110
+ 🫀 mcp-pacemaker top ● :8850 · up 176s · v1.1.0
111
+ SERVER TYPE HEALTH SESS WARM REQ PID TOKEN ERROR / CLIENTS
112
+ filesystem stdio ok 2 1/1 8 186276,12 - ⇄ Visual Studio Code,Claude Code
113
+ github stdio ok 1 - 2 146436 - ⇄ GitHub Copilot
114
+ sqlite stdio ok 1 - 2 102108 - ⇄ Cursor
115
+ slow-tool stdio ok 1 - 2 103776 - ⇄ Codex CLI
116
+ search-api http FAIL×3 0 - 4 - 42m upstream rejected the crede
117
+ docs-api http ? 0 - 0 - -
118
+ ↑↓ select · r recycle · q quit
119
+ ```
120
+
121
+ `HEALTH` is `ok` when the last request to that server succeeded, `FAIL×n` after n consecutive
122
+ failures, and `?` when nothing has called it yet — deliberately not `ok`, since no evidence is not
123
+ the same as working. `WARM` shows `warm/minWarm` for pooled servers and `-` for the rest, `TOKEN`
124
+ the time left on a cached credential, and the last column either the connected clients or the last
125
+ error. `↑↓` selects a row and `r` recycles it.
@@ -0,0 +1,119 @@
1
+ # User-runtime execution contracts
2
+
3
+ The default runtime is an ordinary process belonging to its user. Starting the
4
+ bridge or supervisor, registering autostart, upgrading and recovering do not
5
+ silently elevate, demote, impersonate or select another account. An unavailable
6
+ or privileged context refuses before runtime work. Read-only planning and
7
+ installation with both `--no-start` and `--no-autostart` remain separate.
8
+
9
+ `bin/runtime-context.mjs` is the shared observation boundary:
10
+
11
+ | Platform | Required effective context |
12
+ | --- | --- |
13
+ | Windows | The existing native ordinary-caller proof: no elevation, enabled administrator membership or observed thread impersonation |
14
+ | Linux | Stable non-root real/effective IDs, matching saved/filesystem IDs, and zero inherited/permitted/effective/ambient capabilities |
15
+ | macOS | Stable non-root real/effective user IDs and matching real/effective group IDs |
16
+
17
+ These are snapshots, not a promise that a process can never change context.
18
+ The guard does not grant access to an arbitrary installation. Existing instance,
19
+ file-security, service-registration and account-authorization checks still apply.
20
+ Explicit original-account administration remains the separate Windows-only
21
+ capability described in [account upgrades](account-upgrades.md).
22
+
23
+ ## Test contracts
24
+
25
+ `tools/ci/test-contracts.mjs` declares the execution role, command and required
26
+ capabilities. It verifies that the source contract includes every registered
27
+ source test and preserves the declared test concurrency.
28
+
29
+ | Contract | Required role and coverage |
30
+ | --- | --- |
31
+ | `source` | Ordinary runtime; complete registered source suite. Windows also needs the real caller's WTS logon because its account/session tests use that API. |
32
+ | `source-ordinary` | Windows only. Ordinary runtime without a logon session; every registered file except the four in `SESSION_REQUIRED_TESTS`. The two Windows lanes must together cover every registered file exactly once. |
33
+ | `compatibility` | Ordinary runtime; complete CLI/API compatibility command |
34
+ | `browser` | Ordinary runtime; declared browser compatibility command |
35
+ | `audit` | Verified privileged Windows fixture; the unchanged mandatory audit test, including its ordinary worker and complete result checks |
36
+ | `service-context` | Read-only capability observation; **not** test coverage |
37
+
38
+ Run the same entry point locally or in CI:
39
+
40
+ ```text
41
+ npm run test:contract -- plan
42
+ npm run test:contract -- source --check
43
+ npm run test:contract -- source
44
+ npm run test:contract -- compatibility
45
+ npm run test:contract -- service-context --check
46
+ ```
47
+
48
+ The service-context observation uses the current user's WTS session on Windows,
49
+ owned `/run/user/<uid>` and user `systemd` manager on Linux, or the caller's `gui`
50
+ launchd domain on macOS. A CLI-only or browser contract does not require a desktop
51
+ merely for consistency with Windows.
52
+
53
+ No adapter creates a user or session, changes UAC, edits privileges, rewrites a
54
+ token, or falls back to another identity. Setup capability and ordinary runtime
55
+ capability are not interchangeable. A missing required capability is a blocked
56
+ qualification, not a successful skip.
57
+
58
+ Windows integration fixtures also need a private checkout, home and temporary
59
+ tree whose ancestors satisfy the existing path-security checks. A shared writable
60
+ drive can correctly fail with `UNTRUSTED_PATH_WRITER` even when the process is
61
+ ordinary. Use short, trusted profile-owned test roots: the nested upgrade packages
62
+ must remain within the native .NET executable path limits, not just Node's file
63
+ API limits. Do not change drive ACLs, enable long-path policy or relax the native
64
+ checks to make an unsuitable test root pass.
65
+
66
+ ## CI and release boundaries
67
+
68
+ The public workflow separates three roles rather than assuming an OS label implies a runtime.
69
+
70
+ | Lane | Runner | What it proves |
71
+ | --- | --- | --- |
72
+ | `test (ubuntu-latest\|macos-latest, 20\|22\|24)` | Stock hosted | Ordinary user-runtime source and compatibility suites |
73
+ | `windows-ordinary (20\|22\|24)` | Stock hosted Windows | Install, syntax, pinned native toolchain, three reproducible native builds, the actual refusal of an elevated built-in Administrator, then the `source-ordinary` and `compatibility` contracts run as a provisioned disposable standard account |
74
+ | Windows session lane | Ordinary interactive Windows session | The four `SESSION_REQUIRED_TESTS` files, run on the publishing host by the local publication gate's host half |
75
+
76
+ A stock hosted Windows runner signs in as the built-in Administrator with a full elevated token, and
77
+ Task Scheduler ignores `RunLevel` for that account, so that account cannot host the ordinary
78
+ user-runtime contract. The lane therefore does not try to demote it. It provisions a disposable
79
+ standard account, which is never an administrator, and runs the suite as that account. It also
80
+ proves the elevated refusal itself, and `tools/ci/windows-runner-role.mjs` fails loudly if a Windows
81
+ runner ever does satisfy the ordinary contract, so real coverage is promoted rather than left
82
+ unexecuted.
83
+
84
+ A provisioned account signs in with a password logon and has no interactive logon session, so the
85
+ four `SESSION_REQUIRED_TESTS` files that bind one stay in the session lane. They are still required,
86
+ not skipped. The qualification container cannot host them either, because `ContainerUser` has no
87
+ interactive logon session. The two halves of the local publication gate therefore split the suite.
88
+ Inside the container, `tools/npm-publication/local-gate.mjs` verifies the `source-ordinary` contract,
89
+ runs the contained source suite and records the session files in `sessionDeferredTests`. On the host,
90
+ `tools/npm-publication/local-windows.mjs` runs the `session-required` contract through
91
+ `tools/windows-ci/ordinary-run.mjs`, whose scheduled task supplies an ordinary, non-elevated caller
92
+ inside a real interactive session, and writes `session-lane.json`. `verify-local.mjs` requires that
93
+ receipt to report `role: user-runtime` with `sessionVerified: true`, zero failures, exactly the
94
+ registered session files, and the same commit the contained cases qualified. `testContract` asserts
95
+ the two Windows lanes cover every registered file exactly once, so no file can fall out of both.
96
+
97
+ The CI provider bounds job lifetime and owns job teardown; the contract entry
98
+ point does not claim to provide VM or network isolation.
99
+
100
+ The local command forwards cancellation to its owned test child and waits for
101
+ exit/output closure, escalating termination after five seconds. Unverified
102
+ closure after another five seconds is an error, not successful cleanup. Source
103
+ tests retain a 60-minute command budget and compatibility tests a 45-minute budget;
104
+ the audit keeps its existing 210-second budget and a 16 MiB output limit.
105
+ Windows commands also use the existing native process-lifetime mechanism.
106
+ None of these command bounds changes application startup or shutdown deadlines.
107
+
108
+ No integration runner is provisioned by this change. An approved environment
109
+ must already supply the required ordinary identity and session. Do not attach a
110
+ personal workstation or unrestricted persistent runner to public pull requests.
111
+ Runner admission, trusted-source selection, disposable-machine isolation and
112
+ credential policy require independent infrastructure approval.
113
+
114
+ The existing exact-source CI and local publication requirements remain enforced.
115
+ The local source gate invokes the same read-only preflight and its verifier
116
+ requires that original command receipt. A Windows container without WTS cannot
117
+ stand in for an interactive user session. The privileged audit companion remains
118
+ required and cannot replace the source suite. No missing integration result is
119
+ relabelled as a pass.
@@ -0,0 +1,47 @@
1
+ # HTTP API
2
+
3
+ Everything the dashboards show is plain HTTP on the same loopback port, so you can script it.
4
+ Read endpoints need no auth; administrative writes require a nonce.
5
+
6
+ | Endpoint | Method | Returns |
7
+ |---|---|---|
8
+ | `/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 |
9
+ | `/api/status` | GET | Full snapshot — every server with sessions, pids, warm count, request count, `health`, `lastError`, connected clients |
10
+ | `/api/doctor` | GET | The health checks, as JSON |
11
+ | `/api/events` | GET (SSE) | The same snapshot pushed every 2s — what the dashboard consumes |
12
+ | `/api/logs` | GET (SSE) | Bridge log: replays the last 100 lines, then streams. For anything older, read `bridge.log` next to your config |
13
+ | `/admin/recycle/<name>` | POST | Restart a server's processes. Omit `<name>` to recycle everything |
14
+ | `/admin/reload` | POST | Flush a pending configuration batch, or re-read an externally edited `servers.json`. Only changed definitions are reconfigured |
15
+ | `/admin/servers/<name>/pooling` | POST | Stage enable/disable or batch cancel/undo with admin nonce, bounded input and a matching config revision |
16
+ | `/<name>/mcp` | POST/GET/DELETE | Streamable HTTP transport for that server |
17
+ | `/<name>/sse` | GET | HTTP+SSE transport for that server |
18
+ | `/.well-known/oauth-protected-resource/<name>` | GET | OAuth discovery relayed from the upstream, for HTTP servers where the client authenticates |
19
+
20
+ Pooling mutations require `x-mcp-pooling-batch: 1`. A staged change returns **HTTP 202** with
21
+ `pending: true`, a batch ID, a private cancel/undo receipt, and the public snapshot. Acceptance
22
+ is not activation. `prewarm.batches` in the snapshot reports bounded pending, applying and
23
+ completed outcomes; it does not expose receipt tokens or full configurations.
24
+ Clients without the batch header are rejected before a change is queued, rather than letting
25
+ an older client report a queued save as already enabled.
26
+ Rich snapshots carry an instance-scoped `snapshotVersion` so a delayed HTTP response or older
27
+ SSE frame cannot replace newer active settings or prematurely expire a batch receipt.
28
+
29
+ ```bash
30
+ # is a server actually running, and who is using it?
31
+ curl -s http://127.0.0.1:8850/api/status | jq '.servers[] | {name, sessions, pids, clients}'
32
+
33
+ # watch the bridge log
34
+ curl -N http://127.0.0.1:8850/api/logs
35
+
36
+ # force a restart of one server (nonce is written next to servers.json)
37
+ curl -X POST http://127.0.0.1:8850/admin/recycle/filesystem \
38
+ -H "x-mcp-nonce: $(cat ~/.mcp-pacemaker/admin.nonce)"
39
+ ```
40
+
41
+ `/admin/*` is guarded twice: the request must arrive on loopback, **and** carry the nonce that the
42
+ bridge writes to `admin.nonce` next to your config at startup. The nonce is new on every start, so
43
+ only same-box tooling that can read that file — the CLI, and the dashboard the bridge itself
44
+ served — can change anything.
45
+
46
+ The [dashboards](dashboards.md) use these same endpoints. Custom clients written for 1.3.0 need
47
+ the changes in [upgrading from 1.3.0](compatibility.md#upgrading-from-130).
@@ -0,0 +1,52 @@
1
+ # Install details
2
+
3
+ The [quick start](../README.md#quick-start) has the short version. This page has the full
4
+ requirements, the ordinary-user rule, npm channels and where to install.
5
+
6
+ Use a maintained **Node.js 22 or 24** release. Node 20 remains a compatibility target but is
7
+ end-of-life, not a recommended secure runtime. The bridge has no npm runtime dependencies;
8
+ the setup CLI and dashboard have their own dependencies.
9
+
10
+ **Run as an ordinary user.** From 2.1.0 the bridge, supervisor and default upgrades verify the
11
+ caller before any runtime work and refuse elevated or root callers at startup with
12
+ `User-runtime execution refused: …`. On Linux and macOS that includes `root`, which is the
13
+ default user in most container images, so run the bridge as a normal account there. The
14
+ [user-runtime contract](execution-context.md) describes what is checked and why; explicit
15
+ Windows account administration has a separate
16
+ [controller/worker path](account-upgrades.md).
17
+
18
+ Install the released package from [npm](https://www.npmjs.com/package/mcp-pacemaker).
19
+ Its repository link should point to
20
+ [`girishkvs/mcp-pacemaker`](https://github.com/girishkvs/mcp-pacemaker).
21
+ Use an exact version for a repeatable installation:
22
+
23
+ ```bash
24
+ npm view mcp-pacemaker@2.1.1 version
25
+ npm install -g mcp-pacemaker@2.1.1
26
+ mcp-pacemaker --version
27
+ ```
28
+
29
+ The current line uses the `latest` npm channel; the maintenance line uses `legacy` when that
30
+ channel has been published. Check the actual registry values before choosing a channel:
31
+
32
+ ```bash
33
+ npm view mcp-pacemaker dist-tags --json
34
+ ```
35
+
36
+ GitHub tags and npm publication are separate: a tag does not establish that the same
37
+ version or channel is available on npm, or that its package bytes are identical.
38
+ `@1` and `@2` are version ranges, not the maintained channels. They can select a version
39
+ withdrawn from a channel. `upgrade --self` resolves the installed major's channel and prints
40
+ an exact-version command; it never installs or downgrades anything.
41
+
42
+ Use a durable installation directory for a long-running service. Do not register autostart
43
+ from an `npx` cache: that cache may be removed later. Bare CLI invocation can enter the setup
44
+ wizard; use `--version` for a read-only installation check.
45
+
46
+ Windows setup also needs PowerShell 7 (`pwsh`) and Windows Script Host for its launcher.
47
+ Automatic configuration edits need .NET Framework 4.6.2 or newer. The bundled helper is
48
+ unsigned; npm provenance is not Authenticode signing or an application-control allowlist.
49
+ Windows ARM64 is not validated.
50
+
51
+ For version pairs and downgrades, see [version compatibility](compatibility.md). To replace a
52
+ running installation, see [upgrades and rollback](upgrades.md).
@@ -0,0 +1,99 @@
1
+ # Commands and operations
2
+
3
+ Every command, how start and stop behave, durable logs and server health. The
4
+ [README](../README.md) covers setup and everyday use.
5
+
6
+ ## Command reference
7
+
8
+ | Command | What it does |
9
+ |---|---|
10
+ | `init [--from source] [--client a,b] [--yes]` | Interactive setup: import one source, wire selected hosts, auto-start, launch |
11
+ | `status` | Bridges, wired hosts, servers, and any server currently failing |
12
+ | `reload` | Re-read `servers.json` into the running bridge without restarting it |
13
+ | `doctor` | Diagnose config, bridge reachability, host wiring |
14
+ | `prewarm [--port N] [--json]` | One view of stdio candidates, pools, shared-child state and measured process starts |
15
+ | `prewarm --enable <server> [--count N]` | Explicitly enable warm slots; `--disable <server>` turns them off |
16
+ | `logs [--follow] [--since 2h] [--server name] [--grep text]` | Read and filter durable logs, including retained rotation |
17
+ | `top` / `dashboard` | Live terminal UI / web dashboard |
18
+ | `plan --client <host>` | Dry run — show exactly what `install` would change |
19
+ | `import --from <source>` | Build `~/.mcp-pacemaker/servers.json` from one supported JSON host config |
20
+ | `install --client <host> [--port N]` | Wire one host (its own `--port` gives it its own bridge) |
21
+ | `emit --client <host>` | Print the config entries without writing anything |
22
+ | `start` / `stop` | Start or stop bridges (`--port` for one, else all) |
23
+ | `upgrade --to <version> [--plan] [--yes]` | Stage and replace one managed installation, including its CLI, preserving port/config/host wiring |
24
+ | `upgrade --to <version> --sxs [--port N]` | Keep the old instance and start an isolated new one; omitted port is OS-assigned |
25
+ | `upgrade --recover` | Resume or roll back an interrupted managed replacement from its durable journal |
26
+ | `upgrade --fix-legacy-access [--plan] [--yes]` | Administrator terminal, Windows: one-time fix for a 1.3 install set up as administrator; changes only that task's run-as user entry and the owner of its `bin\cli.mjs` |
27
+ | `[--instance <directory>]` | Select one installation when discovery finds several; valid before or after the command |
28
+ | `upgrade [--self]` | Legacy host repair; `--self` still prints same-major installation guidance only |
29
+ | `update-check [--json]` | Compare the installed version with its maintained npm channel |
30
+ | `uninstall` | Stop managed bridges, remove auto-start, restore available host-file `.bak` copies; native entries are not restored |
31
+
32
+ > Package installation alone does not upgrade a running backend. Use explicit `upgrade --to`
33
+ > for managed replacement. Bare `upgrade` retains the older host-repair behavior.
34
+
35
+ > 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).
36
+
37
+ For a supervisor created by these patch releases, `stop --port N` verifies the matching
38
+ installation/configuration identity, waits for shutdown, and holds managed autostart until an
39
+ explicit `start --port N`. Normal OS shutdown does not create that hold. Use the matching
40
+ installation's CLI. Older or directly launched bridges have no managed stop record and are
41
+ refused rather than reported as stopped; disable their OS autostart and verify shutdown first.
42
+
43
+ On Linux/macOS the control socket lives beside the configuration, independent of `TMPDIR`.
44
+ Its complete path must fit within 103 UTF-8 bytes. `install` and `init` check this before
45
+ changing configuration or autostart; use a shorter configuration location rather than
46
+ relying on an implicit socket-location fallback.
47
+
48
+ Isolated and pooled stdio servers support HTTP+SSE (`/<name>/sse`) and Streamable HTTP
49
+ (`/<name>/mcp`). Shared mode requires Streamable HTTP.
50
+
51
+ Host-file `.bak` backups and what `uninstall` restores are described under
52
+ [Uninstall](../README.md#uninstall) in the README. Managed upgrades, `--sxs` and `--instance`
53
+ are described in [upgrades and rollback](upgrades.md).
54
+
55
+ ## Reading durable logs
56
+
57
+ ```sh
58
+ mcp-pacemaker logs --since 2h --server filesystem
59
+ mcp-pacemaker logs --follow --grep timeout
60
+ mcp-pacemaker logs --config /path/to/servers.json --since 2026-09-07T12:00:00Z
61
+ ```
62
+
63
+ `logs` reads `bridge.log.1` then `bridge.log` beside the selected config, including when
64
+ the bridge is stopped. `--since` accepts durations in seconds/minutes/hours/days or an ISO
65
+ timestamp with timezone. `--server` matches the exact log-header server name; `--grep` is
66
+ a case-insensitive literal match, not a regular expression. Matching multiline records retain
67
+ their header and context.
68
+
69
+ `--follow` handles complete new lines, split UTF-8 writes, truncation and rollover until
70
+ interrupted. It warns if rotation has already discarded an unread generation. Only retained
71
+ history is available; it cannot recover logs the bridge has deleted. Individual lines over
72
+ 1 MiB fail explicitly rather than being silently truncated.
73
+
74
+ ## Server health
75
+
76
+ Every server carries a health verdict in `/api/status`, `doctor`, `top` and the dashboard:
77
+
78
+ | State | Meaning |
79
+ |---|---|
80
+ | `ok` | The last request to this server succeeded |
81
+ | `failing` | Recent requests are failing, with a count of how many in a row |
82
+ | `unknown` | Nothing has called it yet |
83
+
84
+ This is passive and free — it is derived from traffic the bridge is already proxying. A JSON-RPC
85
+ error *from* the server counts as healthy (it answered); a 401/403, a 5xx, a spawn failure or a
86
+ non-zero exit counts as failing, and the state clears the moment a request succeeds again.
87
+
88
+ A server nobody calls stays `unknown` forever, which is honest but not much help if its credential
89
+ quietly expires overnight. `MCP_HEALTH_INTERVAL_MS` adds a periodic probe for HTTP servers so they
90
+ get a verdict without waiting for a client:
91
+
92
+ ```bash
93
+ MCP_HEALTH_INTERVAL_MS=300000 mcp-pacemaker start # probe every 5 minutes
94
+ ```
95
+
96
+ It is off by default because a probe is a real request to somebody else's service. Set
97
+ `"healthIntervalMinutes": 0` on a server to exclude just that one, or a number to give it its own
98
+ interval. stdio servers are not probed: a probe would mean spawning a process, which costs more
99
+ than the request it is meant to pre-empt.