1313
1414import mcp .types as types
1515from mcp .server .fastmcp import FastMCP
16+ from mcp .types import ToolAnnotations
1617from pydantic import Field
1718from pydantic import validate_call
1819
@@ -80,7 +81,13 @@ def format_error_response(error: str) -> ResponseType:
8081 return format_text_response (f"Error: { error } " )
8182
8283
83- @mcp .tool (description = "List all schemas in the database" )
84+ @mcp .tool (
85+ description = "List all schemas in the database" ,
86+ annotations = ToolAnnotations (
87+ title = "List Schemas" ,
88+ readOnlyHint = True ,
89+ ),
90+ )
8491async def list_schemas () -> ResponseType :
8592 """List all schemas in the database."""
8693 try :
@@ -106,7 +113,13 @@ async def list_schemas() -> ResponseType:
106113 return format_error_response (str (e ))
107114
108115
109- @mcp .tool (description = "List objects in a schema" )
116+ @mcp .tool (
117+ description = "List objects in a schema" ,
118+ annotations = ToolAnnotations (
119+ title = "List Objects" ,
120+ readOnlyHint = True ,
121+ ),
122+ )
110123async def list_objects (
111124 schema_name : str = Field (description = "Schema name" ),
112125 object_type : str = Field (description = "Object type: 'table', 'view', 'sequence', or 'extension'" , default = "table" ),
@@ -174,7 +187,13 @@ async def list_objects(
174187 return format_error_response (str (e ))
175188
176189
177- @mcp .tool (description = "Show detailed information about a database object" )
190+ @mcp .tool (
191+ description = "Show detailed information about a database object" ,
192+ annotations = ToolAnnotations (
193+ title = "Get Object Details" ,
194+ readOnlyHint = True ,
195+ ),
196+ )
178197async def get_object_details (
179198 schema_name : str = Field (description = "Schema name" ),
180199 object_name : str = Field (description = "Object name" ),
@@ -307,7 +326,13 @@ async def get_object_details(
307326 return format_error_response (str (e ))
308327
309328
310- @mcp .tool (description = "Explains the execution plan for a SQL query, showing how the database will execute it and provides detailed cost estimates." )
329+ @mcp .tool (
330+ description = "Explains the execution plan for a SQL query, showing how the database will execute it and provides detailed cost estimates." ,
331+ annotations = ToolAnnotations (
332+ title = "Explain Query" ,
333+ readOnlyHint = True ,
334+ ),
335+ )
311336async def explain_query (
312337 sql : str = Field (description = "SQL query to explain" ),
313338 analyze : bool = Field (
@@ -402,7 +427,13 @@ async def execute_sql(
402427 return format_error_response (str (e ))
403428
404429
405- @mcp .tool (description = "Analyze frequently executed queries in the database and recommend optimal indexes" )
430+ @mcp .tool (
431+ description = "Analyze frequently executed queries in the database and recommend optimal indexes" ,
432+ annotations = ToolAnnotations (
433+ title = "Analyze Workload Indexes" ,
434+ readOnlyHint = True ,
435+ ),
436+ )
406437@validate_call
407438async def analyze_workload_indexes (
408439 max_index_size_mb : int = Field (description = "Max index size in MB" , default = 10000 ),
@@ -423,7 +454,13 @@ async def analyze_workload_indexes(
423454 return format_error_response (str (e ))
424455
425456
426- @mcp .tool (description = "Analyze a list of (up to 10) SQL queries and recommend optimal indexes" )
457+ @mcp .tool (
458+ description = "Analyze a list of (up to 10) SQL queries and recommend optimal indexes" ,
459+ annotations = ToolAnnotations (
460+ title = "Analyze Query Indexes" ,
461+ readOnlyHint = True ,
462+ ),
463+ )
427464@validate_call
428465async def analyze_query_indexes (
429466 queries : list [str ] = Field (description = "List of Query strings to analyze" ),
@@ -460,7 +497,11 @@ async def analyze_query_indexes(
460497 "- buffer - checks for buffer cache hit rates for indexes and tables\n "
461498 "- constraint - checks for invalid constraints\n "
462499 "- all - runs all checks\n "
463- "You can optionally specify a single health check or a comma-separated list of health checks. The default is 'all' checks."
500+ "You can optionally specify a single health check or a comma-separated list of health checks. The default is 'all' checks." ,
501+ annotations = ToolAnnotations (
502+ title = "Analyze Database Health" ,
503+ readOnlyHint = True ,
504+ ),
464505)
465506async def analyze_db_health (
466507 health_type : str = Field (
@@ -482,6 +523,10 @@ async def analyze_db_health(
482523@mcp .tool (
483524 name = "get_top_queries" ,
484525 description = f"Reports the slowest or most resource-intensive queries using data from the '{ PG_STAT_STATEMENTS } ' extension." ,
526+ annotations = ToolAnnotations (
527+ title = "Get Top Queries" ,
528+ readOnlyHint = True ,
529+ ),
485530)
486531async def get_top_queries (
487532 sort_by : str = Field (
@@ -546,11 +591,25 @@ async def main():
546591 global current_access_mode
547592 current_access_mode = AccessMode (args .access_mode )
548593
549- # Add the query tool with a description appropriate to the access mode
594+ # Add the query tool with a description and annotations appropriate to the access mode
550595 if current_access_mode == AccessMode .UNRESTRICTED :
551- mcp .add_tool (execute_sql , description = "Execute any SQL query" )
596+ mcp .add_tool (
597+ execute_sql ,
598+ description = "Execute any SQL query" ,
599+ annotations = ToolAnnotations (
600+ title = "Execute SQL" ,
601+ destructiveHint = True ,
602+ ),
603+ )
552604 else :
553- mcp .add_tool (execute_sql , description = "Execute a read-only SQL query" )
605+ mcp .add_tool (
606+ execute_sql ,
607+ description = "Execute a read-only SQL query" ,
608+ annotations = ToolAnnotations (
609+ title = "Execute SQL (Read-Only)" ,
610+ readOnlyHint = True ,
611+ ),
612+ )
554613
555614 logger .info (f"Starting PostgreSQL MCP Server in { current_access_mode .upper ()} mode" )
556615
0 commit comments