mcpField
O
Ollama / vLLM Bridge

Ollama / vLLM Bridge

ローカル
github.com/setheerwagenAI・エージェント01 週間前に更新

Ollama / vLLM Bridge について

Ollama / vLLM Bridge は、MCP クライアントから Ollama、vLLM、または設定済みの OpenAI 互換エンドポイントを通じてローカル LLM を呼び出す MCP サーバーです。JSON 出力を独立してスキーマ検証し、違反時に自動で再試行する点が特徴です。

主な機能

  1. 01Ollama、vLLM、OpenAI 互換バックエンドの呼び出し
  2. 02構造化 JSON のスキーマ検証
  3. 03検証に失敗した出力の再試行
  4. 04利用可能なモデルの一覧表示
  5. 05テキストと埋め込みの生成
  6. 06バックエンド接続をローカルネットワークに制限

活用シーン

  1. 01テキスト要約をローカルモデルに委任
  2. 02テキストからスキーマ準拠データを抽出
  3. 03ローカルバックエンドでテキストを分類
  4. 04設定済みバックエンドで埋め込みを生成

MCP 対応

この MCP サーバーは、設定済みのローカルモデルバックエンドの呼び出し、モデル一覧表示、埋め込み生成用のツールを公開します。MCP クライアントからモデルを呼び出せますが、モデル自体にツールやファイルアクセスはありません。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"setheerwagen-mcp-ollama-vllm": {
"command": "uvx",
"args": [
"mcp-ollama-vllm"
],
"env": {
"LOCAL_BACKEND": "<LOCAL_BACKEND>",
"LOCAL_HOST": "<LOCAL_HOST>",
"LOCAL_API_KEY": "<LOCAL_API_KEY>",
"LOCAL_TIMEOUT": "<LOCAL_TIMEOUT>",
"LOCAL_EMBED_MODEL": "<LOCAL_EMBED_MODEL>",
"LOCAL_EMBED_PATH": "<LOCAL_EMBED_PATH>",
"LOCAL_SCHEMA_MODE": "<LOCAL_SCHEMA_MODE>"
}
}
}
}
起動コマンドuvx mcp-ollama-vllm
設定が必要な環境変数
LOCAL_BACKENDBackend to use: ollama, vllm or openai
LOCAL_HOSTModel endpoint URL; must be local or on your own network. Required for the openai backend
LOCAL_API_KEYシークレットOptional bearer token, openai backend only; sent as a header, never logged
LOCAL_TIMEOUTRead timeout in seconds
LOCAL_EMBED_MODELDefault embedding model
LOCAL_EMBED_PATHOptional, openai backend only: embedding endpoint path
LOCAL_SCHEMA_MODEOptional, openai backend only: auto, response_format or none

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
pypimcp-ollama-vllm1.0.6

ツール

4

掲載時のツール一覧 · 1 週間前に更新

このツールにパラメーターはありません。

詳細

mcp-ollama-vllm

Claude Code などの MCP クライアントが、ローカル言語モデルと直接やり取りできるようにする MCP サーバーです。バックエンドは起動時に Ollama、vLLM、または汎用の OpenAI 互換エンドポイント(LOCAL_BACKEND で一度だけ設定します。詳細は以下を参照)に接続されます。3つ目の openai は、任意のツールを読み込むプラグインローダーではありません。LM Studio、llama.cpp サーバー、LocalAI、TGI、koboldcpp、SGLang、Jan など、vLLM と同じ OpenAI 互換の /v1 エンドポイントを公開するものの、vLLM 自体ではないサーバーを対象とした、もう1つの固定バックエンド実装です。

このサーバーを選ぶ理由

このサーバーが、通常の Ollama/vLLM 呼び出しにはない機能を提供する唯一の点: スキーマ検証済みの JSON 出力です。依存関係のない独自のバリデーターで検証し、違反があれば自動的に再試行します(合計最大3回。具体的な違反内容をモデルに返します)。どちらのバックエンドを設定しても、同じ方法で、同じツール呼び出しを使って動作します。 Ollama の format と vLLM の response_format はモデルを JSON 出力へ誘導しますが、どちらも結果が実際にスキーマに合致することを保証しません。このサーバーはバックエンドの申告を信用せず、独自に検証します。

変更前/変更後(説明用であり、実際の実行結果ではありません):

// Backend's own schema enforcement alone (Ollama "format" / vLLM "response_format"):
// looks like valid JSON, but a required field went missing under load and nobody
// checked it against the schema before handing it onward.
{"amount": 42.10, "currency": "EUR"}

// local_structured: the same case, but validated against the schema and
// automatically retried with the concrete violation fed back to the model.
{"amount": 42.10, "currency": "EUR", "total": 42.10}
// -> attempts: 2, valid_on_first_try: false, outcome already re-validated

意図的に明記している既知の制限:

  • vLLM では、モデル一覧に表示されるメタデータが Ollama より少なくなります。サイズや量子化の情報はなく、これらのフィールドには、根拠のない値ではなく unknown/0 が設定されます。
  • 汎用の openai バックエンドでは、通常、モデル一覧に表示される情報がさらに少なくなります。この種のサーバーの多くは /v1/models に id(および owned_by)しか返さないため、parameter_size/quantization は unknown、サイズは 0 となり、アダプターの有無を示す情報もありません。また、max_context はエンドポイントから実際に報告された場合のみ含まれます。トップレベルの metadata_reported フィールド(以下の list_models を参照)によって、この「報告されていない」状態を正直に確認できます。
  • think パラメーター(推論トレース)は vLLM または openai には影響しません。Ollama でのみ有効です。以下の「think パラメーター」を参照してください。

オペレーティングシステム: CI では Linux 上でセルフテストを実行し、モックを使ったセットアップセルフテスト selftest_vllm.py は Windows 上でも実行します。このサーバーは OS 固有の呼び出しを含まない純粋な Python であり、ライブバックエンドの経路も 2026-08-07 に Windows 上で追加確認済みです。実行中のバックエンドサービス(Ollama または vLLM)が必要です。セットアップセルフテスト selftest_vllm.py はモックを使用するため、実際のサービスは不要です。macOS では個別にテストしていません。

目的

AI エージェントの作業の多くは、文章の作成、要約、分類、テキストからの構造化データの抽出といった、純粋な言語処理です。こうした処理はローカルモデルに任せる一方で、検索、読み取り、書き込み、オーケストレーションは呼び出し元のクライアントに担わせることができます。

これは明確に、エージェントではなくモデル呼び出しを対象としています。 ローカルモデルはテキストを受け取り、テキストを返します。ツールもファイルアクセスもなく、複数のステップにわたる処理も行いません。

セキュリティ上の特性

これらの制限は、単なるドキュメント上の説明ではなく、設計の一部です。

  • ファイルにはアクセスできません。 サーバーには、ファイルの読み取りや書き込みを行うツールはありません。読み取るのは、環境変数から取得する自身の設定のみです。
  • シェルは実行しません。 subprocess、os.system、または同等のものを呼び出す経路はありません。
  • ローカルマシンまたはローカルネットワークのみです。 サーバープロセス自身が外部へ通信するのは、設定されたバックエンドエンドポイント宛てのみです。また、そのエンドポイントのアドレスは起動時と各呼び出し時に再確認され、インターネット上を指している場合、サーバーは起動を拒否するか、呼び出しを拒否します。この確認のために名前を解決し、背後にあるすべてのアドレスがローカルでなければなりません。そうしないと、DNS を介してガードを回避できてしまうためです。実際の接続では、解決済みのアドレスを固定するため、後からパブリックアドレスへ再解決されてもチェックをすり抜けることはありません。ループバックとプライベートネットワーク(RFC 1918)は許可されるため、ローカルネットワーク上の vLLM マシンには接続できますが、パブリックアドレスは拒否されます。
  • 直接の依存関係は 2 つです。 mcp と httpx です。mcp はさらに複数のパッケージ(anyio、pydantic、starlette、uvicorn、jsonschema、pyjwt など)を推移的に取り込みます。依存関係ツリー全体は uv.lock に固定されています。CI では uv lock --check でロックファイルが引き続き pyproject.toml と一致することを確認してから、テスト用に固定されたツリーを正確にインストールするため、ロックファイルにずれがあると CI は失敗します。これとは別に、助言目的の pip-audit ジョブが、main への各プッシュとプルリクエストのたびに、さらに毎週、既知の脆弱性がないかロックされたツリーをチェックします。このジョブはプルリクエストをブロックしません。

これらの特性が制限するのは、このサーバーが行うことです。サーバーを操作する AI ツールに関する保証ではありません。呼び出すモデルはローカルで動作しますが、その呼び出しを決定する Claude/ChatGPT のセッションは別のホスト型サービスであり、他の MCP 呼び出しと同様に、実行した呼び出しを認識します。これが問題となる場合は、ローカルモデルでサーバーを操作してください。

対象となる信頼境界(エージェントではなくオラクルとしてのモデル、モデルの出力を介したプロンプトインジェクション、DNS リバインディング対策のアドレス固定)と、対象外の内容については、SECURITY.md を参照してください。

インストール

以下の /path/to/mcp-ollama-vllm を、このプロジェクトを展開またはクローンしたディレクトリに置き換えてください。

Python 3.10 以降が必要です。仮想環境のセットアップ方法は同等のものが 2 通りあります。お使いのツールに合わせて選択してください。

標準の venv モジュールと pip を使用する場合:

cd /path/to/mcp-ollama-vllm
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

python3 -m venv が失敗する場合(ディストリビューションによっては python3-venv パッケージが不足しています)、そのパッケージをインストールするか、uv を使った方法を選択してください。

cd /path/to/mcp-ollama-vllm
uv venv .venv --seed
.venv/bin/pip install -r requirements.txt

(--seed を指定すると、環境に pip がインストールされます。代わりに --seed を指定せず、uv pip install --python .venv/bin/python -r requirements.txt でインストールする方法もあります。)

モジュールは src/mcp_ollama_vllm/ の下にあります(src レイアウトのため、パッケージ名が他の MCP サーバーのトップレベルモジュールと衝突しません)。上記のいずれかの方法で、PYTHONPATH=src を介して実行できるチェックアウトが用意できます(下記「MCP 設定のエントリ」を参照)。または、.venv/bin/pip install . でパッケージ自体をインストールできます。この場合、mcp-ollama-vllm コンソールスクリプトも提供され、PYTHONPATH は不要です。

実行するには、接続可能な Ollama または vLLM バックエンドが必要です。

  • Ollama:ローカルにインストールし(https://ollama.com を参照)、ollama serve で起動して、少なくとも 1 つのモデルをプルします(例:ollama pull llama3.2:3b)。ollama list で確認してください。local_embed を使う場合は、ollama pull nomic-embed-text のように、埋め込みモデルもプルしてください。
  • vLLM:このマシン上、またはローカルネットワーク上にある OpenAI 互換の vLLM エンドポイントを使用します。バックエンドの切り替えは、以下で説明する環境変数のみで行います。サーバー自体を変更する必要はありません。

MCP 設定エントリー

~/.claude.json、または Claude Code の設定の mcpServers セクションに追加します。

{
  "mcpServers": {
    "local-models": {
      "command": "/path/to/mcp-ollama-vllm/.venv/bin/python",
      "args": ["-m", "mcp_ollama_vllm.server"],
      "env": {
        "PYTHONPATH": "/path/to/mcp-ollama-vllm/src",
        "LOCAL_BACKEND": "ollama",
        "LOCAL_HOST": "http://localhost:11434",
        "LOCAL_TIMEOUT": "600"
      }
    }
  }
}

/path/to/mcp-ollama-vllm を、このプロジェクトを展開した絶対パスに置き換えてください。CLI から設定することもできます。

claude mcp add local-models --env PYTHONPATH=/path/to/mcp-ollama-vllm/src -- \
  /path/to/mcp-ollama-vllm/.venv/bin/python -m mcp_ollama_vllm.server

デフォルトでは、claude mcp add はサーバーを現在の作業ディレクトリに紐付けます。すべてのディレクトリから利用できるようにするには、--scope user を追加してください。

または、.venv/bin/pip install . の実行後(上記の「インストール」を参照)、PYTHONPATH の指定は不要になり、args はそのまま ["-m", "mcp_ollama_vllm.server"] にできます。また、インストール済みのコンソールスクリプトを command として直接使用することもできます。その場合、args は不要です:/path/to/mcp-ollama-vllm/.venv/bin/mcp-ollama-vllm

最初の呼び出し

MCP クライアントを再起動して新しいサーバーを認識させてから、引数なしで list_models を呼び出します。モデルが返されれば、サーバーが起動し、バックエンドを検出して正しく通信できていることを一度に確認できます。

環境変数
変数デフォルト説明
LOCAL_BACKENDollamaバックエンド:ollama、vllm、または openai
LOCAL_HOSTバックエンドによるエンドポイント。ローカル、または自身のネットワーク上にある必要があります
LOCAL_TIMEOUT600読み取りタイムアウト(秒)
LOCAL_EMBED_MODELnomic-embed-text(Ollama)デフォルトの埋め込みモデル
LOCAL_API_KEY-任意。openai のみ。Bearer ヘッダーとして送信され、ログには記録されません
LOCAL_EMBED_PATH/v1/embeddings任意。openai のみ。埋め込みエンドポイントのパス
LOCAL_SCHEMA_MODEauto任意。openai のみ。auto、response_format、または none(下記の local_structured を参照)

LOCAL_HOST のデフォルトは、Ollama の場合は http://localhost:11434、vLLM の場合は http://localhost:8000 です。openai にはデフォルトがありません。汎用バックエンドでは適切なポートを推測できないため、LOCAL_HOST は必須であり、指定せずにサーバーを起動することはできません。

既存の設定が壊れないよう、従来の OLLAMA_HOST、OLLAMA_TIMEOUT、OLLAMA_EMBED_MODEL も引き続き読み込まれます。両方が設定されている場合は、LOCAL_ で始まる名前が優先されます。

リモート vLLM エンドポイントへの切り替え

ローカルネットワーク上の vLLM マシンは、次の 2 つの変数だけで指定できます。サーバー自体を変更する必要はありません。

{
  "mcpServers": {
    "local-models": {
      "command": "/path/to/mcp-ollama-vllm/.venv/bin/python",
      "args": ["-m", "mcp_ollama_vllm.server"],
      "env": {
        "PYTHONPATH": "/path/to/mcp-ollama-vllm/src",
        "LOCAL_BACKEND": "vllm",
        "LOCAL_HOST": "http://192.168.x.x:8000",
        "LOCAL_TIMEOUT": "600",
        "LOCAL_EMBED_MODEL": "BAAI/bge-m3"
      }
    }
  }
}

どちらのバックエンドも異なる名前(例:local-models と local-models-server)で並行して登録できるため、同じセッション内で両方を利用できます。

同じサーバーを複数回登録する

同じサーバーをそれぞれ独自の LOCAL_BACKEND/LOCAL_HOST とともに、複数の名前で同時に登録しても問題ありません。これにより、たとえば次のように、同じセッション内で異なるバックエンドを利用できます。

  • ローカル Ollama(http://localhost:11434)用のエントリー
  • ネットワーク上の別のマシンにある vLLM 用のエントリー
  • 同じリモートマシン上の Ollama 用の 3 つ目のエントリー

これらはすべて、設定だけが異なる同一サーバーのインスタンスです。わかりやすい名前を付け、名前だけを頼りにせず、使用前にどのエントリーが意図したマシンとバックエンドに接続するか確認してください。

openai バックエンドも同じ方法で使え、新しいルーティング機構は必要ありません。異なるポートを使う 2 つの汎用サーバーエントリーを登録します。たとえば、LM Studio は http://localhost:1234、llama.cpp サーバーは http://localhost:8080 です。

{
  "mcpServers": {
    "local-models-lmstudio": {
      "command": "/path/to/mcp-ollama-vllm/.venv/bin/python",
      "args": ["-m", "mcp_ollama_vllm.server"],
      "env": {
        "PYTHONPATH": "/path/to/mcp-ollama-vllm/src",
        "LOCAL_BACKEND": "openai",
        "LOCAL_HOST": "http://localhost:1234"
      }
    },
    "local-models-llamacpp": {
      "command": "/path/to/mcp-ollama-vllm/.venv/bin/python",
      "args": ["-m", "mcp_ollama_vllm.server"],
      "env": {
        "PYTHONPATH": "/path/to/mcp-ollama-vllm/src",
        "LOCAL_BACKEND": "openai",
        "LOCAL_HOST": "http://localhost:8080"
      }
    }
  }
}

チャットに使うエンドポイントが埋め込みも提供しない場合(下記の local_embed を参照)、適切な LOCAL_EMBED_PATH を設定した 2 つ目のエントリー、または埋め込みに対応した別のバックエンドを指すエントリーを登録してください。1 つのエンドポイントで両方を処理できると想定しないでください。

タイムアウトは意図的に長めに設定されています。ローカルモデルは、コンテキストが長い場合や高負荷時に、応答まで数分かかることがあります。これとは別に、接続の確立には 10 秒という短い制限があるため、停止中の Ollama サービスは、10 分間待たされることなくすぐに検出されます。

その他の MCP クライアント

このサーバーは標準の stdio MCP を使用するため、 command/args/env 形式のサーバーエントリーに対応するクライアントなら、 同じように動作します。設定ファイルとキー名はクライアントごとに異なります。 mcpServers 形式のブロック(または同等の設定)がどこにあるか、各クライアントの ドキュメントを確認してください。一般的な例を 2 つ示します。

// Checkout-based, PYTHONPATH points at src/:
{
  "mcpServers": {
    "local-models": {
      "command": "/path/to/mcp-ollama-vllm/.venv/bin/python",
      "args": ["-m", "mcp_ollama_vllm.server"],
      "env": {
        "PYTHONPATH": "/path/to/mcp-ollama-vllm/src",
        "LOCAL_BACKEND": "ollama"
      }
    }
  }
}
// After `pip install .`, using the installed console script directly:
{
  "mcpServers": {
    "local-models": {
      "command": "/path/to/mcp-ollama-vllm/.venv/bin/mcp-ollama-vllm",
      "env": { "LOCAL_BACKEND": "ollama" }
    }
  }
}

ツール

list_models

パラメーターはありません。各モデルについて、名前、パラメーター数、量子化、 ディスク上のサイズ(GB およびバイト)、ファミリー、機能を返します。機能からは、 モデルが embedding に対応しているかどうかなどを確認できます。

vLLM では、バックエンドはサイズと量子化を報告しません。そのため、値を推測して 埋めるのではなく、該当するフィールドにはそれぞれ unknown と 0 が設定されます。 その代わり、次の 3 つのフィールドが設定されます。

  • is_adapter:エントリーが LoRA アダプターかどうかを示します(vLLM では、アダプターは モデルリスト内で独立したエントリーとして公開されます)。Ollama のエントリーにもこの フィールドがありますが、Ollama にはアダプターの概念がないため、常に False です。
  • base_model:アダプターが属するベースモデルを示します(parent フィールドから取得)。 Ollama のエントリーにもこのフィールドがありますが、常に空です。
  • max_context:モデルのコンテキスト長(max_model_len)を示します。vLLM が報告する 場合に設定されます。このフィールドは vLLM 専用です。Ollama のエントリーには そもそも含まれません。

トップレベルには、件数を示す adapters、アクティブなバックエンドの識別子である backend、さらに metadata_reported(True/False)もあります。これは、バックエンドが 上記のメタデータフィールドを実際に報告するかどうかを示すため、呼び出し側は「この バックエンドでは報告されない」(openai では通常 False)のか、「報告されているが、 値がたまたま空」なのかを判別できます。

local_ask
パラメーター必須デフォルト意味
modelはい-モデル名。例:llama3.2:3b
promptはい-指示
systemいいえ-ロール/動作の指示
temperatureいいえ00 = 可能な限り決定的
max_tokensいいえ-生成トークン数の上限
contextいいえ-プロンプトの先頭に追加するテキスト
thinkいいえfalseモデルの推論トレースを有効にします(後述)

戻り値:answer、model、model_digest、backend、duration_seconds、 tokens(input/output/total)、tokens_per_second、finish_reason。 モデル名とダイジェストにより、後から同じラベルが異なる重みを指すようになっても、 実行内容を追跡できます。

think パラメーター

思考モデル(list_models で機能が thinking と表示されるモデル。例:qwen3:4b)は、 実際の回答の前に推論します。この推論トレースも、回答と同じトークン予算を消費します。 そのため、max_tokens を小さく設定すると、呼び出し自体は成功していても回答は 空になります。1 回の呼び出しで、推論トークンが約 3000~4000 個測定されました。

そのため、think のデフォルトは false であり、ブリッジはすべての Ollama 呼び出しで think: false を送信します。推論トレースを明示的に必要とする場合は、think: true を設定します。

think フィールドは、false の場合も含めて、意図的に常に送信されます。 Ollama 0.31.2 での測定では、thinking 機能のないモデルに対する think: false は問題なく、 think: true の場合にのみ HTTP 400 "... does not support thinking" で拒否されます。 そのため、/api/tags による事前の機能確認は不要であり、このケースで発生するエラーメッセージから 原因を明確に特定できます。

vLLM では、ブリッジはこのパラメーターを受け付けますが、動作には反映しません。 思考の制御はまだ接続されていないためです。両方のバックエンドで呼び出し形式を同一に 保っているのは意図的です。汎用の openai バックエンドも、同じ理由から vLLM と同様に think を受け付けますが無視します。汎用的な推論制御(reasoning_effort、 chat_template_kwargs、...)はここでは対象範囲外です。

local_structured
パラメーター必須意味
modelはいモデル名
promptはいどのテキストから何を抽出するかを示す指示
schemaはい期待する結果の JSON スキーマ
systemいいえ役割や振る舞いに関する指示
thinkいいえモデルの推論過程を有効にします。デフォルトは false です(local_ask を参照)

スキーマをバックエンドのスキーマ強制機能に渡し、さらに回答をスキーマ自体に照らして検証します。Ollama では format フィールドを使用し、vLLM では(現在の vLLM ドキュメントの推奨に従い)OpenAI 互換の response_format と {"type": "json_schema", "json_schema": {...}} を使用します。古い vLLM ビルドがこれを HTTP 400 で拒否した場合は、古い guided_json に一度フォールバックするため、そちらでもブリッジを利用できます。汎用の openai バックエンドでも、デフォルトでは json_schema を指定した response_format を使用します。サーバーがこれを HTTP 400 で拒否した場合は(vLLM とは異なり、試せる guided_json はありません)、スキーマなしの呼び出しにフォールバックします。その結果は組み込みバリデーターによって検証され、違反があれば、ほかの未検証の回答と同様、具体的な違反内容をフィードバックして再試行します。どの経路が受け入れられたかはプロセス全体で記憶されるため、以降の呼び出しでは失敗する response_format の往復を省略します。LOCAL_SCHEMA_MODE を指定すると、この判定を上書きできます。response_format は常にこれを必要とし(拒否された場合はフォールバックせず、致命的なエラーになります)、none はこれを送信しません。違反があった場合は、具体的な違反内容をモデルに渡して最大 2 回再試行します。このフィードバックがなければ、温度 0 で再試行しても一字一句同じ結果になるため、意味がありません。それでも失敗した場合は、違反内容と無効な生の出力を含むエラーを返します。

戻り値:outcome(パース済みオブジェクト)、attempts、valid_on_first_try、model、model_digest、backend、duration_seconds、tokens。

検証対象は、実際に format で使用されるサブセットです:type(型のリストも含む)、properties、required、additionalProperties、items(タプル形式も含む)、enum、const、minimum/maximum、minLength/maxLength、minItems/maxItems、anyOf/oneOf/allOf。完全なドラフトではありません。$ref、pattern、format は検証されません。上記に記載されていないキーワードは強制されず、黙って無視されます。これには、exclusiveMinimum/exclusiveMaximum、multipleOf、uniqueItems、patternProperties、if/then/else、contains などが含まれます。

local_embed
パラメーター必須デフォルト意味
textsはい-埋め込み対象のテキストのリスト
modelいいえnomic-embed-text(Ollama)埋め込みモデル

戻り値:vectors、count、dimensions、model、backend、duration_seconds。

セルフテスト

テストは2つに分かれています。selftest.py は実際の Ollama に対して実行され、selftest_vllm.py はバックエンドを一切必要としない、高速なオフラインのモックゲートです。この2つに加えて、vLLM のパスも実際の vLLM に対して一度確認しています(下記の「実際の vLLM に対して測定した内容」を参照してください)。

.venv/bin/python selftest.py --rounds 10 --model llama3.2:3b   # against real Ollama
.venv/bin/python selftest_vllm.py                              # against a mock

どちらも MCP クライアントなしで実行できます。selftest.py は、実際の FastMCP ディスパッチ(mcp.call_tool)を通じてツールを呼び出します。これは接続されたクライアントが通るのと同じ経路であり、モデル自体の取得は行いません。

Ollama のテストでは、まず既知の違反例をまとめて使い、スキーマバリデーター自体を検証します(正確な件数は、バリデーターが対応するキーワードの増加に伴って増えます)。この手順がなければ、スキーマの検出率が高くても意味がありません。バリデーターが単に何も検出していない可能性もあるためです。その後、実際のモデルに指示してスキーマ違反を発生させることはできないため、HTTP アクセスを差し替えて、再試行と中断のロジックを決定論的に検証します。

selftest_vllm.py は、OpenAI 互換の3つのエンドポイントを模倣する小さな HTTP サーバーを起動し、テスト後に終了します。回答が正しく読み取られることと、送信リクエストが正しく構成されること(Ollama の format ではなく、json_schema を指定した response_format が実際に送信されること)の両方を検証します。vLLM のパスも実際の vLLM に対して確認済みの現在でも、高速なオフラインの決定論的ゲートとして有用です。モックはバックエンドを必要とせず、常に同じように実行されます。ただし、使用する vLLM のビルドでは異なる動作をする可能性があるため、実際の環境での確認にも価値があります。

測定した限界

以下の数値は、ビデオメモリが少ない(VRAM 約4 GB)ノートパソコン1台で、Ollama を -np 1、つまり処理スロット1つで実行したテスト結果です。保証値ではなく、おおよその目安としてご覧ください。別のハードウェアでは実行時間が異なります。

すべて問題なしと確認できた項目
項目結果
既知の不正なスキーマに対するスキーマバリデーターすべて正しく検出
再試行および中断のロジック(モデルなし)8/8
list_models(Ollama)8モデル、フィールドはすべて揃っています
local_ask の回答、トークン、時間正常。報告された所要時間は、外部で測定した時間と0.04秒以内で一致
local_ask の context と max_tokensどちらも有効(max_tokens: 16 で出力トークンが正確に16個)
local_embedベクトル3個、768次元、2.05秒
ベクトルの意味類似した文は0.815、類似しない文は0.574
エラー処理(5ケース)すべて理解しやすいメッセージを表示し、スタックトレースなし
実際の MCP クライアントを通じた stdio ハンドシェイク必須フィールドが正しく揃った4つのツールをすべて確認
ネットワークガードLAN とループバックは許可され、パブリックホストとパブリック IP は拒否
モックに対する vLLM のパス70/70
スキーマ忠実度、決定的な指標

qwen3:4b を使用し、30 種類の異なる入力で収集しました。各スキーマにつき 10 件です。測定したのは、組み込みの再試行なし、つまり 初回の試行で出力がスキーマに準拠した割合です。

スキーマn初回の試行で準拠失敗所要時間(秒)(最小値/中央値/最大値)
A フラット(enum、数値の上下限、additionalProperties: false)1010/10035 / 56 / 516
B ネストしたオブジェクト(2 階層)1010/10056 / 93 / 832
C オブジェクトのリスト(minItems、各要素の enum)1010/10063 / 107 / 827
合計3030/30(100 %)035 / 93 / 832

返されたすべてのオブジェクトは、スキーマに対する独立した再検証にも合格しました。また、内容も事実に即していました(例 B:文章から送信者、正味金額、通貨、リマインダーのフラグが正しく抽出されました)。

この 100 % の読み解き方。 バックエンドの文法拘束により、生成時点ですでに JSON 形式が強制されます。そのため、この割合が主に示すのは、モデルの賢さではなく、スキーマが十分に厳密かどうかです。この結果は、これら 3 つのスキーマ、このモデル、短いドイツ語の文章に対するものです。enum も上下限もない自由記述の文字列ばかりのスキーマでは、予想どおり精度が下がります。この一連のテストでは再試行ループは一度も必要ありませんでした。実際の vLLM 実行でも作動しませんでした(local_structured はそちらでも初回の試行で有効な値を返し、attempts: 1 でした)。再試行ループの正しさは、HTTP アクセスを置き換えて決定論的に検証しています。

ランタイムについて、そして測定値を歪めた要因

35 秒から 832 秒までのばらつきは、サーバーに起因するものではありません。測定の前半には、同じマシン上で無関係な別のテストが実行されていました(Python プロセスが一時的に CPU を 1011 % 使用し、ロードアベレージは 21~28 でした)。ここでは Ollama を -np 1 で実行しており、マシン全体で処理スロットは 1 つだけです。測定結果は次のとおりです。

  • 他の処理による負荷があると、1 回の呼び出しに 309~832 秒かかり、負荷がない場合は 35~107 秒でした。
  • 1 回の呼び出しはデフォルトのタイムアウトである 600 秒に達し、失敗しました。
  • 他の処理による負荷がある状態では、llama3.2:3b への切り替えはまったく不可能でした。別のモデルがスロットを使用している間、まだ読み込まれていないモデルへのリクエストは無期限に待機します。2 回試しましたが、応答がないまま 400 秒後に中断しました。
  • 明らかな回避策である num_gpu: 0(4 GB の VRAM 競合を避けるため、CPU のみで計算する方法)も、ボトルネックが GPU ではなく CPU だったため、効果がありませんでした。

これらはどれもこのサーバーの問題ではありませんが、1 台のマシンでこのアプローチを使う場合の実用上の限界であり、そのためここに記載しています。

実際の vLLM で測定した内容

vLLM の経路はモックサーバーを使って検証されています。エンドポイントが正しいこと、正しく構成されたリクエスト(json_schema を含む response_format)、guided_json へのフォールバック、LoRA の検出、統一されたメタデータ、分かりやすいエラーを確認しています。さらに、実際の vLLM に対してツールを一度実行しました。これは Qwen2.5-1.5B-Instruct(max_model_len 8192)を提供する OpenAI 互換のエンドポイントで、実際の FastMCP ディスパッチ(mcp.call_tool)を通じて実行しました。接続済みのクライアントが使用するのと同じ経路です。

  • list_models -> GET /v1/models:バックエンドは vllm と報告され、モデルが認識され、max_context は 8192、adapters は 0 でした。
  • local_ask -> POST /v1/chat/completions:回答テキスト、トークン数(入力/出力/合計)、tokens_per_second、finish_reason stop を確認しました。
  • local_structured -> POST /v1/chat/completions(response_format は json_schema):スキーマに準拠したオブジェクトが返され、初回で有効でした(valid_on_first_try: true、attempts: 1)。これはスキーマ強制の経路そのものであり、モックだけでなく実際の vLLM でも確認できました。
  • local_embed -> POST /v1/embeddings:バックエンドから 404 が返されましたが、サーバーはこれを明確なメッセージ(「バックエンドから報告:Not Found ...;vLLM ではサービスの起動時にモデルが固定されます」)として適切に処理しました。これは正しい動作であり、障害ではありません。テスト用 vLLM が提供していたのはチャットモデルであり、埋め込みモデルではなかったため、呼び出せる埋め込みエンドポイントがなく、サーバーはそのことを明確に伝えました。

したがって、実際に未検証なのは vLLM の埋め込み経路です。実際の Ollama では測定しましたが、埋め込みモデルを提供する vLLM デプロイでは測定していません(テスト用 vLLM はチャット専用でした)。vLLM の埋め込みを使う場合は、埋め込みに対応した vLLM を実行する必要があります。チャット専用の vLLM に対しては、サーバーが意図的に明確なエラーを返します。それ以外にも、お使いのビルドによっては、response_format 経路を使うか、従来の guided_json 経路を使うかが異なる場合や、より大きなモデルと LoRA アダプターを使った場合のスキーマへの忠実度が異なる場合があります。

運用上の注意
  • 一度に使えるモデルは 1 つです。 -np 1 の場合、ローカルの Ollama インスタンスは 2 つの並列エージェントで共有できないリソースです。長時間の実行の前に、curl localhost:11434/api/ps を一度確認しておくと役立ちます。
  • 600 s のタイムアウトは短めで、余裕はありません。 外部負荷があると超過しました。測定を実行する際は LOCAL_TIMEOUT をより大きな値に設定してください。
  • 思考モデルは引き続きコストが高いものの、危険ではなくなりました。 qwen3:4b は「Paris」という回答に対して、推論してから回答するため、出力トークンを 186 個生成しました。分類や抽出には、思考ステップのないモデル(llama3.2:3b、granite4:micro)のほうが引き続き高速で低コストです。qwen3 の各バリアントでは、think パラメーター(デフォルトは false)が推論トレースを制御します。ブリッジはこれを自動でオフにするため、max_tokens を小さく設定しても、空文字列ではなく回答が得られます。
  • 長時間の実行は再開できるようにしてください。 そうしないと、外部負荷による中断のたびに、そこまで収集した値がすべて失われます。

注意点

  • Ollama の format だけでは有効な結果は保証されません。 文法バインディングによって JSON 形式は強制されますが、required、enum、数値の境界は確実には強制されません。そのため、このサーバーは独自に追加検証を行います。独自の検証なしで format を使うと、不完全なオブジェクトが気付かれないまま返されます。
  • 小型モデルには厳密なスキーマが必要です。 enum、required、境界値を多く指定するほど、結果の実用性が高まります。自由記述の文字列だけで構成されたスキーマでは、モデルが内容を作り上げてしまいます。
  • VRAM の競合が最も大きな時間のロスになります。 要求された 2 つのモデルが同時にビデオメモリに収まらない場合、Ollama は切り替えのたびにモデルを再読み込みします。サーバーに問題がなくても、呼び出しに数秒ではなく数分かかることがあります。可能であれば、モデルは 1 つだけ使ってください。

ライセンス

MIT。詳細は LICENSE を参照してください。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。