awslabs.memcached-mcp-server 1.0.2__tar.gz → 1.0.3__tar.gz
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_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/PKG-INFO +70 -1
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/README.md +69 -0
- awslabs_memcached_mcp_server-1.0.3/awslabs/memcached_mcp_server/context.py +39 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/memcached_mcp_server/main.py +14 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/memcached_mcp_server/tools/cache.py +40 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/pyproject.toml +1 -1
- awslabs_memcached_mcp_server-1.0.3/tests/test_cache_readonly.py +163 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/uv.lock +1 -1
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/.gitignore +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/.python-version +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/CHANGELOG.md +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/Dockerfile +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/ELASTICACHECONNECT.md +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/LICENSE +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/NOTICE +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/__init__.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/memcached_mcp_server/__init__.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/memcached_mcp_server/common/config.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/memcached_mcp_server/common/connection.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/memcached_mcp_server/common/server.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/docker-healthcheck.sh +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_cache.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_connection.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_init.py +0 -0
- {awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awslabs.memcached-mcp-server
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: An AWS Labs Model Context Protocol (MCP) server for Amazon ElastiCache Memcached
|
|
5
5
|
Project-URL: homepage, https://awslabs.github.io/mcp/
|
|
6
6
|
Project-URL: docs, https://awslabs.github.io/mcp/servers/memcached-mcp-server/
|
|
@@ -40,6 +40,17 @@ MCP server for interacting with Amazon ElastiCache Memcached through a secure an
|
|
|
40
40
|
- Secure communication with SSL/TLS encryption
|
|
41
41
|
- Automatic connection management and pooling
|
|
42
42
|
- Built-in retry mechanism for failed operations
|
|
43
|
+
- Readonly mode to prevent write operations
|
|
44
|
+
|
|
45
|
+
### Readonly Mode
|
|
46
|
+
|
|
47
|
+
The server can be started in readonly mode, which prevents any write operations from being performed. This is useful for scenarios where you want to ensure that no data is modified, such as:
|
|
48
|
+
|
|
49
|
+
- Read-only replicas
|
|
50
|
+
- Production environments where writes should be restricted
|
|
51
|
+
- Debugging and monitoring without risk of data modification
|
|
52
|
+
|
|
53
|
+
When readonly mode is enabled, any attempt to perform a write operation (set, add, replace, delete, etc.) will return an error message.
|
|
43
54
|
|
|
44
55
|
## Prerequisites
|
|
45
56
|
|
|
@@ -51,6 +62,8 @@ MCP server for interacting with Amazon ElastiCache Memcached through a secure an
|
|
|
51
62
|
|
|
52
63
|
## Installation
|
|
53
64
|
|
|
65
|
+
[](https://cursor.com/install-mcp?name=awslabs.memcached-mcp-server&config=eyJjb21tYW5kIjoidXZ4IGF3c2xhYnMubWVtY2FjaGVkLW1jcC1zZXJ2ZXJAbGF0ZXN0IiwiZW52Ijp7IkZBU1RNQ1BfTE9HX0xFVkVMIjoiRVJST1IiLCJNRU1DQUNIRURfSE9TVCI6InlvdXItbWVtY2FjaGVkLWhvc3QiLCJNRU1DQUNIRURfUE9SVCI6IjExMjExIn0sImRpc2FibGVkIjpmYWxzZSwiYXV0b0FwcHJvdmUiOltdfQ%3D%3D)
|
|
66
|
+
|
|
54
67
|
Here are some ways you can work with MCP (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
|
|
55
68
|
|
|
56
69
|
```json
|
|
@@ -71,6 +84,26 @@ Here are some ways you can work with MCP (e.g. for Amazon Q Developer CLI MCP, `
|
|
|
71
84
|
}
|
|
72
85
|
```
|
|
73
86
|
|
|
87
|
+
To run in readonly mode:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"awslabs.memcached-mcp-server": {
|
|
93
|
+
"command": "uvx",
|
|
94
|
+
"args": ["awslabs.memcached-mcp-server@latest", "--readonly"],
|
|
95
|
+
"env": {
|
|
96
|
+
"FASTMCP_LOG_LEVEL": "ERROR",
|
|
97
|
+
"MEMCACHED_HOST": "your-memcached-host",
|
|
98
|
+
"MEMCACHED_PORT": "11211"
|
|
99
|
+
},
|
|
100
|
+
"disabled": false,
|
|
101
|
+
"autoApprove": []
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
74
107
|
or docker after a successful `docker build -t awslabs/memcached-mcp-server .`:
|
|
75
108
|
|
|
76
109
|
```json
|
|
@@ -98,6 +131,34 @@ or docker after a successful `docker build -t awslabs/memcached-mcp-server .`:
|
|
|
98
131
|
}
|
|
99
132
|
```
|
|
100
133
|
|
|
134
|
+
To run in readonly mode with Docker:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"awslabs.memcached-mcp-server": {
|
|
140
|
+
"command": "docker",
|
|
141
|
+
"args": [
|
|
142
|
+
"run",
|
|
143
|
+
"--rm",
|
|
144
|
+
"--interactive",
|
|
145
|
+
"--env",
|
|
146
|
+
"FASTMCP_LOG_LEVEL=ERROR",
|
|
147
|
+
"--env",
|
|
148
|
+
"MEMCACHED_HOST=your-memcached-host",
|
|
149
|
+
"--env",
|
|
150
|
+
"MEMCACHED_PORT=11211",
|
|
151
|
+
"awslabs/memcached-mcp-server:latest",
|
|
152
|
+
"--readonly"
|
|
153
|
+
],
|
|
154
|
+
"env": {},
|
|
155
|
+
"disabled": false,
|
|
156
|
+
"autoApprove": []
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
101
162
|
## Configuration
|
|
102
163
|
|
|
103
164
|
### Basic Connection Settings
|
|
@@ -155,3 +216,11 @@ docker run -p 8080:8080 \
|
|
|
155
216
|
-e MEMCACHED_PORT=11211 \
|
|
156
217
|
awslabs/memcached-mcp-server
|
|
157
218
|
```
|
|
219
|
+
|
|
220
|
+
To run in readonly mode:
|
|
221
|
+
```bash
|
|
222
|
+
docker run -p 8080:8080 \
|
|
223
|
+
-e MEMCACHED_HOST=host.docker.internal \
|
|
224
|
+
-e MEMCACHED_PORT=11211 \
|
|
225
|
+
awslabs/memcached-mcp-server --readonly
|
|
226
|
+
```
|
|
@@ -10,6 +10,17 @@ MCP server for interacting with Amazon ElastiCache Memcached through a secure an
|
|
|
10
10
|
- Secure communication with SSL/TLS encryption
|
|
11
11
|
- Automatic connection management and pooling
|
|
12
12
|
- Built-in retry mechanism for failed operations
|
|
13
|
+
- Readonly mode to prevent write operations
|
|
14
|
+
|
|
15
|
+
### Readonly Mode
|
|
16
|
+
|
|
17
|
+
The server can be started in readonly mode, which prevents any write operations from being performed. This is useful for scenarios where you want to ensure that no data is modified, such as:
|
|
18
|
+
|
|
19
|
+
- Read-only replicas
|
|
20
|
+
- Production environments where writes should be restricted
|
|
21
|
+
- Debugging and monitoring without risk of data modification
|
|
22
|
+
|
|
23
|
+
When readonly mode is enabled, any attempt to perform a write operation (set, add, replace, delete, etc.) will return an error message.
|
|
13
24
|
|
|
14
25
|
## Prerequisites
|
|
15
26
|
|
|
@@ -21,6 +32,8 @@ MCP server for interacting with Amazon ElastiCache Memcached through a secure an
|
|
|
21
32
|
|
|
22
33
|
## Installation
|
|
23
34
|
|
|
35
|
+
[](https://cursor.com/install-mcp?name=awslabs.memcached-mcp-server&config=eyJjb21tYW5kIjoidXZ4IGF3c2xhYnMubWVtY2FjaGVkLW1jcC1zZXJ2ZXJAbGF0ZXN0IiwiZW52Ijp7IkZBU1RNQ1BfTE9HX0xFVkVMIjoiRVJST1IiLCJNRU1DQUNIRURfSE9TVCI6InlvdXItbWVtY2FjaGVkLWhvc3QiLCJNRU1DQUNIRURfUE9SVCI6IjExMjExIn0sImRpc2FibGVkIjpmYWxzZSwiYXV0b0FwcHJvdmUiOltdfQ%3D%3D)
|
|
36
|
+
|
|
24
37
|
Here are some ways you can work with MCP (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
|
|
25
38
|
|
|
26
39
|
```json
|
|
@@ -41,6 +54,26 @@ Here are some ways you can work with MCP (e.g. for Amazon Q Developer CLI MCP, `
|
|
|
41
54
|
}
|
|
42
55
|
```
|
|
43
56
|
|
|
57
|
+
To run in readonly mode:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"awslabs.memcached-mcp-server": {
|
|
63
|
+
"command": "uvx",
|
|
64
|
+
"args": ["awslabs.memcached-mcp-server@latest", "--readonly"],
|
|
65
|
+
"env": {
|
|
66
|
+
"FASTMCP_LOG_LEVEL": "ERROR",
|
|
67
|
+
"MEMCACHED_HOST": "your-memcached-host",
|
|
68
|
+
"MEMCACHED_PORT": "11211"
|
|
69
|
+
},
|
|
70
|
+
"disabled": false,
|
|
71
|
+
"autoApprove": []
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
44
77
|
or docker after a successful `docker build -t awslabs/memcached-mcp-server .`:
|
|
45
78
|
|
|
46
79
|
```json
|
|
@@ -68,6 +101,34 @@ or docker after a successful `docker build -t awslabs/memcached-mcp-server .`:
|
|
|
68
101
|
}
|
|
69
102
|
```
|
|
70
103
|
|
|
104
|
+
To run in readonly mode with Docker:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"awslabs.memcached-mcp-server": {
|
|
110
|
+
"command": "docker",
|
|
111
|
+
"args": [
|
|
112
|
+
"run",
|
|
113
|
+
"--rm",
|
|
114
|
+
"--interactive",
|
|
115
|
+
"--env",
|
|
116
|
+
"FASTMCP_LOG_LEVEL=ERROR",
|
|
117
|
+
"--env",
|
|
118
|
+
"MEMCACHED_HOST=your-memcached-host",
|
|
119
|
+
"--env",
|
|
120
|
+
"MEMCACHED_PORT=11211",
|
|
121
|
+
"awslabs/memcached-mcp-server:latest",
|
|
122
|
+
"--readonly"
|
|
123
|
+
],
|
|
124
|
+
"env": {},
|
|
125
|
+
"disabled": false,
|
|
126
|
+
"autoApprove": []
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
71
132
|
## Configuration
|
|
72
133
|
|
|
73
134
|
### Basic Connection Settings
|
|
@@ -125,3 +186,11 @@ docker run -p 8080:8080 \
|
|
|
125
186
|
-e MEMCACHED_PORT=11211 \
|
|
126
187
|
awslabs/memcached-mcp-server
|
|
127
188
|
```
|
|
189
|
+
|
|
190
|
+
To run in readonly mode:
|
|
191
|
+
```bash
|
|
192
|
+
docker run -p 8080:8080 \
|
|
193
|
+
-e MEMCACHED_HOST=host.docker.internal \
|
|
194
|
+
-e MEMCACHED_PORT=11211 \
|
|
195
|
+
awslabs/memcached-mcp-server --readonly
|
|
196
|
+
```
|
|
@@ -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 Memcached MCP Server."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Context:
|
|
19
|
+
"""Context class for Memcached 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 memcached MCP Server implementation."""
|
|
16
16
|
|
|
17
|
+
import argparse
|
|
17
18
|
from awslabs.memcached_mcp_server.common.server import mcp
|
|
19
|
+
from awslabs.memcached_mcp_server.context import Context
|
|
18
20
|
from awslabs.memcached_mcp_server.tools import cache # noqa: F401
|
|
19
21
|
from loguru import logger
|
|
20
22
|
from starlette.requests import Request # noqa: F401
|
|
@@ -41,6 +43,18 @@ class MemcachedMCPServer:
|
|
|
41
43
|
|
|
42
44
|
def main():
|
|
43
45
|
"""Run the MCP server with CLI argument support."""
|
|
46
|
+
parser = argparse.ArgumentParser(
|
|
47
|
+
description='An AWS Labs Model Context Protocol (MCP) server for interacting with Memcached'
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
'--readonly',
|
|
51
|
+
action=argparse.BooleanOptionalAction,
|
|
52
|
+
help='Prevents the MCP server from performing mutating operations',
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
args = parser.parse_args()
|
|
56
|
+
Context.initialize(args.readonly)
|
|
57
|
+
|
|
44
58
|
logger.info('Amazon ElastiCache Memcached MCP Server Started...')
|
|
45
59
|
MemcachedMCPServer().run()
|
|
46
60
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
from awslabs.memcached_mcp_server.common.connection import MemcachedConnectionManager
|
|
18
18
|
from awslabs.memcached_mcp_server.common.server import mcp
|
|
19
|
+
from awslabs.memcached_mcp_server.context import Context
|
|
19
20
|
from pymemcache.exceptions import MemcacheError
|
|
20
21
|
from typing import Any, Dict, List, Optional
|
|
21
22
|
|
|
@@ -106,6 +107,9 @@ async def cache_set(key: str, value: Any, expire: Optional[int] = None) -> str:
|
|
|
106
107
|
Returns:
|
|
107
108
|
Success message or error message
|
|
108
109
|
"""
|
|
110
|
+
if Context.readonly_mode():
|
|
111
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
112
|
+
|
|
109
113
|
try:
|
|
110
114
|
client = MemcachedConnectionManager.get_connection()
|
|
111
115
|
client.set(key, value, expire=expire)
|
|
@@ -128,6 +132,9 @@ async def cache_cas(key: str, value: Any, cas: int, expire: Optional[int] = None
|
|
|
128
132
|
Returns:
|
|
129
133
|
Success message or error message
|
|
130
134
|
"""
|
|
135
|
+
if Context.readonly_mode():
|
|
136
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
137
|
+
|
|
131
138
|
try:
|
|
132
139
|
client = MemcachedConnectionManager.get_connection()
|
|
133
140
|
if client.cas(key, value, cas, expire=expire):
|
|
@@ -149,6 +156,9 @@ async def cache_set_many(mapping: Dict[str, Any], expire: Optional[int] = None)
|
|
|
149
156
|
Returns:
|
|
150
157
|
Success message or error message
|
|
151
158
|
"""
|
|
159
|
+
if Context.readonly_mode():
|
|
160
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
161
|
+
|
|
152
162
|
try:
|
|
153
163
|
client = MemcachedConnectionManager.get_connection()
|
|
154
164
|
failed = client.set_many(mapping, expire=expire)
|
|
@@ -186,6 +196,9 @@ async def cache_add(key: str, value: Any, expire: Optional[int] = None) -> str:
|
|
|
186
196
|
Returns:
|
|
187
197
|
Success message or error message
|
|
188
198
|
"""
|
|
199
|
+
if Context.readonly_mode():
|
|
200
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
201
|
+
|
|
189
202
|
try:
|
|
190
203
|
client = MemcachedConnectionManager.get_connection()
|
|
191
204
|
if client.add(key, value, expire=expire):
|
|
@@ -208,6 +221,9 @@ async def cache_replace(key: str, value: Any, expire: Optional[int] = None) -> s
|
|
|
208
221
|
Returns:
|
|
209
222
|
Success message or error message
|
|
210
223
|
"""
|
|
224
|
+
if Context.readonly_mode():
|
|
225
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
226
|
+
|
|
211
227
|
try:
|
|
212
228
|
client = MemcachedConnectionManager.get_connection()
|
|
213
229
|
if client.replace(key, value, expire=expire):
|
|
@@ -229,6 +245,9 @@ async def cache_append(key: str, value: str) -> str:
|
|
|
229
245
|
Returns:
|
|
230
246
|
Success message or error message
|
|
231
247
|
"""
|
|
248
|
+
if Context.readonly_mode():
|
|
249
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
250
|
+
|
|
232
251
|
try:
|
|
233
252
|
client = MemcachedConnectionManager.get_connection()
|
|
234
253
|
if client.append(key, value):
|
|
@@ -249,6 +268,9 @@ async def cache_prepend(key: str, value: str) -> str:
|
|
|
249
268
|
Returns:
|
|
250
269
|
Success message or error message
|
|
251
270
|
"""
|
|
271
|
+
if Context.readonly_mode():
|
|
272
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
273
|
+
|
|
252
274
|
try:
|
|
253
275
|
client = MemcachedConnectionManager.get_connection()
|
|
254
276
|
if client.prepend(key, value):
|
|
@@ -268,6 +290,9 @@ async def cache_delete(key: str) -> str:
|
|
|
268
290
|
Returns:
|
|
269
291
|
Success message or error message
|
|
270
292
|
"""
|
|
293
|
+
if Context.readonly_mode():
|
|
294
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
295
|
+
|
|
271
296
|
try:
|
|
272
297
|
client = MemcachedConnectionManager.get_connection()
|
|
273
298
|
if client.delete(key):
|
|
@@ -287,6 +312,9 @@ async def cache_delete_many(keys: List[str]) -> str:
|
|
|
287
312
|
Returns:
|
|
288
313
|
Success message or error message
|
|
289
314
|
"""
|
|
315
|
+
if Context.readonly_mode():
|
|
316
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
317
|
+
|
|
290
318
|
try:
|
|
291
319
|
client = MemcachedConnectionManager.get_connection()
|
|
292
320
|
failed = client.delete_many(keys)
|
|
@@ -321,6 +349,9 @@ async def cache_incr(key: str, value: int = 1) -> str:
|
|
|
321
349
|
Returns:
|
|
322
350
|
New value or error message
|
|
323
351
|
"""
|
|
352
|
+
if Context.readonly_mode():
|
|
353
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
354
|
+
|
|
324
355
|
try:
|
|
325
356
|
client = MemcachedConnectionManager.get_connection()
|
|
326
357
|
result = client.incr(key, value)
|
|
@@ -342,6 +373,9 @@ async def cache_decr(key: str, value: int = 1) -> str:
|
|
|
342
373
|
Returns:
|
|
343
374
|
New value or error message
|
|
344
375
|
"""
|
|
376
|
+
if Context.readonly_mode():
|
|
377
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
378
|
+
|
|
345
379
|
try:
|
|
346
380
|
client = MemcachedConnectionManager.get_connection()
|
|
347
381
|
result = client.decr(key, value)
|
|
@@ -363,6 +397,9 @@ async def cache_touch(key: str, expire: int) -> str:
|
|
|
363
397
|
Returns:
|
|
364
398
|
Success message or error message
|
|
365
399
|
"""
|
|
400
|
+
if Context.readonly_mode():
|
|
401
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
402
|
+
|
|
366
403
|
try:
|
|
367
404
|
client = MemcachedConnectionManager.get_connection()
|
|
368
405
|
if client.touch(key, expire):
|
|
@@ -400,6 +437,9 @@ async def cache_flush_all(delay: int = 0) -> str:
|
|
|
400
437
|
Returns:
|
|
401
438
|
Success message or error message
|
|
402
439
|
"""
|
|
440
|
+
if Context.readonly_mode():
|
|
441
|
+
return 'Operation not permitted: Server is in readonly mode'
|
|
442
|
+
|
|
403
443
|
try:
|
|
404
444
|
client = MemcachedConnectionManager.get_connection()
|
|
405
445
|
client.flush_all(delay=delay)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Tests for readonly mode in Cache functionality in the memcached MCP server."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from awslabs.memcached_mcp_server.tools.cache import (
|
|
5
|
+
cache_add,
|
|
6
|
+
cache_append,
|
|
7
|
+
cache_cas,
|
|
8
|
+
cache_decr,
|
|
9
|
+
cache_delete,
|
|
10
|
+
cache_delete_many,
|
|
11
|
+
cache_flush_all,
|
|
12
|
+
cache_incr,
|
|
13
|
+
cache_prepend,
|
|
14
|
+
cache_replace,
|
|
15
|
+
cache_set,
|
|
16
|
+
cache_set_many,
|
|
17
|
+
cache_touch,
|
|
18
|
+
)
|
|
19
|
+
from unittest.mock import Mock, patch
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestCacheReadonly:
|
|
23
|
+
"""Tests for Cache operations in readonly mode."""
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def mock_client(self):
|
|
27
|
+
"""Create a mock Memcached client."""
|
|
28
|
+
with patch(
|
|
29
|
+
'awslabs.memcached_mcp_server.common.connection.MemcachedConnectionManager.get_connection'
|
|
30
|
+
) as mock:
|
|
31
|
+
client = Mock()
|
|
32
|
+
mock.return_value = client
|
|
33
|
+
yield client
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_context(self):
|
|
37
|
+
"""Create a mock Context."""
|
|
38
|
+
with patch('awslabs.memcached_mcp_server.tools.cache.Context') as mock_ctx:
|
|
39
|
+
mock_ctx.readonly_mode.return_value = True
|
|
40
|
+
yield mock_ctx
|
|
41
|
+
|
|
42
|
+
@pytest.mark.asyncio
|
|
43
|
+
async def test_cache_set_readonly(self, mock_client, mock_context):
|
|
44
|
+
"""Test setting a value in readonly mode."""
|
|
45
|
+
key = 'test_key'
|
|
46
|
+
value = 'test_value'
|
|
47
|
+
|
|
48
|
+
result = await cache_set(key, value)
|
|
49
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
50
|
+
mock_client.set.assert_not_called()
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_cache_delete_readonly(self, mock_client, mock_context):
|
|
54
|
+
"""Test deleting a value in readonly mode."""
|
|
55
|
+
key = 'test_key'
|
|
56
|
+
|
|
57
|
+
result = await cache_delete(key)
|
|
58
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
59
|
+
mock_client.delete.assert_not_called()
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_cache_incr_readonly(self, mock_client, mock_context):
|
|
63
|
+
"""Test incrementing a counter in readonly mode."""
|
|
64
|
+
key = 'counter'
|
|
65
|
+
|
|
66
|
+
result = await cache_incr(key)
|
|
67
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
68
|
+
mock_client.incr.assert_not_called()
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_cache_append_readonly(self, mock_client, mock_context):
|
|
72
|
+
"""Test appending to a value in readonly mode."""
|
|
73
|
+
key = 'test_key'
|
|
74
|
+
value = 'test_value'
|
|
75
|
+
|
|
76
|
+
result = await cache_append(key, value)
|
|
77
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
78
|
+
mock_client.append.assert_not_called()
|
|
79
|
+
|
|
80
|
+
@pytest.mark.asyncio
|
|
81
|
+
async def test_cache_flush_all_readonly(self, mock_client, mock_context):
|
|
82
|
+
"""Test flushing all cache entries in readonly mode."""
|
|
83
|
+
result = await cache_flush_all()
|
|
84
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
85
|
+
mock_client.flush_all.assert_not_called()
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_cache_cas_readonly(self, mock_client, mock_context):
|
|
89
|
+
"""Test CAS operation in readonly mode."""
|
|
90
|
+
key = 'test_key'
|
|
91
|
+
value = 'test_value'
|
|
92
|
+
cas = 123
|
|
93
|
+
|
|
94
|
+
result = await cache_cas(key, value, cas)
|
|
95
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
96
|
+
mock_client.cas.assert_not_called()
|
|
97
|
+
|
|
98
|
+
@pytest.mark.asyncio
|
|
99
|
+
async def test_cache_set_many_readonly(self, mock_client, mock_context):
|
|
100
|
+
"""Test setting multiple values in readonly mode."""
|
|
101
|
+
mapping = {'key1': 'value1', 'key2': 'value2'}
|
|
102
|
+
|
|
103
|
+
result = await cache_set_many(mapping)
|
|
104
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
105
|
+
mock_client.set_many.assert_not_called()
|
|
106
|
+
|
|
107
|
+
@pytest.mark.asyncio
|
|
108
|
+
async def test_cache_add_readonly(self, mock_client, mock_context):
|
|
109
|
+
"""Test adding a value in readonly mode."""
|
|
110
|
+
key = 'test_key'
|
|
111
|
+
value = 'test_value'
|
|
112
|
+
|
|
113
|
+
result = await cache_add(key, value)
|
|
114
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
115
|
+
mock_client.add.assert_not_called()
|
|
116
|
+
|
|
117
|
+
@pytest.mark.asyncio
|
|
118
|
+
async def test_cache_replace_readonly(self, mock_client, mock_context):
|
|
119
|
+
"""Test replacing a value in readonly mode."""
|
|
120
|
+
key = 'test_key'
|
|
121
|
+
value = 'test_value'
|
|
122
|
+
|
|
123
|
+
result = await cache_replace(key, value)
|
|
124
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
125
|
+
mock_client.replace.assert_not_called()
|
|
126
|
+
|
|
127
|
+
@pytest.mark.asyncio
|
|
128
|
+
async def test_cache_prepend_readonly(self, mock_client, mock_context):
|
|
129
|
+
"""Test prepending to a value in readonly mode."""
|
|
130
|
+
key = 'test_key'
|
|
131
|
+
value = 'test_value'
|
|
132
|
+
|
|
133
|
+
result = await cache_prepend(key, value)
|
|
134
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
135
|
+
mock_client.prepend.assert_not_called()
|
|
136
|
+
|
|
137
|
+
@pytest.mark.asyncio
|
|
138
|
+
async def test_cache_delete_many_readonly(self, mock_client, mock_context):
|
|
139
|
+
"""Test deleting multiple values in readonly mode."""
|
|
140
|
+
keys = ['key1', 'key2']
|
|
141
|
+
|
|
142
|
+
result = await cache_delete_many(keys)
|
|
143
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
144
|
+
mock_client.delete_many.assert_not_called()
|
|
145
|
+
|
|
146
|
+
@pytest.mark.asyncio
|
|
147
|
+
async def test_cache_decr_readonly(self, mock_client, mock_context):
|
|
148
|
+
"""Test decrementing a counter in readonly mode."""
|
|
149
|
+
key = 'counter'
|
|
150
|
+
|
|
151
|
+
result = await cache_decr(key)
|
|
152
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
153
|
+
mock_client.decr.assert_not_called()
|
|
154
|
+
|
|
155
|
+
@pytest.mark.asyncio
|
|
156
|
+
async def test_cache_touch_readonly(self, mock_client, mock_context):
|
|
157
|
+
"""Test touching a key in readonly mode."""
|
|
158
|
+
key = 'test_key'
|
|
159
|
+
expire = 60
|
|
160
|
+
|
|
161
|
+
result = await cache_touch(key, expire)
|
|
162
|
+
assert 'Operation not permitted: Server is in readonly mode' in result
|
|
163
|
+
mock_client.touch.assert_not_called()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/ELASTICACHECONNECT.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/awslabs/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/docker-healthcheck.sh
RENAMED
|
File without changes
|
{awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_cache.py
RENAMED
|
File without changes
|
{awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_connection.py
RENAMED
|
File without changes
|
{awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_init.py
RENAMED
|
File without changes
|
{awslabs_memcached_mcp_server-1.0.2 → awslabs_memcached_mcp_server-1.0.3}/tests/test_main.py
RENAMED
|
File without changes
|