I Connected Claude to 20 Years of Azure SQL Data with a Custom MCP Server. Here's How.
A client needed managers to query 20 years of Azure SQL data via Claude without dashboards or SQL training. The solution involved a self‑hosted MCP server, table whitelisting, a knowledge‑base table, and secure Nginx proxying. The result was instant, accurate reports delivered through natural langu…
A client approached me with a request that sounded simple at first: they had 20 years of business data stored in Azure SQL and wanted managers to ask Claude questions and receive answers instantly. No dashboards, no BI tools, no SQL training—just type a question and get a report based on the actual data.
Why a Custom MCP Server Was Needed
The database was a sprawling mix of hundreds of tables: core business data, legacy tables, staging tables, system tables, half‑finished migrations, and columns that would confuse even a seasoned analyst. The client’s goal was to give managers a single, secure entry point to this data without exposing the entire schema or risking accidental data modification.
To meet these constraints, I built a Model Context Protocol (MCP) server that sits on the client’s own VPS. The data path is Claude → the MCP server → Azure SQL. This eliminates third‑party middleware, keeps the data inside the client’s network, and allows fine‑grained control over what Claude can see.
Three Layers of Protection
1. Whitelist at the MCP Layer – Claude can only query tables listed in a hard‑coded array. Any request for an unknown table is rejected immediately.
2. Regex and Table Checks in run_query – The server verifies that the query starts with SELECT and that every referenced table is on the whitelist.
3. Read‑Only SQL User – The MCP server connects to Azure SQL with a login that can only execute SELECT statements. Even if the previous layers fail, the database itself will reject any non‑SELECT operation.
Building the MCP Server
The MCP server is a Node.js application using the official @modelcontextprotocol SDK. It exposes three core tools:
- list_tables – returns the list of whitelisted tables.
- describe_table – provides column names and data types for a requested table.
- run_query – executes a SELECT query against the allowed tables, capped at 500 rows.
Each tool is defined with a name, description, and JSON schema for its parameters. Claude decides when to call these tools based on the user’s natural‑language question.
Solving the “Too Many Tables” Problem
Simply exposing the entire schema would have caused Claude to pick the wrong tables, especially with multiple legacy “customer” tables or tables with confusing suffixes. The whitelist solves this by limiting Claude’s view to only the tables that matter for each department.
To help Claude choose the right table for a given topic, I added a knowledge‑base table to the database. It maps departments or topics to relevant tables, includes human‑readable descriptions, key columns, and notes on gotchas (e.g., “status 3 means cancelled”).
When a manager asks a question, Claude first calls get_table_guide to retrieve the relevant tables, then uses describe_table for column details, writes the SQL, and finally calls run_query to fetch the data.
Securing the Connection with Nginx
The MCP server listens on a local port on the VPS. Nginx sits in front on a company subdomain (e.g., mcp.company-domain.com) and handles SSL termination with Let’s Encrypt certificates.
Two configuration details are critical:
- Upgrade/Connection headers to support long‑lived streaming connections used by Claude.
- Timeouts extended to 300 seconds to accommodate queries that scan 20 years of data.
Using the Connector in Claude
Managers add the MCP server as a custom connector in Claude, providing the subdomain URL and a shared secret key. Once configured, they can start a session by naming the server in the first message and then ask plain‑English questions.
Claude then orchestrates the tool calls, generates the SQL, executes it, and formats the results into tables, summaries, or charts. Users without the connector configured receive no data, ensuring that only authorized personnel can access the reports.
Future Improvements and Trade‑offs
While the current setup works well for a small group of senior managers, scaling to more users would require per‑user keys or OAuth integration to prevent a single shared key from becoming a risk.
Additional enhancements I plan to implement include:
- Audit logging of every query, user, and timestamp.
- Rate limiting to protect against accidental or malicious overuse.
- Configurable whitelists stored in JSON or a small admin table for easier updates.
- Row limits per table and query cost guards to avoid long‑running scans.
Overall, the solution reduced report turnaround from days to seconds, empowering managers across marketing, HR, R&D, production, procurement, and sales to make data‑driven decisions without IT intervention.
Why it matters
By giving managers instant, accurate access to legacy data through a conversational interface, the company eliminated bottlenecks and improved decision‑making speed.
Key points
- Custom MCP server keeps data on the client’s own VPS
- Three‑layer security: whitelist, regex checks, read‑only DB user
- Knowledge‑base table guides Claude to the right tables
- Nginx handles SSL and long‑running queries
- Shared key simplifies access for a small senior‑management group
Frequently asked questions
What if I need to add a new table for a department?
Add the table name to the ALLOWED_TABLES array in the MCP server code and redeploy, or update the knowledge‑base table if you’re using a dynamic whitelist.
Can I restrict access per user instead of a shared key?
Yes, you can implement per‑user keys or OAuth integration in Claude’s connector system, but it requires additional configuration and policy changes.
Will the read‑only SQL user prevent accidental data changes?
Yes, the database login is limited to SELECT statements, so even if a query is mis‑crafted, it cannot modify data.




