make-runner-mcp 2.1.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.
package/README.md ADDED
@@ -0,0 +1,482 @@
1
+ # make-runner-mcp
2
+
3
+ > **Disclaimer — read before using.** This is a personal convenience tool
4
+ > for running `make` commands in *your own* projects through an AI coding
5
+ > agent, built to narrow what that agent can do compared to raw shell or
6
+ > Docker socket access. **It is not a hardened security boundary, it has
7
+ > not been independently audited, and it is not a substitute for actually
8
+ > reviewing what's in your Makefile.** Don't use it in any context where
9
+ > the blast radius of a mistake matters — shared infrastructure,
10
+ > production systems, other people's data, or anywhere you wouldn't
11
+ > already trust the Makefile itself to be run unattended. See
12
+ > ["Risks and limitations"](#risks-and-limitations) below before relying
13
+ > on it for anything beyond that.
14
+
15
+ Scoped MCP server that discovers a project's Makefile targets and exposes
16
+ each one as an individual MCP tool. No free-form shell/docker access is
17
+ ever given to the model — only targets that literally exist in the
18
+ project's own Makefile, minus anything hard-denied (deploy/destroy/prod/
19
+ publish/release) or excluded via `.mcp-make-config.json`.
20
+
21
+ ## Install
22
+
23
+ Nothing to install ahead of time for end users — each client config below
24
+ runs the server straight from this GitHub repo via `npx`, pinned to a
25
+ release tag. `npx` fetches and caches it on first use.
26
+
27
+ To work on the server itself (e.g. to run it locally or through the
28
+ inspector below):
29
+
30
+ ```bash
31
+ npm install
32
+ ```
33
+
34
+ ## Two ways to run this — pick based on whether your agent is sandboxed
35
+
36
+ **`MCP_TRANSPORT=http` is the default.** This is a breaking change from
37
+ earlier versions, which defaulted to `stdio`: a client config that spawns
38
+ this as a subprocess expecting a stdio handshake (the old default) now
39
+ needs `MCP_TRANSPORT: "stdio"` added to its `env` block explicitly, or
40
+ it'll get an HTTP server trying to bind a port instead of speaking MCP
41
+ over its own stdin/stdout.
42
+
43
+ - **`stdio` (opt-in)** — the client spawns this as a subprocess and speaks
44
+ MCP over its stdin/stdout. Simplest option: no port, no token, no
45
+ process to manage yourself. Use this for direct, unsandboxed use — an
46
+ agent running with normal access to your machine. See
47
+ [Client configs (stdio)](#client-configs-stdio) below.
48
+ - **`http` (default)** — this runs as its own persistent process, with
49
+ clients connecting to it over the network instead of spawning it. Use
50
+ this when the calling agent runs inside its own sandbox (e.g. Gemini
51
+ CLI's `--sandbox`): a sandboxed agent that spawns an MCP server via
52
+ `stdio` spawns it *inside its own sandbox container*, which means the
53
+ server (and anything it shells out to, e.g. `docker`) only has whatever
54
+ access that container has — typically none, and giving it more (like a
55
+ mounted Docker socket) hands that same access to the agent's own native
56
+ shell tool too, since the sandbox isn't scoped per-tool. Running this
57
+ server outside the sandbox instead, reached only over the network, means
58
+ the sandbox never needs Docker access at all — only this server does,
59
+ and it only ever runs the target you named. See
60
+ [Running as a persistent HTTP server](#running-as-a-persistent-http-server-for-a-sandboxed-agent)
61
+ below — this is a real, verified pattern, not a theoretical one.
62
+
63
+ ## Verify it works standalone
64
+
65
+ ```bash
66
+ MCP_TRANSPORT=stdio npx @modelcontextprotocol/inspector node server.js
67
+ ```
68
+
69
+ This opens a local UI to list tools and call them directly, useful for
70
+ confirming the Makefile parsing looks right before wiring it into an agent.
71
+
72
+ For a quicker, non-interactive check — especially useful when a target you
73
+ expect isn't showing up — run:
74
+
75
+ ```bash
76
+ PROJECT_DIR=/path/to/project node server.js --diagnose
77
+ ```
78
+
79
+ This parses that project's Makefile(s) the same way the real server does
80
+ and prints every target it found (and which file each one actually runs
81
+ against), every target the denylist/config blocks and why, and anything it
82
+ had to silently skip while parsing (an unresolvable `$(VAR)` in an
83
+ `include` or `also-read` path, a missing file, a path that resolved
84
+ outside the project) — the causes of the vast majority of "why isn't my
85
+ target showing up" questions. See [`MAKEFILE-GUIDE.md`](./MAKEFILE-GUIDE.md)
86
+ for what to do about each thing it reports.
87
+
88
+ ## Multiple Makefiles (a root Makefile plus e.g. `docker/Makefile`)
89
+
90
+ make-runner-mcp only ever reads from `<PROJECT_DIR>/Makefile`, but that
91
+ file doesn't have to contain every target itself — three ways to pull in a
92
+ second file's targets are supported, in order of preference:
93
+
94
+ 1. **A real `include`/`-include`/`sinclude` directive** — e.g. `include
95
+ docker/Makefile` in the root Makefile. This is the normal GNU Make
96
+ mechanism; use it whenever you can, since it also keeps any variables
97
+ the root Makefile sets in scope for the included file's recipes.
98
+ 2. **The catch-all forwarding idiom** — a bare `%:` rule in the root
99
+ Makefile whose recipe runs `$(MAKE) -C docker $@`, forwarding any goal
100
+ not otherwise defined there into `docker/Makefile`. Common when a
101
+ project genuinely wants two independent Makefiles but still wants `make
102
+ <anything>` to work uniformly from the root.
103
+ 3. **The `## make-runner: also-read docker/Makefile` comment marker** — a
104
+ fallback for cases 1 and 2 can't cover: an `include` path built from a
105
+ variable (`include $(ENV).mk`), or a second Makefile that's genuinely
106
+ separate with no real link to the root one at all. This is a hint read
107
+ by make-runner-mcp only, with no effect on what `make` itself does —
108
+ the linked file's targets are then run directly against that file, from
109
+ its own directory, so they won't see variables the root Makefile sets.
110
+
111
+ Run `--diagnose` (above) after adding any of these to confirm the targets
112
+ you expect actually show up.
113
+
114
+ ### Fixing it automatically, not just diagnosing it
115
+
116
+ The procedure above (find every Makefile, work out which ones
117
+ make-runner-mcp can't currently see, add the `also-read` marker for them)
118
+ is written once, at [`skills/fix-makefile-links/SKILL.md`](./skills/fix-makefile-links/SKILL.md),
119
+ and reachable two ways:
120
+
121
+ - **Already connected to this server** (the normal case — no extra setup):
122
+ it's exposed as an MCP **prompt** named `fix-makefile-links`. Any
123
+ MCP client that supports prompts (Claude Code's `/mcp` prompt picker,
124
+ etc.) can pull it straight from the running server — the prompt text
125
+ comes back with this project's own `PROJECT_DIR`, Makefile path, and a
126
+ ready-to-run `--diagnose` command already filled in, since the server
127
+ serving it already knows all three. Nothing to install.
128
+ - **Not using make-runner-mcp as an MCP server here** (e.g. evaluating it,
129
+ or just want the procedure without wiring up a client): install the same
130
+ file as a standalone Claude Code skill —
131
+ `cp -r skills/fix-makefile-links ~/.claude/skills/` (every project) or
132
+ into a single project's own `.claude/skills/` — then run
133
+ `/fix-makefile-links`. Slightly more manual (it has to search for a
134
+ make-runner-mcp checkout itself to run `--diagnose`, rather than already
135
+ knowing where one is), but requires nothing beyond Claude Code.
136
+
137
+ Both read the exact same procedure file, so there's one place to update it.
138
+
139
+ ## Client configs (stdio)
140
+
141
+ The server is the same everywhere — only the config file and its shape
142
+ differ per client. Things that change between projects/teams:
143
+
144
+ - `PROJECT_DIR` — the target project's root.
145
+ - `#v2.1.0` — bump this to whatever tag you've actually released. Always
146
+ pin to a release tag, never `#main`: for a tool that executes commands,
147
+ an unpinned branch reference means a bad push could silently change what
148
+ runs on everyone's machine.
149
+ - `MCP_TRANSPORT: "stdio"` — required in every block below, since `http`
150
+ is now the default and a stdio-spawned subprocess needs to opt back in
151
+ explicitly.
152
+
153
+ ### Gemini CLI
154
+ `.gemini/settings.json` (project) or `~/.gemini/settings.json` (user-wide):
155
+ ```json
156
+ {
157
+ "mcpServers": {
158
+ "makeRunner": {
159
+ "command": "npx",
160
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
161
+ "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Claude Code
168
+ Project-level `.mcp.json` at the repo root (checked in, shared with the team):
169
+ ```json
170
+ {
171
+ "mcpServers": {
172
+ "makeRunner": {
173
+ "command": "npx",
174
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
175
+ "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
176
+ }
177
+ }
178
+ }
179
+ ```
180
+ Or via the CLI: `claude mcp add makeRunner -e PROJECT_DIR=/path/to/project -e MCP_TRANSPORT=stdio -- npx -y github:davindermahal/make-runner-mcp#v2.1.0`
181
+
182
+ ### Claude Desktop
183
+ `claude_desktop_config.json` (Settings → Developer → Edit Config):
184
+ ```json
185
+ {
186
+ "mcpServers": {
187
+ "makeRunner": {
188
+ "command": "npx",
189
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
190
+ "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ ### Cursor
197
+ `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
198
+ ```json
199
+ {
200
+ "mcpServers": {
201
+ "makeRunner": {
202
+ "command": "npx",
203
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
204
+ "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
205
+ }
206
+ }
207
+ }
208
+ ```
209
+
210
+ ### Any other MCP-compatible client
211
+ All of these follow the same `command` / `args` / `env` shape because it's
212
+ part of the MCP spec's stdio transport — if a client supports MCP at all,
213
+ this same block (adjusted to that client's config file location, with
214
+ `MCP_TRANSPORT: "stdio"` added to `env`) will work without touching the
215
+ server itself.
216
+
217
+ ## Running as a persistent HTTP server (for a sandboxed agent)
218
+
219
+ Verified end to end against a real sandboxed `gemini --sandbox` session
220
+ driving a real Docker-based project (PHPUnit and Composer both ran inside
221
+ the project's container, through this server, with the sandbox itself
222
+ having no `docker` CLI or socket at all — the model's own native shell
223
+ tool inside that same session had zero Docker access, confirmed with
224
+ `which docker` returning nothing).
225
+
226
+ **1. Run the server as its own process**, outside any sandbox, with
227
+ normal access to whatever the project's targets need (Docker included —
228
+ nothing special, just however you'd normally run `docker`/`docker compose`
229
+ on this machine):
230
+
231
+ ```bash
232
+ PROJECT_DIR=/path/to/project \
233
+ MCP_HTTP_TOKEN=$(openssl rand -hex 24) \
234
+ MCP_HTTP_PORT=8791 \
235
+ npx -y github:davindermahal/make-runner-mcp#v2.1.0
236
+ ```
237
+
238
+ Generate a real random token (`openssl rand -hex 24` or equivalent) and
239
+ keep it — the server refuses to start without one (fails closed, not
240
+ silently unauthenticated), and every client needs it to connect. Keep
241
+ this process running for as long as you want the tool available; it's not
242
+ spawned per-client the way `stdio` mode is.
243
+
244
+ **2. Point the client at it over the network, not a subprocess spawn.**
245
+ For a client whose agent itself runs sandboxed (Gemini's `--sandbox`,
246
+ which auto-maps `host.docker.internal` to the host — verified live, zero
247
+ extra network config needed), use that hostname so the sandboxed process
248
+ can reach a server running on the host:
249
+
250
+ ```json
251
+ {
252
+ "mcpServers": {
253
+ "makeRunner": {
254
+ "url": "http://host.docker.internal:8791/mcp",
255
+ "type": "http",
256
+ "headers": { "Authorization": "Bearer <the token from step 1>" }
257
+ }
258
+ }
259
+ }
260
+ ```
261
+
262
+ For an unsandboxed client on the same machine as the server, `127.0.0.1`
263
+ works the same as any other local service.
264
+
265
+ **Security notes specific to this mode** (also see
266
+ [Risks and limitations](#risks-and-limitations)):
267
+
268
+ - `MCP_HTTP_HOST` defaults to `0.0.0.0` — reachable from your local
269
+ network, not just the sandbox, unless your host firewall restricts it.
270
+ The bearer token is the actual access control; treat it like any other
271
+ credential (don't commit it, don't reuse it across machines/projects).
272
+ - Every project you run this way needs its own token and, generally, its
273
+ own port — there's no per-project isolation beyond that; anyone who has
274
+ the token for a given running instance can call any tool it exposes.
275
+
276
+ ## Publishing to npm instead
277
+
278
+ Shipping via GitHub tags (above) is the default — it's a public repo, so
279
+ `npx github:...` works with zero auth setup on any teammate's machine, and
280
+ tagging releases keeps updates a deliberate, visible choice rather than
281
+ whatever `main` happens to be. Publishing to npm is a possible later step
282
+ (marginally faster resolution, no GitHub dependency) but isn't required to
283
+ ship this:
284
+
285
+ ```bash
286
+ npm publish # or: npm publish --registry <your-private-registry>
287
+ ```
288
+
289
+ Once published, every config above can drop the `github:...#tag` args
290
+ entry in favor of `["-y", "make-runner-mcp"]` (add `@<version>` to pin,
291
+ same reasoning as pinning the GitHub tag).
292
+
293
+ ## Passing arguments through to a target
294
+
295
+ Extra `args` on a tool call may only be `make` flags (from a small safe
296
+ allowlist — `-n`, `-k`, `-s`, etc.) or `VAR=value` assignments. Bare
297
+ positional arguments (e.g. an extra target name, or a package name/flag
298
+ with no `VAR=` in front of it) are always rejected — `make` treats a bare
299
+ trailing word as an *additional build goal*, not data, so allowing them
300
+ would let a call to one target silently smuggle a second, possibly-denied
301
+ target onto the same invocation.
302
+
303
+ For a target whose underlying command takes its own arguments (`composer
304
+ install <pkg> --no-dev`, `npm run <script> -- <flags>`, etc.), have its
305
+ recipe read a single variable and pass one `VAR=value` pair at call time:
306
+
307
+ ```makefile
308
+ composer: ## Run composer inside the app container, e.g. ARGS="install symfony/console --no-dev"
309
+ docker compose exec app composer $(ARGS)
310
+ ```
311
+
312
+ Called with `args: ["ARGS=install symfony/console --no-dev"]`. The value
313
+ half of a `VAR=value` pair is checked against a more permissive (but still
314
+ shell-metacharacter-free) pattern than other arguments specifically to
315
+ support this — letters, digits, spaces, and typical package-name/version/
316
+ flag punctuation are allowed; shell control characters (`; & | $ \` ' " < >
317
+ ( ) # \ * ? [ ]`, newlines) are not, since `make` ultimately splices this
318
+ value into a recipe line that runs through a real shell.
319
+
320
+ This keeps the "one call always executes exactly the target it names, in
321
+ addition to whatever its own vetted recipe does with `$(ARGS)`" guarantee
322
+ intact — the passthrough content is only ever data to the recipe you
323
+ already wrote, never a way to select a different target.
324
+
325
+ If a project's Makefile currently relies on `$(MAKECMDGOALS)` or a similar
326
+ bare-word passthrough trick (common for wrapping tools like composer/npm),
327
+ see [`MAKEFILE-GUIDE.md`](./MAKEFILE-GUIDE.md) — a self-contained guide
328
+ written to be handed directly to an AI coding agent, with instructions to
329
+ find and convert those targets to the `ARGS` pattern above.
330
+
331
+ ## Per-project overrides
332
+
333
+ Drop an optional `.mcp-make-config.json` in a project's root to narrow
334
+ which targets are exposed beyond the built-in denylist, and/or restrict
335
+ which environment variables `make` runs with:
336
+
337
+ ```json
338
+ {
339
+ "deny": ["clean-volumes", "seed-prod"],
340
+ "allow": null,
341
+ "envAllowlist": null
342
+ }
343
+ ```
344
+
345
+ `allow`, if set, becomes an explicit whitelist — only those exact target
346
+ names are ever exposed, regardless of what else is in the Makefile.
347
+
348
+ `envAllowlist`, if set, restricts the environment `make` (and everything
349
+ its recipes run) sees to just those variable names — useful since by
350
+ default the child process inherits this MCP server's *entire* environment,
351
+ which in an agent sandbox often includes API keys or tokens that would
352
+ otherwise be readable, and potentially echoable back into the tool result,
353
+ by any recipe. Leave it unset (the default) for unrestricted passthrough,
354
+ matching prior behavior.
355
+
356
+ A config file that fails to parse, or has an `allow`/`envAllowlist` entry
357
+ that isn't a plain array of strings, fails closed — every target is denied
358
+ (or every env var withheld) rather than silently falling back to no
359
+ restriction at all.
360
+
361
+ ## Risks and limitations
362
+
363
+ **This tool reduces risk compared to giving an agent raw shell or Docker
364
+ access. It does not eliminate it, and it is not a security product.**
365
+ Read this before pointing it at anything you'd be upset to lose or break.
366
+
367
+ - **HTTP mode opens a network port that can execute commands.** It's
368
+ authenticated (a required bearer token, fails closed if unset) but bound
369
+ to `0.0.0.0` by default — reachable from anything that can route to that
370
+ port, not just your intended sandboxed agent, unless your host firewall
371
+ restricts it. On a shared network, either firewall the port or bind
372
+ `MCP_HTTP_HOST` more narrowly. The token is the real access control;
373
+ don't commit it, don't log it, don't reuse one across machines/projects.
374
+ - **It's only as safe as your Makefile.** The built-in denylist blocks
375
+ five words (`deploy`, `destroy`, `prod`, `publish`, `release`) and a
376
+ `rm-` prefix — it does not evaluate whether a target is actually
377
+ dangerous. If your Makefile has a target that wipes a volume, drops a
378
+ database, or force-pushes something, and its name doesn't happen to
379
+ contain one of those words, this tool will expose it to the agent like
380
+ any other target. The real protection this tool provides is narrower
381
+ than "safe by default": it's "only what's already in a file you wrote
382
+ and can read," nothing more. Review your Makefile with that in mind —
383
+ and use `.mcp-make-config.json`'s `deny`/`allow` to explicitly exclude
384
+ anything you don't want an agent invoking, don't rely on the built-in
385
+ denylist alone.
386
+ - **The `ARGS="..."` passthrough convention blocks shell injection, not
387
+ dangerous values.** The character set allowed in a `VAR=value` pair
388
+ stops shell metacharacters (`;`, `|`, `` ` ``, etc.) from reaching a
389
+ recipe's shell, but it does not know what your recipe *does* with that
390
+ value. A recipe like `rm -rf $(ARGS)` would still accept a value like
391
+ `-R 777 /` — every one of those characters is allowed. If you write a
392
+ target that consumes `$(ARGS)`, treat that value as agent-controlled
393
+ input and keep the underlying command it's handed to narrow and
394
+ low-risk (a package manager, a test runner) rather than anything
395
+ destructive.
396
+ - **The full environment is passed through by default.** Unless you set
397
+ `envAllowlist` in `.mcp-make-config.json`, every recipe runs with this
398
+ server's entire environment — including any API keys or tokens present
399
+ in the agent's sandbox. Those become readable, and potentially
400
+ echoable back into the tool's output, by any recipe. Set
401
+ `envAllowlist` per project if that matters to you.
402
+ - **Recipe output isn't filtered for secrets.** Whatever a target prints
403
+ to stdout/stderr is returned to the calling agent close to verbatim
404
+ (truncated only by size, at 200KB). If a recipe's own logs happen to
405
+ print something sensitive, this tool won't catch or redact it.
406
+ - **Protected variables cover make's own built-ins, not
407
+ project-specific conventions.** Overriding `PATH`, `SHELL`, and similar
408
+ is blocked. A project-specific convention like `CC=`, `DOCKER=`, or
409
+ `COMPOSE=` used as an executable name inside a recipe has no equivalent
410
+ protection — there's currently no way to extend that list per project.
411
+ - **This has not been independently security-reviewed or audited.** It's
412
+ been built and tested for one person's own use across their own
413
+ projects, not pentested or reviewed by a third party. Read the source
414
+ (`server.js` is a single, deliberately short file) before trusting it
415
+ with anything you care about.
416
+ - **It's designed for a trusted human running their own projects, not for
417
+ isolating untrusted users from each other.** There's no concept of
418
+ per-user permissions, auditing, or multi-tenant isolation. If you need
419
+ any of that, this isn't the right tool as-is.
420
+
421
+ If any of this is a dealbreaker for your use case, the honest fix is
422
+ either: don't expose the target in question at all (`deny`/`allow` it
423
+ out), rewrite the target's recipe to be inherently safe regardless of what
424
+ `$(ARGS)` contains, or don't use this tool for that project.
425
+
426
+ ### Gemini CLI specifically — tested, not theoretical
427
+
428
+ The following was verified against a real `gemini` CLI session (transcript
429
+ inspection, not just reading the final answer), because it changes what
430
+ "safe" means for this tool with that client:
431
+
432
+ - **When make-runner-mcp fails to connect for any reason, Gemini CLI does
433
+ not fail loudly.** It silently falls back to its own native `read_file`
434
+ and `run_shell_command` tools and accomplishes the same task via full,
435
+ unrestricted shell access — with no clear signal beyond a generic "MCP
436
+ issues detected" banner that also fires for unrelated, pre-existing
437
+ broken servers. I triggered this twice, independently: once from an
438
+ untrusted project folder, once from an unrelated `npm` cache permission
439
+ error inside `--sandbox`. Both times the model read the Makefile
440
+ directly and ran the command itself, completely bypassing
441
+ make-runner-mcp, and the final answer looked identical either way — you
442
+ cannot tell from the response alone whether the tool boundary was
443
+ actually enforced. **This means make-runner-mcp's guarantee only holds
444
+ while the MCP connection is actually up; it does not degrade safely.**
445
+ - **A project folder must be in Gemini CLI's persistent trust store**
446
+ before it will load *any* project-level `.gemini/settings.json` MCP
447
+ servers — the `--skip-trust` CLI flag alone is not sufficient for this,
448
+ even though it looks like it should be. Without that trust, make-runner-mcp
449
+ is silently never loaded at all (see the point above for what happens
450
+ next).
451
+ - **`--sandbox` mode and `stdio`-spawned Docker-based targets don't mix —
452
+ this is exactly why `http` is now the default transport.** Gemini's
453
+ `--sandbox` re-execs itself (and everything it spawns via `stdio`, MCP
454
+ servers included) inside its own container. The default sandbox image
455
+ (`gemini-cli/sandbox`) does not include the `docker` CLI at all — a
456
+ `stdio`-spawned target that shells out to `docker`/`docker compose`
457
+ fails with `make: docker: No such file or directory`, verified verbatim.
458
+ The tempting-looking fix — a custom sandbox image with `docker` CLI
459
+ installed and the host's Docker socket mounted in — doesn't actually
460
+ preserve any scoping: Gemini's sandbox is one shared container for the
461
+ entire session, not scoped per-tool, and the native `run_shell_command`
462
+ fallback (previous bullet) runs inside that *same* container. Mounting
463
+ the socket in would hand that same unrestricted native shell tool
464
+ identical Docker access, with no separation between "the vetted make
465
+ targets" and "whatever command the model decides to run directly" — the
466
+ exact Docker-outside-of-Docker exposure this project exists to avoid
467
+ (see `CLAUDE.md`).
468
+
469
+ **The actual fix, verified end to end**: run this server in `http` mode
470
+ (the default) *outside* the sandbox — as its own process on the host,
471
+ with normal Docker access — and let the sandboxed session reach it only
472
+ over the network via Gemini's auto-mapped `host.docker.internal`. The
473
+ sandbox container itself then never has Docker access at all — no CLI,
474
+ no socket — so there's nothing for the native shell tool to fall back
475
+ *to*, and Docker access exists exclusively through this server's vetted
476
+ targets. Confirmed live: `mcp_makeRunner_make__unit-test` and
477
+ `mcp_makeRunner_make__composer` both correctly drove a real project's
478
+ Docker container (real PHPUnit and Composer output) through a sandboxed
479
+ session that had zero `docker` CLI of its own (`which docker` returned
480
+ nothing in that same session). See
481
+ [Running as a persistent HTTP server](#running-as-a-persistent-http-server-for-a-sandboxed-agent)
482
+ above for the exact setup.
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "make-runner-mcp",
3
+ "version": "2.1.0",
4
+ "type": "module",
5
+ "description": "Scoped MCP server that discovers a project's Makefile targets and exposes each one as an individual MCP tool.",
6
+ "main": "server.js",
7
+ "bin": {
8
+ "make-runner-mcp": "./server.js"
9
+ },
10
+ "files": [
11
+ "server.js",
12
+ "skills",
13
+ "MAKEFILE-GUIDE.md"
14
+ ],
15
+ "scripts": {
16
+ "test": "node --test"
17
+ },
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/davindermahal/make-runner-mcp.git"
22
+ },
23
+ "homepage": "https://github.com/davindermahal/make-runner-mcp#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/davindermahal/make-runner-mcp/issues"
26
+ },
27
+ "dependencies": {
28
+ "@modelcontextprotocol/sdk": "1.30.0"
29
+ }
30
+ }