Skip to main content
Glama
VictorC55

MCP Demo

by VictorC55
README.md
# MCP Demo

A learning project for the **Model Context Protocol (MCP)**. It has one MCP
**server** that exposes a few example tools, and two **clients** that let a
language model discover and call those tools — one driven by a **local model via
LM Studio** (free, offline) and one driven by **Claude** (Anthropic API).

The point of the project is to show that **the server is model-agnostic**: the
same `server.py` works with any model — only the client changes.

## How it fits together

```
  You type a question
          │
          ▼
   client (drives the LLM)  ──►  LLM decides to call a tool
          │                              │
          │  spawns over stdio           │
          ▼                              ▼
      server.py  ◄───────────  client executes the tool call
   (add1, multiply2, greet)     and feeds the result back
```

- **`server.py`** — the MCP server (built with FastMCP). Defines the tools.
- **`client_lmstudio.py`** — chat loop that drives a **local** model (LM Studio).
- **`client_claude.py`** — same idea, driven by **Claude** (needs an API key).

The client launches `server.py` as a subprocess and talks to it over **stdio**
(standard in/out). It lists the server's tools, hands them to the model, and runs
the loop: model asks to call a tool → client runs it against the server → result
goes back to the model → repeat until the model gives a final answer.

## Tools exposed by the server

| Tool | What it does |
|------|--------------|
| `add1(a, b)` | Returns `a + b + 1` (the extra `+1` proves the tool actually ran) |
| `multiply2(a, b)` | Returns `a * b * 2` |
| `greet(name, excited=False)` | Returns a greeting; `excited=True` adds enthusiasm |

There's also a `config://version` resource returning the app version.

> These are placeholder tools. To make the project do something real, replace
> the bodies with actual logic (a DB query, an API call, a computation) — the
> wiring stays exactly the same.

## Setup

Requires Python 3.10+.

```bash
python3 -m venv .venv
source .venv/bin/activate

pip install -r requirements.txt
```

That installs everything: `mcp[cli]` (the server + Inspector), `openai` (the LM
Studio client), and `anthropic[mcp]` (the Claude client — only needed if you use
that one).

## Running with a local model (LM Studio) — free, no API key

This is the recommended way to try it — it runs entirely on your machine.

1. **Install and open [LM Studio](https://lmstudio.ai).**
2. **Download a model that supports tool/function calling.** This project uses
   **`qwen2.5-7b-instruct-1m`**, which handles tools well. Other good picks:
   Qwen2.5 Instruct, Llama 3.1/3.2 Instruct. Small or non-instruct models often
   call tools poorly.
3. **Start the local server:** LM Studio → **Developer / Local Server** tab →
   load the model → **Start Server**. It listens on `http://localhost:1234/v1`
   (an OpenAI-compatible endpoint).
4. **Check the model id** shown in LM Studio matches the `MODEL` constant near
   the top of `client_lmstudio.py`; update it if you loaded a different model.
5. **Run the client:**

   ```bash
   .venv/bin/python client_lmstudio.py
   ```

6. **Chat.** Type a question at the `You:` prompt, e.g.
   *"Greet Alice excitedly, then add 5 and 7."* Type `exit` or `quit` to leave.

If the "5 + 7" answer comes back as **13** (not 12), that confirms the model
really called your `add1` tool instead of doing its own arithmetic.

**Notes on local models:** they follow instructions less reliably than cloud
models — responses may be verbose or occasionally skip a tool. Verbosity is tuned
via the system prompt at the top of the client's `messages` list. Swapping to a
stronger model in LM Studio also helps.

## Running with Claude (Anthropic API) — costs a small amount

1. Get an API key at [console.anthropic.com](https://console.anthropic.com)
   (Settings → API Keys).
2. Export it:

   ```bash
   export ANTHROPIC_API_KEY=sk-ant-...
   ```

3. Run:

   ```bash
   .venv/bin/python client_claude.py
   ```

Same behavior, but Claude follows instructions and calls tools more reliably.
Costs are tiny (a fraction of a cent for this demo).

## Inspecting the server directly (no LLM)

To poke the tools by hand in a browser UI:

```bash
.venv/bin/mcp dev server.py
```

This opens the **MCP Inspector**, where you can call each tool manually. (If it
errors trying to spawn `uv`, either install `uv`, or in the Inspector set
**Command** to your venv's python and **Arguments** to `server.py`.)

## Things worth remembering

- **The client and server transport must match.** These clients spawn the server
  and talk over **stdio**, so `server.py` must end with `mcp.run()`. If you switch
  it to `mcp.run(transport="sse")` (an HTTP server), the stdio client breaks —
  it'll try to parse the web-server's log lines as protocol messages.
- **In stdio mode, don't `print()` inside tools** — stdout *is* the protocol
  channel. Use `print(..., file=sys.stderr)` for debugging.
- **The server never changes when you switch models.** MCP standardizes the tool
  server; each model provider just needs its own client (or a framework that
  abstracts them).

## Possible next steps

- Replace the placeholder tools with real functionality.
- Add a graphical UI (Streamlit / Gradio / Chainlit for a quick chat box, or
  FastAPI + a web frontend for a real webapp).
- Serve the tools over HTTP (`transport="streamable-http"`) so they can be
  reached remotely instead of only as a local subprocess.