@brenoxp/gemini-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Breno Pinto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # gemini-mcp
2
+
3
+ MCP server that wraps [Gemini CLI](https://github.com/google-gemini/gemini-cli) for use with Claude Code and other MCP clients.
4
+
5
+ Use Gemini's large context window (1M tokens) and web search from within Claude Code.
6
+
7
+ ## Prerequisites
8
+
9
+ - [Gemini CLI](https://github.com/google-gemini/gemini-cli) installed and authenticated
10
+ - Node.js 18+ or Bun
11
+
12
+ ## Installation
13
+
14
+ ### Via npm (recommended)
15
+
16
+ ```bash
17
+ npm install -g @brenoxp/gemini-mcp
18
+ ```
19
+
20
+ ### From source
21
+
22
+ ```bash
23
+ git clone https://github.com/brenoxp/gemini-mcp.git
24
+ cd gemini-mcp
25
+ npm install
26
+ npm run build
27
+ ```
28
+
29
+ ## Usage with Claude Code
30
+
31
+ Add to your Claude Code MCP configuration:
32
+
33
+ ```bash
34
+ # If installed globally via npm
35
+ claude mcp add gemini-mcp -- gemini-mcp
36
+
37
+ # Or run directly with npx
38
+ claude mcp add gemini-mcp -- npx @brenoxp/gemini-mcp
39
+
40
+ # Or from source
41
+ claude mcp add gemini-mcp -- node /path/to/gemini-mcp/dist/index.js
42
+ ```
43
+
44
+ ## Tools
45
+
46
+ ### gemini
47
+
48
+ Send a prompt directly to Gemini CLI.
49
+
50
+ Parameters:
51
+ - `prompt` (required): The prompt to send
52
+ - `model`: Gemini model to use (default: `gemini-2.5-flash`)
53
+ - `allowed_tools`: Comma-separated tools Gemini can use (e.g., `web_search,shell`)
54
+
55
+ ### gemini_with_agent_md
56
+
57
+ Send a prompt with an agent.md file as context. Useful for delegating tasks that need specific instructions.
58
+
59
+ Parameters:
60
+ - `agent_md_path` (required): Absolute path to agent.md file
61
+ - `prompt` (required): The prompt to send
62
+ - `model`: Override model (otherwise inferred from frontmatter)
63
+ - `allowed_tools`: Comma-separated tools Gemini can use
64
+
65
+ Model mapping from Claude agent.md frontmatter:
66
+ - `haiku` -> `gemini-2.5-flash-lite`
67
+ - `sonnet` -> `gemini-2.5-flash`
68
+ - `opus` -> `gemini-2.5-pro`
69
+
70
+ The tool automatically strips frontmatter from the agent.md before passing to Gemini.
71
+
72
+ ## Example prompts in Claude Code
73
+
74
+ ```
75
+ Use the gemini tool to summarize this 500-page PDF: /path/to/large.pdf
76
+
77
+ Use gemini with web_search to find the latest React 19 features
78
+
79
+ Use gemini_with_agent_md with my research agent to investigate this topic
80
+ ```
81
+
82
+ ## Why use this?
83
+
84
+ - Gemini has 1M token context - process huge files Claude can't handle in one shot
85
+ - Web search built into Gemini CLI
86
+ - Delegate cheap/simple tasks to Gemini, save Claude tokens for complex reasoning
87
+ - Run agents defined in agent.md files through Gemini
88
+
89
+ ## Development
90
+
91
+ ```bash
92
+ # Run directly with Bun
93
+ bun run dev
94
+
95
+ # Type check
96
+ npm run typecheck
97
+
98
+ # Build for distribution
99
+ npm run build
100
+ ```
101
+
102
+ ## License
103
+
104
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { extractModelFromFrontmatter, stripFrontmatter } from "../utils/frontmatter.js";
3
+ describe("extractModelFromFrontmatter", () => {
4
+ it("extracts haiku model", () => {
5
+ const content = `---
6
+ model: haiku
7
+ ---
8
+ # Agent`;
9
+ expect(extractModelFromFrontmatter(content)).toBe("gemini-2.5-flash-lite");
10
+ });
11
+ it("extracts sonnet model", () => {
12
+ const content = `---
13
+ model: sonnet
14
+ ---
15
+ # Agent`;
16
+ expect(extractModelFromFrontmatter(content)).toBe("gemini-2.5-flash");
17
+ });
18
+ it("extracts opus model", () => {
19
+ const content = `---
20
+ model: opus
21
+ ---
22
+ # Agent`;
23
+ expect(extractModelFromFrontmatter(content)).toBe("gemini-2.5-pro");
24
+ });
25
+ it("returns null for unknown model", () => {
26
+ const content = `---
27
+ model: gpt-4
28
+ ---
29
+ # Agent`;
30
+ expect(extractModelFromFrontmatter(content)).toBeNull();
31
+ });
32
+ it("returns null when no frontmatter", () => {
33
+ const content = `# Agent
34
+ Some content`;
35
+ expect(extractModelFromFrontmatter(content)).toBeNull();
36
+ });
37
+ it("returns null when no model in frontmatter", () => {
38
+ const content = `---
39
+ title: My Agent
40
+ ---
41
+ # Agent`;
42
+ expect(extractModelFromFrontmatter(content)).toBeNull();
43
+ });
44
+ it("handles model with extra whitespace", () => {
45
+ const content = `---
46
+ model: haiku
47
+ ---
48
+ # Agent`;
49
+ expect(extractModelFromFrontmatter(content)).toBe("gemini-2.5-flash-lite");
50
+ });
51
+ });
52
+ describe("stripFrontmatter", () => {
53
+ it("removes frontmatter", () => {
54
+ const content = `---
55
+ model: sonnet
56
+ title: My Agent
57
+ ---
58
+ # Agent
59
+ Content here`;
60
+ expect(stripFrontmatter(content)).toBe("# Agent\nContent here");
61
+ });
62
+ it("preserves content without frontmatter", () => {
63
+ const content = `# Agent
64
+ Content here`;
65
+ expect(stripFrontmatter(content)).toBe(content);
66
+ });
67
+ it("handles empty content", () => {
68
+ expect(stripFrontmatter("")).toBe("");
69
+ });
70
+ it("handles frontmatter only", () => {
71
+ const content = `---
72
+ model: haiku
73
+ ---`;
74
+ expect(stripFrontmatter(content)).toBe("");
75
+ });
76
+ it("handles unclosed frontmatter", () => {
77
+ const content = `---
78
+ model: haiku
79
+ # Agent`;
80
+ expect(stripFrontmatter(content)).toBe(content);
81
+ });
82
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { spawn } from "child_process";
3
+ import { EventEmitter } from "events";
4
+ import { executeGemini, DEFAULT_GEMINI_MODEL, DEFAULT_TOOLS } from "../utils/gemini-executor.js";
5
+ vi.mock("child_process", () => ({
6
+ spawn: vi.fn(),
7
+ }));
8
+ const mockSpawn = spawn;
9
+ function createMockProcess(exitCode = 0, stdout = "", stderr = "") {
10
+ const proc = new EventEmitter();
11
+ const stdinChunks = [];
12
+ proc.stdin = {
13
+ write: vi.fn((chunk) => stdinChunks.push(chunk)),
14
+ end: vi.fn(),
15
+ _chunks: stdinChunks,
16
+ };
17
+ proc.stdout = new EventEmitter();
18
+ proc.stderr = new EventEmitter();
19
+ setImmediate(() => {
20
+ proc.stdout?.emit("data", Buffer.from(stdout));
21
+ proc.stderr?.emit("data", Buffer.from(stderr));
22
+ proc.emit("close", exitCode);
23
+ });
24
+ return proc;
25
+ }
26
+ describe("executeGemini", () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ });
30
+ it("spawns gemini with correct base args", async () => {
31
+ const mockProc = createMockProcess(0, '{"response": "test"}');
32
+ mockSpawn.mockReturnValue(mockProc);
33
+ await executeGemini({ prompt: "hello" });
34
+ expect(mockSpawn).toHaveBeenCalledWith("gemini", expect.arrayContaining([
35
+ "-s",
36
+ "--model", DEFAULT_GEMINI_MODEL,
37
+ "--output-format", "json",
38
+ "-p", "hello",
39
+ ]), expect.any(Object));
40
+ });
41
+ it("uses custom model when provided", async () => {
42
+ const mockProc = createMockProcess(0, '{"response": "test"}');
43
+ mockSpawn.mockReturnValue(mockProc);
44
+ await executeGemini({ prompt: "hello", model: "gemini-2.5-pro" });
45
+ expect(mockSpawn).toHaveBeenCalledWith("gemini", expect.arrayContaining(["--model", "gemini-2.5-pro"]), expect.any(Object));
46
+ });
47
+ it("includes default tools in allowed-tools", async () => {
48
+ const mockProc = createMockProcess(0, '{"response": "test"}');
49
+ mockSpawn.mockReturnValue(mockProc);
50
+ await executeGemini({ prompt: "hello" });
51
+ const args = mockSpawn.mock.calls[0][1];
52
+ const toolsIndex = args.indexOf("--allowed-tools");
53
+ const toolsValue = args[toolsIndex + 1];
54
+ for (const tool of DEFAULT_TOOLS) {
55
+ expect(toolsValue).toContain(tool);
56
+ }
57
+ });
58
+ it("merges user tools with default tools", async () => {
59
+ const mockProc = createMockProcess(0, '{"response": "test"}');
60
+ mockSpawn.mockReturnValue(mockProc);
61
+ await executeGemini({ prompt: "hello", allowedTools: "perplexity_search,shell" });
62
+ const args = mockSpawn.mock.calls[0][1];
63
+ const toolsIndex = args.indexOf("--allowed-tools");
64
+ const toolsValue = args[toolsIndex + 1];
65
+ expect(toolsValue).toContain("read_file");
66
+ expect(toolsValue).toContain("write_file");
67
+ expect(toolsValue).toContain("perplexity_search");
68
+ expect(toolsValue).toContain("shell");
69
+ });
70
+ it("deduplicates tools", async () => {
71
+ const mockProc = createMockProcess(0, '{"response": "test"}');
72
+ mockSpawn.mockReturnValue(mockProc);
73
+ await executeGemini({ prompt: "hello", allowedTools: "read_file,shell" });
74
+ const args = mockSpawn.mock.calls[0][1];
75
+ const toolsIndex = args.indexOf("--allowed-tools");
76
+ const toolsValue = args[toolsIndex + 1];
77
+ const tools = toolsValue.split(",");
78
+ const readFileCount = tools.filter((t) => t === "read_file").length;
79
+ expect(readFileCount).toBe(1);
80
+ });
81
+ it("pipes content to stdin when pipeContent provided", async () => {
82
+ const mockProc = createMockProcess(0, '{"response": "test"}');
83
+ mockSpawn.mockReturnValue(mockProc);
84
+ const agentContent = "# Agent\nDo stuff";
85
+ await executeGemini({ prompt: "hello", pipeContent: agentContent });
86
+ expect(mockSpawn).toHaveBeenCalledWith("gemini", expect.any(Array), { stdio: ["pipe", "pipe", "pipe"] });
87
+ expect(mockProc.stdin?.write).toHaveBeenCalledWith(agentContent);
88
+ expect(mockProc.stdin?.end).toHaveBeenCalled();
89
+ });
90
+ it("sets stdin to ignore when no pipeContent", async () => {
91
+ const mockProc = createMockProcess(0, '{"response": "test"}');
92
+ mockSpawn.mockReturnValue(mockProc);
93
+ await executeGemini({ prompt: "hello" });
94
+ expect(mockSpawn).toHaveBeenCalledWith("gemini", expect.any(Array), { stdio: ["ignore", "pipe", "pipe"] });
95
+ });
96
+ it("extracts response from JSON output", async () => {
97
+ const mockProc = createMockProcess(0, '{"response": "the answer"}');
98
+ mockSpawn.mockReturnValue(mockProc);
99
+ const result = await executeGemini({ prompt: "hello" });
100
+ expect(result).toBe("the answer");
101
+ });
102
+ it("returns raw stdout when JSON has no response field", async () => {
103
+ const mockProc = createMockProcess(0, '{"other": "data"}');
104
+ mockSpawn.mockReturnValue(mockProc);
105
+ const result = await executeGemini({ prompt: "hello" });
106
+ expect(result).toBe('{"other": "data"}');
107
+ });
108
+ it("returns raw stdout when output is not JSON", async () => {
109
+ const mockProc = createMockProcess(0, "plain text output");
110
+ mockSpawn.mockReturnValue(mockProc);
111
+ const result = await executeGemini({ prompt: "hello" });
112
+ expect(result).toBe("plain text output");
113
+ });
114
+ it("rejects on non-zero exit code", async () => {
115
+ const mockProc = createMockProcess(1, "", "some error");
116
+ mockSpawn.mockReturnValue(mockProc);
117
+ expect(executeGemini({ prompt: "hello" })).rejects.toThrow("gemini exited with code 1: some error");
118
+ });
119
+ it("rejects on spawn error", async () => {
120
+ const proc = new EventEmitter();
121
+ proc.stdin = null;
122
+ proc.stdout = new EventEmitter();
123
+ proc.stderr = new EventEmitter();
124
+ mockSpawn.mockReturnValue(proc);
125
+ const promise = executeGemini({ prompt: "hello" });
126
+ setImmediate(() => {
127
+ proc.emit("error", new Error("ENOENT"));
128
+ });
129
+ expect(promise).rejects.toThrow("Failed to spawn gemini: ENOENT");
130
+ });
131
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFile } from "fs/promises";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { extractModelFromFrontmatter, stripFrontmatter } from "../utils/frontmatter.js";
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const fixturesDir = path.join(__dirname, "fixtures");
8
+ async function loadFixture(name) {
9
+ return readFile(path.join(fixturesDir, name), "utf-8");
10
+ }
11
+ describe("gemini_with_agent_md with fixtures", () => {
12
+ describe("model extraction from fixture files", () => {
13
+ it("extracts haiku model from agent-haiku.md", async () => {
14
+ const content = await loadFixture("agent-haiku.md");
15
+ expect(extractModelFromFrontmatter(content)).toBe("gemini-2.5-flash-lite");
16
+ });
17
+ it("extracts sonnet model from agent-sonnet.md", async () => {
18
+ const content = await loadFixture("agent-sonnet.md");
19
+ expect(extractModelFromFrontmatter(content)).toBe("gemini-2.5-flash");
20
+ });
21
+ it("extracts opus model from agent-opus.md", async () => {
22
+ const content = await loadFixture("agent-opus.md");
23
+ expect(extractModelFromFrontmatter(content)).toBe("gemini-2.5-pro");
24
+ });
25
+ it("returns null for agent-no-model.md", async () => {
26
+ const content = await loadFixture("agent-no-model.md");
27
+ expect(extractModelFromFrontmatter(content)).toBeNull();
28
+ });
29
+ it("returns null for agent-no-frontmatter.md", async () => {
30
+ const content = await loadFixture("agent-no-frontmatter.md");
31
+ expect(extractModelFromFrontmatter(content)).toBeNull();
32
+ });
33
+ });
34
+ describe("frontmatter stripping from fixture files", () => {
35
+ it("strips frontmatter from agent-haiku.md", async () => {
36
+ const content = await loadFixture("agent-haiku.md");
37
+ const stripped = stripFrontmatter(content);
38
+ expect(stripped).not.toContain("---");
39
+ expect(stripped).not.toContain("model: haiku");
40
+ expect(stripped).toContain("# Test Agent (Haiku)");
41
+ });
42
+ it("strips frontmatter from agent-no-model.md", async () => {
43
+ const content = await loadFixture("agent-no-model.md");
44
+ const stripped = stripFrontmatter(content);
45
+ expect(stripped).not.toContain("---");
46
+ expect(stripped).not.toContain("title:");
47
+ expect(stripped).toContain("# Test Agent (No Model)");
48
+ });
49
+ it("preserves content from agent-no-frontmatter.md", async () => {
50
+ const content = await loadFixture("agent-no-frontmatter.md");
51
+ const stripped = stripFrontmatter(content);
52
+ expect(stripped).toBe(content);
53
+ });
54
+ });
55
+ describe("model resolution logic", () => {
56
+ const DEFAULT_GEMINI_MODEL = "gemini-2.5-flash";
57
+ function resolveModel(explicitModel, inferredModel) {
58
+ return explicitModel ?? inferredModel ?? DEFAULT_GEMINI_MODEL;
59
+ }
60
+ it("uses explicit model when provided", async () => {
61
+ const content = await loadFixture("agent-haiku.md");
62
+ const inferred = extractModelFromFrontmatter(content);
63
+ const result = resolveModel("gemini-2.5-pro", inferred);
64
+ expect(result).toBe("gemini-2.5-pro");
65
+ });
66
+ it("falls back to inferred model when no explicit model", async () => {
67
+ const content = await loadFixture("agent-opus.md");
68
+ const inferred = extractModelFromFrontmatter(content);
69
+ const result = resolveModel(undefined, inferred);
70
+ expect(result).toBe("gemini-2.5-pro");
71
+ });
72
+ it("falls back to default when no explicit or inferred model", async () => {
73
+ const content = await loadFixture("agent-no-model.md");
74
+ const inferred = extractModelFromFrontmatter(content);
75
+ const result = resolveModel(undefined, inferred);
76
+ expect(result).toBe(DEFAULT_GEMINI_MODEL);
77
+ });
78
+ it("falls back to default for file without frontmatter", async () => {
79
+ const content = await loadFixture("agent-no-frontmatter.md");
80
+ const inferred = extractModelFromFrontmatter(content);
81
+ const result = resolveModel(undefined, inferred);
82
+ expect(result).toBe(DEFAULT_GEMINI_MODEL);
83
+ });
84
+ });
85
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { GeminiMCPServer } from './server.js';
3
+ const server = new GeminiMCPServer();
4
+ server.run().catch(console.error);
@@ -0,0 +1,6 @@
1
+ export declare class GeminiMCPServer {
2
+ private readonly server;
3
+ constructor();
4
+ private setupTools;
5
+ run(): Promise<void>;
6
+ }
package/dist/server.js ADDED
@@ -0,0 +1,23 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { registerGeminiTool } from "./tools/gemini.js";
4
+ import { registerGeminiWithAgentMdTool } from "./tools/gemini-with-agent-md.js";
5
+ export class GeminiMCPServer {
6
+ server;
7
+ constructor() {
8
+ this.server = new McpServer({
9
+ name: "gemini-mcp",
10
+ version: "1.0.0",
11
+ });
12
+ this.setupTools();
13
+ }
14
+ setupTools() {
15
+ registerGeminiTool(this.server);
16
+ registerGeminiWithAgentMdTool(this.server);
17
+ }
18
+ async run() {
19
+ const transport = new StdioServerTransport();
20
+ await this.server.connect(transport);
21
+ console.error("Gemini MCP server running on stdio");
22
+ }
23
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare const geminiWithAgentMdInputSchema: {
4
+ agent_md_path: z.ZodString;
5
+ prompt: z.ZodString;
6
+ model: z.ZodOptional<z.ZodString>;
7
+ allowed_tools: z.ZodOptional<z.ZodString>;
8
+ };
9
+ export declare function registerGeminiWithAgentMdTool(server: McpServer): void;
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import { readFile } from "fs/promises";
3
+ import { executeGemini, DEFAULT_GEMINI_MODEL } from "../utils/gemini-executor.js";
4
+ import { extractModelFromFrontmatter, stripFrontmatter } from "../utils/frontmatter.js";
5
+ export const geminiWithAgentMdInputSchema = {
6
+ agent_md_path: z.string().describe("Absolute path to the agent.md file"),
7
+ prompt: z.string().describe("The prompt to send to Gemini"),
8
+ model: z
9
+ .string()
10
+ .optional()
11
+ .describe("Gemini model to use. If not specified, infers from agent.md frontmatter (haiku->gemini-2.5-flash-lite, sonnet->gemini-2.5-flash, opus->gemini-2.5-pro). Falls back to gemini-2.5-flash."),
12
+ allowed_tools: z
13
+ .string()
14
+ .optional()
15
+ .describe('Comma-separated list of tools Gemini can use (e.g., "perplexity_search,shell")'),
16
+ };
17
+ export function registerGeminiWithAgentMdTool(server) {
18
+ server.registerTool("gemini_with_agent_md", {
19
+ title: "Gemini CLI with Agent MD",
20
+ description: "Send a prompt to Gemini CLI with an agent.md file as context. Strips Claude-specific formatting and infers model from frontmatter.",
21
+ inputSchema: geminiWithAgentMdInputSchema,
22
+ }, async ({ agent_md_path, prompt, model, allowed_tools }) => {
23
+ try {
24
+ const content = await readFile(agent_md_path, "utf-8");
25
+ const cleaned = stripFrontmatter(content);
26
+ const inferredModel = extractModelFromFrontmatter(content);
27
+ const finalModel = model ?? inferredModel ?? DEFAULT_GEMINI_MODEL;
28
+ console.error(`Piping cleaned agent.md (${cleaned.length} chars), model: ${finalModel}`);
29
+ const result = await executeGemini({
30
+ prompt,
31
+ model: finalModel,
32
+ allowedTools: allowed_tools,
33
+ pipeContent: cleaned,
34
+ });
35
+ return {
36
+ content: [{ type: "text", text: result }],
37
+ };
38
+ }
39
+ catch (error) {
40
+ return {
41
+ content: [{ type: "text", text: `Error: ${error.message}` }],
42
+ isError: true,
43
+ };
44
+ }
45
+ });
46
+ }
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare const geminiInputSchema: {
4
+ prompt: z.ZodString;
5
+ model: z.ZodOptional<z.ZodString>;
6
+ allowed_tools: z.ZodOptional<z.ZodString>;
7
+ };
8
+ export declare function registerGeminiTool(server: McpServer): void;
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ import { executeGemini, DEFAULT_GEMINI_MODEL } from "../utils/gemini-executor.js";
3
+ export const geminiInputSchema = {
4
+ prompt: z.string().describe("The prompt to send to Gemini"),
5
+ model: z
6
+ .string()
7
+ .optional()
8
+ .describe("Gemini model to use (default: gemini-2.5-flash)"),
9
+ allowed_tools: z
10
+ .string()
11
+ .optional()
12
+ .describe('Comma-separated list of tools Gemini can use (e.g., "perplexity_search,shell")'),
13
+ };
14
+ export function registerGeminiTool(server) {
15
+ server.registerTool("gemini", {
16
+ title: "Gemini CLI",
17
+ description: "Send a prompt to Gemini CLI. Optionally specify model and allowed tools.",
18
+ inputSchema: geminiInputSchema,
19
+ }, async ({ prompt, model, allowed_tools }) => {
20
+ try {
21
+ const result = await executeGemini({
22
+ prompt,
23
+ model: model ?? DEFAULT_GEMINI_MODEL,
24
+ allowedTools: allowed_tools,
25
+ });
26
+ return {
27
+ content: [{ type: "text", text: result }],
28
+ };
29
+ }
30
+ catch (error) {
31
+ return {
32
+ content: [{ type: "text", text: `Error: ${error.message}` }],
33
+ isError: true,
34
+ };
35
+ }
36
+ });
37
+ }
@@ -0,0 +1,2 @@
1
+ export declare function extractModelFromFrontmatter(content: string): string | null;
2
+ export declare function stripFrontmatter(content: string): string;
@@ -0,0 +1,44 @@
1
+ const CLAUDE_TO_GEMINI_MODEL = {
2
+ haiku: "gemini-2.5-flash-lite",
3
+ sonnet: "gemini-2.5-flash",
4
+ opus: "gemini-2.5-pro",
5
+ };
6
+ export function extractModelFromFrontmatter(content) {
7
+ const lines = content.split("\n");
8
+ let inFrontmatter = false;
9
+ for (const line of lines) {
10
+ if (line.trim() === "---") {
11
+ if (!inFrontmatter) {
12
+ inFrontmatter = true;
13
+ continue;
14
+ }
15
+ break;
16
+ }
17
+ if (inFrontmatter) {
18
+ const match = line.match(/^model:\s*(.+)$/);
19
+ if (match) {
20
+ const claudeModel = match[1].trim();
21
+ return CLAUDE_TO_GEMINI_MODEL[claudeModel] ?? null;
22
+ }
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ export function stripFrontmatter(content) {
28
+ const lines = content.split("\n");
29
+ let inFrontmatter = false;
30
+ let frontmatterEnd = 0;
31
+ for (let i = 0; i < lines.length; i++) {
32
+ if (lines[i].trim() === "---") {
33
+ if (!inFrontmatter) {
34
+ inFrontmatter = true;
35
+ continue;
36
+ }
37
+ else {
38
+ frontmatterEnd = i + 1;
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ return lines.slice(frontmatterEnd).join("\n");
44
+ }
@@ -0,0 +1,9 @@
1
+ export declare const DEFAULT_GEMINI_MODEL = "gemini-2.5-flash";
2
+ export declare const DEFAULT_TOOLS: string[];
3
+ export interface ExecuteGeminiOptions {
4
+ prompt: string;
5
+ model?: string;
6
+ allowedTools?: string;
7
+ pipeContent?: string;
8
+ }
9
+ export declare function executeGemini(options: ExecuteGeminiOptions): Promise<string>;
@@ -0,0 +1,45 @@
1
+ import { spawn } from "child_process";
2
+ export const DEFAULT_GEMINI_MODEL = "gemini-2.5-flash";
3
+ export const DEFAULT_TOOLS = ["read_file", "write_file"];
4
+ export async function executeGemini(options) {
5
+ const { prompt, model = DEFAULT_GEMINI_MODEL, allowedTools, pipeContent } = options;
6
+ return new Promise((resolve, reject) => {
7
+ const args = ["-s", "--model", model, "--output-format", "json"];
8
+ const userTools = allowedTools ? allowedTools.split(",").map(t => t.trim()) : [];
9
+ const allTools = [...new Set([...DEFAULT_TOOLS, ...userTools])];
10
+ args.push("--allowed-tools", allTools.join(","));
11
+ args.push("-p", prompt);
12
+ console.error(`Executing: gemini ${args.join(" ")}`);
13
+ const proc = spawn("gemini", args, {
14
+ stdio: [pipeContent ? "pipe" : "ignore", "pipe", "pipe"],
15
+ });
16
+ if (pipeContent && proc.stdin) {
17
+ proc.stdin.write(pipeContent);
18
+ proc.stdin.end();
19
+ }
20
+ let stdout = "";
21
+ let stderr = "";
22
+ proc.stdout?.on("data", (data) => {
23
+ stdout += data.toString();
24
+ });
25
+ proc.stderr?.on("data", (data) => {
26
+ stderr += data.toString();
27
+ });
28
+ proc.on("close", (code) => {
29
+ if (code !== 0) {
30
+ reject(new Error(`gemini exited with code ${code}: ${stderr}`));
31
+ return;
32
+ }
33
+ try {
34
+ const json = JSON.parse(stdout);
35
+ resolve(json.response ?? stdout);
36
+ }
37
+ catch {
38
+ resolve(stdout);
39
+ }
40
+ });
41
+ proc.on("error", (err) => {
42
+ reject(new Error(`Failed to spawn gemini: ${err.message}`));
43
+ });
44
+ });
45
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@brenoxp/gemini-mcp",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "MCP server that wraps Gemini CLI for use with Claude Code and other MCP clients",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "gemini-mcp": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "npm run build",
19
+ "start": "node dist/index.js",
20
+ "dev": "npx tsx src/index.ts",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "gemini",
28
+ "claude",
29
+ "ai",
30
+ "llm",
31
+ "model-context-protocol",
32
+ "claude-code",
33
+ "gemini-cli"
34
+ ],
35
+ "author": "Breno Pinto",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/brenoxp/gemini-mcp.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/brenoxp/gemini-mcp/issues"
43
+ },
44
+ "homepage": "https://github.com/brenoxp/gemini-mcp#readme",
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.0.0",
47
+ "zod": "^4.3.5"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.0.0",
51
+ "typescript": "^5.7.0",
52
+ "vitest": "^4.0.16"
53
+ },
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }