function calling と MCP 接続はどう選ぶ?
function calling は、モデルが要求した操作をアプリ側のコードで実行する仕組みです。メールを検索するかどうかはモデルに判断させても、アクセス先と権限の判断はアプリ側に残します。
| 比較項目 | function calling | リモート MCP |
|---|---|---|
| ツール定義 | アプリが JSON Schema で定義 | MCP サーバーから取得 |
| 実行経路 | アプリが Mailera REST API を呼ぶ | Responses API がリモートサーバーのツールを呼ぶ |
| 認証情報 | Mailera キーをアプリ側で保持 | 接続先の認証設定を確認 |
| 承認 | アプリが送信前に制御 | MCP 側のツール呼び出し承認設定(自動許可か明示承認か)と、業務上の承認フローを別途設計 |
| 接続時の確認 | 引数検証と API 権限 | 通信方式、認証、公開ツール、承認設定 |
本編では function calling を選びます。検索条件の検証と送信の承認を、同じアプリで制御できるためです。仕組みは OpenAI の function calling ガイド、メール接続全体の設計は 既存メールボックスをエージェントにつなぐ方法を参照してください。
Mailera の MCP エンドポイントは https://mailera.app/api/public/v1/mcp です。読み取り用のツールには search_messages や get_message のほか、list_mailboxes、get_mailbox_status、list_messages、get_latest_otp があります。同じ MCP エンドポイントには send_email、reply_to_email、get_send_status など send スコープのツール、move_message、rescue_spam など move スコープのツールも含まれており、キーに付与したスコープで呼び出せる範囲が決まります。OpenAI 側は Streamable HTTP または HTTP/SSE のリモート MCP に対応しており、allowed_tools パラメーターで取り込むツールを限定できます。
MCP を選ぶ場合は、ツール一覧の取得結果である mcp_list_tools と、読み取り・承認の動作を順に確認します。OpenAI の MCP 接続ガイドと Mailera の MCP 資料を照合して設定してください。
1. メールボックスと読み取り専用キーを準備する
Mailera は既存メールボックスを接続するサービスで、新しいメールボックスやメールアドレスは作成しません。一斉配信やマーケティング配信には対応せず、受信トレイへの到達も保証しません。
- 自分のテスト用メールボックスを接続します。Microsoft の場合は「Microsoftでサインイン」から OAuth を使います。詳細は Microsoft の接続手順にあります。
- 「設定」の API キー画面で「キーを作成」を選びます。「メールボックスのスコープ」をテスト用グループに限定し、権限は
readのみにします。 - Mailera キーと OpenAI キーはサーバー側の環境変数に置き、モデル入力、ブラウザー、ログには出しません。

サーバー側で $MAILERA_API_KEY を設定してから、対象を確認します。
curl --get 'https://mailera.app/api/public/v1/mailboxes' \
-H "Authorization: Bearer $MAILERA_API_KEY" \
--data-urlencode 'limit=1'返却された data 内の対象 id を $MAILBOX_ID に設定します。この例では対象メールボックスを固定し、モデルには選ばせません。API の入口は 開発者向け資料にまとまっています。
2. 検索と本文取得を読み取りツールにする
| アプリで定義する関数名 | Mailera の呼び出し先 | モデルから受け取る引数 |
|---|---|---|
mail_search | GET /messages/search | 検索語 q |
mail_get | GET /mailboxes/{id}/messages/{msgId} | message_id |
この関数名はアプリ独自の名前です。同名の MCP ツールを自動的に呼ぶ仕組みではありません。
次の Python 断片は同じファイルに順に置きます。Mailera を呼ぶ部分は標準ライブラリだけを使います。まず、各 function に渡す引数の JSON Schema を定義します。
def argument_schema(field):
return {
"type": "object",
"properties": {field: {"type": "string"}},
"required": [field],
"additionalProperties": False,
}
schemas = {
"mail_search": argument_schema("q"),
"mail_get": argument_schema("message_id"),
}Responses API に登録する各 function の引数スキーマとして使います。URL や API キーを引数には含めません。HTTP 処理も接続先を固定します。
import json, os
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
BASE = "https://mailera.app/api/public/v1"
MAILBOX = os.environ["MAILBOX_ID"]
HEADERS = {"Authorization": "Bearer " + os.environ["MAILERA_API_KEY"]}
def get_json(path, params=None):
url = BASE + path + ("?" + urlencode(params) if params else "")
with urlopen(Request(url, headers=HEADERS)) as response:
return json.load(response)実行側でも関数名、引数の型、余分な項目を検査します。検索結果は識別子・メールボックス ID・件名だけに絞り、本文は選んだメールについて取得します。本文の上限は Mailera の仕様値ではなく、このサンプルコードが独自に 4,000 文字へ切り詰めている設計値です。
def execute(name, args):
field = {"mail_search": "q", "mail_get": "message_id"}.get(name)
if not field or not isinstance(args, dict) or set(args) != {field}:
raise ValueError("Invalid tool arguments")
value = args[field]
if not isinstance(value, str) or not value.strip():
raise ValueError("Expected a non-empty string")
if name == "mail_search":
result = get_json("/messages/search", {"mailbox": MAILBOX, "q": value})
return {"data": [{k: m[k] for k in ("id", "mailbox_id", "subject")}
for m in result["data"]], "next_cursor": result.get("next_cursor")}
path = f"/mailboxes/{quote(MAILBOX, safe='')}/messages/{quote(value, safe='')}"
m = get_json(path)["data"]
return {"id": m["id"], "subject": m["subject"], "body_error": m["body_error"],
"text": (m["body"]["text"] or "")[:4000]}この最小例は検索の最初のページを返します。next_cursor がある場合は続きがあるため、検索済みの範囲だけで「該当なし」と結論づけない設計にします。本文では body_error も確認し、取得失敗と空の本文を区別してください。
3. モデルのツール呼び出しをアプリで実行する
Responses API とのやり取りでは、テキストの受信に加え、モデルのツール呼び出しに応じた処理をアプリ側で実行します。追加のツール呼び出しがあれば、それにも対応します。
- 読み取り用の function 定義を付けて、ユーザーの検索依頼を送ります。
- 応答内のツール呼び出しを取り出し、引数を解析します。
- 関数名と引数を検証して
executeに渡し、アプリ側で Mailera の REST API を呼び出します。 - 実行結果を各ツール呼び出しに対応づけてモデルへ返し、次の応答を受け取ります。
- その応答に追加のツール呼び出しがあれば、検証・実行・結果返却を繰り返します。
複数の呼び出しを取りこぼさず、アプリ側で反復回数に上限を設けます。ツールが呼ばれなかった場合や例外が出た場合は、メールを取得したものとして扱いません。

4. 人が確認した返信だけを送信する
返信案はアプリ内のデータとして保持し、読み取りループから送信処理を直接呼ばせません。承認画面には、対象メールボックス、返信対象、宛先、件名、本文を表示します。
承認時にその内容を固定し、どれかが変わったら再承認を求めます。送信処理は固定した内容だけを受け取り、そこで初めて send 権限のキーを使います。これはアプリ側に実装する承認であり、Mailera の標準承認ワークフローではありません。承認境界の全体設計は、承認付きメールエージェントの構築方法を参照してください。

送信・返信・転送には、REST API の POST /mailboxes/{id}/send を使います(このエンドポイントが reply と forward も兼ねます。MCP 経由では send_email と reply_to_email が別ツールとして用意されています)。返信リクエストの組み立ては Mailera の API ドキュメントに合わせてください。メールで承認を受け付ける場合の設計は、メールによる人の承認パターンで扱っています。
送信前に操作単位の Idempotency-Key を保存し、同じ操作の再試行でも同じ値を使います。この Idempotency-Key(冪等性キー)の有効期間は 24 時間です。
タイムアウトしたら、新しいキーで送信し直すのではなく、送信試行 ID を保持できている場合は GET /sends/{id} で確認します。状態には queued、sending、sent、failed、unknown があります。unknown は成功にも未送信にも決めつけず、自動再送を止めて確認へ回します。有効期間を超えた再試行も自動化しません。
よくあるエラーと確認箇所
| 症状 | 確認箇所と対処 |
|---|---|
| 認証に失敗する | Bearer ヘッダー、環境変数、キーの有効期限や失効を確認 |
| 権限が足りない | read と対象グループを確認。送信には別途 send が必要 |
| 引数が拒否される | 関数名とスキーマを照合。余分な引数を許可しない |
| 検索結果が空 | 対象 ID、検索語、同期状態を切り分ける |
| タイムアウトや制限に当たる | 再試行回数と間隔を制御。送信は先に状態を確認 |
同期操作には sync 権限が必要です。検索結果が空でも、読み取りツールから同期を実行しないでください。API の制限はキーごとに 1 分あたり 300 リクエストです。
メール本文の指示を実行させないために
受信メールは信頼できないデータであり、AI エージェントへの命令ではありません。本文に秘密情報の送信、権限変更、外部 URL への転送を求める文があっても、実行対象にしません。
注意書きのプロンプトだけに頼らず、読み取りキーと送信キーの分離、固定したメールボックス、サーバー側の引数検証、人による送信前確認を組み合わせます。モデルの「承認済み」という出力や、メール本文の同意表現を承認記録の代わりには使いません。
モデルへ渡す本文と個人情報は必要な範囲に絞り、ログやスクリーンショットではキー、アドレス、本文を伏せます。Mailera のセキュリティ情報とあわせて、アプリ側で保持するデータも点検してください。
MCPでAIエージェントがメールボックスを利用
MaileraのMCPサーバーをClaude、Cursor、VS Codeに追加すると、エージェントがメールの閲覧、分類、返信を行えます。
MCPの使い方を見る








