コンテンツクリエイティブ

技術顧問・システム開発の技術事務所

Gmail の MCP サーバーで複数アカウントを扱うときの認証設計

カテゴリ:

AI アシスタントに Gmail を繋ぐ標準のコネクタは、1 つの Google アカウントしか保持できません。仕事用と別組織用の 2 つの受信箱を持っている場合、片方を繋ぐともう片方は見えなくなります。切り替えは可能ですが、「両方を見比べて判断する」用途には使えません。

MCP(Model Context Protocol)サーバーを自作すれば、複数アカウントを同時に扱えます。実装そのものは薄い API ラッパーで済みます。難所は認証です。 ここを踏み外すと、動くには動くが 7 日ごとに再認証を求められる、という状態になります。

この記事では、別ドメインの Google Workspace を 2 つ扱う場合を例に、認証設計と混同防止の実装を整理します。

制限付きスコープという壁

Gmail の API スコープ(gmail.readonlygmail.modify など)は、Google の分類で制限付きスコープに該当します。ここが設計を決定づけます。

OAuth 同意画面の User type には Internal と External の 2 つがあります。External を選んで公開しようとすると、制限付きスコープを使うアプリには CASA(Cloud Application Security Assessment)というサードパーティのセキュリティ審査が要求されます。実費と年次更新が発生し、プライバシーポリシーやデモ動画の提出も必要です。個人が自分用のツールのために通すものではありません。

では External の「テスト」状態のまま使えばよいかというと、こちらはリフレッシュトークンが 7 日で失効します。週に一度ブラウザで再認可する運用は現実的ではありません。

ネット上の解説記事には「External にして公開すれば OK」と書かれたものが少なくありませんが、これは制限付きスコープの条件を踏まえていない説明です。

Internal を使う。ただし 1ドメインにつき 1 プロジェクト

Google Workspace を使っている場合の正攻法は、User type を Internal にすることです。自組織内向けアプリは検証が免除され、トークンも失効しません。

制約は 1 つ。Internal は、Cloud プロジェクトを所有する組織のドメインのユーザーしか認可できません。 つまり example.com の Workspace 配下に作ったプロジェクトのクライアント ID では、example.org のアカウントを認可できません。

したがって、別ドメインの Workspace が 2 つあるなら、Google Cloud プロジェクトを 2 つ作ることになります。それぞれの Workspace 配下に 1 つずつ。それぞれで Gmail API を有効化し、それぞれ Internal の OAuth クライアントを発行する。MCP サーバー側は、クライアント ID とシークレットをアカウント単位で保持します。

{
  "accounts": {
    "work": {
      "email": "user@example.com",
      "client_id": "________.apps.googleusercontent.com",
      "client_secret": "________",
      "token_path": "~/.gmail-mcp/work.json"
    },
    "org": {
      "email": "info@example.org",
      "client_id": "________.apps.googleusercontent.com",
      "client_secret": "________",
      "token_path": "~/.gmail-mcp/org.json"
    }
  }
}

プロジェクト作成時に注意すべき点が 1 つあります。組織が正しく紐づいているかです。個人アカウント配下にプロジェクトを作ってしまうと、そもそも Internal が選択肢に出てきません。「Internal が選べない」という状態になったら、まずここを疑ってください。

もう 1 点、Workspace 管理コンソールの「アプリのアクセス制御」で未構成アプリがブロックされていると、認可画面まで進めません。事前に確認しておくと余計な時間を使わずに済みます。

選択肢としてのドメイン全体の委任

両ドメインの特権管理者権限を持っているなら、サービスアカウント+ドメイン全体の委任(DWD)という方法もあります。管理コンソールでクライアント ID とスコープを許可すれば、対話的な OAuth フロー自体が不要になり、ブラウザでの認可もトークン失効も消えます。運用は圧倒的に楽です。

ただし、そのサービスアカウントは設定次第でドメイン内の任意のユーザーになりすませます。鍵ファイルが漏れたときの被害範囲が桁違いに大きい。自分の 2 アカウントを読むためだけに用意する権限としては過剰で、Internal OAuth のほうが権限の範囲が狭く収まります。

情報が混ざらないようにする

複数アカウントを 1 つのサーバーで扱うとき、「AI が読んだ内容を取り違えないか」という懸念が出ます。これは実際に起こり得ますが、原因の多くはサーバー側の設計にあります。原則は 1 つです。

どのアカウントを触るかを、モデルの推論に委ねない。

具体的には 3 点です。

1. ID を複合キーで返す。 Gmail のメッセージ ID・スレッド ID は、アカウントごとの名前空間です。検索結果として生の ID だけを返すと、後続の「このスレッドを読んで」という操作でどちらのアカウントに投げるべきかが判断できなくなります。そこでモデルが文脈から推測を始めた瞬間に事故が起きます。work:18f2a3... のような複合キーで返し、サーバー側でパースしてアカウントを決定すれば、推測の余地がなくなります。

2. 結果に必ずアカウント名を付ける。 横断検索で複数アカウントの結果をマージするとき、各レコードに所属を示すフィールドがないと、受け取る側では見分けがつきません。1 件ごとに [work] [org] を先頭に付けて返せば、要約や比較を頼まれたときの出典もそこで担保されます。

3. 書き込み系にデフォルト値を持たせない。 DEFAULT_ACCOUNT のような設定があると、アカウント指定が抜けたときに黙って既定側からメールが出ていきます。読み取りの取り違えは読み直せば済みますが、誤送信は取り返しがつきません。書き込み系ツールはアカウント指定を必須にし、返信・転送は引用元スレッドの複合 ID に含まれるアカウントからのみ実行できるよう、サーバー側で強制します。

アカウント数が 2〜3 程度なら、ツール名自体を work_gmail_search / org_gmail_search のように分けてしまう手もあります。引数で渡すより取り違えが起きにくい反面、アカウント数×ツール数だけ定義が増えます。4 アカウントを超えるなら複合キー方式に統一するほうが実務的です。

スコープは読み取りから始める

最初のバージョンは gmail.readonly だけで組むことをお勧めします。次の段階で足すとしても gmail.compose まで。これは下書きの作成までで、送信は含みません。送信は人間が Gmail の画面で行う、という線引きです。

gmail.send を最初から入れる理由は、実のところあまりありません。AI に文面を作らせる価値と、AI が送信ボタンまで押す価値は別物です。前者はほぼ確実に時間を節約しますが、後者が節約するのはクリック 1 回分です。

最初のバージョンで切り捨てるもの

実装で時間を持っていかれやすいのは、コードの骨格ではなく細部の仕様です。日本語件名の MIME エンコード、返信時の In-Reply-To / References ヘッダの引き回し、ページネーション、添付ファイルの扱い。

このうち添付は、要件が固まる前に作り込むと確実に無駄になります。最初は「読み取りと横断検索だけ、書き込みなし」に絞れば、半日で実用段階に届きます。運用感を掴んでから下書き機能を足す、という順序のほうが結果的に早く仕上がります。

実際に作って詰まったところ

ここまでは設計の話です。実際に手を動かすと、設計段階では見えていなかった箇所で止まりました。以下は、同じことをやる人が確実に踏むと思われるものです。

同意画面のメニューが 2 系統ある。 Google は管理画面の構成を変更している途中で、新しい UI では「Google Auth Platform」の「対象」、古い UI では「API とサービス」の「OAuth 同意画面」に同じ設定があります。解説記事を読むときは、どちらの世代の話かを先に見分ける必要があります。

URL の authuser に足をすくわれる。 Cloud Console の URL には authuser=3 のような値が入りますが、これはブラウザにログインしている順番で決まるもので、意図した Workspace アカウントを指しているとは限りません。違うアカウントで作業していると、次に述べる「組織の選択」で候補が出てこず、原因が分からないまま時間を溶かします。作業前に右上のアカウント表示を確認するか、そのアカウントだけでシークレットウィンドウを開くのが確実です。

プロジェクト作成時の「場所」で組織を選び忘れると、後からやり直しになる。 ここを「組織なし」のまま作ってしまうと、同意画面で Internal が選択肢に現れません。プロジェクトの所属組織は後から変更できないため、作り直すことになります。作成ダイアログで一番目立たない項目が、実は一番重要でした。

スコープを後から足すときは、再認可が必要。 これは実装を 2 段階に分けたときに気づきました。最初に読み取り専用で作り、後から下書き作成を足したのですが、同意画面にスコープを追加しただけでは反映されません。保存済みのトークンには認可した時点のスコープが焼き付いているためです。トークンファイルを削除して認可からやり直す必要があります。段階的に権限を広げる設計にするなら、この手戻りを見込んでおくべきです。

切り分け用の単体テストを最初に用意しておくと早い。 実際、MCP 経由で下書き作成を呼んだときに応答が返らず止まりました。そのとき、サーバーを介さずコードだけを実行する小さなスクリプトがあったおかげで、「コードは正常、経路の問題」と数十秒で判別できています。MCP サーバーの開発では、失敗したときに原因がコードなのか通信路なのかクライアントなのか分かりにくいので、この 1 本は最初から書いておく価値があります。

個人アカウントはどうするか

ここまで書いてきた構成は、Workspace のアカウントにしか使えません。Internal の同意画面は組織のドメインに属するユーザーしか認可できないため、無料の個人 Gmail アカウントは載せられないからです。個人アカウント用に External でもう 1 つ作れば、冒頭で述べた 7 日失効の問題に正面からぶつかります。

ただし、これは自作サーバーの中だけの話です。AI アシスタント側が標準で提供しているコネクタは、提供元が審査を通したアプリを使っているため、個人アカウントでも問題なく繋がります。そして標準コネクタと自作サーバーは別枠なので、「コネクタは 1 アカウントまで」という制約とも干渉しません。

結果として、自作サーバーで Workspace を 2 つ、標準コネクタで個人アカウントを 1 つ、合計 3 アカウントを同時に扱えます。何でも自作で解こうとせず、既にあるものと組み合わせるほうが早い場面もある、という当たり前の話でもあります。

配布はできるのか

MCP サーバーには .mcpb(MCP Bundle)という配布形式があり、ワンクリックでインストールできる形に固めることができます。しかし Gmail に関しては、これは成立しません。

Internal の OAuth クライアントは発行元ドメインのユーザーしか認可できないため、他人がインストールしても動きません。他人が使える形にするには、利用者各自に Google Cloud プロジェクトを作ってもらう(30 分以上の作業で、ワンクリックの価値が消えます)か、External 公開+前述の審査を通すか、のどちらかです。

自分のための道具として作るなら、この制約は問題になりません。むしろ、他人のメール認証情報を預からずに済むという意味では、Internal に閉じている構成のほうが安全です。

まとめ

  • ドメインごとに Google Cloud プロジェクトを作り、それぞれ Internal の OAuth クライアントを発行する
  • スコープは gmail.readonly から始める
  • ID は複合キー、結果にはアカウント名、書き込みにデフォルト値を持たせない
  • 添付と返信ヘッダは後回し

認証さえ正しく組めれば、残りは薄いラッパーです。詰まるのはコードではなく、Google 側の仕様のほうです。