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')
|
.join('\n')
|
||||||
: ''
|
: ''
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [server, form])
|
||||||
}, [server])
|
|
||||||
|
|
||||||
// Watch the serverType field to update the form layout dynamically
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const type = form.getFieldValue('serverType')
|
const currentServerType = form.getFieldValue('serverType')
|
||||||
type && setServerType(type)
|
if (currentServerType) {
|
||||||
}, [form])
|
setServerType(currentServerType)
|
||||||
|
}
|
||||||
// Load tools on initial mount if server is active
|
|
||||||
useEffect(() => {
|
|
||||||
fetchTools()
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [form.getFieldValue('serverType')])
|
||||||
|
|
||||||
const fetchTools = async () => {
|
const fetchTools = useCallback(async () => {
|
||||||
if (server.isActive) {
|
if (server.isActive) {
|
||||||
try {
|
try {
|
||||||
setLoadingServer(server.id)
|
setLoadingServer(server.id)
|
||||||
@ -124,7 +119,15 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
setLoadingServer(null)
|
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
|
// Save the form data
|
||||||
const onSave = async () => {
|
const onSave = async () => {
|
||||||
@ -241,10 +244,6 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
[server, t]
|
[server, t]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onFormValuesChange = () => {
|
|
||||||
setIsFormChanged(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatError = (error: any) => {
|
const formatError = (error: any) => {
|
||||||
if (error.message.includes('32000')) {
|
if (error.message.includes('32000')) {
|
||||||
return t('settings.mcp.errors.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 (
|
return (
|
||||||
<SettingContainer>
|
<SettingContainer>
|
||||||
<SettingGroup style={{ marginBottom: 0 }}>
|
<SettingGroup style={{ marginBottom: 0 }}>
|
||||||
@ -302,7 +330,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
onValuesChange={onFormValuesChange}
|
onValuesChange={() => setIsFormChanged(true)}
|
||||||
style={{
|
style={{
|
||||||
// height: 'calc(100vh - var(--navbar-height) - 315px)',
|
// height: 'calc(100vh - var(--navbar-height) - 315px)',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
@ -380,7 +408,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
{server.isActive && <MCPToolsSection tools={tools} />}
|
{server.isActive && <MCPToolsSection tools={tools} server={server} onToggleTool={handleToggleTool} />}
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,27 @@
|
|||||||
import { MCPTool } from '@renderer/types'
|
import { MCPServer, MCPTool } from '@renderer/types'
|
||||||
import { Badge, Collapse, Descriptions, Empty, Flex, Tag, Tooltip, Typography } from 'antd'
|
import { Badge, Collapse, Descriptions, Empty, Flex, Switch, Tag, Tooltip, Typography } from 'antd'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
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()
|
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
|
// Render tool properties from the input schema
|
||||||
const renderToolProperties = (tool: MCPTool) => {
|
const renderToolProperties = (tool: MCPTool) => {
|
||||||
if (!tool.inputSchema?.properties) return null
|
if (!tool.inputSchema?.properties) return null
|
||||||
@ -86,7 +102,8 @@ const MCPToolsSection = ({ tools }: { tools: MCPTool[] }) => {
|
|||||||
<Collapse.Panel
|
<Collapse.Panel
|
||||||
key={tool.id}
|
key={tool.id}
|
||||||
header={
|
header={
|
||||||
<Flex vertical align="flex-start" style={{ width: '100%' }}>
|
<Flex justify="space-between" align="center" style={{ width: '100%' }}>
|
||||||
|
<Flex vertical align="flex-start">
|
||||||
<Flex align="center" style={{ width: '100%' }}>
|
<Flex align="center" style={{ width: '100%' }}>
|
||||||
<Typography.Text strong>{tool.name}</Typography.Text>
|
<Typography.Text strong>{tool.name}</Typography.Text>
|
||||||
<Typography.Text type="secondary" style={{ marginLeft: 8, fontSize: '12px' }}>
|
<Typography.Text type="secondary" style={{ marginLeft: 8, fontSize: '12px' }}>
|
||||||
@ -99,6 +116,8 @@ const MCPToolsSection = ({ tools }: { tools: MCPTool[] }) => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Switch checked={isToolEnabled(tool)} onChange={(checked) => handleToggle(tool, checked)} />
|
||||||
|
</Flex>
|
||||||
}>
|
}>
|
||||||
<SelectableContent>{renderToolProperties(tool)}</SelectableContent>
|
<SelectableContent>{renderToolProperties(tool)}</SelectableContent>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
|
|||||||
@ -106,8 +106,8 @@ export async function fetchChatCompletion({
|
|||||||
if (enabledMCPs && enabledMCPs.length > 0) {
|
if (enabledMCPs && enabledMCPs.length > 0) {
|
||||||
for (const mcpServer of enabledMCPs) {
|
for (const mcpServer of enabledMCPs) {
|
||||||
const tools = await window.api.mcp.listTools(mcpServer)
|
const tools = await window.api.mcp.listTools(mcpServer)
|
||||||
console.debug('tools', tools)
|
const availableTools = tools.filter((tool: any) => !mcpServer.disabledTools?.includes(tool.name))
|
||||||
mcpTools.push(...tools)
|
mcpTools.push(...availableTools)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -372,6 +372,7 @@ export interface MCPServer {
|
|||||||
args?: string[]
|
args?: string[]
|
||||||
env?: Record<string, string>
|
env?: Record<string, string>
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
disabledTools?: string[] // List of tool names that are disabled for this server
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MCPToolInputSchema {
|
export interface MCPToolInputSchema {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user