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: remote-mcp

機能

  • 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/list、prompts/list、server/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-25、2025-06-18、2025-03-26 のサポートは、initialize、notifications/initialized、ping とともに削除されました。それらのリビジョンしか話せないクライアントは、このサーバーと通信できなくなります。単一のリビジョンのみを提供することこそが、以下のリクエストメタデータヘッダーを信頼できるものにします。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 ブロックが含まれます。

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

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

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

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

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

キャッシュヒント

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

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、+3、3 ではありません。数値への変換はそれらを等しいとみなしますが、生のヘッダー文字列に基づいてルーティングする中間層は別のものを認識します。これが、ミラーリングが防ごうとしている非同期です。

オブジェクトプロパティの単純な連鎖を通じて到達可能な string、integer、boolean フィールドのみに注釈を付けることができ、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アプリに認証ミドルウェアを設定する必要があります。Toolのscopesフィールドは主要な認可ゲートであり、フレームワークは呼び出し前にスコープに基づいてツールをフィルタリングします。上記のツール関数内の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が単一の共有オブジェクトを生成するため、互いの書き込みを即座に確認できます。

注意:プレーンなdictとintはスレッドセーフではありません。ツールが並行して実行される場合(例:スレッド経由でディスパッチされる同期ツール)、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: nosniff、Cache-Control: no-store。auth_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)

  • ヘッダーインジェクション — すべてのパラメータ値(realm、resource_metadata、scope、error、error_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ファイルを参照してください。

Available Tools

4 tools
get_called_toolsGet Called ToolsA
Idempotent

Get the list of called tools.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
called_toolsYesThe list of called tools

TDQS

A3.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description doesn't contradict annotations and adds some context by specifying 'list of called tools' (implying retrieval of historical tool usage data). However, annotations already provide rich behavioral information: readOnlyHint=false (potentially confusing for a 'get' operation), openWorldHint=true, idempotentHint=true, destructiveHint=false. The description doesn't add significant behavioral details beyond what annotations already cover, but it doesn't contradict them either.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, clear sentence with zero wasted words. It's front-loaded with the essential information and perfectly sized for a simple tool. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (0 parameters, rich annotations, output schema exists), the description is reasonably complete. The annotations cover safety and behavioral traits, and the output schema will document return values. The description could be more specific about what 'called tools' means in context, but for a simple retrieval tool, it's mostly adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0 parameters and 100% schema description coverage, the schema already fully documents the lack of inputs. The description doesn't need to explain parameters, and it correctly doesn't mention any. The baseline for 0 parameters is 4, as the description appropriately focuses on the tool's purpose rather than nonexistent parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'Get the list of called tools' clearly states the verb ('Get') and resource ('list of called tools'), making the purpose understandable. However, it doesn't distinguish this tool from its siblings (get_time, get_weather, tool_that_access_request) - all are 'get' operations but for different data. The description is adequate but lacks sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. There's no mention of context, prerequisites, or comparison with sibling tools. The agent must infer usage from the tool name alone, which offers minimal guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_timeGet TimeA
Idempotent

Get the current time.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
timeYesThe current time

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already provide key behavioral hints (readOnlyHint=false, openWorldHint=true, idempotentHint=true, destructiveHint=false), so the description doesn't need to repeat these. The description adds minimal context about what 'current time' means, but doesn't elaborate on format, timezone, or other behavioral details beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description 'Get the current time' is a single, efficient sentence that front-loads the core purpose with zero wasted words. It's appropriately sized for a simple tool with no parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (0 parameters, annotations covering key behaviors, and an output schema that presumably handles return values), the description is reasonably complete. However, it could slightly improve by hinting at the output format (e.g., timestamp vs. string) since sibling tools suggest varied contexts.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0 parameters and 100% schema description coverage, the schema fully documents the lack of inputs. The description doesn't need to add parameter information, so it appropriately avoids redundancy. A baseline of 4 is justified since no parameters exist to explain.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'Get the current time' clearly states the verb ('Get') and resource ('current time'), making the purpose immediately understandable. However, it doesn't distinguish this tool from potential sibling tools like 'get_called_tools' or 'get_weather', which prevents a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. There are sibling tools like 'get_weather' that might serve related time/weather queries, but the description doesn't mention any context, prerequisites, or exclusions for usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_weatherGet WeatherB
Idempotent

Get the current weather in a given location.

ParametersJSON Schema
NameRequiredDescriptionDefault
locationYesThe location to get the weather for
unitNoThe unit of temperaturecelsius

Output Schema

ParametersJSON Schema
NameRequiredDescription
weatherYesThe weather in the given location

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already provide key behavioral hints (readOnlyHint=false, openWorldHint=true, idempotentHint=true, destructiveHint=false), so the description doesn't need to repeat these. It adds minimal context by implying real-time data retrieval, but doesn't disclose additional traits like rate limits, error handling, or authentication needs, which would elevate the score.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose without unnecessary details. Every word earns its place, making it highly concise and well-structured for quick understanding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's low complexity (2 parameters, 100% schema coverage, annotations provided, and an output schema exists), the description is reasonably complete. It states what the tool does, though it could benefit from slight enhancements like mentioning the output includes current conditions, but the output schema likely covers return values, reducing the burden.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents the parameters (location and unit). The description mentions 'location' but adds no extra meaning beyond what the schema provides, such as format examples or usage nuances, meeting the baseline for high coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with a specific verb ('Get') and resource ('current weather'), and it specifies the scope ('in a given location'). However, it doesn't distinguish this tool from potential siblings like 'get_forecast' or 'get_historical_weather', which would require explicit differentiation for a score of 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It lacks any mention of prerequisites, exclusions, or comparisons with sibling tools (e.g., 'get_time' or 'get_called_tools'), leaving the agent without context for tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tool_that_access_requestTool That Access RequestC
Idempotent

Access the request.

ParametersJSON Schema
NameRequiredDescriptionDefault
usernameYesThe username of the user

Output Schema

ParametersJSON Schema
NameRequiredDescription
messageYesThe message to the user

TDQS

C2.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide significant behavioral information: readOnlyHint=false (implies mutation), openWorldHint=true (handles unknown inputs), idempotentHint=true (safe to retry), and destructiveHint=false (non-destructive). The description adds no behavioral context beyond these annotations—it doesn't explain what 'access' entails operationally, potential side effects, or any constraints like rate limits. However, it doesn't contradict the annotations, so it meets the lower bar with annotations present.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise at just three words ('Access the request.'), with no wasted language or unnecessary elaboration. It is front-loaded and efficiently communicates the core idea, though this brevity contributes to its vagueness in other dimensions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (1 parameter, annotations provide behavioral hints, output schema exists), the description is minimally adequate but incomplete. It lacks context on what the tool actually does, usage scenarios, or output expectations. The presence of an output schema means return values needn't be explained, but the description should still clarify purpose and guidelines better to be fully helpful.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, with the 'username' parameter fully documented in the schema. The description adds no parameter semantics beyond what the schema provides—it doesn't explain why the username is needed, how it relates to the request, or any contextual details about parameter usage. With high schema coverage, the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose2/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'Access the request' is a tautology that essentially restates the tool name 'tool_that_access_request' without adding meaningful specificity. It doesn't clarify what type of request is being accessed, what resource is involved, or what 'access' means in this context (read, modify, submit?). While it includes a verb ('access') and resource ('request'), it remains vague about the actual purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. There are sibling tools like 'get_called_tools', 'get_time', and 'get_weather', but the description doesn't explain how this tool differs from them or in what context it should be selected. No prerequisites, exclusions, or comparative context are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 4 tool updatesv1.0.0
    • First observedget_called_tools
    • First observedget_time
    • First observedget_weather
    • First observedtool_that_access_request

TDQS

B3/5.0

Scored across 4 tools

Disambiguation5/5

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

Naming Consistency3/5

Three tools follow a consistent 'get_*' verb_noun pattern (get_called_tools, get_time, get_weather), but tool_that_access_request deviates with a noun_verb structure and lacks the 'get' prefix. This mixed convention reduces predictability, though the names remain readable.

Tool Count4/5

With 4 tools, the count is reasonable for a simple HTTP server, avoiding bloat. However, the scope feels slightly thin as it lacks common HTTP operations like making requests or handling responses, which might be expected for such a server.

Completeness2/5

The tool set is severely incomplete for an HTTP server domain. It includes utility functions (time, weather) and internal tracking (called tools, request access) but lacks core HTTP operations such as send_request, get_response, or manage_connections, leaving obvious gaps that will hinder agent workflows.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    A Model Context Protocol (MCP) server based on OpenRPC, providing JSON-RPC function invocation and method discovery services.
    2
    1
    Apache 2.0
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables building and running MCP servers over streamable HTTP, exposing tools to AI assistants like Cursor, with examples of mounting multiple servers in FastAPI.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Framework for building and running MCP servers as HTTP services. Define tools as pure Python functions, wire up with two lines, run with one command.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A lightweight HTTP-based MCP server built with Bun, enabling tool discovery and execution via JSON-RPC 2.0 over HTTP.
    17 npm
    MIT