workspace-mcp 1.1.7__py3-none-any.whl → 1.1.9__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.
gdocs/docs_tools.py CHANGED
@@ -6,9 +6,7 @@ This module provides MCP tools for interacting with Google Docs API and managing
6
6
  import logging
7
7
  import asyncio
8
8
  import io
9
- from typing import List
10
9
 
11
- from mcp import types
12
10
  from googleapiclient.http import MediaIoBaseDownload
13
11
 
14
12
  # Auth & server utilities
@@ -94,24 +92,83 @@ async def get_doc_content(
94
92
 
95
93
  # Step 3: Process based on mimeType
96
94
  if mime_type == "application/vnd.google-apps.document":
97
- logger.info(f"[get_doc_content] Processing as native Google Doc.")
95
+ logger.info("[get_doc_content] Processing as native Google Doc.")
98
96
  doc_data = await asyncio.to_thread(
99
- docs_service.documents().get(documentId=document_id).execute
97
+ docs_service.documents().get(
98
+ documentId=document_id,
99
+ includeTabsContent=True
100
+ ).execute
100
101
  )
102
+ # Tab header format constant
103
+ TAB_HEADER_FORMAT = "\n--- TAB: {tab_name} ---\n"
104
+
105
+ def extract_text_from_elements(elements, tab_name=None, depth=0):
106
+ """Extract text from document elements (paragraphs, tables, etc.)"""
107
+ # Prevent infinite recursion by limiting depth
108
+ if depth > 5:
109
+ return ""
110
+ text_lines = []
111
+ if tab_name:
112
+ text_lines.append(TAB_HEADER_FORMAT.format(tab_name=tab_name))
113
+
114
+ for element in elements:
115
+ if 'paragraph' in element:
116
+ paragraph = element.get('paragraph', {})
117
+ para_elements = paragraph.get('elements', [])
118
+ current_line_text = ""
119
+ for pe in para_elements:
120
+ text_run = pe.get('textRun', {})
121
+ if text_run and 'content' in text_run:
122
+ current_line_text += text_run['content']
123
+ if current_line_text.strip():
124
+ text_lines.append(current_line_text)
125
+ elif 'table' in element:
126
+ # Handle table content
127
+ table = element.get('table', {})
128
+ table_rows = table.get('tableRows', [])
129
+ for row in table_rows:
130
+ row_cells = row.get('tableCells', [])
131
+ for cell in row_cells:
132
+ cell_content = cell.get('content', [])
133
+ cell_text = extract_text_from_elements(cell_content, depth=depth + 1)
134
+ if cell_text.strip():
135
+ text_lines.append(cell_text)
136
+ return "".join(text_lines)
137
+
138
+ def process_tab_hierarchy(tab, level=0):
139
+ """Process a tab and its nested child tabs recursively"""
140
+ tab_text = ""
141
+
142
+ if 'documentTab' in tab:
143
+ tab_title = tab.get('documentTab', {}).get('title', 'Untitled Tab')
144
+ # Add indentation for nested tabs to show hierarchy
145
+ if level > 0:
146
+ tab_title = " " * level + tab_title
147
+ tab_body = tab.get('documentTab', {}).get('body', {}).get('content', [])
148
+ tab_text += extract_text_from_elements(tab_body, tab_title)
149
+
150
+ # Process child tabs (nested tabs)
151
+ child_tabs = tab.get('childTabs', [])
152
+ for child_tab in child_tabs:
153
+ tab_text += process_tab_hierarchy(child_tab, level + 1)
154
+
155
+ return tab_text
156
+
157
+ processed_text_lines = []
158
+
159
+ # Process main document body
101
160
  body_elements = doc_data.get('body', {}).get('content', [])
161
+ main_content = extract_text_from_elements(body_elements)
162
+ if main_content.strip():
163
+ processed_text_lines.append(main_content)
164
+
165
+ # Process all tabs
166
+ tabs = doc_data.get('tabs', [])
167
+ for tab in tabs:
168
+ tab_content = process_tab_hierarchy(tab)
169
+ if tab_content.strip():
170
+ processed_text_lines.append(tab_content)
102
171
 
103
- processed_text_lines: List[str] = []
104
- for element in body_elements:
105
- if 'paragraph' in element:
106
- paragraph = element.get('paragraph', {})
107
- para_elements = paragraph.get('elements', [])
108
- current_line_text = ""
109
- for pe in para_elements:
110
- text_run = pe.get('textRun', {})
111
- if text_run and 'content' in text_run:
112
- current_line_text += text_run['content']
113
- if current_line_text.strip():
114
- processed_text_lines.append(current_line_text)
115
172
  body_text = "".join(processed_text_lines)
116
173
  else:
117
174
  logger.info(f"[get_doc_content] Processing as Drive file (e.g., .docx, other). MimeType: {mime_type}")
gdrive/drive_tools.py CHANGED
@@ -6,10 +6,8 @@ This module provides MCP tools for interacting with Google Drive API.
6
6
  import logging
7
7
  import asyncio
8
8
  import re
9
- from typing import List, Optional, Dict, Any
9
+ from typing import Optional, Dict, Any
10
10
 
11
- from mcp import types
12
- from googleapiclient.errors import HttpError
13
11
  from googleapiclient.http import MediaIoBaseDownload, MediaIoBaseUpload
14
12
  import io
15
13
  import httpx
gforms/forms_tools.py CHANGED
@@ -8,7 +8,6 @@ import logging
8
8
  import asyncio
9
9
  from typing import Optional, Dict, Any
10
10
 
11
- from mcp import types
12
11
 
13
12
  from auth.service_decorator import require_google_service
14
13
  from core.server import server
gmail/gmail_tools.py CHANGED
@@ -7,17 +7,16 @@ This module provides MCP tools for interacting with the Gmail API.
7
7
  import logging
8
8
  import asyncio
9
9
  import base64
10
+ import ssl
10
11
  from typing import Optional, List, Dict, Literal
11
12
 
12
13
  from email.mime.text import MIMEText
13
14
 
14
- from mcp import types
15
15
  from fastapi import Body
16
16
 
17
17
  from auth.service_decorator import require_google_service
18
18
  from core.utils import handle_http_errors
19
19
  from core.server import (
20
- GMAIL_READONLY_SCOPE,
21
20
  GMAIL_SEND_SCOPE,
22
21
  GMAIL_COMPOSE_SCOPE,
23
22
  GMAIL_MODIFY_SCOPE,
@@ -27,6 +26,9 @@ from core.server import (
27
26
 
28
27
  logger = logging.getLogger(__name__)
29
28
 
29
+ GMAIL_BATCH_SIZE = 25
30
+ GMAIL_REQUEST_DELAY = 0.1
31
+
30
32
 
31
33
  def _extract_message_body(payload):
32
34
  """
@@ -108,14 +110,40 @@ def _format_gmail_results_plain(messages: list, query: str) -> str:
108
110
  ]
109
111
 
110
112
  for i, msg in enumerate(messages, 1):
111
- message_url = _generate_gmail_web_url(msg["id"])
112
- thread_url = _generate_gmail_web_url(msg["threadId"])
113
+ # Handle potential null/undefined message objects
114
+ if not msg or not isinstance(msg, dict):
115
+ lines.extend([
116
+ f" {i}. Message: Invalid message data",
117
+ " Error: Message object is null or malformed",
118
+ "",
119
+ ])
120
+ continue
121
+
122
+ # Handle potential null/undefined values from Gmail API
123
+ message_id = msg.get("id")
124
+ thread_id = msg.get("threadId")
125
+
126
+ # Convert None, empty string, or missing values to "unknown"
127
+ if not message_id:
128
+ message_id = "unknown"
129
+ if not thread_id:
130
+ thread_id = "unknown"
131
+
132
+ if message_id != "unknown":
133
+ message_url = _generate_gmail_web_url(message_id)
134
+ else:
135
+ message_url = "N/A"
136
+
137
+ if thread_id != "unknown":
138
+ thread_url = _generate_gmail_web_url(thread_id)
139
+ else:
140
+ thread_url = "N/A"
113
141
 
114
142
  lines.extend(
115
143
  [
116
- f" {i}. Message ID: {msg['id']}",
144
+ f" {i}. Message ID: {message_id}",
117
145
  f" Web Link: {message_url}",
118
- f" Thread ID: {msg['threadId']}",
146
+ f" Thread ID: {thread_id}",
119
147
  f" Thread Link: {thread_url}",
120
148
  "",
121
149
  ]
@@ -161,7 +189,17 @@ async def search_gmail_messages(
161
189
  .list(userId="me", q=query, maxResults=page_size)
162
190
  .execute
163
191
  )
192
+
193
+ # Handle potential null response (but empty dict {} is valid)
194
+ if response is None:
195
+ logger.warning("[search_gmail_messages] Null response from Gmail API")
196
+ return f"No response received from Gmail API for query: '{query}'"
197
+
164
198
  messages = response.get("messages", [])
199
+ # Additional safety check for null messages array
200
+ if messages is None:
201
+ messages = []
202
+
165
203
  formatted_output = _format_gmail_results_plain(messages, query)
166
204
 
167
205
  logger.info(f"[search_gmail_messages] Found {len(messages)} messages")
@@ -247,10 +285,10 @@ async def get_gmail_messages_content_batch(
247
285
  ) -> str:
248
286
  """
249
287
  Retrieves the content of multiple Gmail messages in a single batch request.
250
- Supports up to 100 messages per request using Google's batch API.
288
+ Supports up to 25 messages per batch to prevent SSL connection exhaustion.
251
289
 
252
290
  Args:
253
- message_ids (List[str]): List of Gmail message IDs to retrieve (max 100).
291
+ message_ids (List[str]): List of Gmail message IDs to retrieve (max 25 per batch).
254
292
  user_google_email (str): The user's Google email address. Required.
255
293
  format (Literal["full", "metadata"]): Message format. "full" includes body, "metadata" only headers.
256
294
 
@@ -266,9 +304,9 @@ async def get_gmail_messages_content_batch(
266
304
 
267
305
  output_messages = []
268
306
 
269
- # Process in chunks of 100 (Gmail batch limit)
270
- for chunk_start in range(0, len(message_ids), 100):
271
- chunk_ids = message_ids[chunk_start : chunk_start + 100]
307
+ # Process in smaller chunks to prevent SSL connection exhaustion
308
+ for chunk_start in range(0, len(message_ids), GMAIL_BATCH_SIZE):
309
+ chunk_ids = message_ids[chunk_start : chunk_start + GMAIL_BATCH_SIZE]
272
310
  results: Dict[str, Dict] = {}
273
311
 
274
312
  def _batch_callback(request_id, response, exception):
@@ -303,44 +341,57 @@ async def get_gmail_messages_content_batch(
303
341
  await asyncio.to_thread(batch.execute)
304
342
 
305
343
  except Exception as batch_error:
306
- # Fallback to asyncio.gather if batch API fails
344
+ # Fallback to sequential processing instead of parallel to prevent SSL exhaustion
307
345
  logger.warning(
308
- f"[get_gmail_messages_content_batch] Batch API failed, falling back to asyncio.gather: {batch_error}"
346
+ f"[get_gmail_messages_content_batch] Batch API failed, falling back to sequential processing: {batch_error}"
309
347
  )
310
348
 
311
- async def fetch_message(mid: str):
312
- try:
313
- if format == "metadata":
314
- msg = await asyncio.to_thread(
315
- service.users()
316
- .messages()
317
- .get(
318
- userId="me",
319
- id=mid,
320
- format="metadata",
321
- metadataHeaders=["Subject", "From"],
349
+ async def fetch_message_with_retry(mid: str, max_retries: int = 3):
350
+ """Fetch a single message with exponential backoff retry for SSL errors"""
351
+ for attempt in range(max_retries):
352
+ try:
353
+ if format == "metadata":
354
+ msg = await asyncio.to_thread(
355
+ service.users()
356
+ .messages()
357
+ .get(
358
+ userId="me",
359
+ id=mid,
360
+ format="metadata",
361
+ metadataHeaders=["Subject", "From"],
362
+ )
363
+ .execute
322
364
  )
323
- .execute
324
- )
325
- else:
326
- msg = await asyncio.to_thread(
327
- service.users()
328
- .messages()
329
- .get(userId="me", id=mid, format="full")
330
- .execute
331
- )
332
- return mid, msg, None
333
- except Exception as e:
334
- return mid, None, e
335
-
336
- # Fetch all messages in parallel
337
- fetch_results = await asyncio.gather(
338
- *[fetch_message(mid) for mid in chunk_ids], return_exceptions=False
339
- )
365
+ else:
366
+ msg = await asyncio.to_thread(
367
+ service.users()
368
+ .messages()
369
+ .get(userId="me", id=mid, format="full")
370
+ .execute
371
+ )
372
+ return mid, msg, None
373
+ except ssl.SSLError as ssl_error:
374
+ if attempt < max_retries - 1:
375
+ # Exponential backoff: 1s, 2s, 4s
376
+ delay = 2 ** attempt
377
+ logger.warning(
378
+ f"[get_gmail_messages_content_batch] SSL error for message {mid} on attempt {attempt + 1}: {ssl_error}. Retrying in {delay}s..."
379
+ )
380
+ await asyncio.sleep(delay)
381
+ else:
382
+ logger.error(
383
+ f"[get_gmail_messages_content_batch] SSL error for message {mid} on final attempt: {ssl_error}"
384
+ )
385
+ return mid, None, ssl_error
386
+ except Exception as e:
387
+ return mid, None, e
340
388
 
341
- # Convert to results format
342
- for mid, msg, error in fetch_results:
343
- results[mid] = {"data": msg, "error": error}
389
+ # Process messages sequentially with small delays to prevent connection exhaustion
390
+ for mid in chunk_ids:
391
+ mid_result, msg_data, error = await fetch_message_with_retry(mid)
392
+ results[mid_result] = {"data": msg_data, "error": error}
393
+ # Brief delay between requests to allow connection cleanup
394
+ await asyncio.sleep(GMAIL_REQUEST_DELAY)
344
395
 
345
396
  # Process results for this chunk
346
397
  for mid in chunk_ids:
@@ -582,10 +633,10 @@ async def get_gmail_threads_content_batch(
582
633
  ) -> str:
583
634
  """
584
635
  Retrieves the content of multiple Gmail threads in a single batch request.
585
- Supports up to 100 threads per request using Google's batch API.
636
+ Supports up to 25 threads per batch to prevent SSL connection exhaustion.
586
637
 
587
638
  Args:
588
- thread_ids (List[str]): A list of Gmail thread IDs to retrieve. The function will automatically batch requests in chunks of 100.
639
+ thread_ids (List[str]): A list of Gmail thread IDs to retrieve. The function will automatically batch requests in chunks of 25.
589
640
  user_google_email (str): The user's Google email address. Required.
590
641
 
591
642
  Returns:
@@ -604,9 +655,9 @@ async def get_gmail_threads_content_batch(
604
655
  """Callback for batch requests"""
605
656
  results[request_id] = {"data": response, "error": exception}
606
657
 
607
- # Process in chunks of 100 (Gmail batch limit)
608
- for chunk_start in range(0, len(thread_ids), 100):
609
- chunk_ids = thread_ids[chunk_start : chunk_start + 100]
658
+ # Process in smaller chunks to prevent SSL connection exhaustion
659
+ for chunk_start in range(0, len(thread_ids), GMAIL_BATCH_SIZE):
660
+ chunk_ids = thread_ids[chunk_start : chunk_start + GMAIL_BATCH_SIZE]
610
661
  results: Dict[str, Dict] = {}
611
662
 
612
663
  # Try to use batch API
@@ -621,31 +672,44 @@ async def get_gmail_threads_content_batch(
621
672
  await asyncio.to_thread(batch.execute)
622
673
 
623
674
  except Exception as batch_error:
624
- # Fallback to asyncio.gather if batch API fails
675
+ # Fallback to sequential processing instead of parallel to prevent SSL exhaustion
625
676
  logger.warning(
626
- f"[get_gmail_threads_content_batch] Batch API failed, falling back to asyncio.gather: {batch_error}"
677
+ f"[get_gmail_threads_content_batch] Batch API failed, falling back to sequential processing: {batch_error}"
627
678
  )
628
679
 
629
- async def fetch_thread(tid: str):
630
- try:
631
- thread = await asyncio.to_thread(
632
- service.users()
633
- .threads()
634
- .get(userId="me", id=tid, format="full")
635
- .execute
636
- )
637
- return tid, thread, None
638
- except Exception as e:
639
- return tid, None, e
640
-
641
- # Fetch all threads in parallel
642
- fetch_results = await asyncio.gather(
643
- *[fetch_thread(tid) for tid in chunk_ids], return_exceptions=False
644
- )
680
+ async def fetch_thread_with_retry(tid: str, max_retries: int = 3):
681
+ """Fetch a single thread with exponential backoff retry for SSL errors"""
682
+ for attempt in range(max_retries):
683
+ try:
684
+ thread = await asyncio.to_thread(
685
+ service.users()
686
+ .threads()
687
+ .get(userId="me", id=tid, format="full")
688
+ .execute
689
+ )
690
+ return tid, thread, None
691
+ except ssl.SSLError as ssl_error:
692
+ if attempt < max_retries - 1:
693
+ # Exponential backoff: 1s, 2s, 4s
694
+ delay = 2 ** attempt
695
+ logger.warning(
696
+ f"[get_gmail_threads_content_batch] SSL error for thread {tid} on attempt {attempt + 1}: {ssl_error}. Retrying in {delay}s..."
697
+ )
698
+ await asyncio.sleep(delay)
699
+ else:
700
+ logger.error(
701
+ f"[get_gmail_threads_content_batch] SSL error for thread {tid} on final attempt: {ssl_error}"
702
+ )
703
+ return tid, None, ssl_error
704
+ except Exception as e:
705
+ return tid, None, e
645
706
 
646
- # Convert to results format
647
- for tid, thread, error in fetch_results:
648
- results[tid] = {"data": thread, "error": error}
707
+ # Process threads sequentially with small delays to prevent connection exhaustion
708
+ for tid in chunk_ids:
709
+ tid_result, thread_data, error = await fetch_thread_with_retry(tid)
710
+ results[tid_result] = {"data": thread_data, "error": error}
711
+ # Brief delay between requests to allow connection cleanup
712
+ await asyncio.sleep(GMAIL_REQUEST_DELAY)
649
713
 
650
714
  # Process results for this chunk
651
715
  for tid in chunk_ids:
@@ -841,3 +905,53 @@ async def modify_gmail_message_labels(
841
905
  actions.append(f"Removed labels: {', '.join(remove_label_ids)}")
842
906
 
843
907
  return f"Message labels updated successfully!\nMessage ID: {message_id}\n{'; '.join(actions)}"
908
+
909
+
910
+ @server.tool()
911
+ @handle_http_errors("batch_modify_gmail_message_labels")
912
+ @require_google_service("gmail", GMAIL_MODIFY_SCOPE)
913
+ async def batch_modify_gmail_message_labels(
914
+ service,
915
+ user_google_email: str,
916
+ message_ids: List[str],
917
+ add_label_ids: Optional[List[str]] = None,
918
+ remove_label_ids: Optional[List[str]] = None,
919
+ ) -> str:
920
+ """
921
+ Adds or removes labels from multiple Gmail messages in a single batch request.
922
+
923
+ Args:
924
+ user_google_email (str): The user's Google email address. Required.
925
+ message_ids (List[str]): A list of message IDs to modify.
926
+ add_label_ids (Optional[List[str]]): List of label IDs to add to the messages.
927
+ remove_label_ids (Optional[List[str]]): List of label IDs to remove from the messages.
928
+
929
+ Returns:
930
+ str: Confirmation message of the label changes applied to the messages.
931
+ """
932
+ logger.info(
933
+ f"[batch_modify_gmail_message_labels] Invoked. Email: '{user_google_email}', Message IDs: '{message_ids}'"
934
+ )
935
+
936
+ if not add_label_ids and not remove_label_ids:
937
+ raise Exception(
938
+ "At least one of add_label_ids or remove_label_ids must be provided."
939
+ )
940
+
941
+ body = {"ids": message_ids}
942
+ if add_label_ids:
943
+ body["addLabelIds"] = add_label_ids
944
+ if remove_label_ids:
945
+ body["removeLabelIds"] = remove_label_ids
946
+
947
+ await asyncio.to_thread(
948
+ service.users().messages().batchModify(userId="me", body=body).execute
949
+ )
950
+
951
+ actions = []
952
+ if add_label_ids:
953
+ actions.append(f"Added labels: {', '.join(add_label_ids)}")
954
+ if remove_label_ids:
955
+ actions.append(f"Removed labels: {', '.join(remove_label_ids)}")
956
+
957
+ return f"Labels updated for {len(message_ids)} messages: {'; '.join(actions)}"
gsheets/sheets_tools.py CHANGED
@@ -8,8 +8,6 @@ import logging
8
8
  import asyncio
9
9
  from typing import List, Optional
10
10
 
11
- from mcp import types
12
- from googleapiclient.errors import HttpError
13
11
 
14
12
  from auth.service_decorator import require_google_service
15
13
  from core.server import server
gslides/slides_tools.py CHANGED
@@ -6,10 +6,8 @@ This module provides MCP tools for interacting with Google Slides API.
6
6
 
7
7
  import logging
8
8
  import asyncio
9
- from typing import List, Optional, Dict, Any
9
+ from typing import List, Dict, Any
10
10
 
11
- from mcp import types
12
- from googleapiclient.errors import HttpError
13
11
 
14
12
  from auth.service_decorator import require_google_service
15
13
  from core.server import server
gtasks/tasks_tools.py CHANGED
@@ -6,9 +6,8 @@ This module provides MCP tools for interacting with Google Tasks API.
6
6
 
7
7
  import logging
8
8
  import asyncio
9
- from typing import List, Optional, Dict, Any
9
+ from typing import Optional
10
10
 
11
- from mcp import types
12
11
  from googleapiclient.errors import HttpError
13
12
 
14
13
  from auth.service_decorator import require_google_service
main.py CHANGED
@@ -115,9 +115,9 @@ def main():
115
115
  safe_print(f" {tool_icons[tool]} {tool.title()} - Google {tool.title()} API integration")
116
116
  safe_print("")
117
117
 
118
- safe_print(f"📊 Configuration Summary:")
118
+ safe_print("📊 Configuration Summary:")
119
119
  safe_print(f" 🔧 Tools Enabled: {len(tools_to_import)}/{len(tool_imports)}")
120
- safe_print(f" 🔑 Auth Method: OAuth 2.0 with PKCE")
120
+ safe_print(" 🔑 Auth Method: OAuth 2.0 with PKCE")
121
121
  safe_print(f" 📝 Log Level: {logging.getLogger().getEffectiveLevel()}")
122
122
  safe_print("")
123
123
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workspace-mcp
3
- Version: 1.1.7
3
+ Version: 1.1.9
4
4
  Summary: Comprehensive, highly performant Google Workspace Streamable HTTP & SSE MCP Server for Calendar, Gmail, Docs, Sheets, Slides & Drive
5
5
  Author-email: Taylor Wilsdon <taylor@taylorwilsdon.com>
6
6
  License: MIT
@@ -37,6 +37,7 @@ Requires-Dist: google-auth-httplib2>=0.2.0
37
37
  Requires-Dist: google-auth-oauthlib>=1.2.2
38
38
  Requires-Dist: httpx>=0.28.1
39
39
  Requires-Dist: pyjwt>=2.10.1
40
+ Requires-Dist: ruff>=0.12.4
40
41
  Requires-Dist: tomlkit
41
42
  Dynamic: license-file
42
43
 
@@ -118,7 +119,7 @@ A production-ready MCP server that integrates all major Google Workspace service
118
119
  1. **Download:** Grab the latest `google_workspace_mcp.dxt` from the “Releases” page
119
120
  2. **Install:** Double-click the file – Claude Desktop opens and prompts you to **Install**
120
121
  3. **Configure:** In Claude Desktop → **Settings → Extensions → Google Workspace MCP**, paste your Google OAuth credentials
121
- 4. **Use it:** Start a new Claude chat and call any Google Workspace tool 🎉
122
+ 4. **Use it:** Start a new Claude chat and call any Google Workspace tool
122
123
 
123
124
  >
124
125
  **Why DXT?**
@@ -0,0 +1,48 @@
1
+ main.py,sha256=3ugTmkMRAYjeKwlknFrJqv_6dPqimN44-fvTDWJdEiI,7599
2
+ auth/__init__.py,sha256=gPCU3GE-SLy91S3D3CbX-XfKBm6hteK_VSPKx7yjT5s,42
3
+ auth/google_auth.py,sha256=d9fSTA2shCkg5hL_OB1es9hUvz72sJT_MBvtLEGeAHk,32867
4
+ auth/oauth_callback_server.py,sha256=6MOmk4G5e0on1XSJosuLWBLd_WAndGLKBOmktzzock8,9836
5
+ auth/oauth_responses.py,sha256=qbirSB4d7mBRKcJKqGLrJxRAPaLHqObf9t-VMAq6UKA,7020
6
+ auth/scopes.py,sha256=v091tidkMnhB0pPWOr0O08mU_s9yxSwVZkpVOyvlSwY,3550
7
+ auth/service_decorator.py,sha256=crHax8t3EL4DXLPg4i3h9YHyfdpegRex880yNBTNqqI,15795
8
+ auth/oauth21/__init__.py,sha256=5SiZ9_fISDlCaNKIE0yRy7GaaEZRJlNy034YalgkPuI,2592
9
+ auth/oauth21/compat.py,sha256=PnpYmJrnUqd_XHOjT-CV6QeRwCGl-lKdStu0riciKfM,15830
10
+ auth/oauth21/config.py,sha256=UwveWiWnzP_n16NLvefGT4TZVj3LDhfnQyqmaja71ZI,13969
11
+ auth/oauth21/discovery.py,sha256=5ISMJezdPKHARwxP7IvEacQoMRGjaAbbt85se8Fvybo,9519
12
+ auth/oauth21/example_config.py,sha256=EWs7zffU9QIkIfBDILrd8iC0nAVoHdwr2fmuGIH1TH8,10168
13
+ auth/oauth21/handler.py,sha256=e430VrMa6NtbGHP765aaJYU3azT5FTprfv1vUkUDhWQ,15560
14
+ auth/oauth21/http.py,sha256=dozTcgZX3KZlhdq1maR40d4fPgYx04NdJWMQ60UeHXw,8608
15
+ auth/oauth21/jwt.py,sha256=94STdontA5aLQ_uldnaKK_Z-4WXscYEevcAqApHbDj4,14090
16
+ auth/oauth21/middleware.py,sha256=2UYzE-mnp64wTg4cTu7oNRgrnSwR7blvgMdywsnIqoI,15593
17
+ auth/oauth21/oauth2.py,sha256=nQuF3zJR-kCqy8K9kRA9EiNXy49c1_040RZ9kc4xH1M,13276
18
+ auth/oauth21/sessions.py,sha256=OpKCNpDSycKtZHAkCarsbTCBfTrfcR6Fc2Z2tDECTxc,17890
19
+ auth/oauth21/tokens.py,sha256=0BzYU0vdSXq8LiUpVQ8nncZ8UnvjAhXEVw0iY8NYIhs,14745
20
+ core/__init__.py,sha256=AHVKdPl6v4lUFm2R-KuGuAgEmCyfxseMeLGtntMcqCs,43
21
+ core/comments.py,sha256=ZiPxdZOsQYhxFwkClCDlcefP0iyLvO23qvN7OPLGSPk,11119
22
+ core/context.py,sha256=zNgPXf9EO2EMs9sQkfKiywoy6sEOksVNgOrJMA_c30Y,768
23
+ core/server.py,sha256=xXtHJ0BL_qVm10yZr2Xmr1SqlgnKfrv6mXP4WZ3qX4k,9756
24
+ core/utils.py,sha256=sebbgJtzgmtoj-5PNgAvKEjmPtIKmtMY6b-faVf4Oec,13101
25
+ gcalendar/__init__.py,sha256=D5fSdAwbeomoaj7XAdxSnIy-NVKNkpExs67175bOtfc,46
26
+ gcalendar/calendar_tools.py,sha256=Y-lWqO3cWv8mpmO7Tm9AQExG_EfPXXueoiIk1zRPeHc,22167
27
+ gchat/__init__.py,sha256=XBjH4SbtULfZHgFCxk3moel5XqG599HCgZWl_veIncg,88
28
+ gchat/chat_tools.py,sha256=89Gh_6-r8a6CJJjL43CqAmOB0POogzNLMJry4-U-KR4,7252
29
+ gdocs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ gdocs/docs_tools.py,sha256=OBcIj7Cv5GWeUSgl25D7sxgFNwPFR7PtH-7hh6eq0Mc,10959
31
+ gdrive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ gdrive/drive_tools.py,sha256=8mQDL-o4hgimHcivizV5ZD58GitsBdpIZKG6yJflzEI,15237
33
+ gforms/__init__.py,sha256=pL91XixrEp9YjpM-AYwONIEfeCP2OumkEG0Io5V4boE,37
34
+ gforms/forms_tools.py,sha256=Gj8idOXmZUrKAFmFT1l2RJltV7tnThFw8ATrz1z872k,9575
35
+ gmail/__init__.py,sha256=l8PZ4_7Oet6ZE7tVu9oQ3-BaRAmI4YzAO86kf9uu6pU,60
36
+ gmail/gmail_tools.py,sha256=ZOggJNBGNLso6mqZOe0yUgGGxxU_p1Jn7qlcpNOP-_o,34161
37
+ gsheets/__init__.py,sha256=jFfhD52w_EOVw6N5guf_dIc9eP2khW_eS9UAPJg_K3k,446
38
+ gsheets/sheets_tools.py,sha256=JSL_2f6WMN312k5pQ9TFktb-WYJyqAXqW1e91nST_t0,11887
39
+ gslides/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ gslides/slides_tools.py,sha256=vbhdzqHtNHfkNXN3h2_E9j_rMc747OzUaXH5XBu6XQY,10017
41
+ gtasks/__init__.py,sha256=qwOWUzQbkYLSBrdhCqEkAWPH2lEOljk1mLtrlab9YZc,107
42
+ gtasks/tasks_tools.py,sha256=s95fEMI7-TxyP8K2JoDMiRWftnVE9Fkrrr4j6jVgxAE,26469
43
+ workspace_mcp-1.1.9.dist-info/licenses/LICENSE,sha256=bB8L7rIyRy5o-WHxGgvRuY8hUTzNu4h3DTkvyV8XFJo,1070
44
+ workspace_mcp-1.1.9.dist-info/METADATA,sha256=RgCFapZq3-gsUfr1NhBJnBEpKaxfY9enq7ni5_30K80,23567
45
+ workspace_mcp-1.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
46
+ workspace_mcp-1.1.9.dist-info/entry_points.txt,sha256=kPiEfOTuf-ptDM0Rf2OlyrFudGW7hCZGg4MCn2Foxs4,44
47
+ workspace_mcp-1.1.9.dist-info/top_level.txt,sha256=uAg7uV2mETWYRw5g80XtO1lhxVO1sY6_IihNdG_4n24,80
48
+ workspace_mcp-1.1.9.dist-info/RECORD,,
@@ -1,36 +0,0 @@
1
- main.py,sha256=a4w_AcD_nSJo9697-75tZ3sU0tqOP1J8xTrXXD7qmns,7601
2
- auth/__init__.py,sha256=gPCU3GE-SLy91S3D3CbX-XfKBm6hteK_VSPKx7yjT5s,42
3
- auth/google_auth.py,sha256=h0QIEthpZMxw7dEijYQ5ntXESg2FHNGkDneEjJkdCn4,32868
4
- auth/oauth_callback_server.py,sha256=kcgufdYU3e3ncSNouqgGyIiIOfFCXiR6CiOS8pTYuNo,9837
5
- auth/oauth_responses.py,sha256=qbirSB4d7mBRKcJKqGLrJxRAPaLHqObf9t-VMAq6UKA,7020
6
- auth/scopes.py,sha256=v091tidkMnhB0pPWOr0O08mU_s9yxSwVZkpVOyvlSwY,3550
7
- auth/service_decorator.py,sha256=8UfJnST6oi5Mci2YUdiIocn8--0oAEXm74VrGMroqzQ,15846
8
- core/__init__.py,sha256=AHVKdPl6v4lUFm2R-KuGuAgEmCyfxseMeLGtntMcqCs,43
9
- core/comments.py,sha256=vVfZYjH0kwqFyXcwvBx3m0Ko4WmfTJTkfD3dCQbucuc,11215
10
- core/context.py,sha256=zNgPXf9EO2EMs9sQkfKiywoy6sEOksVNgOrJMA_c30Y,768
11
- core/server.py,sha256=En_sV6Z19kWx8SO4KAnh8Qg5v2HYw8f9f_WJdEGMDSA,9301
12
- core/utils.py,sha256=QLgyM-XNJiAqmr-ac9SLXJBWpN8VxvNurxdW0Ib2N7s,13096
13
- gcalendar/__init__.py,sha256=D5fSdAwbeomoaj7XAdxSnIy-NVKNkpExs67175bOtfc,46
14
- gcalendar/calendar_tools.py,sha256=mlWIn26dw7fNGqiIb8NUQNnPsP0b0RJGzp4BvvC85Fk,22193
15
- gchat/__init__.py,sha256=XBjH4SbtULfZHgFCxk3moel5XqG599HCgZWl_veIncg,88
16
- gchat/chat_tools.py,sha256=cIeXBBxWkFCdQNJ23BkX8IoDho6J8ZcfLsPjctUWyfA,7274
17
- gdocs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- gdocs/docs_tools.py,sha256=WrCjya2-MvM7b-DwFAhoXPcV2kNxq4I0V_xaqV3a1ZI,8494
19
- gdrive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- gdrive/drive_tools.py,sha256=NWna_uQ7dmXV4kRPX01A0P7so0sjRfq5awWczg6sloU,15310
21
- gforms/__init__.py,sha256=pL91XixrEp9YjpM-AYwONIEfeCP2OumkEG0Io5V4boE,37
22
- gforms/forms_tools.py,sha256=0zxXFd1JnHPjCHJp_d1id9UYGxbW3UCklBmVlZTRLHo,9597
23
- gmail/__init__.py,sha256=l8PZ4_7Oet6ZE7tVu9oQ3-BaRAmI4YzAO86kf9uu6pU,60
24
- gmail/gmail_tools.py,sha256=3RpnrjFX_E5d8KUKsyx_K8fWMkDdj01mxha9vZBGwn4,28825
25
- gsheets/__init__.py,sha256=jFfhD52w_EOVw6N5guf_dIc9eP2khW_eS9UAPJg_K3k,446
26
- gsheets/sheets_tools.py,sha256=dUXtlpjxhRnPTN4Y0mklrMtW4noqezBfj8Rga2ivw74,11954
27
- gslides/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- gslides/slides_tools.py,sha256=m9FifqBLPFo_tdxH6mvH1-fDUarAnr_ejJ9oMlgKiYQ,10094
29
- gtasks/__init__.py,sha256=qwOWUzQbkYLSBrdhCqEkAWPH2lEOljk1mLtrlab9YZc,107
30
- gtasks/tasks_tools.py,sha256=6zmZsZ-8JWUpOIDhvxuBIJsjB9bH8LrdUX0yVlsjlM8,26508
31
- workspace_mcp-1.1.7.dist-info/licenses/LICENSE,sha256=bB8L7rIyRy5o-WHxGgvRuY8hUTzNu4h3DTkvyV8XFJo,1070
32
- workspace_mcp-1.1.7.dist-info/METADATA,sha256=uQGTgL89fLrgbowJiorKLgmxV340TMvAjNkIFgj61nU,23544
33
- workspace_mcp-1.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- workspace_mcp-1.1.7.dist-info/entry_points.txt,sha256=kPiEfOTuf-ptDM0Rf2OlyrFudGW7hCZGg4MCn2Foxs4,44
35
- workspace_mcp-1.1.7.dist-info/top_level.txt,sha256=uAg7uV2mETWYRw5g80XtO1lhxVO1sY6_IihNdG_4n24,80
36
- workspace_mcp-1.1.7.dist-info/RECORD,,