mergestorm-mcp 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -6
- package/dist/index.js +50 -45
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Stdio MCP server for Mergestorm. Tools: `whoami`, `credits`, `review_list`, `review_submit`, `review_wait`, `review_wait_pr`, `review_dismiss`, `stack_adopt`, `stack_list`, `stack_set`, `stack_status`, `stack_wait`, `queue_status`, `settings_get`, `settings_set`.
|
|
4
4
|
|
|
5
|
-
Every tool declares MCP `annotations
|
|
5
|
+
Every tool declares MCP `annotations` for read-only access, destructive changes, idempotence, and external systems. Reads are read-only; submission, adoption, dismissal, and policy changes are writes. Tools that can change existing policy, topology, or review gates are marked potentially destructive even when some inputs only preview or read. Annotations describe effects; the client still controls approval and authorization.
|
|
6
6
|
|
|
7
7
|
`review_wait({ job_id, timeout_s? })` reads a local review job by the `job_id` that `review_submit` returned. `review_wait_pr({ owner, repo, pr_number, after_sha?, pass?, after_pass?, timeout_s? })` reads the Vortex pass on a GitHub PR. Both poll one 45s slice by default; `timeout_s: 0` reads once without polling.
|
|
8
8
|
|
|
@@ -18,22 +18,86 @@ Every tool declares MCP `annotations`. The reads carry `readOnlyHint: true`; `re
|
|
|
18
18
|
|
|
19
19
|
The stack tools expose reads plus one scoped policy write:
|
|
20
20
|
|
|
21
|
-
- `stack_adopt({ owner, repo, pr_number, auto_land?, auto_review?, auto_patch? })` adopts an open PR chain with optional per-stack policy. Omitted policy does not force Cyclone off. `auto_review` and `auto_patch` accept `true`, `false`, or `null`; `null` clears the override. Read `stack_status` with the returned `result.stack.id` to verify policy and Cyclone ownership.
|
|
21
|
+
- `stack_adopt({ owner, repo, pr_number, auto_land?, auto_review?, auto_patch? })` adopts an open PR chain with optional per-stack policy. Omitted policy does not force Cyclone off. `auto_review` and `auto_patch` accept `true`, `false`, or `null`; `null` clears the override. Read `stack_status` with the returned `result.stack.id` to verify policy and Cyclone ownership. Adoption needs the Mergestorm Surge GitHub App on the repository (Cyclone is the auto-patch App). A refusal keeps the code `cyclone_not_connected` or `cyclone_not_installed`; its message, and `error.reason` starting with `surge_` when Surge is the one to install, say which App is missing.
|
|
22
22
|
- `stack_list` returns the current API key owner's registered stacks.
|
|
23
23
|
- `stack_set({ stack_id, auto_land?, auto_review?, auto_patch? })` sets per-stack policy on one owned stack. `auto_land` flips Auto land. `auto_review` and `auto_patch` pin Vortex auto-review or Cyclone auto-patch for that stack in either direction (`true` or `false`); `null` clears the pin so the stack follows the account setting. Pass at least one key. Account settings are never changed here.
|
|
24
24
|
- `stack_status({ stack_id })` returns one owned stack with enriched checks and agent state, plus `attention`, `issues`, and `currentCandidate` from the same blocker rules as `stack_wait`, read against that stack's own merge queue. It returns a structured `stack_not_found` or `rate_limited` error.
|
|
25
25
|
- `stack_wait({ stack_id, timeout_s?, enrolled_head_sha?, after_finished_at?, bounce_id? })` waits for a stack or queue state change and returns the latest snapshots. `timeout_s: 0` returns one snapshot; a positive timeout after a snapshot with no attention returns the current `waiting` or `in_progress` status with selectors to use for the next call. An unread deadline returns `failed`. While Cyclone or Vortex is still working on the blocked PR, `stack_wait` and `stack_status` return `in_progress` instead of `attention`, with `busy[]` naming the PR, blocker, and agent, and `agents` carrying that PR's `vortexStatus`, `cycloneStatus`, `vortexReview`, and busy flags. A nonempty `busy[]` means wait. A queued Vortex run holds for at most 15 minutes, `seam_pending` alone does not hold, and reviewing or patching holds until the run finishes or its lease or check run is cleared. A blocker no agent run clears (a merge conflict vs the live parent, a restack `Conflict` or `Restack failed`, a Draft PR, a seam review pending with no review running, "Seam review stuck in reviewing, no review running", "Seam re-review pending, no review running", "Seam verdict is for an older head, no review running", or a review skipped for review quota) is still named during that `in_progress`, in `blocker`, `prNumber`, `headSha`, and `issues[]`, with `actAfter: "agents_idle"` and `waitingOn` listing the busy agents; act when a snapshot after the agents finish returns `attention`, re-reading the blocker then. For the three stuck-seam blockers, `repair` has kind `seam_review_stuck`: do not patch or push; tell the human to click Continue, which re-queues the seam review at the live head. `repair` is included when another concrete repair hint is available. Blockers the agents can change keep `blocker` and `actAfter` null. If a hold looks stuck, refresh `stack_status` and tell the human.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
The background CLI watcher is optional. It monitors Mergestorm without model calls, but does not itself resume an agent conversation. Hand waiting to `mg stack watch <stack-id>` only after the host has confirmed a notification that resumes the task on completion or output matching `MS-WATCH (ATTENTION|LANDED)`. Without that facility, use bounded `stack_wait` calls with `watch.next.args`, or tell the user automatic follow-up is unavailable. A shell session ID is not proof of a wakeup subscription.
|
|
28
|
+
|
|
29
|
+
`watch.next.background` is the monitoring command, not a registered notification. Keep the latest cursor across `stack_wait` calls and reconnects. Cancellation stops the current wait, not the remote stack. After a host notification, read `stack_status` once and follow the returned repair and ownership rules.
|
|
30
|
+
|
|
31
|
+
The watcher exits on attention (exit 3), a failed read that failed again on retry (exit 3), a closed, archived, or missing stack (exit 3), or a confirmed landing (exit 0). Verify missing stacks before reporting a landing. The stack is still not done until it lands. Do not subscribe to GitHub events to wait on a stack.
|
|
28
32
|
|
|
29
33
|
Auth is `MERGESTORM_API_KEY`, then `~/.mergestorm/config.json` (same as `mg`).
|
|
30
34
|
|
|
31
35
|
`context_files` passed to `review_submit` may only read files under the host-configured sandbox root (`MERGESTORM_SANDBOX_ROOT`, default: the MCP server's working directory), so a prompt-injected agent cannot use them to exfiltrate files outside it.
|
|
32
36
|
|
|
37
|
+
## Client setup
|
|
38
|
+
|
|
39
|
+
Requires Node.js 22+ and a Mergestorm API key. Run `mg login` on the machine running the MCP server, or supply `MERGESTORM_API_KEY` through the host's environment. The stdio server uses that credential; it does not provide an OAuth endpoint. Remote hosts need their own configuration and access to any reviewed checkout.
|
|
40
|
+
|
|
41
|
+
### Codex
|
|
42
|
+
|
|
33
43
|
```bash
|
|
34
|
-
|
|
44
|
+
codex mcp add mergestorm -- npx -y mergestorm-mcp
|
|
35
45
|
```
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
For explicit configuration in `~/.codex/config.toml` (replace the checkout path):
|
|
48
|
+
|
|
49
|
+
```toml
|
|
50
|
+
[mcp_servers.mergestorm]
|
|
51
|
+
command = "npx"
|
|
52
|
+
args = ["-y", "mergestorm-mcp"]
|
|
53
|
+
startup_timeout_sec = 30
|
|
54
|
+
tool_timeout_sec = 60
|
|
55
|
+
|
|
56
|
+
[mcp_servers.mergestorm.env]
|
|
57
|
+
MERGESTORM_SANDBOX_ROOT = "/absolute/path/to/checkout"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The server reads the login saved by `mg login`. If using an environment key instead, pass it with `env_vars = ["MERGESTORM_API_KEY"]` in the server table. Approval remains controlled by the host; annotations do not authorize a write. [Codex MCP configuration](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).
|
|
61
|
+
|
|
62
|
+
### Claude Code
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
claude mcp add --transport stdio mergestorm -- npx -y mergestorm-mcp
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Launch with `MERGESTORM_SANDBOX_ROOT` set to the checkout when local file context is needed. [Claude Code MCP configuration](https://code.claude.com/docs/en/mcp).
|
|
69
|
+
|
|
70
|
+
### Cursor
|
|
71
|
+
|
|
72
|
+
Add to project `.cursor/mcp.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"mergestorm": {
|
|
78
|
+
"type": "stdio",
|
|
79
|
+
"command": "npx",
|
|
80
|
+
"args": ["-y", "mergestorm-mcp"],
|
|
81
|
+
"env": {
|
|
82
|
+
"MERGESTORM_SANDBOX_ROOT": "${workspaceFolder}"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For global `~/.cursor/mcp.json`, use an explicit sandbox path. Authentication uses the saved `mg login` on the server machine. [Cursor MCP configuration](https://cursor.com/docs/mcp).
|
|
90
|
+
|
|
91
|
+
### Connection and watch checks
|
|
92
|
+
|
|
93
|
+
In each client, confirm the server connects and lists tools, then call `whoami`. It should return the account key prefix without exposing the full key. On a known owned stack, `stack_wait` with `timeout_s: 0` should return one snapshot; use `watch.next.args` for the next bounded wait. Default waits use 45-second slices, so leave the host tool timeout above 45 seconds.
|
|
94
|
+
|
|
95
|
+
Standard stdio calls are portable; task wakeups are host features. Claude Code supports background notifications in some session modes, while availability in Codex or Cursor depends on the host and execution mode. Confirm the actual notification facility before claiming automatic continuation. This server does not advertise Claude channels or install a Codex automation or Cursor extension. After reconnecting, reuse the last returned cursor; if it was lost, take a fresh snapshot rather than claiming unseen events were handled.
|
|
96
|
+
|
|
97
|
+
Tests exercise the SDK protocol, not the UI or notification delivery of all three apps. Client configuration and the host's ability to resume the task should be checked in that client.
|
|
98
|
+
|
|
99
|
+
## Results and errors
|
|
100
|
+
|
|
101
|
+
Successful results retain their existing fields in `structuredContent`, including meaningful `null` values. The same data is serialized as a text block after the human summary for clients that consume only text content. Shared workflow policy stays in server initialization instructions; stack tools include a short reminder to follow the returned watch cursor.
|
|
38
102
|
|
|
39
|
-
|
|
103
|
+
Failures retain `isError: true` and a readable message. `structuredContent.error` includes the original message and, when supplied by the CLI error, `code`, `reason`, `exit_code`, and `retry_after_seconds`. Missing retry metadata does not authorize repeating a write; inspect the relevant state first. The server does not manufacture request IDs or label arbitrary transport failures safe to retry.
|
package/dist/index.js
CHANGED
|
@@ -229,7 +229,7 @@ function stackWaitStatusLine(envelope) {
|
|
|
229
229
|
...envelope.cursor.afterFinishedAt !== void 0 ? [`after_finished_at ${JSON.stringify(envelope.cursor.afterFinishedAt)}`] : [],
|
|
230
230
|
...envelope.cursor.bounceId !== void 0 ? [`bounce_id ${JSON.stringify(envelope.cursor.bounceId)}`] : []
|
|
231
231
|
];
|
|
232
|
-
return `${base} \xB7 watch not finished: run ${stackWatchBackgroundCommand(envelope.stackId)} as a background command and end your turn
|
|
232
|
+
return `${base} \xB7 watch not finished: only with a confirmed host notification that resumes this task, run ${stackWatchBackgroundCommand(envelope.stackId)} as a background command and end your turn; otherwise call stack_wait again with timeout_s 45 and the same cursor: ${selectors.join(", ")}`;
|
|
233
233
|
}
|
|
234
234
|
async function stackWait(input, cfg, deps = {}) {
|
|
235
235
|
const args = z.object(stackWaitSchema).parse(input);
|
|
@@ -756,7 +756,9 @@ import {
|
|
|
756
756
|
function settingsSummary(settings) {
|
|
757
757
|
const autoPatch = settings.auto_patch_enabled ? "on" : "off";
|
|
758
758
|
const cyclone = settings.cyclone_connected ? "Cyclone connected" : "Cyclone not connected";
|
|
759
|
-
return `auto_patch ${autoPatch} \xB7 ${cyclone}`;
|
|
759
|
+
if (typeof settings.stacks_ready !== "boolean") return `auto_patch ${autoPatch} \xB7 ${cyclone}`;
|
|
760
|
+
const stacks = settings.stacks_ready ? "stacks, merge queue and Auto land ready" : "stacks, merge queue and Auto land not ready (install Mergestorm Surge)";
|
|
761
|
+
return `auto_patch ${autoPatch} \xB7 ${cyclone} (auto patch) \xB7 ${stacks}`;
|
|
760
762
|
}
|
|
761
763
|
async function settingsGet(cfg) {
|
|
762
764
|
const settings = await getSettings(cfg);
|
|
@@ -933,6 +935,8 @@ async function stackAdopt(input, cfg) {
|
|
|
933
935
|
error: {
|
|
934
936
|
code: err instanceof CommandError9 ? err.code ?? "stack_adopt_failed" : "stack_adopt_failed",
|
|
935
937
|
message,
|
|
938
|
+
...err instanceof CommandError9 ? { exit_code: err.exitCode } : {},
|
|
939
|
+
...err instanceof CommandError9 && err.reason !== void 0 ? { reason: err.reason } : {},
|
|
936
940
|
...err instanceof CommandError9 && err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
937
941
|
}
|
|
938
942
|
},
|
|
@@ -1118,15 +1122,15 @@ var MCP_STACK_DONE_INSTRUCTIONS = [
|
|
|
1118
1122
|
"When stack work is done:",
|
|
1119
1123
|
"- Stack work is done only when the stack is landed, or when the human takes it back (archives it or tells you to stop). A clean push, a submit, an adopt, or one merged layer is not done: merging a lower layer can break a layer above it.",
|
|
1120
1124
|
"- Every stack_wait, stack_status, stack_adopt, and stack_set result carries watch: {done, until, reason, next, message}. While watch.done is false, keep calling stack_wait with watch.next.args (the returned cursor), 45s at a time, or hand the waiting to the background watcher below, and do not end your task. Stop only when watch.done is true.",
|
|
1121
|
-
"-
|
|
1125
|
+
"- Only when your host has a confirmed notification that resumes this task, run `mg stack watch <stack-id>` as a background command with a notification on output matching `MS-WATCH (ATTENTION|LANDED)`, then end your turn. The watcher runs stack_wait against the Mergestorm API in slices of at most 300s, carries the cursor, stays silent while the stack is waiting or in_progress (busy[] holds and actAfter included), and costs no model tokens. It exits on attention (`MS-WATCH ATTENTION pr=<n> head=<sha> blocker=... repair=<kind>`, exit 3), on a read that failed twice (`MS-WATCH ATTENTION failed`, exit 3; tell the human the stack is no longer watched), on a closed, archived, or missing stack (`MS-WATCH ATTENTION stack=<id> reason=<reason>`, exit 3; verify missing stacks before reporting a landing), or on a confirmed landing (`MS-WATCH LANDED stack=<id> reason=landed`, exit 0). On an attention wake, read stack_status once, fix the blocker per mergestorm-bounce-watch, push, and start `mg stack watch <stack-id> --head <pushed-sha>` again. The command monitors the stack; it cannot wake an agent by itself. Without a confirmed notification, keep using stack_wait with watch.next.args, or report that automatic follow-up is unavailable before ending the turn. Ending your turn while the watcher runs is not ending the task. Do not subscribe to GitHub events to wait on a stack.",
|
|
1122
1126
|
"- A layer PR shown as merged is merged into mg-stack-<n>, not main. The stack is landed only when the land PR (head mg-stack-<n>) merges into main or watch.done says landed. Never judge landing from GitHub PR state alone.",
|
|
1123
|
-
"- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending
|
|
1127
|
+
"- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending) is a wait, not attention."
|
|
1124
1128
|
].join("\n");
|
|
1125
1129
|
var MCP_STACK_BASE_INSTRUCTIONS = [
|
|
1126
1130
|
"Stack PR bases (Mergestorm sets these; leave them):",
|
|
1127
1131
|
"- Stack of 2+ PRs: after adopt (stack_adopt, mg stack adopt, or mg stack submit) the bottom PR's GitHub base is the owned trunk mg-stack-<n>, not main. That is correct. Layer 2 is based on layer 1; layer 3+ on the mg-park-* freeze; the git parent of layer 2+ is still the layer below. A 1-PR stack stays on main and gets no mg-stack-<n>.",
|
|
1128
1132
|
"- Never retarget a stack PR's GitHub base (gh pr edit --base, a REST base change, or the UI). Moving the bottom to main makes Mergestorm abandon the stack's review unit; setting mg-stack-<n> again does not restore it. If a base was already moved, stop and tell the human; do not run stack land or re-adopt to repair it.",
|
|
1129
|
-
"- mg-stack-<n> is the bottom PR's live parent: merging it into the bottom branch is that PR's conflict repair (bounce-watch rules). Never push to mg-stack-<n> except as the land PR's own repair: once every layer is promoted, the land PR's head is mg-stack-<n> and its live parent is its base (main), so merging main into mg-stack-<n>, a CI fix on it,
|
|
1133
|
+
"- mg-stack-<n> is the bottom PR's live parent: merging it into the bottom branch is that PR's conflict repair (bounce-watch rules). Never push to mg-stack-<n> except as the land PR's own repair: once every layer is promoted, the land PR's head is mg-stack-<n> and its live parent is its base (main), so merging main into mg-stack-<n>, or a CI fix on it, with an ordinary push (no force) is that repair. Fix the land PR only when busy[] shows no Cyclone run on it. Never rebase or merge layer 2+ onto mg-stack-<n> or onto main. mg-park-* is never a merge or rebase target.",
|
|
1130
1134
|
"- mg stack submit prints the base each PR opened with; the bottom moves to mg-stack-<n> right after. stack_status trunkBranch is the current trunk."
|
|
1131
1135
|
].join("\n");
|
|
1132
1136
|
var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
@@ -1149,47 +1153,48 @@ var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
|
1149
1153
|
`- While the stack has a live merge queue entry, stack_wait carries queueWait (the entry's wait reason, how long it has been held, and the stack queued ahead of it) and the status line names it. The watch returns attention, blocker "Merge queue stuck: ..." and repair kind queue_stalled, when the entry (or the entry ahead of it) has been held for 45 minutes without landing or bouncing, or has sat queued for 45 minutes with nothing working on it or ahead of it.`,
|
|
1150
1154
|
'- When no Vortex run works on the head and its review was skipped because auto-review is off (blocker "Vortex auto-review is off, so this head was not reviewed") or because billing could not be confirmed ("Vortex skipped this head: it could not confirm billing"), or it covered only part of the PR ("Vortex reviewed only part of this PR"), the watch returns attention at once with repair kind vortex_skipped. Do not patch for it; the repair names the mention or setting that gets the head reviewed.',
|
|
1151
1155
|
'- A live-head Vortex request_changes handoff returns attention with blocker "Vortex findings need a person" and repair kind vortex_findings; verify the review and inline findings against the live head before repair.',
|
|
1152
|
-
"-
|
|
1153
|
-
'- More states the watch names: a Vortex review skipped because the PR author is on the ignored authors list or because reviews are turned off for the repository is attention at once with repair kind vortex_skipped; a Vortex retry that is scheduled ("Vortex hit a temporary error on this head and will retry by itself") is a wait listed in issues[], never attention; "Review failed" carries repair kind vortex_failed (comment @mergestorm-vortex review or push a new commit; if it fails again tell the human); a PR open on mg-stack-N that is not a layer of the stack is attention at once, blocker "The land PR cannot be queued: ...", repair kind members_remaining (do not patch or push; close, retarget or adopt that PR)
|
|
1156
|
+
"- Cyclone patches a land PR when auto-patch is on for that stack (its override, else the account setting); otherwise the watching agent fixes the land PR's blocker on mg-stack-<n>.",
|
|
1157
|
+
'- More states the watch names: a Vortex review skipped because the PR author is on the ignored authors list or because reviews are turned off for the repository is attention at once with repair kind vortex_skipped; a Vortex retry that is scheduled ("Vortex hit a temporary error on this head and will retry by itself") is a wait listed in issues[], never attention; "Review failed" carries repair kind vortex_failed (comment @mergestorm-vortex review or push a new commit; if it fails again tell the human); a PR open on mg-stack-N that is not a layer of the stack is attention at once, blocker "The land PR cannot be queued: ...", repair kind members_remaining (do not patch or push; close, retarget or adopt that PR).',
|
|
1154
1158
|
"- Use the mergestorm-bounce-watch skill for the fix-then-watch policy."
|
|
1155
1159
|
].join("\n");
|
|
1156
1160
|
|
|
1157
1161
|
// src/server.ts
|
|
1158
1162
|
var MCP_SERVER_NAME = "mergestorm";
|
|
1159
|
-
var MCP_SERVER_VERSION = "0.2.
|
|
1163
|
+
var MCP_SERVER_VERSION = "0.2.13";
|
|
1160
1164
|
var MCP_SERVER_INSTRUCTIONS = [
|
|
1161
1165
|
MCP_STACK_DONE_INSTRUCTIONS,
|
|
1162
1166
|
MCP_PR_LOOP_INSTRUCTIONS,
|
|
1163
1167
|
MCP_STACK_BASE_INSTRUCTIONS,
|
|
1164
1168
|
MCP_STACK_WATCH_INSTRUCTIONS
|
|
1165
1169
|
].join("\n\n");
|
|
1166
|
-
var STACK_WATCH_RESULT_NOTE =
|
|
1167
|
-
function stripNulls(value) {
|
|
1168
|
-
if (Array.isArray(value)) {
|
|
1169
|
-
return value.map((item) => stripNulls(item));
|
|
1170
|
-
}
|
|
1171
|
-
if (value !== null && typeof value === "object") {
|
|
1172
|
-
const out = {};
|
|
1173
|
-
for (const [key, val] of Object.entries(value)) {
|
|
1174
|
-
if (val !== null && val !== void 0) out[key] = stripNulls(val);
|
|
1175
|
-
}
|
|
1176
|
-
return out;
|
|
1177
|
-
}
|
|
1178
|
-
return value;
|
|
1179
|
-
}
|
|
1170
|
+
var STACK_WATCH_RESULT_NOTE = " Follow watch.next.args until watch.done; background watching requires a confirmed host notification. See server instructions.";
|
|
1180
1171
|
function ok(payload) {
|
|
1181
1172
|
return {
|
|
1182
1173
|
...payload.isError ? { isError: true } : {},
|
|
1183
|
-
content: [
|
|
1184
|
-
|
|
1174
|
+
content: [
|
|
1175
|
+
{ type: "text", text: payload.summary },
|
|
1176
|
+
{ type: "text", text: JSON.stringify(payload.data) }
|
|
1177
|
+
],
|
|
1178
|
+
structuredContent: payload.data
|
|
1185
1179
|
};
|
|
1186
1180
|
}
|
|
1187
1181
|
function fail(err) {
|
|
1188
|
-
const message = err instanceof CommandError11 ? err.code ? `Mergestorm request failed (${err.code}): ${err.message}.` : err.message : err instanceof Error ? err.message : String(err);
|
|
1189
|
-
return {
|
|
1182
|
+
const message = err instanceof CommandError11 ? err.code ? `Mergestorm request failed (${err.code}${err.reason ? `, reason ${err.reason}` : ""}): ${err.message}.` : err.message : err instanceof Error ? err.message : String(err);
|
|
1183
|
+
return ok({
|
|
1190
1184
|
isError: true,
|
|
1191
|
-
|
|
1192
|
-
|
|
1185
|
+
summary: message,
|
|
1186
|
+
data: {
|
|
1187
|
+
error: {
|
|
1188
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1189
|
+
...err instanceof CommandError11 ? {
|
|
1190
|
+
exit_code: err.exitCode,
|
|
1191
|
+
...err.code !== void 0 ? { code: err.code } : {},
|
|
1192
|
+
...err.reason !== void 0 ? { reason: err.reason } : {},
|
|
1193
|
+
...err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
1194
|
+
} : {}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1193
1198
|
}
|
|
1194
1199
|
function createMergestormMcpServer() {
|
|
1195
1200
|
const server2 = new McpServer(
|
|
@@ -1208,7 +1213,7 @@ function createMergestormMcpServer() {
|
|
|
1208
1213
|
{
|
|
1209
1214
|
title: "Who am I",
|
|
1210
1215
|
description: "Current Mergestorm API key prefix, plan, and API base.",
|
|
1211
|
-
annotations: { readOnlyHint: true }
|
|
1216
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1212
1217
|
},
|
|
1213
1218
|
async () => {
|
|
1214
1219
|
try {
|
|
@@ -1223,7 +1228,7 @@ function createMergestormMcpServer() {
|
|
|
1223
1228
|
{
|
|
1224
1229
|
title: "Credits",
|
|
1225
1230
|
description: "Current standard-credit usage and reset time.",
|
|
1226
|
-
annotations: { readOnlyHint: true }
|
|
1231
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1227
1232
|
},
|
|
1228
1233
|
async () => {
|
|
1229
1234
|
try {
|
|
@@ -1241,7 +1246,7 @@ function createMergestormMcpServer() {
|
|
|
1241
1246
|
inputSchema: {
|
|
1242
1247
|
limit: z3.number().int().min(1).max(50).optional()
|
|
1243
1248
|
},
|
|
1244
|
-
annotations: { readOnlyHint: true }
|
|
1249
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1245
1250
|
},
|
|
1246
1251
|
async ({ limit }) => {
|
|
1247
1252
|
try {
|
|
@@ -1289,7 +1294,7 @@ function createMergestormMcpServer() {
|
|
|
1289
1294
|
job_id: z3.string().min(1),
|
|
1290
1295
|
timeout_s: z3.number().min(0).max(300).optional()
|
|
1291
1296
|
},
|
|
1292
|
-
annotations: { readOnlyHint: true }
|
|
1297
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1293
1298
|
},
|
|
1294
1299
|
async ({ job_id, timeout_s }) => {
|
|
1295
1300
|
try {
|
|
@@ -1313,7 +1318,7 @@ function createMergestormMcpServer() {
|
|
|
1313
1318
|
after_pass: z3.number().int().min(1).optional(),
|
|
1314
1319
|
timeout_s: z3.number().min(0).max(300).optional()
|
|
1315
1320
|
},
|
|
1316
|
-
annotations: { readOnlyHint: true }
|
|
1321
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1317
1322
|
},
|
|
1318
1323
|
async ({ owner, repo, pr_number, after_sha, pass, after_pass, timeout_s }) => {
|
|
1319
1324
|
try {
|
|
@@ -1350,7 +1355,7 @@ function createMergestormMcpServer() {
|
|
|
1350
1355
|
evidence_url: z3.string().url().optional(),
|
|
1351
1356
|
preview: z3.boolean().optional()
|
|
1352
1357
|
},
|
|
1353
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
1358
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
1354
1359
|
},
|
|
1355
1360
|
async (args) => {
|
|
1356
1361
|
try {
|
|
@@ -1364,9 +1369,9 @@ function createMergestormMcpServer() {
|
|
|
1364
1369
|
"stack_adopt",
|
|
1365
1370
|
{
|
|
1366
1371
|
title: "Adopt PR stack",
|
|
1367
|
-
description: "Adopt an open GitHub PR chain into a Mergestorm stack. auto_land is boolean; auto_review and auto_patch accept true, false, or null, where null clears the override. Omitted auto_land seeds from auto_land_default; omitted auto_review and auto_patch overrides are not seeded. Never changes account settings. Adoption
|
|
1372
|
+
description: "Adopt an open GitHub PR chain into a Mergestorm stack. auto_land is boolean; auto_review and auto_patch accept true, false, or null, where null clears the override. Omitted auto_land seeds from auto_land_default; omitted auto_review and auto_patch overrides are not seeded. Never changes account settings. Adoption needs the Mergestorm Surge GitHub App on the repository. Stacks, the merge queue and Auto land run through that App; Cyclone is the auto-patch App. Without it the API returns cyclone_not_connected or cyclone_not_installed; the message, and error.reason starting with surge_ when Surge is the one to install, say which App is missing \u2014 do not retry, tell the human what the message says. Read stack_status with result.stack.id afterward to verify policy and Cyclone ownership. With 2+ PRs, adopt moves the bottom PR's GitHub base to mg-stack-<n> and parks PR3+ on an mg-park-* freeze; a 1-PR stack stays on main. Leave those bases. Never retarget the bottom back to main." + STACK_WATCH_RESULT_NOTE,
|
|
1368
1373
|
inputSchema: stackAdoptSchema,
|
|
1369
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
1374
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
1370
1375
|
},
|
|
1371
1376
|
async (args) => {
|
|
1372
1377
|
try {
|
|
@@ -1381,7 +1386,7 @@ function createMergestormMcpServer() {
|
|
|
1381
1386
|
{
|
|
1382
1387
|
title: "List stacks",
|
|
1383
1388
|
description: "List the current user's Mergestorm stacks.",
|
|
1384
|
-
annotations: { readOnlyHint: true }
|
|
1389
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1385
1390
|
},
|
|
1386
1391
|
async () => {
|
|
1387
1392
|
try {
|
|
@@ -1402,7 +1407,7 @@ function createMergestormMcpServer() {
|
|
|
1402
1407
|
auto_review: z3.boolean().nullable().optional(),
|
|
1403
1408
|
auto_patch: z3.boolean().nullable().optional()
|
|
1404
1409
|
},
|
|
1405
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
1410
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
1406
1411
|
},
|
|
1407
1412
|
async ({ stack_id, auto_land, auto_review, auto_patch }) => {
|
|
1408
1413
|
try {
|
|
@@ -1426,7 +1431,7 @@ function createMergestormMcpServer() {
|
|
|
1426
1431
|
inputSchema: {
|
|
1427
1432
|
stack_id: z3.string().min(1)
|
|
1428
1433
|
},
|
|
1429
|
-
annotations: { readOnlyHint: true }
|
|
1434
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1430
1435
|
},
|
|
1431
1436
|
async ({ stack_id }) => {
|
|
1432
1437
|
try {
|
|
@@ -1442,12 +1447,12 @@ function createMergestormMcpServer() {
|
|
|
1442
1447
|
title: "Wait for stack attention",
|
|
1443
1448
|
description: `Wait for a stack to need attention. Returns a mergestorm.stack_watch/v1 envelope; timeout_s: 0 returns one snapshot and timeout_s: 1-45 waits up to that many seconds, returning the current status (waiting or in_progress) if a snapshot was read and no attention is found. A timeout with either of those statuses is not a failure; waiting and in_progress end this slice, and the next call is stack_wait again with the same cursor. A timeout of failed means no assessment was produced. A stack that no longer exists returns status failed with watch.done true (reason not_found) and is not an error. While Cyclone or Vortex is still working on the blocked PR it returns in_progress, not attention, with busy[] naming the PR, blocker, and agent; agents carries that PR's vortexStatus, cycloneStatus, vortexReview, and busy flags. A blocker no agent run clears (a merge conflict vs the live parent, a restack Conflict or Restack failed, a Draft PR, a seam review pending with no review running, "Seam review stuck in reviewing, no review running", "Seam re-review pending, no review running", "Seam verdict is for an older head, no review running", or a review skipped for review quota) is still named during that in_progress, in blocker, prNumber, headSha, and issues[], with actAfter: "agents_idle" and waitingOn listing the busy agents: plan the fix, and act when a snapshot after the agents finish returns attention, re-reading the blocker then. Blockers the agents can change keep blocker and actAfter null. Attention never calls for changing a PR's GitHub base. When present, repair names the concrete fix for attention (restack_conflict and merge_conflict carry liveParent, never mg-park-*). For the three stuck-seam blockers, repair kind seam_review_stuck: do not patch or push; tell the human to click Continue, which re-queues the seam review at the live head. A pending land gate (landGatePending) returns in_progress, not attention. Auto land off after a merge_failed, gh_error, or ci_timeout bounce (the stack's autoLandOff reads reason bounced) returns attention with the blocker "Auto land off after <kind> bounce", the bounce in issues[], and a repair of kind auto_land_off: fix the cause, then turn Auto land back on or enqueue. A Vortex request_changes handoff at the live head returns attention with blocker "Vortex findings need a person" and repair kind vortex_findings; verify its findings against the live head.` + STACK_WATCH_RESULT_NOTE,
|
|
1444
1449
|
inputSchema: stackWaitSchema,
|
|
1445
|
-
annotations: { readOnlyHint: true }
|
|
1450
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1446
1451
|
},
|
|
1447
1452
|
async (args, extra) => {
|
|
1448
1453
|
try {
|
|
1449
1454
|
const payload = await stackWait(args, void 0, { signal: extra.signal });
|
|
1450
|
-
return
|
|
1455
|
+
return ok(payload);
|
|
1451
1456
|
} catch (err) {
|
|
1452
1457
|
return fail(err);
|
|
1453
1458
|
}
|
|
@@ -1461,7 +1466,7 @@ function createMergestormMcpServer() {
|
|
|
1461
1466
|
inputSchema: {
|
|
1462
1467
|
stack_id: z3.string().min(1).optional()
|
|
1463
1468
|
},
|
|
1464
|
-
annotations: { readOnlyHint: true }
|
|
1469
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1465
1470
|
},
|
|
1466
1471
|
async ({ stack_id }) => {
|
|
1467
1472
|
try {
|
|
@@ -1475,8 +1480,8 @@ function createMergestormMcpServer() {
|
|
|
1475
1480
|
"settings_get",
|
|
1476
1481
|
{
|
|
1477
1482
|
title: "Get settings",
|
|
1478
|
-
description: "Read the Bearer /api/v1/settings toggles, including auto_patch_enabled, auto_land_settle_seconds (how long Auto land waits before queueing), merge_queue_batch_enabled and merge_queue_batch_size (whether the merge queue tests several pull requests on one CI run, and how many), and
|
|
1479
|
-
annotations: { readOnlyHint: true }
|
|
1483
|
+
description: "Read the Bearer /api/v1/settings toggles, including auto_patch_enabled, auto_land_settle_seconds (how long Auto land waits before queueing), merge_queue_batch_enabled and merge_queue_batch_size (whether the merge queue tests several pull requests on one CI run, and how many), cyclone_connected (the Cyclone auto-patch App), and stacks_ready (whether stacks, the merge queue and Auto land can run for this account, which needs Mergestorm Surge).",
|
|
1484
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1480
1485
|
},
|
|
1481
1486
|
async () => {
|
|
1482
1487
|
try {
|
|
@@ -1502,7 +1507,7 @@ function createMergestormMcpServer() {
|
|
|
1502
1507
|
cyclone_connected: z3.unknown().optional(),
|
|
1503
1508
|
github_connected: z3.unknown().optional()
|
|
1504
1509
|
},
|
|
1505
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
1510
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
1506
1511
|
},
|
|
1507
1512
|
async (args) => {
|
|
1508
1513
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mergestorm-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "Mergestorm MCP server — identity, review, and read-only stack tools over stdio",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
32
|
-
"mergestorm": "0.3.
|
|
32
|
+
"mergestorm": "0.3.41",
|
|
33
33
|
"zod": "^3.24.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|