workspace-mcp 1.0.0__py3-none-any.whl → 1.0.2__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/service_decorator.py +31 -32
- core/server.py +3 -10
- core/utils.py +36 -0
- gcalendar/calendar_tools.py +308 -258
- gchat/chat_tools.py +131 -158
- gdocs/docs_tools.py +121 -149
- gdrive/drive_tools.py +168 -171
- gforms/forms_tools.py +118 -157
- gmail/gmail_tools.py +319 -400
- gsheets/sheets_tools.py +144 -197
- gslides/slides_tools.py +113 -157
- main.py +31 -25
- workspace_mcp-1.0.2.dist-info/METADATA +422 -0
- workspace_mcp-1.0.2.dist-info/RECORD +32 -0
- workspace_mcp-1.0.0.dist-info/METADATA +0 -29
- workspace_mcp-1.0.0.dist-info/RECORD +0 -32
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/WHEEL +0 -0
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/entry_points.txt +0 -0
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {workspace_mcp-1.0.0.dist-info → workspace_mcp-1.0.2.dist-info}/top_level.txt +0 -0
gchat/chat_tools.py
CHANGED
@@ -13,11 +13,13 @@ from googleapiclient.errors import HttpError
|
|
13
13
|
# Auth & server utilities
|
14
14
|
from auth.service_decorator import require_google_service
|
15
15
|
from core.server import server
|
16
|
+
from core.utils import handle_http_errors
|
16
17
|
|
17
18
|
logger = logging.getLogger(__name__)
|
18
19
|
|
19
20
|
@server.tool()
|
20
21
|
@require_google_service("chat", "chat_read")
|
22
|
+
@handle_http_errors("list_spaces")
|
21
23
|
async def list_spaces(
|
22
24
|
service,
|
23
25
|
user_google_email: str,
|
@@ -32,44 +34,37 @@ async def list_spaces(
|
|
32
34
|
"""
|
33
35
|
logger.info(f"[list_spaces] Email={user_google_email}, Type={space_type}")
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
filter_param = "spaceType = DIRECT_MESSAGE"
|
37
|
+
# Build filter based on space_type
|
38
|
+
filter_param = None
|
39
|
+
if space_type == "room":
|
40
|
+
filter_param = "spaceType = SPACE"
|
41
|
+
elif space_type == "dm":
|
42
|
+
filter_param = "spaceType = DIRECT_MESSAGE"
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
request_params = {"pageSize": page_size}
|
45
|
+
if filter_param:
|
46
|
+
request_params["filter"] = filter_param
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
response = await asyncio.to_thread(
|
49
|
+
service.spaces().list(**request_params).execute
|
50
|
+
)
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
spaces = response.get('spaces', [])
|
53
|
+
if not spaces:
|
54
|
+
return f"No Chat spaces found for type '{space_type}'."
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
output = [f"Found {len(spaces)} Chat spaces (type: {space_type}):"]
|
57
|
+
for space in spaces:
|
58
|
+
space_name = space.get('displayName', 'Unnamed Space')
|
59
|
+
space_id = space.get('name', '')
|
60
|
+
space_type_actual = space.get('spaceType', 'UNKNOWN')
|
61
|
+
output.append(f"- {space_name} (ID: {space_id}, Type: {space_type_actual})")
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
except HttpError as e:
|
65
|
-
logger.error(f"API error in list_spaces: {e}", exc_info=True)
|
66
|
-
raise Exception(f"API error: {e}")
|
67
|
-
except Exception as e:
|
68
|
-
logger.exception(f"Unexpected error in list_spaces: {e}")
|
69
|
-
raise Exception(f"Unexpected error: {e}")
|
63
|
+
return "\n".join(output)
|
70
64
|
|
71
65
|
@server.tool()
|
72
66
|
@require_google_service("chat", "chat_read")
|
67
|
+
@handle_http_errors("get_messages")
|
73
68
|
async def get_messages(
|
74
69
|
service,
|
75
70
|
user_google_email: str,
|
@@ -85,48 +80,41 @@ async def get_messages(
|
|
85
80
|
"""
|
86
81
|
logger.info(f"[get_messages] Space ID: '{space_id}' for user '{user_google_email}'")
|
87
82
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
return "\n".join(output)
|
120
|
-
|
121
|
-
except HttpError as error:
|
122
|
-
logger.error(f"[get_messages] API error for space {space_id}: {error}", exc_info=True)
|
123
|
-
raise Exception(f"API error accessing space {space_id}: {error}")
|
124
|
-
except Exception as e:
|
125
|
-
logger.exception(f"[get_messages] Unexpected error for space {space_id}: {e}")
|
126
|
-
raise Exception(f"Unexpected error accessing space {space_id}: {e}")
|
83
|
+
# Get space info first
|
84
|
+
space_info = await asyncio.to_thread(
|
85
|
+
service.spaces().get(name=space_id).execute
|
86
|
+
)
|
87
|
+
space_name = space_info.get('displayName', 'Unknown Space')
|
88
|
+
|
89
|
+
# Get messages
|
90
|
+
response = await asyncio.to_thread(
|
91
|
+
service.spaces().messages().list(
|
92
|
+
parent=space_id,
|
93
|
+
pageSize=page_size,
|
94
|
+
orderBy=order_by
|
95
|
+
).execute
|
96
|
+
)
|
97
|
+
|
98
|
+
messages = response.get('messages', [])
|
99
|
+
if not messages:
|
100
|
+
return f"No messages found in space '{space_name}' (ID: {space_id})."
|
101
|
+
|
102
|
+
output = [f"Messages from '{space_name}' (ID: {space_id}):\n"]
|
103
|
+
for msg in messages:
|
104
|
+
sender = msg.get('sender', {}).get('displayName', 'Unknown Sender')
|
105
|
+
create_time = msg.get('createTime', 'Unknown Time')
|
106
|
+
text_content = msg.get('text', 'No text content')
|
107
|
+
msg_name = msg.get('name', '')
|
108
|
+
|
109
|
+
output.append(f"[{create_time}] {sender}:")
|
110
|
+
output.append(f" {text_content}")
|
111
|
+
output.append(f" (Message ID: {msg_name})\n")
|
112
|
+
|
113
|
+
return "\n".join(output)
|
127
114
|
|
128
115
|
@server.tool()
|
129
116
|
@require_google_service("chat", "chat_write")
|
117
|
+
@handle_http_errors("send_message")
|
130
118
|
async def send_message(
|
131
119
|
service,
|
132
120
|
user_google_email: str,
|
@@ -142,39 +130,32 @@ async def send_message(
|
|
142
130
|
"""
|
143
131
|
logger.info(f"[send_message] Email: '{user_google_email}', Space: '{space_id}'")
|
144
132
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
}
|
149
|
-
|
150
|
-
# Add thread key if provided (for threaded replies)
|
151
|
-
request_params = {
|
152
|
-
'parent': space_id,
|
153
|
-
'body': message_body
|
154
|
-
}
|
155
|
-
if thread_key:
|
156
|
-
request_params['threadKey'] = thread_key
|
157
|
-
|
158
|
-
message = await asyncio.to_thread(
|
159
|
-
service.spaces().messages().create(**request_params).execute
|
160
|
-
)
|
133
|
+
message_body = {
|
134
|
+
'text': message_text
|
135
|
+
}
|
161
136
|
|
162
|
-
|
163
|
-
|
137
|
+
# Add thread key if provided (for threaded replies)
|
138
|
+
request_params = {
|
139
|
+
'parent': space_id,
|
140
|
+
'body': message_body
|
141
|
+
}
|
142
|
+
if thread_key:
|
143
|
+
request_params['threadKey'] = thread_key
|
164
144
|
|
165
|
-
|
166
|
-
|
167
|
-
|
145
|
+
message = await asyncio.to_thread(
|
146
|
+
service.spaces().messages().create(**request_params).execute
|
147
|
+
)
|
168
148
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
149
|
+
message_name = message.get('name', '')
|
150
|
+
create_time = message.get('createTime', '')
|
151
|
+
|
152
|
+
msg = f"Message sent to space '{space_id}' by {user_google_email}. Message ID: {message_name}, Time: {create_time}"
|
153
|
+
logger.info(f"Successfully sent message to space '{space_id}' by {user_google_email}")
|
154
|
+
return msg
|
175
155
|
|
176
156
|
@server.tool()
|
177
157
|
@require_google_service("chat", "chat_read")
|
158
|
+
@handle_http_errors("search_messages")
|
178
159
|
async def search_messages(
|
179
160
|
service,
|
180
161
|
user_google_email: str,
|
@@ -190,65 +171,57 @@ async def search_messages(
|
|
190
171
|
"""
|
191
172
|
logger.info(f"[search_messages] Email={user_google_email}, Query='{query}'")
|
192
173
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
return "\n".join(output)
|
248
|
-
|
249
|
-
except HttpError as e:
|
250
|
-
logger.error(f"API error in search_messages: {e}", exc_info=True)
|
251
|
-
raise Exception(f"API error: {e}")
|
252
|
-
except Exception as e:
|
253
|
-
logger.exception(f"Unexpected error in search_messages: {e}")
|
254
|
-
raise Exception(f"Unexpected error: {e}")
|
174
|
+
# If specific space provided, search within that space
|
175
|
+
if space_id:
|
176
|
+
response = await asyncio.to_thread(
|
177
|
+
service.spaces().messages().list(
|
178
|
+
parent=space_id,
|
179
|
+
pageSize=page_size,
|
180
|
+
filter=f'text:"{query}"'
|
181
|
+
).execute
|
182
|
+
)
|
183
|
+
messages = response.get('messages', [])
|
184
|
+
context = f"space '{space_id}'"
|
185
|
+
else:
|
186
|
+
# Search across all accessible spaces (this may require iterating through spaces)
|
187
|
+
# For simplicity, we'll search the user's spaces first
|
188
|
+
spaces_response = await asyncio.to_thread(
|
189
|
+
service.spaces().list(pageSize=100).execute
|
190
|
+
)
|
191
|
+
spaces = spaces_response.get('spaces', [])
|
192
|
+
|
193
|
+
messages = []
|
194
|
+
for space in spaces[:10]: # Limit to first 10 spaces to avoid timeout
|
195
|
+
try:
|
196
|
+
space_messages = await asyncio.to_thread(
|
197
|
+
service.spaces().messages().list(
|
198
|
+
parent=space.get('name'),
|
199
|
+
pageSize=5,
|
200
|
+
filter=f'text:"{query}"'
|
201
|
+
).execute
|
202
|
+
)
|
203
|
+
space_msgs = space_messages.get('messages', [])
|
204
|
+
for msg in space_msgs:
|
205
|
+
msg['_space_name'] = space.get('displayName', 'Unknown')
|
206
|
+
messages.extend(space_msgs)
|
207
|
+
except HttpError:
|
208
|
+
continue # Skip spaces we can't access
|
209
|
+
context = "all accessible spaces"
|
210
|
+
|
211
|
+
if not messages:
|
212
|
+
return f"No messages found matching '{query}' in {context}."
|
213
|
+
|
214
|
+
output = [f"Found {len(messages)} messages matching '{query}' in {context}:"]
|
215
|
+
for msg in messages:
|
216
|
+
sender = msg.get('sender', {}).get('displayName', 'Unknown Sender')
|
217
|
+
create_time = msg.get('createTime', 'Unknown Time')
|
218
|
+
text_content = msg.get('text', 'No text content')
|
219
|
+
space_name = msg.get('_space_name', 'Unknown Space')
|
220
|
+
|
221
|
+
# Truncate long messages
|
222
|
+
if len(text_content) > 100:
|
223
|
+
text_content = text_content[:100] + "..."
|
224
|
+
|
225
|
+
output.append(f"- [{create_time}] {sender} in '{space_name}': {text_content}")
|
226
|
+
|
227
|
+
return "\n".join(output)
|