Skip to content

Fix: Support for DESCRIBE and SHOW COLUMNS queries #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions src/mysql_mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,40 +110,64 @@ async def list_tools() -> list[Tool]:
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
"""Execute SQL commands."""
config = get_db_config()
logger.info(f"Calling tool: {name} with arguments: {arguments}")

# Verify tool name first, before checking DB config
if name != "execute_sql":
raise ValueError(f"Unknown tool: {name}")

# Then check if query is provided
query = arguments.get("query")
if not query:
raise ValueError("Query is required")

# Now get DB config - this allows the above validation tests to pass
# without requiring actual DB credentials
config = get_db_config()

try:
with connect(**config) as conn:
with conn.cursor() as cursor:
# Always ensure we consume all results to avoid "Unread result found" errors
cursor.execute(query)

# Check the query type by normalizing and checking the first word
query_upper = query.strip().upper()

# Special handling for SHOW TABLES
if query.strip().upper().startswith("SHOW TABLES"):
if query_upper.startswith("SHOW TABLES"):
tables = cursor.fetchall()
result = ["Tables_in_" + config["database"]] # Header
result.extend([table[0] for table in tables])
return [TextContent(type="text", text="\n".join(result))]

# Handle all other queries that return result sets (SELECT, SHOW, DESCRIBE etc.)
# Special handling for DESCRIBE and SHOW COLUMNS
elif query_upper.startswith("DESCRIBE ") or query_upper.startswith("DESC ") or query_upper.startswith("SHOW COLUMNS FROM ") or query_upper.startswith("SHOW FIELDS FROM "):
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()

# Format the results in a more readable way
results = []
results.append(",".join(columns))
for row in rows:
# Convert None values to "NULL" for better readability
formatted_row = [str(val) if val is not None else "NULL" for val in row]
results.append(",".join(formatted_row))

return [TextContent(type="text", text="\n".join(results))]

# Handle all other queries that return result sets (SELECT, SHOW, etc.)
elif cursor.description is not None:
columns = [desc[0] for desc in cursor.description]
try:
rows = cursor.fetchall()
result = [",".join(map(str, row)) for row in rows]
return [TextContent(type="text", text="\n".join([",".join(columns)] + result))]
except Error as e:
logger.warning(f"Error fetching results: {str(e)}")
return [TextContent(type="text", text=f"Query executed but error fetching results: {str(e)}")]
rows = cursor.fetchall()

if not rows:
return [TextContent(type="text", text="Query executed successfully. No results returned.")]

result = [",".join(map(str, row)) for row in rows]
return [TextContent(type="text", text="\n".join([",".join(columns)] + result))]

# Non-SELECT queries
# Non-SELECT queries (INSERT, UPDATE, DELETE, etc.)
else:
conn.commit()
return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {cursor.rowcount}")]
Expand Down