This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
MCP SQL Server is a Python MCP (Model Context Protocol) server that exposes SQL Server database inspection and querying capabilities to Claude Desktop and Claude Code. It implements connection pooling, multi-layer SQL injection prevention, schema/table access controls, and the full MCP 2025-11-25 spec (Tools, Resources, Prompts).
# Install in editable mode with dev dependencies
pip install -e ".[dev]"
# Run unit tests (no database required)
pytest tests/ -v
# Run a single test
pytest tests/test_security_validator.py::test_function_name -v
# Lint
ruff check src/
# Format
ruff format src/
# Test connection to a real SQL Server
python test_connection.py
# Start the server manually (blocks waiting for MCP stdio)
python -m mcp_sqlserver.server --connection-string "Driver={ODBC Driver 17 for SQL Server};Server=...;Database=...;Trusted_Connection=yes"src/mcp_sqlserver/
├── server.py # MCP app instance, list_tools/call_tool handlers, entry point (run/main)
├── config.py # CLI arg parsing + env var fallback; module-level globals mutated by _load_config()
├── security.py # SecurityValidator: is_table_allowed(), validate_query()
├── pool.py # ConnectionPool: thread-safe Queue-based pool with auto-reconnect
├── helpers.py # Markdown table formatting
├── resources.py # MCP Resources: db://schema/overview, db://schema/tables/{name}
├── prompts.py # MCP Prompts: analyze-table, query-builder, data-dictionary
└── tools/
├── __init__.py # Re-exports all handle_* functions
├── list_tables.py
├── describe_table.py
├── execute_query.py
├── relationships.py
├── indexes.py
├── search_columns.py
├── statistics.py
└── views.py
Configuration priority: CLI args → env vars → .env file. _load_config() is called once at startup from main() and mutates module-level globals in config.py. All modules import from mcp_sqlserver import config and read the globals directly.
Connection pool: ConnectionPool wraps a queue.Queue of pyodbc connections. Acquiring uses pool.get(timeout=...) (raises Empty → converted to TimeoutError). Every get_connection() context manager pings SELECT 1 to detect dead connections and replaces them. Connections are always returned to the pool via finally, with a rollback and replacement-on-failure.
Security validation: SecurityValidator in security.py has two public class methods:
is_table_allowed(table_name, schema)— validates identifier format, schema whitelist, and blacklist wildcard patterns.validate_query(query)— six ordered layers: length cap (4096 chars), null-byte check, Unicode normalisation, SELECT-only enforcement, injection regex patterns, dangerous keyword word-boundary check.
Tool handler pattern: Each tool lives in its own file under tools/ and exports a single async def handle_*(pool, arguments) function returning list[TextContent]. server.py dispatches via a plain if/elif chain in call_tool(). To add a new tool: create the handler file, re-export from tools/__init__.py, add a Tool(...) entry in list_tools(), and add the dispatch branch in call_tool().
MCP resources and prompts are registered at module load time via register_resources(app, get_pool) and register_prompts(app) called at the top of server.py, before the tool decorators.
- Create
src/mcp_sqlserver/tools/my_tool.pywithasync def handle_my_tool(pool, arguments). - Re-export from
tools/__init__.py. - Add
Tool(name="my_tool", ...)tolist_tools()inserver.py. - Add
elif name == "my_tool": content = await handle_my_tool(pool, arguments)incall_tool(). - Add tests in
tests/. - Update README.md under "Available Tools".
The unit test suite (tests/test_security_validator.py) covers SecurityValidator and helpers without a real database. All new security logic must have unit tests. Integration testing requires a real SQL Server and ODBC Driver 17+.