@martinloop/mcp 0.5.1 → 0.5.5

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 (45) hide show
  1. package/README.md +52 -14
  2. package/dist/server-validation.js +2 -0
  3. package/dist/server.js +30 -3
  4. package/dist/tools/run-loop.d.ts +1 -0
  5. package/dist/tools/run-loop.js +25 -4
  6. package/dist/tools/tool-response.d.ts +9 -1
  7. package/dist/tools/tool-response.js +7 -5
  8. package/dist/tools/tool-support.js +62 -271
  9. package/dist/vendor/adapters/claude-cli.d.ts +9 -0
  10. package/dist/vendor/adapters/claude-cli.js +19 -2
  11. package/dist/vendor/adapters/codex-capabilities.d.ts +69 -0
  12. package/dist/vendor/adapters/codex-capabilities.js +265 -0
  13. package/dist/vendor/adapters/codex-cli.d.ts +23 -0
  14. package/dist/vendor/adapters/codex-cli.js +72 -0
  15. package/dist/vendor/adapters/codex-host.d.ts +107 -0
  16. package/dist/vendor/adapters/codex-host.js +595 -0
  17. package/dist/vendor/adapters/codex-launcher.d.ts +7 -135
  18. package/dist/vendor/adapters/codex-launcher.js +17 -766
  19. package/dist/vendor/adapters/index.d.ts +4 -2
  20. package/dist/vendor/adapters/index.js +4 -2
  21. package/dist/vendor/contracts/agent-execution.d.ts +9 -0
  22. package/dist/vendor/contracts/agent-execution.js +14 -0
  23. package/dist/vendor/contracts/index.d.ts +9 -0
  24. package/dist/vendor/contracts/index.js +1 -0
  25. package/dist/vendor/core/grounding.js +42 -7
  26. package/dist/vendor/core/index.d.ts +5 -2
  27. package/dist/vendor/core/index.js +12 -3
  28. package/dist/vendor/presentation/governed-plan.d.ts +33 -0
  29. package/dist/vendor/presentation/governed-plan.js +146 -0
  30. package/dist/vendor/presentation/index.d.ts +7 -0
  31. package/dist/vendor/presentation/index.js +7 -0
  32. package/dist/vendor/presentation/motion.d.ts +12 -0
  33. package/dist/vendor/presentation/motion.js +123 -0
  34. package/dist/vendor/presentation/status.d.ts +8 -0
  35. package/dist/vendor/presentation/status.js +11 -0
  36. package/dist/vendor/presentation/table.d.ts +11 -0
  37. package/dist/vendor/presentation/table.js +47 -0
  38. package/dist/vendor/presentation/text.d.ts +6 -0
  39. package/dist/vendor/presentation/text.js +30 -0
  40. package/dist/vendor/presentation/theme.d.ts +31 -0
  41. package/dist/vendor/presentation/theme.js +82 -0
  42. package/dist/vendor/presentation/verified-handoff.d.ts +15 -0
  43. package/dist/vendor/presentation/verified-handoff.js +151 -0
  44. package/package.json +3 -2
  45. package/server.json +2 -2
package/README.md CHANGED
@@ -6,11 +6,31 @@ But did it actually work? Did it pass your tests? How much did it spend? What fi
6
6
 
7
7
  You don't know. And that's the problem.
8
8
 
9
+ ## One system around the coding agent
10
+
11
+ MartinLoop is the execution-control system around coding agents. The coding agent still performs the software work. MartinLoop connects the run from Definition of Done through preflight, control, verification, recovery evidence, receipts, and post-run analysis.
12
+
13
+ ```text
14
+ DEFINE -> PREFLIGHT -> CONTROL -> VERIFY -> RECOVER -> PROVE -> ANALYZE
15
+ ```
16
+
17
+ The product-level flow is **Definition of Done -> Controlled Run -> Verified Handoff**.
18
+
19
+ Through MCP, compatible hosts can use the same MartinLoop lifecycle without giving up the host's own model selection. MartinLoop does not inject a hidden fallback model.
20
+
21
+ For agent-readable product context see [`../../llms.txt`](../../llms.txt), [`../../llms-full.txt`](../../llms-full.txt), and [`../../docs/for-agents.md`](../../docs/for-agents.md).
22
+
9
23
  ## What This Does
10
24
 
11
- MartinLoop is a governed loop for AI coding agents. You tell it what to do, set a budget, and point it at your test suite. It runs the agent, checks your tests after every attempt, and stops when either the tests pass or the money runs out.
25
+ MartinLoop governs AI coding-agent execution. You define what the run needs to accomplish, set execution boundaries such as budget and verifier checks, and let the configured coding agent do the work inside that contract.
26
+
27
+ When it's done, you get a receipt — not a vague summary, but a structured record: dollars spent when available, attempts made, verification results, files changed, recovery state, and what the available evidence established.
28
+
29
+ The final governed outcome is one of:
12
30
 
13
- When it's done, you get a receipt — not a vague summary, but a structured record: dollars spent, attempts made, verification results, files changed, and whether you got what you asked for.
31
+ - `VERIFIED` when the configured evidence supports the Definition of Done
32
+ - `STOPPED` when a configured hard boundary ends the run
33
+ - `NEEDS REVIEW` when completion cannot be established from the available evidence
14
34
 
15
35
  **One line to connect it:**
16
36
 
@@ -18,7 +38,7 @@ When it's done, you get a receipt — not a vague summary, but a structured reco
18
38
  claude mcp add martin-loop -- npx -y @martinloop/mcp
19
39
  ```
20
40
 
21
- That's it. Your agent now runs governed.
41
+ That's it. Your host can now use MartinLoop's governed run and evidence surfaces.
22
42
 
23
43
  ## Real Numbers From Real Runs
24
44
 
@@ -37,27 +57,27 @@ These aren't hypotheticals. The $28 overshoot happened in production testing. We
37
57
 
38
58
  ### Claude Code
39
59
  ```sh
40
- claude mcp add martin-loop -- npx -y @martinloop/mcp
60
+ claude mcp add martin-loop -- npx -y @martinloop/mcp@0.5.5
41
61
  ```
42
62
 
43
63
  Windows:
44
64
  ```sh
45
- claude mcp add --transport stdio --scope user martin-loop -- cmd /c npx -y @martinloop/mcp
65
+ claude mcp add --transport stdio --scope user martin-loop -- cmd /c npx -y @martinloop/mcp@0.5.5
46
66
  ```
47
67
 
48
68
  ### Codex
49
69
  ```sh
50
- codex mcp add martin-loop -- npx -y @martinloop/mcp
70
+ codex mcp add martin-loop -- npx -y @martinloop/mcp@0.5.5
51
71
  ```
52
72
 
53
73
  ### Gemini CLI
54
74
  ```sh
55
- gemini mcp add martin-loop -- npx -y @martinloop/mcp
75
+ gemini mcp add martin-loop -- npx -y @martinloop/mcp@0.5.5
56
76
  ```
57
77
 
58
78
  ### Any MCP Host
59
79
  ```sh
60
- npx -y @martinloop/mcp
80
+ npx -y @martinloop/mcp@0.5.5
61
81
  ```
62
82
 
63
83
  ## How a Governed Run Works
@@ -69,14 +89,16 @@ You: "Fix the auth bug. Budget $3. Verify: npm test"
69
89
  martin_plan → scopes the task, sets constraints
70
90
  martin_preflight → validates before any spend
71
91
  martin_run → agent works inside budget + verifier gates
72
- martin_dossier → receipt: $1.40 spent, 2 attempts, tests pass
92
+ martin_dossier → receipt: spend, attempts, verifier evidence, final outcome
73
93
  ```
74
94
 
75
- Every attempt runs your verifier. Every dollar is tracked. If the agent drifts off-task, the scope contract catches it. If it blows the budget, the circuit breaker kills the subprocess mid-stream — not after the bill arrives.
95
+ Every attempt can be checked by your configured verifier. Budget and policy controls stay attached to the run. If the agent drifts off-task, the scope contract can block the change. If a hard spend boundary is reached, the run stops according to policy instead of silently retrying forever.
76
96
 
77
97
  ## What Your Agent Gets
78
98
 
79
- ### 21 Tools
99
+ ### MCP Tools
100
+
101
+ The exact tool set is discoverable from the running MCP server with `tools/list`; do not rely on a hard-coded count in documentation.
80
102
 
81
103
  **Run the loop:**
82
104
  `martin_doctor` `martin_plan` `martin_preflight` `martin_run` `martin_pause` `martin_continue` `martin_cancel`
@@ -87,7 +109,9 @@ Every attempt runs your verifier. Every dollar is tracked. If the agent drifts o
87
109
  **Ship the work:**
88
110
  `martin_pr_summary` `martin_create_pr` `martin_review_pr`
89
111
 
90
- ### 11 Read-Only Resources
112
+ ### Read-Only Resources
113
+
114
+ The exact resource set is discoverable from the running MCP server; this documentation names the primary public resources without treating a count as a release invariant.
91
115
 
92
116
  Your agent can pull context without side effects:
93
117
 
@@ -106,20 +130,33 @@ npx martin-loop mcp print-config --host claude --profile github-review # + PR w
106
130
 
107
131
  ## What Happens When Things Go Wrong
108
132
 
109
- MartinLoop doesn't just report failures — it tries to fix them:
133
+ MartinLoop does not round failure into success. It preserves the evidence and follows the configured recovery path while budget and policy still allow useful work.
110
134
 
111
135
  | Failure | Old behavior | Now |
112
136
  |---|---|---|
113
137
  | CLI not on PATH | Error message, dead stop | Searches npm global, homebrew, nvm, scoop — uses what it finds |
114
- | Verifier says "command not found" | Generic "verification failed" | Tells the next attempt: "bun is missing, install with `npm i -g bun`" |
138
+ | Verifier says "command not found" | Generic "verification failed" | Carries specific failure context into the next allowed attempt |
115
139
  | `git restore` fails mid-rollback | Throws, leaves dirty state | Retries once, falls back to `git checkout`, cleans up |
116
140
  | Invalid `--profile` flag | Crashes | Warns, falls back to `minimal`, keeps running |
117
141
 
142
+ ## Codex compatibility in 0.5.5
143
+
144
+ MartinLoop no longer assumes one Codex host or one fixed Codex flag set. The CLI resolves the exact Codex executable, reads its advertised capabilities, negotiates a supported write strategy, proves that strategy before reporting launch readiness, and reuses the same contract for the governed run.
145
+
146
+ That means MCP hosts can point MartinLoop at different Codex installations without requiring a documentation-specific approval or sandbox flag to exist everywhere.
147
+
148
+ ## Presentation and evidence
149
+
150
+ MCP responses are Markdown-first for humans while retaining structured content for agents and automation. The presentation layer does not rewrite governed truth.
151
+
152
+ MartinLoop Arcade is a terminal-only experience. It does not run inside MCP and cannot influence MCP evidence, budgets, verification, or final outcomes.
153
+
118
154
  ## Who Uses This
119
155
 
120
156
  - **Engineers running overnight agent loops** — you need a kill switch that actually works, and a receipt in the morning
121
157
  - **Teams with shared API budgets** — you need per-task spend caps, not org-wide prayer
122
158
  - **Anyone reviewing agent-generated PRs** — the dossier shows what the agent tried, what passed, and what didn't
159
+ - **Teams tired of stitching together point tools** — you want one execution-control lifecycle around coding-agent work from preflight through post-run analysis
123
160
 
124
161
  ## Requirements
125
162
 
@@ -129,6 +166,7 @@ MartinLoop doesn't just report failures — it tries to fix them:
129
166
  ## Links
130
167
 
131
168
  - [martin-loop](https://www.npmjs.com/package/martin-loop) — the standalone CLI
169
+ - [MartinLoop for AI Agents](../../docs/for-agents.md)
132
170
  - [GitHub](https://github.com/Keesan12/martin-loop) — source and issues
133
171
  - [martinloop.com](https://martinloop.com)
134
172
 
@@ -151,6 +151,7 @@ function validateRunInput(args) {
151
151
  "maxIterations",
152
152
  "maxTokens",
153
153
  "verifyTimeoutMs",
154
+ "providerExecutionTimeoutMs",
154
155
  "maxMinutes",
155
156
  "maxFilesChanged",
156
157
  "maxCommands",
@@ -180,6 +181,7 @@ function validateRunInput(args) {
180
181
  ...optionalPositiveInteger(record.maxIterations, "maxIterations"),
181
182
  ...optionalPositiveInteger(record.maxTokens, "maxTokens"),
182
183
  ...optionalPositiveInteger(record.verifyTimeoutMs, "verifyTimeoutMs"),
184
+ ...optionalPositiveInteger(record.providerExecutionTimeoutMs, "providerExecutionTimeoutMs"),
183
185
  ...optionalPositiveInteger(record.maxMinutes, "maxMinutes"),
184
186
  ...optionalPositiveInteger(record.maxFilesChanged, "maxFilesChanged"),
185
187
  ...optionalPositiveInteger(record.maxCommands, "maxCommands"),
package/dist/server.js CHANGED
@@ -47,6 +47,7 @@ import { runLoopTool } from "./tools/run-loop.js";
47
47
  import { MARTIN_ENGINE_VALUES } from "./tools/tool-support.js";
48
48
  import { classifyRoute } from "./vendor/core/index.js";
49
49
  import { createToolErrorResult, createToolSuccessResult } from "./tools/tool-response.js";
50
+ import { buildGovernedPlanStages, renderGovernedRunPlanMarkdown, renderVerifiedHandoffMarkdown } from "./vendor/presentation/index.js";
50
51
  import { MartinToolError, toToolFailure } from "./tools/tool-errors.js";
51
52
  import { normalizeLoopBudget } from "./tools/workflow-governance.js";
52
53
  import { resolveSafeRepoRoot, sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
@@ -793,6 +794,16 @@ export function createMartinMcpServer(serverInfo) {
793
794
  exclusiveMinimum: 0,
794
795
  description: "Maximum total tokens across all attempts."
795
796
  },
797
+ verifyTimeoutMs: {
798
+ type: "integer",
799
+ exclusiveMinimum: 0,
800
+ description: "Hard timeout for each verifier command in milliseconds."
801
+ },
802
+ providerExecutionTimeoutMs: {
803
+ type: "integer",
804
+ exclusiveMinimum: 0,
805
+ description: "Hard timeout for each provider coding process in milliseconds."
806
+ },
796
807
  verificationPlan: {
797
808
  type: "array",
798
809
  items: { type: "string" },
@@ -1594,7 +1605,23 @@ export function createMartinMcpServer(serverInfo) {
1594
1605
  budget: output.normalized.budget
1595
1606
  }).catch(() => { });
1596
1607
  }
1597
- return createToolSuccessResult(output, output.summary);
1608
+ const prefPlanView = {
1609
+ ready: output.ok,
1610
+ task: output.normalized.objective,
1611
+ engine: output.normalized.engine,
1612
+ mode: output.readiness.liveMode ? "live" : "proof",
1613
+ budget: output.normalized.budget,
1614
+ verifier: output.normalized.verificationPlan,
1615
+ receiptScope: output.receiptScope,
1616
+ policyProfile: output.policy.name,
1617
+ blockingIssues: output.ok ? [] : [output.summary],
1618
+ warnings: output.warnings,
1619
+ stages: []
1620
+ };
1621
+ prefPlanView.stages = buildGovernedPlanStages(prefPlanView);
1622
+ return createToolSuccessResult(output, output.summary, {
1623
+ human: renderGovernedRunPlanMarkdown(prefPlanView)
1624
+ });
1598
1625
  }
1599
1626
  if (name === "martin_logs") {
1600
1627
  const input = validateToolInput("martin_logs", args);
@@ -1644,12 +1671,12 @@ export function createMartinMcpServer(serverInfo) {
1644
1671
  if (name === "martin_run_dossier") {
1645
1672
  const input = validateToolInput("martin_run_dossier", args);
1646
1673
  const output = await martinRunDossierTool(input);
1647
- return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} with ${output.attempts.length} attempt(s).`);
1674
+ return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} with ${output.attempts.length} attempt(s).`, { human: renderVerifiedHandoffMarkdown(output.verifiedHandoff) });
1648
1675
  }
1649
1676
  if (name === "martin_dossier") {
1650
1677
  const input = validateToolInput("martin_dossier", args);
1651
1678
  const output = await martinRunDossierTool(input);
1652
- return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} in ${output.format} format.`);
1679
+ return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} in ${output.format} format.`, { human: renderVerifiedHandoffMarkdown(output.verifiedHandoff) });
1653
1680
  }
1654
1681
  if (name === "martin_eval") {
1655
1682
  const input = validateToolInput("martin_eval", args);
@@ -11,6 +11,7 @@ export interface RunLoopInput {
11
11
  maxIterations?: number;
12
12
  maxTokens?: number;
13
13
  verifyTimeoutMs?: number;
14
+ providerExecutionTimeoutMs?: number;
14
15
  verificationPlan?: string[];
15
16
  allowedPaths?: string[];
16
17
  deniedPaths?: string[];
@@ -30,7 +30,7 @@ export async function runLoopTool(input) {
30
30
  repoRoot: workingDirectory,
31
31
  runsRoot
32
32
  };
33
- let codexCommandOverride;
33
+ let codexProbeOverride;
34
34
  if (executionMode.liveMode) {
35
35
  if (engine === "codex") {
36
36
  const engineAvailability = resolveCliCommandAvailability("codex");
@@ -43,7 +43,10 @@ export async function runLoopTool(input) {
43
43
  }
44
44
  const codexProbe = probeCodexLaunch({
45
45
  workingDirectory,
46
- availability: engineAvailability
46
+ availability: engineAvailability,
47
+ ...(input.providerExecutionTimeoutMs !== undefined
48
+ ? { providerExecutionTimeoutMs: input.providerExecutionTimeoutMs }
49
+ : {})
47
50
  });
48
51
  if (!codexProbe.ok) {
49
52
  throw new MartinToolError("engine_unavailable", codexProbe.summary, {
@@ -52,7 +55,7 @@ export async function runLoopTool(input) {
52
55
  retryable: false
53
56
  });
54
57
  }
55
- codexCommandOverride = codexProbe.command;
58
+ codexProbeOverride = codexProbe;
56
59
  }
57
60
  else {
58
61
  const engineAvailability = getEngineAvailability(engine);
@@ -76,18 +79,33 @@ export async function runLoopTool(input) {
76
79
  ? createCodexCliAdapter({
77
80
  workingDirectory,
78
81
  ...(input.verifyTimeoutMs !== undefined ? { verifyTimeoutMs: input.verifyTimeoutMs } : {}),
82
+ ...(input.providerExecutionTimeoutMs !== undefined
83
+ ? { providerExecutionTimeoutMs: input.providerExecutionTimeoutMs }
84
+ : {}),
79
85
  ...(model ? { model } : {}),
80
- ...(codexCommandOverride ? { command: codexCommandOverride } : {})
86
+ ...(codexProbeOverride
87
+ ? {
88
+ command: codexProbeOverride.command,
89
+ capabilityProfile: codexProbeOverride.capabilityProfile,
90
+ autonomyResolution: codexProbeOverride.autonomyResolution
91
+ }
92
+ : {})
81
93
  })
82
94
  : engine === "gemini"
83
95
  ? createGeminiCliAdapter({
84
96
  workingDirectory,
85
97
  ...(input.verifyTimeoutMs !== undefined ? { verifyTimeoutMs: input.verifyTimeoutMs } : {}),
98
+ ...(input.providerExecutionTimeoutMs !== undefined
99
+ ? { providerExecutionTimeoutMs: input.providerExecutionTimeoutMs }
100
+ : {}),
86
101
  ...(model ? { model } : {})
87
102
  })
88
103
  : createClaudeCliAdapter({
89
104
  workingDirectory,
90
105
  ...(input.verifyTimeoutMs !== undefined ? { verifyTimeoutMs: input.verifyTimeoutMs } : {}),
106
+ ...(input.providerExecutionTimeoutMs !== undefined
107
+ ? { providerExecutionTimeoutMs: input.providerExecutionTimeoutMs }
108
+ : {}),
91
109
  ...(model ? { model } : {})
92
110
  });
93
111
  const partialBudget = {};
@@ -113,6 +131,9 @@ export async function runLoopTool(input) {
113
131
  ...(input.verifyTimeoutMs !== undefined
114
132
  ? { verificationTimeoutMs: input.verifyTimeoutMs }
115
133
  : {}),
134
+ ...(input.providerExecutionTimeoutMs !== undefined
135
+ ? { providerExecutionTimeoutMs: input.providerExecutionTimeoutMs }
136
+ : {}),
116
137
  repoRoot: workingDirectory,
117
138
  ...(allowedPaths ? { allowedPaths } : {}),
118
139
  ...(deniedPaths ? { deniedPaths } : {})
@@ -3,7 +3,15 @@ interface TextContentBlock {
3
3
  type: "text";
4
4
  text: string;
5
5
  }
6
- export declare function createToolSuccessResult<T extends object>(output: T, summary: string): {
6
+ export interface ToolSuccessRenderOptions {
7
+ /** Explicit human-readable Markdown for content[0].
8
+ * Overrides resolveHumanArtifact fallback when provided. */
9
+ human?: string;
10
+ /** Include compatibility JSON block in content[]. Default: true.
11
+ * Set false only after real-host testing proves structuredContent alone suffices. */
12
+ includeCompatibilityJson?: boolean;
13
+ }
14
+ export declare function createToolSuccessResult<T extends object>(output: T, summary: string, options?: ToolSuccessRenderOptions): {
7
15
  content: TextContentBlock[];
8
16
  structuredContent: T;
9
17
  _meta: Record<string, unknown>;
@@ -8,12 +8,14 @@ function resolveHumanArtifact(output, summary) {
8
8
  const rendered = "rendered" in output ? output.rendered : undefined;
9
9
  return typeof rendered === "string" && rendered.trim().length > 0 ? rendered : summary;
10
10
  }
11
- export function createToolSuccessResult(output, summary) {
11
+ export function createToolSuccessResult(output, summary, options = {}) {
12
+ const humanText = options.human ?? resolveHumanArtifact(output, summary);
13
+ const content = [{ type: "text", text: humanText }];
14
+ if (options.includeCompatibilityJson !== false) {
15
+ content.push(createJsonBlock(output));
16
+ }
12
17
  return {
13
- content: [
14
- { type: "text", text: resolveHumanArtifact(output, summary) },
15
- createJsonBlock(output)
16
- ],
18
+ content,
17
19
  structuredContent: output,
18
20
  _meta: {
19
21
  "martinloop/summary": summary,