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,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": "",
|
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
|
+

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