1. はじめに
この Codelab では、Google Cloud Agent Registry と Agent Development Kit(ADK)を使用して、登録済みの Model Context Protocol(MCP)サーバーを実行時に動的に検出して呼び出す方法を学びます。これにより、エンドポイントとツール構成をハードコードする必要がなくなります。
このラボでは、ADK と Python プログラミング言語を使用してアプリケーションを構築します。同じ原則を他のプログラミング言語や他のエージェント開発フレームワークにも適用できます。
演習内容
- Google Cloud Agent Registry を有効にしてプロジェクトで構成する
- MCP サーバーを Cloud Run にデプロイして Agent Registry に登録する
- ハードコードされた URL を使用して MCP サーバーを呼び出す ADK エージェントを構築する
- エージェントを変更して、Agent Registry で MCP サーバーを ID で検出する
- セマンティック検索を使用して実行時に MCP サーバーを検出するようにエージェントを変更する
必要なもの
- 最新のウェブブラウザ(Chrome など)
- 課金を有効にした Google Cloud プロジェクト
- Google Cloud プロジェクトでリソースを作成および管理し、IAM ポリシーを構成する権限を持つアカウント
2. 環境を設定する
この Codelab の手順では、Google Cloud コンソールの Cloud Shell でコマンドを実行することを想定しています。Cloud Shell は Google Cloud 上で動作するコマンドライン環境で、必要なツールがプリロードされています。ローカル環境に gcloud CLI、git、Python 3 がインストールされている場合は、そこでコマンドを実行することもできます。
Google Cloud プロジェクトの作成
- Google Cloud コンソールのプロジェクト セレクタ ページで、Google Cloud プロジェクトを作成します。
- Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
Cloud Shell ターミナルを起動する
- リソース選択ツールを使用して、作成した Google Cloud プロジェクトを選択します。
- Google Cloud コンソールの上部にある「Cloud Shell をアクティブにする 」アイコン
をクリックします。
- Cloud Shell に接続したら、認証を確認します。
gcloud auth list - プロジェクトが構成されていることを確認します。
gcloud config get project - プロジェクトが想定どおりに設定されていない場合や、Cloud Shell ではなくローカルマシンで環境を使用する場合は、プロジェクト ID を設定します。
export GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID> gcloud config set project "${GOOGLE_CLOUD_PROJECT}"
Google Cloud プロジェクトを選択して環境変数を設定する
- 必要な Google Cloud APIs を有効にします。
gcloud services enable \ agentregistry.googleapis.com \ aiplatform.googleapis.com \ cloudbuild.googleapis.com \ run.googleapis.com \ --project="${GOOGLE_CLOUD_PROJECT}"
Python 環境を設定する
- 仮想環境を作成して有効にし、必要な依存関係をインストールします。
mkdir -p adk-dynamic-discovery && cd adk-dynamic-discovery python3 -m venv .venv source .venv/bin/activate pip install "fastmcp<5.0.0" "google-adk[mcp,a2a,agent-identity]>=2.5.0,<3.0.0"
3. Zoo MCP サーバーを作成して登録する
このステップでは、Zoo MCP サーバーを作成して登録します。このサーバーは、3 つのツールを公開しています。list_species は動物園にいるすべての種を一覧表示し、get_animals_by_species は特定の種の動物を一覧表示し、get_animal_details は特定の動物に関する追加情報を取得します。
このステップのすべてのファイルは GitHub で確認できます。
MCP サーバーを作成する
- MCP サーバー用のフォルダと Python プロジェクトを設定する
mkdir mcp-on-cloudrun && cd mcp-on-cloudrun uv init --description "Example of deploying an MCP server on Cloud Run" --bare --python 3.13 uv add "fastmcp<5.0.0" --no-sync - MCP サーバーコードをダウンロードします。
curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step3/server.py -o server.py
MCP サーバーを Cloud Run にデプロイして Agent Registry に登録する
次に、サーバーの Dockerfile を作成し、サーバーを Cloud Run にデプロイします。
Dockerfileを作成します。cat << EOF > Dockerfile # Use the official Python image FROM python:3.13-slim # Install uv COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ # Install the project into /app COPY . /app WORKDIR /app # Allow statements and log messages to immediately appear in the logs ENV PYTHONUNBUFFERED=1 # Install dependencies RUN uv sync # Run the FastMCP server CMD ["uv", "run", "server.py"] EOF- サーバー用の指定サービス アカウントを作成します。
gcloud iam service-accounts create zoo-mcp-server-sa \ --display-name="Zoo MCP Server Service Account" - サーバーを
us-west1リージョンにデプロイします。 次のプロンプトが表示されたら、新しい Artifact Registry リポジトリの作成を確定します。gcloud run deploy zoo-mcp-server \ --service-account=zoo-mcp-server-sa@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com \ --region=us-west1 \ --source=. \ --labels=dev-tutorial=codelab-dynamic-mcp-discovery \ --invoker-iam-check \ --no-allow-unauthenticatedus-west1リージョンのプロジェクトに Artifact Registry リポジトリがない場合、次のプロンプトが表示されます。Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named [cloud-run-source-deploy] in region [us-west1] will be created. Do you want to continue (Y/n)? - 「global」ロケーションの Agent Registry に MCP サーバーを登録します。
これらのコマンドは、ツール マニフェストを使用してfastmcp inspect server.py --format mcp | jq 'del(..|nulls) | del(.tools[]?._meta?)' > toolspec.json MCP_URL=$(gcloud run services describe zoo-mcp-server --region=us-west1 --format='value(status.url)') gcloud agent-registry services create zoo-mcp-server \ --display-name="zoo-mcp-server" \ --project="${GOOGLE_CLOUD_PROJECT}" \ --location=global \ --mcp-server-spec-type="tool-spec" \ --mcp-server-spec-content="toolspec.json" \ --interfaces="url=${MCP_URL}/mcp,protocolBinding=jsonrpc"toolspec.jsonを生成し、Agent Registry API を呼び出してサーバーを登録します。 - エージェントがレジストリに表示されていることを確認します。
次のような MCP サーバー情報を含む YAML 出力が表示されます。gcloud agent-registry mcp-servers list \ --location=global \ --filter="display_name=zoo-mcp-server"--- attributes: agentregistry.googleapis.com/system/RuntimeReference: uri: //agentregistry.googleapis.com/projects/[PROJECT_NUMBER]/locations/global/services/zoo-mcp-server createTime: '<create time datetime in ISO 8601>' displayName: zoo-mcp-server interfaces: - protocolBinding: JSONRPC url: https://zoo-mcp-server-[PROJECT_NUMBER].us-west1.run.app/mcp mcpServerId: urn:mcp:projects-[PROJECT_NUMBER]:projects:[PROJECT_NUMBER]:locations:global:agentregistry:services:zoo-mcp-server name: projects/[PROJECT_ID]/locations/global/mcpServers/agentregistry-00000000-0000-0000-1234-56789abcdef tools: - annotations: idempotentHint: true readOnlyHint: true description: |- Returns every species in the zoo. Use it first for zoo-wide questions such as counting all animals or listing everything the zoo has. name: list_species - annotations: idempotentHint: true readOnlyHint: true description: |- Retrieves all animals of a specific species from the zoo. Can also be used to collect the base data for aggregate queries of animals of a specific species - like counting the number of penguins or finding the oldest lion. name: get_animals_by_species - annotations: idempotentHint: true readOnlyHint: true description: Retrieves the details of a specific animal by its name. name: get_animal_details updateTime: '<last updated datetime in ISO 8601>'
4. Zoo MCP サーバーを使用する基本的な ADK エージェントを作成する
このステップでは、ハードコードされた動物園の MCP サーバー URL を使用して、動物園に関するユーザーのリクエストに回答する ADK エージェントを作成します。ハードコードすると、エージェントがデプロイされる前に MCP サーバーの URL を把握する必要があります。
このステップのソースファイルは GitHub で入手できます。
エージェント アプリケーションを作成する
ADK プロジェクトと、Zoo MCP サーバーを使用する Zoo エージェントのスキャフォールドを作成します。
- Zoo エージェントのフォルダとプロジェクトを設定します。
mkdir ../sample-agent && cd ../sample-agent uv init --description "Example of ADK agent to showcase dynamic MCP discovery" --bare --python 3.13 uv add "google-adk[mcp,a2a,agent-identity]>=2.5.0,<3.0.0" --no-sync - エージェント パッケージ フォルダを作成し、
__init__.pyファイルをダウンロードします。mkdir zoo_agent curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step4/zoo_agent/__init__.py -o zoo_agent/__init__.py .envファイルを作成し、このステップと次のステップで使用する環境変数を入力します。 このコマンドは、このステップと後続の検出ステップで必要なすべての環境変数をMCP_URL=$(gcloud run services describe zoo-mcp-server --region=us-west1 --format='value(status.url)') cat << EOF > zoo_agent/.env GOOGLE_GENAI_USE_ENTERPRISE=TRUE GOOGLE_CLOUD_LOCATION=global GOOGLE_CLOUD_PROJECT="${GOOGLE_CLOUD_PROJECT}" GOOGLE_CLOUD_PROJECT_NUMBER=$(gcloud projects describe ${GOOGLE_CLOUD_PROJECT} \ --format='value(projectNumber)') MCP_SERVER_URL="${MCP_URL}" EOFzoo_agent/.envに書き込みます。そのため、後でファイルを再作成する必要はありません。このステップでは、次の変数を使用します。GOOGLE_GENAI_USE_ENTERPRISE、GOOGLE_CLOUD_PROJECT、GOOGLE_CLOUD_LOCATION: ADK と Google GenAI SDK が、Google Cloud プロジェクトとロケーション(global)の Vertex AI を介して Gemini モデル呼び出しをルーティングするために暗黙的に使用します。また、MCP サーバーが登録されている Agent Registry のロケーションも保存します。GOOGLE_CLOUD_PROJECT_NUMBER: このステップでは使用されません。次のステップで使用するために、ここで事前入力されています。MCP_SERVER_URL:zoo_agent/agent.pyで直接読み取り、Zoo MCP サーバーのハードコードされた Cloud Run URL を使用してMcpToolsetを構成します。
- Zoo MCP サーバーに接続する Zoo エージェントのコードを含む
agent.pyをダウンロードします。curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step4/zoo_agent/agent.py -o zoo_agent/agent.py
MCP サーバーの認可の仕組み
zoo-mcp-server Cloud Run サービスは --invoker-iam-check フラグと --no-allow-unauthenticated フラグを使用してデプロイされているため、MCP サーバーへのすべての HTTP リクエストには、Authorization ヘッダーに Google 署名付き ID トークンを含める必要があります。トークンは、サービスに対する run.routes.invoke 権限を持つ呼び出し元を識別する必要があります。
zoo_agent/agent.py では、_GoogleAuthHeaders クラスは 2 つの方法のいずれかで ID トークン認証情報を取得します。まず、fetch_id_token_credentials() を呼び出して、メタデータ サーバーまたはサービス アカウント キーからサービス URL の ID トークンを取得します。失敗した場合は、ユーザーのアプリケーションのデフォルト認証情報(ADC)の ID トークンを使用します。インスタンスが呼び出されるたびに、必要に応じてトークンが更新され、Authorization ヘッダーが返されます。
class _GoogleAuthHeaders:
def __init__(self, audience: str):
self._request = Request()
self._id_token = False
try:
self._credentials = id_token.fetch_id_token_credentials(audience, request=self._request)
except exceptions.DefaultCredentialsError:
self._credentials, _ = google.auth.default()
self._id_token = True
if not isinstance(self._credentials, credentials.Credentials):
raise
def __call__(self, unused_context: ReadonlyContext | None = None) -> dict[str, str]:
if not self._credentials.valid or (self._id_token and not self._credentials.id_token):
self._credentials.refresh(self._request)
token = self._credentials.id_token if self._id_token else self._credentials.token
return {"Authorization": f"Bearer {token}"}
エージェントは、クラスのインスタンスを header_provider 引数として McpToolset に渡します。ADK はヘッダー プロバイダを呼び出し、返されたヘッダーを MCP サーバーへのすべてのリクエストに添付します。HTTP クライアントは ADK によって作成されるため、インストールされている MCP Python SDK とその HTTP ライブラリのバージョンに関係なく、同じコードが機能します。
zoo_mcp_toolset = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url=f"{MCP_SERVER_URL}/mcp",
timeout=30.0,
),
header_provider=_GoogleAuthHeaders(MCP_SERVER_URL),
)
リクエストされたオーディエンスはサービス URL(MCP_SERVER_URL)ですが、接続 URL には FastMCP サーバーが MCP プロトコルを提供する /mcp パスが含まれています。30 秒のタイムアウトにより、Cloud Run のコールド スタートに余裕が生まれます。
ローカル環境で ADK プレイグラウンドを使用してエージェントをテストする
エージェントをテストする前に、ユーザー アカウントが MCP サーバーを呼び出せることを確認してください。
- ローカル ターミナルのみ(Cloud Shell ではこのステップをスキップ): ユーザー認証情報を使用して ADC を初期化します。
手順に沿って ADC の初期化を完了します。gcloud auth application-default login - (省略可)リモート MCP サーバーを呼び出す権限をユーザー アカウントに付与します。
gcloud run services add-iam-policy-binding zoo-mcp-server \ --region=us-west1 \ --project=${GOOGLE_CLOUD_PROJECT} \ --member=user:$(gcloud config get-value account) \ --role='roles/run.invoker' adk runコマンドを使用してエージェントを実行します。 または、adk run zoo_agentadk webコマンドを使用して ADK ウェブ インターフェースを実行します。 コマンドによって出力された URL をブラウザで開きます。Cloud Shell を使用する場合は、adk web --port 8080--allow_origins引数を追加して、Cloud Shell からウェブ インターフェースを開けるようにします。 コマンドを実行したら、Cloud Shell ツールバー(右側)にあるウェブでプレビューアイコンadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"
を見つけます。このアイコンをクリックし、[ポート 8080 でプレビュー] を選択します。Cloud Shell で新しいブラウザタブが開き、ADK ウェブ インターフェースが表示されます。- エージェントとのチャットを開始します。たとえば、動物園の動物について次のように質問します。
エージェントはHow many animals are in the zoo?list_speciesを呼び出し、次に各種に対してget_animals_by_speciesを呼び出して回答を計算します。
ADK ウェブ インターフェースを使用すると、動物園 MCP サーバーへの呼び出しなど、エージェントの動作を確認できます。方法については、ウェブ UI のドキュメントをご覧ください。
5. MCP サーバー エンドポイントを動的に検出する
このステップでは、エンドポイント URL をハードコードする代わりに、Agent Registry に登録されている Zoo MCP サーバー エンドポイントを検出するようにエージェントを変更します。
このステップのすべてのファイルは GitHub で確認できます。
ADK と Agent Registry の統合を使用して、サーバーの URL を検索して取得します。実装するには、次の情報が必要です。
- MCP サーバーが登録されている Agent Registry のプロジェクト番号とロケーション(
global)。 - MCP サーバーの登録に使用したサービス ID(
zoo-mcp-server)。
Agent Registry を使用するようにエージェントを更新する
- GitHub から更新された
agent.pyファイルをダウンロードして、エージェントの最初のバージョンをオーバーライドします。curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step5/zoo_agent/agent.py -o zoo_agent/agent.pyこのステップの環境変数
この更新により、エージェントはzoo_agent/.envの次の環境変数に依存するようになります。GOOGLE_CLOUD_PROJECT_NUMBER: MCP サーバーの ID URN(MCP_SERVER_URN)を構築するために必要になりました。GOOGLE_CLOUD_PROJECT、GOOGLE_GENAI_USE_ENTERPRISE、GOOGLE_CLOUD_LOCATION: ADK が Vertex AI アクセスを構成するために使用します。この場所は、MCP サーバーの検出にも使用されます。- 不要: エージェントは環境から読み取るのではなく、Agent Registry から MCP サーバー エンドポイント URL を取得するため、
MCP_SERVER_URLは使用されなくなりました。
Agent Registry では、登録された各 MCP サーバーに、AgentRegistryでの MCP サーバーの ID と検出の仕組みmcpServerIdフィールドに保存された固有識別子があり、Uniform Resource Name(URN)としてフォーマットされています。これは、ステップ 3 のgcloud agent-registry mcp-servers listコマンドの出力で確認できます。エージェントは、環境変数からこの識別子を再構築します。 URN は、レジストリのプロジェクト番号(PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] PROJECT_NUMBER = os.environ["GOOGLE_CLOUD_PROJECT_NUMBER"] LOCATION = os.environ.get("GOOGLE_CLOUD_LOCATION", "global") MCP_SERVER_URN = f"urn:mcp:projects-{PROJECT_NUMBER}:projects:{PROJECT_NUMBER}:locations:{LOCATION}:agentregistry:services:zoo-mcp-server"projects-{PROJECT_NUMBER})、サーバーが登録されているプロジェクト番号とロケーション(projects:{PROJECT_NUMBER}:locations:{LOCATION})、gcloud agent-registry services createコマンドで使用したサービス ID(agentregistry:services:zoo-mcp-server)をエンコードします。エージェントは、registry.list_mcp_servers()を使用して登録済みの MCP サーバーを一覧表示し、各サーバーのmcpServerIdをMCP_SERVER_URNと照合します。次に、一致するサーバーのリソースname(projects/.../locations/.../mcpServers/...)をregistry.get_mcp_toolset()に渡します。registry.get_mcp_toolset()は、サーバーの登録済みインターフェース(https://...run.app/mcp)からエンドポイント URL を読み取り、その URL に接続されたMcpToolsetを返します。header_provider = _GoogleAuthHeaders() registry = AgentRegistry( project_id=PROJECT_ID, location=LOCATION, header_provider=header_provider, ) mcp_server_name = next( ( s["name"] for s in registry.list_mcp_servers().get("mcpServers", []) if s.get("mcpServerId") == MCP_SERVER_URN ), None, ) if not mcp_server_name: raise RuntimeError(f"MCP server '{MCP_SERVER_URN}' not found in Agent Registry.") zoo_mcp_toolset = registry.get_mcp_toolset(mcp_server_name=mcp_server_name) header_provider.bind(zoo_mcp_toolset.connection_params.url) zoo_mcp_toolset.connection_params.timeout = 30.0get_mcp_toolset()は、ツール名の前にサーバーの表示名を付加するため、エージェントにはツールがzoo_mcp_server_list_species、zoo_mcp_server_get_animals_by_species、zoo_mcp_server_get_animal_detailsとして表示されます。この接頭辞により、エージェントが複数の MCP サーバーのツールを使用する際の名前の競合を防ぐことができます。最後の行では、Cloud Run のコールド スタートに備えて、接続タイムアウトをデフォルトの 5 秒から引き上げています。AgentRegistryによる認可の変更AgentRegistryは、list_mcp_servers()などの独自の API 呼び出しを ADC で認証するため、ヘッダー プロバイダは検出された MCP サーバーへの呼び出しにのみ必要です。AgentRegistryは内部でMcpToolsetを作成するため、ヘッダー プロバイダをAgentRegistryコンストラクタに渡します。前のステップでは、オーディエンスは事前にわかっていたMCP_SERVER_URL値でした。URL は検出後にのみ認識されるようになりました。ADK は、AgentRegistryの作成時ではなく、エージェントが MCP サーバーにリクエストを送信するときにヘッダー プロバイダを呼び出します。これにより、エージェントは最初にプロバイダを作成し、get_mcp_toolset()が戻った後に検出されたエンドポイントにバインドできます。bind()メソッドは、前のステップのコンストラクタと同じ方法で認証情報を取得します。オーディエンスは、検出されたエンドポイント URL から/mcpパスなしで取得されます。def bind(self, endpoint_url: str) -> None: parsed = urlparse(endpoint_url) audience = f"{parsed.scheme}://{parsed.netloc}" try: self._credentials = id_token.fetch_id_token_credentials(audience, request=self._request) except exceptions.DefaultCredentialsError: self._credentials, _ = google.auth.default() self._id_token = True if not isinstance(self._credentials, credentials.Credentials): raise__call__メソッドは前のステップと同じですが、bind()の前に呼び出されるとエラーが発生します。 - エージェントを実行して変更をテストします。
adk runコマンドを使用します。 または、adk run zoo_agentadk webコマンドを使用して ADK ウェブ インターフェースを実行します。 コマンドによって出力された URL をブラウザで開きます。Cloud Shell を使用する場合は、adk web --port 8080--allow_origins引数を追加して、Cloud Shell からウェブ インターフェースを開けるようにします。 エージェントに前のステップと同じ質問をします。回答は同じですが、ADK ウェブ インターフェースでは、ツール呼び出しがadk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"zoo_mcp_server_list_speciesとzoo_mcp_server_get_animals_by_speciesとして表示されるようになりました。
この変更により、明示的な MCP サーバー URL が不要になりました。ただし、MCP サーバーの登録に使用されるプロジェクト番号、レジストリのロケーション、サービス ID を把握しておく必要があります。検出は、エージェントの初期化中に静的に行われます。
6. 動的検出で MCP サーバーのセマンティック検索を使用する
このステップでは、起動時の完全な URN マッチングをランタイム セマンティック検索に置き換えることで、MCP サーバーの動的検出を改善します。このアプローチでは、サーバーがデプロイされているプロジェクト番号やサーバーの正確な ID(MCP_SERVER_URN)を把握する必要がありません。また、ツールの検出を初期化時に処理するのではなく、エージェントの標準ランタイム ワークフローに直接統合します。
このステップのすべてのファイルは GitHub で確認できます。
実行時にセマンティック検出を使用するようにエージェントを更新する
- GitHub から更新された
agent.pyファイルをダウンロードして、既存の実装を上書きします。curl -fsSL https://raw.githubusercontent.com/GoogleCloudPlatform/devrel-demos/main/codelabs/agent-registry-dynamic-discovery-with-adk/steps/step6/zoo_agent/agent.py -o zoo_agent/agent.pyこのステップの環境変数
エージェントが MCP サーバー URN を作成しなくなったため、必要な構成がさらに簡素化されます。GOOGLE_CLOUD_PROJECT、GOOGLE_GENAI_USE_ENTERPRISE、GOOGLE_CLOUD_LOCATION: ADK が Vertex AI アクセスを構成するために使用します。この場所は、MCP サーバーの検出にも使用されます。- 不要:
GOOGLE_CLOUD_PROJECT_NUMBERは不要になりました(MCP_SERVER_URLは前のステップですでに削除されています)。
完全一致からセマンティック検索への移行
モジュールの初期化中にregistry.list_mcp_servers()を呼び出して、各エントリを構築されたMCP_SERVER_URNと比較する代わりに、agent.pyはsearch_type="SEMANTIC"を指定してregistry.search_mcp_servers()を呼び出す検出ツール関数discover_zoo_mcp_serversを定義するようになりました。 これにより、エージェントは、必要な機能の自然言語による説明(def discover_zoo_mcp_servers(query: str, tool_context: ToolContext) -> dict[str, Any]: """Discovers Zoo MCP server(s) matching the query using semantic search. Args: query: Semantic description of zoo capabilities needed (e.g. 'zoo animal info') """ cached = _fresh_discovery(tool_context.state) if cached: return { "status": "already_discovered", "message": "Zoo MCP servers are already discovered.", "servers": cached["servers"], } try: response = registry.search_mcp_servers( search_string=query, search_type="SEMANTIC", page_size=5, ) except Exception as e: return { "status": "error", "message": f"Agent Registry search failed: {e}", } found_servers = response.get("mcpServers", []) if not found_servers: return { "status": "not_found", "message": f"No MCP servers found in Agent Registry at {LOCATION} matching '{query}'.", } valid_servers = [s["name"] for s in found_servers if "name" in s] tool_context.state[DISCOVERY_STATE_KEY] = { "servers": valid_servers, "discovered_at": time.time(), } return { "status": "discovered", "message": f"Successfully discovered {len(valid_servers)} Zoo MCP server(s).", "servers": valid_servers, }"zoo animals, exhibits, habitat, care, feeding schedule"など)を使用して、実行時に Agent Registry を検索し、検出されたサーバー リソース名を ADK セッション状態(tool_context.state)に保存できます。動的検出がエージェント ワークフローにどのように統合されているか
実行時に検出されたサーバーからツールをマウントし、検出から呼び出しまでのフローをエージェントに案内するために、agent.pyはカスタムBaseToolset実装(RegistryDiscoveredToolset)と動的指示プロバイダ(zoo_guide_instruction)を組み合わせています。- 動的ツールセット(
RegistryDiscoveredToolset):BaseToolsetをサブクラス化し、self._use_invocation_cache = Falseを設定します。これにより、ADK は、最初に空のツールリストをキャッシュするのではなく、discover_zoo_mcp_serversがセッション状態を更新した直後に、利用可能なツールを再評価します。get_tools()が呼び出されると、検出されたサーバー名を状態から読み取り、ワーカー スレッドで各サーバーの認証済みMcpToolsetを構築し、すべてのアクティブなツールを返します。1 つのサーバーに到達できなくても、他のサーバーには影響しません。_build_toolset()はブロッキング I/O を実行するため、_tools_of()はワーカー スレッドで実行します。class RegistryDiscoveredToolset(BaseToolset): def __init__(self, agent_registry: AgentRegistry): super().__init__() self._registry = agent_registry self._toolsets: dict[str, McpToolset] = {} self._use_invocation_cache = False def _build_toolset(self, server_name: str) -> McpToolset: header_provider = _GoogleAuthHeaders() server_registry = AgentRegistry( project_id=self._registry.project_id, location=self._registry.location, header_provider=header_provider, ) toolset = server_registry.get_mcp_toolset(server_name) header_provider.bind(toolset.connection_params.url) toolset.connection_params.timeout = 30.0 return toolset async def _tools_of(self, server_name: str, ctx: ReadonlyContext) -> list[BaseTool]: try: if server_name not in self._toolsets: self._toolsets[server_name] = await asyncio.to_thread( self._build_toolset, server_name ) return await self._toolsets[server_name].get_tools_with_prefix(ctx) except Exception as e: print(f"Skipping MCP server {server_name}: {e}") return [] async def get_tools(self, readonly_context: ReadonlyContext | None = None) -> list[BaseTool]: if not readonly_context: return [] record = _fresh_discovery(readonly_context.state) if not record or not record.get("servers"): if self._toolsets: await self.close() return [] per_server = await asyncio.gather( *(self._tools_of(name, readonly_context) for name in record["servers"]) ) return [tool for tools in per_server for tool in tools] - 動的エージェント指示(
zoo_guide_instruction): 各モデルターンの前にセッション状態を評価します。サーバーがまだ検出されていない場合は、_DISCOVERY_PROMPTを追加して、エージェントにdiscover_zoo_mcp_serversを最初に呼び出すよう指示します。検出が成功すると、プロンプトが更新されてアクティブな MCP サーバーが一覧表示され、検出を再度実行せずに、マウントされた動物園ツールを直接使用するようエージェントに指示します。def zoo_guide_instruction(ctx: ReadonlyContext) -> str: record = _fresh_discovery(ctx.state) if record is None: return _ROLE + _DISCOVERY_PROMPT return _ROLE + ( f"\nYour active Zoo MCP servers: {', '.join(record['servers'])}. " "Do not run discovery again; use the active zoo tools directly to fulfill the visitor's request." )
動的に検出されたサーバーの認可
セマンティック検索では複数のサーバーが返されることがあり、ADK はリクエストの送信先サーバーを通知せずにヘッダー プロバイダを呼び出します。そのため、_build_toolset()は検出されたサーバーごとに専用の_GoogleAuthHeadersとAgentRegistryを作成し、プロバイダをそのサーバーのエンドポイントにバインドします。プロバイダは前のステップと同じですが、bind()より前に使用されると失敗する_boundフラグが追加されています。保持と更新のロジック
agent.pyは、検出されたエンドポイントを定期的に更新しながら、すべてのターンで Agent Registry にクエリを実行することを回避するために、有効期間(TTL)を使用して状態を保持します。DISCOVERY_STATE_KEY = "app:zoo_mcp_servers" DISCOVERY_TTL_SECONDS = 24 * 60 * 60 def _fresh_discovery(state: Any) -> dict[str, Any] | None: if not state: return None record = state.get(DISCOVERY_STATE_KEY) if not record or not record.get("servers"): return None if time.time() - record.get("discovered_at", 0) > DISCOVERY_TTL_SECONDS: return None return record- アプリケーション スコープの保持: 検出されたサーバー名と
discovered_atタイムスタンプは、app:zoo_mcp_serversキーの下のセッション状態に保存されます。ADK のapp:接頭辞を使用すると、アプリケーションの実行全体でキャッシュに保存された検出レコードのスコープが設定されます。 - TTL の検証と自動更新:
_fresh_discovery()は、保存されたレコードが存在し、その経過時間がDISCOVERY_TTL_SECONDS(24 時間)を超えていないことを確認します。レコードが最新の状態である間、RegistryDiscoveredToolsetはキャッシュに保存されたMcpToolset接続(self._toolsets)を再利用し、zoo_guide_instructionは検出プロンプトを省略します。discover_zoo_mcp_serversを繰り返し呼び出すと、すぐに"already_discovered"が返されます。24 時間の TTL が期限切れになると、_fresh_discovery()はNoneを返します。これにより、RegistryDiscoveredToolset.get_tools()は既存の MCP クライアント セッション(await self.close())を閉じ、zoo_guide_instructionはエージェントに新しい検出を実行するよう促します。
- エージェントを実行して、セマンティック検出ワークフローをテストします。
adk runコマンドを使用します。 または、adk run zoo_agentadk webコマンドを使用して ADK ウェブ インターフェースを実行します。 コマンドによって出力された URL をブラウザで開きます。Cloud Shell を使用する場合は、adk web --port 8080--allow_origins引数を追加して、Cloud Shell からウェブ インターフェースを開けるようにします。 動物園に関する質問(例:adk web --port 8080 --allow_origins="regex:https://.*\.cloudshell\.dev"How many animals are in the zoo?)をエージェントにすると、エージェントはまずdiscover_zoo_mcp_serversを呼び出し、検出された MCP サーバーからツールを動的にマウントします。その後、zoo_mcp_server_list_speciesやzoo_mcp_server_get_animals_by_speciesなどの動物園ツールを呼び出して質問に回答します。フォローアップの質問(例:Which is the oldest lion?)をします。エージェントは、すでにマウントされている動物園ツールを使用して回答し、discover_zoo_mcp_serversを再度呼び出しません。これは、セッション状態の検出レコードがまだ新しいためです。
7. クリーンアップ
オプション 1: プロジェクトを削除する
この Codelab 専用のプロジェクトを作成した場合、課金を停止する最も簡単な方法は、プロジェクトを削除することです。
gcloud projects delete "${GOOGLE_CLOUD_PROJECT}" --quiet
オプション 2: 個別のリソースを削除する
既存のプロジェクトを使用して、それを保持する場合は、この Codelab で作成した個々のリソースを削除します。
- Agent Registry から MCP サーバーの登録を削除します。サーバーを手動で登録したため、Cloud Run サービスを削除しても登録は削除されません。
gcloud agent-registry services delete zoo-mcp-server \ --location=global \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - Cloud Run MCP サーバー サービスを削除します。
gcloud run services delete zoo-mcp-server \ --region=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - MCP サーバー用に作成したサービス アカウントを削除します。
gcloud iam service-accounts delete \ "zoo-mcp-server-sa@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - この Codelab の実施中に
cloud-run-source-deployArtifact Registry リポジトリを作成した場合は、コンテナ イメージのストレージ料金が発生しないように、このリポジトリを削除します。gcloud artifacts repositories delete cloud-run-source-deploy \ --location=us-west1 \ --project="${GOOGLE_CLOUD_PROJECT}" \ --quiet - 必要に応じて、Cloud Shell で作成したローカル プロジェクト ディレクトリを削除します。
cd ~ && rm -rf adk-dynamic-discovery
8. 完了
この Codelab では、Google Cloud Agent Registry と Agent Development Kit(ADK)を使用して MCP サーバーを動的に検出して使用する方法を学びました。
- MCP サーバーを登録した: 安全な MCP サーバーを Cloud Run にデプロイし、Agent Registry に登録しました。
- ID で MCP サーバーを検出: URL をハードコードする代わりに、Agent Registry から MCP サーバー エンドポイントを解決しました。
- MCP サーバーをセマンティックに検出: エージェントが自然言語検索を使用して、実行時に MCP ツールを検出してマウントできるようにします。保持と更新も可能です。