@martinloop/mcp 0.2.0 → 0.2.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 (76) hide show
  1. package/README.md +131 -158
  2. package/dist/discovery-metadata.d.ts +16 -0
  3. package/dist/discovery-metadata.js +62 -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 +445 -74
  10. package/dist/resources.d.ts +27 -5
  11. package/dist/resources.js +557 -71
  12. package/dist/server-validation.d.ts +2 -3
  13. package/dist/server-validation.js +262 -122
  14. package/dist/server.d.ts +76 -7
  15. package/dist/server.js +1126 -400
  16. package/dist/tools/doctor.js +14 -6
  17. package/dist/tools/get-attempt.d.ts +13 -6
  18. package/dist/tools/get-attempt.js +14 -5
  19. package/dist/tools/get-run.d.ts +19 -12
  20. package/dist/tools/get-run.js +20 -11
  21. package/dist/tools/get-status.d.ts +11 -0
  22. package/dist/tools/get-status.js +12 -2
  23. package/dist/tools/get-verification-results.d.ts +10 -7
  24. package/dist/tools/get-verification-results.js +11 -6
  25. package/dist/tools/inspect-loop.d.ts +9 -0
  26. package/dist/tools/inspect-loop.js +11 -2
  27. package/dist/tools/list-runs.d.ts +25 -5
  28. package/dist/tools/list-runs.js +21 -4
  29. package/dist/tools/preflight.js +7 -2
  30. package/dist/tools/run-dossier.d.ts +37 -4
  31. package/dist/tools/run-dossier.js +40 -5
  32. package/dist/tools/run-loop.d.ts +19 -0
  33. package/dist/tools/run-loop.js +41 -3
  34. package/dist/tools/run-store.d.ts +57 -3
  35. package/dist/tools/run-store.js +404 -53
  36. package/dist/tools/tool-errors.d.ts +37 -0
  37. package/dist/tools/tool-errors.js +170 -0
  38. package/dist/tools/tool-response.d.ts +16 -0
  39. package/dist/tools/tool-response.js +34 -0
  40. package/dist/tools/tool-support.d.ts +92 -2
  41. package/dist/tools/tool-support.js +358 -63
  42. package/dist/tools/triage-runs.d.ts +33 -0
  43. package/dist/tools/triage-runs.js +138 -0
  44. package/dist/vendor/adapters/claude-cli.js +0 -1
  45. package/dist/vendor/adapters/cli-bridge.js +0 -1
  46. package/dist/vendor/adapters/direct-provider.js +0 -1
  47. package/dist/vendor/adapters/index.js +0 -1
  48. package/dist/vendor/adapters/runtime-support.js +0 -1
  49. package/dist/vendor/adapters/stub-agent-cli.js +0 -1
  50. package/dist/vendor/adapters/stub-direct-provider.js +0 -1
  51. package/dist/vendor/adapters/verifier-only.js +0 -1
  52. package/dist/vendor/contracts/governance.js +0 -1
  53. package/dist/vendor/contracts/index.d.ts +2 -0
  54. package/dist/vendor/contracts/index.js +1 -1
  55. package/dist/vendor/contracts/operator.d.ts +19 -0
  56. package/dist/vendor/contracts/operator.js +11 -0
  57. package/dist/vendor/core/compiler.js +0 -1
  58. package/dist/vendor/core/context-integrity.js +0 -1
  59. package/dist/vendor/core/grounding.js +0 -1
  60. package/dist/vendor/core/index.js +1 -2
  61. package/dist/vendor/core/leash.js +19 -12
  62. package/dist/vendor/core/persistence/compiler.js +0 -1
  63. package/dist/vendor/core/persistence/index.js +0 -1
  64. package/dist/vendor/core/persistence/ledger.js +0 -1
  65. package/dist/vendor/core/persistence/runs-reader.js +0 -1
  66. package/dist/vendor/core/persistence/store.js +0 -1
  67. package/dist/vendor/core/policy.js +0 -1
  68. package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
  69. package/dist/vendor/core/red-blue/red-phase.js +135 -0
  70. package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
  71. package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
  72. package/dist/vendor/core/rollback.js +2 -3
  73. package/package.json +10 -5
  74. package/server.json +2 -2
  75. package/dist/tools/cockpit-support.d.ts +0 -69
  76. 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,1173 @@
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";
23
22
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
24
23
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
25
24
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
25
+ import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
26
+ import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
27
+ import { listMartinResources, listMartinResourceTemplates, readMartinResource } from "./resources.js";
26
28
  import { martinDoctorTool } from "./tools/doctor.js";
27
- import { getAttemptTool } from "./tools/get-attempt.js";
28
- import { getRunTool } from "./tools/get-run.js";
29
+ import { martinGetAttemptTool } from "./tools/get-attempt.js";
30
+ import { martinGetRunTool } from "./tools/get-run.js";
31
+ import { martinGetVerificationResultsTool } from "./tools/get-verification-results.js";
29
32
  import { getStatusTool } from "./tools/get-status.js";
30
- import { getVerificationResultsTool } from "./tools/get-verification-results.js";
31
33
  import { inspectLoopTool } from "./tools/inspect-loop.js";
32
- import { listRunsTool } from "./tools/list-runs.js";
34
+ import { martinListRunsTool } from "./tools/list-runs.js";
33
35
  import { martinPreflightTool } from "./tools/preflight.js";
34
- import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
35
- import { listMartinResourceTemplates, listMartinResources, readMartinResource } from "./resources.js";
36
+ import { martinRunDossierTool } from "./tools/run-dossier.js";
37
+ import { martinTriageRunsTool } from "./tools/triage-runs.js";
36
38
  import { runLoopTool } from "./tools/run-loop.js";
37
- import { runDossierTool } from "./tools/run-dossier.js";
39
+ import { createToolErrorResult, createToolSuccessResult } from "./tools/tool-response.js";
40
+ import { MartinToolError, toToolFailure } from "./tools/tool-errors.js";
38
41
  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
- }
42
+ const stringArraySchema = {
43
+ type: "array",
44
+ items: { type: "string" }
45
+ };
46
+ const loopPreviewSchema = {
47
+ type: "object",
48
+ additionalProperties: true,
49
+ properties: {
50
+ loopId: { type: "string" },
51
+ title: { type: "string" },
52
+ objective: { type: "string" },
53
+ status: { type: "string" },
54
+ lifecycleState: { type: "string" },
55
+ createdAt: { type: "string" },
56
+ updatedAt: { type: "string" },
57
+ attempts: { type: "integer" },
58
+ costUsd: { type: "number" },
59
+ avoidedUsd: { type: "number" },
60
+ pressure: { type: "string" },
61
+ shouldStop: { type: "boolean" },
62
+ remainingBudgetUsd: { type: "number" },
63
+ remainingIterations: { type: "integer" },
64
+ remainingTokens: { type: "integer" },
65
+ lastAttempt: {
66
+ type: "object",
67
+ additionalProperties: true
68
+ }
69
+ },
70
+ required: [
71
+ "loopId",
72
+ "title",
73
+ "objective",
74
+ "status",
75
+ "lifecycleState",
76
+ "attempts",
77
+ "costUsd",
78
+ "avoidedUsd",
79
+ "pressure",
80
+ "shouldStop",
81
+ "remainingBudgetUsd",
82
+ "remainingIterations",
83
+ "remainingTokens"
84
+ ]
85
+ };
86
+ const budgetSchema = {
87
+ type: "object",
88
+ additionalProperties: false,
89
+ properties: {
90
+ maxUsd: { type: "number" },
91
+ softLimitUsd: { type: "number" },
92
+ maxIterations: { type: "integer" },
93
+ maxTokens: { type: "integer" }
94
+ },
95
+ required: ["maxUsd", "softLimitUsd", "maxIterations", "maxTokens"]
96
+ };
97
+ const costSchema = {
98
+ type: "object",
99
+ additionalProperties: false,
100
+ properties: {
101
+ actualUsd: { type: "number" },
102
+ avoidedUsd: { type: "number" },
103
+ tokensIn: { type: "integer" },
104
+ tokensOut: { type: "integer" }
105
+ },
106
+ required: ["actualUsd", "avoidedUsd", "tokensIn", "tokensOut"]
107
+ };
108
+ const verificationSchema = {
109
+ type: "object",
110
+ additionalProperties: true,
111
+ properties: {
112
+ status: { type: "string", enum: ["passed", "failed", "unavailable"] },
113
+ eventCount: { type: "integer" },
114
+ ledgerEventCount: { type: "integer" },
115
+ latestAttemptIndex: { type: "integer" },
116
+ completedAt: { type: "string" },
117
+ summary: { type: "string" },
118
+ warnings: stringArraySchema
119
+ },
120
+ required: ["status", "eventCount", "ledgerEventCount", "warnings"]
121
+ };
122
+ const artifactSummarySchema = {
123
+ type: "object",
124
+ additionalProperties: true,
125
+ properties: {
126
+ totalCount: { type: "integer" },
127
+ kinds: {
128
+ type: "object",
129
+ additionalProperties: { type: "integer" }
69
130
  },
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: {
131
+ highlights: {
132
+ type: "array",
133
+ items: {
74
134
  type: "object",
75
135
  additionalProperties: false,
76
136
  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
- }
137
+ artifactId: { type: "string" },
138
+ kind: { type: "string" },
139
+ label: { type: "string" },
140
+ uri: { type: "string" }
132
141
  },
133
- required: ["objective"]
142
+ required: ["artifactId", "kind", "label", "uri"]
134
143
  }
144
+ }
145
+ },
146
+ required: ["totalCount", "kinds", "highlights"]
147
+ };
148
+ const attemptArtifactsSchema = {
149
+ type: "object",
150
+ additionalProperties: false,
151
+ properties: {
152
+ directory: { type: "string" },
153
+ available: { type: "boolean" },
154
+ files: stringArraySchema
155
+ },
156
+ required: ["directory", "available", "files"]
157
+ };
158
+ const attemptSummarySchema = {
159
+ type: "object",
160
+ additionalProperties: true,
161
+ properties: {
162
+ index: { type: "integer" },
163
+ attemptId: { type: "string" },
164
+ adapterId: { type: "string" },
165
+ model: { type: "string" },
166
+ failureClass: { type: "string" },
167
+ intervention: { type: "string" },
168
+ startedAt: { type: "string" },
169
+ completedAt: { type: "string" },
170
+ summary: { type: "string" },
171
+ artifacts: attemptArtifactsSchema,
172
+ artifactFiles: stringArraySchema
173
+ },
174
+ required: ["index"]
175
+ };
176
+ const inspectionPathsSchema = {
177
+ type: "object",
178
+ additionalProperties: true,
179
+ properties: {
180
+ runsRoot: { type: "string" },
181
+ runDirectory: { type: "string" },
182
+ loopRecordPath: { type: "string" },
183
+ ledgerPath: { type: "string" },
184
+ canonicalRunDirectory: { type: "string" },
185
+ canonicalLoopRecordPath: { type: "string" }
186
+ },
187
+ required: ["runsRoot"]
188
+ };
189
+ const eventSummarySchema = {
190
+ type: "object",
191
+ additionalProperties: true,
192
+ properties: {
193
+ type: { type: "string" },
194
+ timestamp: { type: "string" },
195
+ lifecycleState: { type: "string" },
196
+ payload: {
197
+ type: "object",
198
+ additionalProperties: true
199
+ }
200
+ },
201
+ required: ["type", "payload"]
202
+ };
203
+ const runOutputSchema = {
204
+ type: "object",
205
+ additionalProperties: true,
206
+ properties: {
207
+ status: { type: "string" },
208
+ lifecycleState: { type: "string" },
209
+ reason: { type: "string" },
210
+ attempts: { type: "integer" },
211
+ costUsd: { type: "number" },
212
+ verificationPassed: { type: "boolean" },
213
+ loopId: { type: "string" },
214
+ pressure: { type: "string" },
215
+ shouldStop: { type: "boolean" },
216
+ remainingBudgetUsd: { type: "number" },
217
+ remainingIterations: { type: "integer" },
218
+ remainingTokens: { type: "integer" },
219
+ engine: { type: "string" },
220
+ workingDirectory: { type: "string" },
221
+ budget: budgetSchema,
222
+ inspection: {
223
+ type: "object",
224
+ additionalProperties: true,
225
+ properties: {
226
+ runsRoot: { type: "string" },
227
+ runDirectory: { type: "string" },
228
+ loopRecordPath: { type: "string" },
229
+ ledgerPath: { type: "string" },
230
+ loop: loopPreviewSchema,
231
+ verification: verificationSchema,
232
+ artifacts: artifactSummarySchema
233
+ },
234
+ required: ["runsRoot", "runDirectory", "loopRecordPath", "ledgerPath", "loop", "verification", "artifacts"]
235
+ }
236
+ },
237
+ required: [
238
+ "status",
239
+ "lifecycleState",
240
+ "reason",
241
+ "attempts",
242
+ "costUsd",
243
+ "verificationPassed",
244
+ "loopId",
245
+ "pressure",
246
+ "shouldStop",
247
+ "remainingBudgetUsd",
248
+ "remainingIterations",
249
+ "remainingTokens",
250
+ "engine",
251
+ "workingDirectory",
252
+ "budget",
253
+ "inspection"
254
+ ]
255
+ };
256
+ const inspectOutputSchema = {
257
+ type: "object",
258
+ additionalProperties: true,
259
+ properties: {
260
+ source: { type: "string" },
261
+ loopCount: { type: "integer" },
262
+ portfolio: {
263
+ type: "object",
264
+ additionalProperties: true
135
265
  },
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."
150
- },
151
- engine: {
152
- type: "string",
153
- enum: ["claude", "codex"],
154
- description: "Which agent CLI to use. Defaults to 'claude'."
155
- },
156
- model: {
157
- type: "string",
158
- description: "Model override passed to the CLI (e.g. 'claude-opus-4-6', 'o3')."
159
- },
160
- maxUsd: {
161
- type: "number",
162
- exclusiveMinimum: 0,
163
- description: "Hard budget ceiling in USD. Defaults to 25."
164
- },
165
- maxIterations: {
166
- type: "integer",
167
- exclusiveMinimum: 0,
168
- description: "Maximum number of loop attempts. Defaults to 8."
169
- },
170
- maxTokens: {
171
- type: "integer",
172
- exclusiveMinimum: 0,
173
- description: "Maximum total tokens across all attempts. Defaults to 80000."
174
- },
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'])."
179
- },
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."
184
- },
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."
266
+ latestRun: loopPreviewSchema,
267
+ recentRuns: {
268
+ type: "array",
269
+ items: loopPreviewSchema
270
+ },
271
+ statusBreakdown: {
272
+ type: "object",
273
+ additionalProperties: { type: "integer" }
274
+ },
275
+ lifecycleBreakdown: {
276
+ type: "object",
277
+ additionalProperties: { type: "integer" }
278
+ },
279
+ inspection: {
280
+ type: "object",
281
+ additionalProperties: false,
282
+ properties: {
283
+ sourceKind: { type: "string", enum: ["file", "runs_root"] }
284
+ },
285
+ required: ["sourceKind"]
286
+ },
287
+ warnings: stringArraySchema
288
+ },
289
+ required: [
290
+ "source",
291
+ "loopCount",
292
+ "portfolio",
293
+ "recentRuns",
294
+ "statusBreakdown",
295
+ "lifecycleBreakdown",
296
+ "inspection",
297
+ "warnings"
298
+ ]
299
+ };
300
+ const statusOutputSchema = {
301
+ type: "object",
302
+ additionalProperties: true,
303
+ properties: {
304
+ source: { type: "string" },
305
+ loopId: { type: "string" },
306
+ status: { type: "string" },
307
+ lifecycleState: { type: "string" },
308
+ attempts: { type: "integer" },
309
+ costUsd: { type: "number" },
310
+ avoidedUsd: { type: "number" },
311
+ pressure: { type: "string" },
312
+ shouldStop: { type: "boolean" },
313
+ remainingBudgetUsd: { type: "number" },
314
+ remainingIterations: { type: "integer" },
315
+ remainingTokens: { type: "integer" },
316
+ budget: budgetSchema,
317
+ inspection: {
318
+ type: "object",
319
+ additionalProperties: false,
320
+ properties: {
321
+ loop: loopPreviewSchema
322
+ },
323
+ required: ["loop"]
324
+ }
325
+ },
326
+ required: [
327
+ "source",
328
+ "loopId",
329
+ "status",
330
+ "lifecycleState",
331
+ "attempts",
332
+ "costUsd",
333
+ "avoidedUsd",
334
+ "pressure",
335
+ "shouldStop",
336
+ "remainingBudgetUsd",
337
+ "remainingIterations",
338
+ "remainingTokens",
339
+ "budget",
340
+ "inspection"
341
+ ]
342
+ };
343
+ const doctorOutputSchema = {
344
+ type: "object",
345
+ additionalProperties: true,
346
+ properties: {
347
+ status: { type: "string", enum: ["ok", "degraded"] },
348
+ summary: { type: "string" },
349
+ server: {
350
+ type: "object",
351
+ additionalProperties: false,
352
+ properties: {
353
+ name: { type: "string" },
354
+ nodeVersion: { type: "string" },
355
+ platform: { type: "string" }
356
+ },
357
+ required: ["name", "nodeVersion", "platform"]
358
+ },
359
+ environment: {
360
+ type: "object",
361
+ additionalProperties: false,
362
+ properties: {
363
+ workspaceRoot: { type: "string" },
364
+ workingDirectory: { type: "string" },
365
+ runsRoot: { type: "string" },
366
+ mode: { type: "string", enum: ["live", "stub"] },
367
+ liveMode: { type: "boolean" }
368
+ },
369
+ required: ["workspaceRoot", "workingDirectory", "runsRoot", "mode", "liveMode"]
370
+ },
371
+ engines: {
372
+ type: "object",
373
+ additionalProperties: true
374
+ },
375
+ requestedEngine: { type: "string" },
376
+ runStore: {
377
+ type: "object",
378
+ additionalProperties: true,
379
+ properties: {
380
+ exists: { type: "boolean" },
381
+ loopCount: { type: "integer" },
382
+ latestRun: loopPreviewSchema
383
+ },
384
+ required: ["exists", "loopCount"]
385
+ },
386
+ warnings: stringArraySchema
387
+ },
388
+ required: ["status", "summary", "server", "environment", "engines", "runStore", "warnings"]
389
+ };
390
+ const preflightOutputSchema = {
391
+ type: "object",
392
+ additionalProperties: true,
393
+ properties: {
394
+ ok: { type: "boolean" },
395
+ summary: { type: "string" },
396
+ warnings: stringArraySchema,
397
+ readiness: {
398
+ type: "object",
399
+ additionalProperties: false,
400
+ properties: {
401
+ mode: { type: "string", enum: ["live", "stub"] },
402
+ liveMode: { type: "boolean" },
403
+ engineReady: { type: "boolean" }
404
+ },
405
+ required: ["mode", "liveMode", "engineReady"]
406
+ },
407
+ normalized: {
408
+ type: "object",
409
+ additionalProperties: true,
410
+ properties: {
411
+ objective: { type: "string" },
412
+ workingDirectory: { type: "string" },
413
+ engine: { type: "string" },
414
+ model: { type: "string" },
415
+ budget: budgetSchema,
416
+ verificationPlan: stringArraySchema,
417
+ allowedPaths: stringArraySchema,
418
+ deniedPaths: stringArraySchema,
419
+ workspaceId: { type: "string" },
420
+ projectId: { type: "string" }
421
+ },
422
+ required: [
423
+ "objective",
424
+ "workingDirectory",
425
+ "engine",
426
+ "budget",
427
+ "verificationPlan",
428
+ "workspaceId",
429
+ "projectId"
430
+ ]
431
+ },
432
+ execution: {
433
+ type: "object",
434
+ additionalProperties: false,
435
+ properties: {
436
+ requestedEngine: { type: "string" },
437
+ engineAvailability: {
438
+ type: "object",
439
+ additionalProperties: true,
440
+ properties: {
441
+ available: { type: "boolean" },
442
+ detail: { type: "string" },
443
+ resolvedPath: { type: "string" }
189
444
  },
190
- workspaceId: {
191
- type: "string",
192
- description: "Workspace identifier for telemetry. Defaults to 'ws_mcp'."
445
+ required: ["available", "detail"]
446
+ },
447
+ runsRoot: { type: "string" },
448
+ pathScope: {
449
+ type: "object",
450
+ additionalProperties: false,
451
+ properties: {
452
+ repoRoot: { type: "string" },
453
+ allowedPathsCount: { type: "integer" },
454
+ deniedPathsCount: { type: "integer" },
455
+ hasScopeConflicts: { type: "boolean" }
193
456
  },
194
- projectId: {
195
- type: "string",
196
- description: "Project identifier for telemetry. Defaults to 'proj_mcp'."
197
- }
457
+ required: ["repoRoot", "allowedPathsCount", "deniedPathsCount", "hasScopeConflicts"]
198
458
  },
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."
459
+ expectedRunLayout: {
460
+ type: "object",
461
+ additionalProperties: false,
462
+ properties: {
463
+ runDirectoryPattern: { type: "string" },
464
+ loopRecordPathPattern: { type: "string" }
212
465
  },
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."
216
- }
466
+ required: ["runDirectoryPattern", "loopRecordPathPattern"]
217
467
  }
218
- }
468
+ },
469
+ required: ["requestedEngine", "engineAvailability", "runsRoot", "pathScope", "expectedRunLayout"]
470
+ }
471
+ },
472
+ required: ["ok", "summary", "warnings", "readiness", "normalized", "execution"]
473
+ };
474
+ const listRunsOutputSchema = {
475
+ type: "object",
476
+ additionalProperties: true,
477
+ properties: {
478
+ source: { type: "string" },
479
+ runsRoot: { type: "string" },
480
+ filters: {
481
+ type: "object",
482
+ additionalProperties: true
219
483
  },
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."
484
+ loopCount: { type: "integer" },
485
+ latestRun: loopPreviewSchema,
486
+ recentRuns: {
487
+ type: "array",
488
+ items: loopPreviewSchema
489
+ },
490
+ statusBreakdown: {
491
+ type: "object",
492
+ additionalProperties: { type: "integer" }
493
+ },
494
+ lifecycleBreakdown: {
495
+ type: "object",
496
+ additionalProperties: { type: "integer" }
497
+ },
498
+ warnings: stringArraySchema
499
+ },
500
+ required: [
501
+ "source",
502
+ "runsRoot",
503
+ "filters",
504
+ "loopCount",
505
+ "recentRuns",
506
+ "statusBreakdown",
507
+ "lifecycleBreakdown",
508
+ "warnings"
509
+ ]
510
+ };
511
+ const triageFindingSchema = {
512
+ type: "object",
513
+ additionalProperties: true,
514
+ properties: {
515
+ severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
516
+ summary: { type: "string" },
517
+ reasonCodes: stringArraySchema,
518
+ loop: loopPreviewSchema,
519
+ verification: verificationSchema,
520
+ suggestedResources: stringArraySchema,
521
+ suggestedPrompts: stringArraySchema
522
+ },
523
+ required: [
524
+ "severity",
525
+ "summary",
526
+ "reasonCodes",
527
+ "loop",
528
+ "verification",
529
+ "suggestedResources",
530
+ "suggestedPrompts"
531
+ ]
532
+ };
533
+ const triageRunsOutputSchema = {
534
+ type: "object",
535
+ additionalProperties: true,
536
+ properties: {
537
+ source: { type: "string" },
538
+ runsRoot: { type: "string" },
539
+ filters: {
540
+ type: "object",
541
+ additionalProperties: true
542
+ },
543
+ evaluatedRuns: { type: "integer" },
544
+ findingCount: { type: "integer" },
545
+ severityBreakdown: {
546
+ type: "object",
547
+ additionalProperties: { type: "integer" }
548
+ },
549
+ findings: {
550
+ type: "array",
551
+ items: triageFindingSchema
552
+ },
553
+ warnings: stringArraySchema
554
+ },
555
+ required: [
556
+ "source",
557
+ "runsRoot",
558
+ "filters",
559
+ "evaluatedRuns",
560
+ "findingCount",
561
+ "severityBreakdown",
562
+ "findings",
563
+ "warnings"
564
+ ]
565
+ };
566
+ const getRunOutputSchema = {
567
+ type: "object",
568
+ additionalProperties: true,
569
+ properties: {
570
+ source: { type: "string" },
571
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
572
+ loop: loopPreviewSchema,
573
+ budget: budgetSchema,
574
+ cost: costSchema,
575
+ verification: verificationSchema,
576
+ artifacts: artifactSummarySchema,
577
+ inspection: inspectionPathsSchema,
578
+ warnings: stringArraySchema
579
+ },
580
+ required: ["source", "sourceKind", "loop", "budget", "cost", "verification", "artifacts", "inspection", "warnings"]
581
+ };
582
+ const getAttemptOutputSchema = {
583
+ type: "object",
584
+ additionalProperties: true,
585
+ properties: {
586
+ source: { type: "string" },
587
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
588
+ loop: loopPreviewSchema,
589
+ attempt: attemptSummarySchema,
590
+ warnings: stringArraySchema
591
+ },
592
+ required: ["source", "sourceKind", "loop", "attempt", "warnings"]
593
+ };
594
+ const verificationResultsOutputSchema = {
595
+ type: "object",
596
+ additionalProperties: true,
597
+ properties: {
598
+ source: { type: "string" },
599
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
600
+ loop: loopPreviewSchema,
601
+ verification: verificationSchema,
602
+ warnings: stringArraySchema
603
+ },
604
+ required: ["source", "sourceKind", "loop", "verification", "warnings"]
605
+ };
606
+ const dossierOutputSchema = {
607
+ type: "object",
608
+ additionalProperties: true,
609
+ properties: {
610
+ source: { type: "string" },
611
+ sourceKind: { type: "string", enum: ["file", "loop_id", "latest", "runs_root"] },
612
+ loop: loopPreviewSchema,
613
+ budget: budgetSchema,
614
+ cost: costSchema,
615
+ attempts: {
616
+ type: "array",
617
+ items: attemptSummarySchema
618
+ },
619
+ verification: verificationSchema,
620
+ artifacts: artifactSummarySchema,
621
+ recentEvents: {
622
+ type: "array",
623
+ items: eventSummarySchema
624
+ },
625
+ related: {
626
+ type: "object",
627
+ additionalProperties: false,
628
+ properties: {
629
+ resources: stringArraySchema,
630
+ prompts: stringArraySchema
631
+ },
632
+ required: ["resources", "prompts"]
633
+ },
634
+ inspection: inspectionPathsSchema,
635
+ warnings: stringArraySchema
636
+ },
637
+ required: [
638
+ "source",
639
+ "sourceKind",
640
+ "loop",
641
+ "budget",
642
+ "cost",
643
+ "attempts",
644
+ "verification",
645
+ "artifacts",
646
+ "recentEvents",
647
+ "related",
648
+ "inspection",
649
+ "warnings"
650
+ ]
651
+ };
652
+ export function createMartinMcpServer(serverInfo) {
653
+ const server = new Server({
654
+ name: serverInfo?.name ?? "martin-loop",
655
+ version: serverInfo?.version ?? MARTIN_MCP_PACKAGE_VERSION
656
+ }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
657
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
658
+ tools: [
659
+ {
660
+ name: "martin_run",
661
+ description: "Execute a governed Martin Loop run on a coding task and return the run summary, spend, artifact rollup, and verification state.",
662
+ annotations: {
663
+ destructiveHint: true,
664
+ idempotentHint: false,
665
+ openWorldHint: false
666
+ },
667
+ inputSchema: {
668
+ type: "object",
669
+ additionalProperties: false,
670
+ properties: {
671
+ objective: {
672
+ type: "string",
673
+ description: "The coding task to complete. Be specific about what needs to change."
674
+ },
675
+ workingDirectory: {
676
+ type: "string",
677
+ description: "Optional repo-root override resolved under the MCP workspace root. Must stay within that safe root."
678
+ },
679
+ engine: {
680
+ type: "string",
681
+ enum: ["claude", "codex"],
682
+ description: "Which agent CLI to use. Defaults to claude."
683
+ },
684
+ model: {
685
+ type: "string",
686
+ description: "Optional model override passed to the CLI."
687
+ },
688
+ maxUsd: {
689
+ type: "number",
690
+ exclusiveMinimum: 0,
691
+ description: "Hard budget ceiling in USD."
692
+ },
693
+ maxIterations: {
694
+ type: "integer",
695
+ exclusiveMinimum: 0,
696
+ description: "Maximum number of loop attempts."
697
+ },
698
+ maxTokens: {
699
+ type: "integer",
700
+ exclusiveMinimum: 0,
701
+ description: "Maximum total tokens across all attempts."
702
+ },
703
+ verificationPlan: {
704
+ type: "array",
705
+ items: { type: "string" },
706
+ description: "Commands that must all exit 0 for the task to be considered complete."
707
+ },
708
+ allowedPaths: {
709
+ type: "array",
710
+ items: { type: "string" },
711
+ description: "Repo-relative path globs Martin may modify."
712
+ },
713
+ deniedPaths: {
714
+ type: "array",
715
+ items: { type: "string" },
716
+ description: "Repo-relative path globs Martin must never modify."
717
+ },
718
+ workspaceId: {
719
+ type: "string",
720
+ description: "Workspace identifier for telemetry."
721
+ },
722
+ projectId: {
723
+ type: "string",
724
+ description: "Project identifier for telemetry."
725
+ }
242
726
  },
243
- latest: {
244
- const: true,
245
- description: "When true, loads the most recently updated loop record in the runs directory."
727
+ required: ["objective"]
728
+ },
729
+ outputSchema: runOutputSchema
730
+ },
731
+ {
732
+ name: "martin_inspect",
733
+ description: "Summarise Martin Loop run records from a saved loop file or run-store directory.",
734
+ annotations: {
735
+ readOnlyHint: true,
736
+ idempotentHint: true
737
+ },
738
+ inputSchema: {
739
+ type: "object",
740
+ additionalProperties: false,
741
+ properties: {
742
+ file: {
743
+ type: "string",
744
+ description: "Optional path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
745
+ },
746
+ runsDir: {
747
+ type: "string",
748
+ description: "Optional runs-root override resolved under the default Martin runs root."
749
+ }
246
750
  }
247
751
  },
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."
752
+ outputSchema: inspectOutputSchema
753
+ },
754
+ {
755
+ name: "martin_status",
756
+ description: "Return the current budget and cost state of a Martin loop record.",
757
+ annotations: {
758
+ readOnlyHint: true,
759
+ idempotentHint: true
760
+ },
761
+ inputSchema: {
762
+ type: "object",
763
+ additionalProperties: false,
764
+ properties: {
765
+ loopJson: { type: "string", description: "JSON-serialized LoopRecord." },
766
+ file: {
767
+ type: "string",
768
+ description: "Optional path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
769
+ },
770
+ loopId: {
771
+ type: "string",
772
+ description: "Loop ID resolved as <runsDir>/<loopId>/loop-record.json."
773
+ },
774
+ runsDir: {
775
+ type: "string",
776
+ description: "Optional runs-root override resolved under the default Martin runs root."
777
+ },
778
+ latest: {
779
+ const: true,
780
+ description: "When true, loads the most recently updated loop record in the runs directory."
781
+ }
266
782
  },
267
- limit: {
268
- type: "integer",
269
- exclusiveMinimum: 0,
270
- description: "Maximum number of runs to return. Defaults to 20."
783
+ oneOf: [
784
+ { required: ["loopJson"] },
785
+ { required: ["file"] },
786
+ { required: ["loopId"] },
787
+ { required: ["latest"] }
788
+ ]
789
+ },
790
+ outputSchema: statusOutputSchema
791
+ },
792
+ {
793
+ name: "martin_doctor",
794
+ description: "Read-only environment and run-store diagnostics for the Martin MCP server.",
795
+ annotations: {
796
+ readOnlyHint: true,
797
+ idempotentHint: true
798
+ },
799
+ inputSchema: {
800
+ type: "object",
801
+ additionalProperties: false,
802
+ properties: {
803
+ workingDirectory: {
804
+ type: "string",
805
+ description: "Optional repo-root override for doctor context."
806
+ },
807
+ runsDir: {
808
+ type: "string",
809
+ description: "Optional runs-root override resolved under the default Martin runs root."
810
+ },
811
+ engine: {
812
+ type: "string",
813
+ enum: ["claude", "codex"],
814
+ description: "Optional engine to highlight in diagnostics."
815
+ }
271
816
  }
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."
285
- },
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."
817
+ },
818
+ outputSchema: doctorOutputSchema
819
+ },
820
+ {
821
+ name: "martin_preflight",
822
+ description: "Read-only validation of a planned Martin run before any execution or spend.",
823
+ annotations: {
824
+ readOnlyHint: true,
825
+ idempotentHint: true
826
+ },
827
+ inputSchema: {
828
+ type: "object",
829
+ additionalProperties: false,
830
+ properties: {
831
+ objective: {
832
+ type: "string",
833
+ description: "The coding task to validate."
834
+ },
835
+ workingDirectory: {
836
+ type: "string",
837
+ description: "Optional repo-root override resolved under the MCP workspace root."
838
+ },
839
+ engine: {
840
+ type: "string",
841
+ enum: ["claude", "codex"],
842
+ description: "Which agent CLI would be used. Defaults to claude."
843
+ },
844
+ model: {
845
+ type: "string",
846
+ description: "Model override passed to the CLI."
847
+ },
848
+ maxUsd: {
849
+ type: "number",
850
+ exclusiveMinimum: 0,
851
+ description: "Hard budget ceiling in USD."
852
+ },
853
+ maxIterations: {
854
+ type: "integer",
855
+ exclusiveMinimum: 0,
856
+ description: "Maximum number of loop attempts."
857
+ },
858
+ maxTokens: {
859
+ type: "integer",
860
+ exclusiveMinimum: 0,
861
+ description: "Maximum total tokens across all attempts."
862
+ },
863
+ verificationPlan: {
864
+ type: "array",
865
+ items: { type: "string" },
866
+ description: "Commands that must all exit 0 for completion."
867
+ },
868
+ allowedPaths: {
869
+ type: "array",
870
+ items: { type: "string" },
871
+ description: "Repo-relative path globs Martin may modify."
872
+ },
873
+ deniedPaths: {
874
+ type: "array",
875
+ items: { type: "string" },
876
+ description: "Repo-relative path globs Martin must never modify."
877
+ },
878
+ workspaceId: { type: "string" },
879
+ projectId: { type: "string" }
289
880
  },
290
- latest: {
291
- const: true,
292
- description: "When true, loads the most recently updated loop record in the runs directory."
881
+ required: ["objective"]
882
+ },
883
+ outputSchema: preflightOutputSchema
884
+ },
885
+ {
886
+ name: "martin_list_runs",
887
+ description: "List recent Martin runs from the run store with lightweight filters for status, lifecycle, engine metadata, and recency.",
888
+ annotations: {
889
+ readOnlyHint: true,
890
+ idempotentHint: true
891
+ },
892
+ inputSchema: {
893
+ type: "object",
894
+ additionalProperties: false,
895
+ properties: {
896
+ runsDir: { type: "string", description: "Optional runs-root override." },
897
+ limit: {
898
+ type: "integer",
899
+ minimum: 1,
900
+ description: "Maximum number of runs to return. Defaults to 20."
901
+ },
902
+ status: { type: "string", description: "Filter by loop status." },
903
+ lifecycleState: { type: "string", description: "Filter by lifecycle state." },
904
+ adapterId: { type: "string", description: "Filter by attempt adapter ID." },
905
+ model: { type: "string", description: "Filter by attempt model." },
906
+ updatedAfter: {
907
+ type: "string",
908
+ description: "Optional ISO-8601 timestamp for recency filtering."
909
+ }
293
910
  }
294
911
  },
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."
308
- },
309
- attemptIndex: {
310
- type: "integer",
311
- exclusiveMinimum: 0,
312
- description: "1-based attempt index to inspect."
313
- },
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."
912
+ outputSchema: listRunsOutputSchema
913
+ },
914
+ {
915
+ name: "martin_triage_runs",
916
+ description: "Prioritize Martin runs that need operator or agent attention based on verification, lifecycle, and budget pressure.",
917
+ annotations: {
918
+ readOnlyHint: true,
919
+ idempotentHint: true
920
+ },
921
+ inputSchema: {
922
+ type: "object",
923
+ additionalProperties: false,
924
+ properties: {
925
+ runsDir: { type: "string", description: "Optional runs-root override." },
926
+ limit: {
927
+ type: "integer",
928
+ minimum: 1,
929
+ description: "Maximum number of runs to triage. Defaults to 20."
930
+ },
931
+ status: { type: "string", description: "Filter by loop status." },
932
+ lifecycleState: {
933
+ type: "string",
934
+ description: "Filter by lifecycle state."
935
+ },
936
+ adapterId: { type: "string", description: "Filter by attempt adapter ID." },
937
+ model: { type: "string", description: "Filter by attempt model." },
938
+ updatedAfter: {
939
+ type: "string",
940
+ description: "Optional ISO-8601 timestamp for recency filtering."
941
+ },
942
+ includeHealthy: {
943
+ type: "boolean",
944
+ description: "When true, include healthy runs instead of only attention-worthy findings."
945
+ }
317
946
  }
318
947
  },
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."
948
+ outputSchema: triageRunsOutputSchema
949
+ },
950
+ {
951
+ name: "martin_get_run",
952
+ description: "Load one Martin run and return its budget, cost, verification, artifact, and canonical path summary.",
953
+ annotations: {
954
+ readOnlyHint: true,
955
+ idempotentHint: true
956
+ },
957
+ inputSchema: {
958
+ type: "object",
959
+ additionalProperties: false,
960
+ properties: {
961
+ file: {
962
+ type: "string",
963
+ description: "Path to a canonical loop-record.json, legacy file, or run-store directory."
964
+ },
965
+ loopId: { type: "string", description: "Loop ID under the run store." },
966
+ runsDir: { type: "string", description: "Optional runs-root override." },
967
+ latest: {
968
+ const: true,
969
+ description: "When true, loads the most recently updated loop record in the run store."
970
+ }
332
971
  },
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."
972
+ oneOf: [
973
+ { required: ["file"] },
974
+ { required: ["loopId"] },
975
+ { required: ["latest"] }
976
+ ]
977
+ },
978
+ outputSchema: getRunOutputSchema
979
+ },
980
+ {
981
+ name: "martin_get_attempt",
982
+ description: "Load one Martin attempt summary with artifact directory references for a canonical run.",
983
+ annotations: {
984
+ readOnlyHint: true,
985
+ idempotentHint: true
986
+ },
987
+ inputSchema: {
988
+ type: "object",
989
+ additionalProperties: false,
990
+ properties: {
991
+ file: {
992
+ type: "string",
993
+ description: "Path to a canonical loop-record.json file or run directory."
994
+ },
995
+ loopId: { type: "string", description: "Loop ID under the run store." },
996
+ runsDir: { type: "string", description: "Optional runs-root override." },
997
+ attemptIndex: {
998
+ type: "integer",
999
+ minimum: 1,
1000
+ description: "Attempt index to inspect. Defaults to the latest attempt."
1001
+ }
336
1002
  },
337
- latest: {
338
- const: true,
339
- description: "When true, loads the most recently updated loop record in the runs directory."
340
- }
1003
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }]
341
1004
  },
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."
1005
+ outputSchema: getAttemptOutputSchema
1006
+ },
1007
+ {
1008
+ name: "martin_get_verification_results",
1009
+ description: "Load verification evidence for a Martin run from stored loop events and ledger entries.",
1010
+ annotations: {
1011
+ readOnlyHint: true,
1012
+ idempotentHint: true
1013
+ },
1014
+ inputSchema: {
1015
+ type: "object",
1016
+ additionalProperties: false,
1017
+ properties: {
1018
+ file: {
1019
+ type: "string",
1020
+ description: "Path to a canonical loop-record.json file or run directory."
1021
+ },
1022
+ loopId: { type: "string", description: "Loop ID under the run store." },
1023
+ runsDir: { type: "string", description: "Optional runs-root override." }
355
1024
  },
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."
1025
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }]
1026
+ },
1027
+ outputSchema: verificationResultsOutputSchema
1028
+ },
1029
+ {
1030
+ name: "martin_run_dossier",
1031
+ description: "Return the full governed execution dossier for one Martin run, including attempts, events, artifacts, and related discovery surfaces.",
1032
+ annotations: {
1033
+ readOnlyHint: true,
1034
+ idempotentHint: true
1035
+ },
1036
+ inputSchema: {
1037
+ type: "object",
1038
+ additionalProperties: false,
1039
+ properties: {
1040
+ file: {
1041
+ type: "string",
1042
+ description: "Path to a canonical loop-record.json, legacy file, or run-store directory."
1043
+ },
1044
+ loopId: { type: "string", description: "Loop ID under the run store." },
1045
+ runsDir: { type: "string", description: "Optional runs-root override." },
1046
+ latest: {
1047
+ const: true,
1048
+ description: "When true, loads the most recently updated loop record in the run store."
1049
+ }
359
1050
  },
360
- latest: {
361
- const: true,
362
- description: "When true, loads the most recently updated loop record in the runs directory."
363
- }
1051
+ oneOf: [
1052
+ { required: ["file"] },
1053
+ { required: ["loopId"] },
1054
+ { required: ["latest"] }
1055
+ ]
364
1056
  },
365
- oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
1057
+ outputSchema: dossierOutputSchema
366
1058
  }
1059
+ ]
1060
+ }));
1061
+ server.setRequestHandler(ListResourcesRequestSchema, () => ({
1062
+ ...listMartinResources()
1063
+ }));
1064
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
1065
+ ...listMartinResourceTemplates()
1066
+ }));
1067
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1068
+ try {
1069
+ return await readMartinResource({ uri: request.params.uri });
367
1070
  }
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) }] };
1071
+ catch (error) {
1072
+ if (error instanceof MartinToolError) {
1073
+ throw error;
1074
+ }
1075
+ throw new Error(sanitizeToolErrorMessage(error));
423
1076
  }
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) }] };
1077
+ });
1078
+ server.setRequestHandler(ListPromptsRequestSchema, () => ({
1079
+ ...listMartinPrompts()
1080
+ }));
1081
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1082
+ try {
1083
+ return await getMartinPrompt({
1084
+ name: request.params.name,
1085
+ arguments: request.params.arguments
1086
+ });
428
1087
  }
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) }] };
1088
+ catch (error) {
1089
+ if (error instanceof MartinToolError) {
1090
+ throw error;
1091
+ }
1092
+ throw new Error(sanitizeToolErrorMessage(error));
433
1093
  }
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) }] };
1094
+ });
1095
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1096
+ const { name, arguments: args } = request.params;
1097
+ try {
1098
+ if (name === "martin_run") {
1099
+ const input = validateToolInput("martin_run", args);
1100
+ const output = await runLoopTool(input);
1101
+ return createToolSuccessResult(output, `Run ${output.loopId} is ${output.status}/${output.lifecycleState} after ${output.attempts} attempt(s); spend ${output.costUsd.toFixed(2)} USD.`);
1102
+ }
1103
+ if (name === "martin_inspect") {
1104
+ const input = validateToolInput("martin_inspect", args);
1105
+ const output = await inspectLoopTool(input);
1106
+ return createToolSuccessResult(output, `Inspected ${output.loopCount} run(s) from ${output.source}; total actual spend ${output.portfolio.totalActualUsd.toFixed(2)} USD.`);
1107
+ }
1108
+ if (name === "martin_status") {
1109
+ const input = validateToolInput("martin_status", args);
1110
+ const output = await getStatusTool(input);
1111
+ return createToolSuccessResult(output, `Loop ${output.loopId} is ${output.status}/${output.lifecycleState}; pressure is ${output.pressure} with ${output.remainingBudgetUsd.toFixed(2)} USD remaining.`);
1112
+ }
1113
+ if (name === "martin_doctor") {
1114
+ const input = validateToolInput("martin_doctor", args);
1115
+ const output = await martinDoctorTool(input);
1116
+ return createToolSuccessResult(output, output.summary);
1117
+ }
1118
+ if (name === "martin_preflight") {
1119
+ const input = validateToolInput("martin_preflight", args);
1120
+ const output = await martinPreflightTool(input);
1121
+ return createToolSuccessResult(output, output.summary);
1122
+ }
1123
+ if (name === "martin_list_runs") {
1124
+ const input = validateToolInput("martin_list_runs", args);
1125
+ const output = await martinListRunsTool(input);
1126
+ return createToolSuccessResult(output, `Listed ${output.loopCount} Martin run(s) from ${output.runsRoot}.`);
1127
+ }
1128
+ if (name === "martin_triage_runs") {
1129
+ const input = validateToolInput("martin_triage_runs", args);
1130
+ const output = await martinTriageRunsTool(input);
1131
+ return createToolSuccessResult(output, `Triaged ${output.evaluatedRuns} Martin run(s) and found ${output.findingCount} attention item(s).`);
1132
+ }
1133
+ if (name === "martin_get_run") {
1134
+ const input = validateToolInput("martin_get_run", args);
1135
+ const output = await martinGetRunTool(input);
1136
+ return createToolSuccessResult(output, `Loaded Martin run ${output.loop.loopId} from ${output.source}.`);
1137
+ }
1138
+ if (name === "martin_get_attempt") {
1139
+ const input = validateToolInput("martin_get_attempt", args);
1140
+ const output = await martinGetAttemptTool(input);
1141
+ return createToolSuccessResult(output, `Loaded attempt ${output.attempt.index} for Martin run ${output.loop.loopId}.`);
1142
+ }
1143
+ if (name === "martin_get_verification_results") {
1144
+ const input = validateToolInput("martin_get_verification_results", args);
1145
+ const output = await martinGetVerificationResultsTool(input);
1146
+ return createToolSuccessResult(output, `Verification for ${output.loop.loopId} is ${output.verification.status}.`);
1147
+ }
1148
+ if (name === "martin_run_dossier") {
1149
+ const input = validateToolInput("martin_run_dossier", args);
1150
+ const output = await martinRunDossierTool(input);
1151
+ return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} with ${output.attempts.length} attempt(s).`);
1152
+ }
1153
+ return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
438
1154
  }
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) }] };
1155
+ catch (error) {
1156
+ return createToolErrorResult(toToolFailure(error));
443
1157
  }
444
- return {
445
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
446
- isError: true
447
- };
1158
+ });
1159
+ return server;
1160
+ }
1161
+ export async function connectMartinMcpStdioServer() {
1162
+ const server = createMartinMcpServer();
1163
+ const transport = new StdioServerTransport();
1164
+ await server.connect(transport);
1165
+ return server;
1166
+ }
1167
+ export function isDirectExecutionEntry(entryPath, moduleUrl = import.meta.url) {
1168
+ if (typeof entryPath !== "string" || entryPath.length === 0) {
1169
+ return false;
1170
+ }
1171
+ const modulePath = realPathOrResolved(fileURLToPath(moduleUrl));
1172
+ const resolvedEntryPath = realPathOrResolved(entryPath);
1173
+ return modulePath === resolvedEntryPath;
1174
+ }
1175
+ function isDirectExecution() {
1176
+ return isDirectExecutionEntry(process.argv[1]);
1177
+ }
1178
+ function realPathOrResolved(filePath) {
1179
+ try {
1180
+ return realpathSync.native(filePath);
448
1181
  }
449
- catch (error) {
450
- const message = sanitizeToolErrorMessage(error);
451
- return {
452
- content: [{ type: "text", text: `Tool error: ${message}` }],
453
- isError: true
454
- };
1182
+ catch {
1183
+ return path.resolve(filePath);
455
1184
  }
456
- });
457
- // ---------------------------------------------------------------------------
458
- // Start
459
- // ---------------------------------------------------------------------------
460
- const transport = new StdioServerTransport();
461
- await server.connect(transport);
462
- //# sourceMappingURL=server.js.map
1185
+ }
1186
+ if (isDirectExecution()) {
1187
+ await connectMartinMcpStdioServer();
1188
+ }