Lesson 04 Your First MCP Server
Theme:

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:

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.