@martinloop/mcp 0.2.0 → 0.2.7

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 (96) hide show
  1. package/README.md +118 -182
  2. package/dist/discovery-metadata.d.ts +21 -0
  3. package/dist/discovery-metadata.js +152 -0
  4. package/dist/discovery-support.d.ts +62 -0
  5. package/dist/discovery-support.js +224 -0
  6. package/dist/package-version.d.ts +1 -0
  7. package/dist/package-version.js +3 -0
  8. package/dist/prompts.d.ts +13 -3
  9. package/dist/prompts.js +537 -74
  10. package/dist/resources.d.ts +35 -5
  11. package/dist/resources.js +788 -71
  12. package/dist/server-validation.d.ts +2 -3
  13. package/dist/server-validation.js +375 -119
  14. package/dist/server.d.ts +76 -7
  15. package/dist/server.js +1478 -394
  16. package/dist/tools/doctor.d.ts +2 -0
  17. package/dist/tools/doctor.js +18 -6
  18. package/dist/tools/eval.d.ts +24 -0
  19. package/dist/tools/eval.js +65 -0
  20. package/dist/tools/get-attempt.d.ts +13 -6
  21. package/dist/tools/get-attempt.js +14 -5
  22. package/dist/tools/get-run.d.ts +19 -12
  23. package/dist/tools/get-run.js +20 -11
  24. package/dist/tools/get-status.d.ts +19 -0
  25. package/dist/tools/get-status.js +30 -2
  26. package/dist/tools/get-verification-results.d.ts +10 -7
  27. package/dist/tools/get-verification-results.js +11 -6
  28. package/dist/tools/inspect-loop.d.ts +9 -0
  29. package/dist/tools/inspect-loop.js +11 -2
  30. package/dist/tools/list-runs.d.ts +25 -5
  31. package/dist/tools/list-runs.js +21 -4
  32. package/dist/tools/logs.d.ts +25 -0
  33. package/dist/tools/logs.js +49 -0
  34. package/dist/tools/plan.d.ts +20 -0
  35. package/dist/tools/plan.js +10 -0
  36. package/dist/tools/pr-tools.d.ts +31 -0
  37. package/dist/tools/pr-tools.js +111 -0
  38. package/dist/tools/preflight.d.ts +10 -0
  39. package/dist/tools/preflight.js +18 -4
  40. package/dist/tools/run-controls.d.ts +36 -0
  41. package/dist/tools/run-controls.js +88 -0
  42. package/dist/tools/run-dossier.d.ts +51 -4
  43. package/dist/tools/run-dossier.js +100 -5
  44. package/dist/tools/run-loop.d.ts +19 -0
  45. package/dist/tools/run-loop.js +61 -4
  46. package/dist/tools/run-store.d.ts +57 -3
  47. package/dist/tools/run-store.js +404 -53
  48. package/dist/tools/tool-errors.d.ts +37 -0
  49. package/dist/tools/tool-errors.js +170 -0
  50. package/dist/tools/tool-response.d.ts +16 -0
  51. package/dist/tools/tool-response.js +34 -0
  52. package/dist/tools/tool-support.d.ts +92 -2
  53. package/dist/tools/tool-support.js +385 -63
  54. package/dist/tools/triage-runs.d.ts +33 -0
  55. package/dist/tools/triage-runs.js +138 -0
  56. package/dist/tools/workflow-governance.d.ts +133 -0
  57. package/dist/tools/workflow-governance.js +581 -0
  58. package/dist/vendor/adapters/claude-cli.js +0 -1
  59. package/dist/vendor/adapters/cli-bridge.d.ts +5 -0
  60. package/dist/vendor/adapters/cli-bridge.js +16 -9
  61. package/dist/vendor/adapters/direct-provider.js +0 -1
  62. package/dist/vendor/adapters/index.d.ts +2 -1
  63. package/dist/vendor/adapters/index.js +2 -1
  64. package/dist/vendor/adapters/openai-compatible.d.ts +47 -0
  65. package/dist/vendor/adapters/openai-compatible.js +242 -0
  66. package/dist/vendor/adapters/runtime-support.js +0 -1
  67. package/dist/vendor/adapters/stub-agent-cli.js +0 -1
  68. package/dist/vendor/adapters/stub-direct-provider.js +0 -1
  69. package/dist/vendor/adapters/verifier-only.js +0 -1
  70. package/dist/vendor/contracts/governance.js +0 -1
  71. package/dist/vendor/contracts/index.d.ts +2 -0
  72. package/dist/vendor/contracts/index.js +1 -1
  73. package/dist/vendor/contracts/operator.d.ts +19 -0
  74. package/dist/vendor/contracts/operator.js +11 -0
  75. package/dist/vendor/core/compiler.js +0 -1
  76. package/dist/vendor/core/context-integrity.js +0 -1
  77. package/dist/vendor/core/grounding.js +0 -1
  78. package/dist/vendor/core/index.js +1 -2
  79. package/dist/vendor/core/leash.js +19 -12
  80. package/dist/vendor/core/persistence/compiler.js +0 -1
  81. package/dist/vendor/core/persistence/index.js +0 -1
  82. package/dist/vendor/core/persistence/ledger.js +0 -1
  83. package/dist/vendor/core/persistence/runs-reader.js +0 -1
  84. package/dist/vendor/core/persistence/store.js +0 -1
  85. package/dist/vendor/core/policy.js +0 -1
  86. package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
  87. package/dist/vendor/core/red-blue/red-phase.js +135 -0
  88. package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
  89. package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
  90. package/dist/vendor/core/rollback.js +2 -3
  91. package/dist/workflow-state.d.ts +25 -0
  92. package/dist/workflow-state.js +102 -0
  93. package/package.json +12 -7
  94. package/server.json +2 -2
  95. package/dist/tools/cockpit-support.d.ts +0 -69
  96. package/dist/tools/cockpit-support.js +0 -108
package/dist/server.js CHANGED
@@ -2,12 +2,9 @@
2
2
  /**
3
3
  * Martin Loop MCP Server
4
4
  *
5
- * Exposes a governed local MCP cockpit over stdio:
6
- * martin_doctor — inspect local readiness and run-store health
7
- * martin_preflight — normalize a proposed run contract before execution
8
- * martin_run — execute a full Martin loop on a coding task
9
- * martin_inspect — summarise a saved loop record file
10
- * martin_status — return cost and pressure state from a loop record
5
+ * Martin Loop MCP is a governed execution cockpit for AI coding agents.
6
+ * It exposes execution, diagnostics, run inspection, resources, and prompts
7
+ * over the Model Context Protocol (stdio transport).
11
8
  *
12
9
  * Setup (Claude Code):
13
10
  * macOS/Linux: claude mcp add --scope user martin-loop -- npx @martinloop/mcp
@@ -19,444 +16,1531 @@
19
16
  * Manual start:
20
17
  * node dist/server.js
21
18
  */
22
- import { createRequire } from "node:module";
19
+ import { fileURLToPath } from "node:url";
20
+ import { realpathSync } from "node:fs";
21
+ import path from "node:path";
22
+ import { resolveRunsRoot } from "./vendor/core/index.js";
23
23
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
24
24
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
25
25
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
26
+ import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
27
+ import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
28
+ import { listMartinResources, listMartinResourceTemplates, readMartinResource } from "./resources.js";
26
29
  import { martinDoctorTool } from "./tools/doctor.js";
27
- import { getAttemptTool } from "./tools/get-attempt.js";
28
- import { getRunTool } from "./tools/get-run.js";
30
+ import { martinEvalTool } from "./tools/eval.js";
31
+ import { martinGetAttemptTool } from "./tools/get-attempt.js";
32
+ import { martinGetRunTool } from "./tools/get-run.js";
33
+ import { martinGetVerificationResultsTool } from "./tools/get-verification-results.js";
29
34
  import { getStatusTool } from "./tools/get-status.js";
30
- import { getVerificationResultsTool } from "./tools/get-verification-results.js";
31
35
  import { inspectLoopTool } from "./tools/inspect-loop.js";
32
- import { listRunsTool } from "./tools/list-runs.js";
36
+ import { martinListRunsTool } from "./tools/list-runs.js";
37
+ import { martinLogsTool } from "./tools/logs.js";
38
+ import { martinPlanTool } from "./tools/plan.js";
33
39
  import { martinPreflightTool } from "./tools/preflight.js";
34
- import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
35
- import { listMartinResourceTemplates, listMartinResources, readMartinResource } from "./resources.js";
40
+ import { martinCreatePrTool, martinPrSummaryTool, martinReviewPrTool } from "./tools/pr-tools.js";
41
+ import { martinRunDossierTool } from "./tools/run-dossier.js";
42
+ import { createRunControlReceipt } from "./tools/run-controls.js";
43
+ import { martinTriageRunsTool } from "./tools/triage-runs.js";
36
44
  import { runLoopTool } from "./tools/run-loop.js";
37
- import { runDossierTool } from "./tools/run-dossier.js";
45
+ import { createToolErrorResult, createToolSuccessResult } from "./tools/tool-response.js";
46
+ import { MartinToolError, toToolFailure } from "./tools/tool-errors.js";
38
47
  import { sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
39
- const require = createRequire(import.meta.url);
40
- const packageJson = require("../package.json");
41
- const server = new Server({ name: "martin-loop", version: packageJson.version }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
42
- // ---------------------------------------------------------------------------
43
- // Tool manifest
44
- // ---------------------------------------------------------------------------
45
- server.setRequestHandler(ListToolsRequestSchema, () => ({
46
- tools: [
47
- {
48
- name: "martin_doctor",
49
- description: "Inspect Martin MCP readiness without changing code. Reports workspace roots, run-store visibility, execution mode, and whether claude or codex is available on PATH.",
50
- inputSchema: {
51
- type: "object",
52
- additionalProperties: false,
53
- properties: {
54
- workingDirectory: {
55
- type: "string",
56
- description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
57
- },
58
- runsDir: {
59
- type: "string",
60
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
61
- },
62
- engine: {
63
- type: "string",
64
- enum: ["claude", "codex"],
65
- description: "Optional engine to emphasize in the readiness report."
66
- }
67
- }
68
- }
48
+ import { recordMcpWorkflowStep } from "./workflow-state.js";
49
+ const stringArraySchema = {
50
+ type: "array",
51
+ items: { type: "string" }
52
+ };
53
+ const loopPreviewSchema = {
54
+ type: "object",
55
+ additionalProperties: true,
56
+ properties: {
57
+ loopId: { type: "string" },
58
+ title: { type: "string" },
59
+ objective: { type: "string" },
60
+ status: { type: "string" },
61
+ lifecycleState: { type: "string" },
62
+ createdAt: { type: "string" },
63
+ updatedAt: { type: "string" },
64
+ attempts: { type: "integer" },
65
+ costUsd: { type: "number" },
66
+ avoidedUsd: { type: "number" },
67
+ pressure: { type: "string" },
68
+ shouldStop: { type: "boolean" },
69
+ remainingBudgetUsd: { type: "number" },
70
+ remainingIterations: { type: "integer" },
71
+ remainingTokens: { type: "integer" },
72
+ lastAttempt: {
73
+ type: "object",
74
+ additionalProperties: true
75
+ }
76
+ },
77
+ required: [
78
+ "loopId",
79
+ "title",
80
+ "objective",
81
+ "status",
82
+ "lifecycleState",
83
+ "attempts",
84
+ "costUsd",
85
+ "avoidedUsd",
86
+ "pressure",
87
+ "shouldStop",
88
+ "remainingBudgetUsd",
89
+ "remainingIterations",
90
+ "remainingTokens"
91
+ ]
92
+ };
93
+ const budgetSchema = {
94
+ type: "object",
95
+ additionalProperties: false,
96
+ properties: {
97
+ maxUsd: { type: "number" },
98
+ softLimitUsd: { type: "number" },
99
+ maxIterations: { type: "integer" },
100
+ maxTokens: { type: "integer" }
101
+ },
102
+ required: ["maxUsd", "softLimitUsd", "maxIterations", "maxTokens"]
103
+ };
104
+ const costSchema = {
105
+ type: "object",
106
+ additionalProperties: false,
107
+ properties: {
108
+ actualUsd: { type: "number" },
109
+ avoidedUsd: { type: "number" },
110
+ tokensIn: { type: "integer" },
111
+ tokensOut: { type: "integer" }
112
+ },
113
+ required: ["actualUsd", "avoidedUsd", "tokensIn", "tokensOut"]
114
+ };
115
+ const verificationSchema = {
116
+ type: "object",
117
+ additionalProperties: true,
118
+ properties: {
119
+ status: { type: "string", enum: ["passed", "failed", "unavailable"] },
120
+ eventCount: { type: "integer" },
121
+ ledgerEventCount: { type: "integer" },
122
+ latestAttemptIndex: { type: "integer" },
123
+ completedAt: { type: "string" },
124
+ summary: { type: "string" },
125
+ warnings: stringArraySchema
126
+ },
127
+ required: ["status", "eventCount", "ledgerEventCount", "warnings"]
128
+ };
129
+ const artifactSummarySchema = {
130
+ type: "object",
131
+ additionalProperties: true,
132
+ properties: {
133
+ totalCount: { type: "integer" },
134
+ kinds: {
135
+ type: "object",
136
+ additionalProperties: { type: "integer" }
69
137
  },
70
- {
71
- name: "martin_preflight",
72
- description: "Validate and normalize a proposed martin_run contract before execution. Reports the effective budget, path scope, engine readiness, and expected run-store layout.",
73
- inputSchema: {
138
+ highlights: {
139
+ type: "array",
140
+ items: {
74
141
  type: "object",
75
142
  additionalProperties: false,
76
143
  properties: {
77
- objective: {
78
- type: "string",
79
- description: "The coding task to complete. Be specific about what needs to change."
80
- },
81
- workingDirectory: {
82
- type: "string",
83
- description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
84
- },
85
- engine: {
86
- type: "string",
87
- enum: ["claude", "codex"],
88
- description: "Which agent CLI to use. Defaults to 'claude'."
89
- },
90
- model: {
91
- type: "string",
92
- description: "Model override passed to the CLI (e.g. 'claude-opus-4-6', 'o3')."
93
- },
94
- maxUsd: {
95
- type: "number",
96
- exclusiveMinimum: 0,
97
- description: "Hard budget ceiling in USD. Defaults to 25."
98
- },
99
- maxIterations: {
100
- type: "integer",
101
- exclusiveMinimum: 0,
102
- description: "Maximum number of loop attempts. Defaults to 8."
103
- },
104
- maxTokens: {
105
- type: "integer",
106
- exclusiveMinimum: 0,
107
- description: "Maximum total tokens across all attempts. Defaults to 80000."
108
- },
109
- verificationPlan: {
110
- type: "array",
111
- items: { type: "string" },
112
- description: "Shell commands that must all exit 0 for the task to be considered complete (e.g. ['pnpm test', 'pnpm build'])."
113
- },
114
- allowedPaths: {
115
- type: "array",
116
- items: { type: "string" },
117
- description: "Repo-relative path globs Martin may modify, such as ['src/**', 'tests/**']. Absolute paths and '..' traversal are rejected."
118
- },
119
- deniedPaths: {
120
- type: "array",
121
- items: { type: "string" },
122
- description: "Repo-relative path globs Martin must never modify, such as ['.env', 'docs/security/**']. Absolute paths and '..' traversal are rejected."
123
- },
124
- workspaceId: {
125
- type: "string",
126
- description: "Workspace identifier for telemetry. Defaults to 'ws_mcp'."
127
- },
128
- projectId: {
129
- type: "string",
130
- description: "Project identifier for telemetry. Defaults to 'proj_mcp'."
131
- }
144
+ artifactId: { type: "string" },
145
+ kind: { type: "string" },
146
+ label: { type: "string" },
147
+ uri: { type: "string" }
132
148
  },
133
- required: ["objective"]
149
+ required: ["artifactId", "kind", "label", "uri"]
134
150
  }
151
+ }
152
+ },
153
+ required: ["totalCount", "kinds", "highlights"]
154
+ };
155
+ const attemptArtifactsSchema = {
156
+ type: "object",
157
+ additionalProperties: false,
158
+ properties: {
159
+ directory: { type: "string" },
160
+ available: { type: "boolean" },
161
+ files: stringArraySchema
162
+ },
163
+ required: ["directory", "available", "files"]
164
+ };
165
+ const attemptSummarySchema = {
166
+ type: "object",
167
+ additionalProperties: true,
168
+ properties: {
169
+ index: { type: "integer" },
170
+ attemptId: { type: "string" },
171
+ adapterId: { type: "string" },
172
+ model: { type: "string" },
173
+ failureClass: { type: "string" },
174
+ intervention: { type: "string" },
175
+ startedAt: { type: "string" },
176
+ completedAt: { type: "string" },
177
+ summary: { type: "string" },
178
+ artifacts: attemptArtifactsSchema,
179
+ artifactFiles: stringArraySchema
180
+ },
181
+ required: ["index"]
182
+ };
183
+ const inspectionPathsSchema = {
184
+ type: "object",
185
+ additionalProperties: true,
186
+ properties: {
187
+ runsRoot: { type: "string" },
188
+ runDirectory: { type: "string" },
189
+ loopRecordPath: { type: "string" },
190
+ ledgerPath: { type: "string" },
191
+ canonicalRunDirectory: { type: "string" },
192
+ canonicalLoopRecordPath: { type: "string" }
193
+ },
194
+ required: ["runsRoot"]
195
+ };
196
+ const eventSummarySchema = {
197
+ type: "object",
198
+ additionalProperties: true,
199
+ properties: {
200
+ type: { type: "string" },
201
+ timestamp: { type: "string" },
202
+ lifecycleState: { type: "string" },
203
+ payload: {
204
+ type: "object",
205
+ additionalProperties: true
206
+ }
207
+ },
208
+ required: ["type", "payload"]
209
+ };
210
+ const runOutputSchema = {
211
+ type: "object",
212
+ additionalProperties: true,
213
+ properties: {
214
+ status: { type: "string" },
215
+ lifecycleState: { type: "string" },
216
+ reason: { type: "string" },
217
+ attempts: { type: "integer" },
218
+ costUsd: { type: "number" },
219
+ verificationPassed: { type: "boolean" },
220
+ loopId: { type: "string" },
221
+ pressure: { type: "string" },
222
+ shouldStop: { type: "boolean" },
223
+ remainingBudgetUsd: { type: "number" },
224
+ remainingIterations: { type: "integer" },
225
+ remainingTokens: { type: "integer" },
226
+ engine: { type: "string" },
227
+ workingDirectory: { type: "string" },
228
+ budget: budgetSchema,
229
+ inspection: {
230
+ type: "object",
231
+ additionalProperties: true,
232
+ properties: {
233
+ runsRoot: { type: "string" },
234
+ runDirectory: { type: "string" },
235
+ loopRecordPath: { type: "string" },
236
+ ledgerPath: { type: "string" },
237
+ loop: loopPreviewSchema,
238
+ verification: verificationSchema,
239
+ artifacts: artifactSummarySchema
240
+ },
241
+ required: ["runsRoot", "runDirectory", "loopRecordPath", "ledgerPath", "loop", "verification", "artifacts"]
242
+ }
243
+ },
244
+ required: [
245
+ "status",
246
+ "lifecycleState",
247
+ "reason",
248
+ "attempts",
249
+ "costUsd",
250
+ "verificationPassed",
251
+ "loopId",
252
+ "pressure",
253
+ "shouldStop",
254
+ "remainingBudgetUsd",
255
+ "remainingIterations",
256
+ "remainingTokens",
257
+ "engine",
258
+ "workingDirectory",
259
+ "budget",
260
+ "inspection"
261
+ ]
262
+ };
263
+ const inspectOutputSchema = {
264
+ type: "object",
265
+ additionalProperties: true,
266
+ properties: {
267
+ source: { type: "string" },
268
+ loopCount: { type: "integer" },
269
+ portfolio: {
270
+ type: "object",
271
+ additionalProperties: true
135
272
  },
136
- {
137
- name: "martin_run",
138
- description: "Execute a full Martin Loop on a coding task. Martin spawns the selected agent CLI (claude or codex), runs the task, classifies failures, and retries within the specified budget. Returns the loop outcome including lifecycle state, attempt count, and spend.",
139
- inputSchema: {
140
- type: "object",
141
- additionalProperties: false,
142
- properties: {
143
- objective: {
144
- type: "string",
145
- description: "The coding task to complete. Be specific about what needs to change."
146
- },
147
- workingDirectory: {
148
- type: "string",
149
- description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
273
+ latestRun: loopPreviewSchema,
274
+ recentRuns: {
275
+ type: "array",
276
+ items: loopPreviewSchema
277
+ },
278
+ statusBreakdown: {
279
+ type: "object",
280
+ additionalProperties: { type: "integer" }
281
+ },
282
+ lifecycleBreakdown: {
283
+ type: "object",
284
+ additionalProperties: { type: "integer" }
285
+ },
286
+ inspection: {
287
+ type: "object",
288
+ additionalProperties: false,
289
+ properties: {
290
+ sourceKind: { type: "string", enum: ["file", "runs_root"] }
291
+ },
292
+ required: ["sourceKind"]
293
+ },
294
+ warnings: stringArraySchema
295
+ },
296
+ required: [
297
+ "source",
298
+ "loopCount",
299
+ "portfolio",
300
+ "recentRuns",
301
+ "statusBreakdown",
302
+ "lifecycleBreakdown",
303
+ "inspection",
304
+ "warnings"
305
+ ]
306
+ };
307
+ const statusOutputSchema = {
308
+ type: "object",
309
+ additionalProperties: true,
310
+ properties: {
311
+ source: { type: "string" },
312
+ loopId: { type: "string" },
313
+ status: { type: "string" },
314
+ lifecycleState: { type: "string" },
315
+ attempts: { type: "integer" },
316
+ costUsd: { type: "number" },
317
+ avoidedUsd: { type: "number" },
318
+ pressure: { type: "string" },
319
+ shouldStop: { type: "boolean" },
320
+ remainingBudgetUsd: { type: "number" },
321
+ remainingIterations: { type: "integer" },
322
+ remainingTokens: { type: "integer" },
323
+ budget: budgetSchema,
324
+ inspection: {
325
+ type: "object",
326
+ additionalProperties: false,
327
+ properties: {
328
+ loop: loopPreviewSchema
329
+ },
330
+ required: ["loop"]
331
+ }
332
+ },
333
+ required: [
334
+ "source",
335
+ "loopId",
336
+ "status",
337
+ "lifecycleState",
338
+ "attempts",
339
+ "costUsd",
340
+ "avoidedUsd",
341
+ "pressure",
342
+ "shouldStop",
343
+ "remainingBudgetUsd",
344
+ "remainingIterations",
345
+ "remainingTokens",
346
+ "budget",
347
+ "inspection"
348
+ ]
349
+ };
350
+ const doctorOutputSchema = {
351
+ type: "object",
352
+ additionalProperties: true,
353
+ properties: {
354
+ status: { type: "string", enum: ["ok", "degraded"] },
355
+ summary: { type: "string" },
356
+ server: {
357
+ type: "object",
358
+ additionalProperties: false,
359
+ properties: {
360
+ name: { type: "string" },
361
+ nodeVersion: { type: "string" },
362
+ platform: { type: "string" }
363
+ },
364
+ required: ["name", "nodeVersion", "platform"]
365
+ },
366
+ environment: {
367
+ type: "object",
368
+ additionalProperties: false,
369
+ properties: {
370
+ workspaceRoot: { type: "string" },
371
+ workingDirectory: { type: "string" },
372
+ runsRoot: { type: "string" },
373
+ mode: { type: "string", enum: ["live", "stub"] },
374
+ liveMode: { type: "boolean" }
375
+ },
376
+ required: ["workspaceRoot", "workingDirectory", "runsRoot", "mode", "liveMode"]
377
+ },
378
+ engines: {
379
+ type: "object",
380
+ additionalProperties: true
381
+ },
382
+ requestedEngine: { type: "string" },
383
+ runStore: {
384
+ type: "object",
385
+ additionalProperties: true,
386
+ properties: {
387
+ exists: { type: "boolean" },
388
+ loopCount: { type: "integer" },
389
+ latestRun: loopPreviewSchema
390
+ },
391
+ required: ["exists", "loopCount"]
392
+ },
393
+ warnings: stringArraySchema
394
+ },
395
+ required: ["status", "summary", "server", "environment", "engines", "runStore", "warnings"]
396
+ };
397
+ const preflightOutputSchema = {
398
+ type: "object",
399
+ additionalProperties: true,
400
+ properties: {
401
+ ok: { type: "boolean" },
402
+ summary: { type: "string" },
403
+ warnings: stringArraySchema,
404
+ readiness: {
405
+ type: "object",
406
+ additionalProperties: false,
407
+ properties: {
408
+ mode: { type: "string", enum: ["live", "stub"] },
409
+ liveMode: { type: "boolean" },
410
+ engineReady: { type: "boolean" }
411
+ },
412
+ required: ["mode", "liveMode", "engineReady"]
413
+ },
414
+ normalized: {
415
+ type: "object",
416
+ additionalProperties: true,
417
+ properties: {
418
+ objective: { type: "string" },
419
+ workingDirectory: { type: "string" },
420
+ engine: { type: "string" },
421
+ model: { type: "string" },
422
+ budget: budgetSchema,
423
+ verificationPlan: stringArraySchema,
424
+ allowedPaths: stringArraySchema,
425
+ deniedPaths: stringArraySchema,
426
+ workspaceId: { type: "string" },
427
+ projectId: { type: "string" }
428
+ },
429
+ required: [
430
+ "objective",
431
+ "workingDirectory",
432
+ "engine",
433
+ "budget",
434
+ "verificationPlan",
435
+ "workspaceId",
436
+ "projectId"
437
+ ]
438
+ },
439
+ execution: {
440
+ type: "object",
441
+ additionalProperties: false,
442
+ properties: {
443
+ requestedEngine: { type: "string" },
444
+ engineAvailability: {
445
+ type: "object",
446
+ additionalProperties: true,
447
+ properties: {
448
+ available: { type: "boolean" },
449
+ detail: { type: "string" },
450
+ resolvedPath: { type: "string" }
150
451
  },
151
- engine: {
152
- type: "string",
153
- enum: ["claude", "codex"],
154
- description: "Which agent CLI to use. Defaults to 'claude'."
452
+ required: ["available", "detail"]
453
+ },
454
+ runsRoot: { type: "string" },
455
+ pathScope: {
456
+ type: "object",
457
+ additionalProperties: false,
458
+ properties: {
459
+ repoRoot: { type: "string" },
460
+ allowedPathsCount: { type: "integer" },
461
+ deniedPathsCount: { type: "integer" },
462
+ hasScopeConflicts: { type: "boolean" }
155
463
  },
156
- model: {
157
- type: "string",
158
- description: "Model override passed to the CLI (e.g. 'claude-opus-4-6', 'o3')."
464
+ required: ["repoRoot", "allowedPathsCount", "deniedPathsCount", "hasScopeConflicts"]
465
+ },
466
+ expectedRunLayout: {
467
+ type: "object",
468
+ additionalProperties: false,
469
+ properties: {
470
+ runDirectoryPattern: { type: "string" },
471
+ loopRecordPathPattern: { type: "string" }
159
472
  },
160
- maxUsd: {
161
- type: "number",
162
- exclusiveMinimum: 0,
163
- description: "Hard budget ceiling in USD. Defaults to 25."
473
+ required: ["runDirectoryPattern", "loopRecordPathPattern"]
474
+ }
475
+ },
476
+ required: ["requestedEngine", "engineAvailability", "runsRoot", "pathScope", "expectedRunLayout"]
477
+ }
478
+ },
479
+ required: ["ok", "summary", "warnings", "readiness", "normalized", "execution"]
480
+ };
481
+ const listRunsOutputSchema = {
482
+ type: "object",
483
+ additionalProperties: true,
484
+ properties: {
485
+ source: { type: "string" },
486
+ runsRoot: { type: "string" },
487
+ filters: {
488
+ type: "object",
489
+ additionalProperties: true
490
+ },
491
+ loopCount: { type: "integer" },
492
+ latestRun: loopPreviewSchema,
493
+ recentRuns: {
494
+ type: "array",
495
+ items: loopPreviewSchema
496
+ },
497
+ statusBreakdown: {
498
+ type: "object",
499
+ additionalProperties: { type: "integer" }
500
+ },
501
+ lifecycleBreakdown: {
502
+ type: "object",
503
+ additionalProperties: { type: "integer" }
504
+ },
505
+ warnings: stringArraySchema
506
+ },
507
+ required: [
508
+ "source",
509
+ "runsRoot",
510
+ "filters",
511
+ "loopCount",
512
+ "recentRuns",
513
+ "statusBreakdown",
514
+ "lifecycleBreakdown",
515
+ "warnings"
516
+ ]
517
+ };
518
+ const triageFindingSchema = {
519
+ type: "object",
520
+ additionalProperties: true,
521
+ properties: {
522
+ severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
523
+ summary: { type: "string" },
524
+ reasonCodes: stringArraySchema,
525
+ loop: loopPreviewSchema,
526
+ verification: verificationSchema,
527
+ suggestedResources: stringArraySchema,
528
+ suggestedPrompts: stringArraySchema
529
+ },
530
+ required: [
531
+ "severity",
532
+ "summary",
533
+ "reasonCodes",
534
+ "loop",
535
+ "verification",
536
+ "suggestedResources",
537
+ "suggestedPrompts"
538
+ ]
539
+ };
540
+ const triageRunsOutputSchema = {
541
+ type: "object",
542
+ additionalProperties: true,
543
+ properties: {
544
+ source: { type: "string" },
545
+ runsRoot: { type: "string" },
546
+ filters: {
547
+ type: "object",
548
+ additionalProperties: true
549
+ },
550
+ evaluatedRuns: { type: "integer" },
551
+ findingCount: { type: "integer" },
552
+ severityBreakdown: {
553
+ type: "object",
554
+ additionalProperties: { type: "integer" }
555
+ },
556
+ findings: {
557
+ type: "array",
558
+ items: triageFindingSchema
559
+ },
560
+ warnings: stringArraySchema
561
+ },
562
+ required: [
563
+ "source",
564
+ "runsRoot",
565
+ "filters",
566
+ "evaluatedRuns",
567
+ "findingCount",
568
+ "severityBreakdown",
569
+ "findings",
570
+ "warnings"
571
+ ]
572
+ };
573
+ const getRunOutputSchema = {
574
+ type: "object",
575
+ additionalProperties: true,
576
+ properties: {
577
+ source: { type: "string" },
578
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
579
+ loop: loopPreviewSchema,
580
+ budget: budgetSchema,
581
+ cost: costSchema,
582
+ verification: verificationSchema,
583
+ artifacts: artifactSummarySchema,
584
+ inspection: inspectionPathsSchema,
585
+ warnings: stringArraySchema
586
+ },
587
+ required: ["source", "sourceKind", "loop", "budget", "cost", "verification", "artifacts", "inspection", "warnings"]
588
+ };
589
+ const getAttemptOutputSchema = {
590
+ type: "object",
591
+ additionalProperties: true,
592
+ properties: {
593
+ source: { type: "string" },
594
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
595
+ loop: loopPreviewSchema,
596
+ attempt: attemptSummarySchema,
597
+ warnings: stringArraySchema
598
+ },
599
+ required: ["source", "sourceKind", "loop", "attempt", "warnings"]
600
+ };
601
+ const verificationResultsOutputSchema = {
602
+ type: "object",
603
+ additionalProperties: true,
604
+ properties: {
605
+ source: { type: "string" },
606
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
607
+ loop: loopPreviewSchema,
608
+ verification: verificationSchema,
609
+ warnings: stringArraySchema
610
+ },
611
+ required: ["source", "sourceKind", "loop", "verification", "warnings"]
612
+ };
613
+ const dossierOutputSchema = {
614
+ type: "object",
615
+ additionalProperties: true,
616
+ properties: {
617
+ source: { type: "string" },
618
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
619
+ loop: loopPreviewSchema,
620
+ budget: budgetSchema,
621
+ cost: costSchema,
622
+ attempts: {
623
+ type: "array",
624
+ items: attemptSummarySchema
625
+ },
626
+ verification: verificationSchema,
627
+ artifacts: artifactSummarySchema,
628
+ recentEvents: {
629
+ type: "array",
630
+ items: eventSummarySchema
631
+ },
632
+ related: {
633
+ type: "object",
634
+ additionalProperties: false,
635
+ properties: {
636
+ resources: stringArraySchema,
637
+ prompts: stringArraySchema
638
+ },
639
+ required: ["resources", "prompts"]
640
+ },
641
+ inspection: inspectionPathsSchema,
642
+ warnings: stringArraySchema
643
+ },
644
+ required: [
645
+ "source",
646
+ "sourceKind",
647
+ "loop",
648
+ "budget",
649
+ "cost",
650
+ "attempts",
651
+ "verification",
652
+ "artifacts",
653
+ "recentEvents",
654
+ "related",
655
+ "inspection",
656
+ "warnings"
657
+ ]
658
+ };
659
+ const planOutputSchema = {
660
+ type: "object",
661
+ additionalProperties: true
662
+ };
663
+ const logsOutputSchema = {
664
+ type: "object",
665
+ additionalProperties: true
666
+ };
667
+ const controlOutputSchema = {
668
+ type: "object",
669
+ additionalProperties: true
670
+ };
671
+ const evalOutputSchema = {
672
+ type: "object",
673
+ additionalProperties: true
674
+ };
675
+ const prSummaryOutputSchema = {
676
+ type: "object",
677
+ additionalProperties: true
678
+ };
679
+ const prReviewOutputSchema = {
680
+ type: "object",
681
+ additionalProperties: true
682
+ };
683
+ export function createMartinMcpServer(serverInfo) {
684
+ const server = new Server({
685
+ name: serverInfo?.name ?? "martin-loop",
686
+ version: serverInfo?.version ?? MARTIN_MCP_PACKAGE_VERSION
687
+ }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
688
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
689
+ tools: [
690
+ {
691
+ name: "martin_run",
692
+ description: "Execute a governed Martin Loop run on a coding task and return the run summary, spend, artifact rollup, and verification state. This hard-blocks until martin_doctor, martin_plan, and martin_preflight receipts exist for the same task.",
693
+ annotations: {
694
+ destructiveHint: true,
695
+ idempotentHint: false,
696
+ openWorldHint: false
697
+ },
698
+ inputSchema: {
699
+ type: "object",
700
+ additionalProperties: false,
701
+ properties: {
702
+ objective: {
703
+ type: "string",
704
+ description: "The coding task to complete. Be specific about what needs to change."
705
+ },
706
+ workingDirectory: {
707
+ type: "string",
708
+ description: "Optional repo-root override resolved under the MCP workspace root. Must stay within that safe root."
709
+ },
710
+ engine: {
711
+ type: "string",
712
+ enum: ["claude", "codex"],
713
+ description: "Which agent CLI to use. Defaults to claude."
714
+ },
715
+ model: {
716
+ type: "string",
717
+ description: "Optional model override passed to the CLI."
718
+ },
719
+ maxUsd: {
720
+ type: "number",
721
+ exclusiveMinimum: 0,
722
+ description: "Hard budget ceiling in USD."
723
+ },
724
+ maxIterations: {
725
+ type: "integer",
726
+ exclusiveMinimum: 0,
727
+ description: "Maximum number of loop attempts."
728
+ },
729
+ maxTokens: {
730
+ type: "integer",
731
+ exclusiveMinimum: 0,
732
+ description: "Maximum total tokens across all attempts."
733
+ },
734
+ verificationPlan: {
735
+ type: "array",
736
+ items: { type: "string" },
737
+ description: "Commands that must all exit 0 for the task to be considered complete."
738
+ },
739
+ allowedPaths: {
740
+ type: "array",
741
+ items: { type: "string" },
742
+ description: "Repo-relative path globs Martin may modify."
743
+ },
744
+ deniedPaths: {
745
+ type: "array",
746
+ items: { type: "string" },
747
+ description: "Repo-relative path globs Martin must never modify."
748
+ },
749
+ workspaceId: {
750
+ type: "string",
751
+ description: "Workspace identifier for telemetry."
752
+ },
753
+ projectId: {
754
+ type: "string",
755
+ description: "Project identifier for telemetry."
756
+ }
164
757
  },
165
- maxIterations: {
166
- type: "integer",
167
- exclusiveMinimum: 0,
168
- description: "Maximum number of loop attempts. Defaults to 8."
758
+ required: ["objective"]
759
+ },
760
+ outputSchema: runOutputSchema
761
+ },
762
+ {
763
+ name: "martin_inspect",
764
+ description: "Summarise Martin Loop run records from a saved loop file or run-store directory.",
765
+ annotations: {
766
+ readOnlyHint: true,
767
+ idempotentHint: true
768
+ },
769
+ inputSchema: {
770
+ type: "object",
771
+ additionalProperties: false,
772
+ properties: {
773
+ file: {
774
+ type: "string",
775
+ description: "Optional path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
776
+ },
777
+ runsDir: {
778
+ type: "string",
779
+ description: "Optional runs-root override resolved under the default Martin runs root."
780
+ }
781
+ }
782
+ },
783
+ outputSchema: inspectOutputSchema
784
+ },
785
+ {
786
+ name: "martin_status",
787
+ description: "Return the current budget and cost state of a Martin loop record.",
788
+ annotations: {
789
+ readOnlyHint: true,
790
+ idempotentHint: true
791
+ },
792
+ inputSchema: {
793
+ type: "object",
794
+ additionalProperties: false,
795
+ properties: {
796
+ loopJson: { type: "string", description: "JSON-serialized LoopRecord." },
797
+ file: {
798
+ type: "string",
799
+ description: "Optional path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
800
+ },
801
+ loopId: {
802
+ type: "string",
803
+ description: "Loop ID resolved as <runsDir>/<loopId>/loop-record.json."
804
+ },
805
+ runsDir: {
806
+ type: "string",
807
+ description: "Optional runs-root override resolved under the default Martin runs root."
808
+ },
809
+ latest: {
810
+ const: true,
811
+ description: "When true, loads the most recently updated loop record in the runs directory."
812
+ }
169
813
  },
170
- maxTokens: {
171
- type: "integer",
172
- exclusiveMinimum: 0,
173
- description: "Maximum total tokens across all attempts. Defaults to 80000."
814
+ oneOf: [
815
+ { required: ["loopJson"] },
816
+ { required: ["file"] },
817
+ { required: ["loopId"] },
818
+ { required: ["latest"] }
819
+ ]
820
+ },
821
+ outputSchema: statusOutputSchema
822
+ },
823
+ {
824
+ name: "martin_doctor",
825
+ description: "Read-only environment and run-store diagnostics for the Martin MCP server. This is the expected first call before governed work begins.",
826
+ annotations: {
827
+ readOnlyHint: true,
828
+ idempotentHint: true
829
+ },
830
+ inputSchema: {
831
+ type: "object",
832
+ additionalProperties: false,
833
+ properties: {
834
+ workingDirectory: {
835
+ type: "string",
836
+ description: "Optional repo-root override for doctor context."
837
+ },
838
+ runsDir: {
839
+ type: "string",
840
+ description: "Optional runs-root override resolved under the default Martin runs root."
841
+ },
842
+ engine: {
843
+ type: "string",
844
+ enum: ["claude", "codex"],
845
+ description: "Optional engine to highlight in diagnostics."
846
+ }
847
+ }
848
+ },
849
+ outputSchema: doctorOutputSchema
850
+ },
851
+ {
852
+ name: "martin_plan",
853
+ description: "Read-only planning step that turns an objective into a scoped implementation plan, verifier proposal, policy pack, and risk recommendation. Use before preflight and before any real coding run.",
854
+ annotations: {
855
+ readOnlyHint: true,
856
+ idempotentHint: true
857
+ },
858
+ inputSchema: {
859
+ type: "object",
860
+ additionalProperties: false,
861
+ properties: {
862
+ objective: { type: "string", description: "The coding objective to plan." },
863
+ workingDirectory: {
864
+ type: "string",
865
+ description: "Optional repo-root override resolved under the MCP workspace root."
866
+ },
867
+ context: { type: "string", description: "Optional extra issue or bug context." },
868
+ policyPack: {
869
+ type: "string",
870
+ enum: ["solo-founder", "startup-team", "enterprise-strict", "oss-maintainer", "security-sensitive"]
871
+ },
872
+ verificationPlan: { type: "array", items: { type: "string" } },
873
+ allowedPaths: { type: "array", items: { type: "string" } },
874
+ deniedPaths: { type: "array", items: { type: "string" } },
875
+ maxUsd: { type: "number", exclusiveMinimum: 0 },
876
+ maxIterations: { type: "integer", exclusiveMinimum: 0 },
877
+ maxTokens: { type: "integer", exclusiveMinimum: 0 },
878
+ maxMinutes: { type: "integer", exclusiveMinimum: 0 },
879
+ maxFilesChanged: { type: "integer", exclusiveMinimum: 0 },
880
+ maxCommands: { type: "integer", exclusiveMinimum: 0 }
174
881
  },
175
- verificationPlan: {
176
- type: "array",
177
- items: { type: "string" },
178
- description: "Shell commands that must all exit 0 for the task to be considered complete (e.g. ['pnpm test', 'pnpm build'])."
882
+ required: ["objective"]
883
+ },
884
+ outputSchema: planOutputSchema
885
+ },
886
+ {
887
+ name: "martin_preflight",
888
+ description: "Read-only validation of a planned Martin run before any execution or spend. This is the last required step before martin_run.",
889
+ annotations: {
890
+ readOnlyHint: true,
891
+ idempotentHint: true
892
+ },
893
+ inputSchema: {
894
+ type: "object",
895
+ additionalProperties: false,
896
+ properties: {
897
+ objective: {
898
+ type: "string",
899
+ description: "The coding task to validate."
900
+ },
901
+ workingDirectory: {
902
+ type: "string",
903
+ description: "Optional repo-root override resolved under the MCP workspace root."
904
+ },
905
+ engine: {
906
+ type: "string",
907
+ enum: ["claude", "codex"],
908
+ description: "Which agent CLI would be used. Defaults to claude."
909
+ },
910
+ model: {
911
+ type: "string",
912
+ description: "Model override passed to the CLI."
913
+ },
914
+ context: {
915
+ type: "string",
916
+ description: "Optional issue context carried into the run contract."
917
+ },
918
+ policyPack: {
919
+ type: "string",
920
+ enum: ["solo-founder", "startup-team", "enterprise-strict", "oss-maintainer", "security-sensitive"]
921
+ },
922
+ maxUsd: {
923
+ type: "number",
924
+ exclusiveMinimum: 0,
925
+ description: "Hard budget ceiling in USD."
926
+ },
927
+ maxIterations: {
928
+ type: "integer",
929
+ exclusiveMinimum: 0,
930
+ description: "Maximum number of loop attempts."
931
+ },
932
+ maxTokens: {
933
+ type: "integer",
934
+ exclusiveMinimum: 0,
935
+ description: "Maximum total tokens across all attempts."
936
+ },
937
+ maxMinutes: {
938
+ type: "integer",
939
+ exclusiveMinimum: 0,
940
+ description: "Estimated wall-clock minutes allowed for the run contract."
941
+ },
942
+ maxFilesChanged: {
943
+ type: "integer",
944
+ exclusiveMinimum: 0,
945
+ description: "Estimated maximum files changed for the run contract."
946
+ },
947
+ maxCommands: {
948
+ type: "integer",
949
+ exclusiveMinimum: 0,
950
+ description: "Estimated maximum commands allowed for the run contract."
951
+ },
952
+ verificationPlan: {
953
+ type: "array",
954
+ items: { type: "string" },
955
+ description: "Commands that must all exit 0 for completion."
956
+ },
957
+ allowedPaths: {
958
+ type: "array",
959
+ items: { type: "string" },
960
+ description: "Repo-relative path globs Martin may modify."
961
+ },
962
+ deniedPaths: {
963
+ type: "array",
964
+ items: { type: "string" },
965
+ description: "Repo-relative path globs Martin must never modify."
966
+ },
967
+ workspaceId: { type: "string" },
968
+ projectId: { type: "string" }
179
969
  },
180
- allowedPaths: {
181
- type: "array",
182
- items: { type: "string" },
183
- description: "Repo-relative path globs Martin may modify, such as ['src/**', 'tests/**']. Absolute paths and '..' traversal are rejected."
970
+ required: ["objective"]
971
+ },
972
+ outputSchema: preflightOutputSchema
973
+ },
974
+ {
975
+ name: "martin_logs",
976
+ description: "Read recent Martin loop events, ledger entries, and operator control receipts for live observability.",
977
+ annotations: {
978
+ readOnlyHint: true,
979
+ idempotentHint: true
980
+ },
981
+ inputSchema: {
982
+ type: "object",
983
+ additionalProperties: false,
984
+ properties: {
985
+ file: { type: "string" },
986
+ loopId: { type: "string" },
987
+ runsDir: { type: "string" },
988
+ latest: { const: true },
989
+ limit: { type: "integer", minimum: 1 }
184
990
  },
185
- deniedPaths: {
186
- type: "array",
187
- items: { type: "string" },
188
- description: "Repo-relative path globs Martin must never modify, such as ['.env', 'docs/security/**']. Absolute paths and '..' traversal are rejected."
991
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
992
+ },
993
+ outputSchema: logsOutputSchema
994
+ },
995
+ {
996
+ name: "martin_pause",
997
+ description: "Record a durable pause request for a Martin run so humans and runtimes can see that execution should pause before risky follow-up work.",
998
+ annotations: {
999
+ destructiveHint: true,
1000
+ idempotentHint: false
1001
+ },
1002
+ inputSchema: {
1003
+ type: "object",
1004
+ additionalProperties: false,
1005
+ properties: {
1006
+ file: { type: "string" },
1007
+ loopId: { type: "string" },
1008
+ runsDir: { type: "string" },
1009
+ latest: { const: true },
1010
+ reason: { type: "string" },
1011
+ requestedBy: { type: "string" }
189
1012
  },
190
- workspaceId: {
191
- type: "string",
192
- description: "Workspace identifier for telemetry. Defaults to 'ws_mcp'."
1013
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1014
+ },
1015
+ outputSchema: controlOutputSchema
1016
+ },
1017
+ {
1018
+ name: "martin_cancel",
1019
+ description: "Record a durable cancellation request for a Martin run. This writes a control receipt; it does not silently kill a process without evidence.",
1020
+ annotations: {
1021
+ destructiveHint: true,
1022
+ idempotentHint: false
1023
+ },
1024
+ inputSchema: {
1025
+ type: "object",
1026
+ additionalProperties: false,
1027
+ properties: {
1028
+ file: { type: "string" },
1029
+ loopId: { type: "string" },
1030
+ runsDir: { type: "string" },
1031
+ latest: { const: true },
1032
+ reason: { type: "string" },
1033
+ requestedBy: { type: "string" }
193
1034
  },
194
- projectId: {
195
- type: "string",
196
- description: "Project identifier for telemetry. Defaults to 'proj_mcp'."
197
- }
1035
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
198
1036
  },
199
- required: ["objective"]
200
- }
201
- },
202
- {
203
- name: "martin_inspect",
204
- description: "Summarise Martin Loop run records from a saved loop file or run-store directory. Supports canonical loop-record.json files, legacy JSONL files, and full runs directories.",
205
- inputSchema: {
206
- type: "object",
207
- additionalProperties: false,
208
- properties: {
209
- file: {
210
- type: "string",
211
- description: "Optional path resolved under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
1037
+ outputSchema: controlOutputSchema
1038
+ },
1039
+ {
1040
+ name: "martin_continue",
1041
+ description: "Record a durable continue or resume request for a Martin run after a human pause or approval checkpoint.",
1042
+ annotations: {
1043
+ destructiveHint: true,
1044
+ idempotentHint: false
1045
+ },
1046
+ inputSchema: {
1047
+ type: "object",
1048
+ additionalProperties: false,
1049
+ properties: {
1050
+ file: { type: "string" },
1051
+ loopId: { type: "string" },
1052
+ runsDir: { type: "string" },
1053
+ latest: { const: true },
1054
+ reason: { type: "string" },
1055
+ requestedBy: { type: "string" }
212
1056
  },
213
- runsDir: {
214
- type: "string",
215
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
1057
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1058
+ },
1059
+ outputSchema: controlOutputSchema
1060
+ },
1061
+ {
1062
+ name: "martin_list_runs",
1063
+ description: "List recent Martin runs from the run store with lightweight filters for status, lifecycle, engine metadata, and recency.",
1064
+ annotations: {
1065
+ readOnlyHint: true,
1066
+ idempotentHint: true
1067
+ },
1068
+ inputSchema: {
1069
+ type: "object",
1070
+ additionalProperties: false,
1071
+ properties: {
1072
+ runsDir: { type: "string", description: "Optional runs-root override." },
1073
+ limit: {
1074
+ type: "integer",
1075
+ minimum: 1,
1076
+ description: "Maximum number of runs to return. Defaults to 20."
1077
+ },
1078
+ status: { type: "string", description: "Filter by loop status." },
1079
+ lifecycleState: { type: "string", description: "Filter by lifecycle state." },
1080
+ adapterId: { type: "string", description: "Filter by attempt adapter ID." },
1081
+ model: { type: "string", description: "Filter by attempt model." },
1082
+ updatedAfter: {
1083
+ type: "string",
1084
+ description: "Optional ISO-8601 timestamp for recency filtering."
1085
+ }
216
1086
  }
217
- }
218
- }
219
- },
220
- {
221
- name: "martin_status",
222
- description: "Return the current budget and cost state of a Martin loop record. Accepts inline JSON, a saved loop file, a loopId under the run store, or the latest run in the store.",
223
- inputSchema: {
224
- type: "object",
225
- additionalProperties: false,
226
- properties: {
227
- loopJson: {
228
- type: "string",
229
- description: "JSON-serialized LoopRecord."
230
- },
231
- file: {
232
- type: "string",
233
- description: "Optional path resolved under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
234
- },
235
- loopId: {
236
- type: "string",
237
- description: "Optional Martin loop ID. Loads <runsDir>/<loopId>/loop-record.json."
238
- },
239
- runsDir: {
240
- type: "string",
241
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
242
- },
243
- latest: {
244
- const: true,
245
- description: "When true, loads the most recently updated loop record in the runs directory."
1087
+ },
1088
+ outputSchema: listRunsOutputSchema
1089
+ },
1090
+ {
1091
+ name: "martin_triage_runs",
1092
+ description: "Prioritize Martin runs that need operator or agent attention based on verification, lifecycle, and budget pressure.",
1093
+ annotations: {
1094
+ readOnlyHint: true,
1095
+ idempotentHint: true
1096
+ },
1097
+ inputSchema: {
1098
+ type: "object",
1099
+ additionalProperties: false,
1100
+ properties: {
1101
+ runsDir: { type: "string", description: "Optional runs-root override." },
1102
+ limit: {
1103
+ type: "integer",
1104
+ minimum: 1,
1105
+ description: "Maximum number of runs to triage. Defaults to 20."
1106
+ },
1107
+ status: { type: "string", description: "Filter by loop status." },
1108
+ lifecycleState: {
1109
+ type: "string",
1110
+ description: "Filter by lifecycle state."
1111
+ },
1112
+ adapterId: { type: "string", description: "Filter by attempt adapter ID." },
1113
+ model: { type: "string", description: "Filter by attempt model." },
1114
+ updatedAfter: {
1115
+ type: "string",
1116
+ description: "Optional ISO-8601 timestamp for recency filtering."
1117
+ },
1118
+ includeHealthy: {
1119
+ type: "boolean",
1120
+ description: "When true, include healthy runs instead of only attention-worthy findings."
1121
+ }
246
1122
  }
247
1123
  },
248
- oneOf: [
249
- { required: ["loopJson"] },
250
- { required: ["file"] },
251
- { required: ["loopId"] },
252
- { required: ["latest"] }
253
- ]
254
- }
255
- },
256
- {
257
- name: "martin_list_runs",
258
- description: "List recent Martin Loop runs from the local run store with budget, verifier, and lifecycle summaries. Read-only.",
259
- inputSchema: {
260
- type: "object",
261
- additionalProperties: false,
262
- properties: {
263
- runsDir: {
264
- type: "string",
265
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
1124
+ outputSchema: triageRunsOutputSchema
1125
+ },
1126
+ {
1127
+ name: "martin_get_run",
1128
+ description: "Load one Martin run and return its budget, cost, verification, artifact, and canonical path summary.",
1129
+ annotations: {
1130
+ readOnlyHint: true,
1131
+ idempotentHint: true
1132
+ },
1133
+ inputSchema: {
1134
+ type: "object",
1135
+ additionalProperties: false,
1136
+ properties: {
1137
+ file: {
1138
+ type: "string",
1139
+ description: "Path to a canonical loop-record.json, legacy file, or run-store directory."
1140
+ },
1141
+ loopId: { type: "string", description: "Loop ID under the run store." },
1142
+ runsDir: { type: "string", description: "Optional runs-root override." },
1143
+ latest: {
1144
+ const: true,
1145
+ description: "When true, loads the most recently updated loop record in the run store."
1146
+ }
266
1147
  },
267
- limit: {
268
- type: "integer",
269
- exclusiveMinimum: 0,
270
- description: "Maximum number of runs to return. Defaults to 20."
271
- }
272
- }
273
- }
274
- },
275
- {
276
- name: "martin_get_run",
277
- description: "Load a read-only run dossier by loopId or latest run selector, including task, budget, cost, and attempts.",
278
- inputSchema: {
279
- type: "object",
280
- additionalProperties: false,
281
- properties: {
282
- loopId: {
283
- type: "string",
284
- description: "Martin loop ID under the run store."
1148
+ oneOf: [
1149
+ { required: ["file"] },
1150
+ { required: ["loopId"] },
1151
+ { required: ["latest"] }
1152
+ ]
1153
+ },
1154
+ outputSchema: getRunOutputSchema
1155
+ },
1156
+ {
1157
+ name: "martin_get_attempt",
1158
+ description: "Load one Martin attempt summary with artifact directory references for a canonical run.",
1159
+ annotations: {
1160
+ readOnlyHint: true,
1161
+ idempotentHint: true
1162
+ },
1163
+ inputSchema: {
1164
+ type: "object",
1165
+ additionalProperties: false,
1166
+ properties: {
1167
+ file: {
1168
+ type: "string",
1169
+ description: "Path to a canonical loop-record.json file or run directory."
1170
+ },
1171
+ loopId: { type: "string", description: "Loop ID under the run store." },
1172
+ runsDir: { type: "string", description: "Optional runs-root override." },
1173
+ attemptIndex: {
1174
+ type: "integer",
1175
+ minimum: 1,
1176
+ description: "Attempt index to inspect. Defaults to the latest attempt."
1177
+ }
285
1178
  },
286
- runsDir: {
287
- type: "string",
288
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
1179
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }]
1180
+ },
1181
+ outputSchema: getAttemptOutputSchema
1182
+ },
1183
+ {
1184
+ name: "martin_get_verification_results",
1185
+ description: "Load verification evidence for a Martin run from stored loop events and ledger entries.",
1186
+ annotations: {
1187
+ readOnlyHint: true,
1188
+ idempotentHint: true
1189
+ },
1190
+ inputSchema: {
1191
+ type: "object",
1192
+ additionalProperties: false,
1193
+ properties: {
1194
+ file: {
1195
+ type: "string",
1196
+ description: "Path to a canonical loop-record.json file or run directory."
1197
+ },
1198
+ loopId: { type: "string", description: "Loop ID under the run store." },
1199
+ runsDir: { type: "string", description: "Optional runs-root override." }
289
1200
  },
290
- latest: {
291
- const: true,
292
- description: "When true, loads the most recently updated loop record in the runs directory."
293
- }
1201
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }]
294
1202
  },
295
- oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
296
- }
297
- },
298
- {
299
- name: "martin_get_attempt",
300
- description: "Load read-only attempt evidence for a single Martin Loop attempt by loopId and attempt index.",
301
- inputSchema: {
302
- type: "object",
303
- additionalProperties: false,
304
- properties: {
305
- loopId: {
306
- type: "string",
307
- description: "Martin loop ID under the run store."
1203
+ outputSchema: verificationResultsOutputSchema
1204
+ },
1205
+ {
1206
+ name: "martin_run_dossier",
1207
+ description: "Return the full governed execution dossier for one Martin run, including attempts, events, artifacts, and related discovery surfaces.",
1208
+ annotations: {
1209
+ readOnlyHint: true,
1210
+ idempotentHint: true
1211
+ },
1212
+ inputSchema: {
1213
+ type: "object",
1214
+ additionalProperties: false,
1215
+ properties: {
1216
+ file: {
1217
+ type: "string",
1218
+ description: "Path to a canonical loop-record.json, legacy file, or run-store directory."
1219
+ },
1220
+ loopId: { type: "string", description: "Loop ID under the run store." },
1221
+ runsDir: { type: "string", description: "Optional runs-root override." },
1222
+ latest: {
1223
+ const: true,
1224
+ description: "When true, loads the most recently updated loop record in the run store."
1225
+ }
308
1226
  },
309
- attemptIndex: {
310
- type: "integer",
311
- exclusiveMinimum: 0,
312
- description: "1-based attempt index to inspect."
1227
+ oneOf: [
1228
+ { required: ["file"] },
1229
+ { required: ["loopId"] },
1230
+ { required: ["latest"] }
1231
+ ]
1232
+ },
1233
+ outputSchema: dossierOutputSchema
1234
+ },
1235
+ {
1236
+ name: "martin_dossier",
1237
+ description: "Alias for martin_run_dossier with support for JSON, Markdown, or GitHub PR formatting. Use after martin_run to understand what happened and whether the result is actually safe to trust.",
1238
+ annotations: {
1239
+ readOnlyHint: true,
1240
+ idempotentHint: true
1241
+ },
1242
+ inputSchema: {
1243
+ type: "object",
1244
+ additionalProperties: false,
1245
+ properties: {
1246
+ file: { type: "string" },
1247
+ loopId: { type: "string" },
1248
+ runsDir: { type: "string" },
1249
+ latest: { const: true },
1250
+ format: { type: "string", enum: ["json", "md", "github-pr"] }
313
1251
  },
314
- runsDir: {
315
- type: "string",
316
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
317
- }
1252
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
318
1253
  },
319
- required: ["loopId", "attemptIndex"]
320
- }
321
- },
322
- {
323
- name: "martin_get_verification_results",
324
- description: "Extract verifier completion events for a run by loopId or latest selector. Read-only.",
325
- inputSchema: {
326
- type: "object",
327
- additionalProperties: false,
328
- properties: {
329
- loopId: {
330
- type: "string",
331
- description: "Martin loop ID under the run store."
1254
+ outputSchema: dossierOutputSchema
1255
+ },
1256
+ {
1257
+ name: "martin_eval",
1258
+ description: "Grade a Martin run for task completion, verifier health, diff discipline, risk, and reviewability.",
1259
+ annotations: {
1260
+ readOnlyHint: true,
1261
+ idempotentHint: true
1262
+ },
1263
+ inputSchema: {
1264
+ type: "object",
1265
+ additionalProperties: false,
1266
+ properties: {
1267
+ file: { type: "string" },
1268
+ loopId: { type: "string" },
1269
+ runsDir: { type: "string" },
1270
+ latest: { const: true }
332
1271
  },
333
- runsDir: {
334
- type: "string",
335
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
1272
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1273
+ },
1274
+ outputSchema: evalOutputSchema
1275
+ },
1276
+ {
1277
+ name: "martin_pr_summary",
1278
+ description: "Generate a PR title and body with a MartinLoop dossier block for a completed run.",
1279
+ annotations: {
1280
+ readOnlyHint: true,
1281
+ idempotentHint: true
1282
+ },
1283
+ inputSchema: {
1284
+ type: "object",
1285
+ additionalProperties: false,
1286
+ properties: {
1287
+ file: { type: "string" },
1288
+ loopId: { type: "string" },
1289
+ runsDir: { type: "string" },
1290
+ latest: { const: true },
1291
+ format: { type: "string", enum: ["json", "md", "github-pr"] }
336
1292
  },
337
- latest: {
338
- const: true,
339
- description: "When true, loads the most recently updated loop record in the runs directory."
340
- }
1293
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
341
1294
  },
342
- oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
343
- }
344
- },
345
- {
346
- name: "martin_run_dossier",
347
- description: "Build a compact read-only dossier for review: summary, budget, attempts, and verification evidence.",
348
- inputSchema: {
349
- type: "object",
350
- additionalProperties: false,
351
- properties: {
352
- loopId: {
353
- type: "string",
354
- description: "Martin loop ID under the run store."
1295
+ outputSchema: prSummaryOutputSchema
1296
+ },
1297
+ {
1298
+ name: "martin_create_pr",
1299
+ description: "Create or preview a GitHub PR with a MartinLoop dossier body. Use execute=true to actually call gh.",
1300
+ annotations: {
1301
+ destructiveHint: true,
1302
+ idempotentHint: false
1303
+ },
1304
+ inputSchema: {
1305
+ type: "object",
1306
+ additionalProperties: false,
1307
+ properties: {
1308
+ file: { type: "string" },
1309
+ loopId: { type: "string" },
1310
+ runsDir: { type: "string" },
1311
+ latest: { const: true },
1312
+ format: { type: "string", enum: ["json", "md", "github-pr"] },
1313
+ title: { type: "string" },
1314
+ base: { type: "string" },
1315
+ execute: { type: "boolean" }
355
1316
  },
356
- runsDir: {
357
- type: "string",
358
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
1317
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1318
+ },
1319
+ outputSchema: prSummaryOutputSchema
1320
+ },
1321
+ {
1322
+ name: "martin_review_pr",
1323
+ description: "Review a PR or PR draft against the Martin dossier and evaluation evidence.",
1324
+ annotations: {
1325
+ readOnlyHint: true,
1326
+ idempotentHint: true
1327
+ },
1328
+ inputSchema: {
1329
+ type: "object",
1330
+ additionalProperties: false,
1331
+ properties: {
1332
+ file: { type: "string" },
1333
+ loopId: { type: "string" },
1334
+ runsDir: { type: "string" },
1335
+ latest: { const: true },
1336
+ format: { type: "string", enum: ["json", "md", "github-pr"] },
1337
+ prBody: { type: "string" }
359
1338
  },
360
- latest: {
361
- const: true,
362
- description: "When true, loads the most recently updated loop record in the runs directory."
363
- }
1339
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
364
1340
  },
365
- oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
1341
+ outputSchema: prReviewOutputSchema
366
1342
  }
1343
+ ]
1344
+ }));
1345
+ server.setRequestHandler(ListResourcesRequestSchema, () => ({
1346
+ ...listMartinResources()
1347
+ }));
1348
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
1349
+ ...listMartinResourceTemplates()
1350
+ }));
1351
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1352
+ try {
1353
+ return await readMartinResource({ uri: request.params.uri });
367
1354
  }
368
- ]
369
- }));
370
- // ---------------------------------------------------------------------------
371
- // Resources and prompts
372
- // ---------------------------------------------------------------------------
373
- server.setRequestHandler(ListResourcesRequestSchema, () => ({
374
- resources: listMartinResources()
375
- }));
376
- server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
377
- resourceTemplates: listMartinResourceTemplates()
378
- }));
379
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
380
- return readMartinResource(request.params.uri);
381
- });
382
- server.setRequestHandler(ListPromptsRequestSchema, () => ({
383
- prompts: listMartinPrompts()
384
- }));
385
- server.setRequestHandler(GetPromptRequestSchema, (request) => {
386
- return getMartinPrompt(request.params.name, request.params.arguments ?? {});
387
- });
388
- // ---------------------------------------------------------------------------
389
- // Tool dispatch
390
- // ---------------------------------------------------------------------------
391
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
392
- const { name, arguments: args } = request.params;
393
- try {
394
- if (name === "martin_doctor") {
395
- const input = validateToolInput("martin_doctor", args);
396
- const output = await martinDoctorTool(input);
397
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
398
- }
399
- if (name === "martin_preflight") {
400
- const input = validateToolInput("martin_preflight", args);
401
- const output = await martinPreflightTool(input);
402
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
403
- }
404
- if (name === "martin_run") {
405
- const input = validateToolInput("martin_run", args);
406
- const output = await runLoopTool(input);
407
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
408
- }
409
- if (name === "martin_inspect") {
410
- const input = validateToolInput("martin_inspect", args);
411
- const output = await inspectLoopTool(input);
412
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
413
- }
414
- if (name === "martin_status") {
415
- const input = validateToolInput("martin_status", args);
416
- const output = await getStatusTool(input);
417
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
418
- }
419
- if (name === "martin_list_runs") {
420
- const input = validateToolInput("martin_list_runs", args);
421
- const output = await listRunsTool(input);
422
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
1355
+ catch (error) {
1356
+ if (error instanceof MartinToolError) {
1357
+ throw error;
1358
+ }
1359
+ throw new Error(sanitizeToolErrorMessage(error));
423
1360
  }
424
- if (name === "martin_get_run") {
425
- const input = validateToolInput("martin_get_run", args);
426
- const output = await getRunTool(input);
427
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
1361
+ });
1362
+ server.setRequestHandler(ListPromptsRequestSchema, () => ({
1363
+ ...listMartinPrompts()
1364
+ }));
1365
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1366
+ try {
1367
+ return await getMartinPrompt({
1368
+ name: request.params.name,
1369
+ arguments: request.params.arguments
1370
+ });
428
1371
  }
429
- if (name === "martin_get_attempt") {
430
- const input = validateToolInput("martin_get_attempt", args);
431
- const output = await getAttemptTool(input);
432
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
1372
+ catch (error) {
1373
+ if (error instanceof MartinToolError) {
1374
+ throw error;
1375
+ }
1376
+ throw new Error(sanitizeToolErrorMessage(error));
433
1377
  }
434
- if (name === "martin_get_verification_results") {
435
- const input = validateToolInput("martin_get_verification_results", args);
436
- const output = await getVerificationResultsTool(input);
437
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
1378
+ });
1379
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1380
+ const { name, arguments: args } = request.params;
1381
+ try {
1382
+ if (name === "martin_run") {
1383
+ const input = validateToolInput("martin_run", args);
1384
+ const output = await runLoopTool(input);
1385
+ return createToolSuccessResult(output, `Run ${output.loopId} is ${output.status}/${output.lifecycleState} after ${output.attempts} attempt(s); spend ${output.costUsd.toFixed(2)} USD.`);
1386
+ }
1387
+ if (name === "martin_inspect") {
1388
+ const input = validateToolInput("martin_inspect", args);
1389
+ const output = await inspectLoopTool(input);
1390
+ return createToolSuccessResult(output, `Inspected ${output.loopCount} run(s) from ${output.source}; total actual spend ${output.portfolio.totalActualUsd.toFixed(2)} USD.`);
1391
+ }
1392
+ if (name === "martin_status") {
1393
+ const input = validateToolInput("martin_status", args);
1394
+ const output = await getStatusTool(input);
1395
+ return createToolSuccessResult(output, `Loop ${output.loopId} is ${output.status}/${output.lifecycleState}; pressure is ${output.pressure} with ${output.remainingBudgetUsd.toFixed(2)} USD remaining.`);
1396
+ }
1397
+ if (name === "martin_doctor") {
1398
+ const input = validateToolInput("martin_doctor", args);
1399
+ const output = await martinDoctorTool(input);
1400
+ await recordMcpWorkflowStep({
1401
+ runsRoot: output.environment.runsRoot,
1402
+ step: "doctor",
1403
+ workingDirectory: output.environment.workingDirectory,
1404
+ engine: input.engine
1405
+ }).catch(() => { });
1406
+ return createToolSuccessResult(output, output.summary);
1407
+ }
1408
+ if (name === "martin_plan") {
1409
+ const input = validateToolInput("martin_plan", args);
1410
+ const output = await martinPlanTool(input);
1411
+ await recordMcpWorkflowStep({
1412
+ runsRoot: resolveRunsRoot(process.env),
1413
+ step: "plan",
1414
+ workingDirectory: output.workingDirectory,
1415
+ objective: output.objective
1416
+ }).catch(() => { });
1417
+ return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
1418
+ }
1419
+ if (name === "martin_preflight") {
1420
+ const input = validateToolInput("martin_preflight", args);
1421
+ const output = await martinPreflightTool(input);
1422
+ if (output.ok) {
1423
+ await recordMcpWorkflowStep({
1424
+ runsRoot: output.execution.runsRoot,
1425
+ step: "preflight",
1426
+ workingDirectory: output.normalized.workingDirectory,
1427
+ objective: output.normalized.objective,
1428
+ engine: output.normalized.engine,
1429
+ verificationPlan: output.normalized.verificationPlan
1430
+ }).catch(() => { });
1431
+ }
1432
+ return createToolSuccessResult(output, output.summary);
1433
+ }
1434
+ if (name === "martin_logs") {
1435
+ const input = validateToolInput("martin_logs", args);
1436
+ const output = await martinLogsTool(input);
1437
+ return createToolSuccessResult(output, `Loaded ${output.logCount} log entries for Martin run ${output.loopId}.`);
1438
+ }
1439
+ if (name === "martin_pause") {
1440
+ const input = validateToolInput("martin_pause", args);
1441
+ const output = await createRunControlReceipt("pause", input);
1442
+ return createToolSuccessResult(output, output.summary);
1443
+ }
1444
+ if (name === "martin_cancel") {
1445
+ const input = validateToolInput("martin_cancel", args);
1446
+ const output = await createRunControlReceipt("cancel", input);
1447
+ return createToolSuccessResult(output, output.summary);
1448
+ }
1449
+ if (name === "martin_continue") {
1450
+ const input = validateToolInput("martin_continue", args);
1451
+ const output = await createRunControlReceipt("continue", input);
1452
+ return createToolSuccessResult(output, output.summary);
1453
+ }
1454
+ if (name === "martin_list_runs") {
1455
+ const input = validateToolInput("martin_list_runs", args);
1456
+ const output = await martinListRunsTool(input);
1457
+ return createToolSuccessResult(output, `Listed ${output.loopCount} Martin run(s) from ${output.runsRoot}.`);
1458
+ }
1459
+ if (name === "martin_triage_runs") {
1460
+ const input = validateToolInput("martin_triage_runs", args);
1461
+ const output = await martinTriageRunsTool(input);
1462
+ return createToolSuccessResult(output, `Triaged ${output.evaluatedRuns} Martin run(s) and found ${output.findingCount} attention item(s).`);
1463
+ }
1464
+ if (name === "martin_get_run") {
1465
+ const input = validateToolInput("martin_get_run", args);
1466
+ const output = await martinGetRunTool(input);
1467
+ return createToolSuccessResult(output, `Loaded Martin run ${output.loop.loopId} from ${output.source}.`);
1468
+ }
1469
+ if (name === "martin_get_attempt") {
1470
+ const input = validateToolInput("martin_get_attempt", args);
1471
+ const output = await martinGetAttemptTool(input);
1472
+ return createToolSuccessResult(output, `Loaded attempt ${output.attempt.index} for Martin run ${output.loop.loopId}.`);
1473
+ }
1474
+ if (name === "martin_get_verification_results") {
1475
+ const input = validateToolInput("martin_get_verification_results", args);
1476
+ const output = await martinGetVerificationResultsTool(input);
1477
+ return createToolSuccessResult(output, `Verification for ${output.loop.loopId} is ${output.verification.status}.`);
1478
+ }
1479
+ if (name === "martin_run_dossier") {
1480
+ const input = validateToolInput("martin_run_dossier", args);
1481
+ const output = await martinRunDossierTool(input);
1482
+ return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} with ${output.attempts.length} attempt(s).`);
1483
+ }
1484
+ if (name === "martin_dossier") {
1485
+ const input = validateToolInput("martin_dossier", args);
1486
+ const output = await martinRunDossierTool(input);
1487
+ return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} in ${output.format} format.`);
1488
+ }
1489
+ if (name === "martin_eval") {
1490
+ const input = validateToolInput("martin_eval", args);
1491
+ const output = await martinEvalTool(input);
1492
+ return createToolSuccessResult(output, `Evaluation for ${output.loopId}: ${output.grade} (${output.score}).`);
1493
+ }
1494
+ if (name === "martin_pr_summary") {
1495
+ const input = validateToolInput("martin_pr_summary", args);
1496
+ const output = await martinPrSummaryTool(input);
1497
+ return createToolSuccessResult(output, `PR summary ready for Martin run ${output.loopId}.`);
1498
+ }
1499
+ if (name === "martin_create_pr") {
1500
+ const input = validateToolInput("martin_create_pr", args);
1501
+ const output = await martinCreatePrTool(input);
1502
+ return createToolSuccessResult(output, output.created
1503
+ ? `Created PR for Martin run ${output.loopId}.`
1504
+ : `PR preview ready for Martin run ${output.loopId}.`);
1505
+ }
1506
+ if (name === "martin_review_pr") {
1507
+ const input = validateToolInput("martin_review_pr", args);
1508
+ const output = await martinReviewPrTool(input);
1509
+ return createToolSuccessResult(output, `PR review verdict for ${output.loopId}: ${output.verdict}.`);
1510
+ }
1511
+ return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
438
1512
  }
439
- if (name === "martin_run_dossier") {
440
- const input = validateToolInput("martin_run_dossier", args);
441
- const output = await runDossierTool(input);
442
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
1513
+ catch (error) {
1514
+ return createToolErrorResult(toToolFailure(error));
443
1515
  }
444
- return {
445
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
446
- isError: true
447
- };
1516
+ });
1517
+ return server;
1518
+ }
1519
+ export async function connectMartinMcpStdioServer() {
1520
+ const server = createMartinMcpServer();
1521
+ const transport = new StdioServerTransport();
1522
+ await server.connect(transport);
1523
+ return server;
1524
+ }
1525
+ export function isDirectExecutionEntry(entryPath, moduleUrl = import.meta.url) {
1526
+ if (typeof entryPath !== "string" || entryPath.length === 0) {
1527
+ return false;
1528
+ }
1529
+ const modulePath = realPathOrResolved(fileURLToPath(moduleUrl));
1530
+ const resolvedEntryPath = realPathOrResolved(entryPath);
1531
+ return modulePath === resolvedEntryPath;
1532
+ }
1533
+ function isDirectExecution() {
1534
+ return isDirectExecutionEntry(process.argv[1]);
1535
+ }
1536
+ function realPathOrResolved(filePath) {
1537
+ try {
1538
+ return realpathSync.native(filePath);
448
1539
  }
449
- catch (error) {
450
- const message = sanitizeToolErrorMessage(error);
451
- return {
452
- content: [{ type: "text", text: `Tool error: ${message}` }],
453
- isError: true
454
- };
1540
+ catch {
1541
+ return path.resolve(filePath);
455
1542
  }
456
- });
457
- // ---------------------------------------------------------------------------
458
- // Start
459
- // ---------------------------------------------------------------------------
460
- const transport = new StdioServerTransport();
461
- await server.connect(transport);
462
- //# sourceMappingURL=server.js.map
1543
+ }
1544
+ if (isDirectExecution()) {
1545
+ await connectMartinMcpStdioServer();
1546
+ }