@martinloop/mcp 0.3.9 → 0.5.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 (96) hide show
  1. package/README.md +82 -141
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +1 -1
  4. package/dist/resources.d.ts +1 -2
  5. package/dist/resources.js +47 -95
  6. package/dist/server-validation.d.ts +1 -1
  7. package/dist/server-validation.js +16 -11
  8. package/dist/server.d.ts +23 -0
  9. package/dist/server.js +152 -7
  10. package/dist/tools/preflight.d.ts +2 -0
  11. package/dist/tools/preflight.js +42 -2
  12. package/dist/tools/run-dossier.d.ts +3 -1
  13. package/dist/tools/run-dossier.js +40 -2
  14. package/dist/tools/run-loop.d.ts +1 -4
  15. package/dist/tools/run-loop.js +6 -15
  16. package/dist/tools/tool-support.d.ts +2 -1
  17. package/dist/tools/tool-support.js +1 -12
  18. package/dist/vendor/adapters/claude-cli.js +38 -35
  19. package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
  20. package/dist/vendor/adapters/cli-bridge.js +54 -13
  21. package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
  22. package/dist/vendor/adapters/codex-launcher.js +118 -19
  23. package/dist/vendor/adapters/index.d.ts +1 -3
  24. package/dist/vendor/adapters/index.js +1 -3
  25. package/dist/vendor/adapters/openai-compatible.js +51 -12
  26. package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
  27. package/dist/vendor/contracts/context-handoff.d.ts +100 -0
  28. package/dist/vendor/contracts/context-handoff.js +11 -0
  29. package/dist/vendor/contracts/context-manifest.d.ts +195 -0
  30. package/dist/vendor/contracts/context-manifest.js +17 -0
  31. package/dist/vendor/contracts/context-shadow.d.ts +75 -0
  32. package/dist/vendor/contracts/context-shadow.js +8 -0
  33. package/dist/vendor/contracts/delivery.d.ts +47 -0
  34. package/dist/vendor/contracts/delivery.js +18 -0
  35. package/dist/vendor/contracts/exits.d.ts +126 -0
  36. package/dist/vendor/contracts/exits.js +32 -0
  37. package/dist/vendor/contracts/index.d.ts +41 -2
  38. package/dist/vendor/contracts/index.js +24 -0
  39. package/dist/vendor/contracts/mission.d.ts +102 -0
  40. package/dist/vendor/contracts/mission.js +64 -0
  41. package/dist/vendor/contracts/operator.d.ts +3 -1
  42. package/dist/vendor/contracts/operator.js +2 -1
  43. package/dist/vendor/contracts/verified-handoff.d.ts +100 -0
  44. package/dist/vendor/contracts/verified-handoff.js +35 -0
  45. package/dist/vendor/core/context-chain-gate.d.ts +89 -0
  46. package/dist/vendor/core/context-chain-gate.js +173 -0
  47. package/dist/vendor/core/context-compiler.d.ts +53 -0
  48. package/dist/vendor/core/context-compiler.js +261 -0
  49. package/dist/vendor/core/context-handoff.d.ts +42 -0
  50. package/dist/vendor/core/context-handoff.js +167 -0
  51. package/dist/vendor/core/context-shadow.d.ts +39 -0
  52. package/dist/vendor/core/context-shadow.js +200 -0
  53. package/dist/vendor/core/delivery/index.d.ts +6 -0
  54. package/dist/vendor/core/delivery/index.js +4 -0
  55. package/dist/vendor/core/delivery/message-client.d.ts +18 -0
  56. package/dist/vendor/core/delivery/message-client.js +38 -0
  57. package/dist/vendor/core/delivery/message-ledger.d.ts +35 -0
  58. package/dist/vendor/core/delivery/message-ledger.js +97 -0
  59. package/dist/vendor/core/delivery/message-schema.d.ts +16 -0
  60. package/dist/vendor/core/delivery/message-schema.js +111 -0
  61. package/dist/vendor/core/delivery/update-check.d.ts +19 -0
  62. package/dist/vendor/core/delivery/update-check.js +107 -0
  63. package/dist/vendor/core/exit-signal.d.ts +88 -0
  64. package/dist/vendor/core/exit-signal.js +339 -0
  65. package/dist/vendor/core/exits.d.ts +41 -0
  66. package/dist/vendor/core/exits.js +258 -0
  67. package/dist/vendor/core/index.d.ts +44 -1
  68. package/dist/vendor/core/index.js +1088 -662
  69. package/dist/vendor/core/mission/aggregate.d.ts +27 -0
  70. package/dist/vendor/core/mission/aggregate.js +42 -0
  71. package/dist/vendor/core/mission/index.d.ts +2 -0
  72. package/dist/vendor/core/mission/index.js +1 -0
  73. package/dist/vendor/core/persistence/compiler.d.ts +8 -0
  74. package/dist/vendor/core/persistence/compiler.js +156 -0
  75. package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
  76. package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
  77. package/dist/vendor/core/persistence/index.d.ts +4 -0
  78. package/dist/vendor/core/persistence/index.js +2 -0
  79. package/dist/vendor/core/persistence/ledger.d.ts +1 -1
  80. package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
  81. package/dist/vendor/core/persistence/mission-store.js +281 -0
  82. package/dist/vendor/core/policy.d.ts +2 -0
  83. package/dist/vendor/core/policy.js +5 -2
  84. package/dist/vendor/core/rollback.js +15 -1
  85. package/dist/vendor/core/savings.d.ts +13 -0
  86. package/dist/vendor/core/savings.js +22 -0
  87. package/dist/vendor/core/termination-store.d.ts +15 -0
  88. package/dist/vendor/core/termination-store.js +44 -0
  89. package/dist/vendor/core/verified-handoff.d.ts +67 -0
  90. package/dist/vendor/core/verified-handoff.js +210 -0
  91. package/package.json +3 -13
  92. package/server.json +3 -3
  93. package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
  94. package/dist/vendor/adapters/stub-agent-cli.js +0 -40
  95. package/dist/vendor/adapters/verifier-only.d.ts +0 -9
  96. package/dist/vendor/adapters/verifier-only.js +0 -63
@@ -0,0 +1,210 @@
1
+ // SPDX-FileCopyrightText: MartinLoop contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { createHash } from "node:crypto";
4
+ // ---------------------------------------------------------------------------
5
+ // Mapping: granular TestIntegrityStatus → public TestIntegrityVerdict
6
+ //
7
+ // A new enum value MUST appear here or TypeScript compilation will fail,
8
+ // ensuring the mapping is always exhaustive.
9
+ // ---------------------------------------------------------------------------
10
+ export function toTestIntegrityVerdict(status) {
11
+ switch (status) {
12
+ case "UNCHANGED":
13
+ case "AUTHORIZED_CHANGE":
14
+ return "VERIFIED";
15
+ case "PREVENTED":
16
+ case "DETECTED_AND_ROLLED_BACK":
17
+ case "DETECTED_NEEDS_REVIEW":
18
+ return "TAMPERING_DETECTED";
19
+ case "NOT_EVALUATED":
20
+ return "NOT_EVALUATED";
21
+ }
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // Outcome resolution — deterministic, no hidden defaults
25
+ // ---------------------------------------------------------------------------
26
+ const STOPPED_LIFECYCLE_STATES = new Set([
27
+ "budget_exit",
28
+ "diminishing_returns",
29
+ "stuck_exit",
30
+ "human_escalation",
31
+ ]);
32
+ export function verifierActuallyPassed(evidence, expected) {
33
+ if (!evidence?.passed || !evidence.binding || !evidence.steps) {
34
+ return false;
35
+ }
36
+ if (evidence.binding.runId !== expected.runId ||
37
+ evidence.binding.workspaceId !== expected.workspaceId ||
38
+ evidence.binding.cwd !== expected.cwd ||
39
+ JSON.stringify(evidence.binding.commands) !== JSON.stringify(expected.commands)) {
40
+ return false;
41
+ }
42
+ if (evidence.steps.length !== expected.commands.length) {
43
+ return false;
44
+ }
45
+ return evidence.steps.every((step, index) => step.command === expected.commands[index] &&
46
+ step.launched === true &&
47
+ step.completed === true &&
48
+ step.crashed === false &&
49
+ step.timedOut !== true &&
50
+ step.exitCode === 0);
51
+ }
52
+ export function resolveVerifiedHandoffOutcome(input) {
53
+ if (STOPPED_LIFECYCLE_STATES.has(input.lifecycleState) ||
54
+ input.executionStatus === "write_blocked" ||
55
+ input.executionStatus === "sandbox_blocked" ||
56
+ input.executionStatus === "policy_rejected") {
57
+ return "STOPPED";
58
+ }
59
+ if (input.executionStatus === "failed" ||
60
+ input.evidenceContradicted === true ||
61
+ (input.mutationRequired === true &&
62
+ (input.changedFileCount ?? 0) === 0 &&
63
+ input.definitionOfDonePreSatisfied !== true)) {
64
+ return "NEEDS_REVIEW";
65
+ }
66
+ const evidenceTrustworthy = input.receiptIntegrity === "verified";
67
+ const scopeAcceptable = input.scopeStatus === undefined ||
68
+ input.scopeStatus === "WITHIN_SCOPE" ||
69
+ input.scopeStatus === "NOT_EVALUATED";
70
+ const testIntegrityAcceptable = input.testIntegrityStatus === undefined ||
71
+ input.testIntegrityStatus === "UNCHANGED" ||
72
+ input.testIntegrityStatus === "AUTHORIZED_CHANGE" ||
73
+ input.testIntegrityStatus === "NOT_EVALUATED";
74
+ if (input.verificationStatus === "passed" &&
75
+ evidenceTrustworthy &&
76
+ scopeAcceptable &&
77
+ testIntegrityAcceptable &&
78
+ input.unresolvedWorkCount === 0) {
79
+ return "VERIFIED";
80
+ }
81
+ return "NEEDS_REVIEW";
82
+ }
83
+ function toEvidenceStatus(status) {
84
+ switch (status) {
85
+ case "passed":
86
+ return "PASSED";
87
+ case "failed":
88
+ return "FAILED";
89
+ case "contradicted":
90
+ return "CONTRADICTED";
91
+ case "not_run":
92
+ return "NOT_RUN";
93
+ }
94
+ }
95
+ function toCheck(step) {
96
+ const status = !step.launched
97
+ ? "NOT_RUN"
98
+ : step.timedOut
99
+ ? "FAILED"
100
+ : step.exitCode === 0
101
+ ? "PASSED"
102
+ : "FAILED";
103
+ return {
104
+ command: step.command,
105
+ status,
106
+ ...(step.exitCode !== undefined ? { exitCode: step.exitCode } : {}),
107
+ ...(step.timedOut !== undefined ? { timedOut: step.timedOut } : {}),
108
+ ...(step.detail ? { detail: step.detail } : {}),
109
+ };
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // Main builder
113
+ // ---------------------------------------------------------------------------
114
+ export function buildVerifiedHandoff(input) {
115
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
116
+ const changedFiles = input.changedFiles ?? [];
117
+ const unresolvedWork = input.unresolvedWork ?? [];
118
+ const scope = {
119
+ status: input.scope?.status ?? "NOT_EVALUATED",
120
+ allowedPaths: input.scope?.allowedPaths ?? input.loop.task.allowedPaths ?? [],
121
+ deniedPaths: input.scope?.deniedPaths ?? input.loop.task.deniedPaths ?? [],
122
+ changedFiles: input.scope?.changedFiles ?? changedFiles,
123
+ violations: input.scope?.violations ?? [],
124
+ };
125
+ const testIntegrityStatus = input.testIntegrity?.status ?? "NOT_EVALUATED";
126
+ const testIntegrity = {
127
+ verdict: toTestIntegrityVerdict(testIntegrityStatus),
128
+ status: testIntegrityStatus,
129
+ protectedPaths: input.testIntegrity?.protectedPaths ?? [],
130
+ changedProtectedPaths: input.testIntegrity?.changedProtectedPaths ?? [],
131
+ findings: input.testIntegrity?.findings ?? [],
132
+ summary: input.testIntegrity?.summary ??
133
+ "Test integrity was not evaluated for this run.",
134
+ };
135
+ const outcome = resolveVerifiedHandoffOutcome({
136
+ lifecycleState: input.loop.lifecycleState,
137
+ executionStatus: input.executionStatus,
138
+ verificationStatus: input.verification.status,
139
+ receiptIntegrity: input.receiptIntegrity.state,
140
+ scopeStatus: scope.status,
141
+ testIntegrityStatus: testIntegrity.status,
142
+ mutationRequired: input.mutationRequired ?? input.loop.task.mutationMode === "edit",
143
+ changedFileCount: scope.changedFiles.length,
144
+ definitionOfDonePreSatisfied: input.definitionOfDonePreSatisfied,
145
+ evidenceContradicted: input.evidenceContradicted ?? input.verification.status === "contradicted",
146
+ unresolvedWorkCount: unresolvedWork.length,
147
+ });
148
+ const handoffId = `vh_${createHash("sha256")
149
+ .update(`${input.loop.loopId}:${generatedAt}`)
150
+ .digest("hex")
151
+ .slice(0, 16)}`;
152
+ return {
153
+ schemaVersion: "1.0.0",
154
+ handoffId,
155
+ loopId: input.loop.loopId,
156
+ generatedAt,
157
+ task: {
158
+ title: input.loop.task.title,
159
+ objective: input.loop.task.objective,
160
+ },
161
+ definitionOfDone: {
162
+ acceptanceCriteria: input.loop.task.acceptanceCriteria ?? [],
163
+ verificationPlan: input.loop.task.verificationPlan,
164
+ },
165
+ outcome,
166
+ sourceStatus: {
167
+ status: input.loop.status,
168
+ lifecycleState: input.loop.lifecycleState,
169
+ },
170
+ verification: {
171
+ status: toEvidenceStatus(input.verification.status),
172
+ summary: input.verification.summary,
173
+ checks: input.verification.steps.map(toCheck),
174
+ warnings: input.verification.warnings,
175
+ },
176
+ requirements: input.requirements ?? [],
177
+ scope,
178
+ testIntegrity,
179
+ unresolvedWork,
180
+ ...(input.stopReason ? { stopReason: input.stopReason } : {}),
181
+ recovery: {
182
+ rollbackBoundaryAvailable: input.recovery?.rollbackBoundaryAvailable ?? false,
183
+ rollbackAttempted: input.recovery?.rollbackAttempted ?? false,
184
+ ...(input.recovery?.rollbackSucceeded !== undefined
185
+ ? { rollbackSucceeded: input.recovery.rollbackSucceeded }
186
+ : {}),
187
+ ...(input.recovery?.isolatedRef
188
+ ? { isolatedRef: input.recovery.isolatedRef }
189
+ : {}),
190
+ ...(input.recovery?.nextCommand
191
+ ? { nextCommand: input.recovery.nextCommand }
192
+ : {}),
193
+ summary: input.recovery?.summary ??
194
+ "No explicit recovery evidence was supplied to the handoff builder.",
195
+ },
196
+ usage: {
197
+ attempts: input.loop.attempts.length,
198
+ actualUsd: input.loop.cost.actualUsd,
199
+ ...(input.loop.cost.estimatedUsd !== undefined
200
+ ? { estimatedUsd: input.loop.cost.estimatedUsd }
201
+ : {}),
202
+ tokensIn: input.loop.cost.tokensIn,
203
+ tokensOut: input.loop.cost.tokensOut,
204
+ costProvenance: input.loop.cost.provenance ??
205
+ "unavailable",
206
+ },
207
+ receiptIntegrity: input.receiptIntegrity,
208
+ nextAction: input.nextAction,
209
+ };
210
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@martinloop/mcp",
3
- "version": "0.3.9",
3
+ "version": "0.5.0",
4
4
  "mcpName": "io.github.Keesan12/martin-loop",
5
5
  "private": false,
6
6
  "type": "module",
7
- "description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
7
+ "description": "MCP server for AI coding agents — verifier gates, stop limits, and Verified Handoffs.",
8
8
  "license": "Apache-2.0",
9
9
  "author": "MartinLoop contributors",
10
10
  "homepage": "https://martinloop.com/",
@@ -18,18 +18,11 @@
18
18
  },
19
19
  "keywords": [
20
20
  "mcp",
21
- "mcp-server",
22
21
  "model-context-protocol",
23
22
  "martin-loop",
24
23
  "ai-agent",
25
- "ai-governance",
26
- "agent-governance",
27
24
  "claude",
28
- "claude-code",
29
25
  "codex",
30
- "cursor",
31
- "budget-guardrails",
32
- "verifier-gates",
33
26
  "martin_doctor",
34
27
  "martin_triage_runs",
35
28
  "martin_run_dossier",
@@ -66,9 +59,7 @@
66
59
  "test": "vitest run --maxWorkers=1",
67
60
  "lint": "tsc -p tsconfig.json --noEmit",
68
61
  "start": "node dist/server.js",
69
- "inspect:live": "node ./scripts/inspect-live.mjs",
70
- "mcpb:build": "node ./mcpb/build-mcpb.mjs",
71
- "mcpb:validate": "mcpb validate ./dist-mcpb/martinloop"
62
+ "inspect:live": "node ./scripts/inspect-live.mjs"
72
63
  },
73
64
  "dependencies": {
74
65
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -80,7 +71,6 @@
80
71
  "ts-morph": "^21.0.0"
81
72
  },
82
73
  "devDependencies": {
83
- "@anthropic-ai/mcpb": "2.1.2",
84
74
  "@martin/contracts": "workspace:*"
85
75
  }
86
76
  }
package/server.json CHANGED
@@ -2,17 +2,17 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.Keesan12/martin-loop",
4
4
  "title": "Martin Loop",
5
- "description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
5
+ "description": "MCP server for AI coding agents — verifier gates, stop limits, and Verified Handoffs.",
6
6
  "repository": {
7
7
  "url": "https://github.com/Keesan12/martin-loop",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.3.9",
10
+ "version": "0.5.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@martinloop/mcp",
15
- "version": "0.3.9",
15
+ "version": "0.5.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -1,8 +0,0 @@
1
- import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
2
- export interface StubAgentCliAdapterOptions {
3
- command: string[];
4
- profile?: string;
5
- label?: string;
6
- responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
7
- }
8
- export declare function createStubAgentCliAdapter(options: StubAgentCliAdapterOptions): MartinAdapter;
@@ -1,40 +0,0 @@
1
- import { createAdapterCapabilities } from "./runtime-support.js";
2
- export function createStubAgentCliAdapter(options) {
3
- const metadata = {
4
- command: options.command.join(" "),
5
- providerId: "agent-cli",
6
- model: options.profile ?? options.command[0] ?? "default",
7
- transport: "cli",
8
- capabilities: createAdapterCapabilities(),
9
- ...(options.profile ? { profile: options.profile } : {})
10
- };
11
- return {
12
- adapterId: `agent-cli:${options.command.join(":")}`,
13
- kind: "agent-cli",
14
- label: options.label ?? `Stub agent CLI (${options.command.join(" ")})`,
15
- metadata,
16
- async execute(request) {
17
- if (options.responder) {
18
- return await options.responder(request);
19
- }
20
- return {
21
- status: "failed",
22
- summary: `Stub agent CLI ${options.command.join(" ")} did not execute a live session.`,
23
- usage: {
24
- actualUsd: 0,
25
- tokensIn: 0,
26
- tokensOut: 0,
27
- provenance: "unavailable"
28
- },
29
- verification: {
30
- passed: false,
31
- summary: "No CLI execution was attempted."
32
- },
33
- failure: {
34
- message: `Agent CLI ${options.command.join(" ")} is not configured for live execution.`,
35
- classHint: "environment_mismatch"
36
- }
37
- };
38
- }
39
- };
40
- }
@@ -1,9 +0,0 @@
1
- import type { MartinAdapter } from "../core/index.js";
2
- import { type SpawnLike } from "./cli-bridge.js";
3
- export interface VerifierOnlyAdapterOptions {
4
- workingDirectory?: string;
5
- verifyTimeoutMs?: number;
6
- label?: string;
7
- spawnImpl?: SpawnLike;
8
- }
9
- export declare function createVerifierOnlyAdapter(options?: VerifierOnlyAdapterOptions): MartinAdapter;
@@ -1,63 +0,0 @@
1
- import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
2
- import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
3
- export function createVerifierOnlyAdapter(options = {}) {
4
- const workingDirectory = options.workingDirectory ?? process.cwd();
5
- const verifyTimeoutMs = options.verifyTimeoutMs ?? 120_000;
6
- return {
7
- adapterId: "direct:verifier:verify-only",
8
- kind: "direct-provider",
9
- label: options.label ?? "Verifier-only adapter",
10
- metadata: {
11
- providerId: "verifier",
12
- model: "verify-only",
13
- transport: "cli",
14
- capabilities: createAdapterCapabilities({
15
- usageSettlement: true,
16
- diffArtifacts: true
17
- })
18
- },
19
- async execute(request) {
20
- const shouldTrackVerifierWrites = request.context.verificationPlan.length > 0 ||
21
- (request.context.verificationStack?.length ?? 0) > 0;
22
- const baselineChangedFiles = shouldTrackVerifierWrites
23
- ? new Set(await readGitChangedFiles(workingDirectory, 5_000, options.spawnImpl))
24
- : new Set();
25
- const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl);
26
- const changedFiles = shouldTrackVerifierWrites
27
- ? (await readGitChangedFiles(workingDirectory, 5_000, options.spawnImpl)).filter((file) => !baselineChangedFiles.has(file))
28
- : [];
29
- const execution = { changedFiles };
30
- if (verification.passed) {
31
- return {
32
- status: "completed",
33
- summary: changedFiles.length > 0
34
- ? `Verifier-only run completed but modified files: ${changedFiles.join(", ")}`
35
- : "Verifier-only run completed without file edits.",
36
- usage: normalizeUsage({
37
- actualUsd: 0,
38
- tokensIn: 0,
39
- tokensOut: 0,
40
- provenance: "actual"
41
- }),
42
- verification,
43
- execution
44
- };
45
- }
46
- return {
47
- status: "failed",
48
- summary: "Verifier-only run failed.",
49
- usage: normalizeUsage({
50
- actualUsd: 0,
51
- tokensIn: 0,
52
- tokensOut: 0,
53
- provenance: "actual"
54
- }),
55
- verification,
56
- execution,
57
- failure: {
58
- message: verification.summary
59
- }
60
- };
61
- }
62
- };
63
- }