Skip to main content
Glama
roshano3o3

mcp-toolserver

by roshano3o3

mcp-toolserver

MCP-сервер, предоставляющий четыре настоящих инструмента (поиск по документам, SQL, арифметика, интроспекция корпуса), а также агентный клиент, который подключается к нему, обнаруживает эти инструменты на лету и выстраивает их в цепочку вместе с Claude, чтобы отвечать на вопросы, на которые ни один инструмент по отдельности ответить не может.

Что это демонстрирует

  • Модель протокола контекста (Model Context Protocol) — открытый протокол (Anthropic, ноябрь 2024), стандартизирующий то, как ИИ-приложение подключается к внешним инструментам и данным. Без него каждому ИИ-приложению нужна собственная интеграция с каждым инструментом, а каждому инструменту — собственная интеграция с каждым ИИ-приложением: проблема N×M. MCP превращает это в N+M: разработчик инструмента создаёт один MCP-сервер, и любой MCP-совместимый клиент может им пользоваться без единого интеграционного кода. Этот репозиторий — маленький конкретный пример такой схемы: сервер и клиент здесь не знают о внутренностях друг друга, их связывает только протокол.

  • Динамическое обнаружение инструментов — агентный клиент никогда не зашивает список инструментов. При подключении он вызывает list_tools() и преобразовывает то, что сервер в данный момент предоставляет, в формат tool-use Anthropic. Добавьте инструмент на сервере или уберите его — клиент подхватит это автоматически, без изменения кода на стороне клиента.

  • Многошаговое связывание инструментов — на один вопрос могут потребоваться два разных инструмента последовательно (сначала найти число, затем вычислить что-то с его помощью), и агентный цикл справляется с этим сам: Claude сам решает вызвать второй инструмент, используя результат первого, без отдельного указания.

Related MCP server: Sentinel Core Agent

Четыре инструмента

Инструмент

Сигнатура

Что делает

search_documents

(query: str, top_k: int = 5) -> list[dict]

Семантический поиск по индексированному PDF-корпусу docmind (плотные эмбеддинги + Chroma). Возвращает {text, source, page, score} для каждого фрагмента.

query_database

(sql: str) -> list[dict]

Read-only SQL по небольшой демонстрационной базе компании (employees, departments). Только SELECT — см. раздел «Безопасность» ниже.

calculate

(expression: str) -> float

Вычисление арифметических выражений (+ - * / ** %, круглые скобки) без пути исполнения кода — см. раздел «Безопасность» ниже.

list_documents

() -> list[dict]

Инвентаризация индексированного корпуса: {source, pages, chunks} на каждый документ.

Докстринг каждого инструмента и есть его MCP-описание — именно это LLM реально читает, решая, когда вызвать инструмент, поэтому они написаны для этой аудитории, а не для человека, просматривающего исходный код.

Живые демозапуски

Все три примера ниже — реальные запуски против реального Claude API и реального порождённого подпроцесса MCP-сервера, а не сфабрикованные трассы. Начинаю с того, где на самом деле связываются два инструмента, — это как раз интересный случай.

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.0

Claude сам написал SQL, прочитал результат, а затем сам написал и выполнил арифметическое выражение — прведённые выше входные данные инструментов принадлежат Claude, они не захардкожены. (Проверено вручную: 5 сотрудников Engineering в тестовых данных дают в сумме $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}]

«Что такое 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]

Ответ опирается на реально извлечённый текст (это файл langgraph_agents.pdf из docmind), а не на общие знания о CRAG, которые у Claude тоже есть, но которые он здесь использовать не был попрошен.

Безопасность

query_database — многоуровневая, а не узловая:

  1. Проверка ключевых слов и формы на уровне приложения — отвергает всё, что не является одним единственным оператором SELECT (или WITH ... SELECT), ещё до того, как запрос вообще достигнет SQLite. Блокирует INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, ATTACH, DETACH, PRAGMA, VACUUM, REINDEX, и сразу же отметачает несколько операторов (через ;) в принципе.

  2. Встроенный режим read-only SQLite — само соединение открывается с ?mode=no в URI. Это предусмотрено движком SQLite, а не кодом приложения, поэтому это по-настоящему нижняя граница, если в п. 1 есть дырка шир: даже запрос, каким-то образом прошедший проверку ключевых слов, физически не может делать запись.

  3. Ограничение строк — каждый запрос оборачивается в SELECT * FROM (<query>) LIMIT 500, поэтому ни один запрос не может вернуть более 500 строк, что бы он ни запрашивал.

  4. Таймаут по реальному времени — обработчик прогресса sqlite3 проверяет истёкшее время и прерывает выполнение оператора, если тот выполняется слишком долго.

calculate — AST-allowlist, а не eval(): выражение разбирается через ast.parse(..., mode="eval") и обходится вручную; разрешены только узлы Constant (числовой литерал), BinOp (+ - * / ** %) и UnaryOp (+/-). Всё остальное — обращение по имени Name, вызов Call, доступ к атрибуту Attribute — не имеет подходящей ветки в обходчике и по построению выбрасывает ValueError. Именно поэтому calculate("__import__('os').system('...')") падает: дело не в том, что он сверяется с чёрным списком опасных вызовов, а в том, что просто не существует пути кода, который вообще мог бы выполнить узел Call.

Проектные решения

  • Явный агентный цикл, а не бета-версия Tool Runner из Anthropic SDK. В SDK vсно есть MCP-мост (anthropic.lib.tools.mcp), который подключает MCP-инструменты напрямую к Tool Runner. Здесь он не использовался, потому что нужен был конкретный, проверяемый контракт возврата — {answer, tool_calls: [{tool, input, output}], iterations} — а он требует ручной бухгалтерии на каждом ходе. Tool Runner скрыл бы как раз ту механику (управление циклом, трассировку каждого вызова), которую этот проект должен показать.

  • Транспорт stdio, а не streamable-motus. Клиент запускает сервер как собственный подпроцесс по требованию; оба находятся в одной границе доверия и не имеют сетевого перехода, поэтому простота stdio (ни портов, ни механизмов аутентификации не нужно) подходит. streamable-http поддерживается (--transport streamable-http / переменная MCP_TRANSPORT) для случая, когда сервер и клиент — действительно отдельные процессы/машины, хотя под этот сценарий ничего здесь не укреплялось (см. «Известные ограничения»).

  • Потолочное ограничение в 8 итераций. Ограничивает стоимость задержек в худшем случае для цикла, вышедшего из-под контроля, — та же логика, что и лимит переписывания в docmind. Все три демозапуска выше завершились за 2–3 итерации; 8 — это щедрый потолок, предназначенный для ловли по-настоящему некорректного запроса или нестабильного поведения модели, а не для обычного режима.

Связь с docmind

search_documents и list_documents читают напрямую сохранённую Chroma-коллекцию самого docmind (DOCMIND_CHROMA_PATH, по умолчанию указывав на data/chroma соседнего проекта docmind), задавая эмбеддинги запросов с той же моделью all-MiniLM-L6-v2, которую docmind использовал при загрузке в корпус. В коде в этом подключении нет ничего специфичного для docmind — это просто коллекция Chroma по настроенному пути, поэтому этот проект является настоящим вторым потребителем этого корпуса, а не его копией. Это небольшое доказательство того, что слой поиска в docmind не вшит именно в FastAPI-бэкенд docmind: он доступен любому MCP-сознательному клиенту, который знает, где расположена коллекция.

Известные ограничения

  • Демонстрационная SQL-база крошечная и синтетическая (12 сотрудников, 4 отдела) — ничего не тестировалось против реально масштабной или враждебной базы.

  • Блоклист ключевых слов SQL — это регулярное выражение по тесту запроса, а не настоящий SQL-парсер, поэтому обон может и переблокировать легальную ссылку (например, на табличную функцию pragma_table_info()), и, in principle, post hyphenate конструкцию, которую никто не подумал тестировать. Режим чтения — это защита, которая не зависит от того, что блоклист полон.

  • calculate поддерживает только числовые литералы и шесть перечисленных операторов — без функций (sqrt, sin, ...), без переменных. Оно намеренно минимально, а не universal expression engine.

  • У MCP-сервера нет аутентификации. Для stdio это нормально (процесс локальный, единая граница доверия); если запускать поэ как через stream filters/http, в текущем виде любой, кто достигнет порта, может вызвать любой инструмент, включая query_database.

  • Лимит в 8 итераций: жёсткий природeking, а notэ elegant̆ное ухудшение — вопрос, которому законно нужно больше ~4 раундов вызовов инструмента, получит сообщение «остановлен после 8 итераций», а не настоящий ответ.

  • Нет памяти разговора между вызовами CLI — каждый вызов python -m toolserver.client.agent "..." начинает диалог с чистого листа, без истории.

  • Нетками потоковой передачи — каждый виток цикла блокирующе вызывает messages.create; медленный инструмент или длинная генерация блокирует весь ход.

  • Тесты полностью мокают Anthropic-клиент и MCP Client (намеренно: корпусе тестов нет настоящих API-вызовов). Это значит, что, если в каком-то SDK изменяются схематы, один pytest этого не поймает в клинике: живая демонстрация выше — единственная проверка на реальном умпе, а она ручная, а не часть CI.

Установка и запуск

Требуются Python 3.12, переменная ANTHROPIC_API_KEY и (для search_documents/list_documents) наличие checkout от 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

По умолчанию DOCMIND_CHROMA_PATH в .env.example указывает на data/chroma в соседнем docmind. Направьте его туда, где реально живёт корпус docmind — или просто он образомignore me: song, and query_database, calculate, а также путь к ошибкам list_documents полностью работают и без чекаута docmind.

Запустите агента напрямую (он сам поднимает 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 — это достаточно сильно отклонение от старого API mcp.server.fastmcp.FastMCP — такого модуля в этой версии не существует. Всё ниже подтверждено чтением исходников установленного пакета и живыми smokри пакета (внутрипроцессный и реальный 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 tool-знания формат expects, поэтому конверсия на стороне клиента является почти прямым отображением, а не транслятот схем.

  • Результаты инструментов: CallToolResult несёт и .content (список контент-блоков MCP, заполняется всегда), и .structured_content (тированные {"result": ...} словарь, заполнятся, когда функция инструмента имеет аннотацию типа возврата, — это верно для всех четырех инструментов здесь).

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    A
    quality
    D
    maintenance
    Enables 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.
    1
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables 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.
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables document ingestion and typed knowledge graph queries through Claude MCP tools, allowing agents to extract, store, and retrieve typed entities and relations from documents.
    2
    MIT

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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