A secure, enterprise-centric MCP server for Gmail that mirrors the official MCP toolset by Google, enabling AI agents to impersonate corporate Gmail users in domain-wide delegation (DWD) scenarios. Assumes a service account, DWD and Workload Identity Federation (WIF) have been set up.
This tool is used by Vincent, a SAS agent running on AWS Bedrock AgentCore.
We kept same names and schemas as Google's official MCP (reference), and added a required email parameter to each tool to set a corporate Gmail user to impersonate:
| Tool | Description |
|---|---|
search_threads |
List threads with optional Gmail query |
get_thread |
Fetch one normalized thread (LLM-shaped messages) |
get_threads |
Fetch multiple normalized threads |
list_drafts |
List drafts |
create_draft |
Create a draft |
list_labels |
List user labels |
create_label |
Create a label |
label_message |
Add labels to a message |
unlabel_message |
Remove labels from a message |
label_thread |
Add labels to a thread |
unlabel_thread |
Remove labels from a thread |
Read tools use typed Pydantic return values so MCP tools/list includes a real outputSchema (field names and types), not dict[str, Any]. Clients that pass outputSchema to the model improve tool selection and parsing.
Use two response shapes:
| Tool | Shape | Message text |
|---|---|---|
search_threads |
SearchThread | Thread id + list snippet — no messages, no body |
get_thread / get_threads |
HydratedThread | Normalized plain text in body only |
Write tools (create_draft, labels, etc.) keep their own schemas and are unrelated to the read shapes above.
search_threads — discover threads (one threads.list call; snippet per row, no per-thread fetches):
Request:
{
"name": "search_threads",
"arguments": {
"email": "user@company.com",
"query": "is:unread",
"pageSize": 20
}
}Response (shape):
{
"threads": [
{ "id": "thread-xyz", "snippet": "Thanks for the update…" },
{ "id": "thread-abc", "snippet": "Meeting tomorrow at 3pm" }
],
"nextPageToken": "..."
}get_thread — hydrate one thread for reading:
Request:
{
"name": "get_thread",
"arguments": {
"email": "user@company.com",
"threadId": "thread-xyz",
"messageLimit": 20,
"maxBodyChars": 16000,
"stripQuotedContent": true
}
}Response (shape):
{
"id": "thread-xyz",
"messages": [
{
"id": "msg-abc",
"subject": "Re: Project status",
"sender": "colleague@company.com",
"toRecipients": ["user@company.com"],
"ccRecipients": [],
"date": "Mon, 12 May 2025 14:30:00 +0000",
"body": "Thanks for the update. I'll review today.",
"attachmentIds": ["att-001"],
"omittedFromThread": false
}
]
}get_threads — batch hydrate with partial success:
Request:
{
"name": "get_threads",
"arguments": {
"email": "user@company.com",
"threadIds": ["thread-xyz", "thread-missing"]
}
}Response (shape):
{
"threads": [ { "id": "thread-xyz", "messages": [ { "id": "msg-abc", "body": "…" } ] } ],
"errors": [
{
"threadId": "thread-missing",
"message": "Thread not found: thread-missing",
"code": "NOT_FOUND"
}
],
"meta": {
"requestedCount": 2,
"successCount": 1,
"errorCount": 1,
"gmailApiCalls": 2,
"quotaUnitsEstimated": 80,
"totalChars": 42,
"truncated": false
}
}- Google Cloud: Service account with Gmail API enabled.
- Google Cloud: Note that
gmailmcp.googleapis.comAPI doesn't have to be enabled. - Workspace admin: Domain-wide delegation for that SA with scopes
https://www.googleapis.com/auth/gmail.modifyandhttps://www.googleapis.com/auth/gmail.settings.basic(orhttps://mail.google.com/). - AWS: WIF external-account JSON (or service account JSON) stored in SSM Parameter Store (SecureString recommended).
- Runtime IAM: Permission to
ssm:GetParameteron the WIF and (if used) allowed-hosts parameters.
Store the Google credential config JSON. Supported type values:
external_account— Workload Identity Federation (recommended on AWS).service_account— classic SA key JSON (if your org still uses keys).
Example (WIF): the JSON downloaded when configuring an AWS workload identity pool provider for your GCP service account.
| Variable | Required | Description |
|---|---|---|
GCP_WIF_CREDENTIAL_CONFIG_SSM_PARAMETER |
Yes | SSM parameter name for the WIF/SA JSON |
AWS_REGION |
No | AWS region for SSM (uses default chain if unset) |
GMAIL_WIF_CACHE_TTL_SECONDS |
No | In-memory cache TTL for SSM parameters (default 3600; 0 = cache until process exit) |
MCP_TRANSPORT |
No | stdio (default), sse, or streamable-http (set in Docker image) |
FASTMCP_HOST / FASTMCP_PORT |
No | HTTP bind address / port (default 127.0.0.1 / 8000; Docker uses 0.0.0.0) |
Standard AWS credential chain applies (AWS_ACCESS_KEY_ID, instance role, etc.).
This MCP server is intended to run on Amazon Bedrock AgentCore Runtime as a custom container (0.0.0.0:8000/mcp, stateless streamable HTTP). That is not an ECS task with an ADOT Collector sidecar on localhost:4317.
The server uses the ADOT Python SDK with in-process auto-instrumentation (Starlette/ASGI, botocore, outbound HTTP) plus custom spans per MCP tool and Gmail API call.
On AgentCore Runtime there is no local OTLP collector. With AGENT_OBSERVABILITY_ENABLED=true, the ADOT aws_distro configures collector-less export to regional AWS OTLP endpoints (using the runtime task role and AWS_REGION):
- Traces →
https://xray.<region>.amazonaws.com/v1/traces - Logs →
https://logs.<region>.amazonaws.com/v1/logs
The Docker image sets this mode by default. Do not set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 on AgentCore — it overrides ADOT’s auto-configuration and spans will not reach CloudWatch / X-Ray.
Prerequisites (once per account): enable CloudWatch Transaction Search and enable Tracing on your AgentCore runtime in the console (spans appear in the aws/spans log group and GenAI Observability).
Optional correlation with runtime logs (replace <agent-id> with your runtime id):
export OTEL_RESOURCE_ATTRIBUTES=service.name=gmail-dwd-mcp-server,aws.log.group.names=/aws/bedrock-agentcore/runtimes/<agent-id>
export OTEL_EXPORTER_OTLP_LOGS_HEADERS=x-aws-log-group=/aws/bedrock-agentcore/runtimes/<agent-id>,x-aws-log-stream=runtime-logs,x-aws-metric-namespace=bedrock-agentcoreFor distributed traces with the invoking agent, propagate X-Amzn-Bedrock-AgentCore-Runtime-Session-Id on MCP requests (ADOT maps this for session correlation). See AgentCore observability.
| Variable | AgentCore default | Description |
|---|---|---|
AGENT_OBSERVABILITY_ENABLED |
true (Docker) |
Enables ADOT regional OTLP export (not localhost collector) |
OTEL_SERVICE_NAME |
gmail-dwd-mcp-server |
Service name in traces / GenAI Observability |
OTEL_PYTHON_DISTRO |
aws_distro |
ADOT defaults |
OTEL_PYTHON_CONFIGURATOR |
aws_configurator |
ADOT SDK wiring |
OTEL_PROPAGATORS |
xray |
X-Ray trace context propagation |
OTEL_EXPORTER_OTLP_PROTOCOL |
http/protobuf |
Required for AWS OTLP endpoints |
OTEL_AWS_APPLICATION_SIGNALS_ENABLED |
false |
Keep off unless using Application Signals |
Telemetry is off unless AGENT_OBSERVABILITY_ENABLED=true (set in the Docker image for AgentCore; omit locally). To disable in AWS, unset the variable or set AGENT_OBSERVABILITY_ENABLED=false on the runtime.
Only if you run outside AgentCore with a collector sidecar, point OTLP at the collector (gRPC is typical):
unset AGENT_OBSERVABILITY_ENABLED
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpcunset AGENT_OBSERVABILITY_ENABLED
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://xray.us-east-1.amazonaws.com/v1/traces
export OTEL_RESOURCE_ATTRIBUTES=service.name=gmail-dwd-mcp-serverdocker build -t gmail-dwd-mcp-server .
docker run --rm -p 8000:8000 \
-e GCP_WIF_CREDENTIAL_CONFIG_SSM_PARAMETER=/your/org/gmail-wif-config \
-e AWS_REGION=us-east-1 \
gmail-dwd-mcp-serverThe image uses python:3.14-slim, listens on port 8000, and runs Streamable HTTP at /mcp (MCP_TRANSPORT=streamable-http). Provide AWS credentials (task role, env keys, etc.) so the container can read SSM.
cd gmail-dwd-mcp-server
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
export GMAIL_WIF_SSM_PARAMETER=/your/org/gmail-wif-config
export AWS_REGION=us-east-1
# stdio (Cursor, Claude Desktop, etc.)
gmail-dwd-mcp
# or
python -m gmail_dwd_mcp{
"mcpServers": {
"gmail-dwd": {
"command": "/path/to/.venv/bin/gmail-dwd-mcp",
"env": {
"GMAIL_WIF_SSM_PARAMETER": "/your/org/gmail-wif-config",
"AWS_REGION": "us-east-1"
}
}
}
}MIT
Built with ❤️ and AI in the District of Columbia