Skip to main content
Glama

Task Trellis MCP

serverStartup.test.ts14.1 kB
import { TaskTrellisService } from "../services/TaskTrellisService"; import { Repository } from "../repositories"; import { promises as fsPromises } from "fs"; // Mock the entire server file jest.mock("@modelcontextprotocol/sdk/server/index.js"); jest.mock("@modelcontextprotocol/sdk/server/stdio.js"); // Mock fs/promises jest.mock("fs", () => ({ ...jest.requireActual("fs"), promises: { access: jest.fn(), mkdir: jest.fn(), readdir: jest.fn().mockResolvedValue([]), readFile: jest.fn(), writeFile: jest.fn(), }, })); const mockFsPromises = fsPromises as jest.Mocked<typeof fsPromises>; // Mock console methods const consoleSpy = { warn: jest.spyOn(console, "warn").mockImplementation(), error: jest.spyOn(console, "error").mockImplementation(), }; // Mock repository and service const mockRepository: jest.Mocked<Repository> = { getObjectById: jest.fn(), getObjects: jest.fn(), saveObject: jest.fn(), deleteObject: jest.fn(), getChildrenOf: jest.fn(), }; const mockService: jest.Mocked<TaskTrellisService> = { createObject: jest.fn(), updateObject: jest.fn(), listObjects: jest.fn(), appendObjectLog: jest.fn(), appendModifiedFiles: jest.fn(), claimTask: jest.fn(), getNextAvailableIssue: jest.fn(), completeTask: jest.fn(), pruneClosed: jest.fn(), }; // Mock the repository and service getter functions jest.mock("../server", () => ({ ...jest.requireActual("../server"), getRepository: () => mockRepository, _getService: () => mockService, })); describe("Server Startup Auto-Prune Integration", () => { beforeEach(() => { jest.clearAllMocks(); consoleSpy.warn.mockClear(); consoleSpy.error.mockClear(); }); afterEach(() => { jest.resetModules(); }); describe("auto-prune disabled", () => { it("should not execute auto-prune when autoPrune is 0", async () => { // Mock server config with autoPrune = 0 const mockServerConfig = { autoPrune: 0 }; // Import and create startServer function with mocked config const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); expect(mockService.pruneClosed).not.toHaveBeenCalled(); expect(consoleSpy.warn).not.toHaveBeenCalledWith( expect.stringContaining("Starting auto-prune"), ); }); it("should not execute auto-prune when autoPrune is negative", async () => { const mockServerConfig = { autoPrune: -1 }; const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); expect(mockService.pruneClosed).not.toHaveBeenCalled(); expect(consoleSpy.warn).not.toHaveBeenCalledWith( expect.stringContaining("Starting auto-prune"), ); }); }); describe("auto-prune enabled", () => { it("should execute auto-prune when autoPrune > 0", async () => { const mockServerConfig = { autoPrune: 7 }; const mockPruneResult = { content: [ { type: "text", text: "Pruned 3 closed objects older than 7 days" }, ], }; mockService.pruneClosed.mockResolvedValue(mockPruneResult); const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); expect(mockService.pruneClosed).toHaveBeenCalledTimes(1); expect(mockService.pruneClosed).toHaveBeenCalledWith(mockRepository, 7); expect(consoleSpy.warn).toHaveBeenCalledWith( "Starting auto-prune for objects older than 7 days...", ); expect(consoleSpy.warn).toHaveBeenCalledWith( "Auto-prune completed: Pruned 3 closed objects older than 7 days", ); }); it("should execute auto-prune with different day values", async () => { const mockServerConfig = { autoPrune: 30 }; const mockPruneResult = { content: [ { type: "text", text: "Pruned 0 closed objects older than 30 days" }, ], }; mockService.pruneClosed.mockResolvedValue(mockPruneResult); const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); expect(mockService.pruneClosed).toHaveBeenCalledWith(mockRepository, 30); expect(consoleSpy.warn).toHaveBeenCalledWith( "Starting auto-prune for objects older than 30 days...", ); }); }); describe("auto-prune error handling", () => { it("should continue startup when auto-prune fails with Error", async () => { const mockServerConfig = { autoPrune: 7 }; const mockError = new Error("Repository connection failed"); mockService.pruneClosed.mockRejectedValue(mockError); const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); expect(mockService.pruneClosed).toHaveBeenCalledTimes(1); expect(consoleSpy.warn).toHaveBeenCalledWith( "Starting auto-prune for objects older than 7 days...", ); expect(consoleSpy.error).toHaveBeenCalledWith( "Auto-prune failed: Repository connection failed", ); }); it("should continue startup when auto-prune fails with non-Error", async () => { const mockServerConfig = { autoPrune: 7 }; const mockError = "Something went wrong"; mockService.pruneClosed.mockRejectedValue(mockError); const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); expect(mockService.pruneClosed).toHaveBeenCalledTimes(1); expect(consoleSpy.error).toHaveBeenCalledWith( "Auto-prune failed: Something went wrong", ); }); it("should continue startup when auto-prune fails with null", async () => { const mockServerConfig = { autoPrune: 7 }; mockService.pruneClosed.mockRejectedValue(null); const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); expect(consoleSpy.error).toHaveBeenCalledWith("Auto-prune failed: null"); }); it("should not crash server when auto-prune throws", async () => { const mockServerConfig = { autoPrune: 7 }; const mockError = new Error("Critical failure"); mockService.pruneClosed.mockRejectedValue(mockError); const { startServer } = createStartServerWithConfig(mockServerConfig); // Should not throw - server continues after auto-prune failure await expect(startServer()).resolves.not.toThrow(); }); }); describe("integration with repository and service", () => { it("should use existing repository and service instances", async () => { const mockServerConfig = { autoPrune: 1 }; const mockPruneResult = { content: [ { type: "text", text: "Pruned 1 closed objects older than 1 days" }, ], }; mockService.pruneClosed.mockResolvedValue(mockPruneResult); const { startServer } = createStartServerWithConfig(mockServerConfig); await startServer(); // Verify the mocked instances were called expect(mockService.pruneClosed).toHaveBeenCalledWith(mockRepository, 1); }); }); }); describe("Server Startup Basic-Claude Agents Copying Integration", () => { beforeEach(() => { jest.clearAllMocks(); consoleSpy.warn.mockClear(); consoleSpy.error.mockClear(); mockFsPromises.access.mockClear(); mockFsPromises.mkdir.mockClear(); mockFsPromises.readdir.mockClear(); mockFsPromises.readFile.mockClear(); mockFsPromises.writeFile.mockClear(); }); afterEach(() => { jest.resetModules(); }); describe("conditional execution", () => { it("should copy agents when both conditions are met", async () => { const options = { promptPackage: "basic-claude", projectRootFolder: "/test/project", }; mockFsPromises.access.mockResolvedValue(undefined); mockFsPromises.mkdir.mockResolvedValue(undefined); (mockFsPromises.readdir as jest.Mock).mockResolvedValue([ "implementation-planner.md", ]); mockFsPromises.readFile.mockResolvedValue("mock file content"); mockFsPromises.writeFile.mockResolvedValue(undefined); const { startServer } = createStartServerWithAgentCopy(options); await startServer(); expect(consoleSpy.warn).toHaveBeenCalledWith( "Starting copy of basic-claude agents...", ); expect(mockFsPromises.access).toHaveBeenCalled(); expect(mockFsPromises.mkdir).toHaveBeenCalled(); expect(mockFsPromises.readdir).toHaveBeenCalled(); expect(mockFsPromises.writeFile).toHaveBeenCalled(); }); it("should skip copying when promptPackage is not basic-claude", async () => { const options = { promptPackage: "basic", projectRootFolder: "/test/project", }; const { startServer } = createStartServerWithAgentCopy(options); await startServer(); expect(consoleSpy.warn).not.toHaveBeenCalledWith( "Starting copy of basic-claude agents...", ); expect(mockFsPromises.access).not.toHaveBeenCalled(); }); it("should skip copying when projectRootFolder is not provided", async () => { const options = { promptPackage: "basic-claude", projectRootFolder: undefined, }; const { startServer } = createStartServerWithAgentCopy(options); await startServer(); expect(consoleSpy.warn).not.toHaveBeenCalledWith( "Starting copy of basic-claude agents...", ); expect(mockFsPromises.access).not.toHaveBeenCalled(); }); }); describe("error handling", () => { it("should continue startup when copying fails", async () => { const options = { promptPackage: "basic-claude", projectRootFolder: "/test/project", }; mockFsPromises.access.mockRejectedValue( new Error("Source directory not found"), ); const { startServer } = createStartServerWithAgentCopy(options); await startServer(); expect(consoleSpy.warn).toHaveBeenCalledWith( "Starting copy of basic-claude agents...", ); expect(consoleSpy.error).toHaveBeenCalledWith( expect.stringContaining( "Agent copy failed: Source directory not found", ), ); }); it("should handle mkdir failures gracefully", async () => { const options = { promptPackage: "basic-claude", projectRootFolder: "/test/project", }; mockFsPromises.access.mockResolvedValue(undefined); mockFsPromises.mkdir.mockRejectedValue(new Error("Permission denied")); const { startServer } = createStartServerWithAgentCopy(options); await startServer(); expect(consoleSpy.error).toHaveBeenCalledWith( expect.stringContaining("Agent copy failed:"), ); }); }); }); /** * Helper function to create a startServer function with a specific server config * This simulates the server startup logic with auto-prune integration */ function createStartServerWithConfig(serverConfig: { autoPrune: number }) { // Mock runServer to avoid actual server connection const mockRunServer = jest.fn().mockResolvedValue(undefined); const startServer = async () => { // Auto-prune closed objects if enabled if (serverConfig.autoPrune > 0) { try { console.warn( `Starting auto-prune for objects older than ${serverConfig.autoPrune} days...`, ); const repository = mockRepository; const service = mockService; const result = await service.pruneClosed( repository, serverConfig.autoPrune, ); console.warn(`Auto-prune completed: ${result.content[0].text}`); } catch (error) { console.error( `Auto-prune failed: ${error instanceof Error ? error.message : String(error)}`, ); // Don't exit - continue starting server even if prune fails } } // Start the main server await mockRunServer(); }; return { startServer, mockRunServer }; } /** * Helper function to create a startServer function with agent copying functionality * This simulates the server startup logic with basic-claude agents copying */ function createStartServerWithAgentCopy(options: { promptPackage: string; projectRootFolder: string | undefined; }) { // Mock runServer to avoid actual server connection const mockRunServer = jest.fn().mockResolvedValue(undefined); // Mock copyBasicClaudeAgents function behavior const mockCopyBasicClaudeAgents = async ( projectRootFolder: string, ): Promise<void> => { // Check if source directory exists await mockFsPromises.access("/mock/source/path"); // Create target directory if it doesn't exist await mockFsPromises.mkdir(`${projectRootFolder}/.claude/agents`, { recursive: true, }); // Read all files from source directory const files = await mockFsPromises.readdir("/mock/source/path"); // Copy each file for (const file of files) { // Read source file content const content = await mockFsPromises.readFile( `/mock/source/path/${file}`, "utf8", ); // Write to target location await mockFsPromises.writeFile( `${projectRootFolder}/.claude/agents/${file}`, content, "utf8", ); } console.warn( `Successfully copied ${files.length} agent file(s) from /mock/source/path to ${projectRootFolder}/.claude/agents`, ); }; const startServer = async () => { // Copy basic-claude agents if conditions are met if (options.promptPackage === "basic-claude" && options.projectRootFolder) { try { console.warn("Starting copy of basic-claude agents..."); await mockCopyBasicClaudeAgents(options.projectRootFolder); } catch (error) { console.error( `Agent copy failed: ${error instanceof Error ? error.message : String(error)}`, ); // Don't exit - continue starting server even if copy fails } } // Start the main server await mockRunServer(); }; return { startServer, mockRunServer }; }

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/langadventurellc/task-trellis-mcp'

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