Recall Modules 5 through 8 — every real piece is now on the table. This module’s entire job is assembly: building one, real, complete server, growing it deliberately, version by version, so you feel exactly why each real addition earns its place.
Version 1: the minimal, real server
Let’s start with the smallest, real thing that actually runs — no capabilities yet, just a genuine, working server.
from fastmcp import FastMCP
mcp = FastMCP("support-server") # a real, named server, with nothing exposed yet
if __name__ == "__main__":
mcp.run() # genuinely starts the server, using stdio by default
Run this, and it works — a real, live MCP server, connectable by any real Client, exposing genuinely nothing useful yet.
Version 2: adding a real tool
Let’s add the first, real, useful capability.
We’ll add a genuine order-status lookup, the same real pattern from Module 5.
from fastmcp import FastMCP
mcp = FastMCP("support-server")
ORDERS = {"O1": "Shipped", "O2": "Processing"}
@mcp.tool()
def get_order_status(order_id: str) -> str:
"""Look up the real, current status of an order."""
return ORDERS.get(order_id, f"No order found with ID {order_id}")
if __name__ == "__main__":
mcp.run()
Version 3: adding a real resource
Let’s add real, read-only context alongside the tool — recall Module 6’s own precise distinction.
We’ll expose the company’s real return policy as genuine, readable context.
@mcp.resource("policy://returns")
def return_policy() -> str:
"""The company's real, current return policy."""
return "Returns are accepted within 30 days of purchase, with a valid receipt."
Notice this real addition doesn’t touch anything from Version 2 at all — Tools and Resources genuinely coexist on the same server, each doing its own, distinct job.
Version 4: adding a real prompt
Let’s add the third, real capability type — recall Module 7’s own reusable, tuned templates.
We’ll expose a genuine, consistent prompt for drafting real customer responses.
@mcp.prompt()
def draft_response(order_id: str, status: str) -> str:
"""A real, reusable prompt for drafting a customer-facing status update."""
return f"Write a brief, friendly update to a customer about order {order_id}, whose real status is: {status}."
The server now genuinely exposes all three real capability types at once — a tool that acts, a resource that informs, and a prompt that shapes how a real response gets written.
Version 5: real error handling and logging
Let’s make this genuinely production-shaped, rather than a fragile demo — recall your own resilience coursework’s real discipline.
We’ll add real, deliberate error handling and genuine, structured logging to the existing tool.
import logging
from fastmcp import FastMCP
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("support-server")
mcp = FastMCP("support-server")
ORDERS = {"O1": "Shipped", "O2": "Processing"}
@mcp.tool()
def get_order_status(order_id: str) -> dict:
"""Look up the real, current status of an order."""
logger.info(f"Looking up order: {order_id}") # a real, genuine log entry
if order_id not in ORDERS:
logger.warning(f"Order not found: {order_id}") # a real, honest warning, not a silent failure
return {"error": f"No order found with ID {order_id}"}
return {"order_id": order_id, "status": ORDERS[order_id]}
@mcp.resource("policy://returns")
def return_policy() -> str:
"""The company's real, current return policy."""
return "Returns are accepted within 30 days of purchase, with a valid receipt."
@mcp.prompt()
def draft_response(order_id: str, status: str) -> str:
"""A real, reusable prompt for drafting a customer-facing status update."""
return f"Write a brief, friendly update to a customer about order {order_id}, whose real status is: {status}."
if __name__ == "__main__":
mcp.run()
Recall Module 5’s own real lesson about tool errors — this final, real version returns an honest, structured error rather than crashing, and logs both the real request and any genuine failure, exactly the observability discipline a real, deployed server actually needs.
The real, complete growth, seen at a glance
flowchart LR
A[V1: Minimal server] --> B[V2: + Tool]
B --> C[V3: + Resource]
C --> D[V4: + Prompt]
D --> E[V5: + Error handling + logging]
Common mistakes worth avoiding
Adding every capability at once, in one large, untested file. Recall this module’s own deliberate progression — building and verifying one, real capability at a time makes any real failure immediately traceable to what you just added.
Skipping logging until something genuinely breaks in production. Recall Version 5’s own real, direct addition — structured logging costs almost nothing to add early, and becomes genuinely invaluable the first time a real, remote user reports something going wrong.
Forgetting if __name__ == "__main__":. Without it, a real server module can be imported elsewhere without accidentally starting a live server — worth keeping this guard even in a small, real file.
What you should take away from this module
- A real MCP server grows genuinely incrementally — start minimal, verify it runs, then add exactly one capability at a time.
- Tools, Resources, and Prompts genuinely coexist on the same server, each handling its own, distinct real responsibility.
- Real, structured logging and honest, returned errors — not crashes — are what separate a genuine, production-shaped server from a fragile demo.
Where this goes next
The next module covers the other half of every connection: Building an MCP Client — connecting, discovering capabilities, and actually calling the real server you just built.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed