Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
capability-index-resources.integration.test.ts17.6 kB
/** * Capability Index Resources Integration Tests * * Tests the full integration of CapabilityIndexResource with MCP server: * - Resource discovery via resources/list RPC * - Resource reading via resources/read RPC * - Configuration-controlled enable/disable * - Variant filtering based on config * * These tests verify the complete workflow from RPC call to response. */ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; // FIX: Removed unused imports 'Server' and 'path' // Previously: import { Server } from '@modelcontextprotocol/sdk/server/index.js'; (never used) // Now: Removed unused imports import { CapabilityIndexResource } from '../../../src/server/resources/CapabilityIndexResource.js'; import { ConfigManager } from '../../../src/config/ConfigManager.js'; // FIX: Added node: prefix to built-in Node.js imports // Previously: import fs from 'fs/promises'; // Now: import fs from 'node:fs/promises'; for Node.js convention import fs from 'node:fs/promises'; import os from 'node:os'; import yaml from 'js-yaml'; // Mock dependencies jest.mock('fs/promises'); jest.mock('os'); // Mock ConfigManager jest.mock('../../../src/config/ConfigManager.js', () => { const actual = jest.requireActual('../../../src/config/ConfigManager.js'); return { ...actual, ConfigManager: { getInstance: jest.fn(), resetForTesting: jest.fn() } }; }); // Mock logger jest.unstable_mockModule('../../../src/utils/logger.js', () => ({ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn() } })); // FIX: Extract helper function to reduce nesting depth // Previously: Function nesting exceeded 4 levels at line 154 // Now: Extracted filtering logic to separate function function filterResourcesByVariants(resources: any[], enabledVariants: string[]): any[] { return resources.filter(r => { return enabledVariants.some(v => r.uri.includes(v)); }); } describe('Capability Index Resources Integration', () => { let resource: CapabilityIndexResource; let mockConfigManager: any; let mockFs: jest.Mocked<typeof fs>; let mockOs: jest.Mocked<typeof os>; const mockCapabilityIndex = { metadata: { version: '2.0.0', created: '2025-10-01T00:00:00.000Z', last_updated: '2025-10-15T12:00:00.000Z', total_elements: 42 }, action_triggers: { debug: ['Debug Detective'], test: ['Test Runner'] } }; beforeEach(() => { jest.clearAllMocks(); // Setup filesystem mocks mockFs = fs as jest.Mocked<typeof fs>; mockOs = os as jest.Mocked<typeof os>; mockOs.homedir.mockReturnValue('/mock/home'); const yamlContent = yaml.dump(mockCapabilityIndex); mockFs.access.mockResolvedValue(undefined); mockFs.readFile.mockResolvedValue(yamlContent); // Setup ConfigManager mock mockConfigManager = { initialize: jest.fn().mockResolvedValue(undefined), getConfig: jest.fn(), getSetting: jest.fn() }; (ConfigManager.getInstance as jest.Mock).mockReturnValue(mockConfigManager); resource = new CapabilityIndexResource(); }); afterEach(() => { jest.restoreAllMocks(); }); describe('Resource Discovery (resources/list)', () => { /** * Verifies that when resources are disabled in config, * resources/list returns empty array */ it('should return empty list when advertise_resources is false', async () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: false, variants: { summary: true, full: true, stats: true } }); // In a real integration, this would be filtered by the server // Here we test the resource handler directly const resources = await resource.listResources(); // Resource handler always returns all resources // Server layer should filter based on config expect(resources.resources).toHaveLength(3); }); /** * Verifies that when resources are enabled, * all 3 resources are returned */ it('should return all 3 resources when advertise_resources is true', async () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: true, variants: { summary: true, full: true, stats: true } }); const resources = await resource.listResources(); expect(resources.resources).toHaveLength(3); expect(resources.resources.map(r => r.uri)).toEqual([ 'dollhouse://capability-index/summary', 'dollhouse://capability-index/full', 'dollhouse://capability-index/stats' ]); }); /** * Verifies that only enabled variants are returned * when variant filtering is applied */ it('should filter resources based on variant configuration', async () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: true, variants: { summary: true, full: false, // Disabled stats: true } }); const allResources = await resource.listResources(); // In practice, server would filter this // This test documents expected behavior // FIX: Use helper function to reduce nesting depth const enabledVariants = ['summary', 'stats']; const filteredResources = filterResourcesByVariants(allResources.resources, enabledVariants); expect(filteredResources).toHaveLength(2); expect(filteredResources.map(r => r.uri)).toEqual([ 'dollhouse://capability-index/summary', 'dollhouse://capability-index/stats' ]); }); /** * Verifies that only stats variant is advertised by default * (safe configuration) */ it('should advertise only stats variant by default', async () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: false, variants: { summary: false, full: false, stats: true // Only stats enabled by default } }); const allResources = await resource.listResources(); const config = mockConfigManager.getSetting(); // Filter based on config const enabledResources = allResources.resources.filter(r => { if (r.uri.includes('summary')) return config.variants.summary; if (r.uri.includes('full')) return config.variants.full; if (r.uri.includes('stats')) return config.variants.stats; return false; }); expect(enabledResources).toHaveLength(1); expect(enabledResources[0].uri).toBe('dollhouse://capability-index/stats'); }); }); describe('Resource Reading (resources/read)', () => { /** * Verifies that summary resource can be read successfully * with valid YAML content */ it('should read summary resource and return valid YAML', async () => { const result = await resource.readResource('dollhouse://capability-index/summary'); expect(result.contents).toHaveLength(1); expect(result.contents[0].mimeType).toBe('text/yaml'); // Verify it's valid YAML const yamlContent = result.contents[0].text .split('\n') .filter(line => !line.startsWith('#') && line.trim() !== '') .join('\n'); const parsed = yaml.load(yamlContent); expect(parsed).toBeDefined(); }); /** * Verifies that full resource can be read successfully * with complete index content */ it('should read full resource and return complete index', async () => { const result = await resource.readResource('dollhouse://capability-index/full'); expect(result.contents).toHaveLength(1); expect(result.contents[0].mimeType).toBe('text/yaml'); expect(result.contents[0].text).toContain('metadata:'); expect(result.contents[0].text).toContain('action_triggers:'); }); /** * Verifies that stats resource returns valid JSON * with all expected fields */ it('should read stats resource and return valid JSON', async () => { const result = await resource.readResource('dollhouse://capability-index/stats'); expect(result.contents).toHaveLength(1); expect(result.contents[0].mimeType).toBe('application/json'); const stats = JSON.parse(result.contents[0].text); expect(stats).toHaveProperty('summarySize'); expect(stats).toHaveProperty('fullSize'); expect(stats).toHaveProperty('estimatedSummaryTokens'); expect(stats).toHaveProperty('estimatedFullTokens'); }); /** * Verifies that reading disabled variant returns error * (in practice, server would prevent this) */ it('should handle reads of disabled variants gracefully', async () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: true, variants: { summary: false, // Disabled full: true, stats: true } }); // Resource handler still allows reads // Server layer would block unauthorized access const result = await resource.readResource('dollhouse://capability-index/summary'); expect(result.contents).toHaveLength(1); }); /** * Verifies that invalid resource URIs return descriptive errors */ it('should return error for invalid resource URI', async () => { await expect( resource.readResource('dollhouse://capability-index/invalid') ).rejects.toThrow('Unknown capability index resource'); }); /** * Verifies that missing capability index file returns error */ it('should return error when capability index does not exist', async () => { mockFs.access.mockRejectedValue(new Error('ENOENT: no such file or directory')); await expect( resource.readResource('dollhouse://capability-index/summary') ).rejects.toThrow('Capability index not available'); }); }); describe('Configuration Integration', () => { /** * Verifies that default configuration has resources disabled * for safety (opt-in model) */ it('should have advertise_resources disabled by default', () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: false, variants: { summary: false, full: false, stats: true } }); const config = mockConfigManager.getSetting(); expect(config.advertise_resources).toBe(false); }); /** * Verifies that variants can be individually toggled */ it('should allow individual variant toggles', () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: true, variants: { summary: true, full: false, stats: true } }); const config = mockConfigManager.getSetting(); expect(config.variants.summary).toBe(true); expect(config.variants.full).toBe(false); expect(config.variants.stats).toBe(true); }); /** * Verifies that enabling all variants works correctly */ it('should support enabling all variants', () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: true, variants: { summary: true, full: true, stats: true } }); const config = mockConfigManager.getSetting(); expect(config.variants.summary).toBe(true); expect(config.variants.full).toBe(true); expect(config.variants.stats).toBe(true); }); /** * Verifies that stats variant is safe to enable by default * (minimal token impact) */ it('should enable stats variant by default as it is safe', () => { mockConfigManager.getSetting.mockReturnValue({ advertise_resources: false, variants: { summary: false, full: false, stats: true // Safe default } }); const config = mockConfigManager.getSetting(); expect(config.variants.stats).toBe(true); }); }); describe('Error Handling', () => { /** * Verifies that filesystem errors are caught and * return meaningful error messages */ it('should handle filesystem errors gracefully', async () => { mockFs.readFile.mockRejectedValue(new Error('EACCES: permission denied')); await expect( resource.readResource('dollhouse://capability-index/summary') ).rejects.toThrow('Capability index not available'); }); /** * Verifies that YAML parsing errors return descriptive messages */ it('should handle YAML parsing errors', async () => { mockFs.readFile.mockResolvedValue('invalid: yaml: [unclosed'); await expect( resource.readResource('dollhouse://capability-index/summary') ).rejects.toThrow('Capability index not available'); }); /** * Verifies that missing file errors include helpful context */ it('should provide helpful error when capability index is missing', async () => { mockFs.access.mockRejectedValue(new Error('ENOENT: no such file')); await expect( resource.readResource('dollhouse://capability-index/summary') ).rejects.toThrow('Capability index not available'); }); }); describe('Cache Behavior in Integration', () => { /** * Verifies that multiple resource reads use cache efficiently */ it('should use cache across multiple resource reads', async () => { // Read summary await resource.readResource('dollhouse://capability-index/summary'); expect(mockFs.readFile).toHaveBeenCalledTimes(1); // Read full - should use cache await resource.readResource('dollhouse://capability-index/full'); expect(mockFs.readFile).toHaveBeenCalledTimes(1); // Read stats - should use cache await resource.readResource('dollhouse://capability-index/stats'); expect(mockFs.readFile).toHaveBeenCalledTimes(1); }); /** * Verifies that cache expires and reloads data */ it('should reload after cache TTL expires', async () => { const originalNow = Date.now; let currentTime = 1000000; Date.now = jest.fn(() => currentTime); try { // First read at t=1000000 await resource.readResource('dollhouse://capability-index/summary'); expect(mockFs.readFile).toHaveBeenCalledTimes(1); // Second read at t=1070000 (70 seconds later, cache expired) currentTime = 1070000; await resource.readResource('dollhouse://capability-index/summary'); expect(mockFs.readFile).toHaveBeenCalledTimes(2); } finally { Date.now = originalNow; } }); }); describe('Performance Characteristics', () => { /** * Verifies that summary variant is lightweight */ it('should generate summary quickly (lightweight variant)', async () => { const start = Date.now(); await resource.readResource('dollhouse://capability-index/summary'); const duration = Date.now() - start; // Should complete quickly (< 100ms) expect(duration).toBeLessThan(100); }); /** * Verifies that stats generation is fast */ it('should generate stats quickly', async () => { const start = Date.now(); await resource.readResource('dollhouse://capability-index/stats'); const duration = Date.now() - start; // Should complete quickly (< 100ms) expect(duration).toBeLessThan(100); }); /** * Verifies that concurrent requests are handled efficiently */ it('should handle concurrent reads efficiently', async () => { const promises = [ resource.readResource('dollhouse://capability-index/summary'), resource.readResource('dollhouse://capability-index/full'), resource.readResource('dollhouse://capability-index/stats') ]; await Promise.all(promises); // Should only load file once due to caching expect(mockFs.readFile).toHaveBeenCalledTimes(1); }); }); describe('Token Estimates', () => { /** * Verifies that summary token estimate is within expected range * (~2.5-3.5K tokens) */ it('should have summary token estimate in expected range', async () => { const result = await resource.readResource('dollhouse://capability-index/stats'); const stats = JSON.parse(result.contents[0].text); // Summary should be relatively small expect(stats.estimatedSummaryTokens).toBeGreaterThan(100); expect(stats.estimatedSummaryTokens).toBeLessThan(10000); }); /** * Verifies that full index token estimate is substantial * (~35-45K tokens) */ it('should have full token estimate significantly larger than summary', async () => { const result = await resource.readResource('dollhouse://capability-index/stats'); const stats = JSON.parse(result.contents[0].text); expect(stats.estimatedFullTokens).toBeGreaterThan(stats.estimatedSummaryTokens); expect(stats.estimatedFullTokens).toBeGreaterThan(1000); }); /** * Verifies that stats resource itself is minimal * (just JSON metadata, ~50 tokens) */ it('should have stats resource be minimal in size', async () => { const result = await resource.readResource('dollhouse://capability-index/stats'); const statsText = result.contents[0].text; // Stats JSON should be small expect(statsText.length).toBeLessThan(500); }); }); });

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/DollhouseMCP/DollhouseMCP'

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