Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tracecat/integrations/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@

from tracecat.integrations.models import ProviderKey
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,
SentryMCPProvider,
]


Expand Down
117 changes: 116 additions & 1 deletion tracecat/integrations/providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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()
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MCPAuthProvider constructor 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
Address the following comment on tracecat/integrations/providers/base.py at line 298:

<comment>The `MCPAuthProvider` constructor performs a synchronous, blocking HTTP request for endpoint discovery. Because provider instances are created for each API request, this will block the server&#39;s event loop and cause severe performance degradation for the entire application.</comment>

<file context>
@@ -269,3 +274,113 @@ async def get_client_credentials_token(self) -&gt; TokenResponse:
+        self.logger = logger.bind(service=f&quot;{self.__class__.__name__}&quot;)
+
+        # Discover OAuth endpoints before parent initialization
+        self._discover_oauth_endpoints()
+        super().__init__(**kwargs)
+
</file context>

[internal] Confidence score: 10/10

[internal] Posted by: System Design Agent

Fix with Cubic

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}"
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discovery base URL omits the path component of _mcp_server_uri, breaking well-known discovery for path-based issuers. Include parsed.path in the base URL.

Prompt for AI agents
Address the following comment on tracecat/integrations/providers/base.py at line 314:

<comment>Discovery base URL omits the path component of `_mcp_server_uri`, breaking well-known discovery for path-based issuers. Include `parsed.path` in the base URL.</comment>

<file context>
@@ -269,3 +274,113 @@ async def get_client_credentials_token(self) -&gt; TokenResponse:
+    def _get_base_url(self) -&gt; str:
+        &quot;&quot;&quot;Extract base URL from MCP server URI.&quot;&quot;&quot;
+        parsed = urlparse(self._mcp_server_uri)
+        return f&quot;{parsed.scheme}://{parsed.netloc}&quot;
+
+    def _discover_oauth_endpoints(self) -&gt; None:
</file context>

[internal] Confidence score: 9/10

[internal] Posted by: General AI Review Agent

Suggested change
return f"{parsed.scheme}://{parsed.netloc}"
return f"{parsed.scheme}://{parsed.netloc}{parsed.path.rstrip('/')}"
Fix with Cubic


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
52 changes: 52 additions & 0 deletions tracecat/integrations/providers/github/mcp.py
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,
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

The 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
Address the following comment on tracecat/integrations/providers/github/mcp.py at line 40:

<comment>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.</comment>

<file context>
@@ -0,0 +1,52 @@
+        name=&quot;GitHub Copilot MCP&quot;,
+        description=&quot;GitHub Copilot Model Context Protocol OAuth provider for AI-powered development assistance&quot;,
+        enabled=True,
+        requires_config=False,
+        setup_instructions=(
+            &quot;Connect to GitHub Copilot MCP to enable AI-powered code assistance and repository context. &quot;
</file context>

[internal] Confidence score: 8/10

[internal] Posted by: General AI Review Agent

Suggested change
requires_config=False,
requires_config=True,
Fix with Cubic

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",
)
46 changes: 46 additions & 0 deletions tracecat/integrations/providers/linear/mcp.py
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",
)
56 changes: 56 additions & 0 deletions tracecat/integrations/providers/microsoft/mcp.py
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=[])
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

The 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
Address the following comment on tracecat/integrations/providers/microsoft/mcp.py at line 35:

<comment>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.</comment>

<file context>
@@ -0,0 +1,56 @@
+    )
+
+    # No default scopes - authorization server determines based on user permissions
+    scopes: ClassVar[ProviderScopes] = ProviderScopes(default=[])
+
+    # Provider metadata
</file context>

[internal] Confidence score: 7/10

[internal] Posted by: General AI Review Agent

Fix with Cubic


# 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",
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 18, 2025

Choose a reason for hiding this comment

The 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.

    DEV MODE: This violation would have been filtered out by GPT-5.

Reasoning:
GPT-5: API docs URL concern is speculative and low impact; not a clear, high-confidence technical issue.

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
Address the following comment on tracecat/integrations/providers/microsoft/mcp.py at line 55:

<comment>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.*

        DEV MODE: This violation would have been filtered out by GPT-5.
Reasoning:
• **GPT-5**: API docs URL concern is speculative and low impact; not a clear, high-confidence technical issue.

• **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</comment>

<file context>
@@ -0,0 +1,56 @@
+            &quot;Review and approve the OAuth permissions&quot;,
+            &quot;Complete authorization to enable Microsoft Learn MCP integration&quot;,
+        ],
+        api_docs_url=&quot;https://github.com/microsoft/mcp&quot;,
+    )
</file context>

[internal] Confidence score: 6/10

[internal] Posted by: General AI Review Agent

Fix with Cubic

)
46 changes: 46 additions & 0 deletions tracecat/integrations/providers/notion/mcp.py
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",
)
Loading