Skip to main content
Glama
README.md
<!--
NOTE: README.md is generated from README.md.in.
Edit README.md.in and run: bash scripts/render-readme.sh
-->

# mcp-bash

[![mcp-bash framework banner](assets/mcp-bash-framework.png)](assets/mcp-bash-framework.png)

[![CI](https://img.shields.io/github/actions/workflow/status/yaniv-golan/mcp-bash-framework/ci.yml?branch=main&label=CI)](https://github.com/yaniv-golan/mcp-bash-framework/actions)
[![License](https://img.shields.io/github/license/yaniv-golan/mcp-bash-framework)](LICENSE)
[![Bash](https://img.shields.io/badge/bash-%3E%3D3.2-green.svg)](https://www.gnu.org/software/bash/)
[![MCP Protocol](https://img.shields.io/badge/MCP-2025--11--25-blue)](https://modelcontextprotocol.io/specification)
[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey)](#runtime-requirements)

> **Repository:** [`mcp-bash-framework`](https://github.com/yaniv-golan/mcp-bash-framework) &nbsp;•&nbsp; **CLI/Binary:** `mcp-bash`

> **For AI agents:** Read [`llms.txt`](llms.txt) (compact) or [`llms-full.txt`](llms-full.txt) (detailed) for agent-optimized context — includes a step-by-step Quick Start, critical allowlist requirement, and CLI wrapper patterns.
>
> **Claude Code skill:** `/plugin install mcp-bash --marketplace yaniv-golan/mcp-bash-framework` installs a skill with the workflow and common traps for building mcp-bash servers. On older Claude Code: `/plugin marketplace add yaniv-golan/mcp-bash-framework`, then `/plugin install mcp-bash@mcp-bash`.

## Contents

- [TL;DR](#tldr)
- [What you’ll build](#what-youll-build)
- [Design Principles](#design-principles)
- [MCP Spec Coverage](#mcp-spec-coverage)
- [Why Bash?](#why-bash)
- [Quick Start](#quick-start)
- [Security Model (60 seconds)](#security-model-60-seconds)
- [Configure Your MCP Client](#configure-your-mcp-client)
- [MCPB Bundles](#mcpb-bundles)
- [Vendoring the Runtime](#vendoring-the-runtime)
- [Project Structure](#project-structure)
- [Direct Tool Execution (run-tool)](#direct-tool-execution-run-tool)
- [Embedded resources in tool output](#embedded-resources-in-tool-output)
- [Diagnostics & Validation](#diagnostics--validation)
- [SDK Discovery](#sdk-discovery)
- [Roots (scoping filesystem access)](#roots-scoping-filesystem-access)
- [Completions](#completions)
- [Tool Policy Hook (optional)](#tool-policy-hook-optional)
- [Learn by Example](#learn-by-example)
- [Features at a Glance](#features-at-a-glance)
- [Configuration](#configuration)
- [Troubleshooting (quick hits)](#troubleshooting-quick-hits)
- [Requirements](#requirements)
- [Uninstall](#uninstall)
- [Documentation](#documentation)
- [Built with mcp-bash](#built-with-mcp-bash)
- [FAQ](#faq)

> **The most complete MCP implementation in pure Bash.** Tools, resources, prompts, elicitation, roots, progress, cancellation—the full spec, no runtimes beyond your shell.
>
> - Runs on Bash 3.2+ (macOS/Linux stock). No Node, no Python, no containers.
> - Handles concurrency, timeouts, and cancellation the way production systems need.
> - You write the tools. The framework stays out of your way.

## TL;DR

**Turn any Bash script into an MCP tool in minutes.** No Node, no Python, no containers.

```bash
mcp-bash new my-server && cd my-server
mcp-bash scaffold tool my-tool   # edit tools/my-tool/tool.sh
mcp-bash config --client cursor  # paste into your MCP client
mcp-bash bundle                  # create distributable package
```

![mcp-bash demo: create a project, scaffold a tool, run it, and print a Claude Desktop config](assets/mcp-bash-demo.gif)

## What you’ll build

```mermaid
flowchart TD
  Client["MCP client<br/>(Claude Desktop / Cursor / Windsurf)"]
  Transport["stdio (JSON-RPC)"]
  Framework["mcp-bash framework<br/>(registry + runtime + policy)"]
  Project["Your project<br/>tools/ resources/ prompts/ server.d/"]

  Client --> Transport --> Framework --> Project
```

## Design Principles

- Tools shouldn’t need another runtime to talk to AI.
- Everything must be inspectable. No magic.
- If it’s not needed in production, it isn’t in the framework.
- Your project stays yours. The framework upgrades cleanly.

## MCP Spec Coverage

mcp-bash targets the **2025-11-25** MCP specification with negotiated downgrades to older versions.

| Category | Coverage | Notes |
|----------|----------|-------|
| Core Protocol | ✅ Full | Lifecycle, ping, capabilities, downgrades |
| Tools | ✅ Full | list, call, icons, errors, listChanged, annotations |
| Resources | ✅ Full | list, read, subscriptions, templates, binary, annotations |
| Prompts | ✅ Full | list, get, arguments, icons |
| Utilities | ✅ Full | Progress, cancellation, logging, completion |
| Elicitation | ✅ Full | Form, URL, enum, multi-choice modes |
| Roots | ✅ Full | Server→client request, listChanged |
| MCP Apps (UI) | ⚠️ Partial | `ui://` resources, templates; UI-initiated tool calls depend on host support |

**Not yet implemented:** Audio content, sampling. Tasks (async job/poll) and server-identity discovery are HTTP-oriented and not applicable to stdio.

Transport is stdio-only by design. See [Remote Connectivity](docs/REMOTE.md) for HTTP/SSE proxy options, including the shared-secret guard (`MCPBASH_REMOTE_TOKEN`) and readiness probe (`mcp-bash --health`).

→ [Full compliance matrix](SPEC-COMPLIANCE.md)

For a complete feature-by-feature breakdown across all MCP versions, see the [Feature Support Matrix](SPEC-COMPLIANCE.md#feature-support-matrix) in `SPEC-COMPLIANCE.md`.

## Why Bash?

| | mcp-bash | TypeScript SDK | Python SDK |
|---|----------|----------------|------------|
| Runtime | Bash 3.2+ (pre-installed) | Node.js 18+ | Python 3.10+ |
| Install | `brew install` or `curl \| bash` | `npm install` | `pip install` |
| Startup | No VM warmup | Node.js startup | Python startup |
| Dependencies | jq or gojq | npm packages | pip packages |
| Best for | Shell automation, existing scripts, air-gapped/minimal environments | Node.js applications | Python applications |

If your tools are already shell scripts, wrapping them in Node or Python adds complexity for no benefit. mcp-bash lets you expose them directly.

## Quick Start

When you run `mcp-bash` from inside a project (a directory containing `server.d/server.meta.json`), it auto-detects the project root. Running `mcp-bash` outside any project starts a temporary getting-started helper tool. For MCP clients, set `MCPBASH_PROJECT_ROOT` so the server can find your project regardless of working directory.

### 1. Check Requirements (10 seconds)

```bash
# Preflight: Bash 3.2+ and jq (or gojq) for full functionality (tools/resources/prompts).
bash --version | head -1
command -v jq >/dev/null 2>&1 || command -v gojq >/dev/null 2>&1 || printf '%s\n' '⚠ jq/gojq missing: minimal mode only (tools/resources/prompts disabled)'
```

### 2. Install the Framework

**Homebrew** (macOS and Linux — recommended):

```bash
brew install yaniv-golan/mcp-bash/mcp-bash
```

Update later with `brew upgrade mcp-bash`.

**curl installer** (CI, Docker, Windows/Git Bash, or environments without Homebrew):

```bash
curl -fsSL "https://raw.githubusercontent.com/yaniv-golan/mcp-bash-framework/v1.9.1/install.sh" | bash -s -- --yes --version "v1.9.1"
```

Verified install (recommended for production / security-sensitive environments):

```bash
version="v1.9.1"
file="mcp-bash-${version}.tar.gz"
curl -fsSLO "https://github.com/yaniv-golan/mcp-bash-framework/releases/download/${version}/${file}"
curl -fsSLO "https://github.com/yaniv-golan/mcp-bash-framework/releases/download/${version}/SHA256SUMS"

# Verify (macOS):
grep -E "([[:space:]]|\\*)${file}$" SHA256SUMS | shasum -a 256 -c -
# Verify (Linux):
grep -E "([[:space:]]|\\*)${file}$" SHA256SUMS | sha256sum -c -

curl -fsSLO "https://raw.githubusercontent.com/yaniv-golan/mcp-bash-framework/${version}/install.sh"
bash install.sh --archive "${file}" --version "${version}"
```

Why this is “verified”: you download the release tarball + `SHA256SUMS`, verify the checksum locally, then run the installer against the verified archive.

For tagged releases (`vX.Y.Z`), the installer also attempts to verify the archive against `SHA256SUMS` automatically when it’s available.

Manual/offline install (for policy-driven or air-gapped environments):

```bash
git clone https://github.com/yaniv-golan/mcp-bash-framework.git ~/.local/share/mcp-bash
mkdir -p ~/.local/bin && ln -sf ~/.local/share/mcp-bash/bin/mcp-bash ~/.local/bin/mcp-bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc  # or ~/.zshrc (if not already in PATH)
```

Pin a release with the installer (auto-prefixes `v` for bare versions):

```bash
bash install.sh --verify <sha256-from-SHA256SUMS> --version 1.9.1
```

### 3. Verify It Works (30 seconds)

Security defaults: the `server.d/register.sh` hook is disabled unless `MCPBASH_ALLOW_PROJECT_HOOKS=true` (`server.d/policy.sh` and `register.json` are always loaded), and tools require an explicit allowlist (`MCPBASH_TOOL_ALLOWLIST=*` to allow all in trusted projects).

```bash
mcp-bash doctor
# Expected output includes lines like:
#   ✓ Bash version: 5.x.x (>= 3.2 required)
#   ✓ jq installed: /usr/bin/jq
#   All checks passed! Ready to build MCP servers.

# Preview and apply managed-install repairs (shim + optional upgrade when --min-version is set):
mcp-bash doctor --dry-run
mcp-bash doctor --fix

# Quick end-to-end test (optional):
mcp-bash new demo-server
cd demo-server
mcp-bash run-tool hello --allow-self --args '{"name":"World"}'
# Expected output includes: "Hello, World!"

# Cleanup (the demo-server directory persists until you remove it):
cd .. && rm -rf demo-server
```

<details>
<summary><strong>Something not working?</strong> (click to expand)</summary>

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `mcp-bash: command not found` | PATH not configured | `export PATH="$HOME/.local/bin:$PATH"` then open a new shell |
| “Operating in minimal mode…” / tools missing | `jq`/`gojq` missing | `brew install jq` or `apt install jq` |
| “blocked by policy” | Default-deny tool policy | CLI: re-run with `--allow-self`. MCP clients: set `MCPBASH_TOOL_ALLOWLIST` in client config |
| Claude Desktop starts but shows no tools | GUI non-login env / PATH | Use `mcp-bash config --wrapper-env` and point the client at the wrapper |
| macOS `Operation not permitted` / quarantine | Gatekeeper quarantine | `scripts/macos-dequarantine.sh ~/.local/share/mcp-bash` (trusted paths only), restart client |
| Windows Git Bash path weirdness | MSYS path conversion | `MSYS2_ARG_CONV_EXCL="*"` (and prefer `jq` on Windows CI) |

Still stuck? Run `mcp-bash doctor` (or `mcp-bash doctor --json`) and include output when opening an issue.

</details>

### 4. Create Your Project

Your server code lives in a separate project directory:

```bash
mcp-bash new my-mcp-server
cd my-mcp-server
```

Already in a directory you created yourself? Run `mcp-bash init --name my-mcp-server [--no-hello]` instead.

### 5. Scaffold Your First Tool

```bash
mcp-bash scaffold tool check-disk
```

This scaffolds `tools/check-disk/tool.sh` and `tools/check-disk/tool.meta.json` in your project. You write the logic.
Each scaffolded tool also includes `tools/check-disk/smoke.sh`—run it after edits to ensure your tool’s stdout is valid JSON (update the sample args in the script if you change `tool.meta.json`).

### 6. (Optional) Add a Test Harness

Create a lightweight runner for tool smoke tests:

```bash
mcp-bash scaffold test
./test/run.sh --verbose  # add run_test calls inside test/run.sh
```

The harness wraps `mcp-bash run-tool`, validates your project before running, and refuses to overwrite existing `test/run.sh` or `test/README.md`.

## Security Model (60 seconds)

mcp-bash is **secure by default**. Here's what that means:

| Layer | Default | What it does |
|-------|---------|--------------|
| **Tool allowlist** | Deny all | Tools won't run unless explicitly listed in `MCPBASH_TOOL_ALLOWLIST` |
| **Hooks** | Disabled | `server.d/register.sh` is not executed unless `MCPBASH_ALLOW_PROJECT_HOOKS=true`. `server.d/policy.sh` is always sourced, so treat it as trusted code; `server.d/health-checks.sh` runs only under `mcp-bash --health`; `server.d/env.sh` is only sourced by `run-tool --with-server-env` |
| **Tool environment** | Minimal | Tools inherit only essential vars (`PATH`, `HOME`, `TMPDIR`, `LANG`, Windows system vars, and `MCP_*`/`MCPBASH_*`). Use `MCPBASH_TOOL_ENV_MODE` to change, or declare it in `server.meta.json` `"env"` |

### Quick reference

```bash
# Allow a single tool (CLI)
mcp-bash run-tool my-tool --allow-self --args '{}'

# Allow specific tools (MCP client config)
MCPBASH_TOOL_ALLOWLIST="tool1,tool2"

# Allow all tools (trusted projects only)
MCPBASH_TOOL_ALLOWLIST="*"

# Enable the registration hook (server.d/register.sh)
MCPBASH_ALLOW_PROJECT_HOOKS=true

# Tool environment modes
MCPBASH_TOOL_ENV_MODE=minimal    # default: PATH, HOME, TMPDIR, LANG (+ Windows system vars)
MCPBASH_TOOL_ENV_MODE=inherit    # pass through parent environment (needs MCPBASH_TOOL_ENV_INHERIT_ALLOW=true)
MCPBASH_TOOL_ENV_MODE=allowlist  # minimal + MCPBASH_TOOL_ENV_ALLOWLIST vars
```

To pass a host-injected secret (such as an MCPB `user_config` API key) to tools and providers, declare the policy in `server.d/server.meta.json` instead of a launcher script; the launch environment still wins:

```json
"env": {"MCPBASH_TOOL_ENV_MODE": "allowlist", "MCPBASH_TOOL_ENV_ALLOWLIST": "MY_API_KEY"}
```

**Demo tip:** For local testing, use `--allow-self` (CLI) or `MCPBASH_TOOL_ALLOWLIST=*` (MCP clients). For production, allowlist only the tools you need.

→ [Full security guide](docs/SECURITY.md) · [Environment reference](docs/ENV_REFERENCE.md)

## Configure Your MCP Client

Every client works the same way: point it at the framework and tell it where your project lives:

1. Set `MCPBASH_PROJECT_ROOT=/path/to/your/project`.
2. Point it at the `mcp-bash` binary (installed to `~/.local/bin/mcp-bash` by the installer).
   - If you generated a wrapper via `mcp-bash config --wrapper` or `--wrapper-env`, you can point clients at `<project-root>/<server-name>.sh`; the wrapper already wires `MCPBASH_PROJECT_ROOT` for you.

### Generate Config (CLI)

```bash
mcp-bash config --show
mcp-bash config --json           # machine-readable descriptor (name/command/env)
mcp-bash config --client cursor  # client-specific snippet
mcp-bash config --inspector      # ready-to-run Inspector command (stdio)
mcp-bash config --wrapper-env    # wrapper that sources your shell profile first (macOS-safe)
mcp-bash config --wrapper        # TTY: creates ./<server-name>.sh; piped/redirected: stdout
```

`config --show` prints one labeled snippet per supported client (headings like `# Claude Desktop`, `# Cursor`, etc.) so you can see which block to copy; use `--client <name>` or `--json` when you only want a single paste-ready block.

Copy the snippet for your client (Claude Desktop/CLI/Code, Cursor, Windsurf, LibreChat, etc.) and paste it into the appropriate config file. This sets `MCPBASH_PROJECT_ROOT` and the `mcp-bash` command path for you. When run in a terminal (stdout is a TTY), `config --wrapper` writes `<project-root>/<server-name>.sh`, marks it executable, and prints the path to stderr; piping or redirecting prints the wrapper script to stdout.
Picking a wrapper:
- Use `--wrapper` when your PATH/env is already correct in non-login shells (e.g., Linux, or macOS with absolute paths).
- Use `--wrapper-env` when you need your login shell to set PATH/version managers/vars before starting the server (common on macOS Claude Desktop).
- Distributing a server? Ship the env wrapper by default for GUI launches (macOS/Windows clients), and include a non-login wrapper or absolute runtime path for CI/WSL/Linux users who want fast, side-effect-free startups.

### Per-Client Snippets

- **Claude Desktop**: Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) and add:
  ```jsonc
  "mcpServers": {
    "mcp-bash": {
      "command": "/Users/you/.local/bin/mcp-bash",
      "env": {
        "MCPBASH_PROJECT_ROOT": "/Users/you/my-mcp-server",
        "MCPBASH_TOOL_ALLOWLIST": "*"
      }
    }
  }
  ```
  - macOS runtime note: Claude Desktop launches servers from a minimal, non-login shell, so your PATH, version managers (nvm/pyenv/uv/rbenv), and env vars from `.zshrc`/`.bash_profile` are skipped. Use absolute paths to runtimes (e.g., `/opt/homebrew/bin/node`) and set missing vars in the `env` block, or generate a login-aware wrapper:
    ```bash
    mcp-bash config --project-root /Users/you/my-mcp-server --wrapper-env > /Users/you/my-mcp-server/mcp-bash.sh
    chmod +x /Users/you/my-mcp-server/mcp-bash.sh
    ```
    Then point Claude Desktop at `/Users/you/my-mcp-server/mcp-bash.sh` as the `command`.
  - macOS quarantine: Gatekeeper can block quarantined downloads (typically from browsers/DMGs/AirDrop) even when paths are correct. CLI downloads (curl/wget/git) often skip quarantine. If you see `ENOENT`, `transport closed unexpectedly`, or `Operation not permitted` despite correct paths, clear quarantine and restart Claude Desktop:
    ```bash
    xattr -r -d com.apple.quarantine ~/.local/share/mcp-bash
    xattr -r -d com.apple.quarantine /Users/you/my-mcp-server
    ```
    Helper: `scripts/macos-dequarantine.sh [path]` will clear quarantine for the repo (or a specific path). `xattr -cr` clears all extended attributes; only use it on trusted paths.
  - macOS folder permissions: Desktop/Documents/Downloads are TCC-protected and Downloads is often quarantined. Move servers to a neutral folder (e.g., `~/mcp-servers`) or grant Claude “Full Disk Access” and “Files and Folders” in System Settings.
- **Claude CLI/Claude Code**: Run once:
  ```bash
  claude mcp add --transport stdio mcp-bash \
    --env MCPBASH_PROJECT_ROOT="$HOME/my-mcp-server" \
    --env MCPBASH_TOOL_ALLOWLIST="*" \
    -- "$HOME/.local/bin/mcp-bash"
  ```
- **Cursor**: Create `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project) with the same `mcpServers` JSON as above.
- **Windsurf (Cascade)**: Edit `~/.codeium/windsurf/mcp_config.json` via Settings → Advanced → Cascade, and add the same `mcpServers` entry.
- **LibreChat**: In `librechat.yaml` add:
  ```yaml
  mcpServers:
    mcp-bash:
      type: stdio
      command: /Users/you/.local/bin/mcp-bash
      env:
        MCPBASH_PROJECT_ROOT: /Users/you/my-mcp-server
        MCPBASH_TOOL_ALLOWLIST: "*"
  ```
- **OpenAI Agents SDK (Python)**: Use `MCPServerStdio(params=...)`; the constructor does not take a `name` kwarg.
  ```python
  import os
  from agents.mcp import MCPServerStdio

  os.environ["MCPBASH_PROJECT_ROOT"] = "/Users/you/my-mcp-server"
  os.environ["MCPBASH_TOOL_ALLOWLIST"] = "*"
  async with MCPServerStdio(
      params={
          "command": "/Users/you/.local/bin/mcp-bash",
          # optionally add args/env/cwd if your server needs them
      }
  ) as server:
      ...
  ```
- **OpenClaw** (formerly Clawdbot): register the server with [OpenClaw](https://github.com/openclaw/openclaw)'s MCP registry, which saves it under `mcp.servers` in OpenClaw's config:
  ```bash
  openclaw mcp add mcp-bash \
    --command /Users/you/.local/bin/mcp-bash \
    --env MCPBASH_PROJECT_ROOT=/Users/you/my-mcp-server \
    --env 'MCPBASH_TOOL_ALLOWLIST=*'
  ```
  Check it with `openclaw mcp show mcp-bash`. Servers in mcporter's own config are a separate registry that `openclaw mcp` does not read.
- **NanoClaw** ([nanoclaw.dev](https://nanoclaw.dev); checked against v2026.10.0): MCP servers run inside each agent group's container, so mount the framework and your project into it, add `jq` (not in the default image), and register the server. Point the registry cache at a writable path, because the mounts are read-only:
  ```bash
  ncl groups config add-mount --id <group-id> --host ~/.local/share/mcp-bash --container /opt/mcp-bash --ro
  ncl groups config add-mount --id <group-id> --host /Users/you/my-mcp-server --container /opt/my-mcp-server --ro
  ncl groups config add-package --id <group-id> --apt jq
  ncl groups config add-mcp-server --id <group-id> --name mcp-bash \
    --command /opt/mcp-bash/bin/mcp-bash \
    --env '{"MCPBASH_PROJECT_ROOT":"/opt/my-mcp-server","MCPBASH_TOOL_ALLOWLIST":"*","MCPBASH_REGISTRY_DIR":"/tmp/mcp-bash-registry"}'
  ncl groups restart --id <group-id> --rebuild
  ```
  `--host` is the framework install directory (`~/.local/share/mcp-bash` for the curl installer). Mounting host paths needs operator approval.
- **Highlight AI**: Go to Highlight → Settings → Connections → Add → Custom command:
  - **Connection name**: `mcp-bash`
  - **Command**: `/Users/you/.local/bin/mcp-bash`
  - **Environment variables**: `MCPBASH_PROJECT_ROOT=/Users/you/my-mcp-server MCPBASH_TOOL_ALLOWLIST=*`
- **Windows note**: Git Bash (CI-tested) or WSL both work. Git Bash ships with Git for Windows; WSL behaves like Linux. See [Windows Support](docs/WINDOWS.md) for details.

> **Security note:** `MCPBASH_TOOL_ALLOWLIST="*"` allows all registered tools. For production deployments, replace `*` with a comma-separated list of specific tool names (e.g., `"search-notes,get-note"`).

### Compatibility Notes

| Client | Status | Known issues / notes |
|--------|--------|----------------------|
| Claude Desktop | Tested (macOS, Windows) | macOS: non-login shell PATH/env (use `config --wrapper-env`); macOS quarantine/TCC can block execution; restart required after config changes |
| Claude CLI / Claude Code | Tested | Generally straightforward; ensure `MCPBASH_PROJECT_ROOT` points at your project |
| OpenClaw (formerly Clawdbot) | Config documented | `openclaw mcp add`; saved under `mcp.servers` in OpenClaw's config |
| NanoClaw | Config documented | Runs in the agent's container: mount the framework and project, add `jq`, set `MCPBASH_REGISTRY_DIR` to a writable path |
| Highlight AI | Tested | UI-based config: Settings → Connections → Add → Custom command |
| Cursor | Config documented | Config file location differs by install; use `mcp-bash config --client cursor` |
| Windsurf (Cascade) | Config documented | Use the app's MCP config UI/file; see snippet in README |
| LibreChat | Config documented | YAML config format; see snippet in README |
| OpenAI Agents SDK | Example provided | Python example only; verify SDK version and stdio wiring |

**Tested** = maintainers have manually verified end-to-end. **Config documented** = configuration instructions provided but not regularly tested.

**CI-tested platforms:** Ubuntu, macOS, Windows (Git Bash). CI validates the MCP protocol layer via integration tests, not specific client applications.

Using a different client? Any MCP-compliant stdio client should work. [Open an issue](https://github.com/yaniv-golan/mcp-bash-framework/issues) if you hit compatibility problems.

## MCPB Bundles

Package your server for one-click installation in Claude Desktop:

```bash
mcp-bash bundle
# Creates: my-server-1.0.0.mcpb
```

Double-click the `.mcpb` file to install, or drag it to Claude Desktop.

### Quick Configuration

Create `mcpb.conf` in your project root to customize the bundle:

```bash
MCPB_NAME="my-server"
MCPB_AUTHOR_NAME="Your Name"
MCPB_AUTHOR_EMAIL="you@example.com"
MCPB_REPOSITORY="https://github.com/you/my-server"
```

Without a config file, metadata is auto-resolved from `server.meta.json`, `VERSION`, and git config.

### Bundle Options

```bash
mcp-bash bundle --validate          # Check without creating
mcp-bash bundle --output ./dist     # Output to specific directory
mcp-bash bundle --verbose           # Show detailed progress
```

The bundle includes your tools, resources, prompts, and an embedded copy of the mcp-bash framework—fully self-contained for distribution.

To list the bundle in the MCP Registry, run `mcp-bash publish my-server-1.0.0.mcpb` (`--dry-run` validates without submitting; needs a registry token, see `mcp-bash publish --help`).

> [Full bundling guide](docs/MCPB.md)

## Vendoring the Runtime

Embed the mcp-bash runtime directly into your project repository so that no system install is needed at runtime. Useful for dev teams, CI, and MCP clients that don't support MCPB bundles:

```bash
mcp-bash vendor          # embed .mcp-bash/ into project root
git add .mcp-bash/       # commit it
mcp-bash vendor --verify # verify integrity at any time
```

> [Full vendoring guide](docs/VENDORING.md)

## Project Structure

```
Framework (Install Once)               Your Project (Version Control This)
~/.local/share/mcp-bash/               ~/my-mcp-server/
├── bin/mcp-bash                       ├── tools/
├── lib/                               │   └── check-disk/
├── handlers/                          │       ├── tool.sh
└── ...                                │       └── tool.meta.json
                                       ├── prompts/
~/.local/bin/                          ├── resources/
└── mcp-bash → ../share/mcp-bash/...   ├── server.d/
                                       │   └── server.meta.json (optional)
                                       └── .registry/ (auto-generated)
```

## Direct Tool Execution (run-tool)

Use `run-tool` to invoke a single tool without starting the full MCP server. This wires the same environment as the server (SDK path, args, metadata, roots). Tool names must match `^[a-zA-Z0-9_-]{1,64}$`; some clients, including Claude Desktop, enforce this and reject dotted names, so prefer hyphens/underscores for namespaces.

```bash
# Basic invocation (project inferred from CWD or MCPBASH_PROJECT_ROOT)
mcp-bash run-tool my-tool --allow-self --args '{"value":"hello"}'

# Simulate roots (comma-separated), stream stderr, override timeout, or print env
mcp-bash run-tool my-tool --allow-self --args '{"value":"hi"}' --roots /tmp/project,/data/shared --verbose --timeout 15
# Inspect wiring without executing
mcp-bash run-tool my-tool --print-env --dry-run

# Dry-run validates metadata/args without executing the tool
mcp-bash run-tool my-tool --dry-run
```

Flags: `--args` (JSON object), `--roots` (comma-separated paths), `--dry-run`, `--timeout <secs>`, `--verbose` (stream tool stderr), `--no-refresh` (reuse the cached registry; by default, run-tool rescans when anything under `tools/` is newer than the cache), `--minimal` (force degraded mode), `--project-root <dir>`, `--print-env` (dump wiring without executing), `--allow-self` / `--allow TOOL` / `--allow-all` (allowlist this run; tools are denied by default), `--with-server-env` (source `server.d/env.sh` first), `--source FILE` (source a file first; repeatable). Elicitation is not supported in CLI mode.

The scaffolder and examples use per-tool directories (e.g., `tools/check-disk/tool.sh`); automatic discovery requires tools to live under subdirectories of `tools/` (root-level scripts are not discovered).

See [**Project Structure Guide**](docs/PROJECT-STRUCTURE.md) for detailed layouts, Docker deployment, and multi-environment setups.

## Embedded resources in tool output

Tools can attach files directly to the MCP response as `type:"resource"` content parts; binary files are auto-base64-encoded into the `blob` field, text stays in `text`.

Use `mcp_result_text_with_resource` to embed files with your tool result:

```bash
payload_path="${MCPBASH_PROJECT_ROOT}/resources/report.txt"
printf 'Report content' >"${payload_path}"
mcp_result_text_with_resource \
  "$(mcp_json_obj message "See embedded report")" \
  --path "${payload_path}" --mime text/plain
```

This helper stays as is in 1.x and changes in 2.0; `mcp-bash migrate` will show the replacement once it is final. See the dedicated example at `examples/06-embedded-resources/` and [BEST-PRACTICES.md](docs/BEST-PRACTICES.md#embedding-resources-in-tool-responses) for full documentation.

## Diagnostics & Validation

- Readiness/health probe: `mcp-bash --health [--project-root DIR] [--timeout SECS]` (`0`=ready, `1`=unhealthy, `2`=misconfigured)
- Project checks: `mcp-bash validate [--project-root DIR] [--fix] [--json] [--explain-defaults] [--strict] [--inspector] [--deprecations-as-errors]`. Uses of APIs that change in 2.0 are reported as deprecations, counted separately from errors and warnings.
- Migrating to the 2.0 helpers: `mcp-bash migrate [--project-root DIR]` lists the uses of APIs removed in 2.0 (check only by default); `--write` applies the safe rewrites (to `mcp_result_json` and `mcp_fail --type`), `--list` / `--explain ID` describe the rules, `--json` gives a machine-readable report. If the project vendors the runtime, run `mcp-bash vendor --upgrade` first. See [BEST-PRACTICES.md](docs/BEST-PRACTICES.md#migrating-to-the-20-helpers).
- Environment check: `mcp-bash doctor [--json] [--dry-run|--fix]`
- Registry cache introspection: `mcp-bash registry status [--project-root DIR]`
- Client config: `mcp-bash config --json` (machine-readable), `--client <name>` (pasteable JSON), `--wrapper` (generate auto-install wrapper)

## SDK Discovery

Every tool sources shared helpers from `sdk/tool-sdk.sh`. When `mcp-bash` launches a tool it exports `MCP_SDK=/path/to/framework/sdk`, so tool scripts can run:

```bash
source "${MCP_SDK}/tool-sdk.sh"
```

If you copy a tool out of this repository (or build your own project layout) and run it directly, set `MCP_SDK` before executing the script:

```bash
export MCP_SDK=~/.local/share/mcp-bash/sdk
./tools/check-disk/tool.sh
```

If the SDK can’t be resolved, the script exits with a clear error.

## Roots (scoping filesystem access)
- If the client supports MCP Roots, mcp-bash requests them after `initialized` and exposes them to tools via env (`MCP_ROOTS_JSON`, `MCP_ROOTS_PATHS`, `MCP_ROOTS_COUNT`) and SDK helpers (`mcp_roots_list`, `mcp_roots_count`, `mcp_roots_contains`).
- If the client does not provide roots or times out, you can supply them via `MCPBASH_ROOTS="/path/one:/path/two"` or an optional `config/roots.json` in your project. Paths are normalized and enforced consistently.

## Completions

Register completions declaratively in `server.d/register.json`, or place a script next to a prompt or resource (for example `prompts/<name>/<name>.completion.sh`; see [docs/COMPLETION.md](docs/COMPLETION.md)):

```json
{
  "version": 1,
  "completions": [
    {"name":"example.completion","path":"completions/example.sh","timeoutSecs":5}
  ]
}
```

Paths are resolved relative to `MCPBASH_PROJECT_ROOT`, and registry refreshes pick them up automatically.

## Tool Policy Hook (optional)

Projects can restrict tool execution centrally in `server.d/policy.sh`. The built-in policy runs first and denies every tool unless it is allowlisted (`MCPBASH_TOOL_ALLOWLIST`, or `--allow-self` for `run-tool`); a project rule can only narrow that further.

The preferred form is the restrict-only hook `mcp_tools_policy_project`. The framework calls it after the built-in checks pass, with the tool name and the call's arguments (JSON):

```bash
# server.d/policy.sh
mcp_tools_policy_project() {
	local tool_name="$1" args_json="$2"
	if [ "${MYPROJECT_READ_ONLY:-0}" = "1" ] && [[ "${tool_name}" != myproj-get* ]]; then
		printf 'Read-only mode: %s is disabled. Ask the user to turn off read-only mode.\n' "${tool_name}"
		return 1
	fi
	return 0
}
```

- Exit 0 allows the call. Any other status denies it, and what the hook printed becomes the message the model and the user see (trailing newlines dropped, capped at 1 KiB; with no output it is `Tool '<name>' denied by server policy`). Output on allow is discarded.
- The hook runs in a subshell (`$(...)`), because the server's stdout is the protocol channel. Variables it sets don't survive between calls: keep any state in files.

A denial, whether from the allowlist or a project rule, comes back as a tool result with `isError: true` and `_meta["mcpbash/error"].type` = `policy_denied`, not as a JSON-RPC error, so clients such as Claude Desktop show the reason. A rejected tool path (a broken install) is still a JSON-RPC error.

Older projects define `mcp_tools_policy_check` instead. That still works, but it **replaces** the built-in policy, including the deny-by-default allowlist (`MCPBASH_TOOL_ALLOWLIST`) and tool path checks, so it must start with `mcp_tools_policy_check_default "$@" || return 1`; `mcp-bash validate` and `doctor` warn when it doesn't. Its denial message (`mcp_tools_error CODE "message" [data]`) and data are kept; the code is not, since a tool result has none. If `policy.sh` defines both, `mcp_tools_policy_check` wins and the project hook is not called. Either way this guards against accidentally widening access, not against a hostile project: `policy.sh` is trusted shell code sourced at startup and can redefine anything.

Keep the logic lightweight; the hook runs on every invocation.

## Learn by Example

The [`examples/`](examples/) directory shows common patterns end-to-end:

| Example | Concepts Covered |
|---------|------------------|
| [**00-hello-tool**](examples/00-hello-tool/) | Basic "Hello World" tool structure and metadata. |
| [**01-args-and-validation**](examples/01-args-and-validation/) | Handling JSON arguments and input validation. |
| [**02-logging-and-levels**](examples/02-logging-and-levels/) | Sending logs to the client and managing verbosity. |
| [**03-progress-and-cancellation**](examples/03-progress-and-cancellation/) | Long-running tasks, reporting progress, and handling user cancellation. |
| [**04-roots-basics**](examples/04-roots-basics/) | MCP roots scoping for tools; allows/denies file reads based on configured roots. |
| [**05-resources-basics**](examples/05-resources-basics/) | Listing and reading resources via the built-in file provider. |
| [**06-embedded-resources**](examples/06-embedded-resources/) | Embedding file content directly in tool responses. |
| [**07-prompts-basics**](examples/07-prompts-basics/) | Discovering and rendering prompt templates. |
| [**08-elicitation**](examples/08-elicitation/) | Client-driven elicitation prompts that gate tool execution. |
| [**09-registry-overrides**](examples/09-registry-overrides/) | Declarative registry overrides, live progress streaming, and a custom resource provider. |
| [**10-completions**](examples/10-completions/) | Completion registration, query filtering, and pagination/hasMore. |
| [**11-resource-templates**](examples/11-resource-templates/) | Resource template discovery, manual overrides, client-side expansion, and template-aware reads. |
| [**12-config-and-downloads**](examples/12-config-and-downloads/) | Layered configuration (`mcp_config_load`) and SSRF-safe downloads (`mcp_download_safe`). |
| [**13-ui-basics**](examples/13-ui-basics/) | MCP Apps: `ui://` resources, tool-associated and standalone UIs, `ui.meta.json`. |
| [**14-ui-templates**](examples/14-ui-templates/) | A result-driven dashboard built with the bundled UI templates. |
| [**15-cli-wrapper**](examples/15-cli-wrapper/) | Wrapping an external CLI: PATH resolution (`mcp_detect_cli`), error handling, result formatting. Start here if your server calls a CLI. |
| [**Advanced: register-sh-hooks**](examples/advanced/register-sh-hooks/) | Dynamic registration from `server.d/register.sh` (opt-in project hook). |
| [**Advanced: ffmpeg-studio**](examples/advanced/ffmpeg-studio/) | Real-world application: video processing pipeline with media inspection (optional, heavy deps). |

## Features at a Glance

- **Auto-Discovery**: Place scripts in your project's `tools/`, `resources/`, or `prompts/` directories—the framework finds them automatically.
- **Scaffolding**: Generate compliant tool, resource, prompt templates, and a test harness (`mcp-bash scaffold <type> <name>`, `mcp-bash scaffold test`).
- **Stdio Transport**: Standard input/output. No custom daemons or sidecars.
- **Framework/Project Separation**: Install the framework once, create unlimited projects.
- **Graceful Degradation**: Automatically detects an available JSON tool (`jq`, then `gojq`) or falls back to minimal mode if none are present.
- **Progress Streaming**: Emits progress and log notifications; set `MCPBASH_ENABLE_LIVE_PROGRESS=true` to stream them during execution (uses a lightweight background flusher).
- **Debug Mode**: Run `mcp-bash debug` to capture all JSON-RPC messages for analysis. See [docs/DEBUGGING.md](docs/DEBUGGING.md).
- **Elicitation**: Ask the user for input mid-tool (form and URL modes) when the client supports it. See [docs/ELICITATION.md](docs/ELICITATION.md).
- **Completions**: Argument completions for prompts and resource templates. See [docs/COMPLETION.md](docs/COMPLETION.md).
- **MCP Apps (UI resources)**: Serve `ui://` HTML views alongside tools, from files or built-in templates. See [docs/guides/ui-resources.md](docs/guides/ui-resources.md).
- **Distribution**: `mcp-bash bundle` builds an MCPB package, `mcp-bash publish` submits it to the MCP Registry, and `mcp-bash vendor` embeds the runtime in your repo. See [MCPB Bundles](#mcpb-bundles) and [Vendoring the Runtime](#vendoring-the-runtime).

## Configuration

### Required Configuration

| Variable | Description |
|----------|-------------|
| `MCPBASH_PROJECT_ROOT` | **Required for MCP clients.** Path to your project directory containing `tools/`, `prompts/`, `resources/`. CLI commands (`mcp-bash scaffold`, `mcp-bash validate`, etc.) auto-detect the project root from the current directory. |

### Optional Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `MCPBASH_TOOLS_DIR` / `MCPBASH_RESOURCES_DIR` / `MCPBASH_PROMPTS_DIR` / `MCPBASH_SERVER_DIR` | Derived from `MCPBASH_PROJECT_ROOT` | Override content and server hook locations. |
| `MCPBASH_REGISTRY_DIR` | `$MCPBASH_PROJECT_ROOT/.registry` | Registry cache location. |
| `MCPBASH_MAX_CONCURRENT_REQUESTS` | `16` | Cap concurrent worker slots. |
| `MCPBASH_MAX_TOOL_OUTPUT_SIZE` | `10485760` | Tool stdout limit; stderr/resources inherit when unset. |
| `MCPBASH_LOG_LEVEL` | `info` | Log level; use `debug` for discovery traces. |
| `MCPBASH_DEBUG_ERRORS` | `false` | Include tool diagnostics in outputSchema validation errors (exit code, stderr tail, trace line). |
| `MCPBASH_DEBUG_LOG` | (unset) | Override per-tool debug log path; SDK `mcp_debug` appends to it. |
| `MCPBASH_ENABLE_LIVE_PROGRESS` | `false` | Stream progress/log notifications during execution (starts a background flusher). |
| `MCPBASH_ENV_PAYLOAD_THRESHOLD` | `65536` | Spill args/metadata to temp files above this size. |
| `MCPBASH_TOOL_ENV_MODE` | `minimal` | Tool env isolation: `minimal`, `inherit`, or `allowlist`. |
| `MCPBASH_TOOL_ENV_INHERIT_ALLOW` | `false` | Must be `true` to allow `MCPBASH_TOOL_ENV_MODE=inherit`. |
| `MCPBASH_DEFAULT_TOOL_TIMEOUT` | `30` | Default tool timeout (seconds). |
| `MCPBASH_REMOTE_TOKEN` | (unset) | Shared secret for proxied deployments (minimum 32 chars; failures throttled). |
| `MCPBASH_HTTPS_ALLOW_HOSTS` / `MCPBASH_HTTPS_DENY_HOSTS` | (unset) | HTTPS provider host allow/deny lists; private/loopback always blocked. Allow list is required unless `MCPBASH_HTTPS_ALLOW_ALL=true`. |
| `MCPBASH_HTTPS_ALLOW_ALL` | `false` | Explicitly allow all public HTTPS hosts (unsafe; prefer `MCPBASH_HTTPS_ALLOW_HOSTS`). |
| `MCPBASH_CI_MODE` | (unset) | CI defaults: safe tmp/log dirs, keep-logs, timestamped logs, failure summary (`failure-summary.jsonl`), env snapshot (`env-snapshot.json`); `MCPBASH_CI_VERBOSE=true` starts at debug; GH annotations when tracing provides file/line. |

Full list and defaults: see [docs/ENV_REFERENCE.md](docs/ENV_REFERENCE.md).

### Server Metadata

Server identity is configured via `server.d/server.meta.json`. All fields are optional—smart defaults are applied when omitted:

| Field | Default | Description |
|-------|---------|-------------|
| `name` | Project directory name | Server identifier (e.g., `my-server`) |
| `title` | Titlecase of name | Human-readable display name (e.g., `My Server`) |
| `version` | `VERSION` file, `package.json`, or `0.0.0` | Server version |
| `description` | (omitted) | Brief description of the server |
| `websiteUrl` | (omitted) | URL to server homepage or documentation |
| `icons` | (omitted) | Array of icon objects for visual identification |
| `env` | (omitted) | Tool/provider env policy: only `MCPBASH_TOOL_ENV_MODE`, `MCPBASH_TOOL_ENV_ALLOWLIST`, `MCPBASH_PROVIDER_ENV_MODE`, `MCPBASH_PROVIDER_ENV_ALLOWLIST`. Never put secret values here. See [docs/MCPB.md](docs/MCPB.md#passing-secrets-to-tools) |

Example `server.d/server.meta.json`:
```json
{
  "name": "weather-api",
  "title": "Weather API Server",
  "version": "1.0.0",
  "description": "Provides weather data for any location",
  "websiteUrl": "https://example.com/weather-api",
  "icons": [
    {"src": "https://example.com/icon.svg", "sizes": ["any"], "mimeType": "image/svg+xml"}
  ]
}
```

If no `server.meta.json` exists, the server uses smart defaults based on your project directory name.

Optional initialize guidance can be defined in `server.d/server.instructions.md`.
When present and non-empty, its contents are returned in `initialize.result.instructions`
for protocol versions `2025-03-26` and newer (omitted for `2024-11-05`).

### Resource Annotations

Resources can carry optional `annotations` metadata (audience hints, priority, etc.) that clients may use for filtering or display.
Annotations are supported through all discovery paths:

- **`*.meta.json`**: include an `annotations` object in the metadata file.
- **Inline `# mcp:` header**: add `annotations` to the JSON payload.
- **Manual registration** (`register.sh`): pass `annotations` in the `mcp_register_resource` JSON.

Example `resources/report.meta.json` with annotations:
```json
{
  "name": "monthly-report",
  "description": "Monthly analytics report",
  "uri": "file:///data/report.csv",
  "mimeType": "text/csv",
  "annotations": {
    "audience": ["user"],
    "priority": 0.8
  }
}
```

When annotations are absent, the field is omitted from `resources/list` responses (not emitted as `null`).

### Tool SDK environment
- `MCPBASH_JSON_TOOL` and `MCPBASH_JSON_TOOL_BIN` point to the detected JSON processor (`gojq`/`jq`) and are injected into tool processes when available.
- `MCPBASH_MODE` is `full` when JSON tooling is present and `minimal` otherwise; SDK helpers warn and downgrade behaviour when running in minimal mode.
- `MCPBASH_DEBUG_LOG` points to a per-invocation debug log file (when available); use `mcp_debug` inside tools to append file-based checkpoints.
- `MCPBASH_TOOL_ENV_MODE` controls isolation for tool processes (`minimal`, `inherit`, or `allowlist`), but MCPBASH/MCP-prefixed variables (including JSON tool hints) are always propagated. Example allowlist for minimal exposure: `MCPBASH_TOOL_ENV_MODE=allowlist MCPBASH_TOOL_ENV_ALLOWLIST=HOME,PATH`.

### Capability Modes

| Mode | Supported surface | Limitations / when it applies |
|------|-------------------|--------------------------------|
| Full | Lifecycle, ping, logging/setLevel, tools/resources/prompts (list, call/read/subscribe), completion, pagination, `listChanged` notifications | Requires `jq`/`gojq` available; default mode. |
| Minimal | Lifecycle, ping, logging/setLevel | Tools/resources/prompts/completion are disabled and registry notifications are suppressed. Activated when no JSON tool is found or `MCPBASH_FORCE_MINIMAL=true`. |

### Registry Maintenance
- Auto-refresh: registries re-scan on TTL expiry (default 5s) and use lightweight file-list hashing to skip rebuilds when nothing changed.
- Manual refresh: `bin/mcp-bash registry refresh [--project-root DIR] [--no-notify] [--quiet] [--filter PATH]` rebuilds `.registry/*.json` and returns a status JSON. In minimal mode the command is skipped gracefully.

## Troubleshooting (quick hits)
- `PATH` issues (mcp-bash not found): ensure `~/.local/bin` is on PATH; rerun `source ~/.bashrc` or `~/.zshrc`.
- Missing JSON tooling (`jq`/`gojq`): install one; otherwise the server enters minimal mode (tools/resources/prompts disabled).
- macOS quarantine blocks execution: run `xattr -d com.apple.quarantine ~/.local/share/mcp-bash/bin/mcp-bash` (and your project path if needed).
- Git Bash/MSYS exec-limit quirks: set `MCPBASH_JSON_TOOL=jq` and `MSYS2_ARG_CONV_EXCL="*"` before running `mcp-bash`.

## Requirements

### Runtime Requirements
*   **Bash**: version 3.2 or higher (standard on macOS, Linux, WSL, and Git Bash on Windows).
*   **JSON Processor**: `jq` (preferred) or `gojq`.
    *   *Note*: If no JSON tool is found, the server runs in "Minimal Mode" (Lifecycle & Ping only).

### Development Requirements
If you plan to contribute to the core framework, see [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions (linting, tests, etc).

### Testing (quick start)
From the repo root:
```bash
./test/lint.sh
./test/unit/run.sh
./test/integration/run.sh
# Optional:
# ./test/compatibility/run.sh
# ./test/stress/run.sh
```

### Windows Notes
- Signals from the client may not reliably terminate subprocesses on Git Bash; prefer explicit `shutdown`/`exit` and short tool timeouts.
- Paths are normalized to `/c/...` style; avoid mixing Windows- and POSIX-style roots in the same project.
- Large payloads can be slower under MSYS; keep registry TTLs reasonable.
See [docs/WINDOWS.md](docs/WINDOWS.md) for full guidance and workarounds.

## Uninstall

**Homebrew:**

```bash
brew uninstall mcp-bash
brew untap yaniv-golan/mcp-bash  # optional
```

**curl installer or manual install:**

```bash
# 1. Remove the framework installation
rm -rf ~/.local/share/mcp-bash

# 2. Remove the symlink
rm -f ~/.local/bin/mcp-bash

# 3. (Optional) Remove the PATH addition from your shell config
#    Edit ~/.bashrc or ~/.zshrc and remove the line:
#    export PATH="$HOME/.local/bin:$PATH"
```

**Either way**, remove the wrapper script if you created one with `mcp-bash config --wrapper` or `--wrapper-env` (one file per project, named after the server; your other scripts stay):

```bash
rm -f /path/to/your/project/<server-name>.sh  # e.g., my-server.sh
```

Your project directories (`tools/`, `resources/`, `prompts/`) are untouched; they live in your own repos. To reinstall, follow [Install the Framework](#2-install-the-framework).

---

## Documentation

The [**documentation index**](docs/README.md) lists every guide.

### Getting Started
- [**Project Structure Guide**](docs/PROJECT-STRUCTURE.md) - Layouts, Docker deployment, multi-environment setups.
- [**Examples**](examples/) - Learn by example: hello-world, args, logging, progress, real-world video processing.

### Feature Guides
- [**MCPB Bundles**](docs/MCPB.md) - One-click distribution via Claude Desktop and MCP Registry.
- [**Vendoring**](docs/VENDORING.md) - Embed the runtime in your git repo for CI and non-MCPB clients.
- [**Elicitation**](docs/ELICITATION.md) - Form and URL modes, SDK helpers, and client capability checks.
- [**Roots**](docs/ROOTS.md) - Roots/list flow, env wiring, validation, and fallbacks.
- [**Completions**](docs/COMPLETION.md) - Manual registration, provider types, pagination, and script contracts.
- [**Registry**](docs/REGISTRY.md) - Registry envelopes, TTL, manual registration, and cache formats.
- [**Resource Templates**](docs/RESOURCE-TEMPLATES.md) - Auto/manual discovery, pagination, collisions, client-side expansion, and template-aware `resources/read`.
- [**Limits & Performance**](docs/LIMITS.md) - Concurrency, payload ceilings, throttling.
- [**Errors**](docs/ERRORS.md) - Protocol errors vs tool execution errors (SEP-1303).
- [**Best Practices**](docs/BEST-PRACTICES.md) - Development, testing, operations guidance.
- [**Environment Variables**](docs/ENV_REFERENCE.md) - Every setting the server reads.
- [**Debugging**](docs/DEBUGGING.md) / [**Logging**](docs/LOGGING.md) / [**Inspector**](docs/INSPECTOR.md) - Capture traffic, read logs, and test with the MCP Inspector.

### MCP Apps (UI)
- [**MCP Apps concepts**](docs/concepts/mcp-apps.md) - How UI resources work and what clients support.
- [**UI resources guide**](docs/guides/ui-resources.md) - Adding `ui://` views to a server.
- [**UI SDK reference**](docs/reference/ui-sdk.md) and [**UI templates reference**](docs/reference/ui-templates.md).

### Deep Dive
- [**Architecture Guide**](docs/ARCHITECTURE.md) - Internal architecture, lifecycle loop, concurrency model.
- [**Protocol Compliance**](SPEC-COMPLIANCE.md) - Detailed MCP protocol support breakdown.
- [**Performance Guide**](docs/PERFORMANCE.md) - Tuning concurrency, timeouts, and registry scans.
- [**Security Policy**](docs/SECURITY.md) - Input validation and execution safety.
- [**Minimal Mode**](docs/MINIMAL-MODE.md) - Behavior when jq/gojq is missing or minimal mode is forced.
- [**Changelog**](CHANGELOG.md) - Notable changes between releases.
- [**Windows Support**](docs/WINDOWS.md) - Running on Git Bash/WSL.
- [**Remote Connectivity**](docs/REMOTE.md) - Exposing mcp-bash over HTTP/SSE via external gateways.

### Scope and Goals
- Bash-only Model Context Protocol server verified on macOS Bash 3.2, Linux Bash ≥3.2 (including Alpine/BusyBox, CI-tested), and Windows (Git Bash is CI-tested; WSL behaves like Linux).
- Targets MCP protocol version `2025-11-25` while supporting negotiated downgrades.
- Transport support is limited to stdio; HTTP/SSE/OAuth transports remain out of scope (see [Remote Connectivity](docs/REMOTE.md) for gateway options).

## Built with mcp-bash

| Project | Description |
|---------|-------------|
| [affinity-sdk](https://github.com/yaniv-golan/affinity-sdk) | Affinity CRM SDK with MCP server support |
| [claude-familiar](https://github.com/yaniv-golan/claude-familiar) | Companion personality and MCP tools for Claude Code |
| [git-hex](https://github.com/yaniv-golan/git-hex) | Safe, powerful git refactoring tools for AI assistants |

If you've built an MCP server using this framework, show it off! Add this badge to your project's README:

[![MCP Bash Framework](https://img.shields.io/badge/MCP-MCP_Bash_Framework-green?logo=modelcontextprotocol)](https://github.com/yaniv-golan/mcp-bash-framework)

```markdown
[![MCP Bash Framework](https://img.shields.io/badge/MCP-MCP_Bash_Framework-green?logo=modelcontextprotocol)](https://github.com/yaniv-golan/mcp-bash-framework)
```

We'd love to see what you build—consider [opening a discussion](https://github.com/yaniv-golan/mcp-bash-framework/discussions) to share your project with the community.

## FAQ

### Why is the repository named `mcp-bash-framework` but the CLI is `mcp-bash`?

The repository name `mcp-bash-framework` reflects what this project is: a framework you install once and use to create multiple MCP server projects. The CLI/binary is named `mcp-bash` because that's what you invoke—short and memorable. The name `mcp-bash` was already taken on GitHub, so we chose `mcp-bash-framework` to accurately describe the architecture while avoiding namespace conflicts.

### Do I need jq? What about gojq?

You need one of them for tools, resources and prompts; without either, the server runs in [minimal mode](docs/MINIMAL-MODE.md) (lifecycle and ping only). Prefer `jq` (`brew install jq`, `apt install jq`); the server looks for it first. `gojq` works too. To choose explicitly, set `MCPBASH_JSON_TOOL=jq` or `gojq`.

---

mcp-bash is intentionally small. It gives you control, clarity, and a predictable surface for AI systems. **Build tools, not infrastructure.**