eset-mcp について
eset-mcpは、MCPホスト経由でESET環境を管理するチームやエージェント向けのMCPサーバーです。複数のクラウド環境とオンプレミス環境を単一プロセスで振り分け、テナント分離と読み取り専用・読み書きモードを備えます。
主な機能
- 01ESET管理APIを幅広くカバー
- 02クラウドとオンプレミスを一括ルーティング
- 03テナントごとの認証情報とトークン分離
- 04読み取り専用・読み書きモードの制御
- 05ページネーション、再試行、レスポンス整形
- 06構造化ログとPrometheusメトリクス
活用シーン
- 01MCPクライアントからデバイスやインシデントを検索
- 02複数のESETテナントへリクエストを振り分け
- 03エージェントの操作を読み取り専用に制限
- 04クラウドとオンプレミスのコンソールを一元接続
MCP 対応
ツール、リソース、プロンプトを通じてESET管理機能をMCPクライアントに公開し、stdioとStreamable HTTPに対応します。テナントとリクエストヘッダーに応じて、クラウドリージョンまたはオンプレミスコンソールへ振り分けます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm ghcr.io/maciekaz/eset-mcp:1.0.3ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/maciekaz/eset-mcp:1.0.3stdioツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
ESET-MCP
ESET Connect(クラウド、全リージョン)、ESET PROTECT On-Prem、ESET Inspect、ESET Cloud Officeを含む、ESETの管理機能全体をカバーするModel Context Protocolサーバーです。ツール、リソース、プロンプトを通じて、あらゆるMCPホスト(Claude Desktop、Claude Code、 またはカスタムエージェント)から操作できます。
任意の数のESETデプロイメントに対応する単一ハブとして構築されています。1つのプロセスで、クラウドとオンプレミスのコンソールを同時に扱えます。クライアントはリクエストごとにヘッダーで対象を選択します。MCPが有効な認証情報(Basic認証に加え、任意のURL上書きと任意のCloudflare Accessサービス トークン)を受け取ると、適切なバックエンドに呼び出しをルーティングし、独自のトークンを発行して、プール内でテナントを分離します。
⚠️ 念のため明記します
これは独立したコミュニティ主導のオープンソースプロジェクトであり、ESET, spol. s r.o.とは提携しておらず、公式のサポートや推奨も受けていません。ESETおよびその製品名は、それぞれの所有者の登録商標です。厳格な読み取り専用モードのゲートなど、本ソフトウェアの安全性と堅牢性を確保するためにあらゆる努力を払っていますが、このコードは一切の保証なく「現状のまま」提供されます。このツールの使用方法およびESET環境に加える変更については、すべて利用者ご自身の責任となります。
目次
- 機能
- アーキテクチャの概要
- セキュリティ
- クイックスタート
- 設定
- マルチテナントデプロイメント(Basic認証モード)
- オンプレミスのESET PROTECTサポート
- 本番環境へのデプロイメント(Caddy経由のHTTPS)
- ツール、リソース、プロンプト
- アーキテクチャ
- テスト
- OpenAPI仕様の更新
- ライセンス
機能
APIを完全にカバー
- 16個の公式ESET Connect OpenAPI 3.0.1仕様から自動生成された102個のツールで、アプリケーション管理、アセット管理、自動化、デバイス管理、ID、インシデント管理、インストーラー管理、モバイルデバイス管理、ネットワークアクセス保護、パッチ管理、ポリシー管理、隔離管理、ユーザー管理、脆弱性管理、ウェブアクセス保護をカバーします。
- 3〜6個の生の呼び出しを1つにまとめる4個の高レベル複合ツール:
eset_search、device_full_profile、incident_full_context、latest_detections。
読み取り専用/読み書きモード
ESET_MODE=RO→ カタログには読み取り専用ツールのみが公開されます(合計51個)。 書き込みツールはlist_toolsに一切表示されません。ESET_MODE=RW→ 106個すべてのツールが公開され、変更を伴うツールにはMCPアノテーションでdestructiveHint: trueが付与されます。- ESETアカウントの基盤となる権限設定とは独立して動作します。
- 多層防御のため、メモリ内ゲートがROモードでのRWツール名を拒否し、HTTPリクエストの送信を防ぎます。
認証
ESET_AUTH_MODE=env- シングルテナントで、認証情報は.envから取得します。ESET_AUTH_MODE=basic- マルチテナントで、クライアントはリクエストごとにAuthorization: Basic <base64(user:password)>を渡します(別のクラウドリージョンを指定する場合は任意でX-ESET-Region、オンプレミスの PROTECT コンソールにリクエストをルーティングする場合はX-ESET-Server-URLも指定できます)。1つのサーバーで複数の ESET アカウントに対応でき、同じプロセス内でクラウドとオンプレミスを混在させられます。- テナントごとの OAuth トークンは、
(user, password_hash, deployment, region-or-server-url, cf_secret_hash)によって プールされ、分離されます。 パスワードまたは Cloudflare Access サービストークンをローテーションすると新しいクライアントが作成されます。同じユーザーのクラウドクライアントとオンプレミスクライアントがプールエントリーを共有することはありません。
トランスポート
- stdio - ローカルホスト向けの stdin/stdout 経由の JSON-RPC です。
- Streamable HTTP - 現在の MCP トランスポート(2025年11月の仕様)です。
マルチリージョン
eu / de / us / ca / jpn。env モードでは ESET_REGION で固定し、
basic モードではリクエストごとに X-ESET-Region で指定します。
1つのプロセスでのクラウドとオンプレミス
クラウドリージョンに加えて、1つの MCP サーバーで顧客がホストする
ESET PROTECT On-Prem コンソールにも対応できます。オンプレミスの認証ワイヤ形式
(camelCase のレスポンスを返す POST /GetTokens)とホストごとの URL
構造は透過的に処理されます。クライアントはリクエストごとに
X-ESET-Server-URL ヘッダーで接続先を選択します。
オンプレミス ESET PROTECT のサポートを参照してください。
Cloudflare Access(任意)
オンプレミスコンソールが Cloudflare Access トンネルの背後にある場合、MCP は
サービス トークン
(環境変数のデフォルト値、またはリクエストごとの X-ESET-CF-Access-Client-Id /
X-ESET-CF-Access-Client-Secret)で認証し、オリジンまでリクエストを中継します。
CF トークンは、ESET アカウントの認証情報に追加される入口レイヤーであり、それらを置き換えるものではありません。
クラウドへのリクエストにこれらのヘッダーが含まれることはありません。
耐障害性
- OAuth2 トークンの有効期限が切れる約5分前に先行更新し、401 の場合は強制的に更新して再試行します。
- 429 は指数バックオフで再試行します(最大3回、
Retry-Afterを尊重します)。 - ページネーション(
nextPageToken)は透過的に処理されます。 response-idヘッダーを使った 202 のロングポーリングは、最大10分間行います。
レスポンス整形(コンテキストウィンドウの保護)
上限のない list_* 呼び出しを1回行うだけで、数百 KB のレスポンスが返る場合があり、モデルのコンテキストがあふれる可能性があります。すべてのツールレスポンスに次の2つの変換を適用します。
fieldsによる投影 - すべての GET ツールで、省略可能なfields: [string]パラメーターを使用できます。このパラメーターは、各リスト項目を指定したキー(例:["uuid", "displayName"])に絞り込みます。取得後にサーバー側で適用されます。- バイト上限(
ESET_MCP_RESPONSE_BYTES_MAX、デフォルトは 100 KB)- ペイロードが引き続き上限を超える場合、最長のリストを切り詰めます。すべてのトップレベルフィールド(nextPageToken、totalSizeなど)は保持され、続行方法を示す実用的なヒントを含む_cappedメタデータブロックが付加されます。 エージェントはページネーションを通じて、引き続きデータ全体にアクセスできます。
エージェントにわかりやすいエラー
HTTP エラーは、わかりやすいヒントに変換されます。403 → ESET PROTECT Hub の Permission Sets を確認してください。401 → サーバーがトークンを自動更新します。429 → バックオフしてください。5xx → 少し待ってから再試行してください。
可観測性(ログ+Prometheus)
- 構造化ログを標準エラー出力に記録します。開発時はテキスト形式(デフォルト)、本番環境のログ収集ツールでは
ESET_MCP_LOG_FORMAT=jsonにより JSON Lines 形式を使用します。すべてのツール呼び出し、トークンの更新、HTTP の再試行、プールからの削除について、低カーディナリティのフィールド(tool、deployment、status、duration_ms、response_bytes など)を含む、型付きのeventレコードを出力します。 - オプトイン方式の
/metricsエンドポイントで Prometheus メトリクスを提供します(ESET_MCP_METRICS_ENABLED=true。pip install eset-mcp[metrics]が必要です)。ツール呼び出し、トークンの更新、HTTP の再試行、上限到達についてはカウンターを、ツールの実行時間とレスポンスサイズについてはヒストグラムを、クライアントプールのサイズについてはゲージを提供します。 - ログやメトリクスに記録されることのない情報:パスワード、
Authorizationヘッダー、CF Access シークレット、リクエスト/レスポンス本文、クエリー文字列、置換後のパスパラメーター(UUID が漏えいする可能性があります)。ロガーには防御用の拒否リストがあり、フォーマッターに渡される前に既知の機密キーを除去します。 - 障害の分離:テレメトリーの出力は try/except で囲まれているため、メトリクスレジストリーやフォーマッターに問題があっても、成功したツール呼び出しがエージェントへのエラーに変わることはありません。メトリクスの公開処理で例外が発生した場合、
/metricsは 503 ではなく 500 を返し、ワーカーは稼働し続けます。 - 本番環境でのログ抑制:
ESET_LOG_LEVEL=WARNINGを設定すると、呼び出しごとの INFO イベントを抑制しつつ、再試行やエラーは引き続き確認できます。ESET_LOG_LEVEL=ERRORを設定すると、重大な障害以外のログをすべて抑制します。ESET_MCP_METRICS_ENABLED=false(デフォルト)でメトリクスを完全に無効化できます。この 3 つの設定はそれぞれ独立しています。
アーキテクチャの概要
最もシンプルな構成は、認証情報を .env に保存し、MCP ホストを 1 つ、ESET クラウドリージョンを 1 つ使用する方法です。個人利用、単一チームでの利用、Claude Desktop や Claude Code のようなデスクトップ AI クライアントに適しています。
実際のマルチテナント環境やエンタープライズ環境では、ESET-MCP の前段に認証情報マネージャーを配置してください。以下の図は、IBM mcp-context-forge を「認証情報管理」レイヤーとして使う構成例です。同等の MCP ゲートウェイ(または独自の認証プロキシ)でも同じように機能します。
forge はテナントごとのシークレットを保持し、リクエストごとに Authorization: Basic、X-ESET-Region、X-ESET-Server-URL、X-ESET-CF-Access-* ヘッダーを挿入します。ESET-MCP は各リクエストを適切なバックエンド(クラウドリージョン、オンプレミスの PROTECT コンソール、または Cloudflare Access の背後にあるオンプレミス環境)にルーティングします。テナントごとの LRU クライアントプールにより、テナント間で OAuth トークンが完全に分離されます。これは構想上のパターンではなく、実際に動作するパターンです。ここで説明するヘッダー、プールキー、ルーティングルールはすべて、tests/test_concurrency.py と tests/test_onprem.py のテストスイートで検証されています。
セキュリティ
認証情報
envモードでは、パスワードは起動時に一度読み込まれ、メモリー上に保持されます。basicモードでは、パスワードが通信上に存在するのはリクエストの処理中のみで、メモリー上に存在するのもテナントごとのクライアントが LRU プール内でアクティブな間のみです。ログに記録されることはありません。- プールキーには、パスワードそのものではなく、パスワードの SHA-256 ハッシュを使用します。
- OAuth アクセストークン/リフレッシュトークンはテナントごとに保持され、1 つのセッション内でテナント間をまたいで共有されることはありません。
認証モードとトランスポート
| モード | 許可されるトランスポート | 認証情報の取得元 |
|---|---|---|
env | stdio または http | .env(ESET_USER / ESET_PASSWORD) |
basic | http のみ | リクエストごとの Authorization: Basic ヘッダー |
プレーン HTTP で basic モードを使用すると、パスワードが漏えいします。サーバーは起動時に basic モードで HTTP トランスポートを強制しますが、TLS は強制しません。TLS の設定はデプロイ側の責任です。prod docker-compose プロファイルでは、Caddy と Let's Encrypt を通じてサーバーを公開します。
basic モードで Authorization がない場合、または形式が不正な場合 → WWW-Authenticate: Basic チャレンジを含む HTTP 401。X-ESET-Region のリージョンが不明な場合 → HTTP 401。
RO / RW の分離
独立した 2 つのレイヤーがあります。
- カタログの非表示 - RO モードでは、
list_toolsが GET 以外のすべてのツールを除外します。エージェントには書き込みツールが表示されません。 - 多層防御ゲート -
call_toolは HTTP リクエストを送信する前に、ツールで宣言されたモードをESET_MODEと照合します。ハードコードされたクライアント、プロンプトインジェクションの試み、古いエージェントのスナップショットもすべてこのゲートに阻まれ、構造化されたModeForbiddenErrorのテキスト応答を受け取ります(例外は発生せず、ネットワーク呼び出しも行われません)。
RW モードでは、変更を伴うツールに destructiveHint: true が付与されます。注釈に対応する MCP ホストでは、呼び出しごとに確認を求めることができます。
テナントごとの分離(basic 認証モード)
- 認証ヘッダーは専用の ASGI ミドルウェアで解析され、
ContextVarに保存されます。リクエスト本文やログに含まれることはありません。 - 各リクエストは、
(user, password_hash, region)をキーとするCredentialsインスタンスに解決されます。 - クライアントプールには LRU の上限が設定されており、ランダムな認証情報を大量に試されてもメモリが無制限に増加するのを防ぎます。
ネットワークの公開範囲
- 開発環境(
docker compose up)では、MCP サーバーが:8765を公開します。 prodプロファイルでは、MCP コンテナに公開ポートはありません。Caddy が同じ Docker ブリッジネットワークに接続し、HTTPS リクエストをプロキシします。ホストで公開されるポートは 80(HTTP-01 ACME)と 443(HTTPS)のみです。*.eset.systems(認証と API)以外へのアウトバウンド通信はありません。
依存関係とコードの監査
- Snyk Code:
eset_mcp/の問題は 0 件です。 - Ruff:問題なし(
select = E F W I B UP RUF)。 - 実行時の依存関係:
mcp、httpx、pydantic、python-dotenv。HTTP で実行する場合はstarletteとuvicornも必要です。
対象範囲外(設計上)
- Webhook の受信機能はありません。
- 永続ストレージはありません。ログは stdout に出力されます。
- OAuth トークンのディスクへのキャッシュはありません。
basicモードの認証情報をディスクに書き戻すことはありません。
責任ある脆弱性開示
公開 issue ではなく、非公開のセキュリティアドバイザリを作成してください: https://github.com/maciekaz/ESET-MCP/security/advisories/new。
クイックスタート
最も手軽なのは、公開済みの Docker イメージを使う方法です。Python のインストールも、venv も、ソースのチェックアウトも不要です。.env と docker run だけで利用できます。イメージはマルチアーキテクチャ(amd64 + arm64)に対応し、cosign で署名済みです。SBOM とビルドの来歴情報が含まれており、リリースのたびに GHCR に公開されます。
cp .env.example .env # fill in ESET_USER / ESET_PASSWORD / ESET_REGION
docker run --rm -i --env-file .env ghcr.io/maciekaz/eset-mcp:1
バージョン固定ポリシー:
:1- 最新の 1.x.x(メジャーバージョンの範囲内で自動更新):1.0- 最新の 1.0.x(マイナーバージョンの範囲内で自動更新):1.0.1- 特定のバージョン(本番環境向け):latest- 最新の安定版リリース:main/:sha-<short>- main からのエッジビルド(本番環境では使用しないでください)
Claude Desktop / Claude Code に接続する(stdio)
// claude_desktop_config.json
{
"mcpServers": {
"eset": {
"command": "docker",
"args": ["run", "--rm", "-i", "--env-file", "/absolute/path/to/.env",
"ghcr.io/maciekaz/eset-mcp:1"]
}
}
}
HTTP トランスポート(シングルテナント、または独自プロキシの背後で使用)
docker run -d --name eset-mcp \
--env-file .env -p 8765:8765 \
-e ESET_MCP_TRANSPORT=http \
ghcr.io/maciekaz/eset-mcp:1
# MCP endpoint: http://localhost:8765/mcp
イメージを検証する(cosign keyless)
cosign verify \
--certificate-identity-regexp '^https://github.com/maciekaz/ESET-MCP/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/maciekaz/eset-mcp:1
ソースから実行する(コントリビューター/ローカルでの開発)
git clone https://github.com/maciekaz/ESET-MCP.git
cd ESET-MCP
cp .env.example .env
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
eset-mcp
Docker Compose(公開済みイメージを使用)
docker compose up -d eset-mcp-http
# MCP endpoint: http://localhost:8765/mcp
デフォルトでは、compose ファイルは ghcr.io/maciekaz/eset-mcp:1 を取得します。ローカルでのビルドは不要で、初回起動も高速です。特定のバージョンに固定するには、docker-compose.yml の image: 行を編集してください。
compose を使って stdio で一度だけ実行する場合:
docker compose --profile stdio run --rm eset-mcp-stdio
ソースを編集していますか?プルする代わりに、ローカルのチェックアウトからビルドするには dev プロファイルを使用してください。
docker compose --profile dev up --build eset-mcp-http-dev
設定
すべての設定は .env に記述します。必須項目は
.env.example に記されています。
| 変数 | デフォルト | 用途 |
|---|---|---|
ESET_AUTH_MODE | env | env(シングルテナント)または basic(マルチテナント) |
ESET_USER | - | API ユーザー(env モードで必須) |
ESET_PASSWORD | - | API パスワード(env モードで必須) |
ESET_MODE | RO | RO(読み取り専用カタログ)または RW |
ESET_REGION | eu | eu / de / us / ca / jpn |
ESET_MCP_TRANSPORT | stdio | stdio または http |
ESET_MCP_HTTP_HOST | 127.0.0.1 | HTTP バインドアドレス |
ESET_MCP_HTTP_PORT | 8765 | HTTP ポート |
ESET_MCP_RESPONSE_BYTES_MAX | 100000 | 1 回の呼び出しあたりのレスポンスバイト数上限。0 で無効化 |
ESET_LOG_LEVEL | INFO | DEBUG / INFO / WARNING / ERROR |
ESET_MCP_LOG_FORMAT | text | text(開発用、人が読みやすい形式)または json(本番用ログ収集ツール向け) |
ESET_MCP_METRICS_ENABLED | false | Prometheus の /metrics をマウント。eset-mcp[metrics] が必要 |
ESET_MCP_METRICS_PATH | /metrics | メトリクスエンドポイントのマウント先 |
ESET_DEPLOYMENT | cloud | cloud(ESET Connect)または onprem(顧客ホスト型 PROTECT) |
ESET_ONPREM_SERVER_URL | - | オンプレミスコンソールの https://host[:port](env+onprem で必須) |
ESET_ONPREM_VERIFY_SSL | true | 自己署名証明書を使用するオンプレミスコンソールでは false に設定 |
ESET_ONPREM_CF_ACCESS_CLIENT_ID | - | Cloudflare Access Service Token の client-id(CF 経由のオンプレミス環境向け) |
ESET_ONPREM_CF_ACCESS_CLIENT_SECRET | - | Cloudflare Access Service Token の client-secret(上記と組み合わせて使用) |
ESET_PUBLIC_DOMAIN | - | Caddy が TLS 証明書を発行するドメイン(prod プロファイルのみ) |
ESET_ACME_EMAIL | - | Let's Encrypt が更新に使用するメールアドレス(prod プロファイルのみ) |
コンソールへのログインには使わず、専用の API ユーザーを使用してください。ESET PROTECT Hub / ESET Business Account → API users で作成してください。
マルチテナントデプロイ(basic 認証モード)
# .env
ESET_AUTH_MODE=basic
ESET_MCP_TRANSPORT=http
ESET_REGION=eu # default region; clients can override per request
すべての HTTP リクエストに、次のヘッダーを含める必要があります。
| ヘッダー | 必須 | 備考 |
|---|---|---|
Authorization | はい | Basic <base64(user:password)> |
X-ESET-Region | いいえ | デフォルトのリージョンを上書き(eu/de/us/ca/jpn) |
X-ESET-Server-URL | いいえ | このリクエストをオンプレミスの PROTECT コンソールにルーティングします(例:https://protect.example.com:9443)。オンプレミス対応を参照してください |
X-ESET-CF-Access-Client-Id | いいえ | Cloudflare Access Service Token の client-id(CF Access 経由のオンプレミス環境向け) |
X-ESET-CF-Access-Client-Secret | いいえ | 上記と組み合わせて使用します。両方を必ず送信してください |
Python クライアントの例:
import base64
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
token = base64.b64encode(b"[email protected]:secret").decode()
headers = {"Authorization": f"Basic {token}", "X-ESET-Region": "us"}
async with streamablehttp_client(
"https://eset-mcp.example.com/mcp/", headers=headers
) as (r, w, _):
async with ClientSession(r, w) as session:
await session.initialize()
tools = await session.list_tools()
⚠️ TLS を使用しない Basic 認証では、認証情報が漏洩します。
basicモードは必ず HTTPS 経由で実行してください。
オンプレミス版 ESET PROTECT のサポート
ESET は、クラウドサービス(*.eset.systems の ESET Connect API)と、お客様がセルフホストするオンプレミスコンソールの両方で PROTECT を提供しています。オンプレミスの REST API は単一のホスト(デフォルトのポートは 9443)上にあり、認証エンドポイントは異なります。JSON ボディを使用する POST /GetTokens と、キャメルケース形式のレスポンスを使用します。それ以外は、クラウド API と同じ URL 構造です。ESET-MCP は、同じプロセス内で両方をサポートします。
サーバーがクラウドとオンプレミスを判別する方法
ESET_AUTH_MODE | リクエストごとのデプロイ先を制御する方法 |
|---|---|
env | 固定:ESET_DEPLOYMENT(クラウド)、または ESET_DEPLOYMENT=onprem + ESET_ONPREM_SERVER_URL |
basic | リクエストごと:X-ESET-Server-URL がある場合、そのリクエストをオンプレミスに切り替えます。ない場合は環境変数のデフォルト(クラウドまたはオンプレミス)にフォールバックします。 |
つまり、1 つの MCP サーバーで、ほとんどのクライアントにはクラウドを提供しつつ、X-ESET-Server-URL で各クライアントが送信する URL に応じて、特定のリクエストを 1 つ以上のオンプレミスコンソールにルーティングできます。
シングルテナントのオンプレミス(env モード)
# .env
ESET_AUTH_MODE=env
ESET_DEPLOYMENT=onprem
ESET_ONPREM_SERVER_URL=https://protect.company.local:9443
ESET_ONPREM_VERIFY_SSL=true # set to false only for self-signed certs you trust
[email protected]
ESET_PASSWORD=...
マルチテナントのオンプレミス(basic 認証、リクエストごとの URL)
# .env
ESET_AUTH_MODE=basic
ESET_MCP_TRANSPORT=http
ESET_DEPLOYMENT=cloud # default; clients opt into on-prem per-request
# ESET_ONPREM_SERVER_URL is optional - if set it becomes the on-prem default
オンプレミスを指定するクライアント:
headers = {
"Authorization": f"Basic {token}",
"X-ESET-Server-URL": "https://protect.client-a.local:9443",
}
同じ MCP サーバーに別のリクエストを送信し、X-ESET-Server-URL 以外のヘッダーを同じにすると、クラウドに接続されます。
オンプレミスとクラウドで利用できる機能
ツールカタログはどちらのデプロイ先でも同じです(OpenAPI から生成された全 102 個のツールと、4 個の複合ツール)。呼び出し時に、サーバーはクラウドの認証情報にはクラウドのパスを、オンプレミスの認証情報にはオンプレミスのパスを使用します。
- 共通で検証済み:
device_*、asset_groups_*、policy_*、およびtask_*(Automation)の大半は、クラウドとオンプレミスで同じように動作します。 - クラウド専用モジュール:
incident_*、mobile_*、wap_*、nap_*、quarantine_*、およびvuln_*の大半は、別個の ESET 製品(ESET Inspect、Cloud Office Security、MDM)に対応しており、オンプレミス版 PROTECT のインストールには含まれません。オンプレミスコンソールに対して呼び出すと、ESET から通常の 404 が返され、特別な処理は行われず、ESET API error: 404というテキストレスポンスとしてエージェントに表示されます。 - パスの上書き:一部のエンドポイントは、オンプレミスでは URL が異なります。たとえば、
POST /v1/devices/{uuid}:renameはオンプレミスでは:renameDeviceです。これらはeset_mcp/openapi/onprem-path-overrides.jsonで定義されており、リクエストがオンプレミスを対象とする場合に自動的に適用されます。
オンプレミスコンソールの手前に Cloudflare Access を配置する
オンプレミス版 PROTECT コンソールが Cloudflare トンネル経由で公開され、Cloudflare Access で保護されている場合、MCP はサービス トークンとして認証できます。接続経路は MCP → Cloudflare Access → ESET オンプレミスとなります。
トークンペアごとに 2 つの値を指定します。.env 経由で指定する場合:
ESET_ONPREM_CF_ACCESS_CLIENT_ID=abc1234567890.access
ESET_ONPREM_CF_ACCESS_CLIENT_SECRET=<long-secret>
…または basic 認証モードでリクエストごとに指定します(環境変数のデフォルト値を上書きします。各テナントがそれぞれ独自のトンネルとサービス トークンを持つ場合に便利です):
headers = {
"Authorization": f"Basic {token}",
"X-ESET-Server-URL": "https://protect.client-a.local:9443",
"X-ESET-CF-Access-Client-Id": "abc1234567890.access",
"X-ESET-CF-Access-Client-Secret": "<long-secret>",
}
MCP は X-ESET-CF-* 入力ヘッダーを、Cloudflare Access が必要とする CF-Access-Client-Id / CF-Access-Client-Secret ヘッダーに変換し、POST /GetTokens の認証ハンドシェイクと、その後のすべての ESET API リクエストの両方を含む、すべての送信呼び出しに付加します。
CF シークレットはパスワードと同様に扱われます。ログには記録されず、SHA-256 ハッシュのみがクライアントプールキーに含まれます。シークレットをローテーションすると、新しいクライアントと新しい ESET トークンが発行されます。ESET Connect は公開 SaaS のため、環境変数のデフォルト値にかかわらず、クラウドへのリクエストに CF Access ヘッダーが付加されることはありません。
オンプレミス環境でのセキュリティに関する注意事項
X-ESET-Server-URLが受け付けるのは、パス、クエリ、フラグメントのないhttps://URL のみです。末尾のスラッシュは削除されます。それ以外の場合は HTTP 400 が返されます。ESET_ONPREM_VERIFY_SSL=falseにすると、TLS 証明書の検証が無効になり、接続が MITM にさらされます。無効になっている場合、サーバーはクライアントの構築ごとに WARNING を 1 回だけログに記録します。交換できない自己署名証明書を使用する、信頼できるイントラネットでのみ使用してください。- オンプレミストークンは、
(user, password_hash, server_url, cf_secret_hash)ごとにメモリ上に保持されます。クラウドトークンと同じ分離ルールが適用されます。プールではトークンごとに別のキーを使用するため、クラウドクライアントとオンプレミスクライアントが衝突することはありません。また、同じオンプレミス URL にアクセスするクライアントでも、CF サービストークンが異なれば、別々のプールエントリーが割り当てられます。 - 2 つの
X-ESET-CF-*ヘッダーのうち片方のみを送信すると、環境変数のデフォルト値に暗黙的にフォールバックせず、HTTP 400 が返されます(オペレーターの入力ミスである可能性が非常に高いためです)。
本番環境へのデプロイ(Caddy 経由の HTTPS)
prod docker-compose プロファイルを起動すると、MCP サーバーの前段で Caddy が起動します。Caddy は初回起動時に Let's Encrypt 証明書を取得し(HTTP-01 チャレンジを使用するため、ポート 80 / 443 がパブリックインターネットから到達可能である必要があります)、内部の MCP コンテナーに HTTPS をプロキシします。
# .env
ESET_AUTH_MODE=basic
ESET_PUBLIC_DOMAIN=eset-mcp.example.com
[email protected]
docker compose --profile prod up -d
# MCP endpoint: https://eset-mcp.example.com/mcp
以下を利用できます。
- 自動更新される Let's Encrypt 証明書を使った、ポート 443 の HTTPS。
- ポート 80 の HTTP-01 チャレンジ。
- Docker ブリッジネットワークにのみバインドされ、ポートが公開されない MCP コンテナー。
- gzip / zstd 圧縮と、標準出力への JSON アクセスログ出力。
ツール、リソース、プロンプト
複合型の高レベルツール
| ツール | 戻り値 |
|---|---|
eset_search(query, kinds?, limit_per_kind?) | デバイス/ユーザー/ポリシー/グループを対象とした、大文字と小文字を区別しない部分一致検索結果 |
device_full_profile(deviceUuid) | デバイスレコード + 最近の検知 + 脆弱性 + 最近のスキャン |
incident_full_context(incidentUuid) | インシデント + コメント + 関連する検知 + 影響を受けたデバイス |
latest_detections(hours=24, limit=10, severity_min?) | 指定した時間内で最も新しい検知。occurTime の降順で並べ替え。v2 から v1 へのフォールバック |
サブ呼び出しが 403/404 を返した場合、各複合ツールは適切に処理を継続します(例:テナントに該当モジュールがない場合)。該当する場合、返されるデータには skipped/truncated フラグが含まれます。
リソース
eset://config/mode-ROまたはRW。eset://config/region- 現在のリージョン(ベーシック認証モードではリクエストごとに取得)。eset://config/deployment- このリクエストのcloudまたはonprem (<server-url>)。eset://config/tools-catalog- 106 個の全ツールの JSON カタログ(名前、モード、メソッド、パス、サービス、説明)。eset://docs/rate-limits- 10 req/s の上限に関する簡単な注意事項。
プロンプト
audit_inactive_devices(days=30)- オフボーディング候補。vulnerability_report- デバイスごとの CVE レポート。incident_triage- オープン中のインシデントと関連する検知。
アーキテクチャ
eset_mcp/
├── __main__.py # entrypoint - stdio or HTTP, wires resolver + pool
├── server.py # MCP server (tools / resources / prompts) + telemetry
├── credentials.py # Credentials + EnvResolver / BasicAuthResolver + ContextVar
├── middleware.py # ASGI Basic-auth middleware (basic mode only)
├── client_pool.py # LRU pool of EsetHttpClient keyed by (user, region, ...)
├── http_client.py # async httpx + 202 polling + 429 retry + 401 refresh
├── auth.py # CloudTokenManager (OAuth2) + OnPremTokenManager (/GetTokens)
├── regions.py # cloud region → per-service domains + on-prem URL resolver
├── modes.py # RO/RW gate
├── errors.py # HTTP error → agent-friendly text
├── config.py # .env loading
├── response_shaping.py # fields projection + byte cap
├── composite_tools.py # hand-written high-level tools
├── tools_loader.py # generator: tools from OpenAPI specs + on-prem path overrides
├── observability/ # JSON/text structured logging + Prometheus metrics
└── openapi/ # 16 ESET Connect OpenAPI 3.0.1 specs + onprem path overrides
テスト
pytest # full suite (RO smoke + unit + integration)
pytest -m "not rw" # RO only (default in CI)
pytest -m rw # RW (requires an account with RW permissions)
統合テストでは実際の ESET テナントに接続します。認証情報は同じ .env から読み込まれます。CI ワークフロー:
.github/workflows/integration.yml
は、PR 時、main へのプッシュ時、および毎日 03:17 UTC に実行されます。cron による定期実行では、サーバーと ESET が公開している OpenAPI 仕様との乖離を検出します。
OpenAPI 仕様の更新
cd eset_mcp/openapi
for name in business-account application-management asset-management automation \
device-management iam incident-management installer-management \
mobile-device-management network-access-protection patch-management \
policy-management quarantine-management user-management \
vulnerability-management web-access-protection; do
curl -sO "https://eu.esetconnect.eset.systems/swagger/api/${name}.json"
done
tests/test_catalog_vs_openapi.py は、更新後に新規または変更されたオペレーションがないかを検出します。
ライセンス
MIT
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。