mcp-toolserver
Provides read-only SQL querying against a SQLite demo company database (employees and departments tables), supporting SELECT queries with a row cap and timeout.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-toolserverWhat's the average salary in Engineering, and what would a 12% raise cost in total?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-toolserver
An MCP server exposing four real tools (document search, SQL, arithmetic, corpus introspection), plus an agent client that connects to it, discovers those tools at runtime, and chains them with Claude to answer questions no single tool could answer alone.
What this demonstrates
The Model Context Protocol — an open protocol (Anthropic, Nov 2024) that standardizes how an AI application connects to external tools and data. Without it, every AI app needs a custom integration for every tool, and every tool needs a custom integration for every AI app — an N×M problem. MCP turns that into N+M: a tool provider builds one MCP server, and any MCP-compatible client can use it with no bespoke integration code. This repo is a small, concrete instance of that: the server and client here don't know about each other's internals, only the protocol between them.
Dynamic tool discovery — the agent client never hardcodes a tool list. It calls
list_tools()at connect time and converts whatever the server currently advertises into Anthropic's tool-use format. Add or remove a tool on the server and the client picks it up automatically, with no client-side code change.Multi-step tool chaining — a single question can require two different tools in sequence (look a number up, then compute with it), and the agent loop handles that itself: Claude decides to call a second tool using the result of the first, without being told to.
Related MCP server: Sentinel Core Agent
The four tools
Tool | Signature | What it does |
|
| Semantic search over docmind's ingested PDF corpus (dense embeddings + Chroma). Returns |
|
| Read-only SQL against a small seeded demo company database ( |
|
| Arithmetic evaluation ( |
|
| Inventory of the ingested corpus: |
Each tool's docstring is its MCP description — that's what the LLM actually reads to decide when to call it, so they're written for that audience, not for a human skimming the source.
Live demo runs
All three below are real runs against the real Claude API and a real spawned MCP server subprocess — not fabricated traces. Leading with the one that actually chains two tools, since that's the interesting case.
1. Multi-step: query_database → calculate
"What's the average salary in Engineering, and what would a 12% raise cost in total?"
Answer:
Here's the breakdown for the Engineering department:
| Metric | Value |
|---|---|
| Average Salary | $141,600 |
| Total Current Payroll | $708,000 |
| Cost of 12% Raise | $84,960 |
| New Total Payroll | $792,960 |
A 12% raise across all Engineering employees would cost an additional $84,960,
bringing the department's total payroll from $708,000 to $792,960.
Iterations: 3
Tool calls:
1. query_database({'sql': "SELECT AVG(salary) as avg_salary, SUM(salary) as total_salary FROM employees WHERE department_id = (SELECT id FROM departments WHERE name = 'Engineering')"})
-> [{'avg_salary': 141600.0, 'total_salary': 708000}]
2. calculate({'expression': '708000 * 0.12'})
-> 84960.0Claude wrote the SQL itself, read the result, then wrote and ran the arithmetic expression itself — the tool inputs above are Claude's, not hardcoded. (Verified by hand: 5 Engineering employees summing to $708,000 in the seed data, ÷5 = $141,600 avg, ×0.12 = $84,960.)
2. Single tool: query_database
"How many employees are in the Engineering department?"
Answer:
There are 5 employees in the Engineering department.
Iterations: 2
Tool calls:
1. query_database({'sql': "SELECT COUNT(*) as employee_count FROM employees e JOIN departments d ON e.department_id = d.id WHERE d.name = 'Engineering'"})
-> [{'employee_count': 5}]3. Single tool: search_documents
"What is corrective RAG?"
Answer:
## Corrective RAG (CRAG)
Corrective RAG (CRAG) is an enhanced version of standard Retrieval-Augmented
Generation (RAG) that adds a self-correction step after the initial retrieval
phase. [...] Standard (vanilla) RAG simply takes the top-k retrieved documents
and passes them directly to the language model generator -- regardless of
whether those documents actually answer the question. CRAG improves on this by
checking retrieval quality before generation.
[... full answer continues with the retrieve -> grade -> (generate | rewrite &
retry) flow and the latency/LLM-call tradeoff, condensed here for length ...]
Iterations: 2
Tool calls:
1. search_documents({'query': 'corrective RAG'})
-> [5 chunks from langgraph_agents.pdf and llm_evaluation.pdf, scores 0.44-0.58]The answer is grounded in the actual retrieved text (docmind's langgraph_agents.pdf) — not general knowledge about CRAG, which Claude also has, but wasn't asked to use here.
Safety
query_database — layered, not single-point:
App-level keyword/shape check — rejects anything that isn't a single
SELECT(orWITH ... SELECT) statement before it reaches SQLite at all. BlocksINSERT,UPDATE,DELETE,DROP,ALTER,CREATE,ATTACH,DETACH,PRAGMA,VACUUM,REINDEX, and rejects multiple statements (;-separated) outright.SQLite's native read-only mode — the connection itself is opened with
?mode=roin the URI. This is enforced by the SQLite engine, not application code, so it's the real backstop if step 1 has a gap: even a query that somehow got past the keyword check physically cannot write.Row cap — every query is wrapped as
SELECT * FROM (<query>) LIMIT 500, so no query can return more than 500 rows regardless of what it asks for.Wall-clock timeout — a
sqlite3progress handler checks elapsed time and aborts the statement if it runs too long.
calculate — AST allowlist, not eval(): the expression is parsed with ast.parse(..., mode="eval") and walked by hand; only Constant (numeric), BinOp (+ - * / ** %), and UnaryOp (+/-) nodes are permitted. Anything else — a Name lookup, a Call, an Attribute — has no matching branch in the walker and raises ValueError by construction. This is why calculate("__import__('os').system('...')") fails: it's not pattern-matched against a blocklist of dangerous calls, there's simply no code path that would ever execute a Call node at all.
Design decisions
Explicit agent loop, not the Anthropic SDK's beta Tool Runner. The SDK does ship an MCP bridge (
anthropic.lib.tools.mcp) that plugs MCP tools straight into the Tool Runner. It wasn't used here because the goal was a specific, inspectable return contract —{answer, tool_calls: [{tool, input, output}], iterations}— which needs hand-rolled bookkeeping around each turn. The Tool Runner would hide exactly the mechanics (loop control, per-call tracing) this project is meant to show.stdio transport, not streamable-http. The client spawns the server as its own subprocess on demand; both live in the same trust boundary and there's no network hop, so stdio's simplicity (no ports, no auth story needed) fits. Streamable-http is supported (
--transport streamable-http/MCP_TRANSPORTenv var) for the case where server and client are genuinely separate processes/machines, but nothing here has been hardened for that (see Limitations).8-iteration cap. Bounds the worst-case cost and latency of a runaway loop — the same reasoning as docmind's rewrite cap. All three demo runs above finished in 2-3 iterations; 8 is a generous ceiling meant to catch a genuinely malformed request or unstable model behavior, not something expected to bind in normal use.
Connection to docmind
search_documents and list_documents read docmind's own persisted Chroma collection directly (DOCMIND_CHROMA_PATH, default pointed at the sibling docmind project's data/chroma), embedding queries with the same all-MiniLM-L6-v2 model docmind used at ingestion time. Nothing about the connection is docmind-specific at the code level — it's just a Chroma collection at a configured path — so this project is a genuine second consumer of that corpus, not a copy of it. It's a small proof that docmind's retrieval layer isn't wired into docmind's own FastAPI backend specifically; it's addressable by any MCP-aware client that knows where the collection lives.
Known limitations
The demo SQL database is tiny and synthetic (12 employees, 4 departments) — nothing here has been tested against a real-scale or adversarial database.
The SQL keyword blocklist is a regex over the query text, not a real SQL parser — it can both over-block (e.g. a legitimate
pragma_table_info()table-valued function reference) and, in principle, miss a construct nobody thought to test. The read-only connection mode is the defense that doesn't depend on the blocklist being complete.calculatesupports only numeric literals and the six listed operators — no functions (sqrt,sin, ...), no variables. Deliberately minimal, not a general expression engine.The MCP server has no authentication. Fine for stdio (process-local, single trust boundary); if run over
streamable-httpas currently implemented, anyone who can reach the port can call any tool, includingquery_database.The 8-iteration cap is a hard stop, not a graceful degradation — a question that legitimately needs more than ~4 tool round-trips gets a "stopped after 8 iterations" message instead of a real answer.
No conversation memory across CLI invocations — each
python -m toolserver.client.agent "..."call starts a fresh conversation with no history.No streaming — each loop iteration is a blocking
messages.createcall; a slow tool or long generation blocks the whole turn.Tests fully mock the Anthropic client and the MCP
Client(by design — no real API calls in the test suite). That means schema drift in either SDK wouldn't be caught bypytestalone; the live demo runs above are the only real-API verification, and they're manual, not part of CI.
Setup & run
Requires Python 3.12, an ANTHROPIC_API_KEY, and (for search_documents/list_documents) a docmind checkout with its corpus already ingested.
git clone https://github.com/roshano3o3/mcp-toolserver.git
cd mcp-toolserver
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
cp .env.example .env # edit .env and set ANTHROPIC_API_KEYBy default DOCMIND_CHROMA_PATH in .env.example points at a sibling docmind checkout's data/chroma. Point it at wherever your docmind corpus actually lives, or ignore it — query_database, calculate, and list_documents' error path all work with no docmind checkout present at all.
Run the agent directly (it spawns the MCP server itself as a subprocess — no separate server process to start):
python -m toolserver.client.agent "How many employees are in the Engineering department?"Or run the MCP server standalone, e.g. to point another MCP client at it:
python -m toolserver.server # stdio (default)
python -m toolserver.server --transport streamable-http # http://127.0.0.1:8765/mcp by defaultTests:
pytest
ruff check .Verified against the installed SDK (not written from memory)
mcp==2.0.0 is a significant departure from the older mcp.server.fastmcp.FastMCP API — that module doesn't exist in this version. Everything below was confirmed by reading the installed package's source and running live smoke tests against it (in-process and real stdio subprocess), not recalled from training data:
Server:
from mcp.server.mcpserver import MCPServer—MCPServer("name"), tools registered with@server.tool()(parens required;@server.toolwithout them raises on purpose).server.run(transport="stdio" | "sse" | "streamable-http").Client:
from mcp.client import Client— the new unified client, replacing directClientSessionuse for most cases. Accepts an in-processServer/MCPServer, a URL string, or aTransport(e.g.stdio_client(StdioServerParameters(...))).Discovery:
await client.list_tools()→ListToolsResult, each tool carryingname,description,input_schema— the same field name Anthropic's tool-use format expects, so the client-side conversion is a near-direct mapping, not a schema translator.Tool results:
CallToolResultcarries both.content(list of MCP content blocks, always populated) and.structured_content(a typed{"result": ...}dict, populated when the tool function has a return type annotation — true for all four tools here).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables Claude Code to perform programmatic tool calling by executing Python scripts that interact with multiple MCP servers in a single round-trip. This reduces latency and token consumption by keeping intermediate tool results within the local Python runtime instead of the conversation context.1MIT
- FlicenseNot gradedqualityDmaintenanceEnables file system operations, web scraping, and AI-powered search through MCP tools for use by LLM agents.1
- FlicenseNot gradedqualityBmaintenanceEnables automatic discovery and reuse of tools from Claude Code execution traces. Provides MCP tools that are distilled from real work, allowing you to reuse previously written scripts without manual effort.
- AlicenseNot gradedqualityBmaintenanceEnables document ingestion and typed knowledge graph queries through Claude MCP tools, allowing agents to extract, store, and retrieve typed entities and relations from documents.2MIT
Related MCP Connectors
Free OpenAI-compatible inference with signed provenance receipts and 3 focused MCP tools.
Hosted MCP with 91 agent tools: X, domains, SEO, Maps, Trends, Search, YouTube, TikTok, and more.
AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/roshano3o3/mcp-toolserver'
If you have feedback or need assistance with the MCP directory API, please join our Discord server