awslabs.s3-tables-mcp-server 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,274 @@
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
+ """AWS S3 Tables MCP Server models."""
16
+
17
+ from awslabs.s3_tables_mcp_server.constants import (
18
+ TABLE_ARN_PATTERN,
19
+ TABLE_BUCKET_ARN_PATTERN,
20
+ TABLE_BUCKET_NAME_PATTERN,
21
+ TABLE_NAME_PATTERN,
22
+ )
23
+ from datetime import datetime
24
+ from enum import Enum
25
+ from pydantic import BaseModel, Field, model_validator
26
+ from typing import List, Optional, Union
27
+
28
+
29
+ # Enums
30
+ class OpenTableFormat(str, Enum):
31
+ """Supported open table formats."""
32
+
33
+ ICEBERG = 'ICEBERG'
34
+
35
+
36
+ class TableBucketType(str, Enum):
37
+ """Table bucket type."""
38
+
39
+ CUSTOMER = 'customer'
40
+ AWS = 'aws'
41
+
42
+
43
+ class TableType(str, Enum):
44
+ """Table type."""
45
+
46
+ CUSTOMER = 'customer'
47
+ AWS = 'aws'
48
+
49
+
50
+ class MaintenanceStatus(str, Enum):
51
+ """Maintenance status."""
52
+
53
+ ENABLED = 'enabled'
54
+ DISABLED = 'disabled'
55
+
56
+
57
+ class JobStatus(str, Enum):
58
+ """Job status."""
59
+
60
+ NOT_YET_RUN = 'Not_Yet_Run'
61
+ SUCCESSFUL = 'Successful'
62
+ FAILED = 'Failed'
63
+ DISABLED = 'Disabled'
64
+
65
+
66
+ class TableBucketMaintenanceType(str, Enum):
67
+ """Table bucket maintenance type."""
68
+
69
+ ICEBERG_UNREFERENCED_FILE_REMOVAL = 'icebergUnreferencedFileRemoval'
70
+
71
+
72
+ class TableMaintenanceType(str, Enum):
73
+ """Table maintenance type."""
74
+
75
+ ICEBERG_COMPACTION = 'icebergCompaction'
76
+ ICEBERG_SNAPSHOT_MANAGEMENT = 'icebergSnapshotManagement'
77
+
78
+
79
+ class TableMaintenanceJobType(str, Enum):
80
+ """Table maintenance job type."""
81
+
82
+ ICEBERG_COMPACTION = 'icebergCompaction'
83
+ ICEBERG_SNAPSHOT_MANAGEMENT = 'icebergSnapshotManagement'
84
+ ICEBERG_UNREFERENCED_FILE_REMOVAL = 'icebergUnreferencedFileRemoval'
85
+
86
+
87
+ # Core Models
88
+ class TableBucketSummary(BaseModel):
89
+ """Table bucket summary."""
90
+
91
+ arn: str = Field(pattern=TABLE_BUCKET_ARN_PATTERN)
92
+ name: str = Field(min_length=3, max_length=63, pattern=TABLE_BUCKET_NAME_PATTERN)
93
+ owner_account_id: str = Field(min_length=12, max_length=12, pattern=r'[0-9].*')
94
+ created_at: datetime
95
+ table_bucket_id: Optional[str] = None
96
+ type: Optional[TableBucketType] = None
97
+
98
+
99
+ class TableBucket(BaseModel):
100
+ """Complete table bucket information."""
101
+
102
+ arn: str = Field(pattern=TABLE_BUCKET_ARN_PATTERN)
103
+ name: str = Field(min_length=3, max_length=63, pattern=TABLE_BUCKET_NAME_PATTERN)
104
+ owner_account_id: str = Field(min_length=12, max_length=12, pattern=r'[0-9].*')
105
+ created_at: datetime
106
+ table_bucket_id: Optional[str] = None
107
+ type: Optional[TableBucketType] = None
108
+
109
+
110
+ class NamespaceSummary(BaseModel):
111
+ """Namespace summary."""
112
+
113
+ namespace: List[str]
114
+ created_at: datetime
115
+ created_by: str = Field(min_length=12, max_length=12, pattern=r'[0-9].*')
116
+ owner_account_id: str = Field(min_length=12, max_length=12, pattern=r'[0-9].*')
117
+ namespace_id: Optional[str] = None
118
+ table_bucket_id: Optional[str] = None
119
+
120
+
121
+ class TableSummary(BaseModel):
122
+ """Table summary."""
123
+
124
+ namespace: List[str]
125
+ name: str = Field(min_length=1, max_length=255, pattern=TABLE_NAME_PATTERN)
126
+ type: TableType
127
+ table_arn: str = Field(pattern=TABLE_ARN_PATTERN)
128
+ created_at: datetime
129
+ modified_at: datetime
130
+ namespace_id: Optional[str] = None
131
+ table_bucket_id: Optional[str] = None
132
+
133
+
134
+ class Table(BaseModel):
135
+ """Complete table information."""
136
+
137
+ name: str = Field(min_length=1, max_length=255, pattern=TABLE_NAME_PATTERN)
138
+ type: TableType
139
+ table_arn: str = Field(pattern=TABLE_ARN_PATTERN, alias='tableARN')
140
+ namespace: List[str]
141
+ namespace_id: Optional[str] = None
142
+ version_token: str = Field(min_length=1, max_length=2048)
143
+ metadata_location: Optional[str] = Field(None, min_length=1, max_length=2048)
144
+ warehouse_location: str = Field(min_length=1, max_length=2048)
145
+ created_at: datetime
146
+ created_by: str = Field(min_length=12, max_length=12, pattern=r'[0-9].*')
147
+ managed_by_service: Optional[str] = None
148
+ modified_at: datetime
149
+ modified_by: str = Field(min_length=12, max_length=12, pattern=r'[0-9].*')
150
+ owner_account_id: str = Field(min_length=12, max_length=12, pattern=r'[0-9].*')
151
+ format: OpenTableFormat
152
+ table_bucket_id: Optional[str] = None
153
+
154
+
155
+ # Maintenance Models
156
+ class IcebergCompactionSettings(BaseModel):
157
+ """Settings for Iceberg compaction."""
158
+
159
+ target_file_size_mb: Optional[int] = Field(None, ge=1, le=2147483647)
160
+
161
+
162
+ class IcebergSnapshotManagementSettings(BaseModel):
163
+ """Settings for Iceberg snapshot management."""
164
+
165
+ min_snapshots_to_keep: Optional[int] = Field(None, ge=1, le=2147483647)
166
+ max_snapshot_age_hours: Optional[int] = Field(None, ge=1, le=2147483647)
167
+
168
+
169
+ class TableMaintenanceJobStatusValue(BaseModel):
170
+ """Table maintenance job status value."""
171
+
172
+ status: JobStatus
173
+ last_run_timestamp: Optional[datetime] = None
174
+ failure_message: Optional[str] = None
175
+
176
+
177
+ class TableMaintenanceConfigurationValue(BaseModel):
178
+ """Table maintenance configuration value."""
179
+
180
+ status: Optional[MaintenanceStatus] = None
181
+ settings: Optional[Union[IcebergCompactionSettings, IcebergSnapshotManagementSettings]] = None
182
+
183
+
184
+ class IcebergUnreferencedFileRemovalSettings(BaseModel):
185
+ """Settings for unreferenced file removal."""
186
+
187
+ unreferenced_days: Optional[int] = Field(None, ge=1, le=2147483647)
188
+ non_current_days: Optional[int] = Field(None, ge=1, le=2147483647)
189
+
190
+
191
+ class TableBucketMaintenanceSettings(BaseModel):
192
+ """Contains details about the maintenance settings for the table bucket."""
193
+
194
+ iceberg_unreferenced_file_removal: Optional[IcebergUnreferencedFileRemovalSettings] = Field(
195
+ None, description='Settings for unreferenced file removal.'
196
+ )
197
+
198
+ @model_validator(mode='after')
199
+ def validate_only_one_setting(self) -> 'TableBucketMaintenanceSettings':
200
+ """Validate that only one setting is specified."""
201
+ settings = [self.iceberg_unreferenced_file_removal]
202
+ if sum(1 for s in settings if s is not None) > 1:
203
+ raise ValueError('Only one maintenance setting can be specified')
204
+ return self
205
+
206
+
207
+ class TableBucketMaintenanceConfigurationValue(BaseModel):
208
+ """Details about the values that define the maintenance configuration for a table bucket."""
209
+
210
+ settings: Optional[TableBucketMaintenanceSettings] = Field(
211
+ None, description='Contains details about the settings of the maintenance configuration.'
212
+ )
213
+ status: Optional[MaintenanceStatus] = Field(
214
+ None, description='The status of the maintenance configuration.'
215
+ )
216
+
217
+
218
+ # Resource Models
219
+ class TableBucketsResource(BaseModel):
220
+ """Resource containing all table buckets."""
221
+
222
+ table_buckets: List[TableBucketSummary]
223
+ total_count: int
224
+
225
+
226
+ class NamespacesResource(BaseModel):
227
+ """Resource containing all namespaces."""
228
+
229
+ namespaces: List[NamespaceSummary]
230
+ total_count: int
231
+
232
+
233
+ class TablesResource(BaseModel):
234
+ """Resource containing all tables."""
235
+
236
+ tables: List[TableSummary]
237
+ total_count: int
238
+
239
+
240
+ # Error Models
241
+ class S3TablesError(BaseModel):
242
+ """S3 Tables error response."""
243
+
244
+ error_code: str
245
+ error_message: str
246
+ request_id: Optional[str] = None
247
+ resource_name: Optional[str] = None
248
+
249
+
250
+ # Schema Models
251
+ class SchemaField(BaseModel):
252
+ """Iceberg schema field."""
253
+
254
+ name: str
255
+ type: str
256
+ required: Optional[bool] = None
257
+
258
+
259
+ class IcebergSchema(BaseModel):
260
+ """Iceberg table schema."""
261
+
262
+ fields: List[SchemaField]
263
+
264
+
265
+ class IcebergMetadata(BaseModel):
266
+ """Iceberg table metadata."""
267
+
268
+ table_schema: IcebergSchema = Field(alias='schema')
269
+
270
+
271
+ class TableMetadata(BaseModel):
272
+ """Table metadata union."""
273
+
274
+ iceberg: Optional[IcebergMetadata] = None
@@ -0,0 +1,63 @@
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
+ """Namespace Management tools for S3 Tables MCP Server."""
16
+
17
+ from .utils import get_s3tables_client, handle_exceptions
18
+ from typing import Any, Dict, Optional
19
+
20
+
21
+ @handle_exceptions
22
+ async def create_namespace(
23
+ table_bucket_arn: str,
24
+ namespace: str,
25
+ region_name: Optional[str] = None,
26
+ ) -> Dict[str, Any]:
27
+ """Create a new namespace."""
28
+ client = get_s3tables_client(region_name)
29
+ response = client.create_namespace(tableBucketARN=table_bucket_arn, namespace=[namespace])
30
+
31
+ return dict(response)
32
+
33
+
34
+ @handle_exceptions
35
+ async def delete_namespace(
36
+ table_bucket_arn: str,
37
+ namespace: str,
38
+ region_name: Optional[str] = None,
39
+ ) -> Dict[str, Any]:
40
+ """Delete a namespace.
41
+
42
+ Permissions:
43
+ You must have the s3tables:DeleteNamespace permission to use this operation.
44
+ """
45
+ client = get_s3tables_client(region_name)
46
+ response = client.delete_namespace(tableBucketARN=table_bucket_arn, namespace=namespace)
47
+ return dict(response)
48
+
49
+
50
+ @handle_exceptions
51
+ async def get_namespace(
52
+ table_bucket_arn: str, namespace: str, region_name: Optional[str] = None
53
+ ) -> Dict[str, Any]:
54
+ """Get details about a namespace.
55
+
56
+ Gets details about a namespace.
57
+
58
+ Permissions:
59
+ You must have the s3tables:GetNamespace permission to use this operation.
60
+ """
61
+ client = get_s3tables_client(region_name)
62
+ response = client.get_namespace(tableBucketARN=table_bucket_arn, namespace=namespace)
63
+ return dict(response)
@@ -0,0 +1,231 @@
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
+ """MCP resource definitions for S3 Tables MCP Server."""
16
+
17
+ import json
18
+ from .models import (
19
+ NamespacesResource,
20
+ NamespaceSummary,
21
+ TableBucketsResource,
22
+ TableBucketSummary,
23
+ TablesResource,
24
+ TableSummary,
25
+ )
26
+ from .utils import get_s3tables_client
27
+ from pydantic import BaseModel
28
+ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar
29
+
30
+
31
+ T = TypeVar('T')
32
+ ResourceT = TypeVar('ResourceT', bound=BaseModel)
33
+
34
+
35
+ def create_error_response(error: Exception, resource_name: str) -> str:
36
+ """Create a standardized error response.
37
+
38
+ Args:
39
+ error: The exception that occurred
40
+ resource_name: The name of the resource being accessed
41
+
42
+ Returns:
43
+ A JSON string containing the error response
44
+ """
45
+ return json.dumps({'error': str(error), resource_name: [], 'total_count': 0})
46
+
47
+
48
+ async def paginate_and_collect(
49
+ paginator: Any,
50
+ collection_key: str,
51
+ item_constructor: Callable[[Dict[str, Any]], T],
52
+ **pagination_args,
53
+ ) -> List[T]:
54
+ """Collect items from a paginated response.
55
+
56
+ Args:
57
+ paginator: The paginator to use
58
+ collection_key: The key in the response that contains the items
59
+ item_constructor: A function that constructs an item from a response
60
+ **pagination_args: Additional arguments to pass to the paginator
61
+
62
+ Returns:
63
+ A list of constructed items
64
+ """
65
+ items = []
66
+ for page in paginator.paginate(**pagination_args):
67
+ for item in page.get(collection_key, []):
68
+ items.append(item_constructor(item))
69
+ return items
70
+
71
+
72
+ async def create_resource_response(
73
+ items: List[T], resource_class: Type[ResourceT], resource_name: str
74
+ ) -> str:
75
+ """Create a resource response.
76
+
77
+ Args:
78
+ items: The items to include in the resource
79
+ resource_class: The resource class to use
80
+ resource_name: The name of the resource
81
+
82
+ Returns:
83
+ A JSON string containing the resource response
84
+ """
85
+ try:
86
+ resource = resource_class(**{resource_name: items, 'total_count': len(items)})
87
+ return resource.model_dump_json()
88
+ except Exception as e:
89
+ return create_error_response(e, resource_name)
90
+
91
+
92
+ async def list_table_buckets_resource(region_name: Optional[str] = None) -> str:
93
+ """List all S3 Tables buckets.
94
+
95
+ Lists table buckets for your account. Requires s3tables:ListTableBuckets permission.
96
+ The API supports pagination with continuationToken and filtering with prefix.
97
+
98
+ Returns:
99
+ A JSON string containing the list of table buckets and total count.
100
+ """
101
+ try:
102
+ client = get_s3tables_client(region_name)
103
+ paginator = client.get_paginator('list_table_buckets')
104
+
105
+ table_buckets = await paginate_and_collect(
106
+ paginator=paginator,
107
+ collection_key='tableBuckets',
108
+ item_constructor=lambda bucket: TableBucketSummary(
109
+ arn=bucket['arn'],
110
+ name=bucket['name'],
111
+ owner_account_id=bucket['ownerAccountId'],
112
+ created_at=bucket['createdAt'],
113
+ table_bucket_id=bucket.get('tableBucketId'),
114
+ type=bucket.get('type'),
115
+ ),
116
+ )
117
+
118
+ return await create_resource_response(
119
+ items=table_buckets, resource_class=TableBucketsResource, resource_name='table_buckets'
120
+ )
121
+
122
+ except Exception as e:
123
+ return create_error_response(e, 'table_buckets')
124
+
125
+
126
+ async def get_table_buckets(region_name: Optional[str] = None) -> List[TableBucketSummary]:
127
+ """Get all table buckets as TableBucketSummary objects.
128
+
129
+ Returns:
130
+ A list of TableBucketSummary objects
131
+ """
132
+ response = await list_table_buckets_resource(region_name=region_name)
133
+ data = json.loads(response)
134
+ if 'error' in data:
135
+ raise Exception(data['error'])
136
+ return [TableBucketSummary(**bucket) for bucket in data['table_buckets']]
137
+
138
+
139
+ async def list_namespaces_resource(region_name: Optional[str] = None) -> str:
140
+ """List all namespaces across all table buckets.
141
+
142
+ Lists the namespaces within table buckets. Requires s3tables:ListNamespaces permission.
143
+ The API supports pagination with continuationToken and filtering with prefix.
144
+
145
+ Returns:
146
+ A JSON string containing the list of namespaces and total count.
147
+ """
148
+ try:
149
+ client = get_s3tables_client(region_name)
150
+
151
+ # Get all table buckets
152
+ table_buckets = await get_table_buckets(region_name=region_name)
153
+
154
+ # Then get namespaces for each bucket
155
+ all_namespaces = []
156
+ for bucket in table_buckets:
157
+ try:
158
+ namespace_paginator = client.get_paginator('list_namespaces')
159
+ namespaces = await paginate_and_collect(
160
+ paginator=namespace_paginator,
161
+ collection_key='namespaces',
162
+ item_constructor=lambda namespace: NamespaceSummary(
163
+ namespace=namespace['namespace'],
164
+ created_at=namespace['createdAt'],
165
+ created_by=namespace['createdBy'],
166
+ owner_account_id=namespace['ownerAccountId'],
167
+ namespace_id=namespace.get('namespaceId'),
168
+ table_bucket_id=namespace.get('tableBucketId'),
169
+ ),
170
+ tableBucketARN=bucket.arn,
171
+ )
172
+ all_namespaces.extend(namespaces)
173
+ except Exception as e:
174
+ return create_error_response(e, 'namespaces')
175
+
176
+ return await create_resource_response(
177
+ items=all_namespaces, resource_class=NamespacesResource, resource_name='namespaces'
178
+ )
179
+
180
+ except Exception as e:
181
+ return create_error_response(e, 'namespaces')
182
+
183
+
184
+ async def list_tables_resource(region_name: Optional[str] = None) -> str:
185
+ """List all Iceberg tables across all table buckets and namespaces.
186
+
187
+ Lists tables in the given table bucket. Requires s3tables:ListTables permission.
188
+ The API supports pagination with continuationToken, filtering with prefix and namespace,
189
+ and limiting results with maxTables.
190
+
191
+ Returns:
192
+ A JSON string containing the list of tables and total count.
193
+ """
194
+ try:
195
+ client = get_s3tables_client(region_name)
196
+
197
+ # Get all table buckets
198
+ table_buckets = await get_table_buckets(region_name=region_name)
199
+
200
+ # Then get tables for each bucket
201
+ all_tables = []
202
+ for bucket in table_buckets:
203
+ try:
204
+ table_paginator = client.get_paginator('list_tables')
205
+
206
+ tables = await paginate_and_collect(
207
+ paginator=table_paginator,
208
+ collection_key='tables',
209
+ item_constructor=lambda table: TableSummary(
210
+ namespace=table['namespace'],
211
+ name=table['name'],
212
+ type=table['type'],
213
+ table_arn=table['tableARN'],
214
+ created_at=table['createdAt'],
215
+ modified_at=table['modifiedAt'],
216
+ namespace_id=table.get('namespaceId'),
217
+ table_bucket_id=table.get('tableBucketId'),
218
+ ),
219
+ tableBucketARN=bucket.arn,
220
+ )
221
+
222
+ all_tables.extend(tables)
223
+ except Exception as e:
224
+ return create_error_response(e, 'tables')
225
+
226
+ return await create_resource_response(
227
+ items=all_tables, resource_class=TablesResource, resource_name='tables'
228
+ )
229
+
230
+ except Exception as e:
231
+ return create_error_response(e, 'tables')
@@ -0,0 +1,43 @@
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
+ """S3 Operations tools for S3 Tables MCP Server."""
16
+
17
+ from .utils import get_s3_client, handle_exceptions
18
+ from typing import Any, Dict, Optional
19
+
20
+
21
+ @handle_exceptions
22
+ async def get_bucket_metadata_table_configuration(
23
+ bucket: str, region_name: Optional[str] = None
24
+ ) -> Dict[str, Any]:
25
+ """Get the metadata table configuration for an S3 bucket.
26
+
27
+ Gets the metadata table configuration for an S3 bucket. This configuration
28
+ determines how metadata is stored and managed for the bucket.
29
+
30
+ Permissions:
31
+ You must have the s3:GetBucketMetadataTableConfiguration permission to use this operation.
32
+
33
+ Args:
34
+ bucket: The name of the S3 bucket
35
+ region_name: Optional AWS region name. If not provided, uses AWS_REGION environment variable
36
+ or defaults to 'us-east-1'.
37
+
38
+ Returns:
39
+ Dict containing the bucket metadata table configuration
40
+ """
41
+ client = get_s3_client(region_name)
42
+ response = client.get_bucket_metadata_table_configuration(Bucket=bucket)
43
+ return dict(response)