Skip to main content
Glama
Demo HTTP MCP Server

Demo HTTP MCP Server

test-http-mcp

Demo Model Context Protocol (MCP) server implemented in Python using the http-mcp package. It can run over HTTP (Starlette/Uvicorn, protected with Auth0) or over stdio, exposing two web tools, web_search and web_fetch, to any MCP-capable client. Each tool runs a Claude sub-agent built with pydantic-ai that checks every web result for prompt injection before reading it.

Project structure

test-http-mcp/
├── backend/                 # Python MCP server
│   ├── app/                 # Application source code
│   │   ├── app.py           # Auth0-protected HTTP app (auth_mcp)
│   │   ├── main.py          # Entry points (HTTP / stdio)
│   │   ├── config.py        # Settings via pydantic-settings
│   │   ├── agents.py        # Claude sub-agents (pydantic-ai) behind the web tools
│   │   ├── auth0/           # Auth0 integration
│   │   │   ├── __init__.py  # JWT token validator
│   │   │   └── client_store.py # Dynamic client registration (RFC 7591, not mounted)
│   │   └── tools/           # MCP tools (web_search, web_fetch)
│   ├── tests/               # pytest suite, incl. end-to-end MCP client tests
│   ├── pyproject.toml       # Python deps & scripts
│   ├── uv.lock              # Locked dependencies
│   ├── ruff.toml            # Linter config
│   ├── mypy.ini             # Type-checker config
│   └── .envrc               # direnv auto-activation
├── flake.nix                # Nix dev shell
├── AGENTS.md
├── LICENSE
└── README.md

Protocol

The server speaks only MCP protocol revision 2026-07-28 (http-mcp >= 0.17). There is no initialize handshake, so clients must use an MCP SDK that supports that revision, for example the Python mcp SDK >= 2.x or FastMCP 4.

Authentication (Auth0, HTTP only)

The HTTP endpoint (/mcp/) is protected with Auth0 OAuth2 authentication. Tools require scopes (tool:web_search, tool:web_fetch) to be present in the access token; define both as permissions on the Auth0 API. stdio mode has no authentication and no tool scopes.

Required environment variables

Variable

Description

AUTH0_DOMAIN

Auth0 tenant domain (e.g. your-tenant.auth0.com)

AUTH0_AUDIENCE

Auth0 API identifier. Must be the MCP resource URI (http://localhost:8000/mcp/): tokens whose audience names another resource are rejected (RFC 8707)

AUTH0_MCP_APP_CLIENT_ID

Auth0 application client ID for the MCP app (used by Auth0ClientStore, currently not mounted)

AUTH0_MGMT_CLIENT_ID

Management API client ID (used by Auth0ClientStore, currently not mounted)

AUTH0_MGMT_CLIENT_SECRET

Management API client secret (used by Auth0ClientStore, currently not mounted)

AUTH0_ENABLED

Set to false to disable scope enforcement (default: true)

Place these in backend/.env (git-ignored).

Claude sub-agents (HTTP and stdio)

Both tools call Claude through the Anthropic API, so the server needs Anthropic credentials. Set ANTHROPIC_API_KEY in backend/.env or the environment, or sign in once with ant auth login: without a key, the Anthropic SDK uses that profile (or ANTHROPIC_AUTH_TOKEN). The server starts without credentials; a tool call then fails with "the server has no Anthropic credentials configured".

Variable

Description

ANTHROPIC_API_KEY

Anthropic API key (optional, see above)

AGENT_MODEL

Claude model for both sub-agents (default: claude-opus-5-5)

AGENT_EFFORT

low, medium, high, xhigh or max (default: low, the usual level for sub-agents)

Requests opt into Anthropic's server-side refusal fallback (fallbacks: "default"): a request a safety classifier declines is re-run on Anthropic's recommended fallback model instead of failing.

HTTP routes

Path

Purpose

Auth

/mcp/

MCP server (Streamable HTTP)

Auth0 Bearer token required

/.well-known/oauth-protected-resource/mcp/

Protected resource metadata (RFC 9728), names Auth0 as the authorization server

Unauthenticated

Clients discover Auth0's endpoints from the tenant itself, so client registration depends on the tenant: enable Dynamic Client Registration in Auth0, or configure the client with a pre-registered client ID.

Requirements

  • Python 3.13

  • uv (recommended) or pip

  • Node.js (optional): only for npx-based MCP clients and servers

Alternatively, Nix supplies all of the above — see Nix dev shell.

Nix dev shell

flake.nix pins the whole toolchain (Python 3.13, uv, Node.js 24, git), so nothing needs to be installed system-wide:

nix develop

With direnv the shell loads automatically on cd, and also syncs backend/.venv:

direnv allow

Library dependencies stay owned by backend/uv.lock, so pinned tool versions (ruff, mypy, pytest) come from uv, not from Nix. Inside the shell uv is pinned to the Nix interpreter (UV_PYTHON, UV_PYTHON_DOWNLOADS=never) so it never downloads a second Python.

To bump the pinned nixpkgs revision:

nix flake update

Install

Using uv:

cd backend
uv sync

Using pip:

cd backend
python3.13 -m venv .venv
source .venv/bin/activate
pip install .

Run

HTTP (needs the Auth0 variables above):

cd backend
uv run run-app          # → MCP endpoint on http://localhost:8000/mcp/
uv run run-app-local    # same, with auto-reload

stdio (no Auth0 configuration needed):

cd backend
uv run run-stdio

Test with an MCP client

With the Python mcp SDK (installed in the dev group):

import asyncio

import httpx2
from mcp import Client, StdioServerParameters
from mcp.client.streamable_http import streamable_http_client


async def main() -> None:
    # stdio: the client launches the server as a subprocess
    stdio = StdioServerParameters(command="uv", args=["run", "--project", "backend", "run-stdio"])
    async with Client(stdio) as client:
        print(await client.list_tools())

    # HTTP: pass an Auth0 access token for this API (audience = the MCP resource URI)
    http_client = httpx2.AsyncClient(headers={"Authorization": "Bearer <access-token>"})
    async with Client(streamable_http_client("http://localhost:8000/mcp/", http_client=http_client)) as client:
        print(await client.call_tool("web_search", {"query": "What is the Model Context Protocol?"}))


asyncio.run(main())

Example .cursor/mcp.json for HTTP mode:

{
  "mcpServers": {
    "test-http-mcp": {
      "type": "http",
      "url": "http://localhost:8000/mcp/"
    }
  }
}

Usage with Gemini:

{
  "mcpServers": {
    "test": {
      "httpUrl": "http://localhost:8000/mcp/",
      "timeout": 5000
    }
  }
}

Example .cursor/mcp.json entry to connect via stdio:

{
  "mcpServers": {
    "test_studio": {
      "command": "uv",
      "args": ["run", "--project", "backend", "run-stdio"]
    }
  }
}

What this server exposes

Two tools (see backend/app/tools/ and backend/app/agents.py), no prompts:

  • web_search(query): a Claude sub-agent searches DuckDuckGo (pydantic-ai's duckduckgo_search_tool) and answers from the results.

  • web_fetch(url, question?): a Claude sub-agent reads the page (pydantic-ai's web_fetch_tool) and answers the question, or summarizes the page. It can follow links on the same site only (the URL's host and its www. twin, redirects included); private and cloud-metadata addresses are refused.

Both return {answer, sources, prompt_injection_detections}. Each sub-agent has pydantic-ai-harness's PromptInjectionDefender with block_high_risk=True: every search result and fetched page is classified (StackOne defender, pattern detection) before the model reads it, high- and critical-risk results are replaced with a short notice, and each flagged result is listed in prompt_injection_detections. Treat answer as untrusted web content all the same: the defender catches known patterns, not every injection.

Anthropic's server-side web search and fetch tools are not used on purpose: they run on Anthropic's side, so the defender would never see their results.

Project scripts

Three console entry points are defined in backend/pyproject.toml:

  • run-app → app.main:run_http (HTTP on localhost:8000)

  • run-app-local → app.app:main (HTTP with auto-reload)

  • run-stdio → app.main:run_stdio

Development

Common tasks (run from the backend/ directory):

uv run ruff check .           # lint
uv run mypy .                 # type check
uv run pytest                 # tests
uv run mdformat .             # format markdown

tests/test_mcp_client_compat.py drives the server with the real mcp SDK client over HTTP (plain and Auth0-protected) and over stdio (run-stdio), so it catches protocol drift between http-mcp and MCP clients. tests/test_web_agents.py runs the real prompt-injection defender against injected search results and pages. No test calls Claude or the web: models are replaced with pydantic-ai's TestModel/FunctionModel, and an autouse fixture fails any test that would reach DuckDuckGo or download a page.

Implementation notes

  • The HTTP app is an Auth0-protected MCP app created by auth_mcp; the MCP endpoint at /mcp/ requires a valid Auth0 Bearer token with the appropriate tool scopes.

  • Auth0ClientStore (dynamic client registration proxied to a pre-created Auth0 application) is kept but not mounted: http-mcp >= 0.19 accepts only HTTPS authorization servers and endpoints unless loopback HTTP is explicitly enabled with AuthorizationTransportPolicy.ALLOW_LOOPBACK_HTTP (0.19.0rc2+, development only), and MCP clients on SDK 2.x require the metadata issuer to match the advertised server, which a localhost proxy for Auth0 cannot satisfy.

  • Only loopback browser origins are accepted on /mcp/ (http-mcp's DNS rebinding defence); clients that send no Origin header are unaffected.

License

MIT — see LICENSE.

Available Tools

4 tools
get_called_toolsGet Called ToolsA
Idempotent

Get the list of called tools.

Inputs: no named parameters. Returns: called_tools

get_timeGet TimeA
Idempotent

Get the current time.

Inputs: no named parameters. Returns: time

get_weatherGet WeatherB
Idempotent

Get the current weather in a given location.

Inputs: location (required), unit. Returns: weather

tool_that_access_requestTool That Access RequestC
Idempotent

Access the request.

Inputs: username (required). Returns: message

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 4 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: get_called_tools retrieves internal server usage data, get_time provides current time, get_weather fetches location-based weather, and tool_that_access_request handles request metadata. There is no overlap in functionality, making tool selection unambiguous.

    Naming Consistency3/5

    Three tools follow a consistent 'get_*' verb_noun pattern, but tool_that_access_request deviates with a noun_verb structure and uses 'that' as a connector. This mixed convention reduces predictability, though the names remain readable.

    Tool Count4/5

    With 4 tools, the count is reasonable for a demo HTTP server, covering basic utilities like time, weather, and request handling. It is slightly thin for broader HTTP operations but appropriate for a limited scope.

    Completeness3/5

    The server covers basic utilities but lacks core HTTP operations like making requests (GET, POST) or handling responses, which are expected for an HTTP server. The tools are standalone utilities rather than a cohesive HTTP interface, leaving notable gaps in functionality.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      A minimal Model Context Protocol server demo that exposes tools through HTTP API, including greeting, weather lookup, and HTTP request capabilities. Demonstrates MCP server implementation with stdio communication and HTTP gateway functionality.
      6 npm
      ISC
    • A
      license
      A
      quality
      D
      maintenance
      A minimal MCP server example demonstrating Tools, Resources, and Prompts. It enables calculations, time queries, note management, and code review prompts via stdio transport.
      3
      19 npm
      MIT