#
tokens: 35333/50000 16/17 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/markuspfundstein/mcp-gsuite?page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── .python-version
├── Dockerfile
├── gmail-api-openapi-spec.yaml
├── gmail.v1.json
├── google-calendar-api-openapi-spec.yaml
├── LICENSE
├── pyproject.toml
├── README.md
├── smithery.yaml
├── src
│   └── mcp_gsuite
│       ├── __init__.py
│       ├── calendar.py
│       ├── gauth.py
│       ├── gmail.py
│       ├── server.py
│       ├── toolhandler.py
│       ├── tools_calendar.py
│       └── tools_gmail.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
3.13

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv
.env
.gauth.json
oauth2creds.json
.accounts.json
.oauth2.*.json

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# mcp-gsuite MCP server

[![smithery badge](https://smithery.ai/badge/mcp-gsuite)](https://smithery.ai/server/mcp-gsuite)
MCP server to interact with Google products.

## Example prompts

Right now, this MCP server supports Gmail and Calendar integration with the following capabilities:

1. General
* Multiple google accounts

2. Gmail
* Get your Gmail user information
* Query emails with flexible search (e.g., unread, from specific senders, date ranges, with attachments)
* Retrieve complete email content by ID
* Create new draft emails with recipients, subject, body and CC options
* Delete draft emails
* Reply to existing emails (can either send immediately or save as draft)
* Retrieve multiple emails at once by their IDs.
* Save multiple attachments from emails to your local system.

3. Calendar
* Manage multiple calendars
* Get calendar events within specified time ranges
* Create calendar events with:
  + Title, start/end times
  + Optional location and description
  + Optional attendees
  + Custom timezone support
  + Notification preferences
* Delete calendar events

Example prompts you can try:

* Retrieve my latest unread messages
* Search my emails from the Scrum Master
* Retrieve all emails from accounting
* Take the email about ABC and summarize it
* Write a nice response to Alice's last email and upload a draft.
* Reply to Bob's email with a Thank you note. Store it as draft

* What do I have on my agenda tomorrow?
* Check my private account's Family agenda for next week
* I need to plan an event with Tim for 2hrs next week. Suggest some time slots.

## Quickstart

### Install

### Installing via Smithery

To install mcp-gsuite for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-gsuite):

```bash
npx -y @smithery/cli install mcp-gsuite --client claude
```

#### Oauth 2

Google Workspace (G Suite) APIs require OAuth2 authorization. Follow these steps to set up authentication:

1. Create OAuth2 Credentials:
   - Go to the [Google Cloud Console](https://console.cloud.google.com/)
   - Create a new project or select an existing one
   - Enable the Gmail API and Google Calendar API for your project
   - Go to "Credentials" → "Create Credentials" → "OAuth client ID"
   - Select "Desktop app" or "Web application" as the application type
   - Configure the OAuth consent screen with required information
   - Add authorized redirect URIs (include `http://localhost:4100/code` for local development)

2. Required OAuth2 Scopes:
   

```json
   [
     "openid",
     "https://mail.google.com/",
     "https://www.googleapis.com/auth/calendar",
     "https://www.googleapis.com/auth/userinfo.email"
   ]
```

3. Then create a `.gauth.json` in your working directory with client

```json
{
    "web": {
        "client_id": "$your_client_id",
        "client_secret": "$your_client_secret",
        "redirect_uris": ["http://localhost:4100/code"],
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://oauth2.googleapis.com/token"
    }
}
```

4. Create a `.accounts.json` file with account information

```json
{
    "accounts": [
        {
            "email": "[email protected]",
            "account_type": "personal",
            "extra_info": "Additional info that you want to tell Claude: E.g. 'Contains Family Calendar'"
        }
    ]
}
```

You can specifiy multiple accounts. Make sure they have access in your Google Auth app. The `extra_info` field is especially interesting as you can add info here that you want to tell the AI about the account (e.g. whether it has a specific agenda)

Note: When you first execute one of the tools for a specific account, a browser will open, redirect you to Google and ask for your credentials, scope, etc. After a successful login, it stores the credentials in a local file called `.oauth.{email}.json` . Once you are authorized, the refresh token will be used.

#### Claude Desktop

On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`

On Windows: `%APPDATA%/Claude/claude_desktop_config.json`

<details>
  <summary>Development/Unpublished Servers Configuration</summary>
  

```json
{
  "mcpServers": {
    "mcp-gsuite": {
      "command": "uv",
      "args": [
        "--directory",
        "<dir_to>/mcp-gsuite",
        "run",
        "mcp-gsuite"
      ]
    }
  }
}
```


Note: You can also use the `uv run mcp-gsuite --accounts-file /path/to/custom/.accounts.json` to specify a different accounts file or `--credentials-dir /path/to/custom/credentials` to specify a different credentials directory.

```json
{
  "mcpServers": {
    "mcp-gsuite": {
      "command": "uv",
      "args": [
        "--directory",
        "<dir_to>/mcp-gsuite",
        "run",
        "mcp-gsuite",
        "--accounts-file",
        "/path/to/custom/.accounts.json",
        "--credentials-dir",
        "/path/to/custom/credentials"
      ]
    }
  }
}
```

</details>

<details>
  <summary>Published Servers Configuration</summary>
  

```json
{
  "mcpServers": {
    "mcp-gsuite": {
      "command": "uvx",
      "args": [
        "mcp-gsuite",
        "--accounts-file",
        "/path/to/custom/.accounts.json",
        "--credentials-dir",
        "/path/to/custom/credentials"
      ]
    }
  }
}
```

</details>

### Configuration Options

The MCP server can be configured with several command-line options to specify custom paths for authentication and account information:

* `--gauth-file`: Specifies the path to the `.gauth.json` file containing OAuth2 client configuration. Default is `./.gauth.json`.
* `--accounts-file`: Specifies the path to the `.accounts.json` file containing information about the Google accounts. Default is `./.accounts.json`.
* `--credentials-dir`: Specifies the directory where OAuth credentials are stored after successful authentication. Default is the current working directory with a subdirectory for each account as `.oauth.{email}.json`.

These options allow for flexibility in managing different environments or multiple sets of credentials and accounts, especially useful in development and testing scenarios.

Example usage:

```bash
uv run mcp-gsuite --gauth-file /path/to/custom/.gauth.json --accounts-file /path/to/custom/.accounts.json --credentials-dir /path/to/custom/credentials
```

This configuration is particularly useful when you have multiple instances of the server running with different configurations or when deploying to environments where the default paths are not suitable.

## Development

### Building and Publishing

To prepare the package for distribution:

1. Sync dependencies and update lockfile:

```bash
uv sync
```

2. Build package distributions:

```bash
uv build
```

This will create source and wheel distributions in the `dist/` directory.

3. Publish to PyPI:

```bash
uv publish
```

Note: You'll need to set PyPI credentials via environment variables or command flags:
* Token: `--token` or `UV_PUBLISH_TOKEN`
* Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`

### Debugging

Since MCP servers run over stdio, debugging can be challenging. For the best debugging
experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).

You can launch the MCP Inspector via [ `npm` ](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:

```bash
npx @modelcontextprotocol/inspector uv --directory /path/to/mcp-gsuite run mcp-gsuite
```

Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.

You can also watch the server logs with this command:

```bash
tail -n 20 -f ~/Library/Logs/Claude/mcp-server-mcp-gsuite.log
```

```

--------------------------------------------------------------------------------
/src/mcp_gsuite/__init__.py:
--------------------------------------------------------------------------------

```python
from . import server
import asyncio

def main():
    """Main entry point for the package."""
    asyncio.run(server.main())

# Optionally expose other important items at package level
__all__ = ['main', 'server']
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "mcp-gsuite"
version = "0.4.1"
description = "MCP Server to connect to Google G-Suite"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
 "beautifulsoup4>=4.12.3",
 "google-api-python-client>=2.154.0",
 "httplib2>=0.22.0",
 "mcp>=1.3.0",
 "oauth2client==4.1.3",
 "python-dotenv>=1.0.1",
 "pytz>=2024.2",
 "requests>=2.32.3",
]
[[project.authors]]
name = "Markus Pfundstein"
email = "[email protected]"

[build-system]
requires = [ "hatchling",]
build-backend = "hatchling.build"

[dependency-groups]
dev = [
    "pyright>=1.1.389",
]

[project.scripts]
mcp-gsuite = "mcp_gsuite:main"

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - gauthFile
      - accountsFile
    properties:
      gauthFile:
        type: string
        description: Path to the OAuth2 client configuration file.
      accountsFile:
        type: string
        description: Path to the Google accounts configuration file.
      credentialsDir:
        type: string
        description: Directory where OAuth credentials are stored.
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    |-
    (config) => ({command: 'uv', args: ['run', 'mcp-gsuite', '--gauth-file', config.gauthFile, '--accounts-file', config.accountsFile, '--credentials-dir', config.credentialsDir]})

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Use a Python image with uv pre-installed
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS uv

# Set the working directory
WORKDIR /app

# Copy necessary configuration files
COPY . .

# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1

# Use the copy link mode for mount points
ENV UV_LINK_MODE=copy

# Sync dependencies and build the project
RUN --mount=type=cache,target=/root/.cache/uv     --mount=type=bind,source=uv.lock,target=uv.lock     --mount=type=bind,source=pyproject.toml,target=pyproject.toml     uv sync --frozen --no-install-project --no-dev --no-editable

# Install the project
RUN --mount=type=cache,target=/root/.cache/uv     uv sync --frozen --no-dev --no-editable

# Final stage: running the application
FROM python:3.13-slim-bookworm

WORKDIR /app

COPY --from=uv /root/.local /root/.local
COPY --from=uv --chown=app:app /app/.venv /app/.venv

# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"

# Expose necessary ports
EXPOSE 4100

# Specify the entrypoint command
ENTRYPOINT ["uv", "run", "mcp-gsuite"]

```

--------------------------------------------------------------------------------
/src/mcp_gsuite/toolhandler.py:
--------------------------------------------------------------------------------

```python
from collections.abc import Sequence
from mcp.types import (
    Tool,
    TextContent,
    ImageContent,
    EmbeddedResource,
)

from . import gauth

USER_ID_ARG = "__user_id__"

class ToolHandler():
    def __init__(self, tool_name: str):
        self.name = tool_name

    def get_account_descriptions(self) -> list[str]:
        return [a.to_description() for a in gauth.get_account_info()]
    
    # we ingest this information into every tool that requires a specified __user_id__. 
    # we also add what information actually can be used (account info). This way Claude
    # will know what to do.
    def get_supported_emails_tool_text(self) -> str:
        return f"""This tool requires a authorized Google account email for {USER_ID_ARG} argument. You can choose one of: {', '.join(self.get_account_descriptions())}"""

    def get_user_id_arg_schema(self) -> dict:
        return {
            "type": "string",
            "description": f"The EMAIL of the Google account for which you are executing this action. Can be one of: {', '.join(self.get_account_descriptions())}"
        }

    def get_tool_description(self) -> Tool:
        raise NotImplementedError()

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        raise NotImplementedError()
```

--------------------------------------------------------------------------------
/src/mcp_gsuite/server.py:
--------------------------------------------------------------------------------

```python

import logging
from collections.abc import Sequence
from functools import lru_cache
import subprocess
from typing import Any
import traceback
from dotenv import load_dotenv
from mcp.server import Server
import threading
import sys
from mcp.types import (
    Tool,
    TextContent,
    ImageContent,
    EmbeddedResource,
)
import json
from . import gauth
from http.server import BaseHTTPRequestHandler,HTTPServer
from urllib.parse import (
    urlparse,
    parse_qs,
)

class OauthListener(BaseHTTPRequestHandler):
    def do_GET(self):
        url = urlparse(self.path)
        if url.path != "/code":
            self.send_response(404)
            self.end_headers()
            return

        query = parse_qs(url.query)
        if "code" not in query:
            self.send_response(400)
            self.end_headers()
            return
        
        self.send_response(200)
        self.end_headers()
        self.wfile.write("Auth successful! You can close the tab!".encode("utf-8"))
        self.wfile.flush()

        storage = {}
        creds = gauth.get_credentials(authorization_code=query["code"][0], state=storage)

        t = threading.Thread(target = self.server.shutdown)
        t.daemon = True
        t.start()

        

load_dotenv()

from . import tools_gmail
from . import tools_calendar
from . import toolhandler


# Load environment variables

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-gsuite")

def start_auth_flow(user_id: str):
    auth_url = gauth.get_authorization_url(user_id, state={})
    if sys.platform == "darwin" or sys.platform.startswith("linux"):
        subprocess.Popen(['open', auth_url])
    else:
        import webbrowser
        webbrowser.open(auth_url)

    # start server for code callback
    server_address = ('', 4100)
    server = HTTPServer(server_address, OauthListener)
    server.serve_forever()


def setup_oauth2(user_id: str):
    accounts = gauth.get_account_info()
    if len(accounts) == 0:
        raise RuntimeError("No accounts specified in .gauth.json")
    if user_id not in [a.email for a in accounts]:
        raise RuntimeError(f"Account for email: {user_id} not specified in .gauth.json")

    credentials = gauth.get_stored_credentials(user_id=user_id)
    if not credentials:
        start_auth_flow(user_id=user_id)
    else:
        if credentials.access_token_expired:
            logger.error("credentials expired. try refresh")

        # this call refreshes access token
        user_info = gauth.get_user_info(credentials=credentials)
        #logging.error(f"User info: {json.dumps(user_info)}")
        gauth.store_credentials(credentials=credentials, user_id=user_id)


app = Server("mcp-gsuite")

tool_handlers = {}
def add_tool_handler(tool_class: toolhandler.ToolHandler):
    global tool_handlers

    tool_handlers[tool_class.name] = tool_class

def get_tool_handler(name: str) -> toolhandler.ToolHandler | None:
    if name not in tool_handlers:
        return None
    
    return tool_handlers[name]

add_tool_handler(tools_gmail.QueryEmailsToolHandler())
add_tool_handler(tools_gmail.GetEmailByIdToolHandler())
add_tool_handler(tools_gmail.CreateDraftToolHandler())
add_tool_handler(tools_gmail.DeleteDraftToolHandler())
add_tool_handler(tools_gmail.ReplyEmailToolHandler())
add_tool_handler(tools_gmail.GetAttachmentToolHandler())
add_tool_handler(tools_gmail.BulkGetEmailsByIdsToolHandler())
add_tool_handler(tools_gmail.BulkSaveAttachmentsToolHandler())

add_tool_handler(tools_calendar.ListCalendarsToolHandler())
add_tool_handler(tools_calendar.GetCalendarEventsToolHandler())
add_tool_handler(tools_calendar.CreateCalendarEventToolHandler())
add_tool_handler(tools_calendar.DeleteCalendarEventToolHandler())

@app.list_tools()
async def list_tools() -> list[Tool]:
    """List available tools."""

    return [th.get_tool_description() for th in tool_handlers.values()]


@app.call_tool()
async def call_tool(name: str, arguments: Any) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
    try:        
        if not isinstance(arguments, dict):
            raise RuntimeError("arguments must be dictionary")
        
        if toolhandler.USER_ID_ARG not in arguments:
            raise RuntimeError("user_id argument is missing in dictionary.")

        setup_oauth2(user_id=arguments.get(toolhandler.USER_ID_ARG, ""))

        tool_handler = get_tool_handler(name)
        if not tool_handler:
            raise ValueError(f"Unknown tool: {name}")

        return tool_handler.run_tool(arguments)
    except Exception as e:
        logging.error(traceback.format_exc())
        logging.error(f"Error during call_tool: str(e)")
        raise RuntimeError(f"Caught Exception. Error: {str(e)}")


async def main():
    print(sys.platform)
    accounts = gauth.get_account_info()
    for account in accounts:
        creds = gauth.get_stored_credentials(user_id=account.email)
        if creds:
            logging.info(f"found credentials for {account.email}")

    from mcp.server.stdio import stdio_server

    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            app.create_initialization_options()
        )
```

--------------------------------------------------------------------------------
/src/mcp_gsuite/calendar.py:
--------------------------------------------------------------------------------

```python
from googleapiclient.discovery import build
from . import gauth
import logging
import traceback
from datetime import datetime
import pytz

class CalendarService():
    def __init__(self, user_id: str):
        credentials = gauth.get_stored_credentials(user_id=user_id)
        if not credentials:
            raise RuntimeError("No Oauth2 credentials stored")
        self.service = build('calendar', 'v3', credentials=credentials)  # Note: using v3 for Calendar API
    
    def list_calendars(self) -> list:
        """
        Lists all calendars accessible by the user.
        
        Returns:
            list: List of calendar objects with their metadata
        """
        try:
            calendar_list = self.service.calendarList().list().execute()

            calendars = []
            
            for calendar in calendar_list.get('items', []):
                if calendar.get('kind') == 'calendar#calendarListEntry':
                    calendars.append({
                        'id': calendar.get('id'),
                        'summary': calendar.get('summary'),
                        'primary': calendar.get('primary', False),
                        'time_zone': calendar.get('timeZone'),
                        'etag': calendar.get('etag'),
                        'access_role': calendar.get('accessRole')
                    })

            return calendars
                
        except Exception as e:
            logging.error(f"Error retrieving calendars: {str(e)}")
            logging.error(traceback.format_exc())
            return []

    def get_events(self, time_min=None, time_max=None, max_results=250, show_deleted=False, calendar_id: str ='primary'):
        """
        Retrieve calendar events within a specified time range.
        
        Args:
            time_min (str, optional): Start time in RFC3339 format. Defaults to current time.
            time_max (str, optional): End time in RFC3339 format
            max_results (int): Maximum number of events to return (1-2500)
            show_deleted (bool): Whether to include deleted events
            
        Returns:
            list: List of calendar events
        """
        try:
            # If no time_min specified, use current time
            if not time_min:
                time_min = datetime.now(pytz.UTC).isoformat()
                
            # Ensure max_results is within limits
            max_results = min(max(1, max_results), 2500)
            
            # Prepare parameters
            params = {
                'calendarId': calendar_id,
                'timeMin': time_min,
                'maxResults': max_results,
                'singleEvents': True,
                'orderBy': 'startTime',
                'showDeleted': show_deleted
            }
            
            # Add optional time_max if specified
            if time_max:
                params['timeMax'] = time_max
                
            # Execute the events().list() method
            events_result = self.service.events().list(**params).execute()
            
            # Extract the events
            events = events_result.get('items', [])
            
            # Process and return the events
            processed_events = []
            for event in events:
                processed_event = {
                    'id': event.get('id'),
                    'summary': event.get('summary'),
                    'description': event.get('description'),
                    'start': event.get('start'),
                    'end': event.get('end'),
                    'status': event.get('status'),
                    'creator': event.get('creator'),
                    'organizer': event.get('organizer'),
                    'attendees': event.get('attendees'),
                    'location': event.get('location'),
                    'hangoutLink': event.get('hangoutLink'),
                    'conferenceData': event.get('conferenceData'),
                    'recurringEventId': event.get('recurringEventId')
                }
                processed_events.append(processed_event)
                
            return processed_events
            
        except Exception as e:
            logging.error(f"Error retrieving calendar events: {str(e)}")
            logging.error(traceback.format_exc())
            return []
        
    def create_event(self, summary: str, start_time: str, end_time: str, 
                location: str | None = None, description: str | None = None, 
                attendees: list | None = None, send_notifications: bool = True,
                timezone: str | None = None,
                calendar_id : str = 'primary') -> dict | None:
        """
        Create a new calendar event.
        
        Args:
            summary (str): Title of the event
            start_time (str): Start time in RFC3339 format
            end_time (str): End time in RFC3339 format
            location (str, optional): Location of the event
            description (str, optional): Description of the event
            attendees (list, optional): List of attendee email addresses
            send_notifications (bool): Whether to send notifications to attendees
            timezone (str, optional): Timezone for the event (e.g. 'America/New_York')
            
        Returns:
            dict: Created event data or None if creation fails
        """
        try:
            # Prepare event data
            event = {
                'summary': summary,
                'start': {
                    'dateTime': start_time,
                    'timeZone': timezone or 'UTC',
                },
                'end': {
                    'dateTime': end_time,
                    'timeZone': timezone or 'UTC',
                }
            }
            
            # Add optional fields if provided
            if location:
                event['location'] = location
            if description:
                event['description'] = description
            if attendees:
                event['attendees'] = [{'email': email} for email in attendees]
                
            # Create the event
            created_event = self.service.events().insert(
                calendarId=calendar_id,
                body=event,
                sendNotifications=send_notifications
            ).execute()
            
            return created_event
            
        except Exception as e:
            logging.error(f"Error creating calendar event: {str(e)}")
            logging.error(traceback.format_exc())
            return None
        
    def delete_event(self, event_id: str, send_notifications: bool = True, calendar_id: str = 'primary') -> bool:
        """
        Delete a calendar event by its ID.
        
        Args:
            event_id (str): The ID of the event to delete
            send_notifications (bool): Whether to send cancellation notifications to attendees
            
        Returns:
            bool: True if deletion was successful, False otherwise
        """
        try:
            self.service.events().delete(
                calendarId=calendar_id,
                eventId=event_id,
                sendNotifications=send_notifications
            ).execute()
            return True
            
        except Exception as e:
            logging.error(f"Error deleting calendar event {event_id}: {str(e)}")
            logging.error(traceback.format_exc())
            return False
```

--------------------------------------------------------------------------------
/src/mcp_gsuite/gauth.py:
--------------------------------------------------------------------------------

```python
import logging
from oauth2client.client import (
    flow_from_clientsecrets,
    FlowExchangeError,
    OAuth2Credentials,
    Credentials,
)
from googleapiclient.discovery import build
import httplib2
from google.auth.transport.requests import Request
import os
import pydantic
import json
import argparse


def get_gauth_file() -> str:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--gauth-file",
        type=str,
        default="./.gauth.json",
        help="Path to client secrets file",
    )
    args, _ = parser.parse_known_args()
    return args.gauth_file


CLIENTSECRETS_LOCATION = get_gauth_file()

REDIRECT_URI = 'http://localhost:4100/code'
SCOPES = [
    "openid",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://mail.google.com/",
    "https://www.googleapis.com/auth/calendar"
]


class AccountInfo(pydantic.BaseModel):

    email: str
    account_type: str
    extra_info: str

    def __init__(self, email: str, account_type: str, extra_info: str = ""):
        super().__init__(email=email, account_type=account_type, extra_info=extra_info)

    def to_description(self):
        return f"""Account for email: {self.email} of type: {self.account_type}. Extra info for: {self.extra_info}"""


def get_accounts_file() -> str:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--accounts-file",
        type=str,
        default="./.accounts.json",
        help="Path to accounts configuration file",
    )
    args, _ = parser.parse_known_args()
    return args.accounts_file


def get_account_info() -> list[AccountInfo]:
    accounts_file = get_accounts_file()
    with open(accounts_file) as f:
        data = json.load(f)
        accounts = data.get("accounts", [])
        return [AccountInfo.model_validate(acc) for acc in accounts]

class GetCredentialsException(Exception):
  """Error raised when an error occurred while retrieving credentials.

  Attributes:
    authorization_url: Authorization URL to redirect the user to in order to
                       request offline access.
  """

  def __init__(self, authorization_url):
    """Construct a GetCredentialsException."""
    self.authorization_url = authorization_url


class CodeExchangeException(GetCredentialsException):
  """Error raised when a code exchange has failed."""


class NoRefreshTokenException(GetCredentialsException):
  """Error raised when no refresh token has been found."""


class NoUserIdException(Exception):
  """Error raised when no user ID could be retrieved."""


def get_credentials_dir() -> str:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--credentials-dir",
        type=str,
        default=".",
        help="Directory to store OAuth2 credentials",
    )
    args, _ = parser.parse_known_args()
    return args.credentials_dir


def _get_credential_filename(user_id: str) -> str:
    creds_dir = get_credentials_dir()
    return os.path.join(creds_dir, f".oauth2.{user_id}.json")


def get_stored_credentials(user_id: str) -> OAuth2Credentials | None:
    """Retrieved stored credentials for the provided user ID.

    Args:
    user_id: User's ID.
    Returns:
    Stored oauth2client.client.OAuth2Credentials if found, None otherwise.
    """
    try:

        cred_file_path = _get_credential_filename(user_id=user_id)
        if not os.path.exists(cred_file_path):
            logging.warning(f"No stored Oauth2 credentials yet at path: {cred_file_path}")
            return None

        with open(cred_file_path, 'r') as f:
            data = f.read()
            return Credentials.new_from_json(data)
    except Exception as e:
        logging.error(e)
        return None

    raise None


def store_credentials(credentials: OAuth2Credentials, user_id: str):
    """Store OAuth 2.0 credentials in the specified directory."""
    cred_file_path = _get_credential_filename(user_id=user_id)
    os.makedirs(os.path.dirname(cred_file_path), exist_ok=True)
    
    data = credentials.to_json()
    with open(cred_file_path, "w") as f:
        f.write(data)


def exchange_code(authorization_code):
    """Exchange an authorization code for OAuth 2.0 credentials.

    Args:
    authorization_code: Authorization code to exchange for OAuth 2.0
                        credentials.
    Returns:
    oauth2client.client.OAuth2Credentials instance.
    Raises:
    CodeExchangeException: an error occurred.
    """
    flow = flow_from_clientsecrets(CLIENTSECRETS_LOCATION, ' '.join(SCOPES))
    flow.redirect_uri = REDIRECT_URI
    try:
        credentials = flow.step2_exchange(authorization_code)
        return credentials
    except FlowExchangeError as error:
        logging.error('An error occurred: %s', error)
        raise CodeExchangeException(None)


def get_user_info(credentials):
    """Send a request to the UserInfo API to retrieve the user's information.

    Args:
    credentials: oauth2client.client.OAuth2Credentials instance to authorize the
                    request.
    Returns:
    User information as a dict.
    """
    user_info_service = build(
        serviceName='oauth2', version='v2',
        http=credentials.authorize(httplib2.Http()))
    user_info = None
    try:
        user_info = user_info_service.userinfo().get().execute()
    except Exception as e:
        logging.error(f'An error occurred: {e}')
    if user_info and user_info.get('id'):
        return user_info
    else:
        raise NoUserIdException()


def get_authorization_url(email_address, state):
    """Retrieve the authorization URL.

    Args:
    email_address: User's e-mail address.
    state: State for the authorization URL.
    Returns:
    Authorization URL to redirect the user to.
    """
    flow = flow_from_clientsecrets(CLIENTSECRETS_LOCATION, ' '.join(SCOPES), redirect_uri=REDIRECT_URI)
    flow.params['access_type'] = 'offline'
    flow.params['approval_prompt'] = 'force'
    flow.params['user_id'] = email_address
    flow.params['state'] = state
    return flow.step1_get_authorize_url(state=state)


def get_credentials(authorization_code, state):
    """Retrieve credentials using the provided authorization code.

    This function exchanges the authorization code for an access token and queries
    the UserInfo API to retrieve the user's e-mail address.
    If a refresh token has been retrieved along with an access token, it is stored
    in the application database using the user's e-mail address as key.
    If no refresh token has been retrieved, the function checks in the application
    database for one and returns it if found or raises a NoRefreshTokenException
    with the authorization URL to redirect the user to.

    Args:
    authorization_code: Authorization code to use to retrieve an access token.
    state: State to set to the authorization URL in case of error.
    Returns:
    oauth2client.client.OAuth2Credentials instance containing an access and
    refresh token.
    Raises:
    CodeExchangeError: Could not exchange the authorization code.
    NoRefreshTokenException: No refresh token could be retrieved from the
                                available sources.
    """
    email_address = ''
    try:
        credentials = exchange_code(authorization_code)
        user_info = get_user_info(credentials)
        import json
        logging.error(f"user_info: {json.dumps(user_info)}")
        email_address = user_info.get('email')
        
        if credentials.refresh_token is not None:
            store_credentials(credentials, user_id=email_address)
            return credentials
        else:
            credentials = get_stored_credentials(user_id=email_address)
            if credentials and credentials.refresh_token is not None:
                return credentials
    except CodeExchangeException as error:
        logging.error('An error occurred during code exchange.')
        # Drive apps should try to retrieve the user and credentials for the current
        # session.
        # If none is available, redirect the user to the authorization URL.
        error.authorization_url = get_authorization_url(email_address, state)
        raise error
    except NoUserIdException:
        logging.error('No user ID could be retrieved.')
        # No refresh token has been retrieved.
    authorization_url = get_authorization_url(email_address, state)
    raise NoRefreshTokenException(authorization_url)


```

--------------------------------------------------------------------------------
/src/mcp_gsuite/tools_calendar.py:
--------------------------------------------------------------------------------

```python
from collections.abc import Sequence
from mcp.types import (
    Tool,
    TextContent,
    ImageContent,
    EmbeddedResource,
    LoggingLevel,
)
from . import gauth
from . import calendar
import json
from . import toolhandler

CALENDAR_ID_ARG="__calendar_id__"

def get_calendar_id_arg_schema() -> dict[str, str]:
    return {
        "type": "string",
        "description": """Optional ID of the specific agenda for which you are executing this action.
                          If not provided, the default calendar is being used. 
                          If not known, the specific calendar id can be retrieved with the list_calendars tool""",
        "default": "primary"
    }


class ListCalendarsToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("list_calendars")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="""Lists all calendars accessible by the user. 
            Call it before any other tool whenever the user specifies a particular agenda (Family, Holidays, etc.).""",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                },
                "required": [toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")

        calendar_service = calendar.CalendarService(user_id=user_id)
        calendars = calendar_service.list_calendars()

        return [
            TextContent(
                type="text",
                text=json.dumps(calendars, indent=2)
            )
        ]

class GetCalendarEventsToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("get_calendar_events")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Retrieves calendar events from the user's Google Calendar within a specified time range.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "__calendar_id__": get_calendar_id_arg_schema(),
                    "time_min": {
                        "type": "string",
                        "description": "Start time in RFC3339 format (e.g. 2024-12-01T00:00:00Z). Defaults to current time if not specified."
                    },
                    "time_max": {
                        "type": "string", 
                        "description": "End time in RFC3339 format (e.g. 2024-12-31T23:59:59Z). Optional."
                    },
                    "max_results": {
                        "type": "integer",
                        "description": "Maximum number of events to return (1-2500)",
                        "minimum": 1,
                        "maximum": 2500,
                        "default": 250
                    },
                    "show_deleted": {
                        "type": "boolean",
                        "description": "Whether to include deleted events",
                        "default": False
                    }
                },
                "required": [toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        
        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")
        
        calendar_service = calendar.CalendarService(user_id=user_id)
        events = calendar_service.get_events(
            time_min=args.get('time_min'),
            time_max=args.get('time_max'),
            max_results=args.get('max_results', 250),
            show_deleted=args.get('show_deleted', False),
            calendar_id=args.get(CALENDAR_ID_ARG, 'primary'),
        )

        return [
            TextContent(
                type="text",
                text=json.dumps(events, indent=2)
            )
        ]

class CreateCalendarEventToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("create_calendar_event")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Creates a new event in a specified Google Calendar of the specified user.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "__calendar_id__": get_calendar_id_arg_schema(),
                    "summary": {
                        "type": "string",
                        "description": "Title of the event"
                    },
                    "location": {
                        "type": "string",
                        "description": "Location of the event (optional)"
                    },
                    "description": {
                        "type": "string",
                        "description": "Description or notes for the event (optional)"
                    },
                    "start_time": {
                        "type": "string",
                        "description": "Start time in RFC3339 format (e.g. 2024-12-01T10:00:00Z)"
                    },
                    "end_time": {
                        "type": "string",
                        "description": "End time in RFC3339 format (e.g. 2024-12-01T11:00:00Z)"
                    },
                    "attendees": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "List of attendee email addresses (optional)"
                    },
                    "send_notifications": {
                        "type": "boolean",
                        "description": "Whether to send notifications to attendees",
                        "default": True
                    },
                    "timezone": {
                        "type": "string",
                        "description": "Timezone for the event (e.g. 'America/New_York'). Defaults to UTC if not specified."
                    }
                },
                "required": [toolhandler.USER_ID_ARG, "summary", "start_time", "end_time"]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        # Validate required arguments
        required = ["summary", "start_time", "end_time"]
        if not all(key in args for key in required):
            raise RuntimeError(f"Missing required arguments: {', '.join(required)}")

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")

        calendar_service = calendar.CalendarService(user_id=user_id)
        event = calendar_service.create_event(
            summary=args["summary"],
            start_time=args["start_time"],
            end_time=args["end_time"],
            location=args.get("location"),
            description=args.get("description"),
            attendees=args.get("attendees", []),
            send_notifications=args.get("send_notifications", True),
            timezone=args.get("timezone"),
            calendar_id=args.get(CALENDAR_ID_ARG, 'primary'),
        )

        return [
            TextContent(
                type="text",
                text=json.dumps(event, indent=2)
            )
        ]
    
class DeleteCalendarEventToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("delete_calendar_event")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Deletes an event from the user's Google Calendar by its event ID.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "__calendar_id__": get_calendar_id_arg_schema(),
                    "event_id": {
                        "type": "string",
                        "description": "The ID of the calendar event to delete"
                    },
                    "send_notifications": {
                        "type": "boolean",
                        "description": "Whether to send cancellation notifications to attendees",
                        "default": True
                    }
                },
                "required": [toolhandler.USER_ID_ARG, "event_id"]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        if "event_id" not in args:
            raise RuntimeError("Missing required argument: event_id")
        
        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")

        calendar_service = calendar.CalendarService(user_id=user_id)
        success = calendar_service.delete_event(
            event_id=args["event_id"],
            send_notifications=args.get("send_notifications", True),
            calendar_id=args.get(CALENDAR_ID_ARG, 'primary'),
        )

        return [
            TextContent(
                type="text",
                text=json.dumps({
                    "success": success,
                    "message": "Event successfully deleted" if success else "Failed to delete event"
                }, indent=2)
            )
        ]
```

--------------------------------------------------------------------------------
/src/mcp_gsuite/gmail.py:
--------------------------------------------------------------------------------

```python
from googleapiclient.discovery import build 
from . import gauth
import logging
import base64
import traceback
from email.mime.text import MIMEText
from typing import Tuple


class GmailService():
    def __init__(self, user_id: str):
        credentials = gauth.get_stored_credentials(user_id=user_id)
        if not credentials:
            raise RuntimeError("No Oauth2 credentials stored")
        self.service = build('gmail', 'v1', credentials=credentials)

    def _parse_message(self, txt, parse_body=False) -> dict | None:
        """
        Parse a Gmail message into a structured format.
        
        Args:
            txt (dict): Raw message from Gmail API
            parse_body (bool): Whether to parse and include the message body (default: False)
        
        Returns:
            dict: Parsed message containing comprehensive metadata
            None: If parsing fails
        """
        try:
            message_id = txt.get('id')
            thread_id = txt.get('threadId')
            payload = txt.get('payload', {})
            headers = payload.get('headers', [])

            metadata = {
                'id': message_id,
                'threadId': thread_id,
                'historyId': txt.get('historyId'),
                'internalDate': txt.get('internalDate'),
                'sizeEstimate': txt.get('sizeEstimate'),
                'labelIds': txt.get('labelIds', []),
                'snippet': txt.get('snippet'),
            }

            for header in headers:
                name = header.get('name', '').lower()
                value = header.get('value', '')
                
                if name == 'subject':
                    metadata['subject'] = value
                elif name == 'from':
                    metadata['from'] = value
                elif name == 'to':
                    metadata['to'] = value
                elif name == 'date':
                    metadata['date'] = value
                elif name == 'cc':
                    metadata['cc'] = value
                elif name == 'bcc':
                    metadata['bcc'] = value
                elif name == 'message-id':
                    metadata['message_id'] = value
                elif name == 'in-reply-to':
                    metadata['in_reply_to'] = value
                elif name == 'references':
                    metadata['references'] = value
                elif name == 'delivered-to':
                    metadata['delivered_to'] = value

            if parse_body:
                body = self._extract_body(payload)
                if body:
                    metadata['body'] = body

                metadata['mimeType'] = payload.get('mimeType')

            return metadata

        except Exception as e:
            logging.error(f"Error parsing message: {str(e)}")
            logging.error(traceback.format_exc())
            return None

    def _extract_body(self, payload) -> str | None:
        """
        Extract the email body from the payload.
        Handles both multipart and single part messages, including nested multiparts.
        """
        try:
            # For single part text/plain messages
            if payload.get('mimeType') == 'text/plain':
                data = payload.get('body', {}).get('data')
                if data:
                    return base64.urlsafe_b64decode(data).decode('utf-8')
            
            # For single part text/html messages
            if payload.get('mimeType') == 'text/html':
                data = payload.get('body', {}).get('data')
                if data:
                    return base64.urlsafe_b64decode(data).decode('utf-8')
            
            # For multipart messages (both alternative and related)
            if payload.get('mimeType', '').startswith('multipart/'):
                parts = payload.get('parts', [])
                
                # First try to find a direct text/plain part
                for part in parts:
                    if part.get('mimeType') == 'text/plain':
                        data = part.get('body', {}).get('data')
                        if data:
                            return base64.urlsafe_b64decode(data).decode('utf-8')
                
                # If no direct text/plain, recursively check nested multipart structures
                for part in parts:
                    if part.get('mimeType', '').startswith('multipart/'):
                        nested_body = self._extract_body(part)
                        if nested_body:
                            return nested_body
                            
                # If still no body found, try the first part as fallback
                if parts and 'body' in parts[0] and 'data' in parts[0]['body']:
                    data = parts[0]['body']['data']
                    return base64.urlsafe_b64decode(data).decode('utf-8')

            return None

        except Exception as e:
            logging.error(f"Error extracting body: {str(e)}")
            return None

    def query_emails(self, query=None, max_results=100):
        """
        Query emails from Gmail based on a search query.
        
        Args:
            query (str, optional): Gmail search query (e.g., 'is:unread', 'from:[email protected]')
                                If None, returns all emails
            max_results (int): Maximum number of emails to retrieve (1-500, default: 100)
        
        Returns:
            list: List of parsed email messages, newest first
        """
        try:
            # Ensure max_results is within API limits
            max_results = min(max(1, max_results), 500)
            
            # Get the list of messages
            result = self.service.users().messages().list(
                userId='me',
                maxResults=max_results,
                q=query if query else ''
            ).execute()

            messages = result.get('messages', [])
            parsed = []

            # Fetch full message details for each message
            for msg in messages:
                txt = self.service.users().messages().get(
                    userId='me', 
                    id=msg['id']
                ).execute()
                parsed_message = self._parse_message(txt=txt, parse_body=False)
                if parsed_message:
                    parsed.append(parsed_message)
                    
            return parsed
            
        except Exception as e:
            logging.error(f"Error reading emails: {str(e)}")
            logging.error(traceback.format_exc())
            return []
        
    def get_email_by_id_with_attachments(self, email_id: str) -> Tuple[dict, dict] | Tuple[None, dict]:
        """
        Fetch and parse a complete email message by its ID including attachment IDs.
        
        Args:
            email_id (str): The Gmail message ID to retrieve
        
        Returns:
            Tuple[dict, list]: Complete parsed email message including body and list of attachment IDs
            Tuple[None, list]: If retrieval or parsing fails, returns None for email and empty list for attachment IDs
        """
        try:
            # Fetch the complete message by ID
            message = self.service.users().messages().get(
                userId='me',
                id=email_id
            ).execute()
            
            # Parse the message with body included
            parsed_email = self._parse_message(txt=message, parse_body=True)

            if parsed_email is None:
                return None, {}

            attachments = {}
            # Check if 'parts' exists in payload before trying to access it
            if "payload" in message and "parts" in message["payload"]:
                for part in message["payload"]["parts"]:
                    if "body" in part and "attachmentId" in part["body"]:
                        attachment_id = part["body"]["attachmentId"]
                        part_id = part["partId"]
                        attachment = {
                            "filename": part["filename"],
                            "mimeType": part["mimeType"],
                            "attachmentId": attachment_id,
                            "partId": part_id
                        }
                        attachments[part_id] = attachment
            else:
                # Handle case when there are no parts (single part message)
                logging.info(f"Email {email_id} does not have 'parts' in payload (likely single part message)")
                if "payload" in message and "body" in message["payload"] and "attachmentId" in message["payload"]["body"]:
                    # Handle potential attachment in single part message
                    attachment_id = message["payload"]["body"]["attachmentId"]
                    attachment = {
                        "filename": message["payload"].get("filename", "attachment"),
                        "mimeType": message["payload"].get("mimeType", "application/octet-stream"),
                        "attachmentId": attachment_id,
                        "partId": "0"
                    }
                    attachments["0"] = attachment

            return parsed_email, attachments
            
        except Exception as e:
            logging.error(f"Error retrieving email {email_id}: {str(e)}")
            logging.error(traceback.format_exc())
            return None, {}
        
    def create_draft(self, to: str, subject: str, body: str, cc: list[str] | None = None) -> dict | None:
        """
        Create a draft email message.
        
        Args:
            to (str): Email address of the recipient
            subject (str): Subject line of the email
            body (str): Body content of the email
            cc (list[str], optional): List of email addresses to CC
            
        Returns:
            dict: Draft message data including the draft ID if successful
            None: If creation fails
        """
        try:
            # Create message body
            message = {
                'to': to,
                'subject': subject,
                'text': body,
            }
            if cc:
                message['cc'] = ','.join(cc)
                
            # Create the message in MIME format
            mime_message = MIMEText(body)
            mime_message['to'] = to
            mime_message['subject'] = subject
            if cc:
                mime_message['cc'] = ','.join(cc)
                
            # Encode the message
            raw_message = base64.urlsafe_b64encode(mime_message.as_bytes()).decode('utf-8')
            
            # Create the draft
            draft = self.service.users().drafts().create(
                userId='me',
                body={
                    'message': {
                        'raw': raw_message
                    }
                }
            ).execute()
            
            return draft
            
        except Exception as e:
            logging.error(f"Error creating draft: {str(e)}")
            logging.error(traceback.format_exc())
            return None
        
    def delete_draft(self, draft_id: str) -> bool:
        """
        Delete a draft email message.
        
        Args:
            draft_id (str): The ID of the draft to delete
            
        Returns:
            bool: True if deletion was successful, False otherwise
        """
        try:
            self.service.users().drafts().delete(
                userId='me',
                id=draft_id
            ).execute()
            return True
            
        except Exception as e:
            logging.error(f"Error deleting draft {draft_id}: {str(e)}")
            logging.error(traceback.format_exc())
            return False
        
    def create_reply(self, original_message: dict, reply_body: str, send: bool = False, cc: list[str] | None = None) -> dict | None:
        """
        Create a reply to an email message and either send it or save as draft.
        
        Args:
            original_message (dict): The original message data (as returned by get_email_by_id)
            reply_body (str): Body content of the reply
            send (bool): If True, sends the reply immediately. If False, saves as draft.
            cc (list[str], optional): List of email addresses to CC
            
        Returns:
            dict: Sent message or draft data if successful
            None: If operation fails
        """
        try:
            to_address = original_message.get('from')
            if not to_address:
                raise ValueError("Could not determine original sender's address")
            
            subject = original_message.get('subject', '')
            if not subject.lower().startswith('re:'):
                subject = f"Re: {subject}"


            original_date = original_message.get('date', '')
            original_from = original_message.get('from', '')
            original_body = original_message.get('body', '')
        
            full_reply_body = (
                f"{reply_body}\n\n"
                f"On {original_date}, {original_from} wrote:\n"
                f"> {original_body.replace('\n', '\n> ') if original_body else '[No message body]'}"
            )

            mime_message = MIMEText(full_reply_body)
            mime_message['to'] = to_address
            mime_message['subject'] = subject
            if cc:
                mime_message['cc'] = ','.join(cc)
                
            mime_message['In-Reply-To'] = original_message.get('id', '')
            mime_message['References'] = original_message.get('id', '')
            
            raw_message = base64.urlsafe_b64encode(mime_message.as_bytes()).decode('utf-8')
            
            message_body = {
                'raw': raw_message,
                'threadId': original_message.get('threadId')  # Ensure it's added to the same thread
            }

            if send:
                # Send the reply immediately
                result = self.service.users().messages().send(
                    userId='me',
                    body=message_body
                ).execute()
            else:
                # Save as draft
                result = self.service.users().drafts().create(
                    userId='me',
                    body={
                        'message': message_body
                    }
                ).execute()
            
            return result
            
        except Exception as e:
            logging.error(f"Error {'sending' if send else 'drafting'} reply: {str(e)}")
            logging.error(traceback.format_exc())
            return None
        
    def get_attachment(self, message_id: str, attachment_id: str) -> dict | None:
        """
        Retrieves a Gmail attachment by its ID.
        
        Args:
            message_id (str): The ID of the Gmail message containing the attachment
            attachment_id (str): The ID of the attachment to retrieve
        
        Returns:
            dict: Attachment data including filename and base64-encoded content
            None: If retrieval fails
        """
        try:
            attachment = self.service.users().messages().attachments().get(
                userId='me',
                messageId=message_id, 
                id=attachment_id
            ).execute()
            return {
                "size": attachment.get("size"),
                "data": attachment.get("data")
            }
            
        except Exception as e:
            logging.error(f"Error retrieving attachment {attachment_id} from message {message_id}: {str(e)}")
            logging.error(traceback.format_exc())
            return None
```

--------------------------------------------------------------------------------
/src/mcp_gsuite/tools_gmail.py:
--------------------------------------------------------------------------------

```python
from collections.abc import Sequence
from mcp.types import (
    Tool,
    TextContent,
    ImageContent,
    EmbeddedResource,
    LoggingLevel,
)
from . import gmail
import json
from . import toolhandler
import base64

def decode_base64_data(file_data):
    standard_base64_data = file_data.replace("-", "+").replace("_", "/")
    missing_padding = len(standard_base64_data) % 4
    if missing_padding:
        standard_base64_data += '=' * (4 - missing_padding)
    return base64.b64decode(standard_base64_data, validate=True)

class QueryEmailsToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("query_gmail_emails")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="""Query Gmail emails based on an optional search query. 
            Returns emails in reverse chronological order (newest first).
            Returns metadata such as subject and also a short summary of the content.
            """,
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "query": {
                        "type": "string",
                        "description": """Gmail search query (optional). Examples:
                            - a $string: Search email body, subject, and sender information for $string
                            - 'is:unread' for unread emails
                            - 'from:[email protected]' for emails from a specific sender
                            - 'newer_than:2d' for emails from last 2 days
                            - 'has:attachment' for emails with attachments
                            If not provided, returns recent emails without filtering.""",
                        "required": False
                    },
                    "max_results": {
                        "type": "integer",
                        "description": "Maximum number of emails to retrieve (1-500)",
                        "minimum": 1,
                        "maximum": 500,
                        "default": 100
                    }
                },
                "required": [toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")

        gmail_service = gmail.GmailService(user_id=user_id)
        query = args.get('query')
        max_results = args.get('max_results', 100)
        emails = gmail_service.query_emails(query=query, max_results=max_results)

        return [
            TextContent(
                type="text",
                text=json.dumps(emails, indent=2)
            )
        ]

class GetEmailByIdToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("get_gmail_email")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Retrieves a complete Gmail email message by its ID, including the full message body and attachment IDs.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "email_id": {
                        "type": "string",
                        "description": "The ID of the Gmail message to retrieve"
                    }
                },
                "required": ["email_id", toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        if "email_id" not in args:
            raise RuntimeError("Missing required argument: email_id")

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")
        gmail_service = gmail.GmailService(user_id=user_id)
        email, attachments = gmail_service.get_email_by_id_with_attachments(args["email_id"])

        if email is None:
            return [
                TextContent(
                    type="text",
                    text=f"Failed to retrieve email with ID: {args['email_id']}"
                )
            ]

        email["attachments"] = attachments

        return [
            TextContent(
                type="text",
                text=json.dumps(email, indent=2)
            )
        ]

class BulkGetEmailsByIdsToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("bulk_get_gmail_emails")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Retrieves multiple Gmail email messages by their IDs in a single request, including the full message bodies and attachment IDs.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "email_ids": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "List of Gmail message IDs to retrieve"
                    }
                },
                "required": ["email_ids", toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        if "email_ids" not in args:
            raise RuntimeError("Missing required argument: email_ids")

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")
        gmail_service = gmail.GmailService(user_id=user_id)
        
        results = []
        for email_id in args["email_ids"]:
            email, attachments = gmail_service.get_email_by_id_with_attachments(email_id)
            if email is not None:
                email["attachments"] = attachments
                results.append(email)

        if not results:
            return [
                TextContent(
                    type="text",
                    text=f"Failed to retrieve any emails from the provided IDs"
                )
            ]

        return [
            TextContent(
                type="text",
                text=json.dumps(results, indent=2)
            )
        ]

class CreateDraftToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("create_gmail_draft")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="""Creates a draft email message from scratch in Gmail with specified recipient, subject, body, and optional CC recipients.
            
            Do NOT use this tool when you want to draft or send a REPLY to an existing message. This tool does NOT include any previous message content. Use the reply_gmail_email tool
            with send=False instead."
            """,
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "to": {
                        "type": "string",
                        "description": "Email address of the recipient"
                    },
                    "subject": {
                        "type": "string",
                        "description": "Subject line of the email"
                    },
                    "body": {
                        "type": "string",
                        "description": "Body content of the email"
                    },
                    "cc": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Optional list of email addresses to CC"
                    }
                },
                "required": ["to", "subject", "body", toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        required = ["to", "subject", "body"]
        if not all(key in args for key in required):
            raise RuntimeError(f"Missing required arguments: {', '.join(required)}")

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")
        gmail_service = gmail.GmailService(user_id=user_id)
        draft = gmail_service.create_draft(
            to=args["to"],
            subject=args["subject"],
            body=args["body"],
            cc=args.get("cc")
        )

        if draft is None:
            return [
                TextContent(
                    type="text",
                    text="Failed to create draft email"
                )
            ]

        return [
            TextContent(
                type="text",
                text=json.dumps(draft, indent=2)
            )
        ]

class DeleteDraftToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("delete_gmail_draft")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Deletes a Gmail draft message by its ID. This action cannot be undone.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "draft_id": {
                        "type": "string",
                        "description": "The ID of the draft to delete"
                    }
                },
                "required": ["draft_id", toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        if "draft_id" not in args:
            raise RuntimeError("Missing required argument: draft_id")

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")
        gmail_service = gmail.GmailService(user_id=user_id)
        success = gmail_service.delete_draft(args["draft_id"])

        return [
            TextContent(
                type="text",
                text="Successfully deleted draft" if success else f"Failed to delete draft with ID: {args['draft_id']}"
            )
        ]

class ReplyEmailToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("reply_gmail_email")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="""Creates a reply to an existing Gmail email message and either sends it or saves as draft.

            Use this tool if you want to draft a reply. Use the 'cc' argument if you want to perform a "reply all".
            """,
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "original_message_id": {
                        "type": "string",
                        "description": "The ID of the Gmail message to reply to"
                    },
                    "reply_body": {
                        "type": "string",
                        "description": "The body content of your reply message"
                    },
                    "send": {
                        "type": "boolean",
                        "description": "If true, sends the reply immediately. If false, saves as draft.",
                        "default": False
                    },
                    "cc": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Optional list of email addresses to CC on the reply"
                    }
                },
                "required": ["original_message_id", "reply_body", toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        if not all(key in args for key in ["original_message_id", "reply_body"]):
            raise RuntimeError("Missing required arguments: original_message_id and reply_body")

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")
        gmail_service = gmail.GmailService(user_id=user_id)
        
        # First get the original message to extract necessary information
        original_message = gmail_service.get_email_by_id(args["original_message_id"])
        if original_message is None:
            return [
                TextContent(
                    type="text",
                    text=f"Failed to retrieve original message with ID: {args['original_message_id']}"
                )
            ]

        # Create and send/draft the reply
        result = gmail_service.create_reply(
            original_message=original_message,
            reply_body=args.get("reply_body", ""),
            send=args.get("send", False),
            cc=args.get("cc")
        )

        if result is None:
            return [
                TextContent(
                    type="text",
                    text=f"Failed to {'send' if args.get('send', True) else 'draft'} reply email"
                )
            ]

        return [
            TextContent(
                type="text",
                text=json.dumps(result, indent=2)
            )
        ]

class GetAttachmentToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("get_gmail_attachment")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Retrieves a Gmail attachment by its ID.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "message_id": {
                        "type": "string",
                        "description": "The ID of the Gmail message containing the attachment"
                    },
                    "attachment_id": {
                        "type": "string",
                        "description": "The ID of the attachment to retrieve"
                    },
                    "mime_type": {
                        "type": "string",
                        "description": "The MIME type of the attachment"
                    },
                    "filename": {
                        "type": "string",
                        "description": "The filename of the attachment"
                    },
                    "save_to_disk": {
                        "type": "string",
                        "description": "The fullpath to save the attachment to disk. If not provided, the attachment is returned as a resource."
                    }
                },
                "required": ["message_id", "attachment_id", "mime_type", "filename", toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        if "message_id" not in args:
            raise RuntimeError("Missing required argument: message_id")
        if "attachment_id" not in args:
            raise RuntimeError("Missing required argument: attachment_id")
        if "mime_type" not in args:
            raise RuntimeError("Missing required argument: mime_type")
        if "filename" not in args:
            raise RuntimeError("Missing required argument: filename")
        filename = args["filename"]
        mime_type = args["mime_type"]
        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")
        gmail_service = gmail.GmailService(user_id=user_id)
        attachment_data = gmail_service.get_attachment(args["message_id"], args["attachment_id"])

        if attachment_data is None:
            return [
                TextContent(
                    type="text",
                    text=f"Failed to retrieve attachment with ID: {args['attachment_id']} from message: {args['message_id']}"
                )
            ]

        file_data = attachment_data["data"]
        attachment_url = f"attachment://gmail/{args['message_id']}/{args['attachment_id']}/{filename}"
        if args.get("save_to_disk"):
            decoded_data = decode_base64_data(file_data)
            with open(args["save_to_disk"], "wb") as f:
                f.write(decoded_data)
            return [
                TextContent(
                    type="text",
                    text=f"Attachment saved to disk: {args['save_to_disk']}"
                )
            ]
        return [
            EmbeddedResource(
                type="resource",
                resource={
                    "blob": file_data,
                    "uri": attachment_url,
                    "mimeType": mime_type,
                },
            )
        ]

class BulkSaveAttachmentsToolHandler(toolhandler.ToolHandler):
    def __init__(self):
        super().__init__("bulk_save_gmail_attachments")

    def get_tool_description(self) -> Tool:
        return Tool(
            name=self.name,
            description="Saves multiple Gmail attachments to disk by their message IDs and attachment IDs in a single request.",
            inputSchema={
                "type": "object",
                "properties": {
                    "__user_id__": self.get_user_id_arg_schema(),
                    "attachments": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "message_id": {
                                    "type": "string",
                                    "description": "ID of the Gmail message containing the attachment"
                                },
                                "part_id": {
                                    "type": "string", 
                                    "description": "ID of the part containing the attachment"
                                },
                                "save_path": {
                                    "type": "string",
                                    "description": "Path where the attachment should be saved"
                                }
                            },
                            "required": ["message_id", "part_id", "save_path"]
                        }
                    }
                },
                "required": ["attachments", toolhandler.USER_ID_ARG]
            }
        )

    def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
        if "attachments" not in args:
            raise RuntimeError("Missing required argument: attachments")

        user_id = args.get(toolhandler.USER_ID_ARG)
        if not user_id:
            raise RuntimeError(f"Missing required argument: {toolhandler.USER_ID_ARG}")

        gmail_service = gmail.GmailService(user_id=user_id)
        results = []

        for attachment_info in args["attachments"]:
            # get attachment data from message_id and part_id
            message, attachments = gmail_service.get_email_by_id_with_attachments(
                attachment_info["message_id"]
            )
            if message is None:
                results.append(
                    TextContent(
                        type="text",
                        text=f"Failed to retrieve message with ID: {attachment_info['message_id']}"
                    )
                )
                continue
            # get attachment_id from part_id
            attachment_id = attachments[attachment_info["part_id"]]["attachmentId"]
            attachment_data = gmail_service.get_attachment(
                attachment_info["message_id"], 
                attachment_id
            )
            if attachment_data is None:
                results.append(
                    TextContent(
                        type="text",
                        text=f"Failed to retrieve attachment with ID: {attachment_id} from message: {attachment_info['message_id']}"
                    )
                )
                continue

            file_data = attachment_data["data"]
            try:    
                decoded_data = decode_base64_data(file_data)
                with open(attachment_info["save_path"], "wb") as f:
                    f.write(decoded_data)
                results.append(
                    TextContent(
                        type="text",
                        text=f"Attachment saved to: {attachment_info['save_path']}"
                    )
                )
            except Exception as e:
                results.append(
                    TextContent(
                        type="text",
                        text=f"Failed to save attachment to {attachment_info['save_path']}: {str(e)}"
                    )
                )
                continue

        return results

```

--------------------------------------------------------------------------------
/google-calendar-api-openapi-spec.yaml:
--------------------------------------------------------------------------------

```yaml
swagger: "2.0"
info:
  title: Calendar
  description: Manipulates events and other calendar data.
  contact:
    name: Google
    url: https://google.com
  version: v3
host: www.googleapis.com
basePath: /calendar/v3
schemes:
- http
produces:
- application/json
consumes:
- application/json
paths:
  /calendars:
    post:
      summary: Create Calendar
      description: Creates a secondary calendar
      operationId: calendar.calendars.insert
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      responses:
        200:
          description: OK
      tags:
      - Calendar
  /calendars/{calendarId}:
    delete:
      summary: CreaDeletete Calendar
      description: Deletes a secondary calendar
      operationId: calendar.calendars.delete
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar
    get:
      summary: Get Calendar
      description: Returns metadata for a calendar
      operationId: calendar.calendars.get
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar
    patch:
      summary: Update Calendar
      description: Updates metadata for a calendar
      operationId: calendar.calendars.patch
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar
    put:
      summary: Update Calendar
      description: Updates metadata for a calendar
      operationId: calendar.calendars.update
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar
  /calendars/{calendarId}/acl:
    get:
      summary: Get Calendar ACL
      description: Returns the rules in the access control list for the calendar
      operationId: calendar.acl.list
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: maxResults
        description: Maximum number of entries returned on one result page
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: query
        name: showDeleted
        description: Whether to include deleted ACLs in the result
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      responses:
        200:
          description: OK
      tags:
      - Calendar ACL
    post:
      summary: Create Calendar ACL
      description: Creates an access control rule
      operationId: calendar.acl.insert
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar ACL
  /calendars/{calendarId}/acl/watch:
    post:
      summary: Watch Calendar ACL
      description: Watch for changes to ACL resources
      operationId: calendar.acl.watch
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: maxResults
        description: Maximum number of entries returned on one result page
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: body
        name: resource
        schema:
          $ref: '#/definitions/holder'
      - in: query
        name: showDeleted
        description: Whether to include deleted ACLs in the result
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      responses:
        200:
          description: OK
      tags:
      - Calendar ACL
  /calendars/{calendarId}/acl/{ruleId}:
    delete:
      summary: Delete Calendar ACL
      description: Deletes an access control rule
      operationId: calendar.acl.delete
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: ruleId
        description: ACL rule identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar ACL
    get:
      summary: Get Calendar ACL
      description: Returns an access control rule
      operationId: calendar.acl.get
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: ruleId
        description: ACL rule identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar ACL
    patch:
      summary: Update Calendar ACL
      description: Updates an access control rule
      operationId: calendar.acl.patch
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: ruleId
        description: ACL rule identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar ACL
    put:
      summary: Update Calendar ACL
      description: Updates an access control rule
      operationId: calendar.acl.update
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: ruleId
        description: ACL rule identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar ACL
  /calendars/{calendarId}/clear:
    post:
      summary: Clear Primary Calendar
      description: Clears a primary calendar
      operationId: calendar.calendars.clear
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Calendar
  /calendars/{calendarId}/events:
    get:
      summary: Get Events
      description: Returns events on the specified calendar
      operationId: calendar.events.list
      parameters:
      - in: query
        name: alwaysIncludeEmail
        description: Whether to always include a value in the email field for the
          organizer, creator and attendees, even if no real email is available (i
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: iCalUID
        description: Specifies event ID in the iCalendar format to be included in
          the response
      - in: query
        name: maxAttendees
        description: The maximum number of attendees to include in the response
      - in: query
        name: maxResults
        description: Maximum number of events returned on one result page
      - in: query
        name: orderBy
        description: The order of the events returned in the result
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: query
        name: privateExtendedProperty
        description: Extended properties constraint specified as propertyName=value
      - in: query
        name: q
        description: Free text search terms to find events that match these terms
          in any field, except for extended properties
      - in: query
        name: sharedExtendedProperty
        description: Extended properties constraint specified as propertyName=value
      - in: query
        name: showDeleted
        description: Whether to include deleted events (with status equals "cancelled")
          in the result
      - in: query
        name: showHiddenInvitations
        description: Whether to include hidden invitations in the result
      - in: query
        name: singleEvents
        description: Whether to expand recurring events into instances and only return
          single one-off events and instances of recurring events, but not the underlying
          recurring events themselves
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      - in: query
        name: timeMax
        description: Upper bound (exclusive) for an event's start time to filter by
      - in: query
        name: timeMin
        description: Lower bound (inclusive) for an event's end time to filter by
      - in: query
        name: timeZone
        description: Time zone used in the response
      - in: query
        name: updatedMin
        description: Lower bound for an event's last modification time (as a RFC3339
          timestamp) to filter by
      responses:
        200:
          description: OK
      tags:
      - Event
    post:
      summary: Create Event
      description: Creates an event
      operationId: calendar.events.insert
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: maxAttendees
        description: The maximum number of attendees to include in the response
      - in: query
        name: sendNotifications
        description: Whether to send notifications about the creation of the new event
      - in: query
        name: supportsAttachments
        description: Whether API client performing operation supports event attachments
      responses:
        200:
          description: OK
      tags:
      - Event
  /calendars/{calendarId}/events/import:
    post:
      summary: Import Event
      description: Imports an event
      operationId: calendar.events.import
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: supportsAttachments
        description: Whether API client performing operation supports event attachments
      responses:
        200:
          description: OK
      tags:
      - Event
  /calendars/{calendarId}/events/quickAdd:
    post:
      summary: Create Event
      description: Creates an event based on a simple text string
      operationId: calendar.events.quickAdd
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: sendNotifications
        description: Whether to send notifications about the creation of the event
      - in: query
        name: text
        description: The text describing the event to be created
      responses:
        200:
          description: OK
      tags:
      - Event
  /calendars/{calendarId}/events/watch:
    post:
      summary: Watch Event
      description: Watch for changes to Events resources
      operationId: calendar.events.watch
      parameters:
      - in: query
        name: alwaysIncludeEmail
        description: Whether to always include a value in the email field for the
          organizer, creator and attendees, even if no real email is available (i
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: iCalUID
        description: Specifies event ID in the iCalendar format to be included in
          the response
      - in: query
        name: maxAttendees
        description: The maximum number of attendees to include in the response
      - in: query
        name: maxResults
        description: Maximum number of events returned on one result page
      - in: query
        name: orderBy
        description: The order of the events returned in the result
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: query
        name: privateExtendedProperty
        description: Extended properties constraint specified as propertyName=value
      - in: query
        name: q
        description: Free text search terms to find events that match these terms
          in any field, except for extended properties
      - in: body
        name: resource
        schema:
          $ref: '#/definitions/holder'
      - in: query
        name: sharedExtendedProperty
        description: Extended properties constraint specified as propertyName=value
      - in: query
        name: showDeleted
        description: Whether to include deleted events (with status equals "cancelled")
          in the result
      - in: query
        name: showHiddenInvitations
        description: Whether to include hidden invitations in the result
      - in: query
        name: singleEvents
        description: Whether to expand recurring events into instances and only return
          single one-off events and instances of recurring events, but not the underlying
          recurring events themselves
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      - in: query
        name: timeMax
        description: Upper bound (exclusive) for an event's start time to filter by
      - in: query
        name: timeMin
        description: Lower bound (inclusive) for an event's end time to filter by
      - in: query
        name: timeZone
        description: Time zone used in the response
      - in: query
        name: updatedMin
        description: Lower bound for an event's last modification time (as a RFC3339
          timestamp) to filter by
      responses:
        200:
          description: OK
      tags:
      - Event
  /calendars/{calendarId}/events/{eventId}:
    delete:
      summary: Delete Event
      description: Deletes an event
      operationId: calendar.events.delete
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: eventId
        description: Event identifier
      - in: query
        name: sendNotifications
        description: Whether to send notifications about the deletion of the event
      responses:
        200:
          description: OK
      tags:
      - Event
    get:
      summary: Get Event
      description: Returns an event
      operationId: calendar.events.get
      parameters:
      - in: query
        name: alwaysIncludeEmail
        description: Whether to always include a value in the email field for the
          organizer, creator and attendees, even if no real email is available (i
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: eventId
        description: Event identifier
      - in: query
        name: maxAttendees
        description: The maximum number of attendees to include in the response
      - in: query
        name: timeZone
        description: Time zone used in the response
      responses:
        200:
          description: OK
      tags:
      - Event
    patch:
      summary: Update Event
      description: Updates an event
      operationId: calendar.events.patch
      parameters:
      - in: query
        name: alwaysIncludeEmail
        description: Whether to always include a value in the email field for the
          organizer, creator and attendees, even if no real email is available (i
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: eventId
        description: Event identifier
      - in: query
        name: maxAttendees
        description: The maximum number of attendees to include in the response
      - in: query
        name: sendNotifications
        description: Whether to send notifications about the event update (e
      - in: query
        name: supportsAttachments
        description: Whether API client performing operation supports event attachments
      responses:
        200:
          description: OK
      tags:
      - Event
    put:
      summary: Update Event
      description: Updates an event
      operationId: calendar.events.update
      parameters:
      - in: query
        name: alwaysIncludeEmail
        description: Whether to always include a value in the email field for the
          organizer, creator and attendees, even if no real email is available (i
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: eventId
        description: Event identifier
      - in: query
        name: maxAttendees
        description: The maximum number of attendees to include in the response
      - in: query
        name: sendNotifications
        description: Whether to send notifications about the event update (e
      - in: query
        name: supportsAttachments
        description: Whether API client performing operation supports event attachments
      responses:
        200:
          description: OK
      tags:
      - Event
  /calendars/{calendarId}/events/{eventId}/instances:
    get:
      summary: Get Event Instance
      description: Returns instances of the specified recurring event
      operationId: calendar.events.instances
      parameters:
      - in: query
        name: alwaysIncludeEmail
        description: Whether to always include a value in the email field for the
          organizer, creator and attendees, even if no real email is available (i
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: path
        name: eventId
        description: Recurring event identifier
      - in: query
        name: maxAttendees
        description: The maximum number of attendees to include in the response
      - in: query
        name: maxResults
        description: Maximum number of events returned on one result page
      - in: query
        name: originalStart
        description: The original start time of the instance in the result
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: query
        name: showDeleted
        description: Whether to include deleted events (with status equals "cancelled")
          in the result
      - in: query
        name: timeMax
        description: Upper bound (exclusive) for an event's start time to filter by
      - in: query
        name: timeMin
        description: Lower bound (inclusive) for an event's end time to filter by
      - in: query
        name: timeZone
        description: Time zone used in the response
      responses:
        200:
          description: OK
      tags:
      - Event
  /calendars/{calendarId}/events/{eventId}/move:
    post:
      summary: Move Event
      description: Moves an event to another calendar, i
      operationId: calendar.events.move
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier of the source calendar where the event currently
          is on
      - in: query
        name: destination
        description: Calendar identifier of the target calendar where the event is
          to be moved to
      - in: path
        name: eventId
        description: Event identifier
      - in: query
        name: sendNotifications
        description: Whether to send notifications about the change of the event's
          organizer
      responses:
        200:
          description: OK
      tags:
      - Event
  /channels/stop:
    post:
      summary: Stop Watching Resource
      description: Stop watching resources through this channel
      operationId: calendar.channels.stop
      parameters:
      - in: body
        name: resource
        schema:
          $ref: '#/definitions/holder'
      responses:
        200:
          description: OK
      tags:
      - Watch
  /colors:
    get:
      summary: Get Colors
      description: Returns the color definitions for calendars and events
      operationId: calendar.colors.get
      responses:
        200:
          description: OK
      tags:
      - Color
  /freeBusy:
    post:
      summary: Return Free/Busy Information
      description: Returns free/busy information for a set of calendars
      operationId: calendar.freebusy.query
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      responses:
        200:
          description: OK
      tags:
      - Free/Busy
  /users/me/calendarList:
    get:
      summary: Return Entries
      description: Returns entries on the user's calendar list
      operationId: calendar.calendarList.list
      parameters:
      - in: query
        name: maxResults
        description: Maximum number of entries returned on one result page
      - in: query
        name: minAccessRole
        description: The minimum access role for the user in the returned entries
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: query
        name: showDeleted
        description: Whether to include deleted calendar list entries in the result
      - in: query
        name: showHidden
        description: Whether to show hidden entries
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      responses:
        200:
          description: OK
      tags:
      - Event
    post:
      summary: Add Entry
      description: Adds an entry to the user's calendar list
      operationId: calendar.calendarList.insert
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: query
        name: colorRgbFormat
        description: Whether to use the foregroundColor and backgroundColor fields
          to write the calendar colors (RGB)
      responses:
        200:
          description: OK
      tags:
      - Event
  /users/me/calendarList/watch:
    post:
      summary: Watch Entry
      description: Watch for changes to CalendarList resources
      operationId: calendar.calendarList.watch
      parameters:
      - in: query
        name: maxResults
        description: Maximum number of entries returned on one result page
      - in: query
        name: minAccessRole
        description: The minimum access role for the user in the returned entries
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: body
        name: resource
        schema:
          $ref: '#/definitions/holder'
      - in: query
        name: showDeleted
        description: Whether to include deleted calendar list entries in the result
      - in: query
        name: showHidden
        description: Whether to show hidden entries
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      responses:
        200:
          description: OK
      tags:
      - Event
  /users/me/calendarList/{calendarId}:
    delete:
      summary: Delete Entry
      description: Deletes an entry on the user's calendar list
      operationId: calendar.calendarList.delete
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Event
    get:
      summary: Get Entry
      description: Returns an entry on the user's calendar list
      operationId: calendar.calendarList.get
      parameters:
      - in: path
        name: calendarId
        description: Calendar identifier
      responses:
        200:
          description: OK
      tags:
      - Event
    patch:
      summary: Update Entry
      description: Updates an entry on the user's calendar list
      operationId: calendar.calendarList.patch
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: colorRgbFormat
        description: Whether to use the foregroundColor and backgroundColor fields
          to write the calendar colors (RGB)
      responses:
        200:
          description: OK
      tags:
      - Event
    put:
      summary: Update Entry
      description: Updates an entry on the user's calendar list
      operationId: calendar.calendarList.update
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: calendarId
        description: Calendar identifier
      - in: query
        name: colorRgbFormat
        description: Whether to use the foregroundColor and backgroundColor fields
          to write the calendar colors (RGB)
      responses:
        200:
          description: OK
      tags:
      - Event
  /users/me/settings:
    get:
      summary: Get Settings
      description: Returns all user settings for the authenticated user
      operationId: calendar.settings.list
      parameters:
      - in: query
        name: maxResults
        description: Maximum number of entries returned on one result page
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      responses:
        200:
          description: OK
      tags:
      - Setting
  /users/me/settings/watch:
    post:
      summary: Watch Settings
      description: Watch for changes to Settings resources
      operationId: calendar.settings.watch
      parameters:
      - in: query
        name: maxResults
        description: Maximum number of entries returned on one result page
      - in: query
        name: pageToken
        description: Token specifying which result page to return
      - in: body
        name: resource
        schema:
          $ref: '#/definitions/holder'
      - in: query
        name: syncToken
        description: Token obtained from the nextSyncToken field returned on the last
          page of results from the previous list request
      responses:
        200:
          description: OK
      tags:
      - Setting
  /users/me/settings/{setting}:
    get:
      summary: Get Setting
      description: Returns a single user setting
      operationId: calendar.settings.get
      parameters:
      - in: path
        name: setting
        description: The id of the user setting
      responses:
        200:
          description: OK
      tags:
      - Setting
definitions:
  Acl:
    properties:
      etag:
        description: This is a default description.
        type: parameters
      items:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      nextPageToken:
        description: This is a default description.
        type: parameters
      nextSyncToken:
        description: This is a default description.
        type: parameters
  AclRule:
    properties:
      etag:
        description: This is a default description.
        type: parameters
      id:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      role:
        description: This is a default description.
        type: parameters
      scope:
        description: This is a default description.
        type: parameters
  Calendar:
    properties:
      description:
        description: This is a default description.
        type: parameters
      etag:
        description: This is a default description.
        type: parameters
      id:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      location:
        description: This is a default description.
        type: parameters
      summary:
        description: This is a default description.
        type: parameters
      timeZone:
        description: This is a default description.
        type: parameters
  CalendarList:
    properties:
      etag:
        description: This is a default description.
        type: parameters
      items:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      nextPageToken:
        description: This is a default description.
        type: parameters
      nextSyncToken:
        description: This is a default description.
        type: parameters
  CalendarListEntry:
    properties:
      accessRole:
        description: This is a default description.
        type: parameters
      backgroundColor:
        description: This is a default description.
        type: parameters
      colorId:
        description: This is a default description.
        type: parameters
      defaultReminders:
        description: This is a default description.
        type: parameters
      deleted:
        description: This is a default description.
        type: parameters
      description:
        description: This is a default description.
        type: parameters
      etag:
        description: This is a default description.
        type: parameters
      foregroundColor:
        description: This is a default description.
        type: parameters
      hidden:
        description: This is a default description.
        type: parameters
      id:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      location:
        description: This is a default description.
        type: parameters
      notificationSettings:
        description: This is a default description.
        type: parameters
      primary:
        description: This is a default description.
        type: parameters
      selected:
        description: This is a default description.
        type: parameters
      summary:
        description: This is a default description.
        type: parameters
      summaryOverride:
        description: This is a default description.
        type: parameters
      timeZone:
        description: This is a default description.
        type: parameters
  CalendarNotification:
    properties:
      method:
        description: This is a default description.
        type: parameters
      type:
        description: This is a default description.
        type: parameters
  Channel:
    properties:
      address:
        description: This is a default description.
        type: parameters
      expiration:
        description: This is a default description.
        type: parameters
      id:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      params:
        description: This is a default description.
        type: parameters
      payload:
        description: This is a default description.
        type: parameters
      resourceId:
        description: This is a default description.
        type: parameters
      resourceUri:
        description: This is a default description.
        type: parameters
      token:
        description: This is a default description.
        type: parameters
      type:
        description: This is a default description.
        type: parameters
  ColorDefinition:
    properties:
      background:
        description: This is a default description.
        type: parameters
      foreground:
        description: This is a default description.
        type: parameters
  Colors:
    properties:
      calendar:
        description: This is a default description.
        type: parameters
      event:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      updated:
        description: This is a default description.
        type: parameters
  Error:
    properties:
      domain:
        description: This is a default description.
        type: parameters
      reason:
        description: This is a default description.
        type: parameters
  Event:
    properties:
      anyoneCanAddSelf:
        description: This is a default description.
        type: parameters
      attachments:
        description: This is a default description.
        type: parameters
      attendees:
        description: This is a default description.
        type: parameters
      attendeesOmitted:
        description: This is a default description.
        type: parameters
      colorId:
        description: This is a default description.
        type: parameters
      created:
        description: This is a default description.
        type: parameters
      creator:
        description: This is a default description.
        type: parameters
      description:
        description: This is a default description.
        type: parameters
      endTimeUnspecified:
        description: This is a default description.
        type: parameters
      etag:
        description: This is a default description.
        type: parameters
      extendedProperties:
        description: This is a default description.
        type: parameters
      gadget:
        description: This is a default description.
        type: parameters
      guestsCanInviteOthers:
        description: This is a default description.
        type: parameters
      guestsCanModify:
        description: This is a default description.
        type: parameters
      guestsCanSeeOtherGuests:
        description: This is a default description.
        type: parameters
      hangoutLink:
        description: This is a default description.
        type: parameters
      htmlLink:
        description: This is a default description.
        type: parameters
      iCalUID:
        description: This is a default description.
        type: parameters
      id:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      location:
        description: This is a default description.
        type: parameters
      locked:
        description: This is a default description.
        type: parameters
      organizer:
        description: This is a default description.
        type: parameters
      privateCopy:
        description: This is a default description.
        type: parameters
      recurrence:
        description: This is a default description.
        type: parameters
      recurringEventId:
        description: This is a default description.
        type: parameters
      reminders:
        description: This is a default description.
        type: parameters
      sequence:
        description: This is a default description.
        type: parameters
      source:
        description: This is a default description.
        type: parameters
      status:
        description: This is a default description.
        type: parameters
      summary:
        description: This is a default description.
        type: parameters
      transparency:
        description: This is a default description.
        type: parameters
      updated:
        description: This is a default description.
        type: parameters
      visibility:
        description: This is a default description.
        type: parameters
  EventAttachment:
    properties:
      fileId:
        description: This is a default description.
        type: parameters
      fileUrl:
        description: This is a default description.
        type: parameters
      iconLink:
        description: This is a default description.
        type: parameters
      mimeType:
        description: This is a default description.
        type: parameters
      title:
        description: This is a default description.
        type: parameters
  EventAttendee:
    properties:
      additionalGuests:
        description: This is a default description.
        type: parameters
      comment:
        description: This is a default description.
        type: parameters
      displayName:
        description: This is a default description.
        type: parameters
      email:
        description: This is a default description.
        type: parameters
      id:
        description: This is a default description.
        type: parameters
      optional:
        description: This is a default description.
        type: parameters
      organizer:
        description: This is a default description.
        type: parameters
      resource:
        description: This is a default description.
        type: parameters
      responseStatus:
        description: This is a default description.
        type: parameters
      self:
        description: This is a default description.
        type: parameters
  EventDateTime:
    properties:
      date:
        description: This is a default description.
        type: parameters
      dateTime:
        description: This is a default description.
        type: parameters
      timeZone:
        description: This is a default description.
        type: parameters
  EventReminder:
    properties:
      method:
        description: This is a default description.
        type: parameters
      minutes:
        description: This is a default description.
        type: parameters
  Events:
    properties:
      accessRole:
        description: This is a default description.
        type: parameters
      defaultReminders:
        description: This is a default description.
        type: parameters
      description:
        description: This is a default description.
        type: parameters
      etag:
        description: This is a default description.
        type: parameters
      items:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      nextPageToken:
        description: This is a default description.
        type: parameters
      nextSyncToken:
        description: This is a default description.
        type: parameters
      summary:
        description: This is a default description.
        type: parameters
      timeZone:
        description: This is a default description.
        type: parameters
      updated:
        description: This is a default description.
        type: parameters
  FreeBusyCalendar:
    properties:
      busy:
        description: This is a default description.
        type: parameters
      errors:
        description: This is a default description.
        type: parameters
  FreeBusyGroup:
    properties:
      calendars:
        description: This is a default description.
        type: parameters
      errors:
        description: This is a default description.
        type: parameters
  FreeBusyRequest:
    properties:
      calendarExpansionMax:
        description: This is a default description.
        type: parameters
      groupExpansionMax:
        description: This is a default description.
        type: parameters
      items:
        description: This is a default description.
        type: parameters
      timeMax:
        description: This is a default description.
        type: parameters
      timeMin:
        description: This is a default description.
        type: parameters
      timeZone:
        description: This is a default description.
        type: parameters
  FreeBusyRequestItem:
    properties:
      id:
        description: This is a default description.
        type: parameters
  FreeBusyResponse:
    properties:
      calendars:
        description: This is a default description.
        type: parameters
      groups:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      timeMax:
        description: This is a default description.
        type: parameters
      timeMin:
        description: This is a default description.
        type: parameters
  Setting:
    properties:
      etag:
        description: This is a default description.
        type: parameters
      id:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      value:
        description: This is a default description.
        type: parameters
  Settings:
    properties:
      etag:
        description: This is a default description.
        type: parameters
      items:
        description: This is a default description.
        type: parameters
      kind:
        description: This is a default description.
        type: parameters
      nextPageToken:
        description: This is a default description.
        type: parameters
      nextSyncToken:
        description: This is a default description.
        type: parameters
  TimePeriod:
    properties:
      end:
        description: This is a default description.
        type: parameters
      start:
        description: This is a default description.
        type: parameters
```

--------------------------------------------------------------------------------
/gmail-api-openapi-spec.yaml:
--------------------------------------------------------------------------------

```yaml
agger: "2.0"
info:
  title: Gmail
  description: Access Gmail mailboxes including sending user email.
  contact:
    name: Google
    url: https://google.com
  version: v1
host: www.googleapis.com
basePath: /gmail/v1/users
schemes:
- http
produces:
- application/json
consumes:
- application/json
paths:
  /{userId}/drafts:
    get:
      summary: Get Drafts
      description: Lists the drafts in the user's mailbox
      operationId: gmail.users.drafts.list
      parameters:
      - in: query
        name: includeSpamTrash
        description: Include drafts from SPAM and TRASH in the results
      - in: query
        name: maxResults
        description: Maximum number of drafts to return
      - in: query
        name: pageToken
        description: Page token to retrieve a specific page of results in the list
      - in: query
        name: q
        description: Only return draft messages matching the specified query
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
    post:
      summary: Update Draft
      description: Creates a new draft with the DRAFT label
      operationId: gmail.users.drafts.create
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/drafts/send:
    post:
      summary: Send Draft
      description: Sends the specified, existing draft to the recipients in the To,
        Cc, and Bcc headers
      operationId: gmail.users.drafts.send
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/drafts/{id}:
    delete:
      summary: Delete Draft
      description: Immediately and permanently deletes the specified draft
      operationId: gmail.users.drafts.delete
      parameters:
      - in: path
        name: id
        description: The ID of the draft to delete
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
    get:
      summary: Get Draft
      description: Gets the specified draft
      operationId: gmail.users.drafts.get
      parameters:
      - in: query
        name: format
        description: The format to return the draft in
      - in: path
        name: id
        description: The ID of the draft to retrieve
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
    put:
      summary: Update Draft
      description: Replaces a draft's content
      operationId: gmail.users.drafts.update
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: id
        description: The ID of the draft to update
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/history:
    get:
      summary: Get History
      description: Lists the history of all changes to the given mailbox
      operationId: gmail.users.history.list
      parameters:
      - in: query
        name: historyTypes
        description: History types to be returned by the function
      - in: query
        name: labelId
        description: Only return messages with a label matching the ID
      - in: query
        name: maxResults
        description: The maximum number of history records to return
      - in: query
        name: pageToken
        description: Page token to retrieve a specific page of results in the list
      - in: query
        name: startHistoryId
        description: Required
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - History
  /{userId}/labels:
    get:
      summary: Get Labels
      description: Lists all labels in the user's mailbox
      operationId: gmail.users.labels.list
      parameters:
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Label
    post:
      summary: Create Label
      description: Creates a new label
      operationId: gmail.users.labels.create
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Label
  /{userId}/labels/{id}:
    delete:
      summary: Delete Lbel
      description: Immediately and permanently deletes the specified label and removes
        it from any messages and threads that it is applied to
      operationId: gmail.users.labels.delete
      parameters:
      - in: path
        name: id
        description: The ID of the label to delete
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Label
    get:
      summary: Get Label
      description: Gets the specified label
      operationId: gmail.users.labels.get
      parameters:
      - in: path
        name: id
        description: The ID of the label to retrieve
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Label
    patch:
      summary: Update Label
      description: Updates the specified label
      operationId: gmail.users.labels.patch
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: id
        description: The ID of the label to update
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Label
    put:
      summary: Update Label
      description: Updates the specified label
      operationId: gmail.users.labels.update
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: id
        description: The ID of the label to update
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Label
  /{userId}/messages:
    get:
      summary: Get Message
      description: Lists the messages in the user's mailbox
      operationId: gmail.users.messages.list
      parameters:
      - in: query
        name: includeSpamTrash
        description: Include messages from SPAM and TRASH in the results
      - in: query
        name: labelIds
        description: Only return messages with labels that match all of the specified
          label IDs
      - in: query
        name: maxResults
        description: Maximum number of messages to return
      - in: query
        name: pageToken
        description: Page token to retrieve a specific page of results in the list
      - in: query
        name: q
        description: Only return messages matching the specified query
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
    post:
      summary: Create Message
      description: Directly inserts a message into only this user's mailbox similar
        to IMAP APPEND, bypassing most scanning and classification
      operationId: gmail.users.messages.insert
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: query
        name: deleted
        description: Mark the email as permanently deleted (not TRASH) and only visible
          in Google Vault to a Vault administrator
      - in: query
        name: internalDateSource
        description: Source for Gmail's internal date of the message
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/batchDelete:
    post:
      summary: Delete Messages
      description: Deletes many messages by message ID
      operationId: gmail.users.messages.batchDelete
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/batchModify:
    post:
      summary: Update Label
      description: Modifies the labels on the specified messages
      operationId: gmail.users.messages.batchModify
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/import:
    post:
      summary: Import Message
      description: Imports a message into only this user's mailbox, with standard
        email delivery scanning and classification similar to receiving via SMTP
      operationId: gmail.users.messages.import
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: query
        name: deleted
        description: Mark the email as permanently deleted (not TRASH) and only visible
          in Google Vault to a Vault administrator
      - in: query
        name: internalDateSource
        description: Source for Gmail's internal date of the message
      - in: query
        name: neverMarkSpam
        description: Ignore the Gmail spam classifier decision and never mark this
          email as SPAM in the mailbox
      - in: query
        name: processForCalendar
        description: Process calendar invites in the email and add any extracted meetings
          to the Google Calendar for this user
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/send:
    post:
      summary: Send Message
      description: Sends the specified message to the recipients in the To, Cc, and
        Bcc headers
      operationId: gmail.users.messages.send
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/{id}:
    delete:
      summary: Delete Message
      description: Immediately and permanently deletes the specified message
      operationId: gmail.users.messages.delete
      parameters:
      - in: path
        name: id
        description: The ID of the message to delete
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
    get:
      summary: Get Message
      description: Gets the specified message
      operationId: gmail.users.messages.get
      parameters:
      - in: query
        name: format
        description: The format to return the message in
      - in: path
        name: id
        description: The ID of the message to retrieve
      - in: query
        name: metadataHeaders
        description: When given and format is METADATA, only include headers specified
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/{id}/modify:
    post:
      summary: Modify message
      description: Modifies the labels on the specified message
      operationId: gmail.users.messages.modify
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: id
        description: The ID of the message to modify
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/{id}/trash:
    post:
      summary: Trash Message
      description: Moves the specified message to the trash
      operationId: gmail.users.messages.trash
      parameters:
      - in: path
        name: id
        description: The ID of the message to Trash
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/{id}/untrash:
    post:
      summary: UnTrash Message
      description: Removes the specified message from the trash
      operationId: gmail.users.messages.untrash
      parameters:
      - in: path
        name: id
        description: The ID of the message to remove from Trash
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/messages/{messageId}/attachments/{id}:
    get:
      summary: Get Attachments
      description: Gets the specified message attachment
      operationId: gmail.users.messages.attachments.get
      parameters:
      - in: path
        name: id
        description: The ID of the attachment
      - in: path
        name: messageId
        description: The ID of the message containing the attachment
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email
  /{userId}/profile:
    get:
      summary: Get Profile
      description: Gets the current user's Gmail profile
      operationId: gmail.users.getProfile
      parameters:
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - User
  /{userId}/settings/autoForwarding:
    get:
      summary: Get Auto-Forwarding Settings
      description: Gets the auto-forwarding setting for the specified account
      operationId: gmail.users.settings.getAutoForwarding
      parameters:
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Settings
    put:
      summary: Update Auto-Forwarding Settings
      description: Updates the auto-forwarding setting for the specified account
      operationId: gmail.users.settings.updateAutoForwarding
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Settings
  /{userId}/settings/filters:
    get:
      summary: Get Message Filters
      description: Lists the message filters of a Gmail user
      operationId: gmail.users.settings.filters.list
      parameters:
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Filters
    post:
      summary: Create Message Filters
      description: Creates a filter
      operationId: gmail.users.settings.filters.create
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Filters
  /{userId}/settings/filters/{id}:
    delete:
      summary: Delete Message Filter
      description: Deletes a filter
      operationId: gmail.users.settings.filters.delete
      parameters:
      - in: path
        name: id
        description: The ID of the filter to be deleted
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Filters
    get:
      summary: Get Message Filter
      description: Gets a filter
      operationId: gmail.users.settings.filters.get
      parameters:
      - in: path
        name: id
        description: The ID of the filter to be fetched
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Filters
  /{userId}/settings/forwardingAddresses:
    get:
      summary: Get Forward Addresses
      description: Lists the forwarding addresses for the specified account
      operationId: gmail.users.settings.forwardingAddresses.list
      parameters:
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Forwarding Address
    post:
      summary: Create Forward Addresse
      description: Creates a forwarding address
      operationId: gmail.users.settings.forwardingAddresses.create
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Forwarding Address
  /{userId}/settings/forwardingAddresses/{forwardingEmail}:
    delete:
      summary: Delete Forward Address
      description: Deletes the specified forwarding address and revokes any verification
        that may have been required
      operationId: gmail.users.settings.forwardingAddresses.delete
      parameters:
      - in: path
        name: forwardingEmail
        description: The forwarding address to be deleted
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Forwarding Address
    get:
      summary: GGetet Forward Address
      description: Gets the specified forwarding address
      operationId: gmail.users.settings.forwardingAddresses.get
      parameters:
      - in: path
        name: forwardingEmail
        description: The forwarding address to be retrieved
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Forwarding Address
  /{userId}/settings/imap:
    get:
      summary: Gets IMAP Settings
      description: Gets IMAP settings
      operationId: gmail.users.settings.getImap
      parameters:
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - IMAP Settings
    put:
      summary: Update IMAP Setting
      description: Updates IMAP settings
      operationId: gmail.users.settings.updateImap
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - IMAP Settings
  /{userId}/settings/pop:
    get:
      summary: Gets POP Settings
      description: Gets POP settings
      operationId: gmail.users.settings.getPop
      parameters:
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - ""
    put:
      summary: Update IMAP Setting
      description: Updates POP settings
      operationId: gmail.users.settings.updatePop
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - POP Settings
  /{userId}/settings/sendAs:
    get:
      summary: Send As Alias
      description: Lists the send-as aliases for the specified account
      operationId: gmail.users.settings.sendAs.list
      parameters:
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Alias
    post:
      summary: Create Alias
      description: Creates a custom "from" send-as alias
      operationId: gmail.users.settings.sendAs.create
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Alias
  /{userId}/settings/sendAs/{sendAsEmail}:
    delete:
      summary: Delete Alias
      description: Deletes the specified send-as alias
      operationId: gmail.users.settings.sendAs.delete
      parameters:
      - in: path
        name: sendAsEmail
        description: The send-as alias to be deleted
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Alias
    get:
      summary: Get Alias
      description: Gets the specified send-as alias
      operationId: gmail.users.settings.sendAs.get
      parameters:
      - in: path
        name: sendAsEmail
        description: The send-as alias to be retrieved
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Alias
    patch:
      summary: Update Alias
      description: Updates a send-as alias
      operationId: gmail.users.settings.sendAs.patch
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: sendAsEmail
        description: The send-as alias to be updated
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Alias
    put:
      summary: Update Alias
      description: Updates a send-as alias
      operationId: gmail.users.settings.sendAs.update
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: sendAsEmail
        description: The send-as alias to be updated
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Alias
  /{userId}/settings/sendAs/{sendAsEmail}/smimeInfo:
    get:
      summary: Get S/MIME Configurations
      description: Lists S/MIME configs for the specified send-as alias
      operationId: gmail.users.settings.sendAs.smimeInfo.list
      parameters:
      - in: path
        name: sendAsEmail
        description: The email address that appears in the "From:" header for mail
          sent using this alias
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - S/MIME Configuration
    post:
      summary: Create S/MIME Configurations
      description: Insert (upload) the given S/MIME config for the specified send-as
        alias
      operationId: gmail.users.settings.sendAs.smimeInfo.insert
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: sendAsEmail
        description: The email address that appears in the "From:" header for mail
          sent using this alias
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - S/MIME Configuration
  /{userId}/settings/sendAs/{sendAsEmail}/smimeInfo/{id}:
    delete:
      summary: Delete S/MIME Configurations
      description: Deletes the specified S/MIME config for the specified send-as alias
      operationId: gmail.users.settings.sendAs.smimeInfo.delete
      parameters:
      - in: path
        name: id
        description: The immutable ID for the SmimeInfo
      - in: path
        name: sendAsEmail
        description: The email address that appears in the "From:" header for mail
          sent using this alias
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - S/MIME Configuration
    get:
      summary: Get S/MIME Configuration
      description: Gets the specified S/MIME config for the specified send-as alias
      operationId: gmail.users.settings.sendAs.smimeInfo.get
      parameters:
      - in: path
        name: id
        description: The immutable ID for the SmimeInfo
      - in: path
        name: sendAsEmail
        description: The email address that appears in the "From:" header for mail
          sent using this alias
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - S/MIME Configuration
  /{userId}/settings/sendAs/{sendAsEmail}/smimeInfo/{id}/setDefault:
    post:
      summary: Create Default S/MIME Configurations
      description: Sets the default S/MIME config for the specified send-as alias
      operationId: gmail.users.settings.sendAs.smimeInfo.setDefault
      parameters:
      - in: path
        name: id
        description: The immutable ID for the SmimeInfo
      - in: path
        name: sendAsEmail
        description: The email address that appears in the "From:" header for mail
          sent using this alias
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - S/MIME Configuration
  /{userId}/settings/sendAs/{sendAsEmail}/verify:
    post:
      summary: Send Verification Email
      description: Sends a verification email to the specified send-as alias address
      operationId: gmail.users.settings.sendAs.verify
      parameters:
      - in: path
        name: sendAsEmail
        description: The send-as alias to be verified
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Verification
  /{userId}/settings/vacation:
    get:
      summary: Get Vacation Settings
      description: Gets vacation responder settings
      operationId: gmail.users.settings.getVacation
      parameters:
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Vacation Settings
    put:
      summary: Update Vacation Settings
      description: Updates vacation responder settings
      operationId: gmail.users.settings.updateVacation
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: User's email address
      responses:
        200:
          description: OK
      tags:
      - Vacation Settings
  /{userId}/stop:
    post:
      summary: Stop Push Notifications
      description: Stop receiving push notifications for the given user mailbox
      operationId: gmail.users.stop
      parameters:
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Push Notification
  /{userId}/threads:
    get:
      summary: Get Threads
      description: Lists the threads in the user's mailbox
      operationId: gmail.users.threads.list
      parameters:
      - in: query
        name: includeSpamTrash
        description: Include threads from SPAM and TRASH in the results
      - in: query
        name: labelIds
        description: Only return threads with labels that match all of the specified
          label IDs
      - in: query
        name: maxResults
        description: Maximum number of threads to return
      - in: query
        name: pageToken
        description: Page token to retrieve a specific page of results in the list
      - in: query
        name: q
        description: Only return threads matching the specified query
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email Thread
  /{userId}/threads/{id}:
    delete:
      summary: Delete Threads
      description: Immediately and permanently deletes the specified thread
      operationId: gmail.users.threads.delete
      parameters:
      - in: path
        name: id
        description: ID of the Thread to delete
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email Thread
    get:
      summary: Get Threads
      description: Gets the specified thread
      operationId: gmail.users.threads.get
      parameters:
      - in: query
        name: format
        description: The format to return the messages in
      - in: path
        name: id
        description: The ID of the thread to retrieve
      - in: query
        name: metadataHeaders
        description: When given and format is METADATA, only include headers specified
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email Thread
  /{userId}/threads/{id}/modify:
    post:
      summary: Modify Thread labels
      description: Modifies the labels applied to the thread
      operationId: gmail.users.threads.modify
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: id
        description: The ID of the thread to modify
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email Thread
  /{userId}/threads/{id}/trash:
    post:
      summary: Trash Thread
      description: Moves the specified thread to the trash
      operationId: gmail.users.threads.trash
      parameters:
      - in: path
        name: id
        description: The ID of the thread to Trash
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email Thread
  /{userId}/threads/{id}/untrash:
    post:
      summary: UnTrash Threat
      description: Removes the specified thread from the trash
      operationId: gmail.users.threads.untrash
      parameters:
      - in: path
        name: id
        description: The ID of the thread to remove from Trash
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Email Thread
  /{userId}/watch:
    post:
      summary: Send Push Notification
      description: Set up or update a push notification watch on the given user mailbox
      operationId: gmail.users.watch
      parameters:
      - in: body
        name: body
        schema:
          $ref: '#/definitions/holder'
      - in: path
        name: userId
        description: The user's email address
      responses:
        200:
          description: OK
      tags:
      - Push Notification
definitions:
  AutoForwarding:
    properties:
      disposition:
        description: This is a default description.
        type: post
      emailAddress:
        description: This is a default description.
        type: post
      enabled:
        description: This is a default description.
        type: post
  BatchDeleteMessagesRequest:
    properties:
      ids:
        description: This is a default description.
        type: post
  BatchModifyMessagesRequest:
    properties:
      addLabelIds:
        description: This is a default description.
        type: post
      ids:
        description: This is a default description.
        type: post
      removeLabelIds:
        description: This is a default description.
        type: post
  Draft:
    properties:
      id:
        description: This is a default description.
        type: post
  Filter:
    properties:
      id:
        description: This is a default description.
        type: post
  FilterAction:
    properties:
      addLabelIds:
        description: This is a default description.
        type: post
      forward:
        description: This is a default description.
        type: post
      removeLabelIds:
        description: This is a default description.
        type: post
  FilterCriteria:
    properties:
      excludeChats:
        description: This is a default description.
        type: post
      from:
        description: This is a default description.
        type: post
      hasAttachment:
        description: This is a default description.
        type: post
      negatedQuery:
        description: This is a default description.
        type: post
      query:
        description: This is a default description.
        type: post
      size:
        description: This is a default description.
        type: post
      sizeComparison:
        description: This is a default description.
        type: post
      subject:
        description: This is a default description.
        type: post
      to:
        description: This is a default description.
        type: post
  ForwardingAddress:
    properties:
      forwardingEmail:
        description: This is a default description.
        type: post
      verificationStatus:
        description: This is a default description.
        type: post
  History:
    properties:
      id:
        description: This is a default description.
        type: post
      labelsAdded:
        description: This is a default description.
        type: post
      labelsRemoved:
        description: This is a default description.
        type: post
      messages:
        description: This is a default description.
        type: post
      messagesAdded:
        description: This is a default description.
        type: post
      messagesDeleted:
        description: This is a default description.
        type: post
  HistoryLabelAdded:
    properties:
      labelIds:
        description: This is a default description.
        type: post
  HistoryLabelRemoved:
    properties:
      labelIds:
        description: This is a default description.
        type: post
  HistoryMessageAdded:
    properties: []
  HistoryMessageDeleted:
    properties: []
  ImapSettings:
    properties:
      autoExpunge:
        description: This is a default description.
        type: post
      enabled:
        description: This is a default description.
        type: post
      expungeBehavior:
        description: This is a default description.
        type: post
      maxFolderSize:
        description: This is a default description.
        type: post
  Label:
    properties:
      id:
        description: This is a default description.
        type: post
      labelListVisibility:
        description: This is a default description.
        type: post
      messageListVisibility:
        description: This is a default description.
        type: post
      messagesTotal:
        description: This is a default description.
        type: post
      messagesUnread:
        description: This is a default description.
        type: post
      name:
        description: This is a default description.
        type: post
      threadsTotal:
        description: This is a default description.
        type: post
      threadsUnread:
        description: This is a default description.
        type: post
      type:
        description: This is a default description.
        type: post
  ListDraftsResponse:
    properties:
      drafts:
        description: This is a default description.
        type: post
      nextPageToken:
        description: This is a default description.
        type: post
      resultSizeEstimate:
        description: This is a default description.
        type: post
  ListFiltersResponse:
    properties:
      filter:
        description: This is a default description.
        type: post
  ListForwardingAddressesResponse:
    properties:
      forwardingAddresses:
        description: This is a default description.
        type: post
  ListHistoryResponse:
    properties:
      history:
        description: This is a default description.
        type: post
      historyId:
        description: This is a default description.
        type: post
      nextPageToken:
        description: This is a default description.
        type: post
  ListLabelsResponse:
    properties:
      labels:
        description: This is a default description.
        type: post
  ListMessagesResponse:
    properties:
      messages:
        description: This is a default description.
        type: post
      nextPageToken:
        description: This is a default description.
        type: post
      resultSizeEstimate:
        description: This is a default description.
        type: post
  ListSendAsResponse:
    properties:
      sendAs:
        description: This is a default description.
        type: post
  ListSmimeInfoResponse:
    properties:
      smimeInfo:
        description: This is a default description.
        type: post
  ListThreadsResponse:
    properties:
      nextPageToken:
        description: This is a default description.
        type: post
      resultSizeEstimate:
        description: This is a default description.
        type: post
      threads:
        description: This is a default description.
        type: post
  Message:
    properties:
      historyId:
        description: This is a default description.
        type: post
      id:
        description: This is a default description.
        type: post
      internalDate:
        description: This is a default description.
        type: post
      labelIds:
        description: This is a default description.
        type: post
      raw:
        description: This is a default description.
        type: post
      sizeEstimate:
        description: This is a default description.
        type: post
      snippet:
        description: This is a default description.
        type: post
      threadId:
        description: This is a default description.
        type: post
  MessagePart:
    properties:
      filename:
        description: This is a default description.
        type: post
      headers:
        description: This is a default description.
        type: post
      mimeType:
        description: This is a default description.
        type: post
      partId:
        description: This is a default description.
        type: post
      parts:
        description: This is a default description.
        type: post
  MessagePartBody:
    properties:
      attachmentId:
        description: This is a default description.
        type: post
      data:
        description: This is a default description.
        type: post
      size:
        description: This is a default description.
        type: post
  MessagePartHeader:
    properties:
      name:
        description: This is a default description.
        type: post
      value:
        description: This is a default description.
        type: post
  ModifyMessageRequest:
    properties:
      addLabelIds:
        description: This is a default description.
        type: post
      removeLabelIds:
        description: This is a default description.
        type: post
  ModifyThreadRequest:
    properties:
      addLabelIds:
        description: This is a default description.
        type: post
      removeLabelIds:
        description: This is a default description.
        type: post
  PopSettings:
    properties:
      accessWindow:
        description: This is a default description.
        type: post
      disposition:
        description: This is a default description.
        type: post
  Profile:
    properties:
      emailAddress:
        description: This is a default description.
        type: post
      historyId:
        description: This is a default description.
        type: post
      messagesTotal:
        description: This is a default description.
        type: post
      threadsTotal:
        description: This is a default description.
        type: post
  SendAs:
    properties:
      displayName:
        description: This is a default description.
        type: post
      isDefault:
        description: This is a default description.
        type: post
      isPrimary:
        description: This is a default description.
        type: post
      replyToAddress:
        description: This is a default description.
        type: post
      sendAsEmail:
        description: This is a default description.
        type: post
      signature:
        description: This is a default description.
        type: post
      treatAsAlias:
        description: This is a default description.
        type: post
      verificationStatus:
        description: This is a default description.
        type: post
  SmimeInfo:
    properties:
      encryptedKeyPassword:
        description: This is a default description.
        type: post
      expiration:
        description: This is a default description.
        type: post
      id:
        description: This is a default description.
        type: post
      isDefault:
        description: This is a default description.
        type: post
      issuerCn:
        description: This is a default description.
        type: post
      pem:
        description: This is a default description.
        type: post
      pkcs12:
        description: This is a default description.
        type: post
  SmtpMsa:
    properties:
      host:
        description: This is a default description.
        type: post
      password:
        description: This is a default description.
        type: post
      port:
        description: This is a default description.
        type: post
      securityMode:
        description: This is a default description.
        type: post
      username:
        description: This is a default description.
        type: post
  Thread:
    properties:
      historyId:
        description: This is a default description.
        type: post
      id:
        description: This is a default description.
        type: post
      messages:
        description: This is a default description.
        type: post
      snippet:
        description: This is a default description.
        type: post
  VacationSettings:
    properties:
      enableAutoReply:
        description: This is a default description.
        type: post
      endTime:
        description: This is a default description.
        type: post
      responseBodyHtml:
        description: This is a default description.
        type: post
      responseBodyPlainText:
        description: This is a default description.
        type: post
      responseSubject:
        description: This is a default description.
        type: post
      restrictToContacts:
        description: This is a default description.
        type: post
      restrictToDomain:
        description: This is a default description.
        type: post
      startTime:
        description: This is a default description.
        type: post
  WatchRequest:
    properties:
      labelFilterAction:
        description: This is a default description.
        type: post
      labelIds:
        description: This is a default description.
        type: post
      topicName:
        description: This is a default description.
        type: post
  WatchResponse:
    properties:
      expiration:
        description: This is a default description.
        type: post
      historyId:
        description: This is a default description.
        type: post
```
Page 1/2FirstPrevNextLast