Skip to main content
Glama
Afreed1607

Filesystem MCP Server

by Afreed1607
README.md
# MCP Resume Matching

A LangGraph resume-matching workflow that accesses resumes exclusively through an MCP stdio server. The official MCP Python SDK owns MCP initialization, capability discovery, and JSON-RPC 2.0 transport; application code does not implement JSON-RPC itself.

## Architecture

```text
LangGraph matching agent
        |
        v
Filesystem MCP client
        |
        | MCP / JSON-RPC 2.0 / stdio
        v
Filesystem MCP server
        |
        v
Filesystem service
        |
        v
Configured resume directory
```

See [docs/architecture.md](docs/architecture.md) for the full component and workflow diagrams.

## Install

Python 3.11 or newer is required.

```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[test]"
```

## Configuration

| Variable | Default | Meaning |
| --- | --- | --- |
| `RESUME_DIRECTORY` | `./resumes` | Allowed root directory for resume files. |
| `RESUME_EXTENSIONS` | `.txt,.md,.pdf,.docx` | Comma-separated extension allowlist. Values may omit the leading period. |
| `MCP_PYTHON` | Active Python interpreter | Optional Python executable used to start the MCP server subprocess. |

For example:

```powershell
$env:RESUME_DIRECTORY = "$PWD\resumes"
$env:RESUME_EXTENSIONS = ".txt,.md,.pdf,.docx"
```

## MCP Tools

| Tool | Purpose |
| --- | --- |
| `list_files()` | Lists supported resume paths relative to the configured root. |
| `read_file(file)` | Reads one allowed resume. |
| `search_files(query)` | Searches allowed resume text case-insensitively. |
| `parse_resume(file)` | Returns basic name, email, skills, and extracted text. |
| `batch_process(files, max_concurrency=4)` | Parses unique inputs concurrently with failure isolation. |
| `watch_directory(known_files=None)` | Performs one bounded polling snapshot for newly visible resumes. |

Tool input schemas are generated from the Python type annotations by FastMCP. Domain failures include an error type and actionable message; the client raises a contextual `MCPToolError` for those failures.

## MCP Resources

Resume text is also available through the parameterized resource URI:

```text
resume://<filename>
```

For example, `resume://candidate.txt` returns the text for `candidate.txt`. This is a resource *template*, so MCP exposes it through `resources/templates/list` as `resume://{file}`; clients then read a concrete URI through `resources/read`. It applies the same root, extension, traversal, and symlink-escape checks as `read_file`.

## Running the MCP Server

The server uses stdio and is normally launched by the client. To run it for MCP Inspector or another MCP host:

```powershell
python filesystem_mcp_server.py
```

Do not type plain text into its terminal: it expects MCP JSON-RPC messages from its host.

## Running the Agent

```powershell
$env:RESUME_DIRECTORY = "$PWD\resumes"
python matching_agent.py
```

The agent starts and closes its own MCP subprocess, discovers required tools, batches the available resumes, and ranks them with the existing deterministic matching logic.

## Batch Processing

`batch_process` accepts only relative paths. It keeps the first occurrence of duplicates, preserves first-seen order, uses bounded worker concurrency, and continues after a missing, unsupported, or malformed file.

```json
{
  "files": ["alice.txt", "missing.txt", "alice.txt"],
  "max_concurrency": 4
}
```

Its result includes `requested`, `duplicates_ignored`, `processed`, `successful`, `failed`, and ordered per-file `results`.

## Directory Watching

`watch_directory` is deliberately a single, stateless polling call rather than an unbounded MCP request or background worker. Call it once, retain `known_files`, then submit that list on the next call:

```json
{"known_files": ["alice.txt", "bob.md"]}
```

It returns `new_files` and the refreshed `known_files`. Unsupported files are excluded. A missing or unavailable configured directory yields a structured error response instead of blocking indefinitely. The legacy `files` and `known` keys are retained as aliases for older callers.

## Error Handling and Security

The filesystem service is the sole authority for filesystem access. It rejects absolute paths, `..` traversal, paths resolving through a symlink outside the configured root, and extensions outside the configured allowlist. It exposes no arbitrary file read capability. Batch operations isolate expected per-file errors while unexpected errors are logged and represented in that file's result.

MCP connection, tool, and resource errors preserve the tool/resource name and diagnostic context. The service logs detailed operational failures but does not return absolute internal paths as part of file-read errors.

## Testing

```powershell
python -m pytest
```

The deterministic suite uses temporary directories and covers service behavior, filesystem boundaries, batch success/failure/duplicates/invalid input, bounded watching, MCP stdio startup and initialization, tool/resource discovery and reads, MCP error propagation, agent-to-MCP integration, and end-to-end ranking.

## Demo Walkthrough (6 minutes)

| Time | Demonstration |
| --- | --- |
| 0:00–0:45 | Explain the problem and show the architecture diagram. |
| 0:45–1:45 | Open `filesystem_mcp_server.py`; show the six tools and `resume://{file}` resource template. |
| 1:45–2:30 | Use an MCP host/Inspector to initialize the server and discover tools and resource templates. |
| 2:30–3:30 | Open `matching_agent.py` and `mcp_client.py`; show discovery, lifecycle management, and no direct filesystem import. |
| 3:30–4:30 | Run `python matching_agent.py` with two sample resumes and rank candidates for a job description. |
| 4:30–5:15 | Demonstrate `batch_process` with a duplicate/missing input, then poll `watch_directory` after adding a resume. |
| 5:15–6:00 | Run `python -m pytest` and revisit the architecture/state diagrams. |

## Limitation

The repository's existing matching policy is deterministic token-overlap ranking. This MCP refactor preserves that existing behavior; it does not introduce an external LLM or a new paid model dependency.