Content hash: 7aa3b3b03b6cc7c142e82d93853cf329e2a16dce5bf818d7849984e7c37b575b
#!/usr/bin/env python3
"""Minimal MCP server with FastMCP: tools, resources, and a prompt.
Run: python mcp_server_example.py
Connect: python mcp_client_example.py python mcp_server_example.py
"""
from __future__ import annotations
import json
from typing import Optional
try:
from mcp.server.fastmcp import FastMCP
except ImportError:
print("Install: pip install mcp", file=__import__("sys").stderr)
raise SystemExit(1)
mcp = FastMCP(
"TodoService",
instructions="Track todos for the user. Each tool does ONE clear thing.",
)
# ── Tool: action (mutate state) ───────────────────────────────────────────
_todos: dict[int, dict] = {}
_next_id = 1
@mcp.tool()
def add_todo(title: str, priority: int = 2) -> str:
"""Add a new todo item. priority: 1=high, 2=normal, 3=low."""
global _next_id
tid = _next_id
_next_id += 1
_todos[tid] = {"title": title, "priority": priority, "done": False}
return f"Added todo #{tid}: {title!r} (priority {priority})"
@mcp.tool()
def complete_todo(todo_id: int) -> str:
"""Mark a todo as complete by its numeric ID. Fails if not found."""
if todo_id not in _todos:
return f"Error: no todo with id {todo_id} found"
_todos[todo_id]["done"] = True
return f"Todo #{todo_id} marked complete"
# ── Resource: read-only data (model reads context) ────────────────────────
@mcp.resource("todos://all")
def get_all_todos() -> str:
"""Return all todos as a formatted list."""
if not _todos:
return "No todos yet."
lines = []
for tid, t in sorted(_todos.items()):
status = "✓" if t["done"] else "○"
lines.append(f"[{status}] #{tid} {t['title']} (p:{t['priority']})")
return "\n".join(lines)
@mcp.resource("todos://{todo_id}")
def get_todo(todo_id: int) -> str:
"""Return a single todo by ID."""
t = _todos.get(todo_id)
if t is None:
return f"No todo with id {todo_id}"
return json.dumps(t, indent=2)
# ── Prompt: reusable message template ─────────────────────────────────────
@mcp.prompt()
def todo_review_prompt(scope: str = "all") -> str:
"""Build a prompt that asks the model to review todos."""
return f"Please review the {scope} outstanding todos and suggest priorities."
# ── Main ───────────────────────────────────────────────────────────────────
if __name__ == "__main__":
# Stdio by default (local). For remote: mcp.run(transport="streamable-http")
mcp.run()