@@ -128,21 +128,36 @@ def execute_query(query: str):
128
128
return rows
129
129
except Exception as err :
130
130
logger .error (f"Error executing query: { err } " )
131
- return f"error running query: { err } "
131
+ # Return a structured dictionary rather than a string to ensure proper serialization
132
+ # by the MCP protocol. String responses for errors can cause BrokenResourceError.
133
+ return {"error" : str (err )}
132
134
133
135
134
136
@mcp .tool ()
135
137
def run_select_query (query : str ):
136
138
"""Run a SELECT query in a ClickHouse database"""
137
139
logger .info (f"Executing SELECT query: { query } " )
138
- future = QUERY_EXECUTOR .submit (execute_query , query )
139
140
try :
140
- result = future .result (timeout = SELECT_QUERY_TIMEOUT_SECS )
141
- return result
142
- except concurrent .futures .TimeoutError :
143
- logger .warning (f"Query timed out after { SELECT_QUERY_TIMEOUT_SECS } seconds: { query } " )
144
- future .cancel ()
145
- return f"Queries taking longer than { SELECT_QUERY_TIMEOUT_SECS } seconds are currently not supported."
141
+ future = QUERY_EXECUTOR .submit (execute_query , query )
142
+ try :
143
+ result = future .result (timeout = SELECT_QUERY_TIMEOUT_SECS )
144
+ # Check if we received an error structure from execute_query
145
+ if isinstance (result , dict ) and "error" in result :
146
+ logger .warning (f"Query failed: { result ['error' ]} " )
147
+ # MCP requires structured responses; string error messages can cause
148
+ # serialization issues leading to BrokenResourceError
149
+ return {"status" : "error" , "message" : f"Query failed: { result ['error' ]} " }
150
+ return result
151
+ except concurrent .futures .TimeoutError :
152
+ logger .warning (f"Query timed out after { SELECT_QUERY_TIMEOUT_SECS } seconds: { query } " )
153
+ future .cancel ()
154
+ # Return a properly structured response for timeout errors
155
+ return {"status" : "error" , "message" : f"Query timed out after { SELECT_QUERY_TIMEOUT_SECS } seconds" }
156
+ except Exception as e :
157
+ logger .error (f"Unexpected error in run_select_query: { str (e )} " )
158
+ # Catch all other exceptions and return them in a structured format
159
+ # to prevent MCP serialization failures
160
+ return {"status" : "error" , "message" : f"Unexpected error: { str (e )} " }
146
161
147
162
148
163
def create_clickhouse_client ():
0 commit comments