An MCP server that wraps common CLI tools (kubectl, terraform, helm, git, argocd, jq, yq, ripgrep, etc.) and returns structured JSON instead of raw text output.
Designed for AI agents and LLM-powered workflows where parsing human-readable CLI output wastes tokens and breaks easily.
Created with the help of Claude since it is the main consumer of the structured data.
Quick Start
Structured JSON wrappers for git, kubectl, terraform, helm, argocd, gh, glab and other CLIs.
It reports your Node version, whether the MCP SDK loads, which CLIs are
available (versions included), and the resolved BASH_MCP_MODE — then exits
non-zero if a critical check fails (Node too old or the SDK can't load). Missing
CLIs are advisory, not failures. Without --doctor, the binary starts the MCP
server as usual.
Claude Code Setup
Add bash-mcp to your Claude Code settings. For all projects (recommended), edit ~/.claude/settings.json. For a single project, edit .claude/settings.json in the project root.
Safe by default: an unset BASH_MCP_MODE resolves to readOnly, which
blocks mutating run/batch commands. The examples set it explicitly for
clarity. For trusted local use, set BASH_MCP_MODE=off to allow writes.
To verify it's working, start Claude Code and check that bash-mcp tools (like cat, rg, git_status) appear in the tool list.
You can also add it from the CLI:
claude mcp add bash-mcp -e BASH_MCP_MODE=readOnly -- npx -y @nickw8/bash-mcp
Steer agents toward the structured tools (optional)
Registering the server exposes the tools, but agents still reach for raw Bash
out of habit. Two assets fix that: a rules file (~/.claude/rules/bash-mcp-tools.md,
auto-loaded into every session — the tool inventory) and a PreToolUse(Bash)
redirect hook. Install both with:
It prints the settings.json hook block to paste in (the rules file needs no
settings — Claude Code auto-loads ~/.claude/rules/*.md). See
hooks/README.md for details and the clone-time
npm run claude:install equivalent. --uninstall-claude removes both files again.
Or install the Claude Code plugin, which bundles the server and the hook (in warn
mode — it never blocks unless BASH_MCP_HOOK=block):
(Use "command": "bash-mcp" with no args if you installed globally.) Any MCP-aware
client works the same way — point it at the bash-mcp binary or npx -y @nickw8/bash-mcp.
Recommended Hooks
Optional Claude Code hooks in hooks/ steer agents toward the structured tools by blocking raw shell commands (git status, kubectl get, cat, …) that have a bash-mcp equivalent, while letting write commands (git commit, kubectl apply) pass through. BASH_MCP_HOOK=warn makes every block advisory; off disables the hook.
Raw CLI tools return text meant for humans. AI agents waste tokens parsing tables, aligning columns, and guessing field boundaries. bash-mcp runs the same CLI tools but returns structured JSON with typed schemas, so the agent gets exactly the data it needs.
# Raw kubectl output: 148 tokens to parse a table
NAME READY STATUS RESTARTS AGE
nginx-7c5b8d6c88-abc12 1/1 Running 0 3d
# bash-mcp output: typed, compact, ready to use
{ "name": "nginx-7c5b8d6c88-abc12", "status": "Running", "age": "3d", ... }
See docs/guides/token-benchmarks.md for measured savings (e.g.
kubectl describe pod → kube_diagnose_pod is ~64% fewer tokens).
Philosophy: structured tools first, run is the escape hatch
Reach for a structured tool first. Every wrapper returns typed JSON the agent can
use directly — no column-counting, no re-parsing on a format change.
Prefer a diagnostic over a chain of raw commands. Tools like kube_diagnose_pod,
helm_release_triage, and argo_app_health_summary answer an operational question
in one call — { status, likelyCauses, suggestedNextCommands, evidence } — instead of
making the agent run get, then logs, then reason across both.
run / batch are the escape hatch. For commands without a dedicated wrapper,
run executes anything with smart truncation. Optional hooks actively steer
agents back to the structured tool when a raw equivalent is invoked.
Read-only tools carry the MCP readOnlyHint annotation. run/batch can be gated with
BASH_MCP_MODE (see Configuration).
Which tool should I use?
Pick the structured tool that matches your intent instead of reaching for a raw command.
The list_guidance tool returns this same index as JSON (filter by intent or category),
so agents can self-select without the README.
I want to…
Use this
Instead of
diagnose a crashing or failing Kubernetes pod
kube_diagnose_pod
kubectl describe pod, kubectl logs
find all not-ready or failing pods in a namespace
kube_pod_failure_summary
kubectl get pods, kubectl get pods | grep
check a deployment's rollout health
kube_deployment_status
kubectl rollout status, kubectl get deployment
understand recent cluster events
kube_events_summary
kubectl get events
summarize the changes in a Terraform plan
tf_plan_summary
terraform plan, terraform show
review the current feature branch before a PR
git_pr_context
git diff, git log, git status
understand the overall state of a repository
repo_health_summary
git status, git log --oneline
read a file's structure without loading the full body
outline
cat (entire file), cat
read one region of a large file
cat
cat (whole file), sed -n '10,40p'
keep a broad search from flooding the context
rg
rg | head
search for exact code references or symbols
rg
grep -r, rg
check whether a pull request's CI checks pass
gh_pr_checks
gh pr checks, gh pr view
find out why a CI / GitHub Actions run failed
gh_run_failure_summary
gh run view --log-failed, gh run list
check whether a GitLab pipeline or merge request's jobs pass
glab_ci_status
glab ci get, glab ci status
find out why a GitLab CI pipeline failed
glab_pipeline_failure_summary
glab ci trace, glab ci view
triage why a Helm release is unhealthy
helm_release_triage
helm status, helm get values
check whether an ArgoCD app is healthy and in sync
argo_app_health_summary
argocd app get, argocd app list
validate a Liquibase changelog for errors
liquibase_validate
liquibase validate
preview the SQL Liquibase would run for pending changesets
liquibase_update_sql
liquibase updateSQL, liquibase updateSQL | grep
list Liquibase changesets not yet applied to the database
liquibase_status
liquibase status --verbose
lint a shell script for bugs and quoting issues
bash_lint
shellcheck, shellcheck -f json1
run a bats or shell test suite
bash_test
bats, bash test.sh
discover which CLIs are installed before calling a tool
check_environment
which, <tool> --version
Tools
📖 Full tool reference → docs/tools.md — generated from the
registered tools (inputs, outputs, and the raw command each one approximates).
Regenerate with npm run docs:tools.
Environment
Tool
Description
check_environment
Report which CLIs are installed (and their versions) so you can pick the right tool before calling it.
list_guidance
Return an intent → preferred-tool index so you can pick the right bash-mcp tool for a goal.
Filesystem
Tool
Description
ls
List files in a directory.
tree
Show directory structure as a compact tree.
du
Show disk usage for paths.
find_files
Find files by name pattern, type, or modification time.
Search
Tool
Description
rg
Search file contents with ripgrep.
glob
Find files matching a glob pattern.
File
Tool
Description
cat
Read one or more files with line numbers and smart truncation.
outline
Show the structural outline of a file — function/class names, top-level comments, imports.
Structured git diff: files changed with insertions/deletions counts.
git_branches
List git branches.
repo_health_summary
One-call snapshot of a git working tree: branch, ahead/behind vs upstream, staged/unstaged/untracked counts, recent commits, and the uncommitted diffstat.
git_pr_context
Collect the commits and file changes of a branch vs a base ref (for writing a PR description): commit list, changed files with status, and a diffstat over base...head.
git_diff_content
Show git diff with structured patch content.
GitHub
Tool
Description
gh_pr_checks
Report a pull request's CI checks as counts per state plus the failing and pending checks.
gh_run_failure_summary
Diagnose a GitHub Actions run in one call: status, the failed jobs and steps, and the ##[error] lines from the failed log as evidence.
GitLab
Tool
Description
glab_ci_status
Report a GitLab pipeline's jobs as counts per status plus the failed, running and pending jobs.
glab_pipeline_failure_summary
Diagnose a GitLab pipeline in one call: status, the failed jobs and why GitLab says they failed, and the last script lines of each failed job's trace as evidence.
Kubernetes
Tool
Description
kube_get
Get Kubernetes resources as structured data.
kube_logs
Get pod logs.
kube_contexts
List available kubectl contexts with current context marked.
kube_diagnose_pod
Diagnose why a pod is unhealthy in one call.
kube_pod_failure_summary
List unhealthy pods in a namespace with their failure reason and evidence — one call instead of get + describe per pod.
kube_deployment_status
Report a deployment's rollout health (ready/desired replicas, conditions) as a structured diagnosis.
kube_events_summary
Summarize Warning events in a namespace (grouped by reason, ordered by count) instead of scrolling raw kubectl get events.
Terraform
Tool
Description
tf_state_list
List resources in Terraform state.
tf_show
Show current Terraform state as structured JSON.
tf_plan_summary
Run terraform plan and return a structured summary of changes (add/change/destroy counts and affected resources).
tf_workspaces
List Terraform workspaces with current workspace marked.
tf_outputs
List Terraform/OpenTofu outputs as [name, value] pairs; a sensitive output is redacted to [name] alone.
tf_providers
List the Terraform/OpenTofu version and selected provider versions for the project.
tf_validate_summary
Validate the Terraform/OpenTofu config and return a compact pass/fail summary with diagnostics.
tf_modules_summary
List the modules used by an initialized Terraform/OpenTofu project (key, source, version).
tf_backend_info
Report the configured backend type and config for an initialized project.
Helm
Tool
Description
helm_list
List Helm releases with status, chart, and app version.
helm_status
Get detailed status of a Helm release.
helm_values
Get the computed values for a Helm release as structured data.
helm_release_triage
Diagnose a Helm release's health in one call: combines helm status + helm history into current status, likely causes, suggested next commands, and recent-revision evidence.
ArgoCD
Tool
Description
argo_apps
List ArgoCD applications with sync/health status.
argo_app_detail
Get detailed status for a single ArgoCD application including resource health.
argo_app_diff
Show what's out of sync for an ArgoCD application.
argo_app_health_summary
Diagnose an ArgoCD application's health in one call: overall sync/health, likely causes, suggested next commands, and the unhealthy resources/conditions as evidence.
Data Processing
Tool
Description
jq
Query and transform JSON using jq expressions.
yq
Query and transform YAML files using yq expressions (mikefarah/yq).
.NET
Tool
Description
dotnet_build
Run dotnet build and return structured diagnostics with file, line, column, message, and error code.
dotnet_test
Run dotnet test and return structured results: pass/fail/skip counts, failure messages.
Liquibase
Tool
Description
liquibase_validate
Validate a Liquibase changelog and return a structured pass/fail result with per-changeset errors (duplicate ids, checksum drift).
liquibase_update_sql
Render the SQL Liquibase would run for pending changesets (updateSQL) as structured per-changeset summaries with a SQL-Server batch lint.
liquibase_status
List Liquibase changesets not yet applied to the target database, as structured JSON.
Node.js
Tool
Description
npm_lint
Run biome check and return structured diagnostics with file, line, column, message, and rule.
npm_test
Run vitest or jest (picked from package.json) and return structured test results: suites, pass/fail counts, failure messages.
npm_typecheck
Run tsc/tsgo --noEmit and return structured type errors with file, line, column, message, and TS error code.
Python
Tool
Description
python_lint
Run ruff check and return structured diagnostics with file, line, column, message, and rule code.
python_test
Run pytest and return structured test results: suites, pass/fail counts, failure messages.
python_typecheck
Run mypy and return structured type errors with file, line, column, message, and error code.
Shell
Tool
Description
bash_syntax_check
Check shell scripts for syntax errors with bash -n and return structured diagnostics (file, line, message).
bash_lint
Run shellcheck and return structured diagnostics with file, line, column, message, and SC rule code.
bash_test
Run a shell test script — bats .bats files via --tap, or a plain .sh harness — and return structured results: per-case pass/fail plus a summary parsed from TAP or N tests, M failures output, falling back to exit-code-only when the format is unrecognized.
Execution
Tool
Description
run
Run a single command — a binary plus an args array, executed directly without a shell (so no pipes, redirects, or ';'/'&&' in the string) — and return structured output with smart truncation.
run_seq
Run an ordered list of labeled commands one after another, stopping at the first failure by default.
batch
Run multiple commands in parallel and return all results.
Results
Tool
Description
result_get
Page through a result the response budget trimmed.
All tf_* tools accept binary: "terraform" | "tofu" (or set $TF_BINARY) to run OpenTofu.
Example Agent Workflows
Triage a crash-looping pod. Before — three raw calls plus reasoning:
kubectl get pods -n payments # find the broken pod
kubectl describe pod api-xyz # read events/state, infer cause
kubectl logs api-xyz --tail=200 # scroll for the error
# ...agent reasons across all three to guess the next step
Wide-event JSON logging to stderr (one line per tool call). error logs only failed calls; info adds successes; off/silent disables. stdout always stays pure MCP. run/batch commands are redacted (metadata only).
BASH_MCP_MODE
readOnly | confirmWrites | off
readOnly
Gates run/batch. Default readOnly blocks commands classified as mutating (confirmWrites likewise). Set off (or dangerous) to disable enforcement — recommended only for trusted local use.
BASH_MCP_PROFILE
core | development | infrastructure | all
all
Which tool groups register. Fewer tools, less schema in context — see tool discovery. An unknown value fails at startup.
BASH_MCP_RESPONSE_MODE
auto | summary | compact | full
auto
Response budget. A cut result carries retained and its full payload is pageable with result_get. full is the v4 behaviour. See output conventions.
BASH_MCP_RESULT_TTL
seconds
900
How long a stored result stays retrievable; 0 disables the store.
BASH_MCP_HOOK
block | warn | off
block (warn in the plugin)
Redirect hook behaviour.
TF_BINARY
terraform | tofu
terraform
Default binary for all tf_* tools (overridable per-call via binary).
Timeouts
Each tool group uses appropriate default timeouts:
Filesystem/search/git: 30 seconds
Kubernetes/Helm/ArgoCD: 15 seconds
Terraform state: 30 seconds
Terraform plan: 120 seconds
.NET build/test: 120 seconds
Python lint: 30 seconds
Python test: 120 seconds
Python typecheck: 60 seconds
Command Buffer
All commands default to a 10 MB output buffer. Override per-tool in src/exec.ts.
Development
# Install dependencies
npm install
# Run in development mode (auto-reloads)
npm run dev
# Build for production
npm run build
# Start production server
npm start
# Run tests
npm test
# Lint and format
npm run lint
npm run format
Each tool file exports a single register*Tools(server) function that registers all tools in its group. Every tool returns both content (text for display) and structuredContent (typed JSON for programmatic use).
Adding a New Tool
Add to an existing category (src/tools/<group>/<group>.ts) or create a new category directory
Define input/output schemas with Zod (add annotations: { readOnlyHint: true } for read-only tools)
Implement the handler using exec() or execJson() from #exec, return ok() or err() from #response
Register it with defineTool from #tool (injects wide-event logging + uniform error catching)
Wire the group's register*Tools into src/index.ts
Add tests in a co-located *.test.ts file
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { exec } from "#exec";
import { ok, err } from "#response";
import { defineTool } from "#tool";
export function registerMyTools(server: McpServer) {
defineTool(server, "my_tool", {
title: "My tool",
description: "What it does and why structured output helps.",
inputSchema: {
path: z.string().describe("Path to operate on"),
},
outputSchema: {
result: z.string(),
},
annotations: { readOnlyHint: true },
}, async ({ path }) => {
const result = await exec("mytool", ["--json", path]);
if (result.exitCode !== 0) {
return err(result.stderr, { result: "" });
}
return ok({ result: result.stdout.trim() });
});
}