Filesystem MCP Server
README.md
# MCP-Based Recruitment Matching System
Converts the Milestone-1 filesystem tools into a real MCP (Model Context
Protocol) server and refactors the LangGraph matching agent to consume it
(plus a second MCP server) as a client, instead of calling local Python
functions directly.
## Project layout
```
mcp_recruitment_project/
├── mcp_servers/
│ ├── filesystem_mcp_server.py # Part A — main deliverable
│ └── rag_mcp_server.py # Part B.2 bonus — 2nd MCP server
├── matching_agent.py # Part B — refactored LangGraph agent
├── state.py # LangGraph agent state schema
├── data/
│ ├── resumes/ # cand_001.txt ... cand_005.txt
│ └── job_descriptions/ # sample_jd.txt
├── reports/ # generate_report() output lands here
├── docs/
│ └── workflow_diagram.md # state machine / sequence diagrams
├── tests/
│ └── test_scenarios.py # 7 automated test scenarios
└── requirements.txt
```
## Part A — `filesystem_mcp_server.py`
* Built on Anthropic's official `mcp` Python SDK — a real JSON-RPC 2.0
server over stdio, not a hand-rolled protocol.
* Exposes the Milestone-1 operations as **MCP tools**:
`list_resumes`, `read_resume`, `read_job_description`, `save_report`.
* Exposes resumes/JDs as **MCP resources** (`resources/list`,
`resources/read`) with `resume://<file>` and `jd://<file>` URIs, so any
MCP client can browse the pool without knowing tool names.
* New MCP-specific capabilities:
* **`watch_directory(seconds, target)`** — polls a folder for up to 120s
and reports any files that appeared, for detecting freshly-uploaded
resumes.
* **`batch_process(candidate_ids, operation)`** — runs `read` /
`word_count` / `validate` across up to 50 files in **one** round trip
instead of one call per file, and stays resilient (per-file errors
collected in an `errors{}` map instead of failing the whole batch).
* Error handling uses distinct JSON-RPC error codes (`ERR_NOT_FOUND`,
`ERR_INVALID_PARAMS`, `ERR_FORBIDDEN`, `ERR_BATCH_TOO_LARGE`, plus the
SDK's own `-32603` internal-error fallback) — see
`docs/workflow_diagram.md` §5.
* `ServerConfig` dataclass centralizes directories, allowed extensions,
batch limits, and watch timing; every path is overridable via
`FS_MCP_RESUME_DIR` / `FS_MCP_JD_DIR` / `FS_MCP_REPORT_DIR` env vars, and
every file access is sandboxed with `_safe_join()` against path
traversal.
Smoke-test the handlers directly (no client needed):
```bash
python mcp_servers/filesystem_mcp_server.py --selftest
```
## Part B — `matching_agent.py`
* All direct `os.listdir`/`open()` calls are gone. On startup the agent
creates a `MultiServerMCPClient` pointed at **two** MCP servers and
discovers their tools at runtime — no tool is hard-imported.
* Each LangGraph node calls MCP tools instead of local functions:
| Node | MCP tool used |
|-----------------------|------------------------------------------|
| `parse_jd` | `filesystem.list_resumes` |
| `extract_requirements`| (LLM, or heuristic fallback if no API key)|
| `search_resumes` | `rag.search_resumes` |
| `rank_candidates` | `filesystem.batch_process` (1 call, not N)|
| `generate_report` | `filesystem.save_report` |
* **Bonus multi-MCP**: `search_resumes` and everything filesystem-related
come from two *independent* stdio server processes/sessions, proving
the agent isn't tied to a single MCP server.
* If `ANTHROPIC_API_KEY` isn't set, `extract_requirements` falls back to a
regex-based JD parser so the whole pipeline still runs end-to-end
without network access (useful for grading/CI).
Run it:
```bash
export ANTHROPIC_API_KEY=sk-... # optional; heuristic fallback works without it
python matching_agent.py --jd sample_jd.txt
```
## Diagrams
See `docs/workflow_diagram.md` for:
1. System architecture (agent + 2 MCP servers)
2. Agent state machine
3. JSON-RPC 2.0 sequence diagram for `batch_process`
4. Error-path sequence diagram
5. Error code reference table
## Tests
`tests/test_scenarios.py` — 7 scenarios, run directly or via pytest:
```bash
python tests/test_scenarios.py
# or
python -m pytest tests/test_scenarios.py -v
```
1. `test_discovery` — JSON-RPC handshake + `tools/list` + `resources/list`
2. `test_resource_read` — `resources/read` on `resume://cand_001.txt`
3. `test_error_handling_not_found` — reading a missing resume returns a
proper `isError=true` JSON-RPC result
4. `test_error_handling_invalid_batch` — empty `candidate_ids` rejected
5. `test_batch_process_efficiency` — all resumes processed in 1 call
6. `test_watch_directory_detects_new_file` — a file created mid-watch is
detected and reported
7. `test_multi_mcp_via_agent` — full LangGraph run pulling tools from both
MCP servers, producing a saved report
All 7 currently pass in this environment.
## Setup
```bash
pip install -r requirements.txt
```
## Demo video
I can't record or render an actual video file in this environment. The
`tests/test_scenarios.py` run and the `matching_agent.py` run above are the
exact sequences to capture — screen-record `python tests/test_scenarios.py`
followed by `python matching_agent.py --jd sample_jd.txt` with e.g. OBS,
QuickTime, or Loom for the 5–6 minute submission video. Suggested narration
beats: (1) show `filesystem_mcp_server.py --selftest`, (2) show the raw
stdio JSON-RPC round trip, (3) walk through `docs/workflow_diagram.md`,
(4) run `matching_agent.py` and narrate each MCP call in the reasoning log,
(5) open the generated `reports/match_report.md`.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues