@martinloop/mcp 0.5.7 → 0.6.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.
@@ -18,15 +18,98 @@ export declare function buildArcadeToolDefinitions(capabilities: unknown): reado
18
18
  };
19
19
  }, {
20
20
  readonly name: "martin_arcade_status";
21
- readonly description: "Read authoritative MartinLoop evidence for the Arcade view without changing run state.";
21
+ readonly description: "Read the read-only evidence projection used by the Arcade view. Provide loopId for one exact run or omit it to load the latest run. Use only for Arcade rendering; use martin_status for budget pressure or martin_run_dossier for full evidence. This tool reads persisted evidence and never changes run state.";
22
+ readonly annotations: {
23
+ readonly readOnlyHint: true;
24
+ readonly destructiveHint: false;
25
+ readonly idempotentHint: true;
26
+ readonly openWorldHint: false;
27
+ };
22
28
  readonly inputSchema: {
23
29
  readonly type: "object";
24
30
  readonly additionalProperties: false;
25
31
  readonly properties: {
26
32
  readonly loopId: {
27
33
  readonly type: "string";
34
+ readonly description: "Optional MartinLoop run identifier. Omit it to read the latest persisted run.";
35
+ };
36
+ };
37
+ };
38
+ readonly outputSchema: {
39
+ readonly type: "object";
40
+ readonly additionalProperties: false;
41
+ readonly properties: {
42
+ readonly loopId: {
43
+ readonly type: "string";
44
+ readonly description: "Run identifier represented by this Arcade snapshot.";
45
+ };
46
+ readonly lifecycleState: {
47
+ readonly type: "string";
48
+ readonly description: "Current persisted lifecycle state.";
49
+ };
50
+ readonly completed: {
51
+ readonly type: "boolean";
52
+ readonly description: "Whether the run is outside an active lifecycle state.";
53
+ };
54
+ readonly displayOutcome: {
55
+ readonly anyOf: readonly [{
56
+ readonly type: "string";
57
+ readonly enum: readonly ["VERIFIED", "STOPPED", "NEEDS REVIEW"];
58
+ }, {
59
+ readonly type: "null";
60
+ }];
61
+ readonly description: "Terminal verified-handoff outcome, or null while the run is active.";
62
+ };
63
+ readonly verification: {
64
+ readonly type: "object";
65
+ readonly additionalProperties: true;
66
+ readonly description: "Persisted verifier evidence summary.";
67
+ };
68
+ readonly receiptIntegrity: {
69
+ readonly type: "object";
70
+ readonly additionalProperties: true;
71
+ readonly description: "Persisted receipt-integrity verdict.";
72
+ };
73
+ readonly attempts: {
74
+ readonly type: "integer";
75
+ readonly minimum: 0;
76
+ readonly description: "Number of recorded attempts.";
77
+ };
78
+ readonly cost: {
79
+ readonly type: "object";
80
+ readonly additionalProperties: false;
81
+ readonly properties: {
82
+ readonly actualUsd: {
83
+ readonly type: "number";
84
+ };
85
+ readonly provenance: {
86
+ readonly type: "string";
87
+ };
88
+ };
89
+ readonly required: readonly ["actualUsd", "provenance"];
90
+ };
91
+ readonly budget: {
92
+ readonly type: "object";
93
+ readonly additionalProperties: false;
94
+ readonly properties: {
95
+ readonly maxUsd: {
96
+ readonly type: "number";
97
+ };
98
+ readonly remainingUsd: {
99
+ readonly type: "number";
100
+ };
101
+ };
102
+ readonly required: readonly ["maxUsd", "remainingUsd"];
103
+ };
104
+ readonly warnings: {
105
+ readonly type: "array";
106
+ readonly items: {
107
+ readonly type: "string";
108
+ };
109
+ readonly description: "Evidence or interpretation warnings for the Arcade view.";
28
110
  };
29
111
  };
112
+ readonly required: readonly ["loopId", "lifecycleState", "completed", "displayOutcome", "verification", "receiptIntegrity", "attempts", "cost", "budget", "warnings"];
30
113
  };
31
114
  readonly _meta: {
32
115
  readonly ui: {
@@ -25,11 +25,84 @@ export function buildArcadeToolDefinitions(capabilities) {
25
25
  },
26
26
  {
27
27
  name: "martin_arcade_status",
28
- description: "Read authoritative MartinLoop evidence for the Arcade view without changing run state.",
28
+ description: "Read the read-only evidence projection used by the Arcade view. Provide loopId for one exact run or omit it to load the latest run. Use only for Arcade rendering; use martin_status for budget pressure or martin_run_dossier for full evidence. This tool reads persisted evidence and never changes run state.",
29
+ annotations: {
30
+ readOnlyHint: true,
31
+ destructiveHint: false,
32
+ idempotentHint: true,
33
+ openWorldHint: false
34
+ },
29
35
  inputSchema: {
30
36
  type: "object",
31
37
  additionalProperties: false,
32
- properties: { loopId: { type: "string" } }
38
+ properties: {
39
+ loopId: {
40
+ type: "string",
41
+ description: "Optional MartinLoop run identifier. Omit it to read the latest persisted run."
42
+ }
43
+ }
44
+ },
45
+ outputSchema: {
46
+ type: "object",
47
+ additionalProperties: false,
48
+ properties: {
49
+ loopId: { type: "string", description: "Run identifier represented by this Arcade snapshot." },
50
+ lifecycleState: { type: "string", description: "Current persisted lifecycle state." },
51
+ completed: { type: "boolean", description: "Whether the run is outside an active lifecycle state." },
52
+ displayOutcome: {
53
+ anyOf: [
54
+ { type: "string", enum: ["VERIFIED", "STOPPED", "NEEDS REVIEW"] },
55
+ { type: "null" }
56
+ ],
57
+ description: "Terminal verified-handoff outcome, or null while the run is active."
58
+ },
59
+ verification: {
60
+ type: "object",
61
+ additionalProperties: true,
62
+ description: "Persisted verifier evidence summary."
63
+ },
64
+ receiptIntegrity: {
65
+ type: "object",
66
+ additionalProperties: true,
67
+ description: "Persisted receipt-integrity verdict."
68
+ },
69
+ attempts: { type: "integer", minimum: 0, description: "Number of recorded attempts." },
70
+ cost: {
71
+ type: "object",
72
+ additionalProperties: false,
73
+ properties: {
74
+ actualUsd: { type: "number" },
75
+ provenance: { type: "string" }
76
+ },
77
+ required: ["actualUsd", "provenance"]
78
+ },
79
+ budget: {
80
+ type: "object",
81
+ additionalProperties: false,
82
+ properties: {
83
+ maxUsd: { type: "number" },
84
+ remainingUsd: { type: "number" }
85
+ },
86
+ required: ["maxUsd", "remainingUsd"]
87
+ },
88
+ warnings: {
89
+ type: "array",
90
+ items: { type: "string" },
91
+ description: "Evidence or interpretation warnings for the Arcade view."
92
+ }
93
+ },
94
+ required: [
95
+ "loopId",
96
+ "lifecycleState",
97
+ "completed",
98
+ "displayOutcome",
99
+ "verification",
100
+ "receiptIntegrity",
101
+ "attempts",
102
+ "cost",
103
+ "budget",
104
+ "warnings"
105
+ ]
33
106
  },
34
107
  _meta: { ui: { visibility: ["app"] } }
35
108
  }
@@ -4,6 +4,7 @@ export declare const MARTIN_MINIMAL_TOOL_NAMES: readonly ["martin_doctor", "mart
4
4
  export declare const MARTIN_DIAGNOSTIC_TOOL_NAMES: readonly ["martin_doctor", "martin_plan", "martin_preflight", "martin_estimate", "martin_logs", "martin_list_runs", "martin_triage_runs", "martin_get_run", "martin_get_attempt", "martin_get_verification_results", "martin_dossier", "martin_eval"];
5
5
  export declare const MARTIN_GITHUB_REVIEW_TOOL_NAMES: readonly ["martin_doctor", "martin_plan", "martin_preflight", "martin_dossier", "martin_eval", "martin_pr_summary", "martin_create_pr", "martin_review_pr"];
6
6
  export declare const MARTIN_PAID_REMOTE_TOOL_NAMES: readonly ["martin_doctor", "martin_plan", "martin_preflight", "martin_estimate", "martin_run", "martin_list_runs", "martin_triage_runs", "martin_get_run", "martin_get_verification_results", "martin_dossier", "martin_eval"];
7
+ export declare const MARTIN_DIRECTORY_TOOL_NAMES: readonly ["martin_doctor", "martin_estimate", "martin_plan", "martin_preflight", "martin_run", "martin_pause", "martin_cancel", "martin_continue", "martin_list_runs", "martin_triage_runs", "martin_get_run", "martin_get_verification_results", "martin_dossier", "martin_logs", "martin_arcade", "martin_arcade_status"];
7
8
  export declare const MARTIN_RESOURCE_URIS: readonly ["martin://server/health", "martin://runs/recent", "martin://runs/triage", "martin://runs/latest", "martin://runs/latest/summary", "martin://runs/latest/proof-card", "martin://runs/latest/budget-status", "martin://runs/latest/verifier-evidence", "martin://runs/latest/rollback-evidence", "martin://policies/current", "martin://repo/risk-map", "martin://verifiers/results", "martin://agent/next-step", "martin://agent/governance-status", "martin://agent/mode-status", "martin://agent/memory-summary", "martin://guides/mcp-usage", "martin://guides/agent-start", "martin://guides/command-map", "martin://guides/ide-onboarding", "martin://guides/operating-rules", "martin://guides/publish-readiness"];
8
9
  export declare const MARTIN_RESOURCE_TEMPLATE_URIS: readonly ["martin://runs/{loopId}", "martin://runs/{loopId}/dossier", "martin://runs/{loopId}/attempts/{attemptIndex}", "martin://runs/{loopId}/verification"];
9
10
  export declare const MARTIN_PROMPT_NAMES: readonly ["martin_start", "martin_preflight", "martin_triage", "martin_resume", "martin_prove", "martin_release_check", "martin_governed_coding_kickoff", "martin_debug_failed_run", "martin_publish_readiness_review", "martin_triage_run_store", "safe_bug_fix", "write_tests_first", "small_refactor", "security_review", "pr_review", "release_check"];
@@ -78,6 +78,24 @@ export const MARTIN_PAID_REMOTE_TOOL_NAMES = [
78
78
  "martin_dossier",
79
79
  "martin_eval"
80
80
  ];
81
+ export const MARTIN_DIRECTORY_TOOL_NAMES = [
82
+ "martin_doctor",
83
+ "martin_estimate",
84
+ "martin_plan",
85
+ "martin_preflight",
86
+ "martin_run",
87
+ "martin_pause",
88
+ "martin_cancel",
89
+ "martin_continue",
90
+ "martin_list_runs",
91
+ "martin_triage_runs",
92
+ "martin_get_run",
93
+ "martin_get_verification_results",
94
+ "martin_dossier",
95
+ "martin_logs",
96
+ "martin_arcade",
97
+ "martin_arcade_status"
98
+ ];
81
99
  export const MARTIN_RESOURCE_URIS = [
82
100
  "martin://server/health",
83
101
  "martin://runs/recent",
@@ -136,6 +154,7 @@ export function buildMartinDiscoveryMetadata(serverVersion) {
136
154
  profiles: {
137
155
  minimal: [...MARTIN_MINIMAL_TOOL_NAMES],
138
156
  diagnostic: [...MARTIN_DIAGNOSTIC_TOOL_NAMES],
157
+ directory: [...MARTIN_DIRECTORY_TOOL_NAMES],
139
158
  "github-review": [...MARTIN_GITHUB_REVIEW_TOOL_NAMES],
140
159
  "full-local": [...MARTIN_TOOL_NAMES],
141
160
  starter: [...MARTIN_STARTER_TOOL_NAMES],
@@ -1 +1 @@
1
- export declare const MARTIN_MCP_PACKAGE_VERSION = "0.5.7";
1
+ export declare const MARTIN_MCP_PACKAGE_VERSION = "0.6.0";
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by build — do not edit by hand.
2
2
  // Source of truth: packages/mcp/package.json#version
3
- export const MARTIN_MCP_PACKAGE_VERSION = "0.5.7";
3
+ export const MARTIN_MCP_PACKAGE_VERSION = "0.6.0";
package/dist/server.d.ts CHANGED
@@ -18,9 +18,12 @@
18
18
  */
19
19
  import { type Server as NodeHttpServer } from "node:http";
20
20
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
21
+ export type MartinMcpToolProfile = "full" | "directory";
22
+ export declare function shouldExposeMartinTool(name: string, profile: MartinMcpToolProfile): boolean;
21
23
  export declare function createMartinMcpServer(serverInfo?: {
22
24
  name?: string;
23
25
  version?: string;
26
+ toolProfile?: MartinMcpToolProfile;
24
27
  }): Server<{
25
28
  method: string;
26
29
  params?: {
@@ -55,7 +58,9 @@ export declare function createMartinMcpServer(serverInfo?: {
55
58
  } | undefined;
56
59
  } | undefined;
57
60
  }>;
58
- export declare function connectMartinMcpStdioServer(): Promise<Server<{
61
+ export declare function connectMartinMcpStdioServer(options?: {
62
+ toolProfile?: MartinMcpToolProfile;
63
+ }): Promise<Server<{
59
64
  method: string;
60
65
  params?: {
61
66
  [x: string]: unknown;
@@ -93,6 +98,7 @@ export interface MartinMcpHttpServerOptions {
93
98
  host?: string;
94
99
  port?: number;
95
100
  path?: string;
101
+ toolProfile?: MartinMcpToolProfile;
96
102
  }
97
103
  export interface MartinMcpHttpServerHandle {
98
104
  server: NodeHttpServer;
@@ -104,11 +110,13 @@ export interface MartinMcpHttpServerHandle {
104
110
  }
105
111
  export declare function parseMartinMcpServerArgs(argv: string[]): {
106
112
  transport: "stdio";
113
+ toolProfile?: MartinMcpToolProfile;
107
114
  } | {
108
115
  transport: "http";
109
116
  host: string;
110
117
  port: number;
111
118
  path: string;
119
+ toolProfile?: MartinMcpToolProfile;
112
120
  };
113
121
  export declare function connectMartinMcpHttpServer(options?: MartinMcpHttpServerOptions): Promise<MartinMcpHttpServerHandle>;
114
122
  export declare function isDirectExecutionEntry(entryPath: string | undefined, moduleUrl?: string): boolean;
package/dist/server.js CHANGED
@@ -26,6 +26,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
26
26
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
27
27
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
28
28
  import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
29
+ import { MARTIN_DIRECTORY_TOOL_NAMES } from "./discovery-metadata.js";
29
30
  import { MARTIN_ARCADE_FALLBACK_TEXT, MARTIN_ARCADE_MIME_TYPE, MARTIN_ARCADE_RESOURCE_URI, MARTIN_ARCADE_UI_EXTENSION_ID, buildArcadeToolDefinitions, supportsMartinArcadeApp } from "./arcade/capabilities.js";
30
31
  import { listArcadeResources, readArcadeResource } from "./arcade/resource.js";
31
32
  import { readArcadeStatus } from "./arcade/status.js";
@@ -59,6 +60,10 @@ const stringArraySchema = {
59
60
  type: "array",
60
61
  items: { type: "string" }
61
62
  };
63
+ const martinDirectoryToolNames = new Set(MARTIN_DIRECTORY_TOOL_NAMES);
64
+ export function shouldExposeMartinTool(name, profile) {
65
+ return profile === "full" || martinDirectoryToolNames.has(name);
66
+ }
62
67
  const loopPreviewSchema = {
63
68
  type: "object",
64
69
  additionalProperties: true,
@@ -839,6 +844,7 @@ const prReviewOutputSchema = {
839
844
  required: ["loopId", "verdict", "findings", "summary"]
840
845
  };
841
846
  export function createMartinMcpServer(serverInfo) {
847
+ const toolProfile = serverInfo?.toolProfile ?? "full";
842
848
  const server = new Server({
843
849
  name: serverInfo?.name ?? "martin-loop",
844
850
  version: serverInfo?.version ?? MARTIN_MCP_PACKAGE_VERSION
@@ -856,7 +862,7 @@ export function createMartinMcpServer(serverInfo) {
856
862
  tools: [
857
863
  {
858
864
  name: "martin_run",
859
- description: "Execute a governed Martin Loop run on a coding task and return the run summary, spend, artifact rollup, and verification state. This hard-blocks until martin_doctor, martin_estimate, martin_plan, and martin_preflight receipts exist for the same task.",
865
+ description: "Execute a governed MartinLoop coding run after MCP workflow admission is satisfied. Use when the user has authorized implementation, bug fixing, tests, or refactoring and doctor/estimate/plan/preflight receipts match this task. Do not use for question-only diagnosis or when policy, budget, credentials, or scope still need consent. Next: read martin_dossier and verifier evidence.",
860
866
  annotations: {
861
867
  destructiveHint: true,
862
868
  idempotentHint: false,
@@ -961,7 +967,7 @@ export function createMartinMcpServer(serverInfo) {
961
967
  },
962
968
  {
963
969
  name: "martin_status",
964
- description: "Return the current budget and cost state of a Martin loop record.",
970
+ description: "Read the current budget, cost, remaining limits, and stop pressure for one MartinLoop run. Provide exactly one selector: loopJson for an inline record, file for a saved record, loopId for a run-store ID, or latest for the newest run; runsDir only changes the run-store root. Use for a compact budget check. Do not use for full events or artifacts; use martin_get_run or martin_run_dossier instead.",
965
971
  annotations: {
966
972
  readOnlyHint: true,
967
973
  idempotentHint: true
@@ -973,11 +979,11 @@ export function createMartinMcpServer(serverInfo) {
973
979
  loopJson: { type: "string", description: "JSON-serialized LoopRecord." },
974
980
  file: {
975
981
  type: "string",
976
- description: "Optional path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
982
+ description: "Path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory. Mutually exclusive with loopJson, loopId, and latest."
977
983
  },
978
984
  loopId: {
979
985
  type: "string",
980
- description: "Loop ID resolved as <runsDir>/<loopId>/loop-record.json."
986
+ description: "Loop ID resolved as <runsDir>/<loopId>/loop-record.json. Mutually exclusive with loopJson, file, and latest."
981
987
  },
982
988
  runsDir: {
983
989
  type: "string",
@@ -985,7 +991,7 @@ export function createMartinMcpServer(serverInfo) {
985
991
  },
986
992
  latest: {
987
993
  const: true,
988
- description: "When true, loads the most recently updated loop record in the runs directory."
994
+ description: "When true, loads the most recently updated loop record. Mutually exclusive with loopJson, file, and loopId."
989
995
  }
990
996
  },
991
997
  oneOf: [
@@ -999,7 +1005,7 @@ export function createMartinMcpServer(serverInfo) {
999
1005
  },
1000
1006
  {
1001
1007
  name: "martin_doctor",
1002
- description: "Read-only environment and run-store diagnostics for the Martin MCP server. This is the expected first call before governed work begins.",
1008
+ description: "Read-only environment, engine, workspace, and run-store diagnostics for MartinLoop. Use first for software work, fresh installs, suspicious state, or before retries. Do not use as proof that a task is complete. Next: call martin_estimate or martin_triage_runs depending on whether this is new work or failed prior work.",
1003
1009
  annotations: {
1004
1010
  readOnlyHint: true,
1005
1011
  idempotentHint: true
@@ -1027,7 +1033,7 @@ export function createMartinMcpServer(serverInfo) {
1027
1033
  },
1028
1034
  {
1029
1035
  name: "martin_plan",
1030
- description: "Read-only planning step that turns an objective into a scoped implementation plan, verifier proposal, policy pack, and risk recommendation. Use before preflight and before any real coding run.",
1036
+ description: "Read-only planning step that turns an objective into bounded scope, verifier proposal, policy pack, and risk recommendation. Use for authorized software changes before preflight/run. Do not use to mutate files or replace the verifier. Next: call martin_preflight with the chosen scope, budget, and verifier.",
1031
1037
  annotations: {
1032
1038
  readOnlyHint: true,
1033
1039
  idempotentHint: true
@@ -1062,7 +1068,7 @@ export function createMartinMcpServer(serverInfo) {
1062
1068
  },
1063
1069
  {
1064
1070
  name: "martin_preflight",
1065
- description: "Read-only validation of a planned Martin run before any execution or spend. This is the last required step before martin_run.",
1071
+ description: "Read-only validation of the exact run contract before execution or spend. Use after planning and before martin_run to check engine, verifier, path scope, and budget. Do not use as execution or completion proof. Next: call martin_run if allowed, otherwise resolve the reported blocker.",
1066
1072
  annotations: {
1067
1073
  readOnlyHint: true,
1068
1074
  idempotentHint: true
@@ -1177,7 +1183,7 @@ export function createMartinMcpServer(serverInfo) {
1177
1183
  },
1178
1184
  {
1179
1185
  name: "martin_pause",
1180
- description: "Record a durable pause request for a Martin run so humans and runtimes can see that execution should pause before risky follow-up work.",
1186
+ description: "Write a durable pause receipt for one canonical MartinLoop run. Provide exactly one selector: file, loopId, or latest; runsDir changes the run-store root, while reason and requestedBy add audit context. Use for a temporary hold before risky follow-up work. This records a request and does not kill a process; use martin_cancel to abandon work or martin_continue to resume.",
1181
1187
  annotations: {
1182
1188
  destructiveHint: true,
1183
1189
  idempotentHint: false
@@ -1186,12 +1192,12 @@ export function createMartinMcpServer(serverInfo) {
1186
1192
  type: "object",
1187
1193
  additionalProperties: false,
1188
1194
  properties: {
1189
- file: { type: "string" },
1190
- loopId: { type: "string" },
1191
- runsDir: { type: "string" },
1192
- latest: { const: true },
1193
- reason: { type: "string" },
1194
- requestedBy: { type: "string" }
1195
+ file: { type: "string", description: "Path to one canonical run record or directory. Mutually exclusive with loopId and latest." },
1196
+ loopId: { type: "string", description: "Canonical MartinLoop run identifier. Mutually exclusive with file and latest." },
1197
+ runsDir: { type: "string", description: "Optional run-store root override used to resolve loopId or latest." },
1198
+ latest: { const: true, description: "When true, targets the latest canonical run. Mutually exclusive with file and loopId." },
1199
+ reason: { type: "string", description: "Optional non-empty reason recorded in the pause receipt." },
1200
+ requestedBy: { type: "string", description: "Optional human or runtime identity label recorded for audit context." }
1195
1201
  },
1196
1202
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1197
1203
  },
@@ -1199,7 +1205,7 @@ export function createMartinMcpServer(serverInfo) {
1199
1205
  },
1200
1206
  {
1201
1207
  name: "martin_cancel",
1202
- description: "Record a durable cancellation request for a Martin run. This writes a control receipt; it does not silently kill a process without evidence.",
1208
+ description: "Write a durable cancellation receipt for one canonical MartinLoop run. Provide exactly one selector: file, loopId, or latest; runsDir changes the run-store root, while reason and requestedBy add audit context. Use when work must be abandoned, not temporarily held. This records a request and does not kill a process; use martin_pause for a reversible hold.",
1203
1209
  annotations: {
1204
1210
  destructiveHint: true,
1205
1211
  idempotentHint: false
@@ -1208,12 +1214,12 @@ export function createMartinMcpServer(serverInfo) {
1208
1214
  type: "object",
1209
1215
  additionalProperties: false,
1210
1216
  properties: {
1211
- file: { type: "string" },
1212
- loopId: { type: "string" },
1213
- runsDir: { type: "string" },
1214
- latest: { const: true },
1215
- reason: { type: "string" },
1216
- requestedBy: { type: "string" }
1217
+ file: { type: "string", description: "Path to one canonical run record or directory. Mutually exclusive with loopId and latest." },
1218
+ loopId: { type: "string", description: "Canonical MartinLoop run identifier. Mutually exclusive with file and latest." },
1219
+ runsDir: { type: "string", description: "Optional run-store root override used to resolve loopId or latest." },
1220
+ latest: { const: true, description: "When true, targets the latest canonical run. Mutually exclusive with file and loopId." },
1221
+ reason: { type: "string", description: "Optional non-empty reason recorded in the cancellation receipt." },
1222
+ requestedBy: { type: "string", description: "Optional human or runtime identity label recorded for audit context." }
1217
1223
  },
1218
1224
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1219
1225
  },
@@ -1272,7 +1278,7 @@ export function createMartinMcpServer(serverInfo) {
1272
1278
  },
1273
1279
  {
1274
1280
  name: "martin_triage_runs",
1275
- description: "Prioritize Martin runs that need operator or agent attention based on verification, lifecycle, and budget pressure.",
1281
+ description: "Read-only prioritization of saved Martin runs that need attention. Use when the user says a prior attempt failed, asks what to fix next, or resumes an interrupted session. Do not use for a brand-new objective with no relevant run history. Next: inspect the selected run or dossier before retrying.",
1276
1282
  annotations: {
1277
1283
  readOnlyHint: true,
1278
1284
  idempotentHint: true
@@ -1393,7 +1399,7 @@ export function createMartinMcpServer(serverInfo) {
1393
1399
  },
1394
1400
  {
1395
1401
  name: "martin_run_dossier",
1396
- description: "Return the full governed execution dossier for one Martin run, including attempts, events, artifacts, and related discovery surfaces.",
1402
+ description: "Read the full structured execution dossier for one MartinLoop run, including attempts, events, artifacts, verification, integrity, cost, and related discovery surfaces. Provide exactly one selector: file, loopId, or latest; runsDir changes the run-store root. Use for comprehensive evidence review. Do not use for a compact state check; use martin_get_run, or use martin_dossier when formatted sharing output is required.",
1397
1403
  annotations: {
1398
1404
  readOnlyHint: true,
1399
1405
  idempotentHint: true
@@ -1404,13 +1410,13 @@ export function createMartinMcpServer(serverInfo) {
1404
1410
  properties: {
1405
1411
  file: {
1406
1412
  type: "string",
1407
- description: "Path to a canonical loop-record.json, legacy file, or run-store directory."
1413
+ description: "Path to a canonical loop-record.json, legacy file, or run-store directory. Mutually exclusive with loopId and latest."
1408
1414
  },
1409
- loopId: { type: "string", description: "Loop ID under the run store." },
1410
- runsDir: { type: "string", description: "Optional runs-root override." },
1415
+ loopId: { type: "string", description: "Loop ID under the run store. Mutually exclusive with file and latest." },
1416
+ runsDir: { type: "string", description: "Optional run-store root override used to resolve loopId or latest." },
1411
1417
  latest: {
1412
1418
  const: true,
1413
- description: "When true, loads the most recently updated loop record in the run store."
1419
+ description: "When true, loads the most recently updated loop record. Mutually exclusive with file and loopId."
1414
1420
  }
1415
1421
  },
1416
1422
  oneOf: [
@@ -1423,7 +1429,7 @@ export function createMartinMcpServer(serverInfo) {
1423
1429
  },
1424
1430
  {
1425
1431
  name: "martin_dossier",
1426
- description: "Alias for martin_run_dossier with support for JSON, Markdown, or GitHub PR formatting. Use after martin_run to understand what happened and whether the result is actually safe to trust.",
1432
+ description: "Read a formatted evidence summary for one MartinLoop run. Provide exactly one selector: file, loopId, or latest; runsDir changes the run-store root. Set format to json, md, or github-pr; json is the default. Use after martin_run, before merge or release claims, or when sharing what happened. Do not use as a substitute for missing verifier evidence; use martin_run_dossier for the full structured record. Next: review verification results, retry, or hand off the receipt.",
1427
1433
  annotations: {
1428
1434
  readOnlyHint: true,
1429
1435
  idempotentHint: true
@@ -1432,11 +1438,11 @@ export function createMartinMcpServer(serverInfo) {
1432
1438
  type: "object",
1433
1439
  additionalProperties: false,
1434
1440
  properties: {
1435
- file: { type: "string" },
1436
- loopId: { type: "string" },
1437
- runsDir: { type: "string" },
1438
- latest: { const: true },
1439
- format: { type: "string", enum: ["json", "md", "github-pr"] }
1441
+ file: { type: "string", description: "Path to one run record or directory. Mutually exclusive with loopId and latest." },
1442
+ loopId: { type: "string", description: "MartinLoop run identifier. Mutually exclusive with file and latest." },
1443
+ runsDir: { type: "string", description: "Optional run-store root override used to resolve loopId or latest." },
1444
+ latest: { const: true, description: "When true, loads the latest run. Mutually exclusive with file and loopId." },
1445
+ format: { type: "string", enum: ["json", "md", "github-pr"], description: "Output format. Defaults to json." }
1440
1446
  },
1441
1447
  oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1442
1448
  },
@@ -1552,7 +1558,7 @@ export function createMartinMcpServer(serverInfo) {
1552
1558
  },
1553
1559
  {
1554
1560
  name: "martin_estimate",
1555
- description: "Estimate the cost, recommended route, and Pre Work Burn for an objective without spending anything. Use before martin_run to understand what a task will cost.",
1561
+ description: "Read-only cost, route, and pre-work burn estimate for a software objective. Use before planning/preflight when a change may spend agent time or exceed budget. Do not use for casual questions or as permission to execute. Next: call martin_plan if the estimate is acceptable, or ask for consent if budget/risk is high.",
1556
1562
  annotations: {
1557
1563
  readOnlyHint: true,
1558
1564
  idempotentHint: true
@@ -1608,7 +1614,7 @@ export function createMartinMcpServer(serverInfo) {
1608
1614
  }
1609
1615
  },
1610
1616
  ...buildArcadeToolDefinitions(server.getClientCapabilities())
1611
- ]
1617
+ ].filter((tool) => shouldExposeMartinTool(tool.name, toolProfile))
1612
1618
  }));
1613
1619
  server.setRequestHandler(ListResourcesRequestSchema, () => {
1614
1620
  const listed = listMartinResources();
@@ -1929,8 +1935,10 @@ function normalizeRunBudget(input) {
1929
1935
  maxTokens: input.maxTokens
1930
1936
  });
1931
1937
  }
1932
- export async function connectMartinMcpStdioServer() {
1933
- const server = createMartinMcpServer();
1938
+ export async function connectMartinMcpStdioServer(options = {}) {
1939
+ const server = createMartinMcpServer({
1940
+ ...(options.toolProfile ? { toolProfile: options.toolProfile } : {})
1941
+ });
1934
1942
  const transport = new StdioServerTransport();
1935
1943
  await server.connect(transport);
1936
1944
  return server;
@@ -1940,12 +1948,22 @@ export function parseMartinMcpServerArgs(argv) {
1940
1948
  let host = "127.0.0.1";
1941
1949
  let port = 3033;
1942
1950
  let path = "/mcp";
1951
+ let toolProfile;
1943
1952
  for (let index = 0; index < argv.length; index += 1) {
1944
1953
  const token = argv[index];
1945
1954
  if (token === "--http") {
1946
1955
  transport = "http";
1947
1956
  continue;
1948
1957
  }
1958
+ if (token === "--tool-profile") {
1959
+ const next = argv[index + 1];
1960
+ if (next !== "full" && next !== "directory") {
1961
+ throw new Error(`Invalid --tool-profile value '${next ?? ""}'.`);
1962
+ }
1963
+ toolProfile = next;
1964
+ index += 1;
1965
+ continue;
1966
+ }
1949
1967
  if (token === "--port") {
1950
1968
  const next = argv[index + 1];
1951
1969
  const parsed = Number(next);
@@ -1975,14 +1993,16 @@ export function parseMartinMcpServerArgs(argv) {
1975
1993
  }
1976
1994
  }
1977
1995
  return transport === "http"
1978
- ? { transport, host, port, path }
1979
- : { transport };
1996
+ ? { transport, host, port, path, ...(toolProfile ? { toolProfile } : {}) }
1997
+ : { transport, ...(toolProfile ? { toolProfile } : {}) };
1980
1998
  }
1981
1999
  export async function connectMartinMcpHttpServer(options = {}) {
1982
2000
  const host = options.host ?? "127.0.0.1";
1983
2001
  const port = options.port ?? 3033;
1984
2002
  const path = options.path ?? "/mcp";
1985
- const mcpServer = createMartinMcpServer();
2003
+ const mcpServer = createMartinMcpServer({
2004
+ ...(options.toolProfile ? { toolProfile: options.toolProfile } : {})
2005
+ });
1986
2006
  const transport = new StreamableHTTPServerTransport({
1987
2007
  sessionIdGenerator: undefined
1988
2008
  });
@@ -2068,7 +2088,7 @@ if (isDirectExecution()) {
2068
2088
  await connectMartinMcpHttpServer(startup);
2069
2089
  }
2070
2090
  else {
2071
- await connectMartinMcpStdioServer();
2091
+ await connectMartinMcpStdioServer({ toolProfile: startup.toolProfile });
2072
2092
  }
2073
2093
  }
2074
2094
  async function readJsonBody(req, res) {
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, appendLoopEvent, createLoopRecord } from "../contracts/index.js";
2
3
  import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
3
4
  import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
@@ -1135,7 +1136,7 @@ export async function runMartin(input) {
1135
1136
  // VERIFY: Run grounding scan on patch diff if available
1136
1137
  // Uses the task's repoRoot to build/load the grounding index, then scans any diff
1137
1138
  let groundingScanResult;
1138
- const patchDiff = buildPatchDiff(result, changedFiles);
1139
+ const patchDiff = buildPatchDiff(result, changedFiles, input.task.repoRoot);
1139
1140
  if (patchDiff && input.task.repoRoot) {
1140
1141
  try {
1141
1142
  const groundingIndex = await loadOrBuildRepoGroundingIndex(input.task.repoRoot);
@@ -1568,21 +1569,35 @@ function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
1568
1569
  }
1569
1570
  return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
1570
1571
  }
1571
- function buildPatchDiff(result, changedFiles) {
1572
+ function buildPatchDiff(result, changedFiles, repoRoot) {
1572
1573
  // Use structured diff stats to build a minimal diff header if no raw diff is available
1573
- if (result.execution?.changedFiles?.length) {
1574
- // Build a synthetic diff header from changed file list
1575
- return result.execution.changedFiles
1576
- .map((file) => `--- a/${file}\n+++ b/${file}\n@@ -0,0 +1 @@\n+`)
1577
- .join("\n");
1578
- }
1579
- if (changedFiles.length > 0) {
1580
- return changedFiles
1581
- .map((file) => `--- a/${file}\n+++ b/${file}\n@@ -0,0 +1 @@\n+`)
1574
+ const files = result.execution?.changedFiles?.length ? result.execution.changedFiles : changedFiles;
1575
+ if (files.length > 0) {
1576
+ return files
1577
+ .map((file) => buildSyntheticDiffHeader(file, repoRoot))
1582
1578
  .join("\n");
1583
1579
  }
1584
1580
  return undefined;
1585
1581
  }
1582
+ function buildSyntheticDiffHeader(file, repoRoot) {
1583
+ const normalizedFile = file.replace(/\\/gu, "/");
1584
+ const isNewWorkspaceFile = repoRoot ? isUntrackedWorkspaceFile(repoRoot, normalizedFile) : false;
1585
+ const before = isNewWorkspaceFile ? "/dev/null" : `a/${normalizedFile}`;
1586
+ return `--- ${before}\n+++ b/${normalizedFile}\n@@ -0,0 +1 @@\n+`;
1587
+ }
1588
+ function isUntrackedWorkspaceFile(repoRoot, file) {
1589
+ const result = spawnSync("git", ["ls-files", "--others", "--exclude-standard", "--", file], {
1590
+ cwd: repoRoot,
1591
+ encoding: "utf8"
1592
+ });
1593
+ if (result.status !== 0 || typeof result.stdout !== "string") {
1594
+ return false;
1595
+ }
1596
+ return result.stdout
1597
+ .split(/\r?\n/u)
1598
+ .map((line) => line.trim().replace(/\\/gu, "/"))
1599
+ .includes(file);
1600
+ }
1586
1601
  function createBudgetSettlement(input) {
1587
1602
  const totalActualUsd = getUsageUsd(input.usage);
1588
1603
  return {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@martinloop/mcp",
3
- "version": "0.5.7",
3
+ "version": "0.6.0",
4
4
  "mcpName": "io.github.Keesan12/martin-loop",
5
5
  "private": false,
6
6
  "type": "module",
7
- "description": "MCP server for AI coding agents \u00e2\u20ac\u201d verifier gates, stop limits, and Verified Handoffs.",
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/",
@@ -41,6 +41,11 @@
41
41
  "martin-loop-mcp": "./dist/server.js"
42
42
  },
43
43
  "exports": {
44
+ ".": {
45
+ "types": "./dist/server.d.ts",
46
+ "import": "./dist/server.js",
47
+ "default": "./dist/server.js"
48
+ },
44
49
  "./server.json": "./server.json",
45
50
  "./package.json": "./package.json"
46
51
  },
@@ -74,12 +79,12 @@
74
79
  "mcpb:smoke": "node ./mcpb/smoke-mcpb.mjs"
75
80
  },
76
81
  "dependencies": {
77
- "@modelcontextprotocol/sdk": "^1.29.0",
82
+ "@modelcontextprotocol/sdk": "1.29.0",
78
83
  "@open-policy-agent/opa-wasm": "^1.10.0",
79
- "@opentelemetry/api-logs": "^0.214.0",
80
- "@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
81
- "@opentelemetry/resources": "^2.6.1",
82
- "@opentelemetry/sdk-logs": "^0.214.0",
84
+ "@opentelemetry/api-logs": "^0.221.0",
85
+ "@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
86
+ "@opentelemetry/resources": "^2.10.0",
87
+ "@opentelemetry/sdk-logs": "^0.221.0",
83
88
  "ts-morph": "^21.0.0"
84
89
  },
85
90
  "devDependencies": {
@@ -87,4 +92,4 @@
87
92
  "@martin/contracts": "workspace:*",
88
93
  "@martin/presentation": "workspace:*"
89
94
  }
90
- }
95
+ }
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.5.7",
10
+ "version": "0.6.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@martinloop/mcp",
15
- "version": "0.5.7",
15
+ "version": "0.6.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }