hanzo-mcp 0.7.7__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 +6 -0
  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.7.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.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/top_level.txt +0 -0
@@ -3,35 +3,35 @@
3
3
  This module provides pagination utilities optimized for FastMCP with minimal latency.
4
4
  """
5
5
 
6
- import base64
7
6
  import json
8
7
  import time
9
- from typing import Any, Dict, List, Optional, Tuple, TypeVar, Generic, Union
10
- from dataclasses import dataclass, field
11
- from datetime import datetime
8
+ import base64
12
9
  import hashlib
10
+ from typing import Any, Dict, List, Union, Generic, TypeVar, Optional
11
+ from dataclasses import field, dataclass
13
12
 
14
- T = TypeVar('T')
13
+ T = TypeVar("T")
15
14
 
16
15
 
17
16
  @dataclass
18
17
  class CursorData:
19
18
  """Cursor data structure for efficient pagination."""
19
+
20
20
  # Primary cursor fields (indexed)
21
21
  last_id: Optional[str] = None
22
22
  last_timestamp: Optional[float] = None
23
23
  offset: int = 0
24
-
24
+
25
25
  # Metadata for validation and optimization
26
26
  page_size: int = 100
27
27
  sort_field: str = "id"
28
28
  sort_order: str = "asc"
29
-
29
+
30
30
  # Security and validation
31
31
  created_at: float = field(default_factory=time.time)
32
32
  expires_at: Optional[float] = None
33
33
  checksum: Optional[str] = None
34
-
34
+
35
35
  def to_cursor(self) -> str:
36
36
  """Convert to opaque cursor string."""
37
37
  data = {
@@ -45,41 +45,44 @@ class CursorData:
45
45
  }
46
46
  if self.expires_at:
47
47
  data["ea"] = self.expires_at
48
-
48
+
49
49
  # Add checksum for integrity
50
- data_str = json.dumps(data, sort_keys=True, separators=(',', ':'))
50
+ data_str = json.dumps(data, sort_keys=True, separators=(",", ":"))
51
51
  data["cs"] = hashlib.md5(data_str.encode()).hexdigest()[:8]
52
-
52
+
53
53
  # Encode as base64
54
- final_str = json.dumps(data, separators=(',', ':'))
55
- return base64.urlsafe_b64encode(final_str.encode()).decode().rstrip('=')
56
-
54
+ final_str = json.dumps(data, separators=(",", ":"))
55
+ return base64.urlsafe_b64encode(final_str.encode()).decode().rstrip("=")
56
+
57
57
  @classmethod
58
- def from_cursor(cls, cursor: str) -> Optional['CursorData']:
58
+ def from_cursor(cls, cursor: str) -> Optional["CursorData"]:
59
59
  """Parse cursor string back to CursorData."""
60
60
  try:
61
61
  # Add padding if needed
62
62
  padding = 4 - (len(cursor) % 4)
63
63
  if padding != 4:
64
- cursor += '=' * padding
65
-
64
+ cursor += "=" * padding
65
+
66
66
  decoded = base64.urlsafe_b64decode(cursor.encode())
67
67
  data = json.loads(decoded)
68
-
68
+
69
69
  # Validate checksum
70
70
  checksum = data.pop("cs", None)
71
71
  if checksum:
72
- data_str = json.dumps({k: v for k, v in data.items() if k != "cs"},
73
- sort_keys=True, separators=(',', ':'))
72
+ data_str = json.dumps(
73
+ {k: v for k, v in data.items() if k != "cs"},
74
+ sort_keys=True,
75
+ separators=(",", ":"),
76
+ )
74
77
  expected = hashlib.md5(data_str.encode()).hexdigest()[:8]
75
78
  if checksum != expected:
76
79
  return None
77
-
80
+
78
81
  # Check expiration
79
82
  expires_at = data.get("ea")
80
83
  if expires_at and time.time() > expires_at:
81
84
  return None
82
-
85
+
83
86
  return cls(
84
87
  last_id=data.get("id"),
85
88
  last_timestamp=data.get("ts"),
@@ -88,7 +91,7 @@ class CursorData:
88
91
  sort_field=data.get("sf", "id"),
89
92
  sort_order=data.get("so", "asc"),
90
93
  created_at=data.get("ca", time.time()),
91
- expires_at=expires_at
94
+ expires_at=expires_at,
92
95
  )
93
96
  except Exception:
94
97
  return None
@@ -96,16 +99,16 @@ class CursorData:
96
99
 
97
100
  class FastMCPPaginator(Generic[T]):
98
101
  """High-performance paginator for FastMCP responses."""
99
-
102
+
100
103
  def __init__(
101
104
  self,
102
105
  page_size: int = 100,
103
106
  max_page_size: int = 1000,
104
107
  cursor_ttl: int = 3600, # 1 hour
105
- enable_prefetch: bool = False
108
+ enable_prefetch: bool = False,
106
109
  ):
107
110
  """Initialize the paginator.
108
-
111
+
109
112
  Args:
110
113
  page_size: Default page size
111
114
  max_page_size: Maximum allowed page size
@@ -117,45 +120,44 @@ class FastMCPPaginator(Generic[T]):
117
120
  self.cursor_ttl = cursor_ttl
118
121
  self.enable_prefetch = enable_prefetch
119
122
  self._cache: Dict[str, Any] = {}
120
-
123
+
121
124
  def paginate_list(
122
125
  self,
123
126
  items: List[T],
124
127
  cursor: Optional[str] = None,
125
128
  page_size: Optional[int] = None,
126
- sort_key: Optional[str] = None
129
+ sort_key: Optional[str] = None,
127
130
  ) -> Dict[str, Any]:
128
131
  """Paginate a list with optimal performance.
129
-
132
+
130
133
  Args:
131
134
  items: List to paginate
132
135
  cursor: Optional cursor from previous request
133
136
  page_size: Override default page size
134
137
  sort_key: Sort field for consistent ordering
135
-
138
+
136
139
  Returns:
137
140
  Dict with items and optional nextCursor
138
141
  """
139
142
  # Parse cursor or create new
140
143
  cursor_data = CursorData.from_cursor(cursor) if cursor else CursorData()
141
-
144
+
142
145
  # Use provided page size or default
143
146
  actual_page_size = min(
144
- page_size or cursor_data.page_size or self.page_size,
145
- self.max_page_size
147
+ page_size or cursor_data.page_size or self.page_size, self.max_page_size
146
148
  )
147
-
149
+
148
150
  # Get starting position
149
151
  start_idx = cursor_data.offset
150
-
152
+
151
153
  # Validate bounds
152
154
  if start_idx >= len(items):
153
155
  return {"items": [], "hasMore": False}
154
-
156
+
155
157
  # Slice the page
156
158
  end_idx = min(start_idx + actual_page_size, len(items))
157
159
  page_items = items[start_idx:end_idx]
158
-
160
+
159
161
  # Build response
160
162
  response = {
161
163
  "items": page_items,
@@ -163,161 +165,156 @@ class FastMCPPaginator(Generic[T]):
163
165
  "startIndex": start_idx,
164
166
  "endIndex": end_idx,
165
167
  "pageSize": len(page_items),
166
- "totalItems": len(items)
167
- }
168
+ "totalItems": len(items),
169
+ },
168
170
  }
169
-
171
+
170
172
  # Create next cursor if more items exist
171
173
  if end_idx < len(items):
172
174
  next_cursor_data = CursorData(
173
175
  offset=end_idx,
174
176
  page_size=actual_page_size,
175
- expires_at=time.time() + self.cursor_ttl if self.cursor_ttl else None
177
+ expires_at=time.time() + self.cursor_ttl if self.cursor_ttl else None,
176
178
  )
177
179
  response["nextCursor"] = next_cursor_data.to_cursor()
178
180
  response["hasMore"] = True
179
181
  else:
180
182
  response["hasMore"] = False
181
-
183
+
182
184
  return response
183
-
185
+
184
186
  def paginate_query(
185
187
  self,
186
188
  query_func,
187
189
  cursor: Optional[str] = None,
188
190
  page_size: Optional[int] = None,
189
- **query_params
191
+ **query_params,
190
192
  ) -> Dict[str, Any]:
191
193
  """Paginate results from a query function.
192
-
194
+
193
195
  This is optimized for database queries using indexed fields.
194
-
196
+
195
197
  Args:
196
198
  query_func: Function that accepts (last_id, last_timestamp, limit, **params)
197
199
  cursor: Optional cursor
198
200
  page_size: Override page size
199
201
  **query_params: Additional query parameters
200
-
202
+
201
203
  Returns:
202
204
  Paginated response
203
205
  """
204
206
  # Parse cursor
205
207
  cursor_data = CursorData.from_cursor(cursor) if cursor else CursorData()
206
-
208
+
207
209
  # Determine page size
208
210
  limit = min(
209
- page_size or cursor_data.page_size or self.page_size,
210
- self.max_page_size
211
+ page_size or cursor_data.page_size or self.page_size, self.max_page_size
211
212
  )
212
-
213
+
213
214
  # Execute query with cursor position
214
215
  results = query_func(
215
216
  last_id=cursor_data.last_id,
216
217
  last_timestamp=cursor_data.last_timestamp,
217
218
  limit=limit + 1, # Fetch one extra to detect more
218
- **query_params
219
+ **query_params,
219
220
  )
220
-
221
+
221
222
  # Check if there are more results
222
223
  has_more = len(results) > limit
223
224
  if has_more:
224
225
  results = results[:limit] # Remove the extra item
225
-
226
+
226
227
  # Build response
227
228
  response = {
228
229
  "items": results,
229
- "pageInfo": {
230
- "pageSize": len(results),
231
- "hasMore": has_more
232
- }
230
+ "pageInfo": {"pageSize": len(results), "hasMore": has_more},
233
231
  }
234
-
232
+
235
233
  # Create next cursor if needed
236
234
  if has_more and results:
237
235
  last_item = results[-1]
238
236
  next_cursor_data = CursorData(
239
- last_id=getattr(last_item, 'id', None),
240
- last_timestamp=getattr(last_item, 'timestamp', None),
237
+ last_id=getattr(last_item, "id", None),
238
+ last_timestamp=getattr(last_item, "timestamp", None),
241
239
  page_size=limit,
242
240
  sort_field=cursor_data.sort_field,
243
241
  sort_order=cursor_data.sort_order,
244
- expires_at=time.time() + self.cursor_ttl if self.cursor_ttl else None
242
+ expires_at=time.time() + self.cursor_ttl if self.cursor_ttl else None,
245
243
  )
246
244
  response["nextCursor"] = next_cursor_data.to_cursor()
247
-
245
+
248
246
  return response
249
247
 
250
248
 
251
249
  class TokenAwarePaginator:
252
250
  """Paginator that respects token limits for LLM responses."""
253
-
251
+
254
252
  def __init__(self, max_tokens: int = 20000):
255
253
  """Initialize token-aware paginator.
256
-
254
+
257
255
  Args:
258
256
  max_tokens: Maximum tokens per response
259
257
  """
260
258
  self.max_tokens = max_tokens
261
259
  self.paginator = FastMCPPaginator()
262
-
260
+
263
261
  def paginate_by_tokens(
264
- self,
265
- items: List[Any],
266
- cursor: Optional[str] = None,
267
- estimate_func=None
262
+ self, items: List[Any], cursor: Optional[str] = None, estimate_func=None
268
263
  ) -> Dict[str, Any]:
269
264
  """Paginate items based on token count.
270
-
265
+
271
266
  Args:
272
267
  items: Items to paginate
273
268
  cursor: Optional cursor
274
269
  estimate_func: Function to estimate tokens for an item
275
-
270
+
276
271
  Returns:
277
272
  Paginated response
278
273
  """
279
274
  from hanzo_mcp.tools.common.truncate import estimate_tokens
280
-
275
+
281
276
  # Default token estimation
282
277
  if not estimate_func:
283
- estimate_func = lambda x: estimate_tokens(json.dumps(x) if not isinstance(x, str) else x)
284
-
278
+ estimate_func = lambda x: estimate_tokens(
279
+ json.dumps(x) if not isinstance(x, str) else x
280
+ )
281
+
285
282
  # Parse cursor
286
283
  cursor_data = CursorData.from_cursor(cursor) if cursor else CursorData()
287
284
  start_idx = cursor_data.offset
288
-
285
+
289
286
  # Build page respecting token limit
290
287
  page_items = []
291
288
  current_tokens = 100 # Base overhead
292
289
  current_idx = start_idx
293
-
290
+
294
291
  while current_idx < len(items) and current_tokens < self.max_tokens:
295
292
  item = items[current_idx]
296
293
  item_tokens = estimate_func(item)
297
-
294
+
298
295
  # Check if adding this item would exceed limit
299
296
  if current_tokens + item_tokens > self.max_tokens and page_items:
300
297
  break
301
-
298
+
302
299
  page_items.append(item)
303
300
  current_tokens += item_tokens
304
301
  current_idx += 1
305
-
302
+
306
303
  # Build response
307
304
  response = {
308
305
  "items": page_items,
309
306
  "pageInfo": {
310
307
  "itemCount": len(page_items),
311
308
  "estimatedTokens": current_tokens,
312
- "hasMore": current_idx < len(items)
313
- }
309
+ "hasMore": current_idx < len(items),
310
+ },
314
311
  }
315
-
312
+
316
313
  # Add next cursor if needed
317
314
  if current_idx < len(items):
318
315
  next_cursor_data = CursorData(offset=current_idx)
319
316
  response["nextCursor"] = next_cursor_data.to_cursor()
320
-
317
+
321
318
  return response
322
319
 
323
320
 
@@ -326,44 +323,44 @@ def create_paginated_response(
326
323
  items: Union[List[Any], Dict[str, Any], str],
327
324
  cursor: Optional[str] = None,
328
325
  page_size: int = 100,
329
- use_token_limit: bool = True
326
+ use_token_limit: bool = True,
330
327
  ) -> Dict[str, Any]:
331
328
  """Create a paginated response compatible with FastMCP.
332
-
329
+
333
330
  Args:
334
331
  items: The items to paginate
335
332
  cursor: Optional cursor from request
336
333
  page_size: Items per page
337
334
  use_token_limit: Whether to use token-based pagination
338
-
335
+
339
336
  Returns:
340
337
  FastMCP-compatible paginated response
341
338
  """
342
339
  if use_token_limit:
343
340
  paginator = TokenAwarePaginator()
344
-
341
+
345
342
  # Convert different types to list
346
343
  if isinstance(items, str):
347
344
  # Split string by lines for pagination
348
- items = items.split('\n')
345
+ items = items.split("\n")
349
346
  elif isinstance(items, dict):
350
347
  # Convert dict to list of key-value pairs
351
348
  items = [{"key": k, "value": v} for k, v in items.items()]
352
-
349
+
353
350
  return paginator.paginate_by_tokens(items, cursor)
354
351
  else:
355
352
  paginator = FastMCPPaginator(page_size=page_size)
356
-
353
+
357
354
  # Handle different input types
358
355
  if isinstance(items, list):
359
356
  return paginator.paginate_list(items, cursor, page_size)
360
357
  else:
361
358
  # Convert to list first
362
359
  if isinstance(items, str):
363
- items = items.split('\n')
360
+ items = items.split("\n")
364
361
  elif isinstance(items, dict):
365
362
  items = [{"key": k, "value": v} for k, v in items.items()]
366
363
  else:
367
364
  items = [items]
368
-
369
- return paginator.paginate_list(items, cursor, page_size)
365
+
366
+ return paginator.paginate_list(items, cursor, page_size)