A local Python MCP (Model Context Protocol) server that exposes 63 utility tools to any LLM — plus a standalone CLI agent (chat.py) built on the Anthropic SDK that demonstrates the full agent loop end-to-end.
Built to learn the MCP protocol from the ground up: tool registration, JSON-RPC over stdio, agentic tool-use loops, and the brain/hands separation that makes any tool reusable across any MCP-aware host (Claude Code, Claude Desktop, Cursor, custom clients).
┌──────────────────────────┐
You ── question ──▶ │ chat.py (orchestrator)│
│ - sends msg to LLM │
│ - dispatches tool calls│
│ - feeds results back │
│ - loops until end_turn │
└────┬───────────────┬─────┘
│ │
Anthropic API│ │ stdio + JSON-RPC
▼ ▼
┌───────────────┐ ┌────────────────────┐
│ Claude (LLM) │ │ mcp_toolbox server │
│ picks tool + │ │ 63 tools, executes │
│ arguments │ │ Python or hits APIs│
└───────────────┘ └────────┬───────────┘
│
▼
┌────────────────────────────────────┐
│ External APIs (DuckDuckGo, Open- │
│ Meteo, frankfurter, CoinGecko, ...)│
└────────────────────────────────────┘
The same MCP server can be consumed by chat.py (custom client) or by Claude Code / Claude Desktop / any MCP-aware host via .mcp.json — write the tools once, plug in anywhere.
| Module | Tools |
|---|---|
| search | web_search, news_search, image_search, wikipedia_search, wikipedia_summary, fetch_url |
| knowledge | dictionary_lookup, thesaurus, rhymes, random_joke, cat_fact, random_quote, random_useless_fact, country_info |
| data | generate_uuid, generate_password, color_convert, lorem_ipsum, random_choice, coin_flip, dice_roll |
| math | calculator, statistics_summary, random_number, base_convert, prime_check, factorize, unit_convert |
| finance | currency_convert, list_currencies, crypto_price, trending_crypto |
| network | dns_lookup, http_status, expand_url, ip_info, public_ip |
| time | current_time, list_timezones, convert_timezone, date_diff, add_to_date, working_days_between, parse_date |
| text | count_words, regex_match, regex_replace, url_encode, url_decode, base64_encode, base64_decode, hash_text, json_format, slugify, diff_text |
| weather | get_weather, geocode, reverse_geocode |
| travel | search_flights, search_hotels, search_trains, search_car_rental, search_restaurants, airport_info (travel search tools return deterministic mock data — designed to be swapped for real Amadeus / Skyscanner APIs) |
git clone https://github.com/Pathuri-Deepesh/mcp-toolbox.git
cd mcp-toolbox
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -e .
pip install anthropic python-dotenvThe custom-built agent that hand-rolls the agentic loop (no LangChain).
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
python chat.py[loaded 63 tools from mcp-toolbox] type 'quit' to exit
Question: convert 1000 USD to INR
-> currency_convert({"amount": 1000, "from_currency": "USD", "to_currency": "INR"})
Response: 1000 USD = ₹92,823 INR (rate from 2026-04-17).
Question: what's the weather in Mumbai?
-> geocode({"place_name": "Mumbai"})
-> get_weather({"latitude": 19.07, "longitude": 72.88})
Response: Mumbai is currently 31°C, partly cloudy, with light winds...
The -> lines are real-time tool invocations — proof Claude is selecting tools from the catalog and the MCP server is executing them.
The bundled .mcp.json is auto-discovered when you launch Claude Code in this directory:
{
"mcpServers": {
"toolbox": {
"command": ".venv/Scripts/python.exe",
"args": ["-m", "mcp_toolbox"]
}
}
}Then in Claude Code, /mcp should list toolbox · ✔ connected. Chat naturally — Claude picks the right tool from the 63 automatically.
npx @modelcontextprotocol/inspector .venv/Scripts/python.exe -m mcp_toolboxOpens a browser UI where you can list tools, invoke them by hand, and inspect the JSON-RPC traffic.
The full agentic loop in plain pseudo-code:
load 63 tool schemas from MCP server
repeat:
user types a question
append to message history
repeat: # inner agentic loop
send message history + tool schemas to Claude
receive response
if response is final answer (end_turn):
print the answer
break
if response contains tool_use blocks:
for each tool_use block:
call MCP server with tool name + arguments
collect the result
append all tool results to message history
# loop back — let Claude decide the next step
That's the whole agent. ~80 lines of Python in chat.py. LangChain's AgentExecutor does the same thing — this version just doesn't hide it.
- Create a function in
src/mcp_toolbox/tools/(or a new module file). - Decorate it with
@mcp.tool()and add a docstring — the LLM uses the docstring to decide when to call it. - Import the new module in
src/mcp_toolbox/server.py.
Example:
from ..server import mcp
@mcp.tool()
def reverse_string(text: str) -> str:
"""Reverse a string. Useful for palindrome checks and debugging."""
return text[::-1]Restart the host (Claude Code or chat.py) and the new tool appears in the catalog automatically.
python -m mcp_toolbox, notpython -m mcp_toolbox.server— theserver.pymodule importstools.*, andtools.*imports back from..server. Runningserver.pydirectly via-mcauses Python to load it twice (as__main__and asmcp_toolbox.server), so tools register on a differentmcpinstance than the onemain()runs. Routing through__main__.pyloadsserver.pyexactly once.chat.pyusesclient.messages.stream()— adaptive thinking +cache_controlon the non-streaming endpoint can return raw SSE strings in some SDK versions. Streaming +.get_final_message()is the canonical fix.sys.stdout.reconfigure(encoding='utf-8')— tool output frequently contains Unicode (currency symbols, language names); the default Windows console codepage (cp1252) can't encode it.
MIT — see LICENSE.