You use Claude or ChatGPT every day. You're good at it — prompts, follow-ups, the whole dance. But every time you need data from your company's CRM, project tracker, or internal database, you become a human USB cable: copy from browser tab A, paste into chatbot tab B, repeat until your soul leaves your body.

Here's the thing most tutorials won't say outright: the gap between "AI that knows the internet" and "AI that knows your business" is exactly one Python file. Not a platform migration. Not a six-figure vendor contract. A single file with a decorator and a docstring. The protocol already exists, every major AI company already adopted it, and you can wire it up in an afternoon. Let me show you.

Why the obvious answers don't work

"Just use the API" — great advice, except your AI doesn't have hands. It can't call your company's REST API (a URL that returns data when you ask nicely) on its own. You could paste API docs into the chat, but that devours your context window — how much text the AI can "see" at once, its working memory — and you're still doing the copying. Zapier-style integrations exist, but they're rigid: predefined triggers, predefined actions, and a pricing page that makes your CFO weep.

What you actually need is a way to let your AI discover and call your internal tools directly — without you being the middleman.

MCP: the standard that solved this

MCP (Model Context Protocol) is a universal plug standard for AI tools — think USB-C, but for connecting AI to your data. Anthropic open-sourced it in November 2024. By March 2026, it hit 97 million monthly SDK downloads. OpenAI, Google, and Microsoft all adopted it. As of April 26, 2026, over 17,000 MCP servers are indexed across registries.

The Python SDK lets you build a working server in a single file. Here's how.

Step 1: Install the SDK

You need Python 3.10+ and either uv (Anthropic's recommended package manager) or plain pip.

# Option A: uv (recommended)
uv init my-mcp-server && cd my-mcp-server
uv add "mcp[cli]" httpx

# Option B: pip
pip install "mcp[cli]" httpx

httpx is a Python HTTP library — it's what your server uses to fetch data from external APIs. If your data lives in a database, swap it for your database driver.

Step 2: Write the server

Create server.py. Here's a complete MCP server that fetches weather alerts from the US National Weather Service — a real API, no auth key needed, perfect for learning:

from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP

# FastMCP — a high-level wrapper that turns
# plain Python functions into MCP tools
mcp = FastMCP("weather")

NWS_API_BASE = "https://api.weather.gov"

async def make_request(url: str) -> dict[str, Any] | None:
    headers = {
        "User-Agent": "mcp-weather-demo/1.0",
        "Accept": "application/geo+json",
    }
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(
                url, headers=headers, timeout=30.0
            )
            response.raise_for_status()
            return response.json()
        except Exception:
            return None

@mcp.tool()
async def get_alerts(state: str) -> str:
    """Get active weather alerts for a US state.

    Args:
        state: Two-letter US state code (e.g. CA, NY)
    """
    data = await make_request(
        f"{NWS_API_BASE}/alerts/active/area/{state}"
    )
    if not data or "features" not in data:
        return "No alerts found."

    alerts = []
    for feature in data["features"][:5]:
        props = feature["properties"]
        alerts.append(
            f"Event: {props.get('event', 'Unknown')}\n"
            f"Area: {props.get('areaDesc', 'Unknown')}\n"
            f"Severity: {props.get('severity', 'Unknown')}"
        )
    return "\n---\n".join(alerts) or "No active alerts."

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

About 40 lines of real code. The @mcp.tool() decorator — a Python shorthand that registers your function as an AI-callable tool — does the heavy lifting. FastMCP reads your type hints (state: str) and docstring (the triple-quoted description) to automatically generate tool definitions that the AI understands.

This is the core pattern. Every MCP server you've ever seen — Slack, GitHub, Figma, all 17,000+ of them — is this same skeleton with more endpoints.

Step 3: Wire it to Claude Desktop

Claude Desktop reads a JSON config file (a settings file in a specific format) to know which MCP servers to launch. Find it at:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Create or edit it:

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/my-mcp-server",
        "run",
        "server.py"
      ]
    }
  }
}

Replace /absolute/path/to/my-mcp-server with the actual full path. Relative paths don't work — this trips everyone up first.

Restart Claude Desktop. Look for the hammer icon in the bottom-right of the chat input. Click it — you should see get_alerts listed. If you don't, check ~/Library/Logs/Claude/mcp*.log on macOS for errors.

Step 4: Ask Claude something

Type: "Are there any weather alerts in California right now?"

Claude recognizes it has a get_alerts tool, asks your permission to call it, hits your server, and responds with live NWS data it couldn't access five minutes ago.

That's the "aha" moment. Your chatbot just stopped being generic.

Step 5: Replace the weather API with yours

Now swap make_request for whatever API your team uses — Jira, Linear, your internal dashboard. If it has an endpoint that returns data, it's one function away from becoming an AI tool. The pattern is always:

  1. Write an async function that fetches data
  2. Decorate it with @mcp.tool()
  3. Write a clear docstring (more on why this matters below)
  4. Add it to your config, restart

A few design principles that separate useful MCP tools from frustrating ones: name your tools with verb-noun pairs (get_alerts, search_tickets, create_issue) so the model can infer intent from the name alone. Keep each tool focused on one action — a function that fetches, filters, and writes back is three tools pretending to be one. And for parameter design, prefer explicit typed arguments over catch-all strings; state: str with a docstring that says "Two-letter US state code" gives the model enough to validate its own input.

Gotchas that will bite you

1. Your tool description IS the product

A February 2026 Queen's University study analyzed 856 MCP tools across 103 servers. Result: 97.1% of tool descriptions had at least one quality defect. 56% failed to even articulate their purpose. When researchers improved the descriptions, task success rates jumped +5.85 percentage points. Your docstring isn't documentation — it's the UI your AI reads. Write it like a product spec: purpose, parameter explanations, limitations, examples.

2. Never print() to stdout

For stdio-based servers (what Claude Desktop uses), writing to stdout corrupts JSON-RPC messages — the protocol your AI and server use to communicate. Your server silently breaks and you spend 45 minutes staring at nothing. Use print("debug", file=sys.stderr) or Python's logging module.

3. Security is YOUR job

MCP has no built-in authentication for local servers. That's fine when it runs on your laptop via stdio — just a local process. But the moment you consider exposing it over HTTP: stop. The OWASP MCP Top 10, published in early 2026, exists because people skipped this step. Thirty-plus CVEs landed against MCP tooling in January–February 2026 alone. FastMCP itself had a command injection vulnerability (CVE-2025-64340, disclosed in late 2025) in all versions below 3.2.0.

If your server touches internal APIs, treat it like any backend service: validate every input the model sends (it will hallucinate parameter values — count on it), enforce least-privilege access so each tool can only reach the data it needs, and pin your dependencies. Local-only stdio is the only safe default until you've solved auth properly.

4. The config path must be absolute

Relative paths in claude_desktop_config.json fail silently. No error, no log, just a missing hammer icon and a developer slowly losing faith. Use /Users/yourname/... on macOS — tilde expansion is unreliable here.

What you can do now

You own a repeatable pattern. Any API your team already uses — project tracker, CRM, internal dashboard, database — is one Python file away from becoming something your AI can query directly. No vendor integration waitlist. No Zapier tax. No more copy-pasting between tabs like it's 2019.

The wall between "AI that knows the internet" and "AI that knows your business" now has a door. You built it in fewer lines than a typical config file.