Skip to main content
Glama

Glif

Official
by glifxyz
tool-factory.test.ts10.1 kB
import { describe, expect, it, vi } from "vitest"; import { z } from "zod"; import type { ToolRequest } from "./request-parsing.js"; import { createErrorResponse, createListResponse, createTextResponse, createTool, type ToolConfig, } from "./tool-factory.js"; // Mock dependencies vi.mock("./request-parsing.js", () => ({ parseToolArguments: vi.fn((request, _schema) => { // Simple mock that just returns the arguments return request.params.arguments; }), })); vi.mock("./utils.js", () => ({ handleApiError: vi.fn((error, context) => { throw new Error(`Handled error in ${context}: ${error.message}`); }), })); describe("Tool Factory", () => { describe("createTool", () => { it("should create a tool with correct definition structure", () => { const config: ToolConfig = { name: "test_tool", description: "A test tool", schema: z.object({ input: z.string() }), properties: { input: { type: "string", description: "Test input", }, }, required: ["input"], }; const handler = vi .fn() .mockResolvedValue(createTextResponse("test response")); const tool = createTool(config, handler); expect(tool.definition).toEqual({ name: "test_tool", description: "A test tool", inputSchema: { type: "object", properties: { input: { type: "string", description: "Test input", }, }, required: ["input"], }, }); expect(tool.schema).toBe(config.schema); expect(typeof tool.handler).toBe("function"); }); it("should create a tool with default empty required array", () => { const config: ToolConfig = { name: "test_tool", description: "A test tool", schema: z.object({}), properties: {}, // No required field specified }; const handler = vi.fn().mockResolvedValue(createTextResponse("test")); const tool = createTool(config, handler); expect(tool.definition.inputSchema.required).toEqual([]); }); it("should create a working handler that calls the provided function", async () => { const mockHandler = vi .fn() .mockResolvedValue(createTextResponse("handler called")); const config: ToolConfig = { name: "test_tool", description: "Test", schema: z.object({ input: z.string() }), properties: { input: { type: "string" } }, }; const tool = createTool(config, mockHandler); const mockRequest: ToolRequest = { params: { name: "test_tool", arguments: { input: "test value" }, }, } as any; const result = await tool.handler(mockRequest); expect(mockHandler).toHaveBeenCalledWith({ input: "test value" }); expect(result).toEqual(createTextResponse("handler called")); }); it("should handle errors through handleApiError", async () => { const mockHandler = vi.fn().mockRejectedValue(new Error("Handler error")); const config: ToolConfig = { name: "error_tool", description: "Error test", schema: z.object({}), properties: {}, }; const tool = createTool(config, mockHandler); const mockRequest: ToolRequest = { params: { name: "error_tool", arguments: {} }, } as any; await expect(tool.handler(mockRequest)).rejects.toThrow( "Handled error in error_tool: Handler error" ); }); }); describe("createTextResponse", () => { it("should create a valid text response", () => { const response = createTextResponse("Hello, world!"); expect(response).toEqual({ content: [ { type: "text", text: "Hello, world!", }, ], }); }); it("should handle empty text", () => { const response = createTextResponse(""); expect(response).toEqual({ content: [ { type: "text", text: "", }, ], }); }); it("should handle multiline text", () => { const multilineText = "Line 1\nLine 2\nLine 3"; const response = createTextResponse(multilineText); expect(response).toEqual({ content: [ { type: "text", text: multilineText, }, ], }); }); }); describe("createListResponse", () => { it("should create a list response without title", () => { const items = ["Item 1", "Item 2", "Item 3"]; const response = createListResponse(items); expect(response).toEqual({ content: [ { type: "text", text: "Item 1\nItem 2\nItem 3", }, ], }); }); it("should create a list response with title", () => { const items = ["Apple", "Banana", "Cherry"]; const response = createListResponse(items, "Fruits"); expect(response).toEqual({ content: [ { type: "text", text: "Fruits\n\nApple\nBanana\nCherry", }, ], }); }); it("should handle empty list", () => { const response = createListResponse([]); expect(response).toEqual({ content: [ { type: "text", text: "", }, ], }); }); it("should handle empty list with title", () => { const response = createListResponse([], "Empty List"); expect(response).toEqual({ content: [ { type: "text", text: "Empty List\n\n", }, ], }); }); it("should handle single item list", () => { const response = createListResponse(["Single item"], "Solo"); expect(response).toEqual({ content: [ { type: "text", text: "Solo\n\nSingle item", }, ], }); }); }); describe("createErrorResponse", () => { it("should create error response from Error object", () => { const error = new Error("Something went wrong"); const response = createErrorResponse(error, "test_context"); expect(response).toEqual({ content: [ { type: "text", text: "Error in test_context: Something went wrong", }, ], }); }); it("should create error response from string", () => { const response = createErrorResponse("String error", "validation"); expect(response).toEqual({ content: [ { type: "text", text: "Error in validation: String error", }, ], }); }); it("should create error response from non-Error object", () => { const errorObj = { message: "Object error", code: 500 }; const response = createErrorResponse(errorObj, "api_call"); expect(response).toEqual({ content: [ { type: "text", text: "Error in api_call: [object Object]", }, ], }); }); it("should handle null/undefined errors", () => { const response1 = createErrorResponse(null, "null_test"); const response2 = createErrorResponse(undefined, "undefined_test"); expect(response1).toEqual({ content: [{ type: "text", text: "Error in null_test: null" }], }); expect(response2).toEqual({ content: [{ type: "text", text: "Error in undefined_test: undefined" }], }); }); }); describe("Integration tests", () => { it("should work with real-world tool configuration", () => { const searchSchema = z.object({ query: z.string(), limit: z.number().optional(), }); const searchConfig: ToolConfig = { name: "search_tool", description: "Search for items", schema: searchSchema, properties: { query: { type: "string", description: "Search query", }, limit: { type: "number", description: "Maximum results", }, }, required: ["query"], }; const searchHandler = vi.fn().mockImplementation(async (args) => { const results = [`Result for: ${args.query}`]; if (args.limit) { results.push(`Limited to: ${args.limit}`); } return createListResponse(results, "Search Results"); }); const tool = createTool(searchConfig, searchHandler); expect(tool.definition.name).toBe("search_tool"); expect(tool.definition.inputSchema.required).toEqual(["query"]); expect(typeof tool.handler).toBe("function"); }); it("should handle complex nested schemas", () => { const complexSchema = z.object({ user: z.object({ name: z.string(), age: z.number(), }), preferences: z.array(z.string()), metadata: z.record(z.string(), z.unknown()).optional(), }); const config: ToolConfig = { name: "complex_tool", description: "Handle complex data", schema: complexSchema, properties: { user: { type: "object", properties: { name: { type: "string" }, age: { type: "number" }, }, }, preferences: { type: "array", items: { type: "string" }, }, metadata: { type: "object", }, }, required: ["user", "preferences"], }; const handler = vi .fn() .mockResolvedValue(createTextResponse("Complex handled")); const tool = createTool(config, handler); expect(tool.definition.inputSchema.required).toEqual([ "user", "preferences", ]); expect(tool.schema).toBe(complexSchema); }); }); });

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/glifxyz/glif-mcp-server'

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