Skip to main content
Glama
RichardHan

Microsoft SQL Server MCP Server

by RichardHan

execute_sql

Execute SQL queries on Microsoft SQL Server databases to retrieve, update, or manage data through a controlled interface.

Instructions

Execute an SQL query on the SQL Server

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesThe SQL query to execute

Implementation Reference

  • The @app.call_tool() handler function that implements the execute_sql tool logic. Connects to MSSQL using pymssql, executes the provided SQL query, handles SELECT vs non-SELECT queries, and returns results or affected rows 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 @app.list_tools() function that registers the 'execute_sql' tool (name from get_command()), including its description and inputSchema requiring a 'query' string.
    @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 inputSchema for the execute_sql tool, defining a required 'query' property of type string.
        description="Execute an SQL query on the SQL Server",
        inputSchema={
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "The SQL query to execute"
                }
            },
            "required": ["query"]
        }
    )
  • get_command() function that returns the tool name, defaulting to 'execute_sql' from MSSQL_COMMAND env var.
    def get_command():
        """Get the command to execute SQL queries."""
        return os.getenv("MSSQL_COMMAND", "execute_sql")
  • get_db_config() helper that constructs the pymssql connection config from environment variables, handling various auth, encryption, LocalDB, etc.
    def get_db_config():
        """Get database configuration from environment variables."""
        # Basic configuration
        server = os.getenv("MSSQL_SERVER", "localhost")
        logger.info(f"MSSQL_SERVER environment variable: {os.getenv('MSSQL_SERVER', 'NOT SET')}")
        logger.info(f"Using server: {server}")
        
        # Handle LocalDB connections (Issue #6)
        # LocalDB format: (localdb)\instancename
        if server.startswith("(localdb)\\"):
            # For LocalDB, pymssql needs special formatting
            # Convert (localdb)\MSSQLLocalDB to localhost\MSSQLLocalDB with dynamic port
            instance_name = server.replace("(localdb)\\", "")
            server = f".\\{instance_name}"
            logger.info(f"Detected LocalDB connection, converted to: {server}")
        
        config = {
            "server": server,
            "user": os.getenv("MSSQL_USER"),
            "password": os.getenv("MSSQL_PASSWORD"),
            "database": os.getenv("MSSQL_DATABASE"),
            "port": os.getenv("MSSQL_PORT", "1433"),  # Default MSSQL port
        }    
        # Port support (Issue #8)
        port = os.getenv("MSSQL_PORT")
        if port:
            try:
                config["port"] = int(port)
            except ValueError:
                logger.warning(f"Invalid MSSQL_PORT value: {port}. Using default port.")
        
        # Encryption settings for Azure SQL (Issue #11)
        # Check if we're connecting to Azure SQL
        if config["server"] and ".database.windows.net" in config["server"]:
            config["tds_version"] = "7.4"  # Required for Azure SQL
            # Azure SQL requires encryption - use connection string format for pymssql 2.3+
            # This improves upon TDS-only approach by being more explicit
            if os.getenv("MSSQL_ENCRYPT", "true").lower() == "true":
                config["server"] += ";Encrypt=yes;TrustServerCertificate=no"
        else:
            # For non-Azure connections, respect the MSSQL_ENCRYPT setting
            # Use connection string format in addition to TDS version for better compatibility
            encrypt_str = os.getenv("MSSQL_ENCRYPT", "false")
            if encrypt_str.lower() == "true":
                config["tds_version"] = "7.4"  # Keep existing TDS approach
                config["server"] += ";Encrypt=yes;TrustServerCertificate=yes"  # Add explicit setting
                
        # Windows Authentication support (Issue #7)
        use_windows_auth = os.getenv("MSSQL_WINDOWS_AUTH", "false").lower() == "true"
        
        if use_windows_auth:
            # For Windows authentication, user and password are not required
            if not config["database"]:
                logger.error("MSSQL_DATABASE is required")
                raise ValueError("Missing required database configuration")
            # Remove user and password for Windows auth
            config.pop("user", None)
            config.pop("password", None)
            logger.info("Using Windows Authentication")
        else:
            # SQL Authentication - user and password are required
            if not all([config["user"], config["password"], config["database"]]):
                logger.error("Missing required database configuration. Please check environment variables:")
                logger.error("MSSQL_USER, MSSQL_PASSWORD, and MSSQL_DATABASE are required")
                raise ValueError("Missing required database configuration")
        
        return config
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool executes SQL queries but fails to mention critical behavioral traits such as whether it's read-only or destructive, authentication requirements, error handling, or performance implications. This leaves significant gaps in understanding how the tool behaves beyond its basic function.

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

Conciseness5/5

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

The description is a single, clear sentence with no wasted words, making it highly concise and front-loaded. It efficiently communicates the core function without unnecessary elaboration, which is ideal for quick comprehension by an AI agent.

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?

For a tool that executes SQL queries with no annotations and no output schema, the description is insufficiently complete. It lacks details on return values, error conditions, security implications, or transactional behavior, which are crucial for an agent to use the tool effectively in a database context. The simplicity of the tool doesn't excuse these omissions.

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 100% description coverage, with the single parameter 'query' documented as 'The SQL query to execute'. The description adds no additional semantic context beyond this, such as query format examples or constraints. Given the high schema coverage, the baseline score of 3 is appropriate, as the schema already provides adequate parameter information.

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 action ('execute') and target resource ('SQL query on the SQL Server'), making the purpose immediately understandable. However, it lacks differentiation from sibling tools, but since there are none, this doesn't penalize the score. The description avoids tautology by specifying the action and context beyond just the tool name.

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?

The description provides no guidance on when to use this tool versus alternatives, prerequisites, or constraints. It simply states what the tool does without context about appropriate use cases, making it minimally helpful for an agent deciding when to invoke it.

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