TECH PLAY

株式会社豆蔵

株式会社豆蔵 の技術ブログ

109

はじめに # Model Context Protocol (MCP) は、2024年11月の初版リリース以来、AIモデルと外部データ・機能を接続する標準プロトコルとして急速に進化してきました。 今回の2026-07-28 Release Candidate(以下、RC)は、単なる機能追加ではなく、プロトコルの実運用前提を大きく変える改訂です。 とくに注目すべきは、プロトコル層のステートレス化です。 セッション管理を外し、ロードバランシング・再試行・観測性・キャッシュ運用をHTTP標準に近い形へ寄せることで、エンタープライズ環境でも扱いやすい構成へ移行しようとしています。 本ページでは、この改訂内容を仕様差分と実装視点で、RCの要点、仕様の変遷、具体的な変更点という流れで説明します。 公式ブログ: The 2026-07-28 MCP Specification Release Candidate RCの要点 # 今回のRCは「機能追加」より、「実運用前提の再設計」が主眼になります。 とくに、プロトコル層セッションを廃止してステートレスにシフトすることで、ロードバランシング、再試行、観測性、キャッシュ運用がHTTP標準の作法に近づきました。 さらに、Extensions(MCP Apps/Tasks)、認可強化、非推奨ライフサイクルが同時に導入され、仕様の進化プロセスも整備されています。 最大のBreaking Changeは、プロトコル層セッションを廃止し、 ステートレスファースト へ移行 Streamable HTTPは、 自己完結リクエスト+必須ヘッダー へ移行 サーバー起点の入力要求は、SSE維持型から InputRequiredResult+再送 のマルチラウンドトリップへ移行 Dynamic Client RegistrationはClient ID Metadata Documentsへの移行が推奨される MCP AppsとTasksは、Extensionとして正式化 入出力スキーマがJSONスキーマ(2020-12)に準拠 MCP仕様の変遷 # timeline 2024-11-05 初版リリース : コアアーキテクチャの定義 (Tools, Resources, Prompts) : 周辺機能の定義 (Roots, Sampling, Logging, HTTP+SSE) 2025-03-26 通信と認証の強化 : StreamableHTTP導入 : OAuth2による認証整備 2025-06-18 プロトコルの簡素化、構造化 : JSON-RPCバッチ処理の廃止 : 構造化出力 (structuredContent)対応 2025-11-25 UXと大規模運用の布石 : 段階的スコープ同意 (認証強化) : アイコン表示対応 : Tasksの実験的サポート 2026-07-28 ステートレスへの転換 : ステートレス化 : キャッシュ管理の導入 : ルーティング最適化 : 初期機能の非推奨化 : MCP Apps : Tasksを拡張へ移動 2026-05-21: RC確定 2026-07-28: 最終仕様の公開予定 非推奨確定~最低12カ月以内: 非推奨機能の削除禁止期間 仕様変更の詳細 # 今回予定されている仕様変更は下記の通りです。 🚨はBreaking Changeを示します。 プロトコルのステートレス化: initialize /セッション依存を廃止し、自己完結リクエストへ移行 🚨 リクエストモデルの変更: 必須ヘッダーと _meta を毎リクエストで送信 🚨 レスポンスモデルの変更: resultType が必須化 🚨 ハンドシェイクとセッションの廃止: initialize , initialized , Mcp-Session-Id を廃止 ステートレスプロトコルにおけるステートフルアプリケーション: Explicit Handle方式で状態を引き回す 🚨 サーバーからクライアントへのリクエストを再構築: input_required , requestState を用いた再送モデルへ 🚨 ルーティング(必須HTTPヘッダーによるトラフィック制御): ボディ解析なしでルーティング可能にし、検証を厳格化 キャッシング機構の導入: ttlMs , cacheScope で鮮度と共有可否を明示 観測性(W3C Trace Contextの伝搬を標準化): traceparent などで分散トレース相関を標準化 🚨 変更通知の再編: subscriptions/listen 中心に再編し、旧購読APIを整理 MCP Apps: サーバーレンダリングUIを公式Extensionとして扱う 🚨 Tasks APIをエクステンションへ移行: コア実験機能からExtensionへ再設計 認可の強化: OAuth2.0/OIDC運用に寄せた要件を強化 🚨 機能の非推奨化: Roots、Sampling、LoggingをDeprecated化 入出力スキーマがJSONスキーマ(2020-12)に準拠: 表現力を拡張し、バリデーション要件を明確化 ガバナンスの変更: Lifecycle、Extensions TrackおよびConformance連動を明文化 1. プロトコルのステートレス化 # これまではStreamable HTTP通信において、まず initialize リクエストでセッションを確立し、以降のリクエストには Mcp-Session-Id ヘッダーを付与する必要がありました。 2026-07-28では この接続確立フロー全体がプロトコル層から除去されます。 リクエストモデルの変更 リクエストモデルに以下の変更が入ります。 リクエストごとに _meta へプロトコルバージョンや機能宣言( capabilities )を内包します。 ヘッダーおよび _meta で指定したプロトコルバージョンが一致していること。不一致の場合は HeaderMismatchError ( -32020 )で拒否されます。 要求したバージョン自体がサーバー非対応の場合は UnsupportedProtocolVersionError ( -32022 )が返されます。 2025-11-25仕様 初回 { "jsonrpc":"2.0","id":1,"method":"initialize", "params":{ "protocolVersion":"2025-11-25", "capabilities":{}, "clientInfo":{"name":"sample-client","version":"1.0.0"} } } 2回目以降 Mcp-Session-Id: 1a2b3c4d-5e6f-7g8h { "jsonrpc": "2.0","id": 2,"method": "tools/call", "params": { "name": "get_user", "arguments":{"user_id":"u123"} } } 2026-07-28仕様 MCP-Protocol-Version: 2026-07-28 Mcp-Method: tools/call Mcp-Name: get_user { "jsonrpc": "2.0","id": 1,"method": "tools/call", "params": { "name": "get_user", "arguments": { "user_id": "u123" }, "_meta": { "io.modelcontextprotocol/protocolVersion":"2026-07-28", "io.modelcontextprotocol/clientInfo":{"name":"sample-client","version":"1.0.0"}, "io.modelcontextprotocol/clientCapabilities":{} } } } レスポンスモデルの変更 レスポンスモデルに以下の変更が入ります。 resultType が必須化(取りうる値: complete , input_required ) { "resultType": "input_required", "inputRequests": { "confirm": { "type": "elicitation", "message": "3ファイルを削除しますか?", "schema": { "type": "boolean" } } }, "requestState": "1a2b3c4d5e6f7g8h9i0j..." } ハンドシェイクとセッションの廃止 通信の開始と維持に使用していたセッション管理が変更されます。 これにより、スティッキールーティングやセッション管理が不要になるため、水平スケーリングが容易になります。 項目 2025-11-25 2026-07-28 initialize , initialized ハンドシェイク 必須 廃止( SEP-2575 ) Mcp-Session-Id ヘッダー 2回目以降のリクエストに必須 廃止( SEP-2567 ) プロトコルバージョン、クライアント情報 初期化時に1回交換 リクエストごとに _meta に含める サーバーケーパビリティの取得 初期化レスポンスで受け取る 新設の server/discover メソッドで都度取得 2025-11-25仕様 sequenceDiagram actor c as MCP Client participant s as MCP Server c->>+s: initialize(セッション確立) s-->>-c: c->>+s: tools/call(<br>Mcp-Session-Id, <br>name: get_user) s-->>-c: 2026-07-28仕様 sequenceDiagram actor c as MCP Client participant s as MCP Server c->>+s: tools/call(<br>MCP-Protocol-Version: 2026-07-28, <br>Mcp-Method: tools/call, <br>Mcp-Name: get_user) s-->>-c: ステートレスプロトコルにおけるステートフルアプリケーション プロトコルがステートレスになっても、アプリケーションが状態を持つことはできます。 ステートレス化に伴い、状態管理の責任はトランスポート層からアプリケーション層(モデルとの対話)へと移動します。 セッションへ依存する替わりに、サーバーは明示的なハンドルをツールの戻り値として発行して、次回のツール呼び出し時に引き渡す設計が推奨されます。 これにより、状態がモデルの推論コンテキストに統合され、より柔軟な推論制御が可能になります。 sequenceDiagram autonumber actor client as Model participant server as MCP Server client ->> server: create_basket server -->> client: {"basket_id": "b001"} client ->> server: add_item("basket_id": "b001", arg0: xyz) 2のレスポンス: basket_id や context_token のような明示的なハンドル(Explicit Handle)をツールの戻り値として発行する必要があります。 サーバーからクライアントへのリクエストを再構築 サーバーが処理中にクライアントへ追加入力を求めるエリシエーションは、接続を維持する替わりに InputRequiredResult を返却し、それを再度リクエストで投げ直す仕組みに再設計されます。 sequenceDiagram autonumber actor client as MCP Client participant server as MCP Server client ->> server: tools/call server -->> client: 追加入力の要求(InputRequiredResult) client ->> server: 同じtools/callをinputResponses+requestStateで再送 server -->> client: 最終的な結果 サーバーからの入力要求は「アクティブなクライアント要求の処理中」に限定されます。「突然のプロンプト」は禁止され、すべてのエリシテーションはユーザーが開始したアクションに紐付く必要があります。 2のレスポンス { "resultType": "input_required", "inputRequests": { "confirm": { "type": "elicitation", "message": "3ファイルを削除しますか?", "schema": { "type": "boolean" } } }, "requestState": "1a2b3c4d5e6f7g8h9i0j..." } InputRequiredResult には requestState が必要。 関連SEP マルチラウンドトリップ: SEP-2322 Multi Round-Trip Requests サーバー発の入力要求の制約: SEP-2260 Server-initiated requests constraints ルーティング(必須HTTPヘッダーによるトラフィック制御) Streamable HTTPで下記の3ヘッダーが必須になります。 これにより、ロードバランサー、ゲートウェイ、レートリミットがリクエストボディを解析(DPI)せずルーティングできるようになります。 ヘッダーとボディの内容が一致しない場合、サーバーはリクエストを拒否します。 ヘッダー 概要 用途 MCP-Protocol-Version プロトコルバージョン(例: 2026-07-28) バージョン整合性の検証 Mcp-Method JSONメソッド名(例: tools/call ) メソッド単位のルーティング、レート制限 Mcp-Name ツール名やリソース名(例: search ) ツールおよびリソース単位のルーティング 関連SEP ルーティングヘッダー: SEP-2243 キャッシング機構の導入 リスト応答(tools/listなど)にキャッシュ機構が導入されます。 これにより、MCPクライアントはレスポンスの有効期間(ttlMs)とスコープ(cacheScope)を認知できます。 リストの変更を知る場合、これまではSSEストリームを維持して検知するのが唯一の手段でしたが、これに代替する手段として活用できます。 ttlMs: ミリ秒単位の有効期間。 cacheScope: public(共有可)またはprivate(個別ユーザー限定)。 関連SEP キャッシュ機構: SEP-2549 観測性(W3C Trace Contextの伝搬を標準化) 分散トレーシングをサポートするため、 _meta フィールドにW3C Trace Context( traceparent , tracestate , baggage )の伝播が標準化されました。 これにより、OpenTelemetry互換のバックエンドで、ホストからサーバー、さらにその先のバックエンドまでを一貫したスパンツリーとして可視化できます。 関連SEP 観測性: SEP-414 変更通知の再編 変更通知もステートレス化に合わせて再編されています。 これまでの「接続やセッションに連なる通知」から、明示的なリクエストに紐づく通知へ再編されます。 主な変更点 変更通知は subscriptions/listen のレスポンスストリームで受信する形に統一されます。 旧来の購読API( resources/subscribe , resources/unsubscribe )は整理されます。 旧来のGETベースのSSE受信および再開機構( Last-Event-ID )は対象外です。 notifications/progress および notifications/message は、 subscriptions/listen ではなく「そのリクエスト自身のレスポンスストリーム」に流れます。 クライアント実装は「変更通知ストリーム」と「個別リクエストの進捗通知」を分離して扱う必要があります。 接続断の復旧時は、過去ストリーム再開ではなく、必要なリクエストを再送する設計が前提になります。 2. MCP Apps # サーバーがHTML UIテンプレートを提供し、クライアントがサンドボックス化されたiframe内で描画する機能です。 データの可視化や複雑なフォーム入力を可能にします。 UI内のアクションはすべてJSON-RPCプロトコルを通じて伝達されるため、監査ログや同意フローの対象として統合管理が可能です。 特徴 単なるテキスト応答ではなく、MCPサーバー主導のUI体験を組み込める ツール定義時にUIテンプレートを宣言する設計が必要 セキュリティレビュー対象が「ツール実装 + UIテンプレート」に広がる 関連SEP MCP Apps: SEP-1865 3. Tasks APIをエクステンションへ移行 # 2025-11-25でコア機能として実験的に導入されたTasksが エクステンションとして再設計されます。 2025-11-25のTasks APIを実装している場合は、移行が必要です。 項目 2025-11-25 2026-07-28 位置づけ コア機能(実験的) エクステンション(正式) タスク作成 クライアント主導 サーバー主導( tools/call へのレスポンスとして返す) タスク取得 tasks/result (ブロッキング) tasks/get (ポーリング) タスク入力 tools/call の再送で対応 tasks/update で入力応答を送信 タスクキャンセル tasks/cancel tasks/cancel tasks/list あり 廃止(セッションなしでは安全なスコープを設定できないため) 関連SEP Tasks API: SEP-2663 4. 認可の強化 # 認可仕様がOAuth2.0+OIDC(OpenID Connect)の実運用に即した形で強化されます。 とくに( RFC9207 ) iss は、MCPの多サーバー接続構成で起きやすいmix-up系リスクへの対策として重要です。 Authorization Responseの iss 検証 認可レスポンス(Authorization Response)に含まれる iss パラメーターの検証が、MCPクライアント側で必須になります。 これにより、1つのクライアントが複数のサーバーと接続するMCP特有の構成において発生しやすい「ミックスアップ攻撃(認可コードを悪意あるサーバーにだまし取られる攻撃)」を低コストで対策できます。 動的クライアント登録(Dynamic Client Registration)時の application_type 宣言 動的クライアント登録する際、クライアントは適切なapplication_typeを宣言することが必須になります。 これにより、認可サーバーがデスクトップやCLIを誤ってWebアプリと判定し、セキュリティ上の理由からlocalhostのリダイレクトURIを拒否してしまうような、実装上の競合を回避します。 クライアント資格情報(Credentials)の認可サーバー(issuer)の紐づけ強化 クライアント資格情報は、それを発行した認可サーバーに厳密に紐づけされなければなりません。他の認可サーバーへの使い回しは禁止され、リソースサーバーの認可サーバーが変更された場合は再登録を義務付けています。 リフレッシュトークンを要求する方法の明文化 ステップアップにおけるスコープ蓄積動作の定義 権限の累積によって段階的に権限付与する動作(e.g. 最初にread権限を付与し、次いでwrite権限を追加。累積する形で権限を構成する) .well-known discovery suffixの定義 関連SEP Authorization Responseの iss 検証: SEP-2468 Dynamic Client Registrationの application_type 宣言: SEP-837 クライアント資格情報のissuer紐づけ: SEP-2352 リフレッシュトークン要求: SEP-2207 ステップアップのスコープ蓄積: SEP-2350 .well-known discovery suffix: SEP-2351 5. 機能の非推奨化 # 3機能が非推奨(Deprecated)になります。 非推奨から削除までは12カ月間の猶予があるため、その間に移行が必要です。 廃止機能 理由 推奨される移行先 Roots ステートフルな設計と不整合 ツールの入力パラメーター(inputSchema) 、リソースURI Sampling 責任境界の明確化 クライアント側の制御、プロバイダーAPI直接連携 Logging 業界標準の観測性ツール推奨 stdio: stderr, 構造化ログ: OpenTelemetry(W3C Trace Context) 関連SEP 機能の非推奨化: SEP-2577 6. 入出力スキーマがJSONスキーマ(2020-12)に準拠 # ツールの入出力スキーマ( inputSchema , outputSchema )がJSON Schema(2020-12)に対応しました。 これにより、 oneOf , anyOf , allOf および $ref による高度なスキーマ定義が可能になり、ツールの型安全性が向上します。 出力スキーマはオブジェクトだけでなく任意のJSON値を使用できます。 また、リソース未検出のエラーコードがMCPカスタム(-32002)から、JSON-RPC標準(-32602: 無効なパラメーター)に変更されます。 関連SEP JSON Schema 2020-12: SEP-2106 リソース未検出エラーコード変更: SEP-2164 7. ガバナンスの変更 # 今後は同規模の破壊的変更を常態化しないための仕組みも同時に導入されています。 機能ライフサイクル(Feature Lifecycle)(Active/Deprecated/Removed) 全機能にActive、DeprecatedおよびRemovedの状態を定義し、DeprecatedからRemovedまで最低12カ月の猶予を義務化します。 これにより、将来改訂での破壊的変更が予測可能になります。 Extensions Trackの正式化 新機能はまず拡張として提案および検証し、成熟したもののみコアへ取り込む運用に整理されました。 公式拡張は独立リポジトリ( ext-* )で管理され、コア仕様と独立して進化できます。 SEPとConformance Suiteの連動強化 標準化トラックのSEPは、対応シナリオがConformance Suiteに入るまでFinalに到達できません。 仕様策定と実装検証の乖離を抑え、SDK実装の相互運用性を高めます。 関連SEP Feature Lifecycle: SEP-2596 Extensions Track: SEP-2133 Conformance Suite連動: SEP-2484 SDK tier system: SEP-1777 実装者向けアクション # 実装者向けに、確認したい項目を簡単に整理します。 プロトコルのステートレス化: トランスポート層の移行 initialize , initialized 前提コードの棚卸し Mcp-Session-Id 依存コードの洗い出し リクエストヘッダーに Mcp-Method , Mcp-Name , MCP-Protocol-Version を設定 _meta に必須キー( protocolVersion , clientInfo , clientCapabilities )をリクエストごとに設定 HTTPヘッダーと _meta の一致検証失敗( -32020 )および非対応バージョン( -32022 )をハンドリング server/discover 呼び出しを実装し、対応バージョンとcapabilitiesを事前確認 レスポンスに resultType を設定 変更通知の受信設計を subscriptions/listen 前提へ移行 認可の強化: 認可運用の見直し Authorizationの iss 検証対応を確認 Dynamic Client Registrationの移行方針を整理 トレースコンテキストの伝播確認とログ収集設計を見直す 拡張機能と非推奨機能の整理 MCP Appsの導入有無を判断 Tasks APIの旧API利用箇所を抽出 Roots, Sampling, Loggingの移行計画を策定 エラーコードの変更: -32002 から -32602 SDKの更新 各言語のTier 1 SDKを最新版にアップデートし、ステートレス実装へ移行する まとめ # 今回の改訂は、単なる仕様の更新ではなく、MCPを「ステートレスなHTTP基盤上で運用するプロトコル」へ再設計するものです。 ステートレス化によるスケーラビリティ、W3C Trace Contextなどによる運用ガバナンス強化は、エンタープライズ導入における技術的障壁を下げる効果が期待できます。
はじめに # Deno 2.9 リリースおめでとうございます。 Deno 2.9 | Deno Electron 大好きな自分としても気になるのはやはり Deno Desktop です。 Tauri と同様 WebView をバックエンドにする構成と Electron と同様 Chromium ベースの構成を選べるとのことで、これは試すしかないと思いました。 公式ドキュメントは以下にあります。 https://docs.deno.com/runtime/desktop/ --> Caution Deno ブログには以下のように書かれており、2.9 時点ではデスクトップ機能は実験的段階です。 deno desktop is experimental in 2.9. The surface described here is stabilizing and some platform features are still landing. 使ってみる # まずは 2.9 にアップグレードしておきます [1] 。 deno upgrade main.ts に Deno.serve を使って普通にサーバープログラムを書きます。 main.ts Deno.serve(() => new Response( "<!DOCTYPE html><h1>Hello from Deno desktop </h1>", { headers: { "content-type": "text/html" } }, ) ); 同じディレクトリで deno desktop main.ts を実行します。 $ deno desktop main.ts ⚠ deno desktop is experimental and subject to change Check main.ts Compile main.ts to hello.dylib Embedded Files hello.dylib └── main.ts (430B) Files: 1.91KB Metadata: 1.38KB Remote modules: 12B Downloading laufey webview backend for aarch64-apple-darwin (v0.4.0) Download laufey-webview-aarch64-apple-darwin.tar.gz 97.44KiB/97.44KiB Codesigning bundle with identity "-" hello.app/Contents/MacOS/laufey_webview: replacing existing signature hello.app/Contents/MacOS/hello.dylib: replacing existing signature Bundle hello.app 最後の出力で、ルートに hello.app (macOS のアプリ実行ファイル)が生成されており、起動できます(Windows の場合は、hello.exe が生成されます)。 Deno のコンセプト通り、追加のモジュールや設定なし(Out of the box)でデスクトップアプリが生成されました。 Deno Desktop の開発体験 # HMR (Hot Module Replacement) オプション付きで起動することで、ローカルの開発サーバを立ち上げ、コード変更を検知してアプリ内容を即時更新してくれます。 deno desktop --hmr main.ts ⚠ deno desktop is experimental and subject to change Compile main.ts to file:///Users/kondoumh/Library/Caches/deno/desktop/5f4a00908e99d886/hello.dylib Embedded Files hello.dylib └── main.ts (422B) Files: 1.9KB Metadata: 1.38KB Remote modules: 12B Running desktop app with HMR (watching /Users/kondoumh/dev/deno-study/desktop/hello) Runtime loaded successfully from: /Users/kondoumh/Library/Caches/deno/desktop/5f4a00908e99d886/hello.dylib Runtime started [desktop] dylib path: "/Users/kondoumh/Library/Caches/deno/desktop/5f4a00908e99d886/hello.dylib" Listening on http://127.0.0.1:52958/ main.ts のコードを書き換えると、保存後すぐに画面へ反映されます。 --> Information Electron では HMR は標準では利用できず、別途 Forge などで開発サーバーを起動する必要があります。 https://developer.mamezou-tech.com/blogs/2024/01/29/electron-forge-introduction/ UI の ローカル HTTP サービスによる実現 # Electron (Forge など) ではローカルサーバーの利用は開発時が中心で、配布後は file:// でアセットを読む構成が一般的です。 これに対し Deno Desktop は、配布後のバイナリでもローカル HTTP サーバーを内部起動し、空きポートを自動割り当てして UI を描画します。サーバーはプロセス内で閉じており、外部公開はされません。ポート衝突を意識せずに済むのも良い点です。 この「開発時もビルド済みバイナリでも、同じ HTTP 実行モデルで UI を提供する」設計により、 開発時とデプロイ時の挙動に差がない コンテンツはブラウザとデスクトップで同じ動きをする Next.js などのフレームワークがそのままデスクトップアプリの中で動く といったメリットが得られます。 https://docs.deno.com/runtime/desktop/serving/ DevTools の起動 # Electron や Tauri と同様、DevTools によるデバッグが可能です。BrowserWindow を起動し、 openDevtools メソッドを呼ぶだけです。 const win = new Deno.BrowserWindow({ title: "My Deno Desktop App", width: 800, height: 600, }); win.openDevtools(); https://docs.deno.com/runtime/desktop/devtools/ --> Information いまのところ、DevTools のフルサポートはバックエンドを cef にしている時のみです。 以下のように、指定して起動する必要があります。 deno desktop --hmr --backend=cef main.ts バックエンドとフロントエンドの通信(Bindings) # Electron の IPC 通信は render.js と main.js を preload.js 経由でブリッジする必要があり、かなり面倒です。Deno デスクトップでは BrowserWindow にバインドした関数を bindings というグローバルオブジェクトにより簡単に呼び出すことができます。 Deno ランタイムとレンダリングバックエンドはスレッドやプロセスとして動作し、呼び出しはプロセス内チャネルを介して行われます。このサンプル構成ではソケットベースの IPC を直接扱わずに済むため、Electron の ipcMain / ipcRenderer、Tauri の invoke と比べて見通しよく書けるのが利点です。 実際のコードで見てみましょう。 const win = new Deno.BrowserWindow({ title: "Bindingsのテスト", width: 800, height: 600, }); // ========================================== // 1. バックエンド側:フロントから呼ばれる関数を登録 // ========================================== win.bind("getSystemInfo", async (userName) => { console.log(`[Deno側] フロントエンドから呼ばれました! 引数: ${userName}`); // Denoの機能を使ってOSの情報を取得 const denoVersion = Deno.version.deno; const os = Deno.build.os; // 少し重い処理をシミュレート(0.5秒待つ) await new Promise(resolve => setTimeout(resolve, 500)); // フロントエンドに返すデータ(JSON化できるものなら何でもOK) return { message: `こんにちは、${userName}さん!`, os: os, denoVersion: denoVersion }; }); // ========================================== // 2. フロントエンド側:画面のHTMLを返す // ========================================== Deno.serve(() => { const html = ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Bindings Test</title> </head> <body> <h1>Deno Desktop Bindings</h1> <button id="btn">システム情報を取得</button> <pre id="result">ここに結果が出ます</pre> <script> // ボタンが押された時の処理 document.querySelector('#btn').addEventListener('click', async () => { const resultArea = document.getElementById('result'); resultArea.textContent = "取得中..."; try { // 💡 bindings を使ってバックエンドの関数を呼び出す const data = await bindings.getSystemInfo("kondoumh"); // 結果を画面に表示 resultArea.textContent = JSON.stringify(data, null, 2); } catch (error) { resultArea.textContent = "error: " + error.message; } }); </script> </body> </html> `; return new Response(html, { headers: { "content-type": "text/html" }, }); }); アプリ画面です。 システム情報を取得 ボタンをクリックするとしばらく呼び出し中になり、結果が表示されます。 結果が表示された状態。 アプリを起動しているバックエンドでは次のようにログが出ています。 [Deno側] フロントエンドから呼ばれました! 引数: kondoumh すごくシンプルです。OS のネイティブ機能と Web UI を簡単に連携できるのがいいですね。 https://docs.deno.com/runtime/desktop/bindings/ メニュー の利用 # アプリケーションメニューの実装。 BrowserWindow の setApplicationMenu メソッド内でメニューオブジェクトを定義して渡します。 BrowserWindow にイベントリスナーを登録してメニューがクリックされた時の振る舞いを実装します。 role などは Electron と同じですね。 win.setApplicationMenu([ { submenu: { label: "File", items: [ { item: { label: "New", id: "new", accelerator: "CmdOrCtrl+N", enabled: true, }, }, { item: { label: "Open…", id: "open", accelerator: "CmdOrCtrl+O", enabled: true, }, }, "separator", { item: { label: "Save", id: "save", accelerator: "CmdOrCtrl+S", enabled: true, }, }, { role: { role: "quit" } }, ], }, }, { submenu: { label: "Edit", items: [ { role: { role: "undo" } }, { role: { role: "redo" } }, "separator", { role: { role: "cut" } }, { role: { role: "copy" } }, { role: { role: "paste" } }, ], }, }, ]); win.addEventListener("menuclick", (e) => { const detail = (e as CustomEvent).detail; switch (detail.id) { case "new": console.log("New clicked"); break; case "open": console.log("Open clicked"); break; case "save": console.log("Save clicked"); break; } }); コンテキストメニューの実装。 Deno.MenuItem の配列を作成して、BrowserWindow の showContextMenu に座標とともに渡します。 const contextMenu: Deno.MenuItem[] = [ { item: { label: "Copy", id: "copy", enabled: true } }, { item: { label: "Paste", id: "paste", enabled: true } }, "separator", { item: { label: "Properties…", id: "props", enabled: true } }, ]; // Trigger from a right-click. The webview may not forward the browser // `contextmenu` event, so handle the secondary mouse button on the window. win.addEventListener("mousedown", (e) => { if (e.button === 2) { win.showContextMenu(e.clientX, e.clientY, contextMenu); } }); win.addEventListener("contextmenuclick", (e) => { if (e.detail.id === "copy") { console.log("Copy clicked"); } if (e.detail.id === "paste") { console.log("Paste clicked"); } if (e.detail.id === "props") { console.log("Properties clicked"); } }); --> Information ここではメニューのクリックイベントでログを出力していますが、ログ自体はアプリを起動しているターミナル側に出ますのでご注意ください。 https://docs.deno.com/runtime/desktop/menus/ フレームワークを利用した開発 # Deno.serve() を利用したサンプルを見てきましたが、Deno デスクトップでは、以下のフレームワークとともに利用可能です。これらのプロジェクトのディレクトリで deno desktop を起動すると、フレームワークを自動検出してアプリを構成します。多くのモダンフレームワークがサポートされています。 Next.js Astro Fresh Remix Nuxt SvelteKit SolidStart TanStack Start Vite ローカルで動いてるのに SSR を使うというのがなんとも不思議な感じですが、ちゃんと動いてセキュアであればヨシ!という感じでしょうか。 https://docs.deno.com/examples/next_tutorial/ Next.js のアプリを作成します。 deno run -A npm:create-next-app@latest 作成したプロジェクトディレクトリへ移動して実行します。 cd <project-dir> deno desktop -A Next.js のアプリが、外部サーバーなしでまるっとデスクトップ内で動いてるのは不思議な感じです。 https://docs.deno.com/runtime/desktop/frameworks/ バックエンドの選択について # デスクトップアプリは配布するバイナリのサイズも重要です。小さいに越したことはありません。 Electron は Chromium を内包するため、インストールされたバイナリサイズは300MBぐらいの大きさになったりします。 Deno Desktop の場合、OS にプリインストールされている WebView を使えば70MB程度です。CEF(Chromium) だとやはり300MB程度になります。 OS 依存の WebView だと、Windows と Mac で微妙に CSS や JS の挙動が変わるクロスブラウザ問題が発生するため、そのための対応やテストも必要になります。機能が少ないうちは WebView でもいいかもしれませんが、機能が増えてくるとテストの手間も何倍にもなっていきます。 Deno Desktop の場合、最初は軽量な WebView でスタートし、クロスブラウザが重荷になってきたら、ちょっと配布サイズは大きくなるけど、CEF にスイッチできるのがいいかなと思います。 https://docs.deno.com/runtime/desktop/backends/ --> Information Tauri だとこの辺、Servo ベースの自前 WebView プロジェクト Verso 待ちですが、Deno は既存の Chromium を選択可能にしているあたり、現時点での割り切りが感じられますね。 Electron との比較 # 既存 Web アプリをデスクトップ化したいユースケースでは、Deno Desktop はかなり有力です。 一方で、Electron の WebContentsView のような高度なマルチビュー構成を前提にしている場合は、現時点では Electron のほうが適しています。たとえば VS Code や Figma のように、複数ビューを細かく制御するタイプのアプリです。 ざっくり整理すると次のような感触です。 単一ウィンドウ中心 + 既存 Web 資産活用: Deno Desktop はかなり良い 複雑なウィンドウ/ビュー管理: Electron が依然強い --> Information マルチビュー構成の対応の弱さは Tauri も同様です。 https://developer.mamezou-tech.com/blogs/2025/12/01/porting-an-electron-app-to-tauri2/ Electron の WebContensView 構成については以下の記事をご参照ください。 /blogs/2024/08/28/electron-webcontentsview-app-structure/ https://docs.deno.com/runtime/desktop/comparison/ さいごに # 以上、Deno Desktop 機能を一通り試しました。 Out of the box でここまでデスクトップ開発体験が整っているのは率直に驚きです。タスクトレイやメニュー、Bindings など、アプリらしさを出すための API が最初から揃っているのも好印象でした。 Tauri と違ってアプリ側をすべて TypeScript で書けるため、既存 Web アプリをベースに「メニューやタスクトレイを追加し、OS 機能と連携する」用途ではかなり相性がよいと感じます。最小構成なら、デスクトップアプリ化自体は1時間もかからないはずです。 --> Information Tauri も JS の API を生やして、Rust 知らない勢を取り込もうとしてはいます。 Deno のキラー機能になる可能性もありますね。experimental から安定版へ向けて、今後の熟成がとても楽しみな機能です。 2026年7月6日現在の最新は 2.9.1 です。 ↩︎
はじめに # AIテクニカルセクターの藤堂です。セクターが独立組織となって以降、全社でAI活用を広げる取り組みが本格化しています。そのような中、6月には 第40回 JSAI2026 に参加しました。 前半ではポスター発表をした、士業資格試験を対象としたLLMベンチマークShigyoBenchについて簡単に触れ、AIの実用化についての所感を述べます。 後半では、人工知能学会で印象に残った発表と、今後のAI研究について思ったことを記します。 どういう論文か # ShigyoBenchは、 日本の士業資格試験を対象にしたLLMベンチマークデータセット です。宅建・行政書士・弁理士・司法書士・司法試験(予備試験含む)・不動産鑑定士・公認会計士の8試験、短答式8,979問を統一フォーマットで整備し、複数のLLMで評価実験を行いました。データセットは Hugging Face で公開しています。 論文の位置づけとしては、専門領域の知識を試験問題で定量評価する研究です。士業領域(行政書士・弁理士・宅建・不動産鑑定士・司法書士など)とCPA全科目について、これまで再現可能な共通ベンチがなかった空白を埋める、というのが動機です。 評価の結果、モデルや試験によって差は大きく、Gemini-3-Proは一部の試験で合格ラインを上回る一方、多くのモデル・科目ではまだ届かない、というのが本研究の結論です。 たとえば次のような問題があります。以下は、 google/gemma-4-26B-A4B-it で推論させて不正解となった一例です。 司法試験・令和2年・民法第8問 # 問題 即時取得に関する次のアからオまでの各記述のうち、判例の趣旨に照らし誤っているものを組み合わせたものは、後記1から5までのうちどれか。 ア. Aは、自己所有の宝石をBに売却して現実の引渡しをした。その後、Bは宝石をCに売却して現実の引渡しをした。さらにその後、AはAB間の売買契約をBの強迫を理由として取り消した。この場合、Cは即時取得により宝石の所有権を取得することはない。 イ. 未成年者Aは、自己所有の宝石をBに売却して現実の引渡しをした。その後、AはAB間の売買契約を未成年であることを理由として取り消した。この場合、Bが即時取得により宝石の所有権を取得することはない。 ウ. Aは、B所有の宝石をBから賃借して引渡しを受けた上、宝石をCに預けていたが、宝石をDに売却し、Cに対し宝石を今後Dのために占有するよう命じ、Dがこれを承諾した。この場合、Dは宝石がA所有であると信じ、かつそのことに過失がなかったとしても、即時取得により宝石の所有権を取得することはない。 エ. Aは、Bが置き忘れた宝石を、自己所有物であると過失なく信じて持ち帰った。この場合、Aが即時取得により宝石の所有権を取得することはない。 オ. Aは、BがCから賃借していた宝石を盗み、Dに贈与した。Dが宝石をAの所有物であると過失なく信じて現実の引渡しを受けた場合、Bは宝石の盗難時から2年間は、Dに宝石の回復を請求することができる。 ア ウ 2. ア エ 3. イ ウ 4. イ オ 5. エ オ google/gemma-4-26B-A4B-it の回答 この問題は、民法192条の「即時取得」に関する記述の正誤を判断し、誤っているものの組み合わせを選ぶ問題です。 ### 各記述の検討 **ア. 正しい** 即時取得が成立するためには、「取引行為」が必要です。Bの強迫による取消しの場合、取消しによって契約は遡及的に無効となりますが、判例によれば、取消しによって権利を失った者(A)からさらに譲り受けた第三者(C)が即時取得を行うためには、**「取消し前」に取引が行われていなければなりません**。しかし、本問ではAが取消しをした「後」にCが譲り受けているわけではなく、時系列としては「A→B(売買)→C(売買)」の後に「Aが取消し」をしています。 ここで重要なのは、強迫による取消しは「詐欺」とは異なり、取消しによって権利が遡及的に失われますが、即時取得の要件である「有効な取引」の観点から、強迫による取消しがなされた場合、Bは無権利者となります。しかし、判例の趣旨(および通説的な考え方)では、強迫による取消しは、善意無過失の第三者(C)に対してもその効力を主張できる(Cは即時取得できない)とされています。したがって、「Cは即時取得により所有権を取得することはない」という記述は正しいです。 **イ. 誤り** 即時取得が成立するためには、占有者の「権利」が欠けていること(無権利者からの取得)が必要ですが、取引行為自体は有効である必要があります。未成年者が単独で行った契約の取消しは、行為能力の瑕疵によるものですが、これは即時取得を妨げる理由にはなりません。Bが善意無過失であれば、即時取得により所有権を取得できます。したがって、「Bが即時取得により所有権を取得することはない」という記述は誤りです。 **ウ. 正しい** 即時取得が成立するためには、「占有の譲渡」などの直接的な占有の移転が必要です。本問のように、AがCに預けていたものを、Dに対して「占有改定(CがDのために占有するよう命じる)」によって占有を移転させた場合、判例は**占有改定による即時取得の成立を否定**しています。したがって、「Dは……即時取得により宝石の所有権を取得することはない」という記述は正しいです。 **エ. 正しい** 即時取得は「取引行為(売買、贈与など)」によって占有を取得する場合に成立します。本問のように、他人の物を勝手に持ち帰る行為(占有の事実上の取得)は「取引行為」には当たらないため、即時取得は成立しません。したがって、記述は正しいです。 **オ. 誤り** 民法193条により、盗品または遺失物の占有者は、盗難または遺失の時から**2年間**、即時取得者に対してその物の回復を請求できます。しかし、本問のDは、Aが「盗んだ物」であることを知らない(善意無過失)ため、即時取得が成立します。即時取得が成立した場合、Dは有効に所有権を取得します。たとえ民法193条の期間内であっても、即時取得によって権利が確定している以上、元の所有者BはDに対して返還を請求することはできません(即時取得は、193条の規定にかかわらず、有効な取得を認めるものです)。したがって、記述は誤りです。 ### まとめ - ア:正しい - イ:誤り - ウ:正しい - エ:正しい - オ:誤り 誤っているものは **イ** と **オ** です。 選択肢を確認すると、イとオの組み合わせは「4」となります。 <answer>4</answer> 正解 : 1 手法や結果の詳細については 論文 をご覧ください。 実用化への道のり # LLMが登場し、ShigyoBenchのような人間と同じ試験問題を使ってLLMの専門知識を測ることが、AI評価の標準として定着しました。それにより、国際数学オリンピックや東京大学の入試試験、医師国家試験など、いくつもの試験がフロンティアLLMで解かれることが知られています。RAGなどを組み合わせれば士業試験で合格ラインに届かせることも、十分あり得ると思います。 ポスター発表時のディスカッションやいくつかのセッションでも、人間の仕事はなくなっていくのでは、という議論が盛んにありました。 一方で、試験問題が解けることと、実用化できることは、別問題だということが現在、議論されています。 最も進んでいる領域の1つと考えられるのが医療分野です。Gong ら(2025)のシステマティックレビューでは、39の医療LLMベンチマークを整理しました。 国家試験(USMLE)型の知識ベース評価では、先行モデルが84〜90%の正答率に達します。 対照的に、診療現場に近い実践ベースの評価では45〜69%程度にとどまります(いわゆる knowledge-practice gap)。 「試験問題が解けることと、患者さんに直接LLMの出力を出せることは別」ということを、知り合いのお医者さんにも聞いたことがあります。 これは医療だけの問題ではなく、実用化にはいくつかのステップがあるのだろうと思います。自由記述での推論、不確実性の管理、マルチターンの対話、文脈の統合、安全性といった実践的なベンチマークに何が求められるかが、議論の焦点となっています。 ソフトウェア開発では、少し様子が違います。こうした「試験と現場のギャップ」を十分に議論する前に、コーディング支援やエージェントが現場に入り始めている印象があります。おそらく、コードは試して直しやすく、フィードバックも速いからでしょう。医療や法律のように、一度の誤りから取り返しのつかない結果が生じにくい、という違いもあるのかもしれません。ただ、1つのシステムにかかわる複雑なコードやドキュメント類の品質をどのように保つのか、テストの合格率だけでは見えないリスクもあります。私自身、実案件でLLMを使いながら、その便利さと危うさの両方を感じています。 フィジカルAIへの期待 # JSAI2026では、ロボティクスやフィジカルAI(Physical AI)に関する発表が数多く見られました。業務都合でその中の1つのセッションしか聞けなかったのですが、日本でも研究者や学生の関心が集まっていることを実感しました。 最も印象に残った発表は、「基盤モデル時代におけるPhysical AI」セッションでの「 自動運転向けの多視点の動画生成における世界ハンドオフ整合性指標の設計 」です。 拡散モデルによる映像生成モデルが登場し、自動運転用の合成データ作成などに役立てられています。そのようなモデルで複数の視点からカメラで写したような映像が取得できますが、それらカメラの間で物理的な不整合(あるカメラでセダンだった車が別のカメラで別の車種になるなど)が発生します。そのような不整合を定量的に評価しようという論文です。 画像におけるハルシネーションという言い方をされていて、原理的には確かにそういったことが起こるなと思いますし、そのことを定量化したという点で価値があります。 松尾豊教授が、「JSAIの3分の1は国際学会で発表できるレベルにある」と言及されていました。個人的な体感として、論文数が増えた現在、そこまで高い割合ではないかなと思いますが、本発表は国際レベルの内容で、このように優秀な若者がフィジカルAIの問題をどんどん解いていくのだろうなと思いました。 生成AIがAI研究自体を変え始めている # 特定のセッションというより、学会全体を通して感じたのは、生成AIがAI研究の進め方そのものを変え始めている、ということでした。 文献の当たり方、プログラミング、実験の設計、データの整理、モデルの学習といった、これまで研究者が時間をかけていた作業の多くを、LLMが肩代わりし始めています。負荷の中心は「実装する」「情報を集める」から、「何を問うか」「どう測るか」へ移っているように見えます。試行が短くなるほど次の成果が出やすくなり、それがまた試行を短くする、というサイクルになっているように見えます。 個人的には、ひとつの論題を深掘りするより、こうしたサイクルを回しやすくする環境づくりに時間を使うようになってきました。 Cursor や Codex のようなコーディングエージェントがあります。 各種 AI API と接続したアプリケーション、Google Colaboratory や Modal のような GPU 基盤も利用しています。 いま利用しているツールを図にすると、おおむね次の構成です。 モデルの使い分け、先行研究の整理の仕方、どこで人間が判断を挟むか、どのGPU をどの実験に回すか。このようにメタ視点で、実験環境全体にリソースを割くことにより、研究が加速していきます。 明日には似た研究が発表されているかもしれない。研究が加速する中でいちばん難しいのは、何を題材にするかだと思います。 おわりに # 現地には最終日しか行けませんでしたが、ポスター発表では名だたるLLMベンダーの方々とお話ができて大変充実していました。よもや国内にAIモデルベンダーと呼ばれる人々が現れるとは5年前には想像もつきませんでしたが、彼らにはどんどん世界にチャレンジしていってもらいたいです。 また、以前お仕事を一緒にさせていただいた懐かしい人にも出会えて、様々なお話ができました。このようなことはリアルの醍醐味ですし、AIコミュニティというのは広そうで狭いコミュニティなのだなと実感しました。 次は長崎です。 参考文献 # 本研究(ShigyoBench) 藤堂真登, 石川真之介. ShigyoBench: 日本の士業資格試験を対象としたLLMベンチマークデータセットの構築と評価. JSAI2026 論文集 , 2026. https://pub.confit.atlas.jp/ja/event/jsai2026/presentation/5Yin-A-16 / データセット: https://huggingface.co/datasets/todo1111/shigyobench 実用化・医療 Gong EJ, Bang CS, Lee JJ, Baik GH. Knowledge-Practice Performance Gap in Clinical LLMs: Systematic Review of 39 Benchmarks. J Med Internet Res . 2025;27:e84120. https://doi.org/10.2196/84120 学会で印象に残った発表 キム ボンジュン, et al. 自動運転向けの多視点の動画生成における世界ハンドオフ整合性指標の設計. JSAI2026 論文集 (基盤モデル時代におけるPhysical AI), 2026. https://pub.confit.atlas.jp/ja/event/jsai2026/presentation/2G4-OS-47a-02 大会 人工知能学会. 2026年度 JSAI(第40回)
今年も半分が過ぎました。2026年4-6月のサマリーです。 記事数・執筆者数 # この3ヶ月で13本の記事が投稿され、記事数は889になりました。 連載 # AIエージェントとシステムをつなぐMCP入門 # MCP(Model Context Protocol) は AI エージェントが外部サービスと通信するための仕様で、2024年に Anthropic 社によって初版がリリースされました。MCP を使用することで、AI エージェントは外部サービスの機能を効果的に利用できます。MCP の基本から実装まで段階を分けて解説するシリーズです。 /blogs/2026/04/24/mcp-impl_introduction/ 現在は以下の6記事が公開されています。 イントロダクション stdio実装編 StreamableHTTPステートレス実装編 StreamableHTTPステートフル実装編 プロンプト編 リソース編 テーマ別記事 # 認定資格 # /blogs/2026/04/13/google_cloud_all_certified_revenge/ /blogs/2026/04/20/aws_certified_generative_ai_developer/ ペアレンタルコントロール # 上記の AWS・Google Cloud 認定を“W全冠”したエンジニアが、クラウドから降りて自宅のネットワークと格闘した異色の記事です。夜中に学校用タブレットでゲームをする子供との「イタチごっこ」に終止符を打つべく、手持ちの家庭用ルータとRaspberry Piを活用して、MACアドレス制限やサブネット分離、Pi-holeによる独自DNS構築まで、本気の「ガチ構成」を徹夜で組み上げる様子を赤裸々に綴っています。DoH(暗号化DNS)対策などのリアルな課題にも触れられており、ネットワークの基礎を学び直したい方や、同じ悩みを持つITエンジニアの親御さん必見の泥臭くも愛に溢れた実践録です。 /blogs/2026/04/09/home_network_control/ スクラムマスターと AI # チームの対話を支えるスクラムマスターにとって、視覚的な資料作成は欠かせませんが、一方で多大な時間がかかるのが共通の悩み。本記事では、そのボトルネックを AI で突破する実践的な手法を解説しています。ChatGPT を思考のパートナーとして構成を練り上げ、最新の生成 AI ツールでスライドを一気に形にする――単なる「時短術」に留まらず、AI との対話を通じてアイデアを磨き、本来注力すべきファシリテーションやコーチングの質を高めるための「共創のプロセス」を紹介しています。資料作成の重圧から解放され、チームの価値最大化に向き合いたいリーダー・マネージャーにとっても役立つ内容となっています。 /blogs/2026/04/27/ai-presentation/ GitHub # GitHub の Organization 運用において、「機密リポジトリを作りたいが、Basic Permission の設定変更による管理コスト爆発は避けたい」というジレンマ。この記事では、高価なEnterprise プランを契約せずとも、Teams プランの制限下で安全かつ効率的にアクセス権を管理する「ホワイトリスト方式」の戦略を解説しています。全メンバー用の統合チーム作成によるセキュリティ境界の構築から、GitHub API と Actions を組み合わせた「チーム追加漏れを防ぐ自動化スクリプト」の実装まで、管理者の負担を増やさない現場目線のハックを紹介しています。 /blogs/2026/06/24/github-manage-organization-access/ CI/CD 環境で広く使われる GitHub Actions に新たに追加された、単一ワークフロー内でのステップ並行実行機能をいち早く検証した記事です。background 属性を使った非同期実行や、parallel ブロックを使った同時実行の基本構文を解説するだけでなく、Go 言語のクロスコンパイルを用いた実践的なパフォーマンス検証も実施。vCPU コア数やコンテキストスイッチの観点から「期待したほど速くならなかった理由」と、「これまでの Matrix ビルドとどう使い分けるべきか」まで深く考察しており、現場の CI 改善に直結する生きた知見が得られます。はてなブックマークでも注目され、公開直後からアクセスが上昇しました。 /blogs/2026/06/27/github-actions-parallel-steps/ さいごに # 以上、2026年度第1四半期のサマリーでした。投稿数が少なかったため、個別の記事紹介を厚めにしてみました。 よかったら フィード の購読、 X や Bluesky でのフォローもお願いします。 Facebook でも本サイトの注目記事をはじめ豆蔵に関するイベントを紹介しています。 note にも時々本サイト関連の記事が掲載されています。
はじめに # 本ページは「AIエージェントとシステムをつなぐMCP入門」の続編です。 今回は、リソースについて説明します。 MCPのリソースは、AIモデルが回答を生成する際に参照するコンテキスト情報(ファイル、ガイド、仕様など)を提供する機能です。 ツールが「AIモデルの判断」で実行され、プロンプトが「ユーザーの意思」で選択されるのに対し、リソースは「アプリケーション主導(Application-driven)」でその組み込みを決定します。 本記事で掲載しているコードは こちら で公開しています。 --> シリーズ目次 連載:AIエージェントとシステムをつなぐMCP入門 イントロダクション stdio実装編 StreamableHTTPステートレス実装編 StreamableHTTPステートフル実装編 プロンプト編 リソース編(本ページ) 今回使用するライブラリなど # npm@11.11.1 node@22.22.0 typescript@6.0.3 @modelcontextprotocol/sdk@1.29.0 zod@4.3.6 使用例 # 開発ガイドの共有: コーディング規約や設計方針をリソース化して、AI生成時の前提情報として渡します 仕様断片の参照: API仕様、エラーコード定義、入力制約などの抜粋をリソースとして公開し、コード生成の精度を高めます 動的パラメーターによって切り替える情報の提供: URIテンプレートを使い、可変パラメーターで内容が変わるリソースを提供します ツール、プロンプト、リソースの違い # これまでの特集でツール、プロンプト、リソースの主要要素に触れてきたので、ここで役割を振り返ります。 要素名 主な役割 制御主体 識別方法 リソース データ・コンテキスト(静的な知識)の提供 アプリケーション主導 リソースのURI プロンプト メッセージ・ワークフローのテンプレート ユーザー主導 プロンプト名 ツール 具体的な関数の実行(能動的なアクション) AIモデルによる実行 ツール名 リソースの種類 # MCPのリソースは、URIの与え方によって2種類に分かれます。 種類 説明 用途例 静的リソース 固定URIで一意に特定されるリソース ガイド、固定仕様、設定情報など リソーステンプレート URIテンプレート(RFC 6570)でパラメーター化されたリソース IDや名前などの可変要素で内容が変わる情報 プロトコルメッセージ # MCPのリソースに関するメッセージは6種類あります。 メッセージ 方向 説明 resources/list クライアント → サーバー 利用可能なリソース一覧を取得する(ページネーション対応) resources/templates/list クライアント → サーバー リソーステンプレート一覧を取得する resources/read クライアント → サーバー URIを指定してリソースのコンテンツを取得する resources/subscribe クライアント → サーバー 指定URIのリソース変更通知を購読する(ケーパビリティ subscribe: true が必要) notifications/resources/updated クライアント ← サーバー 購読中のリソースが変更されたことを通知する notifications/resources/list_changed クライアント ← サーバー リソース一覧が変化したことを通知する(ケーパビリティ listChanged: true が必要) ※通知を検証するためには、リソースの源泉を別に設け かつ 源泉側の変更検知も必要なため、サンプルには含めていません。 sequenceDiagram actor client as MCP Client participant server as MCP Server client ->> server: リソース一覧取得(resources/list) server -->> client: リソース一覧 client ->> server: リソース取得(resources/read) server -->> client: リソース データ構造(Data Types) # リソースコンテンツ uri : リソースを一意に識別可能なURI name : リソース名 title : 表示用のタイトル description (任意): リソースの説明 icons (任意): アイコンリスト mimeType (任意): コンテンツのMIMEタイプ。テキストまたはバイナリ(Base64エンコードが必要)を指定可。 size (任意): バイト数 アノテーション(Annotations): クライアントへのヒント audience : 誰向けのリソースか示す情報。 user , assistant または両方を指定可。 priority : 0.0~1.0の数値で示される重要度(1.0が最重要) lastModified : 最終更新日時(ISO8601形式のタイムスタンプ) URIスキーム # リソースのURIには目的に応じたスキームを選びます。 スキーム 用途 備考 https:// Web上のリソースを参照 クライアント自身がアクセスできる場合に使用する。サーバー経由で取得する場合は独自スキームの検討が推奨されます file:// ファイルシステムのような構造を表現したいリソース ファイルシステムとのマッピングは必須ではなく、値の取得先は自由(DBや外部APIなど) git:// Gitリソース コミット、ブランチ、パスなどのGit固有の構造を表現 カスタム 独自スキームによる任意のリソース RFC3986準拠。本サンプルの memory:// 、 orders:// もこれに該当 --> Information サーバーはすべてのリソースURIを検証する サンプルでは省略していますが、パストラバーサルなど意図しないアクセスを防止しなければいけません。 fileでディレクトリを表現する場合 mimeTypeに inode/directory (XDG規格)を指定することが推奨されます。 実装サンプル # コードの全体は こちら をご覧ください。 静的リソースの登録 # 固定URIでリソースを公開する例です。 registerResource の第2引数にURI文字列を渡すと静的リソースになります。 // リソース: アノテーション付きの静的リソース(テキストコンテンツ) const testGuideUri = "memory://guides/testcase-prompt-playbook"; server.registerResource( "testcase-prompt-playbook", testGuideUri, { mimeType: "text/markdown", description: "テスト観点生成のガイド", annotations: { audience: ["assistant"], // AIへの参照情報として位置づける priority: 0.8, // 重要度(高め) lastModified: "2026-06-28T00:00:00Z", }, }, async () => ({ contents: [{ uri: testGuideUri, mimeType: "text/markdown", text: [ // 実務ではもっと細かい指示が必要になりますが、行数を抑えるため最小限に留めています "# APIテストケース作成ガイド", "- 仕様の観点: 正常系、代替系、異常系、境界値、認可、冪等性", "- 基本フローの正常系を中心に、代替系、異常系、その他の観点を付加する形にまとめる。", "- ユースケースの検証を主眼とし、入力値検証などは含めない(単体テストで担保する)", "- 必要に応じて分類しながら、箇条書きで簡潔にまとめる", ].join("\n") }] }), ); 動作確認: resources/read が確認できるPostmanで確認 バイナリコンテンツの指定例 バイナリコンテンツ(画像など)は blob にBase64エンコード文字列を設定します。 // リソース: バイナリコンテンツの例(画像) server.registerResource( "company-logo", "file://assets/logo.png", { mimeType: "image/png", description: "企業ロゴ画像" }, async () => ({ contents: [{ uri: "file://assets/logo.png", mimeType: "image/png", blob: "<Base64エンコードされたデータ>" }], }), ); リソーステンプレートの登録 # ResourceTemplate クラスを使うと、URIテンプレート(RFC 6570)でパラメーター化されたリソースを公開できます。 {orderId} のようなプレースホルダーがリクエスト時に展開されます。 list コールバックは resources/list で返す候補一覧を定義します(省略不可、不要なら undefined を渡します)。 resources/read リクエスト時、クライアントは orders://O00001/detail のようにテンプレートを展開したURIを指定します。 サーバー側では {orderId} 部分が変数として分解され、ハンドラーに渡されます。 // リソーステンプレート: URIのパラメータで内容が変わる動的リソース server.registerResource( "order-detail", new ResourceTemplate("orders://{orderId}/detail", { list: async () => ({ resources: [ { uri: "orders://O00001/detail", name: "O00001", mimeType: "application/json" }, { uri: "orders://O00002/detail", name: "O00002", mimeType: "application/json" }, ], }), }), { mimeType: "application/json", description: "受注詳細" }, async (uri, { orderId }) => ({ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify({ orderId, status: "pending" }) }], }), ); 動作確認 リソーステンプレートの確認: resources/templates/list が確認できるMCP Inspectorで確認 リソーステンプレートの実行確認: resources/read が確認できるPostmanで確認 まとめ # リソースは、AIへの参照情報を明示的に管理したい場合に有効です。 静的リソースは固定情報(ガイド、仕様など)、リソーステンプレートは可変情報(IDベースの詳細など)に向いています。 アノテーションを設定することで、クライアントがリソースを適切に選択し、優先順位付けできます プロンプトと組み合わせると、生成指示と参照情報の両方を標準化できます
はじめに # 先日、GitHub Actions ワークフローで background 機能が実装され、単一のワークフロー内でステップを並行で実行可能になったことが発表されました。 https://github.blog/changelog/2026-06-25-actions-steps-can-now-be-run-in-parallel/ これまでも、Strategy Matrix を使うと複数のランナーを使って並行処理させることはできましたが、今回の機能で、単一のランナーでの並行処理がサポートされたことになります。 公式ドキュメント には、バックエンドとフロントエンドのビルドを並行で実行するサンプルが掲載されています。 steps: - name: Build frontend id: build-frontend run: npm run build:frontend background: true - name: Build backend id: build-backend run: npm run build:backend background: true - name: Run linter while builds run run: npm run lint - name: Wait for both builds to finish wait: [build-frontend, build-backend] - name: Run tests run: npm test 並行実行を試す (background 版) # ステップ毎に background: true を指定する方法です。この属性を付与したステップは、実行開始後即座にフォアグラウンドに処理を戻します。 name: Background Hello World on: workflow_dispatch: jobs: hello-background: runs-on: ubuntu-latest steps: - name: Background hello 1 #1 id: hello1 run: | echo "hello1 start: $(date -u +%H:%M:%S)" sleep 4 echo "hello1 end: $(date -u +%H:%M:%S)" background: true - name: Background hello 2 #2 id: hello2 run: | echo "hello2 start: $(date -u +%H:%M:%S)" sleep 3 echo "hello2 end: $(date -u +%H:%M:%S)" background: true - name: Foreground step (runs while background steps are active) #3 run: | echo "foreground start: $(date -u +%H:%M:%S)" sleep 1 echo "foreground end: $(date -u +%H:%M:%S)" - name: Wait for background steps #4 wait: [hello1, hello2] - name: Done run: echo "Both background steps have completed." バックグラウンド実行するステップ。4秒間のスリープの開始と終了で時刻を表示します。 background: true を指定します。 バックグラウンド実行するステップ2個目。1個目と同様の処理です。スリープは3秒にしてます。 バックグラウンド実行しながら、フォアグラウンドで実行されるステップ。1秒間スリープします。 バックグラウンドの2つのステップを待ち受けるステップです。 wait でステップの ID を配列で指定するだけです。 実行結果です。 hello1 と hello2 が同一時刻に開始され、1秒違いで終了しています。 フォアグラウンドステップも2つのバックグラウンドステップと同時刻に開始されています。 wait ステップで2つのバックグラウンドステップの完了を待っています。 --> Information 上記のサンプルでは単純な sleep と echo をバックグラウンドで実行し、wait で待機しているだけですが、実際の CI/CD パイプラインでは「バックグラウンドで起動したサーバーやプロセスの結果を後から使いたい」というユースケースがよくあります。このようなケースでは、出力は一時ファイルに書き出しておき、wait後のステップでそれを読み込むといった工夫が必要になります。 並行実行を試す (parallel 版) # parallel キーワード配下にステップを並べるだけで並列化できます。parallel ブロックを抜けると完了するため、wait で待つ必要はありません。 name: Parallel Hello World on: workflow_dispatch: jobs: hello-parallel: runs-on: ubuntu-latest steps: - parallel: #1 - name: Parallel hello 1 run: | echo "parallel-1 start: $(date -u +%H:%M:%S)" sleep 4 echo "parallel-1 end: $(date -u +%H:%M:%S)" - name: Parallel hello 2 run: | echo "parallel-2 start: $(date -u +%H:%M:%S)" sleep 3 echo "parallel-2 end: $(date -u +%H:%M:%S)" - name: Parallel hello 3 run: | echo "parallel-3 start: $(date -u +%H:%M:%S)" sleep 2 echo "parallel-3 end: $(date -u +%H:%M:%S)" - name: Done after all parallel steps #2 run: | echo "done step start: $(date -u +%H:%M:%S)" echo "All parallel steps have completed." echo "done step end: $(date -u +%H:%M:%S)" parallel 配下に3つのステップを配置します。sleep は4秒、3秒、2秒とバリエーションを持たせています。 通常のステップです。parallel ステップ完了後に実行されます。 実行結果です。 hello1, hello2, hello3 が同時刻に開始されています。それぞれ指定通り、4秒、3秒、2秒実行にかかっています。 最後のステップは、parallel ステップ完了後の時刻から開始されていることがわかります。 クロスコンパイルで使ってみる # 応用として、すぐに思いつくのは、クロスコンパイルで複数プラットフォーム向けのバイナリ生成を並行で実行することです。例えば、Go 言語では Linux / macOS / Windows の向けのバイナリをクロスコンパイルできます。 - name: Build run: | GOOS=linux GOARCH=amd64 go build -o build/linux-amd64/sb2md main.go GOOS=linux GOARCH=arm64 go build -o build/linux-arm64/sb2md main.go GOOS=windows GOARCH=amd64 go build -o build/windows/sb2md.exe main.go GOOS=darwin GOARCH=amd64 go build -o build/macos/sb2md main.go GOOS=darwin GOARCH=arm64 go build -o build/macos_arm/sb2md main.go 並列化する以前のビルド結果です。5つのバイナリを生成するのに44秒かかっています。 並列化を適用しました。run で複数行書いてましたが、個別のステップに分けて、parallel 配下に置きました。 - parallel: - name: Build linux amd64 run: GOOS=linux GOARCH=amd64 go build -o build/linux-amd64/sb2md main.go - name: Build linux arm64 run: GOOS=linux GOARCH=arm64 go build -o build/linux-arm64/sb2md main.go - name: Build windows amd64 run: GOOS=windows GOARCH=amd64 go build -o build/windows/sb2md.exe main.go - name: Build darwin amd64 run: GOOS=darwin GOARCH=amd64 go build -o build/macos/sb2md main.go - name: Build darwin arm64 run: GOOS=darwin GOARCH=arm64 go build -o build/macos_arm/sb2md main.go トータルは40秒でした、Linux amd64 のビルドは3秒で終わってますが、他のプラットフォーム用のビルドはそれぞれで40秒かかっています。思ったより短縮されませんでした。考えられる原因としては、やはりランナーの CPU コア数でしょうか。 ランナー(ubuntu-latest)の vCPU が2コアであることから多重度が上がらなかった 5つのプロセスが2つのコアを奪い合ってコンテキストスイッチが大きかった ランナーのアーキテクチャ自体が Linux amd64 なので、ネイティブのバイナリ生成は瞬時に終わった CPU コアの多い Larger Runner にすればもっと短縮できそうですが、40秒が3秒程度に短縮されるだけなら、コストパフォーマンスはイマイチですね。今回のユースケースには合わない感じがします。 さいごに # 以上、GitHub Actions ワークフローでの並行ステップ実行を試してみました。 今回試したGoのクロスコンパイルのように、CPUヘビーで互いに独立したタスクであれば、これまで通りStrategy Matrixを使って別々のランナーを立ち上げた方が高速に処理できる可能性が高いです(課金は高くなりますが)。 一方で、今回の並行ステップ実行は公式ドキュメントのサンプルにあるような「バックエンドとフロントエンドのビルド」や「テスト実行と並行しての Lint 実行」など、単一ランナーの空きリソース(I/O待ちの時間など)を効率よく活用したい場面で輝く機能だと言えるのではないかと思います。
はじめに # GitHub Organization を運用していると、セキュリティと利便性のバランスを取る必要があります。先日、「機密性の高いデータを保持するリポジトリを作りたいので、オーガニゼーションの Basic Permission を write から no permission に変更してほしい」という相談を受けました。 この要望は妥当ですが、そのまま実装すると新規リポジトリ作成のたびにメンバーのアクセス権を個別設定しなければならず、管理者の負担が大幅に増えてしまいます。 本記事では、Enterprise プランではなく Teams プランの制限下で実装できるアクセス権管理戦略をご紹介します。 問題の整理 # 環境の制約 # 大前提として以下があります。 GitHub Enterprise ではなく Team プラン Team ベースのアクセス制御は可能だが、細かい設定に限界がある 元々の課題 # Basic Permission が write に設定されている 機密性の高いリポジトリに対して、デフォルトでアクセス権が与えられてしまう これを防ぐため、Basic Permission を no permission にしたい その先の課題 # Basic Permission を no permission に変更すると、全ての新規リポジトリでメンバーのアクセス設定が必要 新規メンバー追加時にも、リポジトリごとのアクセス設定が必要 管理作業が爆発的に増加する 今回の解決策 # GitHub のことは GitHub Copilot に聞こうということで、Web 版の Copilot くんに相談し、オーガニゼーション内のチームによるセキュリティ境界を構築することにしました。 アクセス権管理の全体像 # 以下に構築したアクセス権管理の全体像を示します。 graph TB Org["Organization<br/>(mamezou-tech)"] Members["Organization Members<br/>(全従業員)"] AllMembers["all-members Team"] NonAllMembers["all-members 未所属メンバー"] SecurityTeam["security-team<br/>(限定メンバー)"] GenRepo["一般リポジトリ<br/>📁 public, docs, tools, etc."] ConfRepo["機密リポジトリ<br/>🔒 secrets, finance, compliance, etc."] Org --> Members Members -->|標準運用| AllMembers Members -->|例外運用| NonAllMembers AllMembers -->|Read/Write アクセス| GenRepo AllMembers -.->|アクセス不可| ConfRepo Members -->|選定メンバーのみ| SecurityTeam SecurityTeam -->|Read/Write アクセス| ConfRepo NonAllMembers -.->|アクセス不可| GenRepo NonAllMembers -.->|アクセス不可| ConfRepo style Org fill:#e1f5ff style Members fill:#f3e5f5 style AllMembers fill:#c8e6c9 style NonAllMembers fill:#eceff1 style SecurityTeam fill:#ffccbc style GenRepo fill:#fff9c4 style ConfRepo fill:#ffcdd2 linkStyle 6,7 stroke:#d32f2f,stroke-width:2px,stroke-dasharray: 6 4 この構成では、 all-members への所属がアクセス可否を決めるセキュリティ境界となり、未所属の Organization メンバーは一般リポジトリ・機密リポジトリのいずれにもアクセスできません。 基本戦略:「ホワイトリスト方式」への転換 # 以下の方針でアクセス権を整理します。 1. 全メンバーが所属する統合チームの作成 all-members という全メンバーが所属するチームを作成します。このチームには、ほぼ全員がアクセスすべき一般的なリポジトリへのアクセス権を付与します。 利点: 新規メンバー追加時は、このチームに追加するだけで大多数のリポジトリにアクセス可能 リポジトリ作成後のデフォルトアクセス設定が不要 2. 機密リポジトリの隔離 機密性の高いリポジトリ(例:構成情報、個人情報、営業秘密など)は、 all-members チームからは見えないようにします。 その代わり、必要なメンバーのみで構成した別チーム(例: security-team 、 finance-team )を作成し、そのチームにのみアクセス権を付与します。 3. リポジトリごとの権限設定 リポジトリA(一般向け) └─ all-members チーム: read リポジトリB(一般向け) └─ all-members チーム: write リポジトリC(機密:セキュリティ) └─ security-team チーム: write └─ 特定ユーザー: admin リポジトリD(機密:財務) └─ finance-team チーム: write └─ 特定ユーザー: admin 実装手順 # Step1: all-members チームの作成 # Organization Settings → Teams 「Create a team」から all-members チームを作成 新規メンバー追加時に画面から本チームを選択して追加 Step2: リポジトリアクセス権の設定 # リポジトリの Settings → Collaborators and teams all-members チームを追加し、適切な権限(Read/Write)を設定 Step3: 機密リポジトリの個別設定 # 機密リポジトリは all-members チームを追加しない 専用チーム(例: security-team )を作成し追加 または、特定ユーザーのみを直接追加 自動化でリポジトリへのチーム追加を効率化 # 前述の戦略を実装する際、課題となるのは 既存・新規リポジトリへの all-members チームの追加を漏れなく行うこと です。GitHub Actions を使ってこのプロセスを自動化できます。 実装の課題 # 新規リポジトリ作成後、 all-members チームの追加を忘れることがある 既存リポジトリ数が多い場合、手動で一括追加するのは手間 リポジトリ管理者により追加漏れが生じる可能性 自動化の仕組み # GitHub API と GitHub CLI を使用し、以下の処理を自動実行します。 スクリプト例 #!/bin/bash set -e ORG="mamezou-tech" TEAM="all-members" DRY_RUN=${DRY_RUN:-false} if [ "$DRY_RUN" = "true" ]; then echo "🔍 DRY RUN MODE - No actual changes will be made" fi # 除外リポジトリの設定(機密リポジトリなど) EXCLUDE_REPOS=( "secret-repo-1" "confidential-data" ) # Organization のリポジトリ一覧を取得 repos=$(gh api --paginate /orgs/$ORG/repos --jq '.[].name') success_count=0 skip_count=0 fail_count=0 for repo in $repos; do # 除外リポジトリをスキップ if [[ " ${EXCLUDE_REPOS[@]} " =~ " ${repo} " ]]; then echo "⊘ Skipping: $repo (excluded)" ((skip_count++)) || true continue fi echo "Adding $TEAM to $ORG/$repo..." if [ "$DRY_RUN" = "true" ]; then echo "✓ (dry-run)" ((success_count++)) || true else if gh api --method PUT \ /orgs/$ORG/teams/$TEAM/repos/$ORG/$repo \ -f permission=push \ --silent; then echo "✓" ((success_count++)) || true else echo "✗ Failed" ((fail_count++)) fi fi done echo "" echo "================================" echo "Repository Sync Summary:" echo " Success: $success_count" echo " Skipped: $skip_count" echo " Failed: $fail_count" echo "================================" スクリプトのポイント EXCLUDE_REPOS : 除外リポジトリを明示的に管理(機密リポジトリはここに列挙) permission=push : 書き込み権限を付与( permission=pull なら読み込み権限のみ) DRY_RUN : true 時はシミュレーション実行 API 呼び出しで一括処理し、人手を削減 --> Information 最初 permission=push のところを Copilot くんが write にしていて、実行時にハマりました。GitHub API Copilot くんでも間違えるほど対称性がない部分があるので注意が必要です。 GitHub Actions ワークフロー # name: Sync all-members Team on: workflow_dispatch: schedule: - cron: '0 2 1 * *' # 毎月1日 2:00 UTC に実行 pull_request: paths: - '.github/workflows/sync-all-members-team.yml' - 'scripts/sync-all-members-repos.sh' jobs: sync-team: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Add all-members team to repositories run: bash scripts/sync-all-members-repos.sh env: GH_TOKEN: ${{ secrets.ORG_MEMBER_PAT }} DRY_RUN: ${{ github.event_name == 'pull_request' }} ワークフロー構成のポイント workflow_dispatch : 手動実行で即座に同期漏れをチェック可能 schedule : 定期実行(例:毎月1日)で同期漏れを自動検出 pull_request : スクリプト更新時に自動テスト実行 DRY_RUN : PR では true で変更をシミュレーション 必要なシークレット設定 ORG_MEMBER_PAT : Organization リポジトリ管理権限を持つ Personal Access Token スコープ: admin:org , repo など 運用のコツ # ✅ スクリプト更新時は PR から自動実行で確認してからマージ ✅ 除外リポジトリは明示的にコメント付きで管理 ✅ 定期実行により同期漏れを定期的に検出・修正 ✅ 手動実行(workflow_dispatch)でオンデマンド同期も可能 ✅ 新規リポジトリ作成直後の手動実行で即座にチーム追加可能 疎通確認 # うまく構築できているはずですが、アクセス制御が意図どおり機能しているかを確認するため、Organization メンバー(非限定メンバー)に実際に機密リポジトリのリンクを開いてもらい、挙動を確認しました。 --> Slack の会話 👦 kondoh 16:16 ちょっとお願いがあるんですが、このリポジトリを開いたらどうなるか教えてください。 https://github.com/mamezou-tech/[private-repo-for-restricted-team] 👧 nakamura 16:27 404になります。 👦 kondoh 16:28 ありがとうございます!機密性の高いリポジトリなので、404で大丈夫です。💯 ポイントと注意点 # 今回の構成には、以下のメリットと注意点があります。 メリット # ✅ Basic Permission を変更せずにセキュリティを向上 ✅ 新規メンバー追加時の作業が最小限で済む ✅ ほとんどのリポジトリへのアクセスが自動的に付与される ✅ GitHub Enterprise に比べて低コスト デメリット・注意事項 # ⚠️ 機密リポジトリの管理は手動作業 ⚠️ チーム構成の変更時にも対応が必要 ⚠️ 定期的なアクセス権の監査が必要 さいごに # GitHub Organization のアクセス権管理は、セキュリティと運用性のバランスが重要です。Enterprise プランがなくても、チーム機能を活用することで、ある程度の制御は可能です。 重要なのは、「全員がアクセスすべきリポジトリ」と「限定的にアクセスすべきリポジトリ」を明確に分類することです。この分類が甘いと、セキュリティリスクが発生したり、逆に運用が複雑になったりしますので注意が必要です。
はじめに # 本ページは「AIエージェントとシステムをつなぐMCP入門」の続編です。 今回は、プロンプトについて説明します。 MCPのプロンプトは、MCPクライアント向けにテンプレート化されたメッセージやワークフローを提供する機能です。 生成指示やツールの利用順序を定義したワークフローなど、テンプレート化して再利用したい場合に有効です。 本記事で掲載しているコードは こちら で公開しています。 --> シリーズ目次 連載:AIエージェントとシステムをつなぐMCP入門 イントロダクション stdio実装編 StreamableHTTPステートレス実装編 StreamableHTTPステートフル実装編 プロンプト編(本ページ) 今回使用するライブラリなど # npm@11.11.1 node@22.22.0 typescript@6.0.3 @modelcontextprotocol/sdk@1.29.0 zod@4.3.6 使用例 # 定形タスクのテンプレート化: コード生成やレビューなど利用頻度の高い指示をテンプレート化することで、クライアント側の指示作成を省略できます ペルソナやルールの適用: 役割や出力形式の制約をコンテキストとして適用します ワークフローのテンプレート化: ツールの利用順序などを定義して一連の流れをガイドします 実装サンプル # コードの全体は こちら をご覧ください。 定形タスクのテンプレート化 # 定形タスクであるレビュー指示をテンプレート化した実装例です。 最近は省略されがちですが、サンプルなので役割も明示しています。 argsSchema(引数スキーマ): プロンプトの変数部分を定義するもの messages: roleとcontentで構成されるメッセージ配列 role: メッセージのスピーカーを示す識別子で、user/assistantのいずれかを設定します user(ユーザー): 人間からの入力を表します。ここにAIへの指示(プロンプト本文)を記述します。 assistant(アシスタント): AIからの返答を表します。 --> プロンプトの指示だけではレスポンスの形式がブレてしまう場合 出力形式の「見本(Few-shot)」をあらかじめAI自身の返答として配置しておくことで、精度の高い結果を引き出せます。 content: メッセージ本体。サンプルはテキストのみですが、バイナリ(画像、音声、埋め込みリソース)も扱えます。 text(テキスト): プレーンテキストのメッセージで、自然言語のやりとりで一般的に使用されるタイプ image(画像): 視覚的な情報を含むメッセージ audio(音声): 音声情報を含むメッセージ resource(埋め込みリソース): サーバーのリソース(ドキュメント、コードサンプルなど)を会話に組み込む。 --> バイナリを扱う場合 Base64でエンコードし、適切なMIMEタイプの設定が必要です。 // プロンプト:コードレビュー指示をテンプレート化 server.registerPrompt( "code-review-prompt", { title: "code-review-prompt", description: "コードレビュー指示", // 引数スキーマの定義:MCP InspectorなどのMCPクライアントのUIで入力フォームとして表示され、サーバー側の関数に渡されます。 argsSchema: { specPath: z.string().describe("仕様書パス"), codePath: z.string().describe("対象コードパス"), }, }, async ({ specPath, codePath }) => { return { messages: [ // user: プロンプト本文 { role: "user", content: { type: "text", // 実務ではもっと細かい指示が必要になりますが、行数を抑えるため最小限に留めています text: [ "コードレビュー指示", "あなたはNode.js/TypeScriptのバックエンド開発スペシャリストです。", "* 優先観点: 仕様整合性、例外設計、認可制御、トランザクション制御、性能、保守性、セキュリティ。", "* 禁止: 推測で仕様補完しない。根拠のない断定をしない。", "* 指摘の重大度: High(必須)/Medium(基本的に対応)/Low(できるだけ対応)を付ける。", "* 指摘は「問題」「対象個所」「修正案」を1行で示す。", "* 出力は最大10件、重複指摘は統合する。", "* 改善コードが必要なら最小差分で提案する。", `* 仕様書パス: ${specPath}`, `* 対象コードパス: ${codePath}`, ].join("\n"), }, }, // assistant: 見本 { role: "assistant", content: { type: "text", text: ["(コードレビュー指摘の出力例)", "1. High: [問題] 仕様に記載のないAPIエンドポイントが存在する。 [対象個所] src/api/user.tsのgetUser関数。 [修正案] 不要なエンドポイントなら削除、仕様が古いなら更新して整合させる。", // omit ].join("\n")}, }, ], }; }, ); 動作確認(MCP Inspector) ワークフローのテンプレート化 # ツールの実行順序をテンプレート化した実装例です。 // プロンプト: 指示やワークフローをテンプレート化。 server.registerPrompt( "inventory-check-workflow", { title: "inventory-check-workflow", description: "在庫確認ワークフロー", argsSchema: { orderNo: z.string().describe("受注番号"), }, }, async ({ orderNo }) => { const prompt = [ "在庫確認ワークフロー", `1. tools/callでget-orderを実行してください。(引数 orderNo: "${orderNo}")`, "2. tools/callでcheck-attached-inventoryを実行してください。(引数: orderId: 1のレスポンスのorderId, quantity: 1のレスポンスのquantityの合計)", "3. 2のレスポンスのavailableがtrueなら「在庫引当済み」、falseなら「在庫不足」と返してください。", ].join("\n") return {messages: [{role: "user", content: {type: "text", text: prompt,}}]}; }, ); server.registerTool( "get-order", // omit: 今回は固定値を返すだけの実装にしています ); server.registerTool( "check-attached-inventory", // omit: 今回は固定値を返すだけの実装にしています ); 動作確認(MCP Inspector) プロンプトの利用(VS Code) 実際にプロンプトを使ってツールの実行を指示して動作を確認。 プロンプトで指示した順にツールを実行し、結果も指示した通りに返されました。 まとめ # プロンプトは、定形タスクを標準化したい場面で効果を発揮します。 プロンプト内でツールの呼び出し順序を指示し、同じサーバー内にそのツールを registerTool で用意しておくと、LLMが指示にしたがって自律的にツールを呼び出し、一連の作業を自動完結できます。 次編ではリソースを扱います。
はじめに # 本ページは「AIエージェントとシステムをつなぐMCP入門」の続編です。 今回は、StreamableHTTPで通信するMCPサーバーのステートフル実装について説明します。 ステートフル構成は、同じ利用者の連続操作を同一セッションとして扱いたい場合に有効です。 たとえば「ツールの呼び出し結果を次の呼び出しに引き継ぐ」「セッション単位で一時状態を保持する」「接続中の文脈を維持する」といった用途で使います。 本記事では、ステートレス実装との違いに焦点を当て、ステートフル構成で押さえるポイントを整理します。 掲載コードは こちら で公開しています。 --> シリーズ目次 連載:AIエージェントとシステムをつなぐMCP入門 イントロダクション stdio実装編 StreamableHTTPステートレス実装編 StreamableHTTPステートフル実装編(本ページ) 今回使用するライブラリなど # npm@11.11.1 node@22.22.0 typescript@6.0.3 @modelcontextprotocol/sdk@1.29.0 zod@4.3.6 ステートレスとの違い # まず、実装方針の差を先に整理します。 観点 ステートレス ステートフル サーバーおよびトランスポートのライフサイクル リクエストごとに生成・破棄 セッションごとの接続コンテキストとして生成し再利用 セッションID 基本は使わない sessionIdGenerator で各トランスポートに紐づくIDを決定して管理 接続処理 リクエストごと セッション初回リクエスト時に1回 終了処理 レスポンス後 SIGINTなどですべてのセッションをまとめて --> サーバー、トランスポート、セッションの関係 「1つのサーバーが、1つのトランスポートを使って、複数のセッションを管理する」形を想像しているとセッション管理の実装に困惑します。 今回使用したバージョンでは McpServer が同時に接続できる transport は1つで、 StreamableHTTPServerTransport も単一の sessionId しか保持できません。 そのため、今回のサンプルはセッションごとにサーバーとトランスポートを管理する形を採っています。 --> SIGINTとは SIGINT は「割り込みシグナル」です。 ターミナルで Ctrl + C を押したときにプロセスへ通知され、Node.jsでは process.on("SIGINT", ...) で終了前処理を実装できます。 今回は、サーバーで保持していた接続や状態の破棄に利用しています。 サーバーの実装 # 簡単にMCPサーバーを実装してステートレスとの差分を説明します。 コードの全体は こちら をご覧ください。 sequenceDiagram participant Client as MCPクライアント participant Server as MCPサーバー Client->>+Server: POST /mcp ※初回リクエスト Server->>Server: 新規セッション作成 Server->>Server: トランスポート接続 Server->>Server: ツール実行 Server-->>Client: 200 OK + MCP-Session-Id alt 2回目以降 Client->>+Server: POST /mcp ※MCP-Session-Idあり Server->>Server: 既存セッション取得(sessionId) Server->>Server: ツール実行 Server-->>Client: 200 OK end par プロセス終了時 Server->>Server: 全セッションをクローズ end 差分1: セッションコンテキストを保持する # ステートレスでは、リクエスト単位で都度生成していました。 ステートフルでは、セッションIDをキーにして接続コンテキストを保持します。 ここでの SessionContext は「MCPサーバープロセスそのもの」ではなく、SDKの接続モデルに合わせたセッション単位の実装コンテキストです。 type SessionContext = { server: McpServer; transport: StreamableHTTPServerTransport; }; const sessions = new Map<string, SessionContext>(); --> 実運用で状態を管理する場合 今回は簡潔さを優先してメモリで状態を管理しています。 実際に運用する場合は、メモリリーク防止や分散環境下の運用を考えるとNoSQLなどの外部ストアを使うことを検討したほうが安全です。 差分2: リクエストをセッション単位で振り分ける # 既存セッションIDがあれば対応するコンテキストを使い、なければ新しいセッションコンテキストを作成します。 sessionIdGenerator 名称から勘違いしてしまいがちですが、汎用的な採番戦略ではありません。 これはトランスポートに紐づくセッションIDを初期化時に決めるためのコールバックです。 MCP-Session-Id 初回のリクエストで振り出され、クライアントが受け取ります。 2回目以降のリクエストでは、 MCP-Session-Id ヘッダーとして付与して再利用します。 async function createSessionContext() { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), }); const server = createServer(() => transport.sessionId); await server.connect(refineTransport(server, transport)); return { server, transport }; } app.post("/mcp", async (req, res) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; let context: SessionContext | undefined; if (sessionId) { context = sessions.get(sessionId); } else { context = await createSessionContext(); } await context.transport.handleRequest(req, res, req.body); // initialize 後に transport 自身へ設定された sessionId をキーに保持する。 const issuedSessionId = context.transport.sessionId; if (issuedSessionId) { sessions.set(issuedSessionId, context); } }); 差分3: プロセス終了時に全セッションをまとめてクローズする # セッションごとにサーバーとトランスポートを保持しているため、終了時にすべてのセッションを明示的にクローズします。 process.on("SIGINT", async () => { for (const context of sessions.values()) { await context.transport.close(); await context.server.close(); } process.exit(0); }); --> 実運用におけるセッションの終了処理 今回のサンプルは、プロセス終了時に全セッションをまとめてクローズする形にしています。 実運用では、一定期間操作がないセッションを自動的に破棄したり、クライアントが明示的にセッションを終了できる仕組みを用意することも重要です。 セッション管理を確認 # セッションごとに値を保持する counter ツールを追加実装して、セッションごとに値が保持されていることを確認します。 確認には複数セッションで操作が必要なので、MCP InspectorとPostmanを併用します。 MCP Inspectorの実行結果 Postmanの実行結果 MCP Inspectorから3回、Postmanから2回ツールを実行した結果です。 図で確認できる通り、それぞれ別のセッションIDが振り出され、セッションごとにcounterでインクリメントする値が管理されていることが確認できました。 --> PostmanでMCPを追加する場所 ひさしぶりにPostmanを使い、MCPのCollection追加に迷ったので場所を掲載しておきます。 セッションが切り替わることを確認 # MCPサーバーを再起動し、セッションが切り替わることを確認します。 先ほどとは異なるセッションIDが割り振られ、countが1に戻っていることが確認できました。 まとめ # ステートフルは、同一セッション内で、リクエストをまたいだ状態を保持できます。 一方、ステートフルにしたことで、終了時のクローズやセッション管理など運用面の責務が増えます。ツールの呼び出しに順序を求めるような場合はステートフルにしたいところですが、単純さを優先するならステートレスにするというのが現実的な判断になると思います。
はじめに # 本ページは「AIエージェントとシステムをつなぐMCP入門」の続編です。 今回はStreamableHTTPで通信するMCPサーバー(ステートレス)の実装について説明します。 前回のstdio実装編はMCPクライアントがサブプロセスとして実行しローカルで完結する構成でした。StreamableHTTPはHTTP経由でMCPサーバーを公開し、複数のMCPクライアントから利用可能な構成です。 MCPサーバーがWebAPIを呼び出してMCPクライアントに最新データを参照させる用途に向いています。 また、ステートレスは、リクエストごとに処理が完結するため、ライフサイクルの管理が単純で扱いやすいのが特徴です。 文量が多くなったので、ステートレスとステートフルは分けて説明します。 本ページで掲載しているコードは こちら で公開しています。 --> シリーズ目次 連載:AIエージェントとシステムをつなぐMCP入門 イントロダクション stdio実装編 StreamableHTTPステートレス実装編(本ページ) 今回使用するライブラリなど # npm@11.11.1 node@22.22.0 typescript@6.0.3 @modelcontextprotocol/sdk@1.29.0 zod@4.3.6 簡単なサーバーを実装 # 簡単にMCPサーバーを実装して動作確認します。 サーバーの実装 # stdioでも触れましたが、基本的な要素(「サーバーインスタンスの生成」「ツールの登録」「起動処理」)は同じです。 stdioに比べて特色のある「起動処理」について説明します。 起動処理 受け付けるエンドポイントを定義 トランスポート設定、ツール登録およびレスポンス後処理などを定義 StreamableHTTPなので、 StreamableHTTPServerTransport のインスタンスをconnectの引数に設定します。 ステートレスとしているので、セッションIDの振り出しもありません。 app.post("/mcp", async (req, res) => { const server = createServer(); const transport = new StreamableHTTPServerTransport(); try { // 1: 下記コラム参照 await server.connect(refineTransport(server, transport)); await transport.handleRequest(req, res, req.body); } catch (error) { console.error("Error handling MCP request:", error); if (!res.headersSent) { res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }); } } finally { await transport.close(); await server.close(); } }); --> 1. 引数型に合わせるため、型アサーションで型を調整しています StreamableHTTPServerTransport は Transport を実装しています。 ただし、今回使用したバージョンでは、 Transport が定義している振る舞いと StreamableHTTPServerTransport の定義に差異があります。(oncloseはその一例) 該当箇所を部分的に掲載しています // transport.d.ts export interface Transport { onclose?: () => void; } // streamableHttp.d.ts export declare class StreamableHTTPServerTransport implements Transport { set onclose(handler: (() => void) | undefined); get onclose(): (() => void) | undefined; } server.connect は Transport を引数型としていますが構造が合わないため、型アサーションで調整しています。 型調整に使用しているコード export const refineTransport = (server: McpServer, transport: StreamableHTTPServerTransport) => { return transport as Parameters<typeof server.connect>[0]; }; 拒否するエンドポイントを定義 拒否したいエンドポイントは405などのレスポンスを定義します。 app.get("/mcp", (_req, res) => { res.writeHead(405).end(JSON.stringify({jsonrpc: "2.0", error: {code: -32000, message: "Method not allowed."}, id: null})); }); ポートにバインディング 指定したポートにバインディングします。 app.listen(MCP_PORT, (error?: Error) => { //omit }); 全コード index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { z } from "zod"; import { refineTransport } from "./transport.util.js"; import { ApplicationError } from "./application.error.js"; const PORT = Number(process.env.PORT ?? "3000"); const WEB_API_BASE_URL = process.env.WEB_API_BASE_URL ?? "http://localhost:3001"; const WEB_API_CALL_FAILED_MESSAGE = "WebAPI call failed"; function createServer() { // サーバーインスタンスの生成 const server = new McpServer({ name: "todo-mcp-stateless", version: "1.0.0", }); // ツールの登録 server.registerTool( "get_todo", { title: "get_todo", description: "Todoを1件取得する", inputSchema: { id: z.number().describe("TodoのID"), }, }, async ({ id }) => { const endpoint = `${WEB_API_BASE_URL}/todos/${id}`; try { const response = await fetch(endpoint, { method: "GET", headers: { "Content-Type": "application/json" }, signal: AbortSignal.timeout(10_000), }); // 登録を省略しているため、レスポンスの検証は省略しています。 const body = await response.json(); return { content: [{ type: "text", text: JSON.stringify(body) }] }; } catch (error) { const message = error instanceof ApplicationError ? error.message : WEB_API_CALL_FAILED_MESSAGE; throw new ApplicationError(message); } }, ); return server; } const app = createMcpExpressApp(); // 起動処理 async function boot() { // POSTリクエストを受け付けるエンドポイント app.post("/mcp", async (req, res) => { const server = createServer(); const transport = new StreamableHTTPServerTransport(); try { await server.connect(refineTransport(server, transport)); await transport.handleRequest(req, res, req.body); } catch (error) { console.error("Error handling MCP request:", error); if (!res.headersSent) { res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }); } } finally { await transport.close(); await server.close(); } }); // GETリクエストを抑止 app.get("/mcp", (_req, res) => { res.writeHead(405).end(JSON.stringify({jsonrpc: "2.0", error: {code: -32000, message: "Method not allowed."}, id: null})); }); // DELETEリクエストを抑止 app.delete("/mcp", (_req, res) => { res.writeHead(405).end(JSON.stringify({jsonrpc: "2.0", error: {code: -32000, message: "Method not allowed."}, id: null})); }); // ポートにバインド app.listen(PORT, (error?: Error) => { if (error) { console.error("Failed to start stateless server:", error); process.exit(1); } console.error(`Stateless MCP endpoint: http://localhost:${PORT}/mcp`); }); } await boot(); サーバーの動作確認 # MCP Inspectorを使って、同じ動作を確認します。 --> 例外がスローされて終了した場合の表示 接続先のAPIサーバーを止めてツールを実行して、表示を確認 まとめ # StreamableHTTPのステートレス実装では、リクエストごとにMcpServerとTransportを生成し、処理後にクローズすることで、シンプルなライフサイクルを保てます。 MCP SDKはマイナーバージョン更新でも型定義や挙動差分の影響を受けることがあるため、依存バージョンは固定し、更新時は検証手順を用意して段階的に確認するのが安全です。 本編では最小構成でステートレスの流れを確認しました。APIのCRUDを網羅したサンプル実装も こちら に公開しているので、必要に応じてご参照ください。 次編ではステートフル構成との違い(セッション管理とサーバーライフサイクル)を扱います。
はじめに # 本ページは「AIエージェントとシステムをつなぐMCP入門」の続編です。 今回はstdioで通信するMCPサーバーの実装について説明します。標準入出力(stdin/stdout)を利用したMCPサーバーの構築手順と、stdio特有の注意点について見ていきます。 本ページで掲載しているコードは こちら で公開しています。 --> シリーズ目次 連載:AIエージェントとシステムをつなぐMCP入門 イントロダクション stdio実装編(本ページ) StreamableHTTPステートレス実装編 今回使用するライブラリなど # npm@11.11.1 node@22.22.0 typescript@6.0.3 @modelcontextprotocol/sdk@1.29.0 zod@4.3.6 簡単なサーバーを実装 # 簡単にMCPサーバーを実装して動作確認します。 サーバーの実装 # サーバーインスタンスの生成 サーバー名やバージョンを設定し、MCPサーバーのインスタンスを生成します。 ツールの登録 MCPサーバーにツール(公開する振る舞い)を登録します。 ここで登録したツールをMCPクライアントから呼び出せます。 ツールには、下記のような内容を実装します。 ツール名 入力スキーマ: ツールが受け取るデータの構造 出力スキーマ: ツールが返却するデータの構造。構造化したデータを返却したい場合のみ定義する。 リクエストハンドラー: ツールの内部処理。contentの返却は必須。 起動処理 MCPサーバーの起動処理。 今回はstdioなので、 StdioServerTransport のインスタンスをconnectの引数に設定します。 index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // サーバーのインスタンス化 const server = new McpServer({ name: "hello-world-server", version: "1.0.0", }); // ツールの登録 server.registerTool( "hello", { title: "hello, world!", inputSchema: { name: z.string().describe("メッセージに追加する名前") }, // 入力スキーマの定義 outputSchema: { message: z.string().describe("メッセージ") }, // 出力スキーマの定義 }, async ({ name }) => { return { content: [{ type: "text", text: `Hello, ${name}!` }], // デフォルトのレスポンス structuredContent: { message: `Hello, ${name}!` }, // 出力スキーマに基づくレスポンス }; }, ); // 起動処理 async function boot() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP Hello World Server (Modern) running on stdio"); // 標準出力にログを出力するとエラーになるため、`console.error`を使用しています } try { await boot(); } catch (error) { console.error("Fatal error:", error); process.exit(1); } サーバーの動作確認 # コンソールで確認 コンソール上でサーバーを起動して、動作を確認します。 起動 サーバーのビルドと起動 npx tsc node dist/index.js ツール一覧取得 tools/list を実行して一覧を取得。 ツール一覧取得と結果 // 標準入出力に下記を入力 {"jsonrpc":"2.0", "id":"1", "method":"tools/list", "params":{}} // ツール一覧が表示されました ※レスポンスは一部成形しています {"result":{"tools":[ { "name":"hello", "title":"hello, world!", "inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"type":"string","description":"メッセージに追加する名前"}},"required":["name"]}, "execution":{"taskSupport":"forbidden"}, "outputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"message":{"type":"string","description":"メッセージ"}},"required":["message"],"additionalProperties":false} } ]},"jsonrpc":"2.0","id":"1"} ツール呼び出し tools/call で hello を実行。 ツールの呼び出しと結果 // 標準入出力に下記を入力 {"jsonrpc":"2.0", "id":"1", "method": "tools/call", "params": { "name": "hello", "arguments": { "name": "MCP" }}} // ツールの実行結果が表示されました ※レスポンスは一部成形しています {"result":{ "content":[{"type":"text","text":"Hello, MCP!"}], "structuredContent":{"message":"Hello, MCP!"}}, "jsonrpc":"2.0","id":"1"} MCP Inspectorで確認 MCP Inspectorを使って、同じ動作を確認します。 デバッグログを出力したらどうなるのか検証 # 通信に使われる標準出力へJSON形式ではない文字列を出力した場合の挙動を検証してみます。 通常、標準エラー出力はプロトコル本体とは分離されるため、併せて挙動を確認します。 ツールを追加実装 # 標準出力(stdout)、標準エラー(stderr)にログを出力するツールを追加実装します。 index.ts server.registerTool( `output_log`, { title: 'output_log' }, async () => { console.log('debug log'); // to stdout console.info('info log'); // to stdout console.warn('warn log'); // to stderr console.error('error log'); // to stderr return { content: [{ type: "text", text: 'output log tool' }] }; }, ); 追加したツールの動作確認 # 追加したツールを実行してみます。 基本的にstdioは標準出力をJSON-RPCメッセージ専用、ログは標準エラー出力へ分離して動作します。 標準出力にJSON形式ではない文字列が混入したタイミングで、パースエラーになりました。 標準エラー出力にも同様の出力していますが、分離されているためエラーは発生しません。(標準出力を汚さないことが前提) MCP Inspectorを起動しているコンソール Received POST message for sessionId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx1xx Error from MCP server: SyntaxError: Unexpected token 'd', "debug log" is not valid JSON at JSON.parse (<anonymous>) at deserializeMessage (file:///C:/Users/~/shared/stdio.js:26:44) at ReadBuffer.readMessage (file:///C:/Users/~/shared/stdio.js:19:16) at StdioClientTransport.processReadBuffer (file:///C:/Users/~/client/stdio.js:126:50) at Socket.<anonymous> (file:///C:/Users/~/client/stdio.js:92:22) at Socket.emit (node:events:519:28) at addChunk (node:internal/streams/readable:561:12) at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) at Readable.push (node:internal/streams/readable:392:5) at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) Error from MCP server: SyntaxError: Unexpected token 'i', "info log" is not valid JSON at JSON.parse (<anonymous>) at deserializeMessage (file:///C:/Users/~/shared/stdio.js:26:44) at ReadBuffer.readMessage (file:///C:/Users/~/shared/stdio.js:19:16) at StdioClientTransport.processReadBuffer (file:///C:/Users/~/client/stdio.js:126:50) at Socket.<anonymous> (file:///C:/Users/~/client/stdio.js:92:22) at Socket.emit (node:events:519:28) at addChunk (node:internal/streams/readable:561:12) at readableAddChunkPushByteMode (node:internal/streams/readable:512:3) at Readable.push (node:internal/streams/readable:392:5) at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) --> MCP Inspectorの表示 上記の通り、コンソール上はパースエラーになりました。 今回使用したInspectorは後続の正しいJSONメッセージを拾ってくれたため、UI上は成功に見えます。 まとめ # stdioでは標準出力をJSON-RPCメッセージ専用にし、ログは標準エラー出力へ分離する。 標準出力に非JSON文字列が混入すると、クライアント側でパースエラーが発生しやすい。
はじめに # アジャイルグループの石田です。 先日、3月24日に開催された 豆寄席「スクラムマスターのAI活用を考える 〜透明性・検査・適応 三本柱を強化する実践アプローチ〜」 に登壇しました。第50回という節目のイベントに想定以上の多くの方にご参加いただき、誠にありがとうございました。改めて、スクラムマスターとAIという組み合わせの注目度の高さを感じました。 発表では、スクラムマスターがAIをどう活用できるかをメインテーマにお話ししましたが、その中でAIを使ったプレゼンテーション作成についても触れました。今回はその内容をより詳しくご紹介します。 プレゼン作成に使えるテンプレートリポジトリを GitHubで公開 しました。細かい使い方はリポジトリのREADMEをご参照ください。この記事では「AIでプレゼンを作ることのメリット」と「テンプレートの特徴」を中心にお伝えします。 AIでプレゼンを作るとはどういうことか # 従来のプレゼン作成の課題 # プレゼン資料の作成は、意外と時間がかかります。大まかな流れを整理すると、次のようなステップを踏むことが多いのではないでしょうか。 伝えたいことを整理し、構成を考える 各スライドの内容を起こす PowerPoint や Keynote でレイアウト・デザインを整える 見直し・修正を繰り返す このうち、特に 「1. 構成を考える」 と 「3. デザインを整える」 に時間を取られがちです。構成はゼロから考えると手が止まりやすく、デザインは凝り始めるとキリがありません。 AIが変えること # 生成AIを活用すると、上記の課題を次のように解消できます。 構成案の生成 :「○○について発表したい」という要件を渡すだけで、章立てやアウトラインを提案してもらえます スライドテキストのドラフト生成 :構成が決まれば、各スライドの本文もAIに任せることができます 仕様書・要件からの変換 :仕様書や議事録など既存のドキュメントをインプットとして与え、プレゼン向けに要約・再構成させることも可能です さらに重要なポイントとして、文字生成を得意とするAIはMarkdownと相性がよく、スライドをMarkdownで書くことでコンテンツ生成・修正を自然な会話の延長で依頼できます。PowerPoint のようにバイナリファイルを直接操作する必要がなく、差分管理もしやすくなります。 テンプレートリポジトリの紹介 # mamezou-presentation は、 Marp + GitHub Actions + GitHub Pages を組み合わせた豆蔵向けのプレゼンテンプレートです。MIT Licenseで公開しているため、社外の方も自由に利用・改変いただけます。 リポジトリの主な構成 # mamezou-presentation/ ├── presentation/ │ ├── 01_intro.md # スライド(数字順に結合される) │ ├── 02_main.md │ └── ... ├── images/ │ └── title.svg # タイトルスライド用画像 ├── mamezou-theme.css # 豆蔵ブランド テーマ(紫系) └── .github/workflows/ # 自動ビルド・デプロイ設定 スライドは presentation/NN_name.md の形式で分割して管理し、数字順に結合されます。1ファイルに全スライドを詰め込む必要がないため、セクションごとにファイルを分けて管理しやすくなっています。 テンプレートの特徴 # Push するだけで公開される # このテンプレート最大の特長は、 mainブランチにプッシュするだけでHTML/PDFが自動生成され、GitHub Pagesに公開される ことです。 「ビルドコマンドどうやるんだっけ」「PDFに書き出すにはどうすればいい」といった手順を覚える必要がありません。Markdownを書いてpushするだけで、発表用の成果物が自動的に用意されます。 git add presentation/01_intro.md git commit -m "スライド追加" git push origin main # → GitHub Actions が起動し、HTML/PDF を自動生成・公開 VS Code + Marp 拡張でリアルタイムプレビュー # ローカル開発では、VS Code の Marp for VS Code 拡張を使うことで、Markdownを編集しながらスライドをリアルタイムにプレビューできます。「書く → 確認 → 修正」のサイクルが素早く回せます。 AIワークフローとの統合(GitHub Spec Kit) # このテンプレートは、 GitHub Spec Kit を介したAIワークフローに対応しています。Gemini CLI や Claude Code と連携し、次のコマンドでスライド作成をAI主導で進められます。 コマンド 内容 /speckit.specify 発表内容の仕様(何を伝えるか)をAIと一緒に整理する /speckit.plan 仕様をもとにスライド構成案を生成する /speckit.tasks 各スライドの作成タスクに分解する 「何を話すか」を仕様として書き出せば、AIがスライドの骨格を提案してくれます。人間はレビューと肉付けに集中できるため、質の高いプレゼンをより短時間で仕上げられます。 また、Spec Kit の Constitution (規約・制限) を活用することで、AIが生成するテキストのトーンやスタイルを統一できます。Constitutionとしてルールを定義しておくことで、複数のスライドにわたって一貫した文体が保たれます。たとえばこのテンプレートでは次のようなルールを定義しています。 スライド本文の口調は「だ・である」調(常体)で統一する(導入部は「です・ます」調も可) 強調には鉤括弧(「」)を避け、HTMLの <strong> タグを使用する 図表の自動変換(Mermaid → PNG) # Mermaid形式( .mmd )のファイルをリポジトリに置くと、GitHub Actions が自動的にPNGへ変換し、スライドに埋め込めるようになります。 graph LR A[仕様を書く] --> B[AIが構成提案] B --> C[Markdownで執筆] C --> D[Push] D --> E[自動ビルド・公開] テキストベースで図を管理できるため、バージョン管理との相性も抜群です。 プレゼンターモード・タイマー機能 # 発表本番を支援する機能も備えています。 プレゼンターモード : ブラウザで p キーを押すと、スピーカーノート付きのプレゼンターモードに切り替わります カウントダウンタイマー : 発表時間を管理するタイマーアプリをAIを活用して作成しており、アジェンダとともにカスタマイズ可能です JavaScriptによる拡張 : プレゼン資料はHTMLベースのため、JavaScriptを組み込むことで各章へのショートカットを作成するなど、自由度の高いカスタマイズが可能です AIでプレゼンを作るメリットまとめ # ここまでの内容を整理すると、AIを活用したプレゼン作成には次のメリットがあります。 メリット 内容 スピード 構成案やドラフトをAIが生成するため、立ち上がりが速い 品質 抜け漏れの少ない構成をAIが提案してくれる 一貫性 テンプレートにより毎回同じクオリティのデザインが保たれる 再現性 Markdownなのでバージョン管理・差分確認が容易 集中力 デザインではなく「何を伝えるか」に集中できる 特に「集中できる」という点は体感として大きく、AIとテンプレートによってデザインの迷いがなくなると、コンテンツの質に意識を向けやすくなります。 おわりに # 今回は豆寄席での発表をきっかけに、AIを使ったプレゼンテーション作成の取り組みをご紹介しました。 テンプレートリポジトリはMIT Licenseで公開していますので、豆蔵社外の方も自由にご利用・ご改変いただけます。 リポジトリ: mamezou-ishida/mamezou-presentation 「試してみたい」「こんな機能があると嬉しい」といったフィードバックや質問は、GitHubのIssueにてお気軽にどうぞ。AIとMarkdownでのプレゼン作成、ぜひ一度お試しください。
はじめに # 本シリーズでは、MCP(Model Context Protocol)の基本から実装まで段階を分けて解説します。 「AIエージェントに社内システムや外部APIの知識を与えたい」という方へ向けた内容になります。 今回はMCPそのものについて説明します。 今後、トランスポート(stdio, Streamable HTTP)ごとの実装、MCPの自動生成などへの展開を予定しています。 --> シリーズ目次 連載:AIエージェントとシステムをつなぐMCP入門 イントロダクション(本ページ) stdio実装編 StreamableHTTPステートレス実装編 なぜMCPが必要なのか # AIエージェント(Claude、GPT-4、Geminiなど)は、膨大な知識を持っていますが「今この瞬間のデータ」や「社内システムの情報」には直接アクセスできません。 たとえば「最新の受注状況」や「在庫数」をAIに聞いても、学習データに含まれていなければ答えられません。 こうした「知識の壁」を越えるための仕組みがMCPです。 MCPを使うことで、AIエージェントが社内外のシステムやデータベースと安全に連携できます。 併せて、認証や認可(アクセス制御)が自身の方式に統合できるように検討するのが重要な課題です。 MCPとは # AIエージェントが外部サービスと通信するための仕様で、2024/11にAnthropic社によって初版がリリースされました。( 公式サイト ) MCPを使用することで、AIエージェントは外部サービスの機能を効果的に利用できます。 MCPサーバーは、AIエージェントが外部ツールやデータソースと通信するため、MCPプロトコルを実装したサーバーを指します。 MCPクライアントは、AIエージェントなどのMCPサーバー利用者を指します。 graph LR Agent[AIエージェント] MCP[MCPサーバー] API1[受注/社内サービス] API2[天気API/社外] DB[社内DB] Agent -- "MCP/HTTP(JSON-RPC)" --> MCP MCP -- "REST/HTTP" --> API1 MCP -- "外部API" --> API2 MCP -- "SQL" --> DB ※MCPは「AIエージェントと社内外サービスをつなぐハブ」のような役割を果たします。 項目 MCP プロトコル JSON-RPC 2.0 over stdio または HTTP/SSE(Streamable HTTP) データ形式 JSON (JSON-RPC 2.0) エンドポイント 統一エンドポイント( /mcp のみ) 操作 JSON-RPCメソッド( tools/list , tools/call ) エラー JSON-RPC Error(code, message) --> SSE(Server-Sent Events)とは HTTP接続を維持したまま、サーバーからイベントを逐次送信する仕組み。( text/event-stream ) MCPでは、ツールの呼び出し結果やストリームレスポンスを段階的に返す用途で利用されます。 MCPサーバーの主な役割 # MCPサーバーが担う主な役割は下記の通りです。 BFFのMCPクライアント版のような役割と言えます。 プロトコル変換(MCP⇔REST) 認証・認可 流量制限 監査ロギング ルーティング エラーハンドリング レスポンスの変換、合成 MCPサーバーによる効果 # AIエージェントは、学習データに含まれていない最新データや外部データは活用できません。 MCPサーバーで外部ツールやデータソースにアクセスできるツールを公開することで、リアルタイムデータにもとづいて返答できるようになります。 sequenceDiagram actor User actor Agent as AIエージェント participant MCP as MCPサーバー participant API as 受注サービス note over User, Agent: MCPなし User->>+Agent: 受注番号O001の状況を教えて Agent-->>-User: 受注番号「O001」の状況を確認する手段がありません。担当の受注管理部門にお問い合わせください。 note over User, Agent: MCPあり User->>+Agent: 受注番号O001の状況を教えて。調べる際はget_orderを使って。 Agent->>+MCP: 受注取得ツール呼び出し<br>get_order(order_id="O001") MCP->>+API: 受注取得APIリクエスト<br>GET /orders/O001 API-->>-MCP: 受注を返却<br>{ "order_id": "O001", "status": "shipped", ... } MCP-->>-Agent: 受注を返却<br>{ "jsonrpc": "2.0", "result": "{ "order_id": "O001", "status": "shipped", ... }", "id": 1 } Agent-->>-User: 受注O001の状況は発送済みです MCPの活用例 AIチャットボットが「受注番号O001の出荷状況を教えて」と聞かれたとき、MCP経由で社内の受注管理システムに問い合わせて最新情報を返す 社内FAQボットが、MCPを通じて人事システムや勤怠データベースから必要な情報を取得し、社員の質問に答える 外部API(天気、為替レートなど)と連携し、AIがリアルタイムなデータをもとにアドバイスを行う トランスポート(通信方式)の種類 # MCPのトランスポートには stdio と Streamable HTTP があります。 stdio(標準入出力) # MCPクライアント(AIエージェントなど)がMCPサーバーをサブプロセスとして起動し、ローカル環境で通信する方式です。 通信 データ形式: 改行区切りのJSON-RPCメッセージ。改行を許容するため、メッセージに改行を含めることはできません。 データ送受信: 標準入出力(stdin/stdout)を利用します。 セッションの終了: クライアントが入力ストリームを閉じるか、プロセスを終了する。 用途 個人のPC上で動くAIエージェントにローカルリソース(ファイルやツール)を操作させる。 Streamable HTTP # MCPサーバーは独立したプロセスとして稼働し、単一のHTTPエンドポイントで複数のMCPクライアントからの接続を受け付ける通信方式です。 通信 POSTとSSEの組み合わせ: クライアントからのリクエストはPOSTで送信し、サーバーからのレスポンスや通知はSSEを利用してストリーミング配信します。 非同期・準双方向通信: GETを使って独立した受信ストリームを開いておくことで、サーバーから任意のタイミングで自発的に通知を送ることが可能です。 回復と状態管理: ネットワークが切断されても Last-Event-ID を用いて途中から再接続できる機能や、 MCP-Session-Id ヘッダーを使ってセッション管理する機能が備わっています。 用途 リモートサーバーでの利用: ネットワーク越しに配置されたサーバーの機能(クラウド上のデータベースや外部APIなど)を利用する場合に適しています。 複数クライアントからの利用: 1つのMCPサーバーを複数のMCPクライアントから利用したい場合、Webサービスのような構成で活躍します。 --> `MCP-Session-Id` クライアントとサーバー間のやり取り(セッション)を管理するIDのこと。 Streamable HTTPにおいて、サーバーは初期化時にセッションID(UUIDやJWTなど)を発行します。 クライアントは、これ以降のすべてのHTTPリクエスト(POSTやGET)に MCP-Session-Id ヘッダーとしてこの値を含める必要があります。 --> Last-Event-ID セッション内で開かれたSSEストリーム通信の連続性を保証するIDのこと。 サーバーからクライアントへメッセージを送るSSEストリーム上の各イベントにはIDが付与されます。 ストリームが切断された際、クライアントは再接続時に Last-Event-ID ヘッダーを用いて「最後に受け取ったイベントID」をサーバーに伝えることで中断したところからストリームを再開できます。 今後の展開 # 本シリーズでは、MCPの基本から実践的な実装方法を解説します。 トランスポートごとの違い、自動生成ツールの活用など、段階的に解説していく予定です。 「AIエージェントとシステムの連携をもっと身近に」するための内容を目指します。 次回もぜひご期待ください。
はじめに # 前回の記事 では、Google Cloud認定の鬼門であった「Professional Security Operations Engineer(PSOE)」を突破し、念願の「Google Cloud認定全冠」を達成したことをご報告しました。 そして今回、AWS側で唯一未取得となっていた最新認定「 AWS Certified Generative AI Developer - Professional (AIP-C01) 」が2026年4月14日に正式リリースされたため、さっそく受験してきました。結果は無事 合格 ! これにより、目標として掲げていた 「AWS&Google CloudのW全冠達成」 をついに果たすことができました。 本記事では、過去のベータ試験での不合格体験を振り返りつつ、リベンジに向けてどのような対策を行ったのか、そして正式リリース版試験の所感についてまとめます。 --> Information 秘密保持契約(NDA)があるため、詳細な試験内容については触れることができませんので、ご了承ください。 また記載の情報は2026年4月時点のものです。 試験の概要:AWS Certified Generative AI Developer - Professional # AWS Certified Generative AI Developer - Professional (AIP-C01) は、GenAI デベロッパーの役割を担う方を対象としたプロフェッショナルレベルの認定です。基盤モデル(FM)をアプリケーションとビジネスワークフローに効果的に統合し、AWSテクノロジーを使用してGenAIソリューションを本番環境に実装する実践的な知識が検証されます。 主な出題分野と重みは以下の通りです。 コンテンツ分野 1: 基盤モデルの統合、データ管理、コンプライアンス (31%) コンテンツ分野 2: 実装と統合 (26%) コンテンツ分野 3: AI の安全性、セキュリティ、ガバナンス (20%) コンテンツ分野 4: GenAIアプリケーションの運用効率と最適化 (12%) コンテンツ分野 5: テスト、検証、トラブルシューティング (11%) 試験は75問(うち10問は採点対象外)で構成されており、100~1,000の換算スコアで750点以上が合格ラインとなります。 ベータ試験での敗因と課題 # 実は2025年12月に、この認定のベータ試験を受験していましたが、残念ながら 不合格 という悔しい思いをしていました。 当時の振り返りでも触れましたが、主な敗因は以下の点にありました。 長文問題による時間不足 : プロフェッショナルレベル特有の非常に長い問題文と選択肢に圧倒されました。複数のサービスを組み合わせた複雑なシナリオから、要件とアーキテクチャ図を素早く頭の中で組み立てるのに時間がかかり、ギリギリまで時間を使ってしまいました。 本番環境運用を考慮した実践的知識の不足 : Amazon Bedrockを中心とした構成において、スケーラビリティ、セキュリティ、コスト最適化などのベストプラクティスに基づいた判断が求められましたが、複数の正解に見える選択肢から最適なものを絞り切れませんでした。 リベンジに向けた対策 # 前回の反省を踏まえ、正式版でのリベンジに向けて以下の対策を重点的に行いました。 試験ガイドの再確認と分野の深掘り : 試験ガイドに記載されている、ベクトルストアやRAGの設計、プロンプトエンジニアリングの適用、エージェンティックAIソリューションの実装など、出題の核となる技術要素を再確認しました。 関連サービスのベストプラクティスの学習 : Amazon Bedrockを使った実際のアプリケーション構築をイメージし、連携するAWS Lambda、Amazon API Gateway、Amazon CloudFrontといったサービス群の詳細な理解を深めました。また、Amazon BedrockガードレールによるAIの安全性とガバナンスや、Amazon Comprehend等を用いたデータ保護についても重点的に学習しました。 長文シナリオへの対応 : 長い文章から即座に「要件は何か」「制約は何か(コスト優先か、レイテンシー優先かなど)」を整理し、構成図をイメージするトレーニングを意識的に行いました。 実際の試験で問われた技術テーマの傾向 # 今回の試験を通して、単なるGenerative AIの機能知識だけでなく、エンタープライズの「本番運用」に直結する実践的な知見が深く問われていると感じました。NDAの範囲内で、特に重要だと感じたテーマをいくつか共有します。 厳格なコンプライアンス要件を伴うユースケース 金融、医療、研究機関といった機密性の高いデータを扱うシナリオが頻繁に登場しました。単にAIを使うだけでなく、「データレジデンシー(特定のリージョン・域内からデータを持ち出さない原則)をどう担保するか」といった法令準拠の観点が求められます。 データ保護と安全性の確保 Amazon Comprehendなどを活用したPII(個人情報)の秘匿化や、Amazon Bedrockのガードレール機能による「暴力・ヘイト等の不適切な表現のフィルタリング」など、AIを安全に提供するためのガードメカニズムの実装手法は必須知識です。 効率的な運用とガバナンス 複数の部署でプラットフォームを共有する際の「部署ごとの権限・コスト分割(IAMやタグ付けの戦略)」をどうアーキテクチャに組み込むかが問われました。また、状況に応じて「アプリケーション側のコードを修正することなく、バックエンドの基盤モデル(FM)を柔軟に切り替える構成」なども重要なテーマでした。 プロンプト管理と稼働中の自動評価・アラート Amazon Bedrock プロンプト管理等を用いた「プロンプトのバージョン管理と本番適用の承認フロー」に加え、稼働中のAIの応答結果をビジネス指標に基づいて継続的かつ自動的に評価し、「品質の低下を検知してアラートを発報する(CloudWatchを通じた異常検知)」といった、リリース後の品質監視運用に対する知識も深く問われました。 マルチアカウント環境での権限管理と閉域網接続 企業での利用を想定し、「アプリケーションアカウント」と「データレイクアカウント」が分かれているような複数アカウント構成において、どのようにセキュアなクロスアカウント権限設定を行うか。また、情報をインターネットに出さずにVPCエンドポイント(AWS PrivateLink)等を経由して閉域網内でAI系APIを呼び出すネットワークアーキテクチャも頻繁に問われました。 生成AI特有のMLOps(LLMOps) 基盤モデルの微調整(ファインチューニング)やRAGの運用において、「データの追加 → モデルの再学習・評価 → テスト → デプロイ」という一連のパイプラインを自動化するMLOps的な知識も一部で求められました。 生成AIのUX向上とフロントエンド連携 アプリケーションのユーザー体験(UX)を向上させるための実践的なアーキテクチャとして、Amplify AI Kit を用いた連携や、Amazon API Gateway 経由でAIの生成結果をストリーミング処理してフロントエンドに返すユースケースなども問われました。 Human-in-the-loop(HITL)ワークフローの設計 AIによる完全自動化だけでなく、AWS Step Functions などを用いて「基盤モデル(FM)の応答内容を最終的に人間がチェック・承認する(Human-in-the-loop)」といった、実運用における安全性を担保するワークフローの設計手法も出題されました。 目的に応じたベクトルデータベースの最適な選定 RAG(検索拡張生成)の構築において、Amazon OpenSearch Serverless を一律に選ぶのではなく、既存データの特性やユースケースに合わせて Amazon Aurora PostgreSQL(pgvector)や Amazon DocumentDB などを最適なベクトルデータベースとして見極める設計力が問われました。 処理時間に合わせたアーキテクチャ選定 推論やデータ処理にかかる「予想処理時間」を事前におおまかに見極め、短時間で終わるなら AWS Lambda、長時間の非同期フローになるなら AWS Step Functions や Amazon EKS、自律的な構成を重視するなら AIエージェント(Agents for Amazon Bedrock等)を活用するといったように、要件に合わせたリソースの正確な使い分けも重要なテーマでした。 コスト・レイテンシ最適化のためのキャッシュ戦略 応答速度の向上やAPIコスト削減の要件において、「推論結果を Amazon ElastiCache や Amazon DynamoDB 等に自前で保存して再利用する」といった従来のアーキテクチャよりも、基盤モデルレベルの組み込み機能である「プロンプトキャッシュ(Prompt Caching)」を素直に活用する構成が、よりスマートな最適解として問われる傾向が見受けられました。 目的に応じた監視手法(監査とメトリクス)の使い分け 監視の要件において、「誰がいつAPIを利用したかといった操作履歴を追う(AWS CloudTrailによる監査機能)」のか、「LLMの応答遅延増やトークン消費量などのパフォーマンス推移を追う(Amazon CloudWatchによるメトリクス監視やアラート)」のかといったように、目的に応じて適切なAWSサービスとアーキテクチャを正確に使い分ける運用知識も重要でした。 制限(クォータ)超過へのレジリエンスとモニタリング AIサービスの時間当たりの利用上限(スロットリング)に達してしまった場合の対策として、「エクスポネンシャルバックオフ(指数関数的バックオフ)」を用いた再試行の実装や、制限到達をAmazon CloudWatch等で適切にモニタリングしアラートを通知する仕組みの構築といった、システムの可用性を高める構成も問われました。 クロスリージョン推論による負荷分散 前述の制限超過対策にも関連して、特定リージョンのトラフィック急増に伴う一時的なスループット低下を回避するため、Amazon Bedrockの「クロスリージョン推論(Cross-region inference)」プロファイルを利用して複数のリージョンへ自動的に負荷分散(ルーティング)を行うアプローチも、非常に実践的な最適化手法として出題されました。 プロフェッショナルレベルの試験では、1つの長文シナリオの中にこれらの観点が3〜4つ同時に絡み合って出題されます。そのため、紛らわしい選択肢の中から最適な設計を選ぶためには、「その課題において達成すべき最優先の要件は何か」を素早く洗い出し、各選択肢がそれらの要件を満たしているかを短時間で正確に判断する総合力が必要不可欠となります。 受験の所感(ついにW全冠達成!) # リベンジとなる今回の受験では、長文問題に対するタイムマネジメントも改善され、ベータ試験の時よりも落ち着いてシナリオを読み解くことができました。学習を重ねたことで、コストとパフォーマンスのトレードオフや、責任あるAIの実装、運用効率の最適化といった観点から自信を持って選択肢を絞れるようになっていたのが大きかったです。 そして結果は合格。無事にAWS側の未取得認定を埋めることができました! 直前の4月11日にGoogle Cloud認定の全冠を達成したばかりだったので、立て続けの朗報に感無量です。 【余談】AIP合格に伴う下位認定の自動更新について # AWS認定には、 公式の再認定プログラム にも記載されている通り、「上位資格に合格すると、関連する下位資格も自動的に更新される」という嬉しい仕様があります。 今回、AIPに合格した場合に具体的にどのアソシエイト・基礎レベル認定が更新対象になるのか、これまで公式ページに明記されておらず不明だったのですが(※本記事公開時点)、今回実際に合格してようやくその詳細が判明しました! 結果として、AIPに合格すると DEA(Data Engineer)、MLA(Machine Learning Engineer)、AIF(AI Practitioner)、CLF(Cloud Practitioner) の4つの認定が一気に自動更新されました。 それぞれの階層(Professional、Associate、Foundational)における、更新の相関関係を整理して図解すると以下のようになります。矢印の元となる認定に合格すると、矢印の先の認定が更新されます。 flowchart TD classDef pro fill:#fce4ec,stroke:#c2185b,stroke-width:2px; classDef assoc fill:#e3f2fd,stroke:#1565c0,stroke-width:2px; classDef found fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px; %% Nodes SAP["SAP<br>(Solutions Architect Pro)"]:::pro DOP["DOP<br>(DevOps Pro)"]:::pro AIP["AIP<br>(GenAI Pro)"]:::pro SAA["SAA<br>(Solutions Architect Assoc)"]:::assoc DVA["DVA<br>(Developer)"]:::assoc SOA["SOA<br>(SysOps)"]:::assoc DEA["DEA<br>(Data Engineer)"]:::assoc MLA["MLA<br>(Machine Learning)"]:::assoc CLF["CLF<br>(Cloud Practitioner)"]:::found AIF["AIF<br>(AI Practitioner)"]:::found %% Subgraphs subgraph Professional SAP DOP AIP end subgraph Associate SAA DVA SOA DEA MLA end subgraph Foundational CLF AIF end %% 更新関係 (AIP) AIP --> DEA AIP --> MLA %% 更新関係 (SAP) SAP --> SAA %% 更新関係 (DOP) DOP --> DVA DOP --> SOA %% 更新関係 (Associate) DEA --> CLF MLA --> CLF MLA --> AIF SAA --> CLF SOA --> CLF DVA --> CLF AIPは生成AIとデータ処理の両領域に深く関わるため、DEAやMLAといった複数の直近に追加されたアソシエイト資格の更新を一度にカバーしてくれる構成になっています。特にDEA、MLA、AIFの3つは2024年に新設されて順次取得した認定であり、来年がちょうど更新時期だったため、今回の合格でまとめて更新されたのは地味に助かりました。複数の資格を維持する立場としては、この更新仕様は非常にありがたい設計ですね。 おわりに # AWS認定全13冠、そしてGoogle Cloud認定全14冠 という、長きにわたる私のクラウド認定挑戦記は、これにて一つの大きな到達点を迎えました。 今回、AWSとGoogle Cloudという主要なクラウドプラットフォームを両方とも体系的に学べたことは、今後マルチクラウド環境での設計や提案の実務に挑むにあたって、間違いなく強力な土台になってくれると信じています。まずは今回の経験や知識の整理も兼ねて、 「AWSとGoogle Cloudのアーキテクチャや思想の違いを対比する記事」 なども積極的に執筆し、皆さんに知見を共有していく予定です。 また、単なる認定保持にとどまらず、こうしたデベロッパーサイトへの継続的な記事の寄稿や情報発信を通して、今後は 「Top Engineer(トップエンジニア)」 としての選出も視野に入れながら、クラウド技術の普及とコミュニティの発展に一層貢献していきたいと考えています。 (短期間にこれだけの認定を一気に取得してしまったため、「数年後に一斉に大量の更新ラッシュが押し寄せてくる恐怖」からは目を逸らしつつ……) これからクラウド認定に挑戦される方の参考になれば幸いです!
はじめに # これまで豆蔵デベロッパーサイトでAWS認定に関する記事を執筆し、直近の2026年3月26日には「 Google Cloud認定全制覇!……まであと一歩で跳ね返されたリアルな軌跡 」という記事を公開しました。 前回の記事では、約2か月の怒涛の受験ラッシュで全冠を目指したものの、最後の1つ「Professional Security Operations Engineer(PSOE)」で不合格となり、無敗記録がストップしてしまった経緯をお伝えしました。 しかしこの度、2026年4月11日にPSOEのリベンジ受験に挑み、無事に合格することができました!これにより、目標であった 「Google Cloud認定全冠」をついに達成 しました。 本記事では、鬼門だったPSOEの突破劇や、前回触れられなかった「再受験ポリシー(Retake Policy)」、PearsonVUE移行に伴う変化、そして今後の目標についてまとめます。 --> Information 秘密保持契約(NDA)があるため、詳細な試験内容については触れることができませんので、ご了承ください。 また記載の情報は2026年4月時点のものです。 待望の「PSOE日本語版」リリースと勉強法の見直し # 前回1月31日に受験した際、PSOEは英語のみの配信でした。セキュリティ特有の特殊なツールが扱われる上に、トラブルシューティングという複雑なシチュエーションを英語で読み解こうとしたことが大きな敗因でした。 そのため、公式の学習プラットフォームである「Google Cloud Skills Boost」を活用し、ハンズオンを通じて「まずは日本語で概念と対処法を理解する」という方針に切り替えて対策を進めていました。 そんな中、朗報が飛び込んできました。なんと 2026年3月31日にPSOEの日本語版がリリース されたのです。PearsonVUEへの移行のタイミングで日本語化される気配はないと前回記事でこぼしていましたが、まさに絶好のタイミングでの対応でした。Skills Boostでの実践的なハンズオン学習に加えて、日本語で問題文の細かなニュアンスを読み取れるようになったことが、今回の合格を大きく後押ししてくれました。 余談ですが、私が2月に英語で苦戦しながら合格した「Professional Cloud Database Engineer(PCDBE)」についても、直近の 4月7日に日本語版がリリース されました。これにより、これからGoogle Cloud認定を目指す方にとっては、 現時点で有効なすべての認定が日本語で受験できる 素晴らしい環境が整ったと言えます! Google Cloud認定の「再受験ポリシー」について # 前回の記事では触れませんでしたが、Google Cloud認定には厳格な 再受験ポリシー(Retake Policy) が設定されています。短期間で合格を目指す上で、この待機期間は大きなネックになり得るため注意が必要です。 1回目の不合格:次回受験までに 14日間 待つ必要がある 2回目の不合格:次回受験までに 60日間 待つ必要がある 3回目の不合格:次回受験までに 365日間 待つ必要がある 私の場合、1月31日に不合格となったため、最短であれば2月14日以降に再受験が可能でした。しかし、演習に使っていたUdemy教材の一部削除による学習方針の転換や、PearsonVUEへの移行タイミングでの日本語版リリースへの淡い期待なども相まって、結果的にしっかりと準備期間を設けて4月11日に受験する形を取りました。特に2回連続で落ちると「60日間(約2ヶ月)」も足止めを食らってしまうため、確実な準備をしてから臨むことが重要です。 見据える先は「Google Cloud Partner All Certification Holders」 # 今回、私がGoogle Cloud認定の全冠にこだわった理由の一つに、 「 Google Cloud Partner All Certification Holders 」 という表彰プログラムの存在があります。これは、Google Cloudの認定資格をすべて保持している方を発表するもので、Google Cloud Japanよりパートナー企業に対して公式に感謝の意が伝えられるプログラムです。直近では2025年11月にも多数の受賞者が発表されています。 将来的に自社がGoogle Cloudのパートナーとして登録されるような機会があれば、その暁にはこの「All Certification Holders」として正式に名を連ねることを密かな目標として見据えています。 最終的な受験履歴(全冠達成!) # これまでの受験履歴の最終版は以下の通りです。 No 受験日 認定名称 略称 受験言語 結果 1 2025-09-15 Associate Cloud Engineer ACE 日本語 合格 2 2025-12-27 Professional Cloud Architect PCA 日本語 合格 3 2026-01-12 Professional Cloud Developer PCD 日本語 合格 4 2026-01-17 Professional Cloud DevOps Engineer PCDOE 日本語 合格 5 2026-01-25 Professional Cloud Network Engineer PCNE 日本語 合格 6 2026-01-29 Cloud Digital Leader CDL 日本語 合格 7 2026-01-31 Professional Cloud Security Engineer PCSE 日本語 合格 8 2026-01-31 Professional Security Operations Engineer PSOE 英語 不合格 9 2026-02-05 Associate Google Workspace Administrator AGWA 日本語 合格 10 2026-02-08 Associate Data Practitioner ADP 日本語 合格 11 2026-02-08 Generative AI Leader GAIL 日本語 合格 12 2026-02-11 Professional Data Engineer PDE 日本語 合格 13 2026-02-13 Professional Cloud Database Engineer PCDBE 英語 合格 14 2026-02-21 Professional Machine Learning Engineer PMLE 日本語 合格 15 2026-04-11 Professional Security Operations Engineer PSOE 日本語 合格 これで、現在提供されているGoogle Cloud認定をすべて網羅することができました。同じテストセンターに何度も通い詰めたため、スタッフさんにも無事に全冠達成の報告ができて感無量です。 PearsonVUE移行に伴う変化と注意点 # 今回の受験から、試験配信プロバイダがKryterionからPearsonVUEへと移行されましたが、実際に受験してみていくつか大きく変わった点や気づきがありましたので、併せて共有しておきます。 1. テストセンターの選択肢増加と、日時変更の柔軟性アップ 私が受験した地域では、Kryterion時代は特定のテストセンター一択でしたが、PearsonVUEに変わったことで複数のテストセンターが選択できるようになりました。また、Kryterionでは試験日時の変更が72時間前でロックされていましたが(手数料を払えば変更可能)、PearsonVUEでは24時間前まで変更可能になったのは受験者にとってかなりの朗報です。 2. 受験システムが使いやすくなった(取り消し線機能) PearsonVUEの試験システムでは、選択肢のテキスト上で「右クリック」をすると、その選択肢に 取り消し線(ストライクスルー)を引くことができます 。明らかに間違っている選択肢を視覚的に除外しながら進められるため、消去法での解答が非常にやりやすくなりました。地味ですがかなり強力で便利な機能です。 3. 合格後の各種通知スピードが格段に早くなった 個人的にとても嬉しかったのが、合格後の正式な通知が送られてくるスピードです。これまでは、Credlyのバッジ発行通知が「翌日6時頃」、Google Cloudからの合格確定通知が「翌々日5時頃」といったスケジュール感でしたが、これが大幅に早まっていました。 Credlyバッジ発行通知:翌日の6時頃 ➔ 受験の約1時間後 Google Cloud合格確定通知:翌々日の5時頃 ➔ 翌日の4時頃 すぐに正式な合格を実感できるのは、受験直後の不安な時間を減らしてくれるため非常にありがたい改善です。 4. 【要注意】全冠達成後はPearsonVUEへの入り口が消滅する これは全冠達成(すべての試験に合格)という特殊な状況ゆえの落とし穴なのですが、未取得の受験可能な試験がなくなってしまうと、Google Cloudの認定ポータルからPearsonVUEのダッシュボードへ遷移するためのアクセス口がなくなってしまいます。 これの何が問題かというと、PearsonVUEマイページにログインできなくなるため、 「領収書の再発行(メールの再送)」の手続きができなくなってしまう 点です。特に「試験を申し込んだ後に受験日を変更した」場合、最初に受領した申し込み時点の領収書と実際の受験日が異なってしまいます。後から正しい受験日が記載された領収書を取り直そうと思ってもポータルに入れないため、会社の経費精算などで困る事態になりかねません。 なお、日本のインボイス制度に対応した「適格請求書発行事業者登録番号」入りの領収書については、PearsonVUE側から別途発行が可能な仕組みにはなっています。 ちなみに、同じくPearsonVUE経由で配信されているAWS認定のポータルでは、既に取得済みの試験からでもPearsonVUEのダッシュボードへ遷移できるためこの問題は起きません。単純に私がGoogle Cloudのポータルで別のアクセス手順を見落としているだけかもしれませんが、もし現在の挙動が仕様であるなら、ぜひGoogle Cloud側にもAWSと同様の改善を期待したいポイントです。 5. 【小ネタ】認定証や有効期限の日付が一時的に米国時間(前日)になる PearsonVUEへの移行による影響か、試験直後に発行される認定証の取得日や有効期限が、システム上で一時的に米国時間ベース(前日付)で登録されることがあるようです。今回、私は日本時間の「4月11日」に受験して合格したのですが、試験当日に発行された認定証は「4月10日」と1日ズレて記載されていました。ただ、翌日になって改めてシステムを確認したところ、正しい日本時間の日付(4月11日)に自動で修正されていましたので、もし皆さんの中で受験直後に日付がズレてしまっている方がいても、慌てずに翌日まで待ってみてください。 おわりに # AWS認定に続き、Google Cloud認定の全冠も達成することができました。 実は、一つだけ未取得となっているAWSの最新認定「Generative AI Developer - Professional (AIP-C01)」が4月14日に正式リリースされるため、4月16日にそちらの受験も予定しています。この認定は以前ベータ版で受験して悔しくも不合格となっているため、私にとってはAWS側での 「リベンジマッチ」 となります。無事に合格できれば晴れて「AWS&Google CloudのW全冠達成」となりますので、その際はまた別の記事でご報告できればと思います。 今回、両方の主要クラウドを深く体系的に学んだことで、マルチクラウド環境での設計・提案力が一段と引き上がったと感じています。今後は、この経験を活かして 「AWSとGoogle Cloudのアーキテクチャや思想の違いを対比する記事」 なども積極的に執筆していく予定です。 一方で、短期間にこれだけの認定を一気に取得してしまったため、 「2年後に一斉に大量の更新ラッシュが押し寄せてくる恐怖」 に早くも震えていたりもします。実は昨年、AWS認定でも同様の更新ラッシュを経験したのですが、AWSには「上位資格を更新すれば関連する下位資格も自動更新される」という素晴らしい制度がありました。Google Cloudには残念ながらその制度がないため、 下位資格も含めすべての認定を一つずつ再受験して更新 しなければなりません。一応は「再認定用の簡易試験(Recertification)」が用意されるという救済措置はあるものの、全冠を維持する道のりを想像するだけで震えが止まりません(笑)。 そして、AWS、Google Cloudと来て、ふと頭をよぎるのは…… 「さて、Azureはどうしよう?」 という問題です。実はAzureに関しても入門資格である「AZ-900」だけは4年前のAWS認定挑戦中に既に取得済みであるため、3大クラウド制覇の道がうっすらと見えてしまっている気もしますが、先述の恐怖の更新ラッシュを考えると少し足踏みしてしまうところです。 ひとまずは、私のGoogle Cloud認定全冠挑戦記はこれにて完結です。これから認定に挑戦される方の参考になれば幸いです!
はじめに # こんにちは。私の参加しているプロジェクトでは、毎朝の朝会で各自のコンディション(ニコニコ、ちょいニコ、普通、ちょいしんど、しんどめ、地獄など)を共有する「ニコニコカレンダー」を活用しています。 先日、私がその日の気分を「ちょいしんど」、理由に「寝不足」と入力していたことから、メンバーに「副鼻腔炎が悪化しましたか?」と心配されました。そこで「最近は副鼻腔炎は落ち着いているんですが、実は子供のネット制限のために徹夜で家庭内ネットワークをいじってまして……」と雑談をしたところ、「興味深いからぜひ記事にしてほしい!」と思わぬリクエストをいただきました。 これまでクラウド資格関連(全冠記など)の記事ばかり書いていたので、「たまには資格以外の息抜き記事も書いてみようかな」と思い立ち、今回筆をとりました。 当サイトの他の記事と違い、少し古い技術の組み合わせにはなりますが、ぜひ息抜きがてら読んでみていただけると嬉しいです。 とはいえ、今回扱っているような泥臭いネットワークの基礎知識(サブネット、DHCP、DNSの仕組みなど)を実際に手を動かして理解しておくことは、クラウド環境を設計・構築する際や、クラウド系の資格を受験する際にも非常に有用な土台となることを、 クラウド認定”ほぼ”全冠の私が保証します! ITエンジニアの皆様におかれましては、お子さんのインターネットやゲームとの付き合い方に悩まれている方も多いのではないでしょうか。我が家で発生した問題と、エンジニアの親として実施した「本気のネットワーク対策」についてご紹介します。 (※なお、本記事に記載しているIPアドレスやサブネットなどのネットワーク情報は、セキュリティの観点から実際の数値から変更してぼかして記載しています) 我が家のネットワーク事情と事の発端 # まずは前提となる我が家の状況(住環境や家族構成、働き方)は以下の通りです。 家族構成、働き方 子供 : 中学生と小学生の2人 妻 : たまに自宅からテレワークをする日がある 私(筆者) : 常に自宅の仕事部屋からフルテレワーク 住環境 : 4DK 周辺住宅からの電波干渉が激しく、2.4GHz帯(11g等)は使い物にならない状態 そのため、障害物に弱いが高速な5GHz帯(11a/ac/ax等)をメインにする必要がある また、夫婦のテレワーク(Web会議等)のため、家中のどこでも「安定したWi-Fi環境」が必須要件 結果として、それぞれの部屋までの物理的な距離や壁を網羅するため、Wi-Fiルータが合計3台必要 子供たちにはそれぞれ学校からタブレットが支給されており、小学生は週に一回、宿題用で持ち帰るのですが、中学生のタブレットは常に自宅に置いている状態です。 ちなみに、子供用のスマホはAndroidを持たせており、そちらはGoogleの「ファミリーリンク」機能を使って利用可能な時間をしっかりと制限しています。 しかしある日、子供が盲点となっていた「学校タブレット」をベッドに持ち込み、夜中にゲーム等をしていることが発覚しました。寝不足で朝起きられなくなり、部活も休みがちになるという悪循環に陥ってしまいました。 (なお、学校タブレットはGoogle Workspaceで管理されているのですが、「Google Workspace側でインストールするアプリや閲覧可能なサイトを制限してほしいものだが。。。」というのが親としての本音です)。 これはいけないと思い、スマホ以外の端末に対しても、最初は以下のような対策を実施しました。 Wifiルータのキッズコントロール機能 を活用し、利用可能時間を制限 プロバイダ提供ルータと家庭用のBuffalo製ルータ(2台)を用いた計3台運用 によるアクセスポイントの分離 中央ルータ(プロバイダ提供ルータ)と私の仕事部屋のルータは同じメインSSIDに設定 子供部屋のルータは別の「子供用SSID」に設定 ゲーム機、学校のタブレット、チャレンジタッチ(進研ゼミ)などの子供用端末は、すべて「子供用SSID」に接続させる なお、この時点ではネットワークは分割しておらず、すべて同じメインのネットワーク(例: 192.168.10.0/24 )内で運用していました。 イタチごっこの始まり # 初期対策で安心したのも束の間、平日の帰宅後にキッズコントロールで許可されている時間内は、勉強をせずにYouTubeを見たりゲームをしたりして過ごすようになってしまいました。 さらに悪いことに、いつの間にか子供が「メインのSSID」に接続しており、再びベッドへ端末を持ち込んでいるのを発見してしまったのです。 スマホはファミリーリンクで制限できていても、他の端末が抜け穴になっては意味がありません。寝不足で部活に行けない、勉強もできていない……。このまま子供の自制心に任せたままにするわけにはいかないと判断し、根本的なネットワーク改修を実施することにしました。 「DHCPの設定で、子供の端末にだけPi-holeのDNSを割り当てればいいのでは?」と思われるかもしれません。しかし、多くの家庭用ルータにはそこまで高度なDHCP機能はなく、また同じサブネット内にいると端末側でDNSを手動設定されるだけで簡単に突破されてしまいます。ネットワーク(サブネット)を物理的・論理的に完全に分けることで、逃げ場をなくす「境界での制御」を重視しました。 今回行った根本的な対策(ガチ構成) # ITエンジニアの親として、物理的・論理的に抜け穴を塞ぐためのネットワーク構成へと変更しました。 実は当初、賃貸物件に無償で付帯しているケーブルテレビ回線のルータがあり、子供用端末はそちらの別回線へ完全に追い出してしまう(物理的な回線分離)という強硬手段も考えました。しかし、無料回線ゆえに下り速度があまりにも遅く、本来の目的である学校の遠隔授業や調べ物に支障が出てしまっては本末転倒です。そのため、あくまでメインの高速回線を生かしつつ、論理的に安全なネットワークを構築する方向で設計しました。 なお、我が家の環境は プロバイダの提供ルータと、一般的な家庭用のBuffalo製ルータの組み合わせ です。設計を考えながら、何度「ああ、VLANさえ使えればどんなに楽だったか……!」と天を仰いだことかわかりません。もし企業向けの多機能ルータなどを使っていれば、VLANを活用してスマートに論理分割できたのですが、今回はあくまで「できるだけ自宅にあるものを組み合わせて一晩で完成させる」ことを裏テーマとし、手持ちの民生機だけで そのまま徹夜で泥臭く一気に組み上げました 。 --> Information 💡 補足: VLAN(仮想LAN)とは 物理的なLANケーブルの配線やルータの接続構成を変えずに、ネットワーク機器(スイッチやルータ)内部の設定だけで論理的にネットワークを安全に分割できる仕組みです。これがあれば、物理的なルータを複数台またがって繋ぎ、手作業で泥臭くサブネットを分離するような苦労は不要になります。 作業中、ふと「豆蔵入社以前に在籍していた会社での社内インフラ構築を思い出すな……」と深夜に我に返りつつも、実施した対策は以下の通りです。 MACアドレス制限の導入 中央のルータにMACアドレスのブラックリストを設定し、子供の端末がメインSSIDに直接繋がらないようにシャットアウトしました。 サブネットの隔離 子供用SSIDのルータを、メインサブネット配下に別のサブネット(例: 192.168.20.0/24 )として構築し、論理的にネットワークを隔離しました。なお、子供用サブネットをメインサブネットの下にぶら下げる構成(NAT)にしているため、メインサブネット側に置かれている家庭用プリンタなどへの通信は引き続き利用可能です。 Raspberry Piによる独自DNS(Pi-hole)の構築 特定のドメインをブラックリストで弾くこと自体は市販のルータの機能でも可能ですが、ルータの標準機能だけでは「有志がインターネット上で公開している膨大なブラックリストをそのまま継続的にインポートして使う」といった柔軟な運用が困難です。そこで、子供用のサブネット内にRaspberry Piを設置し、強力なドメインフィルタリング機能を持つ「Pi-hole」を利用して専用のDNSサーバーを構築しました。 (なぜ自宅にラズパイが転がっているのかは聞かないでください。徹夜で仕上げるために自宅の余り物を深夜にかき集めた結果、今回使用したのは少し古いRaspberry Pi 2Bです)。 DHCPによる独自DNSの強制 子供用ルータのDHCP設定を変更し、ラズパイのIPアドレスをDNSサーバーとして配布するようにしました。 特定ドメインの問い合わせ拒否と正常通信の転送(Upstream DNS) Pi-holeのフィルタリング設定で、ゲームや動画サイトに関するDNS問い合わせをすべて拒否(ブラックホール化)しました。一方で、ブロック対象外の正常な問い合わせ(学習用サイトなど)については、Pi-holeから上位のパブリックDNS(Google DNSの 8.8.8.8 や Cloudflareの 1.1.1.1 など)へ転送させることで、安全な通信のみを許可する構成にしています。 暗号化DNS(DoH / DoT)への対策 実は学校のタブレットがDNS over HTTPS(DoH)などの暗号化されたDNS通信を使用していたため、通常のポート53のフィルタリングだけでは抜け道になってしまいます。そこで、Cloudflareなどの主要なパブリックDNSサービスに対する通信自体も拒否する設定を追加しました。 --> Information 💡 補足: 親(管理者)泣かせの DoH (DNS over HTTPS) 従来のDNS通信(ポート53)は暗号化されていないため、経路上に置いたPi-hole等で「どこにアクセスしようとしているか」を簡単に監視・遮断できました。しかし近年はプライバシー保護の観点から、OSやブラウザがDNS通信をHTTPS(ポート443)の暗号化通信に丸めてしまう機能(DoH)をデフォルトで利用するケースが増えています。これを使われると通信の中身がただのHTTPS通信に見えるため、ローカルのDNSサーバーが意図せずスルー・回避されてしまいます。セキュリティ的には素晴らしい技術ですが、家庭内ネットワークの管理者としては非常に悩ましい壁となります。 以上を踏まえた、最終的な我が家のネットワーク構成図は以下のようになります。 flowchart TD WAN((Internet)) --- CenterRouter[プロバイダ提供ルータ<br>兼 中央ルータ] subgraph MainNetwork[メインネットワーク: 192.168.10.0/24] CenterRouter --- WorkRouter[仕事部屋ルータ] CenterRouter -.- MainSSID((メインSSID)) WorkRouter -.- MainSSID MainSSID --- ParentDevices[親・仕事用端末] MainSSID --- Printer[家庭用プリンタ] CenterRouter -.-x |MACアドレス制限で遮断| BlockedKids[子供用端末] end subgraph KidsNetwork[子供用ネットワーク: 192.168.20.0/24] CenterRouter --- KidsRouter[子供部屋ルータ] KidsRouter -.- KidsSSID((子供用SSID)) KidsSSID --- KidsDevices[子供用端末<br>タブレット・ゲーム機等] KidsRouter --- PiHole[Raspberry Pi 2B<br>Pi-hole] KidsRouter -.-> |DHCPで独自DNSを強制| PiHole PiHole -.-x |特定ドメインの名前解決拒否<br>DoHの問い合わせもブロック| KidsDevices end KidsDevices -.-> |NAT経由で印刷可能| Printer 今後の展望と親の葛藤 # これで現在のところ、子供の夜更かしネットサーフィンは完全に防ぐことができています。 親としては、本来であればこんなシステムでガチガチに管理するのではなく、できれば子供自身の自制に任せて成長を見守りたいというのが本音です。しかし現状では完全に自制に任せきりにすることができず、今回は苦渋の決断としてシステム的な制限を強めることになりました。 ただ、今回の構成も完璧ではありません。子供用サブネットから外部への標準的なDNS通信(UDP 53ポート)は通過させているため、もし今後、子供が知識を付けて、端末のネットワーク設定から自分でGoogle DNS( 8.8.8.8 や 8.8.4.4 )などを設定できるようになれば、この制限は突破されてしまいます。その場合は追加の検討が必要です。 ……とはいえ、もしそこまでのネットワークの仕組みを自力で理解し、制限を突破できるほどの知識を身につけたのであれば、ITエンジニアの親としては「それはそれで喜ばしいことかもしれない?」と、少し複雑な感情も抱いています。いつかはこうした制限をすべて外し、自分自身の力でコントロールできるようになってくれることを願うばかりです。 おわりに # 家庭内ネットワークも、要件(家族の働き方や子供の利用状況、リテラシー、手持ちの機材)に合わせてアーキテクチャを見直していく必要があると痛感しました。同じようなお悩みを持つ方の参考になれば幸いです。
新年度が始まりました。2026年1-3月のサマリーです。 記事数・執筆者数 # この3ヶ月で34本の記事が投稿され、記事数は876になりました。新たに2名が執筆デビューし、累計76名になりました。 連載 # SysML モデリング連載 # 複雑なシステムをモデリングするための新しい言語である SysML v2。この SysML v2モデルを作成・編集するためのグラフィカル・モデリングツール、SysON を紹介するシリーズ。 /blogs/2026/01/08/sysmlv2-tool-syson-intro/ 全6回の連載記事となっています。記事のインデックスは以下のリンクから参照してください。 無料のOSSツールSysONで始めるSysMLv2モデリング スクラムマスターのAI活用 # スクラムマスターの AI 活用に関する連載記事です。 /blogs/2025/12/04/scrum-ai-1/ 全3回です。記事のインデックスは以下のリンクから参照してください。 スクラムマスターのAI活用を考える 記事の筆者石田がこのテーマで「豆寄席」に登壇しました。 https://mamezou.connpass.com/event/386346/ テーマ別の記事 # フロントエンド # /blogs/2026/01/09/nuxt_supabase_auth/ /blogs/2026/02/18/next_storybook_1/ /blogs/2026/02/18/next_storybook_2/ /blogs/2026/03/25/dev-lib-efficiently-using-yalc/ ロボット # /robotics/yaskawa/yaskawa-hses-agent-skills/ /robotics/solar-panel-clean-robot/dji-drone-psdk-introduction/ /robotics/solar-panel-clean-robot/dji-drone-psdk-custom-widget/ /robotics/3dgs/3dgs-beginners-guide/ /robotics/industrial-network/cs-ads-communication/ /robotics/bizen/bizen_sw_architecture_with_grpc/ /robotics/industrial-network/pc_sdk-avoiding-pitfall/ /robotics/lerobot/lerobot_introduction/ AI エージェント # /blogs/2026/01/30/copilot-agent-setting/ /blogs/2026/01/30/kiro_cli_ralph/ /blogs/2026/03/17/use-mcp-on-vscode/ 認定資格 # /blogs/2026/01/12/golden-kubestronaut/ /blogs/2026/03/26/google_cloud_all_certified/ AWS # /blogs/2026/03/06/fms-security-policy-article/ /blogs/2026/03/24/aws-ssm-gitbash-encoding/ /blogs/2026/03/30/quicksight_cicd_part1/ 個人開発 # /blogs/2026/03/26/build-your-own-text-editor/ さいごに # 以上、2025年度第4四半期のサマリーでした。ロボットの記事が非常に多かった印象です。 よかったら フィード の購読、 X や Bluesky でのフォローもお願いします。 Facebook でも本サイトの注目記事をはじめ豆蔵に関するイベントを紹介しています。 note にも時々本サイト関連の記事が掲載されています。
要件定義入門①:要件定義とは何か ~現場での役割と全体像~ # 1. はじめに # 要件定義という言葉はよく耳にするものの、 「実際に何をしているのか分からない」と感じる方が多いのではないでしょうか。 特に現場に入りたての頃は、実装やテストといった開発工程に関わることが多く、 要件定義については「最初にやる工程らしい」という程度の理解に留まりがちです。 そのため、 「要件定義って何をしているのだろう」 と疑問に思う場面も少なくありません。 本記事では、要件定義の役割について、 具体例を交えながら分かりやすく整理していきます。 2. 要件定義とは何か # 要件定義とは、「システムが満たすべき条件(要件)を明確にし、関係者間で合意する工程」です。 ここでいう「要件」とは、 「システムが何を実現すべきか」という条件を指します。 なぜ要件定義が必要なのか # システム開発は、顧客の「やりたいこと(要望)」から始まります。 しかし、この要望を具体化せずに開発側へそのまま委ねてしまうと、 完成したものが「思っていたものと違う」という事態が発生しやすくなります。 さらに、「予算」「人員」「技術的な制約」といった要因により、そもそも実現が難しいケースもあります。 要件定義でやっていること # そのため、要件定義では以下のようなやり取りを繰り返します。 顧客の要望を整理する 実現可能性を開発側で検討する 実現方法や制約を踏まえて提案する 顧客が提案内容を確認・調整する このプロセスを繰り返し、 最終的に「これを作る」という合意内容を定義します。 要件定義は、 「要望」と「現実(制約)」のすり合わせを行い、 実現可能な形に落とし込む工程とも言えます。 また、顧客がすべてを明確にできるとは限らないため、 開発側が質問や提案を通じて要件を具体化していくことが重要です。 3. 設計とはどう違うのか # 要件定義とよく混同されるのが「設計」です。 両者の違いは、以下のように整理できます。 要件定義:何を作るか(What) 設計:どのように作るか(How) 具体例 # 例:ユーザーがログインできる機能。 要件定義 ユーザーがIDとパスワードでログインできる 設計 認証方式はJWTを使用する パスワードはハッシュ化して保存する 要件定義の段階で設計の話をしてしまうと、 後から要件変更があった際に柔軟に対応できなくなります。 そのため、「What」と「How」を意識的に分けることが重要です。 4. 基本的な流れ # 要件定義は、一般的に以下のような流れで進みます。 「要望」と「要求」は似ていますが、本記事では以下のように定義します。 要望:顧客の主観的なやりたいこと 要求:具体化されたニーズ 5. 具体例 # 例:勤怠管理システム。 本節では、要件定義の流れを具体的なケースに当てはめて整理します。 ■ 要望(やりたいこと) # 顧客の初期的な要望は、抽象的な状態で提示されることが一般的です。 (例) 「社員の勤怠を管理したい」 ※この段階では、業務フローや必要な機能は明確になっていません。 ■ 要求(具体化されたニーズ) # 開発側がヒアリングを通じて、要望を具体的な要求へと整理します。 (例) 「出勤・退勤時刻を記録したい」 「スマートフォンから打刻できるようにしたい」 「月ごとの勤務時間および残業時間を集計したい」 ■ 整理・検討(要求の整理・実現可能性の確認) # 要求を整理し、開発側が以下のような観点で検討します。 業務要件として妥当か(業務フローとの整合性) 技術的に実現可能か コスト・スケジュールに収まるか 必要に応じて、要求の取捨選択や優先度付けも行います。 ■ 提案(実現案の提示) # 検討結果を踏まえ、開発側から具体的な実現案を提示します。 (例) 「Webアプリケーションとして提供する」 → PCおよびスマートフォンのブラウザから利用可能とする 「打刻方法はブラウザベースとする」 → 専用アプリ開発は行わない(開発コスト・期間を考慮) 「既存の人事システムとの連携は行わない」 → 初期リリースでは単体運用とし、将来的な拡張を検討する また、必要に応じて以下のような観点も提示します。 機能の優先度(必須 / 任意) スコープ(今回対応範囲) トレードオフ(コスト・品質・スピード) ■ 検討(顧客側確認) # 提案内容について、顧客側で妥当性を確認します。 業務上問題なく利用できるか 必要な機能が満たされているか 不足・過剰な要件がないか 必要に応じて、再度要望の修正・追加をします。 ■ 合意(要件確定) # 顧客と開発側で内容に合意し、 「何をどこまで実現するか」を確定します。 この合意内容が、以降の設計・実装の基準となる「要件」となります。 6. まとめ # 要件定義は「何を作るか(What)」を決める工程 要望と制約をすり合わせ、実現可能な形にする 開発側が主体的に要件を具体化していくことが重要 要件定義は単なる前工程ではなく、 プロジェクト全体の品質を左右する重要な工程です。 次回は、実際に要件をどのように洗い出し、整理していくのかを解説します。
こんなひとにおすすめ # マルチモーダルAI、フィジカルAI、模倣学習、強化学習などロボティクス分野のAIに興味はあるが、どこから手をつけたらいいのか分からない方 実機のロボットの価格が高くて試せないと感じている方 手を動かして学びたいが、低コストで始めたい方 はじめに # 本記事では、オープンソースプロジェクトである LeRobot とオープンソースのアームロボットである SO-101 を題材に、マルチモーダル AI に関する技術紹介と環境構築の手順を解説します。 最終的なゴールは、片方のアームを動かすともう片方のアームが同じ動作を追従する動作を再現することです(下記GIF参照)。 用語の説明 # マルチモーダルAI とは # 従来のロボティクス開発では、ロボット本体、カメラ、通信規格など複数の技術(モダリティ)が個別に動作し、出力されるデータ形式も異なるため、それらを統合して1つのシステムとして運用するのは容易ではありませんでした。 近年、Transformer 系モデルなどの進展により、画像・音声・テキスト・センサー値など複数モダリティを統合して扱う「マルチモーダルAI」の研究・実装が進んでおり、異なるデータ形式の扱いや統合が以前より容易になっています。 これに伴い、ロボティクス領域では前述の問題点の解消が期待され、マルチモーダルAIを活用した開発が増えつつあります。 --> Information 似たような用語として「フィジカルAI」が存在します。こちらはロボットや物理世界での学習・意思決定に焦点を当てる用語で、マルチモーダルAIと重なる部分が多いですが、本記事では区別せず「マルチモーダルAI」として扱います。 LeRobot とは # 公式リポジトリ: LeRobot LeRobot は Hugging Face が公開しているオープンソースのロボティクスライブラリで、実ロボット向けのモデル、データセット、ツールを提供しています。LeRobot を使うことで多様なロボットの制御やデータ収集、学習ワークフローの構築が容易になります。 本記事ではLeRobotのインストールと設定に焦点を当て、最終的にSO-101を動作させる手順を説明します。 [1] SO-101 とは # 公式リポジトリ: SO-101 SO101 Follower の画像。 実物のロボットに近しい構成となっている。 SO101 Leader の画像。 人間が操作しやすいようにグリップ部分がある。 SO-101 は RobotStudio と Hugging Face が共同で開発した、低コストのオープンソース・ロボットアームで、ロボティクス分野への参入ハードルを下げることを目的としています。 SO-101 は「Leader(先導者)」と「Follower(従者)」の2台のロボットアームで構成されます。一般的な利用では Leader をユーザーが手動で操作してデータ収集をし、Follower はその記録や学習済みモデルに基づいて同じ動作を再現することを想定しています。 本記事では SO-101 の入手方法 と、LeRobot で動かす際に必要な手順を解説します。 動作環境 # 本記事では環境構築の手順に重点を置くため環境要件は簡略化しますが、LeRobot の多くのチュートリアルが CLI 操作を前提としていることから OS は Linux または macOSを 推奨します。 本記事では整合性を図るために、Linux を前提としています。 --> GPU に関して 将来的に LeRobot と SO-101 で学習するのであれば、VRAM容量が 8GB 以上の GPU も必要となります。 --> あると便利なもの 本記事の手順を遂行するのに必ずしも必要ありませんが、以下の物があると便利です。 2口以上の電源タップ:2台のロボットアームへの電源供給に必要なため。 2口以上で電源供給可能な USB3.0 ハブ:2台のロボットアームをPCに接続するため。 タックシール:複数のモーターや部品を管理しやすくするため。 タックシールに関しては、ダイソーの タックシール キレイにはがせるタイプ 525枚 をおすすめします。こちらは SO-101 のほぼ全てのパーツの大きさに対応しており、下記画像のようにラベリングが可能になります。 タックシールを用いて、モーターをラベリングしている時の画像。 タックシールを用いて、モーターバスをラベリングしている時の画像。 環境構築方法 # こちらでは LeRobot と SO-101 を用いた環境構築の手順を解説します。 手順としては次のように進めますが… LeRobot の環境構築方法 SO-101 の環境構築 事前に SO-101 の印刷(もしくは購入) を確認いただき、SO-101 のパーツを揃えることをおすすめします。 LeRobot の環境構築方法 # LeRobot の環境構築方法は Hugging Face の Installation ページを参照しているため、重要な手順に絞って解説します。 0. 【任意】conda 系のパッケージがインストールされていない場合 # LeRobot は複数の Python パッケージを扱うため、Conda を用いて仮想環境を構築することを勧めている。そのため、Conda がインストールされていない場合は下記コマンドでインストールします。 wget &quot;https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh&quot; bash Miniforge3-$(uname)-$(uname -m).sh 1. 仮想環境構築 # Conda で仮想環境を構築する # 下記の lerobot は構築する環境の名前なので、お好きなものに変えても構いません # その場合、他の手順も同様に変更を加えてください conda create -y -n lerobot python=3.12 Conda の仮想環境をアクティブする conda activate lerobot ffmpeg を仮想環境にインストールする conda install ffmpeg -c conda-forge LeRobot の現在のバージョンでは、 ffmpeg のバージョン 8.x に対応していないので、前のコマンドでインストールされた ffmpeg のバージョンを確認します: ffmpeg -version このとき、 ffmpeg のバージョン 8.x になっていれば、下記コマンドで ffmpeg のバージョンをダウングレードします: conda install ffmpeg=7.1.1 -c conda-forge 2. LeRobot をインストールする # LeRobot はリポジトリのソース、もしくは PyPl からインストール出来ます。将来的に個人開発したい場合は、コードの編集が可能なソースからのインストールを推奨します。 リポジトリのソースからインストールする場合は下記コマンドを実行します。 git clone https://github.com/huggingface/lerobot.git cd lerobot # 編集可能モードで Conda 環境にインストールする pip install -e . PyPl からインストールする場合は下記コマンドを実行します。 pip install lerobot SO-101 の環境構築 # 1. SO-101 の印刷(もしくは購入) # SO-101 のハードウェア構成は 公式リポジトリ で公開されています。 リポジトリの3Dデータを使ってパーツを3Dプリントするか、リポジトリで案内されている 公認販売サイト からパーツキットを購入して、Follower と Leader の両アームを組み立てられます。 注意点として、公式リポジトリで入手できるのは主に外装・機構パーツで、サーボモーターなどの駆動部品は別途用意する必要があります。モーター購入時は公式リポジトリ内の Parts For Two Arms (Follower and Leader Setup): の記載を参照するか、公認販売元のキットを購入してください。 参考として、筆者の場合は公式リポジトリで紹介されていた秋月電子通商のサイトから、以下の2つのキットを購入しました。また、説明の整合性を図るために、これらのパーツを基準として手順を解説します。 [131169]SO-101 オープンソースロボットアームキット Pro版 [131222]SO-101 オープンソースロボットアームキット 3Dプリントパーツ 2. SO-101 のセットアップ # SO-101 のはセットアップ Hugging Face の SO-101 ページを参照しているため、重要な手順に絞って解説します。また、前述でも触れましたが、SO-101 は Leader と Follower の2台のロボットアームで構成されるため、一部の手順が異なる点には留意してください。 初めに下記のコマンドで SO-101 を動かすために必要な SDK をインストールします。 pip install -e &quot;.[feetech]&quot; 2.1. モーターの仕分け 参考リンク: Configure the motors 初めに 前のセクション で用意したモーターを Leader と Follower 用に分けます。Leader 側は複数種類のギア比を持つモーターで構成されるのに対し、Follower 側は同一仕様(1 / 345)のモーターで構成されるためです。 Leader の各関節に割り当てるモーターID ( 後述を参照 )とギア比は次の通りです。 Leader-Arm Axis Motor Gear Ratio Base / Shoulder Pan 1 1 / 191 Shoulder Lift 2 1 / 345 Elbow Flex 3 1 / 191 Wrist Flex 4 1 / 147 Wrist Roll 5 1 / 147 Gripper 6 1 / 147 モーターの種類は本体のラベル(シール)で判別できます。Leader 用モーターにはギア比が明記されていることが多く、Follower 用は同一仕様のためギア比の表記がないことがあります。 2.2. MotorBus(モーターバス)のセットアップ モーターの仕分けが終わりましたら、モーターバス(下記画像参照)の設定します。 <img width=400" src="https://akizukidenshi.com/img/goods/L/131540.jpg"/> モーターバスは複数のモーターをまとめて管理・通信するための機器です。 前セクション で分けた Leader 用の全モーターに対して1台、Follower 用の全モーターに対して1台のモーターバスを用意します。このとき、Leader と Follower のモーターを誤って混同すると後で修正が大変なので、タックシールなどで全モーターにラベルを貼り、どのモーターバスに対応させるかを明確にするといいです。 以降の手順では便宜上、次の略称を使います。 Leader用モーターバス:Leader_MB Follower用モーターバス:Follower_MB Leader_MB と Follower_MB の電源を入れ、PC に接続する。 各モーターバスの USB ポートを確認する。 両方のモーターバスを接続した状態で下記を実行する。 スクリプト実行中に指示が出たら、Leader_MB の USB ケーブルを抜いて Enter キーを押す。 lerobot-find-port 例)スクリプトの出力例: Finding all available ports for the MotorBus. ['/dev/ttyACM0', '/dev/ttyACM1'] Remove the usb cable from your MotorsBus and press Enter when done. #(対応する Leader_MB のケーブルを抜いて Enter を押す) The port of this MotorsBus is /dev/ttyACM0 Reconnect the USB cable. #(対応する Leader_MB のケーブルを差し直す) Reconnect the USB cable. が表示された時点で Leader_MB のケーブルを差し直してください。上記例では /dev/ttyACM0 が Leader_MB のポートとなるので、こちらを記録してください。 同様に Follower_MB の USB ケーブルを抜いて、Follower_MB のポートを確認・記録してください。上記例では /dev/ttyACM1 が Follower_MB のポート番号になりますが、環境によっては異なることには注意してください。 --> Linux のデバイス権限に関して Linuxではデバイスファイルのアクセス権が原因で認識できないことがあるため、必要に応じて以下を実行して権限を付与してください。下記例では ttyACM0 デバイスの権限を変更しておりますが、デバイス名は環境により変わることには注意してください。 sudo chmod 666 /dev/ttyACM0 2.3. モーターとモーターバスを連動させる 出荷時のモーター ID はすべて 1 に設定されているため、モーターバスと正しく通信・連動させるには各モーターに一意の ID を割り当てる必要があります。 まずは Leader_MB を設定するために、下記コマンドを実行してください。 lerobot-setup-motors \ --robot.type=so101_follower \ --robot.port=/dev/ttyACM0 # Leader_MB のポート番号 --robot.port= には MotorBus(モーターバス)のセットアップ で確認した Leader_MB のポートを入力します。確認しポートが /dev/ttyACM0 以外であれば、適切に変更してから実行してください。 PC と Leader_MB の通信が確率すると、下記メッセージが表示されます。 Connect the controller board to the '<関節名>' motor only and press enter. ここで表示される &lt;関節名&gt; は、これから ID を割り当てるモーターが担当する関節名(例: gripper )を示します。手順は次の通りです。 モーターの仕分け で用意したモーターとその章のテーブルと照合して、該当する種類のモーターを準備する(例:gripper → ギア比 1 / 147 のモーター) 該当モーターをモーターバスに接続する(モーター付属ケーブルを使う) ターミナルで Enter キーを押す ID 割り当てが正常に行われると、次のように表示されます。 '<関節名>' motor id set to <ID> 続けて他の Leader 用モーターにも同様に ID を設定してください。これら手順の一連の流れは下記の公式動画から確認できます。 Leader_MB の設定が終わったら、同じように Follower_MB の設定します。前述の通り、Follower 側のモーターは同一のギア比で構成されていることが多いので、モーターが担当する関節名を照合する必要はありません。ただ、設定した ID を混同しないよう、留意してください。 --> アドバイス 公式リポジトリの動画ではモーターを既にアームに取り付けた状態で ID を設定していますが、筆者は取り付け前に ID を設定することをおすすめします。Leader 側は複数のギア比の異なるモーターで構成され、取り付け済みだと種類判別が難しく、誤った位置に取り付けると分解・ID 再設定が必要になるためです。 事前にIDを振り、ラベル(タックシール等)で識別してからアームに組み付けるとトラブルを大幅に減らせます。 2.4. ロボットアームの組み立て SO-101 の組み立て手順は多岐にわたるため、まずは公式チュートリアル動画や販売元の組み立て動画を参照してください。 LeRobot 公式サイトのチュートリアル WoWRobo が公開している組み立て手順の動画 --> Warning 組み立て後は、各公式ページやチュートリアルで案内されているキャリブレーション手順に従ってモーターの位置合わせを必ず行ってください。キャリブレーションが不十分だと関節角がずれて想定外の姿勢で動作し、周囲の物や人にぶつかるなど危険が生じます。 --> アドバイス 組み立てに関しては、上記のいずれかの動画を見ていけばいいのですが、サーボホーン(下記画像参照)でいくつかアドバイスをしたいと思います。 サーボホーンにロボットアームの外装・機構パーツを固定する際は、大きなネジを使います。モーター本体をロボットアームに固定するためには、小さなネジを使います。 サーボホーンは「凸部(突起)」が外側(表)に見える向きではなく、突起が見えないように裏返して取り付けてください(平らな面を表にする)。裏表を誤るとネジが最後まで締まらず、アームの可動域やスムーズさに影響することがあります。 [2] 3. 動作確認 # 参考リンク: Imitation Learning on Real-World Robots 最後に、LeRobot が提供する模倣学習のサンプルコードを使って、Leader から Follower へのテレオペレーションで動作確認を行います。モーターバスがPCに接続されていることを確認してから、下記コマンドを実行します。 lerobot-teleoperate \ --robot.type=so101_follower \ --robot.port=/dev/ttyACM0 \ # MotorBus(モーターバス)のセットアップで確認した Follower_MB のポートを入力してください --robot.id=my_awesome_follower_arm \ # 好きな変数名を入力してください --teleop.type=so101_leader \ --teleop.port=/dev/ttyttyACM0 \ # MotorBus(モーターバス)のセットアップで確認した Leader_MB のポートを入力してください --teleop.id=my_awesome_leader_arm # 好きな変数名を入力してください 今までの手順が正しく行われていれば、 はじめに の章の GIF のように、Leader を動かすとFollower が追従して動作します。 最後に # 本記事は LeRobot と SO-101 を使ったフィジカルAI入門として、環境構築と基本的な動作確認手順を紹介しました。今後も機会があれば、LeRobotを使った学習(模倣学習・強化学習)や、ロボティクス領域のAI手法の詳細について掘り下げていく予定です。 機会があれば、学習方法に関して記事を書くかもしれません。 ↩︎ 著者はこれのせいで、組み立て済みのロボットアームを分解して、再度組み直すことになりました。 ↩︎
はじめに # こんにちはDX戦の檜尾です。 初めての投稿になりますドキドキ。 日々の業務において、AWSQuickSightのダッシュボード定義をコードとして管理する「BI as Code」の重要性が高まっていると感じます。 従来のGUI上での直接編集はアジリティが高い反面、変更履歴の追跡や誤操作によるロールバックが困難になるという運用上の課題を抱えています。 AWSではこれらの問題に対してビジネスインテリジェンス運用 (BIOps)という考えを適用しようとしています。 DevOpsで行っていたことをBIでも適用できるのではないかということですね。 本記事では、QuickSightの定義ファイルをGitでバージョン管理し、安全に運用するためのCI/CDパイプライン構築手順を全3章に分けて解説します。 第1章:自動アップロード(本記事) QuickSight上でのダッシュボード公開をトリガーとし、定義ファイル(JSON)を自動的にGitHubへバックアップする仕組みを構築。 第2章:自動テスト(次回予定) エクスポートされた定義ファイルに対する静的解析や、依存するデータセットの整合性チェックを自動化する仕組みを解説予定。 第3章:自動デプロイ(次々回予定) GitHub上でレビュー・マージされた定義ファイルを、別環境(本番環境等)のQuickSightへ自動でデプロイするパイプラインを解説予定。 今回はベースとなる 第1章:自動アップロード環境の構築 について、具体的なアーキテクチャと実装手順、および構築時に陥りやすい技術的な仕様(Tips)を解説します。 第1章:自動アップロード環境の構築 # 本章では、QuickSightの「Asset Bundle API」を活用し、イベント駆動型でダッシュボード定義を抽出・保存します。 1. 全体の流れ # AWS CloudTrail / Amazon EventBridge : QuickSightにおけるダッシュボードの公開( UpdateDashboard / CreateDashboard )を検知。 AWS CodeBuild : EventBridgeをトリガーとして起動し、Pythonスクリプトを実行。 AWS QuickSight (Asset Bundle API) : CodeBuildからのリクエストに応じ、ダッシュボードの定義をJSON形式でエクスポート。 GitHub : 抽出されたファイルをCodeBuildが対象リポジトリへCommitおよびPush。 2. 実装手順 # Step 1: GitHub認証情報のAWS Secrets Managerへの登録 CodeBuildがGitHubへアクセスするためのPersonal Access Token (PAT) を発行し、AWS Secrets Managerに保存します。 シークレットのタイプ : その他シークレットのタイプ キー/値のペア : UIのキー・値入力ではなく、「プレーンテキスト」タブから以下のJSON形式で保存する必要がある。理由はCodeBuildのソースフェーズで ServerType is required エラーが発生する為。 { &quot;ServerType&quot;: &quot;GITHUB&quot;, &quot;AuthType&quot;: &quot;PERSONAL_ACCESS_TOKEN&quot;, &quot;Token&quot;: &quot;ghp_から始まるPAT&quot; } シークレット名 : QuickSightGitHubToken Step 2: 実行スクリプトの配置 バックアップ先となるGitHubリポジトリの直下に、エクスポート処理を担うPythonスクリプトとCodeBuildのビルド仕様ファイルを配置します。 1. export.py Boto3を使用してAsset Bundle Export APIを呼び出します。今回はダッシュボード定義のみを対象とするため、 IncludeAllDependencies=False を指定しています。 import boto3 import time import requests import zipfile import io import os import uuid account_id = os.environ['AWS_ACCOUNT_ID'] raw_dashboard_id = os.environ['DASHBOARD_ID'] # EventBridgeからARNが丸ごと渡ってくる region = os.environ['AWS_REGION'] dashboard_id = raw_dashboard_id.split('/')[-1] client = boto3.client('quicksight', region_name=region) job_id = str(uuid.uuid4()) arn = f&quot;arn:aws:quicksight:{region}:{account_id}:dashboard/{dashboard_id}&quot; print(f&quot;Exporting dashboard: {dashboard_id}&quot;) # エクスポートジョブの開始 client.start_asset_bundle_export_job( AwsAccountId=account_id, AssetBundleExportJobId=job_id, ResourceArns=[arn], IncludeAllDependencies=False, ExportFormat='QUICKSIGHT_JSON' ) # 非同期処理の完了待機(ポーリング) while True: response = client.describe_asset_bundle_export_job( AwsAccountId=account_id, AssetBundleExportJobId=job_id ) status = response['JobStatus'] if status == 'SUCCESSFUL': url = response['DownloadUrl'] break elif status in ['FAILED', 'FAILED_PARTIAL']: raise Exception(&quot;Export failed!&quot;) time.sleep(5) # アーカイブのダウンロードと展開 res = requests.get(url) with zipfile.ZipFile(io.BytesIO(res.content)) as z: z.extractall(&quot;quicksight_backup&quot;) print(&quot;Download and extraction complete.&quot;) 2. buildspec.yml CodeBuildの動作を定義します。 version: 0.2 env: secrets-manager: GITHUB_TOKEN: &quot;QuickSightGitHubToken:Token&quot; phases: install: runtime-versions: python: 3.11 commands: - pip install boto3 requests build: commands: - python export.py post_build: commands: - git config --global user.name &quot;QuickSight Auto Backup&quot; - git config --global user.email &quot;bot@example.com&quot; - git remote set-url origin https://${GITHUB_TOKEN}@github.com/YourOrg/YourRepo.git - git add quicksight_backup/ - git commit -m &quot;Auto backup dashboard ID - ${DASHBOARD_ID}&quot; - git push origin main Step 3: IAMロールとCodeBuildプロジェクトの作成 CodeBuildに付与するIAMロールには、最小権限の原則に従い以下のポリシーをアタッチします。 quicksight:StartAssetBundleExportJob quicksight:DescribeAssetBundleExportJob quicksight:DescribeDashboard secretsmanager:GetSecretValue CodeBuildプロジェクトを作成し、ソースプロバイダとして対象のGitHubリポジトリを指定します。アカウント認証情報から接続をしておきます。また、環境変数に AWS_ACCOUNT_ID (12桁の数字)を設定します。 Step 4: EventBridgeルールの設定 CloudTrailが有効化されている前提で、EventBridgeルールを作成します。 イベントパターン : { &quot;source&quot;: [&quot;aws.quicksight&quot;], &quot;detail-type&quot;: [ &quot;AWS API Call via CloudTrail&quot;, &quot;AWS Service Event via CloudTrail&quot; ], &quot;detail&quot;: { &quot;eventSource&quot;: [&quot;quicksight.amazonaws.com&quot;], &quot;eventName&quot;: [&quot;CreateDashboard&quot;, &quot;UpdateDashboard&quot;] } } ターゲット設定 : 作成したCodeBuildプロジェクトを指定し、「入力トランスフォーマー」機能を用いてダッシュボードIDを環境変数として渡します。 入力パス : {&quot;dashboard_id&quot;: &quot;$.detail.serviceEventDetails.eventRequestDetails.dashboardId&quot;} 入力テンプレート : { &quot;environmentVariablesOverride&quot;: [ { &quot;name&quot;: &quot;DASHBOARD_ID&quot;, &quot;type&quot;: &quot;PLAINTEXT&quot;, &quot;value&quot;: &quot;&lt;dashboard_id&gt;&quot; } ] } ここまでの実装でダッシュボードを公開すると、自動的に裏側のGithubにダッシュボードのバックアップができるようになります。 3. Tips: 構築時におけるQuickSight APIの技術的制約と解決策 # 自動化パイプライン構築において、QuickSight特有の仕様によりエクスポートが FAILED となるケースがあります。以下に代表的な事象とその解決策を提示します。 事象: APIにおけるローカルファイルデータセットの非互換性 IncludeAllDependencies=True を指定時、 File source type is not supported in Public API というエラーが発生する。 原因 : Asset Bundle APIは、ユーザーが手動でアップロードしたローカルファイル(CSV/Excel等)の抽出に非対応。 対策 : スクリプト側で IncludeAllDependencies=False を指定しダッシュボードのみを抽出する。もしくはデータソースをS3やAmazon Athena経由の参照モデルに改修する必要がある。 第2章・第3章に向けて # 本章により、ダッシュボードの変更が自動的にGitリポジトリへコミットされる環境が整いました。これにより、変更履歴の可視化とバックアップの自動化が達成されます。 次回の 第2章:自動テスト では、取得したJSON定義に対するスキーマ検証や、不要な変更が含まれていないかを自動検知する仕組みについて解説しようとおもいます。 参考資料 # 本環境を構築するにあたり、以下のAWS公式ドキュメントおよび公式ブログを参考にしています。さらに詳細な仕様やAPIのオプションについて知りたい方は、併せてご参照ください。 Amazon QuickSight BIOps – パート3 : API を使用したアセットのデプロイ (AWS公式ブログ) Boto3 Documentation: QuickSight - start_asset_bundle_export_job Python (Boto3) からエクスポートジョブを実行する際の、詳細なパラメータ( IncludeAllDependencies 等)。 AWS CloudTrail を使用した Amazon QuickSight API コールのログ記録 (AWS公式ドキュメント) QuickSightでの操作がどのようにCloudTrailに記録されるか(API Call と Service Event の違いなど)の仕様が記載。 AWS CodeBuild の buildspec リファレンス (AWS公式ドキュメント) buildspec.yml 内で AWS Secrets Manager から安全に認証情報(GitHub PAT)を取得するための構文規則について解説。