Skip to main content
Glama
http-server.ts10.3 kB
/** * MCP Server with HTTP transport (using fastmcp) * * This module provides a standalone MCP server that communicates * over HTTP using JSON-RPC 2.0 protocol, implemented using the fastmcp framework. * This is used for hosted deployments on Smithery.ai * * FastMCP 3.24.0 best practices: * - Use httpStream transport for hosted deployments * - Enable stateless mode for serverless/load-balanced environments * - Configure health endpoint for container orchestration * - Proper tool annotations for trust & safety * - Progress notifications and streaming output * - CORS enabled by default */ import { logger } from "./utils/logger"; import { FastMCP } from "fastmcp"; import { tools } from "./tools/index"; import { listResources, getResource } from "./mcp/resources"; import { getAllPrompts, renderPrompt } from "./mcp/prompts"; import express, { type Request, type Response } from "express"; import http from "http"; const port = (process.env.PORT ?? "7123") ? parseInt(process.env.PORT ?? "7123", 10) : 7123; const isScanning = process.env.SMITHERY_SCAN === "true"; const isStateless = process.env.FASTMCP_STATELESS === "true" || process.env.SMITHERY_STATELESS === "true"; const VERSION = "1.2.1"; /** * Tool annotations following MCP specification for trust & safety */ interface ToolAnnotations { title?: string; readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; } /** * Get MCP annotations for a tool based on its behavior */ function getToolAnnotations(toolName: string): ToolAnnotations { const title = toolName .split("_") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); // Read-only tools (safe, no side effects) if (toolName.includes("list") || toolName.includes("get") || toolName === "system_info") { return { title, readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }; } // Potentially destructive tools if (toolName.includes("delete") || toolName.includes("uninstall") || toolName.includes("remove")) { return { title, readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }; } // Default: control tools that modify state but aren't destructive return { title, readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }; } async function main(): Promise<void> { try { logger.info(`Starting server initialization on port ${port}`); logger.info(`Initializing FastMCP server with HTTP transport${isScanning ? " (scan mode)" : ""}${isStateless ? " (stateless mode)" : ""}...`); // Create the FastMCP server instance following v3.24.0 best practices const server = new FastMCP({ name: "Home Assistant MCP Server", version: VERSION, // Configure health endpoint for container orchestration health: { enabled: true, message: "ok", path: "/health", status: 200, }, // Configure ping behavior - enable for HTTP to maintain connection health ping: { enabled: true, intervalMs: 30000, // 30 seconds logLevel: "debug", }, // Disable roots support for simpler operation roots: { enabled: false, }, }); logger.info(`FastMCP server instance created (v3.24.0)`); // Add tools from the tools registry with proper annotations const toolCount = tools.length; for (const tool of tools) { try { const annotations = getToolAnnotations(tool.name); server.addTool({ name: tool.name, description: tool.description, parameters: tool.parameters as never, annotations: annotations as never, execute: async (args: unknown, context) => { try { const token = process.env.HASS_TOKEN ?? ""; const hasToken = token.length > 0; if (!hasToken && !isScanning) { throw new Error("Home Assistant token not configured"); } context.log.debug(`Executing tool ${tool.name}`); const result = await tool.execute(args as never); return result as never; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); context.log.error(`Error executing tool ${tool.name}: ${errorMsg}`); throw error; } }, }); logger.info(`Added tool: ${tool.name} (${annotations.title})`); } catch (error) { logger.error(`Failed to add tool ${tool.name}:`, error); } } logger.info(`Successfully added ${toolCount} tools`); // Add system_info tool with proper annotations server.addTool({ name: "system_info", description: "Get basic information about this MCP server including version, transport, and connection status", annotations: { title: "System Info", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, } as never, execute: async (): Promise<string> => { const hasToken = Boolean(process.env.HASS_TOKEN); const hassHost = process.env.HASS_HOST || "not configured"; return Promise.resolve(JSON.stringify({ name: "Home Assistant MCP Server", version: VERSION, transport: "httpStream", stateless: isStateless, hassHost, connected: hasToken, toolCount: toolCount + 1, capabilities: ["tools", "resources", "prompts"], }, null, 2)); }, }); logger.info("Added system_info tool"); // Add resources try { const resources = await listResources(); for (const resource of resources) { server.addResource({ uri: resource.uri, name: resource.name, description: resource.description, mimeType: resource.mimeType, load: async () => { const content = await getResource(resource.uri); if (!content) { throw new Error(`Failed to get resource: ${resource.uri}`); } const text = content.text ?? ""; return { text, }; }, }); logger.info(`Added resource: ${resource.uri}`); } logger.info(`Successfully added ${resources.length} resources`); } catch (error) { logger.error("Error adding resources:", error); } // Add prompts try { const prompts = getAllPrompts(); for (const prompt of prompts) { server.addPrompt({ name: prompt.name, description: prompt.description, arguments: prompt.arguments?.map((arg) => ({ name: arg.name, description: arg.description, required: arg.required || false, })) || [], // eslint-disable-next-line @typescript-eslint/require-await load: async (args) => { const rendered = renderPrompt(prompt.name, args as Record<string, string>); return rendered; }, }); logger.info(`Added prompt: ${prompt.name}`); } logger.info(`Successfully added ${prompts.length} prompts`); } catch (error) { logger.error("Error adding prompts:", error); } // Start the server with HTTP stream transport (FastMCP 3.24.0) logger.info(`Starting FastMCP with HTTP stream transport on port ${port}${isStateless ? " (stateless mode)" : ""}...`); try { // Start FastMCP with custom HTTP handler that includes config endpoints // Health endpoint is already configured via FastMCP options await server.start({ transportType: "httpStream", httpStream: { port: port, endpoint: "/mcp", // Enable stateless mode for serverless/load-balanced deployments stateless: isStateless, middleware: (app: express.Application) => { // MCP config endpoint for Smithery discovery app.get("/.well-known/mcp-config", (_req, res) => { res.json({ mcpServers: { "homeassistant-mcp": { url: "/mcp", transport: "httpStream", }, }, }); }); // Ready endpoint for orchestration app.get("/ready", (_req, res) => { res.json({ status: "ready", ready: 1, total: 1, mode: isStateless ? "stateless" : "stateful", }); }); }, }, }); } catch (startError) { logger.error(`Failed to start HTTP server: ${startError instanceof Error ? startError.message : String(startError)}`); throw startError; } logger.info(`✓ FastMCP HTTP server listening on port ${port}`); logger.info(`✓ Health check available at http://localhost:${port}/health`); logger.info(`✓ Ready check available at http://localhost:${port}/ready`); logger.info(`✓ MCP config available at http://localhost:${port}/.well-known/mcp-config`); logger.info(`✓ MCP endpoint available at http://localhost:${port}/mcp`); logger.info(`✓ SSE endpoint available at http://localhost:${port}/sse`); logger.info(`✓ Server transport: HTTP Stream (FastMCP 3.24.0${isStateless ? ", stateless" : ""})`); logger.info(`✓ Ready for Smithery.ai hosted deployment`); // Graceful shutdown handler const shutdown = (): void => { logger.info("Shutting down FastMCP server..."); try { process.exit(0); } catch (error) { logger.error("Error during shutdown:", error); process.exit(1); } }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); } catch (error) { logger.error("Error starting Home Assistant MCP HTTP server (fastmcp):", error); process.exit(1); } } main().catch((error) => { logger.error("Uncaught error in main (fastmcp):", error); process.exit(1); });

Latest Blog Posts

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/jango-blockchained/advanced-homeassistant-mcp'

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