Files
qiming/qimingclaw/crates/agent-electron-client/src/main/services/packages/mcpHelpers.ts

69 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* MCP Helper Utilities
*
* Pure functions for MCP server configuration comparison and filtering.
* No Electron or side-effectful dependencies — safe to import anywhere,
* including test files without additional mocks.
*/
import * as path from 'path';
import type { McpServerEntry } from './mcp';
/**
* 从 MCP server 配置中过滤掉 bridge 聚合入口
* command 为 "mcp-proxy" 或其绝对路径形式)。
*
* 用于 detectConfigChange 和 engineRawMcpServers 的原始格式快照存储。
*/
export function filterBridgeEntries(
servers: Record<string, McpServerEntry>,
): Record<string, McpServerEntry> {
const result: Record<string, McpServerEntry> = {};
for (const [name, entry] of Object.entries(servers)) {
if ('command' in entry && (entry.command === 'mcp-proxy' || path.basename(entry.command) === 'mcp-proxy')) continue;
result[name] = entry;
}
return result;
}
/**
* 比较两组原始 MCP server 配置是否语义等价。
*
* 只比较决定进程行为的字段command / args / allowTools / denyTools / url远程 server
* 故意忽略 envenv 由 getAppEnv() 每次注入,内容固定,不触发引擎重建。
*
* @param a 本次请求传入的原始 MCP servers已过滤掉 mcp-proxy 入口)
* @param b 上次存储的原始 MCP serversundefined 表示首次(无历史快照)
*/
export function rawMcpServersEqual(
a: Record<string, McpServerEntry>,
b: Record<string, McpServerEntry> | undefined,
): boolean {
if (!b) return false;
const aKeys = Object.keys(a).sort();
const bKeys = Object.keys(b).sort();
if (aKeys.join('\0') !== bKeys.join('\0')) return false;
for (const key of aKeys) {
const ea = a[key];
const eb = b[key];
// 远程 server只比较 URL
if ('url' in ea || 'url' in eb) {
if (('url' in ea ? ea.url : undefined) !== ('url' in eb ? eb.url : undefined)) return false;
continue;
}
// stdio server比较 command / args / allowTools / denyTools
// 同名 key 类型不一致(一方有 command另一方没有→ 视为不等,触发重建
if (!('command' in ea) || !('command' in eb)) return false;
if (ea.command !== eb.command) return false;
if (JSON.stringify(ea.args ?? []) !== JSON.stringify(eb.args ?? [])) return false;
// 对数组型字段排序后比较,避免顺序差异误判
const sortedAllowA = [...(ea.allowTools ?? [])].sort().join('\0');
const sortedAllowB = [...(eb.allowTools ?? [])].sort().join('\0');
if (sortedAllowA !== sortedAllowB) return false;
const sortedDenyA = [...(ea.denyTools ?? [])].sort().join('\0');
const sortedDenyB = [...(eb.denyTools ?? [])].sort().join('\0');
if (sortedDenyA !== sortedDenyB) return false;
}
return true;
}