Two tools, zero hand-written schemas
You have a running chatbot. Now you are going to give it the first two tools it will offer to Claude: one to read a document and one to edit it. The good news is that with the official MCP Python SDK, you will not write a single line of JSON schema by hand.
The @mcp.tool decorator turns a plain Python function into a fully-schematised MCP tool. The SDK inspects your type hints and your Pydantic Field descriptions and generates the schema Claude needs automatically. You write Python; the SDK does the protocol work.
Setting up the server
Open mcp_server.py. The server is initialised in a single line:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("DocumentMCP", log_level="ERROR")
That mcp object is the handle you will decorate every tool against.
Your fake document store is a dictionary keyed by document ID, with content as the value:
docs = {
"deposition.md": "This deposition covers the testimony of Angela Smith, P.E.",
"report.pdf": "The report details the state of a 20m condenser tower.",
"financials.docx": "These financials outline the project's budget and expenditures",
"outlook.pdf": "This document presents the projected future performance of the system",
"plan.md": "The plan outlines the steps for the project's implementation.",
"spec.txt": "These specifications define the technical requirements for the equipment"
}
Six documents, in memory, no persistence. That is the entire storage layer — deliberately trivial, so the MCP mechanics stay in focus.
Tool 1: Reading a document
The first tool takes a document ID and returns the content:
from pydantic import Field
@mcp.tool(
name="read_doc_contents",
description="Read the contents of a document and return it as a string."
)
def read_document(
doc_id: str = Field(description="Id of the document to read")
):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
Three things to notice. The decorator carries the tool's public identity — its name and description are what Claude sees. The Pydantic Field on the parameter contributes the per-argument description that ends up in the JSON schema. And the validation is just Python — if the document does not exist, raise a ValueError and let the MCP layer translate it into an error result.
Tool 2: Editing a document
The edit tool is a simple find-and-replace:
@mcp.tool(
name="edit_document",
description="Edit a document by replacing a string in the documents content with a new string."
)
def edit_document(
doc_id: str = Field(description="Id of the document that will be edited"),
old_str: str = Field(description="The text to replace. Must match exactly, including whitespace."),
new_str: str = Field(description="The new text to insert in place of the old text.")
):
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
docs[doc_id] = docs[doc_id].replace(old_str, new_str)
Same pattern as before: decorator with tool-level metadata, Field-annotated parameters with per-argument descriptions, existence check, then the actual logic. Notice how precise the old_str description is — must match exactly, including whitespace. Claude reads those descriptions when deciding how to call the tool, so invest in them.
The pattern to internalise
Every MCP tool you will ever write follows the same shape:
- Decorator —
@mcp.toolwithnameanddescription. - Function signature — typed parameters with
Field(description=...)for each argument. - Validation — guard against bad input, raise exceptions for anything the tool cannot handle.
- Core logic — the actual work, returning the result.
The SDK collapses everything that used to be JSON schema boilerplate into Python you would have written anyway. Your job is to pick good names, write good descriptions, and validate early.
Key Takeaways
- 1FastMCP lets you initialise an MCP server in a single line and register tools with the @mcp.tool decorator.
- 2The Python SDK auto-generates JSON schemas from type hints and Pydantic Field descriptions, eliminating manual schema writing.
- 3Tool names and descriptions are the contract Claude reads — invest in precise Field descriptions for each parameter.
- 4The repeatable pattern for every tool is decorator, typed parameters, validation, core logic.