From d800b6aad6fd14f29bb21eac210fae836add591f Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sat, 23 May 2026 04:30:29 -0700 Subject: [PATCH] [eric] split: extract Tools integrations, helpers, ToolSection --- frontend/src/app/pages/Tools/ToolSection.tsx | 190 +++++++++ frontend/src/app/pages/Tools/Tools.tsx | 371 +----------------- frontend/src/app/pages/Tools/integrations.tsx | 165 ++++++++ frontend/src/app/pages/Tools/toolsHelpers.ts | 41 ++ 4 files changed, 399 insertions(+), 368 deletions(-) create mode 100644 frontend/src/app/pages/Tools/ToolSection.tsx create mode 100644 frontend/src/app/pages/Tools/integrations.tsx create mode 100644 frontend/src/app/pages/Tools/toolsHelpers.ts diff --git a/frontend/src/app/pages/Tools/ToolSection.tsx b/frontend/src/app/pages/Tools/ToolSection.tsx new file mode 100644 index 00000000..f5c036a3 --- /dev/null +++ b/frontend/src/app/pages/Tools/ToolSection.tsx @@ -0,0 +1,190 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import Chip from '@mui/material/Chip'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import Collapse from '@mui/material/Collapse'; +import Switch from '@mui/material/Switch'; +import TerminalIcon from '@mui/icons-material/Terminal'; +import DescriptionIcon from '@mui/icons-material/Description'; +import SearchIcon from '@mui/icons-material/Search'; +import QuestionAnswerIcon from '@mui/icons-material/QuestionAnswer'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import ScheduleIcon from '@mui/icons-material/Schedule'; +import MapIcon from '@mui/icons-material/Map'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import BlockIcon from '@mui/icons-material/Block'; +import SecurityIcon from '@mui/icons-material/Security'; +import PanToolIcon from '@mui/icons-material/PanTool'; +import CallSplitIcon from '@mui/icons-material/CallSplit'; +import { BuiltinTool } from '@/shared/state/toolsSlice'; +import { useClaudeTokens } from '@/shared/styles/ThemeContext'; +import { CATEGORY_ORDER } from './toolsHelpers'; + +interface ToolSectionProps { + label: string; + icon: React.ReactElement; + count: number; + open: boolean; + onToggle: () => void; + grouped: Record; + collapsedCategories: Record; + toggleCategory: (cat: string) => void; + expandedBuiltin: string | null; + toggleBuiltinExpand: (name: string) => void; + deferred?: boolean; + builtinPermissions: Record; + onPermissionChange: (toolName: string, policy: string) => void; + onCategoryPermissionChange: (toolNames: string[], policy: string) => void; + enabled: boolean; + onEnabledChange: (enabled: boolean) => void; +} + +const ToolSection: React.FC = ({ + label, icon, count, open, onToggle, + grouped, collapsedCategories, toggleCategory, + expandedBuiltin, toggleBuiltinExpand, deferred, + builtinPermissions, onPermissionChange, onCategoryPermissionChange, + enabled, onEnabledChange, +}) => { + const c = useClaudeTokens(); + + const CATEGORY_META: Record = { + filesystem: { label: 'Filesystem', color: c.status.success, icon: }, + system: { label: 'System', color: c.status.warning, icon: }, + search: { label: 'Search', color: '#3b82f6', icon: }, + interaction: { label: 'Interaction', color: '#a855f7', icon: }, + planning: { label: 'Planning', color: '#ec4899', icon: }, + scheduling: { label: 'Scheduling', color: '#14b8a6', icon: }, + agents: { label: 'Agents', color: '#f97316', icon: }, + }; + + const PermToggle = ({ value, onChange, size = 16 }: { value: string; onChange: (v: string) => void; size?: number }) => ( + e.stopPropagation()}> + onChange('always_allow')} sx={{ p: 0.4, borderRadius: 1, bgcolor: value === 'always_allow' ? `${c.status.success}20` : 'transparent', color: value === 'always_allow' ? c.status.success : c.text.ghost, '&:hover': { bgcolor: `${c.status.success}15`, color: c.status.success } }}> + onChange('ask')} sx={{ p: 0.4, borderRadius: 1, bgcolor: value === 'ask' ? `${c.status.warning}20` : 'transparent', color: value === 'ask' ? c.status.warning : c.text.ghost, '&:hover': { bgcolor: `${c.status.warning}15`, color: c.status.warning } }}> + onChange('deny')} sx={{ p: 0.4, borderRadius: 1, bgcolor: value === 'deny' ? `${c.status.error}20` : 'transparent', color: value === 'deny' ? c.status.error : c.text.ghost, '&:hover': { bgcolor: `${c.status.error}15`, color: c.status.error } }}> + + ); + + const getCatGroupPolicy = (tools: BuiltinTool[]) => { + const policies = tools.map((t) => builtinPermissions[t.name] || 'always_allow'); + if (policies.every((p) => p === 'always_allow')) return 'always_allow'; + if (policies.every((p) => p === 'deny')) return 'deny'; + if (policies.every((p) => p === 'ask')) return 'ask'; + return 'mixed'; + }; + + const allSectionTools = CATEGORY_ORDER.filter((cat) => grouped[cat]).flatMap((cat) => grouped[cat]); + const overallPolicy = getCatGroupPolicy(allSectionTools); + const categoryCount = CATEGORY_ORDER.filter((cat) => grouped[cat]).length; + const sectionDescription = deferred + ? 'On-demand actions loaded via ToolSearch for planning, scheduling, and extended operations' + : 'Built-in Claude Agent SDK actions for file operations, shell commands, and search'; + + const firstSentence = (desc: string) => { + if (!desc) return ''; + const match = desc.match(/^(.+?(?:\.|$))/); + return match ? match[1].trim() : desc.substring(0, 100); + }; + + return ( + + + enabled && onToggle()} + sx={{ display: 'flex', alignItems: 'center', gap: 2, cursor: enabled ? 'pointer' : 'default' }} + > + + {icon} + + + + {label} + + {deferred && ( + + )} + + {sectionDescription} + + e.stopPropagation()}> + onEnabledChange(checked)} + sx={{ + '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, + '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, + }} + /> + + {enabled && ( + + + + )} + + + + + + + + Action Permissions + + + + + {CATEGORY_ORDER.filter((cat) => grouped[cat]).map((cat) => { + const meta = CATEGORY_META[cat] || CATEGORY_META.filesystem; + const catTools = grouped[cat]; + const colKey = `${deferred ? 'd_' : ''}${cat}`; + const isOpen = !collapsedCategories[colKey]; + const catPolicy = getCatGroupPolicy(catTools); + return ( + + toggleCategory(colKey)} + > + + + {meta.label} + + + onCategoryPermissionChange(catTools.map((t) => t.name), v)} /> + + + + {catTools.map((bt) => { + const toolPolicy = builtinPermissions[bt.name] || 'always_allow'; + return ( + + + {bt.display_name || bt.name} + {bt.description && {firstSentence(bt.description)}} + + onPermissionChange(bt.name, v)} size={14} /> + + ); + })} + + + + ); + })} + + + + + ); +}; + +export default ToolSection; diff --git a/frontend/src/app/pages/Tools/Tools.tsx b/frontend/src/app/pages/Tools/Tools.tsx index 5c9a8323..24aef897 100644 --- a/frontend/src/app/pages/Tools/Tools.tsx +++ b/frontend/src/app/pages/Tools/Tools.tsx @@ -86,374 +86,9 @@ import { Skeleton } from '@/app/components/Loading'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; import { API_BASE } from '@/shared/config'; - -interface CredentialField { - key: string; - label: string; - placeholder: string; - helpText?: string; -} - -interface Integration { - id: string; - name: string; - description: string; - mcp_config: Record; - color: string; - website: string; - icon: React.ReactNode; - credentialFields?: CredentialField[]; - connectLabel?: string; - connectInstructions?: string; - authType?: 'none' | 'oauth2' | 'env_vars' | 'device_code'; -} - -const INTEGRATIONS: Integration[] = [ - { - id: 'reddit', - name: 'Reddit', - description: 'Browse subreddits, search posts, get post details, analyze users. No API keys required.', - mcp_config: { type: 'stdio', command: 'npx', args: ['-y', 'reddit-mcp-buddy'] }, - color: '#FF4500', - website: 'https://www.npmjs.com/package/reddit-mcp-buddy', - icon: ( - - - - - ), - }, - { - id: 'youtube', - name: 'YouTube', - description: 'Get video transcripts, details, comments, search videos, and channel stats. Transcripts work with no API key.', - mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@kirbah/mcp-youtube'] }, - color: '#FF0000', - website: 'https://www.npmjs.com/package/@kirbah/mcp-youtube', - icon: ( - - - - - ), - }, - { - id: 'google-workspace', - name: 'Google Workspace', - description: 'Including Google Docs, Sheets, Slides, Calendar, and Gmail. (Gemini CLI extension)', - mcp_config: { type: 'stdio', command: 'uvx', args: ['--from', 'google-workspace-mcp', 'google-workspace-worker'] }, - color: '#4285F4', - website: 'https://developers.google.com/gemini-api/docs/mcp', - icon: ( - - - - - - - ), - authType: 'oauth2', - }, - { - id: 'microsoft-365', - name: 'Microsoft 365', - description: 'Outlook email, Calendar, OneDrive, Excel, OneNote, Tasks, Contacts, Teams, and SharePoint.', - mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@softeria/ms-365-mcp-server'] }, - color: '#0078D4', - website: 'https://www.npmjs.com/package/@softeria/ms-365-mcp-server', - icon: ( - - - - ), - authType: 'device_code', - }, - { - id: 'notion', - name: 'Notion', - description: 'Create, read, and update pages, databases, and blocks in Notion workspaces.', - mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@notionhq/notion-mcp-server'] }, - color: '#000000', - website: 'https://www.npmjs.com/package/@notionhq/notion-mcp-server', - icon: ( - - - - ), - authType: 'oauth2', - }, - { - id: 'airtable', - name: 'Airtable', - description: 'Read and write records, manage bases, tables, and fields in Airtable.', - mcp_config: { type: 'http', url: 'https://mcp.airtable.com/mcp' }, - color: '#18BFFF', - website: 'https://airtable.com/developers/web/api/introduction', - icon: ( - - - - - - - ), - authType: 'oauth2', - }, - { - id: 'hubspot', - name: 'HubSpot', - description: 'CRM contacts, deals, companies, tickets, and more. Free CRM tier included.', - mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@hubspot/mcp-server'] }, - color: '#FF7A59', - website: 'https://developers.hubspot.com/docs/guides/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server', - icon: ( - - - - ), - authType: 'oauth2', - }, - { - id: 'slack', - name: 'Slack', - description: 'Search messages, send messages, read channels, DMs, and threads in Slack workspaces.', - mcp_config: { type: 'stdio', command: 'npx', args: ['-y', 'slack-mcp-server@latest', '--transport', 'stdio'], env: { SLACK_MCP_ADD_MESSAGE_TOOL: 'true' } }, - color: '#4A154B', - website: 'https://github.com/korotovsky/slack-mcp-server', - icon: ( - - - - - - - ), - connectLabel: 'Connect Slack', - connectInstructions: 'On macOS with Chrome, tokens are auto-extracted. Otherwise: open app.slack.com in Chrome → F12 → Console → type `JSON.stringify({token: boot_data.api_token, cookie: document.cookie.match(/d=([^;]+)/)?.[1]})` → copy the result.', - credentialFields: [ - { key: 'SLACK_MCP_XOXC_TOKEN', label: 'Slack Token (xoxc-...)', placeholder: 'Auto-detected via Sign in, or paste xoxc- token' }, - { key: 'SLACK_MCP_XOXD_TOKEN', label: 'Slack Cookie (xoxd-...)', placeholder: 'Auto-detected via Sign in, or paste xoxd- cookie' }, - ], - }, - { - id: 'discord', - name: 'Discord', - description: 'Read messages, send messages, manage channels, and interact with Discord servers via the OpenSwarm bot.', - mcp_config: { type: 'stdio', command: 'python', args: ['-m', 'backend.apps.discord_mcp_shim'] }, - color: '#5865F2', - website: 'https://github.com/barryyip0625/mcp-discord', - icon: ( - - - - ), - authType: 'oauth2', - }, -]; - -const CATEGORY_ORDER = ['filesystem', 'system', 'search', 'interaction', 'agents', 'planning', 'scheduling']; - -interface ToolForm { - name: string; - description: string; - command: string; -} - -const emptyForm: ToolForm = { - name: '', - description: '', - command: '', -}; - -function cleanServerName(name: string): string { - const parts = name.split('/'); - return parts[parts.length - 1]; -} - -function serverToToolForm(srv: McpServer): ToolForm { - return { - name: srv.title || cleanServerName(srv.name), - description: srv.description, - command: '', - }; -} - -function serverToMcpConfig(srv: McpServer): Record { - if (srv.remoteUrl) { - return { type: srv.remoteType === 'sse' ? 'sse' : 'http', url: srv.remoteUrl }; - } - if (srv.repositoryUrl && srv.repositoryUrl.includes('github.com')) { - const match = srv.repositoryUrl.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:\/|$)/); - if (match) { - return { type: 'stdio', command: 'npx', args: ['-y', `github:${match[1]}`] }; - } - } - return {}; -} - -interface ToolSectionProps { - label: string; - icon: React.ReactElement; - count: number; - open: boolean; - onToggle: () => void; - grouped: Record; - collapsedCategories: Record; - toggleCategory: (cat: string) => void; - expandedBuiltin: string | null; - toggleBuiltinExpand: (name: string) => void; - deferred?: boolean; - builtinPermissions: Record; - onPermissionChange: (toolName: string, policy: string) => void; - onCategoryPermissionChange: (toolNames: string[], policy: string) => void; - enabled: boolean; - onEnabledChange: (enabled: boolean) => void; -} - -const ToolSection: React.FC = ({ - label, icon, count, open, onToggle, - grouped, collapsedCategories, toggleCategory, - expandedBuiltin, toggleBuiltinExpand, deferred, - builtinPermissions, onPermissionChange, onCategoryPermissionChange, - enabled, onEnabledChange, -}) => { - const c = useClaudeTokens(); - - const CATEGORY_META: Record = { - filesystem: { label: 'Filesystem', color: c.status.success, icon: }, - system: { label: 'System', color: c.status.warning, icon: }, - search: { label: 'Search', color: '#3b82f6', icon: }, - interaction: { label: 'Interaction', color: '#a855f7', icon: }, - planning: { label: 'Planning', color: '#ec4899', icon: }, - scheduling: { label: 'Scheduling', color: '#14b8a6', icon: }, - agents: { label: 'Agents', color: '#f97316', icon: }, - }; - - const PermToggle = ({ value, onChange, size = 16 }: { value: string; onChange: (v: string) => void; size?: number }) => ( - e.stopPropagation()}> - onChange('always_allow')} sx={{ p: 0.4, borderRadius: 1, bgcolor: value === 'always_allow' ? `${c.status.success}20` : 'transparent', color: value === 'always_allow' ? c.status.success : c.text.ghost, '&:hover': { bgcolor: `${c.status.success}15`, color: c.status.success } }}> - onChange('ask')} sx={{ p: 0.4, borderRadius: 1, bgcolor: value === 'ask' ? `${c.status.warning}20` : 'transparent', color: value === 'ask' ? c.status.warning : c.text.ghost, '&:hover': { bgcolor: `${c.status.warning}15`, color: c.status.warning } }}> - onChange('deny')} sx={{ p: 0.4, borderRadius: 1, bgcolor: value === 'deny' ? `${c.status.error}20` : 'transparent', color: value === 'deny' ? c.status.error : c.text.ghost, '&:hover': { bgcolor: `${c.status.error}15`, color: c.status.error } }}> - - ); - - const getCatGroupPolicy = (tools: BuiltinTool[]) => { - const policies = tools.map((t) => builtinPermissions[t.name] || 'always_allow'); - if (policies.every((p) => p === 'always_allow')) return 'always_allow'; - if (policies.every((p) => p === 'deny')) return 'deny'; - if (policies.every((p) => p === 'ask')) return 'ask'; - return 'mixed'; - }; - - const allSectionTools = CATEGORY_ORDER.filter((cat) => grouped[cat]).flatMap((cat) => grouped[cat]); - const overallPolicy = getCatGroupPolicy(allSectionTools); - const categoryCount = CATEGORY_ORDER.filter((cat) => grouped[cat]).length; - const sectionDescription = deferred - ? 'On-demand actions loaded via ToolSearch for planning, scheduling, and extended operations' - : 'Built-in Claude Agent SDK actions for file operations, shell commands, and search'; - - const firstSentence = (desc: string) => { - if (!desc) return ''; - const match = desc.match(/^(.+?(?:\.|$))/); - return match ? match[1].trim() : desc.substring(0, 100); - }; - - return ( - - - enabled && onToggle()} - sx={{ display: 'flex', alignItems: 'center', gap: 2, cursor: enabled ? 'pointer' : 'default' }} - > - - {icon} - - - - {label} - - {deferred && ( - - )} - - {sectionDescription} - - e.stopPropagation()}> - onEnabledChange(checked)} - sx={{ - '& .MuiSwitch-switchBase.Mui-checked': { color: c.accent.primary }, - '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: c.accent.primary }, - }} - /> - - {enabled && ( - - - - )} - - - - - - - - Action Permissions - - - - - {CATEGORY_ORDER.filter((cat) => grouped[cat]).map((cat) => { - const meta = CATEGORY_META[cat] || CATEGORY_META.filesystem; - const catTools = grouped[cat]; - const colKey = `${deferred ? 'd_' : ''}${cat}`; - const isOpen = !collapsedCategories[colKey]; - const catPolicy = getCatGroupPolicy(catTools); - return ( - - toggleCategory(colKey)} - > - - - {meta.label} - - - onCategoryPermissionChange(catTools.map((t) => t.name), v)} /> - - - - {catTools.map((bt) => { - const toolPolicy = builtinPermissions[bt.name] || 'always_allow'; - return ( - - - {bt.display_name || bt.name} - {bt.description && {firstSentence(bt.description)}} - - onPermissionChange(bt.name, v)} size={14} /> - - ); - })} - - - - ); - })} - - - - - ); -}; - +import { Integration, INTEGRATIONS } from './integrations'; +import { CATEGORY_ORDER, ToolForm, emptyForm, cleanServerName, serverToToolForm, serverToMcpConfig } from './toolsHelpers'; +import ToolSection from './ToolSection'; const Tools: React.FC = () => { const c = useClaudeTokens(); diff --git a/frontend/src/app/pages/Tools/integrations.tsx b/frontend/src/app/pages/Tools/integrations.tsx new file mode 100644 index 00000000..727dfad2 --- /dev/null +++ b/frontend/src/app/pages/Tools/integrations.tsx @@ -0,0 +1,165 @@ +import React from 'react'; + +export interface CredentialField { + key: string; + label: string; + placeholder: string; + helpText?: string; +} + +export interface Integration { + id: string; + name: string; + description: string; + mcp_config: Record; + color: string; + website: string; + icon: React.ReactNode; + credentialFields?: CredentialField[]; + connectLabel?: string; + connectInstructions?: string; + authType?: 'none' | 'oauth2' | 'env_vars' | 'device_code'; +} + +export const INTEGRATIONS: Integration[] = [ + { + id: 'reddit', + name: 'Reddit', + description: 'Browse subreddits, search posts, get post details, analyze users. No API keys required.', + mcp_config: { type: 'stdio', command: 'npx', args: ['-y', 'reddit-mcp-buddy'] }, + color: '#FF4500', + website: 'https://www.npmjs.com/package/reddit-mcp-buddy', + icon: ( + + + + + ), + }, + { + id: 'youtube', + name: 'YouTube', + description: 'Get video transcripts, details, comments, search videos, and channel stats. Transcripts work with no API key.', + mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@kirbah/mcp-youtube'] }, + color: '#FF0000', + website: 'https://www.npmjs.com/package/@kirbah/mcp-youtube', + icon: ( + + + + + ), + }, + { + id: 'google-workspace', + name: 'Google Workspace', + description: 'Including Google Docs, Sheets, Slides, Calendar, and Gmail. (Gemini CLI extension)', + mcp_config: { type: 'stdio', command: 'uvx', args: ['--from', 'google-workspace-mcp', 'google-workspace-worker'] }, + color: '#4285F4', + website: 'https://developers.google.com/gemini-api/docs/mcp', + icon: ( + + + + + + + ), + authType: 'oauth2', + }, + { + id: 'microsoft-365', + name: 'Microsoft 365', + description: 'Outlook email, Calendar, OneDrive, Excel, OneNote, Tasks, Contacts, Teams, and SharePoint.', + mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@softeria/ms-365-mcp-server'] }, + color: '#0078D4', + website: 'https://www.npmjs.com/package/@softeria/ms-365-mcp-server', + icon: ( + + + + ), + authType: 'device_code', + }, + { + id: 'notion', + name: 'Notion', + description: 'Create, read, and update pages, databases, and blocks in Notion workspaces.', + mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@notionhq/notion-mcp-server'] }, + color: '#000000', + website: 'https://www.npmjs.com/package/@notionhq/notion-mcp-server', + icon: ( + + + + ), + authType: 'oauth2', + }, + { + id: 'airtable', + name: 'Airtable', + description: 'Read and write records, manage bases, tables, and fields in Airtable.', + mcp_config: { type: 'http', url: 'https://mcp.airtable.com/mcp' }, + color: '#18BFFF', + website: 'https://airtable.com/developers/web/api/introduction', + icon: ( + + + + + + + ), + authType: 'oauth2', + }, + { + id: 'hubspot', + name: 'HubSpot', + description: 'CRM contacts, deals, companies, tickets, and more. Free CRM tier included.', + mcp_config: { type: 'stdio', command: 'npx', args: ['-y', '@hubspot/mcp-server'] }, + color: '#FF7A59', + website: 'https://developers.hubspot.com/docs/guides/apps/developer-platform/build-apps/integrate-with-the-remote-hubspot-mcp-server', + icon: ( + + + + ), + authType: 'oauth2', + }, + { + id: 'slack', + name: 'Slack', + description: 'Search messages, send messages, read channels, DMs, and threads in Slack workspaces.', + mcp_config: { type: 'stdio', command: 'npx', args: ['-y', 'slack-mcp-server@latest', '--transport', 'stdio'], env: { SLACK_MCP_ADD_MESSAGE_TOOL: 'true' } }, + color: '#4A154B', + website: 'https://github.com/korotovsky/slack-mcp-server', + icon: ( + + + + + + + ), + connectLabel: 'Connect Slack', + connectInstructions: 'On macOS with Chrome, tokens are auto-extracted. Otherwise: open app.slack.com in Chrome → F12 → Console → type `JSON.stringify({token: boot_data.api_token, cookie: document.cookie.match(/d=([^;]+)/)?.[1]})` → copy the result.', + credentialFields: [ + { key: 'SLACK_MCP_XOXC_TOKEN', label: 'Slack Token (xoxc-...)', placeholder: 'Auto-detected via Sign in, or paste xoxc- token' }, + { key: 'SLACK_MCP_XOXD_TOKEN', label: 'Slack Cookie (xoxd-...)', placeholder: 'Auto-detected via Sign in, or paste xoxd- cookie' }, + ], + }, + { + id: 'discord', + name: 'Discord', + description: 'Read messages, send messages, manage channels, and interact with Discord servers via the OpenSwarm bot.', + mcp_config: { type: 'stdio', command: 'python', args: ['-m', 'backend.apps.discord_mcp_shim'] }, + color: '#5865F2', + website: 'https://github.com/barryyip0625/mcp-discord', + icon: ( + + + + ), + authType: 'oauth2', + }, +]; diff --git a/frontend/src/app/pages/Tools/toolsHelpers.ts b/frontend/src/app/pages/Tools/toolsHelpers.ts new file mode 100644 index 00000000..bd0af9b5 --- /dev/null +++ b/frontend/src/app/pages/Tools/toolsHelpers.ts @@ -0,0 +1,41 @@ +import { McpServer } from '@/shared/state/mcpRegistrySlice'; + +export const CATEGORY_ORDER = ['filesystem', 'system', 'search', 'interaction', 'agents', 'planning', 'scheduling']; + +export interface ToolForm { + name: string; + description: string; + command: string; +} + +export const emptyForm: ToolForm = { + name: '', + description: '', + command: '', +}; + +export function cleanServerName(name: string): string { + const parts = name.split('/'); + return parts[parts.length - 1]; +} + +export function serverToToolForm(srv: McpServer): ToolForm { + return { + name: srv.title || cleanServerName(srv.name), + description: srv.description, + command: '', + }; +} + +export function serverToMcpConfig(srv: McpServer): Record { + if (srv.remoteUrl) { + return { type: srv.remoteType === 'sse' ? 'sse' : 'http', url: srv.remoteUrl }; + } + if (srv.repositoryUrl && srv.repositoryUrl.includes('github.com')) { + const match = srv.repositoryUrl.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:\/|$)/); + if (match) { + return { type: 'stdio', command: 'npx', args: ['-y', `github:${match[1]}`] }; + } + } + return {}; +}