@martinloop/mcp 0.3.7 → 0.3.9

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 (88) hide show
  1. package/README.md +196 -137
  2. package/dist/resources.js +148 -148
  3. package/dist/server.js +0 -0
  4. package/dist/tools/tool-support.js +11 -1
  5. package/dist/vendor/adapters/claude-cli.d.ts +2 -2
  6. package/dist/vendor/adapters/claude-cli.js +127 -51
  7. package/dist/vendor/adapters/codex-launcher.js +10 -1
  8. package/dist/vendor/adapters/openai-compatible.js +6 -6
  9. package/package.json +86 -76
  10. package/server.json +21 -21
  11. package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +0 -146
  12. package/dist/vendor/adapters/adapters/src/claude-cli.js +0 -1100
  13. package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +0 -103
  14. package/dist/vendor/adapters/adapters/src/cli-bridge.js +0 -569
  15. package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +0 -78
  16. package/dist/vendor/adapters/adapters/src/codex-launcher.js +0 -663
  17. package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +0 -10
  18. package/dist/vendor/adapters/adapters/src/direct-provider.js +0 -40
  19. package/dist/vendor/adapters/adapters/src/index.d.ts +0 -8
  20. package/dist/vendor/adapters/adapters/src/index.js +0 -8
  21. package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +0 -62
  22. package/dist/vendor/adapters/adapters/src/openai-compatible.js +0 -306
  23. package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +0 -17
  24. package/dist/vendor/adapters/adapters/src/runtime-support.js +0 -59
  25. package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +0 -8
  26. package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +0 -40
  27. package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +0 -8
  28. package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +0 -12
  29. package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +0 -9
  30. package/dist/vendor/adapters/adapters/src/verifier-only.js +0 -63
  31. package/dist/vendor/adapters/contracts/src/context-graph.d.ts +0 -48
  32. package/dist/vendor/adapters/contracts/src/context-graph.js +0 -26
  33. package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +0 -78
  34. package/dist/vendor/adapters/contracts/src/execution-policy.js +0 -43
  35. package/dist/vendor/adapters/contracts/src/governance.d.ts +0 -24
  36. package/dist/vendor/adapters/contracts/src/governance.js +0 -11
  37. package/dist/vendor/adapters/contracts/src/identity.d.ts +0 -29
  38. package/dist/vendor/adapters/contracts/src/identity.js +0 -30
  39. package/dist/vendor/adapters/contracts/src/index.d.ts +0 -412
  40. package/dist/vendor/adapters/contracts/src/index.js +0 -225
  41. package/dist/vendor/adapters/contracts/src/operator.d.ts +0 -19
  42. package/dist/vendor/adapters/contracts/src/operator.js +0 -11
  43. package/dist/vendor/adapters/contracts/src/trajectory.d.ts +0 -26
  44. package/dist/vendor/adapters/contracts/src/trajectory.js +0 -27
  45. package/dist/vendor/adapters/core/src/compiler.d.ts +0 -52
  46. package/dist/vendor/adapters/core/src/compiler.js +0 -52
  47. package/dist/vendor/adapters/core/src/context-graph.d.ts +0 -3
  48. package/dist/vendor/adapters/core/src/context-graph.js +0 -371
  49. package/dist/vendor/adapters/core/src/context-integrity.d.ts +0 -26
  50. package/dist/vendor/adapters/core/src/context-integrity.js +0 -80
  51. package/dist/vendor/adapters/core/src/grounding.d.ts +0 -38
  52. package/dist/vendor/adapters/core/src/grounding.js +0 -273
  53. package/dist/vendor/adapters/core/src/identity.d.ts +0 -20
  54. package/dist/vendor/adapters/core/src/identity.js +0 -86
  55. package/dist/vendor/adapters/core/src/index.d.ts +0 -186
  56. package/dist/vendor/adapters/core/src/index.js +0 -1495
  57. package/dist/vendor/adapters/core/src/leash.d.ts +0 -48
  58. package/dist/vendor/adapters/core/src/leash.js +0 -497
  59. package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +0 -18
  60. package/dist/vendor/adapters/core/src/persistence/compiler.js +0 -34
  61. package/dist/vendor/adapters/core/src/persistence/index.d.ts +0 -10
  62. package/dist/vendor/adapters/core/src/persistence/index.js +0 -5
  63. package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +0 -39
  64. package/dist/vendor/adapters/core/src/persistence/integrity.js +0 -252
  65. package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +0 -23
  66. package/dist/vendor/adapters/core/src/persistence/ledger.js +0 -9
  67. package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +0 -63
  68. package/dist/vendor/adapters/core/src/persistence/memory-store.js +0 -115
  69. package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +0 -66
  70. package/dist/vendor/adapters/core/src/persistence/runs-reader.js +0 -140
  71. package/dist/vendor/adapters/core/src/persistence/store.d.ts +0 -89
  72. package/dist/vendor/adapters/core/src/persistence/store.js +0 -125
  73. package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +0 -85
  74. package/dist/vendor/adapters/core/src/persistence/trace-store.js +0 -125
  75. package/dist/vendor/adapters/core/src/policy-compiler.d.ts +0 -2
  76. package/dist/vendor/adapters/core/src/policy-compiler.js +0 -195
  77. package/dist/vendor/adapters/core/src/policy.d.ts +0 -156
  78. package/dist/vendor/adapters/core/src/policy.js +0 -704
  79. package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +0 -53
  80. package/dist/vendor/adapters/core/src/repo-analyzer.js +0 -263
  81. package/dist/vendor/adapters/core/src/rollback.d.ts +0 -15
  82. package/dist/vendor/adapters/core/src/rollback.js +0 -253
  83. package/dist/vendor/adapters/core/src/routing.d.ts +0 -82
  84. package/dist/vendor/adapters/core/src/routing.js +0 -336
  85. package/dist/vendor/adapters/core/src/test-integrity.d.ts +0 -64
  86. package/dist/vendor/adapters/core/src/test-integrity.js +0 -197
  87. package/dist/vendor/adapters/core/src/trajectory.d.ts +0 -18
  88. package/dist/vendor/adapters/core/src/trajectory.js +0 -295
@@ -17,22 +17,18 @@ import { createAdapterCapabilities, normalizeStructuredErrors, normalizeUsage }
17
17
  // ---------------------------------------------------------------------------
18
18
  // Cost estimation
19
19
  //
20
- // Token costs are estimated using a blended average across top models:
21
- // Anthropic Sonnet, OpenAI GPT-4o Mini, Gemini Flash, Meta Llama 3.
22
- // Override at runtime with --input-cost-per-1k / --output-cost-per-1k CLI
23
- // flags or martin.config.yaml pricing section.
20
+ // Streaming enforcement needs a model-specific estimate until Claude emits
21
+ // authoritative total_cost_usd on the final result event. Unknown Claude
22
+ // models are never assigned another model's price.
24
23
  // ---------------------------------------------------------------------------
25
24
  const BLENDED_INPUT_COST_PER_1K = 0.003; // $/1K input tokens
26
25
  const BLENDED_OUTPUT_COST_PER_1K = 0.012; // $/1K output tokens
27
- // Per-model overrides for common Claude models (fallback: blended average)
26
+ // USD per 1K tokens. Claude cache creation uses the documented default
27
+ // five-minute rate; cache reads are priced independently from fresh input.
28
28
  const MODEL_PRICING = {
29
- "claude-opus-4-6": { inputPer1K: 0.015, outputPer1K: 0.075 },
30
- "claude-sonnet-4-6": { inputPer1K: 0.003, outputPer1K: 0.015 },
31
- "claude-haiku-4-5": { inputPer1K: 0.00025, outputPer1K: 0.00125 },
32
- // Keep legacy names working
33
- "claude-opus": { inputPer1K: 0.015, outputPer1K: 0.075 },
34
- "claude-sonnet": { inputPer1K: 0.003, outputPer1K: 0.015 },
35
- "claude-haiku": { inputPer1K: 0.00025, outputPer1K: 0.00125 },
29
+ "claude-opus-4-6": { inputPer1K: 0.005, cachedInputPer1K: 0.0005, cacheCreationInputPer1K: 0.00625, outputPer1K: 0.025 },
30
+ "claude-sonnet-4-6": { inputPer1K: 0.003, cachedInputPer1K: 0.0003, cacheCreationInputPer1K: 0.00375, outputPer1K: 0.015 },
31
+ "claude-haiku-4-5": { inputPer1K: 0.001, cachedInputPer1K: 0.0001, cacheCreationInputPer1K: 0.00125, outputPer1K: 0.005 },
36
32
  // OpenAI coding models
37
33
  "codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
38
34
  "gpt-5-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
@@ -41,6 +37,37 @@ const MODEL_PRICING = {
41
37
  "gpt-5.2-codex": { inputPer1K: 0.00175, cachedInputPer1K: 0.000175, outputPer1K: 0.014 },
42
38
  "codex-mini-latest": { inputPer1K: 0.0015, cachedInputPer1K: 0.000375, outputPer1K: 0.006 }
43
39
  };
40
+ const CLAUDE_MODEL_ALIASES = [
41
+ { canonicalModelId: "claude-opus-4-6", aliases: [/^claude-opus-4-6-\d{8}$/u, /^claude-opus$/u] },
42
+ { canonicalModelId: "claude-sonnet-4-6", aliases: [/^claude-sonnet-4-6-\d{8}$/u, /^claude-sonnet$/u] },
43
+ { canonicalModelId: "claude-haiku-4-5", aliases: [/^claude-haiku-4-5-\d{8}$/u, /^claude-haiku$/u] }
44
+ ];
45
+ function resolveModelPricing(modelLabel) {
46
+ const normalized = modelLabel?.trim().toLowerCase();
47
+ if (!normalized) {
48
+ return { status: "unknown" };
49
+ }
50
+ const exact = MODEL_PRICING[normalized];
51
+ if (exact) {
52
+ return { status: "exact", canonicalModelId: normalized, pricing: exact };
53
+ }
54
+ for (const family of CLAUDE_MODEL_ALIASES) {
55
+ if (family.aliases.some((alias) => alias.test(normalized))) {
56
+ return {
57
+ status: "alias",
58
+ canonicalModelId: family.canonicalModelId,
59
+ pricing: MODEL_PRICING[family.canonicalModelId]
60
+ };
61
+ }
62
+ }
63
+ return { status: "unknown" };
64
+ }
65
+ function calculateUsageCost(usage, pricing) {
66
+ return ((usage.inputTokens / 1000) * pricing.inputPer1K +
67
+ (usage.cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
68
+ (usage.cacheCreationInputTokens / 1000) * (pricing.cacheCreationInputPer1K ?? pricing.inputPer1K) +
69
+ (usage.outputTokens / 1000) * pricing.outputPer1K);
70
+ }
44
71
  function extractUsage(parsed, modelLabel) {
45
72
  if (!parsed?.usage) {
46
73
  return normalizeUsage({
@@ -50,34 +77,38 @@ function extractUsage(parsed, modelLabel) {
50
77
  provenance: "unavailable"
51
78
  });
52
79
  }
53
- const promptTokens = (parsed.usage.inputTokens ?? parsed.usage.input_tokens ?? 0) +
54
- (parsed.usage.cacheCreationInputTokens ?? parsed.usage.cache_creation_input_tokens ?? 0);
80
+ const promptTokens = parsed.usage.inputTokens ?? parsed.usage.input_tokens ?? 0;
81
+ const cacheCreationInputTokens = parsed.usage.cacheCreationInputTokens ?? parsed.usage.cache_creation_input_tokens ?? 0;
55
82
  const cachedInputTokens = parsed.usage.cacheReadInputTokens ?? parsed.usage.cache_read_input_tokens ?? 0;
56
- const tokensIn = promptTokens + cachedInputTokens;
83
+ const tokensIn = promptTokens + cachedInputTokens + cacheCreationInputTokens;
57
84
  const tokensOut = parsed.usage.outputTokens ?? parsed.usage.output_tokens ?? 0;
58
- const pricing = (modelLabel ? MODEL_PRICING[modelLabel] : undefined) ??
59
- { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K };
85
+ const pricingResolution = resolveModelPricing(modelLabel);
60
86
  // Prefer Claude's own authoritative total_cost_usd (present on the final
61
87
  // `result` event in json/stream-json output) over our pricing-table estimate,
62
88
  // which can drift from real billed cost (cache discounts, surcharges, etc).
63
89
  const hasAuthoritativeCost = typeof parsed.total_cost_usd === "number";
64
90
  const actualUsd = hasAuthoritativeCost
65
91
  ? parsed.total_cost_usd
66
- : (promptTokens / 1000) * pricing.inputPer1K +
67
- (cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
68
- (tokensOut / 1000) * pricing.outputPer1K;
92
+ : pricingResolution.pricing
93
+ ? calculateUsageCost({
94
+ inputTokens: promptTokens,
95
+ cachedInputTokens,
96
+ cacheCreationInputTokens,
97
+ outputTokens: tokensOut
98
+ }, pricingResolution.pricing)
99
+ : 0;
69
100
  return normalizeUsage({
70
101
  actualUsd: Number(actualUsd.toFixed(6)),
71
102
  tokensIn,
72
103
  tokensOut,
73
104
  cachedInputTokens,
74
- provenance: hasAuthoritativeCost ? "actual" : "estimated",
105
+ provenance: hasAuthoritativeCost ? "actual" : pricingResolution.pricing ? "estimated" : "unavailable",
75
106
  providerSettlement: {
76
107
  providerId: "claude",
77
108
  model: modelLabel ?? "claude",
78
109
  transport: "cli",
79
110
  source: "claude_json",
80
- inputTokens: promptTokens,
111
+ inputTokens: promptTokens + cacheCreationInputTokens,
81
112
  cachedInputTokens,
82
113
  outputTokens: tokensOut,
83
114
  rawUsageAvailable: true,
@@ -239,20 +270,23 @@ function extractGeminiJsonResult(stdout, modelLabel) {
239
270
  })
240
271
  };
241
272
  }
242
- function createStreamingUsageInspector(capUsd, modelLabel) {
243
- const pricing = (modelLabel ? MODEL_PRICING[modelLabel] : undefined) ??
244
- { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K };
273
+ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate) {
274
+ let pricingResolution = resolveModelPricing(modelLabel);
245
275
  // Safety margin: terminate at 80% of cap to bound one-turn overshoot.
246
276
  // Without this, a single expensive turn can blow past the cap before the
247
277
  // next check fires (proven live: $1.50 cap → $28.42 actual).
248
- const effectiveCapUsd = capUsd * 0.8;
278
+ const largeContext = promptTokenEstimate > 10_000;
279
+ const effectiveCapRatio = largeContext ? 0.7 : 0.8;
280
+ const effectiveCapUsd = capUsd * effectiveCapRatio;
249
281
  // Token-count ceiling fallback: if no usage events are ever parsed (e.g.
250
282
  // Claude changes its stream-json event format), use raw byte volume as a
251
283
  // last-resort circuit breaker. Derived from budget / blended cost per char.
252
- const blendedCostPerChar = (pricing.inputPer1K / 1000 / 4) + (pricing.outputPer1K / 1000 / 4);
253
- const bytesCeiling = blendedCostPerChar > 0
254
- ? Math.ceil((capUsd / blendedCostPerChar) * 2)
255
- : 100_000_000;
284
+ const resolveBlendedCostPerChar = () => {
285
+ const pricing = pricingResolution.pricing;
286
+ return pricing
287
+ ? (pricing.inputPer1K / 1000 / 4) + (pricing.outputPer1K / 1000 / 4)
288
+ : undefined;
289
+ };
256
290
  // Time-based fallback: if we receive data but no usage events for this long,
257
291
  // estimate spend from byte volume and enforce the cap. Prevents the inspector
258
292
  // from going blind when Claude changes its stream-json event format.
@@ -269,11 +303,14 @@ function createStreamingUsageInspector(capUsd, modelLabel) {
269
303
  const checkBudgetExceeded = (terminate) => {
270
304
  if (capUsd > 0 && cumulativeUsd > effectiveCapUsd) {
271
305
  terminate(`Streaming usage cap exceeded after ${String(turns)} turn(s): cumulative cost ~$${cumulativeUsd.toFixed(4)} ` +
272
- `surpassed the per-attempt cap $${capUsd.toFixed(4)} (80% threshold: $${effectiveCapUsd.toFixed(4)}). ` +
306
+ `surpassed the per-attempt cap $${capUsd.toFixed(4)} (${String(Math.round(effectiveCapRatio * 100))}% threshold: $${effectiveCapUsd.toFixed(4)}). ` +
273
307
  `Subprocess terminated to bound runaway overspend.`);
274
308
  }
275
309
  };
276
310
  const extractUsageFromEvent = (event, terminate) => {
311
+ if (event.type === "system" && event.subtype === "init" && typeof event.model === "string") {
312
+ pricingResolution = resolveModelPricing(event.model);
313
+ }
277
314
  // Check for authoritative total_cost_usd on ANY event — if Claude reports
278
315
  // cost exceeding cap, terminate immediately regardless of event type.
279
316
  if (typeof event.total_cost_usd === "number" && event.total_cost_usd > 0) {
@@ -293,18 +330,43 @@ function createStreamingUsageInspector(capUsd, modelLabel) {
293
330
  return;
294
331
  }
295
332
  const usageRecord = usage;
296
- const turnTokensIn = (usageRecord.input_tokens ?? usageRecord.inputTokens ?? 0) +
297
- (usageRecord.cache_read_input_tokens ?? usageRecord.cacheReadInputTokens ?? 0) +
298
- (usageRecord.cache_creation_input_tokens ?? usageRecord.cacheCreationInputTokens ?? 0);
333
+ const turnInputTokens = usageRecord.input_tokens ?? usageRecord.inputTokens ?? 0;
334
+ const turnCachedInputTokens = usageRecord.cache_read_input_tokens ?? usageRecord.cacheReadInputTokens ?? 0;
335
+ const turnCacheCreationInputTokens = usageRecord.cache_creation_input_tokens ?? usageRecord.cacheCreationInputTokens ?? 0;
336
+ const turnTokensIn = turnInputTokens + turnCachedInputTokens + turnCacheCreationInputTokens;
299
337
  const turnTokensOut = usageRecord.output_tokens ?? usageRecord.outputTokens ?? 0;
300
338
  if (turnTokensIn === 0 && turnTokensOut === 0) {
301
339
  return;
302
340
  }
341
+ const turnUsd = pricingResolution.pricing
342
+ ? calculateUsageCost({
343
+ inputTokens: turnInputTokens,
344
+ cachedInputTokens: turnCachedInputTokens,
345
+ cacheCreationInputTokens: turnCacheCreationInputTokens,
346
+ outputTokens: turnTokensOut
347
+ }, pricingResolution.pricing)
348
+ : undefined;
349
+ const remainingBudgetBeforeTurn = Math.max(capUsd - cumulativeUsd, 0);
350
+ if (turnUsd !== undefined &&
351
+ capUsd > 0 &&
352
+ remainingBudgetBeforeTurn > 0 &&
353
+ turnUsd > remainingBudgetBeforeTurn * 0.5) {
354
+ cumulativeUsd += turnUsd;
355
+ tokensIn += turnTokensIn;
356
+ tokensOut += turnTokensOut;
357
+ turns += 1;
358
+ usageEventSeen = true;
359
+ terminate(`Single turn spend ~$${turnUsd.toFixed(4)} consumed more than 50% of the remaining per-attempt budget ` +
360
+ `($${remainingBudgetBeforeTurn.toFixed(4)} before the turn). Subprocess terminated to prevent a one-turn overshoot.`);
361
+ return;
362
+ }
303
363
  tokensIn += turnTokensIn;
304
364
  tokensOut += turnTokensOut;
305
365
  turns += 1;
306
366
  usageEventSeen = true;
307
- cumulativeUsd += (turnTokensIn / 1000) * pricing.inputPer1K + (turnTokensOut / 1000) * pricing.outputPer1K;
367
+ if (turnUsd !== undefined) {
368
+ cumulativeUsd += turnUsd;
369
+ }
308
370
  checkBudgetExceeded(terminate);
309
371
  };
310
372
  const ingestLine = (line, terminate) => {
@@ -319,10 +381,14 @@ function createStreamingUsageInspector(capUsd, modelLabel) {
319
381
  catch {
320
382
  return;
321
383
  }
322
- extractUsageFromEvent(event, terminate);
384
+ // result events contain aggregate usage that duplicates previously streamed
385
+ // assistant-message usage events — skip to avoid double-counting.
323
386
  if (event.type === "result") {
324
387
  finalResult = event;
325
388
  }
389
+ else {
390
+ extractUsageFromEvent(event, terminate);
391
+ }
326
392
  };
327
393
  return {
328
394
  onChunk: (chunk, terminate) => {
@@ -333,7 +399,11 @@ function createStreamingUsageInspector(capUsd, modelLabel) {
333
399
  // Token-count ceiling fallback: if we've ingested a lot of bytes but
334
400
  // never seen a single usage event, the event format may have changed
335
401
  // and the inspector is silently blind. Terminate as a last resort.
336
- if (!usageEventSeen && capUsd > 0 && totalBytes > bytesCeiling) {
402
+ const blendedCostPerChar = resolveBlendedCostPerChar();
403
+ const bytesCeiling = blendedCostPerChar && blendedCostPerChar > 0
404
+ ? Math.ceil((capUsd / blendedCostPerChar) * 2)
405
+ : undefined;
406
+ if (!usageEventSeen && capUsd > 0 && bytesCeiling !== undefined && totalBytes > bytesCeiling) {
337
407
  terminate(`Streaming byte ceiling exceeded (${String(totalBytes)} bytes > ${String(bytesCeiling)} ceiling) ` +
338
408
  `without any usage events parsed. The Claude stream-json event format may have changed. ` +
339
409
  `Subprocess terminated as a fallback budget guard for cap $${capUsd.toFixed(4)}.`);
@@ -348,8 +418,8 @@ function createStreamingUsageInspector(capUsd, modelLabel) {
348
418
  firstChunkAt !== undefined &&
349
419
  Date.now() - firstChunkAt > USAGE_BLIND_TIMEOUT_MS &&
350
420
  totalBytes > 10_000) {
351
- const estimatedUsd = totalBytes * blendedCostPerChar;
352
- if (estimatedUsd > effectiveCapUsd) {
421
+ const estimatedUsd = blendedCostPerChar === undefined ? undefined : totalBytes * blendedCostPerChar;
422
+ if (estimatedUsd !== undefined && estimatedUsd > effectiveCapUsd) {
353
423
  terminate(`No usage events received after ${String(Math.round((Date.now() - firstChunkAt) / 1000))}s ` +
354
424
  `(${String(totalBytes)} bytes). Estimated cost ~$${estimatedUsd.toFixed(4)} exceeds cap ` +
355
425
  `$${capUsd.toFixed(4)}. Subprocess terminated to prevent unmetered spend.`);
@@ -443,11 +513,11 @@ export function createAgentCliAdapter(options) {
443
513
  },
444
514
  async execute(request) {
445
515
  const prompt = buildPrompt(request);
446
- const estimatedUsage = estimateUsage(prompt, options.model ?? options.command);
516
+ const estimatedUsage = estimateUsage(prompt, options.model ?? options.command, options.command);
447
517
  // Preflight: bail if projected cost exceeds remaining budget
448
518
  if (request.context.remainingBudgetUsd > 0) {
449
- const projected = estimatePromptCost(prompt, options.model ?? "");
450
- if (projected > request.context.remainingBudgetUsd * 0.95) {
519
+ const projected = estimatePromptCost(prompt, options.model ?? "", options.command);
520
+ if (projected !== undefined && projected > request.context.remainingBudgetUsd * 0.95) {
451
521
  return {
452
522
  status: "failed",
453
523
  summary: `Preflight: projected cost $${projected.toFixed(4)} exceeds remaining budget $${request.context.remainingBudgetUsd.toFixed(4)}.`,
@@ -463,7 +533,7 @@ export function createAgentCliAdapter(options) {
463
533
  };
464
534
  }
465
535
  }
466
- const args = options.argsBuilder(prompt);
536
+ const args = options.argsBuilder(prompt, request);
467
537
  const stdinData = options.stdinBuilder?.(prompt);
468
538
  // Live cumulative-cost circuit breaker: a single attempt should never be
469
539
  // allowed to spend more than the loop has left. `--output-format json`
@@ -472,7 +542,7 @@ export function createAgentCliAdapter(options) {
472
542
  // instant projected spend crosses what remains — bounding the worst case
473
543
  // to roughly one turn's overshoot rather than the entire runaway session.
474
544
  const streamingUsage = options.streamingUsageCap && request.context.remainingBudgetUsd > 0
475
- ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command)
545
+ ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command, estimatedUsage.tokensIn)
476
546
  : undefined;
477
547
  const agentResult = await runSubprocess(options.command, args, {
478
548
  cwd: workingDirectory,
@@ -797,7 +867,7 @@ export function createClaudeCliAdapter(options = {}) {
797
867
  supportsJsonOutput: true,
798
868
  streamingUsageCap: true,
799
869
  spawnImpl: options.spawnImpl,
800
- argsBuilder: (_prompt) => [
870
+ argsBuilder: (_prompt, request) => [
801
871
  "--output-format",
802
872
  "stream-json",
803
873
  "--verbose",
@@ -1026,21 +1096,27 @@ function sanitizeForPrompt(input) {
1026
1096
  }
1027
1097
  return redactSecretsForPrompt(out);
1028
1098
  }
1029
- function estimatePromptCost(promptText, model) {
1099
+ function estimatePromptCost(promptText, model, providerCommand) {
1030
1100
  const inputTokens = Math.ceil(promptText.length / 3.5);
1031
1101
  const outputTokens = 2000;
1032
- const pricing = MODEL_PRICING[model] ?? { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K };
1102
+ const pricing = resolveModelPricing(model).pricing ?? (providerCommand === "claude"
1103
+ ? undefined
1104
+ : { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K });
1105
+ if (!pricing) {
1106
+ return undefined;
1107
+ }
1033
1108
  return (inputTokens / 1000) * pricing.inputPer1K + (outputTokens / 1000) * pricing.outputPer1K;
1034
1109
  }
1035
- function estimateUsage(promptText, model) {
1110
+ function estimateUsage(promptText, model, providerCommand) {
1036
1111
  const inputTokens = Math.ceil(promptText.length / 3.5);
1037
1112
  const outputTokens = 2_000;
1113
+ const estimatedUsd = estimatePromptCost(promptText, model, providerCommand);
1038
1114
  return normalizeUsage({
1039
- actualUsd: estimatePromptCost(promptText, model),
1040
- estimatedUsd: estimatePromptCost(promptText, model),
1115
+ actualUsd: estimatedUsd ?? 0,
1116
+ ...(estimatedUsd === undefined ? {} : { estimatedUsd }),
1041
1117
  tokensIn: inputTokens,
1042
1118
  tokensOut: outputTokens,
1043
- provenance: "estimated"
1119
+ provenance: estimatedUsd === undefined ? "unavailable" : "estimated"
1044
1120
  });
1045
1121
  }
1046
1122
  function redactSecretsForPrompt(input) {
@@ -357,6 +357,10 @@ function discoverCommandOffPath(command, platform, env) {
357
357
  const localAppData = env.LOCALAPPDATA;
358
358
  if (localAppData)
359
359
  dirs.push(join(localAppData, "OpenAI", "Codex", "bin"));
360
+ // Claude Code native installer places binary at %USERPROFILE%\.local\bin
361
+ const userProfile = env.USERPROFILE ?? env.HOMEPATH;
362
+ if (userProfile)
363
+ dirs.push(join(userProfile, ".local", "bin"));
360
364
  if (home)
361
365
  dirs.push(join(home, "scoop", "shims"));
362
366
  }
@@ -383,8 +387,13 @@ function discoverCommandOffPath(command, platform, env) {
383
387
  return undefined;
384
388
  }
385
389
  function suggestInstall(command) {
390
+ if (command === "claude") {
391
+ const installCmd = process.platform === "win32"
392
+ ? "irm https://claude.ai/install.ps1 | iex"
393
+ : "curl -fsSL https://claude.ai/install.sh | bash";
394
+ return `Install with: ${installCmd}`;
395
+ }
386
396
  const installs = {
387
- claude: "Install with: npm install -g @anthropic-ai/claude-code",
388
397
  codex: "Install with: npm install -g @openai/codex",
389
398
  gemini: "Install with: npm install -g @google/gemini-cli"
390
399
  };
@@ -72,12 +72,12 @@ function estimateCost(model, inputChars, outputChars) {
72
72
  // ---------------------------------------------------------------------------
73
73
  // Prompt builder
74
74
  // ---------------------------------------------------------------------------
75
- const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
76
- Follow these rules exactly:
77
- - Read the task description carefully and implement only what is asked.
78
- - Do not add features, refactors, or improvements beyond the stated task.
79
- - If the task asks you to write or modify code, output the complete file content with changes applied.
80
- - Be precise, minimal, and test-backed in all changes.
75
+ const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
76
+ Follow these rules exactly:
77
+ - Read the task description carefully and implement only what is asked.
78
+ - Do not add features, refactors, or improvements beyond the stated task.
79
+ - If the task asks you to write or modify code, output the complete file content with changes applied.
80
+ - Be precise, minimal, and test-backed in all changes.
81
81
  - State what you changed and why at the end of your response.`;
82
82
  export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
83
83
  export const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
package/package.json CHANGED
@@ -1,76 +1,86 @@
1
- {
2
- "name": "@martinloop/mcp",
3
- "version": "0.3.7",
4
- "mcpName": "io.github.Keesan12/martin-loop",
5
- "private": false,
6
- "type": "module",
7
- "description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
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
- },
64
- "dependencies": {
65
- "@modelcontextprotocol/sdk": "^1.29.0",
66
- "@open-policy-agent/opa-wasm": "^1.10.0",
67
- "@opentelemetry/api-logs": "^0.214.0",
68
- "@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
69
- "@opentelemetry/resources": "^2.6.1",
70
- "@opentelemetry/sdk-logs": "^0.214.0",
71
- "ts-morph": "^21.0.0"
72
- },
73
- "devDependencies": {
74
- "@martin/contracts": "workspace:*"
75
- }
76
- }
1
+ {
2
+ "name": "@martinloop/mcp",
3
+ "version": "0.3.9",
4
+ "mcpName": "io.github.Keesan12/martin-loop",
5
+ "private": false,
6
+ "type": "module",
7
+ "description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
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
+ "mcp-server",
22
+ "model-context-protocol",
23
+ "martin-loop",
24
+ "ai-agent",
25
+ "ai-governance",
26
+ "agent-governance",
27
+ "claude",
28
+ "claude-code",
29
+ "codex",
30
+ "cursor",
31
+ "budget-guardrails",
32
+ "verifier-gates",
33
+ "martin_doctor",
34
+ "martin_triage_runs",
35
+ "martin_run_dossier",
36
+ "mcp-resources",
37
+ "mcp-prompts"
38
+ ],
39
+ "bin": {
40
+ "mcp": "./dist/server.js",
41
+ "martin-loop-mcp": "./dist/server.js"
42
+ },
43
+ "exports": {
44
+ "./server.json": "./server.json",
45
+ "./package.json": "./package.json"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "README.md",
50
+ "server.json"
51
+ ],
52
+ "engines": {
53
+ "node": ">=20.0.0"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "scripts": {
59
+ "build": "node ./scripts/build-package.mjs",
60
+ "prepack": "pnpm build",
61
+ "smoke:pack": "node ./scripts/smoke-package.mjs",
62
+ "smoke:published": "node ./scripts/smoke-published-package.mjs",
63
+ "smoke:published:pack": "node ./scripts/smoke-published-package.mjs --package-spec=pack",
64
+ "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",
65
+ "test:baseline": "node ./scripts/run-test-lane.mjs baseline",
66
+ "test": "vitest run --maxWorkers=1",
67
+ "lint": "tsc -p tsconfig.json --noEmit",
68
+ "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"
72
+ },
73
+ "dependencies": {
74
+ "@modelcontextprotocol/sdk": "^1.29.0",
75
+ "@open-policy-agent/opa-wasm": "^1.10.0",
76
+ "@opentelemetry/api-logs": "^0.214.0",
77
+ "@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
78
+ "@opentelemetry/resources": "^2.6.1",
79
+ "@opentelemetry/sdk-logs": "^0.214.0",
80
+ "ts-morph": "^21.0.0"
81
+ },
82
+ "devDependencies": {
83
+ "@anthropic-ai/mcpb": "2.1.2",
84
+ "@martin/contracts": "workspace:*"
85
+ }
86
+ }
package/server.json CHANGED
@@ -1,21 +1,21 @@
1
- {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.Keesan12/martin-loop",
4
- "title": "Martin Loop",
5
- "description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
6
- "repository": {
7
- "url": "https://github.com/Keesan12/martin-loop",
8
- "source": "github"
9
- },
10
- "version": "0.3.7",
11
- "packages": [
12
- {
13
- "registryType": "npm",
14
- "identifier": "@martinloop/mcp",
15
- "version": "0.3.7",
16
- "transport": {
17
- "type": "stdio"
18
- }
19
- }
20
- ]
21
- }
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.Keesan12/martin-loop",
4
+ "title": "Martin Loop",
5
+ "description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
6
+ "repository": {
7
+ "url": "https://github.com/Keesan12/martin-loop",
8
+ "source": "github"
9
+ },
10
+ "version": "0.3.9",
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "identifier": "@martinloop/mcp",
15
+ "version": "0.3.9",
16
+ "transport": {
17
+ "type": "stdio"
18
+ }
19
+ }
20
+ ]
21
+ }