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.
Files changed (38) hide show
  1. google_workspace_mcp/__init__.py +3 -0
  2. google_workspace_mcp/__main__.py +43 -0
  3. google_workspace_mcp/app.py +8 -0
  4. google_workspace_mcp/auth/__init__.py +7 -0
  5. google_workspace_mcp/auth/gauth.py +62 -0
  6. google_workspace_mcp/config.py +60 -0
  7. google_workspace_mcp/prompts/__init__.py +3 -0
  8. google_workspace_mcp/prompts/calendar.py +36 -0
  9. google_workspace_mcp/prompts/drive.py +18 -0
  10. google_workspace_mcp/prompts/gmail.py +65 -0
  11. google_workspace_mcp/prompts/slides.py +40 -0
  12. google_workspace_mcp/resources/__init__.py +13 -0
  13. google_workspace_mcp/resources/calendar.py +79 -0
  14. google_workspace_mcp/resources/drive.py +93 -0
  15. google_workspace_mcp/resources/gmail.py +58 -0
  16. google_workspace_mcp/resources/sheets_resources.py +92 -0
  17. google_workspace_mcp/resources/slides.py +421 -0
  18. google_workspace_mcp/services/__init__.py +21 -0
  19. google_workspace_mcp/services/base.py +73 -0
  20. google_workspace_mcp/services/calendar.py +256 -0
  21. google_workspace_mcp/services/docs_service.py +388 -0
  22. google_workspace_mcp/services/drive.py +454 -0
  23. google_workspace_mcp/services/gmail.py +676 -0
  24. google_workspace_mcp/services/sheets_service.py +466 -0
  25. google_workspace_mcp/services/slides.py +959 -0
  26. google_workspace_mcp/tools/__init__.py +7 -0
  27. google_workspace_mcp/tools/calendar.py +229 -0
  28. google_workspace_mcp/tools/docs_tools.py +277 -0
  29. google_workspace_mcp/tools/drive.py +221 -0
  30. google_workspace_mcp/tools/gmail.py +344 -0
  31. google_workspace_mcp/tools/sheets_tools.py +322 -0
  32. google_workspace_mcp/tools/slides.py +478 -0
  33. google_workspace_mcp/utils/__init__.py +1 -0
  34. google_workspace_mcp/utils/markdown_slides.py +504 -0
  35. google_workspace_mcp-1.0.0.dist-info/METADATA +547 -0
  36. google_workspace_mcp-1.0.0.dist-info/RECORD +38 -0
  37. google_workspace_mcp-1.0.0.dist-info/WHEEL +4 -0
  38. google_workspace_mcp-1.0.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,92 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ from google_workspace_mcp.app import mcp
5
+ from google_workspace_mcp.services.sheets_service import SheetsService
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ @mcp.resource("sheets://spreadsheets/{spreadsheet_id}/metadata")
11
+ async def get_spreadsheet_metadata_resource(spreadsheet_id: str) -> dict[str, Any]:
12
+ """
13
+ Retrieves metadata for a specific Google Spreadsheet.
14
+ Maps to URI: sheets://spreadsheets/{spreadsheet_id}/metadata
15
+ """
16
+ logger.info(
17
+ f"Executing get_spreadsheet_metadata_resource for spreadsheet_id: {spreadsheet_id}"
18
+ )
19
+ if not spreadsheet_id:
20
+ raise ValueError("Spreadsheet ID is required in the URI path.")
21
+
22
+ sheets_service = SheetsService()
23
+ metadata = sheets_service.get_spreadsheet_metadata(spreadsheet_id=spreadsheet_id)
24
+
25
+ if isinstance(metadata, dict) and metadata.get("error"):
26
+ raise ValueError(
27
+ metadata.get("message", "Error retrieving spreadsheet metadata")
28
+ )
29
+
30
+ if not metadata:
31
+ raise ValueError(
32
+ f"Could not retrieve metadata for spreadsheet ID: {spreadsheet_id}"
33
+ )
34
+
35
+ return metadata
36
+
37
+
38
+ @mcp.resource(
39
+ "sheets://spreadsheets/{spreadsheet_id}/sheets/{sheet_identifier}/metadata"
40
+ )
41
+ async def get_specific_sheet_metadata_resource(
42
+ spreadsheet_id: str, sheet_identifier: str
43
+ ) -> dict[str, Any]:
44
+ """
45
+ Retrieves metadata for a specific sheet within a Google Spreadsheet,
46
+ identified by its title (name) or numeric sheetId.
47
+ Maps to URI: sheets://spreadsheets/{spreadsheet_id}/sheets/{sheet_identifier}/metadata
48
+ """
49
+ logger.info(
50
+ f"Executing get_specific_sheet_metadata_resource for spreadsheet: {spreadsheet_id}, sheet_identifier: {sheet_identifier}"
51
+ )
52
+ if not spreadsheet_id or not sheet_identifier:
53
+ raise ValueError(
54
+ "Spreadsheet ID and sheet identifier (name or ID) are required."
55
+ )
56
+
57
+ sheets_service = SheetsService()
58
+ # Fetch metadata for all sheets first
59
+ full_metadata = sheets_service.get_spreadsheet_metadata(
60
+ spreadsheet_id=spreadsheet_id,
61
+ fields="sheets(properties(sheetId,title,index,sheetType,gridProperties))",
62
+ )
63
+
64
+ if isinstance(full_metadata, dict) and full_metadata.get("error"):
65
+ raise ValueError(
66
+ full_metadata.get(
67
+ "message", "Error retrieving spreadsheet to find sheet metadata"
68
+ )
69
+ )
70
+
71
+ if not full_metadata or not full_metadata.get("sheets"):
72
+ raise ValueError(
73
+ f"No sheets found in spreadsheet {spreadsheet_id} or metadata incomplete."
74
+ )
75
+
76
+ found_sheet = None
77
+ for sheet in full_metadata.get("sheets", []):
78
+ props = sheet.get("properties", {})
79
+ # Try matching by numeric ID first, then by title
80
+ if str(props.get("sheetId")) == sheet_identifier:
81
+ found_sheet = props
82
+ break
83
+ if props.get("title", "").lower() == sheet_identifier.lower():
84
+ found_sheet = props
85
+ break
86
+
87
+ if not found_sheet:
88
+ raise ValueError(
89
+ f"Sheet '{sheet_identifier}' not found in spreadsheet '{spreadsheet_id}'."
90
+ )
91
+
92
+ return found_sheet
@@ -0,0 +1,421 @@
1
+ """
2
+ Slides resources for Google Slides data access.
3
+ """
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from google_workspace_mcp.app import mcp
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @mcp.resource("slides://markdown_formatting_guide")
14
+ async def get_markdown_deck_formatting_guide() -> dict[str, Any]:
15
+ """
16
+ Get comprehensive documentation on how to format Markdown for slide creation using markdowndeck.
17
+
18
+ Maps to URI: slides://markdown_formatting_guide
19
+
20
+ Returns:
21
+ A dictionary containing the formatting guide.
22
+ """
23
+ logger.info("Executing get_markdown_deck_formatting_guide resource")
24
+
25
+ return {
26
+ "title": "MarkdownDeck Formatting Guide",
27
+ "description": "Comprehensive guide for formatting Markdown content for slide creation using markdowndeck.",
28
+ "overview": "MarkdownDeck uses a specialized Markdown format with layout directives to create professional Google Slides presentations with precise control over slide layouts, positioning, and styling.",
29
+ "basic_structure": {
30
+ "slide_separator": "===",
31
+ "description": "Use '===' on a line by itself to separate slides.",
32
+ "example": """
33
+ # First Slide Title
34
+
35
+ Content for first slide
36
+
37
+ ===
38
+
39
+ # Second Slide Title
40
+
41
+ Content for second slide
42
+ """,
43
+ },
44
+ "sections": {
45
+ "vertical_sections": {
46
+ "separator": "---",
47
+ "description": "Creates vertical sections within a slide (stacked top to bottom).",
48
+ "example": """
49
+ # Slide Title
50
+
51
+ Top section content
52
+
53
+ ---
54
+
55
+ Bottom section content
56
+ """,
57
+ },
58
+ "horizontal_sections": {
59
+ "separator": "***",
60
+ "description": "Creates horizontal sections within a slide (side by side).",
61
+ "example": """
62
+ # Slide Title
63
+
64
+ [width=1/3]
65
+ Left column content
66
+
67
+ ***
68
+
69
+ [width=2/3]
70
+ Right column content
71
+ """,
72
+ },
73
+ },
74
+ "layout_directives": {
75
+ "description": "Control size, position, and styling with directives in square brackets at the start of a section.",
76
+ "syntax": "[property=value]",
77
+ "common_directives": [
78
+ {
79
+ "name": "width",
80
+ "values": "fractions (e.g., 1/3, 2/3), percentages (e.g., 50%), or pixels (e.g., 300)",
81
+ "description": "Sets the width of the section or element",
82
+ },
83
+ {
84
+ "name": "height",
85
+ "values": "fractions, percentages, or pixels",
86
+ "description": "Sets the height of the section or element",
87
+ },
88
+ {
89
+ "name": "align",
90
+ "values": "left, center, right, justify",
91
+ "description": "Sets horizontal text alignment",
92
+ },
93
+ {
94
+ "name": "valign",
95
+ "values": "top, middle, bottom",
96
+ "description": "Sets vertical alignment of text within its container",
97
+ },
98
+ {
99
+ "name": "vertical-align",
100
+ "values": "top, middle, bottom",
101
+ "description": "Sets vertical alignment for text elements",
102
+ },
103
+ {
104
+ "name": "background",
105
+ "values": "color (e.g., #f5f5f5, ACCENT1) or url(image_url)",
106
+ "description": "Sets background color or image; can use theme colors",
107
+ },
108
+ {
109
+ "name": "color",
110
+ "values": "color (e.g., #333333, TEXT1)",
111
+ "description": "Sets text color; can use theme colors",
112
+ },
113
+ {
114
+ "name": "fontsize",
115
+ "values": "numeric value (e.g., 18)",
116
+ "description": "Sets font size",
117
+ },
118
+ {
119
+ "name": "font-family",
120
+ "values": "font name (e.g., Arial, Times New Roman)",
121
+ "description": "Sets the font family for text",
122
+ },
123
+ {
124
+ "name": "padding",
125
+ "values": "numeric value or percentage (e.g., 10, 5%)",
126
+ "description": "Sets padding around the content",
127
+ },
128
+ {
129
+ "name": "border",
130
+ "values": "width style color (e.g., 1pt solid #FF0000, 2pt dashed black)",
131
+ "description": "Sets border for elements or sections",
132
+ },
133
+ {
134
+ "name": "border-position",
135
+ "values": "ALL, OUTER, INNER, LEFT, RIGHT, TOP, BOTTOM, INNER_HORIZONTAL, INNER_VERTICAL",
136
+ "description": "Specifies which borders to apply in tables",
137
+ },
138
+ {
139
+ "name": "line-spacing",
140
+ "values": "numeric value (e.g., 1.5)",
141
+ "description": "Sets line spacing for paragraphs",
142
+ },
143
+ {
144
+ "name": "paragraph-spacing",
145
+ "values": "numeric value (e.g., 10)",
146
+ "description": "Sets spacing between paragraphs",
147
+ },
148
+ {
149
+ "name": "indent",
150
+ "values": "numeric value (e.g., 20)",
151
+ "description": "Sets text indentation",
152
+ },
153
+ ],
154
+ "combined_example": "[width=2/3][align=center][background=#f5f5f5][line-spacing=1.5]",
155
+ "example": """
156
+ # Slide Title
157
+
158
+ [width=60%][align=center][padding=10]
159
+ This content takes 60% of the width, is centered, and has 10pt padding.
160
+
161
+ ---
162
+
163
+ [width=40%][background=ACCENT1][color=TEXT1][border=1pt solid black]
164
+ This content has a themed background, theme text color, and a black border.
165
+ """,
166
+ },
167
+ "list_styling": {
168
+ "description": "Control the appearance of lists with directives and nesting",
169
+ "nesting": {
170
+ "description": "Indentation in Markdown translates to visual nesting levels in slides",
171
+ "example": """
172
+ - First level item
173
+ - Second level item
174
+ - Third level item
175
+ - Fourth level item
176
+ """,
177
+ },
178
+ "styling": {
179
+ "description": "Apply styling to list sections using directives",
180
+ "example": """
181
+ [color=#0000FF][fontsize=18][font-family=Arial]
182
+ - Blue list items
183
+ - With larger Arial font
184
+ - Nested items inherit styling
185
+ - [color=red] This item is red
186
+ """,
187
+ },
188
+ },
189
+ "table_styling": {
190
+ "description": "Enhanced control over table appearance",
191
+ "directives": [
192
+ {
193
+ "name": "cell-align",
194
+ "values": "left, center, right, top, middle, bottom",
195
+ "description": "Sets alignment for cells in the table",
196
+ },
197
+ {
198
+ "name": "cell-background",
199
+ "values": "color (e.g., #F0F0F0, ACCENT2)",
200
+ "description": "Sets background color for cells",
201
+ },
202
+ {
203
+ "name": "cell-range",
204
+ "values": "row1,col1:row2,col2 (e.g., 0,0:2,3)",
205
+ "description": "Specifies a range of cells to apply styling to",
206
+ },
207
+ {
208
+ "name": "border",
209
+ "values": "width style color (e.g., 1pt solid black)",
210
+ "description": "Sets border for the entire table",
211
+ },
212
+ ],
213
+ "example": """
214
+ [cell-align=center][cell-background=#F0F0F0][border=1pt solid black]
215
+ | Header 1 | Header 2 | Header 3 |
216
+ |----------|----------|----------|
217
+ | Data 1 | Data 2 | Data 3 |
218
+ | Data 4 | Data 5 | Data 6 |
219
+ """,
220
+ },
221
+ "theme_integration": {
222
+ "description": "Use Google Slides themes and theme colors in your Markdown",
223
+ "theme_id": {
224
+ "description": "Specify a Google Slides theme ID when creating a presentation",
225
+ "note": "The theme_id is passed as a parameter to the create_presentation tool",
226
+ },
227
+ "layouts": {
228
+ "description": "Standard slide layouts allow content to inherit theme styling via placeholders",
229
+ "common_layouts": [
230
+ "TITLE",
231
+ "TITLE_AND_BODY",
232
+ "TITLE_AND_TWO_COLUMNS",
233
+ "TITLE_ONLY",
234
+ "SECTION_HEADER",
235
+ "CAPTION_ONLY",
236
+ "BIG_NUMBER",
237
+ ],
238
+ },
239
+ "theme_colors": {
240
+ "description": "Use theme color names in directives for consistent styling",
241
+ "values": [
242
+ "TEXT1",
243
+ "TEXT2",
244
+ "BACKGROUND1",
245
+ "BACKGROUND2",
246
+ "ACCENT1",
247
+ "ACCENT2",
248
+ "ACCENT3",
249
+ "ACCENT4",
250
+ "ACCENT5",
251
+ "ACCENT6",
252
+ ],
253
+ "example": """
254
+ [background=BACKGROUND2][color=ACCENT1]
255
+ # Theme-Styled Content
256
+
257
+ This content uses theme colors for consistent branding.
258
+ """,
259
+ },
260
+ },
261
+ "special_elements": {
262
+ "footer": {
263
+ "separator": "@@@",
264
+ "description": "Define slide footer content (appears at the bottom of each slide).",
265
+ "example": """
266
+ # Slide Content
267
+
268
+ Main content here
269
+
270
+ @@@
271
+
272
+ Footer text - Confidential
273
+ """,
274
+ },
275
+ "speaker_notes": {
276
+ "syntax": "<!-- notes: Your notes here -->",
277
+ "description": "Add presenter notes (visible in presenter view only).",
278
+ "example": "<!-- notes: Remember to emphasize the quarterly growth figures -->",
279
+ },
280
+ },
281
+ "supported_markdown": {
282
+ "headings": "# H1, ## H2, through ###### H6",
283
+ "text_formatting": "**bold**, *italic*, ~~strikethrough~~, `inline code`",
284
+ "lists": {
285
+ "unordered": "- Item 1\n- Item 2\n - Nested item",
286
+ "ordered": "1. First item\n2. Second item\n 1. Nested item",
287
+ },
288
+ "links": "[Link text](https://example.com)",
289
+ "images": "![Alt text](https://example.com/image.jpg)",
290
+ "code_blocks": "```language\ncode here\n```",
291
+ "tables": "| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |",
292
+ "blockquotes": "> Quote text",
293
+ },
294
+ "layout_examples": [
295
+ {
296
+ "title": "Title and Content Slide",
297
+ "description": "A standard slide with title and bullet points",
298
+ "markdown": """
299
+ # Quarterly Results
300
+
301
+ Q3 2024 Financial Overview
302
+
303
+ - Revenue: $10.2M (+15% YoY)
304
+ - EBITDA: $3.4M (+12% YoY)
305
+ - Cash balance: $15M
306
+ """,
307
+ },
308
+ {
309
+ "title": "Two-Column Layout with Theme Colors",
310
+ "description": "Main content and sidebar layout with theme colors",
311
+ "markdown": """
312
+ # Split Layout
313
+
314
+ [width=60%]
315
+
316
+ ## Main Column
317
+
318
+ - Primary content
319
+ - Important details
320
+ - Key metrics
321
+
322
+ ***
323
+
324
+ [width=40%][background=ACCENT2][color=TEXT1]
325
+
326
+ ## Sidebar
327
+
328
+ Supporting information and notes
329
+ """,
330
+ },
331
+ {
332
+ "title": "Dashboard Layout with Enhanced Styling",
333
+ "description": "Complex layout with multiple sections and styling",
334
+ "markdown": """
335
+ # Dashboard Overview
336
+
337
+ [height=30%][align=center][line-spacing=1.2]
338
+
339
+ ## Key Metrics
340
+
341
+ Revenue: $1.2M | Users: 45K | Conversion: 3.2%
342
+
343
+ ---
344
+
345
+ [width=50%][padding=10]
346
+
347
+ ## Regional Data
348
+ [font-family=Arial][color=ACCENT1]
349
+ - North America: 45%
350
+ - Europe: 30%
351
+ - Asia: 20%
352
+ - Other: 5%
353
+
354
+ ***
355
+
356
+ [width=50%][background=BACKGROUND2][border=1pt solid ACCENT3]
357
+
358
+ ## Quarterly Trend
359
+
360
+ ![Chart](chart-url.jpg)
361
+
362
+ ---
363
+
364
+ [height=20%]
365
+
366
+ ## Action Items
367
+ [line-spacing=1.5]
368
+ 1. Improve APAC conversion
369
+ 2. Launch new pricing tier
370
+ 3. Update dashboards
371
+
372
+ @@@
373
+
374
+ Confidential - Internal Use Only
375
+
376
+ <!-- notes: Discuss action items in detail and assign owners -->
377
+ """,
378
+ },
379
+ {
380
+ "title": "Table with Enhanced Styling",
381
+ "description": "Styled table with cell alignment and backgrounds",
382
+ "markdown": """
383
+ # Product Comparison
384
+
385
+ [width=80%][align=center]
386
+
387
+ [cell-align=center][border=1pt solid black]
388
+ | Feature | Basic Plan | Pro Plan | Enterprise |
389
+ |---------|------------|----------|------------|
390
+ | Users | 5 | 50 | Unlimited |
391
+ | Storage | 10GB | 100GB | 1TB |
392
+ | Support | Email | Priority | 24/7 |
393
+ | Price | $10/mo | $50/mo | Custom |
394
+
395
+ <!-- notes: Emphasize the value proposition of the Pro plan -->
396
+ """,
397
+ },
398
+ ],
399
+ "best_practices": [
400
+ "Start each slide with a clear title using # heading",
401
+ "Keep content concise and visually balanced",
402
+ "Use consistent styling across slides",
403
+ "Limit the number of elements per slide",
404
+ "Use layout directives to control positioning and sizing",
405
+ "Use theme colors for consistent branding",
406
+ "Test complex layouts to ensure they display as expected",
407
+ "Apply appropriate spacing for improved readability",
408
+ ],
409
+ "tips_for_llms": [
410
+ "First plan the overall structure of the presentation",
411
+ "Consider visual hierarchy and content flow",
412
+ "Use layout directives to create visually appealing slides",
413
+ "Balance text with visual elements",
414
+ "For complex presentations, break down into logical sections",
415
+ "Always test with simple layouts before attempting complex ones",
416
+ "When designing sections, ensure width values add up to 1 (or 100%)",
417
+ "Use theme colors (ACCENT1, TEXT1, etc.) for consistent branding",
418
+ "Apply styling consistently for a professional look",
419
+ "Consider line spacing and paragraph spacing for improved readability",
420
+ ],
421
+ }
@@ -0,0 +1,21 @@
1
+ """
2
+ Service layer modules for Google Workspace MCP.
3
+ """
4
+
5
+ from .base import BaseGoogleService
6
+ from .calendar import CalendarService
7
+ from .docs_service import DocsService
8
+ from .drive import DriveService
9
+ from .gmail import GmailService
10
+ from .sheets_service import SheetsService
11
+ from .slides import SlidesService
12
+
13
+ __all__ = [
14
+ "BaseGoogleService",
15
+ "DriveService",
16
+ "GmailService",
17
+ "CalendarService",
18
+ "SlidesService",
19
+ "DocsService",
20
+ "SheetsService",
21
+ ]
@@ -0,0 +1,73 @@
1
+ """
2
+ Base classes and utilities for Google API service implementations.
3
+ """
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from googleapiclient.discovery import build
9
+ from googleapiclient.errors import HttpError
10
+
11
+ from google_workspace_mcp.auth import gauth
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class BaseGoogleService:
17
+ """
18
+ Base class for Google service implementations providing common
19
+ authentication and error handling patterns.
20
+ """
21
+
22
+ def __init__(self, service_name: str, version: str):
23
+ """Initialize the service with credentials."""
24
+ self.service_name = service_name
25
+ self.version = version
26
+ self._service = None
27
+
28
+ @property
29
+ def service(self):
30
+ """Lazy-load the Google API service client."""
31
+ if self._service is None:
32
+ credentials = gauth.get_credentials()
33
+ self._service = build(self.service_name, self.version, credentials=credentials)
34
+ return self._service
35
+
36
+ def handle_api_error(self, operation: str, error: Exception) -> dict[str, Any]:
37
+ """
38
+ Standardized error handling for Google API operations.
39
+
40
+ Args:
41
+ operation (str): The operation being performed.
42
+ error (Exception): The exception that occurred.
43
+
44
+ Returns:
45
+ Dict[str, Any]: Structured error information.
46
+ """
47
+ if isinstance(error, HttpError):
48
+ # Extract meaningful error information from Google API errors
49
+ error_details = {
50
+ "error": True,
51
+ "operation": operation,
52
+ "status_code": error.resp.status,
53
+ "reason": error.resp.reason,
54
+ "message": str(error),
55
+ }
56
+
57
+ # Try to extract additional error details from the response
58
+ if hasattr(error, "error_details"):
59
+ error_details["details"] = error.error_details
60
+
61
+ logger.error(f"Google API error in {operation}: {error.resp.status} {error.resp.reason} - {error}")
62
+
63
+ else:
64
+ # Handle non-HTTP errors
65
+ error_details = {
66
+ "error": True,
67
+ "operation": operation,
68
+ "message": str(error),
69
+ "type": type(error).__name__,
70
+ }
71
+ logger.error(f"Unexpected error in {operation}: {type(error).__name__} - {error}")
72
+
73
+ return error_details