Toolserver
БесплатноНе проверенEnables document search, read-only SQL querying, arithmetic calculation, and corpus introspection through MCP tools, allowing Claude to dynamically discover and
Описание
Enables document search, read-only SQL querying, arithmetic calculation, and corpus introspection through MCP tools, allowing Claude to dynamically discover and chain these tools together to answer multi-step questions.
README
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.
The four tools
| Tool | Signature | What it does |
|---|---|---|
search_documents |
(query: str, top_k: int = 5) -> list[dict] |
Semantic search over docmind's ingested PDF corpus (dense embeddings + Chroma). Returns {text, source, page, score} per chunk. |
query_database |
(sql: str) -> list[dict] |
Read-only SQL against a small seeded demo company database (employees, departments). SELECT only — see Safety below. |
calculate |
(expression: str) -> float |
Arithmetic evaluation (+ - * / ** %, parentheses) with no code execution path — see Safety below. |
list_documents |
() -> list[dict] |
Inventory of the ingested corpus: {source, pages, chunks} per document. |
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.0
Claude 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_KEY
By 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 default
Tests:
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).
Установить Toolserver в Claude Desktop, Claude Code, Cursor
unyly install mcp-toolserverСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add mcp-toolserver -- uvx toolserverПошаговые гайды: как установить Toolserver
FAQ
Toolserver MCP бесплатный?
Да, Toolserver MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Toolserver?
Нет, Toolserver работает без API-ключей и переменных окружения.
Toolserver — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Toolserver в Claude Desktop, Claude Code или Cursor?
Открой Toolserver на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
wenb1n-dev/SmartDB_MCP
A universal database MCP server supporting simultaneous connections to multiple databases. It provides tools for database operations, health analysis, SQL optim
автор: wenb1n-devPostgres Server
This server enables interaction with PostgreSQL databases through the Model Context Protocol, optimized for the AWS Bedrock AgentCore Runtime. It provides tools
автор: madhurprashPostgres
Query your database in natural language
автор: AnthropicPostgreSQL
Read-only database access with schema inspection.
автор: modelcontextprotocolRedis
Interact with Redis key-value stores.
автор: modelcontextprotocolSQLite
Database interaction and business intelligence capabilities.
автор: modelcontextprotocolmxcp
Open-source framework for building enterprise-grade MCP servers using just YAML, SQL, and Python, with built-in auth, monitoring, ETL and policy enforcement.
автор: raw-labstadas-github/a2asearch-mcp
MCP server to search 4,800+ MCP servers, AI agents, CLI tools and agent skills. Install: npx -y a2asearch-mcp. Ask Claude: "Find MCP servers for database access
автор: tadas-githubjulien040/anyquery
Query more than 40 apps with one binary using SQL. It can also connect to your PostgreSQL, MySQL, or SQLite compatible database. Local-first and private by desi
автор: julien040drakonkat/wizzy-mcp-tmdb
A MCP server for The Movie Database API that enables AI assistants to search and retrieve movie, TV show, and person information.
автор: drakonkatCompare Toolserver with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории data
