Latest AI Techniques: RAG, Tool Use, Agents & MCPThe Model Context Protocol (MCP) and open agent standards · Lesson 16 of 20

Building your first MCP server

Article · 15 min · 9 min lecture

Video lecture

Building your first MCP server

15 chapters · about 9 min · full transcript

Coming soon

Chapter 1 of 15

Build your first MCP server

  • Design → code → test → connect → harden
  • A few dozen lines
  • Safe by construction

The narrated lecture is in production

Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.

Chapters

Why build your own server

Ready-made MCP servers exist for many popular products, but the systems that make your business different (your price list, your booking rules, your client portal, your internal knowledge) need servers you control. Building one is surprisingly small: with the official SDKs, a useful server is a few dozen lines. This lesson walks through a first server end to end: design, code, test, connect and harden. The flagship Model Context Protocol (MCP) course covers remote deployment, OAuth, extensions and scaling in depth; here the goal is a solid, safe first build.

Step 1: design before code

Apply the tool design principles from module 3. For a small photography studio's booking system, a good first server might expose:

PrimitiveNamePurposeRisk
Toolcheck_availability(date, package)Free slots for a date and packageRead
Toolhold_slot(date, time, package, client_email)Places a 24-hour provisional hold, never a confirmed bookingWrite (low)
Resourcepackages://listCurrent packages, durations and price rangesRead
Promptdraft_booking_replyTemplate for replying to an enquiry in the studio's voiceNone

Notice what is not there: no cancellation, no refunds, no payments. Start with read tools and one low-risk write tool; expand only with evidence.

Step 2: write the server (Python SDK v2)

The official Python SDK's current major version (v2) supports the 2026-07-28 specification as well as earlier revisions. Install it with the CLI extra:

uv add "mcp[cli]"        # or: pip install "mcp[cli]"
# studio_server.py
import datetime as dt
import re
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError

mcp = MCPServer("Lens & Light Studio")

PACKAGES = {
    "mini": {"minutes": 30, "price_range": "AED 450-600"},
    "portrait": {"minutes": 60, "price_range": "AED 900-1,200"},
    "family": {"minutes": 90, "price_range": "AED 1,400-1,800"},
}
BOOKED = {("2026-10-03", "10:00"), ("2026-10-03", "14:00")}   # replace with your booking system
OPENING = ["10:00", "12:00", "14:00", "16:00"]
EMAIL = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")

def _valid_date(date: str) -> dt.date:
    try:
        d = dt.date.fromisoformat(date)
    except ValueError:
        raise ToolError("date must be YYYY-MM-DD, for example 2026-10-03")
    if d < dt.date.today():
        raise ToolError("date is in the past; ask the client for a future date")
    return d

@mcp.tool()
def check_availability(date: str, package: str) -> dict:
    """Return free start times for one date and package. Read-only.
    package must be one of: mini, portrait, family. date is YYYY-MM-DD."""
    _valid_date(date)
    if package not in PACKAGES:
        raise ToolError(f"unknown package; choose one of {sorted(PACKAGES)}")
    free = [t for t in OPENING if (date, t) not in BOOKED]
    return {"date": date, "package": package, "free_times": free}

@mcp.tool()
def hold_slot(date: str, time: str, package: str, client_email: str) -> dict:
    """Place a 24-hour PROVISIONAL hold (not a confirmed booking; staff confirm later).
    Only use after the client has chosen a time and given an email address."""
    _valid_date(date)
    if time not in OPENING or (date, time) in BOOKED:
        raise ToolError("that time is not available; call check_availability first")
    if not EMAIL.fullmatch(client_email):
        raise ToolError("client_email does not look valid; ask the client to re-type it")
    BOOKED.add((date, time))
    return {"status": "held_24h", "date": date, "time": time, "package": package}

@mcp.resource("packages://list")
def list_packages() -> dict:
    """Current packages with durations and price ranges."""
    return PACKAGES

@mcp.prompt()
def draft_booking_reply(client_name: str, question: str) -> str:
    """Draft a warm, concise reply to a booking enquiry in the studio's voice."""
    return (f"Write a friendly reply to {client_name} (max 90 words). Answer: {question}. "
            "Offer to check availability. Never promise discounts or confirmed bookings.")

if __name__ == "__main__":
    mcp.run()                      # stdio transport by default

Type hints and docstrings become the tool's schema and description. Raise ToolError for failures the model can fix: the SDK returns your message as a tool result flagged as an error, so the model can read it and try again. Any other exception is treated as a crash, and the model learns only that the call failed, so reserve those for genuine bugs.

Step 3: test it

  1. Interactively: uv run mcp dev studio_server.py opens the MCP Inspector, which launches your server and lets you list tools, call them with arguments and read resources.
  2. Automatically: the SDK's Client can connect to your server object in memory, so you can write ordinary tests:
import pytest
from mcp import Client
from studio_server import mcp

@pytest.mark.anyio
async def test_rejects_bad_date():
    async with Client(mcp) as client:
        result = await client.call_tool("check_availability", {"date": "03/10/2026", "package": "mini"})
        assert result.is_error
  1. With a model: connect the server to a host you use (many desktop chat apps and IDEs accept a local command such as uv run studio_server.py) and try realistic requests, including awkward ones: "book me for yesterday", "the family one, whatever time", "cancel someone else's booking".

Step 4: deploy remotely (when you need to)

For a server that several people or apps share, run it over Streamable HTTP: mcp.run(transport="streamable-http", port=3001) serves the endpoint at /mcp. Before exposing it beyond your machine, add authentication and authorisation per the current spec (OAuth-based for protected servers), TLS, rate limits, logging and the transport security settings the SDK documents. Because the 2026-07-28 revision is stateless, remote servers can run as several identical instances behind a normal load balancer.

Step 5: harden

  • Validate every argument (the examples above do); never build SQL or shell commands from model-supplied strings.
  • Enforce the calling user's permissions inside the server, using the identity your auth layer provides.
  • Keep destructive operations out of v1, or behind explicit tools that hosts can gate with approval.
  • Log each call with arguments, result size and caller; review the logs weekly for the first month.
  • Version the server and write a changelog; hosts and security reviewers need to know when tool descriptions change.

Key takeaways

  • Design MCP servers like good tools: task-level, read-first, one low-risk write tool, no destructive actions in v1.
  • With the official Python SDK, typed functions and docstrings become tools, resources and prompts with little code.
  • Test interactively with the MCP Inspector, automatically with an in-memory client, and with realistic model requests.
  • Before remote deployment add authorisation per the current spec, TLS, rate limits, logging and per-user permission checks.

Check your understanding

Quick questions to lock in the lesson. They don’t count towards your certificate.

  1. What should the first version of a booking MCP server usually expose?
  2. How does the Python SDK learn a tool’s input schema and description?
  3. What must you add before exposing a server over Streamable HTTP to other people?

Put it into practice

Design a four-item MCP server (two tools, one resource, one prompt) for a system you use. Build it with the SDK, open it in the MCP Inspector and write one automated test for an invalid input.

Enrol for free to save your progress

Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.