mcp-toolkit
README.md
# mcp-toolkit
[](https://github.com/<your-username>/mcp-toolkit/actions/workflows/tests.yml)
[](https://www.python.org/)
[](LICENSE)
A hands-on exploration of the **Model Context Protocol (MCP)** — the open
protocol (created by Anthropic) that standardizes how AI applications
connect to external tools and data sources. This repo implements both
sides of the protocol: an MCP **client** and an MCP **server**, plus a
test suite and CI.
## Why this project
MCP is becoming the standard way AI assistants (Claude, and increasingly
others) discover and call external tools — think of it as a plugin
interface for LLMs. Rather than just reading about it, I built:
1. A client that connects to Anthropic's official filesystem MCP server
2. A custom MCP server exposing my own tools
3. A client that talks to that custom server
4. Unit + integration tests, and CI to run them on every push
## Architecture
```
MCP protocol (JSON-RPC over stdio)
┌──────────────┐ ┌──────────────────────────────┐
│ client.py │ <────> │ @modelcontextprotocol/ │ (official,
│ │ │ server-filesystem (npx) │ Anthropic)
└──────────────┘ └──────────────────────────────┘
┌──────────────┐ ┌──────────────────────────────┐
│client_local.py│ <────> │ server.py │ (custom,
│ │ │ - calculate(expression) │ this repo)
│ │ │ - get_weather(city) │
└──────────────┘ └──────────────────────────────┘
```
Both clients follow the same MCP lifecycle:
1. **Spawn** the server as a subprocess (stdio transport)
2. **`initialize`** — protocol handshake
3. **`list_tools`** — discover what the server can do, dynamically (no
hardcoded knowledge of the server's capabilities)
4. **`call_tool`** — invoke a tool by name with typed arguments
This is exactly what Claude Desktop does under the hood when you connect
an MCP server.
## Repo structure
```
mcp-toolkit/
├── server.py # custom MCP server (FastMCP) — calculate & get_weather
├── client.py # MCP client -> official filesystem server
├── client_local.py # MCP client -> server.py
├── sandbox_files/ # sample files used by client.py's demo
├── tests/
│ ├── test_calculate.py # unit tests: correctness + security
│ └── test_server.py # integration tests: tool registration & schemas
├── .github/workflows/tests.yml # CI: pytest on Python 3.10 & 3.12
├── requirements.txt
└── LICENSE
```
## Getting started
```bash
git clone https://github.com/<your-username>/mcp-toolkit.git
cd mcp-toolkit
pip install -r requirements.txt
```
Run the client against Anthropic's official filesystem server:
```bash
python3 client.py
```
Run the client against the custom server built in this repo:
```bash
python3 client_local.py
```
Run the test suite:
```bash
pytest -v
```
## Example output
```
$ python3 client_local.py
Connexion au serveur MCP maison (server.py)
✅ Session MCP initialisée
🔧 Outils disponibles :
- get_weather: Donne la météo actuelle (température, vent) pour une ville donnée.
- calculate: Évalue une expression arithmétique (+, -, *, /, %, **).
🧮 Appel de calculate(expression='12 * (3 + 4)') :
12 * (3 + 4) = 84
```
```
$ pytest -v
tests/test_calculate.py::TestCalculateNominal::test_addition PASSED
tests/test_calculate.py::TestCalculateNominal::test_power PASSED
tests/test_calculate.py::TestCalculateSecurity::test_rejects_function_calls PASSED
tests/test_calculate.py::TestCalculateSecurity::test_rejects_attribute_access PASSED
tests/test_server.py::test_registered_tools PASSED
...
21 passed in 0.49s
```
## Engineering notes
A few things worth calling out for anyone reviewing this code:
- **Security-conscious tool design.** `calculate` doesn't use `eval()`.
It parses the expression into a Python AST and walks a restricted
whitelist of node types (`ast.BinOp`, `ast.UnaryOp`, numeric
constants), so arbitrary code execution isn't possible even though the
input is a raw string. Covered by dedicated security tests in
`tests/test_calculate.py` (rejecting function calls, attribute access,
name lookups, etc.).
- **Default environment isolation.** The MCP SDK does *not* forward the
parent process's full environment to spawned servers by default — only
a minimal safe subset (`get_default_environment()`). Servers that need
network access (like `get_weather`, via `httpx`) need `env` passed
explicitly. This tripped up the first version of this project and is
documented here so it doesn't trip up the next person.
- **Tests don't require a live subprocess.** `tests/test_server.py`
exercises the server's tool registration and schemas directly through
the `FastMCP` instance (`mcp.list_tools()`), not by spawning a real
stdio subprocess — faster and more deterministic for CI.
## Connecting to Claude Desktop
To use `server.py` as a real MCP server inside Claude Desktop, add it to
your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"mcp-toolkit-demo": {
"command": "python3",
"args": ["/absolute/path/to/mcp-toolkit/server.py"]
}
}
}
```
Restart Claude Desktop and the `calculate` / `get_weather` tools become
available in conversation.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues