feat(mongodb): support generic runCommand execution

This commit is contained in:
Guoyu Su authored and GitHub committed 2026-08-12 21:05:13 +08:00
1 parent 2d00620fac
commit 9b24d962bb
27 files changed
+580 -18

No files matched your search

@@ -2,7 +2,7 @@
"protocolVersion": 1,
"handshakeMethod": "handshake",
"handshakeResponseFields": ["protocolVersion", "agentProtocolVersion", "capabilities"],
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection"],
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command"],
"capabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl"],
"defaultSqlCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl"],
"commonMethods": [
@@ -39,6 +39,6 @@
"disconnect",
"shutdown"
],
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "update_document", "update_documents", "delete_document", "delete_documents"],
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "update_document", "update_documents", "delete_document", "delete_documents", "run_command"],
"kvMethods": ["kv_list_prefix", "kv_get", "kv_put", "kv_delete", "kv_rename", "kv_history", "kv_status", "etcd_compact", "etcd_defrag", "etcd_watch_start", "etcd_watch_poll", "etcd_watch_stop", "etcd_lease_list", "etcd_lease_get", "etcd_lease_grant", "etcd_lease_keepalive_once", "etcd_lease_revoke", "etcd_auth_user_list", "etcd_auth_user_get", "etcd_auth_user_add", "etcd_auth_user_delete", "etcd_auth_user_change_password", "etcd_auth_user_grant_role", "etcd_auth_user_revoke_role", "etcd_auth_role_list", "etcd_auth_role_get", "etcd_auth_role_add", "etcd_auth_role_delete", "etcd_auth_role_grant_permission", "etcd_auth_role_revoke_permission"]
}
@@ -2,7 +2,7 @@
"protocolVersion": 2,
"handshakeMethod": "handshake",
"handshakeResponseFields": ["protocolVersion", "agentProtocolVersion", "capabilities"],
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "multi_session", "structured_error_v1"],
"allCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "kv", "kv_ttl", "kv_cas", "kv_list_values", "kv_status", "kv_history", "etcd_compaction", "etcd_defrag", "etcd_watch", "etcd_lease", "etcd_auth", "mongo_drop_database", "mongo_clone_collection", "mongo_run_command", "multi_session", "structured_error_v1"],
"capabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "multi_session"],
"defaultSqlCapabilities": ["connect", "test_connection", "metadata", "query", "paged_query", "transaction", "ddl", "multi_session", "structured_error_v1"],
"commonMethods": [
@@ -43,7 +43,7 @@
"disconnect",
"shutdown"
],
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "update_document", "update_documents", "delete_document", "delete_documents"],
"mongoLegacyMethods": ["list_databases", "list_collections", "find_documents", "find_one", "explain_find", "aggregate_documents", "find_documents_extended_json", "count_documents", "server_version", "create_index", "create_user", "drop_indexes", "drop_collection", "clone_collection", "drop_database", "insert_document", "update_document", "update_documents", "delete_document", "delete_documents", "run_command"],
"kvMethods": ["kv_list_prefix", "kv_get", "kv_put", "kv_delete", "kv_rename", "kv_history", "kv_status", "etcd_compact", "etcd_defrag", "etcd_watch_start", "etcd_watch_poll", "etcd_watch_stop", "etcd_lease_list", "etcd_lease_get", "etcd_lease_grant", "etcd_lease_keepalive_once", "etcd_lease_revoke", "etcd_auth_user_list", "etcd_auth_user_get", "etcd_auth_user_add", "etcd_auth_user_delete", "etcd_auth_user_change_password", "etcd_auth_user_grant_role", "etcd_auth_user_revoke_role", "etcd_auth_role_list", "etcd_auth_role_get", "etcd_auth_role_add", "etcd_auth_role_delete", "etcd_auth_role_grant_permission", "etcd_auth_role_revoke_permission"],
"sessionField": "agentSessionId",
"cursorSessionField": "sessionId"
+1
View File
@@ -1387,6 +1387,7 @@ for line in sys.stdin:
"db.items.updateMany({tenant: 7}, {$set: {active: false}})",
"db.items.deleteMany({tenant: 7})",
"db.items.createIndex({tenant: 1})",
"db.runCommand({compact: 'items'})",
r#"db.items.aggregate([{"$out":"items_backup"}])"#,
]
.into_iter()
+26 -3
View File
@@ -973,6 +973,7 @@ pub enum AgentCapability {
EtcdAuth,
MongoDropDatabase,
MongoCloneCollection,
MongoRunCommand,
MultiSession,
StructuredErrorV1,
}
@@ -1055,7 +1056,7 @@ fn parse_agent_rpc_error_header(header: &str) -> (Option<i64>, String) {
}
impl AgentCapability {
pub const ALL: [Self; 22] = [
pub const ALL: [Self; 23] = [
Self::Connect,
Self::TestConnection,
Self::Metadata,
@@ -1076,6 +1077,7 @@ impl AgentCapability {
Self::EtcdAuth,
Self::MongoDropDatabase,
Self::MongoCloneCollection,
Self::MongoRunCommand,
Self::MultiSession,
Self::StructuredErrorV1,
];
@@ -1102,6 +1104,7 @@ impl AgentCapability {
Self::EtcdAuth => "etcd_auth",
Self::MongoDropDatabase => "mongo_drop_database",
Self::MongoCloneCollection => "mongo_clone_collection",
Self::MongoRunCommand => "mongo_run_command",
Self::MultiSession => "multi_session",
Self::StructuredErrorV1 => "structured_error_v1",
}
@@ -1294,10 +1297,11 @@ pub enum MongoAgentMethod {
UpdateDocuments,
DeleteDocument,
DeleteDocuments,
RunCommand,
}
impl MongoAgentMethod {
pub const ALL: [Self; 20] = [
pub const ALL: [Self; 21] = [
Self::ListDatabases,
Self::ListCollections,
Self::FindDocuments,
@@ -1318,6 +1322,7 @@ impl MongoAgentMethod {
Self::UpdateDocuments,
Self::DeleteDocument,
Self::DeleteDocuments,
Self::RunCommand,
];
pub fn as_str(self) -> &'static str {
@@ -1342,6 +1347,7 @@ impl MongoAgentMethod {
Self::UpdateDocuments => "update_documents",
Self::DeleteDocument => "delete_document",
Self::DeleteDocuments => "delete_documents",
Self::RunCommand => "run_command",
}
}
}
@@ -2539,6 +2545,13 @@ impl AgentDriverClient {
self.call_mongo_method(MongoAgentMethod::ServerVersion, mongo_database_params(database)).await
}
pub async fn mongo_run_command<T: DeserializeOwned + Send + 'static>(
&mut self,
params: Value,
) -> Result<T, String> {
self.call_mongo_method(MongoAgentMethod::RunCommand, params).await
}
pub async fn mongo_create_index<T: DeserializeOwned + Send + 'static>(
&mut self,
params: Value,
@@ -2755,6 +2768,7 @@ pub fn agent_supports_capability(handshake: Option<&AgentHandshake>, capability:
| AgentCapability::EtcdAuth
| AgentCapability::MongoDropDatabase
| AgentCapability::MongoCloneCollection
| AgentCapability::MongoRunCommand
) {
return handshake.map(|value| value.supports(capability)).unwrap_or(false);
}
@@ -4343,9 +4357,10 @@ for line in sys.stdin:
assert_eq!(AgentCapability::EtcdAuth.as_str(), "etcd_auth");
assert_eq!(AgentCapability::MongoDropDatabase.as_str(), "mongo_drop_database");
assert_eq!(AgentCapability::MongoCloneCollection.as_str(), "mongo_clone_collection");
assert_eq!(AgentCapability::MongoRunCommand.as_str(), "mongo_run_command");
assert_eq!(AgentCapability::MultiSession.as_str(), "multi_session");
assert_eq!(AgentCapability::StructuredErrorV1.as_str(), "structured_error_v1");
assert_eq!(AgentCapability::ALL.len(), 22);
assert_eq!(AgentCapability::ALL.len(), 23);
}
#[test]
@@ -4702,6 +4717,8 @@ for line in sys.stdin:
assert!(!agent_supports_capability(Some(&handshake), AgentCapability::MongoDropDatabase));
assert!(!agent_supports_capability(None, AgentCapability::MongoCloneCollection));
assert!(!agent_supports_capability(Some(&handshake), AgentCapability::MongoCloneCollection));
assert!(!agent_supports_capability(None, AgentCapability::MongoRunCommand));
assert!(!agent_supports_capability(Some(&handshake), AgentCapability::MongoRunCommand));
let mongo_handshake =
AgentHandshake { capabilities: vec![AgentCapability::MongoDropDatabase.as_str().to_string()], ..handshake };
@@ -4712,6 +4729,12 @@ for line in sys.stdin:
..mongo_handshake
};
assert!(agent_supports_capability(Some(&mongo_clone_handshake), AgentCapability::MongoCloneCollection));
let mongo_run_command_handshake = AgentHandshake {
capabilities: vec![AgentCapability::MongoRunCommand.as_str().to_string()],
..mongo_clone_handshake
};
assert!(agent_supports_capability(Some(&mongo_run_command_handshake), AgentCapability::MongoRunCommand));
}
#[test]
+19
View File
@@ -173,6 +173,25 @@ pub async fn server_version(client: &Client, database: &str) -> Result<String, S
server_version_from_build_info(&result)
}
pub async fn run_command(client: &Client, database: &str, command_json: &str) -> Result<MongoDocumentResult, String> {
let value: serde_json::Value =
serde_json::from_str(command_json).map_err(|error| format!("Invalid MongoDB command JSON: {error}"))?;
let command = json_object_to_document_extended_json(&value)
.map_err(|error| format!("Invalid MongoDB command document: {error}"))?;
if command.is_empty() {
return Err("MongoDB runCommand requires a non-empty command document".to_string());
}
let result = client.database(database).run_command(command).await.map_err(|error| error.to_string())?;
let (document, extended_document) = document_json_views(result);
Ok(MongoDocumentResult {
documents: vec![document],
raw_documents: None,
extended_documents: Some(vec![extended_document]),
total: 1,
total_is_exact: true,
})
}
fn server_version_from_build_info(result: &Document) -> Result<String, String> {
result.get_str("version").map(str::to_string).map_err(|e| format!("MongoDB server version not found: {e}"))
}
+82
View File
@@ -148,6 +148,35 @@ pub async fn mongo_server_version_core(
}
}
pub async fn mongo_run_command_core(
state: &AppState,
connection_id: &str,
database: &str,
command_json: &str,
) -> Result<MongoDocumentResult, String> {
ensure_document_pool(state, connection_id).await?;
let connections = state.connections.read().await;
match connections.get(connection_id).ok_or("Not found")? {
PoolKind::MongoDb(client) => mongo_driver::run_command(client, database, command_json).await,
PoolKind::Agent(client) => {
let mut client = client.lock().await;
if !client.supports_capability(AgentCapability::MongoRunCommand) {
return Err(
"MongoDB Legacy Agent does not support runCommand; upgrade or reinstall the MongoDB Legacy driver"
.to_string(),
);
}
client
.mongo_run_command(serde_json::json!({
"database": database,
"command_json": command_json,
}))
.await
}
_ => Err("Not a MongoDB connection".to_string()),
}
}
pub async fn mongo_collection_stats_core(
state: &AppState,
connection_id: &str,
@@ -821,6 +850,10 @@ pub async fn execute_mongo_command_core(
.await
.map(|version| scalar_query_result("version", Value::String(version))),
MongoCommand::Use { database } => Ok(scalar_query_result("database", Value::String(database.clone()))),
MongoCommand::RunCommand { command_json } => {
let result = mongo_run_command_core(state, connection_id, database, command_json).await?;
Ok(mongo_documents_query_result(result.documents))
}
MongoCommand::Find { collection, filter, projection, sort, collation, skip, limit } => {
let limit = bounded_mongo_find_limit(*limit, max_rows);
let result = mongo_find_documents_without_total_core(
@@ -1347,6 +1380,55 @@ for line in sys.stdin:
assert!(!error.contains("Unknown method"), "{error}");
}
#[cfg(unix)]
#[tokio::test]
async fn mongo_run_command_routes_legacy_connections_to_the_agent() {
let expected_result = serde_json::json!({
"documents": [{"ok": 1, "cursor": {"firstBatch": [{"_id": {"$oid": "507f1f77bcf86cd799439011"}}]}}],
"extended_documents": [{"ok": 1, "cursor": {"firstBatch": [{"_id": {"$oid": "507f1f77bcf86cd799439011"}}]}}],
"total": 1,
});
let (state, _directory) = legacy_mongo_state_with_capabilities(
"run_command",
serde_json::json!({
"database": "app",
"command_json": "{\"ping\":1}",
}),
expected_result,
&[AgentCapability::MongoRunCommand.as_str()],
)
.await;
let result = mongo_run_command_core(&state, "legacy", "app", "{\"ping\":1}").await.unwrap();
assert_eq!(result.total, 1);
assert_eq!(result.documents[0]["ok"], 1);
assert_eq!(
result.extended_documents.as_ref().unwrap()[0]["cursor"]["firstBatch"][0]["_id"],
serde_json::json!({"$oid": "507f1f77bcf86cd799439011"})
);
}
#[cfg(unix)]
#[tokio::test]
async fn mongo_run_command_requires_an_explicit_legacy_agent_capability() {
let (state, _directory) = legacy_mongo_state_with_capabilities(
"run_command",
serde_json::json!({
"database": "app",
"command_json": "{\"ping\":1}",
}),
serde_json::json!({"documents": [{"ok": 1}], "total": 1}),
&[],
)
.await;
let error = mongo_run_command_core(&state, "legacy", "app", "{\"ping\":1}").await.unwrap_err();
assert!(error.contains("upgrade or reinstall"), "{error}");
assert!(!error.contains("Unknown method"), "{error}");
}
#[cfg(unix)]
#[tokio::test]
async fn mongo_drop_collection_routes_legacy_connections_to_the_agent() {
+61 -2
View File
@@ -8,6 +8,11 @@ pub enum MongoCommand {
Version,
#[serde(rename = "use")]
Use { database: String },
#[serde(rename = "runCommand")]
RunCommand {
#[serde(rename = "commandJson")]
command_json: String,
},
#[serde(rename = "createUser")]
CreateUser {
#[serde(rename = "userJson")]
@@ -84,7 +89,8 @@ impl MongoCommand {
pub fn is_mutating(&self) -> bool {
matches!(
self,
Self::CreateUser { .. }
Self::RunCommand { .. }
| Self::CreateUser { .. }
| Self::Insert { .. }
| Self::Update { .. }
| Self::Delete { .. }
@@ -98,7 +104,7 @@ impl MongoCommand {
}
pub fn is_dangerous(&self) -> bool {
matches!(self, Self::CreateUser { .. } | Self::DropCollection { .. })
matches!(self, Self::RunCommand { .. } | Self::CreateUser { .. } | Self::DropCollection { .. })
|| matches!(self, Self::DropIndexes { indexes: None, single: false, .. })
|| matches!(self, Self::Aggregate { pipeline, .. } if aggregate_writes(pipeline))
}
@@ -356,6 +362,19 @@ pub fn parse(input: &str) -> Result<MongoCommand, String> {
if let Some(database) = parse_use_database(source) {
return Ok(MongoCommand::Use { database });
}
if let Some((args, tail)) = database_method_call(source, "runCommand") {
if !tail.is_empty() || args.len() != 1 {
return Err("MongoDB runCommand() requires exactly one command document.".to_string());
}
let command_json = normalized_json(&args[0])?;
let command = parse_json_value(&command_json)
.and_then(|value| value.as_object().cloned())
.ok_or("MongoDB runCommand() requires a command document.")?;
if command.is_empty() {
return Err("MongoDB runCommand() requires a non-empty command document.".to_string());
}
return Ok(MongoCommand::RunCommand { command_json });
}
if let Some((args, tail)) = database_method_call(source, "createUser") {
if !tail.is_empty() || !(1..=2).contains(&args.len()) {
return Err("MongoDB createUser() requires a user document and optional write concern.".to_string());
@@ -1022,6 +1041,43 @@ mod tests {
assert!(parse(r#"db.createUser({user: "test"}, "majority")"#).is_err());
}
#[test]
fn parses_run_command_as_a_dangerous_write() {
let command = parse(
r#"db.runCommand({
find: "orders",
filter: {_id: ObjectId("507f1f77bcf86cd799439011")},
createdAt: ISODate("2025-01-01T00:00:00Z")
})"#,
)
.unwrap();
assert_eq!(
command,
MongoCommand::RunCommand {
command_json: r#"{"find":"orders","filter":{"_id":{"$oid":"507f1f77bcf86cd799439011"}},"createdAt":{"$date":"2025-01-01T00:00:00Z"}}"#.to_string(),
}
);
assert!(command.is_mutating());
assert!(command.is_dangerous());
assert_eq!(validate_safety(&command, false, false, false), Err(MongoSafetyError::WritesDisabled));
assert_eq!(validate_safety(&command, true, false, false), Err(MongoSafetyError::Dangerous));
assert_eq!(validate_safety(&command, true, true, false), Ok(()));
}
#[test]
fn rejects_unsupported_run_command_shapes() {
for source in [
"db.runCommand()",
"db.runCommand({})",
"db.runCommand('ping')",
"db.runCommand({ping: 1}, {readPreference: 'primary'})",
"db.runCommand({ping: 1}).valueOf()",
"db.runCommand([1, 2, 3])",
] {
assert!(parse(source).unwrap_err().contains("runCommand"), "{source}");
}
}
#[test]
fn treats_effectively_unbounded_write_filters_as_dangerous() {
for command in [
@@ -1171,6 +1227,9 @@ mod tests {
serde_json::to_value(parse(r#"db.createUser({user: "app", pwd: "secret", roles: []})"#).unwrap()).unwrap();
assert_eq!(create_user["kind"], "createUser");
assert_eq!(create_user["userJson"], r#"{"user":"app","pwd":"secret","roles":[]}"#);
let run_command = serde_json::to_value(parse("db.runCommand({ping: 1})").unwrap()).unwrap();
assert_eq!(run_command["kind"], "runCommand");
assert_eq!(run_command["commandJson"], r#"{"ping":1}"#);
}
#[test]
@@ -0,0 +1,45 @@
//! Live MongoDB `runCommand` regression.
//!
//! Run with a reachable MongoDB server:
//! ```text
//! DBX_LIVE_MONGODB_URL='mongodb://127.0.0.1:27017' \
//! cargo test -p dbx-core --test live_mongodb_run_command -- --ignored --nocapture
//! ```
use std::time::Duration;
use dbx_core::db::mongo_driver;
#[tokio::test]
#[ignore = "requires DBX_LIVE_MONGODB_URL pointing at a MongoDB database"]
async fn run_command_returns_browser_and_extended_json_documents() {
let url = std::env::var("DBX_LIVE_MONGODB_URL").expect("DBX_LIVE_MONGODB_URL");
let client = mongo_driver::connect(&url, Duration::from_secs(10), Duration::from_secs(60)).await.unwrap();
let database = std::env::var("DBX_LIVE_MONGODB_DATABASE").unwrap_or_else(|_| "admin".to_string());
let result = mongo_driver::run_command(&client, &database, r#"{"ping":1,"comment":"DBX #3050"}"#).await.unwrap();
assert_eq!(result.total, 1);
assert!(result.total_is_exact);
assert_eq!(result.documents.len(), 1);
let extended_documents = result.extended_documents.as_ref().expect("extended JSON documents");
assert_eq!(extended_documents.len(), 1);
assert_eq!(result.documents[0]["ok"].as_f64(), Some(1.0));
assert_eq!(extended_documents[0]["ok"]["$numberDouble"].as_str(), Some("1.0"));
let build_info = mongo_driver::run_command(&client, &database, r#"{"buildInfo":1}"#).await.unwrap();
assert!(build_info.documents[0]["versionArray"].as_array().is_some());
assert!(build_info.extended_documents.as_ref().unwrap()[0]["versionArray"].as_array().is_some());
let collection = format!("dbx_run_command_{}", std::process::id());
let create =
mongo_driver::run_command(&client, &database, &serde_json::json!({ "create": &collection }).to_string())
.await
.unwrap();
assert_eq!(create.documents[0]["ok"].as_f64(), Some(1.0));
let drop = mongo_driver::run_command(&client, &database, &serde_json::json!({ "drop": &collection }).to_string())
.await
.unwrap();
assert_eq!(drop.documents[0]["ok"].as_f64(), Some(1.0));
}
+3
View File
@@ -972,6 +972,9 @@ impl DbxBackend for WebBackend {
Ok(scalar_query_result("version", Value::String(version)))
}
MongoCommand::Use { database } => Ok(scalar_query_result("database", Value::String(database.clone()))),
MongoCommand::RunCommand { .. } => {
Err("MongoDB runCommand is not available through the DBX MCP backend".to_string())
}
MongoCommand::Find { collection, filter, projection, sort, collation, skip, limit } => {
let result = self
.request(
+22
View File
@@ -1059,6 +1059,12 @@ fn validate_mongo_command(
),
)
})?;
if matches!(command, MongoCommand::RunCommand { .. }) {
return Err(tool_error(
"SQL_BLOCKED",
"MongoDB runCommand is not available through MCP; review and execute it manually in DBX.",
));
}
let permissions = mcp_permissions(connection, policy);
let production_database = match &command {
MongoCommand::Aggregate { pipeline, .. } => {
@@ -1498,6 +1504,22 @@ mod tests {
);
}
#[test]
fn mongo_run_command_is_never_exposed_through_mcp() {
let mongo = connection("mongo", "mongo", "mongodb", "staging");
let source = r#"db.runCommand({ping: 1})"#;
for policy in [
McpGlobalPolicy { read_only: true, allow_dangerous_sql: false, allowed_connection_ids: None },
McpGlobalPolicy { read_only: false, allow_dangerous_sql: false, allowed_connection_ids: None },
McpGlobalPolicy { read_only: false, allow_dangerous_sql: true, allowed_connection_ids: None },
] {
let error = validate_mongo_command(&mongo, &policy, "staging", source).unwrap_err();
assert!(result_text(&error).contains("SQL_BLOCKED"));
assert!(result_text(&error).contains("runCommand"));
}
}
#[test]
fn agent_results_preserve_stable_backend_policy_errors() {
let result = agent_result(dbx_core::agent_events::ToolResult {
+1
View File
@@ -785,6 +785,7 @@ async fn main() {
.route("/mongo/distinct", post(routes::mongo::distinct))
.route("/mongo/create-index", post(routes::mongo::create_index))
.route("/mongo/create-user", post(routes::mongo::create_user))
.route("/mongo/run-command", post(routes::mongo::run_command))
.route("/mongo/drop-indexes", post(routes::mongo::drop_indexes))
.route("/mongo/insert-document", post(routes::mongo::insert_document))
.route("/mongo/insert-documents", post(routes::mongo::insert_documents))
+32
View File
@@ -209,6 +209,15 @@ pub struct MongoCreateUserRequest {
pub write_concern_json: Option<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MongoRunCommandRequest {
pub connection_id: String,
pub database: String,
pub command_json: String,
pub execution_id: Option<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MongoDropIndexesRequest {
@@ -674,6 +683,29 @@ pub async fn create_user(
Ok(Json(serde_json::json!({ "affected_rows": affected_rows })))
}
pub async fn run_command(
State(state): State<Arc<WebState>>,
headers: HeaderMap,
Json(req): Json<MongoRunCommandRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
super::mcp_policy::ensure_dangerous_write(
&state,
&headers,
&req.connection_id,
&req.database,
"Run MongoDB command",
)
.await?;
ensure_writable(&state.app, &req.connection_id, "Run MongoDB command").await?;
let result = run_cancellable(
&state,
req.execution_id.clone(),
dbx_core::mongo_ops::mongo_run_command_core(&state.app, &req.connection_id, &req.database, &req.command_json),
)
.await?;
Ok(Json(serde_json::to_value(result).map_err(|e| AppError::from(e.to_string()))?))
}
pub async fn drop_indexes(
State(state): State<Arc<WebState>>,
headers: HeaderMap,