feat: mcp tool permissions (#4348)
* feat(MCPSettings): add tool toggle functionality and update server configuration * fix(McpSettings): improve server type handling and tool fetching logic
This commit is contained in:
parent
4d5cfe06f5
commit
10848f7a45
@ -93,22 +93,17 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
||||
.join('\n')
|
||||
: ''
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [server])
|
||||
}, [server, form])
|
||||
|
||||
// Watch the serverType field to update the form layout dynamically
|
||||
useEffect(() => {
|
||||
const type = form.getFieldValue('serverType')
|
||||
type && setServerType(type)
|
||||
}, [form])
|
||||
|
||||
// Load tools on initial mount if server is active
|
||||
useEffect(() => {
|
||||
fetchTools()
|
||||
const currentServerType = form.getFieldValue('serverType')
|
||||
if (currentServerType) {
|
||||
setServerType(currentServerType)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [form.getFieldValue('serverType')])
|
||||
|
||||
const fetchTools = async () => {
|
||||
const fetchTools = useCallback(async () => {
|
||||
if (server.isActive) {
|
||||
try {
|
||||
setLoadingServer(server.id)
|
||||
@ -124,7 +119,15 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
||||
setLoadingServer(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [server.id])
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Loading tools for server:', server.id, 'Active:', server.isActive)
|
||||
if (server.isActive) {
|
||||
fetchTools()
|
||||
}
|
||||
}, [server.id, server.isActive, fetchTools])
|
||||
|
||||
// Save the form data
|
||||
const onSave = async () => {
|
||||
@ -241,10 +244,6 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
||||
[server, t]
|
||||
)
|
||||
|
||||
const onFormValuesChange = () => {
|
||||
setIsFormChanged(true)
|
||||
}
|
||||
|
||||
const formatError = (error: any) => {
|
||||
if (error.message.includes('32000')) {
|
||||
return t('settings.mcp.errors.32000')
|
||||
@ -278,6 +277,35 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle toggling a tool on/off
|
||||
const handleToggleTool = useCallback(
|
||||
async (tool: MCPTool, enabled: boolean) => {
|
||||
// Create a new disabledTools array or use the existing one
|
||||
let disabledTools = [...(server.disabledTools || [])]
|
||||
|
||||
if (enabled) {
|
||||
// Remove tool from disabledTools if it's being enabled
|
||||
disabledTools = disabledTools.filter((name) => name !== tool.name)
|
||||
} else {
|
||||
// Add tool to disabledTools if it's being disabled
|
||||
if (!disabledTools.includes(tool.name)) {
|
||||
disabledTools.push(tool.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the server with new disabledTools
|
||||
const updatedServer = {
|
||||
...server,
|
||||
disabledTools
|
||||
}
|
||||
|
||||
// Save the updated server configuration
|
||||
// await window.api.mcp.updateServer(updatedServer)
|
||||
updateMCPServer(updatedServer)
|
||||
},
|
||||
[server, updateMCPServer]
|
||||
)
|
||||
|
||||
return (
|
||||
<SettingContainer>
|
||||
<SettingGroup style={{ marginBottom: 0 }}>
|
||||
@ -302,7 +330,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onValuesChange={onFormValuesChange}
|
||||
onValuesChange={() => setIsFormChanged(true)}
|
||||
style={{
|
||||
// height: 'calc(100vh - var(--navbar-height) - 315px)',
|
||||
overflowY: 'auto',
|
||||
@ -380,7 +408,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
{server.isActive && <MCPToolsSection tools={tools} />}
|
||||
{server.isActive && <MCPToolsSection tools={tools} server={server} onToggleTool={handleToggleTool} />}
|
||||
</SettingGroup>
|
||||
</SettingContainer>
|
||||
)
|
||||
|
||||
@ -1,11 +1,27 @@
|
||||
import { MCPTool } from '@renderer/types'
|
||||
import { Badge, Collapse, Descriptions, Empty, Flex, Tag, Tooltip, Typography } from 'antd'
|
||||
import { MCPServer, MCPTool } from '@renderer/types'
|
||||
import { Badge, Collapse, Descriptions, Empty, Flex, Switch, Tag, Tooltip, Typography } from 'antd'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const MCPToolsSection = ({ tools }: { tools: MCPTool[] }) => {
|
||||
interface MCPToolsSectionProps {
|
||||
tools: MCPTool[]
|
||||
server: MCPServer
|
||||
onToggleTool: (tool: MCPTool, enabled: boolean) => void
|
||||
}
|
||||
|
||||
const MCPToolsSection = ({ tools, server, onToggleTool }: MCPToolsSectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 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 renderToolProperties = (tool: MCPTool) => {
|
||||
if (!tool.inputSchema?.properties) return null
|
||||
@ -86,18 +102,21 @@ const MCPToolsSection = ({ tools }: { tools: MCPTool[] }) => {
|
||||
<Collapse.Panel
|
||||
key={tool.id}
|
||||
header={
|
||||
<Flex vertical align="flex-start" style={{ width: '100%' }}>
|
||||
<Flex align="center" style={{ width: '100%' }}>
|
||||
<Typography.Text strong>{tool.name}</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ marginLeft: 8, fontSize: '12px' }}>
|
||||
{tool.id}
|
||||
</Typography.Text>
|
||||
<Flex justify="space-between" align="center" style={{ width: '100%' }}>
|
||||
<Flex vertical align="flex-start">
|
||||
<Flex align="center" style={{ width: '100%' }}>
|
||||
<Typography.Text strong>{tool.name}</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ marginLeft: 8, fontSize: '12px' }}>
|
||||
{tool.id}
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
{tool.description && (
|
||||
<Typography.Text type="secondary" style={{ fontSize: '13px', marginTop: 4 }}>
|
||||
{tool.description}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Flex>
|
||||
{tool.description && (
|
||||
<Typography.Text type="secondary" style={{ fontSize: '13px', marginTop: 4 }}>
|
||||
{tool.description}
|
||||
</Typography.Text>
|
||||
)}
|
||||
<Switch checked={isToolEnabled(tool)} onChange={(checked) => handleToggle(tool, checked)} />
|
||||
</Flex>
|
||||
}>
|
||||
<SelectableContent>{renderToolProperties(tool)}</SelectableContent>
|
||||
|
||||
@ -106,8 +106,8 @@ export async function fetchChatCompletion({
|
||||
if (enabledMCPs && enabledMCPs.length > 0) {
|
||||
for (const mcpServer of enabledMCPs) {
|
||||
const tools = await window.api.mcp.listTools(mcpServer)
|
||||
console.debug('tools', tools)
|
||||
mcpTools.push(...tools)
|
||||
const availableTools = tools.filter((tool: any) => !mcpServer.disabledTools?.includes(tool.name))
|
||||
mcpTools.push(...availableTools)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -372,6 +372,7 @@ export interface MCPServer {
|
||||
args?: string[]
|
||||
env?: Record<string, string>
|
||||
isActive: boolean
|
||||
disabledTools?: string[] // List of tool names that are disabled for this server
|
||||
}
|
||||
|
||||
export interface MCPToolInputSchema {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user