# How to Set Up an MCP Server for AI Projects: A Complete Guide

> Learn to set up an MCP server for AI projects. Follow this guide to implement a JSON-RPC dispatch loop and expose tools resources and prompts via stdio or HTTP.

- Repository: [Rohit Ghumare/ai-engineering-from-scratch](https://github.com/rohitg00/ai-engineering-from-scratch)
- Tags: how-to-guide
- Published: 2026-07-19

---

**Setting up a Model Context Protocol (MCP) server requires implementing a JSON-RPC 2.0 dispatch loop that exposes tools, resources, and prompts via stdio or HTTP transport, starting with the `initialize` handshake and following three strict rules for message handling.**

The Model Context Protocol (MCP) has emerged as the de-facto standard for connecting AI systems to external tools and data sources. This guide walks through the complete implementation found in the `rohitg00/ai-engineering-from-scratch` repository, providing both the foundational stdio server and production-ready graduation paths using FastMCP and HTTP transports.

## Core Architecture of an MCP Server

An MCP server acts as a bridge between LLM clients and external capabilities. According to the source code in [`phases/13-tools-and-protocols/07-building-an-mcp-server/code/main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/13-tools-and-protocols/07-building-an-mcp-server/code/main.py), the architecture revolves around a strict JSON-RPC message protocol and three specialized registries.

### The Dispatch Loop

The heart of every MCP server is the dispatch loop defined in [`docs/en.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/docs/en.md). This loop reads newline-delimited JSON-RPC messages from `stdin`, routes them to appropriate handlers, and writes responses to `stdout`. The implementation follows three inviolable rules:

1. **Only JSON-RPC envelopes** may be printed to `stdout`; all debugging and logging must go to `stderr`.
2. **Every request** must be answered with a response carrying the same `id` as the incoming message.
3. **Notifications** (messages without an `id`) receive no response.

These rules ensure that any MCP-aware client—from Claude Code to the OpenAI SDK—can safely interoperate with your server without parsing ambiguity.

### The Initialize Handshake

Before serving tools or resources, the server must respond to the `initialize` method. In [`main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/main.py), the `handle_initialize` function returns the protocol version and capability flags:

```python
def handle_initialize(params: dict) -> dict:
    return {
        "protocolVersion": "2025-11-25",
        "capabilities": {
            "tools": {"listChanged": False},
            "resources": {"listChanged": False, "subscribe": False},
            "prompts": {"listChanged": False}
        },
        "serverInfo": {"name": "notes-lesson-07", "version": "1.0.0"},
    }

```

This handshake informs the client which features the server supports (tools, resources, prompts) and establishes the protocol version contract.

### The Three Registries

After initialization, the server exposes capabilities through three distinct registries located in [`main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/main.py):

- **Tool Registry** (`TOOLS` list, lines 36-73): Declares executable functions with JSON Schema input definitions and annotations. Tools can perform side effects or read-only queries.
- **Resource Registry** (`resources` list, lines 55-61): Exposes read-only data via URI schemes like `file://` or `notes://`.
- **Prompt Registry** (`prompts` list, lines 76-84): Supplies slash-command templates that clients can inject into LLM prompts.

## Step-by-Step MCP Server Setup

Follow these steps to set up your own MCP server using the reference implementation from the `ai-engineering-from-scratch` curriculum.

### 1. Clone the Repository and Navigate to the Lesson

Start by cloning the repository and moving into the specific lesson directory:

```bash
git clone https://github.com/rohitg00/ai-engineering-from-scratch.git
cd ai-engineering-from-scratch/phases/13-tools-and-protocols/07-building-an-mcp-server/

```

### 2. Run the Minimal stdio Implementation

The lesson requires only Python 3.11+ standard library dependencies. Verify the server works by running the built-in demo:

```bash
python code/main.py --demo

```

This command sends a sequence of `initialize`, `tools/list`, and `tools/call` messages to demonstrate the JSON-RPC flow.

### 3. Test the Server Manually

Drive the server manually using echo and pipes to verify custom payloads:

```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | python code/main.py

```

The server should return a JSON-RPC response with matching `id` and the initialization payload.

### 4. Extend with Custom Tools

To add functionality, modify [`code/main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/code/main.py) by extending the `TOOLS` list with a new entry containing `name`, `description`, `inputSchema`, and `annotations`. Then map the tool name to a Python function in the `TOOL_EXECUTORS` dictionary. The server automatically exposes the new capability through the `tools/list` and `tools/call` endpoints.

### 5. Graduate to FastMCP for Production

For production environments, collapse the 180-line stdlib implementation into less than 80 lines using the FastMCP Python SDK:

```python
from fastmcp import FastMCP

app = FastMCP("my-server")

@app.tool()
def my_tool(arg: str) -> list[dict]:
    return [{"type": "text", "text": f"Result for {arg}"}]

```

FastMCP handles the JSON-RPC framing, transport negotiation, and error handling automatically while maintaining full protocol compliance.

### 6. Deploy via HTTP for Horizontal Scaling

When you need stateless horizontal scaling behind a load balancer, replace the stdio transport with FastMCP's `StreamableHTTP` server. The capstone lesson in [`phases/19-capstone-projects/13-mcp-server-with-registry/outputs/skill-mcp-server.md`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/phases/19-capstone-projects/13-mcp-server-with-registry/outputs/skill-mcp-server.md) provides a production-grade scaffold that includes OAuth 2.1 scope matrices and OPA policy gates for destructive tools.

Deploy the HTTP server using Uvicorn:

```bash
uvicorn your_fastmcp_module:app --host 0.0.0.0 --port 8000

```

## Key Implementation Details from the Source Code

The reference implementation in [`code/main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/code/main.py) demonstrates the strict message handling required by the 2025-11-25 specification. The dispatch loop parses incoming JSON-RPC envelopes, validates the `method` field, and routes to handlers like `handle_initialize` or `handle_tools_call`. Each handler returns a dictionary that the loop wraps in the proper JSON-RPC response format with the original request `id`.

The `TOOLS` list defines the contract between your Python functions and the LLM client. Each tool entry includes an `inputSchema` following JSON Schema Draft 7, allowing clients to validate arguments before invoking the server. The `annotations` field provides hints about whether a tool is read-only or destructive, enabling client-side permission prompts.

## Summary

- **MCP servers** use a JSON-RPC 2.0 protocol over stdio or HTTP to expose tools, resources, and prompts to LLMs.
- **Three strict rules** govern the dispatch loop: stdout-only for JSON-RPC, matching request/response IDs, and no responses for notifications.
- **Initialize handshake** establishes capabilities and protocol version compliance before any tool execution.
- **FastMCP SDK** reduces boilerplate from 180 lines to under 80 while maintaining full specification adherence.
- **Production deployment** uses StreamableHTTP transport with OAuth 2.1 and OPA policies as detailed in Phase 19 of the curriculum.

## Frequently Asked Questions

### What is the difference between stdio and HTTP transport for MCP servers?

The stdio transport runs the server as a subprocess communicating via standard input and output, ideal for local desktop applications like Claude Code or Cursor. HTTP transport, specifically StreamableHTTP, exposes the server as a stateless web endpoint suitable for horizontal scaling and remote access behind load balancers.

### Do I need external dependencies to run the basic MCP server?

No. The lesson implementation in [`code/main.py`](https://github.com/rohitg00/ai-engineering-from-scratch/blob/main/code/main.py) uses only Python 3.11+ standard library modules. However, graduating to FastMCP requires installing the `fastmcp` package from PyPI, and HTTP deployment requires `uvicorn` or another ASGI server.

### How do I add authentication to my MCP server?

For stdio servers, authentication typically happens at the host application level. For HTTP deployments, the Phase 19 capstone in `phases/19-capstone-projects/13-mcp-server-with-registry/` demonstrates OAuth 2.1 scope validation and OPA (Open Policy Agent) gates for controlling access to destructive tools.

### Can an MCP server handle multiple concurrent connections?

The stdio implementation handles sequential messages from a single client. For concurrent connections, use the StreamableHTTP transport with FastMCP, which runs on ASGI servers like Uvicorn that handle concurrency through async workers or multiple processes.