mcp_postgres/actions/
security.rs1use serde_json::{json, Value};
2use tokio_postgres::Client;
3use crate::errors::Result as MCPResult;
4
5const MAX_IDENTIFIER_LEN: usize = 255;
6
7pub async fn list_users(client: &Client, _params: &Option<&Value>) -> MCPResult<Value> {
9 let rows = client
12 .query(
13 "SELECT usename, usesuper, usecreatedb, valuntil::text
14 FROM pg_user
15 ORDER BY usename",
16 &[],
17 )
18 .await?;
19
20 let users: Vec<Value> = rows
21 .iter()
22 .map(|row| {
23 json!({
24 "username": row.get::<_, String>(0),
25 "superuser": row.get::<_, bool>(1),
26 "createdb": row.get::<_, bool>(2),
27 "valid_until": row.get::<_, Option<String>>(3),
28 })
29 })
30 .collect();
31
32 Ok(json!({ "users": users }))
33}
34
35pub async fn list_user_privileges(client: &Client, params: &Option<&Value>) -> MCPResult<Value> {
37 let username = params
38 .as_ref()
39 .and_then(|p| p.get("username").and_then(|v| v.as_str()))
40 .ok_or_else(|| crate::errors::MCPError::InvalidParams("Missing 'username' parameter".into()))?;
41
42 if username.is_empty() || username.len() > MAX_IDENTIFIER_LEN {
43 return Err(crate::errors::MCPError::InvalidParams(
44 format!("'username' must be 1-{MAX_IDENTIFIER_LEN} characters")
45 ));
46 }
47
48 let rows = client
49 .query(
50 "SELECT grantee, table_schema, table_name, privilege_type
51 FROM information_schema.role_table_grants
52 WHERE grantee = $1
53 ORDER BY table_schema, table_name, privilege_type",
54 &[&username],
55 )
56 .await?;
57
58 let privileges: Vec<Value> = rows
59 .iter()
60 .map(|row| {
61 json!({
62 "grantee": row.get::<_, String>(0),
63 "schema": row.get::<_, String>(1),
64 "table": row.get::<_, String>(2),
65 "privilege": row.get::<_, String>(3),
66 })
67 })
68 .collect();
69
70 Ok(json!({ "privileges": privileges }))
71}
72
73pub async fn list_role_memberships(client: &Client, _params: &Option<&Value>) -> MCPResult<Value> {
75 let rows = client
76 .query(
77 "SELECT member.usename as member, role.usename as role, admin_option
78 FROM pg_auth_members
79 JOIN pg_user member ON member.usesysid = pg_auth_members.member
80 JOIN pg_user role ON role.usesysid = pg_auth_members.roleid
81 ORDER BY member.usename, role.usename",
82 &[],
83 )
84 .await?;
85
86 let memberships: Vec<Value> = rows
87 .iter()
88 .map(|row| {
89 json!({
90 "member": row.get::<_, String>(0),
91 "role": row.get::<_, String>(1),
92 "admin": row.get::<_, bool>(2),
93 })
94 })
95 .collect();
96
97 Ok(json!({ "memberships": memberships }))
98}
99
100pub async fn list_database_privileges(client: &Client, _params: &Option<&Value>) -> MCPResult<Value> {
102 let rows = client
103 .query(
104 "SELECT datname, datacl::text
105 FROM pg_database
106 WHERE datistemplate = false
107 ORDER BY datname",
108 &[],
109 )
110 .await?;
111
112 let databases: Vec<Value> = rows
113 .iter()
114 .map(|row| {
115 json!({
116 "database": row.get::<_, String>(0),
117 "acl": row.get::<_, Option<String>>(1),
118 })
119 })
120 .collect();
121
122 Ok(json!({ "databases": databases }))
123}
124
125pub async fn show_session_info(client: &Client, _params: &Option<&Value>) -> MCPResult<Value> {
127 let rows = client
128 .query(
129 "SELECT current_user, current_database(), inet_client_addr()::text,
130 inet_client_port(), inet_server_addr()::text, inet_server_port()",
131 &[],
132 )
133 .await?;
134
135 let row = &rows[0];
136
137 Ok(json!({
138 "current_user": row.get::<_, String>(0),
139 "current_database": row.get::<_, String>(1),
140 "client_address": row.get::<_, Option<String>>(2),
141 "client_port": row.get::<_, Option<i32>>(3),
142 "server_address": row.get::<_, Option<String>>(4),
143 "server_port": row.get::<_, Option<i32>>(5),
144 }))
145}