feat: implement MCP registry client with multi-source search
Add registry client that queries Official, Glama, and Smithery MCP registries with caching, request deduplication, retry logic, and result ranking/dedup. Includes 53 tests covering all components. Also fix null priority values in cancelled tasks (19-21) that broke Task Master, and add new tasks 25-27 for registry completion and CLI discover/install commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
105
src/cli/tests/registry/dedup.test.ts
Normal file
105
src/cli/tests/registry/dedup.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { deduplicateResults } from '../../src/registry/dedup.js';
|
||||
import type { RegistryServer } from '../../src/registry/types.js';
|
||||
|
||||
function makeServer(overrides: Partial<RegistryServer> = {}): RegistryServer {
|
||||
return {
|
||||
name: 'test-server',
|
||||
description: 'A test server',
|
||||
packages: {},
|
||||
envTemplate: [],
|
||||
transport: 'stdio',
|
||||
popularityScore: 0,
|
||||
verified: false,
|
||||
sourceRegistry: 'official',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('deduplicateResults', () => {
|
||||
it('keeps unique servers', () => {
|
||||
const servers = [
|
||||
makeServer({ name: 'server-a', packages: { npm: 'pkg-a' } }),
|
||||
makeServer({ name: 'server-b', packages: { npm: 'pkg-b' } }),
|
||||
];
|
||||
expect(deduplicateResults(servers)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('deduplicates by npm package name, keeps higher popularity', () => {
|
||||
const servers = [
|
||||
makeServer({ name: 'low', packages: { npm: '@test/slack' }, popularityScore: 10, sourceRegistry: 'official' }),
|
||||
makeServer({ name: 'high', packages: { npm: '@test/slack' }, popularityScore: 100, sourceRegistry: 'smithery' }),
|
||||
];
|
||||
const result = deduplicateResults(servers);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.name).toBe('high');
|
||||
expect(result[0]?.popularityScore).toBe(100);
|
||||
});
|
||||
|
||||
it('deduplicates by GitHub URL with different formats', () => {
|
||||
const servers = [
|
||||
makeServer({ name: 'a', repositoryUrl: 'https://github.com/org/repo', popularityScore: 5 }),
|
||||
makeServer({ name: 'b', repositoryUrl: 'git@github.com:org/repo.git', popularityScore: 50 }),
|
||||
];
|
||||
const result = deduplicateResults(servers);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.name).toBe('b');
|
||||
});
|
||||
|
||||
it('merges envTemplate from both sources', () => {
|
||||
const servers = [
|
||||
makeServer({
|
||||
name: 'a',
|
||||
packages: { npm: 'pkg' },
|
||||
envTemplate: [{ name: 'TOKEN', description: 'API token', isSecret: true }],
|
||||
popularityScore: 10,
|
||||
}),
|
||||
makeServer({
|
||||
name: 'b',
|
||||
packages: { npm: 'pkg' },
|
||||
envTemplate: [{ name: 'URL', description: 'Base URL', isSecret: false }],
|
||||
popularityScore: 5,
|
||||
}),
|
||||
];
|
||||
const result = deduplicateResults(servers);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]?.envTemplate).toHaveLength(2);
|
||||
expect(result[0]?.envTemplate.map((e) => e.name)).toContain('TOKEN');
|
||||
expect(result[0]?.envTemplate.map((e) => e.name)).toContain('URL');
|
||||
});
|
||||
|
||||
it('deduplicates envTemplate by var name', () => {
|
||||
const servers = [
|
||||
makeServer({
|
||||
packages: { npm: 'pkg' },
|
||||
envTemplate: [{ name: 'TOKEN', description: 'from a', isSecret: true }],
|
||||
popularityScore: 10,
|
||||
}),
|
||||
makeServer({
|
||||
packages: { npm: 'pkg' },
|
||||
envTemplate: [{ name: 'TOKEN', description: 'from b', isSecret: true }],
|
||||
popularityScore: 5,
|
||||
}),
|
||||
];
|
||||
const result = deduplicateResults(servers);
|
||||
expect(result[0]?.envTemplate).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('merges verified status (OR)', () => {
|
||||
const servers = [
|
||||
makeServer({ packages: { npm: 'pkg' }, verified: true, popularityScore: 10 }),
|
||||
makeServer({ packages: { npm: 'pkg' }, verified: false, popularityScore: 5 }),
|
||||
];
|
||||
const result = deduplicateResults(servers);
|
||||
expect(result[0]?.verified).toBe(true);
|
||||
});
|
||||
|
||||
it('handles servers with no npm or repo', () => {
|
||||
const servers = [
|
||||
makeServer({ name: 'a' }),
|
||||
makeServer({ name: 'b' }),
|
||||
];
|
||||
// No matching key → no dedup
|
||||
expect(deduplicateResults(servers)).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user