fix pnpm play for cursor with MCP access

This commit is contained in:
Shawn Morreau
2025-11-18 18:41:45 -05:00
parent 7ec4fd52b1
commit fc1b035f5d
4 changed files with 174 additions and 80 deletions
+83 -77
View File
@@ -1,10 +1,9 @@
import { spawn } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { log } from "../utils/cli.ts";
import { addInstructions } from "./instructions.ts";
import { agent, installFromCurl } from "./shared.ts";
import { agent, installFromCurl, type AddMcpServerParams } from "./shared.ts";
export const cursor = agent({
name: "cursor",
@@ -15,15 +14,88 @@ export const cursor = agent({
executableName: "cursor-agent",
});
},
run: async ({ prompt, mcpServers, apiKey, cliPath, githubInstallationToken }) => {
addMcpServer: ({ serverName, serverConfig, cliPath }: AddMcpServerParams) => {
const command = serverConfig.command;
const args = serverConfig.args || [];
const envVars = serverConfig.env || {};
// Resolve command to absolute path if it's a relative path
// For commands like "node", keep as-is; for file paths, resolve them
let resolvedCommand = command;
if (!command.includes("/") && !command.includes("\\")) {
// It's a command in PATH (like "node"), keep as-is
resolvedCommand = command;
} else {
// It's a file path, resolve to absolute path
resolvedCommand = resolve(command);
}
// Resolve args to absolute paths if they look like file paths
const resolvedArgs = args.map((arg) => {
// If arg looks like a file path and is relative, resolve it
if (
(arg.includes("/") || arg.includes("\\")) &&
!arg.startsWith("/") &&
!arg.match(/^[A-Z]:\\/)
) {
return resolve(arg);
}
return arg;
});
// Build the server config with resolved paths
const resolvedServerConfig = {
command: resolvedCommand,
args: resolvedArgs,
env: envVars,
};
const tempDir = cliPath.split("/.local/bin/")[0];
const cursorConfigDir = join(tempDir, ".cursor");
const mcpConfigPath = join(cursorConfigDir, "mcp.json");
// Read existing config if it exists
let mcpConfig: { mcpServers: Record<string, unknown> } = { mcpServers: {} };
if (existsSync(mcpConfigPath)) {
try {
const existingConfig = readFileSync(mcpConfigPath, "utf-8");
mcpConfig = JSON.parse(existingConfig);
if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") {
mcpConfig.mcpServers = {};
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to read existing MCP config at ${mcpConfigPath}: ${errorMessage}`);
}
}
// Add the new server
mcpConfig.mcpServers[serverName] = resolvedServerConfig;
log.info(`Adding MCP server '${serverName}' to Cursor configuration...`);
// Create .cursor directory if it doesn't exist
try {
mkdirSync(cursorConfigDir, { recursive: true });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(
`Failed to create Cursor config directory at ${cursorConfigDir}: ${errorMessage}`
);
}
// Write MCP configuration file
try {
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
log.info(`✓ MCP server '${serverName}' added to ${mcpConfigPath}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to write MCP config to ${mcpConfigPath}: ${errorMessage}`);
}
},
run: async ({ prompt, apiKey, cliPath, githubInstallationToken }) => {
process.env.CURSOR_API_KEY = apiKey;
process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken;
// Configure MCP servers for Cursor (global config is fine - not part of repo)
if (mcpServers && Object.keys(mcpServers).length > 0) {
configureMcpServers({ mcpServers, cliPath });
}
try {
// Run cursor-agent in non-interactive mode with the prompt
// Using -p flag for prompt, --output-format text for plain text output
@@ -40,7 +112,7 @@ export const cursor = agent({
return new Promise((resolve) => {
const child = spawn(
cliPath,
["--print", fullPrompt, "--output-format", "text", "--approve-mcps"],
["--print", fullPrompt, "--output-format", "text", "--approve-mcps", "--force"],
{
cwd: process.cwd(), // Run in current working directory
env: {
@@ -55,20 +127,6 @@ export const cursor = agent({
let stdout = "";
let stderr = "";
let hasOutput = false;
// Set a timeout to detect if the process hangs
const timeout = setTimeout(() => {
if (!hasOutput && child.exitCode === null) {
log.warning("Cursor CLI appears to be hanging, killing process...");
child.kill("SIGTERM");
resolve({
success: false,
error: "Cursor CLI timed out - no output received",
output: stdout.trim(),
});
}
}, 300000); // 5 minute timeout
// Log when process starts
child.on("spawn", () => {
@@ -76,7 +134,6 @@ export const cursor = agent({
});
child.stdout?.on("data", (data) => {
hasOutput = true;
const text = data.toString();
stdout += text;
// Stream output in real-time
@@ -84,7 +141,6 @@ export const cursor = agent({
});
child.stderr?.on("data", (data) => {
hasOutput = true;
const text = data.toString();
stderr += text;
// Log errors as they come - but also write to stdout so we can see it
@@ -94,8 +150,6 @@ export const cursor = agent({
// Handle process exit
child.on("close", (code, signal) => {
clearTimeout(timeout);
if (signal) {
log.warning(`Cursor CLI terminated by signal: ${signal}`);
}
@@ -138,51 +192,3 @@ export const cursor = agent({
}
},
});
function configureMcpServers({
mcpServers,
cliPath,
}: {
mcpServers: Record<string, McpServerConfig>;
cliPath: string;
}): void {
log.info("Configuring MCP servers for Cursor...");
const tempDir = cliPath.split("/.local/bin/")[0];
const cursorConfigDir = join(tempDir, ".cursor");
const mcpConfigPath = join(cursorConfigDir, "mcp.json");
// Build MCP configuration object
const mcpConfig: { mcpServers: Record<string, McpServerConfig> } = {
mcpServers: {},
};
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
// Only configure stdio servers (Cursor CLI supports stdio MCP servers)
// Check if it's a stdio server config (has 'command' property)
if (!("command" in serverConfig)) {
log.warning(`MCP server '${serverName}' is not a stdio server, skipping...`);
continue;
}
// Add the server configuration
mcpConfig.mcpServers[serverName] = serverConfig;
log.info(`Adding MCP server '${serverName}'...`);
}
if (Object.keys(mcpConfig.mcpServers).length === 0) {
log.info("No MCP servers to configure");
return;
}
// Create .cursor directory if it doesn't exist
mkdirSync(cursorConfigDir, { recursive: true });
// Write MCP configuration file
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
log.info(`✓ MCP configuration written to ${mcpConfigPath}`);
// Cursor CLI may require approval for MCP servers
// Use --approve-mcps flag when running to automatically approve all MCP servers
log.info("MCP servers configured. Cursor CLI will use --approve-mcps to auto-approve servers.");
}