Discover MCPs & agents
Loading MCPs and agents…
Loading MCPs and agents…
MCP server SDK/example implemented entirely in bash — build a Model Context Protocol server with no runtime dependency beyond a POSIX shell.
Fetched from the repository for this visit.
A lightweight, zero-overhead implementation of the Model Context Protocol (MCP) server in pure Bash — targeting the current 2026-07-28 revision.
📖 Documentation site · 🧩 Examples · 🧠 Architecture decisions · 🔬 Spikes
Why? Most MCP servers are just API wrappers with schema conversion. This implementation provides a zero-overhead alternative to Node.js, Python, or other heavy runtimes.
Why now? The 2026-07-28 revision made MCP stateless: no initialize handshake, no sessions, no server-initiated requests. One line in, one line out — which is exactly the shape of a shell read loop. Bash went from an awkward fit to a natural one.
server/discover, tools/list, tools/call/bin/bash)jq for JSON processing (brew install jq / apt install jq / apk add jq)socat preferred, or netcat — see the note under Transportsjsonschema, only to run the schema conformance testVerified by running the real test suites on each, not by inspection —
./scripts/test-linux.sh all reproduces the Linux rows on any machine with Docker.
| Platform | Shell | Unit | HTTP |
|---|---|---|---|
| macOS | bash 3.2 (/bin/bash) and 5.x | 31/31 | 19/19 |
| Debian / Ubuntu | bash 5.2 | 31/31 | 19/19 |
| Alpine (musl + busybox) | bash 5.3 | 31/31 | 19/19 |
Docker is needed only to verify other platforms — never to run a server.
git clone https://github.com/muthuishere/mcp-server-bash-sdk
cd mcp-server-bash-sdk
chmod +x mcpserver_core.sh moviemcpserver.sh
params._meta:echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' | ./moviemcpserver.sh
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_movies","arguments":{},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' | ./moviemcpserver.sh
./moviemcpserver.sh --http # http://127.0.0.1:3000/mcp
./test_mcpserver_core.sh # 31 unit tests
./test_mcpserver_core.sh test_discover_shape # a single test
./test_conformance.sh # validate against the official schema
./test_http_transport.sh # 19 HTTP transport tests
./scripts/test-linux.sh all # run everything on Debian, Alpine and Ubuntu
┌─────────────┐ stdio ┌──────────────────────────────────────┐
│ MCP Host │◄─────────►│ Your server (moviemcpserver.sh) │
│ (AI System) │ │ │
│ │ HTTP │ ┌────────────────────────────────┐ │
│ │◄─────────►│ │ Transport │ │
└─────────────┘ │ │ run_mcp_server (stdio) │ │
│ │ mcpserver_http.sh (--http) │ │
│ └───────────────┬────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Protocol mcpserver_core.sh │ │
│ │ process_request() │ │
│ └───────────────┬────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Business logic tool_* funcs │ │
│ └───────────────┬────────────────┘ │
└──────────────────┼──────────────────-┘
▼
┌──────────────────────────────┐
│ Config JSON · external APIs │
└──────────────────────────────┘
Both transports call the same process_request, so they cannot drift in protocol behaviour.
Origin, listenertool_* functionstool_, matching the name in your tools JSON$1 containing the arguments as JSONreturn 0return 1 — the caller receives a successful response with isError: true, so the model can read the reason and retry. Tool failures are not transport errors.weatherserver.sh)#!/bin/bash
# Weather API implementation
# Override configuration paths BEFORE sourcing the core
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="${MCP_API_KEY:-default_key}"
# Tool: Get current weather for a location
tool_get_weather() {
local args="$1"
local location=$(echo "$args" | jq -r '.location')
if [[ -z "$location" || "$location" == "null" ]]; then
echo "Missing required parameter: location" # reaches the model as isError
return 1
fi
curl -s "https://api.example.com/weather?location=$location&apikey=$API_KEY"
return 0
}
# stdio by default; --http serves the same tools over Streamable HTTP.
case "${1:-}" in
--http) shift; run_mcp_http_server "$@" ;;
handle-connection) run_mcp_http_server handle-connection ;;
*) run_mcp_server "$@" ;;
esac
For the --http mode also source "$(dirname "${BASH_SOURCE[0]}")/mcpserver_http.sh" next to the core.
Four runnable examples are in examples/, each covering a different problem:
| Example | What it shows |
|---|---|
gitserver.sh | Shelling out safely — argument validation, structured output |
weatherserver.sh | Wrapping a third-party API — secrets in env, network failures, trimming the response |
fileserver.sh | Saying no — read-only filesystem access with a real path-traversal boundary |
moviemcpserver.sh | The minimum, over canned data |
assets/weatherserver_tools.json{
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or coordinates"
}
},
"required": ["location"]
}
}
]
}
Keep the array order stable — the spec asks servers to return tools deterministically so clients (and LLM prompt caches) can cache them.
assets/weatherserver_config.json — this is now the server/discover body, not an initialize result:{
"supportedVersions": ["2026-07-28"],
"serverInfo": {
"name": "WeatherServer",
"version": "1.0.0"
},
"capabilities": {
"tools": {
"listChanged": false
}
},
"ttlMs": 3600000,
"cacheScope": "public",
"instructions": "This server provides weather information."
}
chmod +x weatherserver.sh
Both are the spec's standard bindings, and both run from the same server file.
What an editor or agent launches as a subprocess. This is the default and the one to use unless you specifically need HTTP.
./moviemcpserver.sh
2026-07-28 removed sessions, the GET stream and SSE resumability, so this binding is now
just POST a message, get JSON back — which is why it fits in a shell script at all.
./moviemcpserver.sh --http # http://127.0.0.1:3000/mcp
MCP_HTTP_PORT=8080 ./moviemcpserver.sh --http
| Variable | Default | Purpose |
|---|---|---|
MCP_HTTP_PORT | 3000 | listening port |
MCP_HTTP_BIND | 127.0.0.1 | interface — leave it on loopback |
MCP_HTTP_PATH | /mcp | endpoint path |
MCP_ALLOWED_ORIGINS | http://localhost,http://127.0.0.1 | Origin allowlist; anything else gets 403 |
Every POST must carry MCP-Protocol-Version and Mcp-Method, plus Mcp-Name for
tools/call / resources/read / prompts/get, and each must match the request body —
a mismatch is 400 with -32020. That is a security control, not ceremony: an
intermediary may route on the header while the server executes the body.
⚠️ This is a local endpoint, not a web server. It binds loopback, has no TLS and no auth. Install
socat(brew install socat) and it forks per connection; without it thenetcatfallback serves one connection at a time, with a brief window between connections where the port is refused. To expose it beyond localhost, put a real reverse proxy in front. See ADR-0006 and spike 02.
"mcp": {
"servers": {
"my-weather-server": {
"type": "stdio",
"command": "/path/to/your/weatherserver.sh",
"args": [],
"env": {
"MCP_API_KEY": "your-api-key"
}
}
}
}
⚠️ The client must speak MCP 2026-07-28. This SDK implements that revision only and rejects older ones with -32022 (ADR-0002 explains the trade-off). Editors still on 2025-06-18 / 2025-11-25 will need a shim until they upgrade.
socat it serves one connection at a timenotifications/progress streaming and no subscriptions/listen2026-07-28 are rejectedFor AI assistants and local tool execution, these aren't blocking issues.
This SDK is documented as much by why as by how:
read -N missing from macOS's bash 3.2, and Linux
capping a single argv entry at 128 KB where macOS does not.This project is licensed under the MIT License - see the LICENSE file for details.
Blog : https://medium.com/@muthuishere/why-i-built-an-mcp-server-sdk-in-shell-yes-bash-6f2192072279
Questions, ideas, or built something with this? Join AgentNexus — a Discord for people building with AI agents and open tools. This project lives in #mcp-bash-sdk.
Built and maintained by deemwar — we build messaging and browser automation, and the plumbing that keeps it delivering when it silently stops.
Need help with this, or something like it in production? io@deemwar.com
Loading reviews...
GitHub checked 46h ago - refresh overdue. Recently viewed repositories target hourly refresh.
Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). Connect directly and complete any provider authentication separately; this is not platform-key access.
Tool
OS
Config file: ~/.cursor/mcp.json
{
"mcpServers": {
"mcp-server": {
"url": "{MCP_ENDPOINT_URL}"
}
}
}Paste into mcpServers in the config file. Restart Cursor after saving.
If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.