@martinloop/mcp 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -52
- package/dist/arcade/capabilities.d.ts +36 -0
- package/dist/arcade/capabilities.js +37 -0
- package/dist/arcade/resource.d.ts +31 -0
- package/dist/arcade/resource.js +52 -0
- package/dist/arcade/status.d.ts +23 -0
- package/dist/arcade/status.js +25 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +3 -3
- package/dist/resources.js +1 -13
- package/dist/server.js +241 -75
- package/dist/tools/execution-policy.js +1 -1
- package/dist/tools/get-status.d.ts +2 -4
- package/dist/tools/get-status.js +6 -3
- package/dist/tools/preflight.d.ts +1 -1
- package/dist/tools/run-loop.d.ts +2 -3
- package/dist/tools/run-loop.js +4 -3
- package/dist/tools/run-store.js +1 -1
- package/dist/tools/tool-support.d.ts +3 -2
- package/dist/tools/tool-support.js +9 -2
- package/dist/tools/workflow-governance.d.ts +1 -1
- package/dist/tools/workflow-governance.js +1 -1
- package/dist/vendor/adapters/claude-cli.js +2 -1
- package/dist/vendor/adapters/cli-bridge.js +7 -1
- package/dist/vendor/adapters/index.d.ts +1 -0
- package/dist/vendor/adapters/index.js +1 -0
- package/dist/vendor/adapters/openai-compatible.js +6 -3
- package/dist/vendor/adapters/stub-agent-cli.d.ts +9 -0
- package/dist/vendor/adapters/stub-agent-cli.js +43 -0
- package/dist/vendor/adapters/stub-direct-provider.d.ts +8 -0
- package/dist/vendor/adapters/stub-direct-provider.js +16 -0
- package/dist/vendor/adapters/verifier-only.js +1 -1
- package/dist/vendor/contracts/execution-policy.js +1 -1
- package/dist/vendor/contracts/exits.d.ts +1 -1
- package/dist/vendor/contracts/governance.d.ts +1 -1
- package/dist/vendor/contracts/index.d.ts +1 -1
- package/dist/vendor/contracts/index.js +1 -2
- package/dist/vendor/contracts/operator.d.ts +1 -1
- package/dist/vendor/contracts/operator.js +1 -2
- package/dist/vendor/core/compiler.d.ts +2 -2
- package/dist/vendor/core/compiler.js +3 -1
- package/dist/vendor/core/exits.js +9 -4
- package/dist/vendor/core/grounding.d.ts +1 -0
- package/dist/vendor/core/grounding.js +28 -1
- package/dist/vendor/core/index.d.ts +2 -2
- package/dist/vendor/core/index.js +11 -64
- package/dist/vendor/core/policy-compiler.js +17 -1
- package/dist/vendor/core/policy.d.ts +2 -1
- package/dist/vendor/core/policy.js +15 -5
- package/dist/vendor/core/rollback.d.ts +3 -0
- package/dist/vendor/core/rollback.js +33 -20
- package/dist/vendor/core/verified-handoff.js +25 -6
- package/dist/vendor/presentation/motion.js +0 -4
- package/dist/vendor/presentation/verified-handoff.js +10 -13
- package/dist/workflow-state.js +1 -1
- package/package.json +20 -11
- package/server.json +3 -3
package/dist/server.js
CHANGED
|
@@ -26,6 +26,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
26
26
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
27
27
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
28
28
|
import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
|
|
29
|
+
import { MARTIN_ARCADE_FALLBACK_TEXT, MARTIN_ARCADE_MIME_TYPE, MARTIN_ARCADE_RESOURCE_URI, MARTIN_ARCADE_UI_EXTENSION_ID, buildArcadeToolDefinitions, supportsMartinArcadeApp } from "./arcade/capabilities.js";
|
|
30
|
+
import { listArcadeResources, readArcadeResource } from "./arcade/resource.js";
|
|
31
|
+
import { readArcadeStatus } from "./arcade/status.js";
|
|
29
32
|
import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
|
|
30
33
|
import { listMartinResources, listMartinResourceTemplates, readMartinResource } from "./resources.js";
|
|
31
34
|
import { martinDoctorTool } from "./tools/doctor.js";
|
|
@@ -56,15 +59,6 @@ const stringArraySchema = {
|
|
|
56
59
|
type: "array",
|
|
57
60
|
items: { type: "string" }
|
|
58
61
|
};
|
|
59
|
-
function describeCostProvenance(provenance) {
|
|
60
|
-
if (provenance === "actual")
|
|
61
|
-
return "provider-settled actual";
|
|
62
|
-
if (provenance === "calculated")
|
|
63
|
-
return "calculated from observed usage";
|
|
64
|
-
if (provenance === "estimated")
|
|
65
|
-
return "estimated";
|
|
66
|
-
return "unavailable";
|
|
67
|
-
}
|
|
68
62
|
const loopPreviewSchema = {
|
|
69
63
|
type: "object",
|
|
70
64
|
additionalProperties: true,
|
|
@@ -78,10 +72,6 @@ const loopPreviewSchema = {
|
|
|
78
72
|
updatedAt: { type: "string" },
|
|
79
73
|
attempts: { type: "integer" },
|
|
80
74
|
costUsd: { type: "number" },
|
|
81
|
-
costProvenance: {
|
|
82
|
-
type: "string",
|
|
83
|
-
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
84
|
-
},
|
|
85
75
|
avoidedUsd: { type: "number" },
|
|
86
76
|
pressure: { type: "string" },
|
|
87
77
|
shouldStop: { type: "boolean" },
|
|
@@ -101,13 +91,11 @@ const loopPreviewSchema = {
|
|
|
101
91
|
"lifecycleState",
|
|
102
92
|
"attempts",
|
|
103
93
|
"costUsd",
|
|
104
|
-
"costProvenance",
|
|
105
94
|
"avoidedUsd",
|
|
106
95
|
"pressure",
|
|
107
96
|
"shouldStop",
|
|
108
97
|
"remainingBudgetUsd",
|
|
109
|
-
"remainingIterations"
|
|
110
|
-
"remainingTokens"
|
|
98
|
+
"remainingIterations"
|
|
111
99
|
]
|
|
112
100
|
};
|
|
113
101
|
const budgetSchema = {
|
|
@@ -119,7 +107,7 @@ const budgetSchema = {
|
|
|
119
107
|
maxIterations: { type: "integer" },
|
|
120
108
|
maxTokens: { type: "integer" }
|
|
121
109
|
},
|
|
122
|
-
required: ["maxUsd", "softLimitUsd", "maxIterations"
|
|
110
|
+
required: ["maxUsd", "softLimitUsd", "maxIterations"]
|
|
123
111
|
};
|
|
124
112
|
const costSchema = {
|
|
125
113
|
type: "object",
|
|
@@ -267,10 +255,6 @@ const runOutputSchema = {
|
|
|
267
255
|
reason: { type: "string" },
|
|
268
256
|
attempts: { type: "integer" },
|
|
269
257
|
costUsd: { type: "number" },
|
|
270
|
-
costProvenance: {
|
|
271
|
-
type: "string",
|
|
272
|
-
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
273
|
-
},
|
|
274
258
|
verificationPassed: { type: "boolean" },
|
|
275
259
|
loopId: { type: "string" },
|
|
276
260
|
pressure: { type: "string" },
|
|
@@ -302,14 +286,12 @@ const runOutputSchema = {
|
|
|
302
286
|
"reason",
|
|
303
287
|
"attempts",
|
|
304
288
|
"costUsd",
|
|
305
|
-
"costProvenance",
|
|
306
289
|
"verificationPassed",
|
|
307
290
|
"loopId",
|
|
308
291
|
"pressure",
|
|
309
292
|
"shouldStop",
|
|
310
293
|
"remainingBudgetUsd",
|
|
311
294
|
"remainingIterations",
|
|
312
|
-
"remainingTokens",
|
|
313
295
|
"engine",
|
|
314
296
|
"workingDirectory",
|
|
315
297
|
"budget",
|
|
@@ -370,10 +352,6 @@ const statusOutputSchema = {
|
|
|
370
352
|
lifecycleState: { type: "string" },
|
|
371
353
|
attempts: { type: "integer" },
|
|
372
354
|
costUsd: { type: "number" },
|
|
373
|
-
costProvenance: {
|
|
374
|
-
type: "string",
|
|
375
|
-
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
376
|
-
},
|
|
377
355
|
avoidedUsd: { type: "number" },
|
|
378
356
|
pressure: { type: "string" },
|
|
379
357
|
shouldStop: { type: "boolean" },
|
|
@@ -397,13 +375,11 @@ const statusOutputSchema = {
|
|
|
397
375
|
"lifecycleState",
|
|
398
376
|
"attempts",
|
|
399
377
|
"costUsd",
|
|
400
|
-
"costProvenance",
|
|
401
378
|
"avoidedUsd",
|
|
402
379
|
"pressure",
|
|
403
380
|
"shouldStop",
|
|
404
381
|
"remainingBudgetUsd",
|
|
405
382
|
"remainingIterations",
|
|
406
|
-
"remainingTokens",
|
|
407
383
|
"budget",
|
|
408
384
|
"inspection"
|
|
409
385
|
]
|
|
@@ -725,29 +701,157 @@ const planOutputSchema = {
|
|
|
725
701
|
};
|
|
726
702
|
const logsOutputSchema = {
|
|
727
703
|
type: "object",
|
|
728
|
-
additionalProperties:
|
|
704
|
+
additionalProperties: false,
|
|
705
|
+
properties: {
|
|
706
|
+
source: { type: "string", description: "Resolved path to the loop-record source file." },
|
|
707
|
+
sourceKind: {
|
|
708
|
+
type: "string",
|
|
709
|
+
enum: ["file", "loop_id", "latest", "runs_root"],
|
|
710
|
+
description: "How the run was identified: by file path, loop ID, latest flag, or runs directory."
|
|
711
|
+
},
|
|
712
|
+
loopId: { type: "string", description: "Unique MartinLoop run identifier." },
|
|
713
|
+
logCount: { type: "integer", description: "Number of log entries returned after applying the limit." },
|
|
714
|
+
live: {
|
|
715
|
+
type: "object",
|
|
716
|
+
additionalProperties: false,
|
|
717
|
+
properties: {
|
|
718
|
+
lifecycleState: { type: "string", description: "Current run lifecycle state (e.g. running, completed, cancelled)." },
|
|
719
|
+
pauseState: {
|
|
720
|
+
type: "string",
|
|
721
|
+
enum: ["active", "paused", "cancellation_requested"],
|
|
722
|
+
description: "Whether the run is active, paused, or has a pending cancellation."
|
|
723
|
+
},
|
|
724
|
+
approvalState: {
|
|
725
|
+
type: "string",
|
|
726
|
+
enum: ["not_required", "resume_requested"],
|
|
727
|
+
description: "Whether the run is waiting at a human-approval checkpoint."
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
required: ["lifecycleState", "pauseState", "approvalState"]
|
|
731
|
+
},
|
|
732
|
+
entries: {
|
|
733
|
+
type: "array",
|
|
734
|
+
description: "Log entries sorted by timestamp descending, capped at limit.",
|
|
735
|
+
items: {
|
|
736
|
+
type: "object",
|
|
737
|
+
additionalProperties: false,
|
|
738
|
+
properties: {
|
|
739
|
+
timestamp: { type: "string", description: "ISO 8601 timestamp of the event, if present." },
|
|
740
|
+
source: {
|
|
741
|
+
type: "string",
|
|
742
|
+
enum: ["event", "ledger", "control"],
|
|
743
|
+
description: "Origin stream: loop event bus, governance ledger, or operator control receipts."
|
|
744
|
+
},
|
|
745
|
+
kind: { type: "string", description: "Event type or control action name." },
|
|
746
|
+
payload: { type: "object", additionalProperties: true, description: "Event-specific payload data." }
|
|
747
|
+
},
|
|
748
|
+
required: ["source", "kind", "payload"]
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
required: ["source", "sourceKind", "loopId", "logCount", "live", "entries"]
|
|
729
753
|
};
|
|
730
754
|
const controlOutputSchema = {
|
|
731
755
|
type: "object",
|
|
732
|
-
additionalProperties:
|
|
756
|
+
additionalProperties: false,
|
|
757
|
+
properties: {
|
|
758
|
+
loopId: { type: "string", description: "MartinLoop run identifier the control was applied to." },
|
|
759
|
+
action: {
|
|
760
|
+
type: "string",
|
|
761
|
+
enum: ["pause", "cancel", "continue"],
|
|
762
|
+
description: "The control action that was recorded."
|
|
763
|
+
},
|
|
764
|
+
controlId: { type: "string", description: "Unique receipt ID for this control action." },
|
|
765
|
+
requestedAt: { type: "string", description: "ISO 8601 timestamp when the control was recorded." },
|
|
766
|
+
requestedBy: { type: "string", description: "Identity that requested the control, if provided." },
|
|
767
|
+
reason: { type: "string", description: "Human-readable reason for the control, if provided." }
|
|
768
|
+
},
|
|
769
|
+
required: ["loopId", "action", "controlId", "requestedAt"]
|
|
733
770
|
};
|
|
734
771
|
const evalOutputSchema = {
|
|
735
772
|
type: "object",
|
|
736
|
-
additionalProperties:
|
|
773
|
+
additionalProperties: false,
|
|
774
|
+
properties: {
|
|
775
|
+
source: { type: "string", description: "Resolved path to the loop-record source file." },
|
|
776
|
+
sourceKind: {
|
|
777
|
+
type: "string",
|
|
778
|
+
enum: ["file", "loop_id", "latest", "runs_root"],
|
|
779
|
+
description: "How the run was identified."
|
|
780
|
+
},
|
|
781
|
+
loopId: { type: "string", description: "Unique MartinLoop run identifier." },
|
|
782
|
+
score: { type: "number", description: "Numeric evaluation score from 0–100." },
|
|
783
|
+
grade: {
|
|
784
|
+
type: "string",
|
|
785
|
+
enum: ["mergeable", "mergeable_with_review", "needs_review", "blocked", "insufficient_evidence"],
|
|
786
|
+
description: "Overall merge readiness grade derived from the six check dimensions."
|
|
787
|
+
},
|
|
788
|
+
checks: {
|
|
789
|
+
type: "object",
|
|
790
|
+
additionalProperties: false,
|
|
791
|
+
properties: {
|
|
792
|
+
taskCompletion: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether the run reached a completed status." },
|
|
793
|
+
verifier: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether automated verifiers passed." },
|
|
794
|
+
diffDiscipline: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether file-scope discipline was maintained." },
|
|
795
|
+
regressionRisk: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether regression risk signals were detected." },
|
|
796
|
+
securityRisk: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether security risk signals were detected." },
|
|
797
|
+
reviewability: { type: "string", enum: ["passed", "warning", "failed"], description: "Whether the PR body and dossier are reviewer-ready." }
|
|
798
|
+
},
|
|
799
|
+
required: ["taskCompletion", "verifier", "diffDiscipline", "regressionRisk", "securityRisk", "reviewability"]
|
|
800
|
+
},
|
|
801
|
+
warnings: { type: "array", items: { type: "string" }, description: "Non-blocking advisory warnings from the evaluation." },
|
|
802
|
+
summary: { type: "string", description: "One-paragraph plain-English evaluation summary." }
|
|
803
|
+
},
|
|
804
|
+
required: ["source", "sourceKind", "loopId", "score", "grade", "checks", "warnings", "summary"]
|
|
737
805
|
};
|
|
738
806
|
const prSummaryOutputSchema = {
|
|
739
807
|
type: "object",
|
|
740
|
-
additionalProperties:
|
|
808
|
+
additionalProperties: false,
|
|
809
|
+
properties: {
|
|
810
|
+
loopId: { type: "string", description: "MartinLoop run identifier used to generate the summary." },
|
|
811
|
+
title: { type: "string", description: "Suggested GitHub pull-request title." },
|
|
812
|
+
body: { type: "string", description: "GitHub-flavoured Markdown PR body containing the run dossier." },
|
|
813
|
+
grade: {
|
|
814
|
+
type: "string",
|
|
815
|
+
enum: ["mergeable", "mergeable_with_review", "needs_review", "blocked", "insufficient_evidence"],
|
|
816
|
+
description: "Verification grade assigned to the run."
|
|
817
|
+
},
|
|
818
|
+
score: { type: "number", description: "Numeric verification score from 0–100." }
|
|
819
|
+
},
|
|
820
|
+
required: ["loopId", "title", "body", "grade", "score"]
|
|
741
821
|
};
|
|
742
822
|
const prReviewOutputSchema = {
|
|
743
823
|
type: "object",
|
|
744
|
-
additionalProperties:
|
|
824
|
+
additionalProperties: false,
|
|
825
|
+
properties: {
|
|
826
|
+
loopId: { type: "string", description: "MartinLoop run identifier the review was performed against." },
|
|
827
|
+
verdict: {
|
|
828
|
+
type: "string",
|
|
829
|
+
enum: ["approve_with_review", "needs_changes", "blocked"],
|
|
830
|
+
description: "Merge verdict: approve_with_review means safe to merge with human review; needs_changes requires fixes; blocked means do not merge."
|
|
831
|
+
},
|
|
832
|
+
findings: {
|
|
833
|
+
type: "array",
|
|
834
|
+
items: { type: "string" },
|
|
835
|
+
description: "Specific findings that informed the verdict."
|
|
836
|
+
},
|
|
837
|
+
summary: { type: "string", description: "Plain-English review summary." }
|
|
838
|
+
},
|
|
839
|
+
required: ["loopId", "verdict", "findings", "summary"]
|
|
745
840
|
};
|
|
746
841
|
export function createMartinMcpServer(serverInfo) {
|
|
747
842
|
const server = new Server({
|
|
748
843
|
name: serverInfo?.name ?? "martin-loop",
|
|
749
844
|
version: serverInfo?.version ?? MARTIN_MCP_PACKAGE_VERSION
|
|
750
|
-
}, {
|
|
845
|
+
}, {
|
|
846
|
+
capabilities: {
|
|
847
|
+
tools: {},
|
|
848
|
+
resources: {},
|
|
849
|
+
prompts: {},
|
|
850
|
+
extensions: {
|
|
851
|
+
[MARTIN_ARCADE_UI_EXTENSION_ID]: { mimeTypes: [MARTIN_ARCADE_MIME_TYPE] }
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
});
|
|
751
855
|
server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
752
856
|
tools: [
|
|
753
857
|
{
|
|
@@ -1046,20 +1150,26 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1046
1150
|
},
|
|
1047
1151
|
{
|
|
1048
1152
|
name: "martin_logs",
|
|
1049
|
-
description: "Read recent
|
|
1153
|
+
description: "Read recent MartinLoop events, ledger entries, and operator control receipts for a single run. " +
|
|
1154
|
+
"Use to observe live or completed run activity, diagnose stuck or failed runs, or audit operator actions. " +
|
|
1155
|
+
"Do not use to check run completion status — use martin_get_status instead. " +
|
|
1156
|
+
"Do not use to retrieve verification evidence — use martin_get_verification_results instead. " +
|
|
1157
|
+
"This tool only reads persisted run files and does not execute commands, modify state, or contact GitHub.",
|
|
1050
1158
|
annotations: {
|
|
1051
1159
|
readOnlyHint: true,
|
|
1052
|
-
|
|
1160
|
+
destructiveHint: false,
|
|
1161
|
+
idempotentHint: true,
|
|
1162
|
+
openWorldHint: false
|
|
1053
1163
|
},
|
|
1054
1164
|
inputSchema: {
|
|
1055
1165
|
type: "object",
|
|
1056
1166
|
additionalProperties: false,
|
|
1057
1167
|
properties: {
|
|
1058
|
-
file: { type: "string" },
|
|
1059
|
-
loopId: { type: "string" },
|
|
1060
|
-
runsDir: { type: "string" },
|
|
1061
|
-
latest: { const: true },
|
|
1062
|
-
limit: { type: "integer", minimum: 1 }
|
|
1168
|
+
file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
|
|
1169
|
+
loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
|
|
1170
|
+
runsDir: { type: "string", description: "Override the default run-store root directory. Optional; defaults to the MartinLoop runs directory." },
|
|
1171
|
+
latest: { const: true, description: "When true, loads the most recently updated run in the run store. Mutually exclusive with file and loopId." },
|
|
1172
|
+
limit: { type: "integer", minimum: 1, description: "Maximum number of log entries to return, sorted by timestamp descending. Defaults to 20." }
|
|
1063
1173
|
},
|
|
1064
1174
|
oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
|
|
1065
1175
|
},
|
|
@@ -1255,10 +1365,16 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1255
1365
|
},
|
|
1256
1366
|
{
|
|
1257
1367
|
name: "martin_get_verification_results",
|
|
1258
|
-
description: "Load verification evidence for a
|
|
1368
|
+
description: "Load structured verification evidence for a MartinLoop run — verifier commands, pass/fail outcomes, and contradiction signals — from stored loop events and ledger entries. " +
|
|
1369
|
+
"Use after a run completes to confirm whether automated verifiers passed before merging or promoting the result. " +
|
|
1370
|
+
"Do not use to get a merge-readiness grade — use martin_eval for a scored grade with six check dimensions. " +
|
|
1371
|
+
"Do not use for live run status — use martin_get_status instead. " +
|
|
1372
|
+
"This tool only reads persisted run files and does not execute commands, modify state, or contact GitHub.",
|
|
1259
1373
|
annotations: {
|
|
1260
1374
|
readOnlyHint: true,
|
|
1261
|
-
|
|
1375
|
+
destructiveHint: false,
|
|
1376
|
+
idempotentHint: true,
|
|
1377
|
+
openWorldHint: false
|
|
1262
1378
|
},
|
|
1263
1379
|
inputSchema: {
|
|
1264
1380
|
type: "object",
|
|
@@ -1266,10 +1382,10 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1266
1382
|
properties: {
|
|
1267
1383
|
file: {
|
|
1268
1384
|
type: "string",
|
|
1269
|
-
description: "
|
|
1385
|
+
description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId."
|
|
1270
1386
|
},
|
|
1271
|
-
loopId: { type: "string", description: "
|
|
1272
|
-
runsDir: { type: "string", description: "
|
|
1387
|
+
loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file." },
|
|
1388
|
+
runsDir: { type: "string", description: "Override the default run-store root directory. Optional." }
|
|
1273
1389
|
},
|
|
1274
1390
|
oneOf: [{ required: ["file"] }, { required: ["loopId"] }]
|
|
1275
1391
|
},
|
|
@@ -1328,19 +1444,26 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1328
1444
|
},
|
|
1329
1445
|
{
|
|
1330
1446
|
name: "martin_eval",
|
|
1331
|
-
description: "Grade a
|
|
1447
|
+
description: "Grade a MartinLoop run across six dimensions — task completion, verifier health, diff discipline, regression risk, security risk, and reviewability — and return a scored merge-readiness verdict. " +
|
|
1448
|
+
"Use after a governed run completes to decide whether the result is safe to merge or promote. " +
|
|
1449
|
+
"Use before martin_pr_summary or martin_create_pr to confirm the run is merge-ready. " +
|
|
1450
|
+
"Do not use to retrieve raw verification command output — use martin_get_verification_results for that. " +
|
|
1451
|
+
"Do not use to review an existing PR body — use martin_review_pr instead. " +
|
|
1452
|
+
"This tool reads saved run evidence and inspects local git signals; it does not modify state or contact GitHub.",
|
|
1332
1453
|
annotations: {
|
|
1333
1454
|
readOnlyHint: true,
|
|
1334
|
-
|
|
1455
|
+
destructiveHint: false,
|
|
1456
|
+
idempotentHint: true,
|
|
1457
|
+
openWorldHint: false
|
|
1335
1458
|
},
|
|
1336
1459
|
inputSchema: {
|
|
1337
1460
|
type: "object",
|
|
1338
1461
|
additionalProperties: false,
|
|
1339
1462
|
properties: {
|
|
1340
|
-
file: { type: "string" },
|
|
1341
|
-
loopId: { type: "string" },
|
|
1342
|
-
runsDir: { type: "string" },
|
|
1343
|
-
latest: { const: true }
|
|
1463
|
+
file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
|
|
1464
|
+
loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
|
|
1465
|
+
runsDir: { type: "string", description: "Override the default run-store root directory. Optional." },
|
|
1466
|
+
latest: { const: true, description: "When true, evaluates the most recently updated run in the run store. Mutually exclusive with file and loopId." }
|
|
1344
1467
|
},
|
|
1345
1468
|
oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
|
|
1346
1469
|
},
|
|
@@ -1348,20 +1471,27 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1348
1471
|
},
|
|
1349
1472
|
{
|
|
1350
1473
|
name: "martin_pr_summary",
|
|
1351
|
-
description: "Generate a
|
|
1474
|
+
description: "Generate a GitHub-ready pull-request title and Markdown body from a completed MartinLoop run dossier, including its verification grade and score. " +
|
|
1475
|
+
"Use after a governed run completes when you need PR copy without creating the PR. " +
|
|
1476
|
+
"Use martin_create_pr instead to actually open the PR on GitHub. " +
|
|
1477
|
+
"Use martin_review_pr to evaluate an existing PR body against run evidence. " +
|
|
1478
|
+
"Use martin_eval first if you need a merge-readiness grade before generating the PR body. " +
|
|
1479
|
+
"This tool only reads saved run evidence and does not modify the repository or contact GitHub.",
|
|
1352
1480
|
annotations: {
|
|
1353
1481
|
readOnlyHint: true,
|
|
1354
|
-
|
|
1482
|
+
destructiveHint: false,
|
|
1483
|
+
idempotentHint: true,
|
|
1484
|
+
openWorldHint: false
|
|
1355
1485
|
},
|
|
1356
1486
|
inputSchema: {
|
|
1357
1487
|
type: "object",
|
|
1358
1488
|
additionalProperties: false,
|
|
1359
1489
|
properties: {
|
|
1360
|
-
file: { type: "string" },
|
|
1361
|
-
loopId: { type: "string" },
|
|
1362
|
-
runsDir: { type: "string" },
|
|
1363
|
-
latest: { const: true },
|
|
1364
|
-
format: { type: "string", enum: ["json", "md", "github-pr"] }
|
|
1490
|
+
file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
|
|
1491
|
+
loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
|
|
1492
|
+
runsDir: { type: "string", description: "Override the default run-store root directory. Optional." },
|
|
1493
|
+
latest: { const: true, description: "When true, generates the summary for the most recently updated run. Mutually exclusive with file and loopId." },
|
|
1494
|
+
format: { type: "string", enum: ["json", "md", "github-pr"], description: "Dossier rendering format. Defaults to github-pr for PR body generation. Use md for plain Markdown or json for structured output." }
|
|
1365
1495
|
},
|
|
1366
1496
|
oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
|
|
1367
1497
|
},
|
|
@@ -1393,21 +1523,28 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1393
1523
|
},
|
|
1394
1524
|
{
|
|
1395
1525
|
name: "martin_review_pr",
|
|
1396
|
-
description: "Review a PR or
|
|
1526
|
+
description: "Review a PR body or draft against the MartinLoop run dossier and evaluation evidence, and return a verdict with specific findings. " +
|
|
1527
|
+
"Use when you have an existing PR body and want to check whether it accurately represents the governed run evidence. " +
|
|
1528
|
+
"Supply prBody to review a specific body string; omit it to evaluate the auto-generated dossier body. " +
|
|
1529
|
+
"Do not use to generate a PR body from scratch — use martin_pr_summary instead. " +
|
|
1530
|
+
"Do not use to open or create a PR — use martin_create_pr instead. " +
|
|
1531
|
+
"This tool only reads saved run evidence and does not modify the repository or contact GitHub.",
|
|
1397
1532
|
annotations: {
|
|
1398
1533
|
readOnlyHint: true,
|
|
1399
|
-
|
|
1534
|
+
destructiveHint: false,
|
|
1535
|
+
idempotentHint: true,
|
|
1536
|
+
openWorldHint: false
|
|
1400
1537
|
},
|
|
1401
1538
|
inputSchema: {
|
|
1402
1539
|
type: "object",
|
|
1403
1540
|
additionalProperties: false,
|
|
1404
1541
|
properties: {
|
|
1405
|
-
file: { type: "string" },
|
|
1406
|
-
loopId: { type: "string" },
|
|
1407
|
-
runsDir: { type: "string" },
|
|
1408
|
-
latest: { const: true },
|
|
1409
|
-
format: { type: "string", enum: ["json", "md", "github-pr"] },
|
|
1410
|
-
prBody: { type: "string" }
|
|
1542
|
+
file: { type: "string", description: "Absolute or relative path to a loop-record.json file or run directory. Mutually exclusive with loopId and latest." },
|
|
1543
|
+
loopId: { type: "string", description: "MartinLoop run identifier from the run store. Mutually exclusive with file and latest." },
|
|
1544
|
+
runsDir: { type: "string", description: "Override the default run-store root directory. Optional." },
|
|
1545
|
+
latest: { const: true, description: "When true, reviews against the most recently updated run. Mutually exclusive with file and loopId." },
|
|
1546
|
+
format: { type: "string", enum: ["json", "md", "github-pr"], description: "Dossier format used when generating the reference body for comparison. Defaults to github-pr." },
|
|
1547
|
+
prBody: { type: "string", description: "The PR body text to review. If omitted, the auto-generated dossier body is evaluated instead." }
|
|
1411
1548
|
},
|
|
1412
1549
|
oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
|
|
1413
1550
|
},
|
|
@@ -1469,17 +1606,23 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1469
1606
|
},
|
|
1470
1607
|
required: ["objective", "engine", "budgetUsd", "selectedMode", "confidence", "expectedCostUsd", "expectedPreworkBurnPct", "reason"]
|
|
1471
1608
|
}
|
|
1472
|
-
}
|
|
1609
|
+
},
|
|
1610
|
+
...buildArcadeToolDefinitions(server.getClientCapabilities())
|
|
1473
1611
|
]
|
|
1474
1612
|
}));
|
|
1475
|
-
server.setRequestHandler(ListResourcesRequestSchema, () =>
|
|
1476
|
-
|
|
1477
|
-
|
|
1613
|
+
server.setRequestHandler(ListResourcesRequestSchema, () => {
|
|
1614
|
+
const listed = listMartinResources();
|
|
1615
|
+
const arcade = listArcadeResources(server.getClientCapabilities());
|
|
1616
|
+
return { resources: [...listed.resources, ...arcade.resources] };
|
|
1617
|
+
});
|
|
1478
1618
|
server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
|
|
1479
1619
|
...listMartinResourceTemplates()
|
|
1480
1620
|
}));
|
|
1481
1621
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1482
1622
|
try {
|
|
1623
|
+
if (request.params.uri === MARTIN_ARCADE_RESOURCE_URI) {
|
|
1624
|
+
return await readArcadeResource(request.params.uri);
|
|
1625
|
+
}
|
|
1483
1626
|
return await readMartinResource({ uri: request.params.uri });
|
|
1484
1627
|
}
|
|
1485
1628
|
catch (error) {
|
|
@@ -1509,6 +1652,29 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1509
1652
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1510
1653
|
const { name, arguments: args } = request.params;
|
|
1511
1654
|
try {
|
|
1655
|
+
if (name === "martin_arcade") {
|
|
1656
|
+
const available = supportsMartinArcadeApp(server.getClientCapabilities());
|
|
1657
|
+
return createToolSuccessResult({
|
|
1658
|
+
available,
|
|
1659
|
+
resourceUri: available ? MARTIN_ARCADE_RESOURCE_URI : null
|
|
1660
|
+
}, MARTIN_ARCADE_FALLBACK_TEXT, { human: MARTIN_ARCADE_FALLBACK_TEXT });
|
|
1661
|
+
}
|
|
1662
|
+
if (name === "martin_arcade_status") {
|
|
1663
|
+
const input = args ?? {};
|
|
1664
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
1665
|
+
throw new MartinToolError("invalid_arguments", "Arcade status input must be an object.", {
|
|
1666
|
+
category: "invalid_input"
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
const keys = Object.keys(input);
|
|
1670
|
+
if (keys.some((key) => key !== "loopId") || ("loopId" in input && typeof input.loopId !== "string")) {
|
|
1671
|
+
throw new MartinToolError("invalid_arguments", "Arcade status accepts only an optional loopId.", {
|
|
1672
|
+
category: "invalid_input"
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
const output = await readArcadeStatus(input);
|
|
1676
|
+
return createToolSuccessResult(output, `MartinLoop Arcade status for ${output.loopId}: ${output.displayOutcome ?? output.lifecycleState}.`);
|
|
1677
|
+
}
|
|
1512
1678
|
if (name === "martin_run") {
|
|
1513
1679
|
const input = validateToolInput("martin_run", args);
|
|
1514
1680
|
const runsRoot = resolveRunsRoot(process.env);
|
|
@@ -1544,7 +1710,7 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1544
1710
|
const output = await runLoopTool(input);
|
|
1545
1711
|
return createToolSuccessResult(output, [
|
|
1546
1712
|
`Run ${output.loopId} is ${output.status}/${output.lifecycleState}`,
|
|
1547
|
-
`after ${output.attempts} attempt(s);
|
|
1713
|
+
`after ${output.attempts} attempt(s); spend ${output.costUsd.toFixed(2)} USD.`,
|
|
1548
1714
|
`Execution mode: ${output.executionMode}; governance claim eligible:`,
|
|
1549
1715
|
`${output.governanceClaimEligible ? "yes" : "no"}.`
|
|
1550
1716
|
].join(" "));
|
|
@@ -1552,12 +1718,12 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1552
1718
|
if (name === "martin_inspect") {
|
|
1553
1719
|
const input = validateToolInput("martin_inspect", args);
|
|
1554
1720
|
const output = await inspectLoopTool(input);
|
|
1555
|
-
return createToolSuccessResult(output, `Inspected ${output.loopCount} run(s) from ${output.source}; total
|
|
1721
|
+
return createToolSuccessResult(output, `Inspected ${output.loopCount} run(s) from ${output.source}; total actual spend ${output.portfolio.totalActualUsd.toFixed(2)} USD.`);
|
|
1556
1722
|
}
|
|
1557
1723
|
if (name === "martin_status") {
|
|
1558
1724
|
const input = validateToolInput("martin_status", args);
|
|
1559
1725
|
const output = await getStatusTool(input);
|
|
1560
|
-
return createToolSuccessResult(output, `Loop ${output.loopId} is ${output.status}/${output.lifecycleState};
|
|
1726
|
+
return createToolSuccessResult(output, `Loop ${output.loopId} is ${output.status}/${output.lifecycleState}; pressure is ${output.pressure} with ${output.remainingBudgetUsd.toFixed(2)} USD remaining.`);
|
|
1561
1727
|
}
|
|
1562
1728
|
if (name === "martin_doctor") {
|
|
1563
1729
|
const input = validateToolInput("martin_doctor", args);
|
|
@@ -5,7 +5,7 @@ export function compileMcpExecutionPolicy(input) {
|
|
|
5
5
|
maxUsd: input.maxUsd ?? DEFAULT_BUDGET.maxUsd,
|
|
6
6
|
softLimitUsd: Math.min(DEFAULT_BUDGET.softLimitUsd, input.maxUsd ?? DEFAULT_BUDGET.maxUsd),
|
|
7
7
|
maxIterations: input.maxIterations ?? DEFAULT_BUDGET.maxIterations,
|
|
8
|
-
maxTokens: input.maxTokens
|
|
8
|
+
...(input.maxTokens !== undefined ? { maxTokens: input.maxTokens } : {})
|
|
9
9
|
};
|
|
10
10
|
const policy = compileExecutionPolicy({
|
|
11
11
|
configPath: "mcp://inline",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CostProvenance } from "../vendor/contracts/index.js";
|
|
2
1
|
import { type LoopPreview } from "./tool-support.js";
|
|
3
2
|
export interface GetStatusInput {
|
|
4
3
|
/** JSON-serialized LoopRecord. */
|
|
@@ -19,18 +18,17 @@ export interface GetStatusOutput {
|
|
|
19
18
|
lifecycleState: string;
|
|
20
19
|
attempts: number;
|
|
21
20
|
costUsd: number;
|
|
22
|
-
costProvenance: CostProvenance;
|
|
23
21
|
avoidedUsd: number;
|
|
24
22
|
pressure: string;
|
|
25
23
|
shouldStop: boolean;
|
|
26
24
|
remainingBudgetUsd: number;
|
|
27
25
|
remainingIterations: number;
|
|
28
|
-
remainingTokens
|
|
26
|
+
remainingTokens?: number;
|
|
29
27
|
budget: {
|
|
30
28
|
maxUsd: number;
|
|
31
29
|
softLimitUsd: number;
|
|
32
30
|
maxIterations: number;
|
|
33
|
-
maxTokens
|
|
31
|
+
maxTokens?: number;
|
|
34
32
|
};
|
|
35
33
|
inspection: {
|
|
36
34
|
loop: LoopPreview;
|
package/dist/tools/get-status.js
CHANGED
|
@@ -31,18 +31,21 @@ export async function getStatusTool(input) {
|
|
|
31
31
|
lifecycleState: loop.lifecycleState,
|
|
32
32
|
attempts: loop.attempts.length,
|
|
33
33
|
costUsd: loop.cost.actualUsd,
|
|
34
|
-
costProvenance: loop.cost.provenance ?? "unavailable",
|
|
35
34
|
avoidedUsd: loop.cost.avoidedUsd ?? 0,
|
|
36
35
|
pressure: costState.pressure,
|
|
37
36
|
shouldStop: costState.shouldStop,
|
|
38
37
|
remainingBudgetUsd: costState.remainingBudgetUsd,
|
|
39
38
|
remainingIterations: costState.remainingIterations,
|
|
40
|
-
|
|
39
|
+
...(costState.remainingTokens !== undefined
|
|
40
|
+
? { remainingTokens: costState.remainingTokens }
|
|
41
|
+
: {}),
|
|
41
42
|
budget: {
|
|
42
43
|
maxUsd: loop.budget.maxUsd,
|
|
43
44
|
softLimitUsd: loop.budget.softLimitUsd,
|
|
44
45
|
maxIterations: loop.budget.maxIterations,
|
|
45
|
-
|
|
46
|
+
...(loop.budget.maxTokens !== undefined
|
|
47
|
+
? { maxTokens: loop.budget.maxTokens }
|
|
48
|
+
: {})
|
|
46
49
|
},
|
|
47
50
|
inspection: {
|
|
48
51
|
loop: buildLoopPreview(loop)
|
package/dist/tools/run-loop.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SpawnLike } from "../vendor/adapters/index.js";
|
|
2
2
|
import { type RunStore } from "../vendor/core/index.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ExecutionMode, LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
|
|
4
4
|
import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, type MartinEngine } from "./tool-support.js";
|
|
5
5
|
export interface RunLoopInput {
|
|
6
6
|
objective: string;
|
|
@@ -24,7 +24,6 @@ export interface RunLoopOutput {
|
|
|
24
24
|
reason: string;
|
|
25
25
|
attempts: number;
|
|
26
26
|
costUsd: number;
|
|
27
|
-
costProvenance: CostProvenance;
|
|
28
27
|
verificationPassed: boolean;
|
|
29
28
|
executionMode: ExecutionMode;
|
|
30
29
|
governanceClaimEligible: boolean;
|
|
@@ -33,7 +32,7 @@ export interface RunLoopOutput {
|
|
|
33
32
|
shouldStop: boolean;
|
|
34
33
|
remainingBudgetUsd: number;
|
|
35
34
|
remainingIterations: number;
|
|
36
|
-
remainingTokens
|
|
35
|
+
remainingTokens?: number;
|
|
37
36
|
engine: MartinEngine;
|
|
38
37
|
workingDirectory: string;
|
|
39
38
|
budget: LoopBudget;
|
package/dist/tools/run-loop.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter,
|
|
1
|
+
import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, probeCodexLaunch, resolveCliCommandAvailability, createVerifierOnlyAdapter } from "../vendor/adapters/index.js";
|
|
2
2
|
import { createFileRunStore, evaluateCostGovernor, resolveRunsRoot, runMartin } from "../vendor/core/index.js";
|
|
3
3
|
import { normalizeSafePathPatterns, resolveSafeRepoRoot } from "../server-validation.js";
|
|
4
4
|
import { MartinToolError } from "./tool-errors.js";
|
|
@@ -166,7 +166,6 @@ export async function runLoopTool(input) {
|
|
|
166
166
|
reason: result.decision.reason,
|
|
167
167
|
attempts: result.loop.attempts.length,
|
|
168
168
|
costUsd: result.loop.cost.actualUsd,
|
|
169
|
-
costProvenance: result.loop.cost.provenance ?? "unavailable",
|
|
170
169
|
verificationPassed,
|
|
171
170
|
executionMode: evidenceExecutionMode,
|
|
172
171
|
governanceClaimEligible: evidenceExecutionMode === "governed",
|
|
@@ -175,7 +174,9 @@ export async function runLoopTool(input) {
|
|
|
175
174
|
shouldStop: costState.shouldStop,
|
|
176
175
|
remainingBudgetUsd: costState.remainingBudgetUsd,
|
|
177
176
|
remainingIterations: costState.remainingIterations,
|
|
178
|
-
|
|
177
|
+
...(costState.remainingTokens !== undefined
|
|
178
|
+
? { remainingTokens: costState.remainingTokens }
|
|
179
|
+
: {}),
|
|
179
180
|
engine,
|
|
180
181
|
workingDirectory,
|
|
181
182
|
budget,
|