mcp_postgres/actions/
security.rs1use crate::errors::Result as MCPResult;
2use serde_json::{Value, json};
3use tokio_postgres::Client;
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(|| {
41 crate::errors::MCPError::InvalidParams("Missing 'username' parameter".into())
42 })?;
43
44 if username.is_empty() || username.len() > MAX_IDENTIFIER_LEN {
45 return Err(crate::errors::MCPError::InvalidParams(format!(
46 "'username' must be 1-{MAX_IDENTIFIER_LEN} characters"
47 )));
48 }
49
50 let rows = client
51 .query(
52 "SELECT grantee, table_schema, table_name, privilege_type
53 FROM information_schema.role_table_grants
54 WHERE grantee = $1
55 ORDER BY table_schema, table_name, privilege_type",
56 &[&username],
57 )
58 .await?;
59
60 let privileges: Vec<Value> = rows
61 .iter()
62 .map(|row| {
63 json!({
64 "grantee": row.get::<_, String>(0),
65 "schema": row.get::<_, String>(1),
66 "table": row.get::<_, String>(2),
67 "privilege": row.get::<_, String>(3),
68 })
69 })
70 .collect();
71
72 Ok(json!({ "privileges": privileges }))
73}
74
75pub async fn list_role_memberships(client: &Client, _params: &Option<&Value>) -> MCPResult<Value> {
77 let rows = client
78 .query(
79 "SELECT member.usename as member, role.usename as role, admin_option
80 FROM pg_auth_members
81 JOIN pg_user member ON member.usesysid = pg_auth_members.member
82 JOIN pg_user role ON role.usesysid = pg_auth_members.roleid
83 ORDER BY member.usename, role.usename",
84 &[],
85 )
86 .await?;
87
88 let memberships: Vec<Value> = rows
89 .iter()
90 .map(|row| {
91 json!({
92 "member": row.get::<_, String>(0),
93 "role": row.get::<_, String>(1),
94 "admin": row.get::<_, bool>(2),
95 })
96 })
97 .collect();
98
99 Ok(json!({ "memberships": memberships }))
100}
101
102pub async fn list_database_privileges(
104 client: &Client,
105 _params: &Option<&Value>,
106) -> MCPResult<Value> {
107 let rows = client
108 .query(
109 "SELECT datname, datacl::text
110 FROM pg_database
111 WHERE datistemplate = false
112 ORDER BY datname",
113 &[],
114 )
115 .await?;
116
117 let databases: Vec<Value> = rows
118 .iter()
119 .map(|row| {
120 json!({
121 "database": row.get::<_, String>(0),
122 "acl": row.get::<_, Option<String>>(1),
123 })
124 })
125 .collect();
126
127 Ok(json!({ "databases": databases }))
128}
129
130pub async fn show_session_info(client: &Client, _params: &Option<&Value>) -> MCPResult<Value> {
132 let rows = client
133 .query(
134 "SELECT current_user, current_database(), inet_client_addr()::text,
135 inet_client_port(), inet_server_addr()::text, inet_server_port()",
136 &[],
137 )
138 .await?;
139
140 let row = &rows[0];
141
142 Ok(json!({
143 "current_user": row.get::<_, String>(0),
144 "current_database": row.get::<_, String>(1),
145 "client_address": row.get::<_, Option<String>>(2),
146 "client_port": row.get::<_, Option<i32>>(3),
147 "server_address": row.get::<_, Option<String>>(4),
148 "server_port": row.get::<_, Option<i32>>(5),
149 }))
150}