filesystem-mcp-server
Resume Matcher — MCP Edition
Агент сопоставления резюме, использующий прямые инструменты файловой системы, заменён на автономный MCP-сервер, плюс агент LangGraph, рефакторизованный для общения с ним (и со вторым MCP-сервером) через реальный MCP-клиент вместо локальных вызовов функций.
Учебные цели → что в этом репозитории
Цель | Где |
Понять Model Context Protocol |
|
Заменить пользовательские инструменты MCP-серверами | Каждая операция файловой системы из Milestone 1 теперь является |
Реализовать стандартизированные интерфейсы инструментов | Единая обёртка |
Развернуть production-ready системы | Конфигурация на основе env, ограниченная конкурентность, обработка частичных сбоев, протестированное исправление передачи env, 14 проходящих тестов на уровнях unit и протокола |
Related MCP server: Filesystem MCP Server
Архитектура
flowchart LR
subgraph "Agent process (matching_agent.py)"
A["LangGraph StateGraph"] --> B["MultiServerMCPClient"]
A --> L["Claude (LLM)\nstructured scoring"]
end
B <-->|"JSON-RPC 2.0 / stdio"| C["filesystem_mcp_server.py"]
B <-->|"JSON-RPC 2.0 / stdio"| D["notifications_mcp_server.py"]
C --> E[("sample_data/resumes/\nresults/")]
D --> F[("results/notifications.log")]Два независимых MCP-сервера, каждый в отдельном процессе ОС, каждый не знает о другом или о LangGraph. Агент обнаруживает их инструменты при запуске (client.get_tools()) и вызывает их по имени — в этом и заключается суть рефакторинга: filesystem_mcp_server.py может завтра получить новый инструмент, и matching_agent.py не потребует изменения кода.
Конечный автомат (взаимодействие агента и MCP)
stateDiagram-v2
[*] --> check_new_resumes
check_new_resumes --> batch_extract: new files found
check_new_resumes --> [*]: nothing new — short-circuit
batch_extract --> match: text extracted
match --> rank_and_save: LLM structured scoring
rank_and_save --> notify: results persisted
notify --> [*]: done
note right of check_new_resumes
filesystem server
tool: watch_directory
end note
note right of batch_extract
filesystem server
tool: batch_process
end note
note right of rank_and_save
filesystem server
tool: save_match_result (per match)
end note
note right of notify
notifications server
tool: send_match_notification
(only matches scoring >= 70)
end noteТо, что check_new_resumes вызывает watch_directory первым — до любых других действий — намеренно: запуск без новых данных сразу переходит к [*], не тратя вызов LLM, а режим --watch (см. ниже) повторно обрабатывает только то, что действительно изменилось, а не весь каталог каждый раз.
Запуск локально
Из корня репозитория создайте виртуальное окружение и установите зависимости.
Bash / Git Bash / Linux / macOS
cd [Path To Files]
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
python matching_agent.pyWindows PowerShell
cd [Path To Files]
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
$env:ANTHROPIC_API_KEY = "<your-anthropic-api-key>"
python .\matching_agent.pyЕсли нужно заново просканировать входящие с нуля
Наблюдатель хранит небольшой файл состояния, поэтому он оценивает только недавно добавленные резюме. Если нужно обработать текущую папку снова, сначала очистите состояние наблюдателя:
python matching_agent.py --reset-watchТипичные сценарии использования
# one pass over the bundled sample data
python matching_agent.py
# run against your own job description and resume folder
python matching_agent.py --job-description path/to/jd.txt --resume-dir path/to/resumes
# keep polling for newly added resumes every 15s (Ctrl+C to stop)
python matching_agent.py --watch --interval 15Используйте Python из виртуального окружения проекта, а не системный Python, при запуске агента. В этом репозитории рабочая команда обычно
./.venv/Scripts/python.exe matching_agent.pyна Windows илиsource .venv/bin/activate && python matching_agent.pyна Unix-подобных оболочках.
Запустите любой сервер отдельно, чтобы проверить его напрямую (удобно с MCP Inspector):
python filesystem_mcp_server.py
python notifications_mcp_server.pyКонфигурация управляется через env — см. ServerConfig.from_env() в filesystem_mcp_server.py:
Переменная | По умолчанию |
|
|
|
|
|
|
|
|
|
|
|
|
Тестирование
pytest tests/ -v14 тестов, два уровня:
Unit (
test_filesystem_mcp_server.py, большая часть): вызов функций инструментов напрямую с фикстуройtmp_path— быстро, без подпроцессов. Покрывает успешные пути, коды ошибокRESUME_NOT_FOUND/INVALID_PARAMSи отчёт о частичных сбояхbatch_process.Protocol (
test_server_speaks_mcp_protocol_over_stdio): запускает реальный сервер как подпроцесс и управляет им через официальныйmcpклиентский SDK —tools/list,tools/call,resources/read— по реальному JSON-RPC 2.0, так что проверяется именно протокольный слой, а не только Python под ним.Agent (
test_matching_agent.py): вызов LLM заменён на детерминированный фейк (FakeStructuredModel), поэтому этим тестам не нужен API-ключ — они проверяют связку графа, обнаружение инструментов на нескольких серверах, путь короткого замыкания при отсутствии новых файлов и то, что второй проход в стиле--watchобрабатывает только вновь поступивший файл, а не весь каталог.
Проектные решения
MCP SDK закреплён на mcp>=1.28,<2.0. Линейка v2 Python SDK вышла вместе с ревизией спецификации MCP от 2026-07-28 и переименовывает FastMCP в MCPServer (теперь в mcp.server.mcpserver). v1.x — это то, на чём построена и документирована текущая экосистема LangChain/LangGraph MCP, поэтому проект намеренно закрепляется на ней, а не случайно — стоит пересмотреть, когда langchain-mcp-adapters и более широкая база учебных материалов догонят v2.
stdio вместо HTTP. Нет сетевой поверхности для защиты, не нужно настраивать аутентификацию, и это то, что MultiServerMCPClient ожидает для локального "command" сервера. Компромисс, подтверждённый при создании: каждый вызов инструмента открывает новый сеанс подпроцесса, а не переиспользует существующий — нормально для демо/CLI-агента, и это честная причина, по которой чувствительная к задержкам production-версия перешла бы на долгоживущий streamable-http сервер.
Ошибки — это структурированный JSON, а не проза. Каждый сбой вызывает ToolError с JSON-нагрузкой, содержащей code в диапазоне "server error" JSON-RPC (-32000..-32099) плюс машиночитаемую метку error (RESUME_NOT_FOUND, DIRECTORY_NOT_FOUND, UNSUPPORTED_FILE_TYPE, EXTRACTION_FAILED, INVALID_PARAMS). Подтверждено сквозным тестом с живой клиентской сессией: это проявляется как CallToolResult(isError=True, ...), а _call_tool() в matching_agent.py повторно поднимает его как MCPToolCallError с сохранённым кодом, вместо того чтобы вызывающему приходилось сопоставлять строки сообщения.
watch_directory опрашивает, а не отправляет. Инструменты MCP — это запрос/ответ, поэтому это опрос (JSON-файл состояния имя_файла→mtime, сравниваемый при каждом вызове), а не слушатель inotify/watchdog. Режим --watch в matching_agent.py превращает это в нечто, ощущаемое как живое — фоновый слушатель, отправляющий через MCP подписки на ресурсы, был бы естественным следующим шагом и поддерживается протоколом, но выходит за рамки этого проекта.
batch_process принимает явный список файлов, а не только каталог. Именно это позволяет check_new_resumes → batch_extract повторно обрабатывать только то, что только что сообщил watch_directory, а не всю папку каждый раз — конкурентность (asyncio.Semaphore(MAX_BATCH_CONCURRENCY)) делает "эффективность" из спецификации реальной, когда список длинный.
Передача env явная, и это не опция по умолчанию, которую можно пропустить. Реальная ловушка, обнаруженная при создании: stdio-клиент mcp не наследует окружение родительского процесса — он запускает дочерние серверы с минимальным окружением по умолчанию (только PATH/HOME/TERM), что подтверждено напрямую через mcp.client.stdio.get_default_environment(). Без явной передачи env=dict(os.environ) в конфигурации сервера в matching_agent.py, RESUME_DIRECTORY и другие переменные молча никогда не достигают filesystem_mcp_server.py — агент запускается, отлично обнаруживает инструменты и просто тихо работает с неправильным каталогом. Стоит знать, прежде чем это обойдётся вам в сеанс отладки.
Структура репозитория
resume-matcher-mcp/
├── filesystem_mcp_server.py # Part A
├── notifications_mcp_server.py # Part B bonus: 2nd MCP server
├── matching_agent.py # Part B
├── requirements.txt
├── pytest.ini
├── tests/
│ ├── test_filesystem_mcp_server.py
│ └── test_matching_agent.py
├── sample_data/
│ ├── job_description.txt
│ └── resumes/ # 21 resumes, deliberately strong/partial/weak fit
└── results/ # match_results.jsonl + notifications.log (gitignored)This server cannot be deployed
Maintenance
Related MCP Connectors
Document processing over MCP: merge, split and compress PDFs, run OCR, extract document text.
Build, version and render resumes as PDFs from Claude or any MCP client.
Hosted MCP tools for FFmpeg-style video and audio processing through FFMPEG API.
Patents MCP — wraps the USPTO Open Data Portal (ODP) Patent File Wrapper API
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables file system operations (read, write, list, search, watch, batch process) via MCP over JSON-RPC 2.0, used by a resume matching agent.-
- FlicenseNot gradedqualityDmaintenanceProvides file system tools for resume matching agents, enabling reading, writing, searching, listing, watching, and batch processing of files via the Model Context Protocol.-
- FlicenseNot gradedqualityCmaintenanceProvides MCP tools for reading, listing, writing, searching, watching, and batch-processing files, enabling automated file management and resume matching workflows.-
- FlicenseNot gradedqualityCmaintenanceEnables MCP clients to interact with a filesystem by listing, reading, writing, and monitoring files, as well as extracting resume text and batch processing files.-