-
Notifications
You must be signed in to change notification settings - Fork 0
feat(integrations): MCP OAuth providers #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: eval-pr-1447-target-1758209560486
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,9 @@ | |||||
|
|
||||||
| from abc import ABC | ||||||
| from typing import Any, ClassVar, Self, cast | ||||||
| from urllib.parse import urlparse | ||||||
|
|
||||||
| import httpx | ||||||
| from authlib.integrations.httpx_client import AsyncOAuth2Client | ||||||
| from pydantic import BaseModel, SecretStr | ||||||
|
|
||||||
|
|
@@ -63,10 +65,13 @@ def __init__( | |||||
| client_kwargs = { | ||||||
| "client_id": self.client_id, | ||||||
| "client_secret": self.client_secret, | ||||||
| "scope": " ".join(self.requested_scopes), | ||||||
| "grant_type": self.grant_type, | ||||||
| } | ||||||
|
|
||||||
| # Only add scope if not empty | ||||||
| if self.requested_scopes: | ||||||
| client_kwargs["scope"] = " ".join(self.requested_scopes) | ||||||
|
|
||||||
| # Let subclasses add grant-specific parameters | ||||||
| client_kwargs.update(self._get_client_kwargs()) | ||||||
|
|
||||||
|
|
@@ -269,3 +274,113 @@ async def get_client_credentials_token(self) -> TokenResponse: | |||||
| error=str(e), | ||||||
| ) | ||||||
| raise | ||||||
|
|
||||||
|
|
||||||
| class MCPAuthProvider(AuthorizationCodeOAuthProvider): | ||||||
| """Base OAuth provider for Model Context Protocol (MCP) servers using OAuth 2.1. | ||||||
|
|
||||||
| MCP OAuth follows OAuth 2.1 standards with: | ||||||
| - PKCE required for authorization code flow | ||||||
| - Resource parameter to identify the MCP server | ||||||
| - Flexible scope handling (server determines granted scopes) | ||||||
| - Dynamic discovery of OAuth endpoints | ||||||
| - Optional dynamic client registration | ||||||
| """ | ||||||
|
|
||||||
| _mcp_server_uri: ClassVar[str] | ||||||
|
|
||||||
| def __init__(self, **kwargs): | ||||||
| """Initialize MCP provider with dynamic endpoint discovery.""" | ||||||
| # Initialize logger early for discovery | ||||||
| self.logger = logger.bind(service=f"{self.__class__.__name__}") | ||||||
|
|
||||||
| # Discover OAuth endpoints before parent initialization | ||||||
| self._discover_oauth_endpoints() | ||||||
| super().__init__(**kwargs) | ||||||
|
|
||||||
| @property | ||||||
| def authorization_endpoint(self) -> str: | ||||||
| """Return the discovered authorization endpoint.""" | ||||||
| return self._discovered_auth_endpoint | ||||||
|
|
||||||
| @property | ||||||
| def token_endpoint(self) -> str: | ||||||
| """Return the discovered token endpoint.""" | ||||||
| return self._discovered_token_endpoint | ||||||
|
|
||||||
| def _get_base_url(self) -> str: | ||||||
| """Extract base URL from MCP server URI.""" | ||||||
| parsed = urlparse(self._mcp_server_uri) | ||||||
| return f"{parsed.scheme}://{parsed.netloc}" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discovery base URL omits the path component of Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: General AI Review Agent
Suggested change
|
||||||
|
|
||||||
| def _discover_oauth_endpoints(self) -> None: | ||||||
| """Discover OAuth endpoints from .well-known configuration with fallback support.""" | ||||||
| base_url = self._get_base_url() | ||||||
| discovery_url = f"{base_url}/.well-known/oauth-authorization-server" | ||||||
|
|
||||||
| try: | ||||||
| # Synchronous discovery during initialization | ||||||
| with httpx.Client() as client: | ||||||
| response = client.get(discovery_url, timeout=10.0) | ||||||
| response.raise_for_status() | ||||||
| discovery_doc = response.json() | ||||||
|
|
||||||
| # Store discovered endpoints as instance variables | ||||||
| self._discovered_auth_endpoint = discovery_doc["authorization_endpoint"] | ||||||
| self._discovered_token_endpoint = discovery_doc["token_endpoint"] | ||||||
|
|
||||||
| # Store registration endpoint if available | ||||||
| self._registration_endpoint = discovery_doc.get("registration_endpoint") | ||||||
|
|
||||||
| self.logger.info( | ||||||
| "Discovered OAuth endpoints", | ||||||
| provider=self.id, | ||||||
| authorization=self._discovered_auth_endpoint, | ||||||
| token=self._discovered_token_endpoint, | ||||||
| ) | ||||||
| except Exception as e: | ||||||
| # Check if subclass provides fallback endpoints | ||||||
| if hasattr(self, "_fallback_auth_endpoint") and hasattr( | ||||||
| self, "_fallback_token_endpoint" | ||||||
| ): | ||||||
| self._discovered_auth_endpoint = self._fallback_auth_endpoint | ||||||
| self._discovered_token_endpoint = self._fallback_token_endpoint | ||||||
| self.logger.info( | ||||||
| "Using fallback OAuth endpoints", | ||||||
| provider=self.id, | ||||||
| authorization=self._discovered_auth_endpoint, | ||||||
| token=self._discovered_token_endpoint, | ||||||
| ) | ||||||
| else: | ||||||
| self.logger.error( | ||||||
| "Failed to discover OAuth endpoints", | ||||||
| provider=self.id, | ||||||
| error=str(e), | ||||||
| discovery_url=discovery_url, | ||||||
| ) | ||||||
| raise ValueError( | ||||||
| f"Could not discover OAuth endpoints from {discovery_url} " | ||||||
| f"and no fallback endpoints provided" | ||||||
| ) from e | ||||||
|
|
||||||
| def _use_pkce(self) -> bool: | ||||||
| """PKCE is mandatory for OAuth 2.1 compliance.""" | ||||||
| return True | ||||||
|
|
||||||
| def _get_additional_authorize_params(self) -> dict[str, Any]: | ||||||
| """Add MCP-specific authorization parameters. | ||||||
|
|
||||||
| The resource parameter identifies the MCP server that the token will be used with. | ||||||
| """ | ||||||
| params = super()._get_additional_authorize_params() | ||||||
| params["resource"] = self._mcp_server_uri | ||||||
| return params | ||||||
|
|
||||||
| def _get_additional_token_params(self) -> dict[str, Any]: | ||||||
| """Add MCP-specific token exchange parameters. | ||||||
|
|
||||||
| The resource parameter must be included in token requests per MCP spec. | ||||||
| """ | ||||||
| params = super()._get_additional_token_params() | ||||||
| params["resource"] = self._mcp_server_uri | ||||||
| return params | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,52 @@ | ||||||
| """GitHub Copilot MCP OAuth integration using Model Context Protocol.""" | ||||||
|
|
||||||
| from typing import ClassVar | ||||||
|
|
||||||
| from tracecat.integrations.models import ProviderMetadata, ProviderScopes | ||||||
| from tracecat.integrations.providers.base import MCPAuthProvider | ||||||
|
|
||||||
|
|
||||||
| class GitHubMCPProvider(MCPAuthProvider): | ||||||
| """GitHub Copilot OAuth provider for Model Context Protocol integration. | ||||||
| This provider enables integration with GitHub Copilot's MCP server for: | ||||||
| - Code assistance and suggestions | ||||||
| - Repository context understanding | ||||||
| - Development workflow automation | ||||||
| Uses fallback OAuth endpoints since GitHub doesn't support discovery. | ||||||
| """ | ||||||
|
|
||||||
| id: ClassVar[str] = "github_mcp" | ||||||
|
|
||||||
| # MCP server endpoint | ||||||
| _mcp_server_uri: ClassVar[str] = "https://api.githubcopilot.com/mcp" | ||||||
|
|
||||||
| # Fallback OAuth endpoints (GitHub doesn't support discovery) | ||||||
| _fallback_auth_endpoint: ClassVar[str] = "https://github.com/login/oauth/authorize" | ||||||
| _fallback_token_endpoint: ClassVar[str] = ( | ||||||
| "https://github.com/login/oauth/access_token" | ||||||
| ) | ||||||
|
|
||||||
| # No default scopes - authorization server determines based on user permissions | ||||||
| scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) | ||||||
|
|
||||||
| # Provider metadata | ||||||
| metadata: ClassVar[ProviderMetadata] = ProviderMetadata( | ||||||
| id="github_mcp", | ||||||
| name="GitHub Copilot MCP", | ||||||
| description="GitHub Copilot Model Context Protocol OAuth provider for AI-powered development assistance", | ||||||
| enabled=True, | ||||||
| requires_config=False, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. requires_config=False likely misleads the UI; this provider needs pre-configured client credentials since no dynamic registration is performed and fallback endpoints are used. Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: General AI Review Agent
Suggested change
|
||||||
| setup_instructions=( | ||||||
| "Connect to GitHub Copilot MCP to enable AI-powered code assistance and repository context. " | ||||||
| "Permissions are automatically determined based on your GitHub account and organization settings." | ||||||
| ), | ||||||
| setup_steps=[ | ||||||
| "Click 'Connect' to begin OAuth authorization", | ||||||
| "Authenticate with your GitHub account", | ||||||
| "Review and approve the OAuth client permissions", | ||||||
| "Complete authorization to enable MCP integration", | ||||||
| ], | ||||||
| api_docs_url="https://docs.github.com/en/copilot", | ||||||
| ) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| """Linear MCP OAuth integration using Model Context Protocol.""" | ||
|
|
||
| from typing import ClassVar | ||
|
|
||
| from tracecat.integrations.models import ProviderMetadata, ProviderScopes | ||
| from tracecat.integrations.providers.base import MCPAuthProvider | ||
|
|
||
|
|
||
| class LinearMCPProvider(MCPAuthProvider): | ||
| """Linear OAuth provider for Model Context Protocol integration. | ||
|
|
||
| This provider enables integration with Linear's MCP server for: | ||
| - Accessing and managing issues, projects, and teams | ||
| - Running GraphQL queries against Linear's API | ||
| - Automating workflows and issue management | ||
|
|
||
| OAuth endpoints are automatically discovered from the server. | ||
| """ | ||
|
|
||
| id: ClassVar[str] = "linear_mcp" | ||
|
|
||
| # MCP server endpoint - OAuth endpoints discovered automatically | ||
| _mcp_server_uri: ClassVar[str] = "https://mcp.linear.app/mcp" | ||
|
|
||
| # No default scopes - authorization server determines based on user permissions | ||
| scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) | ||
|
|
||
| # Provider metadata | ||
| metadata: ClassVar[ProviderMetadata] = ProviderMetadata( | ||
| id="linear_mcp", | ||
| name="Linear MCP", | ||
| description="Linear Model Context Protocol OAuth provider for issue tracking and project management", | ||
| enabled=True, | ||
| requires_config=False, | ||
| setup_instructions=( | ||
| "Connect to Linear MCP to access issues, projects, and teams. " | ||
| "Permissions are automatically determined based on your Linear workspace access." | ||
| ), | ||
| setup_steps=[ | ||
| "Click 'Connect' to begin OAuth authorization", | ||
| "Select your Linear workspace if prompted", | ||
| "Review and approve the OAuth client permissions", | ||
| "Complete authorization to enable MCP integration", | ||
| ], | ||
| api_docs_url="https://mcp.linear.app", | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| """Microsoft Learn MCP OAuth integration using Model Context Protocol.""" | ||
|
|
||
| from typing import ClassVar | ||
|
|
||
| from tracecat.integrations.models import ProviderMetadata, ProviderScopes | ||
| from tracecat.integrations.providers.base import MCPAuthProvider | ||
|
|
||
|
|
||
| class MicrosoftLearnMCPProvider(MCPAuthProvider): | ||
| """Microsoft Learn OAuth provider for Model Context Protocol integration. | ||
| This provider enables integration with Microsoft Learn's MCP server for: | ||
| - Real-time access to official Microsoft documentation | ||
| - AI-powered documentation search and retrieval | ||
| - Technical knowledge from Microsoft's documentation library | ||
| Uses Microsoft Entra ID (Azure AD) for authentication. | ||
| Uses fallback OAuth endpoints since discovery is not supported. | ||
| """ | ||
|
|
||
| id: ClassVar[str] = "microsoft_learn_mcp" | ||
|
|
||
| # MCP server endpoint | ||
| _mcp_server_uri: ClassVar[str] = "https://learn.microsoft.com/api/mcp" | ||
|
|
||
| # Microsoft Entra ID OAuth endpoints (fallback since discovery isn't supported) | ||
| _fallback_auth_endpoint: ClassVar[str] = ( | ||
| "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" | ||
| ) | ||
| _fallback_token_endpoint: ClassVar[str] = ( | ||
| "https://login.microsoftonline.com/common/oauth2/v2.0/token" | ||
| ) | ||
|
|
||
| # No default scopes - authorization server determines based on user permissions | ||
| scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty default scopes mean no scope is sent; if using scope-based endpoints, this can cause invalid_request/invalid_scope errors. Provide appropriate scopes or use endpoints that support resource. Prompt for AI agents[internal] Confidence score: 7/10 [internal] Posted by: General AI Review Agent |
||
|
|
||
| # Provider metadata | ||
| metadata: ClassVar[ProviderMetadata] = ProviderMetadata( | ||
| id="microsoft_learn_mcp", | ||
| name="Microsoft Learn MCP", | ||
| description="Microsoft Learn Model Context Protocol OAuth provider for AI-powered documentation access", | ||
| enabled=True, | ||
| requires_config=False, | ||
| setup_instructions=( | ||
| "Connect to Microsoft Learn MCP to access real-time Microsoft documentation. " | ||
| "This integration provides AI assistance with official Microsoft technical documentation. " | ||
| "Authentication is handled through Microsoft Entra ID (Azure AD)." | ||
| ), | ||
| setup_steps=[ | ||
| "Click 'Connect' to begin OAuth authorization", | ||
| "Sign in with your Microsoft account", | ||
| "Review and approve the OAuth permissions", | ||
| "Complete authorization to enable Microsoft Learn MCP integration", | ||
| ], | ||
| api_docs_url="https://github.com/microsoft/mcp", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. api_docs_url likely inaccurate; link to official Microsoft Learn or Microsoft identity/MCP documentation. DEV MODE: This violation would have been filtered out by screening filters. Failing filters: uncertaintyLanguage. Reasoning: • Libraries consulted: Microsoft identity platform v2 endpoint resource parameter scopes authorize token, Azure AD v2.0 endpoint resource parameter not supported scope required authorize token, Microsoft identity platform v1 vs v2 endpoint resource parameter scopes documentation authorize token endpoint, learn.microsoft.com Microsoft identity platform v2 endpoint scopes resource parameter authorize token documentation, Entra-docs Prompt for AI agents[internal] Confidence score: 6/10 [internal] Posted by: General AI Review Agent |
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| """Notion MCP OAuth integration using Model Context Protocol.""" | ||
|
|
||
| from typing import ClassVar | ||
|
|
||
| from tracecat.integrations.models import ProviderMetadata, ProviderScopes | ||
| from tracecat.integrations.providers.base import MCPAuthProvider | ||
|
|
||
|
|
||
| class NotionMCPProvider(MCPAuthProvider): | ||
| """Notion OAuth provider for Model Context Protocol integration. | ||
|
|
||
| This provider enables AI-powered integration with Notion workspaces for: | ||
| - Reading and writing pages, databases, and comments | ||
| - AI-optimized Markdown-based content retrieval | ||
| - Dynamic workspace access based on user permissions | ||
|
|
||
| OAuth endpoints are automatically discovered from the server. | ||
| """ | ||
|
|
||
| id: ClassVar[str] = "notion_mcp" | ||
|
|
||
| # MCP server endpoint - OAuth endpoints discovered automatically | ||
| _mcp_server_uri: ClassVar[str] = "https://mcp.notion.com/mcp" | ||
|
|
||
| # No default scopes - authorization server determines based on user permissions | ||
| scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) | ||
|
|
||
| # Provider metadata | ||
| metadata: ClassVar[ProviderMetadata] = ProviderMetadata( | ||
| id="notion_mcp", | ||
| name="Notion MCP", | ||
| description="Notion Model Context Protocol OAuth provider for AI-powered workspace integration", | ||
| enabled=True, | ||
| requires_config=False, | ||
| setup_instructions=( | ||
| "Connect to Notion MCP to enable AI tools to interact with your Notion workspace. " | ||
| "Full read and write access to pages, databases, and comments based on your permissions." | ||
| ), | ||
| setup_steps=[ | ||
| "Click 'Connect' to begin OAuth authorization", | ||
| "Select your Notion workspace", | ||
| "Review and approve the permissions", | ||
| "Complete authorization to enable MCP integration", | ||
| ], | ||
| api_docs_url="https://developers.notion.com/docs/mcp", | ||
| ) |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
MCPAuthProviderconstructor performs a synchronous, blocking HTTP request for endpoint discovery. Because provider instances are created for each API request, this will block the server's event loop and cause severe performance degradation for the entire application.Prompt for AI agents
[internal] Confidence score: 10/10
[internal] Posted by: System Design Agent