mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
161 lines
6.1 KiB
Python
161 lines
6.1 KiB
Python
"""Registry of MCP services with OAuth support.
|
|
|
|
Each entry maps a URL-safe service key to its MCP server endpoint and
|
|
authentication configuration. Services with ``supports_dcr=True`` use
|
|
RFC 7591 Dynamic Client Registration (the MCP server issues its own
|
|
credentials); the rest use pre-configured credentials via env vars.
|
|
|
|
``allowed_tools`` whitelists which MCP tools to expose to the agent.
|
|
An empty list means "load every tool the server advertises" (used for
|
|
user-managed generic MCP servers). Service-specific entries should
|
|
curate this list to keep the agent's tool count low and selection
|
|
accuracy high.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from app.db import SearchSourceConnectorType
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MCPServiceConfig:
|
|
name: str
|
|
mcp_url: str
|
|
connector_type: str
|
|
supports_dcr: bool = True
|
|
oauth_discovery_origin: str | None = None
|
|
client_id_env: str | None = None
|
|
client_secret_env: str | None = None
|
|
scopes: list[str] = field(default_factory=list)
|
|
scope_param: str = "scope"
|
|
auth_endpoint_override: str | None = None
|
|
token_endpoint_override: str | None = None
|
|
allowed_tools: list[str] = field(default_factory=list)
|
|
readonly_tools: frozenset[str] = field(default_factory=frozenset)
|
|
account_metadata_keys: list[str] = field(default_factory=list)
|
|
"""``connector.config`` keys exposed by ``get_connected_accounts``.
|
|
|
|
Only listed keys are returned to the LLM — tokens and secrets are
|
|
never included. Every service should at least have its
|
|
``display_name`` populated during OAuth; additional service-specific
|
|
fields (e.g. Jira ``cloud_id``) are listed here so the LLM can pass
|
|
them to action tools.
|
|
"""
|
|
|
|
|
|
MCP_SERVICES: dict[str, MCPServiceConfig] = {
|
|
"linear": MCPServiceConfig(
|
|
name="Linear",
|
|
mcp_url="https://mcp.linear.app/mcp",
|
|
connector_type="LINEAR_CONNECTOR",
|
|
allowed_tools=[
|
|
"list_issues",
|
|
"get_issue",
|
|
"save_issue",
|
|
],
|
|
readonly_tools=frozenset({"list_issues", "get_issue"}),
|
|
account_metadata_keys=["organization_name", "organization_url_key"],
|
|
),
|
|
"jira": MCPServiceConfig(
|
|
name="Jira",
|
|
mcp_url="https://mcp.atlassian.com/v1/mcp",
|
|
connector_type="JIRA_CONNECTOR",
|
|
allowed_tools=[
|
|
"getAccessibleAtlassianResources",
|
|
"searchJiraIssuesUsingJql",
|
|
"getVisibleJiraProjects",
|
|
"getJiraProjectIssueTypesMetadata",
|
|
"createJiraIssue",
|
|
"editJiraIssue",
|
|
],
|
|
readonly_tools=frozenset({
|
|
"getAccessibleAtlassianResources",
|
|
"searchJiraIssuesUsingJql",
|
|
"getVisibleJiraProjects",
|
|
"getJiraProjectIssueTypesMetadata",
|
|
}),
|
|
account_metadata_keys=["cloud_id", "site_name", "base_url"],
|
|
),
|
|
"clickup": MCPServiceConfig(
|
|
name="ClickUp",
|
|
mcp_url="https://mcp.clickup.com/mcp",
|
|
connector_type="CLICKUP_CONNECTOR",
|
|
allowed_tools=[
|
|
"clickup_search",
|
|
"clickup_get_task",
|
|
],
|
|
readonly_tools=frozenset({"clickup_search", "clickup_get_task"}),
|
|
account_metadata_keys=["workspace_id", "workspace_name"],
|
|
),
|
|
"slack": MCPServiceConfig(
|
|
name="Slack",
|
|
mcp_url="https://mcp.slack.com/mcp",
|
|
connector_type="SLACK_CONNECTOR",
|
|
supports_dcr=False,
|
|
client_id_env="SLACK_CLIENT_ID",
|
|
client_secret_env="SLACK_CLIENT_SECRET",
|
|
auth_endpoint_override="https://slack.com/oauth/v2_user/authorize",
|
|
token_endpoint_override="https://slack.com/api/oauth.v2.user.access",
|
|
scopes=[
|
|
"search:read.public", "search:read.private", "search:read.mpim", "search:read.im",
|
|
"channels:history", "groups:history", "mpim:history", "im:history",
|
|
],
|
|
allowed_tools=[
|
|
"slack_search_channels",
|
|
"slack_read_channel",
|
|
"slack_read_thread",
|
|
],
|
|
readonly_tools=frozenset({"slack_search_channels", "slack_read_channel", "slack_read_thread"}),
|
|
# TODO: oauth.v2.user.access only returns team.id, not team.name.
|
|
# To populate team_name, either add "team:read" scope and call
|
|
# GET /api/team.info during OAuth callback, or switch to oauth.v2.access.
|
|
account_metadata_keys=["team_id", "team_name"],
|
|
),
|
|
"airtable": MCPServiceConfig(
|
|
name="Airtable",
|
|
mcp_url="https://mcp.airtable.com/mcp",
|
|
connector_type="AIRTABLE_CONNECTOR",
|
|
supports_dcr=False,
|
|
oauth_discovery_origin="https://airtable.com",
|
|
client_id_env="AIRTABLE_CLIENT_ID",
|
|
client_secret_env="AIRTABLE_CLIENT_SECRET",
|
|
scopes=["data.records:read", "schema.bases:read"],
|
|
allowed_tools=[
|
|
"list_bases",
|
|
"list_tables_for_base",
|
|
"list_records_for_table",
|
|
],
|
|
readonly_tools=frozenset({"list_bases", "list_tables_for_base", "list_records_for_table"}),
|
|
account_metadata_keys=["user_id", "user_email"],
|
|
),
|
|
}
|
|
|
|
_CONNECTOR_TYPE_TO_SERVICE: dict[str, MCPServiceConfig] = {
|
|
svc.connector_type: svc for svc in MCP_SERVICES.values()
|
|
}
|
|
|
|
LIVE_CONNECTOR_TYPES: frozenset[SearchSourceConnectorType] = frozenset({
|
|
SearchSourceConnectorType.SLACK_CONNECTOR,
|
|
SearchSourceConnectorType.TEAMS_CONNECTOR,
|
|
SearchSourceConnectorType.LINEAR_CONNECTOR,
|
|
SearchSourceConnectorType.JIRA_CONNECTOR,
|
|
SearchSourceConnectorType.CLICKUP_CONNECTOR,
|
|
SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR,
|
|
SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR,
|
|
SearchSourceConnectorType.AIRTABLE_CONNECTOR,
|
|
SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
|
|
SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
|
|
SearchSourceConnectorType.DISCORD_CONNECTOR,
|
|
SearchSourceConnectorType.LUMA_CONNECTOR,
|
|
})
|
|
|
|
|
|
def get_service(key: str) -> MCPServiceConfig | None:
|
|
return MCP_SERVICES.get(key)
|
|
|
|
|
|
def get_service_by_connector_type(connector_type: str) -> MCPServiceConfig | None:
|
|
"""Look up an MCP service config by its ``connector_type`` enum value."""
|
|
return _CONNECTOR_TYPE_TO_SERVICE.get(connector_type)
|