Files
Codeman/src/mcp-sync-targets.ts
T
Devvyn dc87ef724c fix(cli-registry): address review on GitHub Copilot run mode
- Drop GH_TOKEN / GITHUB_TOKEN from env.allowedKeys. The allowlist is one global list with no
  mode context, so they were settable on every session. COPILOT_GITHUB_TOKEN is already admitted
  by the COPILOT_ prefix.
- Declare capabilities.mcpConfig (copilot-json, COPILOT_HOME relocation) so Settings does not
  list Copilot as unsupported while the sync-only target also lists it. The sync-only row is
  skipped while the run mode is enabled.
- Require a leading alphanumeric on the resume id (path-segment in the registry, matching regex in
  the schema): --resume's value is optional, so `--resume --yolo` made --yolo its own flag and
  bypassed the allowAll clamp. Golden cases added; "ids only" wording corrected.
- Remove the unread overlays.credStore line and document signing in inside a Docker container.
- Add the zh-CN "Run GitHub Copilot" string, pin the new declarations in tests and update the
  MCP registry expectations.
2026-10-18 00:47:47 +08:00

78 lines
3.0 KiB
TypeScript

/**
* @fileoverview MCP sync targets that are not Codeman run modes.
*
* `mcpSyncTargets()` (routes/mcp-sync-routes.ts) takes the registry's enabled CLIs that declare an
* `mcpConfig`. Some tools read an MCP server list worth keeping in step with the others but are not
* something Codeman launches, so they have no registry entry (and no id to branch on): GitHub
* Copilot CLI is the first. It is also a run mode that declares its own `mcpConfig`, so while that
* entry is enabled this row is skipped (`mcpSyncOnlyTargets` drops ids the registry already
* supplies); it applies when the run mode is disabled but the tool is still on the machine. They are
* plain data here, take part only when installed or when their config file already exists (an
* absent tool is reported `absent`, never created), and sort after the registry CLIs, so when two
* definitions of a name differ the registry CLI's is the one copied.
*
* @module mcp-sync-targets
*/
import { accessSync, constants as fsConstants } from 'node:fs';
import { homedir } from 'node:os';
import { delimiter, join } from 'node:path';
import type { McpConfigFormat } from './config/cli-registry/types.js';
import type { McpSyncTarget } from './mcp-sync.js';
export interface McpSyncOnlyTool {
id: string;
label: string;
/** Home-relative default location of the MCP config file. */
path: string;
format: McpConfigFormat;
/** The env var the tool reads to move its home, and the file under it. */
relocation?: { envVar: string; path: string };
/** The executable whose presence on this machine means the tool is installed. */
binary: string;
}
export const MCP_SYNC_ONLY_TOOLS: readonly McpSyncOnlyTool[] = [
{
id: 'copilot',
label: 'GitHub Copilot CLI',
path: '.copilot/mcp-config.json',
format: 'copilot-json',
// COPILOT_HOME replaces ~/.copilot (checked: `COPILOT_HOME=<dir> copilot mcp list` reads <dir>).
relocation: { envVar: 'COPILOT_HOME', path: 'mcp-config.json' },
binary: 'copilot',
},
];
/** `name` is an executable file in the server's PATH, `~/.local/bin` or `/usr/local/bin`. */
export function binaryOnPath(name: string, env: Record<string, string | undefined> = process.env): boolean {
const dirs = [
...(env.PATH ?? '').split(delimiter).filter(Boolean),
join(homedir(), '.local', 'bin'),
'/usr/local/bin',
];
return dirs.some((dir) => {
try {
accessSync(join(dir, name), fsConstants.X_OK);
return true;
} catch {
return false;
}
});
}
/** The sync-only tools as sync targets, skipping any id the registry already provides. */
export function mcpSyncOnlyTargets(
taken: ReadonlySet<string>,
isInstalled: (binary: string) => boolean = binaryOnPath
): McpSyncTarget[] {
return MCP_SYNC_ONLY_TOOLS.filter((t) => !taken.has(t.id)).map((t) => ({
id: t.id,
label: t.label,
path: t.path,
format: t.format,
...(t.relocation ? { relocation: t.relocation } : {}),
installed: isInstalled(t.binary),
}));
}