@martinloop/mcp 0.1.2 → 0.1.3
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.
- package/README.md +83 -41
- package/dist/server.js +27 -12
- package/package.json +5 -8
- package/server.json +21 -0
package/README.md
CHANGED
|
@@ -1,81 +1,110 @@
|
|
|
1
1
|
# @martinloop/mcp
|
|
2
2
|
|
|
3
|
-
Governed MCP server for AI coding agents
|
|
3
|
+
Governed MCP server for AI coding agents that need hard spend limits, verifier gates, scoped file edits, and inspectable run records.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@martinloop/mcp` exposes three stdio tools:
|
|
6
6
|
|
|
7
7
|
- `martin_run`
|
|
8
8
|
- `martin_inspect`
|
|
9
9
|
- `martin_status`
|
|
10
10
|
|
|
11
|
-
## What
|
|
11
|
+
## What This Server Is For
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
13
|
+
Use this MCP when a host already knows how to delegate coding work, but you want Martin Loop to bound that work with:
|
|
14
|
+
|
|
15
|
+
- a hard budget ceiling (`maxUsd`)
|
|
16
|
+
- an attempt ceiling (`maxIterations`)
|
|
17
|
+
- a total token ceiling (`maxTokens`)
|
|
18
|
+
- verifier commands (`verificationPlan`)
|
|
19
|
+
- allowed and denied file globs
|
|
20
|
+
- persisted run records you can inspect afterward
|
|
21
|
+
|
|
22
|
+
It is a good fit for Claude Code, Codex-oriented hosts, and other MCP clients that want governed code-change execution instead of open-ended retry behavior.
|
|
23
|
+
|
|
24
|
+
For host-facing integration guidance, see [MCP for AI Agents](https://github.com/Keesan12/martin-loop/blob/main/docs/oss/MCP-FOR-AI-AGENTS.md).
|
|
19
25
|
|
|
20
26
|
## Quickstart
|
|
21
27
|
|
|
22
28
|
Run the packaged server directly:
|
|
23
29
|
|
|
24
30
|
```sh
|
|
25
|
-
npx @martinloop/mcp
|
|
31
|
+
npx -y @martinloop/mcp
|
|
26
32
|
```
|
|
27
33
|
|
|
28
34
|
Add it to Claude Code:
|
|
29
35
|
|
|
30
36
|
```sh
|
|
31
37
|
# macOS/Linux
|
|
32
|
-
claude mcp add --scope user martin-loop -- npx @martinloop/mcp
|
|
38
|
+
claude mcp add --scope user martin-loop -- npx -y @martinloop/mcp
|
|
33
39
|
|
|
34
40
|
# Windows PowerShell/cmd
|
|
35
|
-
claude mcp add --scope user martin-loop cmd /c "npx @martinloop/mcp"
|
|
41
|
+
claude mcp add --scope user martin-loop cmd /c "npx -y @martinloop/mcp"
|
|
36
42
|
```
|
|
37
43
|
|
|
38
|
-
Generic stdio configuration
|
|
44
|
+
Generic stdio configuration:
|
|
39
45
|
|
|
40
46
|
```json
|
|
41
47
|
{
|
|
42
48
|
"type": "stdio",
|
|
43
49
|
"command": "npx",
|
|
44
|
-
"args": ["@martinloop/mcp"]
|
|
50
|
+
"args": ["-y", "@martinloop/mcp"]
|
|
45
51
|
}
|
|
46
52
|
```
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
Codex host configuration in `~/.codex/config.toml`:
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
56
|
+
```toml
|
|
57
|
+
[mcp_servers.martin-loop]
|
|
58
|
+
command = "npx"
|
|
59
|
+
args = ["-y", "@martinloop/mcp"]
|
|
60
|
+
```
|
|
53
61
|
|
|
54
62
|
## Requirements
|
|
55
63
|
|
|
56
64
|
- Node 20+
|
|
57
|
-
- For live
|
|
58
|
-
- For
|
|
65
|
+
- For live `martin_run` usage, either the `claude` CLI or the `codex` CLI must be available on `PATH`
|
|
66
|
+
- For stub or smoke flows, set `MARTIN_LIVE=false`
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
Example stub launch:
|
|
61
69
|
|
|
62
70
|
```sh
|
|
63
|
-
MARTIN_LIVE=false npx @martinloop/mcp
|
|
71
|
+
MARTIN_LIVE=false npx -y @martinloop/mcp
|
|
64
72
|
```
|
|
65
73
|
|
|
66
|
-
## Tool
|
|
74
|
+
## Tool Contract
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
| Tool | Purpose | Required input | Important optional input | Notes |
|
|
77
|
+
| --- | --- | --- | --- | --- |
|
|
78
|
+
| `martin_run` | Run a governed coding loop | `objective` | `workingDirectory`, `engine`, `model`, `maxUsd`, `maxIterations`, `maxTokens`, `verificationPlan`, `allowedPaths`, `deniedPaths`, `workspaceId`, `projectId` | Unknown arguments are rejected. |
|
|
79
|
+
| `martin_inspect` | Read a saved run record or run folder | none | `file`, `runsDir` | `file` may point to a `loop-record.json`, legacy `.jsonl`, or a run directory under the runs root. |
|
|
80
|
+
| `martin_status` | Report budget pressure and stop conditions | exactly one of `loopJson`, `file`, `loopId`, or `latest` | `runsDir` | `latest` must be `true` when used. |
|
|
81
|
+
|
|
82
|
+
## Safe-Root Path Model
|
|
83
|
+
|
|
84
|
+
This MCP does not let tool callers point at arbitrary filesystem locations. The server resolves tool paths against safe roots chosen when the server starts.
|
|
85
|
+
|
|
86
|
+
- `workingDirectory`
|
|
87
|
+
Defaults to `MARTIN_MCP_WORKSPACE_ROOT` or the server process current directory. If you pass a value, it must still resolve inside that workspace root. `.` and repo-relative subpaths are the safest choices.
|
|
88
|
+
- `file`
|
|
89
|
+
For `martin_inspect` and `martin_status`, `file` resolves under the runs root, not the whole machine. Direct file targets must end in `.json` or `.jsonl`; run directories are also accepted where the tool supports them.
|
|
90
|
+
- `runsDir`
|
|
91
|
+
Defaults to `MARTIN_RUNS_DIR` or `~/.martin/runs`. Passing `runsDir` only re-states or narrows that safe runs root; it does not grant access outside it.
|
|
92
|
+
- `allowedPaths` and `deniedPaths`
|
|
93
|
+
These are relative glob patterns only. Absolute paths, drive-qualified paths, and patterns containing `..` are rejected.
|
|
94
|
+
|
|
95
|
+
Absolute paths can work only when they still resolve inside the corresponding safe root. Escapes above the workspace or runs root are rejected.
|
|
96
|
+
|
|
97
|
+
## Tool Examples
|
|
69
98
|
|
|
70
|
-
|
|
99
|
+
### `martin_run`
|
|
71
100
|
|
|
72
101
|
```json
|
|
73
102
|
{
|
|
74
103
|
"objective": "Fix the auth regression and prove it with tests",
|
|
75
104
|
"engine": "codex",
|
|
76
|
-
"
|
|
77
|
-
"softLimitUsd": 2.25,
|
|
105
|
+
"maxUsd": 3,
|
|
78
106
|
"maxIterations": 3,
|
|
107
|
+
"maxTokens": 20000,
|
|
79
108
|
"verificationPlan": ["pnpm test --filter auth"],
|
|
80
109
|
"workingDirectory": ".",
|
|
81
110
|
"allowedPaths": ["src/**", "tests/**"],
|
|
@@ -85,25 +114,25 @@ Example request body:
|
|
|
85
114
|
|
|
86
115
|
### `martin_inspect`
|
|
87
116
|
|
|
88
|
-
Inspect the default
|
|
117
|
+
Inspect the default runs root:
|
|
89
118
|
|
|
90
119
|
```json
|
|
91
120
|
{}
|
|
92
121
|
```
|
|
93
122
|
|
|
94
|
-
Inspect a
|
|
123
|
+
Inspect a specific saved loop record under the runs root:
|
|
95
124
|
|
|
96
125
|
```json
|
|
97
126
|
{
|
|
98
|
-
"file": "
|
|
127
|
+
"file": "loop-123/loop-record.json"
|
|
99
128
|
}
|
|
100
129
|
```
|
|
101
130
|
|
|
102
|
-
Inspect a
|
|
131
|
+
Inspect a subdirectory under the configured runs root:
|
|
103
132
|
|
|
104
133
|
```json
|
|
105
134
|
{
|
|
106
|
-
"runsDir": "
|
|
135
|
+
"runsDir": "team-a"
|
|
107
136
|
}
|
|
108
137
|
```
|
|
109
138
|
|
|
@@ -121,27 +150,36 @@ Status for a specific persisted loop:
|
|
|
121
150
|
|
|
122
151
|
```json
|
|
123
152
|
{
|
|
124
|
-
"loopId": "loop-123"
|
|
125
|
-
|
|
153
|
+
"loopId": "loop-123"
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Status from inline JSON:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"loopJson": "{\"loopId\":\"loop-123\",\"status\":\"completed\",\"lifecycleState\":\"completed\",\"attempts\":[],\"budget\":{\"maxUsd\":5,\"softLimitUsd\":3,\"maxIterations\":2,\"maxTokens\":1000},\"cost\":{\"actualUsd\":1.25,\"avoidedUsd\":0,\"tokensIn\":20,\"tokensOut\":10}}"
|
|
126
162
|
}
|
|
127
163
|
```
|
|
128
164
|
|
|
129
|
-
##
|
|
165
|
+
## Registry Metadata
|
|
166
|
+
|
|
167
|
+
The registry manifest artifact for this package is `server.json`. In this repository, that manifest is authored at `packages/mcp/server.json`.
|
|
130
168
|
|
|
131
|
-
|
|
169
|
+
Current metadata:
|
|
132
170
|
|
|
133
171
|
- npm package: `@martinloop/mcp`
|
|
134
172
|
- registry server name: `io.github.keesan12/martin-loop`
|
|
135
|
-
- manifest
|
|
173
|
+
- manifest artifact name: `server.json`
|
|
136
174
|
|
|
137
|
-
|
|
175
|
+
Official MCP Registry publication is separate from npm publication. After publishing the package to npm, run the publisher from `packages/mcp`:
|
|
138
176
|
|
|
139
177
|
```sh
|
|
140
178
|
mcp-publisher login github
|
|
141
179
|
mcp-publisher publish
|
|
142
180
|
```
|
|
143
181
|
|
|
144
|
-
##
|
|
182
|
+
## Verification
|
|
145
183
|
|
|
146
184
|
From the repository root:
|
|
147
185
|
|
|
@@ -153,5 +191,9 @@ pnpm --filter @martinloop/mcp smoke:pack
|
|
|
153
191
|
pnpm --filter @martinloop/mcp smoke:published
|
|
154
192
|
```
|
|
155
193
|
|
|
156
|
-
- `smoke:pack`
|
|
157
|
-
- `smoke:published`
|
|
194
|
+
- `smoke:pack` verifies the packed tarball shape and a stdio MCP launch
|
|
195
|
+
- `smoke:published` verifies the npm-installed artifact through `npm install` plus live MCP tool calls
|
|
196
|
+
|
|
197
|
+
## Version Notes
|
|
198
|
+
|
|
199
|
+
The root `CHANGELOG.md` is repo-wide and includes non-MCP changes. For the `@martinloop/mcp` surface, prefer this README, `server.json`, and the MCP release notes under `docs/release/`.
|
package/dist/server.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* Manual start:
|
|
18
18
|
* node dist/server.js
|
|
19
19
|
*/
|
|
20
|
+
import { createRequire } from "node:module";
|
|
20
21
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
23
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -24,7 +25,9 @@ import { getStatusTool } from "./tools/get-status.js";
|
|
|
24
25
|
import { inspectLoopTool } from "./tools/inspect-loop.js";
|
|
25
26
|
import { runLoopTool } from "./tools/run-loop.js";
|
|
26
27
|
import { sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
|
|
27
|
-
const
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
const packageJson = require("../package.json");
|
|
30
|
+
const server = new Server({ name: "martin-loop", version: packageJson.version }, { capabilities: { tools: {} } });
|
|
28
31
|
// ---------------------------------------------------------------------------
|
|
29
32
|
// Tool manifest
|
|
30
33
|
// ---------------------------------------------------------------------------
|
|
@@ -35,6 +38,7 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
35
38
|
description: "Execute a full Martin Loop on a coding task. Martin spawns the selected agent CLI (claude or codex), runs the task, classifies failures, and retries within the specified budget. Returns the loop outcome including lifecycle state, attempt count, and spend.",
|
|
36
39
|
inputSchema: {
|
|
37
40
|
type: "object",
|
|
41
|
+
additionalProperties: false,
|
|
38
42
|
properties: {
|
|
39
43
|
objective: {
|
|
40
44
|
type: "string",
|
|
@@ -42,7 +46,7 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
42
46
|
},
|
|
43
47
|
workingDirectory: {
|
|
44
48
|
type: "string",
|
|
45
|
-
description: "
|
|
49
|
+
description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
|
|
46
50
|
},
|
|
47
51
|
engine: {
|
|
48
52
|
type: "string",
|
|
@@ -55,14 +59,17 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
55
59
|
},
|
|
56
60
|
maxUsd: {
|
|
57
61
|
type: "number",
|
|
62
|
+
exclusiveMinimum: 0,
|
|
58
63
|
description: "Hard budget ceiling in USD. Defaults to 25."
|
|
59
64
|
},
|
|
60
65
|
maxIterations: {
|
|
61
|
-
type: "
|
|
66
|
+
type: "integer",
|
|
67
|
+
exclusiveMinimum: 0,
|
|
62
68
|
description: "Maximum number of loop attempts. Defaults to 8."
|
|
63
69
|
},
|
|
64
70
|
maxTokens: {
|
|
65
|
-
type: "
|
|
71
|
+
type: "integer",
|
|
72
|
+
exclusiveMinimum: 0,
|
|
66
73
|
description: "Maximum total tokens across all attempts. Defaults to 80000."
|
|
67
74
|
},
|
|
68
75
|
verificationPlan: {
|
|
@@ -73,12 +80,12 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
73
80
|
allowedPaths: {
|
|
74
81
|
type: "array",
|
|
75
82
|
items: { type: "string" },
|
|
76
|
-
description: "
|
|
83
|
+
description: "Repo-relative path globs Martin may modify, such as ['src/**', 'tests/**']. Absolute paths and '..' traversal are rejected."
|
|
77
84
|
},
|
|
78
85
|
deniedPaths: {
|
|
79
86
|
type: "array",
|
|
80
87
|
items: { type: "string" },
|
|
81
|
-
description: "
|
|
88
|
+
description: "Repo-relative path globs Martin must never modify, such as ['.env', 'docs/security/**']. Absolute paths and '..' traversal are rejected."
|
|
82
89
|
},
|
|
83
90
|
workspaceId: {
|
|
84
91
|
type: "string",
|
|
@@ -97,14 +104,15 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
97
104
|
description: "Summarise Martin Loop run records from a saved loop file or run-store directory. Supports canonical loop-record.json files, legacy JSONL files, and full runs directories.",
|
|
98
105
|
inputSchema: {
|
|
99
106
|
type: "object",
|
|
107
|
+
additionalProperties: false,
|
|
100
108
|
properties: {
|
|
101
109
|
file: {
|
|
102
110
|
type: "string",
|
|
103
|
-
description: "Optional path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
|
|
111
|
+
description: "Optional path resolved under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
|
|
104
112
|
},
|
|
105
113
|
runsDir: {
|
|
106
114
|
type: "string",
|
|
107
|
-
description: "Optional Martin runs
|
|
115
|
+
description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
118
|
}
|
|
@@ -114,6 +122,7 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
114
122
|
description: "Return the current budget and cost state of a Martin loop record. Accepts inline JSON, a saved loop file, a loopId under the run store, or the latest run in the store.",
|
|
115
123
|
inputSchema: {
|
|
116
124
|
type: "object",
|
|
125
|
+
additionalProperties: false,
|
|
117
126
|
properties: {
|
|
118
127
|
loopJson: {
|
|
119
128
|
type: "string",
|
|
@@ -121,7 +130,7 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
121
130
|
},
|
|
122
131
|
file: {
|
|
123
132
|
type: "string",
|
|
124
|
-
description: "Optional path under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
|
|
133
|
+
description: "Optional path resolved under the Martin runs root to a loop-record.json file, a legacy .jsonl file, or a run-store directory."
|
|
125
134
|
},
|
|
126
135
|
loopId: {
|
|
127
136
|
type: "string",
|
|
@@ -129,13 +138,19 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
|
129
138
|
},
|
|
130
139
|
runsDir: {
|
|
131
140
|
type: "string",
|
|
132
|
-
description: "Optional Martin runs
|
|
141
|
+
description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
|
|
133
142
|
},
|
|
134
143
|
latest: {
|
|
135
|
-
|
|
144
|
+
const: true,
|
|
136
145
|
description: "When true, loads the most recently updated loop record in the runs directory."
|
|
137
146
|
}
|
|
138
|
-
}
|
|
147
|
+
},
|
|
148
|
+
oneOf: [
|
|
149
|
+
{ required: ["loopJson"] },
|
|
150
|
+
{ required: ["file"] },
|
|
151
|
+
{ required: ["loopId"] },
|
|
152
|
+
{ required: ["latest"] }
|
|
153
|
+
]
|
|
139
154
|
}
|
|
140
155
|
}
|
|
141
156
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martinloop/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"mcpName": "io.github.keesan12/martin-loop",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -24,22 +24,18 @@
|
|
|
24
24
|
"claude",
|
|
25
25
|
"codex"
|
|
26
26
|
],
|
|
27
|
-
"main": "./dist/server.js",
|
|
28
|
-
"types": "./dist/server.d.ts",
|
|
29
27
|
"bin": {
|
|
30
28
|
"mcp": "./dist/server.js",
|
|
31
29
|
"martin-loop-mcp": "./dist/server.js"
|
|
32
30
|
},
|
|
33
31
|
"exports": {
|
|
34
|
-
".":
|
|
35
|
-
"types": "./dist/server.d.ts",
|
|
36
|
-
"default": "./dist/server.js"
|
|
37
|
-
},
|
|
32
|
+
"./server.json": "./server.json",
|
|
38
33
|
"./package.json": "./package.json"
|
|
39
34
|
},
|
|
40
35
|
"files": [
|
|
41
36
|
"dist",
|
|
42
|
-
"README.md"
|
|
37
|
+
"README.md",
|
|
38
|
+
"server.json"
|
|
43
39
|
],
|
|
44
40
|
"engines": {
|
|
45
41
|
"node": ">=20.0.0"
|
|
@@ -52,6 +48,7 @@
|
|
|
52
48
|
"prepack": "pnpm build",
|
|
53
49
|
"smoke:pack": "node ./scripts/smoke-package.mjs",
|
|
54
50
|
"smoke:published": "node ./scripts/smoke-published-package.mjs",
|
|
51
|
+
"smoke:published:pack": "node ./scripts/smoke-published-package.mjs --package-spec=pack",
|
|
55
52
|
"test": "vitest run",
|
|
56
53
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
57
54
|
"start": "node dist/server.js"
|
package/server.json
ADDED
|
@@ -0,0 +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, policy checks, and audit trails.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/Keesan12/martin-loop",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.1.3",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"identifier": "@martinloop/mcp",
|
|
15
|
+
"version": "0.1.3",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|