Skip to main content
Glama
yeison-liscano

Simple HTTP MCP Server

シンプルな HTTP MCP サーバーの実装

このプロジェクトは、HTTP 上で Model Context Protocol (MCP) の軽量サーバー実装を提供します。Python 関数をツールやプロンプトとして公開し、JSON-RPC インターフェースを介してリモートから検出・実行できるようにします。Starlette または FastAPI アプリケーションと一緒に使用することを想定しています (demo を参照)。

目次

Related MCP server: wazza-mcp-test-server

機能

  • MCP プロトコル準拠: ツールとプロンプトの検出および実行のための MCP 仕様を実装しています。通知には対応していません。

  • 単一のプロトコルリビジョン: ステートレスな 2026-07-28 リビジョンのみを話します — server/discover、リクエストごとの _meta、ハンドシェイクなし、セッションなし。単一のディスパッチパスにより、リクエストが古いリビジョンを宣言して弱い処理を選択することはできません。

  • HTTP および STDIO トランスポート: 通信に HTTP (POST リクエスト) または STDIO を使用します。

  • 非同期サポート: 非同期リクエスト処理のために Starlette または FastAPI 上に構築されています。

  • 型安全: 堅牢なデータ検証とシリアライズのために Pydantic を活用しています。

  • サーバー状態管理: get_state_key メソッドを使用して、ライフスパンコンテキストを通じて共有状態にアクセスします。

  • リクエストアクセス: ツールとプロンプトから受信リクエストオブジェクトにアクセスします。

  • 認可スコープ: Starlette の認証システムを使用したスコープベースの認可をサポートします。

  • エラーハンドリング: ツールは例外を発生させる代わりに、オプションでエラーメッセージを返すことができます。

  • OAuth 2.1 認可: Bearer トークン検証、Protected Resource Metadata (RFC 9728)、WWW-Authenticate エラーレスポンスを備えたオプションの auth_mcp パッケージ。pip install http-mcp[auth] でインストールします。

サーバーアーキテクチャ

このライブラリは、アプリケーション全体のライフサイクルにわたって共有状態を管理するためにライフスパンを使用する単一の MCPServer クラスを提供します。

MCPServer

MCPServer は、共有サーバー状態を管理するための Starlette のライフスパンシステムと連携するように設計されています。

主な特徴:

  • ライフスパンベース: Starlette のライフスパンイベントを使用して共有サーバー状態を初期化および管理します

  • アプリケーションレベルの状態: 状態はリクエストごとではなく、アプリケーション全体のライフサイクルにわたって保持されます

  • 柔軟性: ライフスパン状態に保存された任意のカスタムコンテキストクラスで使用できます

コンストラクタパラメータ:

  • name (str): MCP サーバーの名前

  • version (str): MCP サーバーのバージョン

  • tools (tuple[Tool, ...]): 公開するツールのタプル (デフォルト: 空のタプル)

  • prompts (tuple[Prompt, ...]): 公開するプロンプトのタプル (デフォルト: 空のタプル)

  • instructions (str | None): AI アシスタント向けの、このサーバーの使い方に関するオプションの手順

  • cache_ttl_ms (int): tools/listprompts/listserver/discover の結果とともに送信されるミリ秒単位の鮮度ヒント (デフォルト: 300000)。0 を指定すると、クライアントにキャッシュしないよう指示できます。キャッシュヒント を参照してください。

  • cache_scope ("public" | "private" | None): 共有キャッシュがそれらの結果を認可コンテキストをまたいで再利用してよいかどうか。省略時は自動的に導出されます。キャッシュヒント を参照してください。

  • allowed_origins (tuple[str, ...]): HTTP トランスポートが受け入れるオリジン (デフォルト: 空、つまりチェックは無効)。オリジン検証 を参照してください。

  • require_origin (bool): allowed_origins が設定されている場合に、Origin ヘッダーをまったく持たないリクエストを拒否するかどうか (デフォルト: False)。オリジン検証 を参照してください。

使用例:

import contextlib
from collections.abc import AsyncIterator
from typing import TypedDict
from dataclasses import dataclass, field
from starlette.applications import Starlette
from http_mcp.server import MCPServer

@dataclass
class Context:
    call_count: int = 0
    user_preferences: dict = field(default_factory=dict)

class State(TypedDict):
    context: Context

@contextlib.asynccontextmanager
async def lifespan(_app: Starlette) -> AsyncIterator[State]:
    yield {"context": Context()}

mcp_server = MCPServer(
    name="my-server",
    version="1.0.0",
    tools=my_tools,
    prompts=my_prompts,
    instructions="Optional instructions for AI assistants on how to use this server"
)

app = Starlette(lifespan=lifespan)
app.mount("/mcp", mcp_server.app)

プロトコルバージョン

サーバーは正確に 1 つのプロトコルリビジョン 2026-07-28 を実装しており、すべてのリクエストは同じ経路をたどります。バージョン交渉も、リクエストが選択できる別のルールセットもありません。

0.17.0 での破壊的変更。 セッションベースのリビジョン 2025-11-252025-06-182025-03-26 のサポートは、initializenotifications/initializedping とともに削除されました。それらのリビジョンしか話せないクライアントは、このサーバーと通信できなくなります。単一のリビジョンのみを提供することこそが、以下のリクエストメタデータヘッダーを信頼できるものにします。2 つの時代が共存していた間は、リクエストが古い方を宣言することでヘッダーチェックを回避できたため、Mcp-Method に基づいてルーティングする中間層が、ボディに基づいて動作するサーバーと同期しなくなる可能性がありました。

0.18.0 での破壊的変更。 ServerInterface.get_tool_input_schema は現在 Request を受け取るため、認可スコープがディスパッチ前に尊重されます。インターフェースの実装は更新する必要がありますが、MCPServer ユーザーは影響を受けません。ミラーリングされた Mcp-Param-* の値は数値ではなくテキストとして比較されるため、"replicas": 3 に対して 3.0 と読めるヘッダーは -32020 を受け取るようになりました。notifications/* メソッドはすべて 404 を返します。このリビジョンでは通知が定義されていません。

2026-07-28 リクエスト形式

このリビジョンにはセッションの概念がありません。実際には次のようになります。

  • ハンドシェイクなし。 すべてのリクエストは、プロトコルバージョンとクライアントのケイパビリティを _meta で再宣言します:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "location": "Seattle, WA" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}
  • server/discover は、ケイパビリティ検出のための initialize を置き換えます。 サポートされているバージョン、ケイパビリティ、手順、サーバー ID を 1 回の呼び出しで報告し、事前のリクエストなしに応答されます:

{
  "resultType": "complete",
  "supportedVersions": ["2026-07-28"],
  "capabilities": { "tools": { "listChanged": false }, "prompts": { "listChanged": false } },
  "_meta": { "io.modelcontextprotocol/serverInfo": { "name": "my-server", "version": "1.0.0" } },
  "ttlMs": 300000,
  "cacheScope": "public"
}
  • すべての結果は resultType: "complete" を持ち、サーバーを指定する _meta ブロックが含まれます。

  • initializenotifications/initializedpinglogging/setLevel は存在しません。セッションおよび SSE 再開の仕組みも同様です。これらは HTTP 404 とともに -32601 を返します。JSON-RPC 通知 — id を持たない notifications/* メッセージ — には、JSON-RPC が応答を禁止しているため、引き続き 202 Accepted とボディなしが返されます。

  • 必須リクエストヘッダー。 すべての POST は MCP-Protocol-VersionMcp-Method を送信する必要があり、tools/callprompts/get ではさらに Mcp-Name も送信する必要があります。それぞれが対応するボディの値と一致する必要があり、一致しない場合、リクエストは -32020 (HeaderMismatch) と HTTP 400 で拒否されます。これにより、ある値に基づいてルーティングするプロキシと、別の値に基づいて動作するサーバーとの間の非同期を防ぎます。プレーンな ASCII として表現できない値は =?base64?...?= エンベロープを使用し、サーバーは比較前にそれをデコードします。

  • 不明なツールとプロンプトは -32602 を報告します。これはツールとプロンプトの仕様が定めるところです。-32002 はこのリビジョンで廃止されました。

  • Mcp-Session-IdLast-Event-ID は無視され、MCP エンドポイントへの GET/DELETE405 Method Not Allowed を返します。

複数ラウンドトリップのリクエスト (elicitation、sampling、roots) と subscriptions/listen は実装されていません。このサーバーはクライアント入力に依存する機能を公開しておらず、listChanged: false を宣言しているため、どちらもこのサーバーには該当しません。

キャッシュヒント

tools/listprompts/listserver/discover の結果には ttlMscacheScope が含まれているため、クライアントは変更されていないリストを再取得することを避けられます:

mcp_server = MCPServer(
    name="my-server",
    version="1.0.0",
    tools=my_tools,
    cache_ttl_ms=300_000,   # clients may treat the list as fresh for 5 minutes
    cache_scope="public",   # shared caches may serve it to any caller
)

ツールとプロンプトは MCPServer の構築時に固定されるため、ttlMs はデータが安定している期間ではなく、クライアントが再デプロイを見逃す可能性がある期間を実際に制限します。0 に設定すると、クライアントにキャッシュしないよう指示できます。

cache_scope は省略すると導出されます。ツールまたはプロンプトがスコープ制限されている場合は "private" — その場合、リストは呼び出し元ごとに異なるため、共有キャッシュは認可コンテキストをまたいで再利用してはなりません — それ以外の場合は "public" になります。デプロイ環境がより適切に判断できる場合は上書きしてください。cacheScope はキャッシュのみを管理することに注意してください。ツールごとのスコープチェックの代わりには決してなりません。

オリジン検証

ブラウザは Origin ヘッダーを添付します。これにより、サーバーは DNS リバインディングによって忍び込まれたリクエストを拒否できます。このチェックはデフォルトでオフになっているため、既存のデプロイは引き続き動作します。エンドポイントがブラウザから到達可能な場所ではオンにしてください:

mcp_server = MCPServer(
    name="my-server",
    version="1.0.0",
    tools=my_tools,
    allowed_origins=("https://app.example.com",),
)

Origin が存在し、リストにないリクエストは 403 Forbidden を受け取ります。Origin をまったく持たないリクエスト — 通常の非ブラウザクライアント — はデフォルトで影響を受けません。ブラウザは POST で常に Origin を送信し、リバインディングの脅威モデルはブラウザ以外のクライアントを対象としていないためです。

エンドポイントがブラウザトラフィックのみを提供する必要がある場合は、require_origin を追加してヘッダーを省略したリクエストも拒否し、許可リストをアドバイザリではなく必須にします:

mcp_server = MCPServer(
    name="my-server",
    version="1.0.0",
    tools=my_tools,
    allowed_origins=("https://app.example.com",),
    require_origin=True,
)

require_origin は単独では何もしません。すでに設定されている許可リストを厳格化するだけです。ローカルで実行する場合は、0.0.0.0 ではなく 127.0.0.1 にバインドしてください。

ツールパラメータのヘッダーへのミラーリング

ツールはクライアントに特定の引数値を Mcp-Param-* ヘッダーにコピーするよう要求できます。これにより、プロキシはボディを解析せずにルーティングやレート制限を行うことができます。フィールドに x-mcp-header で注釈を付けます:

from pydantic import BaseModel, Field

class ExecuteSQLInput(BaseModel):
    region: str = Field(
        description="The region to execute the query in",
        json_schema_extra={"x-mcp-header": "Region"},
    )
    query: str = Field(description="The SQL query to execute")

準拠するクライアントは、呼び出しとともに Mcp-Param-Region: us-west1 を送信し、サーバーはそれをボディと照合します。ヘッダーが欠落している場合、引数と矛盾する場合、または引数が存在しないのに送信された場合、リクエストは -32020 で拒否されます。注釈で要求されていない Mcp-Param-* ヘッダーは無視されます。中間層は認識されないものをそのまま転送することが想定されているためです。

比較はテキストベースで、JSON が書き出す値に対して行われます。"replicas": 3 の場合、ヘッダーは正確に 3 と読める必要があり、3.0+33 ではありません。数値への変換はそれらを等しいとみなしますが、生のヘッダー文字列に基づいてルーティングする中間層は別のものを認識します。これが、ミラーリングが防ごうとしている非同期です。

オブジェクトプロパティの単純な連鎖を通じて到達可能な stringintegerboolean フィールドのみに注釈を付けることができ、2 つのフィールドが同じヘッダー名を要求することはできません。衝突はサーバー構築時に拒否されます。2 つの注釈のうち 1 つを残すと、もう 1 つが黙って強制されなくなるためです。機密性の高い値には注釈を付けないでください。ヘッダーの内容は経路上のすべての仲介者に見えるためです。

ツール

ツールはクライアントが呼び出すことができる関数です。

基本的なツールの例

  1. ツールの引数と出力を定義します:

# app/tools/models.py
from pydantic import BaseModel, Field

class GreetInput(BaseModel):
    question: str = Field(description="The question to answer")

class GreetOutput(BaseModel):
    answer: str = Field(description="The answer to the question")

# Note: the description on Field will be passed when listing the tools.
# Having a description is optional, but it's recommended to provide one.
  1. ツールを定義します:

# app/tools/tools.py
from http_mcp.types import Arguments

from app.tools.models import GreetInput, GreetOutput

def greet(args: Arguments[GreetInput]) -> GreetOutput:
    return GreetOutput(answer=f"Hello, {args.inputs.question}!")
# app/tools/__init__.py

from http_mcp.types import Tool
from app.tools.models import GreetInput, GreetOutput
from app.tools.tools import greet

TOOLS = (
    Tool(
        func=greet,
        inputs=GreetInput,
        output=GreetOutput,
    ),
)

__all__ = ["TOOLS"]
  1. サーバーをインスタンス化します:

# app/main.py
from starlette.applications import Starlette
from http_mcp.server import MCPServer
from app.tools import TOOLS

mcp_server = MCPServer(tools=TOOLS, name="test", version="1.0.0")

app = Starlette()
app.mount(
    "/mcp",
    mcp_server.app,
)

引数のないツール

入力引数を必要としないツールを定義できます:

from datetime import UTC, datetime
from pydantic import BaseModel, Field
from http_mcp.types import Tool

class GetTimeOutput(BaseModel):
    time: str = Field(description="The current time")

async def get_time() -> GetTimeOutput:
    """Get the current time."""
    return GetTimeOutput(time=datetime.now(UTC).strftime("%H:%M:%S"))

TOOLS = (
    Tool(
        func=get_time,
        inputs=type(None),  # No arguments required
        output=GetTimeOutput,
    ),
)

あるいは、明確にするために NoArguments クラスを使用することもできます:

from http_mcp.types import Arguments, NoArguments, Tool

class SimpleOutput(BaseModel):
    success: bool = Field(description="Whether the operation was successful")

def simple_tool(args: Arguments[NoArguments]) -> SimpleOutput:
    """A simple tool with no arguments."""
    # You can still access request and state
    context = args.get_state_key("context", Context)
    return SimpleOutput(success=True)

TOOLS = (
    Tool(
        func=simple_tool,
        inputs=NoArguments,
        output=SimpleOutput,
    ),
)

エラーハンドリングを備えたツール

ツールは例外を発生させる代わりに、オプションでエラーメッセージを返すことができます:

from pydantic import BaseModel, Field
from http_mcp.types import Arguments, Tool
from http_mcp.exceptions import ToolInvocationError

class RiskyToolInput(BaseModel):
    value: int = Field(description="An integer value")

class RiskyToolOutput(BaseModel):
    result: str = Field(description="The result of the operation")

def risky_tool(args: Arguments[RiskyToolInput]) -> RiskyToolOutput:
    """A tool that might fail."""
    if args.inputs.value < 0:
        raise ToolInvocationError("risky_tool", "Value must be positive")
    return RiskyToolOutput(result=f"Success: {args.inputs.value}")

TOOLS = (
    Tool(
        func=risky_tool,
        inputs=RiskyToolInput,
        output=RiskyToolOutput,
        return_error_message=True,  # Return ErrorMessage instead of raising
    ),
)

return_error_message=True の場合、ツールは ToolInvocationError を発生させる代わりに、エラーの詳細を含む ErrorMessage モデルを返します。

認可スコープを備えたツール

認証スコープに基づいてツールへのアクセスを制限できます:

from http_mcp.exceptions import ToolInvocationError
from http_mcp.types import Arguments, NoArguments, Tool
from starlette.authentication import has_required_scope

class SecureOutput(BaseModel):
    message: str = Field(description="A secure message")

def private_tool(args: Arguments[NoArguments]) -> SecureOutput:
    """A tool that requires authentication."""
    if not has_required_scope(args.request, ("private",)):
        raise ToolInvocationError("private_tool", "Insufficient scope")
    return SecureOutput(message="This is private data")

def admin_tool(args: Arguments[NoArguments]) -> SecureOutput:
    """A tool that requires admin or superuser scope."""
    if not has_required_scope(args.request, ("admin", "superuser")):
        raise ToolInvocationError("admin_tool", "Insufficient scope")
    return SecureOutput(message="This is admin data")

TOOLS = (
    Tool(
        func=private_tool,
        inputs=NoArguments,
        output=SecureOutput,
        scopes=("private",),  # Only accessible with 'private' scope
    ),
    Tool(
        func=admin_tool,
        inputs=NoArguments,
        output=SecureOutput,
        scopes=("admin", "superuser"),  # Accessible with either scope
    ),
)

注意:スコープを正しく機能させるには、Starletteアプリに認証ミドルウェアを設定する必要があります。Toolscopesフィールドは主要な認可ゲートであり、フレームワークは呼び出し前にスコープに基づいてツールをフィルタリングします。上記のツール関数内のraise ToolInvocationError(...)呼び出しは、サイレントに失敗する代わりにクライアントに適切なエラーレスポンスを返す、任意の多層防御チェックです。

サーバー状態管理

サーバーはStarletteのlifespanシステムを使用して、アプリケーションライフサイクル全体にわたって共有状態を管理します。状態はアプリケーションの起動時に初期化され、シャットダウンまで保持されます。コンテキストにはArgumentsオブジェクトのget_state_keyメソッドを通じてアクセスします。

これは、データベース接続プール、HTTPクライアント、キャッシュ、その他のアプリケーション状態などのリソースをツール間で共有するのに役立ちます。

データベース接続プール

最も一般的なパターン — 起動時に接続プールを初期化し、すべてのツールで共有し、シャットダウン時に閉じる:

# app/context.py
from dataclasses import dataclass
import asyncpg

@dataclass
class AppContext:
    db: asyncpg.Pool
# app/main.py
import contextlib
import os
from collections.abc import AsyncIterator
from typing import TypedDict
import asyncpg
from starlette.applications import Starlette
from http_mcp.server import MCPServer
from app.context import AppContext

class State(TypedDict):
    ctx: AppContext

@contextlib.asynccontextmanager
async def lifespan(_app: Starlette) -> AsyncIterator[State]:
    pool = await asyncpg.create_pool(os.environ["DATABASE_URL"])
    yield {"ctx": AppContext(db=pool)}
    await pool.close()

mcp_server = MCPServer(tools=TOOLS, name="my-server", version="1.0.0")

app = Starlette(lifespan=lifespan)
app.mount("/mcp", mcp_server.app)
# app/tools.py
from pydantic import BaseModel, Field
from http_mcp.types import Arguments
from app.context import AppContext

class GetUserInput(BaseModel):
    user_id: int = Field(description="The user ID to look up")

class GetUserOutput(BaseModel):
    name: str = Field(description="The user's name")
    email: str = Field(description="The user's email")

async def get_user(args: Arguments[GetUserInput]) -> GetUserOutput:
    """Look up a user by ID."""
    ctx = args.get_state_key("ctx", AppContext)
    row = await ctx.db.fetchrow(
        "SELECT name, email FROM users WHERE id = $1",
        args.inputs.user_id,
    )
    return GetUserOutput(name=row["name"], email=row["email"])

共有HTTPクライアント

単一のhttpx.AsyncClientをツール間で共有して、接続を再利用し、ベースURL、ヘッダー、タイムアウトを一度設定します:

# app/context.py
from dataclasses import dataclass
import httpx

@dataclass
class AppContext:
    http_client: httpx.AsyncClient
# app/main.py
import contextlib
from collections.abc import AsyncIterator
from typing import TypedDict
import httpx
from starlette.applications import Starlette
from http_mcp.server import MCPServer
from app.context import AppContext

class State(TypedDict):
    ctx: AppContext

@contextlib.asynccontextmanager
async def lifespan(_app: Starlette) -> AsyncIterator[State]:
    async with httpx.AsyncClient(
        base_url="https://api.example.com",
        headers={"Authorization": "Bearer <token>"},
    ) as client:
        yield {"ctx": AppContext(http_client=client)}

mcp_server = MCPServer(tools=TOOLS, name="my-server", version="1.0.0")

app = Starlette(lifespan=lifespan)
app.mount("/mcp", mcp_server.app)
# app/tools.py
from pydantic import BaseModel, Field
from http_mcp.types import Arguments
from app.context import AppContext

class SearchInput(BaseModel):
    query: str = Field(description="The search query")

class SearchOutput(BaseModel):
    results: list[str] = Field(description="Search result titles")

async def search(args: Arguments[SearchInput]) -> SearchOutput:
    """Search via an external API."""
    ctx = args.get_state_key("ctx", AppContext)
    resp = await ctx.http_client.get("/search", params={"q": args.inputs.query})
    resp.raise_for_status()
    return SearchOutput(results=[r["title"] for r in resp.json()["items"]])

インメモリキャッシュ

同じサーバーライフサイクル内のツール呼び出し間で、キャッシュやカウンターなどの可変状態を共有します:

# app/context.py
from dataclasses import dataclass, field

@dataclass
class AppContext:
    cache: dict[str, str] = field(default_factory=dict)
    request_count: int = 0
# app/tools.py
from pydantic import BaseModel, Field
from http_mcp.types import Arguments
from app.context import AppContext

class LookupInput(BaseModel):
    key: str = Field(description="The cache key to look up")

class LookupOutput(BaseModel):
    value: str | None = Field(description="The cached value, or null if not found")
    total_requests: int = Field(description="Total requests served")

async def lookup(args: Arguments[LookupInput]) -> LookupOutput:
    """Look up a value in the cache."""
    ctx = args.get_state_key("ctx", AppContext)
    ctx.request_count += 1
    return LookupOutput(
        value=ctx.cache.get(args.inputs.key),
        total_requests=ctx.request_count,
    )

同じAppContextインスタンスを共有するすべてのツールは、lifespanが単一の共有オブジェクトを生成するため、互いの書き込みを即座に確認できます。

注意:プレーンなdictintはスレッドセーフではありません。ツールが並行して実行される場合(例:スレッド経由でディスパッチされる同期ツール)、asyncio.Lockで共有可変状態を保護するか、スレッドセーフなデータ構造を使用してください。

リクエストアクセス

ツールから受信リクエストオブジェクトにアクセスできます。リクエストオブジェクトは各ツール呼び出しに渡され、ヘッダー、クッキー、その他のリクエストデータ(例:request.state、request.scope)にアクセスするために使用できます。

from pydantic import BaseModel, Field
from http_mcp.types import Arguments

class MyToolArguments(BaseModel):
    question: str = Field(description="The question to answer")

class MyToolOutput(BaseModel):
    answer: str = Field(description="The answer to the question")


async def my_tool(args: Arguments[MyToolArguments]) -> MyToolOutput:
    # Access the request
    auth_header = args.request.headers.get("Authorization")
    ...

    return MyToolOutput(answer=f"Hello, {args.inputs.question}!")

# Use MCPServer:
from http_mcp.server import MCPServer

mcp_server = MCPServer(
    name="my-server",
    version="1.0.0",
    tools=(my_tool,),
)

プロンプト

ユーザーの選択によって呼び出されるインタラクティブなテンプレートを追加できます。プロンプトはツールと同様に、lifespan状態へのアクセスをサポートするようになりました。

基本的なプロンプトの例

  1. プロンプトの引数を定義します:

from pydantic import BaseModel, Field

from http_mcp.types import Arguments, Prompt, PromptMessage, TextContent


class GetAdvice(BaseModel):
    topic: str = Field(description="The topic to get advice on")
    include_actionable_steps: bool = Field(
        description="Whether to include actionable steps in the advice", default=False
    )


def get_advice(args: Arguments[GetAdvice]) -> tuple[PromptMessage, ...]:
    """Get advice on a topic."""
    template = """
    You are a helpful assistant that can give advice on {topic}.
    """
    if args.inputs.include_actionable_steps:
        template += """
        The advice should include actionable steps.
        """
    return (
        PromptMessage(
            role="user",
            content=TextContent(
                text=template.format(topic=args.inputs.topic)
            ),
        ),
    )


PROMPTS = (
    Prompt(
        func=get_advice,
        arguments_type=GetAdvice,
    ),
)
  1. サーバーをインスタンス化します:

from starlette.applications import Starlette

from app.prompts import PROMPTS
from http_mcp.server import MCPServer

app = Starlette()
mcp_server = MCPServer(tools=(), prompts=PROMPTS, name="test", version="1.0.0")

app.mount(
    "/mcp",
    mcp_server.app,
)

引数のないプロンプト

入力引数を必要としないプロンプトを定義できます:

from http_mcp.types import Prompt, PromptMessage, TextContent

def help_prompt() -> tuple[PromptMessage, ...]:
    """Use this prompt to get general help."""
    return (
        PromptMessage(
            role="user",
            content=TextContent(
                text="You are a helpful assistant. Help the user with their task."
            ),
        ),
    )

PROMPTS = (
    Prompt(
        func=help_prompt,
        arguments_type=type(None),  # No arguments required
    ),
)

あるいは、NoArgumentsクラスを使用できます:

from http_mcp.types import Arguments, NoArguments, Prompt, PromptMessage, TextContent

def help_prompt_with_context(args: Arguments[NoArguments]) -> tuple[PromptMessage, ...]:
    """Use this prompt to get help with access to context."""
    # You can still access request and state
    context = args.get_state_key("context", Context)
    return (
        PromptMessage(
            role="user",
            content=TextContent(text="You are a helpful assistant."),
        ),
    )

PROMPTS = (
    Prompt(
        func=help_prompt_with_context,
        arguments_type=NoArguments,
    ),
)

lifespan状態を持つプロンプト

from pydantic import BaseModel, Field
from http_mcp.types import Arguments, Prompt, PromptMessage, TextContent
from app.context import Context

class GetAdvice(BaseModel):
    topic: str = Field(description="The topic to get advice on")

def get_advice_with_context(args: Arguments[GetAdvice]) -> tuple[PromptMessage, ...]:
    """Get advice on a topic with context awareness."""
    # Access the context from lifespan state
    context = args.get_state_key("context", Context)
    called_tools = context.get_called_tools()
    template = """
    You are a helpful assistant that can give advice on {topic}.
    Previously called tools: {tools}
    """

    return (
        PromptMessage(
            role="user",
            content=TextContent(
                text=template.format(
                    topic=args.inputs.topic,
                    tools=", ".join(called_tools) if called_tools else "none"
                )
            )
        ),
    )

PROMPTS_WITH_CONTEXT = (
    Prompt(
        func=get_advice_with_context,
        arguments_type=GetAdvice,
    ),
)

認可スコープを持つプロンプト

認証スコープに基づいてプロンプトへのアクセスを制限できます:

from http_mcp.types import Arguments, NoArguments, Prompt, PromptMessage, TextContent

def private_prompt(args: Arguments[NoArguments]) -> tuple[PromptMessage, ...]:
    """Private prompt that is only accessible to authenticated users."""
    return (
        PromptMessage(
            role="user",
            content=TextContent(text="This is a private prompt."),
        ),
    )

def admin_prompt(args: Arguments[NoArguments]) -> tuple[PromptMessage, ...]:
    """Admin prompt accessible to users with admin or superuser scope."""
    return (
        PromptMessage(
            role="user",
            content=TextContent(text="This is an admin prompt."),
        ),
    )

PROMPTS = (
    Prompt(
        func=private_prompt,
        arguments_type=NoArguments,
        scopes=("private",),  # Only accessible with 'private' scope
    ),
    Prompt(
        func=admin_prompt,
        arguments_type=NoArguments,
        scopes=("admin", "superuser"),  # Accessible with either scope
    ),
)

注意:スコープを正しく機能させるには、Starletteアプリに認証ミドルウェアを設定する必要があります。

STDIOトランスポート

HTTPトランスポートに加えて、サーバーは通信用のSTDIOトランスポートをサポートしています。これは、標準入出力を通じて通信するコマンドラインアプリケーションや統合に役立ちます。

STDIOトランスポートの使用

import asyncio
import os
from http_mcp.server import MCPServer
from app.tools import TOOLS
from app.prompts import PROMPTS

mcp_server = MCPServer(
    tools=TOOLS,
    prompts=PROMPTS,
    name="test",
    version="1.0.0"
)

# Run the server with STDIO transport
async def main() -> None:
    request_headers = {
        "Authorization": f"Bearer {os.getenv('MCP_TOKEN', '')}",
        "X-Custom-Header": "value",
    }
    await mcp_server.serve_stdio(request_headers)

asyncio.run(main())

request_headersパラメータを使用すると、リクエストコンテキストに含まれるヘッダーを渡すことができ、STDIOトランスポートを使用する場合でも認証やその他のヘッダーベースの機能を有効にできます。

認証と認可

このライブラリはStarletteの認証システムと統合し、ツールとプロンプトに対するスコープベースの認可を提供します。

認証ミドルウェアの設定

import contextlib
from collections.abc import AsyncIterator
from typing import TypedDict
from starlette.applications import Starlette
from starlette.authentication import (
    AuthCredentials,
    AuthenticationBackend,
    BaseUser,
    SimpleUser,
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import HTTPConnection

from http_mcp.server import MCPServer
from app.context import Context
from app.tools import TOOLS
from app.prompts import PROMPTS


class BasicAuthBackend(AuthenticationBackend):
    def __init__(self, granted_scopes: tuple[str, ...] = ("authenticated",)) -> None:
        self.granted_scopes = granted_scopes
        super().__init__()

    async def authenticate(
        self, conn: HTTPConnection
    ) -> tuple[AuthCredentials, BaseUser] | None:
        # Implement your authentication logic here
        # For example, check Bearer token, API key, etc.
        auth_header = conn.headers.get("Authorization")
        if not auth_header:
            return None

        # Validate token and return credentials with scopes
        return AuthCredentials(self.granted_scopes), SimpleUser("username")


class State(TypedDict):
    context: Context


@contextlib.asynccontextmanager
async def lifespan(_app: Starlette) -> AsyncIterator[State]:
    yield {"context": Context()}


mcp_server = MCPServer(
    tools=TOOLS,
    prompts=PROMPTS,
    name="test",
    version="1.0.0"
)

app = Starlette(
    lifespan=lifespan,
    middleware=[
        Middleware(
            AuthenticationMiddleware,
            backend=BasicAuthBackend(granted_scopes=("private", "admin")),
        ),
    ],
)
app.mount("/mcp", mcp_server.app)

スコープの仕組み

  1. 認証ミドルウェア:ミドルウェアは各リクエストを認証し、AuthCredentialsを通じてユーザーにスコープを割り当てます。

  2. ツール/プロンプトのスコープ:ツールやプロンプトを定義する際に、scopesパラメータを使用して必要なスコープを指定できます。

  3. アクセス制御:サーバーはユーザーに付与されたスコープに基づいてツールとプロンプトを自動的にフィルタリングします。必要なスコープを持たないツールとプロンプトは一覧に表示されず、呼び出すこともできません。

  4. 複数のスコープ:複数のスコープを指定した場合(例:scopes=("admin", "superuser"))、ユーザーはツールまたはプロンプトにアクセスするためにそれらのスコープのうち少なくとも1つを持っている必要があります。

APIリファレンス

Toolクラス

Toolクラスは、クライアントが呼び出せるツールを定義するために使用されます。

パラメータ:

  • func:呼び出される関数。同期または非同期にできます。関数は次のいずれかになります:

    • Arguments[TInputs]パラメータを受け入れる

    • パラメータを受け入れない

  • inputs:入力検証用のPydanticモデルクラス。入力のないツールにはtype(None)またはNoArgumentsを使用します

  • output:出力検証用のPydanticモデルクラス

  • return_error_message(bool):Trueの場合、ツールエラーは例外を発生させる代わりにErrorMessageを返します(デフォルト:False

  • scopes(tuple[str, ...]):このツールにアクセスするために必要な認証スコープ(デフォルト:空のタプル)

プロパティ:

  • name:関数名(func.__name__から派生)

  • title:人間が読めるタイトル(関数名から派生)

  • description:関数のdocstring

  • input_schema:入力パラメータのJSONスキーマ

  • output_schema:出力のJSONスキーマ

Promptクラス

Promptクラスは、クライアントが呼び出せるプロンプトを定義するために使用されます。

パラメータ:

  • func:呼び出される関数。同期または非同期にできます。関数は次のいずれかになります:

    • Arguments[TArguments]パラメータを受け入れる

    • パラメータを受け入れない

    • tuple[PromptMessage, ...]を返す必要があります

  • arguments_type:引数検証用のPydanticモデルクラス。引数のないプロンプトにはtype(None)またはNoArgumentsを使用します

  • scopes(tuple[str, ...]):このプロンプトにアクセスするために必要な認証スコープ(デフォルト:空のタプル)

プロパティ:

  • name:関数名(func.__name__から派生)

  • title:人間が読めるタイトル(関数名から派生)

  • description:関数のdocstring

  • arguments:プロンプトの引数を定義するPromptArgumentオブジェクトのタプル

Argumentsクラス

Argumentsクラスはツール関数とプロンプト関数に渡され、入力、リクエスト、状態へのアクセスを提供します。

パラメータ:

  • request:StarletteのRequestオブジェクト

  • inputs:検証済みの入力/引数データ(型はTool/Promptの定義によって異なります)

メソッド:

  • get_state_key(key: str, _object_type: type[TKey]) -> TKey:lifespan状態から値にアクセスします。キーが存在しない場合はServerErrorを発生させます。

NoArgumentsクラス

ツールやプロンプトを引数なしで定義する際に、type(None)のより明確な代替として使用できる空のPydanticモデルです。

from http_mcp.types import NoArguments

# Use this instead of type(None)
Tool(func=my_func, inputs=NoArguments, output=MyOutput)

OAuth 2.1認可(auth_mcp)

auth_mcpパッケージは、MCPサーバーに標準準拠のOAuth 2.1認可を追加します。authエクストラでインストールします:

pip install http-mcp[auth]

クイックスタート

from http_mcp.server import MCPServer
from auth_mcp.resource_server import (
    ProtectedMCPAppConfig,
    TokenInfo,
    TokenValidator,
    create_protected_mcp_app,
)
from auth_mcp.types import ProtectedResourceMetadata


class MyTokenValidator(TokenValidator):
    async def validate_token(
        self, token: str, resource: str | None = None
    ) -> TokenInfo | None:
        # Validate against your authorization server
        ...


mcp_server = MCPServer(name="my-server", version="1.0.0", tools=MY_TOOLS)

config = ProtectedMCPAppConfig(
    mcp_server=mcp_server,
    token_validator=MyTokenValidator(),
    resource_endpoint=ProtectedResourceMetadata(
        resource="https://mcp.example.com",
        authorization_servers=("https://auth.example.com",),
    ),
)

app = create_protected_mcp_app(config)

これにより以下が得られます:

  • すべてのMCPエンドポイントでのベアラートークン検証(デフォルトでセキュア)

  • /.well-known/oauth-protected-resourceディスカバリーエンドポイント(RFC 9728)

  • resource_metadataパラメータ付きの401/403でのWWW-Authenticateヘッダー

  • セキュリティヘッダー(HSTS、nosniff、no-store)

  • middlewaresパラメータによるオプションのカスタムミドルウェア

完全なドキュメント、ベストプラクティス、セキュリティ面の詳細については、auth_mcp READMEを参照してください。

エンドポイント別のセキュリティ面

POST /mcp — MCP JSON-RPCエンドポイント

  • 認証auth_mcpを使用する場合、ベアラートークンはAuthorizationヘッダーから抽出され、TokenValidatorを介して検証されます。2048文字を超えるトークン、またはRFC 6750のb64tokenパターン外の文字を含むトークンは、バリデーターに到達する前に拒否されます。auth_mcpを使用しない場合、認証はStarletteのAuthenticationMiddlewareによって処理されます。

  • 認可 — Starletteのhas_required_scope()によるスコープベースのフィルタリング。一致するスコープのないツールとプロンプトは一覧から非表示になり、呼び出し時にブロックされます。リクエストヘッダー検証は同じスコープチェックを通じてツールスキーマを解決するため、ツールが非表示になっている呼び出し元は、不一致メッセージからx-mcp-header引数を学習することもできません。

  • 入力検証 — JSON-RPCメッセージはPydanticによって検証されます。リクエストボディは4 MBに制限され、読み取り中に強制されます。過大なContent-Lengthはボディが読み取られる前に拒否され、ストリーム途中で上限を超えたボディはその時点でバッファリングが停止されます。Content-Typeは厳密にチェックされます(application/jsonのみ、メディアタイプパラメータは無視されます)。

  • エラー処理 — ツール名とプロンプト名はエラーメッセージで100文字に切り詰められます。Pydantic検証エラーはレスポンスに含める前にサニタイズされます。

  • レスポンスヘッダー — すべてのレスポンスにX-Content-Type-Options: nosniffCache-Control: no-storeauth_mcpはさらにStrict-Transport-Security: max-age=31536000; includeSubDomainsを追加します。

GET /.well-known/oauth-protected-resource — ディスカバリーエンドポイント(auth_mcp)

  • 認証/mcpと同じ認証ミドルウェアの対象。require_authentication=True(デフォルト)の場合、有効なトークンが必要です。クライアントが認証前に認可サーバーを発見する必要がある場合はFalseに設定します。

  • 入力検証GETのみ許可。その他のメソッドは405 Method Not Allowedを返します。

  • 出力 — 起動時にフリーズされたProtectedResourceMetadataモデルから一度だけシリアライズされます。URIフィールドはPydanticのAnyHttpUrlを介してHTTP/HTTPS URLとして検証されます。

WWW-Authenticateレスポンスヘッダー(auth_mcp)

  • ヘッダーインジェクション — すべてのパラメータ値(realmresource_metadatascopeerrorerror_description)はサニタイズされます:CR/LF文字が除去され、バックスラッシュと二重引用符はRFC 7230のquoted-stringルールに従ってエスケープされます。

  • 情報開示 — エラーレスポンスは一般的なメッセージ("Authentication required")を使用します。元のAuthenticationErrorの詳細は破棄されます。エラーコード(401でのinvalid_token)は内部状態を漏らすことなくRFC 6750に従います。

STDIOトランスポート

  • メッセージサイズ — HTTPトランスポートと同様に4 MBに制限されています。

  • ロギング — ログフラッディングを防ぐため、デバッグログではメッセージが500文字に切り詰められます。トークン値がログに記録されることはありません。

  • ヘッダー — リクエストヘッダーは適切なASGIのlist[tuple[bytes, bytes]]形式に変換されます。

インストール

Python 3.12以上が必要です(PEP 695型パラメータ構文を使用)。

pipまたはuvを使用してパッケージをインストールします:

pip install http-mcp

OAuth 2.1認可サポート付き:

pip install http-mcp[auth]

または

uv add http-mcp

ライセンス

このプロジェクトはMITライセンスの下でライセンスされています。詳細についてはLICENSEファイルを参照してください。

Install Server
A
license - permissive license
B
quality
A
maintenance

Maintenance

Maintainers
Response time
4wRelease cycle
13Releases (12mo)
Commit activity
Issues opened vs closed

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.

  • MCP server exposing the Backtest360 engine API as tools for AI agents.

  • MCP server for Clipkit — gives AI agents a video toolbox via the Clipkit schema.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yeison-liscano/http_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server