【調査レポ12】Pythonで自作MCPサーバーを本格構築!FastMCPによるTools・Resources・Promptsローカル実践ガイド

Antigravity SDK入門 AI

前回の調査レポ11では、オープン標準「MCP(Model Context Protocol)」サーバーへ Google Antigravity SDK から接続し、外部ツールをプラグイン感覚でエージェントに組み込むクライアント側の連携手法を検証しました。

接続方法が分かると、次に湧き上がってくるのが「自社の社内ツールや独自スクリプト、データベースをMCPサーバー化して、AIエージェントに自在に操作させたい!」というニーズです。既存の公開MCPサーバーを利用するだけでなく、自分の目的に合わせたMCPサーバーを自作できるようになると、AIエージェントの適用範囲は一気に広がります。

そこで今回は視点を切り替え、Pythonの公式推奨フレームワーク「FastMCP」を活用し、実務で役立つ「システム管理・ログ監視MCPサーバー」をローカル環境でゼロから構築する手順を詳しくレポートします!前回の簡易版では扱えなかったMCPの主要機能(Tools, Resources, Prompts)を取り入れ、実際に Google Antigravity SDK から自律実行させるまでの一連の流れをお届けします。

なぜ今「自作MCPサーバー」を構築するのか?

これまでのエージェント開発では、AIに実行させたい処理がある場合、各フレームワーク固有の形式でPython関数を書き、エージェント設定に直接登録する手法が一般的でした。

しかし、MCP(Model Context Protocol)の標準規格に沿ってツールサーバーを独立して構築すると、以下のような大きなメリットが得られます。

  • エージェント本体との疎結合:ツールのロジック変更や依存ライブラリの更新をサーバー側で完結でき、エージェント本体のコードを汚しません。
  • マルチクライアントでの再利用性:一度自作したMCPサーバーは、Google Antigravity SDK だけでなく、Claude Desktop やその他のMCP対応ツールからそのまま共通利用できます。
  • 実行権限やセキュリティの局所化:システム操作や外部通信を行う危険な処理を別プロセスとして分離・カプセル化できます。

MCPサーバーの形態:今回は手軽で安全な「ローカル構築(stdio方式)」を実践

「サーバー」という言葉を聞くと、クラウド上で24時間稼働し続ける専用マシンや、ネットワークポートを開放してWebアクセスを待ち受ける大掛かりなシステムを連想するかもしれません。
しかし、MCPにおける「サーバー」とはハードウェアのことではなく、「AI(クライアント)からの要求を受け取って処理結果を返す側」というソフトウェア上の役割を意味しています。

MCPには、利用形態に応じて大きく2つの接続方式が存在します。

  • ローカルスクリプト方式(stdio方式 / 今回採用):
    自分のパソコン(ローカル環境)に置いたPythonスクリプトを、AIエージェントが裏で子プロセスとして自動起動し、標準入出力(キーボード入力と画面出力に相当するパイプ)を通じてJSON形式でやり取りします。ポートの開放やファイアウォール設定が不要で、外部からの不正アクセスの心配もなく、手軽かつ安全に自作・検証が可能です。
  • リモートサーバー方式(SSE / HTTP方式):
    社内サーバーやクラウド上で常時プロセスを稼働させておき、URL(例: https://mcp.example.com/sse)経由でネットワーク越しに接続する方式です。複数のユーザーやエージェントで1つのMCPサーバーを共有したい実務環境に適しています。この場合、ローカル側には処理スクリプト本体は不要となり、AIエージェント側の設定に接続先URLとAPIキーを記載します。

本レポートでは、特別なインフラ契約やネットワーク設定を必要とせず、誰でも手元の環境ですぐに試せる「ローカル環境にPythonスクリプトとしてMCPサーバーを構築し、Google Antigravity SDKから呼び出す手順」を詳しく解説していきます。

MCPサーバーが提供する主要機能の基礎知識

Model Context Protocol において、サーバー側がクライアントに対して公開する代表的な要素として「Tools」「Resources」「Prompts」が定義されています。自作サーバーを構築する際は、提供したい機能がどれに該当するかを意識して設計していきます。

コア機能FastMCPデコレータ主な役割と具体例
Tools(ツール)@mcp.tool()エージェントが自律的に呼び出してアクションを起こす関数です。単にデータを読み取るだけでなく、外部システムの操作・変更や、引数に応じた調査・検索処理を実行する際に適しています。
例:プロセス検索、ポート導通チェック、ファイル作成、外部API実行。
Resources(リソース)@mcp.resource("uri://...")エージェントがコンテキストとして「読み取る(参照する)」ための静的・動的なデータです。システムの状態を変更せず、情報を参照するためにURIを指定して取得します。
例:最新エラーログ(log://system/error)、システム構成情報、DBスキーマ。
Prompts(プロンプト)@mcp.prompt()サーバー側からクライアントに提供できる再利用可能なプロンプトテンプレートです。エージェントに対する定型指示を標準化します。
例:障害発生時の一次切り分けテンプレート(incident_triage)。

FastMCPによる開発環境の準備

PythonでMCPサーバーを構築する際、現在最も広く利用されているのが「FastMCP」です。
公式の mcp パッケージに含まれており、FastAPIライクな直感的なデコレータ構文を用いて、型ヒント(Type Hints)と関数のDocstringからAIが理解可能なJSON Schemaを自動生成してくれます。

# 仮想環境の作成と有効化
python3 -m venv .venv
source .venv/bin/activate

# MCP公式パッケージのインストール
pip install mcp

実践ステップ1:実務型「システム管理・ログ監視MCPサーバー」の構築

サーバー保守やインフラ運用で役立つ機能を備えたMCPサーバー(server_ops_mcp.py)を作成します。
外部の追加パッケージを必要とせず、Python標準ライブラリ(socketsubprocessdatetime)のみを組み合わせて実装しているため、どんな環境でも手軽に検証できます。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
実務型システム管理・ログ監視MCPサーバー (server_ops_mcp.py)
Google Antigravity SDK および Model Context Protocol (MCP) 対応サーバー。
FastMCPフレームワークを活用し、Tools / Resources / Prompts の主要機能を提供します。
"""

import sys
import socket
import datetime
import subprocess
from mcp.server.fastmcp import FastMCP

# =====================================================================
# 0. MCPサーバーインスタンスの初期化
# =====================================================================
# 引数にサーバーの識別名(一意な名前)を指定して初期化します。
# FastMCPが内部でJSON-RPC 2.0メッセージの通信やプロトコル交渉、各種マネージャーを自動構築します。
mcp = FastMCP("ServerOpsMCP")

# =====================================================================
# 1. Tools(能動的アクション実行)
# =====================================================================
# @mcp.tool() デコレータを付与することで、AIエージェントが自律的に呼び出せる関数として公開されます。
# 関数の引数型ヒント(Type Hints)とDocstringが自動解析され、AIが解釈するツール仕様(JSON Schema)になります。

@mcp.tool()
def search_processes(keyword: str) -> str:
    """指定されたキーワードに一致する実行中プロセスを検索し、PIDとプロセス名を一覧表示するツール。

    Args:
        keyword (str): 検索対象のプロセス名またはキーワード (例: 'python', 'node', 'nginx')

    Returns:
        str: 検索結果テキスト
    """
    # 引数が空文字や空白のみの場合は、無駄な外部コマンド実行を避けるため早期リターン
    if not keyword or not keyword.strip():
        return "検索キーワードを指定してください。"

    keyword = keyword.strip()
    try:
        # OSの 'ps' コマンドを実行し、現在稼働中の全プロセスの PID と コマンド名を取得
        # -e: すべてのプロセスを表示
        # -o pid,comm: 出力列を「プロセスID(PID)」と「実行コマンド名(COMM)」のみに絞り込む
        # capture_output=True: 標準出力と標準エラー出力をキャプチャして取得
        # text=True: 出力結果をバイト列ではなく文字列(str)として受け取る
        # timeout=5: 万が一コマンドがハングアップした場合に備え、5秒で安全にタイムアウトさせる
        result = subprocess.run(
            ["ps", "-eo", "pid,comm"],
            capture_output=True,
            text=True,
            timeout=5
        )
        # コマンドの終了ステータスが 0 以外(異常終了)の場合はエラー内容を返却
        if result.returncode != 0:
            return f"プロセス一覧の取得に失敗しました: {result.stderr}"

        matching = []
        # psの出力結果を改行で分割し、1行ずつ走査(1行目は "PID COMM" の見出しヘッダーなので [1:] でスキップ)
        for line in result.stdout.strip().splitlines()[1:]:
            # 空白で最大2つに分割(PID と コマンドパス/名前)
            parts = line.strip().split(None, 1)
            if len(parts) == 2:
                pid, comm = parts
                # 大文字・小文字の差異を無視してキーワードが含まれているか判定
                if keyword.lower() in comm.lower():
                    # 見やすさのため PID を右寄せ6文字にフォーマットしてリストに追加
                    matching.append(f"PID: {pid.rjust(6)} | Command: {comm}")

        # 一致するプロセスが存在しなかった場合の通知メッセージ
        if not matching:
            return f"キーワード '{keyword}' に一致する実行中プロセスは見つかりませんでした。"

        # AIのコンテキスト(トークン消費量)を圧迫しないよう、上限を20件に制限して結果を整形
        header = f"=== キーワード '{keyword}' の一致プロセス ({len(matching)}件) ==="
        return header + "\n" + "\n".join(matching[:20])

    except Exception as e:
        # 【注意点】stdio通信では標準出力がAIとの通信パイプになるため、通常のprint()を使うと
        # 通信メッセージに余計な文字列が混ざり切断の原因になります。ログは別ルートの「標準エラー出力(sys.stderr)」へ書き出します
        sys.stderr.write(f"search_processes error: {e}\n")
        return f"プロセス検索中にエラーが発生しました: {e}"


@mcp.tool()
def check_port_status(port: int, host: str = "127.0.0.1") -> str:
    """指定されたホストとポート番号の接続状態(LISTEN / 疎通可否)をチェックするツール。

    Args:
        port (int): 調査対象のポート番号 (例: 80, 443, 5432)
        host (str): 調査対象のホスト名またはIPアドレス (デフォルト: '127.0.0.1')

    Returns:
        str: ポートの疎通判定結果テキスト
    """
    # ポート番号がTCP規格の有効範囲(1〜65535)に収まっているかを事前にバリデーション
    if not (1 <= port <= 65535):
        return f"無効なポート番号です: {port} (1〜65535の範囲で指定してください)"

    try:
        # Python標準の socket モジュールでTCPソケットを作成
        # AF_INET: IPv4アドレス体系
        # SOCK_STREAM: TCPプロトコルによる接続
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            # 接続待ちのタイムアウトを2秒に設定(ポートが閉じていても過度に待たされないための配慮)
            sock.settimeout(2.0)
            
            # connect_ex() は例外を発生させず、成功なら 0、失敗ならOSのエラーコード(errno)を返す
            result = sock.connect_ex((host, port))
            
            if result == 0:
                # 接続成功(ポートが開いており、サービスがLISTEN待機中)
                return f"[ポート開放中] {host}:{port} はアクセス可能(サービス待機中 / LISTEN)です。"
            else:
                # 接続失敗(ポートが閉じているか、接続拒否された状態)
                return f"[ポート未開放] {host}:{port} には接続できませんでした (エラーコード: {result})。"

    except Exception as e:
        # エラー発生時も stdio の JSON-RPC 通信を壊さないよう stderr に出力
        sys.stderr.write(f"check_port_status error: {e}\n")
        return f"ポートチェック中にエラーが発生しました: {e}"


# =====================================================================
# 2. Resources(動的コンテキスト参照)
# =====================================================================
# @mcp.resource() デコレータを付与することで、エージェントが受動的に「読み取る(参照する)」データになります。
# MCPの仕様ではリソースを一意なURIで識別するため、開発者が自由に決められる独自の住所(カスタムURI、例: "log://system/error")を指定します。
# Toolsと違って引数を取らず、アクセスされた時点の最新データや状態を返します。

@mcp.resource("log://system/error")
def get_error_log() -> str:
    """システムの直近のエラーログや警告メッセージを動的に取得するリソース。

    Returns:
        str: 直近のシステムエラーログ
    """
    # リソースが参照された現在時刻を取得してタイムスタンプを動的に作成
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    # 実環境では /var/log/syslog やアプリケーションログファイルを読み込みますが、
    # 本デモでは外部設定なしで安全に動作確認できるよう、リアルタイムに動的ログテキストを生成して返します
    logs = [
        f"[{now}] [WARN] [AuthService] 連打によるレートリミット警告を検知しました (Client: 192.168.1.50)",
        f"[{now}] [ERROR] [DatabasePool] 接続タイムアウトが発生しました (DB: PostgreSQL, Host: localhost:5432)",
        f"[{now}] [INFO] [SystemMonitor] メモリ使用率: 45.2% | CPU平均負荷: 0.85",
        f"[{now}] [WARN] [DiskObserver] キャッシュディレクトリの使用容量が80%を超過しています"
    ]
    return "=== システム直近ログ(最新4件) ===\n" + "\n".join(logs)


# =====================================================================
# 3. Prompts(再利用可能なプロンプトテンプレート)
# =====================================================================
# @mcp.prompt() デコレータを付与することで、サーバー側が推奨する「調査の定型プロンプト」を公開できます。
# クライアント側はゼロからプロンプトを考えなくても、このテンプレートに沿ってAIに指示を出すことができます。

@mcp.prompt()
def incident_triage(incident_desc: str) -> str:
    """インシデント発生時の一次切り分け調査をエージェントに指示する標準プロンプト。

    Args:
        incident_desc (str): 発生した障害や不調の概要説明

    Returns:
        str: 一次切り分け用インストラクション
    """
    # ユーザーから受け取った障害内容をテンプレートに埋め込み、
    # リソース確認 ➔ ツール実行 ➔ 推奨アクション報告 の3ステップを明確に指示するプロンプトを返却
    return (
        f"以下のインシデントについて、接続されているMCPツールを活用して一次切り分け調査を実施してください。\n\n"
        f"【インシデント概要】\n{incident_desc}\n\n"
        f"【調査指示】\n"
        f"1. 直近のシステムエラーログ(log://system/error)を確認し、関連するエラーを特定してください。\n"
        f"2. 関連するプロセスやポートの稼働状態をツールで確認してください。\n"
        f"3. 判明した事実と推奨される復旧アクションを箇条書きで報告してください。"
    )


# =====================================================================
# 4. サーバーの待機起動(メインブロック)
# =====================================================================
if __name__ == "__main__":
    # stdio トランスポート(標準入出力)でサーバーを待機起動
    # ※【注意点】
    # 通常の print() を使うと、AIとの通信パイプ(標準出力)に余計な文字列が混ざり、
    # 通信メッセージが壊れて切断の原因になります。ログやメッセージの出力には sys.stderr を使用します。
    sys.stderr.write("ServerOpsMCP starting on stdio transport...\n")
    mcp.run()

server_ops_mcp.py の詳細コード解説

上記で実装した自作MCPサーバーコードの設計意図と、各ブロックの重要な実装ポイントを詳しく見ていきましょう。

1. サーバーの初期化(FastMCPインスタンス化)

mcp = FastMCP("ServerOpsMCP") でMCPサーバーの本体を生成しています。引数にはサーバーの識別名(サーバー名)を指定します。
FastMCPが内部でJSON-RPC 2.0のメッセージハンドラやプロトコル交渉、ツール・リソース・プロンプトの各マネージャーを自動的にセットアップしてくれるため、低水準なプロトコル処理を手動で書く必要がありません。

2. Tools(能動的アクション)の実装ポイント

@mcp.tool() デコレータを付与した通常のPython関数が、エージェントから呼び出し可能な「ツール」として自動公開されます。

  • 型ヒントとDocstringの役割(最重要)
    FastMCPは、関数の引数型(keyword: str など)やDocstring(三重引用符内の説明文・Args・Returns)を自動解析し、AIモデルが解釈するツール仕様(JSON Schema)を動的に生成します。そのため、Docstring内に「どんな値を渡すべきか」「何が返ってくるか」を丁寧に書いておくことが、AIによる的確なツール選択・実行を導く鍵となります。
  • プロセス検索ツール(search_processes
    Python標準の subprocess.run(["ps", "-eo", "pid,comm"], ...) を使用してプロセス一覧を取得しています。AIのコンテキスト(トークン消費)を圧迫しないよう、検索結果の上限を20件(matching[:20])に絞り込むようにしています。
  • ポート導通チェックツール(check_port_status
    標準ライブラリの socket を活用し、TCP接続を試行しています。1 <= port <= 65535 によるポート番号の範囲バリデーションを行い、不正な値に対しては即座にエラーメッセージを返します。また、例外を出さずに接続結果を数値で受け取れる sock.connect_ex() を採用することで、LISTEN待機中(返り値 0)か未開放(接続拒否など)かを短時間で安全に判定しています。
  • 標準エラー出力(sys.stderr)によるエラーハンドリング
    関数の except ブロックでは、デバッグログを sys.stderr.write(...) に出力しています。stdioトランスポート環境において、もし print() で標準出力に文字列を書き出してしまうと、JSON-RPCの通信データ列が破損してクライアントとの接続が即座に切断されてしまうためです。

3. Resources(動的コンテキスト参照)の実装ポイント

@mcp.resource("log://system/error") デコレータを付与することで、エージェントがコンテキストとして参照できる「リソース」を定義しています。

  • なぜ「カスタムURI(log://…)」を指定するのか?
    MCPの共通規格では、AIエージェントが参照するデータ(Resources)は「一意な住所(URI)」で識別する仕様になっています。Webサイトの https://... やローカルファイルの file://... と同じ構造であり、先頭の log: は「どのような種類のデータか」を表すスキーム(Scheme)にあたります。
    インターネットの標準規格に縛られず、開発者が自由に分かりやすい名前(独自のカスタムスキーム)を決められるため、今回はログ情報だと一目で伝わるよう log://system/error と定義しました。これにより、AIエージェントに対して「この一意な住所を読めば最新のエラーログが取得できる」と明確にデータソースを指示できます。
  • 動的なデータスナップショットの生成
    リソース取得関数(get_error_log)が呼ばれた瞬間のタイムスタンプ(datetime.now())を付与し、最新のエラーログメッセージをテキストとして生成して返します。実務環境であれば、実際のログファイル(/var/log/syslog 等)やDockerコンテナログを読み出して返す実装に応用できます。

4. Prompts(再利用可能な定型指示)の実装ポイント

@mcp.prompt() デコレータを用いて、サーバー側が推奨する「インシデント調査手順」をプロンプトテンプレート(incident_triage)として定義しています。

  • 標準化された調査ワークフローの提供
    障害概要(incident_desc)を受け取り、「①エラーログリソースを確認 ➔ ②関連ポートやプロセスをツールで調査 ➔ ③推奨アクションを報告」という手順書を自動生成します。クライアント側はプロンプトの設計に悩むことなく、サーバー推奨のフローに沿った調査をエージェントに指示できます。

5. サーバーの待機起動(stdioトランスポート)

スクリプト末尾の mcp.run() により、標準入出力(stdio)モードでMCPサーバーが待機状態に入ります。親プロセス(Google Antigravity SDK やデバッグツール等)から起動された際、標準入力を通じてJSON-RPCリクエストを受け取り、該当するツールやリソースの処理結果を標準出力へJSON形式で返答する通信サイクルが自動確立されます。

6. 活用しているPython標準モジュール

  • subprocess:OSの ps コマンドを安全に呼び出し、実行中プロセスのPIDとコマンド名を取得してキーワード抽出しています。
  • socket:指定したホストとポートに対するTCP接続試行(connect_ex)を行い、ポートのLISTEN待機状態を短時間で判定しています。
  • datetime:リアルタイムにタイムスタンプを付与した動的ログデータを生成するために利用しています。
  • sys:JSON-RPC通信を壊さない安全なエラーロギング(sys.stderr.write)のために活用しています。

実践ステップ2:公式MCP Inspectorによる単体デバッグ

自作したMCPサーバーをAIエージェントと接続する前に、「サーバー単体でツールやリソースが仕様通りに動くか」を事前に確認することが開発効率を高める秘訣です。
Model Context Protocol 公式から、ブラウザGUIでMCPサーバーの動作テストができる「MCP Inspector」というデバッグツールが提供されています。

# MCP Inspector を起動して自作サーバーをGUIでテスト(Node.js 22以降を推奨)
npx -y @modelcontextprotocol/inspector@latest python3 server_ops_mcp.py

コマンドを実行するとローカルサーバーが立ち上がり、ブラウザ画面上で search_processes の引数入力テストや、log://system/error リソースの内容プレビューを手動で手軽に試すことができます。

MCP Inspector の停止方法

動作確認が終わった後の停止手順は以下の通りです。

  • ターミナルで停止コマンドを入力
    Inspector を起動しているターミナルを選択し、キーボードで ControlC(Mac環境でも Command ではなく Control キー)を押します。プロセスが停止して元のプロンプト画面(%$)に戻れば停止完了です(一度で止まらない場合は再度 ControlC を押してください)。
  • ブラウザタブを閉じる
    ターミナル側のサーバープロセスが停止したら、ブラウザで開いていた Inspector 画面のタブはそのまま閉じてしまって問題ありません。

実践ステップ3:Google Antigravity SDKからの接続と自律検証

自作MCPサーバー(server_ops_mcp.py)の準備が整ったら、前回の調査レポ11で学んだ types.McpStdioServer を使って Google Antigravity SDK エージェントと接続します。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Google Antigravity SDK × 自作MCPサーバー連携テスト (test_server_ops_agent.py)
"""

import os
import sys
import asyncio
from pathlib import Path
from google.antigravity import Agent, LocalAgentConfig, types

# .env から APIキーを読み込み
api_key = os.environ.get("GEMINI_API_KEY")

async def main():
    print("=== Google Antigravity SDK × 自作MCPサーバー検証開始 ===\n")

    # 1. 自作MCPサーバースクリプトの絶対パスを解決
    server_script = os.path.abspath("server_ops_mcp.py")

    # 2. McpStdioServer による接続定義
    ops_mcp = types.McpStdioServer(
        name="server_ops_server",
        command=sys.executable,  # 実行中の仮想環境の Python を使用
        args=[server_script]
    )

    # 3. エージェント設計(mcp_servers に自作サーバーを登録)
    config = LocalAgentConfig(
        system_instructions=(
            "あなたは高度なインフラ監視・保守を担当するSREアシスタントです。"
            "接続されているMCPサーバーから提供されるツールを積極的に呼び出し、"
            "正確なプロセス情報やポート稼働状態を調査してエンジニア向けに報告してください。"
        ),
        mcp_servers=[ops_mcp],
        model="gemini-3.1-flash-lite"
    )

    # 4. エージェントへの調査依頼
    user_request = (
        "現在、バックエンドサービス周辺で接続障害の疑いがあります。"
        "PostgreSQLの標準ポート(5432)やWebポート(80)の稼働状態をチェックし、"
        "さらにシステム上で 'python' 関連のプロセスが動いているか調査して報告してください。"
    )

    print(f"【ユーザーからのインシデント調査依頼】:\n{user_request}\n")
    print("▶ エージェントが自作MCPサーバーに接続し、自律的にツールを呼び出して調査を開始します...\n")

    async with Agent(config) as agent:
        response = await agent.chat(user_request)
        final_answer = await response.text()

        print("==================================================")
        print(" 🏆 [自作MCPサーバー連携によるインシデント調査レポート]")
        print("==================================================")
        print(f"{final_answer}\n")

    print("=== MCPサーバー連携タスクが正常に完了しました ===")

if __name__ == "__main__":
    asyncio.run(main())

実行結果とエージェントの自律動作ログ

上記スクリプトを実行した際の出力結果です(※プロセスパスやPIDは一般的な開発環境サンプル値にマスクしています)。

=== Google Antigravity SDK × 自作MCPサーバー検証開始 ===

【ユーザーからのインシデント調査依頼】:
現在、バックエンドサービス周辺で接続障害の疑いがあります。PostgreSQLの標準ポート(5432)やWebポート(80)の稼働状態をチェックし、さらにシステム上で 'python' 関連のプロセスが動いているか調査して報告してください。

▶ エージェントが自作MCPサーバーに接続し、自律的にツールを呼び出して調査を開始します...

==================================================
 🏆 [自作MCPサーバー連携によるインシデント調査レポート]
==================================================
バックエンドサービスの接続状況およびプロセス稼働状態の調査結果をご報告します。

1. ポート稼働状況
調査の結果、指定されたポートは現在いずれも外部からの接続に応答しない状態です。
- PostgreSQL (ポート 5432): 未開放(接続不可)
- Webポート (ポート 80): 未開放(接続不可)

2. Pythonプロセス稼働状況
システム上で稼働している 'python' 関連のプロセスを検知しました。
- PID: 12045 | Command: /usr/bin/python3
- PID: 12080 | Command: /srv/app/.venv/bin/backend_worker

【考察と推奨アクション】:
ポート 5432 および 80 が開いておらず、データベースやWebサーバーのリスナーが正常に起動していない可能性が高いと考えられます。バックエンドプロセスの設定やサービスログ(log://system/error)を確認し、サービスの再起動を推奨します。

=== MCPサーバー連携タスクが正常に完了しました ===

エージェント自身にはポート判定やプロセス検索のロジックが一切書かれていないにもかかわらず、自作MCPサーバーから公開された check_port_statussearch_processes を的確に選択・実行し、インシデントの一次切り分けレポートを自律生成できたことが実証されました!

自作MCPサーバー開発における注意点

自作MCPサーバーを開発・運用する上で押さえておくべき「3つの注意点」です。

① stdio通信における print 文の厳禁(重要)

stdio トランスポートを使用する場合、エージェントとMCPサーバーは標準入出力(stdin / stdout)を介して JSON-RPC メッセージをやり取りします。
そのため、サーバー側のコードでデバッグ目的などで安易に print("debug message") を実行してしまうと、JSON-RPC のデータ列に余計な文字列が混入し、プロトコルエラーで即座に通信が切断されてしまいます。
デバッグログを出力したい場合は、標準エラー出力(sys.stderr.write(...)または Python の logging モジュールを利用することを推奨します。

② 関数のDocstringと型ヒントの重要性

FastMCPは、関数の型アノテーション(port: int など)と Docstring(三重引用符内の説明文)から、自動的にツール定義(JSON Schema)を構築します。
引数の説明や戻り値の型が曖昧だと、AIモデルがツールを呼ぶ際に誤った引数を渡す原因となります。「どの引数に何を渡すべきか」を Docstring 内に明確に記述しておくことが、AIのツール選択精度を高めるポイントです。

③ 実行権限の最小化とセキュリティ対策

MCPサーバーはAIモデルからの指示に応じてOS上のコマンドやデータベース操作を実行します。
特にシステム管理やファイル削除などを伴うツールを作成する場合は、サーバープロセスを実行するOSユーザーの権限を最小限に制限し、引数のパスやコマンドに対して不正なインジェクションが行われないようバリデーションを厳格に設けることが望ましいです。

まとめ

今回は、Pythonの標準フレームワーク「FastMCP」を活用し、実務でそのまま活用できる「システム管理・ログ監視MCPサーバー」をゼロから自作する手法を検証しました。

MCPの主要機能である「Tools(実行)」「Resources(参照)」「Prompts(定型指示)」を適切に組み合わせることで、単なる関数呼び出しを超えた、高度で柔軟なシステム運用アシスタントを構築できることが分かりました。

自作したMCPサーバーは、Google Antigravity SDK だけでなく、様々なMCP対応クライアントから再利用が期待できます。ぜひ手元の業務スクリプトやツール資産をMCPサーバー化し、AIエージェントの力を引き出してみてください!

コメント

タイトルとURLをコピーしました