workspace-mcp 0.2.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.
- auth/__init__.py +1 -0
- auth/google_auth.py +549 -0
- auth/oauth_callback_server.py +241 -0
- auth/oauth_responses.py +223 -0
- auth/scopes.py +108 -0
- auth/service_decorator.py +404 -0
- core/__init__.py +1 -0
- core/server.py +214 -0
- core/utils.py +162 -0
- gcalendar/__init__.py +1 -0
- gcalendar/calendar_tools.py +496 -0
- gchat/__init__.py +6 -0
- gchat/chat_tools.py +254 -0
- gdocs/__init__.py +0 -0
- gdocs/docs_tools.py +244 -0
- gdrive/__init__.py +0 -0
- gdrive/drive_tools.py +362 -0
- gforms/__init__.py +3 -0
- gforms/forms_tools.py +318 -0
- gmail/__init__.py +1 -0
- gmail/gmail_tools.py +807 -0
- gsheets/__init__.py +23 -0
- gsheets/sheets_tools.py +393 -0
- gslides/__init__.py +0 -0
- gslides/slides_tools.py +316 -0
- main.py +160 -0
- workspace_mcp-0.2.0.dist-info/METADATA +29 -0
- workspace_mcp-0.2.0.dist-info/RECORD +32 -0
- workspace_mcp-0.2.0.dist-info/WHEEL +5 -0
- workspace_mcp-0.2.0.dist-info/entry_points.txt +2 -0
- workspace_mcp-0.2.0.dist-info/licenses/LICENSE +21 -0
- workspace_mcp-0.2.0.dist-info/top_level.txt +11 -0
@@ -0,0 +1,241 @@
|
|
1
|
+
"""
|
2
|
+
Transport-aware OAuth callback handling.
|
3
|
+
|
4
|
+
In streamable-http mode: Uses the existing FastAPI server
|
5
|
+
In stdio mode: Starts a minimal HTTP server just for OAuth callbacks
|
6
|
+
"""
|
7
|
+
|
8
|
+
import asyncio
|
9
|
+
import logging
|
10
|
+
import threading
|
11
|
+
import time
|
12
|
+
from typing import Optional, Dict, Any
|
13
|
+
import socket
|
14
|
+
|
15
|
+
from fastapi import FastAPI, Request
|
16
|
+
import uvicorn
|
17
|
+
|
18
|
+
from auth.google_auth import handle_auth_callback, CONFIG_CLIENT_SECRETS_PATH
|
19
|
+
from auth.scopes import OAUTH_STATE_TO_SESSION_ID_MAP, SCOPES
|
20
|
+
from auth.oauth_responses import create_error_response, create_success_response, create_server_error_response
|
21
|
+
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
class MinimalOAuthServer:
|
25
|
+
"""
|
26
|
+
Minimal HTTP server for OAuth callbacks in stdio mode.
|
27
|
+
Only starts when needed and uses the same port (8000) as streamable-http mode.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, port: int = 8000, base_uri: str = "http://localhost"):
|
31
|
+
self.port = port
|
32
|
+
self.base_uri = base_uri
|
33
|
+
self.app = FastAPI()
|
34
|
+
self.server = None
|
35
|
+
self.server_thread = None
|
36
|
+
self.is_running = False
|
37
|
+
|
38
|
+
# Setup the callback route
|
39
|
+
self._setup_callback_route()
|
40
|
+
|
41
|
+
def _setup_callback_route(self):
|
42
|
+
"""Setup the OAuth callback route."""
|
43
|
+
|
44
|
+
@self.app.get("/oauth2callback")
|
45
|
+
async def oauth_callback(request: Request):
|
46
|
+
"""Handle OAuth callback - same logic as in core/server.py"""
|
47
|
+
state = request.query_params.get("state")
|
48
|
+
code = request.query_params.get("code")
|
49
|
+
error = request.query_params.get("error")
|
50
|
+
|
51
|
+
if error:
|
52
|
+
error_message = f"Authentication failed: Google returned an error: {error}. State: {state}."
|
53
|
+
logger.error(error_message)
|
54
|
+
return create_error_response(error_message)
|
55
|
+
|
56
|
+
if not code:
|
57
|
+
error_message = "Authentication failed: No authorization code received from Google."
|
58
|
+
logger.error(error_message)
|
59
|
+
return create_error_response(error_message)
|
60
|
+
|
61
|
+
try:
|
62
|
+
logger.info(f"OAuth callback: Received code (state: {state}). Attempting to exchange for tokens.")
|
63
|
+
|
64
|
+
mcp_session_id: Optional[str] = OAUTH_STATE_TO_SESSION_ID_MAP.pop(state, None)
|
65
|
+
if mcp_session_id:
|
66
|
+
logger.info(f"OAuth callback: Retrieved MCP session ID '{mcp_session_id}' for state '{state}'.")
|
67
|
+
else:
|
68
|
+
logger.warning(f"OAuth callback: No MCP session ID found for state '{state}'. Auth will not be tied to a specific session.")
|
69
|
+
|
70
|
+
# Exchange code for credentials
|
71
|
+
verified_user_id, credentials = handle_auth_callback(
|
72
|
+
client_secrets_path=CONFIG_CLIENT_SECRETS_PATH,
|
73
|
+
scopes=SCOPES,
|
74
|
+
authorization_response=str(request.url),
|
75
|
+
redirect_uri=f"{self.base_uri}:{self.port}/oauth2callback",
|
76
|
+
session_id=mcp_session_id
|
77
|
+
)
|
78
|
+
|
79
|
+
log_session_part = f" (linked to session: {mcp_session_id})" if mcp_session_id else ""
|
80
|
+
logger.info(f"OAuth callback: Successfully authenticated user: {verified_user_id} (state: {state}){log_session_part}.")
|
81
|
+
|
82
|
+
# Return success page using shared template
|
83
|
+
return create_success_response(verified_user_id)
|
84
|
+
|
85
|
+
except Exception as e:
|
86
|
+
error_message_detail = f"Error processing OAuth callback (state: {state}): {str(e)}"
|
87
|
+
logger.error(error_message_detail, exc_info=True)
|
88
|
+
return create_server_error_response(str(e))
|
89
|
+
|
90
|
+
def start(self) -> bool:
|
91
|
+
"""
|
92
|
+
Start the minimal OAuth server.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
True if started successfully, False otherwise
|
96
|
+
"""
|
97
|
+
if self.is_running:
|
98
|
+
logger.info("Minimal OAuth server is already running")
|
99
|
+
return True
|
100
|
+
|
101
|
+
# Check if port is available
|
102
|
+
# Extract hostname from base_uri (e.g., "http://localhost" -> "localhost")
|
103
|
+
try:
|
104
|
+
from urllib.parse import urlparse
|
105
|
+
parsed_uri = urlparse(self.base_uri)
|
106
|
+
hostname = parsed_uri.hostname or 'localhost'
|
107
|
+
except Exception:
|
108
|
+
hostname = 'localhost'
|
109
|
+
|
110
|
+
try:
|
111
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
112
|
+
s.bind((hostname, self.port))
|
113
|
+
except OSError:
|
114
|
+
logger.error(f"Port {self.port} is already in use on {hostname}. Cannot start minimal OAuth server.")
|
115
|
+
return False
|
116
|
+
|
117
|
+
def run_server():
|
118
|
+
"""Run the server in a separate thread."""
|
119
|
+
try:
|
120
|
+
config = uvicorn.Config(
|
121
|
+
self.app,
|
122
|
+
host=hostname,
|
123
|
+
port=self.port,
|
124
|
+
log_level="warning",
|
125
|
+
access_log=False
|
126
|
+
)
|
127
|
+
self.server = uvicorn.Server(config)
|
128
|
+
asyncio.run(self.server.serve())
|
129
|
+
|
130
|
+
except Exception as e:
|
131
|
+
logger.error(f"Minimal OAuth server error: {e}", exc_info=True)
|
132
|
+
|
133
|
+
# Start server in background thread
|
134
|
+
self.server_thread = threading.Thread(target=run_server, daemon=True)
|
135
|
+
self.server_thread.start()
|
136
|
+
|
137
|
+
# Wait for server to start
|
138
|
+
max_wait = 3.0
|
139
|
+
start_time = time.time()
|
140
|
+
while time.time() - start_time < max_wait:
|
141
|
+
try:
|
142
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
143
|
+
result = s.connect_ex((hostname, self.port))
|
144
|
+
if result == 0:
|
145
|
+
self.is_running = True
|
146
|
+
logger.info(f"Minimal OAuth server started on {hostname}:{self.port}")
|
147
|
+
return True
|
148
|
+
except Exception:
|
149
|
+
pass
|
150
|
+
time.sleep(0.1)
|
151
|
+
|
152
|
+
logger.error(f"Failed to start minimal OAuth server on {hostname}:{self.port}")
|
153
|
+
return False
|
154
|
+
|
155
|
+
def stop(self):
|
156
|
+
"""Stop the minimal OAuth server."""
|
157
|
+
if not self.is_running:
|
158
|
+
return
|
159
|
+
|
160
|
+
try:
|
161
|
+
if self.server:
|
162
|
+
if hasattr(self.server, 'should_exit'):
|
163
|
+
self.server.should_exit = True
|
164
|
+
|
165
|
+
if self.server_thread and self.server_thread.is_alive():
|
166
|
+
self.server_thread.join(timeout=3.0)
|
167
|
+
|
168
|
+
self.is_running = False
|
169
|
+
logger.info(f"Minimal OAuth server stopped")
|
170
|
+
|
171
|
+
except Exception as e:
|
172
|
+
logger.error(f"Error stopping minimal OAuth server: {e}", exc_info=True)
|
173
|
+
|
174
|
+
|
175
|
+
# Global instance for stdio mode
|
176
|
+
_minimal_oauth_server: Optional[MinimalOAuthServer] = None
|
177
|
+
|
178
|
+
def get_oauth_redirect_uri(transport_mode: str = "stdio", port: int = 8000, base_uri: str = "http://localhost") -> str:
|
179
|
+
"""
|
180
|
+
Get the appropriate OAuth redirect URI based on transport mode.
|
181
|
+
|
182
|
+
Args:
|
183
|
+
transport_mode: "stdio" or "streamable-http"
|
184
|
+
port: Port number (default 8000)
|
185
|
+
base_uri: Base URI (default "http://localhost")
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
OAuth redirect URI
|
189
|
+
"""
|
190
|
+
return f"{base_uri}:{port}/oauth2callback"
|
191
|
+
|
192
|
+
def ensure_oauth_callback_available(transport_mode: str = "stdio", port: int = 8000, base_uri: str = "http://localhost") -> bool:
|
193
|
+
"""
|
194
|
+
Ensure OAuth callback endpoint is available for the given transport mode.
|
195
|
+
|
196
|
+
For streamable-http: Assumes the main server is already running
|
197
|
+
For stdio: Starts a minimal server if needed
|
198
|
+
|
199
|
+
Args:
|
200
|
+
transport_mode: "stdio" or "streamable-http"
|
201
|
+
port: Port number (default 8000)
|
202
|
+
base_uri: Base URI (default "http://localhost")
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
True if callback endpoint is available, False otherwise
|
206
|
+
"""
|
207
|
+
global _minimal_oauth_server
|
208
|
+
|
209
|
+
if transport_mode == "streamable-http":
|
210
|
+
# In streamable-http mode, the main FastAPI server should handle callbacks
|
211
|
+
logger.debug("Using existing FastAPI server for OAuth callbacks (streamable-http mode)")
|
212
|
+
return True
|
213
|
+
|
214
|
+
elif transport_mode == "stdio":
|
215
|
+
# In stdio mode, start minimal server if not already running
|
216
|
+
if _minimal_oauth_server is None:
|
217
|
+
logger.info(f"Creating minimal OAuth server instance for {base_uri}:{port}")
|
218
|
+
_minimal_oauth_server = MinimalOAuthServer(port, base_uri)
|
219
|
+
|
220
|
+
if not _minimal_oauth_server.is_running:
|
221
|
+
logger.info("Starting minimal OAuth server for stdio mode")
|
222
|
+
result = _minimal_oauth_server.start()
|
223
|
+
if result:
|
224
|
+
logger.info(f"Minimal OAuth server successfully started on {base_uri}:{port}")
|
225
|
+
else:
|
226
|
+
logger.error(f"Failed to start minimal OAuth server on {base_uri}:{port}")
|
227
|
+
return result
|
228
|
+
else:
|
229
|
+
logger.info("Minimal OAuth server is already running")
|
230
|
+
return True
|
231
|
+
|
232
|
+
else:
|
233
|
+
logger.error(f"Unknown transport mode: {transport_mode}")
|
234
|
+
return False
|
235
|
+
|
236
|
+
def cleanup_oauth_callback_server():
|
237
|
+
"""Clean up the minimal OAuth server if it was started."""
|
238
|
+
global _minimal_oauth_server
|
239
|
+
if _minimal_oauth_server:
|
240
|
+
_minimal_oauth_server.stop()
|
241
|
+
_minimal_oauth_server = None
|
auth/oauth_responses.py
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
"""
|
2
|
+
Shared OAuth callback response templates.
|
3
|
+
|
4
|
+
Provides reusable HTML response templates for OAuth authentication flows
|
5
|
+
to eliminate duplication between server.py and oauth_callback_server.py.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from fastapi.responses import HTMLResponse
|
9
|
+
from typing import Optional
|
10
|
+
|
11
|
+
|
12
|
+
def create_error_response(error_message: str, status_code: int = 400) -> HTMLResponse:
|
13
|
+
"""
|
14
|
+
Create a standardized error response for OAuth failures.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
error_message: The error message to display
|
18
|
+
status_code: HTTP status code (default 400)
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
HTMLResponse with error page
|
22
|
+
"""
|
23
|
+
content = f"""
|
24
|
+
<html>
|
25
|
+
<head><title>Authentication Error</title></head>
|
26
|
+
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; text-align: center;">
|
27
|
+
<h2 style="color: #d32f2f;">Authentication Error</h2>
|
28
|
+
<p>{error_message}</p>
|
29
|
+
<p>Please ensure you grant the requested permissions. You can close this window and try again.</p>
|
30
|
+
<script>setTimeout(function() {{ window.close(); }}, 10000);</script>
|
31
|
+
</body>
|
32
|
+
</html>
|
33
|
+
"""
|
34
|
+
return HTMLResponse(content=content, status_code=status_code)
|
35
|
+
|
36
|
+
|
37
|
+
def create_success_response(verified_user_id: Optional[str] = None) -> HTMLResponse:
|
38
|
+
"""
|
39
|
+
Create a standardized success response for OAuth authentication.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
verified_user_id: The authenticated user's email (optional)
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
HTMLResponse with success page
|
46
|
+
"""
|
47
|
+
# Handle the case where no user ID is provided
|
48
|
+
user_display = verified_user_id if verified_user_id else "Google User"
|
49
|
+
|
50
|
+
content = f"""<html>
|
51
|
+
<head>
|
52
|
+
<title>Authentication Successful</title>
|
53
|
+
<style>
|
54
|
+
* {{
|
55
|
+
margin: 0;
|
56
|
+
padding: 0;
|
57
|
+
box-sizing: border-box;
|
58
|
+
}}
|
59
|
+
|
60
|
+
body {{
|
61
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
62
|
+
background: linear-gradient(135deg,#0f172a,#1e293b,#334155);
|
63
|
+
min-height: 100vh;
|
64
|
+
display: flex;
|
65
|
+
align-items: center;
|
66
|
+
justify-content: center;
|
67
|
+
color: #1a1a1a;
|
68
|
+
-webkit-font-smoothing: antialiased;
|
69
|
+
-moz-osx-font-smoothing: grayscale;
|
70
|
+
}}
|
71
|
+
|
72
|
+
.container {{
|
73
|
+
background: rgba(255, 255, 255, 0.95);
|
74
|
+
backdrop-filter: blur(10px);
|
75
|
+
padding: 60px;
|
76
|
+
border-radius: 20px;
|
77
|
+
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.12);
|
78
|
+
text-align: center;
|
79
|
+
max-width: 480px;
|
80
|
+
width: 90%;
|
81
|
+
transform: translateY(-20px);
|
82
|
+
animation: slideUp 0.6s ease-out;
|
83
|
+
}}
|
84
|
+
|
85
|
+
@keyframes slideUp {{
|
86
|
+
from {{
|
87
|
+
opacity: 0;
|
88
|
+
transform: translateY(0);
|
89
|
+
}}
|
90
|
+
to {{
|
91
|
+
opacity: 1;
|
92
|
+
transform: translateY(-20px);
|
93
|
+
}}
|
94
|
+
}}
|
95
|
+
|
96
|
+
.icon {{
|
97
|
+
width: 80px;
|
98
|
+
height: 80px;
|
99
|
+
margin: 0 auto 30px;
|
100
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
101
|
+
border-radius: 50%;
|
102
|
+
display: flex;
|
103
|
+
align-items: center;
|
104
|
+
justify-content: center;
|
105
|
+
font-size: 40px;
|
106
|
+
color: white;
|
107
|
+
animation: pulse 2s ease-in-out infinite;
|
108
|
+
}}
|
109
|
+
|
110
|
+
@keyframes pulse {{
|
111
|
+
0%, 100% {{
|
112
|
+
transform: scale(1);
|
113
|
+
}}
|
114
|
+
50% {{
|
115
|
+
transform: scale(1.05);
|
116
|
+
}}
|
117
|
+
}}
|
118
|
+
|
119
|
+
h1 {{
|
120
|
+
font-size: 28px;
|
121
|
+
font-weight: 600;
|
122
|
+
margin-bottom: 20px;
|
123
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
124
|
+
-webkit-background-clip: text;
|
125
|
+
-webkit-text-fill-color: transparent;
|
126
|
+
background-clip: text;
|
127
|
+
}}
|
128
|
+
|
129
|
+
.message {{
|
130
|
+
font-size: 16px;
|
131
|
+
line-height: 1.6;
|
132
|
+
color: #4a5568;
|
133
|
+
margin-bottom: 20px;
|
134
|
+
}}
|
135
|
+
|
136
|
+
.user-id {{
|
137
|
+
font-weight: 600;
|
138
|
+
color: #667eea;
|
139
|
+
padding: 4px 12px;
|
140
|
+
background: rgba(102, 126, 234, 0.1);
|
141
|
+
border-radius: 6px;
|
142
|
+
display: inline-block;
|
143
|
+
margin: 0 4px;
|
144
|
+
}}
|
145
|
+
|
146
|
+
.button {{
|
147
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
148
|
+
color: white;
|
149
|
+
padding: 16px 40px;
|
150
|
+
border: none;
|
151
|
+
border-radius: 30px;
|
152
|
+
font-size: 16px;
|
153
|
+
font-weight: 500;
|
154
|
+
cursor: pointer;
|
155
|
+
transition: all 0.3s ease;
|
156
|
+
margin-top: 30px;
|
157
|
+
display: inline-block;
|
158
|
+
text-decoration: none;
|
159
|
+
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
160
|
+
}}
|
161
|
+
|
162
|
+
.button:hover {{
|
163
|
+
transform: translateY(-2px);
|
164
|
+
box-shadow: 0 7px 20px rgba(102, 126, 234, 0.4);
|
165
|
+
}}
|
166
|
+
|
167
|
+
.button:active {{
|
168
|
+
transform: translateY(0);
|
169
|
+
}}
|
170
|
+
|
171
|
+
.auto-close {{
|
172
|
+
font-size: 13px;
|
173
|
+
color: #a0aec0;
|
174
|
+
margin-top: 30px;
|
175
|
+
opacity: 0.8;
|
176
|
+
}}
|
177
|
+
</style>
|
178
|
+
<script>
|
179
|
+
setTimeout(function() {{
|
180
|
+
window.close();
|
181
|
+
}}, 10000);
|
182
|
+
</script>
|
183
|
+
</head>
|
184
|
+
<body>
|
185
|
+
<div class="container">
|
186
|
+
<div class="icon">✓</div>
|
187
|
+
<h1>Authentication Successful</h1>
|
188
|
+
<div class="message">
|
189
|
+
You've been authenticated as <span class="user-id">{user_display}</span>
|
190
|
+
</div>
|
191
|
+
<div class="message">
|
192
|
+
Your credentials have been securely saved. You can now close this window and retry your original command.
|
193
|
+
</div>
|
194
|
+
<button class="button" onclick="window.close()">Close Window</button>
|
195
|
+
<div class="auto-close">This window will close automatically in 10 seconds</div>
|
196
|
+
</div>
|
197
|
+
</body>
|
198
|
+
</html>"""
|
199
|
+
return HTMLResponse(content=content)
|
200
|
+
|
201
|
+
|
202
|
+
def create_server_error_response(error_detail: str) -> HTMLResponse:
|
203
|
+
"""
|
204
|
+
Create a standardized server error response for OAuth processing failures.
|
205
|
+
|
206
|
+
Args:
|
207
|
+
error_detail: The detailed error message
|
208
|
+
|
209
|
+
Returns:
|
210
|
+
HTMLResponse with server error page
|
211
|
+
"""
|
212
|
+
content = f"""
|
213
|
+
<html>
|
214
|
+
<head><title>Authentication Processing Error</title></head>
|
215
|
+
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; text-align: center;">
|
216
|
+
<h2 style="color: #d32f2f;">Authentication Processing Error</h2>
|
217
|
+
<p>An unexpected error occurred while processing your authentication: {error_detail}</p>
|
218
|
+
<p>Please try again. You can close this window.</p>
|
219
|
+
<script>setTimeout(function() {{ window.close(); }}, 10000);</script>
|
220
|
+
</body>
|
221
|
+
</html>
|
222
|
+
"""
|
223
|
+
return HTMLResponse(content=content, status_code=500)
|
auth/scopes.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
"""
|
2
|
+
Google Workspace OAuth Scopes
|
3
|
+
|
4
|
+
This module centralizes OAuth scope definitions for Google Workspace integration.
|
5
|
+
Separated from service_decorator.py to avoid circular imports.
|
6
|
+
"""
|
7
|
+
import logging
|
8
|
+
from typing import Dict
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
# Temporary map to associate OAuth state with MCP session ID
|
13
|
+
# This should ideally be a more robust cache in a production system (e.g., Redis)
|
14
|
+
OAUTH_STATE_TO_SESSION_ID_MAP: Dict[str, str] = {}
|
15
|
+
|
16
|
+
# Individual OAuth Scope Constants
|
17
|
+
USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
|
18
|
+
OPENID_SCOPE = 'openid'
|
19
|
+
CALENDAR_READONLY_SCOPE = 'https://www.googleapis.com/auth/calendar.readonly'
|
20
|
+
CALENDAR_EVENTS_SCOPE = 'https://www.googleapis.com/auth/calendar.events'
|
21
|
+
|
22
|
+
# Google Drive scopes
|
23
|
+
DRIVE_READONLY_SCOPE = 'https://www.googleapis.com/auth/drive.readonly'
|
24
|
+
DRIVE_FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file'
|
25
|
+
|
26
|
+
# Google Docs scopes
|
27
|
+
DOCS_READONLY_SCOPE = 'https://www.googleapis.com/auth/documents.readonly'
|
28
|
+
DOCS_WRITE_SCOPE = 'https://www.googleapis.com/auth/documents'
|
29
|
+
|
30
|
+
# Gmail API scopes
|
31
|
+
GMAIL_READONLY_SCOPE = 'https://www.googleapis.com/auth/gmail.readonly'
|
32
|
+
GMAIL_SEND_SCOPE = 'https://www.googleapis.com/auth/gmail.send'
|
33
|
+
GMAIL_COMPOSE_SCOPE = 'https://www.googleapis.com/auth/gmail.compose'
|
34
|
+
GMAIL_MODIFY_SCOPE = 'https://www.googleapis.com/auth/gmail.modify'
|
35
|
+
GMAIL_LABELS_SCOPE = 'https://www.googleapis.com/auth/gmail.labels'
|
36
|
+
|
37
|
+
# Google Chat API scopes
|
38
|
+
CHAT_READONLY_SCOPE = 'https://www.googleapis.com/auth/chat.messages.readonly'
|
39
|
+
CHAT_WRITE_SCOPE = 'https://www.googleapis.com/auth/chat.messages'
|
40
|
+
CHAT_SPACES_SCOPE = 'https://www.googleapis.com/auth/chat.spaces'
|
41
|
+
|
42
|
+
# Google Sheets API scopes
|
43
|
+
SHEETS_READONLY_SCOPE = 'https://www.googleapis.com/auth/spreadsheets.readonly'
|
44
|
+
SHEETS_WRITE_SCOPE = 'https://www.googleapis.com/auth/spreadsheets'
|
45
|
+
|
46
|
+
# Google Forms API scopes
|
47
|
+
FORMS_BODY_SCOPE = 'https://www.googleapis.com/auth/forms.body'
|
48
|
+
FORMS_BODY_READONLY_SCOPE = 'https://www.googleapis.com/auth/forms.body.readonly'
|
49
|
+
FORMS_RESPONSES_READONLY_SCOPE = 'https://www.googleapis.com/auth/forms.responses.readonly'
|
50
|
+
|
51
|
+
# Google Slides API scopes
|
52
|
+
SLIDES_SCOPE = 'https://www.googleapis.com/auth/presentations'
|
53
|
+
SLIDES_READONLY_SCOPE = 'https://www.googleapis.com/auth/presentations.readonly'
|
54
|
+
|
55
|
+
# Base OAuth scopes required for user identification
|
56
|
+
BASE_SCOPES = [
|
57
|
+
USERINFO_EMAIL_SCOPE,
|
58
|
+
OPENID_SCOPE
|
59
|
+
]
|
60
|
+
|
61
|
+
# Service-specific scope groups
|
62
|
+
DOCS_SCOPES = [
|
63
|
+
DOCS_READONLY_SCOPE,
|
64
|
+
DOCS_WRITE_SCOPE
|
65
|
+
]
|
66
|
+
|
67
|
+
CALENDAR_SCOPES = [
|
68
|
+
CALENDAR_READONLY_SCOPE,
|
69
|
+
CALENDAR_EVENTS_SCOPE
|
70
|
+
]
|
71
|
+
|
72
|
+
DRIVE_SCOPES = [
|
73
|
+
DRIVE_READONLY_SCOPE,
|
74
|
+
DRIVE_FILE_SCOPE
|
75
|
+
]
|
76
|
+
|
77
|
+
GMAIL_SCOPES = [
|
78
|
+
GMAIL_READONLY_SCOPE,
|
79
|
+
GMAIL_SEND_SCOPE,
|
80
|
+
GMAIL_COMPOSE_SCOPE,
|
81
|
+
GMAIL_MODIFY_SCOPE,
|
82
|
+
GMAIL_LABELS_SCOPE
|
83
|
+
]
|
84
|
+
|
85
|
+
CHAT_SCOPES = [
|
86
|
+
CHAT_READONLY_SCOPE,
|
87
|
+
CHAT_WRITE_SCOPE,
|
88
|
+
CHAT_SPACES_SCOPE
|
89
|
+
]
|
90
|
+
|
91
|
+
SHEETS_SCOPES = [
|
92
|
+
SHEETS_READONLY_SCOPE,
|
93
|
+
SHEETS_WRITE_SCOPE
|
94
|
+
]
|
95
|
+
|
96
|
+
FORMS_SCOPES = [
|
97
|
+
FORMS_BODY_SCOPE,
|
98
|
+
FORMS_BODY_READONLY_SCOPE,
|
99
|
+
FORMS_RESPONSES_READONLY_SCOPE
|
100
|
+
]
|
101
|
+
|
102
|
+
SLIDES_SCOPES = [
|
103
|
+
SLIDES_SCOPE,
|
104
|
+
SLIDES_READONLY_SCOPE
|
105
|
+
]
|
106
|
+
|
107
|
+
# Combined scopes for all supported Google Workspace operations
|
108
|
+
SCOPES = list(set(BASE_SCOPES + CALENDAR_SCOPES + DRIVE_SCOPES + GMAIL_SCOPES + DOCS_SCOPES + CHAT_SCOPES + SHEETS_SCOPES + FORMS_SCOPES + SLIDES_SCOPES))
|