Skip to main content
Glama

script-runner-mcp

CI License: MIT

An MCP server that lets an AI agent run the local scripts you declared, and nothing else. Arguments are checked before anything starts, and no shell is involved.

Why this exists

My agents work inside a Linux sandbox. Some jobs need the host machine instead. One example is rendering branded images with Puppeteer: the sandbox has no Chrome, no root and no way to download one. The render scripts worked fine on the host, but the agent could not reach them.

So the agent improvised. It drew title images with Pillow, and they came out off-brand. A rule in its instructions saying "never render with Pillow" didn't stop it, because from the agent's point of view a workaround still beat failing the task.

The fix was to give it a proper path: a small MCP server on the host that exposes the render scripts as tools. The first version hard-coded two script paths. This is the general version. You declare each script, its arguments and the parameters the agent may fill in, and the agent gets exactly those tools. There is no generic "run command" tool.

Related MCP server: MCP Process Server

How it works

  • Declared tools only. A JSON config lists each tool: the program (node by default), an optional script, an argument template and typed parameters.

  • Checked parameters. path parameters must resolve, after following symlinks, inside one of the allowed_roots and can be limited to certain extensions. integer parameters have ranges, enum parameters have fixed values, and string parameters have a length limit, an optional pattern and must not start with -, so they cannot smuggle in options. Unknown arguments are rejected.

  • No shell. The process starts via execFile with shell: false, so &&, $(...) or ; in an argument stay plain text.

  • Bounded runs. Each tool has a timeout. Output is capped. A failure comes back as an MCP tool error with exit code and stderr.

  • Zero runtime dependencies. It speaks JSON-RPC over stdio directly in about 500 lines of plain Node.js, so there is little to audit.

Installation

You need Node.js 22 or newer.

npx -y github:michael-schilde/script-runner-mcp --config path/to/config.json --check

--check validates the config and prints the tools it would expose. Then register the server with your MCP client, for example in Claude Desktop's claude_desktop_config.json:

{
  "mcpServers": {
    "script-runner": {
      "command": "npx",
      "args": ["-y", "github:michael-schilde/script-runner-mcp", "--config", "/absolute/path/to/config.json"]
    }
  }
}

You can also clone the repository and point the client at node /path/to/script-runner-mcp/src/cli.js. Instead of --config you can set the SCRIPT_RUNNER_CONFIG environment variable.

Example: a Puppeteer render script

{
  "allowed_roots": ["./images"],
  "tools": [
    {
      "name": "render_title_image",
      "description": "Renders a 1200x630 title image from a JSON spec and prints the PNG path.",
      "script": "./render/render.js",
      "args": ["{spec}"],
      "params": {
        "spec": { "type": "path", "required": true, "extensions": [".json"], "description": "Image spec" }
      },
      "timeout_ms": 120000
    }
  ]
}

The agent sees one tool, render_title_image, with one argument. A spec outside ./images, a .js file or an extra argument is rejected before Node starts.

A runnable demo lives in examples/: count_words counts the words of a text file inside examples/content.

Configuration

Relative paths are resolved against the folder that contains the config file.

Key

Meaning

allowed_roots

Folders that path parameters must stay inside

tools[].name

Tool name (a-z, 0-9, _)

tools[].description

Shown to the agent

tools[].command

node (default, the Node binary running the server), a program on PATH, or a path to an executable

tools[].script

Optional script passed as the first argument

tools[].args

Argument templates; {param} is replaced by the checked value. An argument that refers to an omitted optional parameter is dropped, so write optional flags as --flag={param}

tools[].params

Parameters the agent may pass (see below)

tools[].cwd

Working directory (default: the config folder)

tools[].timeout_ms

Timeout, default 60000, max 600000

tools[].env

Extra environment variables for the process

Parameter rule

Meaning

type

string (default), path, integer or enum

required, default

Whether the agent must pass it, and the value used when it does not

description

Shown to the agent

pattern, maxlen

string: regex and maximum length (default 1000)

min, max

integer: range

values

enum: allowed values

extensions

path: allowed file extensions, e.g. [".json"]

must_exist

path: default true; set false for output files, whose parent folder must still exist

Limits

  • The script is trusted. The server controls what the agent may pass in, not what your script does with it. A script that runs eval on its input is still dangerous.

  • Inherited environment. Child processes get the server's environment plus env, so tools like Puppeteer find their caches. Do not put secrets in the server's environment if your scripts should not see them.

  • No .cmd or .bat on Windows. Without a shell, Node cannot start batch files. Point command at a real executable (node, python, .exe).

  • Tools only. The server implements initialize, ping, tools/list and tools/call. It has no resources, prompts, progress notifications or cancellation, and a long run just waits for its timeout.

  • Platforms. I use it on Windows. CI runs the tests on Linux and Windows.

Development

npm install
npm test
npm run lint

About

I built this with Claude Code. It grew out of the render bridge in my own agent setup. Issues and pull requests are welcome.

License

MIT © Michael Schilde

Related MCP Connectors

Related MCP Servers