Claude-skill-registry google-oauth-integration
Complete Google OAuth integration architecture including token storage and debugging
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/google-oauth-integration" ~/.claude/skills/majiayu000-claude-skill-registry-google-oauth-integration && rm -rf "$T"
skills/data/google-oauth-integration/SKILL.mdGoogle OAuth Integration
This skill documents the complete Google OAuth integration architecture in Orient, including token storage, tool registration, and debugging procedures.
Triggers
Use this skill when:
- Setting up Google OAuth for a new package or service
- Debugging "Unknown tool" errors for Google tools (Calendar, Gmail, Tasks)
- Understanding token storage and refresh mechanics
- Adding new Google service tools to the ToolExecutorRegistry
Token Storage Architecture
Token File Location
Google OAuth tokens are stored in JSON files at:
{cwd}/data/oauth-tokens/google-oauth.json
CRITICAL: The path is resolved using
process.cwd(), meaning different packages look in different locations:
- Dashboard:
packages/dashboard/data/oauth-tokens/google-oauth.json - Slack bot:
packages/bot-slack/data/oauth-tokens/google-oauth.json - WhatsApp bot:
packages/bot-whatsapp/data/oauth-tokens/google-oauth.json - Project root:
data/oauth-tokens/google-oauth.json
Symlink Setup (Required for Multi-Package Access)
Since the dashboard is typically where users connect their Google accounts, other packages need symlinks to access the same tokens:
# For bot-slack mkdir -p packages/bot-slack/data/oauth-tokens ln -sf $(pwd)/packages/dashboard/data/oauth-tokens/google-oauth.json \ packages/bot-slack/data/oauth-tokens/google-oauth.json # For bot-whatsapp mkdir -p packages/bot-whatsapp/data/oauth-tokens ln -sf $(pwd)/packages/dashboard/data/oauth-tokens/google-oauth.json \ packages/bot-whatsapp/data/oauth-tokens/google-oauth.json # For project root (used by MCP servers) mkdir -p data/oauth-tokens ln -sf $(pwd)/packages/dashboard/data/oauth-tokens/google-oauth.json \ data/oauth-tokens/google-oauth.json
Token File Structure
{ "accounts": { "user@gmail.com": { "email": "user@gmail.com", "displayName": "User Name", "accessToken": "ya29...", "refreshToken": "1//...", "expiresAt": 1768578677699, "scopes": [ "https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/gmail.modify" ], "connectedAt": 1768552923717, "lastRefreshAt": 1768575078699 } }, "pendingStates": {} }
Environment Variables
Required environment variables in
.env:
# OAuth Client Credentials (from Google Cloud Console) GOOGLE_OAUTH_CLIENT_ID=<your-client-id>.apps.googleusercontent.com GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-<your-secret> # Callback URL (must match Google Cloud Console) GOOGLE_OAUTH_CALLBACK_URL=http://localhost/api/auth/google/callback
The OAuth service loads credentials in this priority order:
- Secrets database (
,GOOGLE_OAUTH_CLIENT_ID
)GOOGLE_OAUTH_CLIENT_SECRET - Environment variables
- Credentials file:
credentials/client_secret_*.json
Tool Registration Architecture
Two Registration Systems
Orient has two tool registration systems:
-
ToolRegistry (Discovery only)
- Location:
packages/agents/src/services/toolRegistry.ts - Used by:
for listing available toolsdiscover_tools - Does NOT execute tools
- Location:
-
ToolExecutorRegistry (Execution)
- Location:
packages/agents/src/services/toolRegistry.ts - Used by:
inexecuteToolCallFromRegistry()packages/mcp-servers/src/tool-executor.ts - Actually runs the tool code
- Location:
Registering New Google Tools
To add a new Google tool, register it in the
registerGoogleToolHandlers() function:
// In packages/agents/src/services/toolRegistry.ts function registerGoogleToolHandlers(registry: ToolExecutorRegistry): void { const registerHandlers = async () => { const { getCalendarService, getGoogleOAuthService } = await import('@orientbot/integrations/google'); // Register handler registry.registerHandler('google_calendar_list_events', async (args) => { const calendar = getCalendarService(); const events = await calendar.listEvents(options, accountEmail); return createToolResult(JSON.stringify(events, null, 2)); }); }; void registerHandlers(); }
Legacy vs Modern Registration
| Aspect | Legacy (mcp-server.ts) | Modern (ToolExecutorRegistry) |
|---|---|---|
| Location | | |
| Pattern | Giant switch statement | Handler registration |
| Used by | Direct MCP server | assistant-server via base-server |
| Status | Deprecated | Preferred |
Important: The assistant-server (used by Slack/WhatsApp bots) uses
base-server.ts which ONLY uses ToolExecutorRegistry. It does NOT fall back to the legacy switch statement unless setLegacyExecutor() is called.
Token Refresh Mechanics
The OAuth service in
packages/integrations/src/google/oauth.ts handles automatic token refresh:
async getAuthClient(email: string): Promise<Auth.OAuth2Client> { const account = this.data.accounts[email]; // Check if token needs refresh (expired or expiring in < 5 min) if (Date.now() > account.expiresAt - 5 * 60 * 1000) { const { credentials } = await client.refreshAccessToken(); account.accessToken = credentials.access_token; account.expiresAt = credentials.expiry_date; this.saveData(); } return client; }
Key points:
- Tokens are refreshed automatically 5 minutes before expiration
- Requires valid
from initial OAuth consentrefresh_token - Requires OAuth credentials (client ID/secret) to be accessible
Debugging "Unknown Tool" Errors
When Google tools return
{"error":"Unknown tool: google_calendar_list_events"}:
Step 1: Check Tool Discovery
# Tool should appear in discover_tools output curl -X POST http://localhost:4099/... -d '{"tool":"discover_tools","args":{"mode":"search","query":"calendar"}}'
If tool appears in discovery but not execution, it's registered in ToolRegistry but not ToolExecutorRegistry.
Step 2: Check ToolExecutorRegistry Registration
Look for the tool in
packages/agents/src/services/toolRegistry.ts:
// Should see this in registerGoogleToolHandlers(): registry.registerHandler('google_calendar_list_events', async (args) => { ... });
Step 3: Verify Build and Restart
# Rebuild affected packages pnpm --filter @orientbot/agents build pnpm --filter @orientbot/mcp-servers build # Copy to root dist (used by OpenCode) cp packages/mcp-servers/dist/*.js dist/mcp-servers/ # Kill existing MCP server processes ps aux | grep "assistant-server\|coding-server" | grep -v grep | awk '{print $2}' | xargs kill # OpenCode will spawn fresh processes on next tool call
Step 4: Check MCP Server Logs
tail -f logs/mcp-debug-*.log | grep -i "calendar\|google\|unknown"
Look for:
- Tool was registered"Registered tool"
- Tool not found in any executor"Unknown tool"- Error messages during handler registration
Common Issues
Issue: Tokens not found
Symptom: "No Google account connected" Cause: Token file not found at CWD path Fix: Create symlinks as documented above
Issue: Token refresh fails
Symptom: "Failed to refresh token" Cause: OAuth credentials not accessible Fix: Ensure GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET are set
Issue: Tool returns "Unknown tool"
Symptom:
{"error":"Unknown tool: google_calendar_list_events"}
Cause: Tool not registered in ToolExecutorRegistry
Fix: Add handler in registerGoogleToolHandlers() and rebuild
Issue: Stale MCP server
Symptom: Changes not taking effect Cause: Old MCP server process still running Fix: Kill old processes, OpenCode will spawn new ones
Files Reference
| File | Purpose |
|---|---|
| OAuth service, token storage |
| Calendar service implementation |
| Tool discovery + executor registration |
| Tool execution entry point |
| MCP server using executor registry |
| Legacy MCP server with switch statement |