|
8 | 8 | from gg_api_core.mcp_server import GitGuardianFastMCP |
9 | 9 | from gg_api_core.scopes import get_developer_scopes, is_self_hosted_instance, validate_scopes |
10 | 10 | from gg_api_core.utils import parse_repo_url |
11 | | -from pydantic import Field |
12 | 11 |
|
13 | | -from gg_api_core.tools.generate_honey_token import generate_honeytoken |
14 | 12 | 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 |
15 | 14 | from gg_api_core.tools.remediate_secret_incidents import remediate_secret_incidents |
16 | 15 | from gg_api_core.tools.scan_secret import scan_secrets |
17 | | -from secops_mcp_server.server import list_honeytokens |
18 | 16 |
|
19 | 17 | # Configure more detailed logging |
20 | 18 | logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") |
|
122 | 120 | ) |
123 | 121 |
|
124 | 122 |
|
125 | | -@mcp.tool( |
| 123 | +mcp.add_tool( |
| 124 | + list_repo_occurrences, |
126 | 125 | description="List secret occurrences for a specific repository with exact match locations. " |
127 | 126 | "Returns detailed occurrence data including file paths, line numbers, and character indices where secrets were detected. " |
128 | 127 | "Use this tool when you need to locate and remediate secrets in the codebase with precise file locations.", |
129 | 128 | required_scopes=["incidents:read"], |
130 | 129 | ) |
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)}"} |
252 | 130 |
|
253 | 131 |
|
254 | 132 | @mcp.tool( |
|
0 commit comments