SQL MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SQL MCP ServerWhat were my top 10 equity positions by market value last Friday?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
SQL MCP Server
A Python MCP server that lets you query a SQL Server database in plain English through Claude. You ask a question, Claude figures out the SQL, runs it, and gives you an answer. No query writing, no copy-pasting schemas into chat.
Design Concept
Why not just paste the schema?
The obvious approach is to dump your schema into the conversation and ask Claude to write queries. It works, but it falls apart on any real database: large schemas eat the context window, you repeat the process every session, and you still have to copy the generated SQL somewhere to actually run it.
MCP fixes all of that. The Model Context Protocol is an open standard from Anthropic that lets Claude connect to external tools and data sources during a conversation. The server exposes what it can do; Claude decides when to call it. The user just asks questions.
What this server exposes
Three tools and one resource:
list_tables-- lists every user table in the database grouped by schema. Claude uses this when it hits a table name that isn't in the schema context file.describe_table-- returns full column definitions plus three sample rows for a given table. Claude uses this when it needs precise type information before writing a query.run_query-- executes a SELECT statement and returns the results as a Markdown table. This is the main event.schema://context(resource) -- serves the contents ofschema-context.md, a file you maintain that describes your key tables in business terms. Claude reads this before generating any query. A well-written schema context eliminates most of the dynamic tool calls and gets the SQL right on the first try.
How a query actually flows
You: "What were my top 10 equity positions by market value last Friday?"
1. Claude reads schema://context
-> understands Holdings, Instruments, the YYYYMMDD date format, etc.
2. Claude calls run_query with the SQL it composed
3. run_query executes against SQL Server, returns a Markdown table
4. Claude answers in plain English with the data inlineFor tables not covered in schema-context.md, Claude falls back to list_tables and describe_table and explores dynamically before writing the query. It's slower but it works.
Safety
The server uses a read-only database login as the primary control. run_query also rejects anything that doesn't start with SELECT as a secondary check. Neither replaces a properly restricted database user -- always connect with a login that only has SELECT grants on the schemas you care about.
Related MCP server: MS SQL MCP Server
MCP Implementation
Transport
The server uses stdio transport -- it reads JSON-RPC messages on stdin and writes responses to stdout. Claude Code and Claude Desktop launch it as a child process. There's no network port, no listening socket, nothing to expose.
FastMCP
The server is built on FastMCP from the mcp package. It turns decorated Python functions directly into MCP tools and resources. The alternative is the lower-level mcp.server.Server class which gives you full protocol control, but for a server this simple it's just boilerplate. FastMCP removes all of it:
mcp = FastMCP("sql", instructions="...")
@mcp.tool()
def my_tool(arg: str) -> str:
...
@mcp.resource("scheme://uri")
def my_resource() -> str:
...
mcp.run()Tools vs Resources
Tools are functions Claude calls explicitly, with arguments, in the tool-use loop. Every call shows up in the conversation.
Resources are read-only data at a URI. Claude requests them via the resource protocol rather than as a tool call -- better suited for reference data like a schema description that you want Claude to read once per session, not call repeatedly.
Server instructions
FastMCP accepts an instructions string that gets sent to Claude when the connection is established. That's how Claude knows to read schema://context before writing SQL -- without you having to say so in every message. It's part of the MCP spec, separate from the system prompt, and scoped to this server's capabilities.
Code Breakdown
sql-mcp/
├── server.py Everything -- MCP server, tools, resource
├── schema-context.md Your schema reference (the thing you maintain)
├── pyproject.toml Dependencies
├── .env Connection string (not committed)
└── .env.example Templateserver.py
Startup
load_dotenv()
CONN_STR = os.environ["MSSQL_CONN"]python-dotenv loads .env at import time. MSSQL_CONN is required -- missing it raises KeyError immediately, which shows up as a startup failure in Claude's MCP panel rather than a confusing runtime error mid-conversation.
_connect()
def _connect() -> pyodbc.Connection:
conn = pyodbc.connect(CONN_STR, timeout=QUERY_TIMEOUT)
conn.timeout = QUERY_TIMEOUT
return connOpens a fresh connection per call. timeout gets set twice -- once on connect() for the handshake, once on the connection object for statement execution. For a single-user local tool this is fine. If you need to support concurrent sessions, see the connection pool extension below.
_rows_to_markdown()
Converts column names and row tuples into a Markdown table. Database NULLs become the string "NULL" rather than Python's None -- it reads better when Claude includes it in a response.
schema_context (resource)
@mcp.resource("schema://context")
def schema_context() -> str:
if not SCHEMA_CONTEXT_PATH.exists():
return "No schema-context.md found. ..."
return SCHEMA_CONTEXT_PATH.read_text()Reads schema-context.md from disk on every request, so you can edit the file without restarting the server. If the file is missing, Claude falls back to list_tables and describe_table automatically.
list_tables (tool)
Queries INFORMATION_SCHEMA.TABLES for all BASE TABLE entries -- no views. Returns results grouped by schema.
describe_table (tool)
Queries INFORMATION_SCHEMA.COLUMNS for column metadata, then runs SELECT TOP 3 to pull representative rows. The sample data matters more than the column types for getting SQL right -- especially for code columns and non-obvious date formats.
run_query (tool)
Normalises the SQL with .strip().upper() and checks it starts with SELECT. Fetches MAX_ROWS + 1 rows, then truncates to MAX_ROWS and appends a notice if the extra row was there. Avoids a separate COUNT(*) round-trip while still detecting truncation.
Extending the Server
Add a new tool
Decorate a function with @mcp.tool(), give it typed arguments and a clear docstring, and it becomes available to Claude immediately on next server start. Argument names and the docstring go directly into Claude's tool description, so write them like you'd write a comment for a colleague.
Example -- row counts across all tables:
@mcp.tool()
def table_row_counts() -> str:
"""Return the approximate row count for every user table."""
sql = """
SELECT s.name AS schema_name, t.name AS table_name,
p.rows AS row_count
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.partitions p ON p.object_id = t.object_id
AND p.index_id IN (0, 1)
ORDER BY p.rows DESC
"""
with _connect() as conn:
cursor = conn.cursor()
cursor.execute(sql)
rows = cursor.fetchall()
return _rows_to_markdown(["Schema", "Table", "Rows"], rows)Add a connection pool
For concurrent sessions or an HTTP deployment, swap the per-call _connect() for SQLAlchemy:
# pyproject.toml: add "sqlalchemy>=2.0"
from sqlalchemy import create_engine, text
engine = create_engine(
"mssql+pyodbc:///?odbc_connect=" + CONN_STR,
pool_size=5,
max_overflow=10,
)
def _connect():
return engine.connect()Switch cursor-based queries to conn.execute(text(sql)) and adjust result handling.
Switch to HTTP transport
stdio works for local use. To share the server across a team, switch to SSE:
mcp.run(transport="sse", host="0.0.0.0", port=8080)In the Claude config, replace command/args with a url pointing at the server.
Multiple databases
Pass a database argument to the tools and route to the right connection string:
CONNECTIONS = {
"trading": os.environ["MSSQL_CONN_TRADING"],
"risk": os.environ["MSSQL_CONN_RISK"],
}
@mcp.tool()
def run_query(sql: str, database: str = "trading") -> str:
"""Execute a SELECT against the named database (trading or risk)."""
if database not in CONNECTIONS:
return f"Unknown database '{database}'. Options: {', '.join(CONNECTIONS)}"
...The schema-context.md File
Why it matters
This file is the most important thing you can do for query quality. Column names and data types tell Claude what exists; schema-context.md tells it what things mean. A good file eliminates schema-exploration round-trips and gets the SQL right on the first attempt. A stale or missing file makes Claude guess, and it will guess wrong on anything domain-specific.
Structure
Plain Markdown. The server passes the full text to Claude as-is. No required format, but this layout works well:
# Database Schema Context
## Core Tables
### dbo.TableName
One sentence: what does this table represent in business terms?
| Column | Type | Notes |
|--------|------|-------|
| pk_col | INT PK | |
| fk_col | INT FK->OtherTable | |
**Common joins:**
<SQL snippet>
---
## Common Patterns
<Named SQL snippets for your most frequent query shapes>What to put in the Notes column
This is where most of the value lives:
PK / FK -- mark primary and foreign keys explicitly. Claude uses these to build joins.
Code values -- if a column contains codes, list them.
`B` = buy, `S` = sell. Claude cannot infer these from the data.Units and currency -- for monetary columns, note the currency and scale (units, thousands, millions).
Date format quirks -- if dates are stored as integers in YYYYMMDD format, say so and include the conversion expression. This is the single most common source of bad SQL in financial databases.
Default filters -- if nearly every query should include a filter (
WHERE is_active = 1,WHERE status <> 'CANCELLED'), call it out. Claude will include it without being asked.
What to leave out
Don't document every column. Focus on the ones that matter for analysis. If a table has 80 columns and 10 are relevant, document the 10.
Skip infrastructure tables, audit logs, ETL staging, and anything users won't query directly.
Skip indexes, triggers, and stored procedures unless they directly affect how SELECT queries should be written.
Common patterns section
Pre-written SQL snippets for your most frequent query shapes are worth the time to write. They give Claude a template to adapt rather than generating from scratch, which means more consistent SQL that matches your existing conventions.
Keeping it current
The server reads schema-context.md on every resource request, so edits take effect immediately without a restart. Treat it like any other internal documentation -- update it when tables are renamed or columns change meaning. An outdated file is actively worse than no file.
Prerequisites
Python and uv
Requires Python 3.11+. Dependencies are managed with uv:
curl -LsSf https://astral.sh/uv/install.sh | shMicrosoft ODBC Driver for SQL Server
pyodbc needs the ODBC driver installed at the system level.
Fedora / RHEL:
curl https://packages.microsoft.com/config/rhel/9/prod.repo \
| sudo tee /etc/yum.repos.d/mssql-release.repo
sudo ACCEPT_EULA=Y dnf install -y msodbcsql18 unixODBC-develUbuntu / Debian: Use the equivalent Microsoft Ubuntu repo and apt-get install msodbcsql18 unixodbc-dev.
macOS:
brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release
brew install msodbcsql18Read-only database user
Create a login with SELECT-only access before connecting:
CREATE LOGIN sql_mcp WITH PASSWORD = 'StrongPassword123!';
USE YourDatabase;
CREATE USER sql_mcp FOR LOGIN sql_mcp;
GRANT SELECT ON SCHEMA::dbo TO sql_mcp;Build and Run
1. Install dependencies
cd ~/sql-mcp
uv syncCreates .venv inside the project. You don't need to activate it -- uv run handles that.
2. Configure the connection
cp .env.example .envEdit .env:
MSSQL_CONN=DRIVER={ODBC Driver 18 for SQL Server};SERVER=192.168.1.50;DATABASE=TradingDB;UID=sql_mcp;PWD=StrongPassword123!For Windows Authentication:
MSSQL_CONN=DRIVER={ODBC Driver 18 for SQL Server};SERVER=myserver;DATABASE=TradingDB;Trusted_Connection=yes3. Edit schema-context.md
Replace the sample content with documentation for your actual tables. See the Schema Context section above. You can skip this for initial testing, but don't skip it for regular use.
4. Test the server
uv run server.pyIt blocks waiting for MCP messages on stdin. Ctrl-C to stop. If it fails with ImportError: libodbc.so.2, the ODBC driver installation didn't complete.
5. Register with Claude Code
Open ~/.claude/settings.json and confirm this block is present (it should already be there from initial setup):
{
"mcpServers": {
"sql": {
"command": "uv",
"args": ["--directory", "/home/pr0f1t/sql-mcp", "run", "server.py"]
}
}
}Run /mcp in a Claude Code session to confirm the sql server is connected and the three tools and schema://context resource are listed.
6. Register with Claude Desktop
Open the Claude Desktop config file:
Linux:
~/.config/Claude/claude_desktop_config.jsonmacOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Add the same mcpServers block as above and restart Claude Desktop.
7. Ask questions
What are my top 10 positions by market value?
Show me all equity trades from last week.
Which accounts have more than 5% concentration in a single name?
What was the total notional traded per broker this month?Claude reads the schema context, writes the SQL, runs it, and answers. If the result is wrong, correct it in plain English and Claude will adjust and re-run.
Troubleshooting
Server doesn't appear in /mcp
Check that uv is on the PATH Claude Code uses. Run which uv -- if it's under ~/.local/bin, make sure that's in your shell profile's PATH.
ImportError: libodbc.so.2
The unixODBC system library isn't installed. Run the dnf install command from the prerequisites section.
login rejected (code 2)
Wrong credentials in .env, or the server isn't reachable. Test with sqlcmd -S <server> -U <user> -P <password> -Q "SELECT 1".
KeyError: 'MSSQL_CONN'
.env is missing or in the wrong directory. It needs to be at /home/pr0f1t/sql-mcp/.env.
Results are truncated
The default cap is 500 rows. Raise MAX_ROWS in server.py if you need more -- just keep in mind that large result sets consume context window space and slow the response.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/gmines/sql-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server