awslabs.valkey-mcp-server 0.1.1__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.
- awslabs/__init__.py +13 -0
- awslabs/valkey_mcp_server/__init__.py +14 -0
- awslabs/valkey_mcp_server/common/__init__.py +20 -0
- awslabs/valkey_mcp_server/common/config.py +85 -0
- awslabs/valkey_mcp_server/common/connection.py +97 -0
- awslabs/valkey_mcp_server/common/server.py +23 -0
- awslabs/valkey_mcp_server/main.py +84 -0
- awslabs/valkey_mcp_server/tools/__init__.py +28 -0
- awslabs/valkey_mcp_server/tools/bitmap.py +148 -0
- awslabs/valkey_mcp_server/tools/hash.py +283 -0
- awslabs/valkey_mcp_server/tools/hyperloglog.py +58 -0
- awslabs/valkey_mcp_server/tools/json.py +422 -0
- awslabs/valkey_mcp_server/tools/list.py +376 -0
- awslabs/valkey_mcp_server/tools/misc.py +104 -0
- awslabs/valkey_mcp_server/tools/server_management.py +54 -0
- awslabs/valkey_mcp_server/tools/set.py +182 -0
- awslabs/valkey_mcp_server/tools/sorted_set.py +359 -0
- awslabs/valkey_mcp_server/tools/stream.py +343 -0
- awslabs/valkey_mcp_server/tools/string.py +228 -0
- awslabs/valkey_mcp_server/version.py +12 -0
- awslabs_valkey_mcp_server-0.1.1.dist-info/METADATA +164 -0
- awslabs_valkey_mcp_server-0.1.1.dist-info/RECORD +26 -0
- awslabs_valkey_mcp_server-0.1.1.dist-info/WHEEL +4 -0
- awslabs_valkey_mcp_server-0.1.1.dist-info/entry_points.txt +2 -0
- awslabs_valkey_mcp_server-0.1.1.dist-info/licenses/LICENSE +175 -0
- awslabs_valkey_mcp_server-0.1.1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
|
|
4
|
+
# with the License. A copy of the License is located at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
|
|
9
|
+
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
|
|
10
|
+
# and limitations under the License.
|
|
11
|
+
|
|
12
|
+
"""JSON operations for Valkey MCP Server."""
|
|
13
|
+
|
|
14
|
+
from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
|
|
15
|
+
from awslabs.valkey_mcp_server.common.server import mcp
|
|
16
|
+
from typing import Any, Optional, Union
|
|
17
|
+
from valkey.exceptions import ValkeyError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@mcp.tool()
|
|
21
|
+
async def json_set(key: str, path: str, value: Any, nx: bool = False, xx: bool = False) -> str:
|
|
22
|
+
"""Set the JSON value at path.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
key: The name of the key
|
|
26
|
+
path: The path in the JSON document (e.g., "$.name" or "." for root)
|
|
27
|
+
value: The value to set
|
|
28
|
+
nx: Only set if path doesn't exist
|
|
29
|
+
xx: Only set if path exists
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Success message or error message
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
r = ValkeyConnectionManager.get_connection()
|
|
36
|
+
options = {}
|
|
37
|
+
if nx:
|
|
38
|
+
options['nx'] = True
|
|
39
|
+
if xx:
|
|
40
|
+
options['xx'] = True
|
|
41
|
+
|
|
42
|
+
result = r.json().set(key, path, value, **options)
|
|
43
|
+
if result:
|
|
44
|
+
return f"Successfully set value at path '{path}' in '{key}'"
|
|
45
|
+
return f"Failed to set value at path '{path}' in '{key}' (path condition not met)"
|
|
46
|
+
except ValkeyError as e:
|
|
47
|
+
return f"Error setting JSON value in '{key}': {str(e)}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@mcp.tool()
|
|
51
|
+
async def json_get(
|
|
52
|
+
key: str,
|
|
53
|
+
path: Optional[str] = None,
|
|
54
|
+
indent: Optional[int] = None,
|
|
55
|
+
newline: Optional[bool] = None,
|
|
56
|
+
space: Optional[bool] = None,
|
|
57
|
+
) -> str:
|
|
58
|
+
"""Get the JSON value at path.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
key: The name of the key
|
|
62
|
+
path: The path in the JSON document (optional, defaults to root)
|
|
63
|
+
indent: Number of spaces for indentation (optional)
|
|
64
|
+
newline: Add newlines in formatted output (optional)
|
|
65
|
+
space: Add spaces in formatted output (optional)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
JSON value or error message
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
r = ValkeyConnectionManager.get_connection()
|
|
72
|
+
options = {}
|
|
73
|
+
if indent is not None:
|
|
74
|
+
options['indent'] = indent
|
|
75
|
+
if newline is not None:
|
|
76
|
+
options['newline'] = newline
|
|
77
|
+
if space is not None:
|
|
78
|
+
options['space'] = space
|
|
79
|
+
|
|
80
|
+
result = r.json().get(key, path, **options) if path else r.json().get(key)
|
|
81
|
+
if result is None:
|
|
82
|
+
return f"No value found at path '{path or '.'}' in '{key}'"
|
|
83
|
+
return str(result)
|
|
84
|
+
except ValkeyError as e:
|
|
85
|
+
return f"Error getting JSON value from '{key}': {str(e)}"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@mcp.tool()
|
|
89
|
+
async def json_type(key: str, path: Optional[str] = None) -> str:
|
|
90
|
+
"""Get the type of JSON value at path.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
key: The name of the key
|
|
94
|
+
path: The path in the JSON document (optional, defaults to root)
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
JSON type or error message
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
r = ValkeyConnectionManager.get_connection()
|
|
101
|
+
result = r.json().type(key, path) if path else r.json().type(key)
|
|
102
|
+
if result is None:
|
|
103
|
+
return f"No value found at path '{path or '.'}' in '{key}'"
|
|
104
|
+
return f"Type at path '{path or '.'}' in '{key}': {result}"
|
|
105
|
+
except ValkeyError as e:
|
|
106
|
+
return f"Error getting JSON type from '{key}': {str(e)}"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
async def json_numincrby(key: str, path: str, value: Union[int, float]) -> str:
|
|
111
|
+
"""Increment the number at path by value.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
key: The name of the key
|
|
115
|
+
path: The path in the JSON document
|
|
116
|
+
value: The increment value (integer or float)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
New value or error message
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
r = ValkeyConnectionManager.get_connection()
|
|
123
|
+
# Convert float to int by rounding if needed
|
|
124
|
+
int_value = round(value) if isinstance(value, float) else value
|
|
125
|
+
result = r.json().numincrby(key, path, int_value)
|
|
126
|
+
return f"Value at path '{path}' in '{key}' incremented to {result}"
|
|
127
|
+
except ValkeyError as e:
|
|
128
|
+
return f"Error incrementing JSON value in '{key}': {str(e)}"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@mcp.tool()
|
|
132
|
+
async def json_nummultby(key: str, path: str, value: Union[int, float]) -> str:
|
|
133
|
+
"""Multiply the number at path by value.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
key: The name of the key
|
|
137
|
+
path: The path in the JSON document
|
|
138
|
+
value: The multiplier value (integer or float)
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
New value or error message
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
r = ValkeyConnectionManager.get_connection()
|
|
145
|
+
# Convert float to int by rounding if needed
|
|
146
|
+
int_value = round(value) if isinstance(value, float) else value
|
|
147
|
+
result = r.json().nummultby(key, path, int_value)
|
|
148
|
+
return f"Value at path '{path}' in '{key}' multiplied to {result}"
|
|
149
|
+
except ValkeyError as e:
|
|
150
|
+
return f"Error multiplying JSON value in '{key}': {str(e)}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@mcp.tool()
|
|
154
|
+
async def json_strappend(key: str, path: str, value: str) -> str:
|
|
155
|
+
"""Append a string to the string at path.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
key: The name of the key
|
|
159
|
+
path: The path in the JSON document
|
|
160
|
+
value: The string to append
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
New string length or error message
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
r = ValkeyConnectionManager.get_connection()
|
|
167
|
+
result = r.json().strappend(key, path, value)
|
|
168
|
+
return f"String at path '{path}' in '{key}' appended, new length: {result}"
|
|
169
|
+
except ValkeyError as e:
|
|
170
|
+
return f"Error appending to JSON string in '{key}': {str(e)}"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@mcp.tool()
|
|
174
|
+
async def json_strlen(key: str, path: str) -> str:
|
|
175
|
+
"""Get the length of string at path.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
key: The name of the key
|
|
179
|
+
path: The path in the JSON document
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
String length or error message
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
r = ValkeyConnectionManager.get_connection()
|
|
186
|
+
result = r.json().strlen(key, path)
|
|
187
|
+
if result is None:
|
|
188
|
+
return f"No string found at path '{path}' in '{key}'"
|
|
189
|
+
return f"Length of string at path '{path}' in '{key}': {result}"
|
|
190
|
+
except ValkeyError as e:
|
|
191
|
+
return f"Error getting JSON string length from '{key}': {str(e)}"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@mcp.tool()
|
|
195
|
+
async def json_arrappend(key: str, path: str, *values: Any) -> str:
|
|
196
|
+
"""Append values to the array at path.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
key: The name of the key
|
|
200
|
+
path: The path in the JSON document
|
|
201
|
+
*values: One or more values to append
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
New array length or error message
|
|
205
|
+
"""
|
|
206
|
+
try:
|
|
207
|
+
if not values:
|
|
208
|
+
return 'Error: at least one value is required'
|
|
209
|
+
|
|
210
|
+
r = ValkeyConnectionManager.get_connection()
|
|
211
|
+
result = r.json().arrappend(key, path, *values)
|
|
212
|
+
return f"Array at path '{path}' in '{key}' appended, new length: {result}"
|
|
213
|
+
except ValkeyError as e:
|
|
214
|
+
return f"Error appending to JSON array in '{key}': {str(e)}"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@mcp.tool()
|
|
218
|
+
async def json_arrindex(
|
|
219
|
+
key: str, path: str, value: Any, start: Optional[int] = None, stop: Optional[int] = None
|
|
220
|
+
) -> str:
|
|
221
|
+
"""Get the index of value in array at path.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
key: The name of the key
|
|
225
|
+
path: The path in the JSON document
|
|
226
|
+
value: The value to search for
|
|
227
|
+
start: Start offset (optional)
|
|
228
|
+
stop: Stop offset (optional)
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Index or error message
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
r = ValkeyConnectionManager.get_connection()
|
|
235
|
+
args = [value]
|
|
236
|
+
if start is not None:
|
|
237
|
+
args.append(start)
|
|
238
|
+
if stop is not None:
|
|
239
|
+
args.append(stop)
|
|
240
|
+
|
|
241
|
+
result = r.json().arrindex(key, path, *args)
|
|
242
|
+
if result == -1:
|
|
243
|
+
range_str = ''
|
|
244
|
+
if start is not None or stop is not None:
|
|
245
|
+
range_str = f' in range [{start or 0}, {stop or "∞"}]'
|
|
246
|
+
return f"Value not found in array at path '{path}' in '{key}'{range_str}"
|
|
247
|
+
return f"Value found at index {result} in array at path '{path}' in '{key}'"
|
|
248
|
+
except ValkeyError as e:
|
|
249
|
+
return f"Error searching JSON array in '{key}': {str(e)}"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@mcp.tool()
|
|
253
|
+
async def json_arrlen(key: str, path: str) -> str:
|
|
254
|
+
"""Get the length of array at path.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
key: The name of the key
|
|
258
|
+
path: The path in the JSON document
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Array length or error message
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
r = ValkeyConnectionManager.get_connection()
|
|
265
|
+
result = r.json().arrlen(key, path)
|
|
266
|
+
if result is None:
|
|
267
|
+
return f"No array found at path '{path}' in '{key}'"
|
|
268
|
+
return f"Length of array at path '{path}' in '{key}': {result}"
|
|
269
|
+
except ValkeyError as e:
|
|
270
|
+
return f"Error getting JSON array length from '{key}': {str(e)}"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@mcp.tool()
|
|
274
|
+
async def json_arrpop(key: str, path: str, index: int = -1) -> str:
|
|
275
|
+
"""Pop a value from the array at path and index.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
key: The name of the key
|
|
279
|
+
path: The path in the JSON document
|
|
280
|
+
index: The index to pop from (-1 for last element)
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Popped value or error message
|
|
284
|
+
"""
|
|
285
|
+
try:
|
|
286
|
+
r = ValkeyConnectionManager.get_connection()
|
|
287
|
+
result = r.json().arrpop(key, path, index)
|
|
288
|
+
if result is None:
|
|
289
|
+
return f"No value found at index {index} in array at path '{path}' in '{key}'"
|
|
290
|
+
return f"Popped value from index {index} in array at path '{path}' in '{key}': {result}"
|
|
291
|
+
except ValkeyError as e:
|
|
292
|
+
return f"Error popping from JSON array in '{key}': {str(e)}"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@mcp.tool()
|
|
296
|
+
async def json_arrtrim(key: str, path: str, start: int, stop: int) -> str:
|
|
297
|
+
"""Trim array at path to include only elements within range.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
key: The name of the key
|
|
301
|
+
path: The path in the JSON document
|
|
302
|
+
start: Start index (inclusive)
|
|
303
|
+
stop: Stop index (inclusive)
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
New array length or error message
|
|
307
|
+
"""
|
|
308
|
+
try:
|
|
309
|
+
r = ValkeyConnectionManager.get_connection()
|
|
310
|
+
result = r.json().arrtrim(key, path, start, stop)
|
|
311
|
+
return f"Array at path '{path}' in '{key}' trimmed to range [{start}, {stop}], new length: {result}"
|
|
312
|
+
except ValkeyError as e:
|
|
313
|
+
return f"Error trimming JSON array in '{key}': {str(e)}"
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@mcp.tool()
|
|
317
|
+
async def json_objkeys(key: str, path: str) -> str:
|
|
318
|
+
"""Get the keys in the object at path.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
key: The name of the key
|
|
322
|
+
path: The path in the JSON document
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of keys or error message
|
|
326
|
+
"""
|
|
327
|
+
try:
|
|
328
|
+
r = ValkeyConnectionManager.get_connection()
|
|
329
|
+
result = r.json().objkeys(key, path)
|
|
330
|
+
if result is None:
|
|
331
|
+
return f"No object found at path '{path}' in '{key}'"
|
|
332
|
+
if not result:
|
|
333
|
+
return f"Object at path '{path}' in '{key}' has no keys"
|
|
334
|
+
# Filter out None values and ensure all elements are strings
|
|
335
|
+
valid_keys = [str(key) for key in result if key is not None]
|
|
336
|
+
return f"Keys in object at path '{path}' in '{key}': {', '.join(valid_keys)}"
|
|
337
|
+
except ValkeyError as e:
|
|
338
|
+
return f"Error getting JSON object keys from '{key}': {str(e)}"
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@mcp.tool()
|
|
342
|
+
async def json_objlen(key: str, path: str) -> str:
|
|
343
|
+
"""Get the number of keys in the object at path.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
key: The name of the key
|
|
347
|
+
path: The path in the JSON document
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Number of keys or error message
|
|
351
|
+
"""
|
|
352
|
+
try:
|
|
353
|
+
r = ValkeyConnectionManager.get_connection()
|
|
354
|
+
result = r.json().objlen(key, path)
|
|
355
|
+
if result is None:
|
|
356
|
+
return f"No object found at path '{path}' in '{key}'"
|
|
357
|
+
return f"Number of keys in object at path '{path}' in '{key}': {result}"
|
|
358
|
+
except ValkeyError as e:
|
|
359
|
+
return f"Error getting JSON object length from '{key}': {str(e)}"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@mcp.tool()
|
|
363
|
+
async def json_toggle(key: str, path: str) -> str:
|
|
364
|
+
"""Toggle boolean value at path.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
key: The name of the key
|
|
368
|
+
path: The path in the JSON document
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
New boolean value or error message
|
|
372
|
+
"""
|
|
373
|
+
try:
|
|
374
|
+
r = ValkeyConnectionManager.get_connection()
|
|
375
|
+
result = r.json().toggle(key, path)
|
|
376
|
+
if result is None:
|
|
377
|
+
return f"No boolean value found at path '{path}' in '{key}'"
|
|
378
|
+
return f"Boolean value at path '{path}' in '{key}' toggled to: {str(result).lower()}"
|
|
379
|
+
except ValkeyError as e:
|
|
380
|
+
return f"Error toggling JSON boolean in '{key}': {str(e)}"
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@mcp.tool()
|
|
384
|
+
async def json_clear(key: str, path: str) -> str:
|
|
385
|
+
"""Clear container at path (array or object).
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
key: The name of the key
|
|
389
|
+
path: The path in the JSON document
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Success message or error message
|
|
393
|
+
"""
|
|
394
|
+
try:
|
|
395
|
+
r = ValkeyConnectionManager.get_connection()
|
|
396
|
+
result = r.json().clear(key, path)
|
|
397
|
+
if result == 1:
|
|
398
|
+
return f"Successfully cleared container at path '{path}' in '{key}'"
|
|
399
|
+
return f"No container found at path '{path}' in '{key}'"
|
|
400
|
+
except ValkeyError as e:
|
|
401
|
+
return f"Error clearing JSON container in '{key}': {str(e)}"
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@mcp.tool()
|
|
405
|
+
async def json_del(key: str, path: str) -> str:
|
|
406
|
+
"""Delete value at path.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
key: The name of the key
|
|
410
|
+
path: The path in the JSON document
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Success message or error message
|
|
414
|
+
"""
|
|
415
|
+
try:
|
|
416
|
+
r = ValkeyConnectionManager.get_connection()
|
|
417
|
+
result = r.json().delete(key, path)
|
|
418
|
+
if result == 1:
|
|
419
|
+
return f"Successfully deleted value at path '{path}' in '{key}'"
|
|
420
|
+
return f"No value found at path '{path}' in '{key}'"
|
|
421
|
+
except ValkeyError as e:
|
|
422
|
+
return f"Error deleting JSON value in '{key}': {str(e)}"
|