mirror of
https://github.com/hansjone/dsh-search-mcp.git
synced 2026-10-08 22:20:47 +08:00
Based on gxpppp/dsh-search-mcp; includes DSH web settings fixes, Clash fake-IP URL policy, and Bailian default server patch. Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
4.7 KiB
JavaScript
130 lines
4.7 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { clampSearchResults, SEARCH_MCP_CATALOG, resolveServer } from '../lib/catalog.js';
|
|
import { extractSearchResult } from '../lib/extract.js';
|
|
|
|
test('catalog exposes every supported provider preset', () => {
|
|
assert.deepEqual(Object.keys(SEARCH_MCP_CATALOG), [
|
|
'tavily',
|
|
'brave',
|
|
'exa',
|
|
'perplexity',
|
|
'duckduckgo',
|
|
'custom',
|
|
]);
|
|
});
|
|
|
|
test('known providers ignore stored connection overrides', () => {
|
|
const tavily = resolveServer({
|
|
id: 'primary',
|
|
kind: 'tavily',
|
|
transport: 'stdio',
|
|
url: 'https://example.test/mcp',
|
|
authStyle: 'header',
|
|
authParam: 'X-Other-Key',
|
|
toolName: 'other_search',
|
|
apiKeyEnv: 'MY_TAVILY_KEY',
|
|
maxResults: 12,
|
|
});
|
|
assert.equal(tavily.transport, 'http');
|
|
assert.equal(tavily.url, 'https://mcp.tavily.com/mcp/');
|
|
assert.equal(tavily.authStyle, 'query');
|
|
assert.equal(tavily.authParam, 'tavilyApiKey');
|
|
assert.equal(tavily.toolName, 'tavily_search');
|
|
assert.equal(tavily.countArg, 'max_results');
|
|
assert.equal(tavily.apiKeyEnv, 'MY_TAVILY_KEY');
|
|
assert.equal(tavily.maxResults, 12);
|
|
});
|
|
|
|
test('prototype property kinds fall back to custom', () => {
|
|
for (const kind of ['constructor', 'toString', '__proto__']) {
|
|
const resolved = resolveServer({ id: kind, kind, url: 'https://search.example/mcp' });
|
|
assert.equal(resolved.kind, 'custom');
|
|
assert.equal(resolved.url, 'https://search.example/mcp');
|
|
}
|
|
});
|
|
|
|
test('custom providers preserve advanced connection fields', () => {
|
|
const custom = resolveServer({
|
|
id: 'custom',
|
|
kind: 'custom',
|
|
transport: 'stdio',
|
|
command: 'custom-mcp',
|
|
args: ['--stdio'],
|
|
authStyle: 'header',
|
|
authParam: 'X-Key',
|
|
authPrefix: 'Token ',
|
|
toolName: 'search',
|
|
});
|
|
assert.equal(custom.transport, 'stdio');
|
|
assert.equal(custom.command, 'custom-mcp');
|
|
assert.deepEqual(custom.args, ['--stdio']);
|
|
assert.equal(custom.authParam, 'X-Key');
|
|
assert.equal(custom.authPrefix, 'Token ');
|
|
assert.equal(custom.toolName, 'search');
|
|
});
|
|
|
|
test('provider contracts match current upstream transports', () => {
|
|
const brave = resolveServer({ id: 'brave', kind: 'brave' });
|
|
assert.equal(brave.transport, 'stdio');
|
|
assert.equal(brave.command, 'npx');
|
|
assert.deepEqual(brave.args, ['-y', '@brave/brave-search-mcp-server@2.1.3']);
|
|
assert.equal(brave.authParam, 'BRAVE_API_KEY');
|
|
|
|
const perplexity = resolveServer({ id: 'perplexity', kind: 'perplexity' });
|
|
assert.equal(perplexity.url, 'https://api.perplexity.ai/mcp');
|
|
assert.equal(perplexity.authParam, 'Authorization');
|
|
assert.equal(perplexity.authPrefix, 'Bearer ');
|
|
assert.equal(perplexity.toolName, 'perplexity_search');
|
|
|
|
const duckduckgo = resolveServer({ id: 'duckduckgo', kind: 'duckduckgo' });
|
|
assert.equal(duckduckgo.needsKey, false);
|
|
assert.equal(duckduckgo.toolName, 'duckduckgo_web_search');
|
|
assert.equal(duckduckgo.countArg, 'count');
|
|
});
|
|
|
|
test('provider result counts stay within upstream MCP schemas', () => {
|
|
assert.equal(clampSearchResults(resolveServer({ id: 't', kind: 'tavily' }), 1), 5);
|
|
assert.equal(clampSearchResults(resolveServer({ id: 't', kind: 'tavily' }), 50), 20);
|
|
assert.equal(clampSearchResults(resolveServer({ id: 'b', kind: 'brave' }), 50), 20);
|
|
assert.equal(clampSearchResults(resolveServer({ id: 'p', kind: 'perplexity' }), 0), 1);
|
|
assert.equal(clampSearchResults(resolveServer({ id: 'd', kind: 'duckduckgo' }), 50), 20);
|
|
assert.equal(clampSearchResults(resolveServer({ id: 'e', kind: 'exa' }), 50), 50);
|
|
});
|
|
test('extractSearchResult normalizes, deduplicates, and rejects invalid URLs', () => {
|
|
const result = extractSearchResult({
|
|
structuredContent: {
|
|
answer: 'Summary',
|
|
results: [
|
|
{ url: 'https://example.com/a', title: 'A', content: 'alpha', published_date: '2026-08-18' },
|
|
{ url: 'https://example.com/a', title: 'Duplicate' },
|
|
{ url: 'ftp://example.com/ignored', title: 'Ignored' },
|
|
],
|
|
nested: { url: 'http://example.com/b', description: 'beta' },
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(result, {
|
|
sources: [
|
|
{
|
|
url: 'https://example.com/a',
|
|
title: 'A',
|
|
snippet: 'alpha',
|
|
publishedAt: '2026-08-18',
|
|
},
|
|
{ url: 'http://example.com/b', snippet: 'beta' },
|
|
],
|
|
truncated: false,
|
|
content: 'Summary',
|
|
});
|
|
});
|
|
|
|
test('extractSearchResult accepts JSON and plain text MCP blocks', () => {
|
|
const json = extractSearchResult({
|
|
content: [{ type: 'text', text: '{"results":[{"url":"https://example.com"}]}' }],
|
|
});
|
|
assert.equal(json.sources.length, 1);
|
|
|
|
const text = extractSearchResult({ content: [{ type: 'text', text: 'Direct answer' }] });
|
|
assert.deepEqual(text, { sources: [], truncated: false, content: 'Direct answer' });
|
|
});
|