Build Your Own MCP Server · 45 min · Python · MCP Python SDK (FastMCP) · httpx
Building Out the Tool Library
Four tools in one server means four different failure modes to get right, not one pattern copied four times.
Hiring signal: A one-tool MCP server is a demo. A five-tool server that mixes a database, the filesystem, a third-party API, and a webhook is what an actual internal-tools MCP server looks like in production.
What you will learn
- Build a filesystem tool with an explicit allowed-directory boundary
- Wrap a real external REST API as an MCP tool
- Build a data-transformation tool and a notification/webhook tool
- Write tool descriptions specific enough that Claude reliably picks the right one among five
Introduction
One tool proved the pattern works. Today you add four more — a filesystem tool, an external API wrapper, a data-transformation tool, and a notification tool — and immediately hit the real problem of a multi-tool server: with 5 tools instead of 1, Claude now has to choose, and vague descriptions that were merely sloppy at 1 tool become actively confusing at 5.
Tool 2 — filesystem, with a real boundary
A filesystem tool that can read/write anywhere is a server you shouldn't run. Scope it to one directory, explicitly, in code — not as a suggestion in the docstring.
import os
ALLOWED_DIR = os.path.expanduser("~/mcp-toolkit-workspace")
@mcp.tool()
def read_workspace_file(filename: str) -> str:
"""Read a file from the user's MCP workspace directory. Use this to
access files the user has explicitly saved there for the agent."""
path = os.path.join(ALLOWED_DIR, filename)
if not os.path.abspath(path).startswith(os.path.abspath(ALLOWED_DIR)):
return "Error: path escapes the allowed workspace directory"
if not os.path.exists(path):
return f"Error: {filename} not found in workspace"
with open(path, "r", encoding="utf-8", errors="replace") as f:
return f.read()
The os.path.abspath(...).startswith(...) check exists specifically to reject filename values like ../../etc/passwd — a model given a filesystem tool will occasionally construct exactly this kind of path, not out of malice, but because a user's phrasing led it somewhere unexpected. This check is not optional hardening for later; it's the difference between a filesystem tool and a filesystem vulnerability.
This is the tool students most often ship without a boundary
It is very tempting to write open(filename) directly against whatever path the model sends, because it works in your own testing. It works right up until a task phrasing leads the model somewhere you didn't intend. Scope every filesystem tool to one directory, check the resolved path stays inside it, and don't treat this as a "harden it later" item — do it now, in this lesson, before the tool leaves your test script.
Unlock the full lesson
You've read the first 2 sections. The rest of this lesson covers Tool 3 — wrap a real external API, Tool 4 — data transformation, Tool 5 — notification, What You're Building — plus a hands-on lab, quiz, and project artifact.
Create a free account to unlock Phase 0 and Phase 1 of every course — no credit card.
Browse all courses · View pricing · DeVenture Academy