readme changes

#11
by devanggg - opened
README.md CHANGED
@@ -11,9 +11,8 @@ pinned: false
11
  short_description: Answer any questions you have about the content of your mail
12
  ---
13
 
 
14
 
15
- Our Google authentication verification is currently pending. If you would like to proceed with testing, we can add you as a test user. To facilitate this, please complete the Google Form provided below.
16
 
17
- https://forms.gle/FRWwhhMeKaiXDAJQ9
18
 
19
- Link to the vide demo: https://www.youtube.com/watch?v=Ie8kdGU6bjY
 
11
  short_description: Answer any questions you have about the content of your mail
12
  ---
13
 
14
+ An example chatbot using [Gradio](https://gradio.app), [`huggingface_hub`](https://huggingface.co/docs/huggingface_hub/v0.22.2/en/index), and the [Hugging Face Inference API](https://huggingface.co/docs/api-inference/index).
15
 
 
16
 
 
17
 
18
+ uvicorn main:app --reload
agentic_implementation/email_mcp_server_oauth.py CHANGED
@@ -21,12 +21,6 @@ from logger import logger
21
 
22
  load_dotenv()
23
 
24
- if not oauth_manager.client_secrets_file.exists():
25
- oauth_manager.setup_client_secrets(
26
- os.environ["GOOGLE_CLIENT_ID"],
27
- os.environ["GOOGLE_CLIENT_SECRET"]
28
- )
29
-
30
  # Initialize Gmail API scraper
31
  gmail_scraper = GmailAPIScraper()
32
 
@@ -120,17 +114,6 @@ def authenticate_user() -> str:
120
  try:
121
  logger.info("Starting OAuth authentication flow...")
122
 
123
- if oauth_manager.is_authenticated():
124
- user_email = oauth_manager.get_current_account()
125
- return json.dumps({
126
- "success": True,
127
- "message": "Already authenticated!",
128
- "user_email": user_email,
129
- "instructions": [
130
- "You are already authenticated and ready to use email tools",
131
- f"Currently authenticated as: {user_email}"
132
- ]
133
- }, indent=2)
134
  # Check if OAuth is configured
135
  if not oauth_manager.client_secrets_file.exists():
136
  return json.dumps({
@@ -155,44 +138,16 @@ def authenticate_user() -> str:
155
  ]
156
  }
157
  else:
158
- # Authentication not completed, provide manual instructions
159
- auth_url = oauth_manager.get_pending_auth_url()
160
- callback_url = oauth_manager.get_hf_redirect_uri()
161
-
162
- if auth_url:
163
- result = {
164
- "success": False,
165
- "message": "Manual authentication required",
166
- "auth_url": auth_url,
167
- "callback_url": callback_url,
168
- "instructions": [
169
- "Authentication URL has been generated",
170
- "Please click the link below to authenticate:",
171
- "1. Click the authentication URL",
172
- "2. Sign in with your Google account",
173
- "3. Grant Gmail access permissions",
174
- "4. You'll be redirected back automatically",
175
- "5. Try clicking 'Submit' again after completing authentication"
176
- ],
177
- "note": "After completing authentication in the popup, click Submit again to verify"
178
- }
179
- else:
180
- result = {
181
- "success": False,
182
- "error": "Failed to generate authentication URL",
183
- "message": "Could not start authentication process. Check your OAuth configuration."
184
- }
185
-
186
- # result = {
187
- # "success": False,
188
- # "error": "Authentication failed",
189
- # "message": "Please try again or check your internet connection.",
190
- # "instructions": [
191
- # "Make sure you have internet connection",
192
- # "Ensure you complete the authentication in the browser",
193
- # "Try running 'python setup_oauth.py' if problems persist"
194
- # ]
195
- # }
196
 
197
  return json.dumps(result, indent=2)
198
 
@@ -204,79 +159,6 @@ def authenticate_user() -> str:
204
  "message": "Authentication failed due to an error."
205
  }
206
  return json.dumps(error_result, indent=2)
207
-
208
-
209
- def handle_oauth_callback(auth_code: str) -> str:
210
- """Handle OAuth callback for Hugging Face Spaces
211
-
212
- Args:
213
- auth_code: Authorization code from OAuth callback
214
-
215
- Returns:
216
- HTML response string
217
- """
218
- try:
219
- if not auth_code:
220
- return """
221
- <html>
222
- <head><title>OAuth Error</title></head>
223
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
224
- <h1 style="color: #d32f2f;">Authentication Error</h1>
225
- <p>No authorization code received.</p>
226
- <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
227
- </body>
228
- </html>
229
- """
230
-
231
- success = oauth_manager.complete_hf_spaces_auth(auth_code)
232
-
233
- if success:
234
- user_email = oauth_manager.get_current_account()
235
- return f"""
236
- <html>
237
- <head><title>OAuth Success</title></head>
238
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
239
- <h1 style="color: #2e7d32;">πŸŽ‰ Authentication Successful!</h1>
240
- <p>You are now authenticated as:</p>
241
- <p style="font-weight: bold; font-size: 18px; color: #1976d2;">{user_email}</p>
242
- <p>You can now close this window and return to the main application.</p>
243
- <p style="color: #666; font-size: 14px;">This window will close automatically in 5 seconds...</p>
244
- <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #2e7d32; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
245
- <script>
246
- setTimeout(function() {{
247
- window.close();
248
- }}, 5000);
249
- </script>
250
- </body>
251
- </html>
252
- """
253
- else:
254
- return """
255
- <html>
256
- <head><title>OAuth Error</title></head>
257
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
258
- <h1 style="color: #d32f2f;">Authentication Failed</h1>
259
- <p>Unable to complete authentication. Please try again.</p>
260
- <p>Make sure you granted all required permissions.</p>
261
- <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #d32f2f; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
262
- </body>
263
- </html>
264
- """
265
-
266
- except Exception as e:
267
- logger.error(f"Error handling OAuth callback: {e}")
268
- return f"""
269
- <html>
270
- <head><title>OAuth Error</title></head>
271
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
272
- <h1 style="color: #d32f2f;">Authentication Error</h1>
273
- <p>An error occurred during authentication:</p>
274
- <pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; text-align: left; max-width: 500px; margin: 0 auto;">{str(e)}</pre>
275
- <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #d32f2f; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
276
- </body>
277
- </html>
278
- """
279
-
280
 
281
  def switch_account(target_email: str) -> str:
282
  """
 
21
 
22
  load_dotenv()
23
 
 
 
 
 
 
 
24
  # Initialize Gmail API scraper
25
  gmail_scraper = GmailAPIScraper()
26
 
 
114
  try:
115
  logger.info("Starting OAuth authentication flow...")
116
 
 
 
 
 
 
 
 
 
 
 
 
117
  # Check if OAuth is configured
118
  if not oauth_manager.client_secrets_file.exists():
119
  return json.dumps({
 
138
  ]
139
  }
140
  else:
141
+ result = {
142
+ "success": False,
143
+ "error": "Authentication failed",
144
+ "message": "Please try again or check your internet connection.",
145
+ "instructions": [
146
+ "Make sure you have internet connection",
147
+ "Ensure you complete the authentication in the browser",
148
+ "Try running 'python setup_oauth.py' if problems persist"
149
+ ]
150
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  return json.dumps(result, indent=2)
153
 
 
159
  "message": "Authentication failed due to an error."
160
  }
161
  return json.dumps(error_result, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  def switch_account(target_email: str) -> str:
164
  """
agentic_implementation/oauth_manager.py CHANGED
@@ -14,13 +14,8 @@ import webbrowser
14
  import threading
15
  import time
16
  from http.server import HTTPServer, BaseHTTPRequestHandler
17
- from urllib.parse import urlparse,parse_qs
18
  from logger import logger
19
- from dotenv import load_dotenv
20
- load_dotenv()
21
-
22
-
23
- redirect_uri=os.getenv("GOOGLE_REDIRECT_URI")
24
 
25
  class OAuthCallbackHandler(BaseHTTPRequestHandler):
26
  """HTTP request handler for OAuth callback"""
@@ -109,7 +104,7 @@ class GmailOAuthManager:
109
  self._init_encryption()
110
 
111
  # OAuth flow settings
112
- self.redirect_uri = redirect_uri
113
 
114
  # Current account
115
  self.current_account_email = self._load_current_account()
@@ -205,95 +200,41 @@ class GmailOAuthManager:
205
  return auth_url
206
 
207
  def authenticate_interactive(self) -> bool:
208
- """Interactive authentication flow for Hugging Face Spaces
209
 
210
  Returns:
211
  True if authentication successful, False otherwise
212
  """
213
  try:
214
- # Check if already authenticated
215
- if self.is_authenticated():
216
- logger.info("Already authenticated")
217
- return True
218
-
219
 
220
  # Get authorization URL
221
  auth_url = self.get_authorization_url()
222
 
223
- logger.info("Running on Hugging Face Spaces")
224
- logger.info(f"Authentication URL generated: {auth_url}")
225
- logger.info("User must visit the URL manually to complete authentication")
226
 
227
- # Store the auth URL for the Gradio interface to use
228
- self._pending_auth_url = auth_url
229
- self._auth_completed = False
230
 
231
- # For setup_oauth.py and testing contexts, we'll print the URL
232
- # and wait briefly to see if authentication completes
233
- print(f"\n🌐 Please visit this URL to authenticate:")
234
- print(f" {auth_url}")
235
- print("\n⏳ Waiting for authentication completion...")
236
 
237
- # Wait for a reasonable time to see if auth completes
238
- # This allows the callback to potentially complete the auth
239
- timeout = 60 # 1 minute for manual completion
240
  start_time = time.time()
241
 
242
- while (time.time() - start_time) < timeout:
243
- # Check if authentication was completed via callback
244
- if getattr(self, '_auth_completed', False):
245
- logger.info("Authentication completed successfully!")
246
- return True
247
-
248
- # Check if user is now authenticated (credentials were saved)
249
- if self.is_authenticated():
250
- self._auth_completed = True
251
- logger.info("Authentication verified successful!")
252
- return True
253
-
254
- time.sleep(2) # Check every 2 seconds
255
-
256
- # Timeout reached - authentication not completed
257
- logger.info("Authentication timeout. Please complete authentication via the provided URL.")
258
- return False
259
-
260
- except Exception as e:
261
- logger.error(f"Authentication failed: {e}")
262
- return False
263
-
264
- def complete_hf_spaces_auth(self, auth_code: str) -> bool:
265
- """Complete authentication for HF Spaces with received auth code
266
-
267
- Args:
268
- auth_code: Authorization code received from OAuth callback
269
 
270
- Returns:
271
- True if authentication successful, False otherwise
272
- """
273
- try:
274
- success = self._exchange_code_for_credentials(auth_code)
275
-
276
- if success:
277
- # Mark authentication as completed
278
- self._auth_completed = True
279
- logger.info("HF Spaces authentication marked as completed")
280
-
281
- return success
282
-
283
- except Exception as e:
284
- logger.error(f"Failed to complete HF Spaces authentication: {e}")
285
- return False
286
-
287
- def _exchange_code_for_credentials(self, auth_code: str) -> bool:
288
- """Exchange authorization code for credentials
289
-
290
- Args:
291
- auth_code: Authorization code from OAuth flow
292
 
293
- Returns:
294
- True if successful, False otherwise
295
- """
296
- try:
297
  # Exchange authorization code for credentials
298
  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
299
  str(self.client_secrets_file),
@@ -301,7 +242,7 @@ class GmailOAuthManager:
301
  )
302
  flow.redirect_uri = self.redirect_uri
303
 
304
- flow.fetch_token(code=auth_code)
305
  credentials = flow.credentials
306
 
307
  # Get user email from credentials
@@ -320,92 +261,8 @@ class GmailOAuthManager:
320
  return True
321
 
322
  except Exception as e:
323
- logger.error(f"Failed to exchange code for credentials: {e}")
324
  return False
325
-
326
- def get_pending_auth_url(self) -> str:
327
- """Get the pending authentication URL for manual completion
328
-
329
- Returns:
330
- Authentication URL string or None if not available
331
- """
332
- return getattr(self, '_pending_auth_url', None)
333
-
334
- def get_hf_redirect_uri(self) -> str:
335
- """Get the Hugging Face Spaces redirect URI
336
-
337
- Returns:
338
- Redirect URI string
339
- """
340
- space_id = os.getenv('SPACE_ID')
341
- space_author = os.getenv('SPACE_AUTHOR', 'username')
342
- return f"https://{space_author}-{space_id}.hf.space/oauth/callback"
343
-
344
-
345
- # def authenticate_interactive(self) -> bool:
346
- # """Interactive authentication flow that opens browser
347
-
348
- # Returns:
349
- # True if authentication successful, False otherwise
350
- # """
351
- # try:
352
- # # Start local HTTP server for OAuth callback
353
- # server = HTTPServer(('localhost', 8080), OAuthCallbackHandler)
354
- # server.auth_code = None
355
-
356
- # # Get authorization URL
357
- # auth_url = self.get_authorization_url()
358
-
359
- # logger.info("Opening browser for authentication...")
360
- # logger.info(f"If browser doesn't open, visit: {auth_url}")
361
-
362
- # # Open browser
363
- # webbrowser.open(auth_url)
364
-
365
- # # Start server in background thread
366
- # server_thread = threading.Thread(target=server.handle_request)
367
- # server_thread.daemon = True
368
- # server_thread.start()
369
-
370
- # # Wait for callback (max 5 minutes)
371
- # timeout = 300 # 5 minutes
372
- # start_time = time.time()
373
-
374
- # while server.auth_code is None and (time.time() - start_time) < timeout:
375
- # time.sleep(1)
376
-
377
- # if server.auth_code is None:
378
- # logger.error("Authentication timed out")
379
- # return False
380
-
381
- # # Exchange authorization code for credentials
382
- # flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
383
- # str(self.client_secrets_file),
384
- # scopes=self.SCOPES
385
- # )
386
- # flow.redirect_uri = self.redirect_uri
387
-
388
- # flow.fetch_token(code=server.auth_code)
389
- # credentials = flow.credentials
390
-
391
- # # Get user email from credentials
392
- # user_email = self._get_email_from_credentials(credentials)
393
- # if not user_email:
394
- # logger.error("Failed to get user email from credentials")
395
- # return False
396
-
397
- # # Save encrypted credentials for this account
398
- # self._save_credentials(user_email, credentials)
399
-
400
- # # Set as current account
401
- # self._save_current_account(user_email)
402
-
403
- # logger.info("Authentication successful!")
404
- # return True
405
-
406
- # except Exception as e:
407
- # logger.error(f"Authentication failed: {e}")
408
- # return False
409
 
410
  def _get_email_from_credentials(self, credentials: Credentials) -> Optional[str]:
411
  """Get email address from credentials"""
 
14
  import threading
15
  import time
16
  from http.server import HTTPServer, BaseHTTPRequestHandler
17
+ import urllib.parse as urlparse
18
  from logger import logger
 
 
 
 
 
19
 
20
  class OAuthCallbackHandler(BaseHTTPRequestHandler):
21
  """HTTP request handler for OAuth callback"""
 
104
  self._init_encryption()
105
 
106
  # OAuth flow settings
107
+ self.redirect_uri = "http://localhost:8080/oauth2callback"
108
 
109
  # Current account
110
  self.current_account_email = self._load_current_account()
 
200
  return auth_url
201
 
202
  def authenticate_interactive(self) -> bool:
203
+ """Interactive authentication flow that opens browser
204
 
205
  Returns:
206
  True if authentication successful, False otherwise
207
  """
208
  try:
209
+ # Start local HTTP server for OAuth callback
210
+ server = HTTPServer(('localhost', 8080), OAuthCallbackHandler)
211
+ server.auth_code = None
 
 
212
 
213
  # Get authorization URL
214
  auth_url = self.get_authorization_url()
215
 
216
+ logger.info("Opening browser for authentication...")
217
+ logger.info(f"If browser doesn't open, visit: {auth_url}")
 
218
 
219
+ # Open browser
220
+ webbrowser.open(auth_url)
 
221
 
222
+ # Start server in background thread
223
+ server_thread = threading.Thread(target=server.handle_request)
224
+ server_thread.daemon = True
225
+ server_thread.start()
 
226
 
227
+ # Wait for callback (max 5 minutes)
228
+ timeout = 300 # 5 minutes
 
229
  start_time = time.time()
230
 
231
+ while server.auth_code is None and (time.time() - start_time) < timeout:
232
+ time.sleep(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
+ if server.auth_code is None:
235
+ logger.error("Authentication timed out")
236
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
 
 
 
 
238
  # Exchange authorization code for credentials
239
  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
240
  str(self.client_secrets_file),
 
242
  )
243
  flow.redirect_uri = self.redirect_uri
244
 
245
+ flow.fetch_token(code=server.auth_code)
246
  credentials = flow.credentials
247
 
248
  # Get user email from credentials
 
261
  return True
262
 
263
  except Exception as e:
264
+ logger.error(f"Authentication failed: {e}")
265
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
  def _get_email_from_credentials(self, credentials: Credentials) -> Optional[str]:
268
  """Get email address from credentials"""
agentic_implementation/setup_oauth.py CHANGED
@@ -11,10 +11,6 @@ import json
11
  from pathlib import Path
12
  from oauth_manager import oauth_manager
13
  from logger import logger
14
- from dotenv import load_dotenv
15
- load_dotenv()
16
- import os
17
-
18
 
19
  def print_banner():
20
  """Print setup banner"""
@@ -82,16 +78,31 @@ def setup_oauth_credentials():
82
  """Guide user through OAuth credentials setup"""
83
  print_step(3, "OAuth Client Credentials Setup")
84
 
85
- client_id = os.getenv("GOOGLE_CLIENT_ID")
86
- client_secret = os.getenv("GOOGLE_CLIENT_SECRET")
87
-
88
- if not client_id or not client_secret:
89
- print("❌ Missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET in your .env")
90
- print(" Please add:")
91
- print(" GOOGLE_CLIENT_ID=your-client-id")
92
- print(" GOOGLE_CLIENT_SECRET=your-client-secret")
93
- return False
94
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  try:
96
  oauth_manager.setup_client_secrets(client_id, client_secret)
97
  print("βœ… OAuth credentials saved successfully")
 
11
  from pathlib import Path
12
  from oauth_manager import oauth_manager
13
  from logger import logger
 
 
 
 
14
 
15
  def print_banner():
16
  """Print setup banner"""
 
78
  """Guide user through OAuth credentials setup"""
79
  print_step(3, "OAuth Client Credentials Setup")
80
 
81
+ print("Now you need to create OAuth 2.0 client credentials.")
82
+ print("\nπŸ“‹ Follow these steps:")
83
+ print("1. Go to: https://console.cloud.google.com/apis/credentials")
84
+ print("2. Click 'Create Credentials' > 'OAuth client ID'")
85
+ print("3. Choose 'Web application' as the application type")
86
+ print("4. Set the name to 'Gmail MCP Server'")
87
+ print("5. Add this redirect URI:")
88
+ print(" http://localhost:8080/oauth2callback")
89
+ print("6. Click 'Create'")
90
+ print("7. Copy the Client ID and Client Secret")
91
+
92
+ print("\nπŸ”‘ Enter your OAuth credentials:")
93
+
94
+ while True:
95
+ client_id = input("Client ID: ").strip()
96
+ if client_id:
97
+ break
98
+ print("❌ Client ID cannot be empty")
99
+
100
+ while True:
101
+ client_secret = input("Client Secret: ").strip()
102
+ if client_secret:
103
+ break
104
+ print("❌ Client Secret cannot be empty")
105
+
106
  try:
107
  oauth_manager.setup_client_secrets(client_id, client_secret)
108
  print("βœ… OAuth credentials saved successfully")