awslabs.valkey-mcp-server 1.0.2__py3-none-any.whl → 1.0.3__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,39 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Context management for Valkey MCP Server."""
16
+
17
+
18
+ class Context:
19
+ """Context class for Valkey MCP Server."""
20
+
21
+ _readonly = False
22
+
23
+ @classmethod
24
+ def initialize(cls, readonly: bool = False):
25
+ """Initialize the context.
26
+
27
+ Args:
28
+ readonly: Whether to run in readonly mode
29
+ """
30
+ cls._readonly = readonly
31
+
32
+ @classmethod
33
+ def readonly_mode(cls) -> bool:
34
+ """Check if the server is running in readonly mode.
35
+
36
+ Returns:
37
+ True if readonly mode is enabled, False otherwise
38
+ """
39
+ return cls._readonly
@@ -14,7 +14,9 @@
14
14
 
15
15
  """awslabs valkey MCP Server implementation."""
16
16
 
17
+ import argparse
17
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
18
20
  from awslabs.valkey_mcp_server.tools import (
19
21
  bitmap, # noqa: F401
20
22
  hash, # noqa: F401
@@ -56,6 +58,18 @@ class ValkeyMCPServer:
56
58
 
57
59
  def main():
58
60
  """Run the MCP server with CLI argument support."""
61
+ parser = argparse.ArgumentParser(
62
+ description='An AWS Labs Model Context Protocol (MCP) server for interacting with Valkey'
63
+ )
64
+ parser.add_argument(
65
+ '--readonly',
66
+ action=argparse.BooleanOptionalAction,
67
+ help='Prevents the MCP server from performing mutating operations',
68
+ )
69
+
70
+ args = parser.parse_args()
71
+ Context.initialize(args.readonly)
72
+
59
73
  logger.info('Amazon ElastiCache/MemoryDB Valkey MCP Server Started...')
60
74
 
61
75
  server = ValkeyMCPServer()
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Optional
20
21
  from valkey.exceptions import ValkeyError
21
22
 
@@ -32,6 +33,10 @@ async def bitmap_set(key: str, offset: int, value: int) -> str:
32
33
  Returns:
33
34
  Success message or error message
34
35
  """
36
+ # Check if readonly mode is enabled
37
+ if Context.readonly_mode():
38
+ return 'Error: Cannot set bitmap bit in readonly mode'
39
+
35
40
  try:
36
41
  if value not in (0, 1):
37
42
  return f'Error: value must be 0 or 1, got {value}'
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Any, Dict, Optional, Union
20
21
  from valkey.exceptions import ValkeyError
21
22
 
@@ -32,6 +33,10 @@ async def hash_set(key: str, field: str, value: Any) -> str:
32
33
  Returns:
33
34
  Success message or error message
34
35
  """
36
+ # Check if readonly mode is enabled
37
+ if Context.readonly_mode():
38
+ return 'Error: Cannot set hash field in readonly mode'
39
+
35
40
  try:
36
41
  r = ValkeyConnectionManager.get_connection()
37
42
  r.hset(key, field, value)
@@ -51,6 +56,10 @@ async def hash_set_multiple(key: str, mapping: Dict[str, Any]) -> str:
51
56
  Returns:
52
57
  Success message or error message
53
58
  """
59
+ # Check if readonly mode is enabled
60
+ if Context.readonly_mode():
61
+ return 'Error: Cannot set multiple hash fields in readonly mode'
62
+
54
63
  try:
55
64
  r = ValkeyConnectionManager.get_connection()
56
65
  result = r.hset(key, mapping=mapping)
@@ -71,6 +80,10 @@ async def hash_set_if_not_exists(key: str, field: str, value: Any) -> str:
71
80
  Returns:
72
81
  Success message or error message
73
82
  """
83
+ # Check if readonly mode is enabled
84
+ if Context.readonly_mode():
85
+ return 'Error: Cannot set hash field in readonly mode'
86
+
74
87
  try:
75
88
  r = ValkeyConnectionManager.get_connection()
76
89
  result = r.hsetnx(key, field, value)
@@ -153,6 +166,10 @@ async def hash_increment(key: str, field: str, amount: Union[int, float] = 1) ->
153
166
  Returns:
154
167
  New value or error message
155
168
  """
169
+ # Check if readonly mode is enabled
170
+ if Context.readonly_mode():
171
+ return 'Error: Cannot increment hash field in readonly mode'
172
+
156
173
  try:
157
174
  r = ValkeyConnectionManager.get_connection()
158
175
  if isinstance(amount, int):
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from valkey.exceptions import ValkeyError
20
21
 
21
22
 
@@ -30,6 +31,10 @@ async def hll_add(key: str, element: str) -> str:
30
31
  Returns:
31
32
  Success message or error message
32
33
  """
34
+ # Check if readonly mode is enabled
35
+ if Context.readonly_mode():
36
+ return 'Error: Cannot add to HyperLogLog in readonly mode'
37
+
33
38
  try:
34
39
  if not element:
35
40
  return 'Error: an element is required'
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Any, Optional, Union
20
21
  from valkey.exceptions import ValkeyError
21
22
 
@@ -34,15 +35,13 @@ async def json_set(key: str, path: str, value: Any, nx: bool = False, xx: bool =
34
35
  Returns:
35
36
  Success message or error message
36
37
  """
38
+ # Check if readonly mode is enabled
39
+ if Context.readonly_mode():
40
+ return 'Error: Cannot set JSON value in readonly mode'
41
+
37
42
  try:
38
43
  r = ValkeyConnectionManager.get_connection()
39
- options = {}
40
- if nx:
41
- options['nx'] = True
42
- if xx:
43
- options['xx'] = True
44
-
45
- result = r.json().set(key, path, value, **options)
44
+ result = r.json().set(key, path, value, nx=nx, xx=xx)
46
45
  if result:
47
46
  return f"Successfully set value at path '{path}' in '{key}'"
48
47
  return f"Failed to set value at path '{path}' in '{key}' (path condition not met)"
@@ -121,6 +120,10 @@ async def json_numincrby(key: str, path: str, value: Union[int, float]) -> str:
121
120
  Returns:
122
121
  New value or error message
123
122
  """
123
+ # Check if readonly mode is enabled
124
+ if Context.readonly_mode():
125
+ return 'Error: Cannot increment JSON value in readonly mode'
126
+
124
127
  try:
125
128
  r = ValkeyConnectionManager.get_connection()
126
129
  # Convert float to int by rounding if needed
@@ -143,6 +146,10 @@ async def json_nummultby(key: str, path: str, value: Union[int, float]) -> str:
143
146
  Returns:
144
147
  New value or error message
145
148
  """
149
+ # Check if readonly mode is enabled
150
+ if Context.readonly_mode():
151
+ return 'Error: Cannot multiply JSON value in readonly mode'
152
+
146
153
  try:
147
154
  r = ValkeyConnectionManager.get_connection()
148
155
  # Convert float to int by rounding if needed
@@ -165,6 +172,10 @@ async def json_strappend(key: str, path: str, value: str) -> str:
165
172
  Returns:
166
173
  New string length or error message
167
174
  """
175
+ # Check if readonly mode is enabled
176
+ if Context.readonly_mode():
177
+ return 'Error: Cannot append to JSON string in readonly mode'
178
+
168
179
  try:
169
180
  r = ValkeyConnectionManager.get_connection()
170
181
  result = r.json().strappend(key, path, value)
@@ -206,6 +217,10 @@ async def json_arrappend(key: str, path: str, *values: Any) -> str:
206
217
  Returns:
207
218
  New array length or error message
208
219
  """
220
+ # Check if readonly mode is enabled
221
+ if Context.readonly_mode():
222
+ return 'Error: Cannot append to JSON array in readonly mode'
223
+
209
224
  try:
210
225
  if not values:
211
226
  return 'Error: at least one value is required'
@@ -285,6 +300,10 @@ async def json_arrpop(key: str, path: str, index: int = -1) -> str:
285
300
  Returns:
286
301
  Popped value or error message
287
302
  """
303
+ # Check if readonly mode is enabled
304
+ if Context.readonly_mode():
305
+ return 'Error: Cannot pop from JSON array in readonly mode'
306
+
288
307
  try:
289
308
  r = ValkeyConnectionManager.get_connection()
290
309
  result = r.json().arrpop(key, path, index)
@@ -308,6 +327,10 @@ async def json_arrtrim(key: str, path: str, start: int, stop: int) -> str:
308
327
  Returns:
309
328
  New array length or error message
310
329
  """
330
+ # Check if readonly mode is enabled
331
+ if Context.readonly_mode():
332
+ return 'Error: Cannot trim JSON array in readonly mode'
333
+
311
334
  try:
312
335
  r = ValkeyConnectionManager.get_connection()
313
336
  result = r.json().arrtrim(key, path, start, stop)
@@ -373,6 +396,10 @@ async def json_toggle(key: str, path: str) -> str:
373
396
  Returns:
374
397
  New boolean value or error message
375
398
  """
399
+ # Check if readonly mode is enabled
400
+ if Context.readonly_mode():
401
+ return 'Error: Cannot toggle JSON boolean in readonly mode'
402
+
376
403
  try:
377
404
  r = ValkeyConnectionManager.get_connection()
378
405
  result = r.json().toggle(key, path)
@@ -394,6 +421,10 @@ async def json_clear(key: str, path: str) -> str:
394
421
  Returns:
395
422
  Success message or error message
396
423
  """
424
+ # Check if readonly mode is enabled
425
+ if Context.readonly_mode():
426
+ return 'Error: Cannot clear JSON container in readonly mode'
427
+
397
428
  try:
398
429
  r = ValkeyConnectionManager.get_connection()
399
430
  result = r.json().clear(key, path)
@@ -415,6 +446,10 @@ async def json_del(key: str, path: str) -> str:
415
446
  Returns:
416
447
  Success message or error message
417
448
  """
449
+ # Check if readonly mode is enabled
450
+ if Context.readonly_mode():
451
+ return 'Error: Cannot delete JSON value in readonly mode'
452
+
418
453
  try:
419
454
  r = ValkeyConnectionManager.get_connection()
420
455
  result = r.json().delete(key, path)
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Any, Optional
20
21
  from typing import List as PyList
21
22
  from valkey.exceptions import ValkeyError
@@ -32,6 +33,10 @@ async def list_append(key: str, value: Any) -> str:
32
33
  Returns:
33
34
  Success message or error message
34
35
  """
36
+ # Check if readonly mode is enabled
37
+ if Context.readonly_mode():
38
+ return 'Error: Cannot append to list in readonly mode'
39
+
35
40
  try:
36
41
  r = ValkeyConnectionManager.get_connection()
37
42
  result = r.rpush(key, value)
@@ -51,6 +56,10 @@ async def list_prepend(key: str, value: Any) -> str:
51
56
  Returns:
52
57
  Success message or error message
53
58
  """
59
+ # Check if readonly mode is enabled
60
+ if Context.readonly_mode():
61
+ return 'Error: Cannot prepend to list in readonly mode'
62
+
54
63
  try:
55
64
  r = ValkeyConnectionManager.get_connection()
56
65
  result = r.lpush(key, value)
@@ -70,6 +79,10 @@ async def list_append_multiple(key: str, values: PyList[Any]) -> str:
70
79
  Returns:
71
80
  Success message or error message
72
81
  """
82
+ # Check if readonly mode is enabled
83
+ if Context.readonly_mode():
84
+ return 'Error: Cannot append to list in readonly mode'
85
+
73
86
  try:
74
87
  r = ValkeyConnectionManager.get_connection()
75
88
  result = r.rpush(key, *values)
@@ -89,6 +102,10 @@ async def list_prepend_multiple(key: str, values: PyList[Any]) -> str:
89
102
  Returns:
90
103
  Success message or error message
91
104
  """
105
+ # Check if readonly mode is enabled
106
+ if Context.readonly_mode():
107
+ return 'Error: Cannot prepend to list in readonly mode'
108
+
92
109
  try:
93
110
  r = ValkeyConnectionManager.get_connection()
94
111
  result = r.lpush(key, *values)
@@ -130,6 +147,10 @@ async def list_set(key: str, index: int, value: Any) -> str:
130
147
  Returns:
131
148
  Success message or error message
132
149
  """
150
+ # Check if readonly mode is enabled
151
+ if Context.readonly_mode():
152
+ return 'Error: Cannot set list value in readonly mode'
153
+
133
154
  try:
134
155
  r = ValkeyConnectionManager.get_connection()
135
156
  r.lset(key, index, value)
@@ -172,6 +193,10 @@ async def list_trim(key: str, start: int, stop: int) -> str:
172
193
  Returns:
173
194
  Success message or error message
174
195
  """
196
+ # Check if readonly mode is enabled
197
+ if Context.readonly_mode():
198
+ return 'Error: Cannot trim list in readonly mode'
199
+
175
200
  try:
176
201
  r = ValkeyConnectionManager.get_connection()
177
202
  r.ltrim(key, start, stop)
@@ -209,6 +234,10 @@ async def list_pop_left(key: str, count: Optional[int] = None) -> str:
209
234
  Returns:
210
235
  Value(s) or error message
211
236
  """
237
+ # Check if readonly mode is enabled
238
+ if Context.readonly_mode():
239
+ return 'Error: Cannot pop from list in readonly mode'
240
+
212
241
  try:
213
242
  r = ValkeyConnectionManager.get_connection()
214
243
  if count:
@@ -233,6 +262,10 @@ async def list_pop_right(key: str, count: Optional[int] = None) -> str:
233
262
  Returns:
234
263
  Value(s) or error message
235
264
  """
265
+ # Check if readonly mode is enabled
266
+ if Context.readonly_mode():
267
+ return 'Error: Cannot pop from list in readonly mode'
268
+
236
269
  try:
237
270
  r = ValkeyConnectionManager.get_connection()
238
271
  if count:
@@ -299,6 +332,10 @@ async def list_move(
299
332
  Returns:
300
333
  Moved value or error message
301
334
  """
335
+ # Check if readonly mode is enabled
336
+ if Context.readonly_mode():
337
+ return 'Error: Cannot move list elements in readonly mode'
338
+
302
339
  try:
303
340
  r = ValkeyConnectionManager.get_connection()
304
341
  wherefrom = wherefrom.upper()
@@ -327,6 +364,10 @@ async def list_insert_before(key: str, pivot: Any, value: Any) -> str:
327
364
  Returns:
328
365
  Success message or error message
329
366
  """
367
+ # Check if readonly mode is enabled
368
+ if Context.readonly_mode():
369
+ return 'Error: Cannot insert into list in readonly mode'
370
+
330
371
  try:
331
372
  r = ValkeyConnectionManager.get_connection()
332
373
  result = r.linsert(key, 'BEFORE', pivot, value)
@@ -349,6 +390,10 @@ async def list_insert_after(key: str, pivot: Any, value: Any) -> str:
349
390
  Returns:
350
391
  Success message or error message
351
392
  """
393
+ # Check if readonly mode is enabled
394
+ if Context.readonly_mode():
395
+ return 'Error: Cannot insert into list in readonly mode'
396
+
352
397
  try:
353
398
  r = ValkeyConnectionManager.get_connection()
354
399
  result = r.linsert(key, 'AFTER', pivot, value)
@@ -371,6 +416,10 @@ async def list_remove(key: str, value: Any, count: int = 0) -> str:
371
416
  Returns:
372
417
  Success message or error message
373
418
  """
419
+ # Check if readonly mode is enabled
420
+ if Context.readonly_mode():
421
+ return 'Error: Cannot remove from list in readonly mode'
422
+
374
423
  try:
375
424
  r = ValkeyConnectionManager.get_connection()
376
425
  result = r.lrem(key, count, value)
@@ -14,6 +14,7 @@
14
14
 
15
15
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
16
16
  from awslabs.valkey_mcp_server.common.server import mcp
17
+ from awslabs.valkey_mcp_server.context import Context
17
18
  from typing import Any, Dict
18
19
  from valkey.exceptions import ValkeyError as RedisError
19
20
 
@@ -28,6 +29,10 @@ async def delete(key: str) -> str:
28
29
  Returns:
29
30
  str: Confirmation message or an error message.
30
31
  """
32
+ # Check if readonly mode is enabled
33
+ if Context.readonly_mode():
34
+ return 'Error: Cannot delete key in readonly mode'
35
+
31
36
  try:
32
37
  r = ValkeyConnectionManager.get_connection()
33
38
  result = r.delete(key)
@@ -67,6 +72,10 @@ async def expire(name: str, expire_seconds: int) -> str:
67
72
  Returns:
68
73
  A success message or an error message.
69
74
  """
75
+ # Check if readonly mode is enabled
76
+ if Context.readonly_mode():
77
+ return 'Error: Cannot set expiration in readonly mode'
78
+
70
79
  try:
71
80
  r = ValkeyConnectionManager.get_connection()
72
81
  success = r.expire(name, expire_seconds)
@@ -92,6 +101,10 @@ async def rename(old_key: str, new_key: str) -> Dict[str, Any]:
92
101
  On success: {"status": "success", "message": "..."}
93
102
  On error: {"error": "..."}
94
103
  """
104
+ # Check if readonly mode is enabled
105
+ if Context.readonly_mode():
106
+ return {'error': 'Cannot rename key in readonly mode'}
107
+
95
108
  try:
96
109
  r = ValkeyConnectionManager.get_connection()
97
110
 
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Any, Optional
20
21
  from valkey.exceptions import ValkeyError
21
22
 
@@ -31,6 +32,10 @@ async def set_add(key: str, member: str) -> str:
31
32
  Returns:
32
33
  Success message or error message
33
34
  """
35
+ # Check if readonly mode is enabled
36
+ if Context.readonly_mode():
37
+ return 'Error: Cannot add to set in readonly mode'
38
+
34
39
  try:
35
40
  r = ValkeyConnectionManager.get_connection()
36
41
  result = r.sadd(key, member)
@@ -50,6 +55,10 @@ async def set_remove(key: str, member: str) -> str:
50
55
  Returns:
51
56
  Success message or error message
52
57
  """
58
+ # Check if readonly mode is enabled
59
+ if Context.readonly_mode():
60
+ return 'Error: Cannot remove from set in readonly mode'
61
+
53
62
  try:
54
63
  r = ValkeyConnectionManager.get_connection()
55
64
  result = r.srem(key, member)
@@ -69,6 +78,10 @@ async def set_pop(key: str, count: Optional[int] = None) -> str:
69
78
  Returns:
70
79
  Popped member(s) or error message
71
80
  """
81
+ # Check if readonly mode is enabled
82
+ if Context.readonly_mode():
83
+ return 'Error: Cannot pop from set in readonly mode'
84
+
72
85
  try:
73
86
  r = ValkeyConnectionManager.get_connection()
74
87
  if count:
@@ -94,6 +107,10 @@ async def set_move(source: str, destination: str, member: Any) -> str:
94
107
  Returns:
95
108
  Success message or error message
96
109
  """
110
+ # Check if readonly mode is enabled
111
+ if Context.readonly_mode():
112
+ return 'Error: Cannot move set members in readonly mode'
113
+
97
114
  try:
98
115
  r = ValkeyConnectionManager.get_connection()
99
116
  result = r.smove(source, destination, member)
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Any, Dict, Optional
20
21
  from valkey.exceptions import ValkeyError
21
22
 
@@ -31,6 +32,10 @@ async def sorted_set_add(key: str, mapping: Dict[Any, float]) -> str:
31
32
  Returns:
32
33
  Success message or error message
33
34
  """
35
+ # Check if readonly mode is enabled
36
+ if Context.readonly_mode():
37
+ return 'Error: Cannot add to sorted set in readonly mode'
38
+
34
39
  try:
35
40
  r = ValkeyConnectionManager.get_connection()
36
41
  result = r.zadd(key, mapping)
@@ -51,6 +56,10 @@ async def sorted_set_add_incr(key: str, member: Any, score: float) -> str:
51
56
  Returns:
52
57
  Success message or error message
53
58
  """
59
+ # Check if readonly mode is enabled
60
+ if Context.readonly_mode():
61
+ return 'Error: Cannot increment score in sorted set in readonly mode'
62
+
54
63
  try:
55
64
  r = ValkeyConnectionManager.get_connection()
56
65
  result = r.zincrby(key, score, member)
@@ -70,6 +79,10 @@ async def sorted_set_remove(key: str, *members: Any) -> str:
70
79
  Returns:
71
80
  Success message or error message
72
81
  """
82
+ # Check if readonly mode is enabled
83
+ if Context.readonly_mode():
84
+ return 'Error: Cannot remove from sorted set in readonly mode'
85
+
73
86
  try:
74
87
  r = ValkeyConnectionManager.get_connection()
75
88
  result = r.zrem(key, *members)
@@ -90,6 +103,10 @@ async def sorted_set_remove_by_rank(key: str, start: int, stop: int) -> str:
90
103
  Returns:
91
104
  Success message or error message
92
105
  """
106
+ # Check if readonly mode is enabled
107
+ if Context.readonly_mode():
108
+ return 'Error: Cannot remove from sorted set in readonly mode'
109
+
93
110
  try:
94
111
  r = ValkeyConnectionManager.get_connection()
95
112
  result = r.zremrangebyrank(key, start, stop)
@@ -110,6 +127,10 @@ async def sorted_set_remove_by_score(key: str, min_score: float, max_score: floa
110
127
  Returns:
111
128
  Success message or error message
112
129
  """
130
+ # Check if readonly mode is enabled
131
+ if Context.readonly_mode():
132
+ return 'Error: Cannot remove from sorted set in readonly mode'
133
+
113
134
  try:
114
135
  r = ValkeyConnectionManager.get_connection()
115
136
  result = r.zremrangebyscore(key, min_score, max_score)
@@ -130,6 +151,10 @@ async def sorted_set_remove_by_lex(key: str, min_lex: str, max_lex: str) -> str:
130
151
  Returns:
131
152
  Success message or error message
132
153
  """
154
+ # Check if readonly mode is enabled
155
+ if Context.readonly_mode():
156
+ return 'Error: Cannot remove from sorted set in readonly mode'
157
+
133
158
  try:
134
159
  r = ValkeyConnectionManager.get_connection()
135
160
  result = r.zremrangebylex(key, min_lex, max_lex)
@@ -325,6 +350,10 @@ async def sorted_set_popmin(key: str, count: Optional[int] = None) -> str:
325
350
  Returns:
326
351
  Popped members with scores or error message
327
352
  """
353
+ # Check if readonly mode is enabled
354
+ if Context.readonly_mode():
355
+ return 'Error: Cannot pop from sorted set in readonly mode'
356
+
328
357
  try:
329
358
  r = ValkeyConnectionManager.get_connection()
330
359
  if count:
@@ -349,6 +378,10 @@ async def sorted_set_popmax(key: str, count: Optional[int] = None) -> str:
349
378
  Returns:
350
379
  Popped members with scores or error message
351
380
  """
381
+ # Check if readonly mode is enabled
382
+ if Context.readonly_mode():
383
+ return 'Error: Cannot pop from sorted set in readonly mode'
384
+
352
385
  try:
353
386
  r = ValkeyConnectionManager.get_connection()
354
387
  if count:
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Any, Dict, Optional
20
21
  from valkey.exceptions import ValkeyError
21
22
 
@@ -40,6 +41,10 @@ async def stream_add(
40
41
  Returns:
41
42
  Success message or error message
42
43
  """
44
+ # Check if readonly mode is enabled
45
+ if Context.readonly_mode():
46
+ return 'Error: Cannot add to stream in readonly mode'
47
+
43
48
  try:
44
49
  r = ValkeyConnectionManager.get_connection()
45
50
  options = {}
@@ -66,6 +71,10 @@ async def stream_delete(key: str, id: str) -> str:
66
71
  Returns:
67
72
  Success message or error message
68
73
  """
74
+ # Check if readonly mode is enabled
75
+ if Context.readonly_mode():
76
+ return 'Error: Cannot delete from stream in readonly mode'
77
+
69
78
  try:
70
79
  r = ValkeyConnectionManager.get_connection()
71
80
  result = r.xdel(key, id)
@@ -86,6 +95,10 @@ async def stream_trim(key: str, maxlen: int, approximate: bool = True) -> str:
86
95
  Returns:
87
96
  Success message or error message
88
97
  """
98
+ # Check if readonly mode is enabled
99
+ if Context.readonly_mode():
100
+ return 'Error: Cannot trim stream in readonly mode'
101
+
89
102
  try:
90
103
  r = ValkeyConnectionManager.get_connection()
91
104
  result = r.xtrim(key, maxlen=maxlen, approximate=approximate)
@@ -183,6 +196,10 @@ async def stream_group_create(
183
196
  Returns:
184
197
  Success message or error message
185
198
  """
199
+ # Check if readonly mode is enabled
200
+ if Context.readonly_mode():
201
+ return 'Error: Cannot create consumer group in readonly mode'
202
+
186
203
  try:
187
204
  r = ValkeyConnectionManager.get_connection()
188
205
  r.xgroup_create(key, group_name, id=id, mkstream=mkstream)
@@ -202,6 +219,10 @@ async def stream_group_destroy(key: str, group_name: str) -> str:
202
219
  Returns:
203
220
  Success message or error message
204
221
  """
222
+ # Check if readonly mode is enabled
223
+ if Context.readonly_mode():
224
+ return 'Error: Cannot destroy consumer group in readonly mode'
225
+
205
226
  try:
206
227
  r = ValkeyConnectionManager.get_connection()
207
228
  result = r.xgroup_destroy(key, group_name)
@@ -224,6 +245,10 @@ async def stream_group_set_id(key: str, group_name: str, id: str) -> str:
224
245
  Returns:
225
246
  Success message or error message
226
247
  """
248
+ # Check if readonly mode is enabled
249
+ if Context.readonly_mode():
250
+ return 'Error: Cannot set consumer group ID in readonly mode'
251
+
227
252
  try:
228
253
  r = ValkeyConnectionManager.get_connection()
229
254
  r.xgroup_setid(key, group_name, id)
@@ -244,6 +269,10 @@ async def stream_group_delete_consumer(key: str, group_name: str, consumer_name:
244
269
  Returns:
245
270
  Success message or error message
246
271
  """
272
+ # Check if readonly mode is enabled
273
+ if Context.readonly_mode():
274
+ return 'Error: Cannot delete consumer in readonly mode'
275
+
247
276
  try:
248
277
  r = ValkeyConnectionManager.get_connection()
249
278
  result = r.xgroup_delconsumer(key, group_name, consumer_name)
@@ -274,6 +303,10 @@ async def stream_read_group(
274
303
  Returns:
275
304
  List of entries or error message
276
305
  """
306
+ # Check if readonly mode is enabled and acknowledgment is required
307
+ if Context.readonly_mode() and not noack:
308
+ return 'Error: Cannot read from stream with acknowledgment in readonly mode'
309
+
277
310
  try:
278
311
  r = ValkeyConnectionManager.get_connection()
279
312
  streams = {key: '>'} # ">" means read undelivered entries
@@ -16,6 +16,7 @@
16
16
 
17
17
  from awslabs.valkey_mcp_server.common.connection import ValkeyConnectionManager
18
18
  from awslabs.valkey_mcp_server.common.server import mcp
19
+ from awslabs.valkey_mcp_server.context import Context
19
20
  from typing import Any, Optional
20
21
  from valkey.exceptions import ValkeyError
21
22
 
@@ -44,6 +45,10 @@ async def string_set(
44
45
  Returns:
45
46
  Success message or error message
46
47
  """
48
+ # Check if readonly mode is enabled
49
+ if Context.readonly_mode():
50
+ return 'Error: Cannot set string value in readonly mode'
51
+
47
52
  try:
48
53
  r = ValkeyConnectionManager.get_connection()
49
54
  result = r.set(key, value, ex=ex, px=px, nx=nx, xx=xx, keepttl=keepttl)
@@ -85,6 +90,10 @@ async def string_append(key: str, value: str) -> str:
85
90
  Returns:
86
91
  Success message or error message
87
92
  """
93
+ # Check if readonly mode is enabled
94
+ if Context.readonly_mode():
95
+ return 'Error: Cannot append to string value in readonly mode'
96
+
88
97
  try:
89
98
  r = ValkeyConnectionManager.get_connection()
90
99
  result = r.append(key, value)
@@ -126,6 +135,10 @@ async def string_get_set(key: str, value: Any) -> str:
126
135
  Returns:
127
136
  Old value or error message
128
137
  """
138
+ # Check if readonly mode is enabled
139
+ if Context.readonly_mode():
140
+ return 'Error: Cannot set string value in readonly mode'
141
+
129
142
  try:
130
143
  r = ValkeyConnectionManager.get_connection()
131
144
  result = r.getset(key, value)
@@ -147,6 +160,10 @@ async def string_increment(key: str, amount: int = 1) -> str:
147
160
  Returns:
148
161
  New value or error message
149
162
  """
163
+ # Check if readonly mode is enabled
164
+ if Context.readonly_mode():
165
+ return 'Error: Cannot increment string value in readonly mode'
166
+
150
167
  try:
151
168
  r = ValkeyConnectionManager.get_connection()
152
169
  result = r.incrby(key, amount)
@@ -166,6 +183,10 @@ async def string_increment_float(key: str, amount: float) -> str:
166
183
  Returns:
167
184
  New value or error message
168
185
  """
186
+ # Check if readonly mode is enabled
187
+ if Context.readonly_mode():
188
+ return 'Error: Cannot increment float string value in readonly mode'
189
+
169
190
  try:
170
191
  r = ValkeyConnectionManager.get_connection()
171
192
  result = r.incrbyfloat(key, amount)
@@ -185,6 +206,10 @@ async def string_decrement(key: str, amount: int = 1) -> str:
185
206
  Returns:
186
207
  New value or error message
187
208
  """
209
+ # Check if readonly mode is enabled
210
+ if Context.readonly_mode():
211
+ return 'Error: Cannot decrement string value in readonly mode'
212
+
188
213
  try:
189
214
  r = ValkeyConnectionManager.get_connection()
190
215
  result = r.decrby(key, amount)
@@ -223,6 +248,10 @@ async def string_set_range(key: str, offset: int, value: str) -> str:
223
248
  Returns:
224
249
  Success message or error message
225
250
  """
251
+ # Check if readonly mode is enabled
252
+ if Context.readonly_mode():
253
+ return 'Error: Cannot set range in string value in readonly mode'
254
+
226
255
  try:
227
256
  r = ValkeyConnectionManager.get_connection()
228
257
  result = r.setrange(key, offset, value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.valkey-mcp-server
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for valkey
5
5
  Project-URL: homepage, https://awslabs.github.io/mcp/
6
6
  Project-URL: docs, https://awslabs.github.io/mcp/servers/valkey-mcp-server/
@@ -50,6 +50,7 @@ This MCP server provides tools to operate on Valkey data types. For example, it
50
50
  - **Cluster Support**: Support for standalone and clustered Valkey deployments.
51
51
  - **SSL/TLS Security**: Configure secure connections using SSL/TLS.
52
52
  - **Connection Pooling**: Pools connections by default to enable efficient connection management.
53
+ - **Readonly Mode**: Prevent write operations to ensure data safety.
53
54
 
54
55
  ## Prerequisites
55
56
 
@@ -61,6 +62,8 @@ This MCP server provides tools to operate on Valkey data types. For example, it
61
62
 
62
63
  ## Installation
63
64
 
65
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/install-mcp?name=awslabs.valkey-mcp-server&config=eyJjb21tYW5kIjoidXZ4IGF3c2xhYnMudmFsa2V5LW1jcC1zZXJ2ZXJAbGF0ZXN0IiwiZW52Ijp7IlZBTEtFWV9IT1NUIjoiMTI3LjAuMC4xIiwiVkFMS0VZX1BPUlQiOiI2Mzc5IiwiRkFTVE1DUF9MT0dfTEVWRUwiOiJFUlJPUiJ9LCJhdXRvQXBwcm92ZSI6W10sImRpc2FibGVkIjpmYWxzZX0%3D)
66
+
64
67
  Here are some ways you can work with MCP across AWS tools (e.g., for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
65
68
 
66
69
  ```json
@@ -83,6 +86,29 @@ Here are some ways you can work with MCP across AWS tools (e.g., for Amazon Q De
83
86
  }
84
87
  ```
85
88
 
89
+ To run in readonly mode:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "awslabs.valkey-mcp-server": {
95
+ "command": "uvx",
96
+ "args": [
97
+ "awslabs.valkey-mcp-server@latest",
98
+ "--readonly"
99
+ ],
100
+ "env": {
101
+ "VALKEY_HOST": "127.0.0.1",
102
+ "VALKEY_PORT": "6379",
103
+ "FASTMCP_LOG_LEVEL": "ERROR"
104
+ },
105
+ "autoApprove": [],
106
+ "disabled": false
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
86
112
  Or using Docker after a successful `docker build -t awslabs/valkey-mcp-server .`:
87
113
 
88
114
  ```json
@@ -110,6 +136,34 @@ Or using Docker after a successful `docker build -t awslabs/valkey-mcp-server .`
110
136
  }
111
137
  ```
112
138
 
139
+ To run in readonly mode with Docker:
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "awslabs.valkey-mcp-server": {
145
+ "command": "docker",
146
+ "args": [
147
+ "run",
148
+ "--rm",
149
+ "--interactive",
150
+ "--env",
151
+ "FASTMCP_LOG_LEVEL=ERROR",
152
+ "--env",
153
+ "VALKEY_HOST=127.0.0.1",
154
+ "--env",
155
+ "VALKEY_PORT=6379",
156
+ "awslabs/valkey-mcp-server:latest",
157
+ "--readonly"
158
+ ],
159
+ "env": {},
160
+ "disabled": false,
161
+ "autoApprove": []
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
113
167
  ## Configuration
114
168
 
115
169
  The server can be configured using the following environment variables:
@@ -163,3 +217,11 @@ docker run -p 8080:8080 \
163
217
  -e VALKEY_PORT=6379 \
164
218
  awslabs/valkey-mcp-server
165
219
  ```
220
+
221
+ To run in readonly mode:
222
+ ```bash
223
+ docker run -p 8080:8080 \
224
+ -e VALKEY_HOST=host.docker.internal \
225
+ -e VALKEY_PORT=6379 \
226
+ awslabs/valkey-mcp-server --readonly
227
+ ```
@@ -0,0 +1,27 @@
1
+ awslabs/__init__.py,sha256=WuqxdDgUZylWNmVoPKiK7qGsTB_G4UmuXIrJ-VBwDew,731
2
+ awslabs/valkey_mcp_server/__init__.py,sha256=C2RgjQfy3kYBZ2NokGTW308ldYQNXPJOmcifIoSqxqY,672
3
+ awslabs/valkey_mcp_server/context.py,sha256=SSiXTebscjQ4_7q0Sa6CMRd2BS2NWjiF02koIFKfhX0,1202
4
+ awslabs/valkey_mcp_server/main.py,sha256=akwmYY1H5EAsIbmHG9rpVsxn17a-kRzTsa5X7Ki-Wq4,2446
5
+ awslabs/valkey_mcp_server/version.py,sha256=8161BBQQZut_MCnIZdhSJ3B_abfx8cwaKu90EaS__Dw,638
6
+ awslabs/valkey_mcp_server/common/__init__.py,sha256=-c4-Il6p3y3Ds9yZv58p1CHT_p8yO9a6n4wzHEVAFG4,699
7
+ awslabs/valkey_mcp_server/common/config.py,sha256=swcXMANStSJRZGuuHFLFMJckccH88c7HEg2QruzX-Pg,3154
8
+ awslabs/valkey_mcp_server/common/connection.py,sha256=twCbzkIWoc9BCx8TIViJ6m-c9eRNv5tCIQmntd6crOk,4044
9
+ awslabs/valkey_mcp_server/common/server.py,sha256=3cGdifWvtk93fenC_IJ4KD0sBfPr0myPURp6igRK0pQ,1148
10
+ awslabs/valkey_mcp_server/tools/__init__.py,sha256=yAdR7sNlUZwYaqOBba_KLN7qdxsO29PglZnSKsXpsTw,820
11
+ awslabs/valkey_mcp_server/tools/bitmap.py,sha256=aeKjYyAjTDVW5e9kTN1zKV7zCSEB8h42Dfjn27P1A_U,5225
12
+ awslabs/valkey_mcp_server/tools/hash.py,sha256=q4vMzgVH3rnHkzcfttnE2qcdQ4iEXDFL0WCV14wLfAc,8649
13
+ awslabs/valkey_mcp_server/tools/hyperloglog.py,sha256=uydzWwNyivtz0xcMYwKSS5fqS20lNB1iR7wxVUXQvec,2182
14
+ awslabs/valkey_mcp_server/tools/json.py,sha256=Yke-cj9gu_XKryymbChsb5nMu1rkWrGV7GdCxL4H1CQ,15189
15
+ awslabs/valkey_mcp_server/tools/list.py,sha256=Vjs2BA3UWMvmmK_k_U1EeCFkjzcl7z51E5RbBJbnHUY,13032
16
+ awslabs/valkey_mcp_server/tools/misc.py,sha256=3OCCnjkQmM8RBfpj91rr-ENGXq9IidEey20vgZG96yk,3830
17
+ awslabs/valkey_mcp_server/tools/server_management.py,sha256=wL_q_eADfvN6F8VrU5pwXvyVdCL4WFZ4PICm2PDPq14,1955
18
+ awslabs/valkey_mcp_server/tools/set.py,sha256=ZSWmFgvVg7VgVrAQiNg9KAuZD1_wWiFYKgG1QVsHWnw,5824
19
+ awslabs/valkey_mcp_server/tools/sorted_set.py,sha256=lEJLikzltYQIl4qZH61Adki8Gaeiv1nLjDxngK0PERs,12555
20
+ awslabs/valkey_mcp_server/tools/stream.py,sha256=o2gn69xUFpf3Zg32xTsaO0c_Ibaspv7XS67NpVtukmk,11687
21
+ awslabs/valkey_mcp_server/tools/string.py,sha256=W0gMxfIli-gWr_RL67tnKrZJ4QMlCJViAeZxbgbFQII,7486
22
+ awslabs_valkey_mcp_server-1.0.3.dist-info/METADATA,sha256=2Zo8LgmeImknI5VNtXm73kdQQ4OpGRAwv14_VxzYGAE,7171
23
+ awslabs_valkey_mcp_server-1.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ awslabs_valkey_mcp_server-1.0.3.dist-info/entry_points.txt,sha256=pOqhzh9mlelNc6dLngnCQS1bs62Wazi0Y8EqQmXolK0,82
25
+ awslabs_valkey_mcp_server-1.0.3.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
26
+ awslabs_valkey_mcp_server-1.0.3.dist-info/licenses/NOTICE,sha256=02_lxJlcwNuKRYtdONbI4i4RBK09-SqoQCzkqjpbqhY,93
27
+ awslabs_valkey_mcp_server-1.0.3.dist-info/RECORD,,
@@ -1,26 +0,0 @@
1
- awslabs/__init__.py,sha256=WuqxdDgUZylWNmVoPKiK7qGsTB_G4UmuXIrJ-VBwDew,731
2
- awslabs/valkey_mcp_server/__init__.py,sha256=C2RgjQfy3kYBZ2NokGTW308ldYQNXPJOmcifIoSqxqY,672
3
- awslabs/valkey_mcp_server/main.py,sha256=lXPGVljjwMTDWuaJoP3cUij1rebdQE73v7s8cgn3R1s,1987
4
- awslabs/valkey_mcp_server/version.py,sha256=8161BBQQZut_MCnIZdhSJ3B_abfx8cwaKu90EaS__Dw,638
5
- awslabs/valkey_mcp_server/common/__init__.py,sha256=-c4-Il6p3y3Ds9yZv58p1CHT_p8yO9a6n4wzHEVAFG4,699
6
- awslabs/valkey_mcp_server/common/config.py,sha256=swcXMANStSJRZGuuHFLFMJckccH88c7HEg2QruzX-Pg,3154
7
- awslabs/valkey_mcp_server/common/connection.py,sha256=twCbzkIWoc9BCx8TIViJ6m-c9eRNv5tCIQmntd6crOk,4044
8
- awslabs/valkey_mcp_server/common/server.py,sha256=3cGdifWvtk93fenC_IJ4KD0sBfPr0myPURp6igRK0pQ,1148
9
- awslabs/valkey_mcp_server/tools/__init__.py,sha256=yAdR7sNlUZwYaqOBba_KLN7qdxsO29PglZnSKsXpsTw,820
10
- awslabs/valkey_mcp_server/tools/bitmap.py,sha256=v2bnWfbnoVaEaN6j6Nmdz0eWapnLhVbYq_wteOjQub8,5035
11
- awslabs/valkey_mcp_server/tools/hash.py,sha256=9fMUnXDXovukFpVVYkCCetEhyaBfotymuNmrAmpEbZo,8035
12
- awslabs/valkey_mcp_server/tools/hyperloglog.py,sha256=rs5PrS6xQHiuuG5mc1pwJSrSj8ECv-X5MjrTNzVEqds,1988
13
- awslabs/valkey_mcp_server/tools/json.py,sha256=yWr3UgHR1xTrkLTr77JbePQQdHlenMVyf1sRcOVKt00,13846
14
- awslabs/valkey_mcp_server/tools/list.py,sha256=zFxEm3f6X_3skLUAtQNzmQ1_Yk3NXlfpVHk0R_nzPfw,11341
15
- awslabs/valkey_mcp_server/tools/misc.py,sha256=QglRL6SBgUMudMlKQ3HovG2EUgYaQb6W7uH63djTFQA,3372
16
- awslabs/valkey_mcp_server/tools/server_management.py,sha256=wL_q_eADfvN6F8VrU5pwXvyVdCL4WFZ4PICm2PDPq14,1955
17
- awslabs/valkey_mcp_server/tools/set.py,sha256=aSzb4mzNCgm35UTUE3u1dMOr4_IfQSHZrnnRias72og,5229
18
- awslabs/valkey_mcp_server/tools/sorted_set.py,sha256=ZTwaKwopJVAL8nSN6a8nHs82gnw-e-ynDhwInSXLVlg,11353
19
- awslabs/valkey_mcp_server/tools/stream.py,sha256=YdwDsqKRKExdxY_wlXFP-6R_YMYww8Qxf7D2zpeo1_0,10455
20
- awslabs/valkey_mcp_server/tools/string.py,sha256=ip04esz_3VriS5h2J8yCqz_SdpnGxSwEbaqwqVe6fe4,6427
21
- awslabs_valkey_mcp_server-1.0.2.dist-info/METADATA,sha256=Lw0G01bwho2ro20CQSzkxpVFUH6drrE6rFalw31iZJs,5661
22
- awslabs_valkey_mcp_server-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- awslabs_valkey_mcp_server-1.0.2.dist-info/entry_points.txt,sha256=pOqhzh9mlelNc6dLngnCQS1bs62Wazi0Y8EqQmXolK0,82
24
- awslabs_valkey_mcp_server-1.0.2.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
25
- awslabs_valkey_mcp_server-1.0.2.dist-info/licenses/NOTICE,sha256=02_lxJlcwNuKRYtdONbI4i4RBK09-SqoQCzkqjpbqhY,93
26
- awslabs_valkey_mcp_server-1.0.2.dist-info/RECORD,,