Skip to main content
Glama
pyrex41
by pyrex41
README.md
# MCP CLI Wrapper

A flexible MCP (Model Context Protocol) server that wraps any CLI tool and exposes its subcommands as individual MCP tools with typed parameters.

## Features

- **Config-driven tools**: Define CLI subcommands as MCP tools via JSON configuration
- **Fallback mode**: Single `run_cli` tool when no config provided
- **Type-safe parameters**: Zod validation for all inputs before execution
- **Flexible parameter types**: Supports positional args, flags, and boolean switches
- **Preserved parameter order**: Flags and positionals stay interleaved as defined
- **Nested subcommands**: Supports `git remote add` style commands
- **Safe execution**: Uses `spawnSync` to prevent command injection
- **Full output capture**: Returns stdout, stderr, and exit code

## Installation

```bash
npm install
npm run build
```

## Usage

### Basic (Fallback Mode)

Run without a config file to get a single `run_cli` tool that accepts raw arguments:

```bash
node dist/index.js git
```

This exposes one tool:
- `run_cli` - Accepts `args: string[]` and executes `git <args...>`

### With Configuration

Run with a config file to expose multiple typed tools:

```bash
node dist/index.js git ./config.example.json
```

This exposes tools like:
- `git_status` - Show working tree status
- `git_commit` - Commit with typed `message` and optional `all` flag
- `git_add` - Stage files with `pathspec` parameter
- `git_log` - Show logs with `oneline` and `max_count` options
- And more...

## Configuration Format

Create a JSON file with tool definitions:

```json
{
  "tools": [
    {
      "name": "git_commit",
      "description": "Record changes to the repository with a commit message.",
      "subcommand": "commit",
      "parameters": [
        {
          "name": "message",
          "type": "string",
          "description": "The commit message.",
          "flag": "-m",
          "required": true
        },
        {
          "name": "all",
          "type": "boolean",
          "description": "Automatically stage all modified files.",
          "flag": "--all",
          "required": false
        }
      ]
    }
  ]
}
```

### Parameter Configuration

Each parameter supports:

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Parameter name (used in schema) |
| `type` | `"string"` \| `"number"` \| `"boolean"` | Parameter type |
| `description` | string | Description shown to AI agents |
| `flag` | string (optional) | CLI flag (e.g., `-m`, `--force`). If omitted, parameter is positional |
| `required` | boolean | Whether the parameter is required |

### Parameter Order

Parameters are processed in **declaration order**. This allows you to define tools where flags must precede positional arguments:

```json
{
  "name": "docker_run",
  "subcommand": "run",
  "parameters": [
    { "name": "interactive", "flag": "-it", "type": "boolean", "required": false },
    { "name": "image", "type": "string", "required": true },
    { "name": "command", "type": "string", "required": false }
  ]
}
```

This produces: `docker run -it ubuntu bash` (flags before positionals)

### Nested Subcommands

Use spaces in `subcommand` for nested commands:

```json
{
  "name": "git_remote_add",
  "subcommand": "remote add",
  "parameters": [
    { "name": "name", "type": "string", "required": true },
    { "name": "url", "type": "string", "required": true }
  ]
}
```

## MCP Client Configuration

### Claude Desktop

Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "git": {
      "command": "node",
      "args": ["/path/to/mcp-cli-wrapper/dist/index.js", "git", "/path/to/config.json"]
    }
  }
}
```

### Claude Code

Add to your MCP settings:

```json
{
  "mcpServers": {
    "git": {
      "command": "node",
      "args": ["./dist/index.js", "git", "./config.example.json"]
    }
  }
}
```

## Example Configs

### Git (included)

See `config.example.json` for a complete Git configuration with 8 tools.

### Docker (example)

```json
{
  "tools": [
    {
      "name": "docker_ps",
      "description": "List running containers",
      "subcommand": "ps",
      "parameters": [
        { "name": "all", "type": "boolean", "flag": "-a", "description": "Show all containers", "required": false }
      ]
    },
    {
      "name": "docker_run",
      "description": "Run a container",
      "subcommand": "run",
      "parameters": [
        { "name": "detach", "type": "boolean", "flag": "-d", "description": "Run in background", "required": false },
        { "name": "name", "type": "string", "flag": "--name", "description": "Container name", "required": false },
        { "name": "image", "type": "string", "description": "Image to run", "required": true }
      ]
    }
  ]
}
```

## Security

- **No shell interpolation**: Arguments are passed directly via `spawnSync`, preventing command injection
- **Input validation**: All parameters validated with Zod schemas before execution
- **Local execution only**: Commands run on the local machine with the user's permissions

## Development

```bash
# Install dependencies
npm install

# Build
npm run build

# Run in development
npm run dev git ./config.example.json
```

## License

MIT