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.
- microsoft_sql_server_mcp_haibt-0.1.0.dist-info/METADATA +130 -0
- microsoft_sql_server_mcp_haibt-0.1.0.dist-info/RECORD +8 -0
- microsoft_sql_server_mcp_haibt-0.1.0.dist-info/WHEEL +4 -0
- microsoft_sql_server_mcp_haibt-0.1.0.dist-info/entry_points.txt +2 -0
- microsoft_sql_server_mcp_haibt-0.1.0.dist-info/licenses/LICENSE +21 -0
- mssql_mcp_server/__init__.py +9 -0
- mssql_mcp_server/__main__.py +5 -0
- mssql_mcp_server/server.py +292 -0
|
@@ -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
|
+
[](https://pypi.org/project/microsoft_sql_server_mcp/)
|
|
23
|
+
[](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,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,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())
|