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:
@@ -15,9 +15,7 @@
|
||||
],
|
||||
"setup_instructions": [
|
||||
"Install dependencies: pip install -r requirements.txt",
|
||||
"Create secrets/google_oauth_client.json with {\"client_id\": \"...\", \"client_secret\": \"...\"}",
|
||||
"Run: python3 gcal_oauth_setup.py (opens browser for OAuth)",
|
||||
"Set GOOGLE_CREDS_PATH env var or place token at secrets/google_creds.json"
|
||||
"Run: python3 gcal_oauth_setup.py (optional, for standalone use — Skald handles OAuth)"
|
||||
],
|
||||
"docs": [
|
||||
{
|
||||
@@ -33,17 +31,14 @@
|
||||
"https://www.googleapis.com/auth/calendar"
|
||||
],
|
||||
"deliver": {
|
||||
"as": "file",
|
||||
"as": "env",
|
||||
"format": "google_authorized_user",
|
||||
"path": "{secrets}/google_creds.json"
|
||||
"env": "GCAL_CREDS_JSON"
|
||||
}
|
||||
},
|
||||
"mcp_config": {
|
||||
"command": "python3",
|
||||
"args": ["gcal_mcp_server.py"],
|
||||
"env": {
|
||||
"GOOGLE_CREDS_PATH": "{secrets}/google_creds.json"
|
||||
}
|
||||
"args": ["gcal_mcp_server.py"]
|
||||
},
|
||||
"verify": {
|
||||
"command": "python3 verify.py",
|
||||
|
||||
@@ -11,14 +11,13 @@ Capabilities (callable as `mcp__gcal__<tool>`):
|
||||
delete_event — permanently delete an event
|
||||
respond_to_event — set RSVP / attendance response
|
||||
|
||||
Credentials are read from ./secrets/google_creds.json by default.
|
||||
Override with GOOGLE_CREDS_PATH env var.
|
||||
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
|
||||
(or https://www.googleapis.com/auth/calendar.events for events-only)
|
||||
|
||||
Run scripts/gcal_oauth_setup.py to (re-)authenticate.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -138,7 +137,14 @@ def _persist_creds() -> None:
|
||||
|
||||
|
||||
def _build_service() -> Any:
|
||||
"""Build and return a Google Calendar service object, or None on failure."""
|
||||
"""Build and return a Google Calendar service object, or None on failure.
|
||||
|
||||
Credentials are loaded in priority order:
|
||||
1. GCAL_CREDS_JSON env var — full authorized_user JSON injected by Skald
|
||||
(Credentials.from_authorized_user_info)
|
||||
2. GOOGLE_CREDS_PATH env var → file on disk (standalone/legacy)
|
||||
3. Default path ./secrets/google_creds.json (standalone use)
|
||||
"""
|
||||
global _init_error, _creds, _creds_path
|
||||
try:
|
||||
from google.auth.transport.requests import Request
|
||||
@@ -149,25 +155,40 @@ def _build_service() -> Any:
|
||||
log(_init_error)
|
||||
return None
|
||||
|
||||
_creds_path = os.environ.get(
|
||||
"GOOGLE_CREDS_PATH",
|
||||
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "secrets", "google_creds.json"),
|
||||
)
|
||||
_SCOPES = [
|
||||
"https://www.googleapis.com/auth/calendar",
|
||||
]
|
||||
|
||||
if not os.path.exists(_creds_path):
|
||||
_init_error = (
|
||||
f"Credentials file not found at {_creds_path}. "
|
||||
"Run scripts/gcal_oauth_setup.py to authenticate, or set GOOGLE_CREDS_PATH."
|
||||
raw = os.environ.get("GCAL_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 GCAL_CREDS_JSON env var.")
|
||||
except Exception as e:
|
||||
_init_error = f"Failed to load credentials from GCAL_CREDS_JSON: {e}"
|
||||
log(_init_error)
|
||||
return None
|
||||
else:
|
||||
_creds_path = os.environ.get(
|
||||
"GOOGLE_CREDS_PATH",
|
||||
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "secrets", "google_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 GCAL_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
|
||||
@@ -176,7 +197,8 @@ def _build_service() -> Any:
|
||||
if creds.expired and creds.refresh_token:
|
||||
try:
|
||||
creds.refresh(Request())
|
||||
_persist_creds()
|
||||
if _creds_path:
|
||||
_persist_creds()
|
||||
log("Token refreshed and saved.")
|
||||
except Exception as e:
|
||||
log(f"Token refresh failed: {e}")
|
||||
@@ -188,7 +210,7 @@ def _build_service() -> Any:
|
||||
log(_init_error)
|
||||
return None
|
||||
|
||||
log(f"Calendar service built successfully (creds: {_creds_path})")
|
||||
log(f"Calendar service built successfully (creds: env var or {_creds_path})")
|
||||
return service
|
||||
|
||||
|
||||
@@ -319,8 +341,8 @@ def _gcal_status(args: dict | None = None) -> str:
|
||||
if svc is None:
|
||||
return _status_report("❌", "NOT_CONFIGURED", "action needed",
|
||||
f"The Google Calendar service could not be built: {_init_error or 'unknown error'}.",
|
||||
["Run scripts/gcal_oauth_setup.py to authenticate and create secrets/google_creds.json.",
|
||||
"Or set the GOOGLE_CREDS_PATH env var to point at an existing credentials file."])
|
||||
["Run scripts/gcal_oauth_setup.py to authenticate (standalone).",
|
||||
"For Skald mode, ensure GCAL_CREDS_JSON env var is set."])
|
||||
|
||||
# Step 2: live probe — refresh-on-auth-error is handled inside _call.
|
||||
try:
|
||||
|
||||
+57
-26
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user