Practical Examples

Three progressive cases: a local notes resource, wrapping a public HTTP API as a tool, and attaching the same server to Cursor and Claude Code. The only extra dependency is official mcp. Case 2 uses the Python standard library—no separate HTTP client.


Case 1: Local notes resource

Goal: let the host read Markdown under notes/ by URI, without handing your home directory to a filesystem server.

notes/inbox.md:

# Inbox
- Review MCP transports: stdio vs Streamable HTTP
- Write instructions for the workshop server

notes_server.py:

from pathlib import Path

from mcp.server import MCPServer

ROOT = Path(__file__).resolve().parent / "notes"
mcp = MCPServer("notes", instructions="Read markdown notes under ./notes only.")


def _safe(name: str) -> Path:
    path = (ROOT / name).resolve()
    if not str(path).startswith(str(ROOT.resolve())) or path.suffix != ".md":
        raise ValueError("only markdown files under notes/ are allowed")
    return path


@mcp.resource("notes://inbox")
def inbox() -> str:
    """The default inbox note."""
    path = ROOT / "inbox.md"
    return path.read_text(encoding="utf-8") if path.is_file() else "(empty inbox)"


@mcp.resource("notes://file/{name}")
def read_note(name: str) -> str:
    """Read notes/{name}.md (name without extension)."""
    return _safe(f"{name}.md").read_text(encoding="utf-8")


@mcp.prompt()
def weekly_review() -> str:
    """Start a weekly review using the inbox note."""
    return "Read notes://inbox and list three next actions."


if __name__ == "__main__":
    mcp.run()

Check: mcp dev .\notes_server.py, read notes://inbox, then the template notes://file/{name} (name=inbox). The path guard rejects names such as ../secrets.


Case 2: Wrap a public HTTP API

Goal: fetch the current temperature from Open-Meteo (no API key) to show “a tool is a constrained HTTP call.”

weather_server.py:

import json
import urllib.parse
import urllib.request

from mcp.server import MCPServer

mcp = MCPServer("weather", instructions="Public forecast via Open-Meteo. No API key.")


@mcp.tool()
def current_temperature(latitude: float, longitude: float) -> str:
    """Return current temperature (°C) for a WGS84 coordinate pair."""
    query = urllib.parse.urlencode(
        {
            "latitude": latitude,
            "longitude": longitude,
            "current": "temperature_2m",
        }
    )
    url = f"https://api.open-meteo.com/v1/forecast?{query}"
    with urllib.request.urlopen(url, timeout=10) as resp:
        payload = json.loads(resp.read().decode("utf-8"))
    current = payload.get("current", {})
    value = current.get("temperature_2m")
    return f"{value} °C" if value is not None else json.dumps(payload, ensure_ascii=False)


if __name__ == "__main__":
    mcp.run()

Near Shanghai try latitude=31.23, longitude=121.47.

Check: call the tool in Inspector, then ask a host to use current_temperature for that pair. Do not concatenate arbitrary user URLs into urlopen—the host is fixed here. For an authenticated internal API, read the token from the environment and return business fields only.


Case 3: One server, two hosts

Merge cases 1 and 2 into workshop.py (or keep the file from Python Server). One codebase, two configs.

Cursor — .cursor/mcp.json:

{
  "mcpServers": {
    "workshop": {
      "command": "C:\\Users\\you\\mcp-demo\\.venv\\Scripts\\python.exe",
      "args": ["C:\\Users\\you\\mcp-demo\\workshop.py"]
    }
  }
}

Claude Code — .mcp.json at the project root, or:

claude mcp add --scope user workshop -- C:\Users\you\mcp-demo\.venv\Scripts\python.exe C:\Users\you\mcp-demo\workshop.py

On Unix, point command at .venv/bin/python.

Check:

  1. After restart, both hosts list the same tools and resources
  2. Ask Cursor about a line in the inbox; ask Claude Code for the same resource
  3. Call current_temperature on both sides; the numbers should match (same public API)

That is the USB-C metaphor in practice: the protocol stays still; the host only launches the command and approves tools.


Anti-patterns

Anti-patternPrefer
One tool named run that takes an arbitrary shell stringFinite actions with a real schema
A resource that returns .envBusiness documents only
Debugging connectivity only in chatInspector / mcp dev first
Copying business code per hostOne server, many configs

Next

评论