@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.
Files changed (57) hide show
  1. package/README.md +14 -52
  2. package/dist/arcade/capabilities.d.ts +36 -0
  3. package/dist/arcade/capabilities.js +37 -0
  4. package/dist/arcade/resource.d.ts +31 -0
  5. package/dist/arcade/resource.js +52 -0
  6. package/dist/arcade/status.d.ts +23 -0
  7. package/dist/arcade/status.js +25 -0
  8. package/dist/package-version.d.ts +1 -1
  9. package/dist/package-version.js +3 -3
  10. package/dist/resources.js +1 -13
  11. package/dist/server.js +241 -75
  12. package/dist/tools/execution-policy.js +1 -1
  13. package/dist/tools/get-status.d.ts +2 -4
  14. package/dist/tools/get-status.js +6 -3
  15. package/dist/tools/preflight.d.ts +1 -1
  16. package/dist/tools/run-loop.d.ts +2 -3
  17. package/dist/tools/run-loop.js +4 -3
  18. package/dist/tools/run-store.js +1 -1
  19. package/dist/tools/tool-support.d.ts +3 -2
  20. package/dist/tools/tool-support.js +9 -2
  21. package/dist/tools/workflow-governance.d.ts +1 -1
  22. package/dist/tools/workflow-governance.js +1 -1
  23. package/dist/vendor/adapters/claude-cli.js +2 -1
  24. package/dist/vendor/adapters/cli-bridge.js +7 -1
  25. package/dist/vendor/adapters/index.d.ts +1 -0
  26. package/dist/vendor/adapters/index.js +1 -0
  27. package/dist/vendor/adapters/openai-compatible.js +6 -3
  28. package/dist/vendor/adapters/stub-agent-cli.d.ts +9 -0
  29. package/dist/vendor/adapters/stub-agent-cli.js +43 -0
  30. package/dist/vendor/adapters/stub-direct-provider.d.ts +8 -0
  31. package/dist/vendor/adapters/stub-direct-provider.js +16 -0
  32. package/dist/vendor/adapters/verifier-only.js +1 -1
  33. package/dist/vendor/contracts/execution-policy.js +1 -1
  34. package/dist/vendor/contracts/exits.d.ts +1 -1
  35. package/dist/vendor/contracts/governance.d.ts +1 -1
  36. package/dist/vendor/contracts/index.d.ts +1 -1
  37. package/dist/vendor/contracts/index.js +1 -2
  38. package/dist/vendor/contracts/operator.d.ts +1 -1
  39. package/dist/vendor/contracts/operator.js +1 -2
  40. package/dist/vendor/core/compiler.d.ts +2 -2
  41. package/dist/vendor/core/compiler.js +3 -1
  42. package/dist/vendor/core/exits.js +9 -4
  43. package/dist/vendor/core/grounding.d.ts +1 -0
  44. package/dist/vendor/core/grounding.js +28 -1
  45. package/dist/vendor/core/index.d.ts +2 -2
  46. package/dist/vendor/core/index.js +11 -64
  47. package/dist/vendor/core/policy-compiler.js +17 -1
  48. package/dist/vendor/core/policy.d.ts +2 -1
  49. package/dist/vendor/core/policy.js +15 -5
  50. package/dist/vendor/core/rollback.d.ts +3 -0
  51. package/dist/vendor/core/rollback.js +33 -20
  52. package/dist/vendor/core/verified-handoff.js +25 -6
  53. package/dist/vendor/presentation/motion.js +0 -4
  54. package/dist/vendor/presentation/verified-handoff.js +10 -13
  55. package/dist/workflow-state.js +1 -1
  56. package/package.json +20 -11
  57. 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", "maxTokens"]
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: true
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: true
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: true
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: true
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: true
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
- }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
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 Martin loop events, ledger entries, and operator control receipts for live observability.",
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
- idempotentHint: true
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 Martin run from stored loop events and ledger entries.",
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
- idempotentHint: true
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: "Path to a canonical loop-record.json file or run directory."
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: "Loop ID under the run store." },
1272
- runsDir: { type: "string", description: "Optional runs-root override." }
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 Martin run for task completion, verifier health, diff discipline, risk, and reviewability.",
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
- idempotentHint: true
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 PR title and body with a MartinLoop dossier block for a completed run.",
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
- idempotentHint: true
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 PR draft against the Martin dossier and evaluation evidence.",
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
- idempotentHint: true
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
- ...listMartinResources()
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); recorded cost ${output.costUsd.toFixed(2)} USD (${describeCostProvenance(output.costProvenance)}).`,
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 recorded cost ${output.portfolio.totalActualUsd.toFixed(2)} USD.`);
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}; recorded cost is ${output.costUsd.toFixed(2)} USD (${describeCostProvenance(output.costProvenance)}), and pressure is ${output.pressure} with ${output.remainingBudgetUsd.toFixed(2)} USD remaining.`);
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 ?? DEFAULT_BUDGET.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: number;
26
+ remainingTokens?: number;
29
27
  budget: {
30
28
  maxUsd: number;
31
29
  softLimitUsd: number;
32
30
  maxIterations: number;
33
- maxTokens: number;
31
+ maxTokens?: number;
34
32
  };
35
33
  inspection: {
36
34
  loop: LoopPreview;
@@ -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
- remainingTokens: costState.remainingTokens,
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
- maxTokens: loop.budget.maxTokens
46
+ ...(loop.budget.maxTokens !== undefined
47
+ ? { maxTokens: loop.budget.maxTokens }
48
+ : {})
46
49
  },
47
50
  inspection: {
48
51
  loop: buildLoopPreview(loop)
@@ -51,7 +51,7 @@ export interface MartinPreflightOutput {
51
51
  maxUsd: number;
52
52
  softLimitUsd: number;
53
53
  maxIterations: number;
54
- maxTokens: number;
54
+ maxTokens?: number;
55
55
  };
56
56
  verificationPlan: string[];
57
57
  allowedPaths?: string[];
@@ -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 { CostProvenance, ExecutionMode, LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
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: number;
35
+ remainingTokens?: number;
37
36
  engine: MartinEngine;
38
37
  workingDirectory: string;
39
38
  budget: LoopBudget;
@@ -1,4 +1,4 @@
1
- import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, createVerifierOnlyAdapter, probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
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
- remainingTokens: costState.remainingTokens,
177
+ ...(costState.remainingTokens !== undefined
178
+ ? { remainingTokens: costState.remainingTokens }
179
+ : {}),
179
180
  engine,
180
181
  workingDirectory,
181
182
  budget,