@martinloop/mcp 0.5.5 → 0.5.6

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.
@@ -0,0 +1,43 @@
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:stub:${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
+ }
41
+ // Internal test-compatibility re-export. Public builders remove the entire
42
+ // stub compatibility surface before constructing distributable packages.
43
+ export { createStubDirectProviderAdapter } from "./stub-direct-provider.js";
@@ -0,0 +1,8 @@
1
+ import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
2
+ export interface StubDirectProviderAdapterOptions {
3
+ providerId: string;
4
+ model: string;
5
+ label?: string;
6
+ responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
7
+ }
8
+ export declare function createStubDirectProviderAdapter(options: StubDirectProviderAdapterOptions): MartinAdapter;
@@ -0,0 +1,16 @@
1
+ import { createDirectProviderAdapter } from "./direct-provider.js";
2
+ export function createStubDirectProviderAdapter(options) {
3
+ const adapter = createDirectProviderAdapter({
4
+ providerId: options.providerId,
5
+ model: options.model,
6
+ label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
7
+ capabilities: {
8
+ workspaceMutations: false
9
+ },
10
+ responder: options.responder
11
+ });
12
+ return {
13
+ ...adapter,
14
+ adapterId: `direct:stub:${options.providerId}:${options.model}`,
15
+ };
16
+ }
@@ -1,5 +1,3 @@
1
- import { spawnSync } from "node:child_process";
2
- import { isAbsolute, relative, resolve } from "node:path";
3
1
  import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, appendLoopEvent, createLoopRecord } from "../contracts/index.js";
4
2
  import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
5
3
  import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
@@ -796,6 +794,7 @@ export async function runMartin(input) {
796
794
  const attemptCompletedAt = now();
797
795
  const compiledContext = compilePromptPacket(request);
798
796
  const expectedVerifierBinding = resolveExpectedVerifierBinding(request);
797
+ const verifierConfigured = expectedVerifierBinding.commands.length > 0;
799
798
  const verification = normalizeVerificationOutcome(result, expectedVerifierBinding);
800
799
  // PATCH → VERIFY
801
800
  currentPhase = "VERIFY";
@@ -1135,17 +1134,12 @@ export async function runMartin(input) {
1135
1134
  // Uses the task's repoRoot to build/load the grounding index, then scans any diff
1136
1135
  let groundingScanResult;
1137
1136
  const patchDiff = buildPatchDiff(result, changedFiles);
1138
- const workspaceMutationCapable = executingAdapter.metadata.capabilities?.workspaceMutations !== false;
1139
- const hasReportedOrObservedChanges = changedFiles.length > 0 || (result.execution?.changedFiles?.length ?? 0) > 0;
1140
- const groundingEvidenceRequired = result.status === "completed" && workspaceMutationCapable && hasReportedOrObservedChanges;
1141
- let groundingEvidenceAvailable = !groundingEvidenceRequired;
1142
- if (groundingEvidenceRequired && patchDiff && input.task.repoRoot) {
1137
+ if (patchDiff && input.task.repoRoot) {
1143
1138
  try {
1144
1139
  const groundingIndex = await loadOrBuildRepoGroundingIndex(input.task.repoRoot);
1145
- groundingScanResult = reconcileExistingGroundingFiles(scanPatchForGroundingViolations(patchDiff, groundingIndex, {
1140
+ groundingScanResult = scanPatchForGroundingViolations(patchDiff, groundingIndex, {
1146
1141
  allowedPaths: input.task.allowedPaths
1147
- }), input.task.repoRoot, rollbackBoundary);
1148
- groundingEvidenceAvailable = true;
1142
+ });
1149
1143
  if (input.store && groundingScanResult.violations.length > 0) {
1150
1144
  await input.store.appendLedger(loop.loopId, makeLedgerEvent({
1151
1145
  kind: "grounding.violations_found",
@@ -1161,22 +1155,10 @@ export async function runMartin(input) {
1161
1155
  }
1162
1156
  }
1163
1157
  catch {
1164
- // Availability is persisted below so scanner failures and missing
1165
- // grounding inputs follow the same recover-forward trust contract.
1158
+ // Grounding scan is best-effort — never fail the loop because of a scan error
1166
1159
  }
1167
1160
  }
1168
- if (groundingEvidenceRequired && !groundingEvidenceAvailable) {
1169
- // The completed work remains useful, but unavailable grounding evidence
1170
- // cannot be converted into a zero-violation governance claim.
1171
- loop = {
1172
- ...loop,
1173
- metadata: {
1174
- ...loop.metadata,
1175
- groundingEvidenceStatus: "unavailable"
1176
- }
1177
- };
1178
- }
1179
- const shouldEvaluatePatchTruth = result.status === "completed" && tracksWorkspaceMutations && groundingEvidenceAvailable;
1161
+ const shouldEvaluatePatchTruth = result.status === "completed" && tracksWorkspaceMutations && verifierConfigured;
1180
1162
  let patchDecision;
1181
1163
  if (shouldEvaluatePatchTruth) {
1182
1164
  patchDecision = evaluatePatchDecision({
@@ -1237,7 +1219,7 @@ export async function runMartin(input) {
1237
1219
  }
1238
1220
  else {
1239
1221
  await input.store.appendLedger(loop.loopId, makeLedgerEvent({
1240
- kind: verification.passed ? "attempt.kept" : "attempt.discarded",
1222
+ kind: verification.passed || !verifierConfigured ? "attempt.kept" : "attempt.discarded",
1241
1223
  runId: loop.loopId,
1242
1224
  attemptIndex: currentAttemptIndex,
1243
1225
  payload: { reason: verification.summary }
@@ -1314,6 +1296,7 @@ export async function runMartin(input) {
1314
1296
  lastResult: effectiveResult,
1315
1297
  lastFailure: failure,
1316
1298
  costState,
1299
+ verificationRequired: verifierConfigured,
1317
1300
  canSwitchAdapter: failure?.recommendedIntervention === "switch_adapter" &&
1318
1301
  adapterChain[currentAdapterIndex] !== undefined &&
1319
1302
  currentAdapter.adapterId !== executingAdapter.adapterId
@@ -1578,9 +1561,6 @@ function mergeCostProvenance(previous, current) {
1578
1561
  return COST_PROVENANCE_RANK[current] < COST_PROVENANCE_RANK[previous] ? current : previous;
1579
1562
  }
1580
1563
  function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
1581
- if (repoRoot && rollbackBoundary?.headRef) {
1582
- return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
1583
- }
1584
1564
  if (result.execution?.changedFiles !== undefined) {
1585
1565
  return result.execution.changedFiles;
1586
1566
  }
@@ -1601,41 +1581,6 @@ function buildPatchDiff(result, changedFiles) {
1601
1581
  }
1602
1582
  return undefined;
1603
1583
  }
1604
- function reconcileExistingGroundingFiles(scan, repoRoot, boundary) {
1605
- const resolvedFiles = new Set(scan.resolvedFiles);
1606
- const violations = scan.violations.filter((violation) => {
1607
- if (violation.kind !== "file_not_found") {
1608
- return true;
1609
- }
1610
- const absolutePath = resolve(repoRoot, violation.reference);
1611
- const relativePath = relative(repoRoot, absolutePath);
1612
- const isInsideRepo = relativePath.length > 0 && !relativePath.startsWith("..") && !isAbsolute(relativePath);
1613
- if (!isInsideRepo || !isTrackedRepoFileAtBoundary(repoRoot, violation.reference, boundary)) {
1614
- return true;
1615
- }
1616
- resolvedFiles.add(violation.reference);
1617
- return false;
1618
- });
1619
- return {
1620
- ...scan,
1621
- violations,
1622
- resolvedFiles: [...resolvedFiles]
1623
- };
1624
- }
1625
- function isTrackedRepoFileAtBoundary(repoRoot, filePath, boundary) {
1626
- if (!boundary) {
1627
- return false;
1628
- }
1629
- const normalizedPath = filePath.replace(/\\/gu, "/");
1630
- if (boundary.snapshots.some((snapshot) => snapshot.existed && snapshot.path === normalizedPath)) {
1631
- return true;
1632
- }
1633
- if (!boundary.headRef) {
1634
- return false;
1635
- }
1636
- const result = spawnSync("git", ["-C", repoRoot, "cat-file", "-e", `${boundary.headRef}:${normalizedPath}`], { encoding: "utf8", stdio: "ignore", windowsHide: true });
1637
- return result.status === 0;
1638
- }
1639
1584
  function createBudgetSettlement(input) {
1640
1585
  const totalActualUsd = getUsageUsd(input.usage);
1641
1586
  return {
@@ -97,6 +97,7 @@ export declare function inferExit(input: {
97
97
  lastFailure?: FailureAssessment;
98
98
  costState: CostGovernorState;
99
99
  canSwitchAdapter?: boolean;
100
+ verificationRequired?: boolean;
100
101
  }): ExitDecision;
101
102
  export interface BudgetPreflightInput {
102
103
  promptCharCount: number;
@@ -210,6 +210,14 @@ export function calculateCostPerOutcome(input) {
210
210
  };
211
211
  }
212
212
  export function inferExit(input) {
213
+ if (input.lastResult.status === "completed" && input.verificationRequired === false) {
214
+ return {
215
+ shouldExit: true,
216
+ lifecycleState: "completed",
217
+ status: "completed",
218
+ reason: "Execution-only work completed without verifier evidence; outcome is not VERIFIED."
219
+ };
220
+ }
213
221
  if (input.lastResult.status === "completed" && input.lastResult.verification.passed) {
214
222
  return {
215
223
  shouldExit: true,
@@ -1,4 +1,7 @@
1
1
  import type { PatchDecision, RollbackBoundaryArtifact, RollbackOutcomeArtifact } from "../contracts/index.js";
2
+ export declare class RollbackStateUnavailableError extends Error {
3
+ constructor(message: string);
4
+ }
2
5
  export declare function listAttemptChangedFilesSinceBoundary(input: {
3
6
  repoRoot?: string;
4
7
  boundary?: RollbackBoundaryArtifact;
@@ -1,7 +1,12 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { readFileSync } from "node:fs";
3
2
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
3
  import { dirname, relative, resolve } from "node:path";
4
+ export class RollbackStateUnavailableError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "RollbackStateUnavailableError";
8
+ }
9
+ }
5
10
  export function listAttemptChangedFilesSinceBoundary(input) {
6
11
  if (!input.repoRoot) {
7
12
  return [];
@@ -12,24 +17,11 @@ export function listAttemptChangedFilesSinceBoundary(input) {
12
17
  }
13
18
  const baselineTracked = new Set(input.boundary.trackedDirtyFiles);
14
19
  const baselineUntracked = new Set(input.boundary.untrackedFiles);
15
- const baselineContentChanges = input.boundary.snapshots
16
- .filter((snapshot) => snapshot.existed && repoFileDiffersFromSnapshot(input.repoRoot, snapshot))
17
- .map((snapshot) => snapshot.path);
18
20
  return uniqueSorted([
19
21
  ...repoState.trackedDirtyFiles.filter((filePath) => !baselineTracked.has(filePath)),
20
- ...repoState.untrackedFiles.filter((filePath) => !baselineUntracked.has(filePath)),
21
- ...baselineContentChanges
22
+ ...repoState.untrackedFiles.filter((filePath) => !baselineUntracked.has(filePath))
22
23
  ]);
23
24
  }
24
- function repoFileDiffersFromSnapshot(repoRoot, snapshot) {
25
- try {
26
- const current = readFileSync(resolveRepoPath(repoRoot, snapshot.path));
27
- return current.toString("base64") !== snapshot.contentBase64;
28
- }
29
- catch {
30
- return true;
31
- }
32
- }
33
25
  export async function captureRollbackBoundary(input) {
34
26
  if (!input.repoRoot) {
35
27
  return undefined;
@@ -147,6 +139,7 @@ function readRepoState(repoRoot) {
147
139
  };
148
140
  }
149
141
  function readGitLines(repoRoot, args) {
142
+ let lastFailure = "unknown Git failure";
150
143
  for (let attempt = 0; attempt < 2; attempt++) {
151
144
  const result = spawnSync("git", args, {
152
145
  cwd: repoRoot,
@@ -158,13 +151,14 @@ function readGitLines(repoRoot, args) {
158
151
  .map((line) => normalizeRepoPath(line))
159
152
  .filter(Boolean));
160
153
  }
154
+ lastFailure = gitFailureMessage(result);
161
155
  // Retry once after a short delay — handles git lock-file contention
162
156
  // that occurs when another process holds .git/index.lock.
163
157
  if (attempt === 0 && result.status !== 0) {
164
- spawnSync("sleep", ["0.5"], { encoding: "utf8" });
158
+ delaySynchronously(500);
165
159
  }
166
160
  }
167
- return [];
161
+ throw new RollbackStateUnavailableError(`Git rollback state unavailable for ${args.join(" ")}: ${lastFailure}`);
168
162
  }
169
163
  function readGitScalar(repoRoot, args) {
170
164
  const result = spawnSync("git", args, {
@@ -172,7 +166,7 @@ function readGitScalar(repoRoot, args) {
172
166
  encoding: "utf8"
173
167
  });
174
168
  if (result.status !== 0 || typeof result.stdout !== "string") {
175
- return undefined;
169
+ throw new RollbackStateUnavailableError(`Git rollback state unavailable for ${args.join(" ")}: ${gitFailureMessage(result)}`);
176
170
  }
177
171
  const value = result.stdout.trim();
178
172
  return value.length > 0 ? value : undefined;
@@ -188,7 +182,10 @@ async function readRollbackSnapshot(repoRoot, filePath) {
188
182
  contentBase64: contents.toString("base64")
189
183
  };
190
184
  }
191
- catch {
185
+ catch (error) {
186
+ if (!isFileNotFoundError(error)) {
187
+ throw new RollbackStateUnavailableError(`Rollback snapshot unavailable for ${normalizeRepoPath(filePath)}: ${toErrorMessage(error)}`);
188
+ }
192
189
  return {
193
190
  path: normalizeRepoPath(filePath),
194
191
  existed: false,
@@ -214,7 +211,7 @@ function restoreTrackedFileFromHead(repoRoot, filePath) {
214
211
  return;
215
212
  }
216
213
  // Retry once after a short delay (git lock-file contention)
217
- spawnSync("sleep", ["0.5"], { encoding: "utf8" });
214
+ delaySynchronously(500);
218
215
  const retry = spawnSync("git", ["restore", "--staged", "--worktree", "--source=HEAD", "--", filePath], { cwd: repoRoot, encoding: "utf8" });
219
216
  if (retry.status === 0) {
220
217
  return;
@@ -264,3 +261,19 @@ function emptyRepoState() {
264
261
  function toErrorMessage(error) {
265
262
  return error instanceof Error ? error.message : String(error);
266
263
  }
264
+ function gitFailureMessage(result) {
265
+ if (result.error) {
266
+ return result.error.message;
267
+ }
268
+ if (typeof result.stderr === "string" && result.stderr.trim().length > 0) {
269
+ return result.stderr.trim();
270
+ }
271
+ return `git exited with status ${String(result.status)}`;
272
+ }
273
+ function isFileNotFoundError(error) {
274
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
275
+ }
276
+ function delaySynchronously(milliseconds) {
277
+ const signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
278
+ Atomics.wait(signal, 0, 0, milliseconds);
279
+ }
@@ -33,6 +33,9 @@ export function verifierActuallyPassed(evidence, expected) {
33
33
  if (!evidence?.passed || !evidence.binding || !evidence.steps) {
34
34
  return false;
35
35
  }
36
+ if (expected.commands.length === 0) {
37
+ return false;
38
+ }
36
39
  if (evidence.binding.runId !== expected.runId ||
37
40
  evidence.binding.workspaceId !== expected.workspaceId ||
38
41
  evidence.binding.cwd !== expected.cwd ||
@@ -93,7 +96,6 @@ export function deriveVerifiedHandoffExecutionBoundary(loop) {
93
96
  const persistedMode = isExecutionMode(loop.metadata["executionMode"])
94
97
  ? loop.metadata["executionMode"]
95
98
  : undefined;
96
- const groundingEvidenceAvailable = loop.metadata["groundingEvidenceStatus"] !== "unavailable";
97
99
  const executionMode = hasSimulatedAdapter
98
100
  ? "simulated"
99
101
  : hasVerifierOnlyAdapter
@@ -101,7 +103,7 @@ export function deriveVerifiedHandoffExecutionBoundary(loop) {
101
103
  : persistedMode ?? (adapterIds.length > 0 ? "governed" : "simulated");
102
104
  return {
103
105
  executionMode,
104
- governanceClaimEligible: executionMode === "governed" && adapterIds.length > 0 && groundingEvidenceAvailable,
106
+ governanceClaimEligible: executionMode === "governed" && adapterIds.length > 0,
105
107
  };
106
108
  }
107
109
  function toEvidenceStatus(status) {
@@ -157,17 +159,34 @@ export function buildVerifiedHandoff(input) {
157
159
  "Test integrity was not evaluated for this run.",
158
160
  };
159
161
  const executionBoundary = deriveVerifiedHandoffExecutionBoundary(input.loop);
162
+ const verifierStepsActuallyPassed = input.verification.steps.length > 0 &&
163
+ input.verification.steps.every((step) => step.launched === true &&
164
+ step.completed === true &&
165
+ step.crashed === false &&
166
+ step.timedOut !== true &&
167
+ step.exitCode === 0);
168
+ const effectiveVerificationStatus = input.verification.status === "passed" && !verifierStepsActuallyPassed
169
+ ? input.verification.steps.length === 0
170
+ ? "not_run"
171
+ : "contradicted"
172
+ : input.verification.status;
173
+ const verificationWarnings = [
174
+ ...input.verification.warnings,
175
+ ...(effectiveVerificationStatus !== input.verification.status
176
+ ? ["A VERIFIED claim requires at least one launched, completed, exit-zero verifier step."]
177
+ : []),
178
+ ];
160
179
  const outcome = resolveVerifiedHandoffOutcome({
161
180
  lifecycleState: input.loop.lifecycleState,
162
181
  executionStatus: input.executionStatus,
163
- verificationStatus: input.verification.status,
182
+ verificationStatus: effectiveVerificationStatus,
164
183
  receiptIntegrity: input.receiptIntegrity.state,
165
184
  scopeStatus: scope.status,
166
185
  testIntegrityStatus: testIntegrity.status,
167
186
  mutationRequired: input.mutationRequired ?? input.loop.task.mutationMode === "edit",
168
187
  changedFileCount: scope.changedFiles.length,
169
188
  definitionOfDonePreSatisfied: input.definitionOfDonePreSatisfied,
170
- evidenceContradicted: input.evidenceContradicted ?? input.verification.status === "contradicted",
189
+ evidenceContradicted: input.evidenceContradicted ?? effectiveVerificationStatus === "contradicted",
171
190
  governanceClaimEligible: executionBoundary.governanceClaimEligible,
172
191
  unresolvedWorkCount: unresolvedWork.length,
173
192
  });
@@ -196,10 +215,10 @@ export function buildVerifiedHandoff(input) {
196
215
  lifecycleState: input.loop.lifecycleState,
197
216
  },
198
217
  verification: {
199
- status: toEvidenceStatus(input.verification.status),
218
+ status: toEvidenceStatus(effectiveVerificationStatus),
200
219
  summary: input.verification.summary,
201
220
  checks: input.verification.steps.map(toCheck),
202
- warnings: input.verification.warnings,
221
+ warnings: verificationWarnings,
203
222
  },
204
223
  requirements: input.requirements ?? [],
205
224
  scope,
@@ -22,19 +22,6 @@ function evidenceTone(status) {
22
22
  function humanize(value) {
23
23
  return value.replaceAll("_", " ").toLowerCase();
24
24
  }
25
- function formatCost(usd, provenance) {
26
- if (provenance === "unavailable") {
27
- return "unavailable";
28
- }
29
- const amount = "$" + usd.toFixed(2);
30
- if (provenance === "actual") {
31
- return amount + " provider-settled actual";
32
- }
33
- if (provenance === "calculated") {
34
- return amount + " calculated from observed usage";
35
- }
36
- return amount + " estimated";
37
- }
38
25
  function checkSymbol(status) {
39
26
  if (status === "PASSED") {
40
27
  return "✓";
@@ -44,6 +31,16 @@ function checkSymbol(status) {
44
31
  }
45
32
  return "•";
46
33
  }
34
+ function formatCost(usd, provenance) {
35
+ if (provenance === "unavailable")
36
+ return "unavailable";
37
+ const amount = "$" + usd.toFixed(2);
38
+ if (provenance === "actual")
39
+ return amount + " provider-settled actual";
40
+ if (provenance === "calculated")
41
+ return amount + " calculated from observed usage";
42
+ return amount + " estimated";
43
+ }
47
44
  /**
48
45
  * Single source of truth for the trust-authority outcome calculation.
49
46
  * Both terminal and Markdown renderers call this — never duplicate.
package/package.json CHANGED
@@ -1,81 +1,81 @@
1
- {
2
- "name": "@martinloop/mcp",
3
- "version": "0.5.5",
4
- "mcpName": "io.github.Keesan12/martin-loop",
5
- "private": false,
6
- "type": "module",
7
- "description": "MCP server for AI coding agents — verifier gates, stop limits, and Verified Handoffs.",
8
- "license": "Apache-2.0",
9
- "author": "MartinLoop contributors",
10
- "homepage": "https://martinloop.com/",
11
- "repository": {
12
- "type": "git",
13
- "url": "git+https://github.com/Keesan12/martin-loop.git",
14
- "directory": "packages/mcp"
15
- },
16
- "bugs": {
17
- "url": "https://github.com/Keesan12/martin-loop/issues"
18
- },
19
- "keywords": [
20
- "mcp",
21
- "model-context-protocol",
22
- "martin-loop",
23
- "ai-agent",
24
- "claude",
25
- "codex",
26
- "martin_doctor",
27
- "martin_triage_runs",
28
- "martin_run_dossier",
29
- "mcp-resources",
30
- "mcp-prompts"
31
- ],
32
- "bin": {
33
- "mcp": "./dist/server.js",
34
- "martin-loop-mcp": "./dist/server.js"
35
- },
36
- "exports": {
37
- "./server.json": "./server.json",
38
- "./package.json": "./package.json"
39
- },
40
- "files": [
41
- "dist",
42
- "README.md",
43
- "server.json"
44
- ],
45
- "engines": {
46
- "node": ">=20.0.0"
47
- },
48
- "publishConfig": {
49
- "access": "public"
50
- },
51
- "scripts": {
52
- "build": "node ./scripts/build-package.mjs",
53
- "prepack": "pnpm build",
54
- "smoke:pack": "node ./scripts/smoke-package.mjs",
55
- "smoke:published": "node ./scripts/smoke-published-package.mjs",
56
- "smoke:published:pack": "node ./scripts/smoke-published-package.mjs --package-spec=pack",
57
- "verify:release": "node --test ../../scripts/tests/publish-mcp-workflow.test.mjs ../../scripts/tests/mcp-publish-reliability.test.mjs ../../scripts/tests/mcp-release-docs.test.mjs",
58
- "test:baseline": "node ./scripts/run-test-lane.mjs baseline",
59
- "test": "vitest run --maxWorkers=1",
60
- "lint": "tsc -p tsconfig.json --noEmit",
61
- "start": "node dist/server.js",
62
- "inspect:live": "node ./scripts/inspect-live.mjs",
63
- "mcpb:build": "node ./mcpb/build-mcpb.mjs",
64
- "mcpb:validate": "mcpb validate ./dist-mcpb/martinloop",
65
- "mcpb:smoke": "node ./mcpb/smoke-mcpb.mjs"
66
- },
67
- "dependencies": {
68
- "@modelcontextprotocol/sdk": "^1.29.0",
69
- "@open-policy-agent/opa-wasm": "^1.10.0",
70
- "@opentelemetry/api-logs": "^0.214.0",
71
- "@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
72
- "@opentelemetry/resources": "^2.6.1",
73
- "@opentelemetry/sdk-logs": "^0.214.0",
74
- "ts-morph": "^21.0.0"
75
- },
76
- "devDependencies": {
77
- "@anthropic-ai/mcpb": "2.1.2",
78
- "@martin/contracts": "workspace:*",
79
- "@martin/presentation": "workspace:*"
80
- }
81
- }
1
+ {
2
+ "name": "@martinloop/mcp",
3
+ "version": "0.5.6",
4
+ "mcpName": "io.github.Keesan12/martin-loop",
5
+ "private": false,
6
+ "type": "module",
7
+ "description": "MCP server for AI coding agents — verifier gates, stop limits, and Verified Handoffs.",
8
+ "license": "Apache-2.0",
9
+ "author": "MartinLoop contributors",
10
+ "homepage": "https://martinloop.com/",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/Keesan12/martin-loop.git",
14
+ "directory": "packages/mcp"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/Keesan12/martin-loop/issues"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "martin-loop",
23
+ "ai-agent",
24
+ "claude",
25
+ "codex",
26
+ "martin_doctor",
27
+ "martin_triage_runs",
28
+ "martin_run_dossier",
29
+ "mcp-resources",
30
+ "mcp-prompts"
31
+ ],
32
+ "bin": {
33
+ "mcp": "./dist/server.js",
34
+ "martin-loop-mcp": "./dist/server.js"
35
+ },
36
+ "exports": {
37
+ "./server.json": "./server.json",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "README.md",
43
+ "server.json"
44
+ ],
45
+ "engines": {
46
+ "node": ">=20.0.0"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "scripts": {
52
+ "build": "node ./scripts/build-package.mjs",
53
+ "prepack": "pnpm build",
54
+ "smoke:pack": "node ./scripts/smoke-package.mjs",
55
+ "smoke:published": "node ./scripts/smoke-published-package.mjs",
56
+ "smoke:published:pack": "node ./scripts/smoke-published-package.mjs --package-spec=pack",
57
+ "verify:release": "node --test ../../scripts/tests/publish-mcp-workflow.test.mjs ../../scripts/tests/mcp-publish-reliability.test.mjs ../../scripts/tests/mcp-release-docs.test.mjs",
58
+ "test:baseline": "node ./scripts/run-test-lane.mjs baseline",
59
+ "test": "vitest run --maxWorkers=1",
60
+ "lint": "tsc -p tsconfig.json --noEmit",
61
+ "start": "node dist/server.js",
62
+ "inspect:live": "node ./scripts/inspect-live.mjs",
63
+ "mcpb:build": "node ./mcpb/build-mcpb.mjs",
64
+ "mcpb:validate": "mcpb validate ./dist-mcpb/martinloop",
65
+ "mcpb:smoke": "node ./mcpb/smoke-mcpb.mjs"
66
+ },
67
+ "dependencies": {
68
+ "@modelcontextprotocol/sdk": "^1.29.0",
69
+ "@open-policy-agent/opa-wasm": "^1.10.0",
70
+ "@opentelemetry/api-logs": "^0.214.0",
71
+ "@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
72
+ "@opentelemetry/resources": "^2.6.1",
73
+ "@opentelemetry/sdk-logs": "^0.214.0",
74
+ "ts-morph": "^21.0.0"
75
+ },
76
+ "devDependencies": {
77
+ "@anthropic-ai/mcpb": "2.1.2",
78
+ "@martin/contracts": "workspace:*",
79
+ "@martin/presentation": "workspace:*"
80
+ }
81
+ }