@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.
- package/README.md +138 -135
- package/dist/discovery-metadata.d.ts +16 -0
- package/dist/discovery-metadata.js +62 -0
- package/dist/discovery-support.d.ts +62 -0
- package/dist/discovery-support.js +224 -0
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +3 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.js +455 -0
- package/dist/resources.d.ts +29 -0
- package/dist/resources.js +575 -0
- package/dist/server-validation.d.ts +2 -3
- package/dist/server-validation.js +295 -87
- package/dist/server.d.ts +76 -7
- package/dist/server.js +1135 -247
- package/dist/tools/doctor.js +14 -6
- package/dist/tools/get-attempt.d.ts +15 -0
- package/dist/tools/get-attempt.js +15 -0
- package/dist/tools/get-run.d.ts +24 -0
- package/dist/tools/get-run.js +23 -0
- package/dist/tools/get-status.d.ts +11 -0
- package/dist/tools/get-status.js +12 -2
- package/dist/tools/get-verification-results.d.ts +14 -0
- package/dist/tools/get-verification-results.js +14 -0
- package/dist/tools/inspect-loop.d.ts +9 -0
- package/dist/tools/inspect-loop.js +11 -2
- package/dist/tools/list-runs.d.ts +29 -0
- package/dist/tools/list-runs.js +24 -0
- package/dist/tools/preflight.js +7 -2
- package/dist/tools/run-dossier.d.ts +41 -0
- package/dist/tools/run-dossier.js +41 -0
- package/dist/tools/run-loop.d.ts +19 -0
- package/dist/tools/run-loop.js +41 -3
- package/dist/tools/run-store.d.ts +57 -3
- package/dist/tools/run-store.js +404 -53
- package/dist/tools/tool-errors.d.ts +37 -0
- package/dist/tools/tool-errors.js +170 -0
- package/dist/tools/tool-response.d.ts +16 -0
- package/dist/tools/tool-response.js +34 -0
- package/dist/tools/tool-support.d.ts +92 -2
- package/dist/tools/tool-support.js +358 -63
- package/dist/tools/triage-runs.d.ts +33 -0
- package/dist/tools/triage-runs.js +138 -0
- package/dist/vendor/adapters/claude-cli.js +0 -1
- package/dist/vendor/adapters/cli-bridge.js +0 -1
- package/dist/vendor/adapters/direct-provider.js +0 -1
- package/dist/vendor/adapters/index.js +0 -1
- package/dist/vendor/adapters/runtime-support.js +0 -1
- package/dist/vendor/adapters/stub-agent-cli.js +0 -1
- package/dist/vendor/adapters/stub-direct-provider.js +0 -1
- package/dist/vendor/adapters/verifier-only.js +0 -1
- package/dist/vendor/contracts/governance.js +0 -1
- package/dist/vendor/contracts/index.d.ts +2 -0
- package/dist/vendor/contracts/index.js +1 -1
- package/dist/vendor/contracts/operator.d.ts +19 -0
- package/dist/vendor/contracts/operator.js +11 -0
- package/dist/vendor/core/compiler.js +0 -1
- package/dist/vendor/core/context-integrity.js +0 -1
- package/dist/vendor/core/grounding.js +0 -1
- package/dist/vendor/core/index.js +1 -2
- package/dist/vendor/core/leash.js +19 -12
- package/dist/vendor/core/persistence/compiler.js +0 -1
- package/dist/vendor/core/persistence/index.js +0 -1
- package/dist/vendor/core/persistence/ledger.js +0 -1
- package/dist/vendor/core/persistence/runs-reader.js +0 -1
- package/dist/vendor/core/persistence/store.js +0 -1
- package/dist/vendor/core/policy.js +0 -1
- package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
- package/dist/vendor/core/red-blue/red-phase.js +135 -0
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
- package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
- package/dist/vendor/core/rollback.js +2 -3
- package/package.json +10 -3
- 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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
inputSchema: {
|
|
131
|
+
highlights: {
|
|
132
|
+
type: "array",
|
|
133
|
+
items: {
|
|
67
134
|
type: "object",
|
|
68
135
|
additionalProperties: false,
|
|
69
136
|
properties: {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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: ["
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
1051
|
+
oneOf: [
|
|
1052
|
+
{ required: ["file"] },
|
|
1053
|
+
{ required: ["loopId"] },
|
|
1054
|
+
{ required: ["latest"] }
|
|
1055
|
+
]
|
|
240
1056
|
},
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
1088
|
+
catch (error) {
|
|
1089
|
+
if (error instanceof MartinToolError) {
|
|
1090
|
+
throw error;
|
|
1091
|
+
}
|
|
1092
|
+
throw new Error(sanitizeToolErrorMessage(error));
|
|
271
1093
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
288
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
const transport = new StdioServerTransport();
|
|
299
|
-
await server.connect(transport);
|
|
300
|
-
//# sourceMappingURL=server.js.map
|
|
1185
|
+
}
|
|
1186
|
+
if (isDirectExecution()) {
|
|
1187
|
+
await connectMartinMcpStdioServer();
|
|
1188
|
+
}
|