Skip to main content
Glama

Basecamp MCP Server

by jhliberty
mcp-server.test.ts8.42 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { mockBasecampProject, mockBasecampTodo, mockBasecampCard, mockBasecampCardTable, mockOAuthToken, mockEnvironment, mockMCPRequest, mockMCPToolCall } from './utils.js'; import { createServer } from '../index.js'; import { BasecampClient } from '../lib/basecamp-client.js'; import { tokenStorage } from '../lib/token-storage.js'; import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; // Mock the dependencies but use real server logic vi.mock('../lib/basecamp-client.js'); vi.mock('../lib/token-storage.js'); vi.mock('axios'); const MockedBasecampClient = vi.mocked(BasecampClient); const mockTokenStorage = vi.mocked(tokenStorage); // Mock Basecamp client instance const mockBasecampClientInstance = { getProjects: vi.fn(), getProject: vi.fn(), getTodos: vi.fn(), getTodoLists: vi.fn(), createTodo: vi.fn(), completeTodo: vi.fn(), uncompleteTodo: vi.fn(), search: vi.fn(), getCardTable: vi.fn(), getCardTableDetails: vi.fn(), getCards: vi.fn(), createCard: vi.fn(), updateCard: vi.fn(), moveCard: vi.fn(), completeCard: vi.fn(), getComments: vi.fn(), getCampfireLines: vi.fn(), getDailyCheckIns: vi.fn(), getQuestionAnswers: vi.fn(), createAttachment: vi.fn(), getEvents: vi.fn(), getWebhooks: vi.fn(), createWebhook: vi.fn(), deleteWebhook: vi.fn(), getDocuments: vi.fn(), getDocument: vi.fn(), createDocument: vi.fn(), updateDocument: vi.fn(), trashDocument: vi.fn(), testConnection: vi.fn() }; MockedBasecampClient.mockImplementation(() => mockBasecampClientInstance as any); describe('MCP Server Core Functionality', () => { let server: any; beforeEach(() => { vi.clearAllMocks(); mockEnvironment(); // Setup default token storage behavior mockTokenStorage.getToken.mockResolvedValue(mockOAuthToken()); mockTokenStorage.isTokenExpired.mockResolvedValue(false); // Setup mock client methods mockBasecampClientInstance.getProjects.mockResolvedValue([mockBasecampProject()]); mockBasecampClientInstance.getProject.mockResolvedValue(mockBasecampProject()); mockBasecampClientInstance.getCardTable.mockResolvedValue({ id: '1', name: 'card_table' }); mockBasecampClientInstance.getCardTableDetails.mockResolvedValue(mockBasecampCardTable()); mockBasecampClientInstance.createCard.mockResolvedValue(mockBasecampCard()); server = createServer(); }); afterEach(() => { vi.resetAllMocks(); }); describe('Server Initialization', () => { it('should create server instance', () => { expect(server).toBeDefined(); }); it('should be an instance with server properties', () => { expect(server).toHaveProperty('run'); }); }); describe('Tools Registration', () => { it('should have tools available through server', async () => { // This test validates the server exists and can be created expect(server).toBeDefined(); }); }); describe('Authentication Integration', () => { it('should handle token storage setup', async () => { const token = mockOAuthToken(); mockTokenStorage.getToken.mockResolvedValue(token); mockTokenStorage.isTokenExpired.mockResolvedValue(false); // Test that mocks are set up correctly const retrievedToken = await mockTokenStorage.getToken(); const isExpired = await mockTokenStorage.isTokenExpired(); expect(retrievedToken).toEqual(token); expect(isExpired).toBe(false); }); it('should handle missing token', async () => { mockTokenStorage.getToken.mockResolvedValue(null); const retrievedToken = await mockTokenStorage.getToken(); expect(retrievedToken).toBeNull(); }); it('should handle expired token', async () => { const expiredToken = mockOAuthToken(); mockTokenStorage.getToken.mockResolvedValue(expiredToken); mockTokenStorage.isTokenExpired.mockResolvedValue(true); const isExpired = await mockTokenStorage.isTokenExpired(); expect(isExpired).toBe(true); }); }); describe('Client Integration', () => { it('should mock BasecampClient correctly', () => { // The client is only instantiated when actually needed (lazy loading) // So we just verify the mock setup exists expect(MockedBasecampClient).toBeDefined(); }); it('should setup client method mocks', async () => { const projects = await mockBasecampClientInstance.getProjects(); expect(projects).toEqual([mockBasecampProject()]); expect(mockBasecampClientInstance.getProjects).toHaveBeenCalled(); }); it('should handle client method calls', async () => { const project = await mockBasecampClientInstance.getProject('123'); expect(project).toEqual(mockBasecampProject()); expect(mockBasecampClientInstance.getProject).toHaveBeenCalledWith('123'); }); it('should handle card creation', async () => { const card = await mockBasecampClientInstance.createCard('123', '456', 'Test Card'); expect(card).toEqual(mockBasecampCard()); expect(mockBasecampClientInstance.createCard).toHaveBeenCalledWith('123', '456', 'Test Card'); }); }); describe('Error Handling', () => { it('should handle client errors', async () => { mockBasecampClientInstance.getProjects.mockRejectedValue(new Error('API Error')); await expect(mockBasecampClientInstance.getProjects()).rejects.toThrow('API Error'); }); it('should handle network errors', async () => { const networkError = new Error('Network error: Unable to reach Basecamp API'); mockBasecampClientInstance.getProjects.mockRejectedValue(networkError); await expect(mockBasecampClientInstance.getProjects()).rejects.toThrow('Network error'); }); it('should handle rate limiting', async () => { const rateLimitError = new Error('Rate limit exceeded'); mockBasecampClientInstance.getProjects.mockRejectedValue(rateLimitError); await expect(mockBasecampClientInstance.getProjects()).rejects.toThrow('Rate limit exceeded'); }); }); describe('Mock Data Validation', () => { it('should generate valid mock project data', () => { const project = mockBasecampProject(); expect(project).toHaveProperty('id'); expect(project).toHaveProperty('name'); expect(project).toHaveProperty('dock'); }); it('should generate valid mock card data', () => { const card = mockBasecampCard(); expect(card).toHaveProperty('id'); expect(card).toHaveProperty('title'); }); it('should generate valid mock token data', () => { const token = mockOAuthToken(); expect(token).toHaveProperty('access_token'); expect(token).toHaveProperty('account_id'); }); }); }); // Additional simplified tests for tool schemas and validation describe('Tool Schema Validation', () => { const expectedCoreTools = [ 'get_projects', 'get_project', 'get_todolists', 'get_todos', 'get_card_table', 'get_cards', 'create_card', 'search_basecamp', 'global_search' ]; it('should define expected tool names', () => { expectedCoreTools.forEach(toolName => { expect(typeof toolName).toBe('string'); expect(toolName.length).toBeGreaterThan(0); }); }); it('should have required tools for card operations', () => { const cardTools = expectedCoreTools.filter(name => name.includes('card')); expect(cardTools.length).toBeGreaterThan(0); expect(cardTools).toContain('get_card_table'); expect(cardTools).toContain('get_cards'); expect(cardTools).toContain('create_card'); }); it('should have required tools for project operations', () => { const projectTools = expectedCoreTools.filter(name => name.includes('project')); expect(projectTools.length).toBeGreaterThan(0); expect(projectTools).toContain('get_projects'); expect(projectTools).toContain('get_project'); }); it('should have search tools', () => { const searchTools = expectedCoreTools.filter(name => name.includes('search')); expect(searchTools.length).toBeGreaterThan(0); expect(searchTools).toContain('search_basecamp'); expect(searchTools).toContain('global_search'); }); });

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/jhliberty/basecamp-mcp-server'

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