Skip to content

singulart/gmail-dwd-mcp-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Enterprise Gmail MCP Server

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.

Tools

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.

Read tool examples

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
  }
}

Prerequisites

  1. Google Cloud: Service account with Gmail API enabled.
  2. Google Cloud: Note that gmailmcp.googleapis.com API doesn't have to be enabled.
  3. Workspace admin: Domain-wide delegation for that SA with scopes https://www.googleapis.com/auth/gmail.modify and https://www.googleapis.com/auth/gmail.settings.basic (or https://mail.google.com/).
  4. AWS: WIF external-account JSON (or service account JSON) stored in SSM Parameter Store (SecureString recommended).
  5. Runtime IAM: Permission to ssm:GetParameter on the WIF and (if used) allowed-hosts parameters.

SSM parameter value

Store the Google credential config JSON. Supported type values:

  • external_accountWorkload 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.

Configuration

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.).

Observability (OpenTelemetry / ADOT on AgentCore Runtime)

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.

AgentCore Runtime (recommended)

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-agentcore

For 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.

Common environment variables

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.

Local dev or ECS with an ADOT Collector

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=grpc

Manual X-Ray endpoint (non-AgentCore)

unset 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-server

Docker

docker 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-server

The 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.

Install & run

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

Cursor MCP config

{
  "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"
      }
    }
  }
}

License

MIT

Credits

Built with ❤️ and AI in the District of Columbia

About

A secure, enterprise-centric MCP server for Gmail

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors