ai agent
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
Reference in New Issue
Block a user