@martinloop/mcp 0.2.5 → 0.3.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.
Files changed (70) hide show
  1. package/README.md +40 -132
  2. package/dist/discovery-metadata.d.ts +10 -5
  3. package/dist/discovery-metadata.js +95 -5
  4. package/dist/package-version.d.ts +1 -1
  5. package/dist/package-version.js +1 -1
  6. package/dist/prompts.d.ts +1 -1
  7. package/dist/prompts.js +93 -1
  8. package/dist/resources.d.ts +9 -1
  9. package/dist/resources.js +247 -16
  10. package/dist/server-validation.d.ts +2 -1
  11. package/dist/server-validation.js +124 -0
  12. package/dist/server.js +379 -5
  13. package/dist/tools/doctor.d.ts +14 -1
  14. package/dist/tools/doctor.js +43 -8
  15. package/dist/tools/eval.d.ts +24 -0
  16. package/dist/tools/eval.js +66 -0
  17. package/dist/tools/get-run.d.ts +2 -0
  18. package/dist/tools/get-run.js +2 -1
  19. package/dist/tools/get-status.d.ts +8 -0
  20. package/dist/tools/get-status.js +18 -0
  21. package/dist/tools/get-verification-results.d.ts +2 -0
  22. package/dist/tools/get-verification-results.js +2 -1
  23. package/dist/tools/logs.d.ts +25 -0
  24. package/dist/tools/logs.js +49 -0
  25. package/dist/tools/plan.d.ts +20 -0
  26. package/dist/tools/plan.js +10 -0
  27. package/dist/tools/pr-tools.d.ts +31 -0
  28. package/dist/tools/pr-tools.js +112 -0
  29. package/dist/tools/preflight.d.ts +24 -1
  30. package/dist/tools/preflight.js +47 -7
  31. package/dist/tools/run-controls.d.ts +36 -0
  32. package/dist/tools/run-controls.js +88 -0
  33. package/dist/tools/run-dossier.d.ts +16 -0
  34. package/dist/tools/run-dossier.js +64 -2
  35. package/dist/tools/run-loop.d.ts +3 -2
  36. package/dist/tools/run-loop.js +52 -13
  37. package/dist/tools/tool-errors.d.ts +1 -1
  38. package/dist/tools/tool-errors.js +1 -1
  39. package/dist/tools/tool-support.d.ts +6 -3
  40. package/dist/tools/tool-support.js +37 -3
  41. package/dist/tools/workflow-governance.d.ts +133 -0
  42. package/dist/tools/workflow-governance.js +581 -0
  43. package/dist/vendor/adapters/claude-cli.d.ts +25 -0
  44. package/dist/vendor/adapters/claude-cli.js +279 -19
  45. package/dist/vendor/adapters/cli-bridge.d.ts +6 -0
  46. package/dist/vendor/adapters/cli-bridge.js +58 -9
  47. package/dist/vendor/adapters/codex-launcher.d.ts +44 -0
  48. package/dist/vendor/adapters/codex-launcher.js +247 -0
  49. package/dist/vendor/adapters/index.d.ts +4 -2
  50. package/dist/vendor/adapters/index.js +4 -1
  51. package/dist/vendor/adapters/openai-compatible.d.ts +62 -0
  52. package/dist/vendor/adapters/openai-compatible.js +267 -0
  53. package/dist/vendor/adapters/runtime-support.d.ts +3 -0
  54. package/dist/vendor/adapters/runtime-support.js +8 -1
  55. package/dist/vendor/adapters/verifier-only.js +4 -3
  56. package/dist/vendor/contracts/index.d.ts +39 -0
  57. package/dist/vendor/contracts/index.js +2 -0
  58. package/dist/vendor/core/index.d.ts +23 -3
  59. package/dist/vendor/core/index.js +88 -15
  60. package/dist/vendor/core/persistence/index.d.ts +2 -0
  61. package/dist/vendor/core/persistence/index.js +1 -0
  62. package/dist/vendor/core/persistence/integrity.d.ts +38 -0
  63. package/dist/vendor/core/persistence/integrity.js +239 -0
  64. package/dist/vendor/core/persistence/store.d.ts +7 -0
  65. package/dist/vendor/core/persistence/store.js +25 -1
  66. package/dist/vendor/core/policy.d.ts +9 -0
  67. package/dist/workflow-state.d.ts +25 -0
  68. package/dist/workflow-state.js +102 -0
  69. package/package.json +3 -3
  70. package/server.json +2 -2
@@ -1,7 +1,8 @@
1
1
  /// <reference types="node" />
2
- import { appendFile, mkdir, writeFile } from "node:fs/promises";
2
+ import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
+ import { writeReceiptIntegrityMaterial } from "./integrity.js";
5
6
  // ─── FileRunStore implementation ─────────────────────────────────────────────
6
7
  export function resolveRunsRoot(env = process.env) {
7
8
  return env["MARTIN_RUNS_DIR"]?.trim() ??
@@ -31,6 +32,7 @@ export function artifactDir(runsRoot, runId, attemptIndex) {
31
32
  export function createFileRunStore(options = {}) {
32
33
  const runsRoot = options.runsRoot ?? resolveRunsRoot();
33
34
  return {
35
+ runsRoot,
34
36
  async initRun(contract) {
35
37
  const dir = runDir(runsRoot, contract.runId);
36
38
  await mkdir(dir, { recursive: true });
@@ -50,6 +52,9 @@ export function createFileRunStore(options = {}) {
50
52
  const dir = artifactDir(runsRoot, runId, attemptIndex);
51
53
  await mkdir(dir, { recursive: true });
52
54
  await writeJsonFile(join(dir, "compiled-context.json"), artifacts.compiledContext);
55
+ if (artifacts.verification !== undefined) {
56
+ await writeJsonFile(join(dir, "verification.json"), artifacts.verification);
57
+ }
53
58
  if (artifacts.diff !== undefined) {
54
59
  await writeFile(join(dir, "diff.patch"), artifacts.diff, "utf8");
55
60
  }
@@ -79,6 +84,25 @@ export function createFileRunStore(options = {}) {
79
84
  const dir = runDir(runsRoot, runId);
80
85
  await mkdir(dir, { recursive: true });
81
86
  await writeJsonFile(join(dir, "loop-record.json"), loop);
87
+ const ledgerRaw = await readFile(join(dir, "ledger.jsonl"), "utf8").catch(() => "");
88
+ const ledgerEntries = ledgerRaw
89
+ .split(/\r?\n/u)
90
+ .map((line) => line.trim())
91
+ .filter(Boolean)
92
+ .map((line) => JSON.parse(line));
93
+ await writeReceiptIntegrityMaterial({
94
+ runId,
95
+ runsRoot,
96
+ loopRecord: loop,
97
+ ledgerEntries,
98
+ scope: loop.receiptScope ??
99
+ {
100
+ ...(loop.task.repoRoot ? { repoRoot: loop.task.repoRoot } : {}),
101
+ ...(loop.task.repoRoot ? { workingDirectory: loop.task.repoRoot } : {}),
102
+ runsRoot
103
+ },
104
+ signedAt: loop.updatedAt
105
+ });
82
106
  }
83
107
  };
84
108
  }
@@ -38,6 +38,15 @@ export interface MartinAdapterResultLike {
38
38
  verification: {
39
39
  passed: boolean;
40
40
  summary: string;
41
+ steps?: Array<{
42
+ command: string;
43
+ launched: boolean;
44
+ exitCode?: number;
45
+ timedOut: boolean;
46
+ fastFail?: boolean;
47
+ detail?: string;
48
+ }>;
49
+ warnings?: string[];
41
50
  };
42
51
  failure?: {
43
52
  message: string;
@@ -0,0 +1,25 @@
1
+ type McpWorkflowStepName = "doctor" | "plan" | "preflight";
2
+ export interface RecordMcpWorkflowStepInput {
3
+ runsRoot: string;
4
+ step: McpWorkflowStepName;
5
+ workingDirectory: string;
6
+ objective?: string;
7
+ engine?: string;
8
+ verificationPlan?: string[];
9
+ }
10
+ export interface EvaluateMcpRunGateInput {
11
+ runsRoot: string;
12
+ workingDirectory: string;
13
+ objective: string;
14
+ engine?: string;
15
+ verificationPlan?: string[];
16
+ }
17
+ export interface McpRunGateResult {
18
+ allowed: boolean;
19
+ nextAction: string;
20
+ summary: string;
21
+ missingSteps: McpWorkflowStepName[];
22
+ }
23
+ export declare function recordMcpWorkflowStep(input: RecordMcpWorkflowStepInput): Promise<void>;
24
+ export declare function evaluateMcpRunGate(input: EvaluateMcpRunGateInput): Promise<McpRunGateResult>;
25
+ export {};
@@ -0,0 +1,102 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ const WORKFLOW_STATE_DIRECTORY = "_martin";
5
+ const WORKFLOW_STATE_FILENAME = "workflow-state.json";
6
+ const DOCTOR_TTL_MS = 24 * 60 * 60 * 1000;
7
+ const PLAN_TTL_MS = 24 * 60 * 60 * 1000;
8
+ const PREFLIGHT_TTL_MS = 6 * 60 * 60 * 1000;
9
+ export async function recordMcpWorkflowStep(input) {
10
+ const state = await readWorkflowState(input.runsRoot);
11
+ state.mcp ??= {};
12
+ state.mcp[input.step] = {
13
+ step: input.step,
14
+ recordedAt: new Date().toISOString(),
15
+ workingDirectory: normalizeWorkingDirectory(input.workingDirectory),
16
+ ...(input.objective ? { objectiveKey: normalizeObjective(input.objective) } : {}),
17
+ ...(input.engine ? { engine: input.engine } : {}),
18
+ ...(input.verificationPlan ? { verificationPlanKey: hashVerificationPlan(input.verificationPlan) } : {})
19
+ };
20
+ await writeWorkflowState(input.runsRoot, state);
21
+ }
22
+ export async function evaluateMcpRunGate(input) {
23
+ const state = await readWorkflowState(input.runsRoot);
24
+ const mcpState = state.mcp ?? {};
25
+ const workingDirectory = normalizeWorkingDirectory(input.workingDirectory);
26
+ const objectiveKey = normalizeObjective(input.objective);
27
+ const engine = input.engine ?? "claude";
28
+ const verificationPlanKey = hashVerificationPlan(input.verificationPlan ?? []);
29
+ const missingSteps = [];
30
+ if (!isFresh(mcpState["doctor"], DOCTOR_TTL_MS, (receipt) => receipt.workingDirectory === workingDirectory)) {
31
+ missingSteps.push("doctor");
32
+ }
33
+ if (!isFresh(mcpState["plan"], PLAN_TTL_MS, (receipt) => receipt.workingDirectory === workingDirectory &&
34
+ receipt.objectiveKey === objectiveKey)) {
35
+ missingSteps.push("plan");
36
+ }
37
+ if (!isFresh(mcpState["preflight"], PREFLIGHT_TTL_MS, (receipt) => receipt.workingDirectory === workingDirectory &&
38
+ receipt.objectiveKey === objectiveKey &&
39
+ receipt.engine === engine &&
40
+ receipt.verificationPlanKey === verificationPlanKey)) {
41
+ missingSteps.push("preflight");
42
+ }
43
+ if (missingSteps.length === 0) {
44
+ return {
45
+ allowed: true,
46
+ nextAction: "martin_run",
47
+ summary: "Martin MCP governance receipts are present for this task.",
48
+ missingSteps
49
+ };
50
+ }
51
+ const nextAction = missingSteps[0] === "doctor"
52
+ ? "Call martin_doctor for this workingDirectory before any real run."
53
+ : missingSteps[0] === "plan"
54
+ ? "Call martin_plan with the exact objective before martin_run."
55
+ : "Call martin_preflight with the exact objective, verifier plan, and engine before martin_run.";
56
+ return {
57
+ allowed: false,
58
+ nextAction,
59
+ summary: `martin_run is blocked until Martin MCP receipts exist for ${missingSteps.join(", ")}.`,
60
+ missingSteps
61
+ };
62
+ }
63
+ async function readWorkflowState(runsRoot) {
64
+ const statePath = resolveWorkflowStatePath(runsRoot);
65
+ try {
66
+ const raw = await readFile(statePath, "utf8");
67
+ const parsed = JSON.parse(raw);
68
+ return parsed.version === 1 ? parsed : { version: 1 };
69
+ }
70
+ catch {
71
+ return { version: 1 };
72
+ }
73
+ }
74
+ async function writeWorkflowState(runsRoot, state) {
75
+ const statePath = resolveWorkflowStatePath(runsRoot);
76
+ await mkdir(join(resolve(runsRoot), WORKFLOW_STATE_DIRECTORY), { recursive: true });
77
+ await writeFile(statePath, JSON.stringify(state, null, 2), "utf8");
78
+ }
79
+ function resolveWorkflowStatePath(runsRoot) {
80
+ return join(resolve(runsRoot), WORKFLOW_STATE_DIRECTORY, WORKFLOW_STATE_FILENAME);
81
+ }
82
+ function isFresh(receipt, ttlMs, predicate) {
83
+ if (!receipt || !predicate(receipt)) {
84
+ return false;
85
+ }
86
+ const recordedAt = Date.parse(receipt.recordedAt);
87
+ if (Number.isNaN(recordedAt)) {
88
+ return false;
89
+ }
90
+ return Date.now() - recordedAt <= ttlMs;
91
+ }
92
+ function normalizeWorkingDirectory(workingDirectory) {
93
+ const resolved = resolve(workingDirectory);
94
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
95
+ }
96
+ function normalizeObjective(objective) {
97
+ return objective.trim().replace(/\s+/gu, " ").toLowerCase();
98
+ }
99
+ function hashVerificationPlan(verificationPlan) {
100
+ const normalized = verificationPlan.map((step) => step.trim()).filter(Boolean);
101
+ return createHash("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
102
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@martinloop/mcp",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "mcpName": "io.github.Keesan12/martin-loop",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
8
8
  "license": "Apache-2.0",
9
- "author": "Vakeesan Mahalingam and Gobi Shanthan",
9
+ "author": "MartinLoop contributors",
10
10
  "homepage": "https://martinloop.com/",
11
11
  "repository": {
12
12
  "type": "git",
@@ -61,7 +61,7 @@
61
61
  "inspect:live": "node ./scripts/inspect-live.mjs"
62
62
  },
63
63
  "dependencies": {
64
- "@modelcontextprotocol/sdk": "^1.0.0",
64
+ "@modelcontextprotocol/sdk": "^1.29.0",
65
65
  "@open-policy-agent/opa-wasm": "^1.10.0",
66
66
  "@opentelemetry/api-logs": "^0.214.0",
67
67
  "@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
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.2.5",
10
+ "version": "0.3.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@martinloop/mcp",
15
- "version": "0.2.5",
15
+ "version": "0.3.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }