@martinloop/mcp 0.1.4 → 0.2.0
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 +32 -2
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.js +84 -0
- package/dist/resources.d.ts +7 -0
- package/dist/resources.js +89 -0
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +69 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +165 -3
- package/dist/tools/cockpit-support.d.ts +69 -0
- package/dist/tools/cockpit-support.js +108 -0
- package/dist/tools/get-attempt.d.ts +8 -0
- package/dist/tools/get-attempt.js +6 -0
- package/dist/tools/get-run.d.ts +17 -0
- package/dist/tools/get-run.js +14 -0
- package/dist/tools/get-verification-results.d.ts +11 -0
- package/dist/tools/get-verification-results.js +9 -0
- package/dist/tools/list-runs.d.ts +9 -0
- package/dist/tools/list-runs.js +7 -0
- package/dist/tools/run-dossier.d.ts +8 -0
- package/dist/tools/run-dossier.js +6 -0
- package/package.json +4 -2
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -2,20 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Governed MCP server for AI coding agents that need hard spend limits, verifier gates, scoped file edits, and inspectable run records.
|
|
4
4
|
|
|
5
|
-
`@martinloop/mcp@0.
|
|
5
|
+
`@martinloop/mcp@0.2.0` exposes ten stdio tools plus read-only MCP resources, resource templates, and prompts:
|
|
6
6
|
|
|
7
7
|
- `martin_doctor`
|
|
8
8
|
- `martin_preflight`
|
|
9
9
|
- `martin_run`
|
|
10
10
|
- `martin_inspect`
|
|
11
11
|
- `martin_status`
|
|
12
|
+
- `martin_list_runs`
|
|
13
|
+
- `martin_get_run`
|
|
14
|
+
- `martin_get_attempt`
|
|
15
|
+
- `martin_get_verification_results`
|
|
16
|
+
- `martin_run_dossier`
|
|
12
17
|
|
|
13
18
|
Recommended flow:
|
|
14
19
|
|
|
15
20
|
1. `martin_doctor`
|
|
16
21
|
2. `martin_preflight`
|
|
17
22
|
3. `martin_run`
|
|
18
|
-
4. `
|
|
23
|
+
4. `martin_list_runs`, `martin_run_dossier`, `martin_inspect`, or `martin_status`
|
|
19
24
|
|
|
20
25
|
## What This Server Is For
|
|
21
26
|
|
|
@@ -95,6 +100,31 @@ MARTIN_LIVE=false npx -y @martinloop/mcp
|
|
|
95
100
|
| `martin_run` | Run a governed coding loop | `objective` | `workingDirectory`, `engine`, `model`, `maxUsd`, `maxIterations`, `maxTokens`, `verificationPlan`, `allowedPaths`, `deniedPaths`, `workspaceId`, `projectId` | Unknown arguments are rejected. |
|
|
96
101
|
| `martin_inspect` | Read a saved run record or run folder | none | `file`, `runsDir` | `file` may point to a `loop-record.json`, legacy `.jsonl`, or a run directory under the runs root. |
|
|
97
102
|
| `martin_status` | Report budget pressure and stop conditions | exactly one of `loopJson`, `file`, `loopId`, or `latest` | `runsDir` | `latest` must be `true` when used. |
|
|
103
|
+
| `martin_list_runs` | List recent run summaries | none | `runsDir`, `limit` | Read-only cockpit view over local run records. |
|
|
104
|
+
| `martin_get_run` | Load a run dossier | exactly one of `loopId` or `latest` | `runsDir` | Read-only task, budget, cost, and attempt details. |
|
|
105
|
+
| `martin_get_attempt` | Load one attempt | `loopId`, `attemptIndex` | `runsDir` | Read-only attempt evidence. |
|
|
106
|
+
| `martin_get_verification_results` | Extract verifier events | exactly one of `loopId` or `latest` | `runsDir` | Read-only verifier completion summaries. |
|
|
107
|
+
| `martin_run_dossier` | Build a compact review dossier | exactly one of `loopId` or `latest` | `runsDir` | Summary, budget, attempts, and verification evidence. |
|
|
108
|
+
|
|
109
|
+
## Discovery Surface
|
|
110
|
+
|
|
111
|
+
`0.2.0` adds read-only cockpit discovery for MCP hosts that support resources and prompts.
|
|
112
|
+
|
|
113
|
+
Resources:
|
|
114
|
+
|
|
115
|
+
- `martin://runs/summary`
|
|
116
|
+
- `martin://runs/latest`
|
|
117
|
+
|
|
118
|
+
Resource templates:
|
|
119
|
+
|
|
120
|
+
- `martin://runs/{loopId}`
|
|
121
|
+
- `martin://runs/{loopId}/attempts/{attemptIndex}`
|
|
122
|
+
- `martin://runs/{loopId}/verification`
|
|
123
|
+
|
|
124
|
+
Prompts:
|
|
125
|
+
|
|
126
|
+
- `martin_review_run`
|
|
127
|
+
- `martin_triage_failures`
|
|
98
128
|
|
|
99
129
|
## Safe-Root Path Model
|
|
100
130
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { GetPromptResult, ListPromptsResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export declare function listMartinPrompts(): ListPromptsResult["prompts"];
|
|
3
|
+
export declare function getMartinPrompt(name: string, args?: Record<string, unknown>): GetPromptResult;
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function listMartinPrompts() {
|
|
2
|
+
return [
|
|
3
|
+
{
|
|
4
|
+
name: "martin_review_run",
|
|
5
|
+
description: "Review a Martin Loop run for governance, verification, and release-readiness evidence.",
|
|
6
|
+
arguments: [
|
|
7
|
+
{
|
|
8
|
+
name: "loopId",
|
|
9
|
+
description: "Run loopId to review.",
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "objective",
|
|
14
|
+
description: "Review objective or release question.",
|
|
15
|
+
required: false
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "martin_triage_failures",
|
|
21
|
+
description: "Triage failed Martin runs and propose the next safest bounded action.",
|
|
22
|
+
arguments: [
|
|
23
|
+
{
|
|
24
|
+
name: "loopId",
|
|
25
|
+
description: "Optional run loopId to triage. If omitted, use latest run resources.",
|
|
26
|
+
required: false
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
export function getMartinPrompt(name, args = {}) {
|
|
33
|
+
if (name === "martin_review_run") {
|
|
34
|
+
const loopId = requireOptionalText(args.loopId, "loopId") ?? "latest";
|
|
35
|
+
const objective = requireOptionalText(args.objective, "objective") ?? "Assess whether the run is safe to ship.";
|
|
36
|
+
return {
|
|
37
|
+
description: "Review Martin Loop run evidence.",
|
|
38
|
+
messages: [
|
|
39
|
+
{
|
|
40
|
+
role: "user",
|
|
41
|
+
content: {
|
|
42
|
+
type: "text",
|
|
43
|
+
text: [
|
|
44
|
+
`Review Martin Loop run ${loopId}.`,
|
|
45
|
+
`Objective: ${objective}`,
|
|
46
|
+
"Use martin://runs/{loopId}, martin://runs/{loopId}/verification, and the read-only tools before making a shipping recommendation.",
|
|
47
|
+
"Call out missing verifier proof, budget pressure, safety-leash violations, and whether human review is required."
|
|
48
|
+
].join("\n")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (name === "martin_triage_failures") {
|
|
55
|
+
const loopId = requireOptionalText(args.loopId, "loopId") ?? "latest";
|
|
56
|
+
return {
|
|
57
|
+
description: "Triage failed Martin Loop evidence.",
|
|
58
|
+
messages: [
|
|
59
|
+
{
|
|
60
|
+
role: "user",
|
|
61
|
+
content: {
|
|
62
|
+
type: "text",
|
|
63
|
+
text: [
|
|
64
|
+
`Triage Martin Loop run ${loopId}.`,
|
|
65
|
+
"Use the run dossier, attempt evidence, and verification results.",
|
|
66
|
+
"Return the smallest safe next action and do not suggest re-running until the failure class and verifier evidence are clear."
|
|
67
|
+
].join("\n")
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
throw new Error("Unknown prompt.");
|
|
74
|
+
}
|
|
75
|
+
function requireOptionalText(value, name) {
|
|
76
|
+
if (value === undefined) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
80
|
+
throw new Error(`Invalid ${name}.`);
|
|
81
|
+
}
|
|
82
|
+
return value.trim();
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ListResourceTemplatesResult, ListResourcesResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export interface ResourceReadOptions {
|
|
3
|
+
runsDir?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function listMartinResources(): ListResourcesResult["resources"];
|
|
6
|
+
export declare function listMartinResourceTemplates(): ListResourceTemplatesResult["resourceTemplates"];
|
|
7
|
+
export declare function readMartinResource(uri: string, options?: ResourceReadOptions): Promise<ReadResourceResult>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { buildRunDossier, getAttempt, listRunSummaries, loadSelectedRun } from "./tools/cockpit-support.js";
|
|
2
|
+
import { getVerificationResultsTool } from "./tools/get-verification-results.js";
|
|
3
|
+
export function listMartinResources() {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
uri: "martin://runs/summary",
|
|
7
|
+
name: "Martin run summary",
|
|
8
|
+
description: "Read-only summary of recent governed Martin Loop runs.",
|
|
9
|
+
mimeType: "application/json"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
uri: "martin://runs/latest",
|
|
13
|
+
name: "Latest Martin run",
|
|
14
|
+
description: "Read-only dossier for the newest run in the local run store.",
|
|
15
|
+
mimeType: "application/json"
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
export function listMartinResourceTemplates() {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
uriTemplate: "martin://runs/{loopId}",
|
|
23
|
+
name: "Martin run dossier",
|
|
24
|
+
description: "Read-only run dossier by loopId.",
|
|
25
|
+
mimeType: "application/json"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
uriTemplate: "martin://runs/{loopId}/attempts/{attemptIndex}",
|
|
29
|
+
name: "Martin run attempt",
|
|
30
|
+
description: "Read-only attempt evidence for a run.",
|
|
31
|
+
mimeType: "application/json"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
uriTemplate: "martin://runs/{loopId}/verification",
|
|
35
|
+
name: "Martin verification results",
|
|
36
|
+
description: "Verifier results extracted from a run ledger.",
|
|
37
|
+
mimeType: "application/json"
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
export async function readMartinResource(uri, options = {}) {
|
|
42
|
+
const parsed = new URL(uri);
|
|
43
|
+
if (parsed.protocol !== "martin:") {
|
|
44
|
+
throw new Error("Unsupported resource URI.");
|
|
45
|
+
}
|
|
46
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
47
|
+
if (parsed.hostname === "runs" && segments.length === 1 && segments[0] === "summary") {
|
|
48
|
+
return asJsonResource(uri, await listRunSummaries({ runsDir: options.runsDir }));
|
|
49
|
+
}
|
|
50
|
+
if (parsed.hostname === "runs" && segments.length === 1 && segments[0] === "latest") {
|
|
51
|
+
const loop = await loadSelectedRun({ latest: true, runsDir: options.runsDir });
|
|
52
|
+
return asJsonResource(uri, buildRunDossier(loop));
|
|
53
|
+
}
|
|
54
|
+
if (parsed.hostname !== "runs" || segments.length < 1) {
|
|
55
|
+
throw new Error("Unknown resource URI.");
|
|
56
|
+
}
|
|
57
|
+
const [loopId, child, attemptIndex] = segments;
|
|
58
|
+
if (!loopId || !/^[A-Za-z0-9._-]+$/u.test(loopId)) {
|
|
59
|
+
throw new Error("Invalid loopId.");
|
|
60
|
+
}
|
|
61
|
+
if (!child) {
|
|
62
|
+
const loop = await loadSelectedRun({ loopId, runsDir: options.runsDir });
|
|
63
|
+
return asJsonResource(uri, buildRunDossier(loop));
|
|
64
|
+
}
|
|
65
|
+
if (child === "attempts") {
|
|
66
|
+
const parsedAttempt = Number(attemptIndex);
|
|
67
|
+
if (!Number.isInteger(parsedAttempt) || parsedAttempt <= 0) {
|
|
68
|
+
throw new Error("Invalid attemptIndex.");
|
|
69
|
+
}
|
|
70
|
+
const loop = await loadSelectedRun({ loopId, runsDir: options.runsDir });
|
|
71
|
+
return asJsonResource(uri, getAttempt(loop, parsedAttempt));
|
|
72
|
+
}
|
|
73
|
+
if (child === "verification") {
|
|
74
|
+
return asJsonResource(uri, await getVerificationResultsTool({ loopId, runsDir: options.runsDir }));
|
|
75
|
+
}
|
|
76
|
+
throw new Error("Unknown resource URI.");
|
|
77
|
+
}
|
|
78
|
+
function asJsonResource(uri, value) {
|
|
79
|
+
return {
|
|
80
|
+
contents: [
|
|
81
|
+
{
|
|
82
|
+
uri,
|
|
83
|
+
mimeType: "application/json",
|
|
84
|
+
text: JSON.stringify(value, null, 2)
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type ToolName = "martin_doctor" | "martin_preflight" | "martin_run" | "martin_inspect" | "martin_status";
|
|
1
|
+
type ToolName = "martin_doctor" | "martin_preflight" | "martin_run" | "martin_inspect" | "martin_status" | "martin_list_runs" | "martin_get_run" | "martin_get_attempt" | "martin_get_verification_results" | "martin_run_dossier";
|
|
2
2
|
export declare function validateToolInput(name: ToolName, args: unknown): unknown;
|
|
3
3
|
export declare function sanitizeToolErrorMessage(error: unknown): string;
|
|
4
4
|
export declare function resolveSafeRepoRoot(repoRoot?: string, workspaceRoot?: string): string;
|
|
@@ -12,6 +12,16 @@ export function validateToolInput(name, args) {
|
|
|
12
12
|
return validateInspectInput(args);
|
|
13
13
|
case "martin_status":
|
|
14
14
|
return validateStatusInput(args);
|
|
15
|
+
case "martin_list_runs":
|
|
16
|
+
return validateListRunsInput(args);
|
|
17
|
+
case "martin_get_run":
|
|
18
|
+
return validateGetRunInput(args);
|
|
19
|
+
case "martin_get_attempt":
|
|
20
|
+
return validateGetAttemptInput(args);
|
|
21
|
+
case "martin_get_verification_results":
|
|
22
|
+
return validateGetVerificationResultsInput(args);
|
|
23
|
+
case "martin_run_dossier":
|
|
24
|
+
return validateRunDossierInput(args);
|
|
15
25
|
default:
|
|
16
26
|
throw new Error(`Unknown tool: ${name}`);
|
|
17
27
|
}
|
|
@@ -199,6 +209,61 @@ function validateStatusInput(args) {
|
|
|
199
209
|
...(record.latest === true ? { latest: true } : {})
|
|
200
210
|
};
|
|
201
211
|
}
|
|
212
|
+
function validateListRunsInput(args) {
|
|
213
|
+
const record = requireObject(args);
|
|
214
|
+
assertAllowedKeys(record, ["runsDir", "limit"]);
|
|
215
|
+
return {
|
|
216
|
+
...(record.runsDir !== undefined
|
|
217
|
+
? { runsDir: resolveSafeRunsRootPath(requireString(record.runsDir, "runsDir")) }
|
|
218
|
+
: {}),
|
|
219
|
+
...(record.limit !== undefined ? { limit: requirePositiveInteger(record.limit, "limit") } : {})
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function validateGetRunInput(args) {
|
|
223
|
+
const record = requireObject(args);
|
|
224
|
+
assertAllowedKeys(record, ["loopId", "runsDir", "latest"]);
|
|
225
|
+
return validateRunSelector(record);
|
|
226
|
+
}
|
|
227
|
+
function validateGetVerificationResultsInput(args) {
|
|
228
|
+
const record = requireObject(args);
|
|
229
|
+
assertAllowedKeys(record, ["loopId", "runsDir", "latest"]);
|
|
230
|
+
return validateRunSelector(record);
|
|
231
|
+
}
|
|
232
|
+
function validateRunDossierInput(args) {
|
|
233
|
+
const record = requireObject(args);
|
|
234
|
+
assertAllowedKeys(record, ["loopId", "runsDir", "latest"]);
|
|
235
|
+
return validateRunSelector(record);
|
|
236
|
+
}
|
|
237
|
+
function validateGetAttemptInput(args) {
|
|
238
|
+
const record = requireObject(args);
|
|
239
|
+
assertAllowedKeys(record, ["loopId", "attemptIndex", "runsDir"]);
|
|
240
|
+
return {
|
|
241
|
+
loopId: requireLoopId(record.loopId, "loopId"),
|
|
242
|
+
attemptIndex: requirePositiveInteger(record.attemptIndex, "attemptIndex"),
|
|
243
|
+
...(record.runsDir !== undefined
|
|
244
|
+
? { runsDir: resolveSafeRunsRootPath(requireString(record.runsDir, "runsDir")) }
|
|
245
|
+
: {})
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function validateRunSelector(record) {
|
|
249
|
+
const selectors = [
|
|
250
|
+
record.loopId !== undefined ? "loopId" : null,
|
|
251
|
+
record.latest !== undefined ? "latest" : null
|
|
252
|
+
].filter((value) => value !== null);
|
|
253
|
+
if (selectors.length !== 1) {
|
|
254
|
+
throw new Error("Provide exactly one of loopId or latest.");
|
|
255
|
+
}
|
|
256
|
+
if (record.latest !== undefined && record.latest !== true) {
|
|
257
|
+
throw new Error("Invalid latest.");
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
...(record.loopId !== undefined ? { loopId: requireLoopId(record.loopId, "loopId") } : {}),
|
|
261
|
+
...(record.latest === true ? { latest: true } : {}),
|
|
262
|
+
...(record.runsDir !== undefined
|
|
263
|
+
? { runsDir: resolveSafeRunsRootPath(requireString(record.runsDir, "runsDir")) }
|
|
264
|
+
: {})
|
|
265
|
+
};
|
|
266
|
+
}
|
|
202
267
|
function requireObject(value) {
|
|
203
268
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
204
269
|
throw new Error("Tool arguments must be an object.");
|
|
@@ -252,10 +317,13 @@ function optionalPositiveInteger(value, name) {
|
|
|
252
317
|
if (value === undefined) {
|
|
253
318
|
return {};
|
|
254
319
|
}
|
|
320
|
+
return { [name]: requirePositiveInteger(value, name) };
|
|
321
|
+
}
|
|
322
|
+
function requirePositiveInteger(value, name) {
|
|
255
323
|
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
256
324
|
throw new Error(`Invalid ${name}.`);
|
|
257
325
|
}
|
|
258
|
-
return
|
|
326
|
+
return value;
|
|
259
327
|
}
|
|
260
328
|
function optionalStringArray(value, name) {
|
|
261
329
|
if (value === undefined) {
|
package/dist/server.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Martin Loop MCP Server
|
|
4
4
|
*
|
|
5
|
-
* Exposes
|
|
5
|
+
* Exposes a governed local MCP cockpit over stdio:
|
|
6
6
|
* martin_doctor — inspect local readiness and run-store health
|
|
7
7
|
* martin_preflight — normalize a proposed run contract before execution
|
|
8
8
|
* martin_run — execute a full Martin loop on a coding task
|
package/dist/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Martin Loop MCP Server
|
|
4
4
|
*
|
|
5
|
-
* Exposes
|
|
5
|
+
* Exposes a governed local MCP cockpit over stdio:
|
|
6
6
|
* martin_doctor — inspect local readiness and run-store health
|
|
7
7
|
* martin_preflight — normalize a proposed run contract before execution
|
|
8
8
|
* martin_run — execute a full Martin loop on a coding task
|
|
@@ -22,16 +22,23 @@
|
|
|
22
22
|
import { createRequire } from "node:module";
|
|
23
23
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24
24
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
25
|
+
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
26
26
|
import { martinDoctorTool } from "./tools/doctor.js";
|
|
27
|
+
import { getAttemptTool } from "./tools/get-attempt.js";
|
|
28
|
+
import { getRunTool } from "./tools/get-run.js";
|
|
27
29
|
import { getStatusTool } from "./tools/get-status.js";
|
|
30
|
+
import { getVerificationResultsTool } from "./tools/get-verification-results.js";
|
|
28
31
|
import { inspectLoopTool } from "./tools/inspect-loop.js";
|
|
32
|
+
import { listRunsTool } from "./tools/list-runs.js";
|
|
29
33
|
import { martinPreflightTool } from "./tools/preflight.js";
|
|
34
|
+
import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
|
|
35
|
+
import { listMartinResourceTemplates, listMartinResources, readMartinResource } from "./resources.js";
|
|
30
36
|
import { runLoopTool } from "./tools/run-loop.js";
|
|
37
|
+
import { runDossierTool } from "./tools/run-dossier.js";
|
|
31
38
|
import { sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
|
|
32
39
|
const require = createRequire(import.meta.url);
|
|
33
40
|
const packageJson = require("../package.json");
|
|
34
|
-
const server = new Server({ name: "martin-loop", version: packageJson.version }, { capabilities: { tools: {} } });
|
|
41
|
+
const server = new Server({ name: "martin-loop", version: packageJson.version }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
|
|
35
42
|
// ---------------------------------------------------------------------------
|
|
36
43
|
// Tool manifest
|
|
37
44
|
// ---------------------------------------------------------------------------
|
|
@@ -245,10 +252,140 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
245
252
|
{ required: ["latest"] }
|
|
246
253
|
]
|
|
247
254
|
}
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "martin_list_runs",
|
|
258
|
+
description: "List recent Martin Loop runs from the local run store with budget, verifier, and lifecycle summaries. Read-only.",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
type: "object",
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
properties: {
|
|
263
|
+
runsDir: {
|
|
264
|
+
type: "string",
|
|
265
|
+
description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
|
|
266
|
+
},
|
|
267
|
+
limit: {
|
|
268
|
+
type: "integer",
|
|
269
|
+
exclusiveMinimum: 0,
|
|
270
|
+
description: "Maximum number of runs to return. Defaults to 20."
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "martin_get_run",
|
|
277
|
+
description: "Load a read-only run dossier by loopId or latest run selector, including task, budget, cost, and attempts.",
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: "object",
|
|
280
|
+
additionalProperties: false,
|
|
281
|
+
properties: {
|
|
282
|
+
loopId: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Martin loop ID under the run store."
|
|
285
|
+
},
|
|
286
|
+
runsDir: {
|
|
287
|
+
type: "string",
|
|
288
|
+
description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
|
|
289
|
+
},
|
|
290
|
+
latest: {
|
|
291
|
+
const: true,
|
|
292
|
+
description: "When true, loads the most recently updated loop record in the runs directory."
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "martin_get_attempt",
|
|
300
|
+
description: "Load read-only attempt evidence for a single Martin Loop attempt by loopId and attempt index.",
|
|
301
|
+
inputSchema: {
|
|
302
|
+
type: "object",
|
|
303
|
+
additionalProperties: false,
|
|
304
|
+
properties: {
|
|
305
|
+
loopId: {
|
|
306
|
+
type: "string",
|
|
307
|
+
description: "Martin loop ID under the run store."
|
|
308
|
+
},
|
|
309
|
+
attemptIndex: {
|
|
310
|
+
type: "integer",
|
|
311
|
+
exclusiveMinimum: 0,
|
|
312
|
+
description: "1-based attempt index to inspect."
|
|
313
|
+
},
|
|
314
|
+
runsDir: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
required: ["loopId", "attemptIndex"]
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: "martin_get_verification_results",
|
|
324
|
+
description: "Extract verifier completion events for a run by loopId or latest selector. Read-only.",
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: "object",
|
|
327
|
+
additionalProperties: false,
|
|
328
|
+
properties: {
|
|
329
|
+
loopId: {
|
|
330
|
+
type: "string",
|
|
331
|
+
description: "Martin loop ID under the run store."
|
|
332
|
+
},
|
|
333
|
+
runsDir: {
|
|
334
|
+
type: "string",
|
|
335
|
+
description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
|
|
336
|
+
},
|
|
337
|
+
latest: {
|
|
338
|
+
const: true,
|
|
339
|
+
description: "When true, loads the most recently updated loop record in the runs directory."
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "martin_run_dossier",
|
|
347
|
+
description: "Build a compact read-only dossier for review: summary, budget, attempts, and verification evidence.",
|
|
348
|
+
inputSchema: {
|
|
349
|
+
type: "object",
|
|
350
|
+
additionalProperties: false,
|
|
351
|
+
properties: {
|
|
352
|
+
loopId: {
|
|
353
|
+
type: "string",
|
|
354
|
+
description: "Martin loop ID under the run store."
|
|
355
|
+
},
|
|
356
|
+
runsDir: {
|
|
357
|
+
type: "string",
|
|
358
|
+
description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
|
|
359
|
+
},
|
|
360
|
+
latest: {
|
|
361
|
+
const: true,
|
|
362
|
+
description: "When true, loads the most recently updated loop record in the runs directory."
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
|
|
366
|
+
}
|
|
248
367
|
}
|
|
249
368
|
]
|
|
250
369
|
}));
|
|
251
370
|
// ---------------------------------------------------------------------------
|
|
371
|
+
// Resources and prompts
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
server.setRequestHandler(ListResourcesRequestSchema, () => ({
|
|
374
|
+
resources: listMartinResources()
|
|
375
|
+
}));
|
|
376
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
|
|
377
|
+
resourceTemplates: listMartinResourceTemplates()
|
|
378
|
+
}));
|
|
379
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
380
|
+
return readMartinResource(request.params.uri);
|
|
381
|
+
});
|
|
382
|
+
server.setRequestHandler(ListPromptsRequestSchema, () => ({
|
|
383
|
+
prompts: listMartinPrompts()
|
|
384
|
+
}));
|
|
385
|
+
server.setRequestHandler(GetPromptRequestSchema, (request) => {
|
|
386
|
+
return getMartinPrompt(request.params.name, request.params.arguments ?? {});
|
|
387
|
+
});
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
252
389
|
// Tool dispatch
|
|
253
390
|
// ---------------------------------------------------------------------------
|
|
254
391
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -279,6 +416,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
279
416
|
const output = await getStatusTool(input);
|
|
280
417
|
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
281
418
|
}
|
|
419
|
+
if (name === "martin_list_runs") {
|
|
420
|
+
const input = validateToolInput("martin_list_runs", args);
|
|
421
|
+
const output = await listRunsTool(input);
|
|
422
|
+
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
423
|
+
}
|
|
424
|
+
if (name === "martin_get_run") {
|
|
425
|
+
const input = validateToolInput("martin_get_run", args);
|
|
426
|
+
const output = await getRunTool(input);
|
|
427
|
+
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
428
|
+
}
|
|
429
|
+
if (name === "martin_get_attempt") {
|
|
430
|
+
const input = validateToolInput("martin_get_attempt", args);
|
|
431
|
+
const output = await getAttemptTool(input);
|
|
432
|
+
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
433
|
+
}
|
|
434
|
+
if (name === "martin_get_verification_results") {
|
|
435
|
+
const input = validateToolInput("martin_get_verification_results", args);
|
|
436
|
+
const output = await getVerificationResultsTool(input);
|
|
437
|
+
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
438
|
+
}
|
|
439
|
+
if (name === "martin_run_dossier") {
|
|
440
|
+
const input = validateToolInput("martin_run_dossier", args);
|
|
441
|
+
const output = await runDossierTool(input);
|
|
442
|
+
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
|
|
443
|
+
}
|
|
282
444
|
return {
|
|
283
445
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
284
446
|
isError: true
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type LoopRunRecord } from "../vendor/core/index.js";
|
|
2
|
+
export interface RunSelectorInput {
|
|
3
|
+
loopId?: string;
|
|
4
|
+
runsDir?: string;
|
|
5
|
+
latest?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface RunSummary {
|
|
8
|
+
loopId: string;
|
|
9
|
+
title: string;
|
|
10
|
+
objective: string;
|
|
11
|
+
status: string;
|
|
12
|
+
lifecycleState: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
attempts: number;
|
|
16
|
+
costUsd: number;
|
|
17
|
+
avoidedUsd: number;
|
|
18
|
+
pressure: string;
|
|
19
|
+
shouldStop: boolean;
|
|
20
|
+
verificationCount: number;
|
|
21
|
+
}
|
|
22
|
+
export interface VerificationResultSummary {
|
|
23
|
+
eventId?: string;
|
|
24
|
+
timestamp?: string;
|
|
25
|
+
lifecycleState?: string;
|
|
26
|
+
passed?: boolean;
|
|
27
|
+
summary?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function summarizeRun(loop: LoopRunRecord): RunSummary;
|
|
30
|
+
export declare function listRunSummaries(input?: {
|
|
31
|
+
runsDir?: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<RunSummary[]>;
|
|
34
|
+
export declare function loadSelectedRun(input: RunSelectorInput): Promise<LoopRunRecord>;
|
|
35
|
+
export declare function extractVerificationResults(loop: LoopRunRecord): VerificationResultSummary[];
|
|
36
|
+
export declare function getAttempt(loop: LoopRunRecord, attemptIndex: number): import("../vendor/core/index.js").LoopAttemptRecord;
|
|
37
|
+
export declare function buildRunDossier(loop: LoopRunRecord): {
|
|
38
|
+
loopId: string;
|
|
39
|
+
generatedAt: string;
|
|
40
|
+
sections: ({
|
|
41
|
+
kind: string;
|
|
42
|
+
content: {
|
|
43
|
+
title: string;
|
|
44
|
+
objective: string;
|
|
45
|
+
};
|
|
46
|
+
} | {
|
|
47
|
+
kind: string;
|
|
48
|
+
content: {
|
|
49
|
+
budget: {
|
|
50
|
+
maxUsd: number;
|
|
51
|
+
softLimitUsd: number;
|
|
52
|
+
maxIterations: number;
|
|
53
|
+
maxTokens: number;
|
|
54
|
+
};
|
|
55
|
+
cost: {
|
|
56
|
+
actualUsd: number;
|
|
57
|
+
tokensIn: number;
|
|
58
|
+
tokensOut: number;
|
|
59
|
+
avoidedUsd?: number;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
} | {
|
|
63
|
+
kind: string;
|
|
64
|
+
content: import("../vendor/core/index.js").LoopAttemptRecord[];
|
|
65
|
+
} | {
|
|
66
|
+
kind: string;
|
|
67
|
+
content: VerificationResultSummary[];
|
|
68
|
+
})[];
|
|
69
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { evaluateCostGovernor } from "../vendor/core/index.js";
|
|
2
|
+
import { loadLoopRecordForStatus, loadLoopRecordsForInspect } from "./run-store.js";
|
|
3
|
+
export function summarizeRun(loop) {
|
|
4
|
+
const costState = evaluateCostGovernor({
|
|
5
|
+
budget: loop.budget,
|
|
6
|
+
cost: {
|
|
7
|
+
actualUsd: loop.cost.actualUsd,
|
|
8
|
+
avoidedUsd: loop.cost.avoidedUsd ?? 0,
|
|
9
|
+
tokensIn: loop.cost.tokensIn,
|
|
10
|
+
tokensOut: loop.cost.tokensOut
|
|
11
|
+
},
|
|
12
|
+
attemptsUsed: loop.attempts.length
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
loopId: loop.loopId,
|
|
16
|
+
title: loop.task.title,
|
|
17
|
+
objective: loop.task.objective,
|
|
18
|
+
status: loop.status,
|
|
19
|
+
lifecycleState: loop.lifecycleState,
|
|
20
|
+
createdAt: loop.createdAt,
|
|
21
|
+
updatedAt: loop.updatedAt,
|
|
22
|
+
attempts: loop.attempts.length,
|
|
23
|
+
costUsd: loop.cost.actualUsd,
|
|
24
|
+
avoidedUsd: loop.cost.avoidedUsd ?? 0,
|
|
25
|
+
pressure: costState.pressure,
|
|
26
|
+
shouldStop: costState.shouldStop,
|
|
27
|
+
verificationCount: extractVerificationResults(loop).length
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function listRunSummaries(input = {}) {
|
|
31
|
+
const inspection = await loadLoopRecordsForInspect({ runsDir: input.runsDir });
|
|
32
|
+
const summaries = inspection.loops.map((loop) => summarizeRun(loop));
|
|
33
|
+
summaries.sort((left, right) => {
|
|
34
|
+
const leftTime = Date.parse(left.updatedAt ?? left.createdAt);
|
|
35
|
+
const rightTime = Date.parse(right.updatedAt ?? right.createdAt);
|
|
36
|
+
return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
|
|
37
|
+
});
|
|
38
|
+
return summaries.slice(0, input.limit ?? 20);
|
|
39
|
+
}
|
|
40
|
+
export async function loadSelectedRun(input) {
|
|
41
|
+
const selectors = [input.loopId ? "loopId" : null, input.latest ? "latest" : null].filter(Boolean);
|
|
42
|
+
if (selectors.length !== 1) {
|
|
43
|
+
throw new Error("Provide exactly one of loopId or latest.");
|
|
44
|
+
}
|
|
45
|
+
const source = await loadLoopRecordForStatus({
|
|
46
|
+
...(input.loopId ? { loopId: input.loopId } : {}),
|
|
47
|
+
...(input.latest ? { latest: true } : {}),
|
|
48
|
+
...(input.runsDir ? { runsDir: input.runsDir } : {})
|
|
49
|
+
});
|
|
50
|
+
return source.loop;
|
|
51
|
+
}
|
|
52
|
+
export function extractVerificationResults(loop) {
|
|
53
|
+
const events = "events" in loop && Array.isArray(loop.events) ? loop.events : [];
|
|
54
|
+
return events
|
|
55
|
+
.filter((event) => event?.type === "verification.completed")
|
|
56
|
+
.map((event) => {
|
|
57
|
+
const payload = isRecord(event.payload) ? event.payload : {};
|
|
58
|
+
return {
|
|
59
|
+
...(typeof event.eventId === "string" ? { eventId: event.eventId } : {}),
|
|
60
|
+
...(typeof event.timestamp === "string" ? { timestamp: event.timestamp } : {}),
|
|
61
|
+
...(typeof event.lifecycleState === "string" ? { lifecycleState: event.lifecycleState } : {}),
|
|
62
|
+
...(typeof payload.passed === "boolean" ? { passed: payload.passed } : {}),
|
|
63
|
+
...(typeof payload.summary === "string" ? { summary: payload.summary } : {})
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function getAttempt(loop, attemptIndex) {
|
|
68
|
+
const attempt = loop.attempts.find((candidate) => candidate.index === attemptIndex);
|
|
69
|
+
if (!attempt) {
|
|
70
|
+
throw new Error("Attempt not found.");
|
|
71
|
+
}
|
|
72
|
+
return attempt;
|
|
73
|
+
}
|
|
74
|
+
export function buildRunDossier(loop) {
|
|
75
|
+
return {
|
|
76
|
+
loopId: loop.loopId,
|
|
77
|
+
generatedAt: new Date().toISOString(),
|
|
78
|
+
sections: [
|
|
79
|
+
{
|
|
80
|
+
kind: "summary",
|
|
81
|
+
content: summarizeRun(loop)
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
kind: "task",
|
|
85
|
+
content: loop.task
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
kind: "budget",
|
|
89
|
+
content: {
|
|
90
|
+
budget: loop.budget,
|
|
91
|
+
cost: loop.cost
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
kind: "attempts",
|
|
96
|
+
content: loop.attempts
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
kind: "verification",
|
|
100
|
+
content: extractVerificationResults(loop)
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function isRecord(value) {
|
|
106
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=cockpit-support.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getAttempt } from "./cockpit-support.js";
|
|
2
|
+
export interface GetAttemptInput {
|
|
3
|
+
loopId: string;
|
|
4
|
+
attemptIndex: number;
|
|
5
|
+
runsDir?: string;
|
|
6
|
+
}
|
|
7
|
+
export type GetAttemptOutput = ReturnType<typeof getAttempt>;
|
|
8
|
+
export declare function getAttemptTool(input: GetAttemptInput): Promise<GetAttemptOutput>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { getAttempt, loadSelectedRun } from "./cockpit-support.js";
|
|
2
|
+
export async function getAttemptTool(input) {
|
|
3
|
+
const loop = await loadSelectedRun({ loopId: input.loopId, runsDir: input.runsDir });
|
|
4
|
+
return getAttempt(loop, input.attemptIndex);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=get-attempt.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LoopRunRecord } from "../vendor/core/index.js";
|
|
2
|
+
import { summarizeRun } from "./cockpit-support.js";
|
|
3
|
+
export interface GetRunInput {
|
|
4
|
+
loopId?: string;
|
|
5
|
+
runsDir?: string;
|
|
6
|
+
latest?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface GetRunOutput {
|
|
9
|
+
summary: ReturnType<typeof summarizeRun>;
|
|
10
|
+
task: LoopRunRecord["task"];
|
|
11
|
+
budget: LoopRunRecord["budget"];
|
|
12
|
+
cost: LoopRunRecord["cost"];
|
|
13
|
+
attempts: LoopRunRecord["attempts"];
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function getRunTool(input: GetRunInput): Promise<GetRunOutput>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { loadSelectedRun, summarizeRun } from "./cockpit-support.js";
|
|
2
|
+
export async function getRunTool(input) {
|
|
3
|
+
const loop = await loadSelectedRun(input);
|
|
4
|
+
return {
|
|
5
|
+
summary: summarizeRun(loop),
|
|
6
|
+
task: loop.task,
|
|
7
|
+
budget: loop.budget,
|
|
8
|
+
cost: loop.cost,
|
|
9
|
+
attempts: loop.attempts,
|
|
10
|
+
createdAt: loop.createdAt,
|
|
11
|
+
updatedAt: loop.updatedAt
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=get-run.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type VerificationResultSummary } from "./cockpit-support.js";
|
|
2
|
+
export interface GetVerificationResultsInput {
|
|
3
|
+
loopId?: string;
|
|
4
|
+
runsDir?: string;
|
|
5
|
+
latest?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface GetVerificationResultsOutput {
|
|
8
|
+
loopId: string;
|
|
9
|
+
results: VerificationResultSummary[];
|
|
10
|
+
}
|
|
11
|
+
export declare function getVerificationResultsTool(input: GetVerificationResultsInput): Promise<GetVerificationResultsOutput>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { extractVerificationResults, loadSelectedRun } from "./cockpit-support.js";
|
|
2
|
+
export async function getVerificationResultsTool(input) {
|
|
3
|
+
const loop = await loadSelectedRun(input);
|
|
4
|
+
return {
|
|
5
|
+
loopId: loop.loopId,
|
|
6
|
+
results: extractVerificationResults(loop)
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=get-verification-results.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RunSummary } from "./cockpit-support.js";
|
|
2
|
+
export interface ListRunsInput {
|
|
3
|
+
runsDir?: string;
|
|
4
|
+
limit?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ListRunsOutput {
|
|
7
|
+
runs: RunSummary[];
|
|
8
|
+
}
|
|
9
|
+
export declare function listRunsTool(input: ListRunsInput): Promise<ListRunsOutput>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { buildRunDossier } from "./cockpit-support.js";
|
|
2
|
+
export interface RunDossierInput {
|
|
3
|
+
loopId?: string;
|
|
4
|
+
runsDir?: string;
|
|
5
|
+
latest?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type RunDossierOutput = ReturnType<typeof buildRunDossier>;
|
|
8
|
+
export declare function runDossierTool(input: RunDossierInput): Promise<RunDossierOutput>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martinloop/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"mcpName": "io.github.Keesan12/martin-loop",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"claude",
|
|
25
25
|
"codex",
|
|
26
26
|
"martin_doctor",
|
|
27
|
-
"martin_preflight"
|
|
27
|
+
"martin_preflight",
|
|
28
|
+
"martin_list_runs",
|
|
29
|
+
"martin_run_dossier"
|
|
28
30
|
],
|
|
29
31
|
"bin": {
|
|
30
32
|
"mcp": "./dist/server.js",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/Keesan12/martin-loop",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.2.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@martinloop/mcp",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.2.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|