mcp-pacemaker 2.1.0 → 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 +142 -2
- package/CONTRIBUTING.md +11 -4
- package/README.md +116 -764
- package/SECURITY.md +7 -0
- package/THIRD_PARTY_NOTICES.txt +1 -1
- package/autostart/windows/legacy-access.ps1 +359 -0
- package/bin/account-upgrade-controller.mjs +1 -1
- package/bin/account-upgrade-worker.mjs +7 -0
- package/bin/cli-main.mjs +61 -5
- package/bin/dependency-lock.json +642 -0
- package/bin/legacy-access.mjs +73 -0
- package/bin/legacy-installation.mjs +75 -19
- package/bin/legacy-task.mjs +24 -0
- package/bin/legacy-upgrade.mjs +66 -10
- package/bin/managed-upgrade.mjs +102 -12
- package/bin/mcp-bridge.mjs +115 -20
- package/bin/prewarm.mjs +28 -6
- package/bin/service-control.mjs +10 -5
- package/bin/upgrade-capability.json +2 -1
- package/bin/upgrade-dependencies.mjs +67 -6
- package/bin/upgrade-package.mjs +2 -0
- package/bin/windows-legacy/LegacyProcessBroker.build.json +3 -3
- package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
- package/bin/windows-legacy/src/LegacyNative.cs +60 -11
- package/bin/windows-legacy/src/LegacyProcessBroker.cs +317 -197
- package/bin/windows-legacy-process.mjs +12 -5
- package/docs/account-upgrades.md +17 -4
- package/docs/comparison.md +167 -0
- package/docs/compatibility.md +39 -0
- package/docs/configuration.md +146 -0
- package/docs/dashboards.md +125 -0
- package/docs/execution-context.md +11 -5
- package/docs/http-api.md +47 -0
- package/docs/install.md +52 -0
- package/docs/operations.md +99 -0
- package/docs/upgrades.md +324 -0
- package/package.json +11 -3
- package/tools/windows-legacy/README.md +118 -92
- package/tools/windows-legacy/inventory.mjs +8 -6
- package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -1
- package/ui/dist/third-party-manifest.json +6 -6
|
@@ -6,6 +6,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
|
|
7
7
|
const HELPER = fileURLToPath(new URL('./windows-legacy/LegacyProcessBroker.exe', import.meta.url));
|
|
8
8
|
const METADATA = fileURLToPath(new URL('./windows-legacy/LegacyProcessBroker.build.json', import.meta.url));
|
|
9
|
+
// Each wait outlasts the broker's own limit. The broker may take 15 s to read a request,
|
|
10
|
+
// then has a 120 s capture or verification phase, or a 120 s revalidation and a 20 s stop,
|
|
11
|
+
// each with a 10 s watchdog margin. MESSAGE_BYTES equals the broker's request limit.
|
|
12
|
+
const MESSAGE_BYTES = 262144;
|
|
13
|
+
const PREPARE_TIMEOUT_MS = 150000;
|
|
14
|
+
const VERIFY_TIMEOUT_MS = 150000;
|
|
15
|
+
const STOP_TIMEOUT_MS = 170000;
|
|
9
16
|
|
|
10
17
|
class LegacyBroker {
|
|
11
18
|
constructor() {
|
|
@@ -62,7 +69,7 @@ class LegacyBroker {
|
|
|
62
69
|
});
|
|
63
70
|
this.child.stdout.on('data', (chunk) => {
|
|
64
71
|
this.buffer += chunk;
|
|
65
|
-
if (this.buffer.length >
|
|
72
|
+
if (this.buffer.length > MESSAGE_BYTES) {
|
|
66
73
|
this.failure(new Error('Legacy broker response exceeded bounds'));
|
|
67
74
|
this.child.kill();
|
|
68
75
|
return;
|
|
@@ -128,7 +135,7 @@ class LegacyBroker {
|
|
|
128
135
|
|
|
129
136
|
encode(value) {
|
|
130
137
|
const bytes = Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
|
|
131
|
-
if (bytes.length >
|
|
138
|
+
if (bytes.length > MESSAGE_BYTES) throw new Error('Legacy broker request exceeded bounds');
|
|
132
139
|
return bytes;
|
|
133
140
|
}
|
|
134
141
|
|
|
@@ -143,7 +150,7 @@ class LegacyBroker {
|
|
|
143
150
|
this.encode(request);
|
|
144
151
|
this.start();
|
|
145
152
|
try {
|
|
146
|
-
const result = await this.request(request,
|
|
153
|
+
const result = await this.request(request, PREPARE_TIMEOUT_MS);
|
|
147
154
|
const plan = result.plan;
|
|
148
155
|
if (plan?.kind !== 'legacy-observed-process-set' ||
|
|
149
156
|
plan.protocol !== 1 ||
|
|
@@ -183,7 +190,7 @@ class LegacyBroker {
|
|
|
183
190
|
try {
|
|
184
191
|
const response = await this.request({
|
|
185
192
|
action: 'stop', token: this.token, planSha256: this.plan.planSha256,
|
|
186
|
-
},
|
|
193
|
+
}, STOP_TIMEOUT_MS);
|
|
187
194
|
const receipt = this.receipt(response.receipt);
|
|
188
195
|
const exit = await this.finish();
|
|
189
196
|
if (exit.code !== 0 ||
|
|
@@ -223,7 +230,7 @@ class LegacyBroker {
|
|
|
223
230
|
this.encode({ action: 'verify', expected: plan });
|
|
224
231
|
this.start();
|
|
225
232
|
try {
|
|
226
|
-
const response = await this.request({ action: 'verify', expected: plan },
|
|
233
|
+
const response = await this.request({ action: 'verify', expected: plan }, VERIFY_TIMEOUT_MS);
|
|
227
234
|
const receipt = this.receipt(response.receipt);
|
|
228
235
|
const exit = await this.finish();
|
|
229
236
|
if (exit.code !== 0) throw new Error('Legacy verification broker exit failed');
|
package/docs/account-upgrades.md
CHANGED
|
@@ -46,7 +46,11 @@ Administrators membership, and complete native authorization checks. Restricted,
|
|
|
46
46
|
AppContainer, impersonating or unverified cross-account contexts refuse.
|
|
47
47
|
No account label, saved administrator flag or group membership alone grants
|
|
48
48
|
authority. The product does not request UAC, enable privileges, impersonate,
|
|
49
|
-
supply passwords, create accounts or change existing permissions.
|
|
49
|
+
supply passwords, create accounts or change existing permissions. The one
|
|
50
|
+
exception is outside this path: the default-scope `upgrade --fix-legacy-access`
|
|
51
|
+
step, which an administrator runs explicitly for a 1.3 install that was set up
|
|
52
|
+
from an administrator terminal. It changes only that task's run-as user entry and
|
|
53
|
+
the owner of that install's `bin\cli.mjs`.
|
|
50
54
|
|
|
51
55
|
## Where code executes
|
|
52
56
|
|
|
@@ -122,9 +126,11 @@ possible. Manual/external task starts are not claimed suppressed. Conflicting
|
|
|
122
126
|
generations or revisions refuse. Because `IgnoreNew` can block a second task run
|
|
123
127
|
while the worker is active, verified legacy rollback launches the original
|
|
124
128
|
supervisor locally **inside the original-user worker**, not as the administrator.
|
|
125
|
-
Legacy captured roots
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
Legacy captured roots are checked for the worker's session before stop; native
|
|
130
|
+
held-generation revalidation remains stop authority. Plans no longer list
|
|
131
|
+
descendants: the processes the 1.3 bridge started are found and stopped by the
|
|
132
|
+
broker during the stop, through the same handles, without a separate session query.
|
|
133
|
+
Processes that cannot be traced to the bridge are never session-query or kill targets.
|
|
128
134
|
The compact session request accepts at most 512 unique captured identities and
|
|
129
135
|
128 KiB of UTF-8. PID, FILETIME and current-account/session fields are checked
|
|
130
136
|
before discovery. Session queries use at most 32 exact IDs per batch and four
|
|
@@ -138,6 +144,13 @@ pre-bootstrap task revision after fresh authorization. No staging or stop is
|
|
|
138
144
|
authorized by that decline. Failed authorization or restoration remains an
|
|
139
145
|
explicit uncertain outcome.
|
|
140
146
|
|
|
147
|
+
An upgrade refused before anything stops, for example because `servers.json`
|
|
148
|
+
changed while the target downloaded, puts the current version back in service
|
|
149
|
+
and restores the original task before its journal settles, so a later
|
|
150
|
+
`--recover` changes nothing. If the current version could not be resumed or the
|
|
151
|
+
task could not be restored, the journal stays open and `--recover` finishes the
|
|
152
|
+
job.
|
|
153
|
+
|
|
141
154
|
### Controller-only failure before instance publication
|
|
142
155
|
|
|
143
156
|
If bootstrap happened but automatic restoration failed before any managed
|
|
@@ -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).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Version compatibility
|
|
2
|
+
|
|
3
|
+
How the 1.3.x and 2.x lines work together. For npm channels and exact-version installs, see
|
|
4
|
+
[install details](install.md). To replace a running installation, see
|
|
5
|
+
[upgrades and rollback](upgrades.md).
|
|
6
|
+
|
|
7
|
+
This documentation describes **2.1.2**. For 1.3.x immediate-save behavior, see the
|
|
8
|
+
[legacy documentation](https://github.com/girishkvs/mcp-pacemaker/blob/v1.3.1/README.md)
|
|
9
|
+
and [support policy](../SECURITY.md#supported-versions). Those documents do not establish
|
|
10
|
+
that a legacy version is available on npm.
|
|
11
|
+
|
|
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.2**
|
|
14
|
+
candidate requires fresh exact-byte compatibility and service-replacement gates.
|
|
15
|
+
A 2.x client handles legacy immediate saves. A 1.x client cannot write pooling settings to a
|
|
16
|
+
2.x bridge: update the CLI and refresh old dashboard tabs. Before downgrading, settle pending
|
|
17
|
+
or interrupted transactions with 2.x. See the [real-version gates](../CONTRIBUTING.md#real-version-compatibility-gates)
|
|
18
|
+
for exact pairs; only the exact executed pairs are covered.
|
|
19
|
+
|
|
20
|
+
## Upgrading from 1.3.0
|
|
21
|
+
|
|
22
|
+
Version 2.0.0 changes the configuration-save protocol; it does not require a new `servers.json`
|
|
23
|
+
format. Update the bridge and CLI together, then reload any open dashboard pages. A 1.3.0
|
|
24
|
+
client with a current nonce gets HTTP 409 before staging, with an instruction to update the
|
|
25
|
+
client. An already-open tab retains the old nonce after a bridge restart and gets HTTP 401;
|
|
26
|
+
refresh it to load the new dashboard and admin session.
|
|
27
|
+
|
|
28
|
+
Custom API clients must send `x-mcp-pooling-batch: 1`. Treat HTTP 202 as acceptance, not
|
|
29
|
+
completion, and follow the returned batch ID in `prewarm.batches` until it is applied,
|
|
30
|
+
cancelled or failed. CLI scripts must likewise wait for application before relying on new
|
|
31
|
+
settings. Undo now restores the complete batch and may affect several servers.
|
|
32
|
+
Updated clients still understand the immediate HTTP 200 response from a 1.3.0 bridge.
|
|
33
|
+
|
|
34
|
+
Before downgrading, cancel or apply pending changes with 2.0 and resolve any interrupted
|
|
35
|
+
transaction using 2.0 recovery. Stop that bridge before starting 1.3.0 with the active config.
|
|
36
|
+
Do not use a 1.3.0 binary to recover 2.0 transaction files or delete those files to bypass a
|
|
37
|
+
recovery error. Keep a protected backup of the configuration before changing versions.
|
|
38
|
+
If the writer worker exits, further automatic edits are refused until the bridge restarts;
|
|
39
|
+
existing MCP sessions continue.
|
|
@@ -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
|
+

|
|
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
|
+

|
|
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.
|
|
@@ -71,7 +71,7 @@ The public workflow separates three roles rather than assuming an OS label impli
|
|
|
71
71
|
| --- | --- | --- |
|
|
72
72
|
| `test (ubuntu-latest\|macos-latest, 20\|22\|24)` | Stock hosted | Ordinary user-runtime source and compatibility suites |
|
|
73
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,
|
|
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
75
|
|
|
76
76
|
A stock hosted Windows runner signs in as the built-in Administrator with a full elevated token, and
|
|
77
77
|
Task Scheduler ignores `RunLevel` for that account, so that account cannot host the ordinary
|
|
@@ -83,10 +83,16 @@ unexecuted.
|
|
|
83
83
|
|
|
84
84
|
A provisioned account signs in with a password logon and has no interactive logon session, so the
|
|
85
85
|
four `SESSION_REQUIRED_TESTS` files that bind one stay in the session lane. They are still required,
|
|
86
|
-
not skipped
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
90
96
|
|
|
91
97
|
The CI provider bounds job lifetime and owns job teardown; the contract entry
|
|
92
98
|
point does not claim to provide VM or network isolation.
|