gemini-mcp-cli 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: gemini-mcp-cli
3
+ Version: 1.0.0
4
+ Summary: FastMCP server wrapping Google's Gemini CLI — use Gemini models from any MCP client
5
+ Project-URL: Homepage, https://github.com/jxsprt/gemini-mcp-server
6
+ Project-URL: Repository, https://github.com/jxsprt/gemini-mcp-server
7
+ Project-URL: Issues, https://github.com/jxsprt/gemini-mcp-server/issues
8
+ Author-email: Jaspreet Singh <jaspreetsinghintp@gmail.com>
9
+ License: MIT
10
+ Keywords: fastmcp,gemini,gemini-cli,mcp,mcp-server
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: fastmcp>=3.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Gemini CLI MCP Server
27
+
28
+ [![CI](https://github.com/jxsprt/gemini-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/jxsprt/gemini-mcp-server/actions/workflows/ci.yml)
29
+ [![PyPI](https://img.shields.io/badge/PyPI-pip%20install%20gemini--mcp--server-blue)](https://pypi.org/project/gemini-mcp-server/)
30
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
31
+ [![PR to Hermes Agent](https://img.shields.io/badge/PR-Hermes%20Agent-%23NouResearch?label=PR)](https://github.com/NousResearch/hermes-agent/pull/22878)
32
+
33
+ A lightweight [FastMCP](https://gofastmcp.com) server that wraps Google's [Gemini CLI](https://github.com/google-gemini/gemini-cli) as MCP tools. Works with any MCP client — Hermes Agent, Claude Code, Claude Desktop, Cursor, etc.
34
+
35
+ ## Prerequisites
36
+
37
+ - **Node.js** — for the Gemini CLI
38
+ - **Python 3.11+** — for the MCP server
39
+ - **Gemini CLI** — installed and authenticated
40
+
41
+ ```bash
42
+ npm install -g @google/gemini-cli
43
+ gemini --version # Verify install
44
+ gemini # Run once to complete OAuth login
45
+ ```
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ # Clone the repo
51
+ git clone https://github.com/jxsprt/gemini-mcp-server.git
52
+ cd gemini-mcp-server
53
+
54
+ # Create venv and install deps
55
+ python3 -m venv .venv
56
+ .venv/bin/pip install -r requirements.txt
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ Add to your MCP client's config:
62
+
63
+ ### Hermes Agent (`~/.hermes/config.yaml`)
64
+
65
+ ```yaml
66
+ mcp_servers:
67
+ gemini-cli:
68
+ command: "/path/to/gemini-mcp-server/.venv/bin/python3"
69
+ args: ["/path/to/gemini-mcp-server/server.py"]
70
+ timeout: 240
71
+ ```
72
+
73
+ Restart your Hermes gateway. Tools will be available as `mcp_gemini_cli_*`.
74
+
75
+ ### Claude Desktop (`claude_desktop_config.json`)
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "gemini-cli": {
81
+ "command": "/path/to/gemini-mcp-server/.venv/bin/python3",
82
+ "args": ["/path/to/gemini-mcp-server/server.py"]
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### Claude Code
89
+
90
+ ```bash
91
+ fastmcp install claude-code /path/to/gemini-mcp-server/server.py
92
+ ```
93
+
94
+ ### Cursor
95
+
96
+ ```bash
97
+ fastmcp install cursor /path/to/gemini-mcp-server/server.py -e .
98
+ ```
99
+
100
+ ## Tools
101
+
102
+ ### `gemini_prompt`
103
+
104
+ Send any prompt to Gemini CLI non-interactively. Supports model selection and JSON output.
105
+
106
+ **Safe by default** — uses `--approval-mode auto-edit` (auto-approves file edits, prompts for shell). Pass `dangerous=true` for full `yolo` mode.
107
+
108
+ ```python
109
+ gemini_prompt(prompt="Explain TCP handshake", model="gemini-2.5-flash")
110
+ gemini_prompt(prompt="Refactor this module", dangerous=True) # full auto-approval
111
+ ```
112
+
113
+ | Parameter | Type | Required | Default | Description |
114
+ |-----------|------|----------|---------|-------------|
115
+ | `prompt` | string | ✅ | — | Prompt text (max ~100K chars) |
116
+ | `model` | string | ❌ | CLI default | Model name (e.g., `gemini-3-flash-preview`) |
117
+ | `output_format` | string | ❌ | `text` | `text`, `json`, or `stream-json` |
118
+ | `dangerous` | bool | ❌ | `false` | Use `--approval-mode yolo` (auto-approves shell) |
119
+
120
+ ### `gemini_plan`
121
+
122
+ Read-only audit and review mode. Uses `--approval-mode plan` — **guarantees no file mutations or shell execution**. Safe for whole-repo analysis and code reviews.
123
+
124
+ ```python
125
+ gemini_plan(prompt="Review this auth module for security issues")
126
+ ```
127
+
128
+ | Parameter | Type | Required | Default | Description |
129
+ |-----------|------|----------|---------|-------------|
130
+ | `prompt` | string | ✅ | — | Analysis question or review request |
131
+ | `model` | string | ❌ | CLI default | Gemini model |
132
+ | `include_directories` | string | ❌ | — | Comma-separated additional dirs |
133
+
134
+ ### `gemini_version`
135
+
136
+ Returns the installed Gemini CLI version.
137
+
138
+ ```python
139
+ gemini_version() # → "0.41.2"
140
+ ```
141
+
142
+ ## Verification
143
+
144
+ ```bash
145
+ cd /path/to/gemini-mcp-server
146
+ .venv/bin/python -c "
147
+ import server
148
+ print('Version:', server.gemini_version())
149
+ print('Prompt:', server.gemini_prompt('Say hello in one word'))
150
+ print('Plan:', server.gemini_plan('What tools does this server have?'))
151
+ "
152
+ ```
153
+
154
+ ## How It Works
155
+
156
+ The server shells out to `gemini -p "prompt" --approval-mode yolo` for each tool call. It does NOT use the Google Gen AI Python SDK — it goes through the Gemini CLI binary (OAuth-authenticated).
157
+
158
+ **Key design decisions:**
159
+ - **Stdio transport** — runs as a subprocess of your MCP client
160
+ - **No hardcoded paths** — discovers `gemini` on PATH
161
+ - **No API keys in config** — uses the CLI's existing OAuth session
162
+ - **Retry-friendly** — generous timeouts (120s default, 240s for plan mode) to handle Gemini's capacity retries
163
+
164
+ ## Notes
165
+
166
+ - **Capacity errors**: Gemini's reasoning models can hit rate limits. The CLI retries up to 7 times with backoff. If it fails, try again later or use a different model.
167
+ - **Plan mode is safe**: `--approval-mode plan` explicitly prevents the agent from writing files or executing shell commands. Read-only, guaranteed.
168
+ - **1M token context**: Gemini CLI supports the full 1M token context window of Gemini models.
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,7 @@
1
+ gemini_mcp_server/__init__.py,sha256=EYzg5PB1KqwRyAaQ7-cR3UdyragGjktce-zvWbX5U2M,225
2
+ gemini_mcp_server/__main__.py,sha256=dHOSZ1BdFFbUPYxDhEGuiU5i_GmkxtEZW8olFqr8CIU,129
3
+ gemini_mcp_server/server.py,sha256=nwd2s1f-6jxrhTM0P-WJTV0Jo7M6q__ArNcsi_HERZs,9282
4
+ gemini_mcp_cli-1.0.0.dist-info/METADATA,sha256=buaUIkYTShMKN0XbA-QJ-NCzGZ3NzEimLwheNuvlmvU,6010
5
+ gemini_mcp_cli-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ gemini_mcp_cli-1.0.0.dist-info/entry_points.txt,sha256=bWFu_7C87aMqg0wsNdX1vq20Ww77eYpYjHZu2UxqVcA,54
7
+ gemini_mcp_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gemini-mcp = gemini_mcp_server:main
@@ -0,0 +1,5 @@
1
+ """gemini-mcp-server — FastMCP server wrapping Google's Gemini CLI."""
2
+
3
+ from .server import mcp, gemini_prompt, gemini_plan, gemini_version, main
4
+
5
+ __all__ = ["mcp", "gemini_prompt", "gemini_plan", "gemini_version", "main"]
@@ -0,0 +1,6 @@
1
+ """Allow running as: python -m gemini_mcp_server"""
2
+
3
+ from gemini_mcp_server import mcp
4
+
5
+ if __name__ == "__main__":
6
+ mcp.run()
@@ -0,0 +1,263 @@
1
+ """
2
+ gemini-mcp-server — FastMCP server wrapping Google's Gemini CLI.
3
+
4
+ Exposes Gemini CLI as MCP tools consumable by any MCP client
5
+ (Hermes Agent, Claude Code, Claude Desktop, Cursor, etc.).
6
+
7
+ Powered by FastMCP (https://gofastmcp.com).
8
+
9
+ Tools:
10
+ - gemini_prompt → Send a prompt to Gemini CLI (non-interactive, full power)
11
+ - gemini_plan → Read-only audit/review mode (--approval-mode plan)
12
+ - gemini_version → Get Gemini CLI version
13
+ """
14
+
15
+ import logging
16
+ import os
17
+ import shutil
18
+ import subprocess
19
+ from typing import Optional
20
+
21
+ from fastmcp import FastMCP
22
+
23
+ # ── Logging ────────────────────────────────────────────────────────────────
24
+
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format="%(asctime)s [%(levelname)s] %(message)s",
28
+ stream=__import__("sys").stderr,
29
+ )
30
+ logger = logging.getLogger("gemini-mcp")
31
+
32
+ # ── Server ─────────────────────────────────────────────────────────────────
33
+
34
+ mcp = FastMCP("Gemini CLI MCP Server")
35
+
36
+ # ── Configuration (env vars with sensible defaults) ────────────────────────
37
+
38
+ _GEMINI_CLI_PATH = os.environ.get("GEMINI_CLI_PATH")
39
+ DEFAULT_TIMEOUT = int(os.environ.get("GEMINI_TIMEOUT", "120"))
40
+ PLAN_TIMEOUT = int(os.environ.get("GEMINI_PLAN_TIMEOUT", "240"))
41
+ MAX_PROMPT_LENGTH = int(os.environ.get("GEMINI_MAX_PROMPT", "100000"))
42
+ HTTP_ENABLED = os.environ.get("GEMINI_MCP_HTTP", "").lower() in ("1", "true", "yes")
43
+
44
+
45
+ def _get_gemini_bin() -> str:
46
+ """Locate the gemini CLI binary. Returns full path or raises RuntimeError."""
47
+ if _GEMINI_CLI_PATH:
48
+ path = _GEMINI_CLI_PATH
49
+ if os.path.isfile(path) and os.access(path, os.X_OK):
50
+ return path
51
+ raise RuntimeError(
52
+ f"GEMINI_CLI_PATH set to '{path}' but file not found or not executable. "
53
+ "Fix the path or unset it to auto-discover."
54
+ )
55
+ path = shutil.which("gemini")
56
+ if path is None:
57
+ raise RuntimeError(
58
+ "Gemini CLI not found on PATH. Install it with: npm install -g @google/gemini-cli, "
59
+ "or set the GEMINI_CLI_PATH environment variable."
60
+ )
61
+ return path
62
+
63
+
64
+ # ── Helper ─────────────────────────────────────────────────────────────────
65
+
66
+
67
+ def _run_gemini(args: list[str], timeout: int = DEFAULT_TIMEOUT) -> dict:
68
+ """Run `gemini <args>` and return structured result.
69
+
70
+ Returns dict with keys: success, output, error.
71
+ """
72
+ gemini_bin = _get_gemini_bin()
73
+ cmd = [gemini_bin] + args
74
+ logger.info("Running: %s", " ".join(cmd))
75
+
76
+ try:
77
+ result = subprocess.run(
78
+ cmd,
79
+ capture_output=True,
80
+ text=True,
81
+ timeout=timeout,
82
+ )
83
+ stdout = result.stdout.strip()
84
+ stderr = result.stderr.strip()
85
+
86
+ if result.returncode != 0:
87
+ logger.warning("Non-zero exit %d: %s", result.returncode, stderr[:200])
88
+ return {
89
+ "success": False,
90
+ "output": stdout,
91
+ "error": stderr or f"Exit code {result.returncode}",
92
+ }
93
+
94
+ logger.info("Success (%d chars)", len(result.stdout))
95
+ return {"success": True, "output": stdout, "error": None}
96
+
97
+ except subprocess.TimeoutExpired:
98
+ logger.warning("Timed out after %ds", timeout)
99
+ return {
100
+ "success": False,
101
+ "output": "",
102
+ "error": f"Command timed out after {timeout}s",
103
+ }
104
+ except FileNotFoundError:
105
+ msg = f"Gemini CLI not found at {gemini_bin}"
106
+ logger.error(msg)
107
+ return {"success": False, "output": "", "error": msg}
108
+ except Exception as e:
109
+ logger.exception("Unexpected error")
110
+ return {"success": False, "output": "", "error": str(e)}
111
+
112
+
113
+ # ── Tools ──────────────────────────────────────────────────────────────────
114
+
115
+
116
+ @mcp.tool(
117
+ name="gemini_prompt",
118
+ description=(
119
+ "Send a prompt to Gemini CLI in non-interactive mode. "
120
+ "The full power of Gemini models — coding, research, Q&A, analysis. "
121
+ "Supports optional model selection and output format."
122
+ ),
123
+ )
124
+ def gemini_prompt(
125
+ prompt: str,
126
+ model: Optional[str] = None,
127
+ output_format: Optional[str] = None,
128
+ dangerous: bool = False,
129
+ ) -> str:
130
+ """Execute a prompt via Gemini CLI.
131
+
132
+ By default uses --approval-mode auto-edit (safe for file edits in working dir).
133
+ Pass dangerous=True to use --approval-mode yolo (auto-approves ALL actions
134
+ including arbitrary shell commands).
135
+
136
+ Args:
137
+ prompt: The prompt text to send (max ~100K chars).
138
+ model: Gemini model to use (e.g., gemini-3-flash-preview, gemini-2.5-pro).
139
+ Defaults to gemini CLI's built-in default.
140
+ output_format: 'text' (default), 'json', or 'stream-json'.
141
+ JSON includes token/cost stats in the envelope.
142
+ dangerous: If True, uses --approval-mode yolo (auto-approves shell cmds).
143
+ Defaults to False (--approval-mode auto-edit).
144
+
145
+ Returns:
146
+ Gemini CLI output as a string.
147
+ """
148
+ if len(prompt) > MAX_PROMPT_LENGTH:
149
+ prompt = prompt[:MAX_PROMPT_LENGTH]
150
+
151
+ approval = "yolo" if dangerous else "auto_edit"
152
+ args = ["-p", prompt, "--approval-mode", approval]
153
+
154
+ if model:
155
+ args += ["-m", model]
156
+ if output_format:
157
+ args += ["-o", output_format]
158
+
159
+ result = _run_gemini(args, timeout=DEFAULT_TIMEOUT)
160
+
161
+ if result["success"]:
162
+ return result["output"] or "(empty response)"
163
+ else:
164
+ error_msg = result["error"] or "unknown error"
165
+ return f"Error: {error_msg}"
166
+
167
+
168
+ @mcp.tool(
169
+ name="gemini_plan",
170
+ description=(
171
+ "Read-only audit, code review, and research via Gemini CLI. "
172
+ "Uses --approval-mode plan which guarantees NO file mutations or shell execution. "
173
+ "Safe for whole-repo analysis, code reviews, vulnerability assessments, "
174
+ "architecture audits, and deep research."
175
+ ),
176
+ )
177
+ def gemini_plan(
178
+ prompt: str,
179
+ model: Optional[str] = None,
180
+ include_directories: Optional[str] = None,
181
+ ) -> str:
182
+ """Run Gemini CLI in read-only plan mode — no mutations possible.
183
+
184
+ Args:
185
+ prompt: Analysis question or review request.
186
+ model: Gemini model (optional, defaults to gemini CLI default).
187
+ include_directories: Comma-separated additional dirs to include in workspace.
188
+ Only includes subdirectories of the current working directory
189
+ for safety.
190
+
191
+ Returns:
192
+ Gemini CLI's analysis/plan output.
193
+ """
194
+ if len(prompt) > MAX_PROMPT_LENGTH:
195
+ prompt = prompt[:MAX_PROMPT_LENGTH]
196
+
197
+ args = ["-p", prompt, "--approval-mode", "plan"]
198
+
199
+ if model:
200
+ args += ["-m", model]
201
+
202
+ # Validate include_directories — restrict to CWD subdirectories
203
+ if include_directories:
204
+ cwd = os.getcwd()
205
+ validated = []
206
+ for d in include_directories.split(","):
207
+ d = d.strip()
208
+ abs_d = os.path.abspath(os.path.join(cwd, d)) if not os.path.isabs(d) else d
209
+ if abs_d.startswith(cwd.rstrip("/") + "/") or abs_d == cwd:
210
+ validated.append(d)
211
+ else:
212
+ logger.warning("Ignored out-of-workspace path: %s", d)
213
+ if validated:
214
+ args += ["--include-directories", ",".join(validated)]
215
+
216
+ result = _run_gemini(args, timeout=PLAN_TIMEOUT)
217
+
218
+ if result["success"]:
219
+ return result["output"] or "(empty response)"
220
+ else:
221
+ error_msg = result["error"] or "unknown error"
222
+ return f"Error: {error_msg}"
223
+
224
+
225
+ @mcp.tool(
226
+ name="gemini_version",
227
+ description="Get the installed Gemini CLI version.",
228
+ )
229
+ def gemini_version() -> str:
230
+ """Get Gemini CLI version info."""
231
+ try:
232
+ gemini_bin = _get_gemini_bin()
233
+ result = subprocess.run(
234
+ [gemini_bin, "--version"],
235
+ capture_output=True,
236
+ text=True,
237
+ timeout=10,
238
+ )
239
+ output = (result.stdout or result.stderr or "").strip()
240
+ return output or "(no version info)"
241
+ except subprocess.TimeoutExpired:
242
+ return "Error: Command timed out after 10s"
243
+ except RuntimeError as e:
244
+ return f"Error: {e}"
245
+ except Exception as e:
246
+ return f"Error: {e}"
247
+
248
+
249
+ # ── Entrypoint ─────────────────────────────────────────────────────────────
250
+
251
+
252
+ def main() -> None:
253
+ """CLI entry point for pip-installed package."""
254
+ if HTTP_ENABLED:
255
+ logger.info("Starting HTTP transport on port 8000")
256
+ mcp.run(transport="http")
257
+ else:
258
+ logger.info("Starting stdio transport")
259
+ mcp.run()
260
+
261
+
262
+ if __name__ == "__main__":
263
+ main()