diff --git a/app/components/@settings/core/ControlPanel.tsx b/app/components/@settings/core/ControlPanel.tsx index df32755..1beebf8 100644 --- a/app/components/@settings/core/ControlPanel.tsx +++ b/app/components/@settings/core/ControlPanel.tsx @@ -38,6 +38,7 @@ import CloudProvidersTab from '~/components/@settings/tabs/providers/cloud/Cloud import ServiceStatusTab from '~/components/@settings/tabs/providers/status/ServiceStatusTab'; import LocalProvidersTab from '~/components/@settings/tabs/providers/local/LocalProvidersTab'; import TaskManagerTab from '~/components/@settings/tabs/task-manager/TaskManagerTab'; +import McpTab from '~/components/@settings/tabs/mcp/McpTab'; interface ControlPanelProps { open: boolean; @@ -81,6 +82,7 @@ const TAB_DESCRIPTIONS: Record = { update: 'Check for updates and release notes', 'task-manager': 'Monitor system resources and processes', 'tab-management': 'Configure visible tabs and their order', + mcp: 'Configure MCP (Model Context Protocol) servers', }; // Beta status for experimental features @@ -335,6 +337,8 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { return ; case 'service-status': return ; + case 'mcp': + return ; default: return null; } diff --git a/app/components/@settings/core/constants.ts b/app/components/@settings/core/constants.ts index ff72a27..1921c5b 100644 --- a/app/components/@settings/core/constants.ts +++ b/app/components/@settings/core/constants.ts @@ -15,6 +15,7 @@ export const TAB_ICONS: Record = { update: 'i-ph:arrow-clockwise-fill', 'task-manager': 'i-ph:chart-line-fill', 'tab-management': 'i-ph:squares-four-fill', + mcp: 'i-ph:hard-drives-bold', }; export const TAB_LABELS: Record = { @@ -32,6 +33,7 @@ export const TAB_LABELS: Record = { update: 'Updates', 'task-manager': 'Task Manager', 'tab-management': 'Tab Management', + mcp: 'MCP Servers', }; export const TAB_DESCRIPTIONS: Record = { @@ -49,6 +51,7 @@ export const TAB_DESCRIPTIONS: Record = { update: 'Check for updates and release notes', 'task-manager': 'Monitor system resources and processes', 'tab-management': 'Configure visible tabs and their order', + mcp: 'Configure MCP (Model Context Protocol) servers', }; export const DEFAULT_TAB_CONFIG = [ @@ -58,18 +61,19 @@ export const DEFAULT_TAB_CONFIG = [ { id: 'cloud-providers', visible: true, window: 'user' as const, order: 2 }, { id: 'local-providers', visible: true, window: 'user' as const, order: 3 }, { id: 'connection', visible: true, window: 'user' as const, order: 4 }, - { id: 'notifications', visible: true, window: 'user' as const, order: 5 }, - { id: 'event-logs', visible: true, window: 'user' as const, order: 6 }, + { id: 'connection', visible: true, window: 'user' as const, order: 5 }, + { id: 'notifications', visible: true, window: 'user' as const, order: 6 }, + { id: 'mcp', visible: true, window: 'user' as const, order: 7 }, // User Window Tabs (In dropdown, initially hidden) - { id: 'profile', visible: false, window: 'user' as const, order: 7 }, - { id: 'settings', visible: false, window: 'user' as const, order: 8 }, - { id: 'task-manager', visible: false, window: 'user' as const, order: 9 }, - { id: 'service-status', visible: false, window: 'user' as const, order: 10 }, + { id: 'profile', visible: false, window: 'user' as const, order: 8 }, + { id: 'settings', visible: false, window: 'user' as const, order: 9 }, + { id: 'task-manager', visible: false, window: 'user' as const, order: 10 }, + { id: 'service-status', visible: false, window: 'user' as const, order: 11 }, // User Window Tabs (Hidden, controlled by TaskManagerTab) - { id: 'debug', visible: false, window: 'user' as const, order: 11 }, - { id: 'update', visible: false, window: 'user' as const, order: 12 }, + { id: 'debug', visible: false, window: 'user' as const, order: 12 }, + { id: 'update', visible: false, window: 'user' as const, order: 13 }, // Developer Window Tabs (All visible by default) { id: 'features', visible: true, window: 'developer' as const, order: 0 }, diff --git a/app/components/@settings/core/types.ts b/app/components/@settings/core/types.ts index 97d4d36..684a30a 100644 --- a/app/components/@settings/core/types.ts +++ b/app/components/@settings/core/types.ts @@ -16,7 +16,8 @@ export type TabType = | 'event-logs' | 'update' | 'task-manager' - | 'tab-management'; + | 'tab-management' + | 'mcp'; export type WindowType = 'user' | 'developer'; @@ -81,6 +82,7 @@ export const TAB_LABELS: Record = { update: 'Updates', 'task-manager': 'Task Manager', 'tab-management': 'Tab Management', + mcp: 'MCP Servers', }; export const categoryLabels: Record = { diff --git a/app/components/@settings/shared/components/TabManagement.tsx b/app/components/@settings/shared/components/TabManagement.tsx index 9ae1601..513e171 100644 --- a/app/components/@settings/shared/components/TabManagement.tsx +++ b/app/components/@settings/shared/components/TabManagement.tsx @@ -26,6 +26,7 @@ const TAB_ICONS: Record = { update: 'i-ph:arrow-clockwise-fill', 'task-manager': 'i-ph:chart-line-fill', 'tab-management': 'i-ph:squares-four-fill', + mcp: 'i-ph:hard-drives-bold', }; // Define which tabs are default in user mode @@ -37,6 +38,7 @@ const DEFAULT_USER_TABS: TabType[] = [ 'connection', 'notifications', 'event-logs', + 'mcp', ]; // Define which tabs can be added to user mode diff --git a/app/components/@settings/tabs/mcp/McpServerList.tsx b/app/components/@settings/tabs/mcp/McpServerList.tsx new file mode 100644 index 0000000..6e15fa9 --- /dev/null +++ b/app/components/@settings/tabs/mcp/McpServerList.tsx @@ -0,0 +1,99 @@ +import type { MCPServer } from '~/lib/services/mcpService'; +import McpStatusBadge from '~/components/@settings/tabs/mcp/McpStatusBadge'; +import McpServerListItem from '~/components/@settings/tabs/mcp/McpServerListItem'; + +type McpServerListProps = { + serverEntries: [string, MCPServer][]; + expandedServer: string | null; + checkingServers: boolean; + onlyShowAvailableServers?: boolean; + toggleServerExpanded: (serverName: string) => void; +}; + +export default function McpServerList({ + serverEntries, + expandedServer, + checkingServers, + onlyShowAvailableServers = false, + toggleServerExpanded, +}: McpServerListProps) { + if (serverEntries.length === 0) { + return

No MCP servers configured

; + } + + const filteredEntries = onlyShowAvailableServers + ? serverEntries.filter(([, s]) => s.status === 'available') + : serverEntries; + + return ( +
+ {filteredEntries.map(([serverName, mcpServer]) => { + const isAvailable = mcpServer.status === 'available'; + const isExpanded = expandedServer === serverName; + const serverTools = isAvailable ? Object.entries(mcpServer.tools) : []; + + return ( +
+
+
+
toggleServerExpanded(serverName)} + className="flex items-center gap-1.5 text-bolt-elements-textPrimary" + aria-expanded={isExpanded} + > +
+ {serverName} +
+ +
+ {mcpServer.config.type === 'sse' || mcpServer.config.type === 'streamable-http' ? ( + {mcpServer.config.url} + ) : ( + + {mcpServer.config.command} {mcpServer.config.args?.join(' ')} + + )} +
+
+ +
+ {checkingServers ? ( + + ) : ( + + )} +
+
+ + {/* Error message */} + {!isAvailable && mcpServer.error && ( +
Error: {mcpServer.error}
+ )} + + {/* Tool list */} + {isExpanded && isAvailable && ( +
+
Available Tools:
+ {serverTools.length === 0 ? ( +
No tools available
+ ) : ( +
+ {serverTools.map(([toolName, toolSchema]) => ( + + ))} +
+ )} +
+ )} +
+ ); + })} +
+ ); +} diff --git a/app/components/@settings/tabs/mcp/McpServerListItem.tsx b/app/components/@settings/tabs/mcp/McpServerListItem.tsx new file mode 100644 index 0000000..7013dde --- /dev/null +++ b/app/components/@settings/tabs/mcp/McpServerListItem.tsx @@ -0,0 +1,70 @@ +import type { Tool } from 'ai'; + +type ParameterProperty = { + type?: string; + description?: string; +}; + +type ToolParameters = { + jsonSchema: { + properties?: Record; + required?: string[]; + }; +}; + +type McpToolProps = { + toolName: string; + toolSchema: Tool; +}; + +export default function McpServerListItem({ toolName, toolSchema }: McpToolProps) { + if (!toolSchema) { + return null; + } + + const parameters = (toolSchema.parameters as ToolParameters)?.jsonSchema.properties || {}; + const requiredParams = (toolSchema.parameters as ToolParameters)?.jsonSchema.required || []; + + return ( +
+
+

+ {toolName} +

+ +

{toolSchema.description || 'No description available'}

+ + {Object.keys(parameters).length > 0 && ( +
+

Parameters:

+
    + {Object.entries(parameters).map(([paramName, paramDetails]) => ( +
  • +
    + + {paramName} + {requiredParams.includes(paramName) && ( + * + )} + + + + +
    + {paramDetails.type && ( + {paramDetails.type} + )} + {paramDetails.description && ( +
    {paramDetails.description}
    + )} +
    +
    +
  • + ))} +
+
+ )} +
+
+ ); +} diff --git a/app/components/@settings/tabs/mcp/McpStatusBadge.tsx b/app/components/@settings/tabs/mcp/McpStatusBadge.tsx new file mode 100644 index 0000000..3cbbb1f --- /dev/null +++ b/app/components/@settings/tabs/mcp/McpStatusBadge.tsx @@ -0,0 +1,37 @@ +import { useMemo } from 'react'; + +export default function McpStatusBadge({ status }: { status: 'checking' | 'available' | 'unavailable' }) { + const { styles, label, icon, ariaLabel } = useMemo(() => { + const base = 'px-2 py-0.5 rounded-full text-xs font-medium flex items-center gap-1 transition-colors'; + + const config = { + checking: { + styles: `${base} bg-blue-100 text-blue-800 dark:bg-blue-900/80 dark:text-blue-200`, + label: 'Checking...', + ariaLabel: 'Checking server status', + icon: