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
+49 -26
View File
@@ -15,10 +15,9 @@ Capabilities (callable as `mcp__gmail__<tool>`):
Provides read, modify, and send access to Gmail via the Gmail API v1.
Credentials are read from ./secrets/gmail_creds.json by default.
Override with GMAIL_CREDS_PATH env var.
Run scripts/gmail_oauth_setup.py first to generate the OAuth token.
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
@@ -156,7 +155,14 @@ def _persist_creds() -> None:
def _build_service() -> Any:
"""Build and return a Gmail service object, or None on failure."""
"""Build and return a Gmail service object, or None on failure.
Credentials are loaded in priority order:
1. GMAIL_CREDS_JSON env var — full authorized_user JSON injected by Skald
(Credentials.from_authorized_user_info)
2. GMAIL_CREDS_PATH env var → file on disk (standalone/legacy)
3. Default path ./secrets/gmail_creds.json (standalone use)
"""
global _init_error, _creds, _creds_path
try:
from google.auth.transport.requests import Request
@@ -167,25 +173,41 @@ def _build_service() -> Any:
log(_init_error)
return None
_creds_path = os.environ.get(
"GMAIL_CREDS_PATH",
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "secrets", "gmail_creds.json"),
)
_SCOPES = [
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.labels",
]
if not os.path.exists(_creds_path):
_init_error = (
f"Credentials file not found at {_creds_path}. "
"Run scripts/gmail_oauth_setup.py first, or set GMAIL_CREDS_PATH."
raw = os.environ.get("GMAIL_CREDS_JSON")
if raw:
try:
creds = Credentials.from_authorized_user_info(json.loads(raw), _SCOPES)
_creds_path = None # no file, persisted only in env
log("Credentials loaded from GMAIL_CREDS_JSON env var.")
except Exception as e:
_init_error = f"Failed to load credentials from GMAIL_CREDS_JSON: {e}"
log(_init_error)
return None
else:
_creds_path = os.environ.get(
"GMAIL_CREDS_PATH",
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "secrets", "gmail_creds.json"),
)
log(_init_error)
return None
try:
creds = Credentials.from_authorized_user_file(_creds_path)
except Exception as e:
_init_error = f"Failed to load credentials from {_creds_path}: {e}"
log(_init_error)
return None
if not os.path.exists(_creds_path):
_init_error = (
f"Credentials not found. Set GMAIL_CREDS_JSON env var (Skald mode) or "
f"place credentials file at {_creds_path} (standalone mode)."
)
log(_init_error)
return None
try:
creds = Credentials.from_authorized_user_file(_creds_path)
except Exception as e:
_init_error = f"Failed to load credentials from {_creds_path}: {e}"
log(_init_error)
return None
# Publish creds globally so _persist_creds / _call can see them.
_creds = creds
@@ -195,10 +217,11 @@ def _build_service() -> Any:
if not creds.valid:
if creds.expired and creds.refresh_token:
creds.refresh(Request())
_persist_creds()
if _creds_path:
_persist_creds()
log("Token refreshed and saved.")
else:
_init_error = "Credentials invalid and cannot be refreshed. Re-run scripts/gmail_oauth_setup.py."
_init_error = "Credentials invalid and cannot be refreshed. Re-run OAuth setup."
log(_init_error)
return None
except Exception as e:
@@ -213,7 +236,7 @@ def _build_service() -> Any:
log(_init_error)
return None
log(f"Gmail service built successfully (creds: {_creds_path})")
log(f"Gmail service built successfully (creds: env var or {_creds_path})")
return service
@@ -450,8 +473,8 @@ def _gmail_status(args: dict | None = None) -> str:
if svc is None:
return _status_report("", "NOT_CONFIGURED", "action needed",
f"The Gmail service could not be built: {_init_error or 'unknown error'}.",
["Run scripts/gmail_oauth_setup.py to authenticate and create secrets/gmail_creds.json.",
"Or set the GMAIL_CREDS_PATH env var to point at an existing credentials file."])
["Run scripts/gmail_oauth_setup.py to authenticate (standalone).",
"For Skald mode, ensure GMAIL_CREDS_JSON env var is set."])
# Step 2: live probe — refresh-on-auth-error is handled inside _call.
try: