@@ -136,3 +136,102 @@ async def vector_search_hash(query_vector: list,
136136 return [doc .__dict__ for doc in results .docs ]
137137 except RedisError as e :
138138 return f"Error performing vector search on index '{ index_name } ': { str (e )} "
139+
140+
141+ @mcp .tool ()
142+ async def get_all_keys (pattern : str = "*" ) -> list :
143+ """
144+ Retrieve all keys matching a pattern from the Redis database using the KEYS command.
145+
146+ Note: The KEYS command is blocking and can impact performance on large databases.
147+ For production use with large datasets, consider using SCAN instead.
148+
149+ Args:
150+ pattern: Pattern to match keys against (default is "*" for all keys).
151+ Common patterns: "user:*", "cache:*", "*:123", etc.
152+
153+ Returns:
154+ A list of keys matching the pattern or an error message.
155+ """
156+ try :
157+ r = RedisConnectionManager .get_connection ()
158+ keys = r .keys (pattern )
159+ # Convert bytes to strings if needed
160+ return [key .decode ('utf-8' ) if isinstance (key , bytes ) else key for key in keys ]
161+ except RedisError as e :
162+ return f"Error retrieving keys with pattern '{ pattern } ': { str (e )} "
163+
164+
165+ @mcp .tool ()
166+ async def scan_keys (pattern : str = "*" , count : int = 100 , cursor : int = 0 ) -> dict :
167+ """
168+ Scan keys in the Redis database using the SCAN command (non-blocking, production-safe).
169+
170+ The SCAN command iterates through the keyspace in small chunks, making it safe to use
171+ on large databases without blocking other operations.
172+
173+ Args:
174+ pattern: Pattern to match keys against (default is "*" for all keys).
175+ Common patterns: "user:*", "cache:*", "*:123", etc.
176+ count: Hint for the number of keys to return per iteration (default 100).
177+ Redis may return more or fewer keys than this hint.
178+ cursor: The cursor position to start scanning from (0 to start from beginning).
179+
180+ Returns:
181+ A dictionary containing:
182+ - 'cursor': Next cursor position (0 means scan is complete)
183+ - 'keys': List of keys found in this iteration
184+ - 'total_scanned': Number of keys returned in this batch
185+ Or an error message if something goes wrong.
186+ """
187+ try :
188+ r = RedisConnectionManager .get_connection ()
189+ cursor , keys = r .scan (cursor = cursor , match = pattern , count = count )
190+
191+ # Convert bytes to strings if needed
192+ decoded_keys = [key .decode ('utf-8' ) if isinstance (key , bytes ) else key for key in keys ]
193+
194+ return {
195+ 'cursor' : cursor ,
196+ 'keys' : decoded_keys ,
197+ 'total_scanned' : len (decoded_keys ),
198+ 'scan_complete' : cursor == 0
199+ }
200+ except RedisError as e :
201+ return f"Error scanning keys with pattern '{ pattern } ': { str (e )} "
202+
203+
204+ @mcp .tool ()
205+ async def scan_all_keys (pattern : str = "*" , batch_size : int = 100 ) -> list :
206+ """
207+ Scan and return ALL keys matching a pattern using multiple SCAN iterations.
208+
209+ This function automatically handles the SCAN cursor iteration to collect all matching keys.
210+ It's safer than KEYS * for large databases but will still collect all results in memory.
211+
212+ Args:
213+ pattern: Pattern to match keys against (default is "*" for all keys).
214+ batch_size: Number of keys to scan per iteration (default 100).
215+
216+ Returns:
217+ A list of all keys matching the pattern or an error message.
218+ """
219+ try :
220+ r = RedisConnectionManager .get_connection ()
221+ all_keys = []
222+ cursor = 0
223+
224+ while True :
225+ cursor , keys = r .scan (cursor = cursor , match = pattern , count = batch_size )
226+
227+ # Convert bytes to strings if needed and add to results
228+ decoded_keys = [key .decode ('utf-8' ) if isinstance (key , bytes ) else key for key in keys ]
229+ all_keys .extend (decoded_keys )
230+
231+ # Break when scan is complete (cursor returns to 0)
232+ if cursor == 0 :
233+ break
234+
235+ return all_keys
236+ except RedisError as e :
237+ return f"Error scanning all keys with pattern '{ pattern } ': { str (e )} "
0 commit comments