mirror of
https://github.com/hansjone/dsh-search-mcp.git
synced 2026-10-11 12:53:19 +08:00
Publish dsh-search-mcp fork for newer DSH and Bailian WebSearch MCP.
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>
This commit is contained in:
commit
d5c4d2006c
16 changed files with 4821 additions and 0 deletions
112
lib/index.js
Normal file
112
lib/index.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* dsh-search-mcp — replace dsh's built-in web search with search MCP servers.
|
||||
*
|
||||
* Adapted for DeepSeek Harness 0.1.2+: settings use
|
||||
* `ctx.settings.installSection` (the old free-function
|
||||
* `installSettingsSection` from 0.1.1-rc.2 no longer exists).
|
||||
*
|
||||
* A Cordis plugin that
|
||||
* - registers a `ctx.web` search provider under the id `search-mcp`, and
|
||||
* - installs a Settings section (`search-mcp`) where the user manages the
|
||||
* search MCP server list (kind, endpoint/command, API key or key env
|
||||
* reference, tool name) plus `defaultServer` / `maxResults` /
|
||||
* `searchTimeoutMs` from the web Settings → Plugins page.
|
||||
*
|
||||
* The package's `cordis.patch.yml` (bundle layer) switches
|
||||
* `web.searchProvider` to `search-mcp` and disables the built-in
|
||||
* `web-search-deepseek` provider, so while this plugin is enabled the
|
||||
* built-in search is unavailable and every `web_search` call runs through
|
||||
* the configured MCP server(s). Removing the package restores the built-in.
|
||||
*/
|
||||
import z from '@deepseek-ai/schemastery';
|
||||
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
||||
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment';
|
||||
import { SearchMCPProvider } from './provider.js';
|
||||
|
||||
/** Cordis plugin name used by loader diagnostics. */
|
||||
export const name = 'search-mcp';
|
||||
|
||||
/** The web seam this provider registers into. */
|
||||
export const inject = ['web'];
|
||||
|
||||
const serverSchema = z.object({
|
||||
id: z.string(),
|
||||
kind: z.string().default('custom'),
|
||||
transport: z.string().default('http'),
|
||||
url: z.string().default(''),
|
||||
command: z.string().default(''),
|
||||
args: z.array(z.string()).default([]),
|
||||
apiKey: z.string().role('secret'),
|
||||
apiKeyEnv: z.string().role('credential-ref').default(''),
|
||||
authStyle: z.string().default(''),
|
||||
authParam: z.string().default(''),
|
||||
authPrefix: z.string().default(''),
|
||||
toolName: z.string().default(''),
|
||||
// Note: this schemastery fork has no `.optional()`; object fields are
|
||||
// optional unless `.required()` is applied, so absence is already allowed.
|
||||
maxResults: z.number().step(1).min(1).max(50),
|
||||
});
|
||||
|
||||
export const Config = z.object({
|
||||
defaultServer: z.string().default(''),
|
||||
maxResults: z.number().step(1).min(1).max(50).default(8),
|
||||
searchTimeoutMs: z.number().step(1).min(1000).default(30000),
|
||||
servers: z.array(serverSchema).default([]),
|
||||
});
|
||||
|
||||
/** Settings namespace owning this plugin's section (Settings → Plugins card). */
|
||||
export const SEARCH_MCP_SETTINGS_NAMESPACE = 'search-mcp';
|
||||
|
||||
/** Register the search provider and the live settings section. */
|
||||
export function apply(ctx, config) {
|
||||
let current = () => config;
|
||||
// Optional settings seam: fall back to the composition entry when settings
|
||||
// is absent (same pattern as @deepseek-ai/dsh-web-search-deepseek).
|
||||
ctx.inject(['settings'], (settingsCtx) => {
|
||||
settingsCtx.settings.installSection(ctx, SEARCH_MCP_SETTINGS_NAMESPACE, Config, config, {
|
||||
setSource: (source) => {
|
||||
current = source;
|
||||
},
|
||||
// Provider projects the section per search; no re-registration needed.
|
||||
onChange: () => {},
|
||||
});
|
||||
});
|
||||
// `registerSearchProvider` owns its cleanup via ctx.effect (HMR/dispose safe).
|
||||
ctx.web.registerSearchProvider(new SearchMCPProvider(() => resolveOptions(ctx, current())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Project the authoritative config into per-search options. The section
|
||||
* returned by `setSource` (settings.yaml `search-mcp:` block) replaces the
|
||||
* row config entirely, matching how every other settings section behaves.
|
||||
*
|
||||
* @param ctx - plugin context supplying the credential and environment planes.
|
||||
* @param config - the currently authoritative section.
|
||||
* @returns options for one search.
|
||||
*/
|
||||
function resolveOptions(ctx, config) {
|
||||
return {
|
||||
servers: config.servers ?? [],
|
||||
defaultServer: config.defaultServer ?? '',
|
||||
maxResults: config.maxResults ?? 8,
|
||||
searchTimeoutMs: config.searchTimeoutMs ?? 30000,
|
||||
resolveKey: async (server) => {
|
||||
if (server.apiKey !== undefined && server.apiKey.length > 0) return server.apiKey;
|
||||
const envName = server.apiKeyEnv ?? '';
|
||||
if (envName.length === 0) return undefined;
|
||||
const credentials = ctx.get('credentials');
|
||||
if (credentials !== undefined) {
|
||||
try {
|
||||
const resolved = await credentials.resolve(credentialRef(envName));
|
||||
if (resolved !== undefined && resolved.value !== undefined && resolved.value.length > 0) {
|
||||
return resolved.value;
|
||||
}
|
||||
} catch {
|
||||
/* fall through to the launch environment */
|
||||
}
|
||||
}
|
||||
const ambient = launchEnvironmentOf(ctx).get(envName);
|
||||
return ambient !== undefined && ambient.value.length > 0 ? ambient.value : undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue