Skip to content

Commit f8afa81

Browse files
committed
smaller fix
1 parent 319f2bb commit f8afa81

File tree

8 files changed

+217
-344
lines changed

8 files changed

+217
-344
lines changed

packages/developer_mcp_server/src/developer_mcp_server/register_tools.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from fastmcp import FastMCP
2-
from gg_api_core.schema_utils import compress_pydantic_model_schema
32
from gg_api_core.tools.find_current_source_id import find_current_source_id
43
from gg_api_core.tools.generate_honey_token import generate_honeytoken
54
from gg_api_core.tools.list_honey_tokens import list_honeytokens
@@ -46,19 +45,15 @@
4645

4746

4847
def register_developer_tools(mcp: FastMCP):
49-
# Register tools with Pydantic model parameters and compress their schemas
50-
# to work around Claude Code bug that serializes params as JSON strings
51-
52-
remediate_tool = mcp.tool(
48+
mcp.tool(
5349
remediate_secret_incidents,
5450
description="Find and fix secrets in the current repository using exact match locations (file paths, line numbers, character indices). "
5551
"This tool leverages the occurrences API to provide precise remediation instructions without needing to search for secrets in files. "
5652
"By default, this only shows incidents assigned to the current user. Pass mine=False to get all incidents related to this repo.",
5753
required_scopes=["incidents:read", "sources:read"],
5854
)
59-
remediate_tool.parameters = compress_pydantic_model_schema(remediate_tool.parameters)
6055

61-
scan_tool = mcp.tool(
56+
mcp.tool(
6257
scan_secrets,
6358
description="""
6459
Scan multiple content items for secrets and policy breaks.
@@ -70,27 +65,23 @@ def register_developer_tools(mcp: FastMCP):
7065
""",
7166
required_scopes=["scan"],
7267
)
73-
scan_tool.parameters = compress_pydantic_model_schema(scan_tool.parameters)
7468

75-
list_incidents_tool = mcp.tool(
69+
mcp.tool(
7670
list_repo_incidents,
7771
description="List secret incidents or occurrences related to a specific repository, and assigned to the current user."
7872
"By default, this tool only shows incidents assigned to the current user. "
7973
"Only pass mine=False to get all incidents related to this repo if the user explicitly asks for all incidents even the ones not assigned to him.",
8074
required_scopes=["incidents:read", "sources:read"],
8175
)
82-
list_incidents_tool.parameters = compress_pydantic_model_schema(list_incidents_tool.parameters)
8376

84-
list_occurrences_tool = mcp.tool(
77+
mcp.tool(
8578
list_repo_occurrences,
8679
description="List secret occurrences for a specific repository with exact match locations. "
8780
"Returns detailed occurrence data including file paths, line numbers, and character indices where secrets were detected. "
8881
"Use this tool when you need to locate and remediate secrets in the codebase with precise file locations.",
8982
required_scopes=["incidents:read"],
9083
)
91-
list_occurrences_tool.parameters = compress_pydantic_model_schema(list_occurrences_tool.parameters)
9284

93-
# find_current_source_id doesn't use a Pydantic model parameter, so no compression needed
9485
mcp.tool(
9586
find_current_source_id,
9687
description="Find the GitGuardian source_id for a repository. "
@@ -100,23 +91,20 @@ def register_developer_tools(mcp: FastMCP):
10091
required_scopes=["sources:read"],
10192
)
10293

103-
generate_token_tool = mcp.tool(
94+
mcp.tool(
10495
generate_honeytoken,
10596
description="Generate an AWS GitGuardian honeytoken and get injection recommendations",
10697
required_scopes=["honeytokens:write"],
10798
)
108-
generate_token_tool.parameters = compress_pydantic_model_schema(generate_token_tool.parameters)
10999

110-
list_tokens_tool = mcp.tool(
100+
mcp.tool(
111101
list_honeytokens,
112102
description="List honeytokens from the GitGuardian dashboard with filtering options",
113103
required_scopes=["honeytokens:read"],
114104
)
115-
list_tokens_tool.parameters = compress_pydantic_model_schema(list_tokens_tool.parameters)
116105

117-
list_users_tool = mcp.tool(
106+
mcp.tool(
118107
list_users,
119108
description="List users on the workspace/account",
120109
required_scopes=["members:read"],
121110
)
122-
list_users_tool.parameters = compress_pydantic_model_schema(list_users_tool.parameters)

packages/gg_api_core/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ dependencies = [
2828
"python-dotenv>=1.0.0",
2929
"pydantic-settings>=2.0.0",
3030
"jinja2>=3.1.0",
31-
"jsonref>=1.1.0",
3231
]
3332
license = {text = "MIT"}
3433

packages/gg_api_core/src/gg_api_core/mcp_server.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ def __init__(self, *args, default_scopes: list[str] = None, **kwargs):
9797
# Map each tool to its required scopes (instance attribute)
9898
self._tool_scopes: dict[str, set[str]] = {}
9999

100+
# Add middleware for parameter preprocessing (must be first to preprocess before validation)
101+
self.add_middleware(self._parameter_preprocessing_middleware)
100102
self.add_middleware(self._scope_filtering_middleware)
101103

102104
@abstractmethod
@@ -201,6 +203,55 @@ async def get_scopes(self):
201203
logger.debug(f"scopes: {scopes}")
202204
return scopes
203205

206+
async def _parameter_preprocessing_middleware(self, context: MiddlewareContext, call_next: Callable) -> Any:
207+
"""Middleware to preprocess tool parameters to handle Claude Code bug.
208+
209+
Claude Code has a bug where it serializes Pydantic model parameters as JSON strings
210+
instead of proper dictionaries. This middleware intercepts tools/call requests and
211+
converts stringified JSON parameters back to dictionaries before validation.
212+
213+
See: https://github.com/anthropics/claude-code/issues/3084
214+
"""
215+
import json
216+
217+
# Only apply to tools/call requests
218+
if context.method != "tools/call":
219+
return await call_next(context)
220+
221+
# Check if we have arguments to preprocess
222+
if not hasattr(context, "params") or not context.params:
223+
return await call_next(context)
224+
225+
params = context.params
226+
arguments = params.get("arguments", {})
227+
228+
# If arguments is empty or not a dict, nothing to preprocess
229+
if not isinstance(arguments, dict):
230+
return await call_next(context)
231+
232+
# Look for stringified JSON in parameter values
233+
preprocessed_arguments = {}
234+
for key, value in arguments.items():
235+
if isinstance(value, str) and value.strip().startswith("{"):
236+
# Looks like stringified JSON, try to parse it
237+
try:
238+
parsed = json.loads(value)
239+
if isinstance(parsed, dict):
240+
logger.debug(f"Preprocessing parameter '{key}': converted JSON string to dict")
241+
preprocessed_arguments[key] = parsed
242+
else:
243+
preprocessed_arguments[key] = value
244+
except (json.JSONDecodeError, ValueError):
245+
# Not valid JSON, keep original value
246+
preprocessed_arguments[key] = value
247+
else:
248+
preprocessed_arguments[key] = value
249+
250+
# Update context with preprocessed arguments
251+
context.params["arguments"] = preprocessed_arguments
252+
253+
return await call_next(context)
254+
204255
async def _scope_filtering_middleware(self, context: MiddlewareContext, call_next: Callable) -> Any:
205256
"""Middleware to filter tools based on token scopes.
206257

packages/gg_api_core/src/gg_api_core/schema_utils.py

Lines changed: 0 additions & 66 deletions
This file was deleted.

packages/secops_mcp_server/src/secops_mcp_server/server.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from developer_mcp_server.register_tools import register_developer_tools
88
from fastmcp.exceptions import ToolError
99
from gg_api_core.mcp_server import get_mcp_server
10-
from gg_api_core.schema_utils import compress_pydantic_model_schema
1110
from gg_api_core.scopes import set_secops_scopes
1211
from gg_api_core.tools.assign_incident import assign_incident
1312
from gg_api_core.tools.create_code_fix_request import create_code_fix_request
@@ -150,70 +149,59 @@ async def get_current_token_info() -> dict[str, Any]:
150149
raise ToolError(f"Error: {str(e)}")
151150

152151

153-
# Register SecOps tools with schema compression for Pydantic model parameters
154-
155-
update_tags_tool = mcp.tool(
152+
mcp.tool(
156153
update_or_create_incident_custom_tags,
157154
description="Update or create custom tags for a secret incident",
158155
required_scopes=["incidents:write", "custom_tags:write"],
159156
)
160-
update_tags_tool.parameters = compress_pydantic_model_schema(update_tags_tool.parameters)
161157

162-
update_status_tool = mcp.tool(
158+
mcp.tool(
163159
update_incident_status,
164160
description="Update a secret incident with status",
165161
required_scopes=["incidents:write"],
166162
)
167-
update_status_tool.parameters = compress_pydantic_model_schema(update_status_tool.parameters)
168163

169-
read_tags_tool = mcp.tool(
164+
mcp.tool(
170165
read_custom_tags,
171166
description="Read custom tags from the GitGuardian dashboard.",
172167
required_scopes=["custom_tags:read"],
173168
)
174-
read_tags_tool.parameters = compress_pydantic_model_schema(read_tags_tool.parameters)
175169

176-
write_tags_tool = mcp.tool(
170+
mcp.tool(
177171
write_custom_tags,
178172
description="Create or delete custom tags in the GitGuardian dashboard.",
179173
required_scopes=["custom_tags:write"],
180174
)
181-
write_tags_tool.parameters = compress_pydantic_model_schema(write_tags_tool.parameters)
182175

183-
manage_incident_tool = mcp.tool(
176+
mcp.tool(
184177
manage_private_incident,
185178
description="Manage a secret incident (assign, unassign, resolve, ignore, reopen)",
186179
required_scopes=["incidents:write"],
187180
)
188-
manage_incident_tool.parameters = compress_pydantic_model_schema(manage_incident_tool.parameters)
189181

190-
list_users_tool = mcp.tool(
182+
mcp.tool(
191183
list_users,
192184
description="List users on the workspace/account",
193185
required_scopes=["members:read"],
194186
)
195-
list_users_tool.parameters = compress_pydantic_model_schema(list_users_tool.parameters)
196187

197-
revoke_secret_tool = mcp.tool(
188+
mcp.tool(
198189
revoke_secret,
199190
description="Revoke a secret by its ID through the GitGuardian API",
200191
required_scopes=["write:secret"],
201192
)
202-
revoke_secret_tool.parameters = compress_pydantic_model_schema(revoke_secret_tool.parameters)
203193

204-
assign_incident_tool = mcp.tool(
194+
mcp.tool(
205195
assign_incident,
206196
description="Assign a secret incident to a specific member or to the current user",
207197
required_scopes=["incidents:write"],
208198
)
209-
assign_incident_tool.parameters = compress_pydantic_model_schema(assign_incident_tool.parameters)
210199

211-
create_fix_tool = mcp.tool(
200+
mcp.tool(
212201
create_code_fix_request,
213202
description="Create code fix requests for multiple secret incidents with their locations. This will generate pull requests to automatically remediate the detected secrets.",
214203
required_scopes=["incidents:write"],
215204
)
216-
create_fix_tool.parameters = compress_pydantic_model_schema(create_fix_tool.parameters)
217205

218206
# Register common tools for user information and token management
219207
try:

0 commit comments

Comments
 (0)