Skip to main content
Glama

hypertool-mcp

per-app-config.test.tsโ€ข9.33 kB
/** * Integration tests for per-app configuration system */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { TestEnvironment } from '../fixtures/base.js'; import { ExistingConfigScenario } from '../fixtures/scenarios/index.js'; import { join } from 'path'; import { vol } from 'memfs'; import { spawn } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(spawn); describe('Per-App Configuration Integration', () => { let env: TestEnvironment; const baseDir = '/tmp/hypertool-test'; beforeEach(async () => { env = new TestEnvironment(baseDir); await env.setup(); }); afterEach(async () => { await env.teardown(); }); describe('CLI flags', () => { it('should support --linked-app flag', async () => { // Create app-specific config const mcpDir = join(env.getConfig().configRoot, 'mcp'); vol.mkdirSync(mcpDir, { recursive: true }); const appConfig = { mcpServers: { 'test-server': { command: 'test', args: ['--test'] } }, _metadata: { app: 'claude-desktop' } }; vol.writeFileSync( join(mcpDir, 'claude-desktop.json'), JSON.stringify(appConfig, null, 2) ); // Update main config const mainConfig = { version: '1.0.0', applications: { 'claude-desktop': { configPath: '~/Library/Application Support/Claude/claude_desktop_config.json', mcpConfig: 'mcp/claude-desktop.json' } } }; vol.writeFileSync( join(env.getConfig().configRoot, 'config.json'), JSON.stringify(mainConfig, null, 2) ); // Verify the config can be loaded const configPath = join(mcpDir, 'claude-desktop.json'); expect(vol.existsSync(configPath)).toBe(true); const loadedConfig = JSON.parse(vol.readFileSync(configPath, 'utf-8') as string); expect(loadedConfig.mcpServers['test-server']).toBeDefined(); }); it('should support --profile flag with --linked-app', async () => { // Create profile directory const profileDir = join(env.getConfig().configRoot, 'mcp', 'profiles', 'claude-desktop'); vol.mkdirSync(profileDir, { recursive: true }); const profileConfig = { mcpServers: { 'project-server': { command: 'project-tool', args: ['--project'] } }, _metadata: { app: 'claude-desktop', profile: 'my-project' } }; vol.writeFileSync( join(profileDir, 'my-project.json'), JSON.stringify(profileConfig, null, 2) ); // Verify profile config exists const profilePath = join(profileDir, 'my-project.json'); expect(vol.existsSync(profilePath)).toBe(true); const loadedProfile = JSON.parse(vol.readFileSync(profilePath, 'utf-8') as string); expect(loadedProfile._metadata.profile).toBe('my-project'); }); }); describe('config discovery', () => { it('should load app-specific config instead of global', async () => { // Create both global and app-specific configs const globalConfig = { mcpServers: { 'global-server': { command: 'global', args: [] } } }; vol.writeFileSync( join(env.getConfig().configRoot, 'mcp.json'), JSON.stringify(globalConfig, null, 2) ); const mcpDir = join(env.getConfig().configRoot, 'mcp'); vol.mkdirSync(mcpDir, { recursive: true }); const appConfig = { mcpServers: { 'app-server': { command: 'app-specific', args: [] } }, _metadata: { app: 'claude-desktop' } }; vol.writeFileSync( join(mcpDir, 'claude-desktop.json'), JSON.stringify(appConfig, null, 2) ); // Update main config to reference app config const mainConfig = { version: '1.0.0', applications: { 'claude-desktop': { mcpConfig: 'mcp/claude-desktop.json' } } }; vol.writeFileSync( join(env.getConfig().configRoot, 'config.json'), JSON.stringify(mainConfig, null, 2) ); // When loading with --linked-app, should get app-specific config const appConfigPath = join(mcpDir, 'claude-desktop.json'); const loadedConfig = JSON.parse(vol.readFileSync(appConfigPath, 'utf-8') as string); expect(loadedConfig.mcpServers['app-server']).toBeDefined(); expect(loadedConfig.mcpServers['global-server']).toBeUndefined(); }); }); describe('backwards compatibility', () => { it('should maintain global mcp.json during transition', async () => { // Setup apps with configs await env.setup(new ExistingConfigScenario(['claude-desktop', 'cursor'])); // Global mcp.json should exist after import const globalPath = join(env.getConfig().configRoot, 'mcp.json'); // Create a mock global config vol.writeFileSync(globalPath, JSON.stringify({ mcpServers: { 'test': { command: 'test' } } })); expect(vol.existsSync(globalPath)).toBe(true); }); it('should work with legacy --mcp-config flag', async () => { const customPath = join(baseDir, 'custom-mcp.json'); const customConfig = { mcpServers: { 'custom-server': { command: 'custom', args: ['--custom'] } } }; vol.writeFileSync(customPath, JSON.stringify(customConfig, null, 2)); // Verify custom config can be loaded expect(vol.existsSync(customPath)).toBe(true); const loaded = JSON.parse(vol.readFileSync(customPath, 'utf-8') as string); expect(loaded.mcpServers['custom-server']).toBeDefined(); }); }); describe('toolset integration', () => { it('should create app-specific default toolsets', async () => { // Create main config with toolsets const config = { version: '1.0.0', applications: { 'claude-desktop': { mcpConfig: 'mcp/claude-desktop.json' } }, toolsets: { 'claude-desktop-default': { name: 'Claude Desktop Default', description: 'Auto-imported from Claude Desktop', tools: [], metadata: { autoGenerated: true, sourceApp: 'claude-desktop' } } }, appDefaults: { 'claude-desktop': 'claude-desktop-default' } }; vol.writeFileSync( join(env.getConfig().configRoot, 'config.json'), JSON.stringify(config, null, 2) ); // Verify toolset configuration const loaded = JSON.parse( vol.readFileSync(join(env.getConfig().configRoot, 'config.json'), 'utf-8') as string ); expect(loaded.toolsets['claude-desktop-default']).toBeDefined(); expect(loaded.appDefaults['claude-desktop']).toBe('claude-desktop-default'); }); }); describe('migration scenarios', () => { it('should handle servers with metadata', async () => { // Create mcp directory for migration output const mcpDir = join(env.getConfig().configRoot, 'mcp'); vol.mkdirSync(mcpDir, { recursive: true }); // Simulate migration by creating per-app configs const claudeConfig = { mcpServers: { 'claude-server': { command: 'claude-mcp', args: [] } }, _metadata: { app: 'claude-desktop', importedAt: new Date().toISOString() } }; vol.writeFileSync( join(mcpDir, 'claude-desktop.json'), JSON.stringify(claudeConfig, null, 2) ); const cursorConfig = { mcpServers: { 'cursor-server': { command: 'cursor-mcp', args: [] } }, _metadata: { app: 'cursor', importedAt: new Date().toISOString() } }; vol.writeFileSync( join(mcpDir, 'cursor.json'), JSON.stringify(cursorConfig, null, 2) ); // Verify migration output expect(vol.existsSync(join(mcpDir, 'claude-desktop.json'))).toBe(true); expect(vol.existsSync(join(mcpDir, 'cursor.json'))).toBe(true); }); it('should handle servers without metadata', async () => { const mcpDir = join(env.getConfig().configRoot, 'mcp'); vol.mkdirSync(mcpDir, { recursive: true }); // Create default config for unknown servers const defaultConfig = { mcpServers: { 'unknown-server': { command: 'unknown', args: [] } }, _metadata: { app: 'default', importedAt: new Date().toISOString() } }; vol.writeFileSync( join(mcpDir, 'default.json'), JSON.stringify(defaultConfig, null, 2) ); // Verify default config const loaded = JSON.parse( vol.readFileSync(join(mcpDir, 'default.json'), 'utf-8') as string ); expect(loaded._metadata.app).toBe('default'); }); }); });

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/toolprint/hypertool-mcp'

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