はじめに
MCPサーバーは ファイル操作やコマンド操作といったタスクを処理できるので セキュリティ対策としてDockerでサンドボックス化して起動することが推奨されます。
ただ、自作したMCPサーバーをDockerで実行すると、ツールの中間出力(応答やエラーなど)が見えにくくなり、デバッグが難しくなる場面があります。
また、Transport(Stdio/SSE/Streamable HTTP)の切り替えもAIエディタ経由だとやや面倒です。
本記事では、自作したMCPサーバーをターミナル上でクライアントを使って動作確認(デバック)する方法を紹介します。
EPSS MCPサーバーの紹介
本記事で使用するのは 脆弱性情報を取得する 自作のMCPサーバーです。
できること:
- 脆弱性情報(説明、CWE、CVSSスコア)を取得
- EPSSスコアとパーセンタイルを取得(複数のCVE指定も可)
- EPSS時系列データを取得
- EPSSの上位N件を取得
※NVD API、EPSS API を使用しています
ベースのコードは EPSS-MCP を参考にしました。
脆弱性情報取得の実行例
以下は Windsurfで SWE-1(free) モデルを使用した際の実行例です。
「あの脆弱性何だっけ?」風に脆弱性情報を取得してみます。
私: 「ADの権限昇格の脆弱性あったよね。なんだっけ?」
応答:
↓ Web検索と知識ベースからの情報提示(LLM単独での応答)」
私: 「一覧で概要を知りたい」
応答:
↓ ツール(get_cve_info) を使用して 各CVEのCVSS,EPSSスコアを表示
私: 「Zerologon の EPSS 履歴を見せて」
応答:
↓ ツール(get_cve_info) を使用して EPSSの履歴を表示
うまく動けば良いのですが、AIエディタ経由では出力が期待通りでない場合、生の応答に問題があるのか、LLMの生成に問題があるのか等、デバッグが難しいと思うことがあります。
MCPクライアントの実装
MCPサーバーの動作検証やデバッグ1 目的で各トランスポート対応(SSE2, Streamable HTTP)のMCPクライアント用意しました。
MCPクライアント の実装は以下のサイトを参考にさせてもらいました。コードはほぼそのまんまです。
コードの簡単な解説
client.py では ユーザーからのクエリを Chat UIが LLM に送信し、MCPクライアント経由でツールリストを取得したり、選択したツールの呼び出しを行います。
ここでは主要な処理 connect_to_server()とprocess_query() のコードの概要を記載します。
使用モデル : gemini-2.0-flash
MODEL_NAME = "gemini-2.0-flash"
MAX_TOKENS = 1000
MCP接続の初期化
async def connect_to_server(self, server_script_path: str):
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
await self.session.initialize()
...
ツール取得
- MCPクライアント経由でMCPサーバーの利用可能なツール情報を取得します
async def process_query(self, query: str) -> str:
"""Process a query and available tools"""
..
response = await self.session.list_tools()
available_tools = [
{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
},
}
for tool in response.tools
]
LLM に問い合わせ
- LLMにユーザーのクエリ
messagesと、利用可能なツールの一覧をchat.completions.create()で渡します - LLMはどのツールを使うかを判断して返してきます(
response)
..
response = self.openai.chat.completions.create(
model=MODEL_NAME,
max_tokens=MAX_TOKENS,
messages=messages,
tools=available_tools,
)
..
ツール実行
- LLM が選定した
tool_callに従って、MCPクライアント経由で MCPサーバーのツールを実行します - 実行結果を再度LLMに
chat.completions.create()で渡します
message = response.choices[0].message
..
for tool_call in message.tool_calls:
tool_name = tool_call.function.name
tool_call_id = tool_call.id
tool_args = json.loads(tool_call.function.arguments)
tool_result = await self.session.call_tool(tool_name, tool_args)
..
messages.append(
{
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": json.dumps(tool_result_contents),
}
)
..
response = self.openai.chat.completions.create(
model=MODEL_NAME,
max_tokens=MAX_TOKENS,
messages=messages,
tools=available_tools,
)
..
各Transport の実装の違い
client.py(studio), client_sse.py, client_http.py(Streamable HTTP) の3つのクライアントを用意しました。Transportにおける主な違いはパッケージの import と connect_to_server の実装部分のみです。
Studio(client.py)
from mcp.client.stdio import stdio_client
async def connect_to_server(self, server_script_path: str):
..
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write = stdio_transport
Streamable HTTP(client_http.py)
from mcp.client.streamable_http import streamablehttp_client
async def connect_to_server(self, url: str):
streamable_transport = await self.exit_stack.enter_async_context(streamablehttp_client(url))
self.stdio, self.write, _ = streamable_transport
SSE(client_sse.py)
from mcp.client.sse import sse_client
async def connect_to_server(self, url: str):
sse_transport = await self.exit_stack.enter_async_context(sse_client(url))
self.stdio, self.write = sse_transport
MCPクライアントを使った動作確認例
ご自身のMCPサーバーで確認することを想定しています。あくまで例としてご参照ください。
MCPクライアントとMCPサーバーの準備
# リポジトリをクローン
git clone https://github.com/kodamap/epss_mcp/
# uv インストール(ない場合)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 仮想環境を準備
uv sync
. .venv/bin/activate
クライアントChatが連携する LLM(gemini-2.0-flash)を使うには、Gemini API Keyが必要です。
export GOOGLE_API_KEY=<your api key>
ターミナル上で動作確認
ターミナル上で各Transportの動作を確認します。
Studio
プロンプト:「CVE-2025-33053 のEPSS履歴教えて」
# client の引数として MCPサーバーのプログラムを指定
$ uv run client/client.py epss-mcp/epss_mcp.py
Connected to server with tools: ['get_cve_info', 'top_epss_cves']
MCP Client Started!
Type your queries or `quit` to exit.
Query: CVE-2025-33053 のEPSS履歴教えて
> Thinking...
> Thinking...
[Calling tool get_cve_info with args {'time_series': True, 'cve_id': 'CVE-2025-33053'}]
CVE-2025-33053のEPSS履歴は以下の通りです。
| date | epss | percentile |
|------------|-----------|------------|
| 2025-07-09 | 0.417630000 | 0.972670000 |
| 2025-07-08 | 0.417630000 | 0.972640000 |
| 2025-07-07 | 0.417630000 | 0.97265000 |
| 2025-07-06 | 0.37155000 | 0.96976000 |
Streamable HTTP
サーバーを起動
# --transport http を付けて起動
$ uv run epss-mcp/epss_mcp.py --transport http
INFO: Started server process [66074]
INFO: Waiting for application startup.
[07/10/25 17:30:16] INFO StreamableHTTP session manager started streamable_http_manager.py:112
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
クライアントを起動(サーバーは起動したままターミナルの別窓開く)
プロンプト:「CVE-2025-33053 のEPSS取得して」
$ uv run client/client_http.py http://localhost:8000/mcp
Connected to server with tools: ['get_cve_info', 'top_epss_cves']
MCP SSE Client Started!
Type your queries or `quit` to exit.
Query: CVE-2025-33053 のEPSS取得して
> Thinking...
> Thinking...
[Calling tool get_cve_info with args {'cve_id': 'CVE-2025-33053'}]
CVE-2025-33053のEPSSスコアは0.41763、パーセンタイルは97.267です。
SSE(Server-Sent Events)
サーバーを起動
# --transport sse を付けて起動
$ uv run epss-mcp/epss_mcp.py --transport sse
INFO: Started server process [67653]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
クライアントを起動(サーバーは起動したままターミナルの別窓開く)
プロンプト:「EPSSの上位5件を取得して」
$ uv run client/client_sse.py http://localhost:8000/sse
Connected to server with tools: ['get_cve_info', 'top_epss_cves']
MCP SSE Client Started!
Type your queries or `quit` to exit.
Query: EPSSの上位5件を取得して
> Thinking...
> Thinking...
[Calling tool top_epss_cves with args {'top_n': 5}]
上位5件のCVEは以下の通りです。
CVE-2023-42793, EPSSスコア: 0.94584
CVE-2024-27198, EPSSスコア: 0.94577
CVE-2023-23752, EPSSスコア: 0.94532
CVE-2024-27199, EPSSスコア: 0.94489
CVE-2018-7600, EPSSスコア: 0.94489
まとめ
MCPサーバーをAIエディタ経由で使うだけでは、ツールの呼び出しやレスポンスの中身を細かく検証するのは難しい場面もあります。
クライアントの中で ChatをUIとしてLLMを使用しているので、MCP単体ではなくLLMとも連携して確認できるのが便利かなと思います。
MCPサーバーの動作確認やデバッグのお役に立てば幸いです。
-
MCPサーバーのデバックには MCP Inspector があるんですね。執筆時点で知らず。。 ↩
-
SSE(Server-Sent Events) はDeprecated になっています。
Windsurf では、2025/7/10現在 Streamable HTTP はサポートしていないため、 Stdio か SSE を使う必要があります。「Windsurf supports two transport types for MCP servers: stdio and /sse」7/19 追記 Streamable HTTP 対応してました。「We can also support streamable HTTP transport and MCP Authentication.」
https://docs.windsurf.com/windsurf/cascade/mcp ↩




