#
tokens: 10100/50000 9/9 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── pyproject.toml
├── README.md
├── smithery.yaml
├── src
│   └── mcp_server_aws
│       ├── __init__.py
│       ├── server.py
│       ├── tools.py
│       └── utils.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python cache files
 2 | __pycache__/
 3 | *.py[cod]
 4 | *$py.class
 5 | 
 6 | # Distribution / packaging
 7 | dist/
 8 | build/
 9 | *.egg-info/
10 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # AWS MCP Server
 2 | 
 3 | [![smithery badge](https://smithery.ai/badge/mcp-server-aws)](https://smithery.ai/server/mcp-server-aws)
 4 | 
 5 | A [Model Context Protocol](https://www.anthropic.com/news/model-context-protocol) server implementation for AWS operations that currently supports S3 and DynamoDB services. All operations are automatically logged and can be accessed through the `audit://aws-operations` resource endpoint.
 6 | 
 7 | <a href="https://glama.ai/mcp/servers/v69k6ch2gh">
 8 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/v69k6ch2gh/badge" alt="AWS Server MCP server" />
 9 | </a>
10 | 
11 | See a demo video [here](https://www.loom.com/share/99551eeb2e514e7eaf29168c47f297d1?sid=4eb54324-5546-4f44-99a0-947f80b9365c).
12 | 
13 | Listed as a [Community Server](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#-community-servers) within the MCP servers repository.
14 | 
15 | ## Running locally with the Claude desktop app
16 | 
17 | ### Installing via Smithery
18 | 
19 | To install AWS MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-server-aws):
20 | 
21 | ```bash
22 | npx -y @smithery/cli install mcp-server-aws --client claude
23 | ```
24 | 
25 | ### Manual Installation
26 | 1. Clone this repository.
27 | 2. Set up your AWS credentials via one of the two methods below. Note that this server requires an IAM user with RW permissions for your AWS account for S3 and DynamoDB.
28 | - Environment variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION` (defaults to `us-east-1`)
29 | - Default AWS credential chain (set up via AWS CLI with `aws configure`)
30 | 3. Add the following to your `claude_desktop_config.json` file:
31 | - On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
32 | - On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
33 | 
34 | ```
35 | "mcpServers": {
36 |   "mcp-server-aws": {
37 |     "command": "uv",
38 |     "args": [
39 |       "--directory",
40 |       "/path/to/repo/mcp-server-aws",
41 |       "run",
42 |       "mcp-server-aws"
43 |     ]
44 |   }
45 | }
46 | ```
47 | 4. Install and open the [Claude desktop app](https://claude.ai/download).
48 | 5. Try asking Claude to do a read/write operation of some sort to confirm the setup (e.g. create an S3 bucket and give it a random name). If there are issues, use the Debugging tools provided in the MCP documentation [here](https://modelcontextprotocol.io/docs/tools/debugging).
49 | 
50 | ## Available Tools
51 | 
52 | ### S3 Operations
53 | 
54 | - **s3_bucket_create**: Create a new S3 bucket
55 | - **s3_bucket_list**: List all S3 buckets
56 | - **s3_bucket_delete**: Delete an S3 bucket
57 | - **s3_object_upload**: Upload an object to S3
58 | - **s3_object_delete**: Delete an object from S3
59 | - **s3_object_list**: List objects in an S3 bucket
60 | - **s3_object_read**: Read an object's content from S3
61 | 
62 | ### DynamoDB Operations
63 | 
64 | #### Table Operations
65 | - **dynamodb_table_create**: Create a new DynamoDB table
66 | - **dynamodb_table_describe**: Get details about a DynamoDB table
67 | - **dynamodb_table_delete**: Delete a DynamoDB table
68 | - **dynamodb_table_update**: Update a DynamoDB table
69 | 
70 | #### Item Operations
71 | - **dynamodb_item_put**: Put an item into a DynamoDB table
72 | - **dynamodb_item_get**: Get an item from a DynamoDB table
73 | - **dynamodb_item_update**: Update an item in a DynamoDB table
74 | - **dynamodb_item_delete**: Delete an item from a DynamoDB table
75 | - **dynamodb_item_query**: Query items in a DynamoDB table
76 | - **dynamodb_item_scan**: Scan items in a DynamoDB table
77 | #### Batch Operations
78 | - **dynamodb_batch_get**: Batch get multiple items from DynamoDB tables
79 | - **dynamodb_item_batch_write**: Batch write operations (put/delete) for DynamoDB items
80 | - **dynamodb_batch_execute**: Execute multiple PartiQL statements in a batch
81 | 
82 | #### TTL Operations
83 | - **dynamodb_describe_ttl**: Get the TTL settings for a table
84 | - **dynamodb_update_ttl**: Update the TTL settings for a table
```

--------------------------------------------------------------------------------
/src/mcp_server_aws/__init__.py:
--------------------------------------------------------------------------------

```python
1 | from . import server
2 | import asyncio
3 | 
4 | def main():
5 |     """Main entry point for the package."""
6 |     asyncio.run(server.main())
7 | 
8 | # Optionally expose other important items at package level
9 | __all__ = ['main', 'server']
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp-server-aws"
 3 | version = "0.1.0"
 4 | description = "A Model Context Protocol server providing tools to read and manipulate AWS resources using an LLM"
 5 | readme = "README.md"
 6 | requires-python = ">=3.13"
 7 | dependencies = [ 
 8 |     "mcp>=1.0.0",
 9 |     "python-dotenv>=1.0.1",
10 |     "boto3>=1.35.53",
11 | ]
12 | [[project.authors]]
13 | name = "Rishi Kavikondala"
14 | email = "[email protected]"
15 | 
16 | [build-system]
17 | requires = [ "hatchling",]
18 | build-backend = "hatchling.build"
19 | 
20 | [project.scripts]
21 | mcp-server-aws = "mcp_server_aws:main"
22 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_aws/utils.py:
--------------------------------------------------------------------------------

```python
 1 | def get_dynamodb_type(value):
 2 |     if isinstance(value, str):
 3 |         return {'S': value}
 4 |     elif isinstance(value, (int, float)):
 5 |         return {'N': str(value)}
 6 |     elif isinstance(value, bool):
 7 |         return {'BOOL': value}
 8 |     elif value is None:
 9 |         return {'NULL': True}
10 |     elif isinstance(value, list):
11 |         return {'L': [get_dynamodb_type(v) for v in value]}
12 |     elif isinstance(value, dict):
13 |         return {'M': {k: get_dynamodb_type(v) for k, v in value.items()}}
14 |     else:
15 |         raise ValueError(
16 |             f"Unsupported type for DynamoDB: {type(value)}")
17 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | # Use an official Python image
 3 | FROM python:3.13-slim
 4 | 
 5 | # Set environment variables
 6 | ENV PYTHONDONTWRITEBYTECODE=1
 7 | ENV PYTHONUNBUFFERED=1
 8 | 
 9 | # Create and set working directory
10 | WORKDIR /app
11 | 
12 | # Install build system and dependencies
13 | RUN pip install --upgrade pip && \
14 |     pip install hatchling
15 | 
16 | # Copy the project files into the container
17 | COPY . /app
18 | 
19 | # Install the project in editable mode (if necessary)
20 | RUN hatch build && pip install .
21 | 
22 | # Set up environment variables for AWS credentials
23 | ENV AWS_ACCESS_KEY_ID=your_access_key_id
24 | ENV AWS_SECRET_ACCESS_KEY=your_secret_access_key
25 | ENV AWS_REGION=us-east-1
26 | 
27 | # Run the MCP server
28 | CMD ["hatch", "run", "mcp-server-aws"]
29 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - awsAccessKeyId
10 |       - awsSecretAccessKey
11 |     properties:
12 |       awsAccessKeyId:
13 |         type: string
14 |         description: AWS Access Key ID
15 |       awsSecretAccessKey:
16 |         type: string
17 |         description: AWS Secret Access Key
18 |       awsRegion:
19 |         type: string
20 |         default: us-east-1
21 |         description: AWS Region
22 |   commandFunction:
23 |     # A function that produces the CLI command to start the MCP on stdio.
24 |     |-
25 |     (config) => ({ command: 'hatch', args: ['run', 'mcp-server-aws'], env: { AWS_ACCESS_KEY_ID: config.awsAccessKeyId, AWS_SECRET_ACCESS_KEY: config.awsSecretAccessKey, AWS_REGION: config.awsRegion || 'us-east-1' } })
```

--------------------------------------------------------------------------------
/src/mcp_server_aws/server.py:
--------------------------------------------------------------------------------

```python
  1 | import os
  2 | import json
  3 | import logging
  4 | from datetime import datetime
  5 | from pathlib import Path
  6 | from typing import Any, Sequence
  7 | from functools import lru_cache
  8 | import base64
  9 | import io
 10 | import boto3
 11 | import asyncio
 12 | from dotenv import load_dotenv
 13 | import mcp.server.stdio
 14 | from mcp.server import Server, NotificationOptions
 15 | from mcp.server.models import InitializationOptions
 16 | from mcp.types import Resource, Tool, TextContent, ImageContent, EmbeddedResource
 17 | from pydantic import AnyUrl
 18 | from .tools import get_aws_tools
 19 | from .utils import get_dynamodb_type
 20 | 
 21 | load_dotenv()
 22 | logging.basicConfig(level=logging.INFO)
 23 | logger = logging.getLogger("aws-mcp-server")
 24 | 
 25 | 
 26 | def custom_json_serializer(obj):
 27 |     if isinstance(obj, datetime):
 28 |         return obj.isoformat()
 29 |     raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
 30 | 
 31 | 
 32 | class AWSManager:
 33 |     def __init__(self):
 34 |         logger.info("Initializing AWSManager")
 35 |         self.audit_entries: list[dict] = []
 36 | 
 37 |     @lru_cache(maxsize=None)
 38 |     def get_boto3_client(self, service_name: str, region_name: str = None):
 39 |         """Get a boto3 client using explicit credentials if available"""
 40 |         try:
 41 |             logger.info(f"Creating boto3 client for service: {service_name}")
 42 |             region_name = region_name or os.getenv("AWS_REGION", "us-east-1")
 43 |             if not region_name:
 44 |                 raise ValueError(
 45 |                     "AWS region is not specified and not set in the environment.")
 46 | 
 47 |             aws_access_key = os.getenv("AWS_ACCESS_KEY_ID")
 48 |             aws_secret_key = os.getenv("AWS_SECRET_ACCESS_KEY")
 49 | 
 50 |             if aws_access_key and aws_secret_key:
 51 |                 logger.debug("Using explicit AWS credentials")
 52 |                 session = boto3.Session(
 53 |                     aws_access_key_id=aws_access_key,
 54 |                     aws_secret_access_key=aws_secret_key,
 55 |                     region_name=region_name
 56 |                 )
 57 |             else:
 58 |                 aws_profile = os.getenv("AWS_PROFILE")
 59 |                 if aws_profile:
 60 |                     logger.debug(f"Using AWS profile: {aws_profile}")
 61 |                     session = boto3.Session(profile_name=aws_profile, region_name=region_name)
 62 |                 else:
 63 |                     logger.debug("Using default AWS credential chain")
 64 |                     session = boto3.Session(region_name=region_name)
 65 |             return session.client(service_name)
 66 |         except Exception as e:
 67 |             logger.error(f"Failed to create boto3 client for {
 68 |                          service_name}: {e}")
 69 |             raise RuntimeError(f"Failed to create boto3 client: {e}")
 70 | 
 71 |     def _synthesize_audit_log(self) -> str:
 72 |         """Generate formatted audit log from entries"""
 73 |         logger.debug("Synthesizing audit log")
 74 |         if not self.audit_entries:
 75 |             return "No AWS operations have been performed yet."
 76 | 
 77 |         report = "📋 AWS Operations Audit Log 📋\n\n"
 78 |         for entry in self.audit_entries:
 79 |             report += f"[{entry['timestamp']}]\n"
 80 |             report += f"Service: {entry['service']}\n"
 81 |             report += f"Operation: {entry['operation']}\n"
 82 |             report += f"Parameters: {json.dumps(
 83 |                 entry['parameters'], indent=2)}\n"
 84 |             report += "-" * 50 + "\n"
 85 | 
 86 |         return report
 87 | 
 88 |     def log_operation(self, service: str, operation: str, parameters: dict) -> None:
 89 |         """Log an AWS operation to the audit log"""
 90 |         logger.info(
 91 |             f"Logging operation - Service: {service}, Operation: {operation}")
 92 |         audit_entry = {
 93 |             "timestamp": datetime.utcnow().isoformat(),
 94 |             "service": service,
 95 |             "operation": operation,
 96 |             "parameters": parameters
 97 |         }
 98 |         self.audit_entries.append(audit_entry)
 99 | 
100 | 
101 | async def main():
102 |     logger.info("Starting AWS MCP Server")
103 | 
104 |     aws = AWSManager()
105 |     server = Server("aws-mcp-server")
106 | 
107 |     logger.debug("Registering handlers")
108 | 
109 |     @server.list_resources()
110 |     async def handle_list_resources() -> list[Resource]:
111 |         logger.debug("Handling list_resources request")
112 |         return [
113 |             Resource(
114 |                 uri=AnyUrl("audit://aws-operations"),
115 |                 name="AWS Operations Audit Log",
116 |                 description="A log of all AWS operations performed through this server",
117 |                 mimeType="text/plain",
118 |             )
119 |         ]
120 | 
121 |     @server.read_resource()
122 |     async def handle_read_resource(uri: AnyUrl) -> str:
123 |         logger.debug(f"Handling read_resource request for URI: {uri}")
124 |         if uri.scheme != "audit":
125 |             logger.error(f"Unsupported URI scheme: {uri.scheme}")
126 |             raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
127 | 
128 |         path = str(uri).replace("audit://", "")
129 |         if path != "aws-operations":
130 |             logger.error(f"Unknown resource path: {path}")
131 |             raise ValueError(f"Unknown resource path: {path}")
132 | 
133 |         return aws._synthesize_audit_log()
134 | 
135 |     @server.list_tools()
136 |     async def list_tools() -> list[Tool]:
137 |         """List available AWS tools"""
138 |         logger.debug("Handling list_tools request")
139 |         return get_aws_tools()
140 | 
141 |     async def handle_s3_operations(aws: AWSManager, name: str, arguments: dict) -> list[TextContent]:
142 |         """Handle S3-specific operations"""
143 |         s3_client = aws.get_boto3_client('s3')
144 |         response = None
145 | 
146 |         if name == "s3_bucket_create":
147 |             response = s3_client.create_bucket(Bucket=arguments["bucket_name"],
148 |                                                CreateBucketConfiguration={
149 |                                                    'LocationConstraint': os.getenv("AWS_REGION") or 'us-east-1'
150 |                                                })
151 |         elif name == "s3_bucket_list":
152 |             response = s3_client.list_buckets()
153 |         elif name == "s3_bucket_delete":
154 |             response = s3_client.delete_bucket(Bucket=arguments["bucket_name"])
155 |         elif name == "s3_object_upload":
156 |             response = s3_client.upload_fileobj(
157 |                 io.BytesIO(base64.b64decode(arguments["file_content"])),
158 |                 arguments["bucket_name"],
159 |                 arguments["object_key"])
160 |         elif name == "s3_object_delete":
161 |             response = s3_client.delete_object(
162 |                 Bucket=arguments["bucket_name"],
163 |                 Key=arguments["object_key"]
164 |             )
165 |         elif name == "s3_object_list":
166 |             response = s3_client.list_objects_v2(
167 |                 Bucket=arguments["bucket_name"])
168 |         elif name == "s3_object_read":
169 |             logging.info(f"Reading object: {arguments['object_key']}")
170 |             response = s3_client.get_object(
171 |                 Bucket=arguments["bucket_name"],
172 |                 Key=arguments["object_key"]
173 |             )
174 |             content = response['Body'].read().decode('utf-8')
175 |             return [TextContent(type="text", text=content)]
176 |         else:
177 |             raise ValueError(f"Unknown S3 operation: {name}")
178 | 
179 |         aws.log_operation("s3", name.replace("s3_", ""), arguments)
180 |         return [TextContent(type="text", text=f"Operation Result:\n{json.dumps(response, indent=2, default=custom_json_serializer)}")]
181 | 
182 |     async def handle_dynamodb_operations(aws: AWSManager, name: str, arguments: dict) -> list[TextContent]:
183 |         """Handle DynamoDB-specific operations"""
184 |         dynamodb_client = aws.get_boto3_client('dynamodb')
185 |         response = None
186 | 
187 |         if name == "dynamodb_table_create":
188 |             response = dynamodb_client.create_table(
189 |                 TableName=arguments["table_name"],
190 |                 KeySchema=arguments["key_schema"],
191 |                 AttributeDefinitions=arguments["attribute_definitions"],
192 |                 BillingMode="PAY_PER_REQUEST"
193 |             )
194 |         elif name == "dynamodb_table_describe":
195 |             response = dynamodb_client.describe_table(
196 |                 TableName=arguments["table_name"])
197 |         elif name == "dynamodb_table_list":
198 |             response = dynamodb_client.list_tables()
199 |         elif name == "dynamodb_table_delete":
200 |             response = dynamodb_client.delete_table(
201 |                 TableName=arguments["table_name"])
202 |         elif name == "dynamodb_table_update":
203 |             update_params = {
204 |                 "TableName": arguments["table_name"],
205 |                 "AttributeDefinitions": arguments["attribute_definitions"]
206 |             }
207 |             response = dynamodb_client.update_table(**update_params)
208 |         elif name == "dynamodb_describe_ttl":
209 |             response = dynamodb_client.describe_time_to_live(
210 |                 TableName=arguments["table_name"]
211 |             )
212 |         elif name == "dynamodb_update_ttl":
213 |             response = dynamodb_client.update_time_to_live(
214 |                 TableName=arguments["table_name"],
215 |                 TimeToLiveSpecification={
216 |                     'Enabled': arguments["ttl_enabled"],
217 |                     'AttributeName': arguments["ttl_attribute"]
218 |                 }
219 |             )
220 |         elif name == "dynamodb_item_put":
221 |             response = dynamodb_client.put_item(
222 |                 TableName=arguments["table_name"],
223 |                 Item=arguments["item"]
224 |             )
225 |         elif name == "dynamodb_item_get":
226 |             response = dynamodb_client.get_item(
227 |                 TableName=arguments["table_name"],
228 |                 Key=arguments["key"]
229 |             )
230 |         elif name == "dynamodb_item_update":
231 |             response = dynamodb_client.update_item(
232 |                 TableName=arguments["table_name"],
233 |                 Key=arguments["key"],
234 |                 AttributeUpdates=arguments["item"]
235 |             )
236 |         elif name == "dynamodb_item_delete":
237 |             response = dynamodb_client.delete_item(
238 |                 TableName=arguments["table_name"],
239 |                 Key=arguments["key"]
240 |             )
241 |         elif name == "dynamodb_item_query":
242 |             response = dynamodb_client.query(
243 |                 TableName=arguments["table_name"],
244 |                 KeyConditionExpression=arguments["key_condition"],
245 |                 ExpressionAttributeValues=arguments["expression_values"]
246 |             )
247 |         elif name == "dynamodb_item_scan":
248 |             scan_params = {"TableName": arguments["table_name"]}
249 | 
250 |             if "filter_expression" in arguments:
251 |                 scan_params["FilterExpression"] = arguments["filter_expression"]
252 | 
253 |                 if "expression_attributes" in arguments:
254 |                     attrs = arguments["expression_attributes"]
255 |                     if "names" in attrs:
256 |                         scan_params["ExpressionAttributeNames"] = attrs["names"]
257 |                     if "values" in attrs:
258 |                         scan_params["ExpressionAttributeValues"] = attrs["values"]
259 | 
260 |             response = dynamodb_client.scan(**scan_params)
261 |         elif name == "dynamodb_batch_get":
262 |             response = dynamodb_client.batch_get_item(
263 |                 RequestItems=arguments["request_items"]
264 |             )
265 |         elif name == "dynamodb_item_batch_write":
266 |             table_name = arguments["table_name"]
267 |             operation = arguments["operation"]
268 |             items = arguments["items"]
269 | 
270 |             if not items:
271 |                 raise ValueError("No items provided for batch operation")
272 | 
273 |             batch_size = 25
274 |             total_items = len(items)
275 |             processed_items = 0
276 |             failed_items = []
277 | 
278 |             for i in range(0, total_items, batch_size):
279 |                 batch = items[i:i + batch_size]
280 |                 request_items = {table_name: []}
281 | 
282 |                 for item in batch:
283 |                     if operation == "put":
284 |                         formatted_item = {k: get_dynamodb_type(
285 |                             v) for k, v in item.items()}
286 |                         request_items[table_name].append({
287 |                             'PutRequest': {'Item': formatted_item}
288 |                         })
289 |                     elif operation == "delete":
290 |                         key_attrs = arguments.get(
291 |                             "key_attributes", list(item.keys()))
292 |                         formatted_key = {k: get_dynamodb_type(
293 |                             item[k]) for k in key_attrs}
294 |                         request_items[table_name].append({
295 |                             'DeleteRequest': {'Key': formatted_key}
296 |                         })
297 | 
298 |                 try:
299 |                     response = dynamodb_client.batch_write_item(
300 |                         RequestItems=request_items)
301 |                     processed_items += len(batch) - len(
302 |                         response.get('UnprocessedItems', {}
303 |                                      ).get(table_name, [])
304 |                     )
305 | 
306 |                     unprocessed = response.get('UnprocessedItems', {})
307 |                     retry_count = 0
308 |                     max_retries = 3
309 |                     while unprocessed and retry_count < max_retries:
310 |                         await asyncio.sleep(2 ** retry_count)
311 |                         retry_response = dynamodb_client.batch_write_item(
312 |                             RequestItems=unprocessed)
313 |                         unprocessed = retry_response.get(
314 |                             'UnprocessedItems', {})
315 |                         retry_count += 1
316 | 
317 |                     if unprocessed:
318 |                         failed_items.extend([
319 |                             item['PutRequest']['Item'] if 'PutRequest' in item else item['DeleteRequest']['Key']
320 |                             for item in unprocessed.get(table_name, [])
321 |                         ])
322 | 
323 |                 except Exception as e:
324 |                     logger.error(f"Error processing batch: {str(e)}")
325 |                     failed_items.extend(batch)
326 | 
327 |             response = {
328 |                 "total_items": total_items,
329 |                 "processed_items": processed_items,
330 |                 "failed_items": len(failed_items),
331 |                 "failed_items_details": failed_items if failed_items else None
332 |             }
333 |         elif name == "dynamodb_batch_execute":
334 |             response = dynamodb_client.batch_execute_statement(
335 |                 Statements=[{
336 |                     'Statement': statement,
337 |                     'Parameters': params
338 |                 } for statement, params in zip(arguments["statements"], arguments["parameters"])]
339 |             )
340 |         else:
341 |             raise ValueError(f"Unknown DynamoDB operation: {name}")
342 | 
343 |         aws.log_operation("dynamodb", name.replace("dynamodb_", ""), arguments)
344 |         return [TextContent(type="text", text=f"Operation Result:\n{json.dumps(response, indent=2, default=custom_json_serializer)}")]
345 | 
346 |     @server.call_tool()
347 |     async def call_tool(name: str, arguments: Any) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
348 |         """Handle AWS tool operations"""
349 |         logger.info(f"Handling tool call: {name}")
350 |         logger.debug(f"Tool arguments: {arguments}")
351 | 
352 |         if not isinstance(arguments, dict):
353 |             logger.error("Invalid arguments: not a dictionary")
354 |             raise ValueError("Invalid arguments")
355 | 
356 |         try:
357 |             if name.startswith("s3_"):
358 |                 return await handle_s3_operations(aws, name, arguments)
359 |             elif name.startswith("dynamodb_"):
360 |                 return await handle_dynamodb_operations(aws, name, arguments)
361 |             else:
362 |                 raise ValueError(f"Unknown tool: {name}")
363 | 
364 |         except Exception as e:
365 |             logger.error(f"Operation failed: {str(e)}")
366 |             raise RuntimeError(f"Operation failed: {str(e)}")
367 | 
368 |     async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
369 |         logger.info("Server running with stdio transport")
370 |         await server.run(
371 |             read_stream,
372 |             write_stream,
373 |             InitializationOptions(
374 |                 server_name="mcp-server-aws",
375 |                 server_version="0.1.0",
376 |                 capabilities=server.get_capabilities(
377 |                     notification_options=NotificationOptions(),
378 |                     experimental_capabilities={},
379 |                 ),
380 |             ),
381 |         )
382 | 
383 | if __name__ == "__main__":
384 |     asyncio.run(main())
385 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_aws/tools.py:
--------------------------------------------------------------------------------

```python
  1 | from mcp.types import Tool
  2 | 
  3 | 
  4 | def get_s3_tools() -> list[Tool]:
  5 |     return [
  6 |         Tool(
  7 |             name="s3_bucket_create",
  8 |             description="Create a new S3 bucket",
  9 |             inputSchema={
 10 |                 "type": "object",
 11 |                 "properties": {
 12 |                     "bucket_name": {
 13 |                         "type": "string",
 14 |                         "description": "Name of the S3 bucket to create"
 15 |                     }
 16 |                 },
 17 |                 "required": ["bucket_name"]
 18 |             }
 19 |         ),
 20 |         Tool(
 21 |             name="s3_bucket_list",
 22 |             description="List all S3 buckets",
 23 |             inputSchema={
 24 |                 "type": "object",
 25 |                 "properties": {}
 26 |             }
 27 |         ),
 28 |         Tool(
 29 |             name="s3_bucket_delete",
 30 |             description="Delete an S3 bucket",
 31 |             inputSchema={
 32 |                 "type": "object",
 33 |                 "properties": {
 34 |                     "bucket_name": {
 35 |                         "type": "string",
 36 |                         "description": "Name of the S3 bucket to delete"
 37 |                     }
 38 |                 },
 39 |                 "required": ["bucket_name"]
 40 |             }
 41 |         ),
 42 |         Tool(
 43 |             name="s3_object_upload",
 44 |             description="Upload an object to S3",
 45 |             inputSchema={
 46 |                 "type": "object",
 47 |                 "properties": {
 48 |                     "bucket_name": {
 49 |                         "type": "string",
 50 |                         "description": "Name of the S3 bucket"
 51 |                     },
 52 |                     "object_key": {
 53 |                         "type": "string",
 54 |                         "description": "Key/path of the object in the bucket"
 55 |                     },
 56 |                     "file_content": {
 57 |                         "type": "string",
 58 |                         "description": "Base64 encoded file content for upload"
 59 |                     }
 60 |                 },
 61 |                 "required": ["bucket_name", "object_key", "file_content"]
 62 |             }
 63 |         ),
 64 |         Tool(
 65 |             name="s3_object_delete",
 66 |             description="Delete an object from S3",
 67 |             inputSchema={
 68 |                 "type": "object",
 69 |                 "properties": {
 70 |                     "bucket_name": {
 71 |                         "type": "string",
 72 |                         "description": "Name of the S3 bucket"
 73 |                     },
 74 |                     "object_key": {
 75 |                         "type": "string",
 76 |                         "description": "Key/path of the object to delete"
 77 |                     }
 78 |                 },
 79 |                 "required": ["bucket_name", "object_key"]
 80 |             }
 81 |         ),
 82 |         Tool(
 83 |             name="s3_object_list",
 84 |             description="List objects in an S3 bucket",
 85 |             inputSchema={
 86 |                 "type": "object",
 87 |                 "properties": {
 88 |                     "bucket_name": {
 89 |                         "type": "string",
 90 |                         "description": "Name of the S3 bucket"
 91 |                     }
 92 |                 },
 93 |                 "required": ["bucket_name"]
 94 |             }
 95 |         ),
 96 |         Tool(
 97 |             name="s3_object_read",
 98 |             description="Read an object's content from S3",
 99 |             inputSchema={
100 |                 "type": "object",
101 |                 "properties": {
102 |                     "bucket_name": {
103 |                         "type": "string",
104 |                         "description": "Name of the S3 bucket"
105 |                     },
106 |                     "object_key": {
107 |                         "type": "string",
108 |                         "description": "Key/path of the object to read"
109 |                     }
110 |                 },
111 |                 "required": ["bucket_name", "object_key"]
112 |             }
113 |         ),
114 |     ]
115 | 
116 | 
117 | def get_dynamodb_tools() -> list[Tool]:
118 |     return [
119 |         Tool(
120 |             name="dynamodb_table_create",
121 |             description="Create a new DynamoDB table",
122 |             inputSchema={
123 |                 "type": "object",
124 |                 "properties": {
125 |                     "table_name": {
126 |                         "type": "string",
127 |                         "description": "Name of the DynamoDB table"
128 |                     },
129 |                     "key_schema": {
130 |                         "type": "array",
131 |                         "description": "Key schema for table creation"
132 |                     },
133 |                     "attribute_definitions": {
134 |                         "type": "array",
135 |                         "description": "Attribute definitions for table creation"
136 |                     }
137 |                 },
138 |                 "required": ["table_name", "key_schema", "attribute_definitions"]
139 |             }
140 |         ),
141 |         Tool(
142 |             name="dynamodb_table_describe",
143 |             description="Get details about a DynamoDB table",
144 |             inputSchema={
145 |                 "type": "object",
146 |                 "properties": {
147 |                     "table_name": {
148 |                         "type": "string",
149 |                         "description": "Name of the DynamoDB table"
150 |                     }
151 |                 },
152 |                 "required": ["table_name"]
153 |             }
154 |         ),
155 |         Tool(
156 |             name="dynamodb_table_list",
157 |             description="List all DynamoDB tables",
158 |             inputSchema={
159 |                 "type": "object",
160 |                 "properties": {}
161 |             }
162 |         ),
163 |         Tool(
164 |             name="dynamodb_table_delete",
165 |             description="Delete a DynamoDB table",
166 |             inputSchema={
167 |                 "type": "object",
168 |                 "properties": {
169 |                     "table_name": {
170 |                         "type": "string",
171 |                         "description": "Name of the DynamoDB table"
172 |                     }
173 |                 },
174 |                 "required": ["table_name"]
175 |             }
176 |         ),
177 |         Tool(
178 |             name="dynamodb_table_update",
179 |             description="Update a DynamoDB table",
180 |             inputSchema={
181 |                 "type": "object",
182 |                 "properties": {
183 |                     "table_name": {
184 |                         "type": "string",
185 |                         "description": "Name of the DynamoDB table"
186 |                     },
187 |                     "attribute_definitions": {
188 |                         "type": "array",
189 |                         "description": "Updated attribute definitions"
190 |                     }
191 |                 },
192 |                 "required": ["table_name", "attribute_definitions"]
193 |             }
194 |         ),
195 |         Tool(
196 |             name="dynamodb_item_put",
197 |             description="Put an item into a DynamoDB table",
198 |             inputSchema={
199 |                 "type": "object",
200 |                 "properties": {
201 |                     "table_name": {
202 |                         "type": "string",
203 |                         "description": "Name of the DynamoDB table"
204 |                     },
205 |                     "item": {
206 |                         "type": "object",
207 |                         "description": "Item data to put"
208 |                     }
209 |                 },
210 |                 "required": ["table_name", "item"]
211 |             }
212 |         ),
213 |         Tool(
214 |             name="dynamodb_item_get",
215 |             description="Get an item from a DynamoDB table",
216 |             inputSchema={
217 |                 "type": "object",
218 |                 "properties": {
219 |                     "table_name": {
220 |                         "type": "string",
221 |                         "description": "Name of the DynamoDB table"
222 |                     },
223 |                     "key": {
224 |                         "type": "object",
225 |                         "description": "Key to identify the item"
226 |                     }
227 |                 },
228 |                 "required": ["table_name", "key"]
229 |             }
230 |         ),
231 |         Tool(
232 |             name="dynamodb_item_update",
233 |             description="Update an item in a DynamoDB table",
234 |             inputSchema={
235 |                 "type": "object",
236 |                 "properties": {
237 |                     "table_name": {
238 |                         "type": "string",
239 |                         "description": "Name of the DynamoDB table"
240 |                     },
241 |                     "key": {
242 |                         "type": "object",
243 |                         "description": "Key to identify the item"
244 |                     },
245 |                     "item": {
246 |                         "type": "object",
247 |                         "description": "Updated item data"
248 |                     }
249 |                 },
250 |                 "required": ["table_name", "key", "item"]
251 |             }
252 |         ),
253 |         Tool(
254 |             name="dynamodb_item_delete",
255 |             description="Delete an item from a DynamoDB table",
256 |             inputSchema={
257 |                 "type": "object",
258 |                 "properties": {
259 |                     "table_name": {
260 |                         "type": "string",
261 |                         "description": "Name of the DynamoDB table"
262 |                     },
263 |                     "key": {
264 |                         "type": "object",
265 |                         "description": "Key to identify the item"
266 |                     }
267 |                 },
268 |                 "required": ["table_name", "key"]
269 |             }
270 |         ),
271 |         Tool(
272 |             name="dynamodb_item_query",
273 |             description="Query items in a DynamoDB table",
274 |             inputSchema={
275 |                 "type": "object",
276 |                 "properties": {
277 |                     "table_name": {
278 |                         "type": "string",
279 |                         "description": "Name of the DynamoDB table"
280 |                     },
281 |                     "key_condition": {
282 |                         "type": "string",
283 |                         "description": "Key condition expression"
284 |                     },
285 |                     "expression_values": {
286 |                         "type": "object",
287 |                         "description": "Expression attribute values"
288 |                     }
289 |                 },
290 |                 "required": ["table_name", "key_condition", "expression_values"]
291 |             }
292 |         ),
293 |         Tool(
294 |             name="dynamodb_item_scan",
295 |             description="Scan items in a DynamoDB table",
296 |             inputSchema={
297 |                 "type": "object",
298 |                 "properties": {
299 |                     "table_name": {
300 |                         "type": "string",
301 |                         "description": "Name of the DynamoDB table"
302 |                     },
303 |                     "filter_expression": {
304 |                         "type": "string",
305 |                         "description": "Filter expression"
306 |                     },
307 |                     "expression_attributes": {
308 |                         "type": "object",
309 |                         "properties": {
310 |                             "values": {
311 |                                 "type": "object",
312 |                                 "description": "Expression attribute values"
313 |                             },
314 |                             "names": {
315 |                                 "type": "object",
316 |                                 "description": "Expression attribute names"
317 |                             }
318 |                         }
319 |                     }
320 |                 },
321 |                 "required": ["table_name"]
322 |             }
323 |         ),
324 |         Tool(
325 |             name="dynamodb_batch_get",
326 |             description="Batch get multiple items from DynamoDB tables",
327 |             inputSchema={
328 |                 "type": "object",
329 |                 "properties": {
330 |                     "request_items": {
331 |                         "type": "object",
332 |                         "description": "Map of table names to keys to retrieve",
333 |                         "additionalProperties": {
334 |                             "type": "object",
335 |                             "properties": {
336 |                                 "Keys": {
337 |                                     "type": "array",
338 |                                     "items": {
339 |                                         "type": "object"
340 |                                     }
341 |                                 },
342 |                                 "ConsistentRead": {
343 |                                     "type": "boolean"
344 |                                 },
345 |                                 "ProjectionExpression": {
346 |                                     "type": "string"
347 |                                 }
348 |                             },
349 |                             "required": ["Keys"]
350 |                         }
351 |                     }
352 |                 },
353 |                 "required": ["request_items"]
354 |             }
355 |         ),
356 |         Tool(
357 |             name="dynamodb_item_batch_write",
358 |             description="Batch write operations (put/delete) for DynamoDB items",
359 |             inputSchema={
360 |                 "type": "object",
361 |                 "properties": {
362 |                     "table_name": {
363 |                         "type": "string",
364 |                         "description": "Name of the DynamoDB table"
365 |                     },
366 |                     "operation": {
367 |                         "type": "string",
368 |                         "enum": ["put", "delete"],
369 |                         "description": "Type of batch operation (put or delete)"
370 |                     },
371 |                     "items": {
372 |                         "type": "array",
373 |                         "description": "Array of items to process"
374 |                     },
375 |                     "key_attributes": {
376 |                         "type": "array",
377 |                         "description": "For delete operations, specify which attributes form the key",
378 |                         "items": {
379 |                             "type": "string"
380 |                         }
381 |                     }
382 |                 },
383 |                 "required": ["table_name", "operation", "items"]
384 |             }
385 |         ),
386 |         Tool(
387 |             name="dynamodb_describe_ttl",
388 |             description="Get the TTL settings for a table",
389 |             inputSchema={
390 |                 "type": "object",
391 |                 "properties": {
392 |                     "table_name": {
393 |                         "type": "string",
394 |                         "description": "Name of the DynamoDB table"
395 |                     }
396 |                 },
397 |                 "required": ["table_name"]
398 |             }
399 |         ),
400 | 
401 |         Tool(
402 |             name="dynamodb_update_ttl",
403 |             description="Update the TTL settings for a table",
404 |             inputSchema={
405 |                 "type": "object",
406 |                 "properties": {
407 |                     "table_name": {
408 |                         "type": "string",
409 |                         "description": "Name of the DynamoDB table"
410 |                     },
411 |                     "ttl_enabled": {
412 |                         "type": "boolean",
413 |                         "description": "Whether TTL should be enabled"
414 |                     },
415 |                     "ttl_attribute": {
416 |                         "type": "string",
417 |                         "description": "The attribute name to use for TTL"
418 |                     }
419 |                 },
420 |                 "required": ["table_name", "ttl_enabled", "ttl_attribute"]
421 |             }
422 |         ),
423 |         Tool(
424 |             name="dynamodb_batch_execute",
425 |             description="Execute multiple PartiQL statements in a batch",
426 |             inputSchema={
427 |                 "type": "object",
428 |                 "properties": {
429 |                     "statements": {
430 |                         "type": "array",
431 |                         "description": "List of PartiQL statements to execute",
432 |                         "items": {
433 |                             "type": "string"
434 |                         }
435 |                     },
436 |                     "parameters": {
437 |                         "type": "array",
438 |                         "description": "List of parameter lists for each statement",
439 |                         "items": {
440 |                             "type": "array"
441 |                         }
442 |                     }
443 |                 },
444 |                 "required": ["statements", "parameters"]
445 |             }
446 |         ),
447 |     ]
448 | 
449 | 
450 | def get_aws_tools() -> list[Tool]:
451 |     return [
452 |         *get_s3_tools(),
453 |         *get_dynamodb_tools()
454 |     ]
455 | 
```