はじめに
Gemini Enterprise(以下 GE)に独自エージェントを登録し、DocBase の公式 MCP サーバーを接続しようとしましたが、DocBase MCPはOAuth 2.1 認証が必要になり、ClaudeやChatGPT などのAI クライアントでは動作確認済みと公式記載されてましたが、Gemini Enterprise からの接続は動作確認済み一覧になかったため、今回実践いたしました。
全体アーキテクチャ
Gemini Enterprise App │ ①ユーザーごとにOAuth同意 → GEがトークンを取得・保管 ▼ root_agent(オーケストレーター)← GEへ登録 └─ docbase_agent(Docbase探索のサブエージェント) └─ McpToolset ─→ Docbase MCP (https://mcp.docbase.io/mcp) ②実行時にGEが注入したトークンをBearerヘッダーで付与
下記設計の要点です。
- GEに登録するエージェントは1つとし、配下にサブエージェントをぶら下げる構成にしたい。
- トークンはGEがユーザーごとにサーバー側で管理
- 各ユーザーは自分のDocBase権限の範囲でしか検索できない形
エージェント実装(ADK)
エージェントはADK(Agent Development Kit)で実装し、開発には agents-cli を利用しました。
GEがセッションstate に注入するトークン(temp:
AUTH_ID = os.environ.get("DOCBASE_AUTH_ID", "docbase-oauth")
def _docbase_headers(context: ReadonlyContext) -> dict[str, str]:
token = context.state.get(f"temp:{AUTH_ID}") or context.state.get(AUTH_ID)
if not token:
token = os.environ.get("DOCBASE_ACCESS_TOKEN") # ローカル検証用
return {"Authorization": f"Bearer {token}"} if token else {}
McpToolset(
connection_params=StreamableHTTPConnectionParams(url="https://mcp.docbase.io/mcp"),
header_provider=_docbase_headers,
tool_filter=_readonly_tools_only, # search/get系のみ公開(後述)
)
制限として下記2つを入れてます。
1つ目はDocBase MCP経由の操作は読み取り専用としました。
ツール名のプレフィックスで search / get 系だけを利用するようにします。
_READONLY_PREFIXES = ("search", "get", "list", "read", "fetch", "find")
def _readonly_tools_only(tool: BaseTool, context: ReadonlyContext | None) -> bool:
return tool.name.lower().replace("-", "_").startswith(_READONLY_PREFIXES)
2つ目はレスポンス捏造の制御です。
トークン未取得のままLLMが動くと、存在しないメモとURLを生成してしまう事故があり、before_agent_callbackでトークンの有無をチェックし、なければ「認証未完了」の定型文を返すようにしています。
def require_docbase_auth(callback_context: CallbackContext) -> types.Content | None:
state = callback_context.state
token = (
state.get(f"temp:{AUTH_ID}") # 本番: GEが注入
or state.get(AUTH_ID) # ローカル: キャッシュ
or os.environ.get("DOCBASE_ACCESS_TOKEN") # ローカル検証用
)
if token:
return None # 認証OK → 通常どおりエージェントを実行
# トークンなし → LLMを実行せず定型文を返す(Contentを返すとエージェント本体はスキップされる)
return types.Content(
role="model",
parts=[types.Part(text=(
"Docbase への接続認証が完了していないため、探索を実行できません。"
"Docbase 連携の OAuth 認証を完了してから再度お試しください。"
))],
)
これらをサブエージェントに組み込みます。ADK ではbefore_agent_callbackが types.Content を返すと、エージェント本体(LLM呼び出し)はスキップされ、その内容がそのまま応答になります。
docbase_agent = Agent( name="docbase_agent", model=Gemini(model="gemini-flash-latest"), instruction="...", tools=[build_docbase_toolset()], before_agent_callback=require_docbase_auth, # ← 捏造ガード )
DocBase の DCR で scope を絞ると 500 エラー
DocBase の OAuth は Dynamic Client Registration(DCR)に対応しており、API でクライアント登録ができます。
読み取り専用にしたかったので "scope": "read" を指定して登録したところ、同意画面で承認をすると 500 Internal Server Errorになりました。
DCR登録時にscopeを指定して絞ることで起こる問題でした。
scope を指定せずに登録するとデフォルトの全スコープが割り当てられ、同意が正常に通りました。
# NG: "scope": "read" を入れると承認時に500
# OK: scopeを指定しない
curl -X POST https://docbase.io/oauth/register -H 'Content-Type: application/json' -d '{
"client_name": "Gemini Enterprise - my-agent",
"redirect_uris": ["https://vertexaisearch.cloud.google.com/oauth-redirect"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_post"
}'
また、同意画面にはスコープのチェックボックスがあり、利用時に促される承認の際にスコープにて書き込みや削除スコープを選ぶとその内容がそのままトークンに反映されるので、読み取り専用制御はエージェント側のツールフィルタで行いました。
動作確認
今回はDocBase内の社内の事例を検索するエージェントとして構築しました。
GEでエージェントを選び、最初のメッセージを送ると DocBaseアクセスの承認ボタンが表示されます。

その後、ブラウザより承認許可を促されます。

承認すると、以降は普通にチャットするだけでDocBaseの探索が可能になります。
まとめ
今回でGEでのOAuth認証でのMCP接続が可能であることの理解が進みました。
他のツール接続でも活用できる知見となりました。