161 lines
4.8 KiB
TypeScript
161 lines
4.8 KiB
TypeScript
/**
|
|
* Helpers for creating the stable MCP launcher and MCP client configuration.
|
|
*
|
|
* The launcher lives outside the VS Code extension install directory (under
|
|
* ~/.ra3modxml) so AI client configs do not break when the extension is
|
|
* updated to a new version. The extension refreshes the launcher on every
|
|
* activation/update.
|
|
*
|
|
* Pure TypeScript: no VS Code dependency.
|
|
*/
|
|
|
|
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { homedir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { defaultAgentHome } from "./snapshot";
|
|
|
|
export interface McpConfigTarget {
|
|
id: string;
|
|
label: string;
|
|
path: string;
|
|
}
|
|
|
|
/** File name of the stable launcher on the current platform. */
|
|
export function launcherFileName(): string {
|
|
return process.platform === "win32" ? "ra3-mod-xml-mcp.cmd" : "ra3-mod-xml-mcp";
|
|
}
|
|
|
|
/** Absolute path to the stable launcher under the agent home. */
|
|
export function launcherPath(agentHome = defaultAgentHome()): string {
|
|
return join(agentHome, launcherFileName());
|
|
}
|
|
|
|
/**
|
|
* Path to the bundled MCP server inside an extension install/dev directory.
|
|
* The packaged extension ships this file under dist/agent/mcpServer.js.
|
|
*/
|
|
export function bundledMcpServerPath(extensionRoot: string): string {
|
|
return join(extensionRoot, "dist", "agent", "mcpServer.js");
|
|
}
|
|
|
|
/**
|
|
* Creates the stable launcher script. It points to the current extension's
|
|
* bundled MCP server and passes the project directory.
|
|
*/
|
|
export async function writeLauncher(
|
|
extensionRoot: string,
|
|
projectDir: string,
|
|
agentHome = defaultAgentHome(),
|
|
): Promise<string> {
|
|
const server = bundledMcpServerPath(extensionRoot);
|
|
const launcher = launcherPath(agentHome);
|
|
await mkdir(dirname(launcher), { recursive: true });
|
|
if (process.platform === "win32") {
|
|
const content = [
|
|
"@echo off",
|
|
`node "${server}" --project "${projectDir}"`,
|
|
"",
|
|
].join("\r\n");
|
|
await writeFile(launcher, content, "utf8");
|
|
} else {
|
|
const content = [
|
|
"#!/usr/bin/env sh",
|
|
`exec node "${server}" --project "${projectDir}"`,
|
|
"",
|
|
].join("\n");
|
|
await writeFile(launcher, content, "utf8");
|
|
await chmod(launcher, 0o755);
|
|
}
|
|
return launcher;
|
|
}
|
|
|
|
/** MCP client config entry for one project. */
|
|
export function mcpServerConfig(
|
|
launcher: string,
|
|
projectDir: string,
|
|
): Record<string, unknown> {
|
|
return {
|
|
mcpServers: {
|
|
"ra3-mod-xml": {
|
|
command: launcher,
|
|
args: ["--project", projectDir],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Human-readable JSON config block users can paste into AI clients. */
|
|
export function mcpConfigJson(
|
|
launcher: string,
|
|
projectDir: string,
|
|
): string {
|
|
return JSON.stringify(mcpServerConfig(launcher, projectDir), null, 2);
|
|
}
|
|
|
|
/** Claude Desktop config path (Windows/macOS/Linux common locations). */
|
|
export function claudeDesktopConfigPath(): string {
|
|
if (process.env.APPDATA) return join(process.env.APPDATA, "Claude", "claude_desktop_config.json");
|
|
return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
}
|
|
|
|
/** Cursor's global MCP config path. */
|
|
export function cursorGlobalConfigPath(): string {
|
|
return join(homedir(), ".cursor", "mcp.json");
|
|
}
|
|
|
|
/** Cursor's project-scoped MCP config path. */
|
|
export function cursorProjectConfigPath(projectDir: string): string {
|
|
return join(projectDir, ".cursor", "mcp.json");
|
|
}
|
|
|
|
/** Common local MCP config files this extension can offer to update. */
|
|
export function commonMcpConfigTargets(projectDir: string): McpConfigTarget[] {
|
|
return [
|
|
{
|
|
id: "claude-desktop",
|
|
label: "Claude Desktop",
|
|
path: claudeDesktopConfigPath(),
|
|
},
|
|
{
|
|
id: "cursor-global",
|
|
label: "Cursor (global)",
|
|
path: cursorGlobalConfigPath(),
|
|
},
|
|
{
|
|
id: "cursor-project",
|
|
label: "Cursor (current project)",
|
|
path: cursorProjectConfigPath(projectDir),
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Adds the RA3 Mod XML MCP server entry to a JSON config file, preserving any
|
|
* existing keys and mcpServers. Creates the file when it does not exist.
|
|
*/
|
|
export async function addMcpServerToConfigFile(
|
|
filePath: string,
|
|
launcher: string,
|
|
projectDir: string,
|
|
): Promise<void> {
|
|
let config: Record<string, unknown> = {};
|
|
try {
|
|
config = JSON.parse(await readFile(filePath, "utf8")) as Record<string, unknown>;
|
|
} catch {
|
|
// File absent or malformed: start fresh.
|
|
}
|
|
const servers = (config.mcpServers as Record<string, unknown> | undefined) ?? {};
|
|
servers["ra3-mod-xml"] = {
|
|
command: launcher,
|
|
args: ["--project", projectDir],
|
|
};
|
|
config.mcpServers = servers;
|
|
await mkdir(dirname(filePath), { recursive: true });
|
|
await writeFile(filePath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
/** Default path used for the agent home. */
|
|
export function defaultAgentHomeForSetup(): string {
|
|
return join(homedir(), ".ra3modxml");
|
|
}
|