mcp-shell-server-example
# mcp-shell-server-example
[](https://x.com/mcoding_off)
[](https://whatsapp.com/channel/0029Vb7WRtT11ulGgJPp4m3y)
[](LICENSE)
A small, educational [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server built with the [official Python SDK](https://github.com/modelcontextprotocol/python-sdk).
The goal of this repo is **not** to be a production tool — it's a minimal, readable reference for people learning MCP: how a server exposes **tools** and **resources**, how a client (like an AI agent) discovers and calls them, and how to run/test the whole thing locally or in Docker.
## What is MCP?
The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard that lets AI applications (like Claude) connect to external systems in a consistent way. An MCP **server** exposes capabilities — mainly:
- **Tools** — functions the AI can call (e.g. "run this shell command", "fetch this URL")
- **Resources** — data the AI can read (e.g. a file's contents)
An MCP **client** (built into an AI app, or a debugging tool like MCP Inspector) connects to the server, discovers what it offers, and calls it on the AI's behalf. Communication happens over a transport — this server uses **stdio** (standard input/output), the simplest option: the client launches the server as a subprocess and talks to it over its stdin/stdout using JSON-RPC.
## Architecture
```mermaid
flowchart LR
subgraph Client
A[MCP Client<br/>Claude Code / MCP Inspector]
end
subgraph Server["mcp-shell-server-example (stdio)"]
B[FastMCP server.py]
T1[terminal<br/>PowerShell]
T2[terminal_linux<br/>sh]
T3[benign_tool<br/>curl fetch]
R1[mcpreadme<br/>resource]
end
A <-- "JSON-RPC over stdio" --> B
B --> T1
B --> T2
B --> T3
B --> R1
T1 -.-> H[(Host OS)]
T2 -.-> C[(Container OS)]
T3 -.-> G[(Remote gist)]
```
The client launches `server.py` (directly with `uv`, or inside a Docker container) as a child process and exchanges MCP messages with it over stdio — no network port required.
## What this server exposes
| Name | Type | Description |
|---|---|---|
| `terminal` | Tool | Runs a command via **PowerShell** on the host. Meant for local/Windows use. |
| `terminal_linux` | Tool | Runs a command via **`/bin/sh`**. Meant for use inside the Docker container. |
| `benign_tool` | Tool | Downloads content from a fixed URL with `curl` and returns it. Included as a **deliberate demo** of a real MCP risk: a tool with an innocuous name that pulls in remote, untrusted content — a pattern known as *tool poisoning* / indirect prompt injection. Useful for learning to think critically about what MCP tools actually do, not just what they're named. |
| `mcpreadme` | Resource | Returns the contents of [`mcpreadme.md`](mcpreadme.md). |
> ⚠️ **`terminal` / `terminal_linux` run arbitrary shell commands with no sandboxing or allowlist.** That's intentional for a learning project, but treat this as a local playground, not something to expose to untrusted clients or the network.
## Prerequisites
- Python 3.12+
- [uv](https://docs.astral.sh/uv/) — used to manage the virtual environment and run the server
- (Optional) [Docker](https://www.docker.com/) — to run the server in a container
- (Optional) [Node.js](https://nodejs.org/) — needed to run [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) via `npx`
## Running locally
```bash
git clone https://github.com/mohamedelamraoui1/mcp-shell-server-example.git
cd mcp-shell-server-example
uv sync
uv run server.py
```
The server then waits on stdio for an MCP client to connect — this is normal, it won't print anything and won't respond to plain typed text (it only understands JSON-RPC).
## Running in Docker
```bash
docker build -t shell-server-app .
docker run -i --rm shell-server-app
```
Inside the container, use `terminal_linux` instead of `terminal` — PowerShell isn't installed in the (Debian-based) image.
## Testing with MCP Inspector
[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) is a web UI for manually calling a server's tools/resources without needing a full AI client.
**Against the local server:**
```bash
uv run mcp dev server.py
```
**Against the Docker image:**
```bash
npx @modelcontextprotocol/inspector docker run -i --rm shell-server-app
```
Either command prints a local URL — open it, click **Connect**, then use the **Tools** tab to call `terminal` / `terminal_linux` / `benign_tool`, or the **Resources** tab to read `mcpreadme`.
Example: get "HELLO MCP" printed back via the `terminal` tool by passing this as the `command` argument:
```powershell
echo " _ _ _ _ __ __ ____ ____ "
echo "| | | | ___| | | ___ | \/ |/ ___| _ \ "
echo "| |_| |/ _ \ | |/ _ \ | |\/| | | | |_) |"
echo "| _ | __/ | | (_) || | | | |___| __/ "
echo "|_| |_|\___|_|_|\___/ |_| |_|\____|_| "
```
## Connecting it to Claude Code (real-world usage)
This server was built and tested end-to-end using [Claude Code](https://claude.com/claude-code) as the MCP client — including scaffolding the server itself, containerizing it, and then wiring it back in as a live tool Claude Code could call. That's a good demonstration of the MCP loop in practice: an AI agent using a protocol-standard interface to run real commands and fetch real data, instead of a one-off custom integration.
To connect it yourself, add a `.mcp.json` file at your project root (Claude Code loads this automatically and asks you to approve it on startup):
```json
{
"mcpServers": {
"m-shell-server": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/mcp-shell-server-example", "server.py"]
}
}
}
```
Restart Claude Code in that directory, approve the server when prompted, then check it's connected with `/mcp`. From then on, Claude Code can call `terminal`, `terminal_linux`, `benign_tool`, and read the `mcpreadme` resource directly as part of answering your prompts.
## Project structure
```
.
├── server.py # the MCP server: tools + resource definitions
├── mcpreadme.md # content exposed via the mcpreadme resource
├── pyproject.toml # project metadata + dependencies (managed by uv)
├── uv.lock # locked dependency versions
├── Dockerfile # container build, following uv's official Docker guide
├── .dockerignore
└── .mcp.json # example Claude Code project-scoped MCP config
```
## References
- [Model Context Protocol](https://modelcontextprotocol.io/)
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- [uv Docker integration guide](https://docs.astral.sh/uv/guides/integration/docker/#installing-uv)
TDQS
Scored across 3 tools
Tools have distinct functions: benign_tool downloads via curl, terminal runs local commands, terminal_linux runs containerized commands. However, the two terminal tools share the same core purpose (shell execution) and could be confused without careful reading.
Names use snake_case, but 'benign_tool' does not follow a verb_noun pattern like the others. 'terminal' and 'terminal_linux' are similar but 'terminal' is generic, breaking consistency.
Three tools is appropriate for a shell server focused on command execution and file retrieval. No unnecessary bloat or insufficiency.
Covers core shell operations with local and containerized execution, plus URL downloads. Minor gap: no file management or process control tools, but essential workflows are supported.