google-workspace-mcp 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- google_workspace_mcp/__init__.py +3 -0
- google_workspace_mcp/__main__.py +43 -0
- google_workspace_mcp/app.py +8 -0
- google_workspace_mcp/auth/__init__.py +7 -0
- google_workspace_mcp/auth/gauth.py +62 -0
- google_workspace_mcp/config.py +60 -0
- google_workspace_mcp/prompts/__init__.py +3 -0
- google_workspace_mcp/prompts/calendar.py +36 -0
- google_workspace_mcp/prompts/drive.py +18 -0
- google_workspace_mcp/prompts/gmail.py +65 -0
- google_workspace_mcp/prompts/slides.py +40 -0
- google_workspace_mcp/resources/__init__.py +13 -0
- google_workspace_mcp/resources/calendar.py +79 -0
- google_workspace_mcp/resources/drive.py +93 -0
- google_workspace_mcp/resources/gmail.py +58 -0
- google_workspace_mcp/resources/sheets_resources.py +92 -0
- google_workspace_mcp/resources/slides.py +421 -0
- google_workspace_mcp/services/__init__.py +21 -0
- google_workspace_mcp/services/base.py +73 -0
- google_workspace_mcp/services/calendar.py +256 -0
- google_workspace_mcp/services/docs_service.py +388 -0
- google_workspace_mcp/services/drive.py +454 -0
- google_workspace_mcp/services/gmail.py +676 -0
- google_workspace_mcp/services/sheets_service.py +466 -0
- google_workspace_mcp/services/slides.py +959 -0
- google_workspace_mcp/tools/__init__.py +7 -0
- google_workspace_mcp/tools/calendar.py +229 -0
- google_workspace_mcp/tools/docs_tools.py +277 -0
- google_workspace_mcp/tools/drive.py +221 -0
- google_workspace_mcp/tools/gmail.py +344 -0
- google_workspace_mcp/tools/sheets_tools.py +322 -0
- google_workspace_mcp/tools/slides.py +478 -0
- google_workspace_mcp/utils/__init__.py +1 -0
- google_workspace_mcp/utils/markdown_slides.py +504 -0
- google_workspace_mcp-1.0.0.dist-info/METADATA +547 -0
- google_workspace_mcp-1.0.0.dist-info/RECORD +38 -0
- google_workspace_mcp-1.0.0.dist-info/WHEEL +4 -0
- google_workspace_mcp-1.0.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
"""
|
2
|
+
Main entry point for running the MCP server via python -m google_workspace_mcp
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
|
7
|
+
from google_workspace_mcp import config # noqa: F401
|
8
|
+
from google_workspace_mcp.app import mcp # Import instance from central location
|
9
|
+
|
10
|
+
# Import all modules that register components with the FastMCP instance
|
11
|
+
from google_workspace_mcp.prompts import calendar as calendar_prompts # noqa: F401
|
12
|
+
from google_workspace_mcp.prompts import drive as drive_prompts # noqa: F401
|
13
|
+
from google_workspace_mcp.prompts import gmail as gmail_prompts # noqa: F401
|
14
|
+
from google_workspace_mcp.prompts import slides as slides_prompts # noqa: F401
|
15
|
+
|
16
|
+
# Register resources
|
17
|
+
from google_workspace_mcp.resources import calendar as calendar_resources # noqa: F401
|
18
|
+
from google_workspace_mcp.resources import drive as drive_resources # noqa: F401
|
19
|
+
from google_workspace_mcp.resources import gmail as gmail_resources # noqa: F401
|
20
|
+
from google_workspace_mcp.resources import sheets_resources # noqa: F401
|
21
|
+
from google_workspace_mcp.resources import slides as slides_resources # noqa: F401
|
22
|
+
|
23
|
+
# Register tools
|
24
|
+
from google_workspace_mcp.tools import ( # noqa: F401
|
25
|
+
calendar_tools,
|
26
|
+
docs_tools,
|
27
|
+
drive_tools,
|
28
|
+
gmail_tools,
|
29
|
+
sheets_tools,
|
30
|
+
slides_tools,
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
def main():
|
35
|
+
"""Main entry point for the MCP server."""
|
36
|
+
try:
|
37
|
+
asyncio.run(mcp.run())
|
38
|
+
except KeyboardInterrupt:
|
39
|
+
print("\nShutting down MCP server...")
|
40
|
+
|
41
|
+
|
42
|
+
if __name__ == "__main__":
|
43
|
+
main()
|
@@ -0,0 +1,62 @@
|
|
1
|
+
"""
|
2
|
+
Google authentication utilities for Google Workspace MCP.
|
3
|
+
|
4
|
+
Handles OAuth 2.0 authentication using environment variables.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
import os
|
9
|
+
from functools import lru_cache
|
10
|
+
|
11
|
+
import google.auth.exceptions
|
12
|
+
import google.auth.transport.requests
|
13
|
+
from google.oauth2.credentials import Credentials
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
@lru_cache(maxsize=32)
|
19
|
+
def get_credentials():
|
20
|
+
"""
|
21
|
+
Retrieves Google OAuth credentials from environment variables.
|
22
|
+
|
23
|
+
The credentials are cached to avoid redundant processing, but will
|
24
|
+
be automatically refreshed when expired.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
google.oauth2.credentials.Credentials: Initialized credentials object.
|
28
|
+
|
29
|
+
Raises:
|
30
|
+
ValueError: If any of the required environment variables are missing.
|
31
|
+
CredentialsError: If credentials are invalid or revoked.
|
32
|
+
"""
|
33
|
+
client_id = os.environ.get("GOOGLE_WORKSPACE_CLIENT_ID")
|
34
|
+
client_secret = os.environ.get("GOOGLE_WORKSPACE_CLIENT_SECRET")
|
35
|
+
refresh_token = os.environ.get("GOOGLE_WORKSPACE_REFRESH_TOKEN")
|
36
|
+
|
37
|
+
if not client_id:
|
38
|
+
raise ValueError("Environment variable 'GOOGLE_WORKSPACE_CLIENT_ID' is required but not set.")
|
39
|
+
if not client_secret:
|
40
|
+
raise ValueError("Environment variable 'GOOGLE_WORKSPACE_CLIENT_SECRET' is required but not set.")
|
41
|
+
if not refresh_token:
|
42
|
+
raise ValueError("Environment variable 'GOOGLE_WORKSPACE_REFRESH_TOKEN' is required but not set.")
|
43
|
+
|
44
|
+
logger.info("Successfully retrieved Google Workspace credentials from environment variables.")
|
45
|
+
|
46
|
+
try:
|
47
|
+
return Credentials(
|
48
|
+
token=None, # Will be fetched automatically on first use
|
49
|
+
refresh_token=refresh_token,
|
50
|
+
token_uri="https://oauth2.googleapis.com/token",
|
51
|
+
client_id=client_id,
|
52
|
+
client_secret=client_secret,
|
53
|
+
)
|
54
|
+
# The google-auth library automatically handles token refreshing
|
55
|
+
# when needed during API requests using the provided refresh token.
|
56
|
+
|
57
|
+
except google.auth.exceptions.RefreshError as e:
|
58
|
+
logger.exception("Credentials refresh failed - token may be revoked or expired")
|
59
|
+
raise ValueError(f"Invalid or revoked credentials: {e}") from e
|
60
|
+
except Exception as e:
|
61
|
+
logger.exception("Failed to create Credentials object")
|
62
|
+
raise ValueError(f"Failed to initialize credentials: {e}") from e
|
@@ -0,0 +1,60 @@
|
|
1
|
+
"""
|
2
|
+
MCP server utilities for Google Workspace integration.
|
3
|
+
This file now contains utility functions, like parsing capabilities.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
import logging
|
8
|
+
import os
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
# Parse enabled capabilities from environment
|
14
|
+
def get_enabled_capabilities() -> set[str]:
|
15
|
+
"""
|
16
|
+
Get the set of enabled capabilities from environment variables.
|
17
|
+
Expects GOOGLE_WORKSPACE_ENABLED_CAPABILITIES to be a JSON array of strings.
|
18
|
+
E.g., '["drive", "gmail", "docs"]'
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
set[str]: Enabled capability names
|
22
|
+
"""
|
23
|
+
capabilities_str = os.environ.get("GOOGLE_WORKSPACE_ENABLED_CAPABILITIES", "[]") # Default to empty JSON list string
|
24
|
+
|
25
|
+
if not capabilities_str.strip():
|
26
|
+
capabilities_str = "[]" # Treat fully empty or whitespace-only string as empty list
|
27
|
+
|
28
|
+
capabilities: set[str] = set()
|
29
|
+
try:
|
30
|
+
parsed_list = json.loads(capabilities_str)
|
31
|
+
if isinstance(parsed_list, list) and all(isinstance(item, str) for item in parsed_list):
|
32
|
+
capabilities = {cap.strip().lower() for cap in parsed_list if cap.strip()}
|
33
|
+
else:
|
34
|
+
logger.warning(
|
35
|
+
f"GOOGLE_WORKSPACE_ENABLED_CAPABILITIES is not a valid JSON list of strings: {capabilities_str}. "
|
36
|
+
f"Found type: {type(parsed_list)}. All tools may be affected. "
|
37
|
+
'Please use format like \'["drive", "gmail"]\'.'
|
38
|
+
)
|
39
|
+
# Fallback to empty set, effectively disabling tools unless Hub ignores this
|
40
|
+
# Or, decide if we should raise an error or parse common old format as fallback
|
41
|
+
# For now, strict parsing and warning.
|
42
|
+
except json.JSONDecodeError:
|
43
|
+
logger.warning(
|
44
|
+
f"GOOGLE_WORKSPACE_ENABLED_CAPABILITIES is not valid JSON: {capabilities_str}. "
|
45
|
+
'All tools may be affected. Please use format like \'["drive", "gmail"]\'.'
|
46
|
+
)
|
47
|
+
# Fallback for non-JSON or badly formatted JSON
|
48
|
+
# Consider if a fallback to comma-separated parsing is desired for backward compatibility or remove.
|
49
|
+
# For now, strict parsing and warning.
|
50
|
+
|
51
|
+
if not capabilities: # This condition includes empty JSON array "[]"
|
52
|
+
logger.warning(
|
53
|
+
"No GOOGLE_WORKSPACE_ENABLED_CAPABILITIES specified or list is empty. "
|
54
|
+
"All tools might be disabled depending on Hub filtering. "
|
55
|
+
"(Note: FastMCP relies on Hub filtering based on declared capabilities.)"
|
56
|
+
)
|
57
|
+
else:
|
58
|
+
logger.info(f"Declared Google Workspace capabilities via env var: {capabilities}")
|
59
|
+
|
60
|
+
return capabilities
|
@@ -0,0 +1,36 @@
|
|
1
|
+
"""
|
2
|
+
Calendar prompts for Google Calendar operations.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from mcp.server.fastmcp.prompts.base import UserMessage
|
8
|
+
from mcp.server.fastmcp.server import Context
|
9
|
+
|
10
|
+
from google_workspace_mcp.app import mcp
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
@mcp.prompt()
|
16
|
+
async def draft_calendar_agenda(
|
17
|
+
event_id: str,
|
18
|
+
calendar_id: str = "primary",
|
19
|
+
ctx: Context = None, # Add default None for ctx
|
20
|
+
) -> list[UserMessage]:
|
21
|
+
"""Drafts a meeting agenda based on event info (currently simulated)."""
|
22
|
+
logger.info(f"Executing draft_calendar_agenda prompt for event '{event_id}' on calendar '{calendar_id}'")
|
23
|
+
# TODO: Replace simulation with actual call to get event details via resource
|
24
|
+
# try:
|
25
|
+
# event_details_dict = await ctx.read_resource(f"calendar://{calendar_id}/event/{event_id}")
|
26
|
+
# event_details = f"Meeting: {event_details_dict.get('summary', 'No Title')}\n"
|
27
|
+
# event_details += f"Time: {event_details_dict.get('start',{}).get('dateTime')} - {event_details_dict.get('end',{}).get('dateTime')}\n"
|
28
|
+
# event_details += f"Description: {event_details_dict.get('description', 'N/A')}"
|
29
|
+
# except Exception as e:
|
30
|
+
# logger.warning(f"Could not get event details for {event_id}: {e}")
|
31
|
+
# event_details = f"Meeting: {event_id} on Calendar: {calendar_id} - Details unavailable."
|
32
|
+
|
33
|
+
# Simulate details for now
|
34
|
+
event_details = f"Meeting: {event_id} on Calendar: {calendar_id} - Details unavailable."
|
35
|
+
|
36
|
+
return [UserMessage(f"Please draft a simple meeting agenda based on the following event information:\n\n{event_details}")]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"""Drive related MCP Prompts."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
|
5
|
+
from mcp.server.fastmcp.prompts.base import UserMessage
|
6
|
+
|
7
|
+
from google_workspace_mcp.app import mcp
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
|
12
|
+
@mcp.prompt()
|
13
|
+
async def suggest_drive_outline(topic: str) -> list[UserMessage]:
|
14
|
+
"""Suggests a document outline for a given topic."""
|
15
|
+
logger.info(f"Executing suggest_drive_outline prompt for topic: {topic}")
|
16
|
+
return [
|
17
|
+
UserMessage(f"Please suggest a standard document outline (sections and subsections) for a document about: {topic}")
|
18
|
+
]
|
@@ -0,0 +1,65 @@
|
|
1
|
+
"""
|
2
|
+
Gmail prompts for Gmail operations.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
# Use specific types for clarity
|
8
|
+
from mcp.server.fastmcp.prompts.base import UserMessage
|
9
|
+
from mcp.server.fastmcp.server import Context
|
10
|
+
|
11
|
+
from google_workspace_mcp.app import mcp
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
@mcp.prompt()
|
17
|
+
async def summarize_recent_emails(query: str, max_emails: int = 5, ctx: Context = None) -> list[UserMessage]:
|
18
|
+
"""Summarizes recent emails based on a query."""
|
19
|
+
if ctx is None:
|
20
|
+
# This should ideally not happen if context injection works,
|
21
|
+
# but handle defensively for direct calls or tests.
|
22
|
+
logger.error("Context (ctx) is required for summarize_recent_emails but was not provided.")
|
23
|
+
# Return an error message or raise an exception?
|
24
|
+
# Raising seems more appropriate for a required dependency.
|
25
|
+
raise ValueError("Context (ctx) is required for this prompt.")
|
26
|
+
|
27
|
+
logger.info(f"Executing summarize_recent_emails prompt for query: '{query}', max: {max_emails}")
|
28
|
+
|
29
|
+
email_context = "No emails found or error fetching emails."
|
30
|
+
try:
|
31
|
+
# Construct resource URI - ensure query is URL-encoded if needed by framework
|
32
|
+
# Assuming FastMCP handles basic encoding or query is simple enough
|
33
|
+
resource_uri = f"gmail://search?q={query}"
|
34
|
+
logger.debug(f"Reading resource: {resource_uri}")
|
35
|
+
|
36
|
+
# Call the resource via context
|
37
|
+
email_data = await ctx.read_resource(resource_uri)
|
38
|
+
logger.debug(f"Resource returned: {email_data}")
|
39
|
+
|
40
|
+
if email_data and isinstance(email_data, dict) and "emails" in email_data:
|
41
|
+
emails = email_data["emails"]
|
42
|
+
if emails:
|
43
|
+
summary_parts = []
|
44
|
+
for _i, email in enumerate(emails[:max_emails]): # Limit results
|
45
|
+
subject = email.get("subject", "No Subject")
|
46
|
+
sender = email.get("from", "Unknown Sender")
|
47
|
+
snippet = email.get("snippet", "...")
|
48
|
+
summary_parts.append(f"- From: {sender}\n Subject: {subject}\n Snippet: {snippet}")
|
49
|
+
|
50
|
+
if summary_parts:
|
51
|
+
email_context = "\n".join(summary_parts)
|
52
|
+
else:
|
53
|
+
email_context = "No emails found matching the query."
|
54
|
+
elif email_data and isinstance(email_data, dict) and "message" in email_data:
|
55
|
+
# Handle case where resource returns a message like "No emails found"
|
56
|
+
email_context = email_data["message"]
|
57
|
+
|
58
|
+
except ValueError as ve:
|
59
|
+
logger.error(f"ValueError calling resource for email summary: {ve}")
|
60
|
+
email_context = f"Error: Could not fetch emails - {ve}"
|
61
|
+
except Exception as e:
|
62
|
+
logger.exception(f"Unexpected error calling resource for email summary: {e}")
|
63
|
+
email_context = "Error: An unexpected error occurred while fetching emails."
|
64
|
+
|
65
|
+
return [UserMessage(f"Please summarize the key points from these recent emails:\n\n{email_context}")]
|
@@ -0,0 +1,40 @@
|
|
1
|
+
"""
|
2
|
+
Slides prompts for Google Slides operations.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from mcp.server.fastmcp.prompts.base import UserMessage
|
8
|
+
from mcp.server.fastmcp.server import Context
|
9
|
+
|
10
|
+
from google_workspace_mcp.app import mcp
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
@mcp.prompt()
|
16
|
+
async def suggest_slide_content(presentation_topic: str, slide_objective: str) -> list[UserMessage]:
|
17
|
+
"""Suggests content for a presentation slide."""
|
18
|
+
logger.info(f"Executing suggest_slide_content prompt for topic '{presentation_topic}' and objective '{slide_objective}'")
|
19
|
+
return [
|
20
|
+
UserMessage(
|
21
|
+
f"Generate content suggestions for one presentation slide.\n"
|
22
|
+
f"Topic: {presentation_topic}\n"
|
23
|
+
f"Objective: {slide_objective}\n"
|
24
|
+
f"Please provide a concise Title and 3-4 bullet points."
|
25
|
+
)
|
26
|
+
]
|
27
|
+
|
28
|
+
|
29
|
+
@mcp.prompt()
|
30
|
+
def create_slides_presentation(title: str, ctx: Context = None) -> list[UserMessage]:
|
31
|
+
"""Creates a new Google Slides presentation with the specified title."""
|
32
|
+
if ctx is None:
|
33
|
+
# Context should be automatically injected by the MCP framework
|
34
|
+
pass
|
35
|
+
|
36
|
+
prompt_message = UserMessage(
|
37
|
+
role="user",
|
38
|
+
content=f"Create a new Google Slides presentation with the title: {title}",
|
39
|
+
)
|
40
|
+
return [prompt_message]
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Resources for google-workspace-mcp."""
|
2
|
+
|
3
|
+
from .sheets_resources import (
|
4
|
+
get_specific_sheet_metadata_resource,
|
5
|
+
get_spreadsheet_metadata_resource,
|
6
|
+
)
|
7
|
+
from .slides import get_markdown_deck_formatting_guide
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"get_markdown_deck_formatting_guide",
|
11
|
+
"get_spreadsheet_metadata_resource",
|
12
|
+
"get_specific_sheet_metadata_resource",
|
13
|
+
]
|
@@ -0,0 +1,79 @@
|
|
1
|
+
"""
|
2
|
+
Calendar resources for Google Calendar data access.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
from google_workspace_mcp.app import mcp
|
9
|
+
from google_workspace_mcp.services.calendar import CalendarService
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
# --- Calendar Resource Functions --- #
|
15
|
+
|
16
|
+
|
17
|
+
@mcp.resource("calendar://calendars/list")
|
18
|
+
async def list_calendars() -> dict[str, Any]:
|
19
|
+
"""
|
20
|
+
Lists all calendars accessible by the user.
|
21
|
+
|
22
|
+
Maps to URI: calendar://calendars/list
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A dictionary containing the list of calendars or an error message.
|
26
|
+
"""
|
27
|
+
logger.info("Executing list_calendars resource")
|
28
|
+
|
29
|
+
calendar_service = CalendarService()
|
30
|
+
calendars = calendar_service.list_calendars()
|
31
|
+
|
32
|
+
if isinstance(calendars, dict) and calendars.get("error"):
|
33
|
+
raise ValueError(calendars.get("message", "Error listing calendars"))
|
34
|
+
|
35
|
+
if not calendars:
|
36
|
+
return {"message": "No calendars found."}
|
37
|
+
|
38
|
+
# Return raw service result
|
39
|
+
return {"count": len(calendars), "calendars": calendars}
|
40
|
+
|
41
|
+
|
42
|
+
# Keep the existing list_calendars resource
|
43
|
+
|
44
|
+
|
45
|
+
@mcp.resource("calendar://events/today")
|
46
|
+
async def get_today_events() -> dict[str, Any]:
|
47
|
+
"""
|
48
|
+
Get all events for today.
|
49
|
+
|
50
|
+
Maps to URI: calendar://events/today
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
A dictionary containing the list of today's events.
|
54
|
+
"""
|
55
|
+
logger.info("Executing get_today_events resource")
|
56
|
+
|
57
|
+
from datetime import datetime, timedelta
|
58
|
+
|
59
|
+
import pytz
|
60
|
+
|
61
|
+
calendar_service = CalendarService()
|
62
|
+
|
63
|
+
# Get today's start and end time in RFC3339 format
|
64
|
+
now = datetime.now(pytz.UTC)
|
65
|
+
start_of_day = datetime(now.year, now.month, now.day, 0, 0, 0, tzinfo=pytz.UTC)
|
66
|
+
end_of_day = start_of_day + timedelta(days=1)
|
67
|
+
|
68
|
+
time_min = start_of_day.isoformat()
|
69
|
+
time_max = end_of_day.isoformat()
|
70
|
+
|
71
|
+
events = calendar_service.get_events(calendar_id="primary", time_min=time_min, time_max=time_max, max_results=50)
|
72
|
+
|
73
|
+
if isinstance(events, dict) and events.get("error"):
|
74
|
+
raise ValueError(events.get("message", "Error getting today's events"))
|
75
|
+
|
76
|
+
if not events:
|
77
|
+
return {"message": "No events found for today."}
|
78
|
+
|
79
|
+
return {"count": len(events), "events": events}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""
|
2
|
+
Drive resources for Google Drive data access.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
from google_workspace_mcp.app import mcp
|
9
|
+
from google_workspace_mcp.services.drive import DriveService
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
# --- Drive Resource Functions --- #
|
15
|
+
|
16
|
+
|
17
|
+
@mcp.resource("drive://recent")
|
18
|
+
async def get_recent_files() -> dict[str, Any]:
|
19
|
+
"""
|
20
|
+
Get recently modified files (last 7 days).
|
21
|
+
|
22
|
+
Maps to URI: drive://recent
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A dictionary containing the list of recently modified files.
|
26
|
+
"""
|
27
|
+
logger.info("Executing get_recent_files resource")
|
28
|
+
|
29
|
+
drive_service = DriveService()
|
30
|
+
# Using the existing search_files method with a fixed query
|
31
|
+
query = "modifiedTime > 'now-7d'"
|
32
|
+
files = drive_service.search_files(query=query, page_size=10)
|
33
|
+
|
34
|
+
if isinstance(files, dict) and files.get("error"):
|
35
|
+
raise ValueError(files.get("message", "Error getting recent files"))
|
36
|
+
|
37
|
+
if not files:
|
38
|
+
return {"message": "No recent files found."}
|
39
|
+
|
40
|
+
return {"count": len(files), "files": files}
|
41
|
+
|
42
|
+
|
43
|
+
@mcp.resource("drive://shared")
|
44
|
+
async def get_shared_files() -> dict[str, Any]:
|
45
|
+
"""
|
46
|
+
Get files shared with the user.
|
47
|
+
|
48
|
+
Maps to URI: drive://shared
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
A dictionary containing the list of shared files.
|
52
|
+
"""
|
53
|
+
logger.info("Executing get_shared_files resource")
|
54
|
+
|
55
|
+
drive_service = DriveService()
|
56
|
+
# Using the existing search_files method with a fixed query
|
57
|
+
query = "sharedWithMe=true"
|
58
|
+
files = drive_service.search_files(query=query, page_size=10)
|
59
|
+
|
60
|
+
if isinstance(files, dict) and files.get("error"):
|
61
|
+
raise ValueError(files.get("message", "Error getting shared files"))
|
62
|
+
|
63
|
+
if not files:
|
64
|
+
return {"message": "No shared files found."}
|
65
|
+
|
66
|
+
return {"count": len(files), "files": files}
|
67
|
+
|
68
|
+
|
69
|
+
@mcp.resource("drive://files/{file_id}/metadata")
|
70
|
+
async def get_drive_file_metadata(file_id: str) -> dict[str, Any]:
|
71
|
+
"""
|
72
|
+
Get metadata for a specific file from Google Drive.
|
73
|
+
|
74
|
+
Maps to URI: drive://files/{file_id}/metadata
|
75
|
+
|
76
|
+
Args:
|
77
|
+
file_id: The ID of the file to get metadata for
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
A dictionary containing the file metadata.
|
81
|
+
"""
|
82
|
+
logger.info(f"Executing get_drive_file_metadata resource for file_id: {file_id}")
|
83
|
+
|
84
|
+
if not file_id or not file_id.strip():
|
85
|
+
raise ValueError("File ID cannot be empty")
|
86
|
+
|
87
|
+
drive_service = DriveService()
|
88
|
+
metadata = drive_service.get_file_metadata(file_id=file_id)
|
89
|
+
|
90
|
+
if isinstance(metadata, dict) and metadata.get("error"):
|
91
|
+
raise ValueError(metadata.get("message", "Error getting file metadata"))
|
92
|
+
|
93
|
+
return metadata
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""
|
2
|
+
Gmail resources for Gmail data access.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
from google_workspace_mcp.app import mcp
|
9
|
+
from google_workspace_mcp.services.gmail import GmailService
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
# --- Gmail Resource Functions --- #
|
15
|
+
|
16
|
+
|
17
|
+
@mcp.resource("gmail://labels")
|
18
|
+
async def get_gmail_labels() -> dict[str, Any]:
|
19
|
+
"""
|
20
|
+
List all Gmail labels for the authenticated user.
|
21
|
+
|
22
|
+
Maps to URI: gmail://labels
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
A dictionary containing the list of Gmail labels.
|
26
|
+
"""
|
27
|
+
logger.info("Executing get_gmail_labels resource")
|
28
|
+
|
29
|
+
gmail_service = GmailService()
|
30
|
+
# This would require adding a get_labels method to GmailService
|
31
|
+
labels = gmail_service.get_labels()
|
32
|
+
|
33
|
+
if isinstance(labels, dict) and labels.get("error"):
|
34
|
+
raise ValueError(labels.get("message", "Error getting Gmail labels"))
|
35
|
+
|
36
|
+
return {"count": len(labels), "labels": labels}
|
37
|
+
|
38
|
+
|
39
|
+
@mcp.resource("gmail://unread_count")
|
40
|
+
async def get_unread_count() -> dict[str, Any]:
|
41
|
+
"""
|
42
|
+
Get count of unread emails in the inbox.
|
43
|
+
|
44
|
+
Maps to URI: gmail://unread_count
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
A dictionary containing the count of unread emails.
|
48
|
+
"""
|
49
|
+
logger.info("Executing get_unread_count resource")
|
50
|
+
|
51
|
+
gmail_service = GmailService()
|
52
|
+
# This would require adding a get_unread_count method to GmailService
|
53
|
+
result = gmail_service.get_unread_count()
|
54
|
+
|
55
|
+
if isinstance(result, dict) and result.get("error"):
|
56
|
+
raise ValueError(result.get("message", "Error getting unread count"))
|
57
|
+
|
58
|
+
return {"unread_count": result}
|