diff --git a/agents/gemini.ts b/agents/gemini.ts index b6d1dfd..13f9ab0 100644 --- a/agents/gemini.ts +++ b/agents/gemini.ts @@ -173,8 +173,9 @@ export const gemini = agent({ args: [cliPath, "--yolo", "--output-format=stream-json", "-p", sessionPrompt], env: createAgentEnv({ GEMINI_API_KEY: apiKey, + GEMINI_CLI_DISABLE_SCHEMA_VALIDATION: + process.env.GEMINI_CLI_DISABLE_SCHEMA_VALIDATION || "1", }), - timeout: 600000, // 10 minutes onStdout: async (chunk) => { const text = chunk.toString(); finalOutput += text; @@ -257,6 +258,11 @@ function configureGeminiMcpServers({ mcpServers, cliPath }: ConfigureMcpServersP const addResult = spawnSync("node", [cliPath, ...addArgs], { stdio: "pipe", encoding: "utf-8", + env: { + ...process.env, + GEMINI_CLI_DISABLE_SCHEMA_VALIDATION: + process.env.GEMINI_CLI_DISABLE_SCHEMA_VALIDATION || "1", + }, }); if (addResult.status !== 0) { diff --git a/fixtures/basic.txt b/fixtures/basic.txt index bc823bf..b08735f 100644 --- a/fixtures/basic.txt +++ b/fixtures/basic.txt @@ -1 +1 @@ -summarize https://github.com/pullfrogai/scratch/issues/56, its status, and pertinent discussion on the issue \ No newline at end of file +Use the debug_shell_command MCP tool to run git status and show me the output. \ No newline at end of file diff --git a/main.ts b/main.ts index d7ca27e..71f106d 100644 --- a/main.ts +++ b/main.ts @@ -279,6 +279,7 @@ async function startMcpServer(ctx: MainContext): Promise { process.env.GITHUB_REPOSITORY = githubRepository; process.env.PULLFROG_MODES = JSON.stringify(allModes); process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload); + process.env.PULLFROG_AGENT = ctx.agentName; // GITHUB_RUN_ID is already set in GitHub Actions, no need to set it here diff --git a/mcp/shared.ts b/mcp/shared.ts index c6bb750..683a4a8 100644 --- a/mcp/shared.ts +++ b/mcp/shared.ts @@ -44,9 +44,91 @@ export interface McpContext extends RepoContext { export const tool = (toolDef: Tool>) => toolDef; +/** + * Sanitize JSON schema to remove problematic fields that Gemini CLI can't handle + * - Removes $schema field (causes "no schema with key or ref" errors) + * - Converts $defs to definitions (draft-07 compatibility) + * - Removes any draft-2020-12 specific features + */ +function sanitizeSchema(schema: any): any { + if (!schema || typeof schema !== "object") { + return schema; + } + + if (Array.isArray(schema)) { + return schema.map(sanitizeSchema); + } + + const sanitized: any = {}; + + for (const [key, value] of Object.entries(schema)) { + // skip $schema field entirely + if (key === "$schema") { + continue; + } + + // convert $defs to definitions for draft-07 compatibility + if (key === "$defs") { + sanitized.definitions = sanitizeSchema(value); + continue; + } + + // recursively sanitize nested objects + sanitized[key] = sanitizeSchema(value); + } + + return sanitized; +} + +/** + * Wrap a StandardSchemaV1 to intercept toJsonSchema() calls and sanitize the output + */ +function wrapSchema(schema: StandardSchemaV1): StandardSchemaV1 { + const originalToJsonSchema = (schema as any).toJsonSchema?.bind(schema); + + if (!originalToJsonSchema) { + return schema; + } + + // create a proxy that intercepts toJsonSchema calls + return new Proxy(schema, { + get(target, prop) { + if (prop === "toJsonSchema") { + return () => { + const originalSchema = originalToJsonSchema(); + return sanitizeSchema(originalSchema); + }; + } + return (target as any)[prop]; + }, + }) as StandardSchemaV1; +} + +/** + * Transform tool to sanitize its parameter schema for Gemini CLI compatibility + */ +function sanitizeTool>(tool: T): T { + if (!tool.parameters) { + return tool; + } + + // wrap the schema object to intercept toJsonSchema() calls + const wrappedSchema = wrapSchema(tool.parameters); + + // create a new tool with wrapped schema + return { + ...tool, + parameters: wrappedSchema, + } as T; +} + export const addTools = (server: FastMCP, tools: Tool[]) => { + // only sanitize schemas for gemini agent (it has issues with draft-2020-12 schemas) + const shouldSanitize = process.env.PULLFROG_AGENT === "gemini"; + for (const tool of tools) { - server.addTool(tool); + const processedTool = shouldSanitize ? sanitizeTool(tool) : tool; + server.addTool(processedTool); } return server; };