mcp-code-indexer 4.0.1__py3-none-any.whl → 4.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. mcp_code_indexer/__init__.py +7 -5
  2. mcp_code_indexer/ask_handler.py +2 -2
  3. mcp_code_indexer/claude_api_handler.py +10 -5
  4. mcp_code_indexer/cleanup_manager.py +20 -12
  5. mcp_code_indexer/commands/makelocal.py +85 -63
  6. mcp_code_indexer/data/stop_words_english.txt +1 -1
  7. mcp_code_indexer/database/connection_health.py +29 -20
  8. mcp_code_indexer/database/database.py +44 -31
  9. mcp_code_indexer/database/database_factory.py +19 -20
  10. mcp_code_indexer/database/exceptions.py +10 -10
  11. mcp_code_indexer/database/models.py +126 -1
  12. mcp_code_indexer/database/path_resolver.py +22 -21
  13. mcp_code_indexer/database/retry_executor.py +37 -19
  14. mcp_code_indexer/deepask_handler.py +3 -3
  15. mcp_code_indexer/error_handler.py +46 -20
  16. mcp_code_indexer/file_scanner.py +15 -12
  17. mcp_code_indexer/git_hook_handler.py +71 -76
  18. mcp_code_indexer/logging_config.py +13 -5
  19. mcp_code_indexer/main.py +85 -22
  20. mcp_code_indexer/middleware/__init__.py +1 -1
  21. mcp_code_indexer/middleware/auth.py +47 -43
  22. mcp_code_indexer/middleware/error_middleware.py +15 -15
  23. mcp_code_indexer/middleware/logging.py +44 -42
  24. mcp_code_indexer/middleware/security.py +84 -76
  25. mcp_code_indexer/migrations/002_performance_indexes.sql +1 -1
  26. mcp_code_indexer/migrations/004_remove_branch_dependency.sql +14 -14
  27. mcp_code_indexer/migrations/006_vector_mode.sql +189 -0
  28. mcp_code_indexer/query_preprocessor.py +2 -2
  29. mcp_code_indexer/server/mcp_server.py +158 -94
  30. mcp_code_indexer/transport/__init__.py +1 -1
  31. mcp_code_indexer/transport/base.py +19 -17
  32. mcp_code_indexer/transport/http_transport.py +89 -76
  33. mcp_code_indexer/transport/stdio_transport.py +12 -8
  34. mcp_code_indexer/vector_mode/__init__.py +36 -0
  35. mcp_code_indexer/vector_mode/chunking/__init__.py +19 -0
  36. mcp_code_indexer/vector_mode/chunking/ast_chunker.py +403 -0
  37. mcp_code_indexer/vector_mode/chunking/chunk_optimizer.py +500 -0
  38. mcp_code_indexer/vector_mode/chunking/language_handlers.py +478 -0
  39. mcp_code_indexer/vector_mode/config.py +155 -0
  40. mcp_code_indexer/vector_mode/daemon.py +335 -0
  41. mcp_code_indexer/vector_mode/monitoring/__init__.py +19 -0
  42. mcp_code_indexer/vector_mode/monitoring/change_detector.py +312 -0
  43. mcp_code_indexer/vector_mode/monitoring/file_watcher.py +445 -0
  44. mcp_code_indexer/vector_mode/monitoring/merkle_tree.py +418 -0
  45. mcp_code_indexer/vector_mode/providers/__init__.py +72 -0
  46. mcp_code_indexer/vector_mode/providers/base_provider.py +230 -0
  47. mcp_code_indexer/vector_mode/providers/turbopuffer_client.py +338 -0
  48. mcp_code_indexer/vector_mode/providers/voyage_client.py +212 -0
  49. mcp_code_indexer/vector_mode/security/__init__.py +11 -0
  50. mcp_code_indexer/vector_mode/security/patterns.py +297 -0
  51. mcp_code_indexer/vector_mode/security/redactor.py +368 -0
  52. {mcp_code_indexer-4.0.1.dist-info → mcp_code_indexer-4.1.0.dist-info}/METADATA +82 -24
  53. mcp_code_indexer-4.1.0.dist-info/RECORD +66 -0
  54. mcp_code_indexer-4.0.1.dist-info/RECORD +0 -47
  55. {mcp_code_indexer-4.0.1.dist-info → mcp_code_indexer-4.1.0.dist-info}/LICENSE +0 -0
  56. {mcp_code_indexer-4.0.1.dist-info → mcp_code_indexer-4.1.0.dist-info}/WHEEL +0 -0
  57. {mcp_code_indexer-4.0.1.dist-info → mcp_code_indexer-4.1.0.dist-info}/entry_points.txt +0 -0
@@ -5,12 +5,12 @@ Provides Bearer token authentication for HTTP transport.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Optional
8
+ from typing import Any, Awaitable, Callable, List, Optional
9
9
 
10
10
  try:
11
11
  from fastapi import HTTPException, Request
12
- from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
13
12
  from starlette.middleware.base import BaseHTTPMiddleware
13
+ from starlette.responses import Response
14
14
  except ImportError as e:
15
15
  raise ImportError(
16
16
  "HTTP middleware dependencies not installed. "
@@ -24,20 +24,20 @@ logger = logging.getLogger(__name__)
24
24
  class HTTPAuthMiddleware(BaseHTTPMiddleware):
25
25
  """
26
26
  HTTP authentication middleware using Bearer tokens.
27
-
27
+
28
28
  Validates Bearer tokens for protected endpoints while allowing
29
29
  public endpoints to pass through.
30
30
  """
31
-
31
+
32
32
  def __init__(
33
33
  self,
34
- app,
34
+ app: Any,
35
35
  auth_token: Optional[str] = None,
36
- public_paths: Optional[list] = None,
37
- ):
36
+ public_paths: Optional[List[str]] = None,
37
+ ) -> None:
38
38
  """
39
39
  Initialize HTTP authentication middleware.
40
-
40
+
41
41
  Args:
42
42
  app: FastAPI application instance
43
43
  auth_token: Expected Bearer token for authentication
@@ -47,36 +47,38 @@ class HTTPAuthMiddleware(BaseHTTPMiddleware):
47
47
  self.auth_token = auth_token
48
48
  self.public_paths = public_paths or ["/health", "/docs", "/openapi.json"]
49
49
  self.logger = logger.getChild("http_auth")
50
-
50
+
51
51
  # Only enable auth if token is provided
52
52
  self.auth_enabled = auth_token is not None
53
-
53
+
54
54
  if self.auth_enabled:
55
55
  self.logger.info("HTTP authentication enabled")
56
56
  else:
57
57
  self.logger.info("HTTP authentication disabled")
58
-
59
- async def dispatch(self, request: Request, call_next):
58
+
59
+ async def dispatch(
60
+ self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
61
+ ) -> Response:
60
62
  """
61
63
  Process HTTP request and validate authentication.
62
-
64
+
63
65
  Args:
64
66
  request: FastAPI request object
65
67
  call_next: Next middleware in chain
66
-
68
+
67
69
  Returns:
68
70
  HTTP response
69
-
71
+
70
72
  Raises:
71
73
  HTTPException: If authentication fails
72
74
  """
73
75
  # Skip authentication for public paths
74
76
  if not self.auth_enabled or request.url.path in self.public_paths:
75
77
  return await call_next(request)
76
-
78
+
77
79
  # Extract Authorization header
78
80
  auth_header = request.headers.get("authorization")
79
-
81
+
80
82
  if not auth_header:
81
83
  self.logger.warning(
82
84
  f"Missing authorization header for {request.url.path}",
@@ -93,7 +95,7 @@ class HTTPAuthMiddleware(BaseHTTPMiddleware):
93
95
  detail="Authorization header required",
94
96
  headers={"WWW-Authenticate": "Bearer"},
95
97
  )
96
-
98
+
97
99
  # Validate Bearer token format
98
100
  if not auth_header.startswith("Bearer "):
99
101
  self.logger.warning(
@@ -101,7 +103,9 @@ class HTTPAuthMiddleware(BaseHTTPMiddleware):
101
103
  extra={
102
104
  "structured_data": {
103
105
  "path": request.url.path,
104
- "auth_format": auth_header.split(" ")[0] if " " in auth_header else auth_header,
106
+ "auth_format": auth_header.split(" ")[0]
107
+ if " " in auth_header
108
+ else auth_header,
105
109
  "client_ip": self._get_client_ip(request),
106
110
  }
107
111
  },
@@ -111,10 +115,10 @@ class HTTPAuthMiddleware(BaseHTTPMiddleware):
111
115
  detail="Invalid authorization format. Use: Bearer <token>",
112
116
  headers={"WWW-Authenticate": "Bearer"},
113
117
  )
114
-
118
+
115
119
  # Extract and validate token
116
120
  token = auth_header[7:] # Remove "Bearer " prefix
117
-
121
+
118
122
  if token != self.auth_token:
119
123
  self.logger.warning(
120
124
  f"Invalid token for {request.url.path}",
@@ -131,7 +135,7 @@ class HTTPAuthMiddleware(BaseHTTPMiddleware):
131
135
  detail="Invalid authentication token",
132
136
  headers={"WWW-Authenticate": "Bearer"},
133
137
  )
134
-
138
+
135
139
  # Token is valid, proceed with request
136
140
  self.logger.debug(
137
141
  f"Authentication successful for {request.url.path}",
@@ -142,87 +146,87 @@ class HTTPAuthMiddleware(BaseHTTPMiddleware):
142
146
  }
143
147
  },
144
148
  )
145
-
149
+
146
150
  return await call_next(request)
147
-
151
+
148
152
  def _get_client_ip(self, request: Request) -> str:
149
153
  """Extract client IP address from request."""
150
154
  # Check for forwarded headers
151
155
  forwarded_for = request.headers.get("x-forwarded-for")
152
156
  if forwarded_for:
153
157
  return forwarded_for.split(",")[0].strip()
154
-
158
+
155
159
  real_ip = request.headers.get("x-real-ip")
156
160
  if real_ip:
157
161
  return real_ip
158
-
162
+
159
163
  # Fall back to direct client IP
160
- if hasattr(request.client, "host"):
164
+ if request.client and hasattr(request.client, "host"):
161
165
  return request.client.host
162
-
166
+
163
167
  return "unknown"
164
168
 
165
169
 
166
170
  class TokenValidator:
167
171
  """
168
172
  Utility class for token validation logic.
169
-
173
+
170
174
  Provides methods for validating different token formats
171
175
  and managing token-based authentication.
172
176
  """
173
-
177
+
174
178
  @staticmethod
175
179
  def validate_bearer_token(token: str, expected_token: str) -> bool:
176
180
  """
177
181
  Validate Bearer token against expected value.
178
-
182
+
179
183
  Args:
180
184
  token: Token to validate
181
185
  expected_token: Expected token value
182
-
186
+
183
187
  Returns:
184
188
  True if token is valid, False otherwise
185
189
  """
186
190
  if not token or not expected_token:
187
191
  return False
188
-
192
+
189
193
  # Simple string comparison for now
190
194
  # In production, consider using constant-time comparison
191
195
  return token == expected_token
192
-
196
+
193
197
  @staticmethod
194
198
  def generate_token(length: int = 32) -> str:
195
199
  """
196
200
  Generate a random token for authentication.
197
-
201
+
198
202
  Args:
199
203
  length: Length of token to generate
200
-
204
+
201
205
  Returns:
202
206
  Random token string
203
207
  """
204
208
  import secrets
205
209
  import string
206
-
210
+
207
211
  alphabet = string.ascii_letters + string.digits
208
- return ''.join(secrets.choice(alphabet) for _ in range(length))
209
-
212
+ return "".join(secrets.choice(alphabet) for _ in range(length))
213
+
210
214
  @staticmethod
211
215
  def mask_token(token: str, visible_chars: int = 8) -> str:
212
216
  """
213
217
  Mask token for logging purposes.
214
-
218
+
215
219
  Args:
216
220
  token: Token to mask
217
221
  visible_chars: Number of characters to show
218
-
222
+
219
223
  Returns:
220
224
  Masked token string
221
225
  """
222
226
  if not token:
223
227
  return ""
224
-
228
+
225
229
  if len(token) <= visible_chars:
226
230
  return "*" * len(token)
227
-
231
+
228
232
  return token[:visible_chars] + "..." + "*" * (len(token) - visible_chars)
@@ -8,7 +8,7 @@ error handling across all MCP tool implementations.
8
8
  import asyncio
9
9
  import functools
10
10
  import time
11
- from typing import Any, Callable, Dict, List
11
+ from typing import Any, Callable, Dict, List, Optional
12
12
 
13
13
  import aiosqlite
14
14
  from mcp import types
@@ -16,8 +16,8 @@ from mcp import types
16
16
  from mcp_code_indexer.error_handler import ErrorHandler
17
17
  from mcp_code_indexer.logging_config import (
18
18
  get_logger,
19
- log_tool_usage,
20
19
  log_performance_metrics,
20
+ log_tool_usage,
21
21
  )
22
22
 
23
23
  logger = get_logger(__name__)
@@ -30,7 +30,7 @@ class ToolMiddleware:
30
30
  """Initialize middleware with error handler."""
31
31
  self.error_handler = error_handler
32
32
 
33
- def wrap_tool_handler(self, tool_name: str):
33
+ def wrap_tool_handler(self, tool_name: str) -> Callable[[Callable], Callable]:
34
34
  """
35
35
  Decorator to wrap tool handlers with error handling and logging.
36
36
 
@@ -84,7 +84,7 @@ class ToolMiddleware:
84
84
  arguments_count=len(arguments),
85
85
  )
86
86
 
87
- return result
87
+ return result # type: ignore
88
88
 
89
89
  except Exception as e:
90
90
  duration = time.time() - start_time
@@ -135,8 +135,8 @@ class ToolMiddleware:
135
135
  return decorator
136
136
 
137
137
  def validate_tool_arguments(
138
- self, required_fields: List[str], optional_fields: List[str] = None
139
- ):
138
+ self, required_fields: List[str], optional_fields: Optional[List[str]] = None
139
+ ) -> Callable[[Callable], Callable]:
140
140
  """
141
141
  Decorator to validate tool arguments.
142
142
 
@@ -220,7 +220,7 @@ class AsyncTaskManager:
220
220
  self.error_handler = error_handler
221
221
  self._tasks: List[asyncio.Task] = []
222
222
 
223
- def create_task(self, coro, name: str = None) -> asyncio.Task:
223
+ def create_task(self, coro: Any, name: Optional[str] = None) -> asyncio.Task:
224
224
  """
225
225
  Create a managed async task.
226
226
 
@@ -257,7 +257,7 @@ class AsyncTaskManager:
257
257
  if task in self._tasks:
258
258
  self._tasks.remove(task)
259
259
 
260
- async def wait_for_all(self, timeout: float = None) -> None:
260
+ async def wait_for_all(self, timeout: Optional[float] = None) -> None:
261
261
  """
262
262
  Wait for all managed tasks to complete.
263
263
 
@@ -309,12 +309,12 @@ def create_tool_middleware(error_handler: ErrorHandler) -> ToolMiddleware:
309
309
  # Convenience decorators for common patterns
310
310
 
311
311
 
312
- def require_fields(*required_fields):
312
+ def require_fields(*required_fields: str) -> Callable[[Callable], Callable]:
313
313
  """Decorator that requires specific fields in arguments."""
314
314
 
315
- def decorator(func):
315
+ def decorator(func: Callable) -> Callable:
316
316
  @functools.wraps(func)
317
- async def wrapper(self, arguments: Dict[str, Any]):
317
+ async def wrapper(self: Any, arguments: Dict[str, Any]) -> Any:
318
318
  from ..error_handler import ValidationError
319
319
 
320
320
  missing = [field for field in required_fields if field not in arguments]
@@ -328,11 +328,11 @@ def require_fields(*required_fields):
328
328
  return decorator
329
329
 
330
330
 
331
- def handle_file_operations(func):
331
+ def handle_file_operations(func: Callable) -> Callable:
332
332
  """Decorator for file operation error handling."""
333
333
 
334
334
  @functools.wraps(func)
335
- async def wrapper(*args, **kwargs):
335
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
336
336
  try:
337
337
  return await func(*args, **kwargs)
338
338
  except (FileNotFoundError, PermissionError, OSError) as e:
@@ -343,11 +343,11 @@ def handle_file_operations(func):
343
343
  return wrapper
344
344
 
345
345
 
346
- def handle_database_operations(func):
346
+ def handle_database_operations(func: Callable) -> Callable:
347
347
  """Decorator for database operation error handling."""
348
348
 
349
349
  @functools.wraps(func)
350
- async def wrapper(*args, **kwargs):
350
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
351
351
  try:
352
352
  return await func(*args, **kwargs)
353
353
  except Exception as e:
@@ -4,10 +4,9 @@ HTTP logging middleware for MCP Code Indexer.
4
4
  Provides request/response logging and monitoring for HTTP transport.
5
5
  """
6
6
 
7
- import json
8
7
  import logging
9
8
  import time
10
- from typing import Any, Dict, Optional
9
+ from typing import Any, Awaitable, Callable, Dict, List
11
10
 
12
11
  try:
13
12
  from fastapi import Request, Response
@@ -26,15 +25,15 @@ logger = logging.getLogger(__name__)
26
25
  class HTTPLoggingMiddleware(BaseHTTPMiddleware):
27
26
  """
28
27
  HTTP request/response logging middleware.
29
-
28
+
30
29
  Logs HTTP requests and responses with performance metrics
31
30
  and structured data for monitoring.
32
31
  """
33
-
32
+
34
33
  def __init__(self, app: Any, log_level: str = "INFO"):
35
34
  """
36
35
  Initialize HTTP logging middleware.
37
-
36
+
38
37
  Args:
39
38
  app: FastAPI application instance
40
39
  log_level: Logging level for HTTP requests
@@ -43,25 +42,27 @@ class HTTPLoggingMiddleware(BaseHTTPMiddleware):
43
42
  self.log_level = getattr(logging, log_level.upper())
44
43
  self.logger = logger.getChild("http_access")
45
44
  self.logger.setLevel(self.log_level)
46
-
47
- async def dispatch(self, request: Request, call_next) -> Response:
45
+
46
+ async def dispatch(
47
+ self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
48
+ ) -> Response:
48
49
  """
49
50
  Process HTTP request and log access information.
50
-
51
+
51
52
  Args:
52
53
  request: FastAPI request object
53
54
  call_next: Next middleware in chain
54
-
55
+
55
56
  Returns:
56
57
  HTTP response
57
58
  """
58
59
  start_time = time.time()
59
60
  request_id = self._generate_request_id(request)
60
-
61
+
61
62
  # Extract client information
62
63
  client_ip = self._get_client_ip(request)
63
64
  user_agent = request.headers.get("user-agent", "")
64
-
65
+
65
66
  # Log incoming request
66
67
  self.logger.info(
67
68
  f"HTTP {request.method} {request.url.path}",
@@ -79,17 +80,17 @@ class HTTPLoggingMiddleware(BaseHTTPMiddleware):
79
80
  }
80
81
  },
81
82
  )
82
-
83
+
83
84
  # Add request ID to request state for use in handlers
84
85
  request.state.request_id = request_id
85
-
86
+
86
87
  try:
87
88
  # Process request
88
89
  response = await call_next(request)
89
-
90
+
90
91
  # Calculate processing time
91
92
  process_time = time.time() - start_time
92
-
93
+
93
94
  # Log response
94
95
  self._log_response(
95
96
  request_id=request_id,
@@ -99,17 +100,17 @@ class HTTPLoggingMiddleware(BaseHTTPMiddleware):
99
100
  process_time=process_time,
100
101
  response=response,
101
102
  )
102
-
103
+
103
104
  # Add performance headers
104
105
  response.headers["X-Process-Time"] = str(process_time)
105
106
  response.headers["X-Request-ID"] = request_id
106
-
107
+
107
108
  return response
108
-
109
+
109
110
  except Exception as e:
110
111
  # Log error
111
112
  process_time = time.time() - start_time
112
-
113
+
113
114
  self.logger.error(
114
115
  f"HTTP {request.method} {request.url.path} - ERROR",
115
116
  extra={
@@ -124,31 +125,32 @@ class HTTPLoggingMiddleware(BaseHTTPMiddleware):
124
125
  }
125
126
  },
126
127
  )
127
-
128
+
128
129
  raise
129
-
130
+
130
131
  def _generate_request_id(self, request: Request) -> str:
131
132
  """Generate unique request ID."""
132
133
  import uuid
134
+
133
135
  return str(uuid.uuid4())[:8]
134
-
136
+
135
137
  def _get_client_ip(self, request: Request) -> str:
136
138
  """Extract client IP address from request."""
137
139
  # Check for forwarded headers
138
140
  forwarded_for = request.headers.get("x-forwarded-for")
139
141
  if forwarded_for:
140
142
  return forwarded_for.split(",")[0].strip()
141
-
143
+
142
144
  real_ip = request.headers.get("x-real-ip")
143
145
  if real_ip:
144
146
  return real_ip
145
-
147
+
146
148
  # Fall back to direct client IP
147
- if hasattr(request.client, "host"):
149
+ if request.client and hasattr(request.client, "host"):
148
150
  return request.client.host
149
-
151
+
150
152
  return "unknown"
151
-
153
+
152
154
  def _log_response(
153
155
  self,
154
156
  request_id: str,
@@ -166,14 +168,14 @@ class HTTPLoggingMiddleware(BaseHTTPMiddleware):
166
168
  log_level = logging.WARNING
167
169
  else:
168
170
  log_level = self.log_level
169
-
171
+
170
172
  # Extract response information
171
173
  content_length = response.headers.get("content-length")
172
174
  content_type = response.headers.get("content-type")
173
-
175
+
174
176
  # Check if this is a streaming response
175
177
  is_streaming = isinstance(response, StreamingResponse)
176
-
178
+
177
179
  self.logger.log(
178
180
  log_level,
179
181
  f"HTTP {method} {path} - {status_code}",
@@ -192,7 +194,7 @@ class HTTPLoggingMiddleware(BaseHTTPMiddleware):
192
194
  }
193
195
  },
194
196
  )
195
-
197
+
196
198
  def _categorize_performance(self, process_time: float) -> str:
197
199
  """Categorize request performance."""
198
200
  if process_time < 0.1:
@@ -208,18 +210,18 @@ class HTTPLoggingMiddleware(BaseHTTPMiddleware):
208
210
  class HTTPMetricsCollector:
209
211
  """
210
212
  Collect HTTP metrics for monitoring.
211
-
213
+
212
214
  Tracks request counts, response times, and error rates.
213
215
  """
214
-
215
- def __init__(self):
216
+
217
+ def __init__(self) -> None:
216
218
  """Initialize metrics collector."""
217
219
  self.request_count = 0
218
220
  self.error_count = 0
219
221
  self.total_response_time = 0.0
220
- self.response_times = []
222
+ self.response_times: List[float] = []
221
223
  self.max_response_times = 1000 # Keep last 1000 response times
222
-
224
+
223
225
  def record_request(
224
226
  self,
225
227
  method: str,
@@ -230,16 +232,16 @@ class HTTPMetricsCollector:
230
232
  """Record HTTP request metrics."""
231
233
  self.request_count += 1
232
234
  self.total_response_time += response_time
233
-
235
+
234
236
  # Track error rates
235
237
  if status_code >= 400:
236
238
  self.error_count += 1
237
-
239
+
238
240
  # Track response times
239
241
  self.response_times.append(response_time)
240
242
  if len(self.response_times) > self.max_response_times:
241
243
  self.response_times.pop(0)
242
-
244
+
243
245
  def get_metrics(self) -> Dict[str, Any]:
244
246
  """Get current metrics summary."""
245
247
  if not self.request_count:
@@ -251,12 +253,12 @@ class HTTPMetricsCollector:
251
253
  "p95_response_time": 0.0,
252
254
  "p99_response_time": 0.0,
253
255
  }
254
-
256
+
255
257
  # Calculate percentiles
256
258
  sorted_times = sorted(self.response_times)
257
259
  p95_index = int(0.95 * len(sorted_times))
258
260
  p99_index = int(0.99 * len(sorted_times))
259
-
261
+
260
262
  return {
261
263
  "request_count": self.request_count,
262
264
  "error_count": self.error_count,
@@ -265,7 +267,7 @@ class HTTPMetricsCollector:
265
267
  "p95_response_time": sorted_times[p95_index] if sorted_times else 0.0,
266
268
  "p99_response_time": sorted_times[p99_index] if sorted_times else 0.0,
267
269
  }
268
-
270
+
269
271
  def reset_metrics(self) -> None:
270
272
  """Reset all metrics."""
271
273
  self.request_count = 0