microsoft_sql_server_mcp_haibt 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.
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: microsoft_sql_server_mcp_haibt
3
+ Version: 0.1.0
4
+ Summary: A Model Context Protocol (MCP) server that enables secure interaction with Microsoft SQL Server databases.
5
+ Author-email: Richard Han <noreply@example.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: ai,database,mcp,mssql,sql-server
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: mcp>=1.0.0
17
+ Requires-Dist: pymssql>=2.2.8
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Microsoft SQL Server MCP Server
21
+
22
+ [![PyPI](https://img.shields.io/pypi/v/microsoft_sql_server_mcp)](https://pypi.org/project/microsoft_sql_server_mcp/)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
+
25
+ <a href="https://glama.ai/mcp/servers/29cpe19k30">
26
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/29cpe19k30/badge" alt="Microsoft SQL Server MCP server" />
27
+ </a>
28
+
29
+ A Model Context Protocol (MCP) server for secure SQL Server database access through Claude Desktop.
30
+
31
+ ## Features
32
+
33
+ - 🔍 List database tables
34
+ - 📊 Execute SQL queries (SELECT, INSERT, UPDATE, DELETE)
35
+ - 🔐 Multiple authentication methods (SQL, Windows, Azure AD)
36
+ - 🏢 LocalDB and Azure SQL support
37
+ - 🔌 Custom port configuration
38
+
39
+ ## Quick Start
40
+
41
+ ### Install with Claude Desktop
42
+
43
+ Add to your `claude_desktop_config.json`:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "mssql": {
49
+ "command": "uvx",
50
+ "args": ["microsoft_sql_server_mcp"],
51
+ "env": {
52
+ "MSSQL_SERVER": "localhost",
53
+ "MSSQL_DATABASE": "your_database",
54
+ "MSSQL_USER": "your_username",
55
+ "MSSQL_PASSWORD": "your_password"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Configuration
63
+
64
+ ### Basic SQL Authentication
65
+ ```bash
66
+ MSSQL_SERVER=localhost # Required
67
+ MSSQL_DATABASE=your_database # Required
68
+ MSSQL_USER=your_username # Required for SQL auth
69
+ MSSQL_PASSWORD=your_password # Required for SQL auth
70
+ ```
71
+
72
+ ### Windows Authentication
73
+ ```bash
74
+ MSSQL_SERVER=localhost
75
+ MSSQL_DATABASE=your_database
76
+ MSSQL_WINDOWS_AUTH=true # Use Windows credentials
77
+ ```
78
+
79
+ ### Azure SQL Database
80
+ ```bash
81
+ MSSQL_SERVER=your-server.database.windows.net
82
+ MSSQL_DATABASE=your_database
83
+ MSSQL_USER=your_username
84
+ MSSQL_PASSWORD=your_password
85
+ # Encryption is automatic for Azure
86
+ ```
87
+
88
+ ### Optional Settings
89
+ ```bash
90
+ MSSQL_PORT=1433 # Custom port (default: 1433)
91
+ MSSQL_ENCRYPT=true # Force encryption
92
+ ```
93
+
94
+ ## Alternative Installation Methods
95
+
96
+ ### Using pip
97
+ ```bash
98
+ pip install microsoft_sql_server_mcp
99
+ ```
100
+
101
+ Then in `claude_desktop_config.json`:
102
+ ```json
103
+ {
104
+ "mcpServers": {
105
+ "mssql": {
106
+ "command": "python",
107
+ "args": ["-m", "mssql_mcp_server"],
108
+ "env": { ... }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Development
115
+ ```bash
116
+ git clone https://github.com/RichardHan/mssql_mcp_server.git
117
+ cd mssql_mcp_server
118
+ pip install -e .
119
+ ```
120
+
121
+ ## Security
122
+
123
+ - Create a dedicated SQL user with minimal permissions
124
+ - Never use admin/sa accounts
125
+ - Use Windows Authentication when possible
126
+ - Enable encryption for sensitive data
127
+
128
+ ## License
129
+
130
+ MIT
@@ -0,0 +1,8 @@
1
+ mssql_mcp_server/__init__.py,sha256=3GoujrBtz9Bx5vpCCaKSOC1MnW0odabng5OZxr9Zj6U,201
2
+ mssql_mcp_server/__main__.py,sha256=BQ8CJ7aUIgiqSi6lNln5GuFEIfuum7cvlCwFuI6wMdM,135
3
+ mssql_mcp_server/server.py,sha256=h8sYT5n0o_iFFrg1kcOT5C7gS49iFiTkYtoqmmlD4hU,11067
4
+ microsoft_sql_server_mcp_haibt-0.1.0.dist-info/METADATA,sha256=nSSHLdA4mfqe7TLlB5BwNFm8bNTtq-lxBLZUvFnYyuM,3279
5
+ microsoft_sql_server_mcp_haibt-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ microsoft_sql_server_mcp_haibt-0.1.0.dist-info/entry_points.txt,sha256=Gk_aj65OiFpsKSbHAFVB6PXS4ldaraVJM5tyk634ztM,59
7
+ microsoft_sql_server_mcp_haibt-0.1.0.dist-info/licenses/LICENSE,sha256=_gkQipKDpXYdaw6FDUtwa9VSUJjq7cYRIC7Wc2frP5s,1087
8
+ microsoft_sql_server_mcp_haibt-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
+ mssql_mcp_server = mssql_mcp_server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Dana K. Williams
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 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.
@@ -0,0 +1,9 @@
1
+ from . import server
2
+ import asyncio
3
+
4
+ def main():
5
+ """Main entry point for the package."""
6
+ asyncio.run(server.main())
7
+
8
+ # Expose important items at package level
9
+ __all__ = ['main', 'server']
@@ -0,0 +1,5 @@
1
+ """Entry point for running the module with python -m mssql_mcp_server."""
2
+ from . import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,292 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import re
5
+ import pymssql
6
+ from mcp.server import Server
7
+ from mcp.types import Resource, Tool, TextContent
8
+ from pydantic import AnyUrl
9
+
10
+ # Configure logging
11
+ logging.basicConfig(
12
+ level=logging.INFO,
13
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
14
+ )
15
+ logger = logging.getLogger("mssql_mcp_server")
16
+
17
+ def validate_table_name(table_name: str) -> str:
18
+ """Validate and escape table name to prevent SQL injection."""
19
+ # Allow only alphanumeric, underscore, and dot (for schema.table)
20
+ if not re.match(r'^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)?$', table_name):
21
+ raise ValueError(f"Invalid table name: {table_name}")
22
+
23
+ # Split schema and table if present
24
+ parts = table_name.split('.')
25
+ if len(parts) == 2:
26
+ # Escape both schema and table name
27
+ return f"[{parts[0]}].[{parts[1]}]"
28
+ else:
29
+ # Just table name
30
+ return f"[{table_name}]"
31
+
32
+ def get_db_config():
33
+ """Get database configuration from environment variables."""
34
+ # Basic configuration
35
+ server = os.getenv("MSSQL_SERVER", "localhost")
36
+ logger.info(f"MSSQL_SERVER environment variable: {os.getenv('MSSQL_SERVER', 'NOT SET')}")
37
+ logger.info(f"Using server: {server}")
38
+
39
+ # Handle LocalDB connections (Issue #6)
40
+ # LocalDB format: (localdb)\instancename
41
+ if server.startswith("(localdb)\\"):
42
+ # For LocalDB, pymssql needs special formatting
43
+ # Convert (localdb)\MSSQLLocalDB to localhost\MSSQLLocalDB with dynamic port
44
+ instance_name = server.replace("(localdb)\\", "")
45
+ server = f".\\{instance_name}"
46
+ logger.info(f"Detected LocalDB connection, converted to: {server}")
47
+
48
+ config = {
49
+ "server": server,
50
+ "user": os.getenv("MSSQL_USER"),
51
+ "password": os.getenv("MSSQL_PASSWORD"),
52
+ "database": os.getenv("MSSQL_DATABASE"),
53
+ "port": os.getenv("MSSQL_PORT", "1433"), # Default MSSQL port
54
+ }
55
+ # Port support (Issue #8)
56
+ port = os.getenv("MSSQL_PORT")
57
+ if port:
58
+ try:
59
+ config["port"] = int(port)
60
+ except ValueError:
61
+ logger.warning(f"Invalid MSSQL_PORT value: {port}. Using default port.")
62
+
63
+ # Encryption settings for Azure SQL (Issue #11)
64
+ # Check if we're connecting to Azure SQL
65
+ if config["server"] and ".database.windows.net" in config["server"]:
66
+ config["tds_version"] = "7.4" # Required for Azure SQL
67
+ # Azure SQL requires encryption - use connection string format for pymssql 2.3+
68
+ # This improves upon TDS-only approach by being more explicit
69
+ if os.getenv("MSSQL_ENCRYPT", "true").lower() == "true":
70
+ config["server"] += ";Encrypt=yes;TrustServerCertificate=no"
71
+ else:
72
+ # For non-Azure connections, respect the MSSQL_ENCRYPT setting
73
+ # Use connection string format in addition to TDS version for better compatibility
74
+ encrypt_str = os.getenv("MSSQL_ENCRYPT", "false")
75
+ if encrypt_str.lower() == "true":
76
+ config["tds_version"] = "7.4" # Keep existing TDS approach
77
+ config["server"] += ";Encrypt=yes;TrustServerCertificate=yes" # Add explicit setting
78
+
79
+ # Windows Authentication support (Issue #7)
80
+ use_windows_auth = os.getenv("MSSQL_WINDOWS_AUTH", "false").lower() == "true"
81
+
82
+ if use_windows_auth:
83
+ # For Windows authentication, user and password are not required
84
+ if not config["database"]:
85
+ logger.error("MSSQL_DATABASE is required")
86
+ raise ValueError("Missing required database configuration")
87
+ # Remove user and password for Windows auth
88
+ config.pop("user", None)
89
+ config.pop("password", None)
90
+ logger.info("Using Windows Authentication")
91
+ else:
92
+ # SQL Authentication - user and password are required
93
+ if not all([config["user"], config["password"], config["database"]]):
94
+ logger.error("Missing required database configuration. Please check environment variables:")
95
+ logger.error("MSSQL_USER, MSSQL_PASSWORD, and MSSQL_DATABASE are required")
96
+ raise ValueError("Missing required database configuration")
97
+
98
+ return config
99
+
100
+ def get_command():
101
+ """Get the command to execute SQL queries."""
102
+ return os.getenv("MSSQL_COMMAND", "execute_sql")
103
+
104
+ def is_select_query(query: str) -> bool:
105
+ """
106
+ Check if a query is a SELECT statement, accounting for comments.
107
+ Handles both single-line (--) and multi-line (/* */) SQL comments.
108
+ """
109
+ # Remove multi-line comments /* ... */
110
+ query_cleaned = re.sub(r'/\*.*?\*/', '', query, flags=re.DOTALL)
111
+
112
+ # Remove single-line comments -- ...
113
+ lines = query_cleaned.split('\n')
114
+ cleaned_lines = []
115
+ for line in lines:
116
+ # Find -- comment marker and remove everything after it
117
+ comment_pos = line.find('--')
118
+ if comment_pos != -1:
119
+ line = line[:comment_pos]
120
+ cleaned_lines.append(line)
121
+
122
+ query_cleaned = '\n'.join(cleaned_lines)
123
+
124
+ # Get the first non-empty word after stripping whitespace
125
+ first_word = query_cleaned.strip().split()[0] if query_cleaned.strip() else ""
126
+ return first_word.upper() == "SELECT"
127
+
128
+ # Initialize server
129
+ app = Server("mssql_mcp_server")
130
+
131
+ @app.list_resources()
132
+ async def list_resources() -> list[Resource]:
133
+ """List SQL Server tables as resources."""
134
+ config = get_db_config()
135
+ try:
136
+ conn = pymssql.connect(**config)
137
+ cursor = conn.cursor()
138
+ # Query to get user tables from the current database
139
+ cursor.execute("""
140
+ SELECT TABLE_NAME
141
+ FROM INFORMATION_SCHEMA.TABLES
142
+ WHERE TABLE_TYPE = 'BASE TABLE'
143
+ """)
144
+ tables = cursor.fetchall()
145
+ logger.info(f"Found tables: {tables}")
146
+
147
+ resources = []
148
+ for table in tables:
149
+ resources.append(
150
+ Resource(
151
+ uri=f"mssql://{table[0]}/data",
152
+ name=f"Table: {table[0]}",
153
+ mimeType="text/plain",
154
+ description=f"Data in table: {table[0]}"
155
+ )
156
+ )
157
+ cursor.close()
158
+ conn.close()
159
+ return resources
160
+ except Exception as e:
161
+ logger.error(f"Failed to list resources: {str(e)}")
162
+ return []
163
+
164
+ @app.read_resource()
165
+ async def read_resource(uri: AnyUrl) -> str:
166
+ """Read table contents."""
167
+ config = get_db_config()
168
+ uri_str = str(uri)
169
+ logger.info(f"Reading resource: {uri_str}")
170
+
171
+ if not uri_str.startswith("mssql://"):
172
+ raise ValueError(f"Invalid URI scheme: {uri_str}")
173
+
174
+ parts = uri_str[8:].split('/')
175
+ table = parts[0]
176
+
177
+ try:
178
+ # Validate table name to prevent SQL injection
179
+ safe_table = validate_table_name(table)
180
+
181
+ conn = pymssql.connect(**config)
182
+ cursor = conn.cursor()
183
+ # Use TOP 100 for MSSQL (equivalent to LIMIT in MySQL)
184
+ cursor.execute(f"SELECT TOP 100 * FROM {safe_table}")
185
+ columns = [desc[0] for desc in cursor.description]
186
+ rows = cursor.fetchall()
187
+ result = [",".join(map(str, row)) for row in rows]
188
+ cursor.close()
189
+ conn.close()
190
+ return "\n".join([",".join(columns)] + result)
191
+
192
+ except Exception as e:
193
+ logger.error(f"Database error reading resource {uri}: {str(e)}")
194
+ raise RuntimeError(f"Database error: {str(e)}")
195
+
196
+ @app.list_tools()
197
+ async def list_tools() -> list[Tool]:
198
+ """List available SQL Server tools."""
199
+ command = get_command()
200
+ logger.info("Listing tools...")
201
+ return [
202
+ Tool(
203
+ name=command,
204
+ description="Execute an SQL query on the SQL Server",
205
+ inputSchema={
206
+ "type": "object",
207
+ "properties": {
208
+ "query": {
209
+ "type": "string",
210
+ "description": "The SQL query to execute"
211
+ }
212
+ },
213
+ "required": ["query"]
214
+ }
215
+ )
216
+ ]
217
+
218
+ @app.call_tool()
219
+ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
220
+ """Execute SQL commands."""
221
+ config = get_db_config()
222
+ command = get_command()
223
+ logger.info(f"Calling tool: {name} with arguments: {arguments}")
224
+
225
+ if name != command:
226
+ raise ValueError(f"Unknown tool: {name}")
227
+
228
+ query = arguments.get("query")
229
+ if not query:
230
+ raise ValueError("Query is required")
231
+
232
+ try:
233
+ conn = pymssql.connect(**config)
234
+ cursor = conn.cursor()
235
+ cursor.execute(query)
236
+
237
+ # Special handling for table listing
238
+ if is_select_query(query) and "INFORMATION_SCHEMA.TABLES" in query.upper():
239
+ tables = cursor.fetchall()
240
+ result = ["Tables_in_" + config["database"]] # Header
241
+ result.extend([table[0] for table in tables])
242
+ cursor.close()
243
+ conn.close()
244
+ return [TextContent(type="text", text="\n".join(result))]
245
+
246
+ # Regular SELECT queries
247
+ elif is_select_query(query):
248
+ columns = [desc[0] for desc in cursor.description]
249
+ rows = cursor.fetchall()
250
+ result = [",".join(map(str, row)) for row in rows]
251
+ cursor.close()
252
+ conn.close()
253
+ return [TextContent(type="text", text="\n".join([",".join(columns)] + result))]
254
+
255
+ # Non-SELECT queries
256
+ else:
257
+ conn.commit()
258
+ affected_rows = cursor.rowcount
259
+ cursor.close()
260
+ conn.close()
261
+ return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {affected_rows}")]
262
+
263
+ except Exception as e:
264
+ logger.error(f"Error executing SQL '{query}': {e}")
265
+ return [TextContent(type="text", text=f"Error executing query: {str(e)}")]
266
+
267
+ async def main():
268
+ """Main entry point to run the MCP server."""
269
+ from mcp.server.stdio import stdio_server
270
+
271
+ logger.info("Starting MSSQL MCP server...")
272
+ config = get_db_config()
273
+ # Log connection info without exposing sensitive data
274
+ server_info = config['server']
275
+ if 'port' in config:
276
+ server_info += f":{config['port']}"
277
+ user_info = config.get('user', 'Windows Auth')
278
+ logger.info(f"Database config: {server_info}/{config['database']} as {user_info}")
279
+
280
+ async with stdio_server() as (read_stream, write_stream):
281
+ try:
282
+ await app.run(
283
+ read_stream,
284
+ write_stream,
285
+ app.create_initialization_options()
286
+ )
287
+ except Exception as e:
288
+ logger.error(f"Server error: {str(e)}", exc_info=True)
289
+ raise
290
+
291
+ if __name__ == "__main__":
292
+ asyncio.run(main())