Skip to main content
Glama

HomeAssistant MCP

generic-mcp-client.test.ts7.94 kB
/** * Generic MCP Client Integration Tests * * Tests common MCP client patterns across all platforms * Tests: * - Transport layer abstraction (stdio, HTTP) * - Tool registration and discovery * - Error handling and recovery * - Connection lifecycle */ import { describe, it, expect, beforeEach } from "bun:test"; type TransportType = "stdio" | "http"; interface MCPClientConfig { transport: TransportType; host?: string; port?: number; command?: string; args?: string[]; env?: Record<string, string>; } interface MCPServerInfo { name: string; version: string; transport: TransportType; connected: boolean; } interface MCPToolInfo { name: string; description: string; parameters: Record<string, unknown>; } interface MCPExecutionResult { status: "success" | "error"; data?: unknown; error?: string; } class GenericMCPClient { private config: MCPClientConfig; private connected = false; private tools: Map<string, MCPToolInfo> = new Map(); private serverInfo: MCPServerInfo | null = null; constructor(config: MCPClientConfig) { this.config = config; } async connect(): Promise<void> { // eslint-disable-next-line @typescript-eslint/no-unused-vars await Promise.resolve(); // Validate configuration if (this.config.transport === "http") { if (this.config.host === undefined || this.config.port === undefined) { throw new Error("HTTP transport requires host and port"); } } else if (this.config.transport === "stdio") { if (this.config.command === undefined) { throw new Error("Stdio transport requires command"); } } this.connected = true; this.serverInfo = { name: "homeassistant-mcp", version: "1.0.0", transport: this.config.transport, connected: true }; } async disconnect(): Promise<void> { // eslint-disable-next-line @typescript-eslint/no-unused-vars await Promise.resolve(); this.connected = false; this.tools.clear(); } async discoverTools(): Promise<MCPToolInfo[]> { // eslint-disable-next-line @typescript-eslint/no-unused-vars await Promise.resolve(); if (!this.connected) { throw new Error("Client not connected"); } const tools: MCPToolInfo[] = [ { name: "control", description: "Control Home Assistant entities", parameters: { type: "object", properties: { command: { type: "string" } } } }, { name: "list_devices", description: "List all Home Assistant devices", parameters: { type: "object", properties: {} } }, { name: "history", description: "Get entity history", parameters: { type: "object", properties: { entity_id: { type: "string" } } } }, { name: "notify", description: "Send notification", parameters: { type: "object", properties: { message: { type: "string" } } } } ]; tools.forEach(tool => this.tools.set(tool.name, tool)); return tools; } async executeTool(toolName: string, params: Record<string, unknown>): Promise<MCPExecutionResult> { // eslint-disable-next-line @typescript-eslint/no-unused-vars await Promise.resolve(); if (!this.connected) { return { status: "error", error: "Client not connected" }; } const tool = this.tools.get(toolName); if (!tool) { return { status: "error", error: `Tool not found: ${toolName}` }; } // Mock tool execution return { status: "success", data: { tool: toolName, params, executedAt: new Date().toISOString() } }; } isConnected(): boolean { return this.connected; } getServerInfo(): MCPServerInfo | null { return this.serverInfo; } getToolCount(): number { return this.tools.size; } getTool(name: string): MCPToolInfo | undefined { return this.tools.get(name); } } describe("Generic MCP Client Integration", () => { let client: GenericMCPClient; beforeEach(() => { client = new GenericMCPClient({ transport: "http", host: "localhost", port: 7123 }); }); it("should require host/port for HTTP transport", async () => { const invalidClient = new GenericMCPClient({ transport: "http" }); try { await invalidClient.connect(); expect(true).toBe(false); } catch (error) { expect(error instanceof Error).toBe(true); const err = error as Error; expect(err.message).toContain("host and port"); } }); it("should require command for stdio transport", async () => { const invalidClient = new GenericMCPClient({ transport: "stdio" }); try { await invalidClient.connect(); expect(true).toBe(false); } catch (error) { expect(error instanceof Error).toBe(true); const err = error as Error; expect(err.message).toContain("command"); } }); it("should connect with valid HTTP configuration", async () => { expect(client.isConnected()).toBe(false); await client.connect(); expect(client.isConnected()).toBe(true); }); it("should provide server info after connecting", async () => { await client.connect(); const info = client.getServerInfo(); expect(info).toBeDefined(); expect(info?.name).toBe("homeassistant-mcp"); expect(info?.transport).toBe("http"); expect(info?.connected).toBe(true); }); it("should disconnect properly", async () => { await client.connect(); expect(client.isConnected()).toBe(true); await client.disconnect(); expect(client.isConnected()).toBe(false); }); it("should discover tools", async () => { await client.connect(); const tools = await client.discoverTools(); expect(Array.isArray(tools)).toBe(true); expect(tools.length).toBeGreaterThan(0); expect(tools.some(t => t.name === "control")).toBe(true); expect(tools.some(t => t.name === "list_devices")).toBe(true); }); it("should fail to discover tools when disconnected", async () => { try { await client.discoverTools(); expect(true).toBe(false); } catch (error) { expect(error instanceof Error).toBe(true); const err = error as Error; expect(err.message).toContain("not connected"); } }); it("should execute tools", async () => { await client.connect(); await client.discoverTools(); const result = await client.executeTool("control", { command: "turn_on", entity_id: "light.bedroom" }); expect(result.status).toBe("success"); expect(result.data).toBeDefined(); }); it("should fail to execute when disconnected", async () => { const result = await client.executeTool("control", {}); expect(result.status).toBe("error"); expect(result.error).toContain("not connected"); }); it("should fail to execute unknown tool", async () => { await client.connect(); await client.discoverTools(); const result = await client.executeTool("unknown_tool", {}); expect(result.status).toBe("error"); expect(result.error).toContain("not found"); }); it("should track tool count", async () => { await client.connect(); expect(client.getToolCount()).toBe(0); await client.discoverTools(); expect(client.getToolCount()).toBeGreaterThan(0); }); it("should retrieve individual tool info", async () => { await client.connect(); await client.discoverTools(); const tool = client.getTool("control"); expect(tool).toBeDefined(); expect(tool?.name).toBe("control"); expect(tool?.description).toContain("Home Assistant"); }); it("should clear tools on disconnect", async () => { await client.connect(); await client.discoverTools(); expect(client.getToolCount()).toBeGreaterThan(0); await client.disconnect(); expect(client.getToolCount()).toBe(0); }); });

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