On this page
- What Is an MCP Server? (And Why Python Developers Should Care)
- What You'll Build in This Tutorial
- Step 1 - Set Up Your Environment
- Step 2 - Initialize Your MCP Server
- Step 3 - Add Your First Tool (add\task)
- Step 4 - Add a Second Tool (complete\task)
- Step 5 - Add a Resource (list\tasks)
- Step 6 - Add a Prompt Template
- Step 7 - Run and Test Your MCP Server
- The Complete server.py (Full Code)
- Connect Your MCP Server to Claude Desktop (Real-World Usage)
- What to Build Next (MCP Server Ideas)
- Key Takeaways
- FAQ
- Useful Resources
In November 2024, Anthropic open-sourced a protocol that lets any AI assistant call your Python functions directly. Most developers still haven't heard of it.
That protocol is the Model Context Protocol (MCP) - and building an MCP server in Python is now one of the highest-leverage things you can do as a developer working with AI.
TL;DR
MCP (Model Context Protocol) is an open standard that lets AI assistants like Claude call your Python tools and read your data - like an API, but for AI.
You'll build a working Task Manager MCP server with 2 tools, 1 resource, and 1 prompt template.
Time required: ~20 minutes, start to finish.
You need: Python 3.10+, pip, and basic Python knowledge. No external API keys.
What Is an MCP Server? (And Why Python Developers Should Care)
MCP (Model Context Protocol) is an open standard, released by Anthropic in November 2024, that lets AI assistants call your tools and read your data through a single, unified interface. Think of it as an API layer - but designed specifically for AI clients. (If you want the conceptual grounding first, see what is Model Context Protocol.)
The analogy that sticks: MCP is the USB-C of AI integrations. One protocol, any AI client. Before MCP, every AI integration was a custom one-off. Now you write the server once and any MCP-compatible client - Claude, Cursor, Windsurf, and increasingly ChatGPT - can use it.
The three core primitives
Tools - Functions the AI can call. Think:
add_task(),run_sql_query(),send_email().Resources - Data the AI can read. Think: a live database snapshot, a file, a list of records.
Prompts - Reusable instruction templates. Think: "Summarize all pending tasks in bullet form."
Imagine Claude reading your live Postgres database or triggering a Python function mid-conversation. That's not a demo - that's exactly what MCP enables today.
MCP vs. traditional API - what's the difference?
A REST API requires the human to call it and pass results to the AI. With MCP, the AI itself decides when to call your tool, calls it, and uses the result - all inside a single conversation turn. No glue code. No manual copy-paste.
What You'll Build in This Tutorial
We're building a Task Manager MCP server - a self-contained Python MCP server example that exposes:
add_tasktool - creates a new task with a title and optional descriptioncomplete_tasktool - marks a task as done by IDtasks://allresource - returns all tasks as a readable listtask_summary_promptprompt - a reusable template for asking the AI to summarize pending work
Why this example? It's practical, it covers all three MCP primitives, and it needs zero external API keys. You can run it right now on your laptop.
Expected time: ~20 minutes.
Prerequisites:
Python 3.10 or higher
pipinstalledBasic Python knowledge (functions, decorators, type hints)
Claude Desktop (optional, for real-world testing)
Step 1 - Set Up Your Environment
01. Install uv - the fast Python package manager we'll use throughout.
pip install uv
# or, on macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
02. Create your project:
uv init mcp-task-server && cd mcp-task-server
03. Create and activate a virtual environment:
uv venv && source .venv/bin/activate
# Windows:
# .venv\Scripts\activate
04. Install FastMCP:
uv add fastmcp
FastMCP is the official high-level Python framework for building MCP servers. It wraps the low-level MCP Python SDK so you write decorators instead of boilerplate. It's the fastest path from idea to working mcp python server.
Your final folder structure:
mcp-task-server/
├── .venv/
├── server.py ← we'll create this
└── pyproject.toml
Step 2 - Initialize Your MCP Server
Create server.py and add these two lines:
from fastmcp import FastMCP
mcp = FastMCP("Task Manager") # names your server; registers it as an MCP-compliant endpoint
That's it. FastMCP("Task Manager") names the server, sets up the MCP handshake, and prepares the decorator registry. Everything else - tools, resources, prompts - gets added on top of this instance.
Note: This 2-line init is all the boilerplate you need. No class inheritance, no manual schema definitions. FastMCP handles the MCP spec compliance for you.
Step 3 - Add Your First Tool (add_task)
Tools are functions the AI can call. You register them with the @mcp.tool() decorator. The AI sees your function's name, type hints, and docstring - so write the docstring clearly.
First, set up an in-memory task store:
tasks = [] # holds all task dicts
task_counter = 0 # auto-incrementing ID
Now add the add_task tool:
from datetime import datetime
@mcp.tool()
def add_task(title: str, description: str = "") -> dict:
"""Add a new task to the task manager. Returns the created task."""
global task_counter
task_counter += 1 # increment ID before creating the task
task = {
"id": task_counter,
"title": title,
"description": description,
"status": "pending",
"created_at": datetime.now().isoformat(), # ISO 8601 timestamp
}
tasks.append(task)
return task # the AI receives this dict as the tool result
The AI sees your function's docstring as its description - write it clearly. A vague docstring means the AI won't know when to call your tool. Be specific about what the tool does and what it returns. (For the deeper craft here, see our guide to writing effective MCP tool schemas.)
Step 4 - Add a Second Tool (complete_task)
Same pattern - @mcp.tool(), clear docstring, typed parameters.
@mcp.tool()
def complete_task(task_id: int) -> dict:
"""Mark a task as completed by its ID. Returns the updated task or an error."""
for task in tasks:
if task["id"] == task_id: # find the matching task
task["status"] = "completed"
task["completed_at"] = datetime.now().isoformat() # record completion time
return task
# task not found - return a structured error so the AI can handle it gracefully
return {"error": f"Task with ID {task_id} not found."}
Always handle the not-found case. If you raise an unhandled exception, the AI gets a cryptic error. Return a structured dict with an "error" key instead - the AI can read it and respond sensibly.
Step 5 - Add a Resource (list_tasks)
Resources are data the AI can read - think of them like GET endpoints in a REST API. You register them with @mcp.resource() and give them a URI. The AI client references that URI to fetch the data.
@mcp.resource("tasks://all") # URI the AI uses to request this resource
def list_tasks() -> str:
"""Returns all tasks as a formatted string."""
if not tasks:
return "No tasks yet."
lines = []
for task in tasks:
# format each task as a readable line
status_icon = "✅" if task["status"] == "completed" else "⏳"
lines.append(f"{status_icon} [{task['id']}] {task['title']} - {task['status']}")
return "\n".join(lines)
The URI "tasks://all" is how AI clients reference this resource in conversation. You can define multiple resources with different URIs - for example, "tasks://pending" for filtered views.
Step 6 - Add a Prompt Template
Prompts are reusable instruction templates. They're optional, but powerful for keeping AI behavior consistent across sessions. Register them with @mcp.prompt().
@mcp.prompt()
def task_summary_prompt() -> str:
"""A reusable prompt asking the AI to summarize pending tasks."""
return (
"You are a productivity assistant. "
"Read the tasks://all resource and provide a concise summary of all PENDING tasks. "
"Group them by priority if possible. "
"End with a recommended next action."
)
Prompts are optional - your MCP python server works fine without them. But if you're building for a team or a product, prompts lock in the AI's behavior so every user gets a consistent experience.
Step 7 - Run and Test Your MCP Server
Add the run block at the bottom of server.py:
if __name__ == "__main__":
mcp.run() # starts the MCP server using stdio transport by default
Run the server:
python server.py
# or, using the FastMCP CLI:
fastmcp run server.py
Test interactively in the browser:
fastmcp dev server.py
fastmcp dev launches a built-in browser UI where you can call your tools manually and inspect responses - no AI client needed.
Or test programmatically with a Python client:
import asyncio
from fastmcp import Client
async def test_server():
async with Client("server.py") as client:
# add a task
result = await client.call_tool("add_task", {"title": "Write blog post", "description": "MCP tutorial"})
print("Added:", result)
# complete it
result = await client.call_tool("complete_task", {"task_id": 1})
print("Completed:", result)
# read the resource
tasks = await client.read_resource("tasks://all")
print("All tasks:\n", tasks)
asyncio.run(test_server())
Expected terminal output:
Added: {'id': 1, 'title': 'Write blog post', 'description': 'MCP tutorial', 'status': 'pending', 'created_at': '2025-06-22T10:00:00'}
Completed: {'id': 1, 'title': 'Write blog post', ..., 'status': 'completed', 'completed_at': '2025-06-22T10:00:05'}
All tasks:
✅ [1] Write blog post - completed
Tip: Use
fastmcp devfor interactive testing before connecting to any AI client. It's the fastest feedback loop when you build an MCP server in Python. (When something misbehaves, our guide on how to debug your MCP server walks through the Inspector workflow.)
The Complete server.py (Full Code)
Here's the entire server.py - every piece together, clean, commented. Copy this, run it, it works.
from datetime import datetime
from fastmcp import FastMCP
# --- Server initialization ---
mcp = FastMCP("Task Manager") # register as MCP-compliant endpoint
# --- In-memory task store ---
tasks = []
task_counter = 0
# --- Tool 1: add_task ---
@mcp.tool()
def add_task(title: str, description: str = "") -> dict:
"""Add a new task to the task manager. Returns the created task."""
global task_counter
task_counter += 1
task = {
"id": task_counter,
"title": title,
"description": description,
"status": "pending",
"created_at": datetime.now().isoformat(),
}
tasks.append(task)
return task
# --- Tool 2: complete_task ---
@mcp.tool()
def complete_task(task_id: int) -> dict:
"""Mark a task as completed by its ID. Returns the updated task or an error."""
for task in tasks:
if task["id"] == task_id:
task["status"] = "completed"
task["completed_at"] = datetime.now().isoformat()
return task
return {"error": f"Task with ID {task_id} not found."}
# --- Resource: list_tasks ---
@mcp.resource("tasks://all")
def list_tasks() -> str:
"""Returns all tasks as a formatted string."""
if not tasks:
return "No tasks yet."
lines = []
for task in tasks:
icon = "✅" if task["status"] == "completed" else "⏳"
lines.append(f"{icon} [{task['id']}] {task['title']} - {task['status']}")
return "\n".join(lines)
# --- Prompt: task_summary_prompt ---
@mcp.prompt()
def task_summary_prompt() -> str:
"""A reusable prompt for summarizing pending tasks."""
return (
"You are a productivity assistant. "
"Read the tasks://all resource and provide a concise summary of all PENDING tasks. "
"Group them by priority if possible. "
"End with a recommended next action."
)
# --- Entry point ---
if __name__ == "__main__":
mcp.run()
Connect Your MCP Server to Claude Desktop (Real-World Usage)
Claude Desktop connects to local MCP servers via a JSON config file. This is how you go from "it works in the terminal" to "Claude can call my tools in a real conversation."
01. Find your config file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
02. Add your server:
{
"mcpServers": {
"task-manager": {
"command": "python",
"args": ["/absolute/path/to/your/server.py"]
}
}
}
Replace /absolute/path/to/your/server.py with the actual path on your machine.
03. Restart Claude Desktop.
04. Open a new conversation. Claude will now list your Task Manager tools in its tool panel. You can say: "Add a task: Review pull requests" - and Claude will call add_task directly.
[Screenshot: Claude Desktop showing your Task Manager tools in the tool panel]
Warning: Use the absolute path to
server.py, not a relative one. Claude Desktop launches the server from a different working directory, so relative paths will fail silently.
What to Build Next (MCP Server Ideas)
Once you've shipped your first simple MCP server example, the real leverage starts. Here are five directions worth pursuing:
01. Database query tool - Let the AI run read-only SQL against your Postgres or MySQL database. Pair it with a schema resource so the AI knows your table structure.
02. GitHub integration - Create issues, read open PRs, summarize recent commits. The GitHub REST API maps cleanly to MCP tools. (See our walkthrough on wrapping a REST API as an MCP server.)
03. Internal docs search - Build a RAG pipeline over your company wiki or Notion workspace. Expose it as a resource. Your team's AI assistant suddenly knows everything.
04. Calendar tool - Read and write Google Calendar events. Combine with a task tool and you have a personal AI chief of staff.
05. SaaS workflow automation - Trigger multi-step business workflows: create a CRM record, send a Slack notification, update a project board - all from a single AI instruction. → See how Gingerlabs.ai automates enterprise workflows with AI agents at scale, embedded directly inside SaaS products.
Key Takeaways
MCP is the standard for connecting AI assistants to your tools and data - one protocol, any client.
FastMCP makes it fast: three decorators (
@mcp.tool,@mcp.resource,@mcp.prompt) cover 90% of what you need to create an MCP server in Python.The three primitives - Tools, Resources, Prompts - map to call, read, and instruct. Learn them once, apply everywhere.
Testing is built in:
fastmcp dev server.pygives you a browser UI before you touch any AI client.Claude Desktop is the fastest path to real-world usage - one JSON config and you're live.
MCP turns your Python functions into AI-callable superpowers. Ship your first server today.
FAQ
What is an MCP server in Python?
An MCP server in Python is a program that implements the Model Context Protocol, exposing tools, resources, and prompts that AI clients (like Claude or Cursor) can call directly. You build it with FastMCP - a Python framework - and the AI treats your functions like built-in capabilities it can invoke mid-conversation.
Do I need to know the MCP specification to build a server?
No. FastMCP abstracts the entire spec. You write normal Python functions with decorators, and FastMCP handles the protocol-level handshake, schema generation, and transport. You only need to read the spec if you're building a custom MCP client or doing something the framework doesn't cover.
What's the difference between FastMCP and the MCP Python SDK?
The MCP Python SDK is the low-level official library - it gives you full protocol control but requires more boilerplate. FastMCP is a high-level framework built on top of it, designed for developer speed. For most use cases - including this entire tutorial - FastMCP is the right choice. Drop to the raw SDK only when you need fine-grained control over transport or message handling.
Can I connect my MCP server to ChatGPT or only Claude?
Both. Claude Desktop and Cursor support local MCP servers via stdio. ChatGPT supports remote MCP servers (not localhost) through its Responses API and, as of late 2025, through ChatGPT Developer Mode for Plus, Pro, Business, and Enterprise users. To connect to ChatGPT, deploy your server with transport="streamable-http" over HTTPS and register it as a remote connector.
How do I deploy an MCP server to production?
Use transport="streamable-http" (the default in FastMCP 2.3+) instead of stdio. Deploy to Google Cloud Run, Cloudflare Workers, or any HTTPS-capable host. Add authentication (Bearer token or OAuth 2.0) - production MCP servers must be secured (our walkthrough on adding authentication to your MCP server covers the OAuth PKCE flow end to end). Never expose an unauthenticated HTTP MCP endpoint to the public internet.
Is MCP server Python suitable for enterprise use?
Yes, with the right architecture. The protocol itself is production-grade - Anthropic open-sourced it and donated it to the Agentic AI Foundation in 2025. For enterprise deployments, you'll want: HTTPS + auth, structured logging, rate limiting, and a deployment platform like Cloud Run or AWS (walk through securing your MCP server before you go live). The Python ecosystem (FastMCP, async support, type safety) handles enterprise-scale workloads cleanly.
If you're thinking about embedding AI agents directly into your SaaS product, explore what Gingerlabs.ai makes possible.
Useful Resources
Official MCP Python SDK - github.com/modelcontextprotocol/python-sdk
FastMCP documentation - gofastmcp.com
MCP specification - modelcontextprotocol.io
Anthropic MCP announcement (November 25, 2024) - anthropic.com/news/model-context-protocol
Claude Desktop download - claude.ai/download
Keep reading
How to Wrap a REST API as an MCP Server for AI Agents
A hands-on Python tutorial for wrapping any REST API as an MCP server so AI agents like Claude can discover and call your tools at runtime.
MCP Integration for Salesforce, SAP, and NetSuite: A Practical Guide
A step-by-step guide to MCP integration for Salesforce, SAP, and NetSuite - setup, security, use cases, and connecting AI agents to your enterprise systems.
Building an MCP Server for Your SaaS: A Guide for Product Teams
A practical, step-by-step guide for SaaS product managers and engineering leads on how to build an MCP server - from concepts to deployment, auth, and best practices.



