| name | mcp-server-bash-sdk |
| description | Build lightweight MCP servers in pure Bash with zero runtime overhead for AI tool integration |
| triggers | ["create an MCP server in bash","implement MCP protocol with shell scripts","build a bash MCP server","add tools to bash MCP server","configure MCP server in shell","write MCP tool functions in bash","debug bash MCP server","integrate bash MCP with Claude"] |
MCP Server Bash SDK
Skill by ara.so — MCP Skills collection.
Overview
The MCP Server Bash SDK is a lightweight, zero-overhead implementation of the Model Context Protocol (MCP) server in pure Bash. It allows you to create MCP servers without Node.js, Python, or other heavy runtimes—just Bash and jq for JSON processing. The SDK handles JSON-RPC 2.0 protocol communication over stdio while you focus on implementing tool functions.
Key Benefits:
- Zero runtime overhead compared to Node.js/Python
- Simple function-based tool definition
- Automatic tool discovery via naming convention
- External JSON configuration for tools and server metadata
- Perfect for API wrappers and system utilities
Installation
Requirements
- Bash shell (4.0+)
jq for JSON processing
Install jq:
brew install jq
sudo apt-get install jq
sudo yum install jq
Clone and Setup
git clone https://github.com/muthuishere/mcp-server-bash-sdk
cd mcp-server-bash-sdk
chmod +x mcpserver_core.sh moviemcpserver.sh
Test Installation
echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "get_movies"}, "id": 1}' | ./moviemcpserver.sh
Core Concepts
Architecture
- mcpserver_core.sh: Protocol layer handling JSON-RPC and MCP communication
- Your server script: Business logic with
tool_* functions
- assets/: JSON configuration files for tools and server metadata
- Communication: JSON-RPC 2.0 over stdio
Tool Function Contract
All tool functions must follow these rules:
- Naming: Prefix with
tool_ + exact name from tools_list.json
- Parameters: Accept single parameter
$1 containing JSON arguments
- Success: Echo result and
return 0
- Failure: Echo error message and
return 1
- Discovery: Automatically exposed based on
tools_list.json
Creating Your First MCP Server
Step 1: Create Server Script
Create weatherserver.sh:
#!/bin/bash
MCP_CONFIG_FILE="$(dirname "${BASH_SOURCE[0]}")/assets/weatherserver_config.json"
MCP_TOOLS_LIST_FILE="$(dirname "${BASH_SOURCE[0]}")/assets/weatherserver_tools.json"
MCP_LOG_FILE="$(dirname "${BASH_SOURCE[0]}")/logs/weatherserver.log"
source "$(dirname "${BASH_SOURCE[0]}")/mcpserver_core.sh"
API_KEY="${WEATHER_API_KEY:-}"
BASE_URL="${WEATHER_API_URL:-https://api.openweathermap.org/data/2.5}"
tool_get_weather() {
local args="$1"
local location=$(echo "$args" | jq -r '.location')
if [[ -z "$location" ]]; then
echo "Missing required parameter: location"
return 1
fi
if [[ -z "$API_KEY" ]]; then
echo
1
response=$(curl -s )
cod=$( | jq -r )
[[ != ]];
message=$( | jq -r )
1
| jq
0
}
() {
args=
location=$( | jq -r )
days=$( | jq -r )
[[ -z ]];
1
response=$(curl -s )
| jq
0
}
run_mcp_server
Step 2: Create Tools Definition
Create assets/weatherserver_tools.json:
{
"tools": [
{
"name": "get_weather",
"description": "Get current weather conditions for a specified location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name (e.g., 'London', 'New York') or coordinates"
}
},
"required": ["location"]
}
},
{
"name": "get_forecast",
"description": "Get weather forecast for the next few days",
"inputSchema"
Step 3: Create Server Configuration
Create assets/weatherserver_config.json:
{
"protocolVersion": "2025-03-26",
"serverInfo": {
"name": "WeatherServer",
"version": "1.0.0"
},
"capabilities": {
"tools": {
"listChanged": true
}
},
"instructions": "Provides weather information and forecasts using OpenWeatherMap API. Requires WEATHER_API_KEY environment variable."
}
Step 4: Make Executable and Test
chmod +x weatherserver.sh
echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "get_weather", "arguments": {"location": "London"}}, "id": 1}' | WEATHER_API_KEY=your_key ./weatherserver.sh
echo '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' | ./weatherserver.sh
MCP Protocol Methods
Initialize
echo '{"jsonrpc": "2.0", "method": "initialize", "params": {"protocolVersion": "2025-03-26", "capabilities": {}}, "id": 1}' | ./weatherserver.sh
List Tools
echo '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' | ./weatherserver.sh
Call Tool
echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "get_weather", "arguments": {"location": "Paris"}}, "id": 1}' | ./weatherserver.sh
List Prompts (if configured)
echo '{"jsonrpc": "2.0", "method": "prompts/list", "id": 1}' | ./weatherserver.sh
Configuration
Environment Variables
Access environment variables in your tool functions:
API_KEY="${MY_API_KEY:-default_value}"
BASE_URL="${MY_BASE_URL:-https://api.example.com}"
DEBUG="${MCP_DEBUG:-false}"
tool_example() {
local args="$1"
if [[ "$DEBUG" == "true" ]]; then
echo "Debug: Processing with API key: ${API_KEY:0:5}..." >&2
fi
curl -H "Authorization: Bearer $API_KEY" "$BASE_URL/endpoint"
}
Custom Configuration Paths
Override default paths before sourcing core:
#!/bin/bash
MCP_CONFIG_FILE="/custom/path/config.json"
MCP_TOOLS_LIST_FILE="/custom/path/tools.json"
MCP_LOG_FILE="/var/log/myserver.log"
source "$(dirname "${BASH_SOURCE[0]}")/mcpserver_core.sh"
Logging
Enable debug logging:
MCP_LOG_FILE="./logs/debug.log"
tail -f ./logs/debug.log
Integration with AI Assistants
VS Code with GitHub Copilot
Add to .vscode/settings.json:
{
"mcp": {
"servers": {
"weather-server": {
"type": "stdio",
"command": "/absolute/path/to/weatherserver.sh",
"args": [],
"env": {
"WEATHER_API_KEY": "your-api-key",
"MCP_DEBUG": "false"
}
}
}
}
}
Claude Desktop
Add to Claude config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"weather-server": {
"command": "/absolute/path/to/weatherserver.sh",
"args": [],
"env": {
"WEATHER_API_KEY": "your-api-key"
}
}
}
}
Testing with Copilot Chat
@workspace /mcp weather-server get weather for Tokyo
Common Patterns
Pattern: External API Wrapper
#!/bin/bash
source "$(dirname "${BASH_SOURCE[0]}")/mcpserver_core.sh"
API_TOKEN="${GITHUB_TOKEN:-}"
API_BASE="https://api.github.com"
tool_get_repo_info() {
local args="$1"
local owner=$(echo "$args" | jq -r '.owner')
local repo=$(echo "$args" | jq -r '.repo')
if [[ -z "$owner" ]] || [[ -z "$repo" ]]; then
echo "Missing required parameters: owner and repo"
return 1
fi
local response=$(curl -s -H "Authorization: token $API_TOKEN" \
"${API_BASE}/repos/${owner}/${repo}")
echo "$response" | jq '{
name: .name,
stars: .stargazers_count,
forks: .forks_count,
description: .description
}'
return 0
}
run_mcp_server "$@"
Pattern: System Command Wrapper
#!/bin/bash
source "$(dirname "${BASH_SOURCE[0]}")/mcpserver_core.sh"
tool_disk_usage() {
local args="$1"
local path=$(echo "$args" | jq -r '.path // "/"')
if [[ ! -d "$path" ]]; then
echo "Path does not exist: $path"
return 1
fi
df -h "$path" | awk 'NR==2 {
print "{\"path\":\"'$path'\",\"size\":\""$2"\",\"used\":\""$3"\",\"available\":\""$4"\",\"use_percent\":\""$5"\"}"
}'
return 0
}
tool_process_list() {
local args="$1"
local filter=$(echo "$args" | jq -r '.filter // ""')
ps aux | grep -i "$filter" | head -20 | jq -R -s -c 'split("\n") | map(select(length > 0))'
return 0
}
run_mcp_server "$@"
Pattern: Data Transformation
#!/bin/bash
source "$(dirname "${BASH_SOURCE[0]}")/mcpserver_core.sh"
tool_json_to_csv() {
local args="$1"
local json_data=$(echo "$args" | jq -r '.data')
if [[ -z "$json_data" ]]; then
echo "Missing required parameter: data"
return 1
fi
echo "$json_data" | jq -r '
(.[0] | keys_unsorted) as $keys |
$keys,
(.[] | [.[$keys[]]] | @csv)
' | paste -sd ',' -
return 0
}
run_mcp_server "$@"
Pattern: File Operations
#!/bin/bash
source "$(dirname "${BASH_SOURCE[0]}")/mcpserver_core.sh"
WORKSPACE_DIR="${WORKSPACE_DIR:-./workspace}"
tool_read_file() {
local args="$1"
local filepath=$(echo "$args" | jq -r '.path')
local fullpath="${WORKSPACE_DIR}/${filepath}"
if [[ ! -f "$fullpath" ]]; then
echo "File not found: $filepath"
return 1
fi
jq -n --arg content "$(cat "$fullpath")" '{content: $content}'
return 0
}
tool_write_file() {
local args="$1"
local filepath=$(echo "$args" | jq -r '.path')
local content=$(echo "$args" | jq -r '.content')
local fullpath="${WORKSPACE_DIR}/"
=$( )
-p
>
0
}
run_mcp_server
Troubleshooting
Server Not Responding
Symptom: No output when sending JSON-RPC requests
Solutions:
chmod +x yourserver.sh
head -1 yourserver.sh
which jq
echo '{"test": "value"}' | jq .
MCP_LOG_FILE="./debug.log" ./yourserver.sh
Tool Function Not Found
Symptom: Error "Tool not found" when calling a tool
Solutions:
jq . assets/yourserver_tools.json
grep "tool_get_weather" yourserver.sh
echo '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' | ./yourserver.sh
JSON Parsing Errors
Symptom: jq errors or malformed JSON responses
Solutions:
echo "$args" | jq . 2>&1
local result=$(curl ... | jq -R -s .)
echo "$data" | jq . | jq '.field' | jq -c .
Environment Variables Not Set
Symptom: Tool fails due to missing API keys
Solutions:
API_KEY="${MY_API_KEY:-default_key}"
if [[ -z "$API_KEY" ]]; then
echo "MY_API_KEY environment variable required"
return 1
fi
MY_API_KEY=test ./yourserver.sh
cURL or External Command Failures
Symptom: Tool returns errors from external commands
Solutions:
local response=$(curl -s "https://api.example.com/endpoint")
if [[ $? -ne 0 ]]; then
echo "API request failed"
return 1
fi
curl -s --max-time 30 "https://api.example.com"
local http_code=$(curl -s -w "%{http_code}" -o /tmp/response.json "url")
if [[ "$http_code" -ge 400 ]]; then
echo "HTTP error: $http_code"
return 1
fi
if ! echo "$response" | jq -e . >/dev/null 2>&1; then
echo "Invalid JSON response"
return 1
fi
Configuration File Not Found
Symptom: Server fails to start or reads wrong config
Solutions:
MCP_CONFIG_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/assets/config.json"
ls -la "$MCP_CONFIG_FILE" "$MCP_TOOLS_LIST_FILE"
chmod 644 assets/*.json
mkdir -p assets logs
Debugging Tips
set -x
echo "Debug: tool called with: $args" >&2
validate_json() {
if ! echo "$1" | jq . >/dev/null 2>&1; then
echo "Invalid JSON: $1" >&2
return 1
fi
}
tool_get_weather '{"location":"London"}'
echo "Exit code: $?"
Advanced Examples
Multi-Step Tool with Error Handling
tool_github_pr_summary() {
local args="$1"
local owner=$(echo "$args" | jq -r '.owner')
local repo=$(echo "$args" | jq -r '.repo')
local pr_number=$(echo "$args" | jq -r '.pr_number')
if [[ -z "$owner" ]] || [[ -z "$repo" ]] || [[ -z "$pr_number" ]]; then
echo "Missing required parameters"
return 1
fi
local pr_data=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
"https://api.github.com/repos/${owner}/${repo}/pulls/${pr_number}")
if [[ $(echo "$pr_data" | jq -r '.message // empty') == "Not Found" ]]; then
echo "Pull request not found"
return 1
fi
commits=$(curl -s -H \
)
jq -n \
--argjson \
--argjson commits \
0
}
This skill provides comprehensive guidance for building, configuring, and troubleshooting MCP servers using the Bash SDK.