Origin: This repository is based on the starter project provided as part of the Anthropic MCP Course on Skilljar. The original project was a skeleton implementation intended as a course exercise. I downloaded it, completed the TODOs, extended the architecture to support multiple LLM providers (Google Gemini and Perplexity in addition to Anthropic), and used it as a personal learning sandbox for exploring the Model Context Protocol.
MCP Chat is a command-line interface application that enables interactive conversation
with AI language models through the
Model Context Protocol (MCP). The application
supports document retrieval via @mentions, slash-command-driven prompts, tab
completion, and extensible tool integrations — all backed by a pluggable LLM provider
layer.
The provider is selected via a single environment variable. All three providers share the same code interface, so switching between them requires no code changes.
| Provider | Recommended Model | Free Tier | API Key |
|---|---|---|---|
| Anthropic | claude-haiku-4-5 |
No — paid credits required | console.anthropic.com |
| Google Gemini | gemini-2.0-flash |
Yes — via AI Studio | aistudio.google.com |
| Perplexity | sonar |
Yes — $5 trial credit | perplexity.ai/api |
- Python 3.10 or higher
- An API key for at least one of the supported providers above
Copy .env.example to .env and fill in your values:
cp .env.example .envOpen .env and set LLM_PROVIDER to your chosen provider, then provide the
corresponding model name and API key:
# Choose your provider: anthropic | gemini | perplexity
LLM_PROVIDER="perplexity"
# Anthropic
CLAUDE_MODEL="claude-haiku-4-5"
ANTHROPIC_API_KEY=""
# Google Gemini
GEMINI_MODEL="gemini-2.0-flash"
GEMINI_API_KEY=""
# Perplexity
PERPLEXITY_MODEL="sonar"
PERPLEXITY_API_KEY=""Only the key and model for the selected provider need to be filled in.
Update the single LLM_PROVIDER line in .env:
LLM_PROVIDER="anthropic" # Use Anthropic Claude
LLM_PROVIDER="gemini" # Use Google Gemini
LLM_PROVIDER="perplexity" # Use Perplexityuv is a fast Python package manager.
# Install uv if not already installed
pip install uv
# Create virtual environment and install dependencies
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv pip install -e .
# Run
uv run main.pypython -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
python main.pyType any message and press Enter:
> What is the boiling point of water?
Prefix a document ID with @ to include its content in your query:
> Summarize @deposition.md
> Compare @report.pdf and @outlook.pdf
Document IDs auto-complete when you type @.
Use / to invoke an MCP prompt. Available commands auto-complete on Tab:
> /summarize deposition.md
> /rewrite_as_markdown report.pdf
| Key | Action |
|---|---|
Tab |
Open completion menu |
/ |
Open command completion |
@ |
Open document completion |
Ctrl+C |
Exit |
.
├── main.py # Entry point — provider selection and app bootstrap
├── mcp_server.py # MCP server exposing docs as tools, resources, and prompts
├── mcp_client.py # MCP client wrapper (async, stdio transport)
├── core/
│ ├── chat.py # Base agentic loop (tool-use handling)
│ ├── cli_chat.py # CLI-aware chat: document retrieval and command handling
│ ├── cli.py # Interactive REPL (prompt_toolkit, completions, key bindings)
│ ├── tools.py # MCP tool aggregation and dispatch
│ ├── claude.py # Anthropic provider
│ ├── gemini.py # Google Gemini provider
│ └── perplexity.py # Perplexity provider
├── .env.example # Environment variable template
└── pyproject.toml # Project metadata and dependencies
Building MCP server and client implementations revealed the protocol's power as a bridge between applications and LLMs. Key insights:
- Resource abstraction — exposing documents as MCP resources enables structured LLM access without embedding data in prompts
- Tool composition — organizing LLM-invocable functions as MCP tools scales better than hardcoded function calls
- Protocol simplicity — the stdio transport and JSON-RPC foundation make MCP straightforward to reason about
Implementing three distinct LLM providers (Anthropic, Gemini, Perplexity) exposed both commonalities and critical differences:
- Unified interfaces — abstracting chat, message formatting, and response parsing into provider-agnostic methods reduces coupling
- API divergence — each provider has different tool schemas, error handling, and streaming semantics; a thin adapter layer handles these elegantly
- Cost vs. capability tradeoffs — free-tier availability (Gemini, Perplexity) vs. enterprise features (Anthropic) shapes real-world deployment decisions
This project relies heavily on Python's async ecosystem for concurrent I/O:
- AsyncExitStack — managing multiple async context managers (MCP clients) in a single cleanup operation prevents resource leaks
- Async generators & streaming — handling streamed responses from LLM APIs without blocking the CLI requires careful async/await patterns
- Event loop lifecycle — Windows requires explicit event loop policy configuration; macOS/Linux default behavior can hide platform-specific issues
Building an interactive REPL with tab completion and keybindings taught practical UX patterns:
- Completion strategies — lazy-loading completions (documents, commands) keeps the CLI responsive even with many resources
- Prompt state — maintaining context across turns (MCP connections, chat history) while remaining reactive to user input
- Keyboard UX — well-chosen shortcuts (Tab for completions, Ctrl+C for exit) significantly improve usability without documentation
Edit the docs dictionary in mcp_server.py:
docs = {
"my_document.md": "Document content goes here.",
...
}- Create
core/<provider>.pyimplementing four methods:chat(messages, system, tools, ...)— send a request, return a response with.stop_reasonadd_user_message(messages, message)— append a user turnadd_assistant_message(messages, message)— append an assistant turntext_from_message(message)— extract text from a response object
- Import and wire it into
build_llm_service()inmain.py - Add the provider SDK to
pyproject.toml
The MCP server (mcp_server.py) can be extended with additional:
- Tools — functions the LLM can invoke (decorated with
@mcp.tool()) - Resources — data the client can read (decorated with
@mcp.resource(uri)) - Prompts — reusable prompt templates (decorated with
@mcp.prompt())
Additional MCP servers can be connected by passing their scripts as CLI arguments:
uv run main.py path/to/another_server.py