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.
@@ -0,0 +1,343 @@
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
+ """Stream 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, Dict, Optional
17
+ from valkey.exceptions import ValkeyError
18
+
19
+
20
+ @mcp.tool()
21
+ async def stream_add(
22
+ key: str,
23
+ field_dict: Dict[str, Any],
24
+ id: str = '*',
25
+ maxlen: Optional[int] = None,
26
+ approximate: bool = True,
27
+ ) -> str:
28
+ """Add entry to stream.
29
+
30
+ Args:
31
+ key: The name of the key
32
+ field_dict: Dictionary of field-value pairs
33
+ id: Entry ID (default "*" for auto-generation)
34
+ maxlen: Maximum length of stream (optional)
35
+ approximate: Whether maxlen is approximate
36
+
37
+ Returns:
38
+ Success message or error message
39
+ """
40
+ try:
41
+ r = ValkeyConnectionManager.get_connection()
42
+ options = {}
43
+ if maxlen is not None:
44
+ if approximate:
45
+ options['maxlen'] = '~' + str(maxlen)
46
+ else:
47
+ options['maxlen'] = maxlen
48
+
49
+ result = r.xadd(key, field_dict, id=id, **options)
50
+ return f"Successfully added entry with ID '{result}' to stream '{key}'"
51
+ except ValkeyError as e:
52
+ return f"Error adding to stream '{key}': {str(e)}"
53
+
54
+
55
+ @mcp.tool()
56
+ async def stream_delete(key: str, id: str) -> str:
57
+ """Delete entries from stream.
58
+
59
+ Args:
60
+ key: The name of the key
61
+ id: Entry ID to delete
62
+
63
+ Returns:
64
+ Success message or error message
65
+ """
66
+ try:
67
+ r = ValkeyConnectionManager.get_connection()
68
+ result = r.xdel(key, id)
69
+ return f"Successfully deleted {result} entries from stream '{key}'"
70
+ except ValkeyError as e:
71
+ return f"Error deleting from stream '{key}': {str(e)}"
72
+
73
+
74
+ @mcp.tool()
75
+ async def stream_trim(key: str, maxlen: int, approximate: bool = True) -> str:
76
+ """Trim stream to specified length.
77
+
78
+ Args:
79
+ key: The name of the key
80
+ maxlen: Maximum length to trim to
81
+ approximate: Whether maxlen is approximate
82
+
83
+ Returns:
84
+ Success message or error message
85
+ """
86
+ try:
87
+ r = ValkeyConnectionManager.get_connection()
88
+ result = r.xtrim(key, maxlen=maxlen, approximate=approximate)
89
+ return f"Successfully trimmed stream '{key}', removed {result} entries"
90
+ except ValkeyError as e:
91
+ return f"Error trimming stream '{key}': {str(e)}"
92
+
93
+
94
+ @mcp.tool()
95
+ async def stream_length(key: str) -> str:
96
+ """Get length of stream.
97
+
98
+ Args:
99
+ key: The name of the key
100
+
101
+ Returns:
102
+ Length or error message
103
+ """
104
+ try:
105
+ r = ValkeyConnectionManager.get_connection()
106
+ result = r.xlen(key)
107
+ return str(result)
108
+ except ValkeyError as e:
109
+ return f"Error getting stream length for '{key}': {str(e)}"
110
+
111
+
112
+ @mcp.tool()
113
+ async def stream_range(
114
+ key: str, start: str = '-', end: str = '+', count: Optional[int] = None, reverse: bool = False
115
+ ) -> str:
116
+ """Get range of entries from stream.
117
+
118
+ Args:
119
+ key: The name of the key
120
+ start: Start ID (default "-" for beginning)
121
+ end: End ID (default "+" for end)
122
+ count: Maximum number of entries to return
123
+ reverse: Return entries in reverse order
124
+
125
+ Returns:
126
+ List of entries or error message
127
+ """
128
+ try:
129
+ r = ValkeyConnectionManager.get_connection()
130
+ result = (
131
+ r.xrevrange(key, end, start, count=count)
132
+ if reverse
133
+ else r.xrange(key, start, end, count=count)
134
+ )
135
+ if not result:
136
+ return f"No entries found in range for stream '{key}'"
137
+ return str(result)
138
+ except ValkeyError as e:
139
+ return f"Error getting range from stream '{key}': {str(e)}"
140
+
141
+
142
+ @mcp.tool()
143
+ async def stream_read(
144
+ key: str, count: Optional[int] = None, block: Optional[int] = None, last_id: str = '$'
145
+ ) -> str:
146
+ """Read entries from stream.
147
+
148
+ Args:
149
+ key: The name of the key
150
+ count: Maximum number of entries to return
151
+ block: Milliseconds to block (optional)
152
+ last_id: Last ID received (default "$" for new entries only)
153
+
154
+ Returns:
155
+ List of entries or error message
156
+ """
157
+ try:
158
+ r = ValkeyConnectionManager.get_connection()
159
+ streams = {key: last_id}
160
+ result = r.xread(streams, count=count, block=block)
161
+ if not result:
162
+ return f"No new entries in stream '{key}'"
163
+ return str(result)
164
+ except ValkeyError as e:
165
+ return f"Error reading from stream '{key}': {str(e)}"
166
+
167
+
168
+ @mcp.tool()
169
+ async def stream_group_create(
170
+ key: str, group_name: str, id: str = '$', mkstream: bool = False
171
+ ) -> str:
172
+ """Create consumer group.
173
+
174
+ Args:
175
+ key: The name of the key
176
+ group_name: Name of consumer group
177
+ id: ID to start reading from (default "$" for new entries only)
178
+ mkstream: Create stream if it doesn't exist
179
+
180
+ Returns:
181
+ Success message or error message
182
+ """
183
+ try:
184
+ r = ValkeyConnectionManager.get_connection()
185
+ r.xgroup_create(key, group_name, id=id, mkstream=mkstream)
186
+ return f"Successfully created consumer group '{group_name}' for stream '{key}'"
187
+ except ValkeyError as e:
188
+ return f'Error creating consumer group: {str(e)}'
189
+
190
+
191
+ @mcp.tool()
192
+ async def stream_group_destroy(key: str, group_name: str) -> str:
193
+ """Destroy consumer group.
194
+
195
+ Args:
196
+ key: The name of the key
197
+ group_name: Name of consumer group
198
+
199
+ Returns:
200
+ Success message or error message
201
+ """
202
+ try:
203
+ r = ValkeyConnectionManager.get_connection()
204
+ result = r.xgroup_destroy(key, group_name)
205
+ if result:
206
+ return f"Successfully destroyed consumer group '{group_name}' from stream '{key}'"
207
+ return f"Consumer group '{group_name}' not found in stream '{key}'"
208
+ except ValkeyError as e:
209
+ return f'Error destroying consumer group: {str(e)}'
210
+
211
+
212
+ @mcp.tool()
213
+ async def stream_group_set_id(key: str, group_name: str, id: str) -> str:
214
+ """Set consumer group's last delivered ID.
215
+
216
+ Args:
217
+ key: The name of the key
218
+ group_name: Name of consumer group
219
+ id: ID to set as last delivered
220
+
221
+ Returns:
222
+ Success message or error message
223
+ """
224
+ try:
225
+ r = ValkeyConnectionManager.get_connection()
226
+ r.xgroup_setid(key, group_name, id)
227
+ return f"Successfully set last delivered ID for group '{group_name}' in stream '{key}'"
228
+ except ValkeyError as e:
229
+ return f'Error setting group ID: {str(e)}'
230
+
231
+
232
+ @mcp.tool()
233
+ async def stream_group_delete_consumer(key: str, group_name: str, consumer_name: str) -> str:
234
+ """Delete consumer from group.
235
+
236
+ Args:
237
+ key: The name of the key
238
+ group_name: Name of consumer group
239
+ consumer_name: Name of consumer to delete
240
+
241
+ Returns:
242
+ Success message or error message
243
+ """
244
+ try:
245
+ r = ValkeyConnectionManager.get_connection()
246
+ result = r.xgroup_delconsumer(key, group_name, consumer_name)
247
+ return f"Successfully deleted consumer '{consumer_name}' from group '{group_name}', {result} pending entries"
248
+ except ValkeyError as e:
249
+ return f'Error deleting consumer: {str(e)}'
250
+
251
+
252
+ @mcp.tool()
253
+ async def stream_read_group(
254
+ key: str,
255
+ group_name: str,
256
+ consumer_name: str,
257
+ count: Optional[int] = None,
258
+ block: Optional[int] = None,
259
+ noack: bool = False,
260
+ ) -> str:
261
+ """Read entries from stream as part of consumer group.
262
+
263
+ Args:
264
+ key: The name of the key
265
+ group_name: Name of consumer group
266
+ consumer_name: Name of this consumer
267
+ count: Maximum number of entries to return
268
+ block: Milliseconds to block (optional)
269
+ noack: Don't require acknowledgment
270
+
271
+ Returns:
272
+ List of entries or error message
273
+ """
274
+ try:
275
+ r = ValkeyConnectionManager.get_connection()
276
+ streams = {key: '>'} # ">" means read undelivered entries
277
+ result = r.xreadgroup(
278
+ group_name, consumer_name, streams, count=count, block=block, noack=noack
279
+ )
280
+ if not result:
281
+ return f"No new entries for consumer '{consumer_name}' in group '{group_name}'"
282
+ return str(result)
283
+ except ValkeyError as e:
284
+ return f'Error reading from group: {str(e)}'
285
+
286
+
287
+ @mcp.tool()
288
+ async def stream_info(key: str) -> str:
289
+ """Get information about stream.
290
+
291
+ Args:
292
+ key: The name of the key
293
+
294
+ Returns:
295
+ Stream information or error message
296
+ """
297
+ try:
298
+ r = ValkeyConnectionManager.get_connection()
299
+ result = r.xinfo_stream(key)
300
+ return str(result)
301
+ except ValkeyError as e:
302
+ return f"Error getting stream info for '{key}': {str(e)}"
303
+
304
+
305
+ @mcp.tool()
306
+ async def stream_info_groups(key: str) -> str:
307
+ """Get information about consumer groups.
308
+
309
+ Args:
310
+ key: The name of the key
311
+
312
+ Returns:
313
+ Consumer groups information or error message
314
+ """
315
+ try:
316
+ r = ValkeyConnectionManager.get_connection()
317
+ result = r.xinfo_groups(key)
318
+ if not result:
319
+ return f"No consumer groups found for stream '{key}'"
320
+ return str(result)
321
+ except ValkeyError as e:
322
+ return f"Error getting consumer groups info for '{key}': {str(e)}"
323
+
324
+
325
+ @mcp.tool()
326
+ async def stream_info_consumers(key: str, group_name: str) -> str:
327
+ """Get information about consumers in group.
328
+
329
+ Args:
330
+ key: The name of the key
331
+ group_name: Name of consumer group
332
+
333
+ Returns:
334
+ Consumers information or error message
335
+ """
336
+ try:
337
+ r = ValkeyConnectionManager.get_connection()
338
+ result = r.xinfo_consumers(key, group_name)
339
+ if not result:
340
+ return f"No consumers found in group '{group_name}'"
341
+ return str(result)
342
+ except ValkeyError as e:
343
+ return f'Error getting consumers info: {str(e)}'
@@ -0,0 +1,228 @@
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
+ """String 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
17
+ from valkey.exceptions import ValkeyError
18
+
19
+
20
+ @mcp.tool()
21
+ async def string_set(
22
+ key: str,
23
+ value: Any,
24
+ ex: Optional[int] = None,
25
+ px: Optional[int] = None,
26
+ nx: bool = False,
27
+ xx: bool = False,
28
+ keepttl: bool = False,
29
+ ) -> str:
30
+ """Set string value.
31
+
32
+ Args:
33
+ key: The name of the key
34
+ value: The value to set
35
+ ex: Expire time in seconds
36
+ px: Expire time in milliseconds
37
+ nx: Only set if key does not exist
38
+ xx: Only set if key exists
39
+ keepttl: Retain the time to live associated with the key
40
+
41
+ Returns:
42
+ Success message or error message
43
+ """
44
+ try:
45
+ r = ValkeyConnectionManager.get_connection()
46
+ result = r.set(key, value, ex=ex, px=px, nx=nx, xx=xx, keepttl=keepttl)
47
+ if result is None:
48
+ return f"Failed to set value for key '{key}' (condition not met)"
49
+ return f"Successfully set value for key '{key}'"
50
+ except ValkeyError as e:
51
+ return f"Error setting string value for '{key}': {str(e)}"
52
+
53
+
54
+ @mcp.tool()
55
+ async def string_get(key: str) -> str:
56
+ """Get string value.
57
+
58
+ Args:
59
+ key: The name of the key
60
+
61
+ Returns:
62
+ Value or error message
63
+ """
64
+ try:
65
+ r = ValkeyConnectionManager.get_connection()
66
+ result = r.get(key)
67
+ if result is None:
68
+ return f"Key '{key}' not found"
69
+ return str(result)
70
+ except ValkeyError as e:
71
+ return f"Error getting string value from '{key}': {str(e)}"
72
+
73
+
74
+ @mcp.tool()
75
+ async def string_append(key: str, value: str) -> str:
76
+ """Append to string value.
77
+
78
+ Args:
79
+ key: The name of the key
80
+ value: String to append
81
+
82
+ Returns:
83
+ Success message or error message
84
+ """
85
+ try:
86
+ r = ValkeyConnectionManager.get_connection()
87
+ result = r.append(key, value)
88
+ return f"Successfully appended to key '{key}', new length: {result}"
89
+ except ValkeyError as e:
90
+ return f"Error appending to string '{key}': {str(e)}"
91
+
92
+
93
+ @mcp.tool()
94
+ async def string_get_range(key: str, start: int, end: int) -> str:
95
+ """Get substring.
96
+
97
+ Args:
98
+ key: The name of the key
99
+ start: Start index (inclusive)
100
+ end: End index (inclusive)
101
+
102
+ Returns:
103
+ Substring or error message
104
+ """
105
+ try:
106
+ r = ValkeyConnectionManager.get_connection()
107
+ result = r.getrange(key, start, end)
108
+ if not result:
109
+ return f"No characters found in range [{start}, {end}] for key '{key}'"
110
+ return str(result)
111
+ except ValkeyError as e:
112
+ return f"Error getting range from string '{key}': {str(e)}"
113
+
114
+
115
+ @mcp.tool()
116
+ async def string_get_set(key: str, value: Any) -> str:
117
+ """Set new value and return old value.
118
+
119
+ Args:
120
+ key: The name of the key
121
+ value: New value to set
122
+
123
+ Returns:
124
+ Old value or error message
125
+ """
126
+ try:
127
+ r = ValkeyConnectionManager.get_connection()
128
+ result = r.getset(key, value)
129
+ if result is None:
130
+ return f"No previous value found for key '{key}'"
131
+ return str(result)
132
+ except ValkeyError as e:
133
+ return f"Error getting and setting string '{key}': {str(e)}"
134
+
135
+
136
+ @mcp.tool()
137
+ async def string_increment(key: str, amount: int = 1) -> str:
138
+ """Increment integer value.
139
+
140
+ Args:
141
+ key: The name of the key
142
+ amount: Amount to increment by (default 1)
143
+
144
+ Returns:
145
+ New value or error message
146
+ """
147
+ try:
148
+ r = ValkeyConnectionManager.get_connection()
149
+ result = r.incrby(key, amount)
150
+ return str(result)
151
+ except ValkeyError as e:
152
+ return f"Error incrementing string '{key}': {str(e)}"
153
+
154
+
155
+ @mcp.tool()
156
+ async def string_increment_float(key: str, amount: float) -> str:
157
+ """Increment float value.
158
+
159
+ Args:
160
+ key: The name of the key
161
+ amount: Amount to increment by
162
+
163
+ Returns:
164
+ New value or error message
165
+ """
166
+ try:
167
+ r = ValkeyConnectionManager.get_connection()
168
+ result = r.incrbyfloat(key, amount)
169
+ return str(result)
170
+ except ValkeyError as e:
171
+ return f"Error incrementing float string '{key}': {str(e)}"
172
+
173
+
174
+ @mcp.tool()
175
+ async def string_decrement(key: str, amount: int = 1) -> str:
176
+ """Decrement integer value.
177
+
178
+ Args:
179
+ key: The name of the key
180
+ amount: Amount to decrement by (default 1)
181
+
182
+ Returns:
183
+ New value or error message
184
+ """
185
+ try:
186
+ r = ValkeyConnectionManager.get_connection()
187
+ result = r.decrby(key, amount)
188
+ return str(result)
189
+ except ValkeyError as e:
190
+ return f"Error decrementing string '{key}': {str(e)}"
191
+
192
+
193
+ @mcp.tool()
194
+ async def string_length(key: str) -> str:
195
+ """Get string length.
196
+
197
+ Args:
198
+ key: The name of the key
199
+
200
+ Returns:
201
+ Length or error message
202
+ """
203
+ try:
204
+ r = ValkeyConnectionManager.get_connection()
205
+ result = r.strlen(key)
206
+ return str(result)
207
+ except ValkeyError as e:
208
+ return f"Error getting string length for '{key}': {str(e)}"
209
+
210
+
211
+ @mcp.tool()
212
+ async def string_set_range(key: str, offset: int, value: str) -> str:
213
+ """Overwrite part of string.
214
+
215
+ Args:
216
+ key: The name of the key
217
+ offset: Position to start overwriting
218
+ value: String to write
219
+
220
+ Returns:
221
+ Success message or error message
222
+ """
223
+ try:
224
+ r = ValkeyConnectionManager.get_connection()
225
+ result = r.setrange(key, offset, value)
226
+ return f"Successfully set range in string '{key}', new length: {result}"
227
+ except ValkeyError as e:
228
+ return f"Error setting range in string '{key}': {str(e)}"
@@ -0,0 +1,12 @@
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
+ __version__ = '0.1.0'