UNPKG

@toolplex/client

Version:

The official ToolPlex client for AI agent tool discovery and execution

251 lines (250 loc) 12.4 kB
#!/usr/bin/env node import path from "path"; import { fileURLToPath } from "url"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { ClientContext } from "./clientContext.js"; import Registry from "./registry.js"; import { PRE_INITIALIZATION_PROMPTS } from "./staticPrompts.js"; import { createToolDefinitions } from "./tools.js"; import { handleInitialize } from "./toolHandlers/initHandler.js"; import { handleSearchTool } from "./toolHandlers/searchHandler.js"; import { handleInstallServer } from "./toolHandlers/installServerHandler.js"; import { handleListTools } from "./toolHandlers/listToolsHandler.js"; import { handleListServers } from "./toolHandlers/listServersHandler.js"; import { handleCallTool } from "./toolHandlers/callToolHandler.js"; import { handleUninstallServer } from "./toolHandlers/uninstallServerHandler.js"; import { handleSavePlaybook } from "./toolHandlers/savePlaybookHandler.js"; import { handleLogPlaybookUsage } from "./toolHandlers/logPlaybookUsageHandler.js"; import { handleLookupEntityTool } from "./toolHandlers/lookupEntityHandler.js"; import { handleSubmitFeedback } from "./toolHandlers/submitFeedbackHandler.js"; import { handleGetServerConfig } from "./toolHandlers/getServerConfigHandler.js"; import { StdioServerManagerClient } from "../shared/stdioServerManagerClient.js"; import { FileLogger } from "../shared/fileLogger.js"; import { CallToolParamsSchema, InitializeToolplexParamsSchema, InstallParamsSchema, ListToolsParamsSchema, SearchParamsSchema, UninstallParamsSchema, SavePlaybookParamsSchema, LogPlaybookUsageParamsSchema, LookupEntityParamsSchema, SubmitFeedbackParamsSchema, GetServerConfigParamsSchema, } from "../shared/mcpServerTypes.js"; import { version as clientVersion } from "../version.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const logger = FileLogger; export async function serve(config) { const clientContext = new ClientContext(); clientContext.dev = config.dev; clientContext.apiKey = config.apiKey; clientContext.clientMode = config.clientMode; clientContext.clientName = config.clientName; clientContext.clientVersion = clientVersion; await Registry.init(clientContext); await logger.info(`Starting Toolplex server in ${config.dev ? "development" : "production"} mode`); const server = new Server({ name: "toolplex-server", version: clientContext.clientVersion, }, { capabilities: { resources: {}, tools: {}, }, }); // Initialize server manager clients await logger.info("Initializing server manager clients"); const serverManagerClients = { node: new StdioServerManagerClient("node", [path.join(__dirname, "..", "server-manager", "index.js")], { LOG_LEVEL: config.logLevel }), }; // Start all server manager clients await logger.info("Starting server manager clients"); await Promise.all(Object.values(serverManagerClients).map((client) => client.start())); await logger.info("All server manager clients started successfully"); Registry.setServerManagerClients(serverManagerClients); // List tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { await logger.debug("Handling list tools request"); return { tools: createToolDefinitions(), }; }); // Call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: params } = request.params; await logger.info(`Handling tool call request for tool: ${name}`); let result; if (!Registry.getToolDefinitionsCache().isInitialized()) { result = { isError: true, content: [ { type: "text", text: PRE_INITIALIZATION_PROMPTS.tools_initialization_error, }, ], }; return result; } if (!clientContext.isInitialized() && name !== "initialize_toolplex") { result = { role: "system", content: [ { type: "text", text: PRE_INITIALIZATION_PROMPTS.enforce_init_toolplex.replace("{TOOL_NAME}", name), }, ], }; return result; } try { switch (name) { case "initialize_toolplex": { await logger.debug("Handling initialize_toolplex request"); const parsed = InitializeToolplexParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid initialize_toolplex params: ${parsed.error}`); clientContext.llmContext = parsed.data.llm_context; result = await handleInitialize(parsed.data); break; } case "search": { await logger.debug("Handling search request"); const parsed = SearchParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid search params: ${parsed.error}`); result = await handleSearchTool(parsed.data); break; } case "install": { await logger.debug("Handling install request"); const parsed = InstallParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid install params: ${parsed.error}`); result = await handleInstallServer(parsed.data); server.sendToolListChanged(); break; } case "list_tools": { await logger.debug("Handling list_tools request"); const parsed = ListToolsParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid list_toolplex_tools params: ${parsed.error}`); result = await handleListTools(parsed.data); break; } case "list_servers": { await logger.debug("Handling list_toolplex_tools request"); result = await handleListServers(); break; } case "call_tool": { await logger.debug("Handling call_tool request"); const parsed = CallToolParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid call_tool params: ${parsed.error}`); result = await handleCallTool(parsed.data); break; } case "uninstall": { await logger.debug("Handling uninstall request"); const parsed = UninstallParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid uninstall params: ${parsed.error}`); result = await handleUninstallServer(parsed.data); break; } case "save_playbook": { await logger.debug("Handling save_playbook request"); const parsed = SavePlaybookParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid save_playbook params: ${parsed.error}`); if (!clientContext.isInitialized()) throw new Error(`ToolPlex is not initialized`); result = await handleSavePlaybook(parsed.data); break; } case "log_playbook_usage": { await logger.debug("Handling log_playbook_usage request"); const parsed = LogPlaybookUsageParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid log_playbook_usage params: ${parsed.error}`); if (!clientContext.isInitialized()) throw new Error(`ToolPlex is not initialized`); result = await handleLogPlaybookUsage(parsed.data); break; } case "lookup_entity": { await logger.debug("Handling lookup_entity request"); const parsed = LookupEntityParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid lookup_entity params: ${parsed.error}`); result = await handleLookupEntityTool(parsed.data); break; } case "submit_feedback": { await logger.debug("Handling submit_feedback request"); const parsed = SubmitFeedbackParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid submit_feedback params: ${parsed.error}`); if (!clientContext.isInitialized()) throw new Error(`ToolPlex is not initialized`); result = await handleSubmitFeedback(parsed.data); break; } // Add get_server_config tool handler case "get_server_config": { await logger.debug("Handling get_server_config request"); const parsed = GetServerConfigParamsSchema.safeParse(params); if (!parsed.success) throw new Error(`Invalid get_server_config params: ${parsed.error}`); result = await handleGetServerConfig(parsed.data); break; } default: await logger.warn(`Unknown tool requested: ${name}`); result = { role: "system", content: [ { type: "text", text: PRE_INITIALIZATION_PROMPTS.unknown_tool.replace("{TOOL_NAME}", name), }, ], }; } } catch (error) { let errorMessage = "Unknown error occurred"; if (error instanceof Error) { errorMessage = error.message; } await logger.error(`Error calling ToolPlex: ${errorMessage}`); result = { role: "system", content: [ { type: "text", text: PRE_INITIALIZATION_PROMPTS.unexpected_error.replace("{ERROR}", errorMessage), }, ], }; } return result; }); const transport = new StdioServerTransport(); await logger.info("Connecting server transport"); await server.connect(transport); await logger.info("Server transport connected successfully"); // Clean up on process exit process.on("exit", async () => { await logger.info("Process exit - stopping server manager clients"); await logger.flush(); Object.values(serverManagerClients).forEach((client) => client.stop()); }); process.on("SIGINT", async () => { await logger.warn("SIGINT received - stopping server manager clients"); await logger.flush(); Object.values(serverManagerClients).forEach((client) => client.stop()); process.exit(); }); process.on("SIGTERM", async () => { await logger.warn("SIGTERM received - stopping server manager clients"); await logger.flush(); Object.values(serverManagerClients).forEach((client) => client.stop()); process.exit(); }); }