Skip to main content
Glama
nuvolalabs

MCP Tool Server

by nuvolalabs
README.md
# MCP Tool Server

A **Model Context Protocol (MCP)** server implemented from scratch over stdio
using **JSON-RPC 2.0** — plus a tiny client to drive it. No SDK, no
dependencies: ~350 lines that show exactly how MCP handshakes and tool calls
work on the wire.

## Why MCP

MCP is the emerging standard for connecting LLM agents to external tools and
data. This repo demonstrates the protocol mechanics — `initialize`,
`notifications/initialized`, `tools/list`, `tools/call`, `ping` — so an agent
(Claude, Copilot, custom) can discover and invoke tools over a pipe.

## Architecture

```
  MCP client  ──JSON-RPC over stdio──▶  mcp_server
  (client.py)      newline-delimited      ├── protocol.py  (errors, envelopes)
                   {jsonrpc,id,method}    ├── server.py    (dispatch + stdio loop)
                                          └── tools.py     (echo, add, sqrt, now_utc)
```

## Quickstart

```bash
# run the demo client against a live server subprocess
PYTHONPATH=src python3 client.py

# run the server directly (speak JSON-RPC on stdin)
PYTHONPATH=src python3 -m mcp_server

# tests (in-process dispatch + a real spawned-subprocess handshake)
python3 tests/test_mcp_server.py
```

## Protocol coverage

| Method | Behaviour |
|---|---|
| `initialize` | Negotiates protocol version, returns capabilities + serverInfo |
| `notifications/initialized` | Marked initialized; **no response** (correct notification semantics) |
| `tools/list` | Returns each tool's `name`, `description`, `inputSchema` |
| `tools/call` | Runs the handler; **tool errors return in-band** (`isError: true`) |
| `ping` | Liveness check |

Malformed JSON → `-32700` parse error; unknown method → `-32601`; bad params →
`-32602`. Notifications never get a reply, as the spec requires.

## Design notes

- **Handler errors never kill the server** — they're converted to JSON-RPC errors
  or in-band tool results, so one bad call can't take the transport down.
- **Injectable streams** (`serve(lines=..., stdout=...)`) make the stdio loop
  unit-testable without spawning processes; a separate test does the real spawn.
- **Tool schemas are JSON Schema**, matching MCP's `inputSchema` contract.

## Skills demonstrated

`Model Context Protocol (MCP)` · `JSON-RPC 2.0` · `stdio transport` · `tool schemas`
· `protocol error handling` · `subprocess integration testing` · `Python packaging`

## License

MIT