Purge standalone OAuth setup for Gmail and Gcal

- Deleted gmail_oauth_setup.py and gcal_oauth_setup.py (Skald handles OAuth)
- Removed google-auth-oauthlib dependency from requirements.txt and connector.json
- Simplified setup_instructions to point to Skald's OAuth flow
- Updated docstrings and error messages in *_mcp_server.py (no more refs to
  *_oauth_setup.py, point to Skald OAuth / env-var instead)
- connectors.json: removed oauth_setup.py from files[], updated sha256 hashes
This commit is contained in:
2026-07-17 21:53:17 +01:00
parent 46c1446eae
commit b1a3944fc8
9 changed files with 24 additions and 256 deletions
+10 -16
View File
@@ -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
}
]
},
+2 -4
View File
@@ -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": [
{
+5 -8
View File
@@ -13,7 +13,6 @@ Capabilities (callable as `mcp__gcal__<tool>`):
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 ""
-106
View File
@@ -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()
-1
View File
@@ -1,3 +1,2 @@
google-api-python-client>=2.150.0
google-auth>=2.35.0
google-auth-oauthlib>=1.2.0
+2 -4
View File
@@ -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": [
{
+5 -8
View File
@@ -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 "
-108
View File
@@ -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()
-1
View File
@@ -1,3 +1,2 @@
google-api-python-client>=2.150.0
google-auth>=2.35.0
google-auth-oauthlib>=1.2.0