
PrefectHQ/fastmcp28kSearch docs, Skills and MCP

MCP server SDK/example implemented entirely in bash — build a Model Context Protocol server with no runtime dependency beyond a POSIX shell.
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? [email protected]

PrefectHQ/fastmcp28k
modelcontextprotocol/python-sdk25k
modelcontextprotocol/typescript-sdk14k
modelcontextprotocol/csharp-sdk4.6kThe official C# SDK for Model Context Protocol servers and clients. Maintained in collaboration with Microsoft.
AI & agents

modelcontextprotocol/java-sdk3.7kThe official Java SDK for Model Context Protocol servers and clients. Maintained in collaboration with Spring AI
AI & agents

modelcontextprotocol/php-sdk1.6kThe official PHP SDK for Model Context Protocol servers and clients. Maintained in collaboration with The PHP Foundation.
AI & agents