A file tool is only useful once it can actually answer 'what's in this file' and 'what files exist here' correctly.
1list_files Needs to Filter, Not Just Dump Everything
A real project can have hundreds of files. A useful list_files tool accepts a prefix (like "src/") and returns only matching paths, so a model exploring a project can narrow its view instead of receiving an unusable wall of every file at once.
2Handlers Should Return Failures, Not Raise Them
Inside dispatch, an uncaught exception from a handler would crash the whole request — one bad path argument shouldn't take down the server process handling every other tool call too. Returning a descriptive error string (or a structured error object) keeps a single failed call contained and reportable back to the client.
3Step-by-Step Breakdown
A Real Project to Serve. DevAssist needs to serve more than one hardcoded file. This lesson gives read_file and list_files a small simulated project — a few real files with real content — and asks list_files to actually filter by a path prefix, the same way a real client browsing a folder would expect.
Return Real File Contents. list_files_handler and the missing-file guard are done. Finish read_file_handler so that when a path really exists, it returns the file's actual contents instead of falling through to None.
Why does read_file_handler return a plain error string for a missing file instead of letting Python raise a KeyError?
- →So the MCP server can package the failure as a normal tool result sent back to the client, instead of an unhandled exception crashing request handling.
- →Because Python is incapable of raising exceptions inside a function called from a dictionary.
Files Done, Tasks Next. read_file and list_files now serve a real, if small, project. DevAssist's second job is managing tasks — next lesson, create_task and list_tasks, backed by real in-memory state that changes as tools are called.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Return Empty Lists, Not Errors, for Zero Matches
A prefix that matches nothing is a normal outcome, not a failure — returning an empty list rather than an error lets any UI rendering results show 'no files found' cleanly instead of an alarming error state.
list_files_handler("nonexistent/") # -> []SEO Implications
- 1
Target 'MCP file system tool example' and 'read_file tool handler Python' separately
Developers building this exact tool pair search for the MCP framing and the plain implementation approach independently.
Best Practices
Keep Simulated State in Module-Level Data During Development
Building against an in-memory dict first lets you verify your handler logic and schemas end-to-end before wiring up a real filesystem, database, or network call underneath.
Frequent Bugs
Forgetting the guard clause and indexing FILESYSTEM[path] directly.
Without the `if path not in FILESYSTEM` check first, a missing path raises a raw KeyError instead of a clean tool-level error result.
Real-World Examples
Real Filesystem MCP Servers
Production filesystem MCP servers follow this exact shape — list_directory and read_file tools — but read from the real disk with path-traversal protection, which this masterclass adds explicitly in a later lesson.
os.path.realpath(path).startswith(PROJECT_ROOT)