Switch Gmail and Gcal to env-var OAuth delivery pattern

auth.deliver changes from as:file to as:env:
- Gmail: GMAIL_CREDS_JSON env var (Google authorized_user JSON)
- Gcal: GCAL_CREDS_JSON env var

mcp_config.env removed entirely — Skald injects the env var at runtime,
no path on disk needed.

Server scripts updated:
- Check GMAIL_CREDS_JSON / GCAL_CREDS_JSON env var first
- Use Credentials.from_authorized_user_info() instead of
  from_authorized_user_file()
- Fall back to file-based loading for standalone/legacy use
- _persist_creds only writes to disk when _creds_path is set

gcal/verify.py: support GCAL_CREDS_JSON env var with shared _check_api()

Docs (SKALD.md): updated examples, field table, connector table.
This commit is contained in:
2026-07-17 21:00:58 +01:00
parent 3a3bc4e1e9
commit 46c1446eae
7 changed files with 180 additions and 113 deletions
+57 -26
View File
@@ -1,9 +1,12 @@
#!/usr/bin/env python3
"""Verify Google Calendar OAuth credentials are valid.
Reads GOOGLE_CREDS_PATH env var (or falls back to secrets/google_creds.json),
loads the authorized_user credentials, and does one cheap API probe
(calendarList.list(maxResults=1)).
Priority:
1. GCAL_CREDS_JSON env var — authorized_user JSON injected by Skald.
2. GOOGLE_CREDS_PATH env var → file on disk (standalone).
3. Default path ./secrets/google_creds.json (standalone).
Loads credentials and does one cheap API probe (calendarList.list(maxResults=1)).
Output: single JSON object on stdout:
{"ok": true, "message": "..."}
@@ -17,40 +20,20 @@ import os
import sys
def main() -> None:
creds_path = os.environ.get(
"GOOGLE_CREDS_PATH",
os.path.join(os.path.dirname(__file__), "secrets", "google_creds.json"),
)
if not os.path.exists(creds_path):
_fail(f"Credentials file not found at {creds_path}. Run gcal_oauth_setup.py first.")
return
def _check_api(creds) -> None:
"""Perform a live Calendar API probe with the given credentials."""
try:
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
except ImportError as e:
_fail(f"Missing dependencies: {e}. Install google-api-python-client and google-auth.")
return
try:
creds = Credentials.from_authorized_user_file(
creds_path,
["https://www.googleapis.com/auth/calendar"],
)
except Exception as e:
_fail(f"Failed to load credentials: {e}")
return
if creds.expired and creds.refresh_token:
try:
creds.refresh(Request())
with open(creds_path, "w") as f:
f.write(creds.to_json())
except Exception as e:
_fail(f"Token refresh failed: {e}. Re-run gcal_oauth_setup.py.")
_fail(f"Token refresh failed: {e}")
return
try:
@@ -64,12 +47,60 @@ def main() -> None:
_fail(f"API probe failed: {e}")
def main() -> None:
# Priority 1: env var with full JSON (Skald mode)
raw = os.environ.get("GCAL_CREDS_JSON")
if raw:
try:
from google.oauth2.credentials import Credentials
creds = Credentials.from_authorized_user_info(
json.loads(raw),
["https://www.googleapis.com/auth/calendar"],
)
_check_api(creds)
return
except ImportError as e:
_fail(f"Missing dependencies: {e}. Install google-api-python-client and google-auth.")
return
except Exception as e:
_fail(f"Failed to load credentials from GCAL_CREDS_JSON: {e}")
return
# Priority 2/3: file-based (standalone use)
creds_path = os.environ.get(
"GOOGLE_CREDS_PATH",
os.path.join(os.path.dirname(__file__), "secrets", "google_creds.json"),
)
if not os.path.exists(creds_path):
_fail(f"Credentials not found. Set GCAL_CREDS_JSON env var (Skald mode) or "
f"place credentials file at {creds_path} (standalone mode).")
return
try:
from google.oauth2.credentials import Credentials
creds = Credentials.from_authorized_user_file(
creds_path,
["https://www.googleapis.com/auth/calendar"],
)
except ImportError as e:
_fail(f"Missing dependencies: {e}. Install google-api-python-client and google-auth.")
return
except Exception as e:
_fail(f"Failed to load credentials: {e}")
return
_check_api(creds)
def _ok(message: str) -> None:
json.dump({"ok": True, "message": message}, sys.stdout)
sys.stdout.write("\n")
def _fail(message: str) -> None:
json.dump({"ok": False, "message": message}, sys.stdout)
sys.stdout.write("\n")
sys.exit(1)