I built my first Model Context Protocol (MCP) server on a Saturday morning with zero prior experience, and I want to save you the three hours of confusion I went through. This tutorial walks absolute beginners from an empty folder to a working tool that Cursor (the AI code editor) can call as if it were a native function. We will wrap a simple REST API into an MCP server using FastAPI, then point Cursor at it. Every step has a screenshot hint in the prose so you know exactly what your screen should look like.
What is MCP, in plain English?
MCP stands for Model Context Protocol. Think of it as a USB-C cable for AI assistants: instead of writing a separate integration plugin for every editor, you build one small server that exposes "tools" (named functions with inputs and outputs), and any MCP-compatible client (Cursor, Claude Desktop, Continue.dev, Zed) can plug into it. When you ask Cursor "summarize the latest GitHub issues," Cursor calls your MCP tool, gets the result back, and weaves it into its answer.
The official MCP spec uses JSON-RPC over stdio or HTTP. For Cursor, the easiest transport is stdio: Cursor launches your Python script as a subprocess and talks to it line by line. We will wrap that subprocess-launcher with a tiny FastAPI wrapper so the same tool is also callable over HTTP for debugging.
Prerequisites (install in this order)
- Python 3.10 or newer — download from python.org, tick "Add to PATH" in the installer (screenshot hint: the very first checkbox on the installer's first screen).
- Cursor editor — cursor.sh, the free Hobby tier is enough.
- A HolySheep AI account — sign up here to get an API key. HolySheep charges ¥1 per $1 (saves 85%+ versus the industry-standard ¥7.3 per $1), supports WeChat and Alipay, and returns responses in under 50 ms of network latency. New accounts get free credits on signup so you can test without a card.
Open a terminal (macOS: Terminal.app, Windows: PowerShell, Linux: your favorite) and run:
python --version
pip --version
You should see something like Python 3.11.9 and pip 24.0. If either command says "not found," reinstall Python with the "Add to PATH" box ticked.
Step 1 — Create a project folder and a virtual environment
A virtual environment keeps this project's libraries isolated so they do not conflict with anything else on your machine.
mkdir mcp-holysheep-demo
cd mcp-holysheep-demo
python -m venv .venv
macOS / Linux
source .venv/bin/activate
Windows PowerShell
.\.venv\Scripts\Activate.ps1
Screenshot hint: your terminal prompt should now begin with (.venv). If it does not, the activation command failed — close the terminal and reopen it.
Step 2 — Install the libraries
pip install --upgrade pip
pip install fastapi uvicorn httpx "mcp[cli]" pydantic
What each one does, in one line:
fastapi— the web framework that turns Python functions into HTTP endpoints.uvicorn— the server that actually runs FastAPI.httpx— a modern HTTP client, used to call HolySheep.mcp[cli]— the official Model Context Protocol SDK plus a CLI for testing.pydantic— already a FastAPI dependency, but we list it so beginners see the version.
Step 3 — Write the REST API we want to wrap
For this tutorial our "REST API" is a single function that asks HolySheep to summarize a piece of text. Save this as holysheep_client.py:
# holysheep_client.py
import os
import httpx
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
def summarize(text: str, model: str = "gpt-4.1") -> str:
"""Send text to HolySheep and return a one-paragraph summary."""
headers = {
"Authorization": f"Bearer {HOLYSHEEP_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": model,
"messages": [
{"role": "system", "content": "You are a concise summarizer. Reply in one paragraph."},
{"role": "user", "content": f"Summarize the following text:\n\n{text}"},
],
"max_tokens": 300,
"temperature": 0.2,
}
with httpx.Client(timeout=30.0) as client:
r = client.post(
f"{HOLYSHEEP_BASE_URL}/chat/completions",
headers=headers, json=payload,
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"].strip()
if __name__ == "__main__":
sample = ("MCP stands for Model Context Protocol. "
"It is a standard way to expose tools to AI editors.")
print(summarize(sample))
Set your key as an environment variable so it never lands in source control:
# macOS / Linux
export HOLYSHEEP_API_KEY="hs-xxxxxxxxxxxxxxxxxxxxxxxx"
Windows PowerShell
$env:HOLYSHEEP_API_KEY="hs-xxxxxxxxxxxxxxxxxxxxxxxx"
Run python holysheep_client.py. If you see a one-paragraph summary print out, the REST wrapper works. Screenshot hint: the output should be a single paragraph of plain English, not an error traceback.
Step 4 — Wrap it as an MCP server
Now we expose the same summarize function through MCP. Save as mcp_server.py:
# mcp_server.py
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
from holysheep_client import summarize
app = Server("holysheep-summarizer")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="summarize_text",
description=("Summarize a piece of text using HolySheep AI. "
"Returns a single concise paragraph."),
inputSchema={
"type": "object",
"properties": {
"text": {"type": "string",
"description": "The text to summarize. Up to ~8000 words."},
"model": {"type": "string",
"enum": ["gpt-4.1", "claude-sonnet-4.5",
"gemini-2.5-flash", "deepseek-v3.2"],
"default": "gpt-4.1",
"description": "Which HolySheep-hosted model to call."},
},
"required": ["text"],
},
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name != "summarize_text":
raise ValueError(f"Unknown tool: {name}")
result = summarize(arguments["text"], arguments.get("model", "gpt-4.1"))
return [TextContent(type="text", text=result)]
async def main():
async with stdio_server() as (read, write):
await app.run(read, write, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
This is the smallest possible MCP server: one tool called summarize_text, two parameters, and a single text result.
Step 5 — Add a FastAPI debug wrapper (optional but useful)
When something breaks it is much easier to debug over HTTP than over stdio. Add debug_server.py:
# debug_server.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from holysheep_client import summarize
app = FastAPI(title="HolySheep MCP Debug Server")
class SummarizeRequest(BaseModel):
text: str = Field(..., min_length=1, max_length=200000)
model: str = Field(default="gpt-4.1")
@app.post("/summarize")
def summarize_endpoint(req: SummarizeRequest):
try:
return {"summary": summarize(req.text, req.model)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8765)
Run it with python debug_server.py, then in a second terminal:
curl -X POST http://127.0.0.1:8765/summarize \
-H "Content-Type: application/json" \
-d '{"text":"MCP servers let editors call your tools."}'
Screenshot hint: you should see a JSON response containing a "summary" field with one paragraph of text.
Step 6 — Connect Cursor to the MCP server
Open Cursor → Settings (gear icon, bottom-left) → Features → Model Context Protocol. Screenshot hint: the panel has a small "Add new MCP server" button at the top right.
Click it and paste this configuration, changing /Users/you/code/mcp-holysheep-demo to your actual project path:
{
"mcpServers": {
"holysheep-summarizer": {
"command": "python",
"args": ["/Users/you/code/mcp-holysheep-demo/mcp_server.py"],
"env": {
"HOLYSHEEP_API_KEY": "hs-xxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
Click Save, then toggle the green switch next to holysheep-summarizer so it turns on. Cursor will spawn your Python script as a subprocess. Screenshot hint: the indicator next to the server name should be green and say "Connected." If it stays yellow or red, jump to the error section below.