Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| // Tests for the MCP tool catalog helpers. | |
| // | |
| // Focus: the pure pieces the route relies on - | |
| // 1. Deterministic Gradio MCP endpoint resolution from a Space slug | |
| // (the one field clients actually consume). | |
| // 2. Space -> catalog mapping (official/blocked flags, advisory mcp tag). | |
| // 3. Name dedup policy (official > oldest > most likes). | |
| // 4. The HF fetch path: tag filter, sort order, list/error handling | |
| // (network stubbed via an injected fetch). | |
| // | |
| // Run: `npm test` (uses node --test, no extra deps). | |
| import { test } from 'node:test'; | |
| import assert from 'node:assert/strict'; | |
| import { | |
| MCP_TOOL_TAG, | |
| buildSpaceMcpUrl, | |
| mapSpaceToMcpTool, | |
| dedupToolsByName, | |
| fetchMcpToolsFromHF, | |
| } from '../server/mcpTools.js'; | |
| // ── buildSpaceMcpUrl ──────────────────────────────────────────────── | |
| test('buildSpaceMcpUrl matches the HF default subdomain rule', () => { | |
| // Underscores and the slash collapse to single dashes, lowercased - | |
| // same value HF assigns (cf. pollen-robotics-reachy-mini-central). | |
| assert.equal( | |
| buildSpaceMcpUrl('pollen-robotics/reachy_mini_central'), | |
| 'https://pollen-robotics-reachy-mini-central.hf.space/gradio_api/mcp/', | |
| ); | |
| assert.equal( | |
| buildSpaceMcpUrl('Tfrere/My.Cool_Tool'), | |
| 'https://tfrere-my-cool-tool.hf.space/gradio_api/mcp/', | |
| ); | |
| }); | |
| test('buildSpaceMcpUrl is null-safe', () => { | |
| assert.equal(buildSpaceMcpUrl(''), 'https://.hf.space/gradio_api/mcp/'); | |
| assert.equal(buildSpaceMcpUrl(undefined), 'https://.hf.space/gradio_api/mcp/'); | |
| }); | |
| // ── mapSpaceToMcpTool ─────────────────────────────────────────────── | |
| test('mapSpaceToMcpTool builds the catalog shape with mcpUrl + flags', () => { | |
| const officialSet = new Set(['owner/official-tool']); | |
| const blockedSet = new Set(['owner/bad-tool']); | |
| const tool = mapSpaceToMcpTool( | |
| { | |
| id: 'owner/official-tool', | |
| tags: ['reachy-mini-tool', 'mcp'], | |
| likes: 12, | |
| cardData: { short_description: 'does things', sdk: 'gradio' }, | |
| siblings: [{ rfilename: 'public/icon.svg' }], | |
| }, | |
| { officialSet, blockedSet }, | |
| ); | |
| assert.equal(tool.id, 'owner/official-tool'); | |
| assert.equal(tool.name, 'official-tool'); | |
| assert.equal(tool.source_kind, 'hf_space'); | |
| assert.equal( | |
| tool.mcpUrl, | |
| 'https://owner-official-tool.hf.space/gradio_api/mcp/', | |
| ); | |
| assert.equal(tool.isOfficial, true); | |
| assert.equal(tool.isBlocked, false); | |
| assert.equal(tool.extra.hasMcpTag, true); | |
| assert.equal(tool.extra.likes, 12); | |
| assert.equal( | |
| tool.iconUrl, | |
| 'https://huggingface.co/spaces/owner/official-tool/resolve/main/public/icon.svg', | |
| ); | |
| }); | |
| test('mapSpaceToMcpTool flags blocked + missing mcp tag', () => { | |
| const tool = mapSpaceToMcpTool( | |
| { id: 'owner/bad-tool', tags: ['reachy-mini-tool'] }, | |
| { officialSet: new Set(), blockedSet: new Set(['owner/bad-tool']) }, | |
| ); | |
| assert.equal(tool.isBlocked, true); | |
| assert.equal(tool.extra.hasMcpTag, false); | |
| assert.equal(tool.iconUrl, null); | |
| }); | |
| // ── dedupToolsByName ──────────────────────────────────────────────── | |
| test('dedupToolsByName: official wins over a more-liked fork', () => { | |
| const deduped = dedupToolsByName([ | |
| { name: 'lamp', isOfficial: false, extra: { likes: 99, createdAt: '2024-01-01' } }, | |
| { name: 'lamp', isOfficial: true, extra: { likes: 1, createdAt: '2025-01-01' } }, | |
| ]); | |
| assert.equal(deduped.length, 1); | |
| assert.equal(deduped[0].isOfficial, true); | |
| }); | |
| test('dedupToolsByName: among non-official, oldest wins then likes', () => { | |
| const deduped = dedupToolsByName([ | |
| { name: 'lamp', isOfficial: false, extra: { likes: 1, createdAt: '2025-01-01' } }, | |
| { name: 'lamp', isOfficial: false, extra: { likes: 5, createdAt: '2024-01-01' } }, | |
| ]); | |
| assert.equal(deduped.length, 1); | |
| assert.equal(deduped[0].extra.createdAt, '2024-01-01'); | |
| }); | |
| // ── fetchMcpToolsFromHF (network stubbed) ─────────────────────────── | |
| function stubFetch(routes) { | |
| return async (url) => { | |
| for (const [needle, response] of routes) { | |
| if (url.includes(needle)) return response; | |
| } | |
| return { ok: false, status: 404, json: async () => ({}) }; | |
| }; | |
| } | |
| test('fetchMcpToolsFromHF filters on the tool tag and sorts official-first', async () => { | |
| const calls = []; | |
| const fetchImpl = async (url) => { | |
| calls.push(url); | |
| return stubFetch([ | |
| ['official-app-list', { ok: true, json: async () => ['owner/official'] }], | |
| ['blocked-app-list', { ok: true, json: async () => [] }], | |
| [ | |
| '/api/spaces', | |
| { | |
| ok: true, | |
| json: async () => [ | |
| { id: 'owner/popular', tags: ['reachy-mini-tool'], likes: 50 }, | |
| { id: 'owner/official', tags: ['reachy-mini-tool'], likes: 2 }, | |
| ], | |
| }, | |
| ], | |
| ])(url); | |
| }; | |
| const tools = await fetchMcpToolsFromHF({ | |
| hfSpacesApi: 'https://huggingface.co/api/spaces', | |
| limit: 1000, | |
| officialListUrl: 'https://example/config/official-app-list.json', | |
| blockListUrl: 'https://example/config/blocked-app-list.json', | |
| fetchImpl, | |
| }); | |
| // Tag filter is part of the spaces URL. | |
| assert.ok(calls.some((u) => u.includes(`filter=${encodeURIComponent(MCP_TOOL_TAG)}`))); | |
| // Official is sorted first even though it has fewer likes. | |
| assert.equal(tools[0].id, 'owner/official'); | |
| assert.equal(tools[0].isOfficial, true); | |
| assert.equal(tools[1].id, 'owner/popular'); | |
| }); | |
| test('fetchMcpToolsFromHF unions the tool-only official list with the app one', async () => { | |
| const fetchImpl = stubFetch([ | |
| ['official-app-list', { ok: true, json: async () => ['owner/from-app-list'] }], | |
| ['official-tool-list', { ok: true, json: async () => ['owner/from-tool-list'] }], | |
| ['blocked-app-list', { ok: true, json: async () => [] }], | |
| [ | |
| '/api/spaces', | |
| { | |
| ok: true, | |
| json: async () => [ | |
| { id: 'owner/from-app-list', tags: ['reachy-mini-tool'] }, | |
| { id: 'owner/from-tool-list', tags: ['reachy-mini-tool'] }, | |
| { id: 'owner/third-party', tags: ['reachy-mini-tool'] }, | |
| ], | |
| }, | |
| ], | |
| ]); | |
| const tools = await fetchMcpToolsFromHF({ | |
| hfSpacesApi: 'https://huggingface.co/api/spaces', | |
| limit: 1000, | |
| officialListUrl: 'https://example/config/official-app-list.json', | |
| officialToolListUrl: 'https://example/config/official-tool-list.json', | |
| blockListUrl: 'https://example/config/blocked-app-list.json', | |
| fetchImpl, | |
| }); | |
| const officialById = new Map(tools.map((t) => [t.id, t.isOfficial])); | |
| // Either list can vouch for a tool Space. | |
| assert.equal(officialById.get('owner/from-app-list'), true); | |
| assert.equal(officialById.get('owner/from-tool-list'), true); | |
| assert.equal(officialById.get('owner/third-party'), false); | |
| }); | |
| test('fetchMcpToolsFromHF tolerates a missing tool-only official list', async () => { | |
| // A deployment that predates `config/official-tool-list.json` gets a 404 | |
| // for it; the app list must still apply and the catalog must not throw. | |
| const fetchImpl = stubFetch([ | |
| ['official-app-list', { ok: true, json: async () => ['owner/official'] }], | |
| ['official-tool-list', { ok: false, status: 404 }], | |
| ['blocked-app-list', { ok: true, json: async () => [] }], | |
| [ | |
| '/api/spaces', | |
| { | |
| ok: true, | |
| json: async () => [ | |
| { id: 'owner/official', tags: ['reachy-mini-tool'] }, | |
| { id: 'owner/third-party', tags: ['reachy-mini-tool'] }, | |
| ], | |
| }, | |
| ], | |
| ]); | |
| const tools = await fetchMcpToolsFromHF({ | |
| hfSpacesApi: 'https://huggingface.co/api/spaces', | |
| limit: 1000, | |
| officialListUrl: 'https://example/config/official-app-list.json', | |
| officialToolListUrl: 'https://example/config/official-tool-list.json', | |
| blockListUrl: 'https://example/config/blocked-app-list.json', | |
| fetchImpl, | |
| }); | |
| assert.equal(tools.length, 2); | |
| assert.equal(tools[0].id, 'owner/official'); | |
| assert.equal(tools[0].isOfficial, true); | |
| }); | |
| test('fetchMcpToolsFromHF keeps the block-list shared with the app catalog', async () => { | |
| // The killswitch is global by design: a Space blocked for the app | |
| // catalog must come back flagged here too, so the route can hide it. | |
| const fetchImpl = stubFetch([ | |
| ['official-app-list', { ok: true, json: async () => [] }], | |
| ['official-tool-list', { ok: false, status: 404 }], | |
| ['blocked-app-list', { ok: true, json: async () => ['Owner/Bad-Tool'] }], | |
| [ | |
| '/api/spaces', | |
| { | |
| ok: true, | |
| json: async () => [{ id: 'owner/bad-tool', tags: ['reachy-mini-tool'] }], | |
| }, | |
| ], | |
| ]); | |
| const tools = await fetchMcpToolsFromHF({ | |
| hfSpacesApi: 'https://huggingface.co/api/spaces', | |
| limit: 1000, | |
| officialListUrl: 'https://example/config/official-app-list.json', | |
| officialToolListUrl: 'https://example/config/official-tool-list.json', | |
| blockListUrl: 'https://example/config/blocked-app-list.json', | |
| fetchImpl, | |
| }); | |
| // Matching is case-insensitive on both sides of the comparison. | |
| assert.equal(tools[0].isBlocked, true); | |
| }); | |
| test('fetchMcpToolsFromHF throws when the spaces listing fails', async () => { | |
| const fetchImpl = stubFetch([ | |
| ['official-app-list', { ok: true, json: async () => [] }], | |
| ['blocked-app-list', { ok: true, json: async () => [] }], | |
| ['/api/spaces', { ok: false, status: 503 }], | |
| ]); | |
| await assert.rejects( | |
| fetchMcpToolsFromHF({ | |
| hfSpacesApi: 'https://huggingface.co/api/spaces', | |
| limit: 10, | |
| officialListUrl: 'https://example/config/official-app-list.json', | |
| blockListUrl: 'https://example/config/blocked-app-list.json', | |
| fetchImpl, | |
| }), | |
| /HF API returned 503/, | |
| ); | |
| }); | |