diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 53a3aba3..1d9d3cff 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -42,7 +42,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 87, + version: 88, blacklist: ['runtime', 'messages'], migrate }, diff --git a/src/renderer/src/store/mcp.ts b/src/renderer/src/store/mcp.ts index 554e3865..9b8e69ce 100644 --- a/src/renderer/src/store/mcp.ts +++ b/src/renderer/src/store/mcp.ts @@ -1,8 +1,20 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { MCPConfig, MCPServer } from '@renderer/types' +import { createSlice, type PayloadAction } from '@reduxjs/toolkit' +import { nanoid } from '@reduxjs/toolkit' +import type { MCPConfig, MCPServer } from '@renderer/types' const initialState: MCPConfig = { - servers: [] + servers: [ + { + id: nanoid(), + name: 'mcp-auto-install', + description: 'Automatically install MCP services (Beta version)', + baseUrl: '', + command: 'npx', + args: ['-y', '@mcpmarket/mcp-auto-install', 'connect', '--json'], + env: {}, + isActive: false + } + ] } const mcpSlice = createSlice({ @@ -47,5 +59,6 @@ export const { getActiveServers, getAllServers } = mcpSlice.selectors // Type-safe selector for accessing this slice from the root state export const selectMCP = (state: { mcp: MCPConfig }) => state.mcp +export { mcpSlice } // Export the reducer as default export export default mcpSlice.reducer diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index c35693c6..bc9512c8 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -12,6 +12,7 @@ import { createMigrate } from 'redux-persist' import { RootState } from '.' import { INITIAL_PROVIDERS, moveProvider } from './llm' +import { mcpSlice } from './mcp' import { DEFAULT_SIDEBAR_ICONS } from './settings' // remove logo base64 data to reduce the size of the state @@ -1148,6 +1149,22 @@ const migrateConfig = { } catch (error) { return state } + }, + '88': (state: RootState) => { + try { + if (state?.mcp?.servers) { + const hasAutoInstall = state.mcp.servers.some((server) => server.name === 'mcp-auto-install') + if (!hasAutoInstall) { + const defaultServer = mcpSlice.getInitialState().servers[0] + state.mcp.servers = [{ ...defaultServer, id: nanoid() }, ...state.mcp.servers] + } + } + } catch (error) { + console.error(error) + return state + } + + return state } } diff --git a/src/renderer/src/utils/mcp-tools.ts b/src/renderer/src/utils/mcp-tools.ts index 6d099a35..3b0110cb 100644 --- a/src/renderer/src/utils/mcp-tools.ts +++ b/src/renderer/src/utils/mcp-tools.ts @@ -1,6 +1,8 @@ import { Tool, ToolUnion, ToolUseBlock } from '@anthropic-ai/sdk/resources' import { FunctionCall, FunctionDeclaration, SchemaType, Tool as geminiToool } from '@google/generative-ai' +import { nanoid } from '@reduxjs/toolkit' import store from '@renderer/store' +import { addMCPServer } from '@renderer/store/mcp' import { MCPServer, MCPTool, MCPToolResponse } from '@renderer/types' import { ChatCompletionMessageToolCall, ChatCompletionTool } from 'openai/resources' @@ -126,6 +128,23 @@ export async function callMCPTool(tool: MCPTool): Promise { }) console.log(`[MCP] Tool called: ${tool.serverName} ${tool.name}`, resp) + + if (tool.serverName === 'mcp-auto-install') { + if (resp.data) { + const mcpServer: MCPServer = { + id: nanoid(), + name: resp.data.name, + description: resp.data.description, + baseUrl: resp.data.baseUrl, + command: resp.data.command, + args: resp.data.args, + env: resp.data.env, + isActive: false + } + store.dispatch(addMCPServer(mcpServer)) + } + } + return resp } catch (e) { console.error(`[MCP] Error calling Tool: ${tool.serverName} ${tool.name}`, e)