diff --git a/connectors/connectors.json b/connectors/connectors.json index 36e12de..3341e8e 100644 --- a/connectors/connectors.json +++ b/connectors/connectors.json @@ -31,14 +31,11 @@ "files": [ { "path": "gmail_mcp_server.py", - "sha256": "53e6cdbc4ff40a095d29f7538ec23ac8fc10fb83cb28496bb28a83e7c49deaae", - "size": 47833 + "sha256": "e96568dda3d0198be0a34092458dac2aaafee4a7e282a3fb427923f517e536e3", + "size": 47789 }, { - "path": "gmail_oauth_setup.py", - "sha256": "e488acb289c43a3e6d541140d254451ac5688c0014e8959619375b564afe8748", - "size": 3627 - }, + "path": "icon_lg.svg", { "path": "icon_lg.svg", "sha256": "93c8d9c8dae96f0206e5ae3fcfe89dff3a08553ed48badf504a5abd09f5596b0", @@ -51,8 +48,8 @@ }, { "path": "requirements.txt", - "sha256": "3f659cc5e5f0543f132699b06ccf9016ffe9afb40f9df4d110e0445b8d20f63e", - "size": 82 + "sha256": "41a908f999095b052865efdcf793cfe7b032a3609c0cdd9f7711b2af5b3b7827", + "size": 54 } ] }, @@ -85,14 +82,11 @@ "files": [ { "path": "gcal_mcp_server.py", - "sha256": "87b44a407feac90ed073997bd0a558c7002eec0c408d50c45da7d99011dd6107", - "size": 38203 + "sha256": "c526751c8891a8d97ba37d14da282d223be7cd061eb67c76cea6a12672921db0", + "size": 38177 }, { - "path": "gcal_oauth_setup.py", - "sha256": "4abbfcf6af50cc55ca00d283353b417acc6fee7720aa10abb0fbaa0e4aa79900", - "size": 3430 - }, + "path": "verify.py", { "path": "verify.py", "sha256": "d994ae435d8a5a3d9cdc85c8f73805a877c1a8818637b2912f6c31c693fdded4", @@ -110,8 +104,8 @@ }, { "path": "requirements.txt", - "sha256": "3f659cc5e5f0543f132699b06ccf9016ffe9afb40f9df4d110e0445b8d20f63e", - "size": 82 + "sha256": "41a908f999095b052865efdcf793cfe7b032a3609c0cdd9f7711b2af5b3b7827", + "size": 54 } ] }, diff --git a/connectors/gcal/connector.json b/connectors/gcal/connector.json index 4355bb2..62ad2d1 100644 --- a/connectors/gcal/connector.json +++ b/connectors/gcal/connector.json @@ -10,12 +10,10 @@ "tags": ["calendar", "mcp", "local", "google"], "dependencies": [ "google-api-python-client>=2.150.0", - "google-auth>=2.35.0", - "google-auth-oauthlib>=1.2.0" + "google-auth>=2.35.0" ], "setup_instructions": [ - "Install dependencies: pip install -r requirements.txt", - "Run: python3 gcal_oauth_setup.py (optional, for standalone use — Skald handles OAuth)" + "Activated from Skald: an admin configures the Google sign-in provider, then each user signs in from the connector page (OAuth handled by Skald)." ], "docs": [ { diff --git a/connectors/gcal/gcal_mcp_server.py b/connectors/gcal/gcal_mcp_server.py index 5a8c263..72f266f 100755 --- a/connectors/gcal/gcal_mcp_server.py +++ b/connectors/gcal/gcal_mcp_server.py @@ -13,7 +13,6 @@ Capabilities (callable as `mcp__gcal__`): Skald mode: Skald injects credentials via GCAL_CREDS_JSON env var (authorized_user JSON). Standalone mode: reads from GOOGLE_CREDS_PATH or ./secrets/google_creds.json. -Run scripts/gcal_oauth_setup.py to (re-)authenticate (standalone). Required OAuth scopes: https://www.googleapis.com/auth/calendar @@ -282,21 +281,20 @@ def _format_google_error(e: Exception, api_label: str) -> str: if RefreshError is not None and isinstance(e, RefreshError): return ( f"Error: {api_label} API token refresh failed (the refresh token may have been revoked " - "or expired). Re-run scripts/gcal_oauth_setup.py to re-authenticate." + "or expired). Re-authenticate via Skald's OAuth flow (Sign in from the connector page)." ) if HttpError is not None and isinstance(e, HttpError): status = getattr(e, "status_code", None) if status == 401: return ( - f"Error: {api_label} API rejected the access token (401). The OAuth token is invalid " - "or revoked. Re-run scripts/gcal_oauth_setup.py to re-authenticate." - ) + f"Error: {api_label} API rejected the access token (401). The OAuth token is invalid " + "or revoked. Re-authenticate via Skald's OAuth flow (Sign in from the connector page)." ) if status == 403: return ( f"Error: {api_label} API returned 403 Forbidden. The OAuth scopes granted are " "insufficient for this operation, or the Calendar API is disabled in the Google Cloud " - "Console. Verify the scopes in scripts/gcal_oauth_setup.py and the API enablement." + "Console. Verify the scopes in the Google OAuth provider config and the API enablement." ) if status == 404: return ( @@ -350,9 +348,8 @@ def _gcal_status(args: dict | None = None) -> str: except Exception as e: return _status_report("❌", "AUTH_OR_API_ERROR", "action needed", f"The Calendar API did not respond to the probe call: {_format_google_error(e, 'Calendar')}", - ["Run scripts/gcal_oauth_setup.py to refresh / re-issue credentials.", + ["Re-authenticate via Skald's OAuth flow (Sign in from the connector page).", "If credentials are valid, verify the Google Calendar API is enabled in the Google Cloud Console."]) - items = result.get("items", []) if isinstance(result, dict) else [] primary = next((c for c in items if c.get("primary")), None) suffix = f"\nAccount: {primary.get('id')}" if primary else "" diff --git a/connectors/gcal/gcal_oauth_setup.py b/connectors/gcal/gcal_oauth_setup.py deleted file mode 100644 index 562d8df..0000000 --- a/connectors/gcal/gcal_oauth_setup.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 -"""Generate a Google OAuth token for the Calendar API (read + write). - -This script runs a local OAuth flow that: -1. Opens your browser automatically to the Google authorization page -2. Handles the callback via a local HTTP server -3. Saves the resulting token to ./secrets/google_creds.json - -Required OAuth scope: https://www.googleapis.com/auth/calendar -(full access — needed for create, update, delete, respond). - -No manual copy-paste required. -""" - -from __future__ import annotations - -import json -import os -import sys - -SCOPES = [ - "https://www.googleapis.com/auth/calendar", -] - -_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SECRET_PATH = os.path.join(_ROOT, "secrets", "google_creds.json") -_OAUTH_CLIENT_PATH = os.path.join(_ROOT, "secrets", "google_oauth_client.json") - - -def _load_oauth_client() -> tuple[str, str]: - if not os.path.exists(_OAUTH_CLIENT_PATH): - print(f"Missing OAuth client file: {_OAUTH_CLIENT_PATH}") - print('Create it with: {"client_id": "...", "client_secret": "..."}') - sys.exit(1) - with open(_OAUTH_CLIENT_PATH) as f: - data = json.load(f) - return data["client_id"], data["client_secret"] - - -def main() -> None: - try: - from google.auth.transport.requests import Request - from google.oauth2.credentials import Credentials - from google_auth_oauthlib.flow import InstalledAppFlow - except ImportError as e: - print(f"Missing dependencies: {e}") - print("Install with: pip install google-auth google-auth-oauthlib google-api-python-client") - sys.exit(1) - - creds = None - - # Try to load existing credentials first. - if os.path.exists(SECRET_PATH): - print(f"Existing credentials found at {SECRET_PATH}") - try: - creds = Credentials.from_authorized_user_file(SECRET_PATH, SCOPES) - except Exception: - creds = None - - if creds and creds.valid: - print("Credentials are already valid!") - print(f" Scopes: {creds.scopes}") - return - - if creds and creds.expired and creds.refresh_token: - print("Token expired. Attempting refresh...") - try: - creds.refresh(Request()) - print("Token refreshed successfully!") - except Exception as e: - print(f"Refresh failed: {e}") - creds = None - - if not creds or not creds.valid: - client_id, client_secret = _load_oauth_client() - flow = InstalledAppFlow.from_client_config( - { - "installed": { - "client_id": client_id, - "client_secret": client_secret, - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "redirect_uris": ["http://localhost"], - } - }, - SCOPES, - ) - - print("\nOpening browser for Google authorization...") - creds = flow.run_local_server( - port=0, - open_browser=True, - prompt="consent", - access_type="offline", - ) - - os.makedirs(os.path.dirname(SECRET_PATH), exist_ok=True) - with open(SECRET_PATH, "w") as f: - f.write(creds.to_json()) - - print(f"\n✅ Google Calendar OAuth token saved to {SECRET_PATH}") - print(f" Scopes: {creds.scopes}") - - -if __name__ == "__main__": - main() diff --git a/connectors/gcal/requirements.txt b/connectors/gcal/requirements.txt index 633433f..4218938 100644 --- a/connectors/gcal/requirements.txt +++ b/connectors/gcal/requirements.txt @@ -1,3 +1,2 @@ google-api-python-client>=2.150.0 google-auth>=2.35.0 -google-auth-oauthlib>=1.2.0 diff --git a/connectors/gmail/connector.json b/connectors/gmail/connector.json index 452fe97..800520c 100644 --- a/connectors/gmail/connector.json +++ b/connectors/gmail/connector.json @@ -11,12 +11,10 @@ ], "dependencies": [ "google-api-python-client>=2.150.0", - "google-auth>=2.35.0", - "google-auth-oauthlib>=1.2.0" + "google-auth>=2.35.0" ], "setup_instructions": [ - "Install dependencies: pip install -r requirements.txt", - "Run: python3 gmail_oauth_setup.py (optional, for standalone use — Skald handles OAuth)" + "Activated from Skald: an admin configures the Google sign-in provider, then each user signs in from the connector page (OAuth handled by Skald)." ], "docs": [ { diff --git a/connectors/gmail/gmail_mcp_server.py b/connectors/gmail/gmail_mcp_server.py index 0fbf9b9..7822fbf 100644 --- a/connectors/gmail/gmail_mcp_server.py +++ b/connectors/gmail/gmail_mcp_server.py @@ -17,7 +17,6 @@ Provides read, modify, and send access to Gmail via the Gmail API v1. Skald mode: Skald injects credentials via GMAIL_CREDS_JSON env var (authorized_user JSON). Standalone mode: reads from GMAIL_CREDS_PATH or ./secrets/gmail_creds.json. -Run scripts/gmail_oauth_setup.py first to generate the OAuth token (standalone). """ from __future__ import annotations @@ -308,21 +307,20 @@ def _format_google_error(e: Exception, api_label: str) -> str: if RefreshError is not None and isinstance(e, RefreshError): return ( f"Error: {api_label} API token refresh failed (the refresh token may have been revoked " - "or expired). Re-run scripts/gmail_oauth_setup.py to re-authenticate." + "or expired). Re-authenticate via Skald's OAuth flow (Sign in from the connector page)." ) if HttpError is not None and isinstance(e, HttpError): status = getattr(e, "status_code", None) if status == 401: return ( - f"Error: {api_label} API rejected the access token (401). The OAuth token is invalid " - "or revoked. Re-run scripts/gmail_oauth_setup.py to re-authenticate." - ) + f"Error: {api_label} API rejected the access token (401). The OAuth token is invalid " + "or revoked. Re-authenticate via Skald's OAuth flow (Sign in from the connector page)." ) if status == 403: return ( f"Error: {api_label} API returned 403 Forbidden. The OAuth scopes granted are " "insufficient for this operation, or the Gmail API is disabled in the Google Cloud " - "Console. Verify the scopes in scripts/gmail_oauth_setup.py and the API enablement." + "Console. Verify the scopes in the Google OAuth provider config and the API enablement." ) if status == 404: return ( @@ -482,9 +480,8 @@ def _gmail_status(args: dict | None = None) -> str: except Exception as e: return _status_report("❌", "AUTH_OR_API_ERROR", "action needed", f"The Gmail API did not respond to the probe call: {_format_google_error(e, 'Gmail')}", - ["Run scripts/gmail_oauth_setup.py to refresh / re-issue credentials.", + ["Re-authenticate via Skald's OAuth flow (Sign in from the connector page).", "If credentials are valid, verify the Gmail API is enabled in the Google Cloud Console."]) - email = profile.get("emailAddress", "?") return _status_report("✅", "READY", "ok", "Google Gmail integration is operational: credentials load, the access token refreshes " diff --git a/connectors/gmail/gmail_oauth_setup.py b/connectors/gmail/gmail_oauth_setup.py deleted file mode 100644 index 2a06ae4..0000000 --- a/connectors/gmail/gmail_oauth_setup.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 -"""Generate a Google OAuth token for Gmail API. - -This script runs a local OAuth flow that: -1. Opens your browser automatically to the Google authorization page -2. Handles the callback via a local HTTP server -3. Saves the resulting token to ./secrets/gmail_creds.json - -No manual copy-paste required. -""" - -from __future__ import annotations - -import json -import os -import sys - -SCOPES = [ - "https://www.googleapis.com/auth/gmail.modify", - "https://www.googleapis.com/auth/gmail.labels", -] - -_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SECRET_PATH = os.path.join(_ROOT, "secrets", "gmail_creds.json") -_OAUTH_CLIENT_PATH = os.path.join(_ROOT, "secrets", "google_oauth_client.json") - - -def _load_oauth_client() -> tuple[str, str]: - if not os.path.exists(_OAUTH_CLIENT_PATH): - print(f"Missing OAuth client file: {_OAUTH_CLIENT_PATH}") - print("Create it with: {\"client_id\": \"...\", \"client_secret\": \"...\"}") - sys.exit(1) - with open(_OAUTH_CLIENT_PATH) as f: - data = json.load(f) - return data["client_id"], data["client_secret"] - - -def main() -> None: - # Lazy-import so we can show helpful errors if not installed. - try: - from google.auth.transport.requests import Request - from google.oauth2.credentials import Credentials - from google_auth_oauthlib.flow import InstalledAppFlow - except ImportError as e: - print(f"Missing dependencies: {e}") - print("Install with: pip3 install google-auth google-auth-oauthlib google-api-python-client") - sys.exit(1) - - creds = None - - # Try to load existing credentials first, in case they have refresh token. - if os.path.exists(SECRET_PATH): - print(f"Existing credentials found at {SECRET_PATH}") - try: - creds = Credentials.from_authorized_user_file(SECRET_PATH, SCOPES) - except Exception: - creds = None - - # If creds exist and are valid, we're good. - if creds and creds.valid: - print("Credentials are already valid!") - return - - # If creds exist but expired, try to refresh. - if creds and creds.expired and creds.refresh_token: - print("Token expired. Attempting refresh...") - try: - creds.refresh(Request()) - print("Token refreshed successfully!") - except Exception as e: - print(f"Refresh failed: {e}") - creds = None - - if not creds or not creds.valid: - client_id, client_secret = _load_oauth_client() - # Start OAuth flow using local server (opens browser automatically). - flow = InstalledAppFlow.from_client_config( - { - "installed": { - "client_id": client_id, - "client_secret": client_secret, - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "redirect_uris": ["http://localhost"], - } - }, - SCOPES, - ) - - print("\nOpening browser for Google authorization...") - creds = flow.run_local_server( - port=0, # pick a random available port - open_browser=True, - prompt="consent", - access_type="offline", - ) - - # Save credentials. - os.makedirs(os.path.dirname(SECRET_PATH), exist_ok=True) - with open(SECRET_PATH, "w") as f: - f.write(creds.to_json()) - - print(f"\n✅ Gmail OAuth token saved to {SECRET_PATH}") - print(f" Scopes: {creds.scopes}") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/connectors/gmail/requirements.txt b/connectors/gmail/requirements.txt index 633433f..4218938 100644 --- a/connectors/gmail/requirements.txt +++ b/connectors/gmail/requirements.txt @@ -1,3 +1,2 @@ google-api-python-client>=2.150.0 google-auth>=2.35.0 -google-auth-oauthlib>=1.2.0