Skip to main content
Glama
RichardHan

Microsoft SQL Server MCP Server

by RichardHan

execute_sql

Run SQL queries on Microsoft SQL Server databases through a secure, controlled MCP interface.

Instructions

Execute an SQL query on the SQL Server

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesThe SQL query to execute

Implementation Reference

  • The call_tool function is the actual handler for execute_sql. It receives tool invocations, looks up the query argument, connects to MSSQL via pymssql, executes the query, and returns results as TextContent.
    @app.call_tool()
    async def call_tool(name: str, arguments: dict) -> list[TextContent]:
        """Execute SQL commands."""
        config = get_db_config()
        command = get_command()
        logger.info(f"Calling tool: {name} with arguments: {arguments}")
        
        if name != command:
            raise ValueError(f"Unknown tool: {name}")
        
        query = arguments.get("query")
        if not query:
            raise ValueError("Query is required")
        
        try:
            conn = pymssql.connect(**config)
            cursor = conn.cursor()
            cursor.execute(query)
            
            # Special handling for table listing
            if is_select_query(query) and "INFORMATION_SCHEMA.TABLES" in query.upper():
                tables = cursor.fetchall()
                result = ["Tables_in_" + config["database"]]  # Header
                result.extend([table[0] for table in tables])
                cursor.close()
                conn.close()
                return [TextContent(type="text", text="\n".join(result))]
            
            # Regular SELECT queries
            elif is_select_query(query):
                columns = [desc[0] for desc in cursor.description]
                rows = cursor.fetchall()
                result = [",".join(map(str, row)) for row in rows]
                cursor.close()
                conn.close()
                return [TextContent(type="text", text="\n".join([",".join(columns)] + result))]
            
            # Non-SELECT queries
            else:
                conn.commit()
                affected_rows = cursor.rowcount
                cursor.close()
                conn.close()
                return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {affected_rows}")]
                    
        except Exception as e:
            logger.error(f"Error executing SQL '{query}': {e}")
            return [TextContent(type="text", text=f"Error executing query: {str(e)}")]
  • The list_tools function registers the execute_sql tool with its schema (name, description, and inputSchema requiring a 'query' string parameter).
    @app.list_tools()
    async def list_tools() -> list[Tool]:
        """List available SQL Server tools."""
        command = get_command()
        logger.info("Listing tools...")
        return [
            Tool(
                name=command,
                description="Execute an SQL query on the SQL Server",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "The SQL query to execute"
                        }
                    },
                    "required": ["query"]
                }
            )
        ]
  • The get_command function returns 'execute_sql' as the default tool command name, and is used by both list_tools and call_tool to define/check the tool name.
    def get_command():
        """Get the command to execute SQL queries."""
        return os.getenv("MSSQL_COMMAND", "execute_sql")
  • The is_select_query helper function determines if a query is a SELECT statement, used by the handler to decide how to process query results.
    def is_select_query(query: str) -> bool:
        """
        Check if a query is a SELECT statement, accounting for comments.
        Handles both single-line (--) and multi-line (/* */) SQL comments.
        """
        # Remove multi-line comments /* ... */
        query_cleaned = re.sub(r'/\*.*?\*/', '', query, flags=re.DOTALL)
        
        # Remove single-line comments -- ...
        lines = query_cleaned.split('\n')
        cleaned_lines = []
        for line in lines:
            # Find -- comment marker and remove everything after it
            comment_pos = line.find('--')
            if comment_pos != -1:
                line = line[:comment_pos]
            cleaned_lines.append(line)
        
        query_cleaned = '\n'.join(cleaned_lines)
        
        # Get the first non-empty word after stripping whitespace
        first_word = query_cleaned.strip().split()[0] if query_cleaned.strip() else ""
        return first_word.upper() == "SELECT"
  • The validate_table_name helper function validates and escapes table names to prevent SQL injection.
    def validate_table_name(table_name: str) -> str:
        """Validate and escape table name to prevent SQL injection."""
        # Allow only alphanumeric, underscore, and dot (for schema.table)
        if not re.match(r'^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)?$', table_name):
            raise ValueError(f"Invalid table name: {table_name}")
        
        # Split schema and table if present
        parts = table_name.split('.')
        if len(parts) == 2:
            # Escape both schema and table name
            return f"[{parts[0]}].[{parts[1]}]"
        else:
            # Just table name
            return f"[{table_name}]"
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, and the description fails to disclose whether queries are read-only or can modify data, potential risks like SQL injection, or result formats. This is a significant gap for a tool that can execute arbitrary SQL.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is very concise at one sentence, but it lacks structure and does not front-load key information beyond the basic action. It could include more detail without becoming verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the lack of output schema and annotations, the description is incomplete. It does not explain what the tool returns (e.g., result sets, affected rows), error behavior, or any constraints. Critical context for safe usage is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has high coverage (100%) and describes the 'query' parameter sufficiently. The description adds no extra meaning beyond what the schema already provides, so a baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'execute' and the resource 'SQL query on the SQL Server', making the purpose straightforward. However, it restates the tool name without adding depth beyond stating it runs queries.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool or when to avoid it. No mention of prerequisites, security considerations, or alternatives. The description provides no contextual usage direction.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RichardHan/mssql_mcp_server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server