diff --git a/backend/apps/agents/agent_manager.py b/backend/apps/agents/agent_manager.py index a5a29362..958afbc4 100644 --- a/backend/apps/agents/agent_manager.py +++ b/backend/apps/agents/agent_manager.py @@ -200,8 +200,9 @@ class AgentManager: continue if tool.auth_type == "oauth2" and tool.auth_status == "connected": - if tool.name.lower() == "discord": - # Discord uses a shared bot token from .env, not user OAuth tokens. + if tool.name.lower() in ("discord", "github"): + # Discord uses a shared bot token; GitHub OAuth-app tokens don't + # expire and carry no refresh_token. Nothing to refresh either way. refreshed = True elif tool.name.lower() == "airtable": refreshed = await refresh_airtable_token(tool) diff --git a/backend/apps/agents/core/mcp_preflight.py b/backend/apps/agents/core/mcp_preflight.py index 73641f2c..de58feed 100644 --- a/backend/apps/agents/core/mcp_preflight.py +++ b/backend/apps/agents/core/mcp_preflight.py @@ -56,6 +56,11 @@ CURATED_SHORTLIST: list[CuratedEntry] = [ "title": "Airtable", "description": "Read and write records, manage bases, tables, and fields in the user's Airtable.", }, + { + "id": "GitHub", + "title": "GitHub", + "description": "Repos, issues, pull requests, Actions, code search, gists; when the task involves the user's GitHub.", + }, { "id": "Reddit", "title": "Reddit", diff --git a/backend/apps/tools_lib/oauth_tokens.py b/backend/apps/tools_lib/oauth_tokens.py index a1b60e63..3c4ce7e6 100644 --- a/backend/apps/tools_lib/oauth_tokens.py +++ b/backend/apps/tools_lib/oauth_tokens.py @@ -27,6 +27,7 @@ _TOOL_NAME_TO_PROVIDER = { "hubspot": "hubspot", "discord": "discord", "notion": "notion", + "github": "github", # Built-in Google tool's name is "Google Workspace"; accept the bare # "google" alias too for forward compatibility. "google workspace": "google", @@ -61,6 +62,12 @@ def _persist_cloud_tokens(tool: ToolDefinition, tokens: dict) -> None: elif name == "notion": tool.oauth_tokens = {"access_token": tokens.get("access_token", "")} tool.connected_account_email = tokens.get("workspace_name", "Notion workspace") + elif name == "github": + # GitHub OAuth-App tokens don't expire and carry no refresh_token, so + # store the bare token; the cloud callback enriches `login` for the label. + tool.oauth_tokens = {"access_token": tokens.get("access_token", "")} + login = tokens.get("login") + tool.connected_account_email = f"@{login}" if login else "" else: tool.oauth_tokens = { "access_token": tokens.get("access_token", ""), diff --git a/frontend/src/app/pages/Tools/integrations.tsx b/frontend/src/app/pages/Tools/integrations.tsx index 727dfad2..2326ee8f 100644 --- a/frontend/src/app/pages/Tools/integrations.tsx +++ b/frontend/src/app/pages/Tools/integrations.tsx @@ -50,6 +50,20 @@ export const INTEGRATIONS: Integration[] = [ ), }, + { + id: 'github', + name: 'GitHub', + description: 'Repos, issues, pull requests, Actions, code search, and gists. Connects with your GitHub account.', + mcp_config: { type: 'http', url: 'https://api.githubcopilot.com/mcp/' }, + color: '#181717', + website: 'https://github.com/github/github-mcp-server', + icon: ( + + + + ), + authType: 'oauth2', + }, { id: 'google-workspace', name: 'Google Workspace',