mcp-toolserver
mcp-toolserver
MCP サーバーが、実際に動作する4つのツール(ドキュメント検索、SQL、算術演算、コーパス内省)を公開し、さらにそれに接続するエージェントクライアントを提供します。クライアントは実行時にそれらのツールを発見し、Claude と組み合わせて、単一のツールだけでは答えられない質問に答えます。
このプロジェクトが示すこと
The Model Context Protocol — AI アプリケーションが外部のツールやデータに接続する方法を標準化する、オープンなプロトコルです(Anthropic、2024年11月)。これがないと、すべてのAIアプリはすべてのツールに対してカスタム統合を、すべてのツールはすべてのAIアプリに対してカスタム統合を持たなければならず、N×M の問題になります。MCP はこれを N+M に変えます。つまり、ツールプロバイダは1つの MCP サーバーを構築するだけで、どの MCP 互換クライアントからも特別な統合コードなしで利用できます。このリポジトリはその小さな具体例です。ここでのサーバーとクライアントは、互いの内部を知る必要はなく、共通するのはプロトコルだけです。
動的ツール検出 — エージェントクライアントはツールリストをハードコードしません。接続時に
list_tools()を呼び出し、サーバーが現在公開しているツールを Anthropic のツール使用フォーマットへ変換します。サーバー側でツールを追加または削除すると、クライアント側のコード変更なしに自動的に認識されます。マルチステップのツール連鎖 — ひとつの質問に対して、2つの異なるツールを順番に使う必要がある場合があります(数値を調べて、その数値で計算するなど)。エージェントループはそれ自体で処理します。Claude は、指示されることなく、1つ目のツールの結果を使って2つ目のツールを呼び出す判断をします。
Related MCP server: Nexus
4つのツール
Tool | Signature | What it does |
|
| docmind が取り込んだコーパスに対するセマンティック検索(高密度埋め込み + Chroma)。チャンクごとに |
|
| シードデータ入りの小さなデモ企業データベース( |
|
| 算術式の評価( |
|
| 取り込まれたコーパスの一覧: ドキュメントごとに |
各ツールの docstring が そのまま MCP の説明文です。LLM がツールを呼ぶかどうかを判断するときに実際に読むのはこの説明文なので、ソースをざっと読む人間のためではなく、LLM 向けに書かれています。
ライブデモの実行結果
以下の3つはすべて、実際の Claude API と、実際に起動された MCP サーバーのサブプロセスに対する実行結果です。作り話の追跡ではありません。最初に、実際に2つのツールを連鎖させたケースを載せます。それが興味深いケースだからです。
1. マルチステップ: query_database → calculate
「Engineering 部門の平均給与はいくらですか?また、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。)
2. 単一ツール: query_database
"Engineering 部門の従業員は何人ですか?"
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]回答は、実際に取得されたテキスト(doc の langgraph_agents.pdf)に基づいています。Claude が持っている RAG に関する一般的な知識ではなく、ここでは知識を使うようには指示されていません。
セキュリティ
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行を超えて結果を返すことができません。実時間タイムアウト —
sqlite3のプログレスハンドラが経過時間をチェックし、実行時間が長すぎる場合はステートメントを中断します。
calculate — 許可リスト方式の AST であって、eval() ではありません: 式は ast.parse(..., mode="eval") でパースされ、手動でツリーを走査します。許可されるのは Constant(数値)、BinOp(+ - * / ** %)、UnaryOp(+ / -)のノードのみです。それ以外のもの — Name の参照、Call、Attribute — は、ウォーカーに一致する分岐が存在しないため、必然的に ValueError をスローします。つまり calculate("__import__('os').system('...')") が失敗するのは、危険な呼び出しのブロックリストとパターンマッチしているからではなく、Call ノードをそもそも実行するコードパスが一切存在しない理由です。
設計決定
Anthropic SDK のベータ版 Tool Runner ではなく、明示的なエージェントループ — SDK には、MCP ツールを Tool Runner に直接差し込む MCP ブリッジ(
anthropic.lib.tools.mcp)がありますが、ここでは使っていません。目標は具体的で検証可能な返却契約 —{answer, tool_calls: [{tool, input, output}], iterations}— であり、それには各ターンごとに手作業で簿測る必要があるためです。Tool Runner は、このプロジェクトが示そうとしているまティック(ループ制御、呼び出しごとのトレース)をまさに隠すでしょう。streamable-http ではなく stdio トランスポート — クライアントはオンデマンドでサーバを自身のサブプロセスとして起動します。両者が同じトラスト境界内にあり、ネットワークホップがないため、stdio の単純性(ポートなし、認証不要)が適合します。streamable-http がサポートされています(
--transport streamable-http/MCP_TRANSPORT環境変数)が、サーバーとクライアントが本当に別プロセス・別マシンに分かれている場合のためです。ただし、この対策はここでは一切したことがない(「既知の制限」を参照)。8回のイテレーション上限 — 暴走したループの最悪のコストと遅延に上限を付けます。これは docmind のリライト上限と同じ考え方です。上記のデモ3つはすべて2〜3回で終了しましたが、8回は本当に不正なリクエストや不安定なモデル動作に使う上限であり、通常の使用で発动することを想定していません。
docmind との連携
search_documents と list_documents は、docmind の永続された Chroma コレクションを直接読み取ります(DOCMIND_CHROMA_PATH。デフォルトは docmind プロジェクトの data/chroma を指しており、docmind の側の docmind ディレクトリ)。クエリの埋め込みには、docmind が取り込み時に使用したのと同じ all-MiniLM-L6-v2 モデルを使用します。コードレベルでは、この連携は docmind 特有ではないのです。ただ、設定したパスにある Chroma コレクションを操作するだけです。これで、このプロジェクトは、そのコピーではなく、そのコレクションの真の第2のコンシューマであることを示しています。docmind の検索層基盤は docそ の FastAPI バックエンドに特化ではなく、コレクションの場所を知っている MCP 対応クライアントを使えばアクセスできることの小さな証明でもあります。
既知の制限
デモ用 SQL データベースは小さな合成データで(従業員12人、部署4つ)、本番規模や敵対的なデータベースでテストされたことはありません。
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を完全にモックしています(意図的な設計 — テストスイート内で実 API 呼び出しは行いません)。つまり、どちらかの SDK のスキーマドリフトはpytestだけでは検出できません。上記のライブデモ実行が唯一の実API検証であり、手動でありCI 的ではありません。
セットアップと実行
必要なのは Python 3.12と ANTHROPIC_API_KEY です。そして(vertical_documents/list_documents 用に)コーパスがすでに取り込み済みの docmind チェックアウトです。
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 チェックアウトの data/chroma を指しています。実際に自分の docmind コーパスがある場所の指定先にしてください。または無視してください — docmind のチェックアウトがまったくなくても、query_database、calculate、および list_documents のエラーパスはすべて動作します。
エージェントを直接実行します(MCP サーバー自身をサブプロセスとして生成するので、サーバープロセスを別途起動する必要はありません):
python -m toolserver.client.agent "How many employees are in the Engineering department?"または、MCP サーバーを単体で実行します(例: 別の MCP クライアントをこのサーバーに接続する場合):
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 から大きく逸脱しています — そのモジュールはこのバージョンでは存在しません。以下の内容はすべて、インストール済みパッケージのソースを読み、それに対してライブスモークテスト(プロセス内と実際の 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 のツール使用フォーマットが期待するフィールド名と同じなので、クライアント側の変換はほぼ直接のマッピングであり、スキーマトランスレータではありません。ツール結果:
CallToolResultは.content(MCP コンテンツブロックのリストで、常に値が入る)と.structured_content({"result": ...}という形の型付き dict で、ツール関数に戻り値の型アノテーションがある場合にします — ここでは4つすべてのツールが該当します)の両方を保持します。
This server cannot be deployed
Maintenance
Related MCP Connectors
AI Visibility and Content Intelligence tools for Claude and MCP-compatible agents.
One MCP endpoint for Claude, GPT & Gemini: 100+ tools + no-code connectors + agent workers.
Multiple MCP tools, persistent graph memory, token-saving data pointers, and more.
- WauldoOAuthcom.wauldo
Stateless agentic tools over MCP: concept extraction, long-context, knowledge graph, planning.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables MCP-compatible clients to perform retrieval-grounded Q&A over FordA dataset notes and safe arithmetic calculations through callable tools.-
- AlicenseNot gradedqualityCmaintenanceEnables models to query enterprise systems—structured data, live GitHub REST API, and unstructured document embeddings—through a single, per-caller scoped MCP tool surface with task-shaped tools and recovery-aware errors.90 npmISC
- FlicenseNot gradedqualityCmaintenanceEnables an LLM to perform safe arithmetic calculations and web searches through a standardized MCP interface, returning results to generate informed responses.-
- AlicenseNot gradedqualityBmaintenanceEnables Claude Code and Codex assistants to research and read/write a team's knowledge base, manage projects and files, and use integrations through MCP tools.Apache 2.0