Skip to main content
Glama
filesystem-mcp-server

filesystem-mcp-server

by GAVTIN

Resume Matcher — MCP Edition

履歴書マッチングエージェントの直接的なファイルシステムツールを、スタンドアロンのMCPサーバーに置き換え、さらにLangGraphエージェントを、ローカル関数呼び出しの代わりに実際のMCPクライアントを通じて(そして2つ目のMCPサーバーとも)通信するようにリファクタリングしたものです。

学習目標 → このリポジトリの内容

目標

場所

Model Context Protocolを理解する

filesystem_mcp_server.py はツールとリソースを実装し、tests/ 内の実際のJSON-RPC 2.0ワイヤプロトコルに対してテストされています(モックではありません)

カスタムツールをMCPサーバーに置き換える

Milestone 1のすべてのファイルシステム操作は現在 @mcp.tool() です。matching_agent.py にはファイルシステムコードを直接インポートするものはありません

標準化されたツールインターフェースを実装する

すべてのツールで一貫した {"success": bool, ...} エンベロープ。構造化されたJSON-RPCスタイルのエラーコード(下記参照)

本番対応システムをデプロイする

環境変数ベースの設定、制限付き並行性、部分失敗の処理、テスト済みの環境変数転送修正、ユニット+プロトコル層にわたる14の合格テスト

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")]

2つの独立したMCPサーバーは、それぞれ別のOSプロセスであり、互いや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.py

Windows 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ではなく、プロジェクトのvenvのPythonを使用してください。このリポジトリでは、Windowsでは通常 ./.venv/Scripts/python.exe matching_agent.py、Unix系シェルでは source .venv/bin/activate && python matching_agent.py が動作するコマンドです。

どちらかのサーバーをスタンドアロンで実行して直接触ってみてください(MCP Inspector と一緒に使うと便利です):

python filesystem_mcp_server.py
python notifications_mcp_server.py

設定は環境変数で駆動されます — filesystem_mcp_server.py の ServerConfig.from_env() を参照してください:

変数

デフォルト

RESUME_DIRECTORY

./sample_data/resumes

RESULTS_DIRECTORY

./results

ALLOWED_EXTENSIONS

.txt,.pdf,.docx

MAX_BATCH_CONCURRENCY

5

NOTIFY_SCORE_THRESHOLD

70 (matching_agent.py)

MATCHING_AGENT_MODEL

anthropic:claude-sonnet-5

テスト

pytest tests/ -v

14のテスト、2つのレイヤー:

  • ユニット (test_filesystem_mcp_server.py、大部分): ツール関数を tmp_path フィクスチャに対して直接呼び出します — 高速で、サブプロセスはありません。成功パス、RESUME_NOT_FOUND / INVALID_PARAMS エラーコード、batch_process の部分失敗レポートをカバーします。

  • プロトコル (test_server_speaks_mcp_protocol_over_stdio): 実際のサーバーをサブプロセスとして起動し、公式の mcp クライアントSDK — tools/list、tools/call、resources/read — を実際のJSON-RPC 2.0上で駆動します。つまり、プロトコル層を証明しており、その下のPythonだけではありません。

  • エージェント (test_matching_agent.py): LLM呼び出しは決定論的なフェイク (FakeStructuredModel) に置き換えられているため、APIキーは不要です。グラフの配線、マルチサーバーのツール発見、新規ファイルなしのショートサーキットパス、そして2回目の --watch スタイルのパスがディレクトリ全体ではなく新しく到着したファイルだけを再処理することを確認しています。

設計上の決定

MCP SDKは mcp>=1.28,<2.0 に固定されています。 Python SDKのv2系は2026-07-28のMCP仕様改訂とともにリリースされ、FastMCP を MCPServer(現在は mcp.server.mcpserver の下)に改名しました。v1.xは現在のLangChain/LangGraph MCPエコシステムが構築され、文書化されているバージョンであるため、このプロジェクトは偶然ではなく意図的にv1.xに固定しています。langchain-mcp-adapters とより広いチュートリアル基盤がv2に対応したら再検討する価値があります。

HTTPではなくstdio。 保護すべきネットワーク面がなく、認証を配線する必要もなく、ローカルの「コマンド」サーバーに対して MultiServerMCPClient が期待するものです。これを構築中に確認したトレードオフ:各ツール呼び出しは再利用ではなく新しいサブプロセスセッションを開きます — デモ/CLIエージェントには問題ありませんが、レイテンシに敏感な本番バージョンが代わりに長寿命の streamable-http サーバーに移行する正直な理由です。

エラーは散文ではなく構造化されたJSONです。 すべての失敗は、JSON-RPCの「サーバーエラー」範囲(-32000..-32099)の code と、機械可読な error ラベル(RESUME_NOT_FOUND、DIRECTORY_NOT_FOUND、UNSUPPORTED_FILE_TYPE、EXTRACTION_FAILED、INVALID_PARAMS)を運ぶJSONペイロードを持つ ToolError を発生させます。ライブクライアントセッションに対してエンドツーエンドで確認済み:CallToolResult(isError=True, ...) として表面化し、matching_agent.py の _call_tool() は、呼び出し側がメッセージを文字列マッチングする代わりに、コードをそのまま保持した MCPToolCallError として再発生させます。

watch_directory はポーリングであり、プッシュではありません。 MCPツールはリクエスト/レスポンスなので、これはinotify/watchdogリスナーではなくポーリング(ファイル名→mtimeのJSON状態ファイルを各呼び出しで差分比較)です。matching_agent.py の --watch モードがそれをライブ感のあるものに変えています — MCPリソースのサブスクリプションを通じてプッシュするバックグラウンドリスナーが自然な次のステップであり、プロトコルでサポートされていますが、ここでは範囲外です。

batch_process はディレクトリだけでなく、明示的なファイルリストを受け取ります。 これにより、check_new_resumes → batch_extract は毎回フォルダ全体ではなく、watch_directory がちょうど報告したものだけを再処理できます — 並行性(asyncio.Semaphore(MAX_BATCH_CONCURRENCY))が、リストが長いときに仕様の「効率的」を実際に真にしているのです。

環境変数の転送は明示的であり、スキップできるデフォルトではありません。 これを構築中に実際に遭遇した落とし穴:mcp のstdioクライアントは親プロセスの環境を継承しません — 子サーバーを最小限のデフォルト(PATH/HOME/TERM のみ)で起動します。これは mcp.client.stdio.get_default_environment() に対して直接確認済みです。matching_agent.py のサーバー設定で env=dict(os.environ) を明示的に渡さないと、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)

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables file system operations (read, write, list, search, watch, batch process) via MCP over JSON-RPC 2.0, used by a resume matching agent.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides file system tools for resume matching agents, enabling reading, writing, searching, listing, watching, and batch processing of files via the Model Context Protocol.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides MCP tools for reading, listing, writing, searching, watching, and batch-processing files, enabling automated file management and resume matching workflows.
    -