From 591c84572d152f3e6e0fbbac0dda8c5ad2fd90ef Mon Sep 17 00:00:00 2001 From: Roamin <97863888+roaminro@users.noreply.github.com> Date: Sat, 5 Jul 2025 22:11:06 +0000 Subject: [PATCH 01/11] chore(deps): update ai package to 4.3.16 --- package.json | 2 +- pnpm-lock.yaml | 79 +++++++++++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 9051a74..dd7d89b 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@xterm/addon-fit": "^0.10.0", "@xterm/addon-web-links": "^0.11.0", "@xterm/xterm": "^5.5.0", - "ai": "4.1.2", + "ai": "4.3.16", "chalk": "^5.4.1", "chart.js": "^4.4.7", "class-variance-authority": "^0.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4f026d..8217c55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,8 +183,8 @@ importers: specifier: ^5.5.0 version: 5.5.0 ai: - specifier: 4.1.2 - version: 4.1.2(react@18.3.1)(zod@3.24.2) + specifier: 4.3.16 + version: 4.3.16(react@18.3.1)(zod@3.24.2) chalk: specifier: ^5.4.1 version: 5.4.1 @@ -585,6 +585,12 @@ packages: zod: optional: true + '@ai-sdk/provider-utils@2.2.8': + resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.23.8 + '@ai-sdk/provider@0.0.12': resolution: {integrity: sha512-oOwPQD8i2Ynpn22cur4sk26FW3mSy6t6/X/K1Ay2yGBKYiSpRyLfObhOrZEGsXDx+3euKy4nEZ193R36NM+tpQ==} engines: {node: '>=18'} @@ -609,26 +615,25 @@ packages: resolution: {integrity: sha512-hwj/gFNxpDgEfTaYzCYoslmw01IY9kWLKl/wf8xuPvHtQIzlfXWmmUwc8PnCwxyt8cKzIuV0dfUghCf68HQ0SA==} engines: {node: '>=18'} - '@ai-sdk/react@1.1.2': - resolution: {integrity: sha512-bBcRsDaNHzCKSIBbPngMeqbnwZ1RFadXQo9XzHoGrvLANYRwuphGNB8XTXYVLC/eXjoaGVGw2wWf/TYigEnCuA==} + '@ai-sdk/provider@1.1.3': + resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} + engines: {node: '>=18'} + + '@ai-sdk/react@1.2.12': + resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc - zod: ^3.0.0 + zod: ^3.23.8 peerDependenciesMeta: - react: - optional: true zod: optional: true - '@ai-sdk/ui-utils@1.1.2': - resolution: {integrity: sha512-+0kfBF4Y9jmlg1KlbNKIxchmXx9PzuReSpgRNWhpU10vfl1eeer4xK/XL2qHnzAWhsMFe/SVZXJIQObk44zNEQ==} + '@ai-sdk/ui-utils@1.2.11': + resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==} engines: {node: '>=18'} peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true + zod: ^3.23.8 '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} @@ -3584,17 +3589,15 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ai@4.1.2: - resolution: {integrity: sha512-11efhPorWFphIpeCgjW6r/jk4wB5RWUGjxayHblBXCq6YEc7o5ki7vlmSnESprsDkMEfmONBWb/xM8pWjR5O2g==} + ai@4.3.16: + resolution: {integrity: sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc - zod: ^3.0.0 + zod: ^3.23.8 peerDependenciesMeta: react: optional: true - zod: - optional: true ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} @@ -8262,6 +8265,13 @@ snapshots: optionalDependencies: zod: 3.24.2 + '@ai-sdk/provider-utils@2.2.8(zod@3.24.2)': + dependencies: + '@ai-sdk/provider': 1.1.3 + nanoid: 3.3.11 + secure-json-parse: 2.7.0 + zod: 3.24.2 + '@ai-sdk/provider@0.0.12': dependencies: json-schema: 0.4.0 @@ -8286,23 +8296,26 @@ snapshots: dependencies: json-schema: 0.4.0 - '@ai-sdk/react@1.1.2(react@18.3.1)(zod@3.24.2)': + '@ai-sdk/provider@1.1.3': dependencies: - '@ai-sdk/provider-utils': 2.1.2(zod@3.24.2) - '@ai-sdk/ui-utils': 1.1.2(zod@3.24.2) + json-schema: 0.4.0 + + '@ai-sdk/react@1.2.12(react@18.3.1)(zod@3.24.2)': + dependencies: + '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) + '@ai-sdk/ui-utils': 1.2.11(zod@3.24.2) + react: 18.3.1 swr: 2.3.3(react@18.3.1) throttleit: 2.1.0 optionalDependencies: - react: 18.3.1 zod: 3.24.2 - '@ai-sdk/ui-utils@1.1.2(zod@3.24.2)': + '@ai-sdk/ui-utils@1.2.11(zod@3.24.2)': dependencies: - '@ai-sdk/provider': 1.0.6 - '@ai-sdk/provider-utils': 2.1.2(zod@3.24.2) - zod-to-json-schema: 3.24.5(zod@3.24.2) - optionalDependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) zod: 3.24.2 + zod-to-json-schema: 3.24.5(zod@3.24.2) '@ampproject/remapping@2.3.0': dependencies: @@ -11713,17 +11726,17 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ai@4.1.2(react@18.3.1)(zod@3.24.2): + ai@4.3.16(react@18.3.1)(zod@3.24.2): dependencies: - '@ai-sdk/provider': 1.0.6 - '@ai-sdk/provider-utils': 2.1.2(zod@3.24.2) - '@ai-sdk/react': 1.1.2(react@18.3.1)(zod@3.24.2) - '@ai-sdk/ui-utils': 1.1.2(zod@3.24.2) + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) + '@ai-sdk/react': 1.2.12(react@18.3.1)(zod@3.24.2) + '@ai-sdk/ui-utils': 1.2.11(zod@3.24.2) '@opentelemetry/api': 1.9.0 jsondiffpatch: 0.6.0 + zod: 3.24.2 optionalDependencies: react: 18.3.1 - zod: 3.24.2 ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: From 5de162eec8c792be4f91d9db48bb5dbe26f3e56d Mon Sep 17 00:00:00 2001 From: Roamin <97863888+roaminro@users.noreply.github.com> Date: Thu, 10 Jul 2025 17:54:15 +0000 Subject: [PATCH 02/11] feat(mcp): add Model Context Protocol integration Add MCP integration including: - New MCP settings tab with server configuration - Tool invocation UI components - API endpoints for MCP management - Integration with chat system for tool execution - Example configurations --- .../@settings/core/ControlPanel.tsx | 4 + app/components/@settings/core/constants.ts | 20 +- app/components/@settings/core/types.ts | 4 +- .../shared/components/TabManagement.tsx | 2 + .../@settings/tabs/mcp/McpServerList.tsx | 99 ++++ .../@settings/tabs/mcp/McpServerListItem.tsx | 70 +++ .../@settings/tabs/mcp/McpStatusBadge.tsx | 37 ++ app/components/@settings/tabs/mcp/McpTab.tsx | 239 +++++++++ app/components/chat/AssistantMessage.tsx | 28 ++ app/components/chat/BaseChat.tsx | 5 + app/components/chat/Chat.client.tsx | 165 ++++--- app/components/chat/ChatBox.tsx | 2 + app/components/chat/MCPTools.tsx | 129 +++++ app/components/chat/Messages.client.tsx | 5 +- app/components/chat/ToolInvocations.tsx | 367 ++++++++++++++ app/lib/services/mcpService.ts | 457 ++++++++++++++++++ app/lib/stores/mcp.ts | 115 +++++ app/routes/api.chat.ts | 70 +-- app/routes/api.mcp-check.ts | 16 + app/routes/api.mcp-update-config.ts | 23 + app/styles/components/code.scss | 2 +- app/types/context.ts | 8 + app/utils/constants.ts | 7 + icons/mcp.svg | 1 + package.json | 3 + pnpm-lock.yaml | 260 ++++++++++ 26 files changed, 2040 insertions(+), 98 deletions(-) create mode 100644 app/components/@settings/tabs/mcp/McpServerList.tsx create mode 100644 app/components/@settings/tabs/mcp/McpServerListItem.tsx create mode 100644 app/components/@settings/tabs/mcp/McpStatusBadge.tsx create mode 100644 app/components/@settings/tabs/mcp/McpTab.tsx create mode 100644 app/components/chat/MCPTools.tsx create mode 100644 app/components/chat/ToolInvocations.tsx create mode 100644 app/lib/services/mcpService.ts create mode 100644 app/lib/stores/mcp.ts create mode 100644 app/routes/api.mcp-check.ts create mode 100644 app/routes/api.mcp-update-config.ts create mode 100644 icons/mcp.svg 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: