Skip to main content
Glama
Sanket08kumbhare

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