hanzo-mcp 0.7.6__py3-none-any.whl → 0.8.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.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (178) hide show
  1. hanzo_mcp/__init__.py +7 -1
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.6.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.6.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
@@ -4,107 +4,114 @@ This module provides a base class that automatically handles pagination
4
4
  for all tool responses that exceed MCP token limits.
5
5
  """
6
6
 
7
- import json
8
7
  from abc import abstractmethod
9
- from typing import Any, Dict, Optional, Union
8
+ from typing import Any, Dict, Union
10
9
 
11
10
  from mcp.server.fastmcp import Context as MCPContext
12
11
 
13
12
  from hanzo_mcp.tools.common.base import BaseTool, handle_connection_errors
14
- from hanzo_mcp.tools.common.paginated_response import paginate_if_needed
15
13
  from hanzo_mcp.tools.common.pagination import CursorManager
14
+ from hanzo_mcp.tools.common.paginated_response import paginate_if_needed
16
15
 
17
16
 
18
17
  class PaginatedBaseTool(BaseTool):
19
18
  """Base class for tools with automatic pagination support.
20
-
19
+
21
20
  This base class automatically handles pagination for responses that
22
21
  exceed MCP token limits, making all tools pagination-aware by default.
23
22
  """
24
-
23
+
25
24
  def __init__(self):
26
25
  """Initialize the paginated base tool."""
27
26
  super().__init__()
28
27
  self._supports_pagination = True
29
-
28
+
30
29
  @abstractmethod
31
30
  async def execute(self, ctx: MCPContext, **params: Any) -> Any:
32
31
  """Execute the tool logic and return raw results.
33
-
32
+
34
33
  This method should be implemented by subclasses to perform the
35
34
  actual tool logic. The base class will handle pagination of
36
35
  the returned results automatically.
37
-
36
+
38
37
  Args:
39
38
  ctx: MCP context
40
39
  **params: Tool parameters
41
-
40
+
42
41
  Returns:
43
42
  Raw tool results (will be paginated if needed)
44
43
  """
45
44
  pass
46
-
45
+
47
46
  @handle_connection_errors
48
47
  async def call(self, ctx: MCPContext, **params: Any) -> Union[str, Dict[str, Any]]:
49
48
  """Execute the tool with automatic pagination support.
50
-
49
+
51
50
  This method wraps the execute() method and automatically handles
52
51
  pagination if the response exceeds token limits.
53
-
52
+
54
53
  Args:
55
54
  ctx: MCP context
56
55
  **params: Tool parameters including optional 'cursor'
57
-
56
+
58
57
  Returns:
59
58
  Tool result, potentially paginated
60
59
  """
61
60
  # Extract cursor if provided
62
61
  cursor = params.pop("cursor", None)
63
-
62
+
64
63
  # Validate cursor if provided
65
64
  if cursor and not CursorManager.parse_cursor(cursor):
66
65
  return {"error": "Invalid cursor provided", "code": -32602}
67
-
66
+
68
67
  # Check if this is a continuation request
69
68
  if cursor:
70
69
  # For continuation, check if we have cached results
71
70
  cursor_data = CursorManager.parse_cursor(cursor)
72
- if cursor_data and "tool" in cursor_data and cursor_data["tool"] != self.name:
71
+ if (
72
+ cursor_data
73
+ and "tool" in cursor_data
74
+ and cursor_data["tool"] != self.name
75
+ ):
73
76
  return {"error": "Cursor is for a different tool", "code": -32602}
74
-
77
+
75
78
  # Execute the tool
76
79
  try:
77
80
  result = await self.execute(ctx, **params)
78
81
  except Exception as e:
79
82
  # Format errors consistently
80
83
  return {"error": str(e), "type": type(e).__name__}
81
-
84
+
82
85
  # Handle pagination automatically
83
86
  if self._supports_pagination:
84
87
  paginated_result = paginate_if_needed(result, cursor)
85
-
88
+
86
89
  # If pagination occurred, add tool info to help with continuation
87
90
  if isinstance(paginated_result, dict) and "nextCursor" in paginated_result:
88
91
  # Enhance the cursor with tool information
89
92
  if "nextCursor" in paginated_result:
90
- cursor_data = CursorManager.parse_cursor(paginated_result["nextCursor"])
93
+ cursor_data = CursorManager.parse_cursor(
94
+ paginated_result["nextCursor"]
95
+ )
91
96
  if cursor_data:
92
97
  cursor_data["tool"] = self.name
93
98
  cursor_data["params"] = params # Store params for continuation
94
- paginated_result["nextCursor"] = CursorManager.create_cursor(cursor_data)
95
-
99
+ paginated_result["nextCursor"] = CursorManager.create_cursor(
100
+ cursor_data
101
+ )
102
+
96
103
  return paginated_result
97
104
  else:
98
105
  # Return raw result if pagination is disabled
99
106
  return result
100
-
107
+
101
108
  def disable_pagination(self):
102
109
  """Disable automatic pagination for this tool.
103
-
110
+
104
111
  Some tools may want to handle their own pagination logic.
105
112
  """
106
113
  self._supports_pagination = False
107
-
114
+
108
115
  def enable_pagination(self):
109
116
  """Re-enable automatic pagination for this tool."""
110
117
  self._supports_pagination = True
@@ -112,22 +119,22 @@ class PaginatedBaseTool(BaseTool):
112
119
 
113
120
  class PaginatedFileSystemTool(PaginatedBaseTool):
114
121
  """Base class for filesystem tools with pagination support."""
115
-
122
+
116
123
  def __init__(self, permission_manager):
117
124
  """Initialize filesystem tool with pagination.
118
-
125
+
119
126
  Args:
120
127
  permission_manager: Permission manager for access control
121
128
  """
122
129
  super().__init__()
123
130
  self.permission_manager = permission_manager
124
-
131
+
125
132
  def is_path_allowed(self, path: str) -> bool:
126
133
  """Check if a path is allowed according to permission settings.
127
-
134
+
128
135
  Args:
129
136
  path: Path to check
130
-
137
+
131
138
  Returns:
132
139
  True if the path is allowed, False otherwise
133
140
  """
@@ -136,95 +143,96 @@ class PaginatedFileSystemTool(PaginatedBaseTool):
136
143
 
137
144
  def migrate_tool_to_paginated(tool_class):
138
145
  """Decorator to migrate existing tools to use pagination.
139
-
146
+
140
147
  This decorator can be applied to existing tool classes to add
141
148
  automatic pagination support without modifying their code.
142
-
149
+
143
150
  Usage:
144
151
  @migrate_tool_to_paginated
145
152
  class MyTool(BaseTool):
146
153
  ...
147
154
  """
155
+
148
156
  class PaginatedWrapper(PaginatedBaseTool):
149
157
  def __init__(self, *args, **kwargs):
150
158
  super().__init__()
151
159
  self._wrapped_tool = tool_class(*args, **kwargs)
152
-
160
+
153
161
  @property
154
162
  def name(self):
155
163
  return self._wrapped_tool.name
156
-
157
- @property
164
+
165
+ @property
158
166
  def description(self):
159
167
  # Add pagination info to description
160
168
  desc = self._wrapped_tool.description
161
169
  if not "pagination" in desc.lower():
162
170
  desc += "\n\nThis tool supports automatic pagination. If the response is too large, it will be split across multiple requests. Use the returned cursor to continue."
163
171
  return desc
164
-
172
+
165
173
  async def execute(self, ctx: MCPContext, **params: Any) -> Any:
166
174
  # Call the wrapped tool's call method
167
175
  return await self._wrapped_tool.call(ctx, **params)
168
-
176
+
169
177
  def register(self, mcp_server):
170
178
  # Need to create a new registration that includes cursor parameter
171
179
  tool_self = self
172
-
180
+
173
181
  # Get the original registration function
174
182
  original_register = self._wrapped_tool.register
175
-
183
+
176
184
  # Create a new registration that adds cursor support
177
185
  def register_with_pagination(server):
178
186
  # First register the original tool
179
187
  original_register(server)
180
-
188
+
181
189
  # Then override with pagination support
182
190
  import inspect
183
-
191
+
184
192
  # Get the registered function
185
193
  tool_func = None
186
194
  for name, func in server._tools.items():
187
195
  if name == self.name:
188
196
  tool_func = func
189
197
  break
190
-
198
+
191
199
  if tool_func:
192
200
  # Get original signature
193
201
  sig = inspect.signature(tool_func)
194
202
  params = list(sig.parameters.values())
195
-
203
+
196
204
  # Add cursor parameter if not present
197
205
  has_cursor = any(p.name == "cursor" for p in params)
198
206
  if not has_cursor:
199
- from typing import Optional
200
207
  import inspect
201
-
208
+ from typing import Optional
209
+
202
210
  # Create new parameter with cursor
203
211
  cursor_param = inspect.Parameter(
204
212
  "cursor",
205
213
  inspect.Parameter.KEYWORD_ONLY,
206
214
  default=None,
207
- annotation=Optional[str]
215
+ annotation=Optional[str],
208
216
  )
209
-
217
+
210
218
  # Insert before ctx parameter
211
219
  new_params = []
212
220
  for p in params:
213
221
  if p.name == "ctx":
214
222
  new_params.append(cursor_param)
215
223
  new_params.append(p)
216
-
224
+
217
225
  # Create wrapper function
218
226
  async def paginated_wrapper(**kwargs):
219
227
  return await tool_self.call(kwargs.get("ctx"), **kwargs)
220
-
228
+
221
229
  # Update registration
222
230
  server._tools[self.name] = paginated_wrapper
223
-
231
+
224
232
  register_with_pagination(mcp_server)
225
-
233
+
226
234
  # Set the class name
227
235
  PaginatedWrapper.__name__ = f"Paginated{tool_class.__name__}"
228
236
  PaginatedWrapper.__qualname__ = f"Paginated{tool_class.__qualname__}"
229
-
230
- return PaginatedWrapper
237
+
238
+ return PaginatedWrapper
@@ -5,30 +5,31 @@ when they exceed token limits.
5
5
  """
6
6
 
7
7
  import json
8
- from typing import Any, Dict, List, Optional, Union
8
+ from typing import Any, Dict, List, Union, Optional
9
+
9
10
  from hanzo_mcp.tools.common.truncate import estimate_tokens
10
11
  from hanzo_mcp.tools.common.pagination import CursorManager
11
12
 
12
13
 
13
14
  class AutoPaginatedResponse:
14
15
  """Automatically paginate responses that exceed token limits."""
15
-
16
+
16
17
  # MCP token limit with safety buffer
17
18
  MAX_TOKENS = 20000 # Leave 5k buffer from 25k limit
18
-
19
+
19
20
  @staticmethod
20
21
  def create_response(
21
22
  content: Union[str, Dict[str, Any], List[Any]],
22
23
  cursor: Optional[str] = None,
23
- max_tokens: int = MAX_TOKENS
24
+ max_tokens: int = MAX_TOKENS,
24
25
  ) -> Dict[str, Any]:
25
26
  """Create a response that automatically paginates if too large.
26
-
27
+
27
28
  Args:
28
29
  content: The content to return
29
30
  cursor: Optional cursor from request
30
31
  max_tokens: Maximum tokens allowed in response
31
-
32
+
32
33
  Returns:
33
34
  Dict with content and optional nextCursor
34
35
  """
@@ -54,12 +55,10 @@ class AutoPaginatedResponse:
54
55
  return AutoPaginatedResponse._handle_string_response(
55
56
  str(content), cursor, max_tokens
56
57
  )
57
-
58
+
58
59
  @staticmethod
59
60
  def _handle_string_response(
60
- content: str,
61
- cursor: Optional[str],
62
- max_tokens: int
61
+ content: str, cursor: Optional[str], max_tokens: int
63
62
  ) -> Dict[str, Any]:
64
63
  """Handle pagination for string responses."""
65
64
  # Parse cursor to get offset
@@ -68,28 +67,28 @@ class AutoPaginatedResponse:
68
67
  cursor_data = CursorManager.parse_cursor(cursor)
69
68
  if cursor_data and "offset" in cursor_data:
70
69
  offset = cursor_data["offset"]
71
-
70
+
72
71
  # For strings, paginate by lines
73
- lines = content.split('\n')
74
-
72
+ lines = content.split("\n")
73
+
75
74
  if offset >= len(lines):
76
75
  return {"content": "", "message": "No more content"}
77
-
76
+
78
77
  # Build response line by line, checking tokens
79
78
  result_lines = []
80
79
  current_tokens = 0
81
80
  line_index = offset
82
-
81
+
83
82
  # Add header if this is a continuation
84
83
  if offset > 0:
85
84
  header = f"[Continued from line {offset + 1}]\n"
86
85
  current_tokens = estimate_tokens(header)
87
86
  result_lines.append(header)
88
-
87
+
89
88
  while line_index < len(lines):
90
89
  line = lines[line_index]
91
90
  line_tokens = estimate_tokens(line + "\n")
92
-
91
+
93
92
  # Check if adding this line would exceed limit
94
93
  if current_tokens + line_tokens > max_tokens:
95
94
  # Need to paginate
@@ -99,38 +98,34 @@ class AutoPaginatedResponse:
99
98
  result_lines.append(truncated_line)
100
99
  line_index += 1
101
100
  break
102
-
101
+
103
102
  result_lines.append(line)
104
103
  current_tokens += line_tokens
105
104
  line_index += 1
106
-
105
+
107
106
  # Build response
108
- response = {
109
- "content": '\n'.join(result_lines)
110
- }
111
-
107
+ response = {"content": "\n".join(result_lines)}
108
+
112
109
  # Add pagination info
113
110
  if line_index < len(lines):
114
111
  response["nextCursor"] = CursorManager.create_offset_cursor(line_index)
115
112
  response["pagination_info"] = {
116
113
  "current_lines": f"{offset + 1}-{line_index}",
117
114
  "total_lines": len(lines),
118
- "has_more": True
115
+ "has_more": True,
119
116
  }
120
117
  else:
121
118
  response["pagination_info"] = {
122
119
  "current_lines": f"{offset + 1}-{len(lines)}",
123
120
  "total_lines": len(lines),
124
- "has_more": False
121
+ "has_more": False,
125
122
  }
126
-
123
+
127
124
  return response
128
-
125
+
129
126
  @staticmethod
130
127
  def _handle_list_response(
131
- items: List[Any],
132
- cursor: Optional[str],
133
- max_tokens: int
128
+ items: List[Any], cursor: Optional[str], max_tokens: int
134
129
  ) -> Dict[str, Any]:
135
130
  """Handle pagination for list responses."""
136
131
  # Parse cursor to get offset
@@ -139,28 +134,28 @@ class AutoPaginatedResponse:
139
134
  cursor_data = CursorManager.parse_cursor(cursor)
140
135
  if cursor_data and "offset" in cursor_data:
141
136
  offset = cursor_data["offset"]
142
-
137
+
143
138
  if offset >= len(items):
144
139
  return {"items": [], "message": "No more items"}
145
-
140
+
146
141
  # Build response item by item, checking tokens
147
142
  result_items = []
148
143
  current_tokens = 100 # Base overhead
149
144
  item_index = offset
150
-
145
+
151
146
  # Add header if continuation
152
147
  header_obj = {}
153
148
  if offset > 0:
154
149
  header_obj["continuation_from"] = offset
155
150
  current_tokens += 50
156
-
151
+
157
152
  while item_index < len(items):
158
153
  item = items[item_index]
159
-
154
+
160
155
  # Estimate tokens for this item
161
156
  item_str = json.dumps(item) if not isinstance(item, str) else item
162
157
  item_tokens = estimate_tokens(item_str)
163
-
158
+
164
159
  # Check if adding this item would exceed limit
165
160
  if current_tokens + item_tokens > max_tokens:
166
161
  if not result_items:
@@ -169,22 +164,22 @@ class AutoPaginatedResponse:
169
164
  truncated = item[:5000] + "... [truncated]"
170
165
  result_items.append(truncated)
171
166
  else:
172
- result_items.append({"error": "Item too large", "index": item_index})
167
+ result_items.append(
168
+ {"error": "Item too large", "index": item_index}
169
+ )
173
170
  item_index += 1
174
171
  break
175
-
172
+
176
173
  result_items.append(item)
177
174
  current_tokens += item_tokens
178
175
  item_index += 1
179
-
176
+
180
177
  # Build response
181
- response = {
182
- "items": result_items
183
- }
184
-
178
+ response = {"items": result_items}
179
+
185
180
  if header_obj:
186
181
  response.update(header_obj)
187
-
182
+
188
183
  # Add pagination info
189
184
  if item_index < len(items):
190
185
  response["nextCursor"] = CursorManager.create_offset_cursor(item_index)
@@ -192,70 +187,66 @@ class AutoPaginatedResponse:
192
187
  "returned_items": len(result_items),
193
188
  "total_items": len(items),
194
189
  "has_more": True,
195
- "next_index": item_index
190
+ "next_index": item_index,
196
191
  }
197
192
  else:
198
193
  response["pagination_info"] = {
199
194
  "returned_items": len(result_items),
200
195
  "total_items": len(items),
201
- "has_more": False
196
+ "has_more": False,
202
197
  }
203
-
198
+
204
199
  return response
205
-
200
+
206
201
  @staticmethod
207
202
  def _handle_dict_response(
208
- content: Dict[str, Any],
209
- cursor: Optional[str],
210
- max_tokens: int
203
+ content: Dict[str, Any], cursor: Optional[str], max_tokens: int
211
204
  ) -> Dict[str, Any]:
212
205
  """Handle pagination for dict responses."""
213
206
  # For dicts, check if it's too large as-is
214
207
  content_str = json.dumps(content, indent=2)
215
208
  content_tokens = estimate_tokens(content_str)
216
-
209
+
217
210
  if content_tokens <= max_tokens:
218
211
  # Fits within limit
219
212
  return content
220
-
213
+
221
214
  # Too large - need to paginate
222
215
  # Strategy: Convert to key-value pairs and paginate
223
216
  items = list(content.items())
224
217
  offset = 0
225
-
218
+
226
219
  if cursor:
227
220
  cursor_data = CursorManager.parse_cursor(cursor)
228
221
  if cursor_data and "offset" in cursor_data:
229
222
  offset = cursor_data["offset"]
230
-
223
+
231
224
  if offset >= len(items):
232
225
  return {"content": {}, "message": "No more content"}
233
-
226
+
234
227
  # Build paginated dict
235
228
  result = {}
236
229
  current_tokens = 100 # Base overhead
237
-
230
+
238
231
  for i in range(offset, len(items)):
239
232
  key, value = items[i]
240
-
233
+
241
234
  # Estimate tokens for this entry
242
235
  entry_str = json.dumps({key: value})
243
236
  entry_tokens = estimate_tokens(entry_str)
244
-
237
+
245
238
  if current_tokens + entry_tokens > max_tokens:
246
239
  if not result:
247
240
  # Single entry too large
248
241
  result[key] = "[Value too large - use specific key access]"
249
242
  break
250
-
243
+
251
244
  result[key] = value
252
245
  current_tokens += entry_tokens
253
-
246
+
254
247
  # Wrap in response
255
- response = {
256
- "content": result
257
- }
258
-
248
+ response = {"content": result}
249
+
259
250
  # Add pagination info
260
251
  processed = offset + len(result)
261
252
  if processed < len(items):
@@ -263,45 +254,47 @@ class AutoPaginatedResponse:
263
254
  response["pagination_info"] = {
264
255
  "keys_returned": len(result),
265
256
  "total_keys": len(items),
266
- "has_more": True
257
+ "has_more": True,
267
258
  }
268
259
  else:
269
260
  response["pagination_info"] = {
270
261
  "keys_returned": len(result),
271
262
  "total_keys": len(items),
272
- "has_more": False
263
+ "has_more": False,
273
264
  }
274
-
265
+
275
266
  return response
276
267
 
277
268
 
278
269
  def paginate_if_needed(
279
- response: Any,
280
- cursor: Optional[str] = None,
281
- force_pagination: bool = False
270
+ response: Any, cursor: Optional[str] = None, force_pagination: bool = False
282
271
  ) -> Union[str, Dict[str, Any]]:
283
272
  """Wrap a response with automatic pagination if needed.
284
-
273
+
285
274
  Args:
286
275
  response: The response to potentially paginate
287
276
  cursor: Optional cursor from request
288
277
  force_pagination: Force pagination even for small responses
289
-
278
+
290
279
  Returns:
291
280
  Original response if small enough, otherwise paginated dict
292
281
  """
293
282
  # Quick check - if response is already paginated, return as-is
294
- if isinstance(response, dict) and ("nextCursor" in response or "pagination_info" in response):
283
+ if isinstance(response, dict) and (
284
+ "nextCursor" in response or "pagination_info" in response
285
+ ):
295
286
  return response
296
-
287
+
297
288
  # For small responses, don't paginate unless forced
298
289
  if not force_pagination:
299
290
  try:
300
- response_str = json.dumps(response) if not isinstance(response, str) else response
291
+ response_str = (
292
+ json.dumps(response) if not isinstance(response, str) else response
293
+ )
301
294
  if len(response_str) < 10000: # Quick heuristic
302
295
  return response
303
- except:
296
+ except Exception:
304
297
  pass
305
-
298
+
306
299
  # Create paginated response
307
- return AutoPaginatedResponse.create_response(response, cursor)
300
+ return AutoPaginatedResponse.create_response(response, cursor)