mcp-toolserver
mcp-toolserver
一个 MCP 服务器,提供四个真实工具(文档搜索、SQL、算术、语料库自省),外加一个 agent 客户端:它连接该服务器、在运行时发现这些工具,并把它们与 Claude 串联起来,回答单个工具无法独自回答的问题。
它演示了什么
Model Context Protocol(模型上下文协议) —— 一种开放协议(Anthropic,2024 年 11 月),将 AI 应用连接外部工具和数据方式标准化。没有它,每个 AI 应用都需要为每个工具做定制集成,每个工具也需要为每个 AI 应用做定制集成——这是一个 N×M 的问题。MCP 将其变成 N+M:工具提供方构建一个 MCP 服务器,任何兼容 MCP 的客户端都可以使用它,无需任何定制集成代码。本仓库就是其中的一个具体小型实例:这里的服务器和客户端互不了解对方内部实现,只了解它们之间的协议。
动态工具发现 —— agent 客户端从不硬编码工具列表。它在连接时调用
list_tools(),并把服务器当前通告的任何工具转换成 Anthropic 的 tool-use 格式。在服务端新增或移除一个工具,客户端都会自动发现,无需改动任何客户端代码。多步工具串联 —— 单一问题可能需要先后使用两个不同的工具(先查出一个数值,再拿来计算),而 agent 循环能自行处理这一切:Claude 用第一个工具的结果去调用第二个工具,而无需被明确告知。
Related MCP server: Sentinel Core Agent
四个工具
工具 | 签名 | 用途 |
|
| 对 docmind 已收录的 PDF 语料库做语义搜索(稠密向量 embeddings + Chroma)。每个分块返回 |
|
| 对一个小型seed演示(预置种子数据)公司数据库( |
|
| 算术求值( |
|
| 已收录语料库的清单:每份文档返回 |
每个工具的 docstring 本身就是它的 MCP 描述——LLM 正是靠着读它来决定何时调用该工具,所以它们是写给 LLM 看的,不是写给快速浏览源码的人看的。
真实运行演示
下面三种情况都是真实运行的,跑在真实的 Claude API 和一个真实派生的 MCP 服务器子进程上——不是伪造的调用轨迹。最先展示的是真正串联了两个工具的那一个,因为那才是最有意思的场景。
1. 多步串联:query_database → calculate
“工程部门的平均工资是多少?给整个部门涨 12% 的价格是多少?”
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 亲自写出了 SQL、读取了结果,然后又亲自写出并执行了算术表达式——上面的工具输入数据全部自动由 Claude 带出来的,不是硬编码。(手动验证:种子数据中 Engineering 部门 5 名员工工资合计 $708,000,÷5 = 平均 $141,600,×0.12 = $84,960。)
如果想看完整对话……GXP1
2. 单一工具:query_database
工程部门有多少员工?
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. 单一工具:search_documents
什么是 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]这个答案来自实际检索到的相关文本(docmind 的 langgraph_agents.pdf),不是来自关于 CRAG 的一般常识——Claude 其实也懂 CRAG,但在这次问答中并未要求它使用那些常识。
安全
query_database —— 分层防御,而不是单点防线:
应用级关键字/形状检查 —— 在进入 SQLite 之前就淘汰所有不是单个
SELECT(或WITH ... SELECT)的语句。这里的路上拦截了INSERT、UPDATE、DELETE、DROP、ALTER、CREATE、ATTACH、DETACH、PRAGMA、VACUUM、REINDEX,并且直接拒绝多语句(;分隔)。SQLite 原生只读模式 —— 连接本身通过 URI 中的
?mode=ro打开。这是由 SQLite 引擎强制生效的,不是应用代码来做这件事,所以当第 1 步存在缺口时,“只读”就是真正的后端防线:任何一个能奇谈绕过关键字检查的查询,在内的前提下都无法写入。行数上限 —— 每次查询都会被包成
SELECT * FROM (<query>) LIMIT 500,所以无论查询怎么要求,都不可能返回超过 500 列或行。真实时间(wall-clock)上限 —— 使用
sqlite3的 progress handler 检查已运行时长,超过期限就籽中止该语句。
calculate —— AST 白名单而不是 eval(): 这个工具会用 ast.parse(..., mode="eval") 解析表达式,再手动遍历 AST;只允许 Constant(数值)、BinOp(+ - * / ** %)和 UnaryOp(+/-)节点。任何其他的内容——比如 Name 变量查询、Call 调用、Attribute 属性访问——在遍历器里都没有对应的分支,因而会立刻抛出 ValueError。这正是它不会执行当前这个攻击,因为全部对应分支都是空:在调用路径上,根本不存在任何能执行 Call 节点的路径,而不是靠危险调用黑名单匹配。
设计决策
,而不是 Anthropic SDK 的 beta Tool Runner。 SDK 自带了一个 MCP bridge(
anthropic.lib.tools.mcp)可直接将 MCP 工具插到 Tool Runner 中。但这里没有用它,因为本项目目标是一个明确、可检查的返回契约 ——{answer, tool_calls: [{tool, input, output}], iterations}——需要围绕每一步手工记账。要用 Tool Runner 反而会把项目想展示的那些机制(循环控制、单次调用追踪)隐藏掉。stdio 传输,而不是 streamable-http。 客户端会把MCP服务器按需拉起成自己的子进程;代理双方都在同一个信任边界,也没有网络链路,于是 stdio 的简单性(不需要端口、不需要多余的鉴权)非常合适。streamable-http 本身也是支持的(
--transport streamable-http/ 环境变量MCP_TRANSPORT),用于服务器与客户端真正以独立进程/机器形式传输的场景,但本项目没有对此做任何对生产加固(见 已知限制)。8 次 8 次迭代上限。 避免一个出现失控的循环导致最坏情况下的时间和延迟过高——这也是 docmind 的 rewrite cap 相同的思路。上面三个演示运行都用了 2~3 次就完成了;8 次某个很富余的上限,它是用来拦住真畸形请求或模型不稳定时,而不是常规使用中会被碰到的全家。
与 docmind 的关系
search_documents 和 list_documents 直接读取 docmind 自己持久化的 Chroma collection(DOCMIND_CHROMA_PATH,默认现在指向相邻的 docmind 项目里的 data/chroma),查询时使用和 docmind 当时 intake 时用相同的 all-MiniLM-L6-v2 模型做 embedding。这层关联在代码上和 docmind 没有任何绑定——它只不过是一个配置好的路径下的 Chroma collection——所以这个项目也语料库的第二个消费者,而不是源。它是这样一个小小的验证:docmind 的检索层并不只接在 docmind 自家的 FastAPI 后端上;任何知道这个 collection 位置的协议感知客户端也都能直接访问。
已知限制
示例 SQL 数据库非常小且数据是捏造的夸张(12 名员工,5 个部门),本项目当前没有在真实规模或对抗性数据库环境中做过测试。
SQL 关键字/否定列表是对查询文本做一个正则判断,并没有拿真正的 SQL 解析器来跑——它会误( 例如一个不构成风险的
pragma_table_info()表值函数引用 ),也原则上也可能漏掉某些没法想到的语法。在不会被拦截的情况下由一个“只读连接”模式作为不依赖该列表完备性的最终防线。calculate只支持数值字面量与注释里列出的 6 个运算符——不支持函数(sqrt、sin…… 也不支持变量。刻意保持极小化,而不是做一个通用表达式引擎。MCP 服务器没有接入认证。对 stdio 这个正是刚合适(进程内单边条件下),但若按照当前实现那样跑在
streamable-http上,任何人只要能够触达这个端口,就可以调用任何工具,包括query_database。8 次迭代上限是硬性终止,而非优雅降级——某个合法问题需要多于约 4 次工具往返时,它得到的会是“在循环 8 次后停止”报错,而不是真实答案。
CLI 调用之间没有上下文链接记忆——每次
python -m toolserver.client.agent "..."提问都会从一个全新的空会话开始,不保留历史。不支持流式输出——每个循环迭代都是一次阻塞的
messages.create调用;某个比较慢的工具或长输出就会卡掉整一个人回合。测试完成了对 Anthropic 客户端和 MCP
Client的完全 mock(这是设计如此——测试套件不访问任何真实 API)。这意味着任一 SDK 出现 schema 漂移的话单凭pytest完全不可能发现;项目上面验证过的那些“实际运行演示”,是唯一的真实 API 验证方式——它是手动验证,并包含不进 CI。
安装与运行
需要 Python 3.12、环境变量中提供一个 ANTHROPIC_API_KEY,并且(若要运行 search_documents/list_documents)需要一个已经进入了 docmind 的 repo 副本。
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默认情况下,.env.example 里的 DOCMIND_CHROMA_PATH 指向一个相邻 docmind 仓库checkout 的 data/chroma。请把它指向你自己 docmind 语料所在的真实位置,或者干脆忽略它——query_database、calculate 以及 list_documents 的错误路径在完全没有 docmind 仓库时也能正常运行。
直接运行 agent(它会自动把 MCP server 作为子进程跑起来,不需要单独启动一个 server 进程):
python -m toolserver.client.agent "How many employees are in the Engineering department?"也可以单独启动 MCP server,例如需要把其他 MCP 客户端指到这个 server 时:
python -m toolserver.server # stdio (default)
python -m toolserver.server --transport streamable-http # http://127.0.0.1:8765/mcp by default运行测试:
pytest
ruff check .对着已安装 SDK 的验证(不是凭空记忆盲目写的)
mcp==2.0.0 稳定版本与旧版 mcp.server.fastmcp.FastMCP API 差异很大——那个新老 library 在这样的版本已经被替换。下面所有这些结论都是靠读已安装包的源码,并跑了 live smoke test(in-process 和真实 stdio 子进程两种方式)验证,而不是代称数据背出来:
服务端:
from mcp.server.mcpserver import MCPServer——MCPServer("name")创建一个服务。工具用@server.tool()注册(括号是必需的;不区分)@server.tool完全没有括号的话在注册时会故意照成错误。server.run(transport="stdio" | "sse" | "streamable-http")。客户端:
from mcp.client import Client—— 新的统一客户端,替换了大多数情况下的直接用ClientSession。它接收一个进程内的Server/MCPServer,一个 URL 字符串,或一个Transport(例如stdio_client(StdioServerParameters(...)))。发现工具:
await client.list_tools()→ 返回ListToolsResult,其中每个工具包含name、description、input_schema,它们与 Anthropic message 工具调用格式的字段名称完全一致,客户端转换为 from 。 ,几乎只是直接映射,无需做 schema 翻译。工具运行结果:
CallToolResult同时携带.content(MCP content block 的列表,永远有条目)和.structured_content(一个带类型的{"result": ...}dict,在工具函数本身返回了类型标注时会填充——这里四个工具正好都属于这种情况)。
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