Skip to main content
Glama
Bloody-Regina

gmail-mcp

あなたのAIアシスタントにGmailを。複数アカウントを同時に、あなた自身のサーバーで。

MIT Cloudflare Workers MCP OAuth 2.1 27 tools tests

日本語版 · 简体中文

gmail-mcp は、Gmail を Claude やその他の MCP クライアントに接続します。メールの検索・既読、引用付きの返信・全員に返信転送添付ファイルやインライン画像の処理、下書き・ラベル・スレッドの管理を、複数の Google アカウントを同時に行えます。

これはあなた自身の Cloudflare Worker 上のリモートサーバーとして実行されるため、ノートパソコン上の Claude Code、ブラウザ上の claude.ai、スマートフォン上の Claude から、同じ接続に応答します。各接続は1つの Google アカウントにサインインし、Google の更新トークンはあなたの Cloudflare アカウントに残ります。

このプロジェクトに人々が惹かれる理由は2つあります。Claude と Google に組み込まれている Gmail コネクタは、メールの読み取りと下書きの作成はできますが、送信はできず、アシスタントアカウントごとに1つの Google アカウントしか保持できません。送信できるサーバーは通常ローカルプロセスであり、机の上では問題なくても、スマートフォンからは見えません。


比較

gmail-mcp

ClaudeGoogle ビルトイン

taylorwilsdon/google_workspace_mcp

ArtyMcLabin/Gmail-MCP-Server

shinzo-labs/gmail-mcp

aaronsb/google-workspace-mcp

実行場所

Cloudflare Workers

ベンダーホスト型

あなたのサーバーまたはローカル

ローカル

ローカル

ローカル

スマートフォンからアクセス可能

複数メールボックスを同時に

✅ 接続ごとにバインド

✅ 呼び出しごとに選択

❌ エイリアスのみ

✅ 呼び出しごとに選択

メール送信

添付ファイル・インライン cid: 画像

ドキュメント化されていません

引用付き全員に返信

下書きのみ

引用なし

転送

各パーツの文字コードを尊重

❌ UTF-8 と仮定

❌ UTF-8 と仮定

CRLF ヘッダーインジェクションを拒否

✅ フレームワーク

✅ 削除

なし

メールボックス設定(フィルタ、休暇応答)

❌ 対象外

フィルタ

フィルタ

ツール数

24

11–16

14 (Gmail)

30

64

11

更新トークンの保持者

あなた

ベンダー

あなた

あなた

あなた

あなた

google_workspace_mcp は、ここにあるプロジェクトの中で最も完全なものです。Gmail だけでなく Workspace 全体をカバーし、Gmail の署名を追加したり、URL から直接添付ファイルを取得したりできますが、gmail-mcp はどちらも行いません。shinzo-labs/gmail-mcp は、64 個のツールを通じて休暇応答、委任、S/MIME にアクセスできます。これらは gmail.settings.* の下にあり、gmail-mcp が要求することのないスコープであるため、権限がどうなろうと、その範囲外にあります。

残りのほとんどは、2つの設計上の違いによって決まります。呼び出し引数でアカウントをルーティングすると、1つの権限で接続されているすべてのメールボックスにアクセスできますが、メールボックスを接続にバインドすると、誤った引数では何にも到達できません。また、読み取り時には、ローカルサーバーはすべてのパーツを UTF-8 としてデコードするため、ISO-2022-JP や Shift_JIS のメールは文字化けし、Gmail が添付ファイルの blob として保存する長いメッセージは、本文が空になって返されます。


デプロイ

約10分かかります。Cloudflare アカウント、bun、Google アカウントが必要です。Cloudflare アカウントのドメインはオプションです。なくても Worker は workers.dev で応答します。

1 · Google OAuth クライアントを作成する

PROJECT="gmail-mcp-$(openssl rand -hex 3)"
gcloud auth login
gcloud projects create "$PROJECT" --name="gmail-mcp"
gcloud config set project "$PROJECT"
gcloud services enable gmail.googleapis.com

Google は次の2つのステップの API を公開していないため、これらは Cloud コンソールで行われます。

  • OAuth 同意画面外部 を選択し、対象ユーザー セクションで アプリを公開 を押します。 テスト版のままにしておくと、Google は 7 日ごとにすべての更新トークンを失効させ、各接続はトークンとともに切れます。公開すると、アプリはサインイン時に未確認アプリの警告を表示し、最大 100 アカウントまで利用できます。

  • 認証情報認証情報を作成OAuth クライアント IDウェブ アプリ を選択し、許可されたリダイレクト URI に https://<your-host>/callback を設定します。クライアント ID とシークレットは保管してください。

<your-host> は Worker に名前を付けるドメイン、またはその他の場合に割り当てられる workers.dev のホスト名です。先にデプロイしてからここを埋めに戻っても問題ありません。Worker が / で提供するガイドに正確な値が表示されます。

2 · Worker をデプロイする

Deploy to Cloudflare

このボタンは、リポジトリをあなたの GitHub アカウントにコピーし、KV 名前空間と Durable Object を作成し、4 つのシークレットを尋ねます。workers.dev にデプロイされます。カスタム ドメインは、後で Settings → Domains & Routes で追加します。

ターミナルから実行する場合は次の通りです。

git clone https://github.com/mkpoli/gmail-mcp && cd gmail-mcp
bun install
bun run setup

bun run setup は、応答するドメインを尋ね、OAUTH_KV 名前空間を作成または再利用し、クライアント ID とシークレットを受け取り、クッキー鍵を生成してデプロイします。これらの最初の 2 つの回答は wrangler.local.jsonc に保存されますが、これは git によって無視されます。wrangler.jsonc にはアカウントの名前空間も、誰のドメインも記載されていないため、クローンはどこでもデプロイできます。1 つのシークレットだけを交換するために setup を再実行しても安全です。

3 · クライアントを接続する

クライアント ID とクライアント シークレットは空のままにします。MCP クライアントは自身を登録します。

claude mcp add --transport http gmail-personal https://<your-host>/mcp
claude mcp add --transport http gmail-work     https://<your-host>/mcp/work

Claude Code で /mcp を実行すると、各接続をそれぞれの Google アカウントにサインインできます。Claude.ai では、Settings → Connectors → Add custom connector に同じ URL を設定します。/mcp/ の後には任意の単一セグメントのラベルが使えます。これが、URL を共有する 2 つのサーバーを異なるメールボックスとして扱わないクライアントに対して、1 つのデプロイで複数のメールボックスを提供する方法です。

このガイドは、https://<your-host>/ で公開されています。


できること

whoami search_messages get_message get_thread get_attachment

send_message reply_all forward_message create_draft update_draft send_draft delete_draft list_drafts stage_attachment_begin stage_attachment_append stage_attachment_finish

list_labels create_label update_label delete_label modify_labels modify_thread_labels batch_modify_messages trash_message · untrash_message trash_thread · untrash_thread

メッセージは、メールクライアントが送信するのと同じ形式で送られます。ファイル添付、cid: で参照されるインライン画像を、multipart/mixed › multipart/related › multipart/alternative として入れ子にした、HTML 版を伴うプレーンテキストです。件名と表示名には RFC 2047 が、ファイル名には RFC 2231 が使われるため、日本語、中国語、絵文字もきちんと保存されます。

reply_all は元のメールの Reply-ToFromToCc を読み取り、自分のメールアドレスと、自分が送信に使うアドレスを除き、送信者が宛て先にしたアドレスから返信します。References チェーンを引き継ぎ、送信する部分に元のメールを引用できます。forward_message は転送元を再現し、元のファイルを再添付することもできます。

create_draftreplyToMessageId と一緒に使うと、返信を下書きとして作成し、送信前に編集できます。元のスレッドに参加し、In-Reply-ToReferences を引き継ぎ、全員への返信の宛先と件名の Re: を生成し、元のメールを引用します。update_draft は与えられたフィールドだけを変更します。受信者、テキスト、任意のクライアントで手動で追加されたファイル、下書きが返信するスレッドは読み戻して保持されます。base64 がツールの引数に収まらないファイルは、代わりにステージングされます。stage_attachment_begin は、生バイトを curl -T で 1 回送信できるアップロード URL を返し、stage_attachment_append は base64 をチャンクで受け取り、すべての attachments フィールドが結果の stagingId を受け取ります。

読み取りには意図的に上限があります。メッセージとスレッド本文には文字数の上限、レスポンス全体にはバイト数の上限があり、添付ファイルは十分に小さいときだけインラインで返されます。長いメーリングリストのスレッドや大きいファイルは、説明のノートを付けてトリミングされ、アシスタントのコンテキストを埋めないようになっています。


仕組み

1 つの Worker 内で 2 つの OAuth フローが接続します。MCP クライアントは Worker に対して認証します。Worker はあなたに代わって Google に対して認証します。どちらかが相手の資格情報を保持することはありません。

sequenceDiagram
    autonumber
    participant C as MCP client<br/>(Claude Code · claude.ai)
    participant W as Worker<br/>(OAuthProvider + McpAgent)
    participant G as Google<br/>(OAuth + Gmail API)

    C->>W: POST /register (dynamic client registration)
    C->>W: GET /authorize (PKCE challenge)
    W->>C: approval dialog
    C->>G: consent screen — pick the account
    G->>W: GET /callback?code=…
    W->>W: allowlist check on the verified email
    W->>G: exchange code → access + refresh token
    W->>C: MCP access token (Google tokens sealed inside the grant)
    C->>W: POST /mcp — tools/call
    W->>G: Gmail REST (token refreshed as needed)
    G->>W: message / thread / label data
    W->>C: tool result

特徴

ファイル

役割

🔐 MCP 側 OAuth

workers-oauth-provider

動的クライアント登録、PKCE、Google トークンを封入した KV 内のグラント

🔗 Google 側 OAuth

src/google-handler.ts

オフラインアクセス用の認可コード、ブラウザセッションに束縛された使い捨て state、二重送信 CSRF、確認済みメールアドレスの許可タンス

🤖 エージェント

src/index.ts

MCP セッションごとに 1 つの Durable Object を開いたアカウントに紐付け。シングルフライトのトークン更新、スロットリングされたファンナウト

✉️ メール

src/gmail.ts

RFC 822 の構築、MIME ツリーの走査、文字コードのデコード、返信と転送の構成

構成に使用したもの

  • TypeScriptCloudflare Workers — Durable Objects が MCP セッションを 1 つずつ保持し、KV が OAuth グラントを保持

  • Hono — OAuth エンドポイント、Google コールバック、/ のセットアップページのルーティング

Gmail 自体は、REST API へ plain fetch で呼び出されます。公式の googleapis SDK は Node を前提としており、Worker が同梱するべきな量を大きく超えているため、メッセージ構築、MIME パース、トークン更新はかわって src/gmail.tssrc/utils.ts に含まれています。

エンドポイント

Path

用途

/mcp

MCP エンドポイント

/mcp/<label>

任意の単一セグメントラベルで同じサーバーを提供し、URL 共有を拒否するクライアントをサポート

/

このセットアップ ガイド

/authorize · /token · /register · /callbackpath

OAuth 仕組み


サインインできるユーザー

ALLOWED_EMAILS が決定します。同意後、グラントが存在する前に、Google が確認済みとして報告する アドレスと照合されます。

誰が入れるか

(空)

なし

you@gmail.com, work@company.com

それらのアカウント

*@company.com

そのドメインの誰でも

*

すべての確認済みの Google アカウント

各グラントは認証したメールボックスだけに届くため、このリスト を広げても、すで接続されているメールボックスへのアクセスは広がりません。* を設定すると、見知らぬ人にあなたのデプロイとあなたの Google クライアントの割り当てを自分のメールに取って使ってもらうことになります。


制限

共有デプロイが尽きないための 2 つの上限があり、どちらも wrangler.jsonc で設定します。

| 設定 | 場所 | デフォルト | 制限の対象 | | :------------------------------ | :---------------- | :---------------- | :----------------------------TO | | MAX_ACCOUNTS | vars | 25 | サインインできる Google アカウントの数で凡その上限。上限に達しても既に接続されたアカウントは動作し続け、新しいアカウントは拒否されます。同時にサインインされた場合、いずれにも記録される前にカウントが読まれるため、実際の総数がこの数を少し超えることがあります。Google は未確認アプリを 100 ユーザーで制限しているため、下に余裕を残してください。 | | RATE_LIMITER.simple.limit | unsafe.bindings | 120 per 60秒 | そのウィンドウ内で、アカウントがすべてのセッション経由で Gmail を呼び出せる回数。Cloudflare はこの回数をロケーションごとに維持するため、2 つの地域から接続するアカウントは、それぞれほぼ同じ数を使用できます。広い読み取りは複数回消費します:search_messages が 50 件を返すと 51 回の呼び出しになります。 | | REGISTER_LIMITER.simple.limit | unsafe.bindings | 10 per 60s| 1つのアドレスがそのウィンドウ内で行えるクライアント登録数。クライアントは一度登録し、割り当てられた ID を保持するため、通常の使用ではここに近づくことはありません。この上限があるのは、登録には資格情報が不要で、登録ごとに KV に書き込むからです。 |

Need check for typos and formatting. I see some issues:

  • In "### 2" "Worker をデプロイする" maybe okay.

  • "## 2nd" maybe "作動手順" hmm.

  • "Credentials" bullet maybe "ウェブ アプリ" with spaces? okay.

  • The "<your-host>" paragraph: "ガイドに正確な値が表示されます" good.

But I noticed in final translation I accidentally wrote "Worker ー" in "Worker はあなたに代わって" but got a mid-word hyphen? The output above has "Worker ー は..."? Actually I should fix: "Worker はあなたに代わって". Also "十分" maybe.

Need thoroughly scrutinize output for correctness and no accidental errors.

Let's produce final translate from beginning to end.

I'll rewrite final carefully.

Start:

  • OAuth 同意画面外部 を選択し、対象ユーザーアプリを公開 を押します。テスト版のままにしておくと、Google は 7 日後にすべての更新トークンを失効させ、各接続はそのトークンとともに期限切れになります。公開すると、サインイン時に未確認アプリの警告が表示され、最大 100 アカウントまで利用できます。

  • 認証情報認証情報を作成OAuth クライアント IDウェブ アプリ を選択し、承認済みのリダイレクト URI として https://<your-host>/callback を設定します。クライアント ID と クライアント シークレットは保管してください。

<your-host> は Worker に割り当てるドメイン、あるいは デフォルトで割り当てられた workers.dev のホスト名です。先にデプロイしてからここを埋めに戻っても問題ありません。Worker が / で提供するガイドに正確な値が表示されます。

2 · Worker をデプロイする

Deploy to Cloudflare

このボタンは、リポジトリをあなたの GitHub アカウントにコピーし、KV 名前空間と Durable Object を作成し、4 つのシークレットを要求します。workers.dev にデプロイされ、カスタム ドメインは後から Settings → Domains & Routes で追加します。

ターミナルから行う場合はこちらです。

git clone https://github.com/mkpoli/gmail-mcp && cd gmail-mcp
bun install
bun run setup

bun run setup は、応答するドメインを尋ね、OAUTH_KV 名前空間を作成または再利用し、クライアント ID と シークレットを受け取り、Cookie キーを生成してデプロイします。最初の 2 つの回答は wrangler.local.jsonc に保存され、git はこれを無視します。wrangler.jsonc にはどのアカウントの名前空間も誰のドメインも含まれないため、クローンをどこにでもデプロイできます。単一のシークレットをローテーションするために setup を再実行しても安全です。

3 · クライアントを接続する

クライアント ID と クライアント シークレットは空のままにします。MCP クライアントは自分自身を登録するためです。

claude mcp add --transport http gmail-personal https://<your-host>/mcp
claude mcp add --transport http gmail-work     https://<your-host>/mcp/work

Claude Code で /mcp を実行すると、各接続を対応する Google アカウントにサインインできます。Claude.ai では、Settings → Connectors → Add custom connector で同じ URL を設定します。/mcp/ の後には任意の単一セグメントのラベルが使えます。これは、URL を共有する 2 つのサーバーを受け付けないクライアントに、1 つのデプロイが複数のメールボックスを提供する方法です。

あなたのデプロイは、このガイドを https://<your-host>/ で提供しています。


できること

... etc.

Need "What can it do" section table.

Potential issue in "Messages leave the way a mail client sends them:" translated "送られる" but "send" could be "送信". Good.

"楁意地描写" Not.

Let's write the full section as code.

Need ensure "Th" text: "📖 読む", "✍️ 送る", "🏷 整理する". There is no "organizing" verb. "整理" okay.

Now "Text in code spans unchanged." Good.

"forward_message reproduces the envelope" keep.

Now "The final output has no code fences. We must not include code fence around output. That's okay.

Need consider the placeholder tokens "GXP2", "GXP3", "GXP4" line maybe they should be left as same line? In the input, GXP2 is after a blank line and before a paragraph. In output, yes.

Need perhaps "MCP clients register themselves" translation "MCP クライアントが自己登録します" not "そのため" maybe.

Need "language of <your-host>" okay.

Now, let's consider "some clients that reject two servers with the same URL" — I translated "2 つのサーバーを同じ URL で受け入れない". Good.

Now "products", "projects", "repository names" such as Google API Console maybe "OAuth 同意画面" not exact. But preserve hyperlinks.

Need maybe "Deploy to Cloudflare" alt translation. But to be safe, we can keep alt text in English? Let's ponder. The instruction says "Translate prose only. Keep verbatim: Product, project, organization, repository names (Claude Desktop, Playwright, Node.js, owner/my-mcp-server)." "Deploy to Cloudflare" is not one. "Cloudflare" is product. But "Deploy to" is prose. Hmm. In Markdown image alt text, it's not inline prose? It's an image caption. Since the button itself contains the phrase "Deploy to Cloudflare". Since the image is external, the alt text is the only visible if image missing. Translating alt may be okay but visible button remains English. If this is a raw HTML ![](button), not an HTML attribute. The instruction says "Do not drop images/badges/links." It doesn't say "alt text must be unchanged." I think translation is fine. But to be safe from over-translation, maybe leave alt text as "Deploy to Cloudflare" because it's a product name? Actually "Deploy to Cloudflare" is a standardized button from Cloudflare. The label contains Cloudflare and is not necessarily translated in docs. Many docs in Japanese keep "Deploy to Cloudflare" or "Cloudflare にデプロイ". The alt could be translated. But the instruction "Product names" not button labels. I think okay either way.

Need ensure "Cloudflare" in alt remains. Good.

Now, in final output maybe "Durable Object" might be plural "Durable Objects" in body, but original "Durable Object" not link. Keep "Durable Object" (singular). Japanese doesn't have plural marking.

Need "KV" is acronym for "namespace", okay.

Need use "シークレット" not "secret", "トークン" okay.

Now let's examine "Google expires every refresh token after 7 days" — "Google は 7 日後にすべてのリフレッシュ トークンを失効させます". Good.

Now "各接続はそのトークンとともに失われる" -> "each connection dies with its token" meaning "接続がトークンごと死ぬ". Translate "各接続はトークンとともに利用できなくなる" better.

"Published, the app shows unverified app warning at sign-in and serves up to 100 accounts." Good.

Need "In claude.ai it is Settings → Connectors → Add custom connector と同じ URL;" "それ" refers to URL. Good.

Need "under Audience" "対象ユーザー" maybe "Audience" is a section. Good.

Now about "the first two answers land in wrangler.local.jsonc" - "answers" = setup responses. Good.

Need "The first two answers" are domain and KV namespace? Actually exactly. "answer" maybe "matum".

Now maybe "wrangler.jsonc name_no account's all" - no.

Need "creates or reuses the KV namespace" — "KV" namespace should be "名前空間 (namespace)" good.

Now code inline "unsafe.bindings" etc. Good.

Potential issue "own client ID and secret" -> "クライアント ID とクライアント シークレット" in Google OAuth "シークレット" is correct.

Now final "limits" table row "120 per "60s". The original has `` 120per60s `` code spans. We need preserve exactly "120 per 60s" with backticks around numbers. I need make sure final Markdown has `` 120per60s `` not accidentally "per 60 s". The original string: '120per60s' with backticks around "60" and then "s outside code. I should reproduce exactly "120per60s". In output, I used "120per60`s". good.

Need table cell with pipe character in text? None.

Need "what bounds" row long. The table row may contain "search_messages" etc. Fine.

Need "the total can settle a little above" translation "実数がこの数字を少し上回ることがあります" good.

Need "one location" maybe "per location" "ロケーション単位" better.

Need "Cloudflare keeps the count per location" "Cloudflare はこのカウントをロケーション単位で保持します" okay.

Need "a wide fan-out" maybe "broad read" etc.

Need "mails for thousands?" No.

Need "2つの上限" and "shared deployment" "共用デプロイ" good.

Let's think of "Google client's quota" — "あなたの Google クライアントの利用枠(クォータ)" maybe.

Need "a shared deployment" - "デプロイを他者が使い尽くす" etc.

Now maybe "plain fetch" -> "素の fetch" or "プレーンな fetch". "plain fetch" in code: "just builtin fetch". We translated "素の fetch". Good.

Need "far more than a Worker should ship" "Worker が同梱するべきではない量" good.

Now check for accidental "Workerk" or "Woker" typo.

Let's compose final with careful strings. I will produce final in one block.

Before final, let's consider if translation of "What it can do" maybe "可能な操作" better. "できること" okay.

"二つの OAuth flows meet in one Worker" - if "meet" "互いにつながる" perhaps. Better:

「2つの OAuth フローが1つの Worker 内で繋がります。」Or "合流" good.

"Neither side holds the other's credentials." -> "どちら側も相手の資格情報を持ちません."

Need "MCP クライアントは Worker に対して認証し、Workerはあなたの代わりに Google に対して認証します。" good.

Need "The MCP client authenticates to the Worker" not "with Worker" but "向かって".

Now "grant" in OAuth context Japanese "grant" often "グラント" (OAuth 付与). In the table "grants in KV" is "grant を KV に保存". "Google token sealed inside" "その中にGoogleトークンをシールした". Let's phrase "KV 内のグラント(Google トークンを密封したもの)" to be clear. Maybe table text:

  • "動的クライアント登録、PKCE、Google トークンを密封した KV 内のグラント(付与)" Fine.

Need "memo" maybe.

In final "T.A."?

Now let's ensure all raw HTML remained unaltered except th text. The <source media="..." srcset="..." remains. <img ...> remains. Good.

Need perhaps "alt" attribute "An MCP client connects..." should remain because raw attributes. We'll leave.

Now "Endpoints" table. Original final row: | /authorize · /token · /register · /callback | OAuth machinery | I accidentally wrote "/authorize". I need avoid "callbackpath" typo. Let's use exact.

In final table, last row: | /authorize·/token·/register·/callback | OAuth の仕組み | Good.

Now "MCP-side OAuth" translated row: "動的クライアント登録、PKCE、Google トークンを密封した KV 内のグラント". Good.

Need "allowlist on verified email" in "Google-side OAuth" row: "確認済みメールアドレスの許可リスト" good.

Need "printed/fan-out" "スロットルされるファンアウト" maybe awkward. "rate-limited fan-out" "スロットル付きファンアウト" maybe. Good.

Now final table "What it bounds" too.

Let's write final.

One more thought: The instruction says "Translate prose only" and "keep the following verbatim ... package, module, tool, function, argument names ...". In descriptions, I should retain "MCP clients" but "MCP" is a protocol. Keep MCP. "Google account" translated "Google アカウント" but Google product name remains "Google". Good.

Need also "OAuth consent screen" is a console page. Japanese product "OAuth 同意画面" fine.

Need "OpenID" not.

Now final output should not have code fences. I will output just text. Ensure no triple backth.

Let's craft the final answer eleg* OAuth 同意画面外部 を選択し、対象ユーザー の下にある アプリを公開 を押します。Testing のままにすると、Google は 7 日後にすべてのリフレッシュ トークンを失効させ、各接続はトークンとともに使えなくなります。公開すると、サインイン時に未確認アプリの警告が表示されますが、最大 100 アカウントまで扱えます。

  • 認証情報認証情報を作成OAuth クライアント IDウェブ アプリ を選択し、承認済みのリダイレクト URI として https://<your-host>/callback を設定します。クライアント ID とクライアント シークレットは保管してください。

<your-host> は Worker に向けるドメイン、またはデフォルトで割り当てられる workers.dev のホスト名です。先にデプロイしてからここを埋めに戻っても問題ありません。Worker が / で提供するガイドに正確な値が表示されます。

2 · Worker をデプロイする

Deploy to Cloudflare

このボタンは、リポジトリをあなたの GitHub アカウントにコピーし、KV 名前空間と Durable Object を作成し、4 つのシークレットを要求します。workers.dev にデプロイされ、カスタム ドメインは後から Settings → Domains & Routes で追加します。

ターミナルからは、次のようにします。

git clone https://github.com/mkpoli/gmail-mcp && cd gmail-mcp
bun install
bun run setup

bun run setup は、応答するドメインを尋ね、OAUTH_KV 名前空間を作成または再利用し、クライアント ID とシークレットを受け取り、Cookie キーを生成してデプロイします。最初の 2 つの答えは wrangler.local.jsonc に保存され、これは git から無視されます。wrangler.jsonc にはどのアカウントの名前空間も誰のドメインも入っていないため、クローンでどこにでもデプロイできます。1 つのシークレットを入れ替えるために setup を再実行しても安全です。

3 · クライアントを接続する

クライアント ID とクライアント シークレットは空のままにします。MCP クライアントは自分自身を登録するためです。

claude mcp add --transport http gmail-personal https://<your-host>/mcp
claude mcp add --transport http gmail-work     https://<your-host>/mcp/work

Claude Code で /mcp を実行すると、各接続を対応する Google アカウントにサインインできます。Claude.ai では Settings → Connectors → Add custom connector に同じ URL を設定します。/mcp/ の後には任意の単一セグメントのラベルが使えます。これは、URL を共有する 2 つのサーバーを受け付けないクライアントに、1 つのデプロイが複数のメールボックスを提供する仕組みです。

このガイドは https://<your-host>/ で公開されています。


できること

whoami search_messages get_message get_thread get_attachment

send_message reply_all forward_message create_draft update_draft send_draft delete_draft list_drafts stage_attachment_begin stage_attachment_append stage_attachment_finish

list_labels create_label update_label delete_label modify_labels modify_thread_labels batch_modify_messages trash_message · untrash_message trash_thread · untrash_thread

メッセージは、メールクライアントが送るのと同じ形で送信されます。プレーンテキストと HTML の代替版、ファイル添付、cid: で参照されるインライン画像を、multipart/mixed › multipart/related › multipart/alternative として入れ子にします。件名と表示名は RFC 2047、ファイル名は RFC 2231 に従うため、日本語、中国語、絵文字もそのまま送れます。

reply_all は元メールの Reply-ToFromToCc を読み取り、自分のアドレスとメール送信に使うアドレスを除外し、差出人が宛てたアドレスから返信します。References チェーンを引き継ぎ、送信するパートに元メールを引用します。forward_message は転送のエンベロープを再現し、元のファイルを再添付できます。

create_draftreplyToMessageId と使うと、送信前に編集できる下書きとして返信を作成します。元のスレッドに参加し、In-Reply-ToReferences を引き渡し、全員返信用の宛先と件名に Re: を付け、元メールを引用します。update_draft は渡されたフィールドだけを変更し、受信者、本文、任意のクライアントで手動で追加されたファイル、下書きが返信するスレッドを読み戻して保持します。base64 がツールの引数に収まらないファイルは、ステージングされます。stage_attachment_begin は、生バイトを curl -T で一度に送れるアップロード URL を返し、stage_attachment_append は base64 をチャンクで受け取り、すべての attachments フィールドが生成された stagingId を受け付けます。

読み取りには意図的に上限があります。メッセージとスレッド本文には文字数、レスポンス全体にはバイト数の上限があり、添付ファイルは十分に小さい場合のみインラインで返されます。長いメーリングリストのスレッドや大きいファイルは、アシスタントのコンテキストを圧迫するのではなく、その旨の注記付きで切り詰められて返されます。


仕組み

2 つの OAuth フローが 1 つの Worker 内で合流します。MCP クライアントは Worker に対して認証し、Worker はユーザーに代わって Google に対して認証します。どちら側も相手の資格情報を保持しません。

sequenceDiagram
    autonumber
    participant C as MCP client<br/>(Claude Code · claude.ai)
    participant W as Worker<br/>(OAuthProvider + McpAgent)
    participant G as Google<br/>(OAuth + Gmail API)

    C->>W: POST /register (dynamic client registration)
    C->>W: GET /authorize (PKCE challenge)
    W->>C: approval dialog
    C->>G: consent screen — pick the account
    G->>W: GET /callback?code=…
    W->>W: allowlist check on the verified email
    W->>G: exchange code → access + refresh token
    W->>C: MCP access token (Google tokens sealed inside the grant)
    C->>W: POST /mcp — tools/call
    W->>G: Gmail REST (token refreshed as needed)
    G->>W: message / thread / label data
    W->>C: tool result

役割

ファイル

動作

🔐 MCP 側 OAuth

workers-oauth-provider

動的クライアント登録、PKCE、Google トークンを封入した KV 内のグラント

🔗 Google 側 OAuth

src/google-handler.ts

オフラインアクセス用の認可コード、ブラウザセッションに結び付けた使い捨ての state、二重送信 CSRF、確認済みメアドの許可リスト

🤖 エージェント

src/index.ts

MCP セッションごとに Durable Object。開いたアカウントに結び付け、シングルフライトのトークン更新、スロットル付きファンアウト

✉️ メール

src/gmail.ts

RFC 822 の構築、MIME ツリー走査、文字コード復号、返信・転送の作成

使っているもの

  • TypeScriptCloudflare Workers — Durable Objects が MCP セッションを 1 つずつ保持し、KV が OAuth グラントを保存します

  • Hono — OAuth エンドポイント、Google コールバック、/ のセットアップページのルーティング

  • @cloudflare/workers-oauth-provider — MCP クライアントが登録する OAuth 2.1 サーバー

  • agents — Durable Objects 上の MCP トランスポート, McpAgent

  • @modelcontextprotocol/sdkZod — ツール定義と引数検証

  • Bun, Biome, Wrangler — インストール、テスト、リント、デプロイ

Gmail 自体は REST API に素の fetch で呼び出します。公式 googleapis SDK は Node 前提で、Worker として配信するには大きすぎるため、メッセージ構築・MIME の解析・トークン更新は src/gmail.tssrc/utils.ts に実装しています。

エンドポイント

Path

用途

/mcp

MCP エンドポイント

/mcp/<label>

任意の単一セグメントのラベルで同じサーバーを提供。URL を共有する 2 つのサーバーを受け付けないクライアントに対応

/

このセットアップガイド

/authorize · /token · /register · /callback

OAuth の仕組み

サインインできるユーザー

ALLOWED_EMAILS で決定し、Google が確認済みとして報告するアドレスと照合されます。同意後、グラントが存在する前に行われます。

入れるのは

(空)

だれも

you@gmail.com, work@company.com

そのアカウントのみ

*@company.com

そのドメインの任意のアカウント

*

確認済みのすべての Google アカウント

各グラントは、認証したメールボックスにしか到達できません。したがって、このリストを広げても、既に接続されているメールボックスへのアクセスが広がるわけではありません。* を設定すると、外部の人があなたのデプロイとあなたの Google クライアントの使用枠を、自分のメールのために消費できるようになります。

上限

共有デプロイを枯渇させないための 2 つの上限があり、どちらも wrangler.jsonc で設定されています。

設定

場所

デフォルト

説明

MAX_ACCOUNTS

vars

25

サインインを完了できる異なる Google アカウントの目安。上限に達しても接続済みのアカウントは動作し続け、新規は拒否されます。同時にサインインされた場合、記録前に各サインイン count を読むため、総数はこの値を少し超えることがあります。Google は未確認アプリを 100 ユーザーに制限しているため、その範囲を下回るようにします。

RATE_LIMITER.simple.limit

unsafe.bindings

120 per 60s

そのウィンドウ内で、1 アカウントが全体のセッションを合わせて Gmail を呼び出せる回数。Cloudflare はこの数をロケーションごとに保持するので、2 つの地域から接続する場合、それぞれの地域でほぼその回数になります。広範囲の読み取りは複数消費します。search_messages が 50 件を返すと 51 回の呼び出しになります。

REGISTER_LIMITER.simple.limit

unsafe.bindings

10 per 60s

1 つのアドレスがそのウィンドウ内で行えるクライアント登録数。クライアントは 1 回登録し、付与された ID を保持し続けるため、通常の使用ではこの値に近づくことはありません。この上限があるのは、登録には資格情報が不要で、登録のたびに KV へ書き込まれるからです。

Workers Free プランではさらに上限が適用されます。呼び出し1回につき50件の送信リクエストです。広範囲の読み取りはメッセージごとに1回消費するため、search_messageslist_drafts では、そこでは maxResults を45以下に設定する必要があります。それを超えると、超過分は結果ではなくメッセージごとのエラーとして返されます。有料プランでは1000件まで許可されます。

どちらかを引き上げて再デプロイしてください。Cloudflare のレートリミッターはビルド時にバインディングから上限を読み取るため、各々の simple.limit がそれを変更できる唯一の場所です。シングルユーザーのデプロイでは、両方ともそのままにしておけます。通常のアシスタント利用は、それらをはるかに下回るからです。


セキュリティ

セルフホスティングは信頼の問題を移すだけで、取り除くわけではありません。そこで、すべてがどこにあるのかを以下に示します。

  • トークンはあなたのもののままです。 リフレッシュトークンは、あなたの KV 名前空間内の OAuth グラント内で暗号化されています。セッションの Durable Object は、1時間有効なアクセストークンを保持し、MCP エージェントフレームワークは、オブジェクトが存続する間、リフレッシュトークンを含むグラントのコピーをそこに保持します。両方のストアはあなた自身の Cloudflare アカウントであり、保存時に暗号化されています。メールは保存されません。通過するだけです。

  • 1セッション、1メールボックス。 MCP セッションはそれを開いたアカウントにバインドされるため、あるメールボックスのグラントは、借用したセッション ID を通じて別のメールボックスに作用することはできません。

  • スコープの最小化。 gmail.modify は、読み取り、送信、ラベル、ゴミ箱をカバーします。完全な削除と gmail.settings.* のすべてを除外し、自動転送ルールとフィルターの外部持ち出し(典型的なメールボックスのバックドア)を、盗まれたグラントができることの範囲外に置きます。読み取り専用のスコープが2つ、それに加えて要求されます。userinfo.emailuserinfo.profile です。これらは、許可リストとセッションバインディングがどのアカウントがサインインしたかを知る方法であり、メールには到達しません。

  • ヘッダーは密輸できません。 送信されるすべてのヘッダー値は、CR、LF、または NUL を含む場合に拒否されるため、引数が自身のフィールドから抜け出して別のフィールドを追加することはできません。たとえば、件名内の Bcc などです。メディアタイプは検証され、引用された履歴は HTML エスケープされます。これが行わないのは、引数自体を監視することです。bcc は実際のパラメータであるため、メッセージ本文に隠された指示に基づいて行動するモデルがそれを埋める可能性があり、クライアントの承認プロンプトがそのチェックとして残ります。

  • アクセスは取り消せます。 ALLOWED_EMAILS を狭めると、新しいサインインが停止します。単一のアカウントのアクセスは、myaccount.google.com/connections で取り消されます。Google クライアントシークレットをローテーションすると、すべてのグラントが一度に無効になります。

Worker は、リクエストの処理中にメールをメモリ内で復号します。これは、ホスト型リレーが行わなければならないことです。特定のメールボックスにとってそれが受け入れられない場合は、そのメールボックス用にローカル MCP サーバーを実行してください。


テスト方法

253のユニットテストが、メッセージ構築(MIME ネスト、RFC 2047 折り返し、RFC 2231 ファイル名、CR/LF 拒否、base64 ラッピング)、文字セットをまたぐ本文抽出、返信と転送の構成、Google トークンフロー、サインイン許可リスト、サインインのブラウザ側を保護する CSRF とステートバインディングチェック、そして Gmail の代役に対するツール自体(セッション所有権、受信者構成、添付ファイル選択、部分的に失敗した読み取りが返すもの)をカバーしています。

それに加えて、すべてのツールが実際の Gmail アカウントに対して実行され、別のアカウントが届いたものを確認しました。

領域

結果

エンコーディング

日本語の件名がエンコードされた単語をまたいで折り返されました。絵文字、ZWJ シーケンス、RTL アラビア語、結合マーク、まれな CJK が変更されずに往復しました

添付ファイル

請求書.csv という名前の CSV が送信され、配信され、バイト単位で同一のものとしてダウンロードされました。インラインの cid: 画像が受信者によってレンダリングされました

スレッド

reply_all は送信者に宛て、第三者 Cc を保持し、自身のアドレスを削除し、同じスレッド内で元のメッセージを引用しました

2つのアカウント

両方が同時に1つのデプロイに接続されました。一方からのメッセージ ID は、他方では 404 を返しました

整理

ネストされた CJK ラベルが作成、名前変更、バッチ適用、削除されました。スレッドとメッセージのゴミ箱は両方とも元に戻りました

スケール

15,000メッセージのメールボックスが、Gmail 演算子とページネーションを使用して、レート制限に引っかからずに検索されました


開発

bun run dev     # wrangler dev on :8788
bun run check   # biome + tsc
bun test        # 253 unit tests
bun run assets  # regenerate the light and dark diagrams
bun run deploy

質問とバグ

issue を開いてください。


ライセンス

Copyright © 2026 mkpoli。MIT License の下で公開されています。

src/workers-oauth-utils.ts は、cloudflare/ai 内の remote-mcp-github-oauth demo から派生しており、Copyright © 2025 Cloudflare, Inc.、MIT License の下で使用されています。THIRD-PARTY.md を参照してください。

-
license - not tested
Not graded
quality - not tested
C
maintenance

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Bloody-Regina/personal-gmail-mcp-bloodyregina'

If you have feedback or need assistance with the MCP directory API, please join our Discord server