|
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; |
|
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; |
|
|
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 { 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 } = {}; |
|
|
|
|
|
export const initUpstreamServers = async (): Promise<void> => { |
|
|
await registerAllTools(true); |
|
|
}; |
|
|
|
|
|
export const getMcpServer = (sessionId?: string, group?: string): Server => { |
|
|
if (!sessionId) { |
|
|
return createMcpServer(config.mcpHubName, config.mcpHubVersion, group); |
|
|
} |
|
|
|
|
|
if (!servers[sessionId]) { |
|
|
const serverGroup = group || getGroup(sessionId); |
|
|
const server = createMcpServer(config.mcpHubName, config.mcpHubVersion, serverGroup); |
|
|
servers[sessionId] = server; |
|
|
} else { |
|
|
console.log(`MCP server already exists for sessionId: ${sessionId}`); |
|
|
} |
|
|
return servers[sessionId]; |
|
|
}; |
|
|
|
|
|
export const deleteMcpServer = (sessionId: string): void => { |
|
|
delete servers[sessionId]; |
|
|
}; |
|
|
|
|
|
export const notifyToolChanged = async () => { |
|
|
await registerAllTools(false); |
|
|
Object.values(servers).forEach((server) => { |
|
|
server |
|
|
.sendToolListChanged() |
|
|
.catch((error) => { |
|
|
console.warn('Failed to send tool list changed notification:', error.message); |
|
|
}) |
|
|
.then(() => { |
|
|
console.log('Tool list changed notification sent successfully'); |
|
|
}); |
|
|
}); |
|
|
}; |
|
|
|
|
|
|
|
|
let serverInfos: ServerInfo[] = []; |
|
|
|
|
|
|
|
|
export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] => { |
|
|
const settings = loadSettings(); |
|
|
const existingServerInfos = serverInfos; |
|
|
serverInfos = []; |
|
|
|
|
|
for (const [name, conf] of Object.entries(settings.mcpServers)) { |
|
|
|
|
|
if (conf.enabled === false) { |
|
|
console.log(`Skipping disabled server: ${name}`); |
|
|
serverInfos.push({ |
|
|
name, |
|
|
status: 'disconnected', |
|
|
error: null, |
|
|
tools: [], |
|
|
createTime: Date.now(), |
|
|
enabled: false, |
|
|
}); |
|
|
continue; |
|
|
} |
|
|
|
|
|
|
|
|
const existingServer = existingServerInfos.find( |
|
|
(s) => s.name === name && s.status === 'connected', |
|
|
); |
|
|
if (existingServer) { |
|
|
serverInfos.push({ |
|
|
...existingServer, |
|
|
enabled: conf.enabled === undefined ? true : conf.enabled, |
|
|
}); |
|
|
console.log(`Server '${name}' is already connected.`); |
|
|
continue; |
|
|
} |
|
|
|
|
|
let transport; |
|
|
if (conf.type === 'streamable-http') { |
|
|
const options: any = {}; |
|
|
if (conf.headers && Object.keys(conf.headers).length > 0) { |
|
|
options.requestInit = { |
|
|
headers: conf.headers, |
|
|
}; |
|
|
} |
|
|
transport = new StreamableHTTPClientTransport(new URL(conf.url || ''), options); |
|
|
} else if (conf.url) { |
|
|
|
|
|
const options: any = {}; |
|
|
if (conf.headers && Object.keys(conf.headers).length > 0) { |
|
|
options.eventSourceInit = { |
|
|
headers: conf.headers, |
|
|
}; |
|
|
options.requestInit = { |
|
|
headers: conf.headers, |
|
|
}; |
|
|
} |
|
|
transport = new SSEClientTransport(new URL(conf.url), options); |
|
|
} else if (conf.command && conf.args) { |
|
|
|
|
|
const env: Record<string, string> = { |
|
|
...(process.env as Record<string, string>), |
|
|
...replaceEnvVars(conf.env || {}), |
|
|
}; |
|
|
env['PATH'] = expandEnvVars(process.env.PATH as string) || ''; |
|
|
|
|
|
|
|
|
const settings = loadSettings(); |
|
|
if ( |
|
|
settings.systemConfig?.install?.pythonIndexUrl && |
|
|
(conf.command === 'uvx' || conf.command === 'uv' || conf.command === 'python') |
|
|
) { |
|
|
env['UV_DEFAULT_INDEX'] = settings.systemConfig.install.pythonIndexUrl; |
|
|
} |
|
|
|
|
|
|
|
|
if ( |
|
|
settings.systemConfig?.install?.npmRegistry && |
|
|
(conf.command === 'npm' || |
|
|
conf.command === 'npx' || |
|
|
conf.command === 'pnpm' || |
|
|
conf.command === 'yarn' || |
|
|
conf.command === 'node') |
|
|
) { |
|
|
env['npm_config_registry'] = settings.systemConfig.install.npmRegistry; |
|
|
} |
|
|
|
|
|
transport = new StdioClientTransport({ |
|
|
command: conf.command, |
|
|
args: conf.args, |
|
|
env: env, |
|
|
stderr: 'pipe', |
|
|
}); |
|
|
transport.stderr?.on('data', (data) => { |
|
|
console.log(`[${name}] [child] ${data}`); |
|
|
}); |
|
|
} else { |
|
|
console.warn(`Skipping server '${name}': missing required configuration`); |
|
|
serverInfos.push({ |
|
|
name, |
|
|
status: 'disconnected', |
|
|
error: 'Missing required configuration', |
|
|
tools: [], |
|
|
createTime: Date.now(), |
|
|
}); |
|
|
continue; |
|
|
} |
|
|
|
|
|
const client = new Client( |
|
|
{ |
|
|
name: `mcp-client-${name}`, |
|
|
version: '1.0.0', |
|
|
}, |
|
|
{ |
|
|
capabilities: { |
|
|
prompts: {}, |
|
|
resources: {}, |
|
|
tools: {}, |
|
|
}, |
|
|
}, |
|
|
); |
|
|
const timeout = isInit ? Number(config.initTimeout) : Number(config.timeout); |
|
|
client |
|
|
.connect(transport, { timeout: timeout }) |
|
|
.then(() => { |
|
|
console.log(`Successfully connected client for server: ${name}`); |
|
|
|
|
|
client |
|
|
.listTools({}, { timeout: timeout }) |
|
|
.then((tools) => { |
|
|
console.log(`Successfully listed ${tools.tools.length} tools for server: ${name}`); |
|
|
const serverInfo = getServerByName(name); |
|
|
if (!serverInfo) { |
|
|
console.warn(`Server info not found for server: ${name}`); |
|
|
return; |
|
|
} |
|
|
|
|
|
serverInfo.tools = tools.tools.map((tool) => ({ |
|
|
name: tool.name, |
|
|
description: tool.description || '', |
|
|
inputSchema: tool.inputSchema || {}, |
|
|
})); |
|
|
serverInfo.status = 'connected'; |
|
|
serverInfo.error = null; |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
} |
|
|
}) |
|
|
.catch((error) => { |
|
|
console.error( |
|
|
`Failed to list tools for server ${name} by error: ${error} with stack: ${error.stack}`, |
|
|
); |
|
|
const serverInfo = getServerByName(name); |
|
|
if (serverInfo) { |
|
|
serverInfo.status = 'disconnected'; |
|
|
serverInfo.error = `Failed to list tools: ${error.stack} `; |
|
|
} |
|
|
}); |
|
|
}) |
|
|
.catch((error) => { |
|
|
console.error( |
|
|
`Failed to connect client for server ${name} by error: ${error} with stack: ${error.stack}`, |
|
|
); |
|
|
const serverInfo = getServerByName(name); |
|
|
if (serverInfo) { |
|
|
serverInfo.status = 'disconnected'; |
|
|
serverInfo.error = `Failed to connect: ${error.stack} `; |
|
|
} |
|
|
}); |
|
|
serverInfos.push({ |
|
|
name, |
|
|
status: 'connecting', |
|
|
error: null, |
|
|
tools: [], |
|
|
client, |
|
|
transport, |
|
|
createTime: Date.now(), |
|
|
}); |
|
|
console.log(`Initialized client for server: ${name}`); |
|
|
} |
|
|
|
|
|
return serverInfos; |
|
|
}; |
|
|
|
|
|
|
|
|
export const registerAllTools = async (isInit: boolean): Promise<void> => { |
|
|
initializeClientsFromSettings(isInit); |
|
|
}; |
|
|
|
|
|
|
|
|
export const getServersInfo = (): Omit<ServerInfo, 'client' | 'transport'>[] => { |
|
|
const settings = loadSettings(); |
|
|
const infos = serverInfos.map(({ name, status, tools, createTime, error }) => { |
|
|
const serverConfig = settings.mcpServers[name]; |
|
|
const enabled = serverConfig ? serverConfig.enabled !== false : true; |
|
|
return { |
|
|
name, |
|
|
status, |
|
|
error, |
|
|
tools, |
|
|
createTime, |
|
|
enabled, |
|
|
}; |
|
|
}); |
|
|
infos.sort((a, b) => { |
|
|
if (a.enabled === b.enabled) return 0; |
|
|
return a.enabled ? -1 : 1; |
|
|
}); |
|
|
return infos; |
|
|
}; |
|
|
|
|
|
|
|
|
const getServerByName = (name: string): ServerInfo | undefined => { |
|
|
return serverInfos.find((serverInfo) => serverInfo.name === name); |
|
|
}; |
|
|
|
|
|
|
|
|
const getServerByTool = (toolName: string): ServerInfo | undefined => { |
|
|
return serverInfos.find((serverInfo) => serverInfo.tools.some((tool) => tool.name === toolName)); |
|
|
}; |
|
|
|
|
|
|
|
|
export const addServer = async ( |
|
|
name: string, |
|
|
config: ServerConfig, |
|
|
): Promise<{ success: boolean; message?: string }> => { |
|
|
try { |
|
|
const settings = loadSettings(); |
|
|
if (settings.mcpServers[name]) { |
|
|
return { success: false, message: 'Server name already exists' }; |
|
|
} |
|
|
|
|
|
settings.mcpServers[name] = config; |
|
|
if (!saveSettings(settings)) { |
|
|
return { success: false, message: 'Failed to save settings' }; |
|
|
} |
|
|
|
|
|
return { success: true, message: 'Server added successfully' }; |
|
|
} catch (error) { |
|
|
console.error(`Failed to add server: ${name}`, error); |
|
|
return { success: false, message: 'Failed to add server' }; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
export const removeServer = (name: string): { success: boolean; message?: string } => { |
|
|
try { |
|
|
const settings = loadSettings(); |
|
|
if (!settings.mcpServers[name]) { |
|
|
return { success: false, message: 'Server not found' }; |
|
|
} |
|
|
|
|
|
delete settings.mcpServers[name]; |
|
|
|
|
|
if (!saveSettings(settings)) { |
|
|
return { success: false, message: 'Failed to save settings' }; |
|
|
} |
|
|
|
|
|
serverInfos = serverInfos.filter((serverInfo) => serverInfo.name !== name); |
|
|
return { success: true, message: 'Server removed successfully' }; |
|
|
} catch (error) { |
|
|
console.error(`Failed to remove server: ${name}`, error); |
|
|
return { success: false, message: `Failed to remove server: ${error}` }; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
export const updateMcpServer = async ( |
|
|
name: string, |
|
|
config: ServerConfig, |
|
|
): Promise<{ success: boolean; message?: string }> => { |
|
|
try { |
|
|
const settings = loadSettings(); |
|
|
if (!settings.mcpServers[name]) { |
|
|
return { success: false, message: 'Server not found' }; |
|
|
} |
|
|
|
|
|
settings.mcpServers[name] = config; |
|
|
if (!saveSettings(settings)) { |
|
|
return { success: false, message: 'Failed to save settings' }; |
|
|
} |
|
|
|
|
|
closeServer(name); |
|
|
|
|
|
serverInfos = serverInfos.filter((serverInfo) => serverInfo.name !== name); |
|
|
return { success: true, message: 'Server updated successfully' }; |
|
|
} catch (error) { |
|
|
console.error(`Failed to update server: ${name}`, error); |
|
|
return { success: false, message: 'Failed to update server' }; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
function closeServer(name: string) { |
|
|
const serverInfo = serverInfos.find((serverInfo) => serverInfo.name === name); |
|
|
if (serverInfo && serverInfo.client && serverInfo.transport) { |
|
|
serverInfo.client.close(); |
|
|
serverInfo.transport.close(); |
|
|
console.log(`Closed client and transport for server: ${serverInfo.name}`); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export const toggleServerStatus = async ( |
|
|
name: string, |
|
|
enabled: boolean, |
|
|
): Promise<{ success: boolean; message?: string }> => { |
|
|
try { |
|
|
const settings = loadSettings(); |
|
|
if (!settings.mcpServers[name]) { |
|
|
return { success: false, message: 'Server not found' }; |
|
|
} |
|
|
|
|
|
|
|
|
settings.mcpServers[name].enabled = enabled; |
|
|
|
|
|
if (!saveSettings(settings)) { |
|
|
return { success: false, message: 'Failed to save settings' }; |
|
|
} |
|
|
|
|
|
|
|
|
if (!enabled) { |
|
|
closeServer(name); |
|
|
|
|
|
|
|
|
const index = serverInfos.findIndex((s) => s.name === name); |
|
|
if (index !== -1) { |
|
|
serverInfos[index] = { |
|
|
...serverInfos[index], |
|
|
status: 'disconnected', |
|
|
enabled: false, |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
return { success: true, message: `Server ${enabled ? 'enabled' : 'disabled'} successfully` }; |
|
|
} catch (error) { |
|
|
console.error(`Failed to toggle server status: ${name}`, error); |
|
|
return { success: false, message: 'Failed to toggle server status' }; |
|
|
} |
|
|
}; |
|
|
|
|
|
export const handleListToolsRequest = async (_: any, extra: any) => { |
|
|
const sessionId = extra.sessionId || ''; |
|
|
const group = getGroup(sessionId); |
|
|
console.log(`Handling ListToolsRequest for group: ${group}`); |
|
|
|
|
|
|
|
|
if (group === '$smart') { |
|
|
return { |
|
|
tools: [ |
|
|
{ |
|
|
name: 'search_tools', |
|
|
description: (() => { |
|
|
|
|
|
const availableServers = serverInfos.filter( |
|
|
(server) => server.status === 'connected' && server.enabled !== false, |
|
|
); |
|
|
|
|
|
const serversList = availableServers |
|
|
.map((server) => { |
|
|
return `${server.name}`; |
|
|
}) |
|
|
.join(', '); |
|
|
return `STEP 1 of 2: Use this tool FIRST to discover and search for relevant tools across all available servers. This tool and call_tool work together as a two-step process: 1) search_tools to find what you need, 2) call_tool to execute it. |
|
|
|
|
|
For optimal results, use specific queries matching your exact needs. Call this tool multiple times with different queries for different parts of complex tasks. Example queries: "image generation tools", "code review tools", "data analysis", "translation capabilities", etc. Results are sorted by relevance using vector similarity. |
|
|
|
|
|
After finding relevant tools, you MUST use the call_tool to actually execute them. The search_tools only finds tools - it doesn't execute them. |
|
|
|
|
|
Available servers: ${serversList}`; |
|
|
})(), |
|
|
inputSchema: { |
|
|
type: 'object', |
|
|
properties: { |
|
|
query: { |
|
|
type: 'string', |
|
|
description: |
|
|
'The search query to find relevant tools. Be specific and descriptive about the task you want to accomplish.', |
|
|
}, |
|
|
limit: { |
|
|
type: 'integer', |
|
|
description: |
|
|
'Maximum number of results to return. Use higher values (20-30) for broad searches and lower values (5-10) for specific searches.', |
|
|
default: 10, |
|
|
}, |
|
|
}, |
|
|
required: ['query'], |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
name: 'call_tool', |
|
|
description: |
|
|
"STEP 2 of 2: Use this tool AFTER search_tools to actually execute/invoke any tool you found. This is the execution step - search_tools finds tools, call_tool runs them.\n\nWorkflow: search_tools → examine results → call_tool with the chosen tool name and required arguments.\n\nIMPORTANT: Always check the tool's inputSchema from search_tools results before invoking to ensure you provide the correct arguments. The search results will show you exactly what parameters each tool expects.", |
|
|
inputSchema: { |
|
|
type: 'object', |
|
|
properties: { |
|
|
toolName: { |
|
|
type: 'string', |
|
|
description: 'The exact name of the tool to invoke (from search_tools results)', |
|
|
}, |
|
|
arguments: { |
|
|
type: 'object', |
|
|
description: |
|
|
'The arguments to pass to the tool based on its inputSchema (optional if tool requires no arguments)', |
|
|
}, |
|
|
}, |
|
|
required: ['toolName'], |
|
|
}, |
|
|
}, |
|
|
], |
|
|
}; |
|
|
} |
|
|
|
|
|
const allServerInfos = serverInfos.filter((serverInfo) => { |
|
|
if (serverInfo.enabled === false) return false; |
|
|
if (!group) return true; |
|
|
const serversInGroup = getServersInGroup(group); |
|
|
if (!serversInGroup || serversInGroup.length === 0) return serverInfo.name === group; |
|
|
return serversInGroup.includes(serverInfo.name); |
|
|
}); |
|
|
|
|
|
const allTools = []; |
|
|
for (const serverInfo of allServerInfos) { |
|
|
if (serverInfo.tools && serverInfo.tools.length > 0) { |
|
|
allTools.push(...serverInfo.tools); |
|
|
} |
|
|
} |
|
|
|
|
|
return { |
|
|
tools: allTools, |
|
|
}; |
|
|
}; |
|
|
|
|
|
export const handleCallToolRequest = async (request: any, extra: any) => { |
|
|
console.log(`Handling CallToolRequest for tool: ${JSON.stringify(request.params)}`); |
|
|
try { |
|
|
|
|
|
if (request.params.name === 'search_tools') { |
|
|
const { query, limit = 10 } = request.params.arguments || {}; |
|
|
|
|
|
if (!query || typeof query !== 'string') { |
|
|
throw new Error('Query parameter is required and must be a string'); |
|
|
} |
|
|
|
|
|
const limitNum = Math.min(Math.max(parseInt(String(limit)) || 10, 1), 100); |
|
|
|
|
|
|
|
|
let thresholdNum = 0.3; |
|
|
|
|
|
|
|
|
if (query.length < 10 || query.split(' ').length <= 2) { |
|
|
thresholdNum = 0.2; |
|
|
} |
|
|
|
|
|
|
|
|
if (query.length > 30 || query.includes('specific') || query.includes('exact')) { |
|
|
thresholdNum = 0.4; |
|
|
} |
|
|
|
|
|
console.log(`Using similarity threshold: ${thresholdNum} for query: "${query}"`); |
|
|
const servers = undefined; |
|
|
|
|
|
const searchResults = await searchToolsByVector(query, limitNum, thresholdNum, servers); |
|
|
console.log(`Search results: ${JSON.stringify(searchResults)}`); |
|
|
|
|
|
const tools = searchResults.map((result) => { |
|
|
|
|
|
const server = serverInfos.find( |
|
|
(serverInfo) => |
|
|
serverInfo.name === result.serverName && |
|
|
serverInfo.status === 'connected' && |
|
|
serverInfo.enabled !== false, |
|
|
); |
|
|
if (server && server.tools && server.tools.length > 0) { |
|
|
|
|
|
const actualTool = server.tools.find((tool) => tool.name === result.toolName); |
|
|
if (actualTool) { |
|
|
|
|
|
return actualTool; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return { |
|
|
name: result.toolName, |
|
|
description: result.description || '', |
|
|
inputSchema: result.inputSchema || {}, |
|
|
}; |
|
|
}); |
|
|
|
|
|
|
|
|
const response = { |
|
|
tools, |
|
|
metadata: { |
|
|
query: query, |
|
|
threshold: thresholdNum, |
|
|
totalResults: tools.length, |
|
|
guideline: |
|
|
tools.length > 0 |
|
|
? "Found relevant tools. If these tools don't match exactly what you need, try another search with more specific keywords." |
|
|
: 'No tools found. Try broadening your search or using different keywords.', |
|
|
nextSteps: |
|
|
tools.length > 0 |
|
|
? 'To use a tool, call call_tool with the toolName and required arguments.' |
|
|
: 'Consider searching for related capabilities or more general terms.', |
|
|
}, |
|
|
}; |
|
|
|
|
|
|
|
|
return { |
|
|
content: [ |
|
|
{ |
|
|
type: 'text', |
|
|
text: JSON.stringify(response), |
|
|
}, |
|
|
], |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
if (request.params.name === 'call_tool') { |
|
|
const { toolName, arguments: toolArgs = {} } = request.params.arguments || {}; |
|
|
|
|
|
if (!toolName) { |
|
|
throw new Error('toolName parameter is required'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let targetServerInfo: ServerInfo | undefined; |
|
|
if (extra && extra.server) { |
|
|
targetServerInfo = getServerByName(extra.server); |
|
|
} else { |
|
|
|
|
|
targetServerInfo = serverInfos.find( |
|
|
(serverInfo) => |
|
|
serverInfo.status === 'connected' && |
|
|
serverInfo.enabled !== false && |
|
|
serverInfo.tools.some((tool) => tool.name === toolName), |
|
|
); |
|
|
} |
|
|
|
|
|
if (!targetServerInfo) { |
|
|
throw new Error(`No available servers found with tool: ${toolName}`); |
|
|
} |
|
|
|
|
|
|
|
|
const toolExists = targetServerInfo.tools.some((tool) => tool.name === toolName); |
|
|
if (!toolExists) { |
|
|
throw new Error(`Tool '${toolName}' not found on server '${targetServerInfo.name}'`); |
|
|
} |
|
|
|
|
|
|
|
|
const client = targetServerInfo.client; |
|
|
if (!client) { |
|
|
throw new Error(`Client not found for server: ${targetServerInfo.name}`); |
|
|
} |
|
|
|
|
|
|
|
|
const finalArgs = |
|
|
toolArgs && Object.keys(toolArgs).length > 0 ? toolArgs : request.params.arguments || {}; |
|
|
|
|
|
console.log( |
|
|
`Invoking tool '${toolName}' on server '${targetServerInfo.name}' with arguments: ${JSON.stringify(finalArgs)}`, |
|
|
); |
|
|
|
|
|
const result = await client.callTool({ |
|
|
name: toolName, |
|
|
arguments: finalArgs, |
|
|
}); |
|
|
|
|
|
console.log(`Tool invocation result: ${JSON.stringify(result)}`); |
|
|
return result; |
|
|
} |
|
|
|
|
|
|
|
|
const serverInfo = getServerByTool(request.params.name); |
|
|
if (!serverInfo) { |
|
|
throw new Error(`Server not found: ${request.params.name}`); |
|
|
} |
|
|
const client = serverInfo.client; |
|
|
if (!client) { |
|
|
throw new Error(`Client not found for server: ${request.params.name}`); |
|
|
} |
|
|
const result = await client.callTool(request.params); |
|
|
console.log(`Tool call result: ${JSON.stringify(result)}`); |
|
|
return result; |
|
|
} catch (error) { |
|
|
console.error(`Error handling CallToolRequest: ${error}`); |
|
|
return { |
|
|
content: [ |
|
|
{ |
|
|
type: 'text', |
|
|
text: `Error: ${error}`, |
|
|
}, |
|
|
], |
|
|
isError: true, |
|
|
}; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
export const createMcpServer = (name: string, version: string, group?: string): Server => { |
|
|
|
|
|
let serverName = name; |
|
|
|
|
|
if (group) { |
|
|
|
|
|
const serversInGroup = getServersInGroup(group); |
|
|
if (!serversInGroup || serversInGroup.length === 0) { |
|
|
|
|
|
serverName = `${name}_${group}`; |
|
|
} else { |
|
|
|
|
|
serverName = `${name}_${group}_group`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const server = new Server({ name: serverName, version }, { capabilities: { tools: {} } }); |
|
|
server.setRequestHandler(ListToolsRequestSchema, handleListToolsRequest); |
|
|
server.setRequestHandler(CallToolRequestSchema, handleCallToolRequest); |
|
|
return server; |
|
|
}; |
|
|
|