mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 01:50:13 +08:00
✨ feat(MCP): add support for enabling/disabling MCPServers per message (#2989)
* ✨ feat: add MCP servers in chat input - Introduce MCPToolsButton component for managing MCP servers - Add new icon for MCP server tools in iconfont.css - Update Inputbar to include MCP tools functionality - Add toggle functionality for enabling/disabling MCP servers - Implement styled dropdown menu for server selection - Add necessary type imports and useState for MCP server management * ✨ feat: add support for enabling/disabling MCPServers per message (main) - Added `enabledMCPs` property to the `Message` type to track enabled MCPServers. - Modified `MCPToolsButton` to enable all active MCPServers by default using a new `enableAll` state. - Introduced `filterMCPTools` utility to filter tools based on enabled MCPServers. - Updated `AnthropicProvider`, `GeminiProvider`, and `OpenAIProvider` to filter tools using `filterMCPTools`. - Enhanced `Inputbar` to include `enabledMCPs` in the message payload when set.
This commit is contained in:
parent
a0351fb5ad
commit
a8451b7c3d
@ -19,6 +19,10 @@
|
|||||||
content: '\e623';
|
content: '\e623';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-mcp:before {
|
||||||
|
content: '\e78e';
|
||||||
|
}
|
||||||
|
|
||||||
.icon-icon-adaptive-width:before {
|
.icon-icon-adaptive-width:before {
|
||||||
content: '\e87a';
|
content: '\e87a';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { translateText } from '@renderer/services/TranslateService'
|
|||||||
import WebSearchService from '@renderer/services/WebSearchService'
|
import WebSearchService from '@renderer/services/WebSearchService'
|
||||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { setGenerating, setSearching } from '@renderer/store/runtime'
|
import { setGenerating, setSearching } from '@renderer/store/runtime'
|
||||||
import { Assistant, FileType, KnowledgeBase, Message, Model, Topic } from '@renderer/types'
|
import { Assistant, FileType, KnowledgeBase, MCPServer, Message, Model, Topic } from '@renderer/types'
|
||||||
import { classNames, delay, getFileExtension, uuid } from '@renderer/utils'
|
import { classNames, delay, getFileExtension, uuid } from '@renderer/utils'
|
||||||
import { abortCompletion } from '@renderer/utils/abortController'
|
import { abortCompletion } from '@renderer/utils/abortController'
|
||||||
import { getFilesFromDropEvent } from '@renderer/utils/input'
|
import { getFilesFromDropEvent } from '@renderer/utils/input'
|
||||||
@ -45,6 +45,7 @@ import NarrowLayout from '../Messages/NarrowLayout'
|
|||||||
import AttachmentButton from './AttachmentButton'
|
import AttachmentButton from './AttachmentButton'
|
||||||
import AttachmentPreview from './AttachmentPreview'
|
import AttachmentPreview from './AttachmentPreview'
|
||||||
import KnowledgeBaseButton from './KnowledgeBaseButton'
|
import KnowledgeBaseButton from './KnowledgeBaseButton'
|
||||||
|
import MCPToolsButton from './MCPToolsButton'
|
||||||
import MentionModelsButton from './MentionModelsButton'
|
import MentionModelsButton from './MentionModelsButton'
|
||||||
import MentionModelsInput from './MentionModelsInput'
|
import MentionModelsInput from './MentionModelsInput'
|
||||||
import SendMessageButton from './SendMessageButton'
|
import SendMessageButton from './SendMessageButton'
|
||||||
@ -88,6 +89,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
|||||||
const [isTranslating, setIsTranslating] = useState(false)
|
const [isTranslating, setIsTranslating] = useState(false)
|
||||||
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
|
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
|
||||||
const [mentionModels, setMentionModels] = useState<Model[]>([])
|
const [mentionModels, setMentionModels] = useState<Model[]>([])
|
||||||
|
const [enabledMCPs, setEnabledMCPs] = useState<MCPServer[]>([])
|
||||||
const [isMentionPopupOpen, setIsMentionPopupOpen] = useState(false)
|
const [isMentionPopupOpen, setIsMentionPopupOpen] = useState(false)
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [textareaHeight, setTextareaHeight] = useState<number>()
|
const [textareaHeight, setTextareaHeight] = useState<number>()
|
||||||
@ -157,6 +159,11 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
|||||||
if (mentionModels.length > 0) {
|
if (mentionModels.length > 0) {
|
||||||
message.mentions = mentionModels
|
message.mentions = mentionModels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enabledMCPs.length > 0) {
|
||||||
|
message.enabledMCPs = enabledMCPs
|
||||||
|
}
|
||||||
|
|
||||||
currentMessageId.current = message.id
|
currentMessageId.current = message.id
|
||||||
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE, message)
|
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE, message)
|
||||||
|
|
||||||
@ -587,6 +594,17 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
|||||||
setMentionModels(mentionModels.filter((m) => m.id !== model.id))
|
setMentionModels(mentionModels.filter((m) => m.id !== model.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggelEnableMCP = (mcp: MCPServer) => {
|
||||||
|
setEnabledMCPs((prev) => {
|
||||||
|
const exists = prev.some((item) => item.name === mcp.name)
|
||||||
|
if (exists) {
|
||||||
|
return prev.filter((item) => item.name !== mcp.name)
|
||||||
|
} else {
|
||||||
|
return [...prev, mcp]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const onEnableWebSearch = () => {
|
const onEnableWebSearch = () => {
|
||||||
console.log(assistant)
|
console.log(assistant)
|
||||||
if (!isWebSearchModel(model)) {
|
if (!isWebSearchModel(model)) {
|
||||||
@ -682,6 +700,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
|||||||
onMentionModel={(model) => onMentionModel(model, mentionFromKeyboard)}
|
onMentionModel={(model) => onMentionModel(model, mentionFromKeyboard)}
|
||||||
ToolbarButton={ToolbarButton}
|
ToolbarButton={ToolbarButton}
|
||||||
/>
|
/>
|
||||||
|
<MCPToolsButton enabledMCPs={enabledMCPs} onEnableMCP={toggelEnableMCP} ToolbarButton={ToolbarButton} />
|
||||||
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
|
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
|
||||||
<ToolbarButton type="text" onClick={onEnableWebSearch}>
|
<ToolbarButton type="text" onClick={onEnableWebSearch}>
|
||||||
<GlobalOutlined
|
<GlobalOutlined
|
||||||
|
|||||||
204
src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx
Normal file
204
src/renderer/src/pages/home/Inputbar/MCPToolsButton.tsx
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
|
import { MCPServer } from '@renderer/types'
|
||||||
|
import { Dropdown, Switch, Tooltip } from 'antd'
|
||||||
|
import { FC, useEffect, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { createGlobalStyle } from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
enabledMCPs: MCPServer[]
|
||||||
|
onEnableMCP: (server: MCPServer) => void
|
||||||
|
ToolbarButton: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const MCPToolsButton: FC<Props> = ({ enabledMCPs, onEnableMCP, ToolbarButton }) => {
|
||||||
|
const { mcpServers } = useMCPServers()
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const [enableAll, setEnableAll] = useState(true)
|
||||||
|
const dropdownRef = useRef<any>(null)
|
||||||
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const truncateText = (text: string, maxLength: number = 50) => {
|
||||||
|
if (!text || text.length <= maxLength) return text
|
||||||
|
return text.substring(0, maxLength) + '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all active servers are enabled
|
||||||
|
const activeServers = mcpServers.filter((s) => s.isActive)
|
||||||
|
|
||||||
|
// Enable all active servers by default
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeServers.length > 0) {
|
||||||
|
activeServers.forEach((server) => {
|
||||||
|
if (enableAll && !enabledMCPs.includes(server)) {
|
||||||
|
onEnableMCP(server)
|
||||||
|
}
|
||||||
|
if (!enableAll && enabledMCPs.includes(server)) {
|
||||||
|
onEnableMCP(server)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [enableAll])
|
||||||
|
|
||||||
|
const menu = (
|
||||||
|
<div ref={menuRef} className="ant-dropdown-menu">
|
||||||
|
<div className="dropdown-header">
|
||||||
|
<div className="header-content">
|
||||||
|
<h4>{t('settings.mcp.title')}</h4>
|
||||||
|
<div className="enable-all-container">
|
||||||
|
{/* <span className="enable-all-label">{t('mcp.enable_all')}</span> */}
|
||||||
|
<Switch size="small" checked={enableAll} onChange={setEnableAll} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mcpServers.length > 0 ? (
|
||||||
|
mcpServers
|
||||||
|
.filter((s) => s.isActive)
|
||||||
|
.map((server) => (
|
||||||
|
<div key={server.name} className="ant-dropdown-menu-item mcp-server-item">
|
||||||
|
<div className="server-info">
|
||||||
|
<div className="server-name">{server.name}</div>
|
||||||
|
{server.description && (
|
||||||
|
<Tooltip title={server.description} placement="bottom">
|
||||||
|
<div className="server-description">{truncateText(server.description)}</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{server.baseUrl && <div className="server-url">{server.baseUrl}</div>}
|
||||||
|
</div>
|
||||||
|
<Switch size="small" checked={enabledMCPs.includes(server)} onChange={() => onEnableMCP(server)} />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="ant-dropdown-menu-item-group">
|
||||||
|
<div className="ant-dropdown-menu-item no-results">{t('models.no_matches')}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenuStyle />
|
||||||
|
<Dropdown
|
||||||
|
dropdownRender={() => menu}
|
||||||
|
trigger={['click']}
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
overlayClassName="mention-models-dropdown">
|
||||||
|
<Tooltip placement="top" title="MCP Servers" arrow>
|
||||||
|
<ToolbarButton type="text" ref={dropdownRef}>
|
||||||
|
<i className="iconfont icon-mcp" style={{ fontSize: 18 }}></i>
|
||||||
|
</ToolbarButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Dropdown>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropdownMenuStyle = createGlobalStyle`
|
||||||
|
.mention-models-dropdown {
|
||||||
|
.ant-dropdown-menu {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 4px 0;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--color-scrollbar-thumb);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-scrollbar-thumb-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-results {
|
||||||
|
padding: 8px 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
cursor: default;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-header {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enable-all-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.enable-all-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcp-server-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
|
||||||
|
.server-info {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.server-name {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
margin-top: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-url {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--color-text-4);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default MCPToolsButton
|
||||||
@ -19,7 +19,13 @@ import OpenAI from 'openai'
|
|||||||
|
|
||||||
import { CompletionsParams } from '.'
|
import { CompletionsParams } from '.'
|
||||||
import BaseProvider from './BaseProvider'
|
import BaseProvider from './BaseProvider'
|
||||||
import { anthropicToolUseToMcpTool, callMCPTool, mcpToolsToAnthropicTools, upsertMCPToolResponse } from './mcpToolUtils'
|
import {
|
||||||
|
anthropicToolUseToMcpTool,
|
||||||
|
callMCPTool,
|
||||||
|
filterMCPTools,
|
||||||
|
mcpToolsToAnthropicTools,
|
||||||
|
upsertMCPToolResponse
|
||||||
|
} from './mcpToolUtils'
|
||||||
|
|
||||||
type ReasoningEffort = 'high' | 'medium' | 'low'
|
type ReasoningEffort = 'high' | 'medium' | 'low'
|
||||||
|
|
||||||
@ -139,6 +145,8 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userMessages = flatten(userMessagesParams)
|
const userMessages = flatten(userMessagesParams)
|
||||||
|
const lastUserMessage = _messages.findLast((m) => m.role === 'user')
|
||||||
|
mcpTools = filterMCPTools(mcpTools, lastUserMessage?.enabledMCPs)
|
||||||
const tools = mcpTools ? mcpToolsToAnthropicTools(mcpTools) : undefined
|
const tools = mcpTools ? mcpToolsToAnthropicTools(mcpTools) : undefined
|
||||||
|
|
||||||
const body: MessageCreateParamsNonStreaming = {
|
const body: MessageCreateParamsNonStreaming = {
|
||||||
@ -189,8 +197,6 @@ export default class AnthropicProvider extends BaseProvider {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastUserMessage = _messages.findLast((m) => m.role === 'user')
|
|
||||||
|
|
||||||
const { abortController, cleanup } = this.createAbortController(lastUserMessage?.id)
|
const { abortController, cleanup } = this.createAbortController(lastUserMessage?.id)
|
||||||
const { signal } = abortController
|
const { signal } = abortController
|
||||||
const toolResponses: MCPToolResponse[] = []
|
const toolResponses: MCPToolResponse[] = []
|
||||||
|
|||||||
@ -27,7 +27,13 @@ import OpenAI from 'openai'
|
|||||||
|
|
||||||
import { CompletionsParams } from '.'
|
import { CompletionsParams } from '.'
|
||||||
import BaseProvider from './BaseProvider'
|
import BaseProvider from './BaseProvider'
|
||||||
import { callMCPTool, geminiFunctionCallToMcpTool, mcpToolsToGeminiTools, upsertMCPToolResponse } from './mcpToolUtils'
|
import {
|
||||||
|
callMCPTool,
|
||||||
|
filterMCPTools,
|
||||||
|
geminiFunctionCallToMcpTool,
|
||||||
|
mcpToolsToGeminiTools,
|
||||||
|
upsertMCPToolResponse
|
||||||
|
} from './mcpToolUtils'
|
||||||
|
|
||||||
export default class GeminiProvider extends BaseProvider {
|
export default class GeminiProvider extends BaseProvider {
|
||||||
private sdk: GoogleGenerativeAI
|
private sdk: GoogleGenerativeAI
|
||||||
@ -161,7 +167,7 @@ export default class GeminiProvider extends BaseProvider {
|
|||||||
for (const message of userMessages) {
|
for (const message of userMessages) {
|
||||||
history.push(await this.getMessageContents(message))
|
history.push(await this.getMessageContents(message))
|
||||||
}
|
}
|
||||||
|
mcpTools = filterMCPTools(mcpTools, userLastMessage?.enabledMCPs)
|
||||||
const tools = mcpToolsToGeminiTools(mcpTools)
|
const tools = mcpToolsToGeminiTools(mcpTools)
|
||||||
const toolResponses: MCPToolResponse[] = []
|
const toolResponses: MCPToolResponse[] = []
|
||||||
if (assistant.enableWebSearch && isWebSearchModel(model)) {
|
if (assistant.enableWebSearch && isWebSearchModel(model)) {
|
||||||
|
|||||||
@ -35,7 +35,13 @@ import {
|
|||||||
|
|
||||||
import { CompletionsParams } from '.'
|
import { CompletionsParams } from '.'
|
||||||
import BaseProvider from './BaseProvider'
|
import BaseProvider from './BaseProvider'
|
||||||
import { callMCPTool, mcpToolsToOpenAITools, openAIToolsToMcpTool, upsertMCPToolResponse } from './mcpToolUtils'
|
import {
|
||||||
|
callMCPTool,
|
||||||
|
filterMCPTools,
|
||||||
|
mcpToolsToOpenAITools,
|
||||||
|
openAIToolsToMcpTool,
|
||||||
|
upsertMCPToolResponse
|
||||||
|
} from './mcpToolUtils'
|
||||||
|
|
||||||
type ReasoningEffort = 'high' | 'medium' | 'low'
|
type ReasoningEffort = 'high' | 'medium' | 'low'
|
||||||
|
|
||||||
@ -298,6 +304,7 @@ export default class OpenAIProvider extends BaseProvider {
|
|||||||
const { abortController, cleanup } = this.createAbortController(lastUserMessage?.id)
|
const { abortController, cleanup } = this.createAbortController(lastUserMessage?.id)
|
||||||
const { signal } = abortController
|
const { signal } = abortController
|
||||||
|
|
||||||
|
mcpTools = filterMCPTools(mcpTools, lastUserMessage?.enabledMCPs)
|
||||||
const tools = mcpTools && mcpTools.length > 0 ? mcpToolsToOpenAITools(mcpTools) : undefined
|
const tools = mcpTools && mcpTools.length > 0 ? mcpToolsToOpenAITools(mcpTools) : undefined
|
||||||
|
|
||||||
const reqMessages: ChatCompletionMessageParam[] = [systemMessage, ...userMessages].filter(
|
const reqMessages: ChatCompletionMessageParam[] = [systemMessage, ...userMessages].filter(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Tool, ToolUnion, ToolUseBlock } from '@anthropic-ai/sdk/resources'
|
import { Tool, ToolUnion, ToolUseBlock } from '@anthropic-ai/sdk/resources'
|
||||||
import { FunctionCall, FunctionDeclaration, SchemaType, Tool as geminiToool } from '@google/generative-ai'
|
import { FunctionCall, FunctionDeclaration, SchemaType, Tool as geminiToool } from '@google/generative-ai'
|
||||||
import { MCPTool, MCPToolResponse } from '@renderer/types'
|
import { MCPServer, MCPTool, MCPToolResponse } from '@renderer/types'
|
||||||
import { ChatCompletionMessageToolCall, ChatCompletionTool } from 'openai/resources'
|
import { ChatCompletionMessageToolCall, ChatCompletionTool } from 'openai/resources'
|
||||||
|
|
||||||
import { ChunkCallbackData } from '.'
|
import { ChunkCallbackData } from '.'
|
||||||
@ -146,3 +146,17 @@ export function upsertMCPToolResponse(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterMCPTools(
|
||||||
|
mcpTools: MCPTool[] | undefined,
|
||||||
|
enabledServers: MCPServer[] | undefined
|
||||||
|
): MCPTool[] | undefined {
|
||||||
|
if (mcpTools) {
|
||||||
|
if (enabledServers) {
|
||||||
|
mcpTools = mcpTools.filter((t) => enabledServers.some((m) => m.name === t.serverName))
|
||||||
|
} else {
|
||||||
|
mcpTools = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mcpTools
|
||||||
|
}
|
||||||
|
|||||||
@ -67,6 +67,7 @@ export type Message = {
|
|||||||
askId?: string
|
askId?: string
|
||||||
useful?: boolean
|
useful?: boolean
|
||||||
error?: Record<string, any>
|
error?: Record<string, any>
|
||||||
|
enabledMCPs?: MCPServer[]
|
||||||
metadata?: {
|
metadata?: {
|
||||||
// Gemini
|
// Gemini
|
||||||
groundingMetadata?: any
|
groundingMetadata?: any
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user