CLI tool
muthuishere/mcp-server-bash-sdk avatar
muthuishere/mcp-server-bash-sdk

mcp-server-bash-sdk: an MCP server written in pure Bash, with no runtime beyond jq

MCP server SDK/example implemented entirely in bash — build a Model Context Protocol server with no runtime dependency beyond a POSIX shell.

514 stars48 forksShellMIT

At a glance

What is it?
The Model Context Protocol 2026-07-28 revision made MCP stateless, and this SDK leans on that: a read loop, jq for JSON, and the same dispatch function behind both stdio and Streamable HTTP. It is a good fit for shell-native tooling and a poor fit for anything that needs sessions.
Who is it for?
Adopt it if your tools are already shell commands, your host can spawn a process, and jq is acceptable as the only dependency; the naming convention for tool_ functions and the JSON files under assets/ are the whole contract you have to learn. Do not adopt it if you need server-initiated requests, sessions, or a Python or TypeScript SDK's type checking, because the 2026-07-28 revision this targets removed those anyway.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: MCP servers that are mostly runtime

The README states the motivation plainly: most MCP servers are API wrappers with schema conversion, and the project positions itself as a zero-overhead alternative to Node.js, Python, or other heavy runtimes. That framing is the whole pitch. If your tool already exists as a shell command, wrapping it in a Node process means shipping a package manager, a lockfile, and a container image to do what a five-line function could do.

The audience is narrow and specific. It is engineers who already automate in shell, who run agents on machines where installing a runtime is friction (a router, a CI runner, an Alpine container), and who want the server to be a file they can read end to end. The README's own platform table lists macOS with bash 3.2, Debian/Ubuntu with bash 5.2, and Alpine with musl and busybox, which tells you the target is not a developer laptop but whatever machine the agent happens to run on.

The design bet is stated in the README: the 2026-07-28 revision made MCP stateless, with no initialize handshake, no sessions, and no server-initiated requests. One line in, one line out. That is a read loop, and a read loop is what shell does well. Without that revision, this project would be fighting the protocol.

How dispatch works: transport, core, and tool_ functions

The architecture diagram in the README splits the server into three layers, and the split is the interesting part. The transport layer is either run_mcp_server for stdio or mcpserver_http.sh when the server is started with --http. Below it sits mcpserver_core.sh, which handles JSON-RPC framing, MCP dispatch, version negotiation, and result envelopes through a function called process_request. Below that sits your business logic, which is a set of functions named tool_*.

The README is explicit that both transports call the same process_request, so they cannot drift in protocol behaviour. That is a real constraint, not a slogan: it means an HTTP bug and a stdio bug are the same bug, and the HTTP test suite is checking the binding rather than a second implementation of the protocol.

Tool discovery follows a naming convention. Every function prefixed with tool_ is dispatched by name, and the tools JSON controls what clients are told exists. Each tool function takes a single parameter, $1, containing the arguments as JSON. Success means echoing the result and returning 0. Failure means echoing an explanatory message and returning 1, and the README notes that the caller then receives a successful response with isError set to true, so the model can read the reason and retry. Tool failures are not transport errors. That decision is worth pausing on: it means a broken tool never kills the connection, but it also means a client that only checks the transport status will see a healthy server returning nothing useful.

Version negotiation is per request rather than per connection. Every request carries its protocol version in params._meta, which is why the README's examples include an io.modelcontextprotocol/protocolVersion field on each call rather than a one-time handshake.

Installing it and calling server/discover from a shell

The README gives a four-step quick start. Clone the repository, make the two scripts executable, then pipe a request into the movie server. There is no build step, no package install, and no generated code.

The first block clones the repository and marks the core and the example server as executable. The README names both files explicitly, so there is no ambiguity about which ones need the bit set.

bash
git clone https://github.com/muthuishere/mcp-server-bash-sdk
cd mcp-server-bash-sdk
chmod +x mcpserver_core.sh moviemcpserver.sh

The second block is the first real use. It sends a server/discover request over stdio, with the protocol version and client capabilities nested under params._meta. According to the README, every request carries its version this way, so this is the shape of every call you will make, not just discovery.

bash
echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' | ./moviemcpserver.sh

The third block calls a tool. The name get_movies comes from the movie example, and the arguments object is empty. If you are building your own server, the equivalent call would use whatever name your tools JSON advertises and whatever function you wrote with the tool_ prefix.

bash
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

To run the same file as a local HTTP endpoint instead, the README gives a single flag and states the address it binds: http://127.0.0.1:3000/mcp. That is the Streamable HTTP transport, and per the architecture section it reaches the same process_request as the stdio path.

bash
./moviemcpserver.sh --http

Before writing your own tools, run the shipped tests. The README lists a 31-test unit suite, a conformance test that validates against the official published JSON Schema vendored under spec/, and a 19-test HTTP transport suite. It also documents ./scripts/test-linux.sh all, which reproduces the Linux rows of the platform table on any machine with Docker. Docker is needed only to verify other platforms, never to run a server.

Where the shell choice costs you

The dependency list is short, and each entry is a real constraint. Bash 3.2 or newer is required, and the README notes that 3.2 is what macOS ships as /bin/bash, which is why the platform table tests both 3.2 and 5.x. jq is required for JSON processing, installed via brew install jq, apt install jq, or apk add jq. For the HTTP transport, socat is preferred, with netcat as the fallback, and the README points to a note under Transports for the details. Python with jsonschema is optional and only needed to run the schema conformance test.

That jq requirement is the honest boundary of the "no runtime dependency" claim. The README's own phrasing is "no runtime dependency beyond a POSIX shell," and jq is not part of a POSIX shell. On a minimal container you are still installing a binary. It is a smaller binary than Node, and it is one package instead of a toolchain, but it is not zero.

The bigger limitation is the protocol revision. Because the project targets 2026-07-28, it has no initialize handshake, no sessions, and no server-initiated requests. If you have an MCP host that still expects a session-based handshake, this server is the wrong tool, and no amount of shell cleverness fixes it. The README treats this as the reason the project exists rather than a gap, and that reading is fair, but it does mean your host has to be on the same revision.

Error semantics are the third sharp edge. A tool that returns 1 produces a successful transport response with isError set to true. That is deliberate, and the README explains the reasoning (the model reads the reason and retries). It also means a monitoring setup that watches exit codes or HTTP status will not notice a tool that fails on every call. You have to inspect the response body.

Compared with a Python or TypeScript MCP SDK

The obvious alternative is an MCP SDK in a general-purpose language, and the difference is not speed, it is where the work happens. A Python or TypeScript SDK gives you typed request and response objects, a schema definition layer that generates the tool descriptions, and an async runtime that can hold state across calls. This project gives you a naming convention, a JSON file describing your tools, and a function that takes $1 as a JSON string. Everything a typed SDK would check at compile time, you check by running ./test_mcpserver_core.sh.

That trade is reasonable when your tool is already a shell command and unreasonable when it is not. If your tool needs an HTTP client with retries, a database driver, or a schema that changes often, you are writing that logic in Bash and debugging it with echo. The SDK's own examples directory is the honest signal here: the README lists four runnable servers and a build-your-own walkthrough, with examples/fileserver.sh, examples/gitserver.sh, and examples/weatherserver.sh alongside the movie server. Those are all cases where the underlying operation is a command or a simple fetch.

The second alternative is a hosted MCP server, where you do not run a process at all. The trade there is the opposite: no dependency management, but your tool calls leave your machine and the server operator sees the arguments. This project's whole value proposition is that the server is a file you own and can read, which is the case a hosted service cannot make.

Maintenance, versioning, and the MIT licence

The repository is not archived, and the last push was on 2026-09-09. The most recent release listed is v0.4.1 on 2026-08-04, described as "MCP 2026-07-28, both transports, verified on Linux." The version number is worth reading carefully: this is a 0.x project, so the interface between your tool_ functions and the core can change between releases. The repository carries a VERSIONS.md file and a docs/adr/ directory of architecture decisions, which is where you would look before upgrading.

The upgrade cost is mostly the protocol revision. Because version negotiation is per request and the server validates against a vendored schema under spec/, moving to a newer MCP revision means updating that vendored schema and the conformance test together. The README's test commands are the upgrade checklist: ./test_mcpserver_core.sh for the unit suite, ./test_conformance.sh for the schema, and ./test_http_transport.sh for the HTTP binding. If all three pass after a pull, your tool_ functions almost certainly still work, because the convention between them and the core is just the name prefix and the $1 argument.

The licence is MIT, which permits commercial use and modification provided the copyright notice and permission notice are retained. That is a permissive licence with minimal obligations, but it is not legal advice and the LICENSE file in the repository is the authoritative text. If you vendor the core into a proprietary product, the notice requirement is the part to get right.

Editorial conclusion

Adopt it if your tools are already shell commands, your host can spawn a process, and jq is acceptable as the only dependency; the naming convention for tool_ functions and the JSON files under assets/ are the whole contract you have to learn. Do not adopt it if you need server-initiated requests, sessions, or a Python or TypeScript SDK's type checking, because the 2026-07-28 revision this targets removed those anyway. Before committing, run ./test_mcpserver_core.sh and ./test_conformance.sh on your own machine and confirm the server/discover response carries the protocol version your client sends.

Frequently asked questions

What is an MCP SDK?

An SDK for the Model Context Protocol provides the JSON-RPC framing, method dispatch, and result envelopes a server needs, so you only write the tool logic. In this project that layer is mcpserver_core.sh, which handles framing, dispatch, version negotiation, and result envelopes through process_request.

What do I use an MCP server for?

It exposes tools to an MCP host so a model can call them. This repository ships a movie server as its example, where a tools/call request with the name get_movies returns the result, and the examples directory adds fileserver.sh, gitserver.sh, and weatherserver.sh.

What is a hosted MCP server, and is mcp-server-bash-sdk one?

A hosted MCP server runs on someone else's infrastructure; this project is the opposite, a server file you clone and run yourself, either over stdio or as a local HTTP endpoint at http://127.0.0.1:3000/mcp when started with --http.

Official sources

  1. License: MIT
  2. muthuishere/mcp-server-bash-sdk on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/muthuishere-mcp-server-bash-sdk.svg)](https://hysenlabs.com/projects/muthuishere-mcp-server-bash-sdk)