CourseModel Context Protocol · Module 5: Building Servers · part 30 of 83
Part 30 · Module 5: Building Servers

Topic 3: Server structure

22 min read·22 Sept 2026

Separating protocol handling from business logic

Look at what server.py does not contain: no file reading, no ranking, no slug rules, no frontmatter parsing. All of that is in NoteStore. Each handler in server.py does four things and nothing else: receive validated arguments, call one store method, translate store errors into MCP errors, and shape the result.

This split pays for itself three ways:

  • Tests get cheaper. The 20 NoteStore unit tests in Part 3 run in about 0.06 s with no event loop and no MCP at all. Rules such as "never overwrite a note" and "reject ../ in ids" are tested where they live.
  • build_server(store) takes its dependency as an argument. This is plain dependency injection: tests hand it a store over a temporary folder, the Module Lab hands it a copy of the notes, and main() hands it the real folder. Nothing inside the server reads NOTES_DIR or any other global.
  • The protocol layer can change without touching the rules. Module 7 adds authentication to server.py and Module 11 hardens it; store.py does not change in either.
SituationUse thisWhy
A rule about notes (valid ids, no overwrites, ranking)NoteStore in store.pyOne place to test it; every caller (server, scripts, future CLI) gets it
Turning a failure into something the model can readthe handler in server.py (except InvalidNote: raise ToolError)Error vocabulary is a protocol concern
Where the notes live, which port to useconfig.py, read once in main()Configuration is deployment-specific and must not leak into logic or tests
A dependency tests need to replacea parameter of build_serverTests pass their own; no monkeypatching of globals

The rest of this course is yours to keep

This course is bought on its own, once, and stays readable afterwards, including the parts added to it later.