diff --git a/agents/cursor.ts b/agents/cursor.ts index 2ac7b6f..04be952 100644 --- a/agents/cursor.ts +++ b/agents/cursor.ts @@ -21,24 +21,18 @@ export const cursor = agent({ configureCursorMcpServers({ 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 - // and --approve-mcps to automatically approve all MCP servers const fullPrompt = addInstructions(payload); - // Find temp directory from cliPath to set HOME for MCP config lookup const tempDir = cliPath.split("/.local/bin/")[0]; log.info("Running Cursor CLI..."); - // Use spawn to handle streaming output - // Use --print flag explicitly for non-interactive mode return new Promise((resolve) => { const child = spawn( cliPath, ["--print", fullPrompt, "--output-format", "text", "--approve-mcps", "--force"], { - cwd: process.cwd(), // Run in current working directory + cwd: process.cwd(), env: { ...process.env, CURSOR_API_KEY: apiKey, @@ -52,7 +46,6 @@ export const cursor = agent({ let stdout = ""; let stderr = ""; - // Log when process starts child.on("spawn", () => { log.debug("Cursor CLI process spawned"); }); @@ -67,12 +60,10 @@ export const cursor = agent({ child.stderr?.on("data", (data) => { const text = data.toString(); stderr += text; - // Log errors as they come - but also write to stdout so we can see it process.stderr.write(text); log.warning(text); }); - // Handle process exit child.on("close", (code, signal) => { if (signal) { log.warning(`Cursor CLI terminated by signal: ${signal}`); diff --git a/agents/gemini.ts b/agents/gemini.ts index 84a826b..299fdbc 100644 --- a/agents/gemini.ts +++ b/agents/gemini.ts @@ -32,10 +32,11 @@ export const gemini = agent({ try { const result = await spawn({ cmd: "node", - args: [cliPath, "--yolo", "--output-format", "text", "-p", sessionPrompt], + args: [cliPath, "--yolo", "--output-format=text", "-p", sessionPrompt], env: { GEMINI_API_KEY: apiKey, GITHUB_INSTALLATION_TOKEN: githubInstallationToken, + GEMINI_CLI_DISABLE_SCHEMA_VALIDATION: "1", }, onStdout: (chunk) => { const trimmed = chunk.trim(); @@ -94,7 +95,6 @@ function configureGeminiMcpServers({ mcpServers, cliPath }: ConfigureMcpServersP const addArgs = ["mcp", "add", serverName, command, ...args]; - // Add environment variables as --env flags for (const [key, value] of Object.entries(envVars)) { addArgs.push("--env", `${key}=${value}`); } diff --git a/agents/shared.ts b/agents/shared.ts index 754d392..0ad584b 100644 --- a/agents/shared.ts +++ b/agents/shared.ts @@ -1,5 +1,7 @@ import { spawnSync } from "node:child_process"; import { chmodSync, createWriteStream, existsSync } from "node:fs"; +import { mkdtemp } from "node:fs/promises"; +import { tmpdir } from "node:os"; import { join } from "node:path"; import { pipeline } from "node:stream/promises"; import type { McpStdioServerConfig } from "@anthropic-ai/claude-agent-sdk"; diff --git a/fixtures/basic.txt b/fixtures/basic.txt index 48607c8..6d14410 100644 --- a/fixtures/basic.txt +++ b/fixtures/basic.txt @@ -1 +1 @@ -create a comment on https://github.com/pullfrogai/scratch/pull/29 that says Things just got out of hand \ No newline at end of file +comment on https://github.com/pullfrogai/scratch/pull/29 that writes a really funny joke about Arktype \ No newline at end of file diff --git a/mcp/server.ts b/mcp/server.ts index 96e68e0..d650b2e 100644 --- a/mcp/server.ts +++ b/mcp/server.ts @@ -36,4 +36,60 @@ addTools(server, [ GetCheckSuiteLogsTool, ]); +// intercept stdout to remove $schema fields from JSON-RPC messages +// FastMCP uses stdout for JSON-RPC communication, so we intercept here +const originalStdoutWrite = process.stdout.write.bind(process.stdout); +process.stdout.write = function (chunk: any, encoding?: any, cb?: any): boolean { + if (typeof chunk === "string" || Buffer.isBuffer(chunk)) { + try { + const text = typeof chunk === "string" ? chunk : chunk.toString("utf-8"); + // MCP uses JSON-RPC over stdio, messages are JSON objects on single lines + const lines = text.split("\n"); + const modifiedLines = lines.map((line) => { + const trimmed = line.trim(); + // only process lines that look like JSON-RPC messages + if (trimmed.startsWith("{") && trimmed.includes('"jsonrpc"')) { + try { + const parsed = JSON.parse(trimmed); + // recursively remove $schema fields from the message + const cleaned = removeSchemaFields(parsed); + return JSON.stringify(cleaned) + "\n"; + } catch { + // if parsing fails, return original line + return line + (line.endsWith("\n") ? "" : "\n"); + } + } + return line + (line.endsWith("\n") ? "" : "\n"); + }); + const result = modifiedLines.join(""); + return originalStdoutWrite(result, encoding, cb); + } catch { + // if anything fails, just pass through + } + } + return originalStdoutWrite(chunk, encoding, cb); +}; + +// recursively remove $schema fields from JSON objects +function removeSchemaFields(obj: any): any { + if (obj === null || typeof obj !== "object") { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(removeSchemaFields); + } + + const result: any = {}; + for (const [key, value] of Object.entries(obj)) { + // skip $schema fields + if (key === "$schema") { + continue; + } + result[key] = removeSchemaFields(value); + } + + return result; +} + server.start(); diff --git a/mcp/shared.ts b/mcp/shared.ts index d86490d..79ae24d 100644 --- a/mcp/shared.ts +++ b/mcp/shared.ts @@ -65,9 +65,36 @@ export const tool = (toolDef: Tool>) return toolDef; }; +// recursively remove $schema fields from JSON Schema objects +function removeSchemaFields(obj: any): any { + if (obj === null || typeof obj !== "object") { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(removeSchemaFields); + } + + const result: any = {}; + for (const [key, value] of Object.entries(obj)) { + // skip $schema fields + if (key === "$schema") { + continue; + } + result[key] = removeSchemaFields(value); + } + + return result; +} + export const addTools = (server: FastMCP, tools: Tool[]) => { for (const tool of tools) { - server.addTool(tool); + // clone tool and remove $schema from parameters schema + const cleanedTool = { + ...tool, + parameters: tool.parameters ? removeSchemaFields(tool.parameters) : undefined, + }; + server.addTool(cleanedTool); } return server; };