Skip to content
← All articles
protocolAdvanced14 min readLast updated: Apr 30, 2026

MCP Server From Scratch: Connecting Your Site to Claude

Stand up a production-ready Model Context Protocol server with FastMCP, publish the well-known manifest, and turn AIDE's proto-mcp-server check into a PASS.

  • #mcp
  • #agents
  • #protocol
  • #advanced
  • #fastmcp

This guide stands up an MCP server from zero and turns AIDE's proto-mcp-server check from FAIL into PASS. Instead of conceptual prose we'll ship working code: ~80 lines of Python, a Caddy reverse-proxy block, a manifest JSON.

What AIDE actually checks

proto-mcp-server validates three things in order:

  1. Does https://<domain>/.well-known/mcp.json return HTTP 200?
  2. Does the JSON match the schema — required transport, endpoint, auth fields with correct types?
  3. Does the endpoint URL respond to a JSON-RPC 2.0 initialize request with a spec-compliant payload?

All three pass → check passes. One missing → WARNING or FAIL. This guide solves them in that exact order.

Step 1 — Publish .well-known/mcp.json

The manifest tells the agent how to discover your server. AIDE reads this first, learning the URL and auth method.

{
  "mcp_version": "2025-03-26",
  "transport": "streamable_http",
  "endpoint": "https://mcp.aide.tr/v1/rpc",
  "name": "AIDE Scan",
  "description": "AIDE scan engine — send a URL, receive an AI-readiness score.",
  "auth": {
    "type": "bearer",
    "token_url": "https://aide.tr/account/api-keys",
    "scopes": ["scan:create", "scan:read"]
  },
  "capabilities": {
    "tools": true,
    "resources": false,
    "prompts": false
  }
}

transport: "streamable_http" is what the 2025-Q2 spec recommends. Older stdio and sse transports aren't always supported by browser-based or cloud-hosted agents. The endpoint must be HTTPS — AIDE rejects HTTP responses.

Step 2 — FastMCP server

FastMCP is the fastest path on Python. You declare tools as decorators; FastMCP handles JSON-RPC 2.0 framing, capability negotiation, and error serialization for you.

# mcp_server.py
import os
from fastmcp import FastMCP, Context
from pydantic import BaseModel, Field
import httpx

mcp = FastMCP("AIDE Scan")


class ScanInput(BaseModel):
    url: str = Field(..., description="URL to scan")
    profile: str = Field(default="ai_ready", description="Scan profile")


@mcp.tool()
async def scan_site(payload: ScanInput, ctx: Context) -> dict:
    """Scan a site and return its score."""
    api_key = ctx.request_context.headers.get("authorization", "").removeprefix("Bearer ")
    if not api_key:
        raise ValueError("AIDE API key required")

    async with httpx.AsyncClient(timeout=60) as client:
        resp = await client.post(
            "https://api.aide.tr/v1/scans",
            json={"url": payload.url, "profile": payload.profile},
            headers={"Authorization": f"Bearer {api_key}"},
        )
        resp.raise_for_status()
        return resp.json()


if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8001, path="/v1/rpc")

This server exports a single tool (scan_site). When Claude Desktop connects, it sees the tool in its catalog and can invoke it from user intent.

Step 3 — Reverse proxy + TLS

MCP clients (Claude Desktop, mcp-inspector, the OpenAI sandbox) expect HTTP/2 + TLS 1.3. The shortest Caddy block:

mcp.aide.tr {
    encode gzip zstd

    # MCP streamable_http transport uses SSE-like flushing —
    # proxy buffering must be off, otherwise responses lag.
    @rpc path /v1/rpc*
    reverse_proxy @rpc localhost:8001 {
        flush_interval -1
        transport http {
            read_timeout 30m
        }
    }
}

flush_interval -1 tells the proxy to stream the body without buffering. Without this AIDE's probe times out at 30 s and the check drops to WARNING.

Step 4 — initialize smoke test

Once the server is up, do exactly what AIDE will do:

curl -X POST https://mcp.aide.tr/v1/rpc \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AIDE_API_KEY" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": {"name": "smoke-test", "version": "1.0"}
    }
  }'

Expected response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {"tools": {"listChanged": true}},
    "serverInfo": {"name": "AIDE Scan", "version": "0.1.0"}
  }
}

If protocolVersion differs from what you sent, that's not a mismatch — the server returned the highest version it supports. AIDE counts that as PASS.

Common mistakes

| Mistake | Symptom | Fix | |---|---|---| | well-known returns 404 instead of 200 | AIDE WARNING with "manifest not found" | Serve .well-known/mcp.json from the domain root, not Next.js public/. Use Caddy handle_path or an Nginx location block. | | Server returns 401 even when I declared auth: none | Middleware requiring Authorization header | Set auth: { "type": "none" } in the manifest and leave the path unauthenticated in your middleware | | 406 Not Acceptable without Accept: text/event-stream | streamable_http uses SSE-style framing | Document the required Accept: application/json, text/event-stream header in your manifest | | Cloudflare cache returns stale RPC responses | Second call sees a cached body | Add Cache-Control: no-store to RPC responses |

Production hardening

  • Rate limiting: Per-API-key throttling, especially on expensive tools. Add slowapi or fastapi-limiter.
  • Tool granularity: Replace one fat scan_site with start_scan + get_scan_result so agents can parallelize.
  • Capability negotiation: When you add a new tool, emit a notifications/tools/list_changed event — Claude Desktop refreshes its tool list live.
  • Observability: Wire OpenTelemetry. The mcp.tool.invocations counter shows which tool is hot per minute.

Verifying with an AIDE scan

Once all three steps land:

curl -X POST https://api.aide.tr/v1/scans \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://your-site.com","profile":"ai_ready"}'

The proto-mcp-server check should now report status: pass. If it's still WARNING, inspect the evidence field — it tells you which sub-step failed.

Related resources

1284 subscribers

Weekly AI-Readiness newsletter

New articles, industry trends, check updates — one email a week.

Sitenizde deneyin

Tek bir tıklamayla bu kontrolü çalıştırın.

Which check do you want?
MCP Server From Scratch: Connecting Your Site to Claude | AIDE