awslabs.dynamodb-mcp-server 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of awslabs.dynamodb-mcp-server might be problematic. Click here for more details.

@@ -0,0 +1,806 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import boto3
4
+ import json
5
+ import os
6
+ from awslabs.dynamodb_mcp_server.common import (
7
+ AttributeDefinition,
8
+ AttributeValue,
9
+ CreateTableInput,
10
+ DeleteItemInput,
11
+ GetItemInput,
12
+ GetResourcePolicyInput,
13
+ GlobalSecondaryIndex,
14
+ GlobalSecondaryIndexUpdate,
15
+ KeyAttributeValue,
16
+ KeySchemaElement,
17
+ OnDemandThroughput,
18
+ ProvisionedThroughput,
19
+ PutItemInput,
20
+ PutResourcePolicyInput,
21
+ QueryInput,
22
+ ReplicationGroupUpdate,
23
+ ScanInput,
24
+ Select,
25
+ SSESpecification,
26
+ StreamSpecification,
27
+ Tag,
28
+ TimeToLiveSpecification,
29
+ UpdateItemInput,
30
+ UpdateTableInput,
31
+ WarmThroughput,
32
+ handle_exceptions,
33
+ )
34
+ from botocore.config import Config
35
+ from fastmcp import FastMCP
36
+ from pydantic import Field
37
+ from typing import Any, Dict, List, Literal, Union
38
+
39
+
40
+ app = FastMCP(
41
+ name='dynamodb-server',
42
+ instructions='The official MCP Server for interacting with AWS DynamoDB',
43
+ version='0.1.0',
44
+ )
45
+
46
+
47
+ def get_dynamodb_client(region_name: str | None):
48
+ """Create a boto3 DynamoDB client using credentials from environment variables. Falls back to 'us-west-2' if no region is specified or found in environment."""
49
+ # Use provided region, or get from env, or fall back to us-west-2
50
+ region = region_name or os.getenv('AWS_REGION') or 'us-west-2'
51
+
52
+ # Configure custom user agent to identify requests from LLM/MCP
53
+ config = Config(user_agent_extra='MCP/DynamoDBServer')
54
+
55
+ # Create a new session to force credentials to reload
56
+ # so that if user changes credential, it will be reflected immediately in the next call
57
+ session = boto3.Session()
58
+
59
+ # boto3 will automatically load credentials from environment variables:
60
+ # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
61
+ return session.client('dynamodb', region_name=region, config=config)
62
+
63
+
64
+ table_name = Field(description='Table Name or Amazon Resource Name (ARN)')
65
+ index_name = Field(
66
+ default=None,
67
+ description='The name of a GSI',
68
+ )
69
+ key: Dict[str, KeyAttributeValue] = Field(description='The primary key of an item')
70
+ filter_expression: str = Field(
71
+ default=None,
72
+ description='Filter conditions expression that DynamoDB applies to filter out data',
73
+ )
74
+ projection_expression: str = Field(
75
+ default=None,
76
+ description='Attributes to retrieve, can include scalars, sets, or elements of a JSON document.',
77
+ )
78
+ expression_attribute_names: Dict[str, str] = Field(
79
+ default=None, description='Substitution tokens for attribute names in an expression.'
80
+ )
81
+ expression_attribute_values: Dict[str, AttributeValue] = Field(
82
+ default=None, description='Values that can be substituted in an expression'
83
+ )
84
+ select: Select = Field(
85
+ default=None,
86
+ description='The attributes to be returned. Valid values: ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, SPECIFIC_ATTRIBUTES, COUNT',
87
+ )
88
+ limit: int = Field(default=None, description='The maximum number of items to evaluate', ge=1)
89
+ exclusive_start_key: Dict[str, KeyAttributeValue] = Field(
90
+ default=None, description='Use the LastEvaluatedKey from the previous call.'
91
+ )
92
+
93
+ billing_mode: Literal['PROVISIONED', 'PAY_PER_REQUEST'] = Field(
94
+ default=None,
95
+ description='Specifies if billing is PAY_PER_REQUEST or by provisioned throughput',
96
+ )
97
+ resource_arn: str = Field(description='The Amazon Resource Name (ARN) of the DynamoDB resource')
98
+
99
+
100
+ @app.tool()
101
+ @handle_exceptions
102
+ async def put_resource_policy(
103
+ resource_arn: str = resource_arn,
104
+ policy: Union[str, Dict[str, Any]] = Field(
105
+ description='An AWS resource-based policy document in JSON format or dictionary.'
106
+ ),
107
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
108
+ ) -> dict:
109
+ """Attaches a resource-based policy document (max 20 KB) to a DynamoDB table or stream. You can control permissions for both tables and their indexes through the policy."""
110
+ client = get_dynamodb_client(region_name)
111
+ # Convert policy to string if it's a dictionary
112
+ policy_str = json.dumps(policy) if isinstance(policy, dict) else policy
113
+
114
+ params: PutResourcePolicyInput = {'ResourceArn': resource_arn, 'Policy': policy_str}
115
+
116
+ response = client.put_resource_policy(**params)
117
+ return {'RevisionId': response.get('RevisionId')}
118
+
119
+
120
+ @app.tool()
121
+ @handle_exceptions
122
+ async def get_resource_policy(
123
+ resource_arn: str = resource_arn,
124
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
125
+ ) -> dict:
126
+ """Returns the resource-based policy document attached to a DynamoDB table or stream in JSON format."""
127
+ client = get_dynamodb_client(region_name)
128
+ params: GetResourcePolicyInput = {'ResourceArn': resource_arn}
129
+
130
+ response = client.get_resource_policy(**params)
131
+ return {'Policy': response.get('Policy'), 'RevisionId': response.get('RevisionId')}
132
+
133
+
134
+ @app.tool()
135
+ @handle_exceptions
136
+ async def scan(
137
+ table_name: str = table_name,
138
+ index_name: str = index_name,
139
+ filter_expression: str = filter_expression,
140
+ projection_expression: str = projection_expression,
141
+ expression_attribute_names: Dict[str, str] = expression_attribute_names,
142
+ expression_attribute_values: Dict[str, AttributeValue] = expression_attribute_values,
143
+ select: Select = select,
144
+ limit: int = limit,
145
+ exclusive_start_key: Dict[str, KeyAttributeValue] = exclusive_start_key,
146
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
147
+ ) -> dict:
148
+ """Returns items and attributes by scanning a table or secondary index. Reads up to Limit items or 1 MB of data, with optional FilterExpression to reduce results."""
149
+ client = get_dynamodb_client(region_name)
150
+ params: ScanInput = {'TableName': table_name}
151
+
152
+ if index_name:
153
+ params['IndexName'] = index_name
154
+ if filter_expression:
155
+ params['FilterExpression'] = filter_expression
156
+ if projection_expression:
157
+ params['ProjectionExpression'] = projection_expression
158
+ if expression_attribute_names:
159
+ params['ExpressionAttributeNames'] = expression_attribute_names
160
+ if expression_attribute_values:
161
+ params['ExpressionAttributeValues'] = expression_attribute_values
162
+ if select:
163
+ params['Select'] = select
164
+ if limit:
165
+ params['Limit'] = limit
166
+ if exclusive_start_key:
167
+ params['ExclusiveStartKey'] = exclusive_start_key
168
+ params['ReturnConsumedCapacity'] = 'TOTAL'
169
+
170
+ response = client.scan(**params)
171
+ return {
172
+ 'Items': response.get('Items', []),
173
+ 'Count': response.get('Count'),
174
+ 'ScannedCount': response.get('ScannedCount'),
175
+ 'LastEvaluatedKey': response.get('LastEvaluatedKey'),
176
+ 'ConsumedCapacity': response.get('ConsumedCapacity'),
177
+ }
178
+
179
+
180
+ @app.tool()
181
+ @handle_exceptions
182
+ async def query(
183
+ table_name: str = table_name,
184
+ key_condition_expression: str = Field(
185
+ description='Key condition expression. Must perform an equality test on partition key value.'
186
+ ),
187
+ index_name: str = index_name,
188
+ filter_expression: str = filter_expression,
189
+ projection_expression: str = projection_expression,
190
+ expression_attribute_names: Dict[str, str] = expression_attribute_names,
191
+ expression_attribute_values: Dict[str, AttributeValue] = expression_attribute_values,
192
+ select: Select = select,
193
+ limit: int = limit,
194
+ scan_index_forward: bool = Field(
195
+ default=None, description='Ascending (true) or descending (false).'
196
+ ),
197
+ exclusive_start_key: Dict[str, KeyAttributeValue] = exclusive_start_key,
198
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
199
+ ) -> dict:
200
+ """Returns items from a table or index matching a partition key value, with optional sort key filtering."""
201
+ client = get_dynamodb_client(region_name)
202
+ params: QueryInput = {
203
+ 'TableName': table_name,
204
+ 'KeyConditionExpression': key_condition_expression,
205
+ }
206
+
207
+ if index_name:
208
+ params['IndexName'] = index_name
209
+ if filter_expression:
210
+ params['FilterExpression'] = filter_expression
211
+ if projection_expression:
212
+ params['ProjectionExpression'] = projection_expression
213
+ if expression_attribute_names:
214
+ params['ExpressionAttributeNames'] = expression_attribute_names
215
+ if expression_attribute_values:
216
+ params['ExpressionAttributeValues'] = expression_attribute_values
217
+ if select:
218
+ params['Select'] = select
219
+ if limit:
220
+ params['Limit'] = limit
221
+ if scan_index_forward is not None:
222
+ params['ScanIndexForward'] = scan_index_forward
223
+ if exclusive_start_key:
224
+ params['ExclusiveStartKey'] = exclusive_start_key
225
+ params['ReturnConsumedCapacity'] = 'TOTAL'
226
+
227
+ response = client.query(**params)
228
+ return {
229
+ 'Items': response.get('Items', []),
230
+ 'Count': response.get('Count'),
231
+ 'ScannedCount': response.get('ScannedCount'),
232
+ 'LastEvaluatedKey': response.get('LastEvaluatedKey'),
233
+ 'ConsumedCapacity': response.get('ConsumedCapacity'),
234
+ }
235
+
236
+
237
+ @app.tool()
238
+ @handle_exceptions
239
+ async def update_item(
240
+ table_name: str = table_name,
241
+ key: Dict[str, KeyAttributeValue] = key,
242
+ update_expression: str = Field(
243
+ default=None,
244
+ description="""Defines the attributes to be updated, the action to be performed on them, and new value(s) for them. The following actions are available:
245
+ * SET - Adds one or more attributes and values to an item. If any of these attributes already exist, they are replaced by the new values.
246
+ * REMOVE - Removes one or more attributes from an item.
247
+ * ADD - Only supports Number and Set data types. Adds a value to a number attribute or adds elements to a set.
248
+ * DELETE - Only supports Set data type. Removes elements from a set.
249
+ For example: 'SET a=:value1, b=:value2 DELETE :value3, :value4, :value5'""",
250
+ ),
251
+ condition_expression: str = Field(
252
+ default=None,
253
+ description='A condition that must be satisfied in order for a conditional update to succeed.',
254
+ ),
255
+ expression_attribute_names: Dict[str, str] = expression_attribute_names,
256
+ expression_attribute_values: Dict[str, AttributeValue] = expression_attribute_values,
257
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
258
+ ) -> dict:
259
+ """Edits an existing item's attributes, or adds a new item to the table if it does not already exist."""
260
+ client = get_dynamodb_client(region_name)
261
+ params: UpdateItemInput = {'TableName': table_name, 'Key': key}
262
+
263
+ if update_expression:
264
+ params['UpdateExpression'] = update_expression
265
+ if condition_expression:
266
+ params['ConditionExpression'] = condition_expression
267
+ if expression_attribute_names:
268
+ params['ExpressionAttributeNames'] = expression_attribute_names
269
+ if expression_attribute_values:
270
+ params['ExpressionAttributeValues'] = expression_attribute_values
271
+ params['ReturnConsumedCapacity'] = 'TOTAL'
272
+ params['ReturnValuesOnConditionCheckFailure'] = 'ALL_OLD'
273
+
274
+ response = client.update_item(**params)
275
+ return {
276
+ 'Attributes': response.get('Attributes'),
277
+ 'ConsumedCapacity': response.get('ConsumedCapacity'),
278
+ }
279
+
280
+
281
+ @app.tool()
282
+ @handle_exceptions
283
+ async def get_item(
284
+ table_name: str = table_name,
285
+ key: Dict[str, KeyAttributeValue] = key,
286
+ expression_attribute_names: Dict[str, str] = expression_attribute_names,
287
+ projection_expression: str = projection_expression,
288
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
289
+ ) -> dict:
290
+ """Returns attributes for an item with the given primary key. Uses eventually consistent reads by default, or set ConsistentRead=true for strongly consistent reads."""
291
+ client = get_dynamodb_client(region_name)
292
+ params: GetItemInput = {'TableName': table_name, 'Key': key}
293
+
294
+ if expression_attribute_names:
295
+ params['ExpressionAttributeNames'] = expression_attribute_names
296
+ if projection_expression:
297
+ params['ProjectionExpression'] = projection_expression
298
+ params['ReturnConsumedCapacity'] = 'TOTAL'
299
+
300
+ response = client.get_item(**params)
301
+ return {'Item': response.get('Item'), 'ConsumedCapacity': response.get('ConsumedCapacity')}
302
+
303
+
304
+ @app.tool()
305
+ @handle_exceptions
306
+ async def put_item(
307
+ table_name: str = table_name,
308
+ item: Dict[str, AttributeValue] = Field(
309
+ description='A map of attribute name/value pairs, one for each attribute.'
310
+ ),
311
+ condition_expression: str = Field(
312
+ default=None,
313
+ description='A condition that must be satisfied in order for a conditional put operation to succeed.',
314
+ ),
315
+ expression_attribute_names: Dict[str, str] = expression_attribute_names,
316
+ expression_attribute_values: Dict[str, Any] = expression_attribute_values,
317
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
318
+ ) -> dict:
319
+ """Creates a new item or replaces an existing item in a table. Use condition expressions to control whether to create new items or update existing ones."""
320
+ client = get_dynamodb_client(region_name)
321
+ params: PutItemInput = {'TableName': table_name, 'Item': item}
322
+
323
+ if condition_expression:
324
+ params['ConditionExpression'] = condition_expression
325
+ if expression_attribute_names:
326
+ params['ExpressionAttributeNames'] = expression_attribute_names
327
+ if expression_attribute_values:
328
+ params['ExpressionAttributeValues'] = expression_attribute_values
329
+ params['ReturnConsumedCapacity'] = 'TOTAL'
330
+
331
+ response = client.put_item(**params)
332
+ return {
333
+ 'Attributes': response.get('Attributes'),
334
+ 'ConsumedCapacity': response.get('ConsumedCapacity'),
335
+ }
336
+
337
+
338
+ @app.tool()
339
+ @handle_exceptions
340
+ async def delete_item(
341
+ table_name: str = table_name,
342
+ key: Dict[str, KeyAttributeValue] = key,
343
+ condition_expression: str = Field(
344
+ default=None,
345
+ description='The condition that must be satisfied in order for delete to succeed.',
346
+ ),
347
+ expression_attribute_names: Dict[str, str] = expression_attribute_names,
348
+ expression_attribute_values: Dict[str, AttributeValue] = expression_attribute_values,
349
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
350
+ ) -> dict:
351
+ """Deletes a single item in a table by primary key. You can perform a conditional delete operation that deletes the item if it exists, or if it has an expected attribute value."""
352
+ client = get_dynamodb_client(region_name)
353
+ params: DeleteItemInput = {'TableName': table_name, 'Key': key}
354
+
355
+ if condition_expression:
356
+ params['ConditionExpression'] = condition_expression
357
+ if expression_attribute_names:
358
+ params['ExpressionAttributeNames'] = expression_attribute_names
359
+ if expression_attribute_values:
360
+ params['ExpressionAttributeValues'] = expression_attribute_values
361
+ params['ReturnConsumedCapacity'] = 'TOTAL'
362
+
363
+ response = client.delete_item(**params)
364
+ return {
365
+ 'Attributes': response.get('Attributes'),
366
+ 'ConsumedCapacity': response.get('ConsumedCapacity'),
367
+ 'ItemCollectionMetrics': response.get('ItemCollectionMetrics'),
368
+ }
369
+
370
+
371
+ @app.tool()
372
+ @handle_exceptions
373
+ async def update_time_to_live(
374
+ table_name: str = table_name,
375
+ time_to_live_specification: TimeToLiveSpecification = Field(
376
+ description='The new TTL settings'
377
+ ),
378
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
379
+ ) -> dict:
380
+ """Enables or disables Time to Live (TTL) for the specified table. Note: The epoch time format is the number of seconds elapsed since 12:00:00 AM January 1, 1970 UTC."""
381
+ client = get_dynamodb_client(region_name)
382
+ response = client.update_time_to_live(
383
+ TableName=table_name, TimeToLiveSpecification=time_to_live_specification
384
+ )
385
+ return response['TimeToLiveSpecification']
386
+
387
+
388
+ @app.tool()
389
+ @handle_exceptions
390
+ async def update_table(
391
+ table_name: str = table_name,
392
+ attribute_definitions: List[AttributeDefinition] = Field(
393
+ default=None,
394
+ description='Describe the key schema for the table and indexes. Required when adding a new GSI.',
395
+ ),
396
+ billing_mode: Literal['PROVISIONED', 'PAY_PER_REQUEST'] = billing_mode,
397
+ deletion_protection_enabled: bool = Field(
398
+ default=None, description='Indicates whether deletion protection is to be enabled'
399
+ ),
400
+ global_secondary_index_updates: List[GlobalSecondaryIndexUpdate] = Field(
401
+ default=None, description='List of GSIs to be added, updated or deleted.'
402
+ ),
403
+ on_demand_throughput: OnDemandThroughput = Field(
404
+ default=None, description='Set the max number of read and write units.'
405
+ ),
406
+ provisioned_throughput: ProvisionedThroughput = Field(
407
+ default=None, description='The new provisioned throughput settings.'
408
+ ),
409
+ replica_updates: List[ReplicationGroupUpdate] = Field(
410
+ default=None, description='A list of replica update actions (create, delete, or update).'
411
+ ),
412
+ sse_specification: SSESpecification = Field(
413
+ default=None, description='The new server-side encryption settings.'
414
+ ),
415
+ stream_specification: StreamSpecification = Field(
416
+ default=None, description='DynamoDB Streams configuration.'
417
+ ),
418
+ table_class: Literal['STANDARD', 'STANDARD_INFREQUENT_ACCESS'] = Field(
419
+ default=None, description='The new table class.'
420
+ ),
421
+ warm_throughput: WarmThroughput = Field(
422
+ default=None, description='The new warm throughput settings.'
423
+ ),
424
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
425
+ ) -> dict:
426
+ """Modifies table settings including provisioned throughput, global secondary indexes, and DynamoDB Streams configuration. This is an asynchronous operation."""
427
+ client = get_dynamodb_client(region_name)
428
+ params: UpdateTableInput = {'TableName': table_name}
429
+
430
+ if attribute_definitions:
431
+ params['AttributeDefinitions'] = attribute_definitions
432
+ if billing_mode:
433
+ params['BillingMode'] = billing_mode
434
+ if deletion_protection_enabled is not None:
435
+ params['DeletionProtectionEnabled'] = deletion_protection_enabled
436
+ if global_secondary_index_updates:
437
+ params['GlobalSecondaryIndexUpdates'] = global_secondary_index_updates
438
+ if on_demand_throughput:
439
+ params['OnDemandThroughput'] = on_demand_throughput
440
+ if provisioned_throughput:
441
+ params['ProvisionedThroughput'] = provisioned_throughput
442
+ if replica_updates:
443
+ params['ReplicaUpdates'] = replica_updates
444
+ if sse_specification:
445
+ params['SSESpecification'] = sse_specification
446
+ if stream_specification:
447
+ params['StreamSpecification'] = stream_specification
448
+ if table_class:
449
+ params['TableClass'] = table_class
450
+ if warm_throughput:
451
+ params['WarmThroughput'] = warm_throughput
452
+
453
+ response = client.update_table(**params)
454
+ return response['TableDescription']
455
+
456
+
457
+ @app.tool()
458
+ @handle_exceptions
459
+ async def list_tables(
460
+ exclusive_start_table_name: str = Field(
461
+ default=None,
462
+ description='The LastEvaluatedTableName value from the previous paginated call',
463
+ ),
464
+ limit: int = Field(
465
+ default=None,
466
+ description='Max number of table names to return',
467
+ ),
468
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
469
+ ) -> dict:
470
+ """Returns a paginated list of table names in your account."""
471
+ client = get_dynamodb_client(region_name)
472
+ params = {}
473
+ if exclusive_start_table_name:
474
+ params['ExclusiveStartTableName'] = exclusive_start_table_name
475
+ if limit:
476
+ params['Limit'] = limit
477
+ response = client.list_tables(**params)
478
+ return {
479
+ 'TableNames': response['TableNames'],
480
+ 'LastEvaluatedTableName': response.get('LastEvaluatedTableName'),
481
+ }
482
+
483
+
484
+ @app.tool()
485
+ @handle_exceptions
486
+ async def create_table(
487
+ table_name: str = Field(
488
+ description='The name of the table to create.',
489
+ ),
490
+ attribute_definitions: List[AttributeDefinition] = Field(
491
+ description='Describe the key schema for the table and indexes.'
492
+ ),
493
+ key_schema: List[KeySchemaElement] = Field(
494
+ description='Specifies primary key attributes of the table.'
495
+ ),
496
+ billing_mode: Literal['PROVISIONED', 'PAY_PER_REQUEST'] = billing_mode,
497
+ global_secondary_indexes: List[GlobalSecondaryIndex] = Field(
498
+ default=None, description='GSIs to be created on the table.'
499
+ ),
500
+ provisioned_throughput: ProvisionedThroughput = Field(
501
+ default=None,
502
+ description='Provisioned throughput settings. Required if BillingMode is PROVISIONED.',
503
+ ),
504
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
505
+ ) -> dict:
506
+ """Creates a new DynamoDB table with optional secondary indexes. This is an asynchronous operation."""
507
+ client = get_dynamodb_client(region_name)
508
+ params: CreateTableInput = {
509
+ 'TableName': table_name,
510
+ 'AttributeDefinitions': attribute_definitions,
511
+ 'KeySchema': key_schema,
512
+ }
513
+
514
+ if billing_mode:
515
+ params['BillingMode'] = billing_mode
516
+ if global_secondary_indexes:
517
+ params['GlobalSecondaryIndexes'] = global_secondary_indexes
518
+ if provisioned_throughput:
519
+ params['ProvisionedThroughput'] = provisioned_throughput
520
+
521
+ response = client.create_table(**params)
522
+ return response['TableDescription']
523
+
524
+
525
+ @app.tool()
526
+ @handle_exceptions
527
+ async def describe_table(
528
+ table_name: str = table_name,
529
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
530
+ ) -> dict:
531
+ """Returns table information including status, creation time, key schema and indexes."""
532
+ client = get_dynamodb_client(region_name)
533
+ response = client.describe_table(TableName=table_name)
534
+ return response['Table']
535
+
536
+
537
+ @app.tool()
538
+ @handle_exceptions
539
+ async def create_backup(
540
+ table_name: str = table_name,
541
+ backup_name: str = Field(
542
+ description='Specified name for the backup.',
543
+ ),
544
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
545
+ ) -> dict:
546
+ """Creates a backup of a DynamoDB table."""
547
+ client = get_dynamodb_client(region_name)
548
+ response = client.create_backup(TableName=table_name, BackupName=backup_name)
549
+ return response['BackupDetails']
550
+
551
+
552
+ @app.tool()
553
+ @handle_exceptions
554
+ async def describe_backup(
555
+ backup_arn: str = Field(
556
+ description='The Amazon Resource Name (ARN) associated with the backup.',
557
+ ),
558
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
559
+ ) -> dict:
560
+ """Describes an existing backup of a table."""
561
+ client = get_dynamodb_client(region_name)
562
+ response = client.describe_backup(BackupArn=backup_arn)
563
+ return response['BackupDescription']
564
+
565
+
566
+ @app.tool()
567
+ @handle_exceptions
568
+ async def list_backups(
569
+ table_name: str = table_name,
570
+ backup_type: str = Field(
571
+ default=None,
572
+ description='Filter by backup type: USER (on-demand backup created by you), SYSTEM (automatically created by DynamoDB), AWS_BACKUP (created by AWS Backup), or ALL (all types).',
573
+ pattern='^(USER|SYSTEM|AWS_BACKUP|ALL)$',
574
+ ),
575
+ exclusive_start_backup_arn: str = Field(
576
+ default=None,
577
+ description='LastEvaluatedBackupArn from a previous paginated call.',
578
+ ),
579
+ limit: int = Field(
580
+ default=None, description='Maximum number of backups to return.', ge=1, le=100
581
+ ),
582
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
583
+ ) -> dict:
584
+ """Returns a list of table backups."""
585
+ client = get_dynamodb_client(region_name)
586
+ params = {}
587
+ if backup_type:
588
+ params['BackupType'] = backup_type
589
+ if exclusive_start_backup_arn:
590
+ params['ExclusiveStartBackupArn'] = exclusive_start_backup_arn
591
+ if limit:
592
+ params['Limit'] = limit
593
+ if table_name:
594
+ params['TableName'] = table_name
595
+
596
+ response = client.list_backups(**params)
597
+ return {
598
+ 'BackupSummaries': response.get('BackupSummaries', []),
599
+ 'LastEvaluatedBackupArn': response.get('LastEvaluatedBackupArn'),
600
+ }
601
+
602
+
603
+ @app.tool()
604
+ @handle_exceptions
605
+ async def restore_table_from_backup(
606
+ backup_arn: str = Field(
607
+ description='The Amazon Resource Name (ARN) associated with the backup.',
608
+ ),
609
+ target_table_name: str = Field(
610
+ description='The name of the new table.',
611
+ ),
612
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
613
+ ) -> dict:
614
+ """Creates a new table from a backup."""
615
+ client = get_dynamodb_client(region_name)
616
+ params = {'BackupArn': backup_arn, 'TargetTableName': target_table_name}
617
+
618
+ response = client.restore_table_from_backup(**params)
619
+ return response['TableDescription']
620
+
621
+
622
+ @app.tool()
623
+ @handle_exceptions
624
+ async def describe_limits(
625
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
626
+ ) -> dict:
627
+ """Returns the current provisioned-capacity quotas for your AWS account and tables in a Region."""
628
+ client = get_dynamodb_client(region_name)
629
+ response = client.describe_limits()
630
+ return {
631
+ 'AccountMaxReadCapacityUnits': response['AccountMaxReadCapacityUnits'],
632
+ 'AccountMaxWriteCapacityUnits': response['AccountMaxWriteCapacityUnits'],
633
+ 'TableMaxReadCapacityUnits': response['TableMaxReadCapacityUnits'],
634
+ 'TableMaxWriteCapacityUnits': response['TableMaxWriteCapacityUnits'],
635
+ }
636
+
637
+
638
+ @app.tool()
639
+ @handle_exceptions
640
+ async def describe_time_to_live(
641
+ table_name: str = table_name,
642
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
643
+ ) -> dict:
644
+ """Returns the Time to Live (TTL) settings for a table."""
645
+ client = get_dynamodb_client(region_name)
646
+ response = client.describe_time_to_live(TableName=table_name)
647
+ return response['TimeToLiveDescription']
648
+
649
+
650
+ @app.tool()
651
+ @handle_exceptions
652
+ async def describe_endpoints(
653
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
654
+ ) -> dict:
655
+ """Returns DynamoDB endpoints for the current region."""
656
+ client = get_dynamodb_client(region_name)
657
+ response = client.describe_endpoints()
658
+ return {'Endpoints': response['Endpoints']}
659
+
660
+
661
+ @app.tool()
662
+ @handle_exceptions
663
+ async def describe_export(
664
+ export_arn: str = Field(
665
+ description='The Amazon Resource Name (ARN) associated with the export.',
666
+ ),
667
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
668
+ ) -> dict:
669
+ """Returns information about a table export."""
670
+ client = get_dynamodb_client(region_name)
671
+ response = client.describe_export(ExportArn=export_arn)
672
+ return response['ExportDescription']
673
+
674
+
675
+ @app.tool()
676
+ @handle_exceptions
677
+ async def list_exports(
678
+ max_results: int = Field(
679
+ default=None,
680
+ description='Maximum number of results to return per page.',
681
+ ),
682
+ next_token: str = Field(default=None, description='Token to fetch the next page of results.'),
683
+ table_arn: str = Field(
684
+ default=None,
685
+ description='The Amazon Resource Name (ARN) associated with the exported table.',
686
+ ),
687
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
688
+ ) -> dict:
689
+ """Returns a list of table exports."""
690
+ client = get_dynamodb_client(region_name)
691
+ params = {}
692
+ if max_results:
693
+ params['MaxResults'] = max_results
694
+ if next_token:
695
+ params['NextToken'] = next_token
696
+ if table_arn:
697
+ params['TableArn'] = table_arn
698
+
699
+ response = client.list_exports(**params)
700
+ return {
701
+ 'ExportSummaries': response.get('ExportSummaries', []),
702
+ 'NextToken': response.get('NextToken'),
703
+ }
704
+
705
+
706
+ @app.tool()
707
+ @handle_exceptions
708
+ async def describe_continuous_backups(
709
+ table_name: str = table_name,
710
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
711
+ ) -> dict:
712
+ """Returns continuous backup and point in time recovery status for a table."""
713
+ client = get_dynamodb_client(region_name)
714
+ response = client.describe_continuous_backups(TableName=table_name)
715
+ return response['ContinuousBackupsDescription']
716
+
717
+
718
+ @app.tool()
719
+ @handle_exceptions
720
+ async def untag_resource(
721
+ resource_arn: str = resource_arn,
722
+ tag_keys: List[str] = Field(description='List of tags to remove.', min_length=1),
723
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
724
+ ) -> dict:
725
+ """Removes tags from a DynamoDB resource."""
726
+ client = get_dynamodb_client(region_name)
727
+ response = client.untag_resource(ResourceArn=resource_arn, TagKeys=tag_keys)
728
+ return response
729
+
730
+
731
+ @app.tool()
732
+ @handle_exceptions
733
+ async def tag_resource(
734
+ resource_arn: str = resource_arn,
735
+ tags: List[Tag] = Field(description='Tags to be assigned.'),
736
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
737
+ ) -> dict:
738
+ """Adds tags to a DynamoDB resource."""
739
+ client = get_dynamodb_client(region_name)
740
+ response = client.tag_resource(ResourceArn=resource_arn, Tags=tags)
741
+ return response
742
+
743
+
744
+ @app.tool()
745
+ @handle_exceptions
746
+ async def list_tags_of_resource(
747
+ resource_arn: str = resource_arn,
748
+ next_token: str = Field(
749
+ default=None, description='The NextToken from the previous paginated call'
750
+ ),
751
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
752
+ ) -> dict:
753
+ """Returns tags for a DynamoDB resource."""
754
+ client = get_dynamodb_client(region_name)
755
+ params = {'ResourceArn': resource_arn}
756
+ if next_token:
757
+ params['NextToken'] = next_token
758
+
759
+ response = client.list_tags_of_resource(**params)
760
+ return {'Tags': response.get('Tags', []), 'NextToken': response.get('NextToken')}
761
+
762
+
763
+ @app.tool()
764
+ @handle_exceptions
765
+ async def delete_table(
766
+ table_name: str = table_name,
767
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
768
+ ) -> dict:
769
+ """The DeleteTable operation deletes a table and all of its items. This is an asynchronous operation that puts the table into DELETING state until DynamoDB completes the deletion."""
770
+ client = get_dynamodb_client(region_name)
771
+ response = client.delete_table(TableName=table_name)
772
+ return response['TableDescription']
773
+
774
+
775
+ @app.tool()
776
+ @handle_exceptions
777
+ async def update_continuous_backups(
778
+ table_name: str = table_name,
779
+ point_in_time_recovery_enabled: bool = Field(
780
+ description='Enable or disable point in time recovery.'
781
+ ),
782
+ recovery_period_in_days: int = Field(
783
+ default=None,
784
+ description='Number of days to retain point in time recovery backups.',
785
+ ),
786
+ region_name: str = Field(default=None, description='The aws region to run the tool'),
787
+ ) -> dict:
788
+ """Enables or disables point in time recovery for the specified table."""
789
+ client = get_dynamodb_client(region_name)
790
+ params = {
791
+ 'TableName': table_name,
792
+ 'PointInTimeRecoverySpecification': {
793
+ 'PointInTimeRecoveryEnabled': point_in_time_recovery_enabled
794
+ },
795
+ }
796
+ if recovery_period_in_days:
797
+ params['PointInTimeRecoverySpecification']['RecoveryPeriodInDays'] = (
798
+ recovery_period_in_days
799
+ )
800
+
801
+ response = client.update_continuous_backups(**params)
802
+ return response['ContinuousBackupsDescription']
803
+
804
+
805
+ if __name__ == '__main__':
806
+ app.run()