From 84416e43a0b43d5f9bb1f2f83ecc8265297fb4ef Mon Sep 17 00:00:00 2001 From: topher-lo <46541035+topher-lo@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:49:09 -0400 Subject: [PATCH 1/5] feat(integrations): Add MCP OAuth provider with dynamic discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements Model Context Protocol (MCP) OAuth 2.1 authorization code flow with: - MCPAuthProvider base class with dynamic endpoint discovery - PKCE enabled by default for OAuth 2.1 compliance - Resource parameter to identify MCP servers - RunReveal MCP provider for security data analysis - Atlassian MCP provider for Jira, Confluence, Compass OAuth endpoints are discovered automatically from /.well-known/oauth-authorization-server, eliminating the need for hardcoded endpoints. Scopes are determined by the authorization server based on user permissions. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- tracecat/integrations/providers/__init__.py | 4 + .../integrations/providers/atlassian/mcp.py | 56 ++++++++++ tracecat/integrations/providers/base.py | 102 ++++++++++++++++++ .../integrations/providers/runreveal/mcp.py | 47 ++++++++ 4 files changed, 209 insertions(+) create mode 100644 tracecat/integrations/providers/atlassian/mcp.py create mode 100644 tracecat/integrations/providers/runreveal/mcp.py diff --git a/tracecat/integrations/providers/__init__.py b/tracecat/integrations/providers/__init__.py index c242eb20f3..a674699ed6 100644 --- a/tracecat/integrations/providers/__init__.py +++ b/tracecat/integrations/providers/__init__.py @@ -1,6 +1,7 @@ from typing import Final from tracecat.integrations.models import ProviderKey +from tracecat.integrations.providers.atlassian.mcp import AtlassianMCPProvider from tracecat.integrations.providers.base import BaseOAuthProvider from tracecat.integrations.providers.microsoft.graph import ( MicrosoftGraphACProvider, @@ -10,12 +11,15 @@ MicrosoftTeamsACProvider, MicrosoftTeamsCCProvider, ) +from tracecat.integrations.providers.runreveal.mcp import RunRevealMCPProvider _PROVIDER_CLASSES: list[type[BaseOAuthProvider]] = [ MicrosoftGraphACProvider, MicrosoftGraphCCProvider, MicrosoftTeamsACProvider, MicrosoftTeamsCCProvider, + RunRevealMCPProvider, + AtlassianMCPProvider, ] diff --git a/tracecat/integrations/providers/atlassian/mcp.py b/tracecat/integrations/providers/atlassian/mcp.py new file mode 100644 index 0000000000..4329c71913 --- /dev/null +++ b/tracecat/integrations/providers/atlassian/mcp.py @@ -0,0 +1,56 @@ +"""Atlassian 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 AtlassianMCPProvider(MCPAuthProvider): + """Atlassian OAuth provider for Model Context Protocol integration. + + This provider enables integration with Atlassian's MCP server for: + - Jira: Search, create/update issues, bulk operations + - Confluence: Summarize pages, create content, navigate spaces + - Compass: Create components, query dependencies, manage service landscape + - Combined tasks across all three products + + Permissions are based on the user's existing Atlassian Cloud access. + All actions respect existing project or space-level roles. + OAuth endpoints are automatically discovered from the server. + """ + + id: ClassVar[str] = "atlassian_mcp" + + # MCP server endpoint - OAuth endpoints discovered automatically + _mcp_server_uri: ClassVar[str] = "https://mcp.atlassian.com/v1/sse" + + # No default scopes - authorization server determines based on user permissions + scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) + + # Provider metadata + metadata: ClassVar[ProviderMetadata] = ProviderMetadata( + id="atlassian_mcp", + name="Atlassian MCP", + description=( + "Atlassian Model Context Protocol OAuth provider for " + "Jira, Confluence, and Compass integration" + ), + enabled=True, + requires_config=False, + setup_instructions=( + "Connect to Atlassian MCP to access Jira, Confluence, and Compass. " + "Access is granted only to data you already have permission to view in " + "Atlassian Cloud. All actions respect existing project or space-level roles." + ), + setup_steps=[ + "Click 'Connect' to begin OAuth authorization", + "Authenticate with your Atlassian account", + "Review and approve the OAuth permissions", + "Complete authorization to enable MCP integration with Jira, Confluence, and Compass", + ], + api_docs_url=( + "https://support.atlassian.com/atlassian-rovo-mcp-server/docs/" + "getting-started-with-the-atlassian-remote-mcp-server/" + ), + ) diff --git a/tracecat/integrations/providers/base.py b/tracecat/integrations/providers/base.py index 59cc9f173f..241effa83b 100644 --- a/tracecat/integrations/providers/base.py +++ b/tracecat/integrations/providers/base.py @@ -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 @@ -269,3 +271,103 @@ 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 (e.g., "https://api.runreveal.com/mcp") + _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}" + + def _discover_oauth_endpoints(self) -> None: + """Discover OAuth endpoints from .well-known configuration.""" + 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: + self.logger.error( + "Failed to discover OAuth endpoints", + provider=self.id, + error=str(e), + discovery_url=discovery_url, + ) + # Let subclasses provide fallback endpoints if needed + if not hasattr(self, "_discovered_auth_endpoint"): + 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 diff --git a/tracecat/integrations/providers/runreveal/mcp.py b/tracecat/integrations/providers/runreveal/mcp.py new file mode 100644 index 0000000000..f514c3ee98 --- /dev/null +++ b/tracecat/integrations/providers/runreveal/mcp.py @@ -0,0 +1,47 @@ +"""RunReveal 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 RunRevealMCPProvider(MCPAuthProvider): + """RunReveal OAuth provider for Model Context Protocol integration. + + This provider enables integration with RunReveal's MCP server for: + - Running queries and detections + - Accessing table schemas + - Managing detection configurations + + Permissions are determined by the user's role in the selected workspace. + OAuth endpoints are automatically discovered from the server. + """ + + id: ClassVar[str] = "runreveal_mcp" + + # MCP server endpoint - OAuth endpoints discovered automatically + _mcp_server_uri: ClassVar[str] = "https://api.runreveal.com/mcp" + + # No default scopes - authorization server determines based on user/workspace permissions + scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) + + # Provider metadata + metadata: ClassVar[ProviderMetadata] = ProviderMetadata( + id="runreveal_mcp", + name="RunReveal MCP", + description="RunReveal Model Context Protocol OAuth provider for security data analysis", + enabled=True, + requires_config=False, + setup_instructions=( + "Connect to RunReveal MCP to access queries, detections, and table schemas. " + "Permissions are automatically determined based on your workspace role." + ), + setup_steps=[ + "Click 'Connect' to begin OAuth authorization", + "Select your RunReveal workspace", + "Review and approve the OAuth client permissions", + "Complete authorization to enable MCP integration", + ], + api_docs_url="https://docs.runreveal.com/ai-chat/model-context-protocol", + ) From cb4ecc8855ebc662988a7007ce08b19f99390fcc Mon Sep 17 00:00:00 2001 From: topher-lo <46541035+topher-lo@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:04:49 -0400 Subject: [PATCH 2/5] Drop atlassian MCP (does not support http) --- .../integrations/providers/atlassian/mcp.py | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 tracecat/integrations/providers/atlassian/mcp.py diff --git a/tracecat/integrations/providers/atlassian/mcp.py b/tracecat/integrations/providers/atlassian/mcp.py deleted file mode 100644 index 4329c71913..0000000000 --- a/tracecat/integrations/providers/atlassian/mcp.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Atlassian 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 AtlassianMCPProvider(MCPAuthProvider): - """Atlassian OAuth provider for Model Context Protocol integration. - - This provider enables integration with Atlassian's MCP server for: - - Jira: Search, create/update issues, bulk operations - - Confluence: Summarize pages, create content, navigate spaces - - Compass: Create components, query dependencies, manage service landscape - - Combined tasks across all three products - - Permissions are based on the user's existing Atlassian Cloud access. - All actions respect existing project or space-level roles. - OAuth endpoints are automatically discovered from the server. - """ - - id: ClassVar[str] = "atlassian_mcp" - - # MCP server endpoint - OAuth endpoints discovered automatically - _mcp_server_uri: ClassVar[str] = "https://mcp.atlassian.com/v1/sse" - - # No default scopes - authorization server determines based on user permissions - scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) - - # Provider metadata - metadata: ClassVar[ProviderMetadata] = ProviderMetadata( - id="atlassian_mcp", - name="Atlassian MCP", - description=( - "Atlassian Model Context Protocol OAuth provider for " - "Jira, Confluence, and Compass integration" - ), - enabled=True, - requires_config=False, - setup_instructions=( - "Connect to Atlassian MCP to access Jira, Confluence, and Compass. " - "Access is granted only to data you already have permission to view in " - "Atlassian Cloud. All actions respect existing project or space-level roles." - ), - setup_steps=[ - "Click 'Connect' to begin OAuth authorization", - "Authenticate with your Atlassian account", - "Review and approve the OAuth permissions", - "Complete authorization to enable MCP integration with Jira, Confluence, and Compass", - ], - api_docs_url=( - "https://support.atlassian.com/atlassian-rovo-mcp-server/docs/" - "getting-started-with-the-atlassian-remote-mcp-server/" - ), - ) From 96c9e4b2391d2a6af45e504b7409f9d058e18932 Mon Sep 17 00:00:00 2001 From: topher-lo <46541035+topher-lo@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:27:00 -0400 Subject: [PATCH 3/5] fix(integrations): Improve MCP OAuth provider implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix empty scopes handling in BaseOAuthProvider to avoid sending scope="" - Add fallback OAuth endpoint support for providers without discovery - Simplify GitHub and Microsoft MCP providers to use fallback pattern - Ensure all MCP providers handle empty scopes correctly 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- tracecat/integrations/providers/__init__.py | 12 +++- tracecat/integrations/providers/base.py | 35 ++++++++---- tracecat/integrations/providers/github/mcp.py | 52 +++++++++++++++++ tracecat/integrations/providers/linear/mcp.py | 46 +++++++++++++++ .../integrations/providers/microsoft/mcp.py | 56 +++++++++++++++++++ tracecat/integrations/providers/notion/mcp.py | 46 +++++++++++++++ tracecat/integrations/providers/sentry/mcp.py | 48 ++++++++++++++++ 7 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 tracecat/integrations/providers/github/mcp.py create mode 100644 tracecat/integrations/providers/linear/mcp.py create mode 100644 tracecat/integrations/providers/microsoft/mcp.py create mode 100644 tracecat/integrations/providers/notion/mcp.py create mode 100644 tracecat/integrations/providers/sentry/mcp.py diff --git a/tracecat/integrations/providers/__init__.py b/tracecat/integrations/providers/__init__.py index a674699ed6..602fc709c7 100644 --- a/tracecat/integrations/providers/__init__.py +++ b/tracecat/integrations/providers/__init__.py @@ -1,25 +1,33 @@ from typing import Final from tracecat.integrations.models import ProviderKey -from tracecat.integrations.providers.atlassian.mcp import AtlassianMCPProvider from tracecat.integrations.providers.base import BaseOAuthProvider +from tracecat.integrations.providers.github.mcp import GitHubMCPProvider +from tracecat.integrations.providers.linear.mcp import LinearMCPProvider from tracecat.integrations.providers.microsoft.graph import ( MicrosoftGraphACProvider, MicrosoftGraphCCProvider, ) +from tracecat.integrations.providers.microsoft.mcp import MicrosoftLearnMCPProvider from tracecat.integrations.providers.microsoft.teams import ( MicrosoftTeamsACProvider, MicrosoftTeamsCCProvider, ) +from tracecat.integrations.providers.notion.mcp import NotionMCPProvider from tracecat.integrations.providers.runreveal.mcp import RunRevealMCPProvider +from tracecat.integrations.providers.sentry.mcp import SentryMCPProvider _PROVIDER_CLASSES: list[type[BaseOAuthProvider]] = [ MicrosoftGraphACProvider, MicrosoftGraphCCProvider, MicrosoftTeamsACProvider, MicrosoftTeamsCCProvider, + MicrosoftLearnMCPProvider, + GitHubMCPProvider, + LinearMCPProvider, + NotionMCPProvider, RunRevealMCPProvider, - AtlassianMCPProvider, + SentryMCPProvider, ] diff --git a/tracecat/integrations/providers/base.py b/tracecat/integrations/providers/base.py index 241effa83b..935eb7f16d 100644 --- a/tracecat/integrations/providers/base.py +++ b/tracecat/integrations/providers/base.py @@ -65,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()) @@ -284,7 +287,6 @@ class MCPAuthProvider(AuthorizationCodeOAuthProvider): - Optional dynamic client registration """ - # MCP server URI (e.g., "https://api.runreveal.com/mcp") _mcp_server_uri: ClassVar[str] def __init__(self, **kwargs): @@ -312,7 +314,7 @@ def _get_base_url(self) -> str: return f"{parsed.scheme}://{parsed.netloc}" def _discover_oauth_endpoints(self) -> None: - """Discover OAuth endpoints from .well-known configuration.""" + """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" @@ -337,14 +339,25 @@ def _discover_oauth_endpoints(self) -> None: token=self._discovered_token_endpoint, ) except Exception as e: - self.logger.error( - "Failed to discover OAuth endpoints", - provider=self.id, - error=str(e), - discovery_url=discovery_url, - ) - # Let subclasses provide fallback endpoints if needed - if not hasattr(self, "_discovered_auth_endpoint"): + # 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" diff --git a/tracecat/integrations/providers/github/mcp.py b/tracecat/integrations/providers/github/mcp.py new file mode 100644 index 0000000000..d5b82ef6a5 --- /dev/null +++ b/tracecat/integrations/providers/github/mcp.py @@ -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, + 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", + ) diff --git a/tracecat/integrations/providers/linear/mcp.py b/tracecat/integrations/providers/linear/mcp.py new file mode 100644 index 0000000000..3920e1508c --- /dev/null +++ b/tracecat/integrations/providers/linear/mcp.py @@ -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", + ) diff --git a/tracecat/integrations/providers/microsoft/mcp.py b/tracecat/integrations/providers/microsoft/mcp.py new file mode 100644 index 0000000000..f19e733cf8 --- /dev/null +++ b/tracecat/integrations/providers/microsoft/mcp.py @@ -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=[]) + + # 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", + ) diff --git a/tracecat/integrations/providers/notion/mcp.py b/tracecat/integrations/providers/notion/mcp.py new file mode 100644 index 0000000000..1e5b8b7154 --- /dev/null +++ b/tracecat/integrations/providers/notion/mcp.py @@ -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", + ) diff --git a/tracecat/integrations/providers/sentry/mcp.py b/tracecat/integrations/providers/sentry/mcp.py new file mode 100644 index 0000000000..b937cef53d --- /dev/null +++ b/tracecat/integrations/providers/sentry/mcp.py @@ -0,0 +1,48 @@ +"""Sentry 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 SentryMCPProvider(MCPAuthProvider): + """Sentry OAuth provider for Model Context Protocol integration. + + This provider enables integration with Sentry's MCP server for: + - Accessing and managing error tracking and performance monitoring + - Querying issues, events, and performance data + - Managing projects, teams, and organizations + - Analyzing error patterns and performance metrics + + OAuth endpoints are automatically discovered from the server. + """ + + id: ClassVar[str] = "sentry_mcp" + + # MCP server endpoint - OAuth endpoints discovered automatically + _mcp_server_uri: ClassVar[str] = "https://mcp.sentry.dev/mcp" + + # No default scopes - authorization server determines based on user permissions + scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[]) + + # Provider metadata + metadata: ClassVar[ProviderMetadata] = ProviderMetadata( + id="sentry_mcp", + name="Sentry MCP", + description="Sentry Model Context Protocol OAuth provider for error tracking and performance monitoring", + enabled=True, + requires_config=False, + setup_instructions=( + "Connect to Sentry MCP to access error tracking, performance monitoring, and issue management. " + "Permissions are automatically determined based on your Sentry organization access." + ), + setup_steps=[ + "Click 'Connect' to begin OAuth authorization", + "Authenticate with your Sentry account", + "Select your Sentry organization if prompted", + "Review and approve the OAuth client permissions", + "Complete authorization to enable MCP integration", + ], + api_docs_url="https://mcp.sentry.dev", + ) From fc6a5a12da901c281056e9224bcc8c4950a799eb Mon Sep 17 00:00:00 2001 From: topher-lo <46541035+topher-lo@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:39:55 -0400 Subject: [PATCH 4/5] refactor(integrations): Update MCP provider descriptions and API documentation links - Simplified descriptions for GitHub, Linear, Microsoft Learn, Notion, RunReveal, and Sentry MCP providers to better reflect their functionalities. - Updated API documentation URLs for Linear, Microsoft Learn, and Sentry MCP providers to point to the correct resources. --- tracecat/integrations/providers/github/mcp.py | 2 +- tracecat/integrations/providers/linear/mcp.py | 4 ++-- tracecat/integrations/providers/microsoft/mcp.py | 4 ++-- tracecat/integrations/providers/notion/mcp.py | 2 +- tracecat/integrations/providers/runreveal/mcp.py | 2 +- tracecat/integrations/providers/sentry/mcp.py | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tracecat/integrations/providers/github/mcp.py b/tracecat/integrations/providers/github/mcp.py index d5b82ef6a5..a40354826f 100644 --- a/tracecat/integrations/providers/github/mcp.py +++ b/tracecat/integrations/providers/github/mcp.py @@ -35,7 +35,7 @@ class GitHubMCPProvider(MCPAuthProvider): metadata: ClassVar[ProviderMetadata] = ProviderMetadata( id="github_mcp", name="GitHub Copilot MCP", - description="GitHub Copilot Model Context Protocol OAuth provider for AI-powered development assistance", + description="GitHub Copilot MCP provider for repo and code access", enabled=True, requires_config=False, setup_instructions=( diff --git a/tracecat/integrations/providers/linear/mcp.py b/tracecat/integrations/providers/linear/mcp.py index 3920e1508c..ea616ee8f0 100644 --- a/tracecat/integrations/providers/linear/mcp.py +++ b/tracecat/integrations/providers/linear/mcp.py @@ -29,7 +29,7 @@ class LinearMCPProvider(MCPAuthProvider): metadata: ClassVar[ProviderMetadata] = ProviderMetadata( id="linear_mcp", name="Linear MCP", - description="Linear Model Context Protocol OAuth provider for issue tracking and project management", + description="Linear MCP providerfor issue tracking and project management", enabled=True, requires_config=False, setup_instructions=( @@ -42,5 +42,5 @@ class LinearMCPProvider(MCPAuthProvider): "Review and approve the OAuth client permissions", "Complete authorization to enable MCP integration", ], - api_docs_url="https://mcp.linear.app", + api_docs_url="https://linear.app/docs/mcp", ) diff --git a/tracecat/integrations/providers/microsoft/mcp.py b/tracecat/integrations/providers/microsoft/mcp.py index f19e733cf8..b8db4695b8 100644 --- a/tracecat/integrations/providers/microsoft/mcp.py +++ b/tracecat/integrations/providers/microsoft/mcp.py @@ -38,7 +38,7 @@ class MicrosoftLearnMCPProvider(MCPAuthProvider): 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", + description="Microsoft Learn MCP provider for Learn knowlege services.", enabled=True, requires_config=False, setup_instructions=( @@ -52,5 +52,5 @@ class MicrosoftLearnMCPProvider(MCPAuthProvider): "Review and approve the OAuth permissions", "Complete authorization to enable Microsoft Learn MCP integration", ], - api_docs_url="https://github.com/microsoft/mcp", + api_docs_url="https://learn.microsoft.com/en-us/training/support/mcp", ) diff --git a/tracecat/integrations/providers/notion/mcp.py b/tracecat/integrations/providers/notion/mcp.py index 1e5b8b7154..dbd83e09dc 100644 --- a/tracecat/integrations/providers/notion/mcp.py +++ b/tracecat/integrations/providers/notion/mcp.py @@ -29,7 +29,7 @@ class NotionMCPProvider(MCPAuthProvider): metadata: ClassVar[ProviderMetadata] = ProviderMetadata( id="notion_mcp", name="Notion MCP", - description="Notion Model Context Protocol OAuth provider for AI-powered workspace integration", + description="Notion MCP provider for Notion workspace access", enabled=True, requires_config=False, setup_instructions=( diff --git a/tracecat/integrations/providers/runreveal/mcp.py b/tracecat/integrations/providers/runreveal/mcp.py index f514c3ee98..88716df0f6 100644 --- a/tracecat/integrations/providers/runreveal/mcp.py +++ b/tracecat/integrations/providers/runreveal/mcp.py @@ -30,7 +30,7 @@ class RunRevealMCPProvider(MCPAuthProvider): metadata: ClassVar[ProviderMetadata] = ProviderMetadata( id="runreveal_mcp", name="RunReveal MCP", - description="RunReveal Model Context Protocol OAuth provider for security data analysis", + description="RunReveal MCP provider for security data analysis", enabled=True, requires_config=False, setup_instructions=( diff --git a/tracecat/integrations/providers/sentry/mcp.py b/tracecat/integrations/providers/sentry/mcp.py index b937cef53d..a11fb29ca2 100644 --- a/tracecat/integrations/providers/sentry/mcp.py +++ b/tracecat/integrations/providers/sentry/mcp.py @@ -30,11 +30,11 @@ class SentryMCPProvider(MCPAuthProvider): metadata: ClassVar[ProviderMetadata] = ProviderMetadata( id="sentry_mcp", name="Sentry MCP", - description="Sentry Model Context Protocol OAuth provider for error tracking and performance monitoring", + description="Sentry MCP provider for issues tracking and performance monitoring", enabled=True, requires_config=False, setup_instructions=( - "Connect to Sentry MCP to access error tracking, performance monitoring, and issue management. " + "Connect to Sentry MCP to access issues and performance monitoring. " "Permissions are automatically determined based on your Sentry organization access." ), setup_steps=[ @@ -44,5 +44,5 @@ class SentryMCPProvider(MCPAuthProvider): "Review and approve the OAuth client permissions", "Complete authorization to enable MCP integration", ], - api_docs_url="https://mcp.sentry.dev", + api_docs_url="https://docs.sentry.io/product/sentry-mcp/", ) From 4a8d8b2c033bc744208032970d47e96f721ae117 Mon Sep 17 00:00:00 2001 From: topher-lo <46541035+topher-lo@users.noreply.github.com> Date: Tue, 16 Sep 2025 16:45:12 -0400 Subject: [PATCH 5/5] feat(integrations): Enhance MCP provider handling in UI - Introduced a utility function to identify MCP providers, allowing for tailored UI behavior. - Updated the ProviderDetailPage and ProviderConfigForm components to conditionally render content based on MCP status. - Simplified the configuration tab visibility and button actions for MCP providers, ensuring a streamlined user experience. - Added informative messaging for MCP providers to clarify the lack of required client credentials. --- .../integrations/[providerId]/page.tsx | 155 ++++++++++-------- .../src/components/provider-config-form.tsx | 46 ++++++ 2 files changed, 132 insertions(+), 69 deletions(-) diff --git a/frontend/src/app/workspaces/[workspaceId]/integrations/[providerId]/page.tsx b/frontend/src/app/workspaces/[workspaceId]/integrations/[providerId]/page.tsx index 31b44254ec..3ca7c9e284 100644 --- a/frontend/src/app/workspaces/[workspaceId]/integrations/[providerId]/page.tsx +++ b/frontend/src/app/workspaces/[workspaceId]/integrations/[providerId]/page.tsx @@ -103,6 +103,15 @@ export default function ProviderDetailPage() { type ProviderDetailTab = "overview" | "configuration" +/** + * Check if a provider is an MCP (Model Context Protocol) provider. + * MCP providers don't require user-provided client credentials. + */ +function isMCPProvider(provider: ProviderRead): boolean { + // MCP providers follow the naming convention of ending with "_mcp" + return provider.metadata.id.endsWith("_mcp") +} + function ProviderDetailContent({ provider }: { provider: ProviderRead }) { const workspaceId = useWorkspaceId() const router = useRouter() @@ -110,11 +119,14 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) { const [errorMessage, setErrorMessage] = useState("") const [_showConnectPrompt, setShowConnectPrompt] = useState(false) const providerId = provider.metadata.id + const isMCP = isMCPProvider(provider) // Get active tab from URL query params, default to "overview" + // For MCP providers, always use "overview" since there's no configuration tab const activeTab = ( searchParams && - ["overview", "configuration"].includes(searchParams.get("tab") || "") + ["overview", "configuration"].includes(searchParams.get("tab") || "") && + !isMCP // Don't allow configuration tab for MCP providers ? (searchParams.get("tab") ?? "overview") : "overview" ) as ProviderDetailTab @@ -294,11 +306,21 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) { variant="outline" size="sm" className="h-[22px] px-2 py-0 text-xs font-medium" - onClick={() => handleTabChange("configuration")} - disabled={!isEnabled} + onClick={ + isMCP + ? handleOAuthConnect + : () => handleTabChange("configuration") + } + disabled={!isEnabled || (isMCP && connectProviderIsPending)} > - - Configure + {isMCP && connectProviderIsPending ? ( + + ) : isMCP ? ( + + ) : ( + + )} + {isMCP ? "Connect" : "Configure"} )} @@ -307,15 +329,6 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) { - {errorMessage && ( - - - - {errorMessage} - - - )} - {/* Tabs */} Overview - - - Configuration - + {!isMCP && ( + + + Configuration + + )} @@ -514,54 +529,56 @@ function ProviderDetailContent({ provider }: { provider: ProviderRead }) { - - {/* Configuration Form */} -
- - {testConnectionIsPending ? ( - <> - - Testing... - - ) : ( - <> - - Test connection - - )} - - ) : ( - - ) - ) : null - } - /> -
-
+ {!isMCP && ( + + {/* Configuration Form */} +
+ + {testConnectionIsPending ? ( + <> + + Testing... + + ) : ( + <> + + Test connection + + )} + + ) : ( + + ) + ) : null + } + /> +
+
+ )}
) diff --git a/frontend/src/components/provider-config-form.tsx b/frontend/src/components/provider-config-form.tsx index 7b44427e5d..8ebee33d25 100644 --- a/frontend/src/components/provider-config-form.tsx +++ b/frontend/src/components/provider-config-form.tsx @@ -86,6 +86,15 @@ interface ProviderConfigFormProps { additionalButtons?: React.ReactNode } +/** + * Check if a provider is an MCP (Model Context Protocol) provider. + * MCP providers don't require user-provided client credentials. + */ +function isMCPProvider(provider: ProviderRead): boolean { + // MCP providers follow the naming convention of ending with "_mcp" + return provider.metadata.id.endsWith("_mcp") +} + export function ProviderConfigForm({ provider, onSuccess, @@ -98,6 +107,7 @@ export function ProviderConfigForm({ grant_type: grantType, } = provider const workspaceId = useWorkspaceId() + const isMCP = isMCPProvider(provider) const { integration, integrationIsLoading, @@ -171,6 +181,42 @@ export function ProviderConfigForm({ return } + // For MCP providers, show a simplified message + if (isMCP) { + return ( +
+ + + MCP OAuth Provider + + +

+ This is a Model Context Protocol (MCP) provider that uses + server-managed OAuth credentials. No client configuration is + required - simply click "Connect" to authenticate. +

+ {defaultScopes && defaultScopes.length > 0 && ( +
+ +
+ {defaultScopes.map((scope) => ( + + {scope} + + ))} +
+

+ The authorization server will determine the granted scopes + based on your permissions. +

+
+ )} +
+
+
+ ) + } + return (
{/* Current Configuration Summary */}