@@ -8,20 +8,255 @@ import (
88// Backend defines the interface for executing queries from MCP clients.
99// This abstraction allows for different backend implementations (in-memory, TCP, etc.)
1010// while maintaining compatibility with the MCP protocol.
11- type Backend interface {
12- // Execute runs a query and returns the results.
13- // The query string and parameters are provided by the MCP client.
14- Execute (ctx context.Context , query string , params map [string ]interface {}) (QueryResult , error )
1511
16- // GetSchema returns metadata about available resources and their structure.
17- // This is used by MCP clients to understand what data is available.
18- GetSchema (ctx context.Context ) (SchemaProvider , error )
12+ /*
13+ The Backend interface should include all of the tools from the below python snippet:
14+
15+ ```python
16+
17+ @mcp.tool()
18+ def server_info() -> Dict[str, Any]:
19+
20+ """Return server and environment info useful for clients."""
21+ return _BACKEND.server_info()
22+
23+ @mcp.tool()
24+ def db_identity() -> Dict[str, Any]:
25+
26+ """Return current DB identity details: db, user, host, port, search_path, server version, cluster name."""
27+ return _BACKEND.db_identity()
28+
29+ @mcp.tool()
30+ def query(
31+
32+ sql: str,
33+ parameters: Optional[List[Any]] = None,
34+ row_limit: int = 500,
35+ format: str = "markdown",
36+
37+ ) -> str:
38+
39+ """Execute a SQL query (legacy signature). Prefer run_query with typed input."""
40+ return _BACKEND.query(sql, parameters, row_limit, format)
41+
42+ @mcp.tool()
43+ def query_json(sql: str, parameters: Optional[List[Any]] = None, row_limit: int = 500) -> List[Dict[str, Any]]:
44+
45+ """Execute a SQL query and return JSON-serializable rows (legacy signature). Prefer run_query_json with typed input."""
46+ return _BACKEND.query_json(sql, parameters, row_limit)
47+
48+ @mcp.tool()
49+ def run_query(input: QueryInput) -> str:
50+
51+ """Execute a SQL query with typed input (preferred)."""
52+ return _BACKEND.run_query(input)
53+
54+ @mcp.tool()
55+ def run_query_json(input: QueryJSONInput) -> List[Dict[str, Any]]:
56+
57+ """Execute a SQL query and return JSON rows with typed input (preferred)."""
58+ return _BACKEND.run_query_json(input)
59+
60+ @mcp.tool()
61+ def list_table_resources(schema: str = 'public') -> List[str]:
62+
63+ """List resource URIs for tables in a schema (fallback for clients without resource support)."""
64+ return _BACKEND.list_table_resources(schema)
65+
66+ @mcp.tool()
67+ def read_table_resource(schema: str, table: str, row_limit: int = 100) -> List[Dict[str, Any]]:
68+
69+ """Read rows from a table resource (fallback)."""
70+ return _BACKEND.read_table_resource(schema, table, row_limit)
71+
72+ # Try to register proper MCP resources if available in FastMCP
73+
74+ try:
75+
76+ resource_decorator = getattr(mcp, "resource")
77+ if callable(resource_decorator):
78+ @resource_decorator("table://{schema}/{table}") # type: ignore
79+ def table_resource(schema: str, table: str, row_limit: int = 100):
80+ """Resource reader for table rows."""
81+ rows = _BACKEND.read_table_resource(schema, table, row_limit=row_limit)
82+ # Return as JSON string to be universally consumable
83+ return json.dumps(rows, default=str)
84+
85+ except Exception as e:
86+
87+ logger.debug(f"Resource registration skipped: {e}")
88+
89+ try:
90+
91+ prompt_decorator = getattr(mcp, "prompt")
92+ if callable(prompt_decorator):
93+ @prompt_decorator("write_safe_select") # type: ignore
94+ def prompt_write_safe_select():
95+ return _BACKEND.prompt_write_safe_select_tool()
96+
97+ @prompt_decorator("explain_plan_tips") # type: ignore
98+ def prompt_explain_plan_tips():
99+ return _BACKEND.prompt_explain_plan_tips_tool()
100+
101+ except Exception as e:
102+
103+ logger.debug(f"Prompt registration skipped: {e}")
104+
105+ #
106+
107+ @mcp.tool()
108+ def prompt_write_safe_select_tool() -> str:
109+
110+ """Prompt: guidelines for writing safe SELECT queries."""
111+ return _BACKEND.prompt_write_safe_select_tool()
112+
113+ @mcp.tool()
114+ def prompt_explain_plan_tips_tool() -> str:
115+
116+ """Prompt: tips for reading EXPLAIN ANALYZE output."""
117+ return _BACKEND.prompt_explain_plan_tips_tool()
118+
119+ @mcp.tool()
120+ def list_schemas_json(input: ListSchemasInput) -> List[Dict[str, Any]]:
121+
122+ """List schemas with filters and return JSON rows."""
123+ return _BACKEND.list_schemas_json(input)
124+
125+ @mcp.tool()
126+ def list_schemas_json_page(input: ListSchemasPageInput) -> Dict[str, Any]:
127+
128+ """List schemas with pagination and filters. Returns { items: [...], next_cursor: str|null }"""
129+ return _BACKEND.list_schemas_json_page(input)
130+
131+ @mcp.tool()
132+ def list_tables_json(input: ListTablesInput) -> List[Dict[str, Any]]:
133+
134+ """List tables in a schema with optional filters and return JSON rows."""
135+ return _BACKEND.list_tables_json(input)
136+
137+ @mcp.tool()
138+ def list_tables_json_page(input: ListTablesPageInput) -> Dict[str, Any]:
139+
140+ """List tables with pagination and filters. Returns { items, next_cursor }."""
141+ return _BACKEND.list_tables_json_page(input)
142+
143+ @mcp.tool()
144+ def list_schemas() -> str:
145+
146+ """List all schemas in the database."""
147+ return _BACKEND.list_schemas()
148+
149+ @mcp.tool()
150+ def list_tables(db_schema: Optional[str] = None) -> str:
151+
152+ """List all tables in a specific schema.
153+
154+ Args:
155+ db_schema: The schema name to list tables from (defaults to 'public')
156+ """
157+ return _BACKEND.list_tables(db_schema)
158+
159+ @mcp.tool()
160+ def describe_table(table_name: str, db_schema: Optional[str] = None) -> str:
161+
162+ """Get detailed information about a table.
163+ When dealing with a stackql backend (ie: when the server is initialised to consume stackql using the 'dbapp' parameter), the required query input and returned schema can differ even across the one "resource" (table) object.
164+ This is because stackql has required where parameters for some access methods, where this can vary be SQL verb.
165+ In line with this, stackql responses will contain information about required where parameters, if applicable.
166+
167+ Args:
168+ table_name: The name of the table to describ
169+ db_schema: The schema name (defaults to 'public')
170+ """
171+ return _BACKEND.describe_table(table_name, db_schema=db_schema)
172+
173+ @mcp.tool()
174+ def get_foreign_keys(table_name: str, db_schema: Optional[str] = None) -> str:
175+
176+ """Get foreign key information for a table.
177+
178+ Args:
179+ table_name: The name of the table to get foreign keys from
180+ db_schema: The schema name (defaults to 'public')
181+ """
182+ return _BACKEND.get_foreign_keys(table_name, db_schema)
183+
184+ @mcp.tool()
185+ def find_relationships(table_name: str, db_schema: Optional[str] = None) -> str:
186+
187+ """Find both explicit and implied relationships for a table.
188+
189+ Args:
190+ table_name: The name of the table to analyze relationships for
191+ db_schema: The schema name (defaults to 'public')
192+ """
193+ return _BACKEND.find_relationships(table_name, db_schema)
194+
195+ ```
196+ */
197+ type Backend interface {
19198
20199 // Ping verifies the backend connection is active.
21200 Ping (ctx context.Context ) error
22201
23202 // Close gracefully shuts down the backend connection.
24203 Close () error
204+ // Server and environment info
205+ ServerInfo (ctx context.Context ) (map [string ]interface {}, error )
206+
207+ // Current DB identity details
208+ DBIdentity (ctx context.Context ) (map [string ]interface {}, error )
209+
210+ // Execute a SQL query (legacy signature)
211+ Query (ctx context.Context , sql string , parameters []interface {}, rowLimit int , format string ) (string , error )
212+
213+ // Execute a SQL query and return JSON-serializable rows (legacy signature)
214+ QueryJSON (ctx context.Context , sql string , parameters []interface {}, rowLimit int ) ([]map [string ]interface {}, error )
215+
216+ // Execute a SQL query with typed input (preferred)
217+ RunQuery (ctx context.Context , input QueryInput ) (string , error )
218+
219+ // Execute a SQL query and return JSON rows with typed input (preferred)
220+ RunQueryJSON (ctx context.Context , input QueryJSONInput ) ([]map [string ]interface {}, error )
221+
222+ // List resource URIs for tables in a schema
223+ ListTableResources (ctx context.Context , schema string ) ([]string , error )
224+
225+ // Read rows from a table resource
226+ ReadTableResource (ctx context.Context , schema string , table string , rowLimit int ) ([]map [string ]interface {}, error )
227+
228+ // Prompt: guidelines for writing safe SELECT queries
229+ PromptWriteSafeSelectTool (ctx context.Context ) (string , error )
230+
231+ // Prompt: tips for reading EXPLAIN ANALYZE output
232+ PromptExplainPlanTipsTool (ctx context.Context ) (string , error )
233+
234+ // List schemas with filters and return JSON rows
235+ ListSchemasJSON (ctx context.Context , input ListSchemasInput ) ([]map [string ]interface {}, error )
236+
237+ // List schemas with pagination and filters
238+ ListSchemasJSONPage (ctx context.Context , input ListSchemasPageInput ) (map [string ]interface {}, error )
239+
240+ // List tables in a schema with optional filters and return JSON rows
241+ ListTablesJSON (ctx context.Context , input ListTablesInput ) ([]map [string ]interface {}, error )
242+
243+ // List tables with pagination and filters
244+ ListTablesJSONPage (ctx context.Context , input ListTablesPageInput ) (map [string ]interface {}, error )
245+
246+ // List all schemas in the database
247+ ListSchemas (ctx context.Context ) (string , error )
248+
249+ // List all tables in a specific schema
250+ ListTables (ctx context.Context , dbSchema string ) (string , error )
251+
252+ // Get detailed information about a table
253+ DescribeTable (ctx context.Context , tableName string , dbSchema string ) (string , error )
254+
255+ // Get foreign key information for a table
256+ GetForeignKeys (ctx context.Context , tableName string , dbSchema string ) (string , error )
257+
258+ // Find both explicit and implied relationships for a table
259+ FindRelationships (ctx context.Context , tableName string , dbSchema string ) (string , error )
25260}
26261
27262// QueryResult represents the result of a query execution.
0 commit comments