Skip to content

Commit 542e15c

Browse files
committed
adding MCPs for gemini
1 parent 3f879af commit 542e15c

File tree

11 files changed

+475
-22
lines changed

11 files changed

+475
-22
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Available coders:
2323
metacoder "Write a Python function to calculate fibonacci numbers" -c claude -w my-scripts/
2424
...
2525

26+
# With custom instructions
27+
metacoder "Refactor this code" -c claude --instructions coding_guidelines.md
28+
...
29+
2630
# Using MCPs
2731
metacoder "Fix issue 1234" -w path/to/my-repo --mcp-collection github_mcps.yaml
2832
...

docs/configuration.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,43 @@ For detailed information about MCP configuration and usage, see the [MCP Support
4747
metacoder "Your prompt" --config myconfig.yaml
4848
```
4949

50+
## Custom Instructions
51+
52+
You can provide custom instructions to any coder using the `--instructions` option:
53+
54+
```bash
55+
metacoder "Your prompt" --instructions custom_instructions.md
56+
```
57+
58+
The instructions file is loaded and passed to the coder's instruction handling mechanism. Each coder will use these instructions according to its own configuration:
59+
60+
- **Claude**: Instructions are written to `CLAUDE.md`
61+
- **Goose**: Instructions are written to `.goosehints`
62+
- **Gemini**: Instructions are written to `GEMINI.md`
63+
- **Other coders**: Check the specific coder documentation
64+
65+
Example instructions file:
66+
67+
```markdown
68+
# Custom Instructions
69+
70+
You are an expert Python developer following these guidelines:
71+
1. Use type hints for all functions
72+
2. Write comprehensive docstrings
73+
3. Follow PEP 8 style guidelines
74+
4. Include unit tests for all new functions
75+
```
76+
77+
You can combine instructions with other configuration options:
78+
79+
```bash
80+
metacoder "Refactor this code" \
81+
--coder claude \
82+
--instructions style_guide.md \
83+
--config claude_config.yaml \
84+
--mcp-collection tools.yaml
85+
```
86+
5087
## Environment Variables
5188

5289
Config files support environment variable expansion:

docs/getting-started.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ metacoder "Analyze this project" --workdir ./myproject
5151
# Use configuration file
5252
metacoder "Build feature X" --config config.yaml
5353

54+
# Use custom instructions file
55+
metacoder "Review code" --instructions guidelines.md
56+
5457
# Use MCP extensions
5558
metacoder "Search for papers on LLMs" --mcp-collection research_mcps.yaml
5659
```

docs/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Available coders:
2323
metacoder "Write a Python function to calculate fibonacci numbers" -c claude -w my-scripts/
2424
...
2525

26+
# With custom instructions
27+
metacoder "Refactor this code" -c claude --instructions coding_guidelines.md
28+
...
29+
2630
# Using MCPs
2731
metacoder "Fix issue 1234" -w path/to/my-repo --mcp-collection github_mcps.yaml
2832
...
@@ -57,6 +61,7 @@ Mungall, C. (2025, May 28). How to make your KG interoperable: Ontologies and Se
5761

5862
- Unified CLI for all supported coders
5963
- Consistent [configuration](configuration.md) format (YAML-based)
64+
- Custom instructions support via `--instructions` flag
6065
- Unified [MCP configuration](mcps.md)
6166
- Standardized working directory management
6267

src/metacoder/coders/dummy.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
from pathlib import Path
12
from metacoder.coders.base_coder import BaseCoder, CoderConfigObject, CoderOutput, ToolUse
3+
from metacoder.configuration import ConfigFileRole
24

35

46
class DummyCoder(BaseCoder):
@@ -18,14 +20,34 @@ def supports_mcp(cls) -> bool:
1820
"""DummyCoder supports MCP for testing purposes."""
1921
return True
2022

23+
@classmethod
24+
def default_config_paths(cls) -> dict[Path, ConfigFileRole]:
25+
return {
26+
Path("INSTRUCTIONS.md"): ConfigFileRole.PRIMARY_INSTRUCTION,
27+
}
28+
2129
def default_config_objects(self) -> list[CoderConfigObject]:
2230
return []
2331

2432
def run(self, input_text: str) -> CoderOutput:
33+
# Check if instructions were set
34+
instructions_content = None
35+
if self.config_objects:
36+
for obj in self.config_objects:
37+
if obj.relative_path == "INSTRUCTIONS.md" or obj.relative_path == str(Path("INSTRUCTIONS.md")):
38+
instructions_content = obj.content
39+
break
40+
41+
# Create response based on whether instructions exist
42+
if instructions_content:
43+
response = f"Instructions loaded: {instructions_content}\nProcessing: {input_text}"
44+
else:
45+
response = f"you said: {input_text}"
46+
2547
output = CoderOutput(
26-
stdout="you said: " + input_text,
48+
stdout=response,
2749
stderr="",
28-
result_text="you said: " + input_text,
50+
result_text=response,
2951
)
3052

3153
# Add fake tool uses if input mentions tools, MCP, or specific services

src/metacoder/coders/gemini.py

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import shutil
66
import re
7+
from typing import Any
78

89
from metacoder.coders.base_coder import (
910
BaseCoder,
@@ -12,6 +13,7 @@
1213
FileType,
1314
change_directory,
1415
)
16+
from metacoder.configuration import ConfigFileRole, MCPConfig, MCPType
1517

1618

1719
logger = logging.getLogger(__name__)
@@ -21,23 +23,106 @@ class GeminiCoder(BaseCoder):
2123
"""
2224
Google Gemini AI assistant integration.
2325
24-
Note: Requires gemini CLI to be installed.
26+
Gemini-specific configuration:
27+
28+
You can provide the following files in your configuration directory:
29+
30+
- `GEMINI.md` - Primary instructions for the assistant
31+
- `.gemini/settings.json` - Configuration including MCP servers
32+
- `.gemini/commands/` - Custom commands directory
33+
34+
MCP Support:
35+
36+
Gemini CLI supports MCP (Model Context Protocol) servers through the
37+
mcpServers configuration in .gemini/settings.json. When MCPs are configured
38+
through Metacoder, they will be automatically added to the settings file.
39+
40+
The Gemini CLI expects MCP servers to be configured in the following format:
41+
{
42+
"mcpServers": {
43+
"server_name": {
44+
"command": "npx",
45+
"args": ["-y", "@modelcontextprotocol/server-name"],
46+
"env": {"API_KEY": "${API_KEY}"},
47+
"timeout": 30000
48+
}
49+
}
50+
}
51+
52+
Note: Requires gemini CLI to be installed (npm install -g @google/gemini-cli).
2553
"""
2654

2755
@classmethod
2856
def is_available(cls) -> bool:
2957
"""Check if gemini command is available."""
3058
return shutil.which("gemini") is not None
3159

60+
@classmethod
61+
def supports_mcp(cls) -> bool:
62+
"""GeminiCoder supports MCP extensions."""
63+
return True
64+
65+
@classmethod
66+
def default_config_paths(cls) -> dict[Path, ConfigFileRole]:
67+
return {
68+
Path("GEMINI.md"): ConfigFileRole.PRIMARY_INSTRUCTION,
69+
Path(".gemini/settings.json"): ConfigFileRole.CONFIG,
70+
Path(".gemini/commands"): ConfigFileRole.CONFIG,
71+
}
72+
73+
def mcp_config_to_gemini_format(self, mcp: MCPConfig) -> dict[str, Any]:
74+
"""Convert MCPConfig to Gemini's MCP server format."""
75+
server_config: dict[str, Any] = {}
76+
77+
# For stdio type MCPs
78+
if mcp.type == MCPType.STDIO and mcp.command:
79+
server_config["command"] = mcp.command
80+
if mcp.args:
81+
server_config["args"] = mcp.args
82+
if mcp.env:
83+
server_config["env"] = mcp.env
84+
# Add optional timeout if needed
85+
server_config["timeout"] = 30000 # 30 seconds default
86+
87+
# For HTTP type MCPs
88+
elif mcp.type == MCPType.HTTP:
89+
raise NotImplementedError(
90+
"HTTP MCPs are not supported for Gemini CLI yet"
91+
)
92+
93+
return server_config
94+
3295
def default_config_objects(self) -> list[CoderConfigObject]:
33-
"""Default configuration for Gemini."""
34-
return [
35-
CoderConfigObject(
36-
file_type=FileType.YAML,
37-
relative_path=".codex/config.yaml",
38-
content={"model": "gemini-2.5-pro", "provider": "google"},
96+
"""Generate config objects including MCP configuration."""
97+
config_objects = []
98+
99+
# Create .gemini/settings.json if we have MCP extensions
100+
settings_content: dict[str, Any] = {}
101+
102+
# Add MCP servers configuration if extensions are present
103+
if self.config and self.config.extensions:
104+
mcp_servers = {}
105+
for mcp in self.config.extensions:
106+
if mcp.enabled:
107+
mcp_servers[mcp.name] = self.mcp_config_to_gemini_format(mcp)
108+
109+
if mcp_servers:
110+
settings_content["mcpServers"] = mcp_servers
111+
112+
# Add settings.json if we have content to write
113+
if settings_content:
114+
config_objects.append(
115+
CoderConfigObject(
116+
file_type=FileType.JSON,
117+
relative_path=".gemini/settings.json",
118+
content=settings_content,
119+
)
39120
)
40-
]
121+
122+
# Add GEMINI.md if present in config
123+
# This could contain instructions specific to the task
124+
125+
return config_objects
41126

42127
def run(self, input_text: str) -> CoderOutput:
43128
"""
@@ -50,14 +135,15 @@ def run(self, input_text: str) -> CoderOutput:
50135
# Gemini expects HOME to be current directory for config
51136
env["HOME"] = "."
52137

53-
# Validate required files
54-
if not Path("./.codex/config.yaml").exists():
55-
raise ValueError("Codex config.yaml file not found")
56-
57138
text = self.expand_prompt(input_text)
58-
command = ["gemini", "-d", "-m", "gemini-2.5-pro", "-y", "-p", text]
59-
60-
logger.info(f"🤖 Running command: {' '.join(command)}")
139+
140+
# Build the command
141+
# The gemini CLI uses conversational interface, so we need to handle it differently
142+
# For now, we'll use echo to pipe the prompt
143+
command = ["sh", "-c", f'echo "{text}" | gemini']
144+
145+
logger.info("💎 Running command: gemini with prompt")
146+
logger.debug(f"💎 Full command: {' '.join(command)}")
61147
start_time = time.time()
62148

63149
try:
@@ -72,7 +158,7 @@ def run(self, input_text: str) -> CoderOutput:
72158
)
73159

74160
end_time = time.time()
75-
print(f"🤖 Command took {end_time - start_time} seconds")
161+
logger.info(f"💎 Command took {end_time - start_time} seconds")
76162

77163
# Parse the output
78164
ao = CoderOutput(stdout=result.stdout, stderr=result.stderr)

src/metacoder/configuration.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ class AIModelProvider(BaseModel):
5151
An AI model provider is a provider of an AI model.
5252
"""
5353

54-
name: str
55-
api_key: str | None = None
56-
metadata: dict[str, Any] = {}
54+
name: str = Field(..., description="Name of the model provider")
55+
api_key: str | None = Field(None, description="API key for the model provider")
56+
metadata: dict[str, Any] = Field({}, description="Metadata for the model provider")
57+
base_url: str | None = Field(None, description="Base URL for the model provider")
5758

5859

5960
class AIModelConfig(BaseModel):

src/metacoder/metacoder.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ def cli(ctx):
243243
)
244244
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
245245
@click.option("--quiet", "-q", is_flag=True, help="Quiet mode")
246+
@click.option(
247+
"--instructions",
248+
"-i",
249+
type=click.Path(exists=True),
250+
help="Path to instructions file to load and pass to the coder",
251+
)
246252
def run(
247253
prompt: str,
248254
coder: str,
@@ -255,6 +261,7 @@ def run(
255261
model: Optional[str],
256262
verbose: bool,
257263
quiet: bool,
264+
instructions: Optional[str],
258265
):
259266
"""
260267
Run a prompt with the specified coder.
@@ -271,6 +278,10 @@ def run(
271278
# Use specific coder with config
272279
metacoder run "Fix the bug in main.py" --coder goose --config goose_config.yaml
273280
281+
\b
282+
# Use custom instructions from a file
283+
metacoder run "Write tests" --coder claude --instructions instructions.md
284+
274285
\b
275286
# Use MCP collection
276287
metacoder run "Search for papers on LLMs" --mcp-collection mcps.yaml
@@ -283,6 +294,10 @@ def run(
283294
# Load MCPs from registry
284295
metacoder run "Fetch a webpage" --registry metacoder.basics --enable-mcp fetch
285296
297+
\b
298+
# Combine instructions with MCP configuration
299+
metacoder run "Analyze data" --instructions guide.md --mcp-collection tools.yaml
300+
286301
\b
287302
# Load all MCPs from registry
288303
metacoder run "Process PDF" --registry metacoder --enable-mcp pdfreader
@@ -419,6 +434,16 @@ def run(
419434
except Exception as e:
420435
raise click.ClickException(f"Failed to create coder: {e}")
421436

437+
# Load instructions if provided
438+
if instructions:
439+
try:
440+
with open(instructions, "r") as f:
441+
instructions_content = f.read()
442+
coder_instance.set_instructions(instructions_content)
443+
click.echo(f"📋 Loaded instructions from: {instructions}")
444+
except Exception as e:
445+
raise click.ClickException(f"Failed to load instructions file: {e}")
446+
422447
# Run the coder
423448
click.echo(f"🚀 Running prompt: {prompt}")
424449
result = coder_instance.run(prompt)

0 commit comments

Comments
 (0)