workspace-mcp 1.1.4__tar.gz → 1.1.6__tar.gz

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.
Files changed (44) hide show
  1. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/PKG-INFO +62 -13
  2. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/README.md +61 -12
  3. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/auth/google_auth.py +6 -8
  4. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/auth/oauth_callback_server.py +22 -8
  5. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/core/server.py +1 -1
  6. workspace_mcp-1.1.6/core/utils.py +296 -0
  7. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gTasks/tasks_tools.py +13 -0
  8. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gcalendar/calendar_tools.py +7 -7
  9. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gdocs/docs_tools.py +4 -5
  10. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gdrive/drive_tools.py +5 -5
  11. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gforms/forms_tools.py +22 -23
  12. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gmail/gmail_tools.py +199 -82
  13. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gsheets/sheets_tools.py +7 -7
  14. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gslides/slides_tools.py +25 -25
  15. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/pyproject.toml +1 -1
  16. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/tests/test_oauth_callback_server.py +0 -2
  17. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/workspace_mcp.egg-info/PKG-INFO +62 -13
  18. workspace_mcp-1.1.4/core/utils.py +0 -197
  19. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/LICENSE +0 -0
  20. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/auth/__init__.py +0 -0
  21. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/auth/oauth_responses.py +0 -0
  22. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/auth/scopes.py +0 -0
  23. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/auth/service_decorator.py +0 -0
  24. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/core/__init__.py +0 -0
  25. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/core/comments.py +0 -0
  26. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/core/context.py +0 -0
  27. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gTasks/__init__.py +0 -0
  28. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gcalendar/__init__.py +0 -0
  29. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gchat/__init__.py +0 -0
  30. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gchat/chat_tools.py +0 -0
  31. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gdocs/__init__.py +0 -0
  32. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gdrive/__init__.py +0 -0
  33. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gforms/__init__.py +0 -0
  34. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gmail/__init__.py +0 -0
  35. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gsheets/__init__.py +0 -0
  36. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/gslides/__init__.py +0 -0
  37. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/main.py +0 -0
  38. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/setup.cfg +0 -0
  39. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/tests/test_auth.py +0 -0
  40. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/workspace_mcp.egg-info/SOURCES.txt +0 -0
  41. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/workspace_mcp.egg-info/dependency_links.txt +0 -0
  42. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/workspace_mcp.egg-info/entry_points.txt +0 -0
  43. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/workspace_mcp.egg-info/requires.txt +0 -0
  44. {workspace_mcp-1.1.4 → workspace_mcp-1.1.6}/workspace_mcp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workspace-mcp
3
- Version: 1.1.4
3
+ Version: 1.1.6
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
@@ -47,11 +47,11 @@ Dynamic: license-file
47
47
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
48
48
  [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
49
49
  [![PyPI](https://img.shields.io/pypi/v/workspace-mcp.svg)](https://pypi.org/project/workspace-mcp/)
50
- [![UV](https://img.shields.io/badge/Package%20Installer-UV-blueviolet)](https://github.com/astral-sh/uv)
50
+ [![PyPI Downloads](https://static.pepy.tech/badge/workspace-mcp/month)](https://pepy.tech/projects/workspace-mcp)
51
51
  [![Website](https://img.shields.io/badge/Website-workspacemcp.com-green.svg)](https://workspacemcp.com)
52
52
  [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/eebbc4a6-0f8c-41b2-ace8-038e5516dba0)
53
53
 
54
- **This is the single most feature-complete Google Workspace MCP server**
54
+ **This is the single most feature-complete Google Workspace MCP server** now with 1-click Claude installation
55
55
 
56
56
  *Full natural language control over Google Calendar, Drive, Gmail, Docs, Sheets, Slides, Forms, Tasks, and Chat through all MCP clients, AI assistants and developer tools.*
57
57
 
@@ -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,48 @@ 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 **one click** – 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
+
131
+ | Variable | Purpose |
132
+ |----------|---------|
133
+ | `GOOGLE_OAUTH_CLIENT_ID` | OAuth client ID from Google Cloud |
134
+ | `GOOGLE_OAUTH_CLIENT_SECRET` | OAuth client secret |
135
+ | `USER_GOOGLE_EMAIL` *(optional)* | Default email for single-user auth |
136
+ | `OAUTHLIB_INSECURE_TRANSPORT=1` | Development only (allows `http://` redirect) |
137
+
138
+ Claude Desktop stores these securely in the OS keychain; set them once in the extension pane.
139
+ </details>
140
+
141
+ <div align="center">
142
+ <video width="832" src="https://github.com/user-attachments/assets/83cca4b3-5e94-448b-acb3-6e3a27341d3a"></video>
143
+ </div>
144
+ ---
145
+
146
+ ### 2. Advanced / Cross-Platform Installation
147
+
148
+ If you’re developing, deploying to servers, or using another MCP-capable client, keep reading.
149
+
150
+ #### Instant CLI (uvx)
151
+
152
+ ```bash
153
+ # Requires Python 3.11+ and uvx
154
+ export GOOGLE_OAUTH_CLIENT_ID="xxx"
155
+ export GOOGLE_OAUTH_CLIENT_SECRET="yyy"
156
+ uvx workspace-mcp --tools gmail drive calendar
157
+ ```
115
158
 
116
159
  > 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
160
 
@@ -187,9 +230,10 @@ uv run main.py
187
230
 
188
231
  3. **Server Configuration**:
189
232
  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.
233
+ - `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
234
  - `WORKSPACE_MCP_PORT`: Sets the port the server listens on (default: 8000). This affects the server_url, port, and OAUTH_REDIRECT_URI.
192
235
  - `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`.
236
+ - `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
237
 
194
238
  ### Start the Server
195
239
 
@@ -221,7 +265,8 @@ The server supports two transport modes:
221
265
 
222
266
  #### Stdio Mode (Default - Recommended for Claude Desktop)
223
267
 
224
- **Easiest Setup (Recommended)**
268
+ **Guided Setup (Recommended if not using DXT)**
269
+
225
270
  ```bash
226
271
  python install_claude.py
227
272
  ```
@@ -266,8 +311,12 @@ After running the script, just restart Claude Desktop and you're ready to go.
266
311
  "mcpServers": {
267
312
  "google_workspace": {
268
313
  "command": "uv",
269
- "args": ["run", "main.py"],
270
- "cwd": "/path/to/google_workspace_mcp",
314
+ "args": [
315
+ "run",
316
+ "--directory",
317
+ "/path/to/repo/google_workspace_mcp",
318
+ "main.py"
319
+ ],
271
320
  "env": {
272
321
  "GOOGLE_OAUTH_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
273
322
  "GOOGLE_OAUTH_CLIENT_SECRET": "your-client-secret",
@@ -5,11 +5,11 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
7
7
  [![PyPI](https://img.shields.io/pypi/v/workspace-mcp.svg)](https://pypi.org/project/workspace-mcp/)
8
- [![UV](https://img.shields.io/badge/Package%20Installer-UV-blueviolet)](https://github.com/astral-sh/uv)
8
+ [![PyPI Downloads](https://static.pepy.tech/badge/workspace-mcp/month)](https://pepy.tech/projects/workspace-mcp)
9
9
  [![Website](https://img.shields.io/badge/Website-workspacemcp.com-green.svg)](https://workspacemcp.com)
10
10
  [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/eebbc4a6-0f8c-41b2-ace8-038e5516dba0)
11
11
 
12
- **This is the single most feature-complete Google Workspace MCP server**
12
+ **This is the single most feature-complete Google Workspace MCP server** now with 1-click Claude installation
13
13
 
14
14
  *Full natural language control over Google Calendar, Drive, Gmail, Docs, Sheets, Slides, Forms, Tasks, and Chat through all MCP clients, AI assistants and developer tools.*
15
15
 
@@ -37,13 +37,15 @@
37
37
  ---
38
38
 
39
39
  ### A quick plug for AI-Enhanced Docs
40
+ <details>
41
+ <summary>But why?</summary>
40
42
 
41
- > **This README was crafted with AI assistance, and here's why that matters**
43
+ **This README was written with AI assistance, and here's why that matters**
42
44
  >
43
- > 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.
45
+ > 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.
44
46
  >
45
- > In this case, Sonnet 4 took a pass & a human (me) verified them 6/28/25.
46
-
47
+ > In this case, Sonnet 4 took a pass & a human (me) verified them 7/10/25.
48
+ </details>
47
49
 
48
50
  ## Overview
49
51
 
@@ -61,7 +63,7 @@ A production-ready MCP server that integrates all major Google Workspace service
61
63
  - **📝 Google Forms**: Form creation, retrieval, publish settings, and response management
62
64
  - **✓ Google Tasks**: Complete task and task list management with hierarchy, due dates, and status tracking
63
65
  - **💬 Google Chat**: Space management and messaging capabilities
64
- - **🔄 Multiple Transports**: HTTP with SSE fallback, OpenAPI compatibility via `mcpo`
66
+ - **🔄 All Transports**: Stdio, Streamable HTTP & SSE, OpenAPI compatibility via `mcpo`
65
67
  - **⚡ High Performance**: Service caching, thread-safe sessions, FastMCP integration
66
68
  - **🧩 Developer Friendly**: Minimal boilerplate, automatic service injection, centralized configuration
67
69
 
@@ -69,7 +71,48 @@ A production-ready MCP server that integrates all major Google Workspace service
69
71
 
70
72
  ## 🚀 Quick Start
71
73
 
72
- ### Simplest Start (uvx - Recommended)
74
+ ### 1. One-Click Claude Desktop Install (Recommended)
75
+
76
+ 1. **Download:** Grab the latest `google_workspace_mcp.dxt` from the “Releases” page
77
+ 2. **Install:** Double-click the file – Claude Desktop opens and prompts you to **Install**
78
+ 3. **Configure:** In Claude Desktop → **Settings → Extensions → Google Workspace MCP**, paste your Google OAuth credentials
79
+ 4. **Use it:** Start a new Claude chat and call any Google Workspace tool 🎉
80
+
81
+ >
82
+ **Why DXT?**
83
+ > Desktop Extensions (`.dxt`) bundle the server, dependencies, and manifest so users go from download → working MCP in **one click** – no terminal, no JSON editing, no version conflicts.
84
+
85
+ #### Required Configuration
86
+ <details>
87
+ <summary>Environment - you will configure these in Claude itself, see screenshot:</summary>
88
+
89
+ | Variable | Purpose |
90
+ |----------|---------|
91
+ | `GOOGLE_OAUTH_CLIENT_ID` | OAuth client ID from Google Cloud |
92
+ | `GOOGLE_OAUTH_CLIENT_SECRET` | OAuth client secret |
93
+ | `USER_GOOGLE_EMAIL` *(optional)* | Default email for single-user auth |
94
+ | `OAUTHLIB_INSECURE_TRANSPORT=1` | Development only (allows `http://` redirect) |
95
+
96
+ Claude Desktop stores these securely in the OS keychain; set them once in the extension pane.
97
+ </details>
98
+
99
+ <div align="center">
100
+ <video width="832" src="https://github.com/user-attachments/assets/83cca4b3-5e94-448b-acb3-6e3a27341d3a"></video>
101
+ </div>
102
+ ---
103
+
104
+ ### 2. Advanced / Cross-Platform Installation
105
+
106
+ If you’re developing, deploying to servers, or using another MCP-capable client, keep reading.
107
+
108
+ #### Instant CLI (uvx)
109
+
110
+ ```bash
111
+ # Requires Python 3.11+ and uvx
112
+ export GOOGLE_OAUTH_CLIENT_ID="xxx"
113
+ export GOOGLE_OAUTH_CLIENT_SECRET="yyy"
114
+ uvx workspace-mcp --tools gmail drive calendar
115
+ ```
73
116
 
74
117
  > 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.
75
118
 
@@ -145,9 +188,10 @@ uv run main.py
145
188
 
146
189
  3. **Server Configuration**:
147
190
  The server's base URL and port can be customized using environment variables:
148
- - `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.
191
+ - `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.
149
192
  - `WORKSPACE_MCP_PORT`: Sets the port the server listens on (default: 8000). This affects the server_url, port, and OAUTH_REDIRECT_URI.
150
193
  - `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`.
194
+ - `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
151
195
 
152
196
  ### Start the Server
153
197
 
@@ -179,7 +223,8 @@ The server supports two transport modes:
179
223
 
180
224
  #### Stdio Mode (Default - Recommended for Claude Desktop)
181
225
 
182
- **Easiest Setup (Recommended)**
226
+ **Guided Setup (Recommended if not using DXT)**
227
+
183
228
  ```bash
184
229
  python install_claude.py
185
230
  ```
@@ -224,8 +269,12 @@ After running the script, just restart Claude Desktop and you're ready to go.
224
269
  "mcpServers": {
225
270
  "google_workspace": {
226
271
  "command": "uv",
227
- "args": ["run", "main.py"],
228
- "cwd": "/path/to/google_workspace_mcp",
272
+ "args": [
273
+ "run",
274
+ "--directory",
275
+ "/path/to/repo/google_workspace_mcp",
276
+ "main.py"
277
+ ],
229
278
  "env": {
230
279
  "GOOGLE_OAUTH_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
231
280
  "GOOGLE_OAUTH_CLIENT_SECRET": "your-client-secret",
@@ -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
  """
@@ -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"])