Skip to main content
Glama
schema.test.ts6.43 kB
import { describe, it, beforeEach, afterEach } from "mocha"; import { expect } from "chai"; import { createMcpServer } from "../../../src/server/index"; import { ToolRegistry } from "../../../src/server/toolRegistry"; import { McpTestFixture } from "../../fixtures/mcpTestFixture"; describe("MCP Tools Schema", () => { let fixture: McpTestFixture; beforeEach(async () => { fixture = new McpTestFixture(); await fixture.setup(); }); afterEach(async () => { if (fixture) { await fixture.teardown(); } }); // Helper function to check if emulator CLI is available async function checkEmulatorAvailable(): Promise<boolean> { try { const { execSync } = await import("child_process"); execSync("emulator -version", { stdio: "ignore" }); return true; } catch (error) { return false; } } it("should validate tool schema definitions conform to MCP standards", () => { createMcpServer(); const toolDefinitions = ToolRegistry.getToolDefinitions(); // Each tool should conform to MCP protocol requirements toolDefinitions.forEach(tool => { // Required MCP tool properties expect(tool).to.have.property("name"); expect(tool).to.have.property("description"); expect(tool).to.have.property("inputSchema"); // Type validation expect(typeof tool.name).to.equal("string"); expect(typeof tool.description).to.equal("string"); expect(typeof tool.inputSchema).to.equal("object"); // MCP protocol requirements expect(tool.name.length).to.be.greaterThan(0); expect(tool.description.length).to.be.greaterThan(0); // Schema should be a valid JSON Schema-like object const schema = tool.inputSchema as any; expect(schema).to.have.property("type"); if (schema.type === "object") { expect(schema).to.have.property("properties"); } }); }); it("given a request that matches valid schema, should return a valid response", async function() { this.timeout(10000); const { client } = fixture.getContext(); const { z } = await import("zod"); const toolResponseSchema = z.object({ content: z.array(z.object({ type: z.string(), text: z.string().optional() })).optional() }).passthrough(); // Test listDeviceImages tool which requires emulator CLI const emulatorAvailable = await checkEmulatorAvailable(); if (!emulatorAvailable) { this.skip(); // Skip test if emulator CLI is not available } const result = await client.request({ method: "tools/call", params: { name: "listDeviceImages", arguments: { platform: "android" } } }, toolResponseSchema); expect(result).to.be.an("object"); }); it("given a request omits fields that are optional by the schema, should return a valid response", async function() { this.timeout(10000); const { client } = fixture.getContext(); const { z } = await import("zod"); const toolResponseSchema = z.object({ content: z.array(z.object({ type: z.string(), text: z.string().optional() })).optional() }).passthrough(); // Test listDeviceImages without optional parameters (listDeviceImages has no required params) const emulatorAvailable = await checkEmulatorAvailable(); if (!emulatorAvailable) { this.skip(); // Skip test if emulator CLI is not available } const result = await client.request({ method: "tools/call", params: { name: "listDeviceImages", arguments: { platform: "android" } } }, toolResponseSchema); expect(result).to.be.an("object"); }); it("given a request contains fields that are not defined by the schema, should return an error response", async function() { this.timeout(10000); const { client } = fixture.getContext(); // Test with listDeviceImages and unknown parameter to avoid device dependency const emulatorAvailable = await checkEmulatorAvailable(); if (!emulatorAvailable) { this.skip(); // Skip test if emulator CLI is not available } try { const { z } = await import("zod"); const result = await client.request({ method: "tools/call", params: { name: "listDeviceImages", arguments: { platform: "android", unknownField: "should not be allowed" } } }, z.any()); // If we reach here without error, the schema allows additional properties // This is actually valid behavior - some schemas are permissive expect(result).to.be.an("object"); } catch (error: any) { // If it fails, it should be due to schema validation expect(error.message).to.satisfy((msg: string) => msg.includes("Invalid parameters") || msg.includes("Failed to execute") || msg.includes("Unknown tool") ); } }); it("given a request contains fields that are defined by the schema but have incorrect types, should return an error response", async function() { this.timeout(10000); const { client } = fixture.getContext(); // Test tapOn with string instead of number try { const { z } = await import("zod"); await client.request({ method: "tools/call", params: { name: "tapOn", arguments: { x: "not a number", y: 200 } } }, z.any()); expect.fail("Should have thrown an error for incorrect type"); } catch (error: any) { expect(error.message).to.include("Invalid parameters"); } }); it("given a request contains fields that are defined by the schema but have incorrect values, should return an error response", async function() { this.timeout(10000); const { client } = fixture.getContext(); // Test with an invalid tool name to trigger schema validation error try { const { z } = await import("zod"); await client.request({ method: "tools/call", params: { name: "nonExistentTool", arguments: {} } }, z.any()); expect.fail("Should have thrown an error for unknown tool"); } catch (error: any) { // This should fail because the tool doesn't exist expect(error.message).to.include("Unknown tool"); } }); });

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/zillow/auto-mobile'

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