Implementing Model Context Protocol (MCP) Servers and Clients from Scratch: A Complete Guide

The Model Context Protocol (MCP) enables any LLM host to discover and invoke external tools, resources, and prompts through a standardized JSON‑RPC 2.0 interface, implemented via three core primitives registered on an MCPServer and consumed by an MCPClient.

Implementing Model Context Protocol (MCP) servers and clients from scratch allows you to expose custom capabilities to LLM hosts like Claude Desktop or ChatGPT without vendor‑lock. According to the rohitg00/ai-engineering-from-scratch repository, MCP has emerged as the de‑facto standard for local and remote tool integration, using Python type hints to auto‑generate JSON schemas and supporting both stdio and HTTP transports.

What Is the Model Context Protocol (MCP)?

MCP is an open specification that defines how an LLM host (the application) communicates with external servers that provide tools, resources, and prompts. In phases/11-llm-engineering/14-model-context-protocol/docs/en.md, the protocol is described as a JSON‑RPC 2.0 wire format where:

  • Tools are executable functions (like add or delete_user) that perform side effects or calculations.
  • Resources are read‑only data sources (like config://app) that provide context without executing code.
  • Prompts are reusable templates (like code_review) that standardize common interactions.

The server advertises these primitives via JSON schemas derived from Python type hints, and the client drives discovery and invocation through a well‑defined handshake.

Core Architecture of MCP

The Three Logical Components

Every MCP deployment consists of three distinct roles:

Component Responsibility Implementation
Server Registers tools, resources, and prompts; handles JSON‑RPC requests MCPServer class in code/main.py (lines 48‑58)
Client Manages connection, discovery, and invocation; embedded in the host MCPClient class in code/main.py (lines 31‑44)
Host The LLM application that translates model‑generated tool_use blocks into tools/call RPCs Orchestrates the client and feeds it tool schemas

The Handshake and Discovery Flow

Before invocation, the client and server perform a capability exchange. In phases/11-llm-engineering/14-model-context-protocol/code/main.py (lines 84‑90), the client sends an initialize request containing its protocol version, and the server responds with its name, version, and advertised capabilities.

Following initialization, the host queries the server for available primitives:

  1. tools/list – Returns JSON schemas for every function registered with @server.tool().
  2. resources/list – Returns URI patterns for read‑only data registered with @server.resource().
  3. prompts/list – Returns templates registered with @server.prompt().

The server builds these schemas automatically from Python type hints supplied to the registration decorators (lines 59‑75).

Invocation and Transport

When the LLM emits a tool_use block, the host calls tools/call with the tool name and arguments. The server executes the registered handler and returns a JSON‑encoded result (lines 101‑105).

MCP supports two transport mechanisms:

  • Stdio – A child process that streams JSON‑RPC messages over stdin/stdout. Ideal for local development and simple integrations.
  • Streamable HTTP – A POST‑based HTTP server with optional Server‑Sent Events for progress updates. Recommended for remote SaaS tools and production deployments.

Building an MCP Server from Scratch

Minimal Server with FastMCP

For production use, the FastMCP class provides a high‑level decorator interface. This example from the lesson docs registers a calculator tool, a configuration resource, and a code review prompt:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("demo-server")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

@mcp.resource("config://app")
def app_config() -> str:
    """Return the app's current JSON config."""
    return '{"env": "prod", "region": "us-east-1"}'

@mcp.prompt()
def code_review(language: str, code: str) -> str:
    """Review code for correctness and style."""
    return f"You are a senior {language} reviewer. Review:\n\n{code}"

Source: phases/11-llm-engineering/14-model-context-protocol/docs/en.md (lines 46‑70)

In-Process Server and Client

The repository ships a dependency‑free reference implementation in code/main.py that demonstrates registration, discovery, and invocation without external libraries:


# Build a demo server

server = MCPServer("demo-server")

@server.tool(
    name="add",
    description="Add two integers and return the sum.",
    schema={"type": "object", "properties": {"a": {"type": "integer"},
                                             "b": {"type": "integer"}},
            "required": ["a", "b"]},
)
def add(a: int, b: int) -> dict[str, int]:
    return {"sum": a + b}

# In-process client

client = MCPClient(server)
init = client.request("initialize", {"protocolVersion": PROTOCOL_VERSION,
                                    "clientInfo": {"name": "demo-client"}})
tools = client.request("tools/list")["tools"]
add_result = client.request("tools/call",
                            {"name": "add", "arguments": {"a": 40, "b": 2}})
print(add_result["content"][0]["text"])   # → {"sum": 42}

Source: phases/11-llm-engineering/14-model-context-protocol/code/main.py (lines 53‑66)

Production Deployment with HTTP Transport

To expose your server over the network, switch from stdio to the streamable HTTP transport:

if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8765)

Source: phases/11-llm-engineering/14-model-context-protocol/docs/en.md (lines 97‑104)

Safety Patterns and Security Controls

Implementing Model Context Protocol (MCP) servers and clients from scratch requires strict safety controls to prevent malicious exploitation. The rohitg00/ai-engineering-from-scratch repository mandates three defensive patterns:

  • Capability allowlists – Restrict which filesystem or network paths a tool may access, ensuring tools cannot read sensitive files outside their scope.
  • Human‑in‑the‑loop for destructive actions – Annotate dangerous tools with destructiveHint: true to force UI confirmation before execution.
  • Tool‑poisoning defense – Treat resource content as untrusted data; never inject resources directly into system messages without sanitization.

Summary

  • MCP standardizes tool, resource, and prompt exposure via JSON‑RPC 2.0, implemented through the MCPServer and MCPClient classes in code/main.py.
  • Three primitives define the protocol: executable tools, read‑only resources, and template prompts, registered via decorators that introspect Python type hints.
  • Two transports are available: stdio for local development and streamable HTTP for production SaaS deployments.
  • Security is mandatory—implement capability allowlists, destructive action confirmations, and resource sanitization to prevent tool poisoning.

Frequently Asked Questions

What is the Model Context Protocol (MCP) used for?

MCP provides a universal interface for LLM hosts to discover and invoke external capabilities. It allows developers to expose local scripts, database queries, or API integrations as standardized tools that any compatible AI assistant can use, eliminating the need for custom plugins per platform.

How does MCP differ from traditional REST API integrations?

Unlike REST APIs that require manual schema documentation and HTTP client configuration, MCP uses JSON‑RPC 2.0 with automatic schema generation from Python type hints. The protocol includes built‑in discovery mechanisms (tools/list, resources/list) and supports bidirectional streaming via stdio or HTTP, whereas REST typically operates over HTTP only with static OpenAPI specs.

What transport protocols does MCP support?

MCP supports two primary transports: stdio for local subprocess communication (ideal for desktop applications like Claude Desktop) and streamable HTTP for remote network access (suitable for cloud‑hosted services). The transport is configurable via the run() method in the FastMCP server implementation.

How do I secure an MCP server against malicious tool calls?

Secure your server by implementing capability allowlists to restrict filesystem and network access, marking destructive operations with destructiveHint to trigger human confirmation, and sanitizing all resource content before injection into prompts. These patterns are documented in phases/11-llm-engineering/14-model-context-protocol/docs/en.md and are essential for production deployments.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →