Restore Desktop Search MCP settings form with Bailian defaults.

Keep boot safe on DSH 0.2 while registering a distinct settings section,
lazy remote.credentials wiring, and credential badge state that survives
describe redaction after Save.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-10 04:24:13 +08:00
parent 8fdb55b025
commit d729b03f70
7 changed files with 733 additions and 412 deletions

View file

@ -6,6 +6,17 @@
* `custom` is the only kind whose connection details come from the user.
*/
export const SEARCH_MCP_CATALOG = {
bailian: {
transport: 'http',
url: 'https://dashscope.aliyuncs.com/api/v1/mcps/WebSearch/mcp',
authStyle: 'header',
authParam: 'Authorization',
authPrefix: 'Bearer ',
toolName: 'bailian_web_search',
countArg: '',
apiKeyEnv: 'DASHSCOPE_API_KEY',
needsKey: true,
},
tavily: {
transport: 'http',
url: 'https://mcp.tavily.com/mcp/',

File diff suppressed because it is too large Load diff

View file

@ -56,11 +56,17 @@ const serverSchema = z.object({
maxResults: vol(z.number().step(1).min(1).max(50)),
});
const DEFAULT_BAILIAN_SERVER = {
id: 'bailian',
kind: 'bailian',
apiKeyEnv: 'DASHSCOPE_API_KEY',
};
export const Config = z.object({
defaultServer: vol(z.string().default('')),
defaultServer: vol(z.string().default('bailian')),
maxResults: vol(z.number().step(1).min(1).max(50).default(8)),
searchTimeoutMs: vol(z.number().step(1).min(1000).default(30000)),
servers: vol(z.array(serverSchema).default([])),
servers: vol(z.array(serverSchema).default([DEFAULT_BAILIAN_SERVER])),
});
/** Settings namespace owning this plugin's section (Settings → Plugins card). */
@ -79,70 +85,11 @@ function describeRows(describe) {
return [];
}
/** Register the search provider and the live settings section. */
/** Register the search provider. Settings UI is owned by the web client. */
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) => {
const settings = settingsCtx.settings;
const hooks = {
setSource: (source) => {
current = source;
},
onChange: () => {},
};
if (typeof settings?.installSection === 'function') {
try {
settings.installSection(ctx, SEARCH_MCP_SETTINGS_NAMESPACE, Config, config, hooks);
return;
} catch (error) {
settingsCtx.logger?.warn?.(
'dsh-search-mcp: installSection failed (will try register/describe): %s',
error instanceof Error ? error.message : error,
);
}
}
// ≤0.1.5 / some Desktop builds: explicit namespace registration.
// ≥0.1.7: Config is projected from the Loader entry — register may be
// absent or refuse; follow describe().
if (typeof settings?.register === 'function') {
try {
settings.register(SEARCH_MCP_SETTINGS_NAMESPACE, Config, { base: config, applies: 'live' });
settingsCtx.logger?.info?.(
'dsh-search-mcp: settings namespace "%s" registered',
SEARCH_MCP_SETTINGS_NAMESPACE,
);
} catch (error) {
settingsCtx.logger?.warn?.(
'dsh-search-mcp: settings.register failed (will follow describe): %s',
error instanceof Error ? error.message : error,
);
}
}
// DSH ≥0.1.7 / 0.2.0 — Config projection via describe() (no installSection).
const readLive = () => {
const row = describeRows(settings?.describe).find((item) => item.ns === SEARCH_MCP_SETTINGS_NAMESPACE);
if (row?.value !== null && typeof row?.value === 'object' && !Array.isArray(row.value)) {
return { ...config, ...row.value };
}
return config;
};
hooks.setSource(readLive);
let last = JSON.stringify(readLive());
const timer = setInterval(() => {
const next = readLive();
const fingerprint = JSON.stringify(next);
if (fingerprint === last) return;
last = fingerprint;
hooks.onChange();
}, 2_000);
settingsCtx.effect(() => () => clearInterval(timer), 'dsh-search-mcp: settings describe poll');
settingsCtx.logger?.info?.(
'dsh-search-mcp: following settings via describe() (DSH ≥0.1.7 / 0.2 path)',
);
});
// `registerSearchProvider` owns its cleanup via ctx.effect (HMR/dispose safe).
const current = () => config;
// Desktop 0.2: avoid soft-inject(['settings']) + describe poll during bring-up.
// Loader already projects Config from the cordis entry / patch insert.
ctx.web.registerSearchProvider(new SearchMCPProvider(() => resolveOptions(ctx, current())));
}