feat: add tool management features including toggle and description updates (#163)

Co-authored-by: samanhappy@qq.com <my6051199>
This commit is contained in:
samanhappy
2025-06-04 16:03:45 +08:00
committed by GitHub
parent 4039a85ee1
commit 503b60edb7
12 changed files with 518 additions and 57 deletions

View File

@@ -11,10 +11,11 @@ interface ServerCardProps {
server: Server
onRemove: (serverName: string) => void
onEdit: (server: Server) => void
onToggle?: (server: Server, enabled: boolean) => void
onToggle?: (server: Server, enabled: boolean) => Promise<boolean>
onRefresh?: () => void
}
const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) => {
const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCardProps) => {
const { t } = useTranslation()
const { showToast } = useToast()
const [isExpanded, setIsExpanded] = useState(false)
@@ -102,6 +103,29 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
setShowDeleteDialog(false)
}
const handleToolToggle = async (toolName: string, enabled: boolean) => {
try {
const { toggleTool } = await import('@/services/toolService')
const result = await toggleTool(server.name, toolName, enabled)
if (result.success) {
showToast(
t(enabled ? 'tool.enableSuccess' : 'tool.disableSuccess', { name: toolName }),
'success'
)
// Trigger refresh to update the tool's state in the UI
if (onRefresh) {
onRefresh()
}
} else {
showToast(result.error || t('tool.toggleFailed'), 'error')
}
} catch (error) {
console.error('Error toggling tool:', error)
showToast(t('tool.toggleFailed'), 'error')
}
}
return (
<>
<div className={`bg-white shadow rounded-lg p-6 mb-6 ${server.enabled === false ? 'opacity-60' : ''}`}>
@@ -217,7 +241,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle }: ServerCardProps) =>
<h6 className={`font-medium ${server.enabled === false ? 'text-gray-600' : 'text-gray-900'} mb-4`}>{t('server.tools')}</h6>
<div className="space-y-4">
{server.tools.map((tool, index) => (
<ToolCard key={index} server={server.name} tool={tool} />
<ToolCard key={index} server={server.name} tool={tool} onToggle={handleToolToggle} />
))}
</div>
</div>

View File

@@ -1,22 +1,48 @@
import { useState, useCallback } from 'react'
import { useState, useCallback, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Tool } from '@/types'
import { ChevronDown, ChevronRight, Play, Loader } from '@/components/icons/LucideIcons'
import { callTool, ToolCallResult } from '@/services/toolService'
import { ChevronDown, ChevronRight, Play, Loader, Edit, Check } from '@/components/icons/LucideIcons'
import { callTool, ToolCallResult, updateToolDescription } from '@/services/toolService'
import { Switch } from './ToggleGroup'
import DynamicForm from './DynamicForm'
import ToolResult from './ToolResult'
interface ToolCardProps {
server: string
tool: Tool
onToggle?: (toolName: string, enabled: boolean) => void
onDescriptionUpdate?: (toolName: string, description: string) => void
}
const ToolCard = ({ tool, server }: ToolCardProps) => {
const ToolCard = ({ tool, server, onToggle, onDescriptionUpdate }: ToolCardProps) => {
const { t } = useTranslation()
const [isExpanded, setIsExpanded] = useState(false)
const [showRunForm, setShowRunForm] = useState(false)
const [isRunning, setIsRunning] = useState(false)
const [result, setResult] = useState<ToolCallResult | null>(null)
const [isEditingDescription, setIsEditingDescription] = useState(false)
const [customDescription, setCustomDescription] = useState(tool.description || '')
const descriptionInputRef = useRef<HTMLInputElement>(null)
const descriptionTextRef = useRef<HTMLSpanElement>(null)
const [textWidth, setTextWidth] = useState<number>(0)
// Focus the input when editing mode is activated
useEffect(() => {
if (isEditingDescription && descriptionInputRef.current) {
descriptionInputRef.current.focus()
// Set input width to match text width
if (textWidth > 0) {
descriptionInputRef.current.style.width = `${textWidth + 20}px` // Add some padding
}
}
}, [isEditingDescription, textWidth])
// Measure text width when not editing
useEffect(() => {
if (!isEditingDescription && descriptionTextRef.current) {
setTextWidth(descriptionTextRef.current.offsetWidth)
}
}, [isEditingDescription, customDescription])
// Generate a unique key for localStorage based on tool name and server
const getStorageKey = useCallback(() => {
@@ -28,6 +54,49 @@ const ToolCard = ({ tool, server }: ToolCardProps) => {
localStorage.removeItem(getStorageKey())
}, [getStorageKey])
const handleToggle = (enabled: boolean) => {
if (onToggle) {
onToggle(tool.name, enabled)
}
}
const handleDescriptionEdit = () => {
setIsEditingDescription(true)
}
const handleDescriptionSave = async () => {
try {
const result = await updateToolDescription(server, tool.name, customDescription)
if (result.success) {
setIsEditingDescription(false)
if (onDescriptionUpdate) {
onDescriptionUpdate(tool.name, customDescription)
}
} else {
// Revert on error
setCustomDescription(tool.description || '')
console.error('Failed to update tool description:', result.error)
}
} catch (error) {
console.error('Error updating tool description:', error)
setCustomDescription(tool.description || '')
setIsEditingDescription(false)
}
}
const handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setCustomDescription(e.target.value)
}
const handleDescriptionKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleDescriptionSave()
} else if (e.key === 'Escape') {
setCustomDescription(tool.description || '')
setIsEditingDescription(false)
}
}
const handleRunTool = async (arguments_: Record<string, any>) => {
setIsRunning(true)
try {
@@ -68,13 +137,61 @@ const ToolCard = ({ tool, server }: ToolCardProps) => {
>
<div className="flex-1">
<h3 className="text-lg font-medium text-gray-900">
{tool.name}
<span className="ml-2 text-sm font-normal text-gray-600">
{tool.description || t('tool.noDescription')}
{tool.name.replace(server + '/', '')}
<span className="ml-2 text-sm font-normal text-gray-600 inline-flex items-center">
{isEditingDescription ? (
<>
<input
ref={descriptionInputRef}
type="text"
className="px-2 py-1 border border-blue-300 rounded bg-white text-sm"
value={customDescription}
onChange={handleDescriptionChange}
onKeyDown={handleDescriptionKeyDown}
onClick={(e) => e.stopPropagation()}
style={{
minWidth: '100px',
width: textWidth > 0 ? `${textWidth + 20}px` : 'auto'
}}
/>
<button
className="ml-2 p-1 text-green-600 hover:text-green-800"
onClick={(e) => {
e.stopPropagation()
handleDescriptionSave()
}}
>
<Check size={16} />
</button>
</>
) : (
<>
<span ref={descriptionTextRef}>{customDescription || t('tool.noDescription')}</span>
<button
className="ml-2 p-1 text-gray-500 hover:text-blue-600 transition-colors"
onClick={(e) => {
e.stopPropagation()
handleDescriptionEdit()
}}
>
<Edit size={14} />
</button>
</>
)}
</span>
</h3>
</div>
<div className="flex items-center space-x-2">
<div
className="flex items-center space-x-2"
onClick={(e) => e.stopPropagation()}
>
<Switch
checked={tool.enabled ?? true}
onCheckedChange={handleToggle}
disabled={isRunning}
/>
</div>
<button
onClick={(e) => {
e.stopPropagation()
@@ -82,7 +199,7 @@ const ToolCard = ({ tool, server }: ToolCardProps) => {
setShowRunForm(true)
}}
className="flex items-center space-x-1 px-3 py-1 text-sm text-blue-600 bg-blue-50 hover:bg-blue-100 rounded-md transition-colors"
disabled={isRunning}
disabled={isRunning || !tool.enabled}
>
{isRunning ? (
<Loader size={14} className="animate-spin" />
@@ -112,7 +229,7 @@ const ToolCard = ({ tool, server }: ToolCardProps) => {
{/* Run Form */}
{showRunForm && (
<div className="border border-gray-300 rounded-lg p-4 bg-blue-50">
<h4 className="text-sm font-medium text-gray-900 mb-3">{t('tool.runToolWithName', { name: tool.name })}</h4>
<h4 className="text-sm font-medium text-gray-900 mb-3">{t('tool.runToolWithName', { name: tool.name.replace(server + '/', '') })}</h4>
<DynamicForm
schema={tool.inputSchema || { type: 'object' }}
onSubmit={handleRunTool}

View File

@@ -284,7 +284,11 @@
"toolResult": "Tool result",
"noParameters": "This tool does not require any parameters.",
"selectOption": "Select an option",
"enterValue": "Enter {{type}} value"
"enterValue": "Enter {{type}} value",
"enabled": "Enabled",
"enableSuccess": "Tool {{name}} enabled successfully",
"disableSuccess": "Tool {{name}} disabled successfully",
"toggleFailed": "Failed to toggle tool status"
},
"settings": {
"enableGlobalRoute": "Enable Global Route",

View File

@@ -285,7 +285,11 @@
"toolResult": "工具结果",
"noParameters": "此工具不需要任何参数。",
"selectOption": "选择一个选项",
"enterValue": "输入{{type}}值"
"enterValue": "输入{{type}}值",
"enabled": "已启用",
"enableSuccess": "工具 {{name}} 启用成功",
"disableSuccess": "工具 {{name}} 禁用成功",
"toggleFailed": "切换工具状态失败"
},
"settings": {
"enableGlobalRoute": "启用全局路由",

View File

@@ -125,6 +125,7 @@ const ServersPage: React.FC = () => {
onRemove={handleServerRemove}
onEdit={handleEditClick}
onToggle={handleServerToggle}
onRefresh={triggerRefresh}
/>
))}
</div>

View File

@@ -70,3 +70,90 @@ export const callTool = async (
};
}
};
/**
* Toggle a tool's enabled state for a specific server
*/
export const toggleTool = async (
serverName: string,
toolName: string,
enabled: boolean,
): Promise<{ success: boolean; error?: string }> => {
try {
const token = getToken();
if (!token) {
throw new Error('Authentication token not found. Please log in.');
}
const response = await fetch(getApiUrl(`/servers/${serverName}/tools/${toolName}/toggle`), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token,
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ enabled }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return {
success: data.success,
error: data.success ? undefined : data.message,
};
} catch (error) {
console.error('Error toggling tool:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
};
}
};
/**
* Update a tool's description for a specific server
*/
export const updateToolDescription = async (
serverName: string,
toolName: string,
description: string,
): Promise<{ success: boolean; error?: string }> => {
try {
const token = getToken();
if (!token) {
throw new Error('Authentication token not found. Please log in.');
}
const response = await fetch(
getApiUrl(`/servers/${serverName}/tools/${toolName}/description`),
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'x-auth-token': token,
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ description }),
},
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return {
success: data.success,
error: data.success ? undefined : data.message,
};
} catch (error) {
console.error('Error updating tool description:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
};
}
};

View File

@@ -67,6 +67,7 @@ export interface Tool {
name: string;
description: string;
inputSchema: ToolInputSchema;
enabled?: boolean;
}
// Server config types
@@ -78,6 +79,7 @@ export interface ServerConfig {
env?: Record<string, string>;
headers?: Record<string, string>;
enabled?: boolean;
tools?: Record<string, { enabled: boolean; description?: string }>; // Tool-specific configurations with enable/disable state and custom descriptions
}
// Server types

View File

@@ -6,6 +6,7 @@ import {
removeServer,
updateMcpServer,
notifyToolChanged,
syncToolEmbedding,
toggleServerStatus,
} from '../services/mcpService.js';
import { loadSettings, saveSettings } from '../config/index.js';
@@ -318,6 +319,136 @@ export const toggleServer = async (req: Request, res: Response): Promise<void> =
}
};
// Toggle tool status for a specific server
export const toggleTool = async (req: Request, res: Response): Promise<void> => {
try {
const { serverName, toolName } = req.params;
const { enabled } = req.body;
if (!serverName || !toolName) {
res.status(400).json({
success: false,
message: 'Server name and tool name are required',
});
return;
}
if (typeof enabled !== 'boolean') {
res.status(400).json({
success: false,
message: 'Enabled status must be a boolean',
});
return;
}
const settings = loadSettings();
if (!settings.mcpServers[serverName]) {
res.status(404).json({
success: false,
message: 'Server not found',
});
return;
}
// Initialize tools config if it doesn't exist
if (!settings.mcpServers[serverName].tools) {
settings.mcpServers[serverName].tools = {};
}
// Set the tool's enabled state
settings.mcpServers[serverName].tools![toolName] = { enabled };
if (!saveSettings(settings)) {
res.status(500).json({
success: false,
message: 'Failed to save settings',
});
return;
}
// Notify that tools have changed
notifyToolChanged();
res.json({
success: true,
message: `Tool ${toolName} ${enabled ? 'enabled' : 'disabled'} successfully`,
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error',
});
}
};
// Update tool description for a specific server
export const updateToolDescription = async (req: Request, res: Response): Promise<void> => {
try {
const { serverName, toolName } = req.params;
const { description } = req.body;
if (!serverName || !toolName) {
res.status(400).json({
success: false,
message: 'Server name and tool name are required',
});
return;
}
if (typeof description !== 'string') {
res.status(400).json({
success: false,
message: 'Description must be a string',
});
return;
}
const settings = loadSettings();
if (!settings.mcpServers[serverName]) {
res.status(404).json({
success: false,
message: 'Server not found',
});
return;
}
// Initialize tools config if it doesn't exist
if (!settings.mcpServers[serverName].tools) {
settings.mcpServers[serverName].tools = {};
}
// Set the tool's description
if (!settings.mcpServers[serverName].tools![toolName]) {
settings.mcpServers[serverName].tools![toolName] = { enabled: true };
}
settings.mcpServers[serverName].tools![toolName].description = description;
if (!saveSettings(settings)) {
res.status(500).json({
success: false,
message: 'Failed to save settings',
});
return;
}
// Notify that tools have changed
notifyToolChanged();
syncToolEmbedding(serverName, toolName);
res.json({
success: true,
message: `Tool ${toolName} description updated successfully`,
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Internal server error',
});
}
};
export const updateSystemConfig = (req: Request, res: Response): void => {
try {
const { routing, install, smartRouting } = req.body;

View File

@@ -8,6 +8,8 @@ import {
updateServer,
deleteServer,
toggleServer,
toggleTool,
updateToolDescription,
updateSystemConfig,
} from '../controllers/serverController.js';
import {
@@ -46,6 +48,8 @@ export const initRoutes = (app: express.Application): void => {
router.put('/servers/:name', updateServer);
router.delete('/servers/:name', deleteServer);
router.post('/servers/:name/toggle', toggleServer);
router.post('/servers/:serverName/tools/:toolName/toggle', toggleTool);
router.put('/servers/:serverName/tools/:toolName/description', updateToolDescription);
router.put('/system-config', updateSystemConfig);
// Group management routes

View File

@@ -4,12 +4,11 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { ServerInfo, ServerConfig } from '../types/index.js';
import { ServerInfo, ServerConfig, ToolInfo } from '../types/index.js';
import { loadSettings, saveSettings, expandEnvVars, replaceEnvVars } from '../config/index.js';
import config from '../config/index.js';
import { getGroup } from './sseService.js';
import { getServersInGroup } from './groupService.js';
import { getSmartRoutingConfig } from '../utils/smartRouting.js';
import { saveToolsAsVectorEmbeddings, searchToolsByVector } from './vectorSearchService.js';
const servers: { [sessionId: string]: Server } = {};
@@ -51,6 +50,21 @@ export const notifyToolChanged = async () => {
});
};
export const syncToolEmbedding = async (serverName: string, toolName: string) => {
const serverInfo = getServerByName(serverName);
if (!serverInfo) {
console.warn(`Server not found: ${serverName}`);
return;
}
const tool = serverInfo.tools.find((t) => t.name === toolName);
if (!tool) {
console.warn(`Tool not found: ${toolName} on server: ${serverName}`);
return;
}
// Save tool as vector embedding for search
saveToolsAsVectorEmbeddings(serverName, [tool]);
};
// Store all server information
let serverInfos: ServerInfo[] = [];
@@ -189,27 +203,15 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
}
serverInfo.tools = tools.tools.map((tool) => ({
name: tool.name,
name: name + '/' + tool.name,
description: tool.description || '',
inputSchema: tool.inputSchema || {},
}));
serverInfo.status = 'connected';
serverInfo.error = null;
// Save tools as vector embeddings for search (only when smart routing is enabled)
if (serverInfo.tools.length > 0) {
try {
const smartRoutingConfig = getSmartRoutingConfig();
if (smartRoutingConfig.enabled) {
console.log(
`Smart routing enabled - saving vector embeddings for server ${name}`,
);
saveToolsAsVectorEmbeddings(name, serverInfo.tools);
}
} catch (vectorError) {
console.warn(`Failed to save vector embeddings for server ${name}:`, vectorError);
}
}
// Save tools as vector embeddings for search
saveToolsAsVectorEmbeddings(name, serverInfo.tools);
})
.catch((error) => {
console.error(
@@ -258,11 +260,22 @@ export const getServersInfo = (): Omit<ServerInfo, 'client' | 'transport'>[] =>
const infos = serverInfos.map(({ name, status, tools, createTime, error }) => {
const serverConfig = settings.mcpServers[name];
const enabled = serverConfig ? serverConfig.enabled !== false : true;
// Add enabled status and custom description to each tool
const toolsWithEnabled = tools.map((tool) => {
const toolConfig = serverConfig?.tools?.[tool.name];
return {
...tool,
description: toolConfig?.description || tool.description, // Use custom description if available
enabled: toolConfig?.enabled !== false, // Default to true if not explicitly disabled
};
});
return {
name,
status,
error,
tools,
tools: toolsWithEnabled,
createTime,
enabled,
};
@@ -279,6 +292,23 @@ const getServerByName = (name: string): ServerInfo | undefined => {
return serverInfos.find((serverInfo) => serverInfo.name === name);
};
// Filter tools by server configuration
const filterToolsByConfig = (serverName: string, tools: ToolInfo[]): ToolInfo[] => {
const settings = loadSettings();
const serverConfig = settings.mcpServers[serverName];
if (!serverConfig || !serverConfig.tools) {
// If no tool configuration exists, all tools are enabled by default
return tools;
}
return tools.filter((tool) => {
const toolConfig = serverConfig.tools?.[tool.name];
// If tool is not in config, it's enabled by default
return toolConfig?.enabled !== false;
});
};
// Get server by tool name
const getServerByTool = (toolName: string): ServerInfo | undefined => {
return serverInfos.find((serverInfo) => serverInfo.tools.some((tool) => tool.name === toolName));
@@ -489,7 +519,21 @@ Available servers: ${serversList}`;
const allTools = [];
for (const serverInfo of allServerInfos) {
if (serverInfo.tools && serverInfo.tools.length > 0) {
allTools.push(...serverInfo.tools);
// Filter tools based on server configuration and apply custom descriptions
const enabledTools = filterToolsByConfig(serverInfo.name, serverInfo.tools);
// Apply custom descriptions from configuration
const settings = loadSettings();
const serverConfig = settings.mcpServers[serverInfo.name];
const toolsWithCustomDescriptions = enabledTools.map((tool) => {
const toolConfig = serverConfig?.tools?.[tool.name];
return {
...tool,
description: toolConfig?.description || tool.description, // Use custom description if available
};
});
allTools.push(...toolsWithCustomDescriptions);
}
}
@@ -530,30 +574,54 @@ export const handleCallToolRequest = async (request: any, extra: any) => {
const searchResults = await searchToolsByVector(query, limitNum, thresholdNum, servers);
console.log(`Search results: ${JSON.stringify(searchResults)}`);
// Find actual tool information from serverInfos by serverName and toolName
const tools = searchResults.map((result) => {
// Find the server in serverInfos
const server = serverInfos.find(
(serverInfo) =>
serverInfo.name === result.serverName &&
serverInfo.status === 'connected' &&
serverInfo.enabled !== false,
);
if (server && server.tools && server.tools.length > 0) {
// Find the tool in server.tools
const actualTool = server.tools.find((tool) => tool.name === result.toolName);
if (actualTool) {
// Return the actual tool info from serverInfos
return actualTool;
}
}
const tools = searchResults
.map((result) => {
// Find the server in serverInfos
const server = serverInfos.find(
(serverInfo) =>
serverInfo.name === result.serverName &&
serverInfo.status === 'connected' &&
serverInfo.enabled !== false,
);
if (server && server.tools && server.tools.length > 0) {
// Find the tool in server.tools
const actualTool = server.tools.find((tool) => tool.name === result.toolName);
if (actualTool) {
// Check if the tool is enabled in configuration
const enabledTools = filterToolsByConfig(server.name, [actualTool]);
if (enabledTools.length > 0) {
// Apply custom description from configuration
const settings = loadSettings();
const serverConfig = settings.mcpServers[server.name];
const toolConfig = serverConfig?.tools?.[actualTool.name];
// Fallback to search result if server or tool not found
return {
name: result.toolName,
description: result.description || '',
inputSchema: result.inputSchema || {},
};
});
// Return the actual tool info from serverInfos with custom description
return {
...actualTool,
description: toolConfig?.description || actualTool.description,
};
}
}
}
// Fallback to search result if server or tool not found or disabled
return {
name: result.toolName,
description: result.description || '',
inputSchema: result.inputSchema || {},
};
})
.filter((tool) => {
// Additional filter to remove tools that are disabled
if (tool.name) {
const serverName = searchResults.find((r) => r.toolName === tool.name)?.serverName;
if (serverName) {
const enabledTools = filterToolsByConfig(serverName, [tool as ToolInfo]);
return enabledTools.length > 0;
}
}
return true; // Keep fallback results
});
// Add usage guidance to the response
const response = {
@@ -586,7 +654,7 @@ export const handleCallToolRequest = async (request: any, extra: any) => {
// Special handling for call_tool
if (request.params.name === 'call_tool') {
const { toolName, arguments: toolArgs = {} } = request.params.arguments || {};
let { toolName, arguments: toolArgs = {} } = request.params.arguments || {};
if (!toolName) {
throw new Error('toolName parameter is required');
@@ -631,6 +699,9 @@ export const handleCallToolRequest = async (request: any, extra: any) => {
`Invoking tool '${toolName}' on server '${targetServerInfo.name}' with arguments: ${JSON.stringify(finalArgs)}`,
);
toolName = toolName.startsWith(`${targetServerInfo.name}/`)
? toolName.replace(`${targetServerInfo.name}/`, '')
: toolName;
const result = await client.callTool({
name: toolName,
arguments: finalArgs,
@@ -649,6 +720,10 @@ export const handleCallToolRequest = async (request: any, extra: any) => {
if (!client) {
throw new Error(`Client not found for server: ${request.params.name}`);
}
request.params.name = request.params.name.startsWith(`${serverInfo.name}/`)
? request.params.name.replace(`${serverInfo.name}/`, '')
: request.params.name;
const result = await client.callTool(request.params);
console.log(`Tool call result: ${JSON.stringify(result)}`);
return result;

View File

@@ -193,6 +193,16 @@ export const saveToolsAsVectorEmbeddings = async (
tools: ToolInfo[],
): Promise<void> => {
try {
if (tools.length === 0) {
console.warn(`No tools to save for server: ${serverName}`);
return;
}
const smartRoutingConfig = getSmartRoutingConfig();
if (!smartRoutingConfig.enabled) {
return;
}
const config = getOpenAIConfig();
const vectorRepository = getRepositoryFactory(
'vectorEmbeddings',

View File

@@ -105,6 +105,7 @@ export interface ServerConfig {
env?: Record<string, string>; // Environment variables
headers?: Record<string, string>; // HTTP headers for SSE/streamable-http servers
enabled?: boolean; // Flag to enable/disable the server
tools?: Record<string, { enabled: boolean; description?: string }>; // Tool-specific configurations with enable/disable state and custom descriptions
}
// Information about a server's status and tools
@@ -124,6 +125,7 @@ export interface ToolInfo {
name: string; // Name of the tool
description: string; // Brief description of the tool
inputSchema: Record<string, unknown>; // Input schema for the tool
enabled?: boolean; // Whether the tool is enabled (optional, defaults to true)
}
// Standardized API response structure