Skip to main content

mcp_postgres/
tools.rs

1//! Single source of truth for all MCP tools.
2//!
3//! Each entry declares the tool name, whether it requires a DB connection,
4//! and whether it is a write operation (blocked in restricted mode).
5//!
6//! Adding a tool here automatically populates existence checks and
7//! restricted-mode enforcement.  The dispatch `match` in `server.rs`
8//! must also be kept in sync — if they diverge, the test
9//! `test_tool_registry_matches_dispatch` will fail.
10
11pub struct ToolMeta {
12    pub name: &'static str,
13    pub needs_db: bool,
14    pub write: bool,
15}
16
17#[rustfmt::skip]
18pub const ALL_TOOLS: &[ToolMeta] = &[
19    // add_column
20    ToolMeta { name: "add_column",                 needs_db: true,  write: true  },
21    // add_compression_policy
22    ToolMeta { name: "add_compression_policy",     needs_db: true,  write: true  },
23    // add_continuous_aggregate
24    ToolMeta { name: "add_continuous_aggregate",   needs_db: true,  write: true  },
25    // add_foreign_key
26    ToolMeta { name: "add_foreign_key",            needs_db: true,  write: true  },
27    // add_retention_policy
28    ToolMeta { name: "add_retention_policy",       needs_db: true,  write: true  },
29    // add_unique_constraint
30    ToolMeta { name: "add_unique_constraint",      needs_db: true,  write: true  },
31    // alter_column_type
32    ToolMeta { name: "alter_column_type",          needs_db: true,  write: true  },
33    // alter_index
34    ToolMeta { name: "alter_index",                needs_db: true,  write: true  },
35    // alter_role
36    ToolMeta { name: "alter_role",                 needs_db: true,  write: true  },
37    // alter_user
38    ToolMeta { name: "alter_user",                 needs_db: true,  write: true  },
39    // alter_view
40    ToolMeta { name: "alter_view",                 needs_db: true,  write: true  },
41    // analyze_db_health
42    ToolMeta { name: "analyze_db_health",          needs_db: true,  write: false },
43    // analyze_table
44    ToolMeta { name: "analyze_table",              needs_db: true,  write: true  },
45    // analyze_table_bloat
46    ToolMeta { name: "analyze_table_bloat",        needs_db: true,  write: false },
47    // async_batch_delete
48    ToolMeta { name: "async_batch_delete",         needs_db: true,  write: true  },
49    // async_batch_insert
50    ToolMeta { name: "async_batch_insert",         needs_db: true,  write: true  },
51    // async_batch_insert_copy
52    ToolMeta { name: "async_batch_insert_copy",    needs_db: true,  write: true  },
53    // async_batch_update
54    ToolMeta { name: "async_batch_update",         needs_db: true,  write: true  },
55    // async_execute_delete
56    ToolMeta { name: "async_execute_delete",       needs_db: true,  write: true  },
57    // async_execute_insert
58    ToolMeta { name: "async_execute_insert",       needs_db: true,  write: true  },
59    // async_execute_update
60    ToolMeta { name: "async_execute_update",       needs_db: true,  write: true  },
61    // audit_role_usage
62    ToolMeta { name: "audit_role_usage",           needs_db: true,  write: false },
63    // backup_table
64    ToolMeta { name: "backup_table",               needs_db: true,  write: true  },
65    // bm25_force_merge
66    ToolMeta { name: "bm25_force_merge",           needs_db: true,  write: true  },
67    // bm25_index_stats
68    ToolMeta { name: "bm25_index_stats",           needs_db: true,  write: false },
69    // cancel_query
70    ToolMeta { name: "cancel_query",               needs_db: true,  write: true  },
71    // clone_table_schema
72    ToolMeta { name: "clone_table_schema",         needs_db: true,  write: true  },
73    // compress_chunk
74    ToolMeta { name: "compress_chunk",             needs_db: true,  write: true  },
75    // create_bm25_index
76    ToolMeta { name: "create_bm25_index",          needs_db: true,  write: true  },
77    // create_database
78    ToolMeta { name: "create_database",            needs_db: true,  write: true  },
79    // create_extension
80    ToolMeta { name: "create_extension",           needs_db: true,  write: true  },
81    // create_hypertable
82    ToolMeta { name: "create_hypertable",          needs_db: true,  write: true  },
83    // create_index
84    ToolMeta { name: "create_index",               needs_db: true,  write: true  },
85    // create_partition
86    ToolMeta { name: "create_partition",           needs_db: true,  write: true  },
87    // create_role
88    ToolMeta { name: "create_role",                needs_db: true,  write: true  },
89    // create_schema
90    ToolMeta { name: "create_schema",              needs_db: true,  write: true  },
91    // create_sequence
92    ToolMeta { name: "create_sequence",            needs_db: true,  write: true  },
93    // create_table
94    ToolMeta { name: "create_table",               needs_db: true,  write: true  },
95    // create_user
96    ToolMeta { name: "create_user",                needs_db: true,  write: true  },
97    // create_vector_index
98    ToolMeta { name: "create_vector_index",        needs_db: true,  write: true  },
99    // create_view
100    ToolMeta { name: "create_view",                needs_db: true,  write: true  },
101    // describe_table
102    ToolMeta { name: "describe_table",             needs_db: true,  write: false },
103    // drop_bm25_index
104    ToolMeta { name: "drop_bm25_index",            needs_db: true,  write: true  },
105    // drop_column
106    ToolMeta { name: "drop_column",                needs_db: true,  write: true  },
107    // drop_constraint
108    ToolMeta { name: "drop_constraint",            needs_db: true,  write: true  },
109    // drop_extension
110    ToolMeta { name: "drop_extension",             needs_db: true,  write: true  },
111    // drop_foreign_key
112    ToolMeta { name: "drop_foreign_key",           needs_db: true,  write: true  },
113    // drop_index
114    ToolMeta { name: "drop_index",                 needs_db: true,  write: true  },
115    // drop_partition
116    ToolMeta { name: "drop_partition",             needs_db: true,  write: true  },
117    // drop_role
118    ToolMeta { name: "drop_role",                  needs_db: true,  write: true  },
119    // drop_schema
120    ToolMeta { name: "drop_schema",                needs_db: true,  write: true  },
121    // drop_sequence
122    ToolMeta { name: "drop_sequence",              needs_db: true,  write: true  },
123    // drop_table
124    ToolMeta { name: "drop_table",                 needs_db: true,  write: true  },
125    // drop_user
126    ToolMeta { name: "drop_user",                  needs_db: true,  write: true  },
127    // drop_view
128    ToolMeta { name: "drop_view",                  needs_db: true,  write: true  },
129    // execute_delete
130    ToolMeta { name: "execute_delete",             needs_db: true,  write: true  },
131    // execute_insert
132    ToolMeta { name: "execute_insert",             needs_db: true,  write: true  },
133    // execute_query
134    ToolMeta { name: "execute_query",              needs_db: true,  write: false },
135    // execute_update
136    ToolMeta { name: "execute_update",             needs_db: true,  write: true  },
137    // explain_query
138    ToolMeta { name: "explain_query",              needs_db: true,  write: false },
139    // export_csv
140    ToolMeta { name: "export_csv",                 needs_db: true,  write: false },
141    // find_missing_fk_indexes
142    ToolMeta { name: "find_missing_fk_indexes",    needs_db: true,  write: false },
143    // find_tables_without_pk
144    ToolMeta { name: "find_tables_without_pk",     needs_db: true,  write: false },
145    // generate_create_index_ddl
146    ToolMeta { name: "generate_create_index_ddl",  needs_db: true,  write: false },
147    // generate_create_table_ddl
148    ToolMeta { name: "generate_create_table_ddl",  needs_db: true,  write: false },
149    // get_cache_hit_ratio
150    ToolMeta { name: "get_cache_hit_ratio",        needs_db: true,  write: false },
151    // get_index_stats
152    ToolMeta { name: "get_index_stats",            needs_db: true,  write: false },
153    // get_object_details
154    ToolMeta { name: "get_object_details",         needs_db: true,  write: false },
155    // get_pg_stat_statements
156    ToolMeta { name: "get_pg_stat_statements",     needs_db: true,  write: false },
157    // get_setting
158    ToolMeta { name: "get_setting",                needs_db: true,  write: false },
159    // get_table_stats
160    ToolMeta { name: "get_table_stats",            needs_db: true,  write: false },
161    // grant_privileges
162    ToolMeta { name: "grant_privileges",           needs_db: true,  write: true  },
163    // import_from_url
164    ToolMeta { name: "import_from_url",            needs_db: true,  write: true  },
165    // list_bm25_indexes
166    ToolMeta { name: "list_bm25_indexes",          needs_db: true,  write: false },
167    // list_connections
168    ToolMeta { name: "list_connections",           needs_db: true,  write: false },
169    // list_database_privileges
170    ToolMeta { name: "list_database_privileges",   needs_db: true,  write: false },
171    // list_databases
172    ToolMeta { name: "list_databases",             needs_db: true,  write: false },
173    // list_duplicate_indexes
174    ToolMeta { name: "list_duplicate_indexes",     needs_db: true,  write: false },
175    // list_extensions
176    ToolMeta { name: "list_extensions",            needs_db: true,  write: false },
177    // list_indexes
178    ToolMeta { name: "list_indexes",               needs_db: true,  write: false },
179    // list_partitions
180    ToolMeta { name: "list_partitions",            needs_db: true,  write: false },
181    // list_replication_slots
182    ToolMeta { name: "list_replication_slots",     needs_db: true,  write: false },
183    // list_role_memberships
184    ToolMeta { name: "list_role_memberships",      needs_db: true,  write: false },
185    // list_schemas
186    ToolMeta { name: "list_schemas",               needs_db: false, write: false },
187    // list_standby_servers
188    ToolMeta { name: "list_standby_servers",       needs_db: true,  write: false },
189    // list_tables
190    ToolMeta { name: "list_tables",                needs_db: false, write: false },
191    // list_triggers
192    ToolMeta { name: "list_triggers",              needs_db: true,  write: false },
193    // list_unused_indexes
194    ToolMeta { name: "list_unused_indexes",        needs_db: true,  write: false },
195    // list_user_privileges
196    ToolMeta { name: "list_user_privileges",       needs_db: true,  write: false },
197    // list_users
198    ToolMeta { name: "list_users",                 needs_db: true,  write: false },
199    // list_vector_columns
200    ToolMeta { name: "list_vector_columns",        needs_db: true,  write: false },
201    // reindex_database
202    ToolMeta { name: "reindex_database",           needs_db: true,  write: true  },
203    // reindex_table
204    ToolMeta { name: "reindex_table",              needs_db: true,  write: true  },
205    // rename_column
206    ToolMeta { name: "rename_column",              needs_db: true,  write: true  },
207    // rename_index
208    ToolMeta { name: "rename_index",               needs_db: true,  write: true  },
209    // rename_schema
210    ToolMeta { name: "rename_schema",              needs_db: true,  write: true  },
211    // rename_table
212    ToolMeta { name: "rename_table",               needs_db: true,  write: true  },
213    // reset_statistics
214    ToolMeta { name: "reset_statistics",           needs_db: true,  write: true  },
215    // revoke_privileges
216    ToolMeta { name: "revoke_privileges",          needs_db: true,  write: true  },
217    // sample_data
218    ToolMeta { name: "sample_data",                needs_db: true,  write: false },
219    // search_bm25
220    ToolMeta { name: "search_bm25",                needs_db: true,  write: false },
221    // security_audit
222    ToolMeta { name: "security_audit",             needs_db: true,  write: false },
223    // show_active_transactions
224    ToolMeta { name: "show_active_transactions",   needs_db: true,  write: false },
225    // show_all_settings
226    ToolMeta { name: "show_all_settings",          needs_db: true,  write: false },
227    // show_autocommit_status
228    ToolMeta { name: "show_autocommit_status",     needs_db: true,  write: false },
229    // show_base_backup_progress
230    ToolMeta { name: "show_base_backup_progress",  needs_db: true,  write: false },
231    // show_blocked_queries
232    ToolMeta { name: "show_blocked_queries",       needs_db: true,  write: false },
233    // show_chunks
234    ToolMeta { name: "show_chunks",                needs_db: true,  write: false },
235    // show_connection_summary
236    ToolMeta { name: "show_connection_summary",    needs_db: true,  write: false },
237    // show_constraints
238    ToolMeta { name: "show_constraints",           needs_db: false, write: false },
239    // show_current_user
240    ToolMeta { name: "show_current_user",          needs_db: true,  write: false },
241    // show_database_size
242    ToolMeta { name: "show_database_size",         needs_db: true,  write: false },
243    // show_deadlocks
244    ToolMeta { name: "show_deadlocks",             needs_db: true,  write: false },
245    // show_hypertable_details
246    ToolMeta { name: "show_hypertable_details",    needs_db: true,  write: false },
247    // show_locks
248    ToolMeta { name: "show_locks",                 needs_db: true,  write: false },
249    // show_log_settings
250    ToolMeta { name: "show_log_settings",          needs_db: true,  write: false },
251    // show_memory_settings
252    ToolMeta { name: "show_memory_settings",       needs_db: true,  write: false },
253    // show_performance_settings
254    ToolMeta { name: "show_performance_settings",  needs_db: true,  write: false },
255    // show_replication_status
256    ToolMeta { name: "show_replication_status",    needs_db: true,  write: false },
257    // show_running_queries
258    ToolMeta { name: "show_running_queries",       needs_db: true,  write: false },
259    // show_session_info
260    ToolMeta { name: "show_session_info",          needs_db: true,  write: false },
261    // show_table_size
262    ToolMeta { name: "show_table_size",            needs_db: true,  write: false },
263    // show_transaction_isolation
264    ToolMeta { name: "show_transaction_isolation", needs_db: true,  write: false },
265    // show_transaction_timeout
266    ToolMeta { name: "show_transaction_timeout",   needs_db: true,  write: false },
267    // show_vacuum_progress
268    ToolMeta { name: "show_vacuum_progress",       needs_db: true,  write: false },
269    // show_waiting_locks
270    ToolMeta { name: "show_waiting_locks",         needs_db: true,  write: false },
271    // show_wal_info
272    ToolMeta { name: "show_wal_info",              needs_db: true,  write: false },
273    // suggest_indexes
274    ToolMeta { name: "suggest_indexes",            needs_db: true,  write: false },
275    // table_dependencies
276    ToolMeta { name: "table_dependencies",         needs_db: true,  write: false },
277    // terminate_connection
278    ToolMeta { name: "terminate_connection",       needs_db: true,  write: true  },
279    // truncate_table
280    ToolMeta { name: "truncate_table",             needs_db: true,  write: true  },
281    // vacuum
282    ToolMeta { name: "vacuum",                     needs_db: true,  write: true  },
283    // vacuum_analyze
284    ToolMeta { name: "vacuum_analyze",             needs_db: true,  write: true  },
285    // vacuum_full
286    ToolMeta { name: "vacuum_full",                needs_db: true,  write: true  },
287    // vector_search
288    ToolMeta { name: "vector_search",              needs_db: true,  write: false },
289];
290
291#[inline]
292pub fn tool_exists(name: &str) -> bool {
293    ALL_TOOLS.binary_search_by(|t| t.name.as_bytes().cmp(name.as_bytes())).is_ok()
294}
295
296#[inline]
297pub fn is_write_tool(name: &str) -> bool {
298    ALL_TOOLS.binary_search_by(|t| t.name.as_bytes().cmp(name.as_bytes()))
299        .map(|i| ALL_TOOLS[i].write)
300        .unwrap_or(false)
301}
302
303#[inline]
304pub fn needs_db(name: &str) -> bool {
305    ALL_TOOLS.binary_search_by(|t| t.name.as_bytes().cmp(name.as_bytes()))
306        .map(|i| ALL_TOOLS[i].needs_db)
307        .unwrap_or(false)
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_all_tools_unique() {
316        let mut names: Vec<&str> = ALL_TOOLS.iter().map(|t| t.name).collect();
317        names.sort();
318        names.dedup();
319        assert_eq!(names.len(), ALL_TOOLS.len(), "Duplicate tool names in ALL_TOOLS");
320    }
321
322    #[test]
323    fn test_tool_exists_known() {
324        assert!(tool_exists("execute_query"));
325        assert!(tool_exists("list_tables"));
326        assert!(tool_exists("import_from_url"));
327    }
328
329    #[test]
330    fn test_tool_exists_unknown() {
331        assert!(!tool_exists("nonexistent_tool"));
332    }
333
334    #[test]
335    fn test_is_write_tool() {
336        assert!(is_write_tool("create_table"));
337        assert!(is_write_tool("import_from_url"));
338        assert!(!is_write_tool("execute_query"));
339        assert!(!is_write_tool("list_tables"));
340    }
341
342    #[test]
343    fn test_needs_db() {
344        assert!(needs_db("execute_query"));
345        assert!(!needs_db("list_tables"));
346        assert!(!needs_db("list_schemas"));
347        assert!(!needs_db("show_constraints"));
348    }
349
350    #[test]
351    fn test_all_tools_registered_in_tools_json() {
352        let content = std::fs::read_to_string("tools.json")
353            .expect("Failed to read tools.json");
354        let json: serde_json::Value = serde_json::from_str(&content)
355            .expect("tools.json is not valid JSON");
356        let json_tools = json.as_array().expect("tools.json must be an array");
357
358        let json_names: Vec<&str> = json_tools.iter()
359            .filter_map(|t| t.get("name").and_then(|n| n.as_str()))
360            .collect();
361
362        for meta in ALL_TOOLS {
363            assert!(
364                json_names.contains(&meta.name),
365                "Tool '{}' is in ALL_TOOLS but missing from tools.json",
366                meta.name,
367            );
368        }
369
370        for name in &json_names {
371            assert!(
372                tool_exists(name),
373                "Tool '{}' is in tools.json but missing from ALL_TOOLS",
374                name,
375            );
376        }
377
378        assert_eq!(json_names.len(), ALL_TOOLS.len(),
379            "tools.json has {} tools but ALL_TOOLS has {}",
380            json_names.len(), ALL_TOOLS.len());
381    }
382}