Skip to main content
Glama

hypertool-mcp

server.tsโ€ข12.1 kB
#!/usr/bin/env node /** * Dedicated MCP server entry point for Smithery installation * * This file provides a clean entry point focused on stdio transport * without the CLI scaffolding from index.ts */ // Set max listeners early to prevent warnings const maxListeners = process.env.HYPERTOOL_MAX_LISTENERS ? parseInt(process.env.HYPERTOOL_MAX_LISTENERS, 10) : 10; if (!isNaN(maxListeners) && maxListeners > 0) { process.setMaxListeners(maxListeners); } import { MetaMCPServerFactory } from "./server/index.js"; import { RuntimeOptions, RuntimeTransportType } from "./types/runtime.js"; import type { TransportConfig, ServerInitOptions } from "./server/types.js"; import { discoverMcpConfig } from "./config/mcpConfigLoader.js"; import { getLogger } from "./utils/logging.js"; import { parseEnvDotNotation, hasSmitheryConfig, getConfigSourceDescription, validateParsedConfig, type ParsedEnvConfig, } from "./utils/envConfigParser.js"; import * as fs from "fs"; import * as path from "path"; import * as os from "os"; // Configuration interface for Smithery export interface SmitheryConfig { mcpConfigPath?: string; mcpServers?: any; // Flexible object for MCP server configuration from Smithery debug?: boolean; logLevel?: string; group?: string; equipToolset?: string; transport?: RuntimeTransportType; port?: number; host?: string; } /** * Main server entry point for Smithery */ async function startServer(config: SmitheryConfig = {}): Promise<void> { // Create runtime options from Smithery config first for logging const runtimeOptions: RuntimeOptions = { transport: config.transport || ("stdio" as RuntimeTransportType), port: config.port, host: config.host, debug: config.debug || false, insecure: false, // Always secure for Smithery installs equipToolset: config.equipToolset, configPath: config.mcpConfigPath, logLevel: config.logLevel || "info", group: config.group, }; // Initialize logger with transport-aware configuration const logger = getLogger(undefined, runtimeOptions); // Parse environment variables for Smithery configuration const envConfig = parseEnvDotNotation(); const hasEnvConfig = hasSmitheryConfig(); // Log all available configuration sources for experimentation logger.info("=== SMITHERY CONFIGURATION EXPERIMENT ==="); logger.info("CLI Arguments:", process.argv.slice(2)); logger.info( "Environment Variables:", Object.keys(process.env) .filter( (k) => k.includes("MCP") || k.includes("SMITHERY") || k.includes("CONFIG") || k.includes("SERVER") ) .reduce((acc, key) => ({ ...acc, [key]: process.env[key] }), {}) ); logger.info("Passed Config Object:", config); if (hasEnvConfig) { logger.info("Parsed Environment Config:", envConfig); logger.info( "Environment Config Sources:", getConfigSourceDescription(envConfig) ); // Validate environment configuration const validation = validateParsedConfig(envConfig); if (!validation.valid) { logger.warn( "Environment configuration validation issues:", validation.errors ); } // Merge environment config (but CLI args and passed config take precedence) config = { ...envConfig, ...config, }; } // Check for JSON configuration in environment const jsonConfigEnv = process.env.SMITHERY_CONFIG || process.env.MCP_CONFIG || process.env.CONFIG_JSON; if (jsonConfigEnv) { try { const parsedJsonConfig = JSON.parse(jsonConfigEnv); logger.info("Parsed JSON Config from Environment:", parsedJsonConfig); // Merge JSON config if found (but other config takes precedence) config = { ...parsedJsonConfig, ...config, }; } catch (error) { logger.warn("Failed to parse JSON config from environment:", error); } } // Set up process warning listener process.on("warning", (warning) => { logger.warn("Node.js warning", { name: warning.name, message: warning.message, stack: warning.stack, }); }); try { // Handle MCP server configuration from Smithery let configPath: string | null = null; let configSource = "unknown"; let temporaryConfigFile: string | null = null; if (config.mcpServers && Object.keys(config.mcpServers).length > 0) { logger.info("Using MCP configuration from Smithery:", config.mcpServers); // Create a temporary file with Smithery configuration try { const tempDir = os.tmpdir(); temporaryConfigFile = path.join( tempDir, `smithery-mcp-config-${Date.now()}.json` ); const mcpConfig = { mcpServers: config.mcpServers }; await fs.promises.writeFile( temporaryConfigFile, JSON.stringify(mcpConfig, null, 2) ); configPath = temporaryConfigFile; configSource = "smithery"; logger.info(`Created temporary config file: ${temporaryConfigFile}`); } catch (error) { logger.error("Failed to create temporary configuration file:", error); process.exit(1); } } else { // Fallback to file-based configuration discovery logger.info( "No MCP configuration from Smithery, using file-based discovery" ); const configResult = await discoverMcpConfig( runtimeOptions.configPath, false, // Don't update preference for Smithery installs runtimeOptions.linkedApp, runtimeOptions.profile ); configPath = configResult.configPath; configSource = configResult.configSource?.type || "file"; // Handle configuration discovery results if (!configPath) { logger.error("No MCP configuration found"); if (configResult.errorMessage) { logger.error(configResult.errorMessage); } process.exit(1); } } // Create transport config based on runtime options const transportConfig: TransportConfig = { type: runtimeOptions.transport, ...(runtimeOptions.transport === "http" && { port: runtimeOptions.port || 3000, host: runtimeOptions.host || "localhost", }), }; // Create server instance const server = MetaMCPServerFactory.createDefaultServer(transportConfig); // Setup graceful shutdown let isShuttingDown = false; const shutdown = async (signal?: string) => { if (isShuttingDown) { return; } isShuttingDown = true; if (runtimeOptions.debug) { logger.debug( `Shutting down HyperTool server... (${signal || "manual"})` ); } // Clean up temporary config file if created if (temporaryConfigFile) { try { await fs.promises.unlink(temporaryConfigFile); logger.debug( `Cleaned up temporary config file: ${temporaryConfigFile}` ); } catch (error) { logger.warn(`Failed to clean up temporary config file: ${error}`); } } // Set a hard timeout for graceful shutdown const forceExitTimeout = setTimeout(() => { logger.error( "Forcefully exiting after timeout - graceful shutdown failed" ); process.exit(1); }, 5000); try { await server.stop(); clearTimeout(forceExitTimeout); process.exit(0); } catch (error) { logger.error("Error during shutdown:", error); clearTimeout(forceExitTimeout); process.exit(1); } }; // Handle signals process.on("SIGINT", () => setImmediate(() => shutdown("SIGINT"))); process.on("SIGTERM", () => setImmediate(() => shutdown("SIGTERM"))); process.on("SIGHUP", () => setImmediate(() => shutdown("SIGHUP"))); // Handle uncaught exceptions process.on("uncaughtException", (error) => { logger.error("Uncaught exception:", error); shutdown("uncaughtException"); }); // Handle stdin end as shutdown signal only for stdio transport (and not in test environment) if ( runtimeOptions.transport === "stdio" && process.env.NODE_ENV !== "test" ) { process.stdin.on("end", () => { logger.debug("stdin ended, shutting down..."); shutdown("stdin-end"); }); } // Create initialization options const initOptions: ServerInitOptions = MetaMCPServerFactory.createInitOptions({ transport: transportConfig, debug: runtimeOptions.debug, configPath: configPath!, configSource: configSource, }); // Start the server await server.start(initOptions, runtimeOptions); } catch (error) { logger.error("Failed to start HyperTool server:", error); process.exit(1); } } // For programmatic use export { startServer }; /** * Simple CLI argument parser for basic flags */ function parseCliArgs(args: string[]): Partial<SmitheryConfig> { const config: Partial<SmitheryConfig> = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; const nextArg = args[i + 1]; switch (arg) { case "--mcp-config": if (nextArg && !nextArg.startsWith("--")) { config.mcpConfigPath = nextArg; i++; // skip next arg since we consumed it } break; case "--debug": config.debug = true; break; case "--log-level": if (nextArg && !nextArg.startsWith("--")) { config.logLevel = nextArg; i++; // skip next arg since we consumed it } break; case "--group": if (nextArg && !nextArg.startsWith("--")) { config.group = nextArg; i++; // skip next arg since we consumed it } break; case "--equip-toolset": if (nextArg && !nextArg.startsWith("--")) { config.equipToolset = nextArg; i++; // skip next arg since we consumed it } break; case "--transport": if (nextArg && !nextArg.startsWith("--")) { config.transport = nextArg as RuntimeTransportType; i++; // skip next arg since we consumed it } break; case "--port": if (nextArg && !nextArg.startsWith("--")) { config.port = parseInt(nextArg, 10); i++; // skip next arg since we consumed it } break; case "--host": if (nextArg && !nextArg.startsWith("--")) { config.host = nextArg; i++; // skip next arg since we consumed it } break; } } return config; } // Auto-start if this file is run directly if (import.meta.url === `file://${process.argv[1]}`) { // Parse CLI arguments first const cliArgs = parseCliArgs(process.argv.slice(2)); // Parse configuration from environment variables (fallback) const envConfig: SmitheryConfig = { mcpConfigPath: process.env.MCP_CONFIG_PATH, debug: process.env.DEBUG === "true", logLevel: process.env.LOG_LEVEL || "info", group: process.env.SERVER_GROUP, equipToolset: process.env.EQUIP_TOOLSET, }; // Parse Smithery-style configuration from environment variables const parsedEnvConfig = parseEnvDotNotation(); const hasSmitheryEnvConfig = hasSmitheryConfig(); if (hasSmitheryEnvConfig) { console.log("Found Smithery configuration in environment:"); console.log("Parsed config:", JSON.stringify(parsedEnvConfig, null, 2)); console.log("Config sources:", getConfigSourceDescription(parsedEnvConfig)); // Validate the parsed configuration const validation = validateParsedConfig(parsedEnvConfig); if (!validation.valid) { console.warn("Configuration validation warnings:", validation.errors); } // Merge environment configuration (CLI args take precedence) Object.assign(envConfig, parsedEnvConfig); } // Merge configurations - CLI args take precedence over env vars const config: SmitheryConfig = { ...envConfig, ...cliArgs, }; await startServer(config); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/toolprint/hypertool-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server