Skip to main content
Glama
evalstate

Hugging Face MCP Server

by evalstate
tool-selection-strategy.test.ts26 kB
import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { ToolSelectionStrategy, ToolSelectionMode, type ToolSelectionContext } from '../../../src/server/utils/tool-selection-strategy.js'; import { McpApiClient, type ApiClientConfig } from '../../../src/server/utils/mcp-api-client.js'; import type { AppSettings } from '../../../src/shared/settings.js'; import type { TransportInfo } from '../../../src/shared/transport-info.js'; import { ALL_BUILTIN_TOOL_IDS, TOOL_ID_GROUPS } from '@llmindset/hf-mcp'; import { extractAuthBouquetAndMix } from '../../../src/server/utils/auth-utils.js'; import { normalizeBuiltInTools } from '../../../src/shared/tool-normalizer.js'; import { BOUQUETS } from '../../../src/shared/bouquet-presets.js'; describe('extractBouquetAndMix', () => { it('should extract bouquet from headers', () => { const headers = { 'x-mcp-bouquet': 'search' }; const { bouquet, mix } = extractAuthBouquetAndMix(headers); expect(bouquet).toBe('search'); expect(mix).toBeUndefined(); }); it('should extract mix from headers', () => { const headers = { 'x-mcp-mix': 'hf_api' }; const result = extractAuthBouquetAndMix(headers); expect(result.bouquet).toBeUndefined(); expect(result.mix).toEqual(['hf_api']); }); it('should extract both bouquet and mix from headers', () => { const headers = { 'x-mcp-bouquet': 'search', 'x-mcp-mix': 'hf_api', }; const result = extractAuthBouquetAndMix(headers); expect(result.bouquet).toBe('search'); expect(result.mix).toEqual(['hf_api']); }); it('should handle null headers', () => { const result = extractAuthBouquetAndMix(null); expect(result.bouquet).toBeUndefined(); expect(result.mix).toBeUndefined(); }); it('should handle empty headers', () => { const result = extractAuthBouquetAndMix({}); expect(result.bouquet).toBeUndefined(); expect(result.mix).toBeUndefined(); }); it('should parse comma-separated mix list', () => { const headers = { 'x-mcp-mix': 'hf_api, jobs ,hub_repo_details_readme' }; const result = extractAuthBouquetAndMix(headers); expect(result.mix).toEqual(['hf_api', 'jobs', 'hub_repo_details_readme']); }); }); describe('BOUQUETS configuration', () => { it('should have correct hf_api bouquet', () => { const bouquet = BOUQUETS.hf_api; expect(bouquet).toBeDefined(); if (bouquet) { expect(bouquet.builtInTools).toEqual(TOOL_ID_GROUPS.hf_api); expect(bouquet.spaceTools).toEqual([]); } }); it('should have correct spaces bouquet', () => { const bouquet = BOUQUETS.spaces; expect(bouquet).toBeDefined(); if (bouquet) { expect(bouquet.builtInTools).toEqual(TOOL_ID_GROUPS.spaces); expect(bouquet.spaceTools).toEqual([]); } }); it('should have correct search bouquet', () => { const bouquet = BOUQUETS.search; expect(bouquet).toBeDefined(); if (bouquet) { expect(bouquet.builtInTools).toEqual(TOOL_ID_GROUPS.search); expect(bouquet.spaceTools).toEqual([]); } }); it('should have correct all bouquet', () => { const bouquet = BOUQUETS.all; expect(bouquet).toBeDefined(); if (bouquet) { expect(bouquet.builtInTools).toEqual(ALL_BUILTIN_TOOL_IDS); expect(bouquet.spaceTools).toEqual([]); } }); }); describe('ToolSelectionStrategy', () => { let strategy: ToolSelectionStrategy; let mockApiClient: McpApiClient; // Create a real API client with minimal config for testing beforeEach(() => { const config: ApiClientConfig = { type: 'polling', baseUrl: 'http://localhost:3000', pollInterval: 5000, staticGradioEndpoints: [], }; const transportInfo: TransportInfo = { transport: 'streamableHttpJson', port: 3000, defaultHfTokenSet: false, jsonResponseEnabled: true, externalApiMode: false, stdioClient: null, }; mockApiClient = new McpApiClient(config, transportInfo); strategy = new ToolSelectionStrategy(mockApiClient); }); describe('Bouquet Override (Highest Precedence)', () => { it('should use bouquet override for search bouquet', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'search' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.enabledToolIds).toEqual(TOOL_ID_GROUPS.search); expect(result.reason).toBe('Bouquet override: search'); expect(result.baseSettings).toBeUndefined(); expect(result.mixedBouquet).toBeUndefined(); }); it('should use bouquet override for hf_api bouquet', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'hf_api' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.enabledToolIds).toEqual(TOOL_ID_GROUPS.hf_api); expect(result.reason).toBe('Bouquet override: hf_api'); }); it('should use bouquet override for spaces bouquet', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'spaces' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.enabledToolIds).toEqual(TOOL_ID_GROUPS.spaces); expect(result.reason).toBe('Bouquet override: spaces'); }); it('should use bouquet override for all bouquet', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'all' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.enabledToolIds).toEqual(normalizeBuiltInTools(ALL_BUILTIN_TOOL_IDS)); expect(result.reason).toBe('Bouquet override: all'); }); it('should ignore invalid bouquet names', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'invalid_bouquet' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); // Should fall through to fallback since no valid bouquet or user settings expect(result.mode).toBe(ToolSelectionMode.FALLBACK); expect(result.enabledToolIds).toEqual(normalizeBuiltInTools(ALL_BUILTIN_TOOL_IDS)); }); it('should prefer bouquet over mix when both are present', async () => { const userSettings: AppSettings = { builtInTools: ['hf_semantic_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'search', 'x-mcp-mix': 'hf_api', }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.enabledToolIds).toEqual(TOOL_ID_GROUPS.search); expect(result.reason).toBe('Bouquet override: search'); }); }); describe('Mix Mode (Second Precedence)', () => { it('should mix hf_api tools with user settings', async () => { const userSettings: AppSettings = { builtInTools: ['hf_semantic_search', 'hf_dataset_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'hf_api' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.reason).toBe('User settings + mix(hf_api)'); expect(result.baseSettings).toEqual(userSettings); expect(result.mixedBouquet).toEqual(['hf_api']); // Should contain user tools + hf_api tools (deduplicated) const expectedTools = [...new Set([...userSettings.builtInTools, ...TOOL_ID_GROUPS.hf_api])]; expect(result.enabledToolIds).toEqual(expectedTools); }); it('should mix search tools with user settings', async () => { const userSettings: AppSettings = { builtInTools: ['hf_whoami', 'hf_duplicate_space'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'search' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.reason).toBe('User settings + mix(search)'); const expectedTools = [...new Set([...userSettings.builtInTools, ...TOOL_ID_GROUPS.search])]; expect(result.enabledToolIds).toEqual(expectedTools); }); it('should deduplicate tools when mixing', async () => { const userSettings: AppSettings = { builtInTools: ['hf_semantic_search', 'hf_model_search'], // Already has some search tools spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'search' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); // Should not have duplicates const uniqueTools = [...new Set(result.enabledToolIds)]; expect(result.enabledToolIds).toEqual(uniqueTools); expect(result.enabledToolIds.length).toBe(uniqueTools.length); }); it('should mix multiple bouquets when comma separated', async () => { const userSettings: AppSettings = { builtInTools: ['hf_whoami'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'hf_api,search' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.reason).toBe('User settings + mix(hf_api,search)'); expect(result.mixedBouquet).toEqual(['hf_api', 'search']); const expectedTools = normalizeBuiltInTools([ ...new Set([...userSettings.builtInTools, ...TOOL_ID_GROUPS.hf_api, ...TOOL_ID_GROUPS.search]), ]); expect(result.enabledToolIds).toEqual(expectedTools); }); it('should ignore mix when no user settings available', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'hf_api' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); // Should fall through to fallback since no user settings to mix with expect(result.mode).toBe(ToolSelectionMode.FALLBACK); expect(result.enabledToolIds).toEqual(normalizeBuiltInTools(ALL_BUILTIN_TOOL_IDS)); }); it('should ignore invalid mix bouquet names', async () => { const userSettings: AppSettings = { builtInTools: ['hf_semantic_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'invalid_mix' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); // Should use user settings without mixing expect(result.mode).toBe(ToolSelectionMode.INTERNAL_API); expect(result.enabledToolIds).toEqual(userSettings.builtInTools); }); }); describe('User Settings Mode (Third Precedence)', () => { it('should use provided user settings in internal API mode', async () => { const userSettings: AppSettings = { builtInTools: ['hf_semantic_search', 'hf_model_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.INTERNAL_API); expect(result.enabledToolIds).toEqual(userSettings.builtInTools); expect(result.reason).toBe('Internal API user settings'); expect(result.baseSettings).toEqual(userSettings); }); it('should use provided user settings in external API mode', async () => { // Create external API mode client const externalConfig: ApiClientConfig = { type: 'external', externalUrl: 'https://api.example.com/settings', hfToken: 'test-token', }; const externalTransportInfo: TransportInfo = { transport: 'streamableHttpJson', port: 3000, defaultHfTokenSet: false, jsonResponseEnabled: true, externalApiMode: true, stdioClient: null, }; const externalApiClient = new McpApiClient(externalConfig, externalTransportInfo); const externalStrategy = new ToolSelectionStrategy(externalApiClient); const userSettings: AppSettings = { builtInTools: ['hf_paper_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await externalStrategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.EXTERNAL_API); expect(result.enabledToolIds).toEqual(userSettings.builtInTools); expect(result.reason).toBe('External API user settings'); }); }); describe('Fallback Mode (Lowest Precedence)', () => { it('should use fallback when no configuration is available', async () => { const context: ToolSelectionContext = { headers: {}, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.FALLBACK); expect(result.enabledToolIds).toEqual(normalizeBuiltInTools(ALL_BUILTIN_TOOL_IDS)); expect(result.reason).toBe('Fallback - no settings available'); expect(result.baseSettings).toBeUndefined(); }); it('should use fallback when headers are null', async () => { const context: ToolSelectionContext = { headers: null, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.FALLBACK); expect(result.enabledToolIds).toEqual(normalizeBuiltInTools(ALL_BUILTIN_TOOL_IDS)); }); }); describe('Complex Scenarios', () => { it('should handle empty user settings', async () => { const userSettings: AppSettings = { builtInTools: [], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.INTERNAL_API); expect(result.enabledToolIds).toEqual([]); expect(result.baseSettings).toEqual(userSettings); }); it('should handle mix with empty user settings', async () => { const userSettings: AppSettings = { builtInTools: [], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'search' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.enabledToolIds).toEqual(TOOL_ID_GROUPS.search); expect(result.mixedBouquet).toEqual(['search']); }); it('should handle all possible tool types in mix', async () => { const userSettings: AppSettings = { builtInTools: ['hf_whoami'], // Start with one tool spaceTools: [], }; // Test mixing with each bouquet type for (const [bouquetName, bouquetConfig] of Object.entries(BOUQUETS)) { const context: ToolSelectionContext = { headers: { 'x-mcp-mix': bouquetName }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.mixedBouquet).toEqual([bouquetName]); const expectedTools = [...new Set([...userSettings.builtInTools, ...bouquetConfig.builtInTools])]; expect(result.enabledToolIds).toEqual(normalizeBuiltInTools(expectedTools)); } }); it('should preserve gradio endpoints when mixing with all bouquet in internal API mode', async () => { const userSettings: AppSettings = { builtInTools: ['hf_whoami'], // Most tools disabled via frontend spaceTools: [ { name: 'My Custom GPT', subdomain: 'user123-my-custom-gpt', _id: 'custom-1', emoji: '🤖', }, { name: 'Company Analytics', subdomain: 'corp-analytics-tool', _id: 'custom-2', emoji: '📊', }, ], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'all' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.mixedBouquet).toEqual(['all']); expect(result.reason).toBe('User settings + mix(all)'); // Should get user's minimal tools + ALL built-in tools (deduplicated) const expectedBuiltInTools = [...new Set([...userSettings.builtInTools, ...ALL_BUILTIN_TOOL_IDS])]; expect(result.enabledToolIds).toEqual(normalizeBuiltInTools(expectedBuiltInTools)); // Should preserve base settings including gradio endpoints expect(result.baseSettings).toEqual(userSettings); expect(result.baseSettings?.spaceTools).toHaveLength(2); expect(result.baseSettings?.spaceTools).toEqual([ { name: 'My Custom GPT', subdomain: 'user123-my-custom-gpt', _id: 'custom-1', emoji: '🤖', }, { name: 'Company Analytics', subdomain: 'corp-analytics-tool', _id: 'custom-2', emoji: '📊', }, ]); }); }); describe('SEARCH_ENABLES_FETCH feature', () => { const originalEnv = process.env.SEARCH_ENABLES_FETCH; afterEach(() => { // Restore original env value if (originalEnv === undefined) { delete process.env.SEARCH_ENABLES_FETCH; } else { process.env.SEARCH_ENABLES_FETCH = originalEnv; } }); it('should not auto-enable hf_doc_fetch when SEARCH_ENABLES_FETCH is not set', async () => { delete process.env.SEARCH_ENABLES_FETCH; const userSettings: AppSettings = { builtInTools: ['hf_doc_search', 'hf_model_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.enabledToolIds).toEqual(['hf_doc_search', 'hf_model_search']); expect(result.enabledToolIds).not.toContain('hf_doc_fetch'); }); it('should not auto-enable hf_doc_fetch when SEARCH_ENABLES_FETCH is false', async () => { process.env.SEARCH_ENABLES_FETCH = 'false'; const userSettings: AppSettings = { builtInTools: ['hf_doc_search', 'hf_model_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.enabledToolIds).toEqual(['hf_doc_search', 'hf_model_search']); expect(result.enabledToolIds).not.toContain('hf_doc_fetch'); }); it('should auto-enable hf_doc_fetch when SEARCH_ENABLES_FETCH=true and hf_doc_search is enabled', async () => { process.env.SEARCH_ENABLES_FETCH = 'true'; const userSettings: AppSettings = { builtInTools: ['hf_doc_search', 'hf_model_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.enabledToolIds).toContain('hf_doc_search'); expect(result.enabledToolIds).toContain('hf_doc_fetch'); expect(result.enabledToolIds).toContain('hf_model_search'); expect(result.enabledToolIds).toHaveLength(3); }); it('should not add hf_doc_fetch when hf_doc_search is not enabled', async () => { process.env.SEARCH_ENABLES_FETCH = 'true'; const userSettings: AppSettings = { builtInTools: ['hf_model_search', 'hf_dataset_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.enabledToolIds).not.toContain('hf_doc_search'); expect(result.enabledToolIds).not.toContain('hf_doc_fetch'); expect(result.enabledToolIds).toEqual(['hf_model_search', 'hf_dataset_search']); }); it('should not duplicate hf_doc_fetch if already enabled', async () => { process.env.SEARCH_ENABLES_FETCH = 'true'; const userSettings: AppSettings = { builtInTools: ['hf_doc_search', 'hf_doc_fetch', 'hf_model_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: {}, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.enabledToolIds).toEqual(['hf_doc_search', 'hf_doc_fetch', 'hf_model_search']); expect(result.enabledToolIds.filter((id) => id === 'hf_doc_fetch')).toHaveLength(1); }); it('should work with bouquet override', async () => { process.env.SEARCH_ENABLES_FETCH = 'true'; const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'search' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.enabledToolIds).toContain('hf_doc_search'); expect(result.enabledToolIds).toContain('hf_doc_fetch'); }); it('should work with mix mode', async () => { process.env.SEARCH_ENABLES_FETCH = 'true'; const userSettings: AppSettings = { builtInTools: ['hf_model_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'search' }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.enabledToolIds).toContain('hf_doc_search'); expect(result.enabledToolIds).toContain('hf_doc_fetch'); expect(result.enabledToolIds).toContain('hf_model_search'); }); it('should work with fallback mode when all tools are enabled', async () => { process.env.SEARCH_ENABLES_FETCH = 'true'; const context: ToolSelectionContext = { headers: {}, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.FALLBACK); // In fallback mode, all tools are enabled, so both should already be there expect(result.enabledToolIds).toContain('hf_doc_search'); expect(result.enabledToolIds).toContain('hf_doc_fetch'); }); }); describe('Gradio endpoint handling', () => { it('should include gradio endpoints in bouquet override mode', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'search', 'x-mcp-gradio': 'microsoft/Florence-2-large,meta-llama/Llama-2-7b-chat-hf', }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.enabledToolIds).toEqual(TOOL_ID_GROUPS.search); expect(result.reason).toBe('Bouquet override: search + 2 gradio endpoints'); expect(result.gradioSpaceTools).toBeDefined(); expect(result.gradioSpaceTools).toHaveLength(2); expect(result.gradioSpaceTools?.[0].name).toBe('microsoft/Florence-2-large'); expect(result.gradioSpaceTools?.[1].name).toBe('meta-llama/Llama-2-7b-chat-hf'); }); it('should include gradio endpoints in mix mode', async () => { const userSettings: AppSettings = { builtInTools: ['hf_whoami'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-mix': 'hf_api', 'x-mcp-gradio': 'foo/bar', }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.MIX); expect(result.reason).toBe('User settings + mix(hf_api) + 1 gradio endpoints'); expect(result.gradioSpaceTools).toBeDefined(); expect(result.gradioSpaceTools).toHaveLength(1); expect(result.gradioSpaceTools?.[0].name).toBe('foo/bar'); }); it('should include gradio endpoints in user settings mode', async () => { const userSettings: AppSettings = { builtInTools: ['hf_semantic_search'], spaceTools: [], }; const context: ToolSelectionContext = { headers: { 'x-mcp-gradio': 'test/space', }, userSettings, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.INTERNAL_API); expect(result.reason).toBe('Internal API user settings + 1 gradio endpoints'); expect(result.gradioSpaceTools).toBeDefined(); expect(result.gradioSpaceTools).toHaveLength(1); }); it('should include gradio endpoints in fallback mode', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-gradio': 'fallback/test', }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.FALLBACK); expect(result.reason).toBe('Fallback - no settings available + 1 gradio endpoints'); expect(result.gradioSpaceTools).toBeDefined(); expect(result.gradioSpaceTools).toHaveLength(1); }); it('should not include gradio endpoints when not specified', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'search' }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.mode).toBe(ToolSelectionMode.BOUQUET_OVERRIDE); expect(result.reason).toBe('Bouquet override: search'); expect(result.gradioSpaceTools).toBeUndefined(); }); it('should handle multiple gradio endpoints with various formats', async () => { const context: ToolSelectionContext = { headers: { 'x-mcp-bouquet': 'hf_api', 'x-mcp-gradio': 'user/space-one,org/space-two', }, hfToken: 'test-token', }; const result = await strategy.selectTools(context); expect(result.gradioSpaceTools).toBeDefined(); expect(result.gradioSpaceTools).toHaveLength(2); expect(result.gradioSpaceTools?.map(s => s.name)).toEqual([ 'user/space-one', 'org/space-two', ]); }); }); });

Latest Blog Posts

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/evalstate/hf-mcp-server'

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