Homework solutions
Lesson 04 · MCP Servers · Solutions
Reference answers for the three homework tasks. After each solution, a common mistake block highlights the failure mode we saw most often when trialling this lesson.
Solution to Task 1 — reverse
After adding the second tool, your pkb/server.py should contain both
echo and reverse:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("pkb", version="0.1.0")
@mcp.tool()
def echo(message: str) -> str:
"""Echo the message back."""
return message
@mcp.tool()
def reverse(message: str) -> str:
"""Return the message with characters reversed."""
return message[::-1]
def main() -> None:
mcp.run()
if __name__ == "__main__":
main()
The [::-1] slice is the canonical Python idiom for reversing a string and works
identically on every CPython version we care about.
Common mistake
Forgetting the docstring. If you write
def reverse(message: str) -> str: with no docstring, FastMCP still registers the
tool, but it shows up in Claude Desktop and the Inspector without a description.
Claude will be far less likely to pick the tool, because the model has nothing to read about what
the tool does. Always write a one-line docstring — that single line becomes the tool description
the model sees.
Solution to Task 2 — repeat
Add a third decorator block. Both parameters are required positional arguments with type hints,
so FastMCP produces a two-property JSON Schema with both listed under required:
@mcp.tool()
def repeat(message: str, n: int) -> str:
"""Return the message repeated n times."""
return message * n
In the Inspector, the auto-generated form shows message as a text field and
n as a number field. Calling with {"message": "ab", "n": 3} returns the
string "ababab".
Common mistake
Default value of None on a typed parameter. If you write
def repeat(message: str, n: int = None) -> str:, FastMCP cannot decide what JSON
Schema type to emit for n — the annotation says int, but the default
says None. The fix is either drop the default, or change the annotation to
Optional[int] and pick a default of the matching type. Rule of thumb: always
give defaults of the same type as the annotation.
Solution to Task 3 — The echo JSON Schema
When you copy the inputSchema of the echo tool from the Inspector, you
should see something equivalent to this (field order may vary, but the keys are the same):
{
"type": "object",
"properties": {
"message": {"type": "string", "title": "Message"}
},
"required": ["message"],
"title": "echoArguments"
}
Walk through it once:
"type": "object"— all MCP tool argument schemas are JSON objects.properties.message.type == "string"— straight from themessage: strtype hint."required": ["message"]— because the parameter has no default value.titlefields — auto-generated names FastMCP uses for the wrapper model.
Common mistake
Confusing FastMCP's schema with raw Pydantic output. If you build a
BaseModel for the same shape and call .model_json_schema(), Pydantic
returns a schema that looks similar but uses $defs and richer metadata. FastMCP
wraps Pydantic and re-shapes the output so it is MCP-spec compliant — that is why your captured
schema is flatter than a raw Pydantic dump, and why the title values are normalised.
Lesson 05 turns this into proper Pydantic-typed schemas with error handling.