feat(mcp): include saved notes in connection listings

This commit is contained in:
ninggc
2026-10-08 21:30:48 +08:00
committed by GitHub
parent 05a57cbfde
commit f262f5c80c
6 changed files with 68 additions and 13 deletions
+11
View File
@@ -35,6 +35,7 @@ pub struct ConnectionSummary {
pub port: u16,
pub database: String,
pub group_path: Vec<String>,
pub note: String,
}
impl From<&ConnectionConfig> for ConnectionSummary {
@@ -51,6 +52,7 @@ impl From<&ConnectionConfig> for ConnectionSummary {
port: config.port,
database: config.database.clone().unwrap_or_default(),
group_path: Vec::new(),
note: config.note.clone(),
}
}
}
@@ -2967,6 +2969,15 @@ mod tests {
.unwrap()
}
#[test]
fn connection_summary_preserves_notes() {
let mut config = mutation_test_connection();
for note in ["", "Application database | staging\nRead-only queries"] {
config.note = note.to_string();
assert_eq!(ConnectionSummary::from(&config).note, note);
}
}
#[tokio::test]
async fn local_connection_mutations_notify_desktop_only_after_success() {
let data_dir = tempfile::tempdir().unwrap();
+11 -4
View File
@@ -1178,7 +1178,7 @@ impl DbxMcpServer {
#[tool(
name = "dbx_list_connections",
description = "List database connections configured in DBX. Returns connection IDs, names, group paths, database types, endpoints, and selected databases."
description = "List database connections configured in DBX. Returns connection IDs, names, group paths, database types, endpoints, selected databases, and saved connection notes."
)]
async fn list_connections(
&self,
@@ -3774,7 +3774,9 @@ impl ServerHandler for DbxMcpServer {
.then(|| {
Resource::new(CONNECTIONS_RESOURCE_URI, "dbx_connections")
.with_title("DBX connections")
.with_description("Database connections visible to the current DBX MCP scope")
.with_description(
"Database connections visible to the current DBX MCP scope, including saved notes",
)
.with_mime_type("text/markdown")
})
.into_iter()
@@ -5011,11 +5013,11 @@ fn ambiguous_connections(name: &str, connections: &[dbx_core::models::connection
fn format_connections(connections: &[ConnectionSummary]) -> String {
let mut output = String::from(
"| ID | Name | Group Path | Type | Host | Port | Database |\n| --- | --- | --- | --- | --- | --- | --- |",
"| ID | Name | Group Path | Type | Host | Port | Database | Note |\n| --- | --- | --- | --- | --- | --- | --- | --- |",
);
for connection in connections {
output.push_str(&format!(
"\n| {} | {} | {} | {} | {} | {} | {} |",
"\n| {} | {} | {} | {} | {} | {} | {} | {} |",
escape_cell(&connection.id),
escape_cell(&connection.name),
escape_cell(&connection.group_path.join(" / ")),
@@ -5023,6 +5025,7 @@ fn format_connections(connections: &[ConnectionSummary]) -> String {
escape_cell(&connection.host),
connection.port,
escape_cell(&connection.database),
escape_cell(&connection.note),
));
}
output
@@ -6261,10 +6264,14 @@ mod tests {
port: 5432,
database: "app".to_string(),
group_path: vec!["Project|A".to_string(), "Staging\nWest".to_string()],
note: "Application | staging\nRead-only queries".to_string(),
}]);
assert!(output.contains("id\\|1"));
assert!(output.contains("local pg"));
assert!(output.contains("Project\\|A / Staging West"));
assert!(output.contains("| Database | Note |"));
assert!(output.contains("| Application \\| staging Read-only queries |"));
assert_eq!(output.lines().count(), 3);
}
#[test]
+37 -6
View File
@@ -572,6 +572,10 @@ async fn remove_connection_respects_global_connection_scope() {
#[tokio::test]
async fn enforces_global_connection_scope_and_read_only_policy() {
let mut allowed = test_connection("allowed", "shared-db");
allowed.note = "Allowed connection note".to_string();
let mut blocked_connection = test_connection("blocked", "blocked-db");
blocked_connection.note = "Hidden connection note".to_string();
let backend = PolicyBackend {
policy: McpGlobalPolicy {
read_only: true,
@@ -579,11 +583,7 @@ async fn enforces_global_connection_scope_and_read_only_policy() {
allowed_connection_ids: Some(vec!["allowed".to_string(), "allowed-staging".to_string()]),
..Default::default()
},
connections: vec![
test_connection("allowed", "shared-db"),
test_connection("allowed-staging", "shared-db"),
test_connection("blocked", "blocked-db"),
],
connections: vec![allowed, test_connection("allowed-staging", "shared-db"), blocked_connection],
group_paths: Ok(HashMap::from([
("allowed".to_string(), vec!["Project".to_string(), "Production".to_string()]),
("allowed-staging".to_string(), vec!["Project".to_string(), "Staging".to_string()]),
@@ -603,6 +603,18 @@ async fn enforces_global_connection_scope_and_read_only_policy() {
assert!(listed_text.contains("Project / Production"));
assert!(listed_text.contains("Project / Staging"));
assert!(!listed_text.contains("Secret"));
assert!(listed_text.contains("Allowed connection note"));
assert!(!listed_text.contains("Hidden connection note"));
let resource = client
.peer()
.read_resource(ReadResourceRequestParams::new("dbx://connections"))
.await
.expect("read scoped connections resource");
let ResourceContents::TextResourceContents { text, .. } = &resource.contents[0] else {
panic!("connections resource should be text");
};
assert_eq!(text, &listed_text);
let blocked = client
.peer()
@@ -763,6 +775,8 @@ async fn connection_group_path_failure_preserves_connection_listing() {
assert_ne!(listed.is_error, Some(true));
assert!(listed_text.contains("| ID | Name | Group Path |"));
assert!(listed_text.contains("local-db"));
assert!(listed_text.contains("| Database | Note |"));
assert!(listed_text.contains("| :memory: | |"));
client.cancel().await.expect("close MCP client");
server_task.abort();
@@ -770,9 +784,13 @@ async fn connection_group_path_failure_preserves_connection_listing() {
#[tokio::test]
async fn runtime_connection_scope_preserves_group_paths() {
let mut scoped = test_connection("scoped", "shared-db");
scoped.note = "业务库 | TEST\n只读查询".to_string();
let mut outside = test_connection("outside", "shared-db");
outside.note = "Out-of-scope connection note".to_string();
let backend = PolicyBackend {
policy: McpGlobalPolicy::default(),
connections: vec![test_connection("scoped", "shared-db"), test_connection("outside", "shared-db")],
connections: vec![scoped, outside],
group_paths: Ok(HashMap::from([
("scoped".to_string(), vec!["Project".to_string(), "Production".to_string()]),
("outside".to_string(), vec!["Project".to_string(), "Staging".to_string()]),
@@ -793,6 +811,19 @@ async fn runtime_connection_scope_preserves_group_paths() {
assert!(listed_text.contains("| scoped | shared-db | Project / Production |"));
assert!(!listed_text.contains("outside"));
assert!(!listed_text.contains("Project / Staging"));
assert!(listed_text.contains("业务库 \\| TEST 只读查询"));
assert!(!listed_text.contains("Out-of-scope connection note"));
assert_eq!(listed_text.lines().count(), 3);
let resource = client
.peer()
.read_resource(ReadResourceRequestParams::new("dbx://connections"))
.await
.expect("read scoped connections resource");
let ResourceContents::TextResourceContents { text, .. } = &resource.contents[0] else {
panic!("connections resource should be text");
};
assert_eq!(text, &listed_text);
client.cancel().await.expect("close MCP client");
server_task.abort();
+3 -1
View File
@@ -122,7 +122,7 @@ DBX MCP 当前提供以下工具(具体可用工具取决于构建功能及权
| 工具 | 说明 |
| --- | --- |
| `dbx_list_connections` | 列出当前 MCP 会话可见的连接 |
| `dbx_list_connections` | 列出当前 MCP 会话可见的连接,包括已保存的备注 |
| `dbx_list_databases` | 列出连接中可通过 MCP 访问的数据库,并遵守该连接的数据库范围 |
| `dbx_add_connection` | 添加连接到 DBX 存储 |
| `dbx_duplicate_connection` | 复制一个连接及其完整配置 |
@@ -148,6 +148,8 @@ DBX MCP 当前提供以下工具(具体可用工具取决于构建功能及权
| `dbx_open_table` | 在运行中的 DBX 桌面端打开表 |
| `dbx_execute_and_show` | 执行查询并在 DBX 中展示结果 |
`dbx_list_connections` 和 `dbx://connections` 会返回已保存的连接备注,不受侧边栏显示开关影响。可访问该连接的 MCP 客户端也能看到备注,请勿在备注中保存密码或其他敏感信息。
`dbx_execute_query` 返回行数:
- `max_rows`:取值 1–1000,默认 100。越界值会被夹取到范围内,而不是报错。仅对 SQL 连接生效——MongoDB shell 命令始终最多返回 100 行,路由到批量执行器的多语句脚本每条语句最多返回 100 行。
+3 -1
View File
@@ -122,7 +122,7 @@ DBX MCP provides the following tools (availability depends on build features and
| Tool | Description |
| --- | --- |
| `dbx_list_connections` | List connections visible to the MCP session |
| `dbx_list_connections` | List connections visible to the MCP session, including saved notes |
| `dbx_list_databases` | List databases available through a connection, respecting its MCP database scope |
| `dbx_add_connection` | Add a connection to DBX storage |
| `dbx_duplicate_connection` | Duplicate a DBX connection with its complete settings |
@@ -148,6 +148,8 @@ DBX MCP provides the following tools (availability depends on build features and
| `dbx_open_table` | Open a table in the running DBX desktop app |
| `dbx_execute_and_show` | Execute a query and display the result in DBX |
`dbx_list_connections` and `dbx://connections` include saved connection notes regardless of the sidebar display setting. Notes are visible to MCP clients that can access the connection; do not store passwords or other secrets in notes.
`dbx_execute_query` row controls:
- `max_rows`: 1–1000, defaults to 100. Out-of-range values are clamped, not rejected. It applies to SQL connections only — MongoDB shell commands always return at most 100 rows, and multi-statement scripts routed to the batch executor return at most 100 rows per statement.
+3 -1
View File
@@ -140,9 +140,11 @@ Ask the MCP client to:
## Tools
`dbx_list_connections` and `dbx://connections` include saved connection notes regardless of the sidebar display setting. Notes are visible to MCP clients that can access the connection; do not store passwords or other secrets in notes.
| Tool | Description |
| --- | --- |
| `dbx_list_connections` | List connections visible to the MCP session |
| `dbx_list_connections` | List connections visible to the MCP session, including saved notes |
| `dbx_list_databases` | List databases available through a connection, respecting its MCP database scope |
| `dbx_add_connection` | Add a connection to DBX storage |
| `dbx_duplicate_connection` | Duplicate a DBX connection with its complete settings |