sql-mcp-server 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
db.py ADDED
@@ -0,0 +1,27 @@
1
+ import pyodbc
2
+ import os
3
+ from dotenv import load_dotenv
4
+ from pathlib import Path
5
+
6
+ # Force load .env from the project folder
7
+ load_dotenv(dotenv_path=Path(__file__).parent / ".env")
8
+
9
+ # db.py — support both auth modes
10
+ def get_connection():
11
+ driver = os.getenv("DB_DRIVER", "ODBC Driver 17 for SQL Server")
12
+ server = os.getenv("DB_SERVER")
13
+ database = os.getenv("DB_NAME")
14
+ username = os.getenv("DB_USER") # new
15
+ password = os.getenv("DB_PASSWORD") # new
16
+
17
+ if username and password:
18
+ conn_str = (
19
+ f"DRIVER={{{driver}}};SERVER={server};DATABASE={database};"
20
+ f"UID={username};PWD={password};TrustServerCertificate=yes;"
21
+ )
22
+ else:
23
+ conn_str = (
24
+ f"DRIVER={{{driver}}};SERVER={server};DATABASE={database};"
25
+ "Trusted_Connection=yes;TrustServerCertificate=yes;"
26
+ )
27
+ return pyodbc.connect(conn_str)
server.py ADDED
@@ -0,0 +1,229 @@
1
+ from mcp.server.fastmcp import FastMCP
2
+ from db import get_connection
3
+ import json
4
+ import re
5
+
6
+ # Initialize FastMCP server
7
+ mcp = FastMCP("sql-assistant")
8
+
9
+ # ─────────────────────────────────────────────
10
+ # Helpers
11
+ # ─────────────────────────────────────────────
12
+ FORBIDDEN_KEYWORDS = ["INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE", "ALTER", "CREATE", "EXEC"]
13
+
14
+ def _check_select_only(sql: str) -> str | None:
15
+ """Return an error message if `sql` contains a forbidden keyword, else None."""
16
+ sql_upper = sql.strip().upper()
17
+ for keyword in FORBIDDEN_KEYWORDS:
18
+ if sql_upper.startswith(keyword) or f" {keyword} " in sql_upper:
19
+ return f"❌ '{keyword}' statements are not allowed. Only SELECT queries are permitted."
20
+ return None
21
+
22
+ def _validate_identifier(name: str) -> bool:
23
+ """Return True if `name` is a safe SQL identifier (letters, digits, underscores)."""
24
+ return bool(re.match(r'^[a-zA-Z0-9_]+$', name))
25
+
26
+
27
+ # ─────────────────────────────────────────────
28
+ # TOOL 1: list_tables
29
+ # ─────────────────────────────────────────────
30
+ @mcp.tool()
31
+ def list_tables() -> str:
32
+ """
33
+ List all user-created tables in the connected MS SQL database.
34
+ Returns table names along with their schema.
35
+ """
36
+ query = """
37
+ SELECT TABLE_SCHEMA, TABLE_NAME
38
+ FROM INFORMATION_SCHEMA.TABLES
39
+ WHERE TABLE_TYPE = 'BASE TABLE'
40
+ ORDER BY TABLE_SCHEMA, TABLE_NAME
41
+ """
42
+ try:
43
+ conn = get_connection()
44
+ cursor = conn.cursor()
45
+ cursor.execute(query)
46
+ rows = cursor.fetchall()
47
+ conn.close()
48
+
49
+ if not rows:
50
+ return "No tables found in the database."
51
+
52
+ result = "Tables in database:\n\n"
53
+ for schema, table in rows:
54
+ result += f" [{schema}].[{table}]\n"
55
+ return result
56
+
57
+ except Exception as e:
58
+ return f"Error fetching tables: {str(e)}"
59
+
60
+
61
+ # ─────────────────────────────────────────────
62
+ # TOOL 2: describe_table
63
+ # ─────────────────────────────────────────────
64
+ @mcp.tool()
65
+ def describe_table(table_name: str, schema: str = "dbo") -> str:
66
+ """
67
+ Describe the structure of a specific table — columns, data types, nullability.
68
+
69
+ Args:
70
+ table_name: Name of the table (e.g. Customers)
71
+ schema: Schema name, default is 'dbo'
72
+ """
73
+ query = """
74
+ SELECT
75
+ COLUMN_NAME,
76
+ DATA_TYPE,
77
+ CHARACTER_MAXIMUM_LENGTH,
78
+ IS_NULLABLE,
79
+ COLUMN_DEFAULT
80
+ FROM INFORMATION_SCHEMA.COLUMNS
81
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?
82
+ ORDER BY ORDINAL_POSITION
83
+ """
84
+ try:
85
+ conn = get_connection()
86
+ cursor = conn.cursor()
87
+ cursor.execute(query, (table_name, schema))
88
+ rows = cursor.fetchall()
89
+ conn.close()
90
+
91
+ if not rows:
92
+ return f"Table [{schema}].[{table_name}] not found or has no columns."
93
+
94
+ result = f"Structure of [{schema}].[{table_name}]:\n\n"
95
+
96
+ for col_name, data_type, max_len, nullable, default in rows:
97
+ max_len = max_len if max_len else "-"
98
+ default = default if default else "-"
99
+
100
+ result += f"{col_name} | {data_type} | {max_len} | {nullable} | {default}\n"
101
+
102
+ return result
103
+
104
+ except Exception as e:
105
+ return f"Error describing table: {str(e)}"
106
+
107
+
108
+ # ─────────────────────────────────────────────
109
+ # TOOL 3: run_query
110
+ # ─────────────────────────────────────────────
111
+ @mcp.tool()
112
+ def run_query(sql: str, max_rows: int = 50) -> str:
113
+ """
114
+ Run a SELECT SQL query on the MS SQL database and return results.
115
+ Only SELECT statements are allowed for safety.
116
+
117
+ Args:
118
+ sql: A valid T-SQL SELECT query
119
+ max_rows: Maximum rows to return (default 50, max 200)
120
+ """
121
+ # Safety check — block destructive statements
122
+ err = _check_select_only(sql)
123
+ if err:
124
+ return err
125
+
126
+ max_rows = min(max_rows, 200) # hard cap
127
+
128
+ try:
129
+ conn = get_connection()
130
+ cursor = conn.cursor()
131
+ cursor.execute(sql)
132
+ rows = cursor.fetchmany(max_rows)
133
+ columns = [desc[0] for desc in cursor.description]
134
+ conn.close()
135
+
136
+ if not rows:
137
+ return "Query executed successfully. No rows returned."
138
+
139
+ # Format as a readable table
140
+ col_widths = [max(len(str(col)), max((len(str(row[i])) for row in rows), default=0)) for i, col in enumerate(columns)]
141
+
142
+ header = " | ".join(str(col).ljust(col_widths[i]) for i, col in enumerate(columns))
143
+ separator = "-+-".join("-" * w for w in col_widths)
144
+ result_lines = [header, separator]
145
+
146
+ for row in rows:
147
+ line = " | ".join(str(val).ljust(col_widths[i]) for i, val in enumerate(row))
148
+ result_lines.append(line)
149
+
150
+ result_lines.append(f"\n({len(rows)} rows returned)")
151
+ return "\n".join(result_lines)
152
+
153
+ except Exception as e:
154
+ return f"Query error: {str(e)}"
155
+
156
+
157
+ # ─────────────────────────────────────────────
158
+ # TOOL 4: explain_query
159
+ # ─────────────────────────────────────────────
160
+ @mcp.tool()
161
+ def explain_query(sql: str) -> str:
162
+ """
163
+ Show the estimated execution plan for a SELECT query using SET SHOWPLAN_TEXT.
164
+ Helps understand how SQL Server processes the query.
165
+
166
+ Args:
167
+ sql: A valid T-SQL SELECT query to explain
168
+ """
169
+ # Safety check — same rules as run_query
170
+ err = _check_select_only(sql)
171
+ if err:
172
+ return err
173
+
174
+ conn = None
175
+ try:
176
+ conn = get_connection()
177
+ cursor = conn.cursor()
178
+
179
+ cursor.execute("SET SHOWPLAN_TEXT ON")
180
+ cursor.execute(sql)
181
+ rows = cursor.fetchall()
182
+
183
+ if not rows:
184
+ return "No execution plan returned."
185
+
186
+ plan_lines = [str(row[0]) for row in rows]
187
+ return "Execution Plan:\n\n" + "\n".join(plan_lines)
188
+
189
+ except Exception as e:
190
+ return f"Error fetching execution plan: {str(e)}"
191
+ finally:
192
+ if conn:
193
+ try:
194
+ conn.cursor().execute("SET SHOWPLAN_TEXT OFF")
195
+ except Exception:
196
+ pass
197
+ conn.close()
198
+
199
+
200
+ # ─────────────────────────────────────────────
201
+ # TOOL 5: get_table_sample
202
+ # ─────────────────────────────────────────────
203
+ @mcp.tool()
204
+ def get_table_sample(table_name: str, schema: str = "dbo", rows: int = 5) -> str:
205
+ """
206
+ Preview the first N rows of any table quickly.
207
+
208
+ Args:
209
+ table_name: Name of the table
210
+ schema: Schema name, default is 'dbo'
211
+ rows: Number of rows to preview (default 5)
212
+ """
213
+ if not _validate_identifier(table_name):
214
+ return f"❌ Invalid table name: '{table_name}'. Only letters, digits, and underscores are allowed."
215
+ if not _validate_identifier(schema):
216
+ return f"❌ Invalid schema name: '{schema}'. Only letters, digits, and underscores are allowed."
217
+
218
+ sql = f"SELECT TOP {min(rows, 20)} * FROM [{schema}].[{table_name}]"
219
+ return run_query(sql)
220
+
221
+
222
+ # ─────────────────────────────────────────────
223
+ # Entry Point
224
+ # ─────────────────────────────────────────────
225
+ def main():
226
+ mcp.run(transport="stdio")
227
+
228
+ if __name__ == "__main__":
229
+ main()
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: sql-mcp-server
3
+ Version: 0.1.0
4
+ Summary: MCP server that connects Claude to a Microsoft SQL Server database
5
+ Author: Snehangshu
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: ai,claude,database,mcp,sql-server
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Database
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: mcp[cli]>=1.26.0
17
+ Requires-Dist: pyodbc>=5.3.0
18
+ Requires-Dist: python-dotenv>=1.2.2
19
+ Description-Content-Type: text/markdown
20
+
21
+ # SQL Assistant MCP Server
22
+
23
+ An MCP server that connects Claude to a Microsoft SQL Server database over stdio. Ask Claude to query your database, check table structures, or see how a query runs — without leaving the chat window.
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install sql-mcp-server
31
+ ```
32
+
33
+ You also need the **ODBC Driver for SQL Server** installed:
34
+ https://learn.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server
35
+
36
+ ---
37
+
38
+ ## Tools
39
+
40
+ **list_tables** — Lists every user-created table in the database, grouped by schema.
41
+
42
+ **describe_table** — Shows columns, data types, nullability, and defaults for a table.
43
+
44
+ **run_query** — Runs a SELECT query and returns results. Write operations (INSERT, UPDATE, DELETE, DROP, etc.) are blocked.
45
+
46
+ **explain_query** — Returns SQL Server's estimated execution plan via `SET SHOWPLAN_TEXT`. Useful before running a slow or unfamiliar query.
47
+
48
+ **get_table_sample** — Returns the first N rows (up to 20) from a table. Default is 5 — pass `rows=10` for more.
49
+
50
+ ---
51
+
52
+ ## Project structure
53
+ ```
54
+ sql-mcp-server/
55
+ ├── server.py # MCP server and tool definitions
56
+ ├── db.py # SQL Server connection helper (loads .env)
57
+ ├── pyproject.toml # Project metadata and dependencies
58
+ ├── uv.lock # Locked dependency versions (uv)
59
+ ├── .env # Connection config (not committed)
60
+ └── README.md
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Setup
66
+
67
+ ### 1. Configure the database connection
68
+
69
+ Create a `.env` file in the project root:
70
+
71
+ **Windows Authentication (Trusted Connection):**
72
+ ```env
73
+ DB_DRIVER=ODBC Driver 17 for SQL Server
74
+ DB_SERVER=YOUR_SERVER_NAME\\SQLEXPRESS
75
+ DB_NAME=your_database_name
76
+ ```
77
+
78
+ **SQL Server Authentication (username + password):**
79
+ ```env
80
+ DB_DRIVER=ODBC Driver 17 for SQL Server
81
+ DB_SERVER=YOUR_SERVER_NAME\\SQLEXPRESS
82
+ DB_NAME=your_database_name
83
+ DB_USER=your_username
84
+ DB_PASSWORD=your_password
85
+ ```
86
+
87
+ If you're using the Claude Desktop config below, put your credentials in the `env` block — no `.env` file needed.
88
+
89
+ ---
90
+
91
+ ### 2. Register with Claude Desktop
92
+
93
+ Add this to `claude_desktop_config.json`:
94
+
95
+ **Windows Authentication:**
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "sql-assistant": {
100
+ "command": "uv",
101
+ "args": [
102
+ "--directory",
103
+ "C:/path/to/sql-mcp-server",
104
+ "run",
105
+ "server.py"
106
+ ],
107
+ "env": {
108
+ "DB_SERVER": "YOUR_SERVER_NAME\\\\SQLEXPRESS",
109
+ "DB_NAME": "your_database_name",
110
+ "DB_DRIVER": "ODBC Driver 17 for SQL Server"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ **SQL Server Authentication:**
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "sql-assistant": {
122
+ "command": "uv",
123
+ "args": [
124
+ "--directory",
125
+ "C:/path/to/sql-mcp-server",
126
+ "run",
127
+ "server.py"
128
+ ],
129
+ "env": {
130
+ "DB_SERVER": "YOUR_SERVER_NAME\\\\SQLEXPRESS",
131
+ "DB_NAME": "your_database_name",
132
+ "DB_DRIVER": "ODBC Driver 17 for SQL Server",
133
+ "DB_USER": "your_username",
134
+ "DB_PASSWORD": "your_password"
135
+ }
136
+ }
137
+ }
138
+ }
139
+ ```
140
+
141
+ Replace the placeholder values with your actual details. Restart Claude Desktop after saving.
142
+
143
+ ---
144
+
145
+ ## Usage
146
+
147
+ With the server running, try asking Claude:
148
+
149
+ - "What tables are in the database?"
150
+ - "Describe the Orders table."
151
+ - "Show me 10 rows from Customers."
152
+ - "Run: SELECT TOP 5 * FROM Sales.Orders WHERE Status = 'Pending'"
153
+ - "Explain this query: SELECT * FROM Products WHERE Price > 100"
154
+
155
+ ---
156
+
157
+ ## Safety
158
+
159
+ Only SELECT queries run. These keywords are blocked before they reach the database:
160
+
161
+ `INSERT` `UPDATE` `DELETE` `DROP` `TRUNCATE` `ALTER` `CREATE` `EXEC`
162
+
163
+ Table and schema names are validated against injection patterns. Results cap at 200 rows.
164
+
165
+ > ⚠️ **Don't commit your `.env` file** — it contains your connection credentials.
166
+
167
+ ---
168
+
169
+ ## Requirements
170
+
171
+ - Python 3.12+
172
+ - SQL Server with ODBC Driver 17 or 18
173
+ - Claude Desktop with MCP support
174
+
175
+ ---
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,7 @@
1
+ db.py,sha256=4qP9HX4IDamu7zqxSMiCQG_zOCPbIGFtNkrE68uXVio,916
2
+ server.py,sha256=JC9wI6WJ4ZzxUCal69WrNBOr_uvbt_740_Fxakwr_eM,8578
3
+ sql_mcp_server-0.1.0.dist-info/METADATA,sha256=HAsMJ-fsfi90eNxitTHQWv3lOv1qpGF_h2g6y4f4gGU,4557
4
+ sql_mcp_server-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ sql_mcp_server-0.1.0.dist-info/entry_points.txt,sha256=GJySdKWFd3S7nJoNcbECz1Nz8teN-aJBrJH9AbIySgM,47
6
+ sql_mcp_server-0.1.0.dist-info/licenses/LICENSE,sha256=EbNPKITs5IpzocH7nlUejIW62m8GMwcQa7Atd_4nUWU,1094
7
+ sql_mcp_server-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sql-mcp-server = server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Snehangshu Bhuin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.