@martinloop/mcp 0.1.4 → 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 (74) hide show
  1. package/README.md +138 -135
  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 -0
  9. package/dist/prompts.js +455 -0
  10. package/dist/resources.d.ts +29 -0
  11. package/dist/resources.js +575 -0
  12. package/dist/server-validation.d.ts +2 -3
  13. package/dist/server-validation.js +295 -87
  14. package/dist/server.d.ts +76 -7
  15. package/dist/server.js +1135 -247
  16. package/dist/tools/doctor.js +14 -6
  17. package/dist/tools/get-attempt.d.ts +15 -0
  18. package/dist/tools/get-attempt.js +15 -0
  19. package/dist/tools/get-run.d.ts +24 -0
  20. package/dist/tools/get-run.js +23 -0
  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 +14 -0
  24. package/dist/tools/get-verification-results.js +14 -0
  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 +29 -0
  28. package/dist/tools/list-runs.js +24 -0
  29. package/dist/tools/preflight.js +7 -2
  30. package/dist/tools/run-dossier.d.ts +41 -0
  31. package/dist/tools/run-dossier.js +41 -0
  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 -3
  74. package/server.json +2 -2
package/dist/server.js CHANGED
@@ -2,12 +2,9 @@
2
2
  /**
3
3
  * Martin Loop MCP Server
4
4
  *
5
- * Exposes five tools over the Model Context Protocol (stdio transport):
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,282 +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
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
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";
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";
27
32
  import { getStatusTool } from "./tools/get-status.js";
28
33
  import { inspectLoopTool } from "./tools/inspect-loop.js";
34
+ import { martinListRunsTool } from "./tools/list-runs.js";
29
35
  import { martinPreflightTool } from "./tools/preflight.js";
36
+ import { martinRunDossierTool } from "./tools/run-dossier.js";
37
+ import { martinTriageRunsTool } from "./tools/triage-runs.js";
30
38
  import { runLoopTool } from "./tools/run-loop.js";
39
+ import { createToolErrorResult, createToolSuccessResult } from "./tools/tool-response.js";
40
+ import { MartinToolError, toToolFailure } from "./tools/tool-errors.js";
31
41
  import { sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
32
- const require = createRequire(import.meta.url);
33
- const packageJson = require("../package.json");
34
- const server = new Server({ name: "martin-loop", version: packageJson.version }, { capabilities: { tools: {} } });
35
- // ---------------------------------------------------------------------------
36
- // Tool manifest
37
- // ---------------------------------------------------------------------------
38
- server.setRequestHandler(ListToolsRequestSchema, () => ({
39
- tools: [
40
- {
41
- name: "martin_doctor",
42
- 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.",
43
- inputSchema: {
44
- type: "object",
45
- additionalProperties: false,
46
- properties: {
47
- workingDirectory: {
48
- type: "string",
49
- description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
50
- },
51
- runsDir: {
52
- type: "string",
53
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
54
- },
55
- engine: {
56
- type: "string",
57
- enum: ["claude", "codex"],
58
- description: "Optional engine to emphasize in the readiness report."
59
- }
60
- }
61
- }
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" }
62
130
  },
63
- {
64
- name: "martin_preflight",
65
- description: "Validate and normalize a proposed martin_run contract before execution. Reports the effective budget, path scope, engine readiness, and expected run-store layout.",
66
- inputSchema: {
131
+ highlights: {
132
+ type: "array",
133
+ items: {
67
134
  type: "object",
68
135
  additionalProperties: false,
69
136
  properties: {
70
- objective: {
71
- type: "string",
72
- description: "The coding task to complete. Be specific about what needs to change."
73
- },
74
- workingDirectory: {
75
- type: "string",
76
- description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
77
- },
78
- engine: {
79
- type: "string",
80
- enum: ["claude", "codex"],
81
- description: "Which agent CLI to use. Defaults to 'claude'."
82
- },
83
- model: {
84
- type: "string",
85
- description: "Model override passed to the CLI (e.g. 'claude-opus-4-6', 'o3')."
86
- },
87
- maxUsd: {
88
- type: "number",
89
- exclusiveMinimum: 0,
90
- description: "Hard budget ceiling in USD. Defaults to 25."
91
- },
92
- maxIterations: {
93
- type: "integer",
94
- exclusiveMinimum: 0,
95
- description: "Maximum number of loop attempts. Defaults to 8."
96
- },
97
- maxTokens: {
98
- type: "integer",
99
- exclusiveMinimum: 0,
100
- description: "Maximum total tokens across all attempts. Defaults to 80000."
101
- },
102
- verificationPlan: {
103
- type: "array",
104
- items: { type: "string" },
105
- description: "Shell commands that must all exit 0 for the task to be considered complete (e.g. ['pnpm test', 'pnpm build'])."
106
- },
107
- allowedPaths: {
108
- type: "array",
109
- items: { type: "string" },
110
- description: "Repo-relative path globs Martin may modify, such as ['src/**', 'tests/**']. Absolute paths and '..' traversal are rejected."
111
- },
112
- deniedPaths: {
113
- type: "array",
114
- items: { type: "string" },
115
- description: "Repo-relative path globs Martin must never modify, such as ['.env', 'docs/security/**']. Absolute paths and '..' traversal are rejected."
116
- },
117
- workspaceId: {
118
- type: "string",
119
- description: "Workspace identifier for telemetry. Defaults to 'ws_mcp'."
120
- },
121
- projectId: {
122
- type: "string",
123
- description: "Project identifier for telemetry. Defaults to 'proj_mcp'."
124
- }
137
+ artifactId: { type: "string" },
138
+ kind: { type: "string" },
139
+ label: { type: "string" },
140
+ uri: { type: "string" }
125
141
  },
126
- required: ["objective"]
142
+ required: ["artifactId", "kind", "label", "uri"]
127
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
128
265
  },
129
- {
130
- name: "martin_run",
131
- 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.",
132
- inputSchema: {
133
- type: "object",
134
- additionalProperties: false,
135
- properties: {
136
- objective: {
137
- type: "string",
138
- description: "The coding task to complete. Be specific about what needs to change."
139
- },
140
- workingDirectory: {
141
- type: "string",
142
- description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
143
- },
144
- engine: {
145
- type: "string",
146
- enum: ["claude", "codex"],
147
- description: "Which agent CLI to use. Defaults to 'claude'."
148
- },
149
- model: {
150
- type: "string",
151
- description: "Model override passed to the CLI (e.g. 'claude-opus-4-6', 'o3')."
152
- },
153
- maxUsd: {
154
- type: "number",
155
- exclusiveMinimum: 0,
156
- description: "Hard budget ceiling in USD. Defaults to 25."
157
- },
158
- maxIterations: {
159
- type: "integer",
160
- exclusiveMinimum: 0,
161
- description: "Maximum number of loop attempts. Defaults to 8."
162
- },
163
- maxTokens: {
164
- type: "integer",
165
- exclusiveMinimum: 0,
166
- description: "Maximum total tokens across all attempts. Defaults to 80000."
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" }
167
444
  },
168
- verificationPlan: {
169
- type: "array",
170
- items: { type: "string" },
171
- description: "Shell commands that must all exit 0 for the task to be considered complete (e.g. ['pnpm test', 'pnpm build'])."
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" }
172
456
  },
173
- allowedPaths: {
174
- type: "array",
175
- items: { type: "string" },
176
- description: "Repo-relative path globs Martin may modify, such as ['src/**', 'tests/**']. Absolute paths and '..' traversal are rejected."
457
+ required: ["repoRoot", "allowedPathsCount", "deniedPathsCount", "hasScopeConflicts"]
458
+ },
459
+ expectedRunLayout: {
460
+ type: "object",
461
+ additionalProperties: false,
462
+ properties: {
463
+ runDirectoryPattern: { type: "string" },
464
+ loopRecordPathPattern: { type: "string" }
177
465
  },
178
- deniedPaths: {
179
- type: "array",
180
- items: { type: "string" },
181
- description: "Repo-relative path globs Martin must never modify, such as ['.env', 'docs/security/**']. Absolute paths and '..' traversal are rejected."
466
+ required: ["runDirectoryPattern", "loopRecordPathPattern"]
467
+ }
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
483
+ },
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
+ }
182
726
  },
183
- workspaceId: {
184
- type: "string",
185
- description: "Workspace identifier for telemetry. Defaults to 'ws_mcp'."
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
+ }
750
+ }
751
+ },
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
+ }
186
782
  },
187
- projectId: {
188
- type: "string",
189
- description: "Project identifier for telemetry. Defaults to 'proj_mcp'."
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
+ }
190
816
  }
191
817
  },
192
- required: ["objective"]
193
- }
194
- },
195
- {
196
- name: "martin_inspect",
197
- 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.",
198
- inputSchema: {
199
- type: "object",
200
- additionalProperties: false,
201
- properties: {
202
- file: {
203
- type: "string",
204
- description: "Optional path resolved under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
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" }
205
880
  },
206
- runsDir: {
207
- type: "string",
208
- description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
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
+ }
209
910
  }
210
- }
211
- }
212
- },
213
- {
214
- name: "martin_status",
215
- 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.",
216
- inputSchema: {
217
- type: "object",
218
- additionalProperties: false,
219
- properties: {
220
- loopJson: {
221
- type: "string",
222
- description: "JSON-serialized LoopRecord."
911
+ },
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
+ }
946
+ }
947
+ },
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
+ }
223
971
  },
224
- file: {
225
- type: "string",
226
- description: "Optional path resolved under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
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
+ }
227
1002
  },
228
- loopId: {
229
- type: "string",
230
- description: "Optional Martin loop ID. Loads <runsDir>/<loopId>/loop-record.json."
1003
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }]
1004
+ },
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." }
231
1024
  },
232
- runsDir: {
233
- type: "string",
234
- 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
+ }
235
1050
  },
236
- latest: {
237
- const: true,
238
- description: "When true, loads the most recently updated loop record in the runs directory."
239
- }
1051
+ oneOf: [
1052
+ { required: ["file"] },
1053
+ { required: ["loopId"] },
1054
+ { required: ["latest"] }
1055
+ ]
240
1056
  },
241
- oneOf: [
242
- { required: ["loopJson"] },
243
- { required: ["file"] },
244
- { required: ["loopId"] },
245
- { required: ["latest"] }
246
- ]
1057
+ outputSchema: dossierOutputSchema
247
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 });
248
1070
  }
249
- ]
250
- }));
251
- // ---------------------------------------------------------------------------
252
- // Tool dispatch
253
- // ---------------------------------------------------------------------------
254
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
255
- const { name, arguments: args } = request.params;
256
- try {
257
- if (name === "martin_doctor") {
258
- const input = validateToolInput("martin_doctor", args);
259
- const output = await martinDoctorTool(input);
260
- 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));
261
1076
  }
262
- if (name === "martin_preflight") {
263
- const input = validateToolInput("martin_preflight", args);
264
- const output = await martinPreflightTool(input);
265
- 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
+ });
266
1087
  }
267
- if (name === "martin_run") {
268
- const input = validateToolInput("martin_run", args);
269
- const output = await runLoopTool(input);
270
- 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));
271
1093
  }
272
- if (name === "martin_inspect") {
273
- const input = validateToolInput("martin_inspect", args);
274
- const output = await inspectLoopTool(input);
275
- 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}`)));
276
1154
  }
277
- if (name === "martin_status") {
278
- const input = validateToolInput("martin_status", args);
279
- const output = await getStatusTool(input);
280
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
1155
+ catch (error) {
1156
+ return createToolErrorResult(toToolFailure(error));
281
1157
  }
282
- return {
283
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
284
- isError: true
285
- };
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);
286
1181
  }
287
- catch (error) {
288
- const message = sanitizeToolErrorMessage(error);
289
- return {
290
- content: [{ type: "text", text: `Tool error: ${message}` }],
291
- isError: true
292
- };
1182
+ catch {
1183
+ return path.resolve(filePath);
293
1184
  }
294
- });
295
- // ---------------------------------------------------------------------------
296
- // Start
297
- // ---------------------------------------------------------------------------
298
- const transport = new StdioServerTransport();
299
- await server.connect(transport);
300
- //# sourceMappingURL=server.js.map
1185
+ }
1186
+ if (isDirectExecution()) {
1187
+ await connectMartinMcpStdioServer();
1188
+ }