diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index bb6fc2835a..0d5384f239 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -450,6 +450,35 @@ class McpService { return cachedListTools(server) } + /** + * Apply default parameters to tool arguments + */ + private applyDefaultParameters( + toolName: string, + server: MCPServer, + providedArgs: Record = {} + ): Record { + const toolConfig = server.customToolConfigs?.find((config) => config.toolName === toolName) + + if (!toolConfig) { + return providedArgs + } + + const mergedArgs = { ...providedArgs } + + toolConfig.parameters.forEach((paramConfig) => { + if ( + paramConfig.defaultValue !== undefined && + paramConfig.defaultValue !== null && + paramConfig.defaultValue !== '' + ) { + mergedArgs[paramConfig.name] = paramConfig.defaultValue + } + }) + + return mergedArgs + } + /** * Call a tool on an MCP server */ @@ -466,8 +495,13 @@ class McpService { Logger.error('[MCP] args parse error', args) } } + + Logger.info('[MCP] Calling with args:', server.name, name, args) + const mergedArgs = this.applyDefaultParameters(name, server, args || {}) + Logger.info('[MCP] Calling with merged args:', server.name, name, mergedArgs) + const client = await this.initClient(server) - const result = await client.callTool({ name, arguments: args }, undefined, { + const result = await client.callTool({ name, arguments: mergedArgs }, undefined, { timeout: server.timeout ? server.timeout * 1000 : 60000 // Default timeout of 1 minute }) return result as MCPCallToolResponse diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 7579c9d474..bb49859dbb 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -45,7 +45,10 @@ "search.no_results": "No results found", "sorting.title": "Sorting", "settings": { - "title": "Agent Setting" + "title": "Agent Configuration", + "subscription": { + "title": "Agent Subscription Configuration" + } }, "tag.agent": "Agent", "tag.default": "Default", @@ -57,50 +60,50 @@ "title": "Assistants", "abbr": "Assistants", "settings.title": "Assistant Settings", - "clear.content": "Clearing the topic will delete all topics and files in the assistant. Are you sure you want to continue?", - "clear.title": "Clear topics", + "clear.content": "Clearing topics will delete all topics and files under the assistant. Are you sure you want to continue?", + "clear.title": "Clear Topics", "copy.title": "Copy Assistant", - "delete.content": "Deleting an assistant will delete all topics and files under the assistant. Are you sure you want to delete it?", + "delete.content": "Deleting an assistant will delete all topics and files under that assistant. Are you sure you want to continue?", "delete.title": "Delete Assistant", "edit.title": "Edit Assistant", "save.success": "Saved successfully", - "save.title": "Save to agent", + "save.title": "Save to Agent", "icon.type": "Assistant Icon", - "search": "Search assistants...", + "search": "Search Assistants", + "settings.mcp": "MCP Servers", + "settings.mcp.enableFirst": "Please enable this server in MCP Settings first", + "settings.mcp.title": "MCP Settings", + "settings.mcp.noServersAvailable": "No MCP servers available. Please add a server in settings.", + "settings.mcp.description": "Default enabled MCP servers", "settings.default_model": "Default Model", "settings.knowledge_base": "Knowledge Base Settings", - "settings.mcp": "MCP Servers", - "settings.mcp.enableFirst": "Enable this server in MCP settings first", - "settings.mcp.title": "MCP Settings", - "settings.mcp.noServersAvailable": "No MCP servers available. Add servers in settings", - "settings.mcp.description": "Default enabled MCP servers", - "settings.model": "Model Settings", - "settings.preset_messages": "Preset Messages", - "settings.prompt": "Prompt Settings", - "settings.reasoning_effort": "Reasoning effort", - "settings.reasoning_effort.off": "Off", - "settings.reasoning_effort.high": "Think harder", - "settings.reasoning_effort.low": "Think less", - "settings.reasoning_effort.medium": "Think normally", - "settings.reasoning_effort.default": "Default", - "settings.more": "Assistant Settings", - "settings.knowledge_base.recognition.tip": "The assistant will use the large model's intent recognition capability to determine whether to use the knowledge base for answering. This feature will depend on the model's capabilities", - "settings.knowledge_base.recognition": "Use Knowledge Base", - "settings.knowledge_base.recognition.off": "Force Search", + "settings.knowledge_base.recognition.tip": "The agent will use the model's intent recognition ability to determine if it needs to call the knowledge base for an answer. This feature will depend on the model's capabilities.", + "settings.knowledge_base.recognition": "Call Knowledge Base", + "settings.knowledge_base.recognition.off": "Force Retrieval", "settings.knowledge_base.recognition.on": "Intent Recognition", "settings.tool_use_mode": "Tool Use Mode", "settings.tool_use_mode.function": "Function", "settings.tool_use_mode.prompt": "Prompt", + "settings.model": "Model Settings", + "settings.preset_messages": "Preset Messages", + "settings.prompt": "Prompt Settings", + "settings.reasoning_effort": "Chain of Thought Length", + "settings.reasoning_effort.off": "Off", + "settings.reasoning_effort.low": "Imagine", + "settings.reasoning_effort.medium": "Consider", + "settings.reasoning_effort.high": "Ponder", + "settings.reasoning_effort.default": "Default", + "settings.more": "Assistant Settings", "settings.regular_phrases": { - "title": "Regular Phrase", + "title": "Regular Phrases", "add": "Add Phrase", "edit": "Edit Phrase", "delete": "Delete Phrase", - "deleteConfirm": "Are you sure to delete this phrase?", + "deleteConfirm": "Are you sure you want to delete this phrase?", "titleLabel": "Title", "titlePlaceholder": "Enter title", "contentLabel": "Content", - "contentPlaceholder": "Please enter phrase content, support using variables, and press Tab to quickly locate the variable to modify. For example: \nHelp me plan a route from ${from} to ${to}, and send it to ${email}." + "contentPlaceholder": "Enter phrase content, supports variables, then press Tab to quickly navigate to variables for modification. E.g.:\nHelp me plan a route from ${from} to ${to}, then send to ${email}" } }, "auth": { @@ -355,7 +358,7 @@ "add": "Add", "advanced_settings": "Advanced Settings", "and": "and", - "assistant": "Assistant", + "assistant": "Agent", "avatar": "Avatar", "back": "Back", "cancel": "Cancel", @@ -376,9 +379,9 @@ "edit": "Edit", "expand": "Expand", "collapse": "Collapse", - "footnote": "Reference content", - "footnotes": "References", - "fullscreen": "Entered fullscreen mode. Press F11 to exit", + "footnote": "Citation", + "footnotes": "Citations", + "fullscreen": "Entered fullscreen mode, press F11 to exit", "knowledge_base": "Knowledge Base", "language": "Language", "loading": "Loading...", @@ -392,7 +395,10 @@ "regenerate": "Regenerate", "rename": "Rename", "reset": "Reset", + "required": "REQUIRED", + "allowed_values": "Allowed values", "save": "Save", + "unsaved_changes": "You have unsaved changes", "search": "Search", "select": "Select", "selectedMessages": "Selected {{count}} messages", @@ -821,7 +827,7 @@ "style_type": "Style", "rendering_speed": "Rendering Speed", "learn_more": "Learn More", - "paint_course":"tutorial", + "paint_course": "tutorial", "prompt_placeholder_edit": "Enter your image description, text drawing uses \"double quotes\" to wrap", "proxy_required": "Currently, you need to open a proxy to view the generated images, it will be supported in the future", "image_file_required": "Please upload an image first", @@ -1373,7 +1379,13 @@ "inputSchema": "Input Schema", "availableTools": "Available Tools", "noToolsAvailable": "No tools available", - "loadError": "Get tools Error" + "loadError": "Get tools Error", + "configureDefaults": "Configure Default Parameters", + "configureDefaultsDescription": "Configure default values for tool parameters. When enabled, these values will be automatically used if the AI model doesn't provide them.", + "defaultValue": "Default Value", + "configSaved": "Tool configuration saved successfully", + "hasDefaults": "DEFAULTS", + "hasDefaultTooltip": "This tool has configured default parameters." }, "prompts": { "availablePrompts": "Available Prompts", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 140ea19a16..d9591396e0 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -50,7 +50,10 @@ "tag.system": "システム", "title": "エージェント", "settings": { - "title": "エージェント設定" + "title": "エージェント設定", + "subscription": { + "title": "エージェントサブスクリプション設定" + } } }, "assistants": { @@ -406,7 +409,10 @@ "pinyin.asc": "ピンインで昇順ソート", "pinyin.desc": "ピンインで降順ソート" }, - "no_results": "検索結果なし" + "no_results": "検索結果なし", + "required": "必須", + "allowed_values": "許可された値", + "unsaved_changes": "未保存の変更があります" }, "docs": { "title": "ドキュメント" @@ -821,7 +827,7 @@ "style_type": "スタイル", "learn_more": "詳しくはこちら", "prompt_placeholder_edit": "画像の説明を入力します。テキスト描画には '二重引用符' を使用します", - "paint_course":"チュートリアル", + "paint_course": "チュートリアル", "proxy_required": "現在、プロキシを開く必要があります。これは、将来サポートされる予定です", "image_file_required": "画像を先にアップロードしてください", "image_file_retry": "画像を先にアップロードしてください", @@ -1369,7 +1375,13 @@ "inputSchema": "入力スキーマ", "availableTools": "利用可能なツール", "noToolsAvailable": "利用可能なツールなし", - "loadError": "ツール取得エラー" + "loadError": "ツール取得エラー", + "configureDefaults": "デフォルトパラメーターを設定", + "configureDefaultsDescription": "ツールパラメーターのデフォルト値を設定します。有効にすると、AIモデルが提供しない場合、これらの値が自動的に使用されます。", + "defaultValue": "デフォルト値", + "configSaved": "ツール設定が正常に保存されました", + "hasDefaults": "デフォルトあり", + "hasDefaultTooltip": "このツールには、設定済みのデフォルトパラメーターがあります。" }, "prompts": { "availablePrompts": "利用可能なプロンプト", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index fc5a35278c..66091e2e1d 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -50,7 +50,10 @@ "agent": "Экспорт агента" }, "settings": { - "title": "Настройки агента" + "title": "Настройки агента", + "subscription": { + "title": "Конфигурация подписки агента" + } } }, "assistants": { @@ -406,7 +409,10 @@ "pinyin.asc": "Сортировать по пиньинь (А-Я)", "pinyin.desc": "Сортировать по пиньинь (Я-А)" }, - "no_results": "Результатов не найдено" + "no_results": "Результатов не найдено", + "required": "Обязательно", + "allowed_values": "Допустимые значения", + "unsaved_changes": "У вас есть несохраненные изменения" }, "docs": { "title": "Документация" @@ -822,7 +828,7 @@ "rendering_speed": "Скорость рендеринга", "learn_more": "Узнать больше", "prompt_placeholder_edit": "Введите ваше описание изображения, текстовая отрисовка использует двойные кавычки для обертки", - "paint_course":"Руководство / Учебник", + "paint_course": "Руководство / Учебник", "proxy_required": "Сейчас необходимо открыть прокси для просмотра сгенерированных изображений, в будущем будет поддерживаться прямое соединение", "image_file_required": "Пожалуйста, сначала загрузите изображение", "image_file_retry": "Пожалуйста, сначала загрузите изображение", @@ -888,7 +894,6 @@ "seed_tip": "Контролирует случайный характер увеличения изображений для воспроизводимых результатов", "magic_prompt_option_tip": "Улучшает увеличение изображений с помощью интеллектуального оптимизирования промптов" }, - "rendering_speed": "Скорость рендеринга", "text_desc_required": "Пожалуйста, сначала введите описание изображения" }, "prompts": { @@ -1370,7 +1375,13 @@ "inputSchema": "Схема ввода", "availableTools": "Доступные инструменты", "noToolsAvailable": "Нет доступных инструментов", - "loadError": "Ошибка получения инструментов" + "loadError": "Ошибка получения инструментов", + "configureDefaults": "Настроить параметры по умолчанию", + "configureDefaultsDescription": "Настройте значения по умолчанию для параметров инструмента. Когда эта функция включена, эти значения будут использоваться автоматически, если их не предоставит AI модель.", + "defaultValue": "Значение по умолчанию", + "configSaved": "Конфигурация инструмента успешно сохранена", + "hasDefaults": "ЗНАЧЕНИЯ ПО УМОЛЧАНИЮ", + "hasDefaultTooltip": "Для этого инструмента настроены параметры по умолчанию." }, "prompts": { "availablePrompts": "Доступные подсказки", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 5fb9880ee3..1d6f3e4d4b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -50,7 +50,10 @@ "tag.system": "系统", "title": "智能体", "settings": { - "title": "智能体配置" + "title": "智能体配置", + "subscription": { + "title": "智能体订阅配置" + } } }, "assistants": { @@ -392,7 +395,10 @@ "regenerate": "重新生成", "rename": "重命名", "reset": "重置", + "required": "必填", + "allowed_values": "允许的值", "save": "保存", + "unsaved_changes": "您有未保存的更改", "search": "搜索", "select": "选择", "selectedMessages": "选中 {{count}} 条消息", @@ -821,7 +827,7 @@ "style_type": "风格", "rendering_speed": "渲染速度", "learn_more": "了解更多", - "paint_course":"教程", + "paint_course": "教程", "prompt_placeholder_edit": "输入你的图片描述,文本绘制用 \"双引号\" 包裹", "proxy_required": "目前需要打开代理才能查看生成图片,后续会支持国内直连", "image_file_required": "请先上传图片", @@ -1373,7 +1379,13 @@ "inputSchema": "输入模式", "availableTools": "可用工具", "noToolsAvailable": "无可用工具", - "loadError": "获取工具失败" + "loadError": "获取工具失败", + "configureDefaults": "配置默认参数", + "configureDefaultsDescription": "配置工具参数的默认值。启用后,如果AI模型没有提供这些参数,将自动使用这些默认值。", + "defaultValue": "默认值", + "configSaved": "工具配置保存成功", + "hasDefaults": "有默认值", + "hasDefaultTooltip": "此工具已配置默认参数。" }, "prompts": { "availablePrompts": "可用提示", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 4cc142714b..63c12510b5 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -50,7 +50,10 @@ "tag.system": "系統", "title": "智慧代理人", "settings": { - "title": "智慧代理人設定" + "title": "智慧代理人設定", + "subscription": { + "title": "智慧代理人訂閱設定" + } } }, "assistants": { @@ -406,7 +409,10 @@ "pinyin.asc": "按拼音升序", "pinyin.desc": "按拼音降序" }, - "no_results": "沒有結果" + "no_results": "沒有結果", + "required": "必填", + "allowed_values": "允許的值", + "unsaved_changes": "您有未儲存的變更" }, "docs": { "title": "說明文件" @@ -594,12 +600,11 @@ "citations": "引用內容", "copied": "已複製!", "copy.failed": "複製失敗", - "copy.success": "已複製!", + "copy.success": "複製成功", "delete.confirm.title": "刪除確認", "delete.confirm.content": "確認刪除選中的 {{count}} 條訊息嗎?", "delete.failed": "刪除失敗", "delete.success": "刪除成功", - "copy.success": "複製成功", "empty_url": "無法下載圖片,可能是提示詞包含敏感內容或違禁詞彙", "error.chunk_overlap_too_large": "分段重疊不能大於分段大小", "error.dimension_too_large": "內容尺寸過大", @@ -822,7 +827,7 @@ "style_type": "風格", "learn_more": "了解更多", "prompt_placeholder_edit": "輸入你的圖片描述,文本繪製用 '雙引號' 包裹", - "paint_course":"教程", + "paint_course": "教程", "proxy_required": "目前需要打開代理才能查看生成圖片,後續會支持國內直連", "image_file_required": "請先上傳圖片", "image_file_retry": "請重新上傳圖片", @@ -1373,7 +1378,13 @@ "inputSchema": "輸入模式", "availableTools": "可用工具", "noToolsAvailable": "無可用工具", - "loadError": "獲取工具失敗" + "loadError": "獲取工具失敗", + "configureDefaults": "配置預設參數", + "configureDefaultsDescription": "配置工具參數的預設值。啟用後,如果 AI 模型未提供這些值,將自動使用。", + "defaultValue": "預設值", + "configSaved": "工具配置已成功保存", + "hasDefaults": "預設值已配置", + "hasDefaultTooltip": "此工具已配置預設參數。" }, "prompts": { "availablePrompts": "可用提示", diff --git a/src/renderer/src/pages/settings/DataSettings/AgentsSubscribeUrlSettings.tsx b/src/renderer/src/pages/settings/DataSettings/AgentsSubscribeUrlSettings.tsx index eb37f41737..a8ddec1cc5 100755 --- a/src/renderer/src/pages/settings/DataSettings/AgentsSubscribeUrlSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/AgentsSubscribeUrlSettings.tsx @@ -22,10 +22,7 @@ const AgentsSubscribeUrlSettings: FC = () => { return ( - - {t('agents.tag.agent')} - {t('settings.websearch.subscribe_add')} - + {t('agents.settings.subscription.title')} {t('settings.websearch.subscribe_url')} diff --git a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx index c20df064d3..3e3755417f 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx @@ -2,7 +2,8 @@ import { DeleteOutlined, SaveOutlined } from '@ant-design/icons' import { useTheme } from '@renderer/context/ThemeProvider' import { useMCPServer, useMCPServers } from '@renderer/hooks/useMCPServers' import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription' -import { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types' +import { MCPPrompt, MCPResource, MCPServer, MCPTool, MCPToolConfig, MCPToolParameterConfig } from '@renderer/types' +import { isEmpty } from '@renderer/utils' import { formatMcpError } from '@renderer/utils/error' import { Button, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd' import TextArea from 'antd/es/input/TextArea' @@ -398,6 +399,42 @@ const McpSettings: React.FC = () => { [server, updateMCPServer] ) + // Handle updating tool parameter configuration + const handleUpdateToolConfig = useCallback( + async (toolName: string, parameterConfig: MCPToolParameterConfig[]) => { + let customToolConfigs = [...(server.customToolConfigs || [])] + + const existingConfigIndex = customToolConfigs.findIndex((config) => config.toolName === toolName) + + const newToolConfig: MCPToolConfig = { + toolName, + parameters: parameterConfig + } + + if (existingConfigIndex >= 0) { + customToolConfigs[existingConfigIndex] = newToolConfig + } else { + customToolConfigs.push(newToolConfig) + } + + customToolConfigs = customToolConfigs.filter((config) => + config.parameters.some((param) => !isEmpty(param.defaultValue)) + ) + + const updatedServer = { + ...server, + customToolConfigs + } + + updateMCPServer(updatedServer) + window.message.success({ + content: t('settings.mcp.tools.configSaved'), + key: 'mcp-tool-config' + }) + }, + [server, updateMCPServer, t] + ) + const tabs = [ { key: 'settings', @@ -592,7 +629,14 @@ const McpSettings: React.FC = () => { { key: 'tools', label: t('settings.mcp.tabs.tools'), - children: + children: ( + + ) }, { key: 'prompts', diff --git a/src/renderer/src/pages/settings/MCPSettings/McpTool.tsx b/src/renderer/src/pages/settings/MCPSettings/McpTool.tsx index 788db5b77e..b40e561e51 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpTool.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpTool.tsx @@ -1,5 +1,21 @@ -import { MCPServer, MCPTool } from '@renderer/types' -import { Badge, Collapse, Descriptions, Empty, Flex, Switch, Tag, Tooltip, Typography } from 'antd' +import { CloseOutlined, SaveOutlined, SettingOutlined } from '@ant-design/icons' +import { MCPServer, MCPTool, MCPToolParameterConfig } from '@renderer/types' +import { isEmpty } from '@renderer/utils' +import { + Button, + Collapse, + Descriptions, + Empty, + Flex, + Input, + InputNumber, + Space, + Switch, + Tag, + Tooltip, + Typography +} from 'antd' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -7,22 +23,211 @@ interface MCPToolsSectionProps { tools: MCPTool[] server: MCPServer onToggleTool: (tool: MCPTool, enabled: boolean) => void + onUpdateToolConfig?: (toolName: string, config: MCPToolParameterConfig[]) => void } -const MCPToolsSection = ({ tools, server, onToggleTool }: MCPToolsSectionProps) => { +const MCPToolsSection = ({ tools, server, onToggleTool, onUpdateToolConfig }: MCPToolsSectionProps) => { const { t } = useTranslation() + const [editingToolName, setEditingToolName] = useState(null) + const [editableToolParams, setEditableToolParams] = useState([]) + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) + + // Effect to reset editable params when editingToolName changes or server config changes + useEffect(() => { + if (editingToolName) { + const tool = tools.find((t) => t.name === editingToolName) + if (tool) { + initializeEditableParams(tool) + } else { + setEditingToolName(null) + } + } else { + setEditableToolParams([]) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editingToolName, server.customToolConfigs, tools]) - // Check if a tool is enabled (not in the disabledTools array) const isToolEnabled = (tool: MCPTool) => { return !server.disabledTools?.includes(tool.name) } - // Handle tool toggle const handleToggle = (tool: MCPTool, checked: boolean) => { onToggleTool(tool, checked) } - // Render tool properties from the input schema + const getToolConfig = (toolName: string): MCPToolParameterConfig[] => { + const toolConfig = server.customToolConfigs?.find((config) => config.toolName === toolName) + return toolConfig?.parameters || [] + } + + const getDefaultValueForType = (type: string): any => { + switch (type) { + case 'string': + return '' + case 'number': + return 0 + case 'boolean': + return false + case 'array': + return [] + case 'object': + return {} + default: + return '' + } + } + + const initializeEditableParams = (tool: MCPTool) => { + const currentConfig = getToolConfig(tool.name) + const initialConfig: MCPToolParameterConfig[] = [] + if (tool.inputSchema?.properties) { + Object.entries(tool.inputSchema.properties).forEach(([paramName, paramDef]: [string, any]) => { + const existingConfig = currentConfig.find((c) => c.name === paramName) + initialConfig.push({ + name: paramName, + defaultValue: existingConfig?.defaultValue ?? getDefaultValueForType(paramDef.type), + description: paramDef.description || '' + }) + }) + } + setEditableToolParams(initialConfig) + } + + const handleEditToolParams = (tool: MCPTool) => { + if (editingToolName === tool.name) { + // If already editing this tool, cancel editing + setEditingToolName(null) + setEditableToolParams([]) + setHasUnsavedChanges(false) + } else { + setEditingToolName(tool.name) + setHasUnsavedChanges(false) + // initializeEditableParams will be called by the useEffect + } + } + + const handleSaveToolParams = (toolName: string) => { + if (onUpdateToolConfig) { + onUpdateToolConfig(toolName, editableToolParams) + } + setEditingToolName(null) + setEditableToolParams([]) + setHasUnsavedChanges(false) + } + + const handleCancelEditToolParams = () => { + setEditingToolName(null) + setEditableToolParams([]) + setHasUnsavedChanges(false) + } + + const updateParameterConfig = (index: number, field: keyof MCPToolParameterConfig, value: any) => { + const newConfig = [...editableToolParams] + newConfig[index] = { ...newConfig[index], [field]: value } + setEditableToolParams(newConfig) + setHasUnsavedChanges(true) + } + + const handleParameterBlur = (index: number, field: keyof MCPToolParameterConfig, value: any) => { + updateParameterConfig(index, field, value) + } + + const renderParameterInput = (param: MCPToolParameterConfig, index: number, paramDef: any) => { + const { type } = paramDef + + switch (type) { + case 'number': + return ( + updateParameterConfig(index, 'defaultValue', value)} + onBlur={(e) => { + const value = e.target.value ? Number(e.target.value) : undefined + handleParameterBlur(index, 'defaultValue', value) + }} + style={{ width: '100%' }} + placeholder="Enter default number" + /> + ) + case 'boolean': + return ( + { + updateParameterConfig(index, 'defaultValue', checked) + // Switch 组件没有 onBlur,直接在 onChange 中处理 + handleParameterBlur(index, 'defaultValue', checked) + }} + /> + ) + case 'array': + return ( + { + const lines = e.target.value.split('\n').filter((line) => line.trim()) + updateParameterConfig(index, 'defaultValue', lines) + }} + onBlur={(e) => { + const lines = e.target.value.split('\n').filter((line) => line.trim()) + handleParameterBlur(index, 'defaultValue', lines) + }} + placeholder="Enter array items (one per line)" + rows={3} + /> + ) + default: // 'string' and 'object' (as JSON string for simplicity here) + if (type === 'object') { + return ( + { + try { + const val = e.target.value + // Attempt to parse if it's meant to be an object, otherwise store as string + if (val.trim().startsWith('{') || val.trim().startsWith('[')) { + updateParameterConfig(index, 'defaultValue', JSON.parse(val)) + } else { + updateParameterConfig(index, 'defaultValue', val) + } + } catch (error) { + // If JSON is invalid while typing, keep the string value + updateParameterConfig(index, 'defaultValue', e.target.value) + } + }} + onBlur={(e) => { + try { + const val = e.target.value + if (val.trim().startsWith('{') || val.trim().startsWith('[')) { + handleParameterBlur(index, 'defaultValue', JSON.parse(val)) + } else { + handleParameterBlur(index, 'defaultValue', val) + } + } catch (error) { + // If JSON is invalid, keep the string value + handleParameterBlur(index, 'defaultValue', e.target.value) + } + }} + placeholder="Enter default JSON object or string" + rows={3} + /> + ) + } + return ( + updateParameterConfig(index, 'defaultValue', e.target.value)} + onBlur={(e) => handleParameterBlur(index, 'defaultValue', e.target.value)} + placeholder="Enter default value" + /> + ) + } + } + const renderToolProperties = (tool: MCPTool) => { if (!tool.inputSchema?.properties) return null @@ -43,52 +248,123 @@ const MCPToolsSection = ({ tools, server, onToggleTool }: MCPToolsSectionProps) } } + const toolConfig = getToolConfig(tool.name) + const isEditingThisTool = editingToolName === tool.name + return (
- {t('settings.mcp.tools.inputSchema')}: + + + {t('settings.mcp.tools.inputSchema')}: + + + + + + + )}
) } @@ -97,35 +373,65 @@ const MCPToolsSection = ({ tools, server, onToggleTool }: MCPToolsSectionProps)
{t('settings.mcp.tools.availableTools')} {tools.length > 0 ? ( - + {tools.map((tool) => ( - + - {tool.name} - - {tool.id} + + {tool.name} + + ({tool.id}) + + {server.customToolConfigs + ?.find((c) => c.toolName === tool.name) + ?.parameters.some((p) => !isEmpty(p.defaultValue)) && ( + + + {t('settings.mcp.tools.hasDefaults')} + + + )} {tool.description && ( - - {tool.description.length > 100 ? `${tool.description.substring(0, 100)}...` : tool.description} + + {tool.description.length > 150 ? `${tool.description.substring(0, 150)}...` : tool.description} )} - { - event?.stopPropagation() - handleToggle(tool, checked) - }} - /> + e.stopPropagation()} style={{ marginLeft: 10 }}> + { + handleToggle(tool, checked) + }} + /> + }> - {renderToolProperties(tool)} + e.stopPropagation() /* Prevent collapse toggle when clicking content */}> + {renderToolProperties(tool)} + ))} @@ -150,7 +456,7 @@ const SectionTitle = styled.h3` const SelectableContent = styled.div` user-select: text; - padding: 0 12px; + padding: 0 12px 12px 12px; ` export default MCPToolsSection diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index db8323e322..deb3b53783 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -518,6 +518,19 @@ export interface MCPConfigSample { env?: Record | undefined } +// MCP工具自定义参数配置接口 +export interface MCPToolParameterConfig { + name: string + defaultValue: any + description?: string +} + +// MCP工具配置接口 +export interface MCPToolConfig { + toolName: string + parameters: MCPToolParameterConfig[] +} + export interface MCPServer { id: string name: string @@ -530,6 +543,7 @@ export interface MCPServer { env?: Record isActive: boolean disabledTools?: string[] // List of tool names that are disabled for this server + customToolConfigs?: MCPToolConfig[] configSample?: MCPConfigSample headers?: Record // Custom headers to be sent with requests to this server searchKey?: string diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 07410cc246..f7bb5f3f85 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -4,6 +4,17 @@ import { ModalFuncProps } from 'antd/es/modal/interface' // @ts-ignore next-line` import { v4 as uuidv4 } from 'uuid' +export function isEmpty(value: any) { + return ( + value === null || + value === undefined || + value === '' || + (Array.isArray(value) && value.length === 0) || + (typeof value === 'object' && Object.keys(value).length === 0) || + (typeof value === 'number' && value === 0) + ) +} + /** * 异步执行一个函数。 * @param fn 要执行的函数