workspace-mcp 1.1.4__py3-none-any.whl → 1.1.5__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.
auth/google_auth.py CHANGED
@@ -1,14 +1,16 @@
1
1
  # auth/google_auth.py
2
2
 
3
- import os
3
+ import asyncio
4
4
  import json
5
+ import jwt
5
6
  import logging
6
- import asyncio
7
- from typing import List, Optional, Tuple, Dict, Any, Callable
8
7
  import os
9
8
 
9
+ from datetime import datetime
10
+ from typing import List, Optional, Tuple, Dict, Any
11
+
10
12
  from google.oauth2.credentials import Credentials
11
- from google_auth_oauthlib.flow import Flow, InstalledAppFlow
13
+ from google_auth_oauthlib.flow import Flow
12
14
  from google.auth.transport.requests import Request
13
15
  from google.auth.exceptions import RefreshError
14
16
  from googleapiclient.discovery import build
@@ -161,8 +163,6 @@ def load_credentials_from_file(
161
163
  expiry = None
162
164
  if creds_data.get("expiry"):
163
165
  try:
164
- from datetime import datetime
165
-
166
166
  expiry = datetime.fromisoformat(creds_data["expiry"])
167
167
  except (ValueError, TypeError) as e:
168
168
  logger.warning(
@@ -789,8 +789,6 @@ async def get_authenticated_google_service(
789
789
  # Try to get email from credentials if needed for validation
790
790
  if credentials and credentials.id_token:
791
791
  try:
792
- import jwt
793
-
794
792
  # Decode without verification (just to get email for logging)
795
793
  decoded_token = jwt.decode(
796
794
  credentials.id_token, options={"verify_signature": False}
@@ -5,15 +5,17 @@ In streamable-http mode: Uses the existing FastAPI server
5
5
  In stdio mode: Starts a minimal HTTP server just for OAuth callbacks
6
6
  """
7
7
 
8
+ import os
8
9
  import asyncio
9
10
  import logging
10
11
  import threading
11
12
  import time
12
- from typing import Optional, Dict, Any
13
13
  import socket
14
+ import uvicorn
14
15
 
15
16
  from fastapi import FastAPI, Request
16
- import uvicorn
17
+ from typing import Optional
18
+ from urllib.parse import urlparse
17
19
 
18
20
  from auth.google_auth import handle_auth_callback, check_client_secrets
19
21
  from auth.scopes import OAUTH_STATE_TO_SESSION_ID_MAP, SCOPES
@@ -73,10 +75,11 @@ class MinimalOAuthServer:
73
75
  logger.warning(f"OAuth callback: No MCP session ID found for state '{state}'. Auth will not be tied to a specific session.")
74
76
 
75
77
  # Exchange code for credentials
78
+ redirect_uri = get_oauth_redirect_uri(port=self.port, base_uri=self.base_uri)
76
79
  verified_user_id, credentials = handle_auth_callback(
77
80
  scopes=SCOPES,
78
81
  authorization_response=str(request.url),
79
- redirect_uri=f"{self.base_uri}:{self.port}/oauth2callback",
82
+ redirect_uri=redirect_uri,
80
83
  session_id=mcp_session_id
81
84
  )
82
85
 
@@ -105,7 +108,6 @@ class MinimalOAuthServer:
105
108
  # Check if port is available
106
109
  # Extract hostname from base_uri (e.g., "http://localhost" -> "localhost")
107
110
  try:
108
- from urllib.parse import urlparse
109
111
  parsed_uri = urlparse(self.base_uri)
110
112
  hostname = parsed_uri.hostname or 'localhost'
111
113
  except Exception:
@@ -179,19 +181,31 @@ class MinimalOAuthServer:
179
181
  # Global instance for stdio mode
180
182
  _minimal_oauth_server: Optional[MinimalOAuthServer] = None
181
183
 
182
- def get_oauth_redirect_uri(transport_mode: str = "stdio", port: int = 8000, base_uri: str = "http://localhost") -> str:
184
+ def get_oauth_redirect_uri(port: int = 8000, base_uri: str = "http://localhost") -> str:
183
185
  """
184
- Get the appropriate OAuth redirect URI based on transport mode.
186
+ Get the appropriate OAuth redirect URI.
187
+
188
+ Priority:
189
+ 1. GOOGLE_OAUTH_REDIRECT_URI environment variable
190
+ 2. Constructed from port and base URI
185
191
 
186
192
  Args:
187
- transport_mode: "stdio" or "streamable-http"
188
193
  port: Port number (default 8000)
189
194
  base_uri: Base URI (default "http://localhost")
190
195
 
191
196
  Returns:
192
197
  OAuth redirect URI
193
198
  """
194
- return f"{base_uri}:{port}/oauth2callback"
199
+ # Highest priority: Use the environment variable if it's set
200
+ env_redirect_uri = os.getenv("GOOGLE_OAUTH_REDIRECT_URI")
201
+ if env_redirect_uri:
202
+ logger.info(f"Using redirect URI from GOOGLE_OAUTH_REDIRECT_URI: {env_redirect_uri}")
203
+ return env_redirect_uri
204
+
205
+ # Fallback to constructing the URI based on server settings
206
+ constructed_uri = f"{base_uri}:{port}/oauth2callback"
207
+ logger.info(f"Constructed redirect URI: {constructed_uri}")
208
+ return constructed_uri
195
209
 
196
210
  def ensure_oauth_callback_available(transport_mode: str = "stdio", port: int = 8000, base_uri: str = "http://localhost") -> bool:
197
211
  """
core/server.py CHANGED
@@ -82,7 +82,7 @@ def set_transport_mode(mode: str):
82
82
 
83
83
  def get_oauth_redirect_uri_for_current_mode() -> str:
84
84
  """Get OAuth redirect URI based on current transport mode."""
85
- return get_oauth_redirect_uri(_current_transport_mode, WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI)
85
+ return get_oauth_redirect_uri(WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI)
86
86
 
87
87
  # Health check endpoint
88
88
  @server.custom_route("/health", methods=["GET"])
@@ -12,6 +12,7 @@ from typing import List, Optional, Dict, Any
12
12
 
13
13
  from mcp import types
14
14
  from googleapiclient.errors import HttpError
15
+ from googleapiclient.discovery import build
15
16
 
16
17
  from auth.service_decorator import require_google_service
17
18
  from core.utils import handle_http_errors
@@ -268,7 +269,6 @@ async def create_event(
268
269
  if attachments:
269
270
  # Accept both file URLs and file IDs. If a URL, extract the fileId.
270
271
  event_body["attachments"] = []
271
- from googleapiclient.discovery import build
272
272
  drive_service = None
273
273
  try:
274
274
  drive_service = service._http and build("drive", "v3", http=service._http)
gdocs/docs_tools.py CHANGED
@@ -9,7 +9,6 @@ import io
9
9
  from typing import List
10
10
 
11
11
  from mcp import types
12
- from googleapiclient.errors import HttpError
13
12
  from googleapiclient.http import MediaIoBaseDownload
14
13
 
15
14
  # Auth & server utilities
gforms/forms_tools.py CHANGED
@@ -6,10 +6,9 @@ This module provides MCP tools for interacting with Google Forms API.
6
6
 
7
7
  import logging
8
8
  import asyncio
9
- from typing import List, Optional, Dict, Any
9
+ from typing import Optional, Dict, Any
10
10
 
11
11
  from mcp import types
12
- from googleapiclient.errors import HttpError
13
12
 
14
13
  from auth.service_decorator import require_google_service
15
14
  from core.server import server
@@ -47,10 +46,10 @@ async def create_form(
47
46
  "title": title
48
47
  }
49
48
  }
50
-
49
+
51
50
  if description:
52
51
  form_body["info"]["description"] = description
53
-
52
+
54
53
  if document_title:
55
54
  form_body["info"]["document_title"] = document_title
56
55
 
@@ -61,7 +60,7 @@ async def create_form(
61
60
  form_id = created_form.get("formId")
62
61
  edit_url = f"https://docs.google.com/forms/d/{form_id}/edit"
63
62
  responder_url = created_form.get("responderUri", f"https://docs.google.com/forms/d/{form_id}/viewform")
64
-
63
+
65
64
  confirmation_message = f"Successfully created form '{created_form.get('info', {}).get('title', title)}' for {user_google_email}. Form ID: {form_id}. Edit URL: {edit_url}. Responder URL: {responder_url}"
66
65
  logger.info(f"Form created successfully for {user_google_email}. ID: {form_id}")
67
66
  return confirmation_message
@@ -95,10 +94,10 @@ async def get_form(
95
94
  title = form_info.get("title", "No Title")
96
95
  description = form_info.get("description", "No Description")
97
96
  document_title = form_info.get("documentTitle", title)
98
-
97
+
99
98
  edit_url = f"https://docs.google.com/forms/d/{form_id}/edit"
100
99
  responder_url = form.get("responderUri", f"https://docs.google.com/forms/d/{form_id}/viewform")
101
-
100
+
102
101
  items = form.get("items", [])
103
102
  questions_summary = []
104
103
  for i, item in enumerate(items, 1):
@@ -106,9 +105,9 @@ async def get_form(
106
105
  item_type = item.get("questionItem", {}).get("question", {}).get("required", False)
107
106
  required_text = " (Required)" if item_type else ""
108
107
  questions_summary.append(f" {i}. {item_title}{required_text}")
109
-
108
+
110
109
  questions_text = "\n".join(questions_summary) if questions_summary else " No questions found"
111
-
110
+
112
111
  result = f"""Form Details for {user_google_email}:
113
112
  - Title: "{title}"
114
113
  - Description: "{description}"
@@ -118,7 +117,7 @@ async def get_form(
118
117
  - Responder URL: {responder_url}
119
118
  - Questions ({len(items)} total):
120
119
  {questions_text}"""
121
-
120
+
122
121
  logger.info(f"Successfully retrieved form for {user_google_email}. ID: {form_id}")
123
122
  return result
124
123
 
@@ -190,7 +189,7 @@ async def get_form_response(
190
189
  response_id = response.get("responseId", "Unknown")
191
190
  create_time = response.get("createTime", "Unknown")
192
191
  last_submitted_time = response.get("lastSubmittedTime", "Unknown")
193
-
192
+
194
193
  answers = response.get("answers", {})
195
194
  answer_details = []
196
195
  for question_id, answer_data in answers.items():
@@ -200,9 +199,9 @@ async def get_form_response(
200
199
  answer_details.append(f" Question ID {question_id}: {answer_text}")
201
200
  else:
202
201
  answer_details.append(f" Question ID {question_id}: No answer provided")
203
-
202
+
204
203
  answers_text = "\n".join(answer_details) if answer_details else " No answers found"
205
-
204
+
206
205
  result = f"""Form Response Details for {user_google_email}:
207
206
  - Form ID: {form_id}
208
207
  - Response ID: {response_id}
@@ -210,7 +209,7 @@ async def get_form_response(
210
209
  - Last Submitted: {last_submitted_time}
211
210
  - Answers:
212
211
  {answers_text}"""
213
-
212
+
214
213
  logger.info(f"Successfully retrieved response for {user_google_email}. Response ID: {response_id}")
215
214
  return result
216
215
 
@@ -252,7 +251,7 @@ async def list_form_responses(
252
251
 
253
252
  responses = responses_result.get("responses", [])
254
253
  next_page_token = responses_result.get("nextPageToken")
255
-
254
+
256
255
  if not responses:
257
256
  return f"No responses found for form {form_id} for {user_google_email}."
258
257
 
@@ -261,19 +260,19 @@ async def list_form_responses(
261
260
  response_id = response.get("responseId", "Unknown")
262
261
  create_time = response.get("createTime", "Unknown")
263
262
  last_submitted_time = response.get("lastSubmittedTime", "Unknown")
264
-
263
+
265
264
  answers_count = len(response.get("answers", {}))
266
265
  response_details.append(
267
266
  f" {i}. Response ID: {response_id} | Created: {create_time} | Last Submitted: {last_submitted_time} | Answers: {answers_count}"
268
267
  )
269
268
 
270
269
  pagination_info = f"\nNext page token: {next_page_token}" if next_page_token else "\nNo more pages."
271
-
270
+
272
271
  result = f"""Form Responses for {user_google_email}:
273
272
  - Form ID: {form_id}
274
273
  - Total responses returned: {len(responses)}
275
274
  - Responses:
276
275
  {chr(10).join(response_details)}{pagination_info}"""
277
-
276
+
278
277
  logger.info(f"Successfully retrieved {len(responses)} responses for {user_google_email}. Form ID: {form_id}")
279
278
  return result
gmail/gmail_tools.py CHANGED
@@ -13,7 +13,6 @@ from email.mime.text import MIMEText
13
13
 
14
14
  from mcp import types
15
15
  from fastapi import Body
16
- from googleapiclient.errors import HttpError
17
16
 
18
17
  from auth.service_decorator import require_google_service
19
18
  from core.utils import handle_http_errors
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workspace-mcp
3
- Version: 1.1.4
3
+ Version: 1.1.5
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
@@ -79,13 +79,15 @@ Dynamic: license-file
79
79
  ---
80
80
 
81
81
  ### A quick plug for AI-Enhanced Docs
82
+ <details>
83
+ <summary>But why?</summary>
82
84
 
83
- > **This README was crafted with AI assistance, and here's why that matters**
85
+ **This README was written with AI assistance, and here's why that matters**
84
86
  >
85
- > As a solo developer building open source tools that may only ever serve my own needs, comprehensive documentation often wouldn't happen without AI help. Using agentic dev tools like **Roo** & **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
87
+ > As a solo dev building open source tools that many never see outside use, comprehensive documentation often wouldn't happen without AI help. Using agentic dev tools like **Roo** & **Claude Code** that understand the entire codebase, AI doesn't just regurgitate generic content - it extracts real implementation details and creates accurate, specific documentation.
86
88
  >
87
- > In this case, Sonnet 4 took a pass & a human (me) verified them 6/28/25.
88
-
89
+ > In this case, Sonnet 4 took a pass & a human (me) verified them 7/10/25.
90
+ </details>
89
91
 
90
92
  ## Overview
91
93
 
@@ -103,7 +105,7 @@ A production-ready MCP server that integrates all major Google Workspace service
103
105
  - **📝 Google Forms**: Form creation, retrieval, publish settings, and response management
104
106
  - **✓ Google Tasks**: Complete task and task list management with hierarchy, due dates, and status tracking
105
107
  - **💬 Google Chat**: Space management and messaging capabilities
106
- - **🔄 Multiple Transports**: HTTP with SSE fallback, OpenAPI compatibility via `mcpo`
108
+ - **🔄 All Transports**: Stdio, Streamable HTTP & SSE, OpenAPI compatibility via `mcpo`
107
109
  - **⚡ High Performance**: Service caching, thread-safe sessions, FastMCP integration
108
110
  - **🧩 Developer Friendly**: Minimal boilerplate, automatic service injection, centralized configuration
109
111
 
@@ -111,7 +113,45 @@ A production-ready MCP server that integrates all major Google Workspace service
111
113
 
112
114
  ## 🚀 Quick Start
113
115
 
114
- ### Simplest Start (uvx - Recommended)
116
+ ### 1. One-Click Claude Desktop Install (Recommended)
117
+
118
+ 1. **Download:** Grab the latest `google_workspace_mcp.dxt` from the “Releases” page
119
+ 2. **Install:** Double-click the file – Claude Desktop opens and prompts you to **Install**
120
+ 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
+
123
+ >
124
+ **Why DXT?**
125
+ > Desktop Extensions (`.dxt`) bundle the server, dependencies, and manifest so users go from download → working MCP in **three clicks** – no terminal, no JSON editing, no version conflicts.
126
+
127
+ #### Required Configuration
128
+ <details>
129
+ <summary>Environment - you will configure these in Claude itself, see screenshot:</summary>
130
+ | Variable | Purpose |
131
+ |----------|---------|
132
+ | `GOOGLE_OAUTH_CLIENT_ID` | OAuth client ID from Google Cloud |
133
+ | `GOOGLE_OAUTH_CLIENT_SECRET` | OAuth client secret |
134
+ | `USER_GOOGLE_EMAIL` *(optional)* | Default email for single-user auth |
135
+ | `OAUTHLIB_INSECURE_TRANSPORT=1` | Development only (allows `http://` redirect) |
136
+
137
+ Claude Desktop stores these securely in the OS keychain; set them once in the extension pane.
138
+ </details>
139
+ Screenshot here
140
+
141
+ ---
142
+
143
+ ### 2. Advanced / Cross-Platform Installation
144
+
145
+ If you’re developing, deploying to servers, or using another MCP-capable client, keep reading.
146
+
147
+ #### Instant CLI (uvx)
148
+
149
+ ```bash
150
+ # Requires Python 3.11+ and uvx
151
+ export GOOGLE_OAUTH_CLIENT_ID="xxx"
152
+ export GOOGLE_OAUTH_CLIENT_SECRET="yyy"
153
+ uvx workspace-mcp --tools gmail drive calendar
154
+ ```
115
155
 
116
156
  > Run instantly without manual installation - you must configure OAuth credentials when using uvx. You can use either environment variables (recommended for production) or set the `GOOGLE_CLIENT_SECRET_PATH` (or legacy `GOOGLE_CLIENT_SECRETS`) environment variable to point to your `client_secret.json` file.
117
157
 
@@ -187,9 +227,10 @@ uv run main.py
187
227
 
188
228
  3. **Server Configuration**:
189
229
  The server's base URL and port can be customized using environment variables:
190
- - `WORKSPACE_MCP_BASE_URI`: Sets the base URI for the server (default: http://localhost). This affects the server_url used for Gemini native function calling and the OAUTH_REDIRECT_URI.
230
+ - `WORKSPACE_MCP_BASE_URI`: Sets the base URI for the server (default: http://localhost). This affects the `server_url` used to construct the default `OAUTH_REDIRECT_URI` if `GOOGLE_OAUTH_REDIRECT_URI` is not set.
191
231
  - `WORKSPACE_MCP_PORT`: Sets the port the server listens on (default: 8000). This affects the server_url, port, and OAUTH_REDIRECT_URI.
192
232
  - `USER_GOOGLE_EMAIL`: Optional default email for authentication flows. If set, the LLM won't need to specify your email when calling `start_google_auth`.
233
+ - `GOOGLE_OAUTH_REDIRECT_URI`: Sets an override for OAuth redirect specifically, must include a full address (i.e. include port if necessary). Use this if you want to run your OAuth redirect separately from the MCP. This is not recommended outside of very specific cases
193
234
 
194
235
  ### Start the Server
195
236
 
@@ -221,7 +262,8 @@ The server supports two transport modes:
221
262
 
222
263
  #### Stdio Mode (Default - Recommended for Claude Desktop)
223
264
 
224
- **Easiest Setup (Recommended)**
265
+ **Guided Setup (Recommended if not using DXT)**
266
+
225
267
  ```bash
226
268
  python install_claude.py
227
269
  ```
@@ -1,36 +1,36 @@
1
1
  main.py,sha256=a4w_AcD_nSJo9697-75tZ3sU0tqOP1J8xTrXXD7qmns,7601
2
2
  auth/__init__.py,sha256=gPCU3GE-SLy91S3D3CbX-XfKBm6hteK_VSPKx7yjT5s,42
3
- auth/google_auth.py,sha256=JiGrHFpzhuxQgUNumZtAbyl8HTisDVdnvVFeSqpkCfg,32939
4
- auth/oauth_callback_server.py,sha256=igrur3fkZSY0bawufrH4AN9fMNpobUdAUp1BG7AQC6w,9341
3
+ auth/google_auth.py,sha256=h0QIEthpZMxw7dEijYQ5ntXESg2FHNGkDneEjJkdCn4,32868
4
+ auth/oauth_callback_server.py,sha256=kcgufdYU3e3ncSNouqgGyIiIOfFCXiR6CiOS8pTYuNo,9837
5
5
  auth/oauth_responses.py,sha256=qbirSB4d7mBRKcJKqGLrJxRAPaLHqObf9t-VMAq6UKA,7020
6
6
  auth/scopes.py,sha256=v091tidkMnhB0pPWOr0O08mU_s9yxSwVZkpVOyvlSwY,3550
7
7
  auth/service_decorator.py,sha256=8UfJnST6oi5Mci2YUdiIocn8--0oAEXm74VrGMroqzQ,15846
8
8
  core/__init__.py,sha256=AHVKdPl6v4lUFm2R-KuGuAgEmCyfxseMeLGtntMcqCs,43
9
9
  core/comments.py,sha256=vVfZYjH0kwqFyXcwvBx3m0Ko4WmfTJTkfD3dCQbucuc,11215
10
10
  core/context.py,sha256=zNgPXf9EO2EMs9sQkfKiywoy6sEOksVNgOrJMA_c30Y,768
11
- core/server.py,sha256=KNAo43WTgzb6WSOyYE4Nixs5yLSg3NGqT6V2v3h3Wxo,9326
11
+ core/server.py,sha256=En_sV6Z19kWx8SO4KAnh8Qg5v2HYw8f9f_WJdEGMDSA,9301
12
12
  core/utils.py,sha256=sUNPhM0xh3tqgyCZxTcoje37Et-pbNJTksybTatDyho,10127
13
13
  gcalendar/__init__.py,sha256=D5fSdAwbeomoaj7XAdxSnIy-NVKNkpExs67175bOtfc,46
14
- gcalendar/calendar_tools.py,sha256=SIiSJRxG3G9KsScow0pYwew600_PdtFqlOo-y2vXQRo,22144
14
+ gcalendar/calendar_tools.py,sha256=DYbvwqfyFlRQTXIxqezie_2u_2PLGXczhofvkLmNhOU,22136
15
15
  gchat/__init__.py,sha256=XBjH4SbtULfZHgFCxk3moel5XqG599HCgZWl_veIncg,88
16
16
  gchat/chat_tools.py,sha256=cIeXBBxWkFCdQNJ23BkX8IoDho6J8ZcfLsPjctUWyfA,7274
17
17
  gdocs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- gdocs/docs_tools.py,sha256=gWPBXf2M_ucP9LasAW0JAlCFAwixlcbAFDGS62xspZ4,8482
18
+ gdocs/docs_tools.py,sha256=x7YfzDAgy3bRgZt0P9TfSF0HLP2Yp8SkCeRl7hAvEuc,8437
19
19
  gdrive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  gdrive/drive_tools.py,sha256=l-6IpHTstRMKIY2CU4DFTTNfEQ5rVbafgwo8BrbJ9Bk,15257
21
21
  gforms/__init__.py,sha256=pL91XixrEp9YjpM-AYwONIEfeCP2OumkEG0Io5V4boE,37
22
- gforms/forms_tools.py,sha256=reJF3qw9WwW6-aCOkS2x5jVBvdRx4Za8onEZBC57RXk,9663
22
+ gforms/forms_tools.py,sha256=2kV2ZZTMCLcLdP9l-X6Ob9HRSYbPrYezKDc8lOwnRjU,9540
23
23
  gmail/__init__.py,sha256=l8PZ4_7Oet6ZE7tVu9oQ3-BaRAmI4YzAO86kf9uu6pU,60
24
- gmail/gmail_tools.py,sha256=UIcws__Akw0kxbasc9fYH7rkzDw_7L-LJU1LQU_p-sA,24754
24
+ gmail/gmail_tools.py,sha256=7Rbzh8MJ8UwfQ8Gmpu9fustv7vXZ2JWZ5b35Fe28D00,24709
25
25
  gsheets/__init__.py,sha256=jFfhD52w_EOVw6N5guf_dIc9eP2khW_eS9UAPJg_K3k,446
26
26
  gsheets/sheets_tools.py,sha256=TVlJ-jcIvJ_sJt8xO4-sBWIshb8rabJhjTmZfzHIJsU,11898
27
27
  gslides/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  gslides/slides_tools.py,sha256=wil3XRyUMzUbpBUMqis0CW5eRuwOrP0Lp7-6WbF4QVU,10117
29
29
  gtasks/__init__.py,sha256=qwOWUzQbkYLSBrdhCqEkAWPH2lEOljk1mLtrlab9YZc,107
30
30
  gtasks/tasks_tools.py,sha256=Gy_j1VTeaa4HD2HQe0U1QjG3dQrkijtPNe0dUq5mAZQ,26021
31
- workspace_mcp-1.1.4.dist-info/licenses/LICENSE,sha256=bB8L7rIyRy5o-WHxGgvRuY8hUTzNu4h3DTkvyV8XFJo,1070
32
- workspace_mcp-1.1.4.dist-info/METADATA,sha256=9e72G7CXNSO56YIRgxtGYHW1gEttRlOMKVGjuPlkxvc,21435
33
- workspace_mcp-1.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- workspace_mcp-1.1.4.dist-info/entry_points.txt,sha256=kPiEfOTuf-ptDM0Rf2OlyrFudGW7hCZGg4MCn2Foxs4,44
35
- workspace_mcp-1.1.4.dist-info/top_level.txt,sha256=uAg7uV2mETWYRw5g80XtO1lhxVO1sY6_IihNdG_4n24,80
36
- workspace_mcp-1.1.4.dist-info/RECORD,,
31
+ workspace_mcp-1.1.5.dist-info/licenses/LICENSE,sha256=bB8L7rIyRy5o-WHxGgvRuY8hUTzNu4h3DTkvyV8XFJo,1070
32
+ workspace_mcp-1.1.5.dist-info/METADATA,sha256=MYM6nexE1H11xoH07B_AVg3E8M5LeQ0n8LlD00W6e7U,23325
33
+ workspace_mcp-1.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ workspace_mcp-1.1.5.dist-info/entry_points.txt,sha256=kPiEfOTuf-ptDM0Rf2OlyrFudGW7hCZGg4MCn2Foxs4,44
35
+ workspace_mcp-1.1.5.dist-info/top_level.txt,sha256=uAg7uV2mETWYRw5g80XtO1lhxVO1sY6_IihNdG_4n24,80
36
+ workspace_mcp-1.1.5.dist-info/RECORD,,