awslabs.documentdb-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,176 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4
+ # with the License. A copy of the License is located at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
9
+ # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
10
+ # and limitations under the License.
11
+
12
+ """Database management tools for DocumentDB MCP Server."""
13
+
14
+ from awslabs.documentdb_mcp_server.config import serverConfig
15
+ from awslabs.documentdb_mcp_server.connection_tools import DocumentDBConnection
16
+ from loguru import logger
17
+ from pydantic import Field
18
+ from typing import Annotated, Any, Dict, List
19
+
20
+
21
+ async def list_databases(
22
+ connection_id: Annotated[
23
+ str, Field(description='The connection ID returned by the connect tool')
24
+ ],
25
+ ) -> Dict[str, Any]:
26
+ """List all available databases in the DocumentDB cluster.
27
+
28
+ This tool returns the names of all accessible databases in the connected cluster.
29
+
30
+ Returns:
31
+ Dict[str, Any]: List of database names
32
+ """
33
+ try:
34
+ client = DocumentDBConnection.get_connection(connection_id)
35
+ databases = client.list_database_names()
36
+ logger.info(f'Found {len(databases)} databases')
37
+ return {'databases': databases, 'count': len(databases)}
38
+ except ValueError as e:
39
+ logger.error(f'Connection error: {str(e)}')
40
+ raise ValueError(str(e))
41
+ except Exception as e:
42
+ logger.error(f'Error listing databases: {str(e)}')
43
+ raise ValueError(f'Failed to list databases: {str(e)}')
44
+
45
+
46
+ async def create_collection(
47
+ connection_id: Annotated[
48
+ str, Field(description='The connection ID returned by the connect tool')
49
+ ],
50
+ database: Annotated[str, Field(description='Name of the database')],
51
+ collection: Annotated[str, Field(description='Name of the collection to create')],
52
+ ) -> Dict[str, Any]:
53
+ """Create a new collection in a DocumentDB database.
54
+
55
+ This tool creates a new collection in the specified database.
56
+
57
+ Returns:
58
+ Dict[str, Any]: Status of collection creation
59
+ """
60
+ # Check if server is in read-only mode
61
+ if serverConfig.read_only_mode:
62
+ logger.warning('Create collection operation denied: Server is in read-only mode')
63
+ raise ValueError('Operation not permitted: Server is configured in read-only mode')
64
+
65
+ try:
66
+ # Get connection
67
+ if connection_id not in DocumentDBConnection._connections:
68
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
69
+
70
+ connection_info = DocumentDBConnection._connections[connection_id]
71
+ client = connection_info.client
72
+
73
+ db = client[database]
74
+
75
+ # Check if collection already exists
76
+ existing_collections = db.list_collection_names()
77
+ if collection in existing_collections:
78
+ return {
79
+ 'success': False,
80
+ 'message': f"Collection '{collection}' already exists in database '{database}'",
81
+ }
82
+
83
+ # Create the collection
84
+ db.create_collection(collection)
85
+
86
+ logger.info(f"Created collection '{collection}' in database '{database}'")
87
+ return {'success': True, 'message': f"Collection '{collection}' created successfully"}
88
+ except ValueError as e:
89
+ logger.error(f'Connection error: {str(e)}')
90
+ raise ValueError(str(e))
91
+ except Exception as e:
92
+ logger.error(f'Error creating collection: {str(e)}')
93
+ raise ValueError(f'Failed to create collection: {str(e)}')
94
+
95
+
96
+ async def list_collections(
97
+ connection_id: Annotated[
98
+ str, Field(description='The connection ID returned by the connect tool')
99
+ ],
100
+ database: Annotated[str, Field(description='Name of the database')],
101
+ ) -> List[str]:
102
+ """List collections in a DocumentDB database.
103
+
104
+ This tool returns the names of all collections in a specified database.
105
+
106
+ Returns:
107
+ List[str]: List of collection names
108
+ """
109
+ try:
110
+ # Get connection
111
+ if connection_id not in DocumentDBConnection._connections:
112
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
113
+
114
+ connection_info = DocumentDBConnection._connections[connection_id]
115
+ client = connection_info.client
116
+ db = client[database]
117
+ collections = db.list_collection_names()
118
+ logger.info(f"Found {len(collections)} collections in database '{database}'")
119
+ return collections
120
+ except ValueError as e:
121
+ logger.error(f'Connection error: {str(e)}')
122
+ raise ValueError(str(e))
123
+ except Exception as e:
124
+ logger.error(f'Error listing collections: {str(e)}')
125
+ raise ValueError(f'Failed to list collections: {str(e)}')
126
+
127
+
128
+ async def drop_collection(
129
+ connection_id: Annotated[
130
+ str, Field(description='The connection ID returned by the connect tool')
131
+ ],
132
+ database: Annotated[str, Field(description='Name of the database')],
133
+ collection: Annotated[str, Field(description='Name of the collection to drop')],
134
+ ) -> Dict[str, Any]:
135
+ """Drop a collection from a DocumentDB database.
136
+
137
+ This tool completely removes a collection and all its documents from the specified database.
138
+ This operation cannot be undone, so use it with caution.
139
+
140
+ Returns:
141
+ Dict[str, Any]: Status of the drop operation
142
+ """
143
+ # Check if server is in read-only mode
144
+ if serverConfig.read_only_mode:
145
+ logger.warning('Drop collection operation denied: Server is in read-only mode')
146
+ raise ValueError('Operation not permitted: Server is configured in read-only mode')
147
+
148
+ try:
149
+ # Get connection
150
+ if connection_id not in DocumentDBConnection._connections:
151
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
152
+
153
+ connection_info = DocumentDBConnection._connections[connection_id]
154
+ client = connection_info.client
155
+
156
+ db = client[database]
157
+
158
+ # Check if collection exists
159
+ existing_collections = db.list_collection_names()
160
+ if collection not in existing_collections:
161
+ return {
162
+ 'success': False,
163
+ 'message': f"Collection '{collection}' does not exist in database '{database}'",
164
+ }
165
+
166
+ # Drop the collection
167
+ db.drop_collection(collection)
168
+
169
+ logger.info(f"Dropped collection '{collection}' from database '{database}'")
170
+ return {'success': True, 'message': f"Collection '{collection}' dropped successfully"}
171
+ except ValueError as e:
172
+ logger.error(f'Connection error: {str(e)}')
173
+ raise ValueError(str(e))
174
+ except Exception as e:
175
+ logger.error(f'Error dropping collection: {str(e)}')
176
+ raise ValueError(f'Failed to drop collection: {str(e)}')
@@ -0,0 +1,121 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4
+ # with the License. A copy of the License is located at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
9
+ # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
10
+ # and limitations under the License.
11
+
12
+ """Query tools for DocumentDB MCP Server."""
13
+
14
+ from awslabs.documentdb_mcp_server.connection_tools import DocumentDBConnection
15
+ from loguru import logger
16
+ from pydantic import Field
17
+ from typing import Annotated, Any, Dict, List, Optional
18
+
19
+
20
+ async def find(
21
+ connection_id: Annotated[
22
+ str, Field(description='The connection ID returned by the connect tool')
23
+ ],
24
+ database: Annotated[str, Field(description='Name of the database')],
25
+ collection: Annotated[str, Field(description='Name of the collection')],
26
+ query: Annotated[
27
+ Dict[str, Any], Field(description='Query filter (e.g., {"name": "example"})')
28
+ ],
29
+ projection: Annotated[
30
+ Optional[Dict[str, Any]],
31
+ Field(description='Fields to include/exclude (e.g., {"_id": 0, "name": 1})'),
32
+ ] = None,
33
+ limit: Annotated[
34
+ int, Field(description='Maximum number of documents to return (default: 10)')
35
+ ] = 10,
36
+ ) -> List[Dict[str, Any]]:
37
+ """Run a query against a DocumentDB collection.
38
+
39
+ This tool queries documents from a specified collection based on a filter.
40
+
41
+ Returns:
42
+ List[Dict[str, Any]]: List of matching documents
43
+ """
44
+ try:
45
+ # Get connection
46
+ if connection_id not in DocumentDBConnection._connections:
47
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
48
+
49
+ connection_info = DocumentDBConnection._connections[connection_id]
50
+ client = connection_info.client
51
+
52
+ db = client[database]
53
+ coll = db[collection]
54
+
55
+ result = list(coll.find(query, projection).limit(limit))
56
+
57
+ # Convert ObjectId to string for JSON serialization
58
+ for doc in result:
59
+ if '_id' in doc and not isinstance(doc['_id'], str):
60
+ doc['_id'] = str(doc['_id'])
61
+
62
+ logger.info(f'Query returned {len(result)} documents')
63
+ return result
64
+ except ValueError as e:
65
+ logger.error(f'Connection error: {str(e)}')
66
+ raise ValueError(str(e))
67
+ except Exception as e:
68
+ logger.error(f'Error querying DocumentDB: {str(e)}')
69
+ raise ValueError(f'Failed to query DocumentDB: {str(e)}')
70
+
71
+
72
+ async def aggregate(
73
+ connection_id: Annotated[
74
+ str, Field(description='The connection ID returned by the connect tool')
75
+ ],
76
+ database: Annotated[str, Field(description='Name of the database')],
77
+ collection: Annotated[str, Field(description='Name of the collection')],
78
+ pipeline: Annotated[
79
+ List[Dict[str, Any]], Field(description='DocumentDB aggregation pipeline')
80
+ ],
81
+ limit: Annotated[
82
+ int, Field(description='Maximum number of documents to return (default: 10)')
83
+ ] = 10,
84
+ ) -> List[Dict[str, Any]]:
85
+ """Run an aggregation pipeline against a DocumentDB collection.
86
+
87
+ This tool executes a DocumentDB aggregation pipeline on a specified collection.
88
+
89
+ Returns:
90
+ List[Dict[str, Any]]: List of aggregation results
91
+ """
92
+ try:
93
+ # Get connection
94
+ if connection_id not in DocumentDBConnection._connections:
95
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
96
+
97
+ connection_info = DocumentDBConnection._connections[connection_id]
98
+ client = connection_info.client
99
+
100
+ db = client[database]
101
+ coll = db[collection]
102
+
103
+ # Add limit stage if not already in pipeline
104
+ if limit > 0 and not any('$limit' in stage for stage in pipeline):
105
+ pipeline.append({'$limit': limit})
106
+
107
+ result = list(coll.aggregate(pipeline))
108
+
109
+ # Convert ObjectId to string for JSON serialization
110
+ for doc in result:
111
+ if '_id' in doc and not isinstance(doc['_id'], str):
112
+ doc['_id'] = str(doc['_id'])
113
+
114
+ logger.info(f'Aggregation returned {len(result)} results')
115
+ return result
116
+ except ValueError as e:
117
+ logger.error(f'Connection error: {str(e)}')
118
+ raise ValueError(str(e))
119
+ except Exception as e:
120
+ logger.error(f'Error running aggregation in DocumentDB: {str(e)}')
121
+ raise ValueError(f'Failed to run aggregation: {str(e)}')
@@ -0,0 +1,159 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4
+ # with the License. A copy of the License is located at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
9
+ # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
10
+ # and limitations under the License.
11
+
12
+ """AWS Labs DocumentDB MCP Server implementation for querying AWS DocumentDB."""
13
+
14
+ import argparse
15
+ from awslabs.documentdb_mcp_server.analytic_tools import (
16
+ analyze_schema,
17
+ count_documents,
18
+ explain_operation,
19
+ get_collection_stats,
20
+ get_database_stats,
21
+ )
22
+ from awslabs.documentdb_mcp_server.config import serverConfig
23
+ from awslabs.documentdb_mcp_server.connection_tools import (
24
+ DocumentDBConnection,
25
+ connect,
26
+ disconnect,
27
+ )
28
+ from awslabs.documentdb_mcp_server.db_management_tools import (
29
+ create_collection,
30
+ drop_collection,
31
+ list_collections,
32
+ list_databases,
33
+ )
34
+ from awslabs.documentdb_mcp_server.query_tools import aggregate, find
35
+ from awslabs.documentdb_mcp_server.write_tools import delete, insert, update
36
+ from loguru import logger
37
+ from mcp.server.fastmcp import FastMCP
38
+
39
+
40
+ # Create the FastMCP server
41
+ mcp = FastMCP(
42
+ 'awslabs.documentdb-mcp-server',
43
+ instructions="""DocumentDB MCP Server provides tools to connect to and query AWS DocumentDB databases.
44
+
45
+ Usage pattern:
46
+ 1. First use the `connect` tool to establish a connection and get a connection_id
47
+ 2. Use the connection_id with other tools to perform operations
48
+ 3. When finished, use the `disconnect` tool to release resources
49
+
50
+ Server Configuration:
51
+ - The server can be configured in read-only mode, which blocks write operations
52
+ while still allowing read operations.""",
53
+ dependencies=[
54
+ 'pydantic',
55
+ 'loguru',
56
+ 'pymongo',
57
+ ],
58
+ )
59
+
60
+
61
+ # Register all tools
62
+
63
+ # Connection tools
64
+ mcp.tool(name='connect')(connect)
65
+ mcp.tool(name='disconnect')(disconnect)
66
+
67
+ # Query tools
68
+ mcp.tool(name='find')(find)
69
+ mcp.tool(name='aggregate')(aggregate)
70
+
71
+ # Write tools
72
+ mcp.tool(name='insert')(insert)
73
+ mcp.tool(name='update')(update)
74
+ mcp.tool(name='delete')(delete)
75
+
76
+ # Database management tools
77
+ mcp.tool(name='listDatabases')(list_databases)
78
+ mcp.tool(name='createCollection')(create_collection)
79
+ mcp.tool(name='listCollections')(list_collections)
80
+ mcp.tool(name='dropCollection')(drop_collection)
81
+
82
+ # Analytic tools
83
+ mcp.tool(name='countDocuments')(count_documents)
84
+ mcp.tool(name='getDatabaseStats')(get_database_stats)
85
+ mcp.tool(name='getCollectionStats')(get_collection_stats)
86
+ mcp.tool(name='analyzeSchema')(analyze_schema)
87
+ mcp.tool(name='explainOperation')(explain_operation)
88
+
89
+
90
+ def main():
91
+ """Run the MCP server with CLI argument support."""
92
+ parser = argparse.ArgumentParser(
93
+ description='An AWS Labs Model Context Protocol (MCP) server for DocumentDB'
94
+ )
95
+ parser.add_argument('--sse', action='store_true', help='Use SSE transport')
96
+ parser.add_argument('--port', type=int, default=8888, help='Port to run the server on')
97
+ parser.add_argument('--host', type=str, default='127.0.0.1', help='Host to bind the server to')
98
+ parser.add_argument(
99
+ '--log-level',
100
+ type=str,
101
+ default='INFO',
102
+ choices=['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL'],
103
+ help='Set the logging level',
104
+ )
105
+ parser.add_argument(
106
+ '--connection-timeout',
107
+ type=int,
108
+ default=30,
109
+ help='Idle connection timeout in minutes (default: 30)',
110
+ )
111
+ parser.add_argument(
112
+ '--allow-write',
113
+ action='store_true',
114
+ help='Allow write operations (insert, update, delete). By default, the server runs in read-only mode.',
115
+ )
116
+
117
+ args = parser.parse_args()
118
+
119
+ # Configure logging
120
+ logger.remove()
121
+ logger.add(
122
+ lambda msg: print(msg),
123
+ level=args.log_level,
124
+ format='<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>',
125
+ )
126
+
127
+ logger.info(f'Starting DocumentDB MCP Server on {args.host}:{args.port}')
128
+ logger.info(f'Log level: {args.log_level}')
129
+
130
+ # Set connection timeout
131
+ DocumentDBConnection._idle_timeout = args.connection_timeout
132
+ logger.info(f'Idle connection timeout: {args.connection_timeout} minutes')
133
+
134
+ # Configure read-only mode
135
+ serverConfig.read_only_mode = not args.allow_write
136
+ if serverConfig.read_only_mode:
137
+ logger.warning('Server is running in READ-ONLY mode. Write operations will be blocked.')
138
+ else:
139
+ logger.info('Server is running with WRITE operations ENABLED. Database can be modified.')
140
+
141
+ try:
142
+ # Run server with appropriate transport
143
+ if args.sse:
144
+ mcp.settings.port = args.port
145
+ mcp.settings.host = args.host
146
+ mcp.run(transport='sse')
147
+ else:
148
+ mcp.settings.port = args.port
149
+ mcp.settings.host = args.host
150
+ mcp.run()
151
+ except Exception as e:
152
+ logger.critical(f'Failed to start server: {str(e)}')
153
+ finally:
154
+ # Close all DB connections
155
+ DocumentDBConnection.close_all_connections()
156
+
157
+
158
+ if __name__ == '__main__':
159
+ main()
@@ -0,0 +1,202 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4
+ # with the License. A copy of the License is located at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
9
+ # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
10
+ # and limitations under the License.
11
+
12
+ """Write tools for DocumentDB MCP Server."""
13
+
14
+ from awslabs.documentdb_mcp_server.config import serverConfig
15
+ from awslabs.documentdb_mcp_server.connection_tools import DocumentDBConnection
16
+ from loguru import logger
17
+ from pydantic import Field
18
+ from typing import Annotated, Any, Dict, List, Union
19
+
20
+
21
+ async def insert(
22
+ connection_id: Annotated[
23
+ str, Field(description='The connection ID returned by the connect tool')
24
+ ],
25
+ database: Annotated[str, Field(description='Name of the database')],
26
+ collection: Annotated[str, Field(description='Name of the collection')],
27
+ documents: Annotated[
28
+ Union[Dict[str, Any], List[Dict[str, Any]]],
29
+ Field(description='Document or list of documents to insert'),
30
+ ],
31
+ ) -> Dict[str, Any]:
32
+ """Insert one or more documents into a DocumentDB collection.
33
+
34
+ This tool inserts new documents into a specified collection.
35
+
36
+ Returns:
37
+ Dict[str, Any]: Insert operation results including document IDs
38
+ """
39
+ # Check if server is in read-only mode
40
+ if serverConfig.read_only_mode:
41
+ logger.warning('Insert operation denied: Server is in read-only mode')
42
+ raise ValueError('Operation not permitted: Server is configured in read-only mode')
43
+
44
+ try:
45
+ # Get connection
46
+ if connection_id not in DocumentDBConnection._connections:
47
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
48
+
49
+ connection_info = DocumentDBConnection._connections[connection_id]
50
+ client = connection_info.client
51
+
52
+ db = client[database]
53
+ coll = db[collection]
54
+
55
+ # Handle single document or multiple documents
56
+ if isinstance(documents, dict):
57
+ result = coll.insert_one(documents)
58
+ inserted_ids = [str(result.inserted_id)]
59
+ count = 1
60
+ else:
61
+ result = coll.insert_many(documents)
62
+ inserted_ids = [str(id) for id in result.inserted_ids]
63
+ count = len(inserted_ids)
64
+
65
+ logger.info(f'Inserted {count} documents')
66
+ return {'success': True, 'inserted_count': count, 'inserted_ids': inserted_ids}
67
+ except ValueError as e:
68
+ logger.error(f'Connection error: {str(e)}')
69
+ raise ValueError(str(e))
70
+ except Exception as e:
71
+ logger.error(f'Error inserting into DocumentDB: {str(e)}')
72
+ raise ValueError(f'Failed to insert documents: {str(e)}')
73
+
74
+
75
+ async def update(
76
+ connection_id: Annotated[
77
+ str, Field(description='The connection ID returned by the connect tool')
78
+ ],
79
+ database: Annotated[str, Field(description='Name of the database')],
80
+ collection: Annotated[str, Field(description='Name of the collection')],
81
+ filter: Annotated[Dict[str, Any], Field(description='Filter to select documents to update')],
82
+ update: Annotated[
83
+ Dict[str, Any],
84
+ Field(
85
+ description='Update operations to apply. It should either include DocumentDB operators like $set, or an entire document if the entire document needs to be replaced.'
86
+ ),
87
+ ],
88
+ upsert: Annotated[
89
+ bool,
90
+ Field(
91
+ description='Whether to create a new document if no match is found (default: False)'
92
+ ),
93
+ ] = False,
94
+ many: Annotated[
95
+ bool, Field(description='Whether to update multiple documents (default: False)')
96
+ ] = False,
97
+ ) -> Dict[str, Any]:
98
+ """Update documents in a DocumentDB collection.
99
+
100
+ This tool updates existing documents that match a specified filter.
101
+
102
+ Returns:
103
+ Dict[str, Any]: Update operation results
104
+ """
105
+ # Check if server is in read-only mode
106
+ if serverConfig.read_only_mode:
107
+ logger.warning('Update operation denied: Server is in read-only mode')
108
+ raise ValueError('Operation not permitted: Server is configured in read-only mode')
109
+
110
+ try:
111
+ # Get connection
112
+ if connection_id not in DocumentDBConnection._connections:
113
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
114
+
115
+ connection_info = DocumentDBConnection._connections[connection_id]
116
+ client = connection_info.client
117
+
118
+ db = client[database]
119
+ coll = db[collection]
120
+
121
+ # If the update doesn't have any operators, then it's a replace
122
+ if not any(key.startswith('$') for key in update.keys()):
123
+ result = coll.replace_one(filter, update, upsert=upsert)
124
+ matched = result.matched_count
125
+ modified = result.modified_count
126
+ # If the update needs to update multiple documents
127
+ elif many:
128
+ result = coll.update_many(filter, update, upsert=upsert)
129
+ matched = result.matched_count
130
+ modified = result.modified_count
131
+ # Else only a single document needs to be updated
132
+ else:
133
+ result = coll.update_one(filter, update, upsert=upsert)
134
+ matched = result.matched_count
135
+ modified = result.modified_count
136
+
137
+ upserted_id = str(result.upserted_id) if result.upserted_id else None
138
+
139
+ logger.info(f'Updated {modified} documents (matched {matched})')
140
+ return {
141
+ 'success': True,
142
+ 'matched_count': matched,
143
+ 'modified_count': modified,
144
+ 'upserted_id': upserted_id,
145
+ }
146
+ except ValueError as e:
147
+ logger.error(f'Connection error: {str(e)}')
148
+ raise ValueError(str(e))
149
+ except Exception as e:
150
+ logger.error(f'Error updating DocumentDB: {str(e)}')
151
+ raise ValueError(f'Failed to update documents: {str(e)}')
152
+
153
+
154
+ async def delete(
155
+ connection_id: Annotated[
156
+ str, Field(description='The connection ID returned by the connect tool')
157
+ ],
158
+ database: Annotated[str, Field(description='Name of the database')],
159
+ collection: Annotated[str, Field(description='Name of the collection')],
160
+ filter: Annotated[Dict[str, Any], Field(description='Filter to select documents to delete')],
161
+ many: Annotated[
162
+ bool, Field(description='Whether to delete multiple documents (default: False)')
163
+ ] = False,
164
+ ) -> Dict[str, Any]:
165
+ """Delete documents from a DocumentDB collection.
166
+
167
+ This tool deletes documents that match a specified filter.
168
+
169
+ Returns:
170
+ Dict[str, Any]: Delete operation results
171
+ """
172
+ # Check if server is in read-only mode
173
+ if serverConfig.read_only_mode:
174
+ logger.warning('Delete operation denied: Server is in read-only mode')
175
+ raise ValueError('Operation not permitted: Server is configured in read-only mode')
176
+
177
+ try:
178
+ # Get connection
179
+ if connection_id not in DocumentDBConnection._connections:
180
+ raise ValueError(f'Connection ID {connection_id} not found. You must connect first.')
181
+
182
+ connection_info = DocumentDBConnection._connections[connection_id]
183
+ client = connection_info.client
184
+
185
+ db = client[database]
186
+ coll = db[collection]
187
+
188
+ if many:
189
+ result = coll.delete_many(filter)
190
+ deleted = result.deleted_count
191
+ else:
192
+ result = coll.delete_one(filter)
193
+ deleted = result.deleted_count
194
+
195
+ logger.info(f'Deleted {deleted} documents')
196
+ return {'success': True, 'deleted_count': deleted}
197
+ except ValueError as e:
198
+ logger.error(f'Connection error: {str(e)}')
199
+ raise ValueError(str(e))
200
+ except Exception as e:
201
+ logger.error(f'Error deleting from DocumentDB: {str(e)}')
202
+ raise ValueError(f'Failed to delete documents: {str(e)}')