Skip to content

Commit c875ece

Browse files
committed
🔧 Make repository parameter required in MCP tools
Enforce explicit repository path specification for MCP tools This change improves reliability when Git-Iris is used through MCP clients: - Add repository parameter validation across all MCP tools - Update parameter description from optional to required - Remove fallback to default repo in resolve_git_repo - Add validation to ensure local paths exist and are git repos - Update documentation with examples for both local and remote repos - Add explanation of why repository parameter is required This addresses reliability issues with clients like Cursor that don't provide consistent project path information.
1 parent 62e698f commit c875ece

File tree

7 files changed

+149
-57
lines changed

7 files changed

+149
-57
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,34 @@ git-iris serve --transport sse --port 3077
281281

282282
This allows you to use Git-Iris features directly from Claude, Cursor, VSCode, and other MCP-compatible tools. See [MCP.md](docs/MCP.md) for detailed documentation.
283283

284+
#### MCP Tool Parameters
285+
286+
All MCP tools **require** the `repository` parameter, which must be a local project path or a remote repository URL. This is necessary because some clients (like Cursor) do not reliably provide the project root.
287+
288+
**Example (local path):**
289+
```json
290+
{
291+
"from": "v1.0.0",
292+
"to": "v2.0.0",
293+
"detail_level": "detailed",
294+
"repository": "/home/bliss/dev/myproject"
295+
}
296+
```
297+
298+
**Example (remote URL):**
299+
```json
300+
{
301+
"from": "v1.0.0",
302+
"to": "v2.0.0",
303+
"detail_level": "detailed",
304+
"repository": "https://github.com/example/repo.git"
305+
}
306+
```
307+
308+
**Why is `repository` required?**
309+
310+
> Due to limitations in some MCP clients, the server cannot reliably infer the project root or repository path. To ensure Git-Iris always operates on the correct repository, you must explicitly specify the `repository` parameter for every tool call. This eliminates ambiguity and ensures your commands are always precise and predictable.
311+
284312
### Interactive Commit Process
285313

286314
The interactive CLI allows you to refine and perfect your commit messages:

docs/MCP.md

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,29 @@ Generate commit messages and perform Git commits.
4343
- `no_verify`: (boolean) Skip verification for commit action
4444
- `preset`: (string) Instruction preset to use
4545
- `custom_instructions`: (string) Custom instructions for the AI
46-
- `repository`: (string, optional) Repository path or URL
46+
- `repository`: (string, required) Repository path (local) or URL (remote). **Required.**
4747

48-
Example:
48+
Example (local path):
4949

5050
```json
5151
{
5252
"auto_commit": true,
5353
"use_gitmoji": true,
5454
"preset": "conventional",
55-
"custom_instructions": "Include the ticket number from JIRA"
55+
"custom_instructions": "Include the ticket number from JIRA",
56+
"repository": "/home/bliss/dev/myproject"
57+
}
58+
```
59+
60+
Example (remote URL):
61+
62+
```json
63+
{
64+
"auto_commit": true,
65+
"use_gitmoji": true,
66+
"preset": "conventional",
67+
"custom_instructions": "Include the ticket number from JIRA",
68+
"repository": "https://github.com/example/repo.git"
5669
}
5770
```
5871

@@ -66,15 +79,27 @@ Generate comprehensive code reviews with options for staged changes, unstaged ch
6679
- `commit_id`: (string) Specific commit to review (hash, branch name, or reference)
6780
- `preset`: (string) Preset instruction set to use for the review
6881
- `custom_instructions`: (string) Custom instructions for the AI
69-
- `repository`: (string, optional) Repository path or URL
82+
- `repository`: (string, required) Repository path (local) or URL (remote). **Required.**
7083

71-
Example:
84+
Example (local path):
7285

7386
```json
7487
{
7588
"preset": "security",
7689
"custom_instructions": "Focus on performance issues",
77-
"include_unstaged": true
90+
"include_unstaged": true,
91+
"repository": "/home/bliss/dev/myproject"
92+
}
93+
```
94+
95+
Example (remote URL):
96+
97+
```json
98+
{
99+
"preset": "security",
100+
"custom_instructions": "Focus on performance issues",
101+
"include_unstaged": true,
102+
"repository": "https://github.com/example/repo.git"
78103
}
79104
```
80105

@@ -88,16 +113,29 @@ Generate a detailed changelog between two Git references.
88113
- `to`: (string) Ending reference (defaults to HEAD if not specified)
89114
- `detail_level`: (string) Level of detail for the changelog (minimal, standard, detailed)
90115
- `custom_instructions`: (string) Custom instructions for the AI
91-
- `repository`: (string, optional) Repository path or URL
116+
- `repository`: (string, required) Repository path (local) or URL (remote). **Required.**
92117

93-
Example:
118+
Example (local path):
94119

95120
```json
96121
{
97122
"from": "v1.0.0",
98123
"to": "v2.0.0",
99124
"detail_level": "detailed",
100-
"custom_instructions": "Group changes by component"
125+
"custom_instructions": "Group changes by component",
126+
"repository": "/home/bliss/dev/myproject"
127+
}
128+
```
129+
130+
Example (remote URL):
131+
132+
```json
133+
{
134+
"from": "v1.0.0",
135+
"to": "v2.0.0",
136+
"detail_level": "detailed",
137+
"custom_instructions": "Group changes by component",
138+
"repository": "https://github.com/example/repo.git"
101139
}
102140
```
103141

@@ -111,19 +149,36 @@ Generate comprehensive release notes between two Git references.
111149
- `to`: (string) Ending reference (defaults to HEAD if not specified)
112150
- `detail_level`: (string) Level of detail for the release notes (minimal, standard, detailed)
113151
- `custom_instructions`: (string) Custom instructions for the AI
114-
- `repository`: (string, optional) Repository path or URL
152+
- `repository`: (string, required) Repository path (local) or URL (remote). **Required.**
153+
154+
Example (local path):
155+
156+
```json
157+
{
158+
"from": "v1.0.0",
159+
"to": "v2.0.0",
160+
"detail_level": "standard",
161+
"custom_instructions": "Highlight breaking changes",
162+
"repository": "/home/bliss/dev/myproject"
163+
}
164+
```
115165

116-
Example:
166+
Example (remote URL):
117167

118168
```json
119169
{
120170
"from": "v1.0.0",
121171
"to": "v2.0.0",
122172
"detail_level": "standard",
123-
"custom_instructions": "Highlight breaking changes"
173+
"custom_instructions": "Highlight breaking changes",
174+
"repository": "https://github.com/example/repo.git"
124175
}
125176
```
126177

178+
## Why is `repository` required?
179+
180+
Due to limitations in some MCP clients (such as Cursor and others), the server cannot reliably infer the project root or repository path. To ensure Git-Iris always operates on the correct repository, you must explicitly specify the `repository` parameter for every tool call. This can be either a local filesystem path (for local projects) or a remote repository URL (for remote operations). This eliminates ambiguity and ensures your commands are always precise and predictable.
181+
127182
## Using Git-Iris with Claude
128183

129184
### Claude Desktop Integration

src/mcp/tools/changelog.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::git::GitRepo;
88
use crate::log_debug;
99
use crate::mcp::tools::utils::{
1010
GitIrisTool, apply_custom_instructions, create_text_result, parse_detail_level,
11-
resolve_git_repo,
11+
resolve_git_repo, validate_repository_parameter,
1212
};
1313

1414
use rmcp::handler::server::tool::cached_schema_for_type;
@@ -37,8 +37,7 @@ pub struct ChangelogTool {
3737
#[serde(default)]
3838
pub custom_instructions: String,
3939

40-
/// Repository path or URL (optional)
41-
#[serde(default)]
40+
/// Repository path (local) or URL (remote). Required.
4241
pub repository: String,
4342
}
4443

@@ -66,13 +65,9 @@ impl GitIrisTool for ChangelogTool {
6665
) -> Result<CallToolResult, anyhow::Error> {
6766
log_debug!("Generating changelog with: {:?}", self);
6867

69-
// Resolve repository based on the repository parameter
70-
let repo_path = if self.repository.trim().is_empty() {
71-
None
72-
} else {
73-
Some(self.repository.as_str())
74-
};
75-
let git_repo = resolve_git_repo(repo_path, git_repo)?;
68+
// Validate repository parameter
69+
validate_repository_parameter(&self.repository)?;
70+
let git_repo = resolve_git_repo(Some(self.repository.as_str()), git_repo)?;
7671
log_debug!("Using repository: {}", git_repo.repo_path().display());
7772

7873
// Parse detail level using shared utility

src/mcp/tools/codereview.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::git::GitRepo;
99
use crate::log_debug;
1010
use crate::mcp::tools::utils::{
1111
GitIrisTool, apply_custom_instructions, create_text_result, resolve_git_repo,
12+
validate_repository_parameter,
1213
};
1314

1415
use rmcp::handler::server::tool::cached_schema_for_type;
@@ -38,8 +39,7 @@ pub struct CodeReviewTool {
3839
#[serde(default)]
3940
pub custom_instructions: String,
4041

41-
/// Repository path or URL (optional)
42-
#[serde(default)]
42+
/// Repository path (local) or URL (remote). Required.
4343
pub repository: String,
4444
}
4545

@@ -67,13 +67,9 @@ impl GitIrisTool for CodeReviewTool {
6767
) -> Result<CallToolResult, anyhow::Error> {
6868
log_debug!("Generating code review with: {:?}", self);
6969

70-
// Resolve repository based on the repository parameter
71-
let repo_path = if self.repository.trim().is_empty() {
72-
None
73-
} else {
74-
Some(self.repository.as_str())
75-
};
76-
let git_repo = resolve_git_repo(repo_path, git_repo)?;
70+
// Validate repository parameter
71+
validate_repository_parameter(&self.repository)?;
72+
let git_repo = resolve_git_repo(Some(self.repository.as_str()), git_repo)?;
7773
log_debug!("Using repository: {}", git_repo.repo_path().display());
7874

7975
// Check if local operations are required without a specific commit

src/mcp/tools/commit.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ use crate::commit::types::format_commit_message;
77
use crate::config::Config as GitIrisConfig;
88
use crate::git::GitRepo;
99
use crate::log_debug;
10-
use crate::mcp::tools::utils::{GitIrisTool, create_text_result, resolve_git_repo};
10+
use crate::mcp::tools::utils::{
11+
GitIrisTool, create_text_result, resolve_git_repo, validate_repository_parameter,
12+
};
1113

1214
use rmcp::handler::server::tool::cached_schema_for_type;
1315
use rmcp::model::{CallToolResult, Tool};
@@ -40,8 +42,7 @@ pub struct CommitTool {
4042
#[serde(default)]
4143
pub custom_instructions: String,
4244

43-
/// Repository path or URL (optional)
44-
#[serde(default)]
45+
/// Repository path (local) or URL (remote). Required.
4546
pub repository: String,
4647
}
4748

@@ -69,13 +70,9 @@ impl GitIrisTool for CommitTool {
6970
) -> Result<CallToolResult, anyhow::Error> {
7071
log_debug!("Processing commit request with: {:?}", self);
7172

72-
// Resolve repository based on the repository parameter
73-
let repo_path = if self.repository.trim().is_empty() {
74-
None
75-
} else {
76-
Some(self.repository.as_str())
77-
};
78-
let git_repo = resolve_git_repo(repo_path, git_repo)?;
73+
// Validate repository parameter
74+
validate_repository_parameter(&self.repository)?;
75+
let git_repo = resolve_git_repo(Some(self.repository.as_str()), git_repo)?;
7976
log_debug!("Using repository: {}", git_repo.repo_path().display());
8077

8178
// Check if we can perform the operation on this repository

src/mcp/tools/releasenotes.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::git::GitRepo;
88
use crate::log_debug;
99
use crate::mcp::tools::utils::{
1010
GitIrisTool, apply_custom_instructions, create_text_result, parse_detail_level,
11-
resolve_git_repo,
11+
resolve_git_repo, validate_repository_parameter,
1212
};
1313

1414
use rmcp::handler::server::tool::cached_schema_for_type;
@@ -37,8 +37,7 @@ pub struct ReleaseNotesTool {
3737
#[serde(default)]
3838
pub custom_instructions: String,
3939

40-
/// Repository path or URL (optional)
41-
#[serde(default)]
40+
/// Repository path (local) or URL (remote). Required.
4241
pub repository: String,
4342
}
4443

@@ -66,13 +65,9 @@ impl GitIrisTool for ReleaseNotesTool {
6665
) -> Result<CallToolResult, anyhow::Error> {
6766
log_debug!("Generating release notes with: {:?}", self);
6867

69-
// Resolve repository based on the repository parameter
70-
let repo_path = if self.repository.trim().is_empty() {
71-
None
72-
} else {
73-
Some(self.repository.as_str())
74-
};
75-
let git_repo = resolve_git_repo(repo_path, git_repo)?;
68+
// Validate repository parameter
69+
validate_repository_parameter(&self.repository)?;
70+
let git_repo = resolve_git_repo(Some(self.repository.as_str()), git_repo)?;
7671
log_debug!("Using repository: {}", git_repo.repo_path().display());
7772

7873
// Parse detail level using shared utility

src/mcp/tools/utils.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::common::DetailLevel;
66
use crate::config::Config as GitIrisConfig;
77
use crate::git::GitRepo;
88
use rmcp::model::{Annotated, CallToolResult, Content, RawContent, RawTextContent};
9-
use std::path::Path;
109
use std::sync::Arc;
1110

1211
/// Common trait for all Git-Iris MCP tools
@@ -57,13 +56,38 @@ pub fn apply_custom_instructions(config: &mut crate::config::Config, custom_inst
5756
}
5857
}
5958

60-
/// Resolves a Git repository from an optional `repo_path` parameter
59+
/// Validates the repository parameter: must be non-empty, and if local, must exist and be a git repo
60+
pub fn validate_repository_parameter(repo: &str) -> Result<(), anyhow::Error> {
61+
if repo.trim().is_empty() {
62+
return Err(anyhow::anyhow!(
63+
"The `repository` parameter is required and must be a valid local path or remote URL."
64+
));
65+
}
66+
if !(repo.starts_with("http://") || repo.starts_with("https://") || repo.starts_with("git@")) {
67+
let path = std::path::Path::new(repo);
68+
if !path.exists() {
69+
return Err(anyhow::anyhow!(format!(
70+
"The specified repository path does not exist: {}",
71+
repo
72+
)));
73+
}
74+
if !path.join(".git").exists() {
75+
return Err(anyhow::anyhow!(format!(
76+
"The specified path is not a git repository: {}",
77+
repo
78+
)));
79+
}
80+
}
81+
Ok(())
82+
}
83+
84+
/// Resolves a Git repository from a `repo_path` parameter
6185
///
6286
/// If `repo_path` is provided, creates a new `GitRepo` for that path/URL.
63-
/// Otherwise, falls back to the default `git_repo` provided by the handler.
87+
/// Assumes the parameter has already been validated.
6488
pub fn resolve_git_repo(
6589
repo_path: Option<&str>,
66-
default_git_repo: Arc<GitRepo>,
90+
_default_git_repo: Arc<GitRepo>,
6791
) -> Result<Arc<GitRepo>, anyhow::Error> {
6892
match repo_path {
6993
Some(path) if !path.trim().is_empty() => {
@@ -75,10 +99,12 @@ pub fn resolve_git_repo(
7599
Ok(Arc::new(GitRepo::new_from_url(Some(path.to_string()))?))
76100
} else {
77101
// Handle local repository path
78-
let path = Path::new(path);
102+
let path = std::path::Path::new(path);
79103
Ok(Arc::new(GitRepo::new(path)?))
80104
}
81105
}
82-
_ => Ok(default_git_repo),
106+
_ => Err(anyhow::anyhow!(
107+
"The `repository` parameter is required and must be a valid local path or remote URL."
108+
)),
83109
}
84110
}

0 commit comments

Comments
 (0)