Skip to content

Commit 88d9246

Browse files
committed
chore(tools): Extract one tool as a file
1 parent 54ef12c commit 88d9246

File tree

3 files changed

+137
-127
lines changed

3 files changed

+137
-127
lines changed

packages/developer_mcp_server/src/developer_mcp_server/server.py

Lines changed: 3 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88
from gg_api_core.mcp_server import GitGuardianFastMCP
99
from gg_api_core.scopes import get_developer_scopes, is_self_hosted_instance, validate_scopes
1010
from gg_api_core.utils import parse_repo_url
11-
from pydantic import Field
1211

13-
from gg_api_core.tools.generate_honey_token import generate_honeytoken
1412
from gg_api_core.tools.list_repo_incidents import list_repo_incidents
13+
from gg_api_core.tools.list_repo_occurrences import list_repo_occurrences
1514
from gg_api_core.tools.remediate_secret_incidents import remediate_secret_incidents
1615
from gg_api_core.tools.scan_secret import scan_secrets
17-
from secops_mcp_server.server import list_honeytokens
1816

1917
# Configure more detailed logging
2018
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
@@ -122,133 +120,13 @@
122120
)
123121

124122

125-
@mcp.tool(
123+
mcp.add_tool(
124+
list_repo_occurrences,
126125
description="List secret occurrences for a specific repository with exact match locations. "
127126
"Returns detailed occurrence data including file paths, line numbers, and character indices where secrets were detected. "
128127
"Use this tool when you need to locate and remediate secrets in the codebase with precise file locations.",
129128
required_scopes=["incidents:read"],
130129
)
131-
async def list_repo_occurrences(
132-
repository_name: str | None = Field(
133-
default=None,
134-
description="The full repository name. For example, for https://github.com/GitGuardian/gg-mcp.git the full name is GitGuardian/gg-mcp. Pass the current repository name if not provided. Not required if source_id is provided."
135-
),
136-
source_id: str | None = Field(
137-
default=None,
138-
description="The GitGuardian source ID to filter by. Can be obtained using find_current_repo_source_id. If provided, repository_name is not required."
139-
),
140-
from_date: str | None = Field(
141-
default=None, description="Filter occurrences created after this date (ISO format: YYYY-MM-DD)"
142-
),
143-
to_date: str | None = Field(
144-
default=None, description="Filter occurrences created before this date (ISO format: YYYY-MM-DD)"
145-
),
146-
presence: str | None = Field(default=None, description="Filter by presence status"),
147-
tags: list[str] | None = Field(default=None, description="Filter by tags (list of tag IDs)"),
148-
ordering: str | None = Field(default=None, description="Sort field (e.g., 'date', '-date' for descending)"),
149-
per_page: int = Field(default=20, description="Number of results per page (default: 20, min: 1, max: 100)"),
150-
cursor: str | None = Field(default=None, description="Pagination cursor for fetching next page of results"),
151-
get_all: bool = Field(default=False, description="If True, fetch all results using cursor-based pagination"),
152-
) -> dict[str, Any]:
153-
"""
154-
List secret occurrences for a specific repository using the GitGuardian v1/occurrences/secrets API.
155-
156-
This tool returns detailed occurrence data with EXACT match locations, including:
157-
- File path where the secret was found
158-
- Line number in the file
159-
- Start and end character indices of the match
160-
- The type of secret detected
161-
- Match context and patterns
162-
163-
This is particularly useful for automated remediation workflows where the agent needs to:
164-
1. Locate the exact position of secrets in files
165-
2. Read the surrounding code context
166-
3. Make precise edits to remove or replace secrets
167-
4. Verify that secrets have been properly removed
168-
169-
Use list_repo_incidents for a higher-level view of incidents grouped by secret type.
170-
171-
Args:
172-
repository_name: The full repository name (e.g., 'GitGuardian/gg-mcp')
173-
source_id: The GitGuardian source ID (alternative to repository_name)
174-
from_date: Filter occurrences created after this date (ISO format: YYYY-MM-DD)
175-
to_date: Filter occurrences created before this date (ISO format: YYYY-MM-DD)
176-
presence: Filter by presence status
177-
tags: Filter by tags (list of tag IDs)
178-
ordering: Sort field (e.g., 'date', '-date' for descending)
179-
per_page: Number of results per page (default: 20, min: 1, max: 100)
180-
cursor: Pagination cursor for fetching next page of results
181-
get_all: If True, fetch all results using cursor-based pagination
182-
183-
Returns:
184-
List of secret occurrences with detailed match information including file locations and indices
185-
"""
186-
client = mcp.get_client()
187-
188-
# Validate that at least one of repository_name or source_id is provided
189-
if not repository_name and not source_id:
190-
return {"error": "Either repository_name or source_id must be provided"}
191-
192-
logger.debug(f"Listing occurrences with repository_name={repository_name}, source_id={source_id}")
193-
194-
try:
195-
# Call the list_occurrences method with appropriate filter
196-
if source_id:
197-
# Use source_id directly
198-
result = await client.list_occurrences(
199-
source_id=source_id,
200-
from_date=from_date,
201-
to_date=to_date,
202-
presence=presence,
203-
tags=tags,
204-
per_page=per_page,
205-
cursor=cursor,
206-
ordering=ordering,
207-
get_all=get_all,
208-
)
209-
else:
210-
# Use source_name (legacy path)
211-
source_name = repository_name.strip()
212-
result = await client.list_occurrences(
213-
source_name=source_name,
214-
source_type="github", # Default to github, could be made configurable
215-
from_date=from_date,
216-
to_date=to_date,
217-
presence=presence,
218-
tags=tags,
219-
per_page=per_page,
220-
cursor=cursor,
221-
ordering=ordering,
222-
get_all=get_all,
223-
)
224-
225-
# Handle the response format
226-
if isinstance(result, dict):
227-
occurrences = result.get("occurrences", [])
228-
return {
229-
"repository": repository_name,
230-
"occurrences_count": len(occurrences),
231-
"occurrences": occurrences,
232-
"cursor": result.get("cursor"),
233-
"has_more": result.get("has_more", False),
234-
}
235-
elif isinstance(result, list):
236-
# If get_all=True, we get a list directly
237-
return {
238-
"repository": repository_name,
239-
"occurrences_count": len(result),
240-
"occurrences": result,
241-
}
242-
else:
243-
return {
244-
"repository": repository_name,
245-
"occurrences_count": 0,
246-
"occurrences": [],
247-
}
248-
249-
except Exception as e:
250-
logger.error(f"Error listing repository occurrences: {str(e)}")
251-
return {"error": f"Failed to list repository occurrences: {str(e)}"}
252130

253131

254132
@mcp.tool(
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from typing import Any
2+
import logging
3+
4+
from pydantic import Field
5+
6+
from gg_api_core.utils import get_client
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
async def list_repo_occurrences(
12+
repository_name: str | None = Field(
13+
default=None,
14+
description="The full repository name. For example, for https://github.com/GitGuardian/gg-mcp.git the full name is GitGuardian/gg-mcp. Pass the current repository name if not provided. Not required if source_id is provided."
15+
),
16+
source_id: str | None = Field(
17+
default=None,
18+
description="The GitGuardian source ID to filter by. Can be obtained using find_current_repo_source_id. If provided, repository_name is not required."
19+
),
20+
from_date: str | None = Field(
21+
default=None, description="Filter occurrences created after this date (ISO format: YYYY-MM-DD)"
22+
),
23+
to_date: str | None = Field(
24+
default=None, description="Filter occurrences created before this date (ISO format: YYYY-MM-DD)"
25+
),
26+
presence: str | None = Field(default=None, description="Filter by presence status"),
27+
tags: list[str] | None = Field(default=None, description="Filter by tags (list of tag IDs)"),
28+
ordering: str | None = Field(default=None, description="Sort field (e.g., 'date', '-date' for descending)"),
29+
per_page: int = Field(default=20, description="Number of results per page (default: 20, min: 1, max: 100)"),
30+
cursor: str | None = Field(default=None, description="Pagination cursor for fetching next page of results"),
31+
get_all: bool = Field(default=False, description="If True, fetch all results using cursor-based pagination"),
32+
) -> dict[str, Any]:
33+
"""
34+
List secret occurrences for a specific repository using the GitGuardian v1/occurrences/secrets API.
35+
36+
This tool returns detailed occurrence data with EXACT match locations, including:
37+
- File path where the secret was found
38+
- Line number in the file
39+
- Start and end character indices of the match
40+
- The type of secret detected
41+
- Match context and patterns
42+
43+
This is particularly useful for automated remediation workflows where the agent needs to:
44+
1. Locate the exact position of secrets in files
45+
2. Read the surrounding code context
46+
3. Make precise edits to remove or replace secrets
47+
4. Verify that secrets have been properly removed
48+
49+
Use list_repo_incidents for a higher-level view of incidents grouped by secret type.
50+
51+
Args:
52+
repository_name: The full repository name (e.g., 'GitGuardian/gg-mcp')
53+
source_id: The GitGuardian source ID (alternative to repository_name)
54+
from_date: Filter occurrences created after this date (ISO format: YYYY-MM-DD)
55+
to_date: Filter occurrences created before this date (ISO format: YYYY-MM-DD)
56+
presence: Filter by presence status
57+
tags: Filter by tags (list of tag IDs)
58+
ordering: Sort field (e.g., 'date', '-date' for descending)
59+
per_page: Number of results per page (default: 20, min: 1, max: 100)
60+
cursor: Pagination cursor for fetching next page of results
61+
get_all: If True, fetch all results using cursor-based pagination
62+
63+
Returns:
64+
List of secret occurrences with detailed match information including file locations and indices
65+
"""
66+
client = get_client()
67+
68+
# Validate that at least one of repository_name or source_id is provided
69+
if not repository_name and not source_id:
70+
return {"error": "Either repository_name or source_id must be provided"}
71+
72+
logger.debug(f"Listing occurrences with repository_name={repository_name}, source_id={source_id}")
73+
74+
try:
75+
# Call the list_occurrences method with appropriate filter
76+
if source_id:
77+
# Use source_id directly
78+
result = await client.list_occurrences(
79+
source_id=source_id,
80+
from_date=from_date,
81+
to_date=to_date,
82+
presence=presence,
83+
tags=tags,
84+
per_page=per_page,
85+
cursor=cursor,
86+
ordering=ordering,
87+
get_all=get_all,
88+
)
89+
else:
90+
# Use source_name (legacy path)
91+
source_name = repository_name.strip()
92+
result = await client.list_occurrences(
93+
source_name=source_name,
94+
source_type="github", # Default to github, could be made configurable
95+
from_date=from_date,
96+
to_date=to_date,
97+
presence=presence,
98+
tags=tags,
99+
per_page=per_page,
100+
cursor=cursor,
101+
ordering=ordering,
102+
get_all=get_all,
103+
)
104+
105+
# Handle the response format
106+
if isinstance(result, dict):
107+
occurrences = result.get("occurrences", [])
108+
return {
109+
"repository": repository_name,
110+
"occurrences_count": len(occurrences),
111+
"occurrences": occurrences,
112+
"cursor": result.get("cursor"),
113+
"has_more": result.get("has_more", False),
114+
}
115+
elif isinstance(result, list):
116+
# If get_all=True, we get a list directly
117+
return {
118+
"repository": repository_name,
119+
"occurrences_count": len(result),
120+
"occurrences": result,
121+
}
122+
else:
123+
return {
124+
"repository": repository_name,
125+
"occurrences_count": 0,
126+
"occurrences": [],
127+
}
128+
129+
except Exception as e:
130+
logger.error(f"Error listing repository occurrences: {str(e)}")
131+
return {"error": f"Failed to list repository occurrences: {str(e)}"}

packages/gg_api_core/src/gg_api_core/tools/remediate_secret_incidents.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
from pydantic import Field
55

6-
from developer_mcp_server.server import list_repo_occurrences
6+
from gg_api_core.utils import get_client
7+
from .list_repo_occurrences import list_repo_occurrences
78
from .list_repo_incidents import list_repo_incidents
89

910
logger = logging.getLogger(__name__)
@@ -90,7 +91,7 @@ async def remediate_secret_incidents(
9091
# Filter by assignee if mine=True
9192
if mine:
9293
# Get current user info to filter by assignee
93-
client = mcp.get_client()
94+
client = get_client()
9495
try:
9596
token_info = await client.get_current_token_info()
9697
current_user_id = token_info.get("user_id") if token_info else None

0 commit comments

Comments
 (0)