reachy-mini-api / test /mcpTools.test.mjs
tfrere's picture
tfrere HF Staff
feat(api): fail-closed moderation for the MCP tool catalog
ba2090d
Raw
History Blame Contribute Delete
9.93 kB
// 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/,
);
});