mcp_server_example.py

script

← Back to skill

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()