sync: update MCP client from GameCraftTable
This commit is contained in:
+62
-1
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* 管理 CraftTable MCP 在 Codex、Claude Code 与 OpenCode 中的注册和 Skill 安装。
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { constants } from "node:fs";
|
||||
import { copyFile, cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
||||
@@ -9,16 +15,24 @@ import spawn from "cross-spawn";
|
||||
import type { ClientOptions } from "./config.js";
|
||||
import { errorMessage } from "./config.js";
|
||||
|
||||
/** CraftTable MCP 服务及随附 Skill 使用的注册名称。 */
|
||||
export const SERVER_NAME = "crafttable";
|
||||
/** 支持自动配置的 Agent CLI。 */
|
||||
export type AgentName = "codex" | "claude" | "opencode";
|
||||
/** 单个受支持 Agent 或全部受支持 Agent。 */
|
||||
export type AgentTarget = AgentName | "all";
|
||||
/** 安装或移除托管 CraftTable Skill 的结果。 */
|
||||
export type SkillAction = "installed" | "replaced" | "removed" | "unchanged" | "absent" | "preserved" | "would-install" | "would-replace" | "would-remove" | "would-preserve";
|
||||
|
||||
/** 外部 Agent CLI 调用的捕获结果。 */
|
||||
export type CommandResult = { code: number; stdout: string; stderr: string };
|
||||
/** Agent 配置使用的可注入命令执行边界。 */
|
||||
export interface CommandRunner {
|
||||
/** 不经过 shell 执行命令并捕获其输出。 */
|
||||
run(command: string, args: string[]): Promise<CommandResult>;
|
||||
}
|
||||
|
||||
/** 控制 MCP 注册和随附 Skill 安装的输入。 */
|
||||
export type ConfigureInput = {
|
||||
target: AgentTarget;
|
||||
options: ClientOptions;
|
||||
@@ -34,12 +48,22 @@ export type ConfigureInput = {
|
||||
skillPaths?: Partial<Record<AgentName, string>>;
|
||||
};
|
||||
|
||||
/** 配置或取消配置操作中每个 Agent 的结果。 */
|
||||
export type ConfigureResult = {
|
||||
agent: AgentName;
|
||||
action: "added" | "replaced" | "removed" | "unchanged" | "absent" | "would-add" | "would-replace" | "would-remove";
|
||||
skillAction: SkillAction;
|
||||
};
|
||||
|
||||
/**
|
||||
* 为选定 Agent 注册 stdio 桥接并安装随附 Skill。
|
||||
*
|
||||
* 除非现有配置与托管内容一致、用户确认替换或启用 `force`,否则保留现有配置。
|
||||
*
|
||||
* @param input - 目标 Agent、启动设置与变更策略。
|
||||
* @returns 每个选定 Agent 的 MCP 与 Skill 操作结果。
|
||||
* @throws 无法检查配置、无法取得确认或写入/CLI 命令失败时抛出。
|
||||
*/
|
||||
export async function configureAgents(input: ConfigureInput): Promise<ConfigureResult[]> {
|
||||
const agents = expandTarget(input.target);
|
||||
const command = launchCommand(input.options, input.cliEntry, input.nodePath ?? process.execPath);
|
||||
@@ -55,6 +79,15 @@ export async function configureAgents(input: ConfigureInput): Promise<ConfigureR
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从选定 Agent 中移除托管的 MCP 注册与随附 Skill。
|
||||
*
|
||||
* 除非启用 `force`,否则保留用户修改过的 Skill。
|
||||
*
|
||||
* @param input - 目标 Agent、路径与变更策略。
|
||||
* @returns 每个选定 Agent 的 MCP 与 Skill 操作结果。
|
||||
* @throws 无法检查配置或写入/CLI 命令失败时抛出。
|
||||
*/
|
||||
export async function unconfigureAgents(input: ConfigureInput): Promise<ConfigureResult[]> {
|
||||
const agents = expandTarget(input.target);
|
||||
const runner = input.runner ?? new SpawnCommandRunner();
|
||||
@@ -88,6 +121,14 @@ async function unconfigureCliAgent(
|
||||
return { agent, action: "removed" };
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造注册到 Agent CLI 的完整可执行程序与参数。
|
||||
*
|
||||
* @param options - 远端 MCP 与 OAuth 回调设置。
|
||||
* @param cliEntry - CraftTable MCP CLI 入口模块路径。
|
||||
* @param nodePath - 用于启动入口模块的 Node.js 可执行文件。
|
||||
* @returns 可用于 Agent 配置的 argv 风格命令数组。
|
||||
*/
|
||||
export function launchCommand(options: ClientOptions, cliEntry: string, nodePath: string): string[] {
|
||||
return [
|
||||
path.resolve(nodePath),
|
||||
@@ -147,7 +188,7 @@ function outputMatchesCommand(output: string, command: string[]): boolean {
|
||||
const document = JSON.parse(output) as unknown;
|
||||
if (findCommand(document, command)) return true;
|
||||
} catch {
|
||||
// Claude currently returns a human-readable record.
|
||||
// Claude 当前返回便于阅读的文本记录。
|
||||
}
|
||||
return command.every((part) => output.includes(part));
|
||||
}
|
||||
@@ -201,6 +242,10 @@ async function unconfigureOpenCode(input: ConfigureInput): Promise<Omit<Configur
|
||||
return { agent: "opencode", action: "removed" };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param env - 用于解析用户配置根目录的环境变量。
|
||||
* @returns 默认 OpenCode JSONC 配置路径。
|
||||
*/
|
||||
export function defaultOpenCodePath(env: NodeJS.ProcessEnv = process.env): string {
|
||||
const base = env.XDG_CONFIG_HOME || (process.platform === "win32"
|
||||
? path.join(env.USERPROFILE || os.homedir(), ".config")
|
||||
@@ -208,10 +253,19 @@ export function defaultOpenCodePath(env: NodeJS.ProcessEnv = process.env): strin
|
||||
return path.join(base, "opencode", "opencode.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cliEntry - 已安装 CLI 入口模块的路径。
|
||||
* @returns 与入口相邻的随附 CraftTable Skill 目录。
|
||||
*/
|
||||
export function bundledSkillPath(cliEntry: string): string {
|
||||
return path.resolve(path.dirname(cliEntry), "..", "skills", SERVER_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param agent - 需要解析用户级 Skill 目录的 Agent。
|
||||
* @param env - 用于解析用户配置根目录的环境变量。
|
||||
* @returns CraftTable Skill 的默认目标目录。
|
||||
*/
|
||||
export function defaultSkillPath(agent: AgentName, env: NodeJS.ProcessEnv = process.env): string {
|
||||
const home = env.USERPROFILE || env.HOME || os.homedir();
|
||||
if (agent === "codex") return path.join(env.CODEX_HOME || path.join(home, ".codex"), "skills", SERVER_NAME);
|
||||
@@ -377,7 +431,14 @@ function safeCommandError(result: CommandResult): string {
|
||||
return (result.stderr || result.stdout || `exit code ${result.code}`).trim();
|
||||
}
|
||||
|
||||
/** 以隐藏且不经过 shell 的子进程运行 Agent CLI 配置命令。 */
|
||||
export class SpawnCommandRunner implements CommandRunner {
|
||||
/**
|
||||
* @param command - 可执行文件名称或路径。
|
||||
* @param args - 不经 shell 解释而传递的原始参数列表。
|
||||
* @returns 捕获的退出码、标准输出与标准错误。
|
||||
* @throws 子进程无法启动时抛出。
|
||||
*/
|
||||
run(command: string, args: string[]): Promise<CommandResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, { shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
||||
|
||||
Reference in New Issue
Block a user