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 [](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 ```