mcp-chat-cli

Model Context Protocol, visualized

Watch a message travel through MCP.

A Claude-like terminal chat that runs a free local model (Ollama qwen2.5:7b) and reaches its tools through the Model Context Protocol. This page traces a single message — forward to the model, out to an MCP server, and back — so you can see exactly how a client and server talk.

The protocol

MCP gives a model three things.

The Model Context Protocol is a standard way for an app to expose capabilities to an LLM. This CLI ships one MCP server (mcp_server.py) over an in-memory document store, and it offers all three:

Tools

Actions the model calls

The model can request a function. The app runs it and feeds the result back into the conversation.

read_documentedit_document
Resources

Data fetched by URI

The app pulls content by address — surfaced in chat as @mentions you can drop inline.

docs://documentsdocs://documents/{id}@welcome.md
Prompts

Reusable instructions

Parameterized prompts you trigger with a slash command; they expand into messages for the model.

/summarize/format

The signature — message flow

One question. Watch every hop.

You ask: “Using your tools, read equipment.pdf and tell me the recommended brew temperature.” Press Play, or step through. Amber is a request travelling outward; cyan is a result coming back. Notice the loop: the model asks for a tool, we run it, then send the grown conversation back to the model until it answers.

0 / 11

Press Play to begin

The trace follows the real call path through the codebase.

Each step shows the function that runs and the JSON message on the wire at that moment.

message on the wire
// the conversation starts empty
forward — request travelling outward return — result coming back ↻ loop back = the agentic tool-calling loop

The engine

The agent loop is the glue.

agent_loop.py repeats one cycle until the model stops asking for tools. That single rule — keep going while finish_reason == "tool_calls" — is what lets the model read a document, see the result, and only then answer.

  1. List tools from every MCP server and convert them to the model's function format.
  2. Call the model with the full conversation + the available tools.
  3. Tool call? Run it via the MCP client, append the result, and loop back to step 1.
  4. No tool call? Take the message as the final answer and break.
# agent/agent_loop.py — the cycle
while True:
    tools = tool_manager.get_all_tools()
    resp  = llm.chat(messages, tools)

    if resp.finish_reason == "tool_calls":
        messages.append(resp.message)        # assistant's call
        results = tool_manager.execute_tool_requests(resp)
        messages += results                  # tool output
        continue                             # ↻ ask again

    return resp.message.content            # final answer
# main.py — the server is a subprocess
server = StdioServerParameters(
    command="uv",
    args=["run", "mcp_server.py"],
)
# protocol rides the pipes:
#   client.stdin  ──▶ server.stdin
#   server.stdout ──▶ client.stdout

Transport

It talks over stdio.

There's no network here. The MCP server is launched as a child process, and the protocol runs over its stdin/stdout pipes. The client (mcp_client.py) writes requests in; the server writes results back out.

Local-first by construction: your conversation, your documents, and your model can all stay on your machine.

Provider-agnostic

One .env line swaps the brain.

Every model call goes through litellm, which routes by the model-string prefix. Default is local and free; cloud fallbacks are one edit away.

ProviderLLM_PROVIDERLLM_MODEL
Ollamaollamaollama_chat/qwen2.5:7blocal · default · $0
Groqgroqgroq/openai/gpt-oss-120bcloud fallback
GitHub Modelsgithubopenai/gpt-4o-minicloud fallback

Proof

See it for real.

A full session on the local model — @-mentions, /summarize, /format, a persisted edit_document (Monday → Tuesday), and a clear “not found” error.

A terminal session of the MCP Chat CLI showing @-mentions, the /summarize and /format commands, a document edit, and an error message.
live-demo/cli-session.png — local qwen2.5:7b
Exploring the server with the MCP Inspector — uv run mcp dev mcp_server.py
MCP Inspector launch screen
Inspector launch
list_docs resource returning document ids
Resource · list_docs
fetch_doc template returning one document's content
Resource · fetch_doc template
format prompt example in the MCP Inspector
Prompt · format
read_document tool in the MCP Inspector
Tool · read_document
edit_document tool in the MCP Inspector
Tool · edit_document

Run it locally

Four commands.

Pull the model

ollama pull qwen2.5:7b

Configure your environment

cp .env.example .env   # defaults to local Ollama; keys stay on your machine

Install dependencies

uv sync

Run the CLI

uv run main.py

Needs Python 3.10+, uv, and Ollama running. Use a real terminal — the CLI uses prompt_toolkit and needs an interactive console.