mssql-agent-mcp 1.0.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,9 @@
1
+ """MSSQL Agent MCP Server.
2
+
3
+ A Model Context Protocol server for Microsoft SQL Server with support for
4
+ database queries, stored procedure management, and SQL Server Agent jobs.
5
+ """
6
+
7
+ from .server import main, mcp
8
+
9
+ __all__ = ["main", "mcp"]
@@ -0,0 +1,6 @@
1
+ """Entry point for running the MSSQL MCP server as a module."""
2
+
3
+ from .server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,25 @@
1
+ """Configuration and environment variables for MSSQL MCP Server."""
2
+
3
+ import os
4
+
5
+ # Security configuration
6
+ READONLY_MODE = os.getenv("MSSQL_READONLY", "true").lower() in ("true", "1", "yes")
7
+
8
+ # Database configuration from environment variables
9
+ DB_CONFIG = {
10
+ "server": os.getenv("MSSQL_SERVER", "localhost"),
11
+ "database": os.getenv("MSSQL_DATABASE", "master"),
12
+ "user": os.getenv("MSSQL_USER", ""),
13
+ "password": os.getenv("MSSQL_PASSWORD", ""),
14
+ "port": os.getenv("MSSQL_PORT", "1433"),
15
+ "driver": os.getenv("MSSQL_DRIVER", "ODBC Driver 18 for SQL Server"),
16
+ "encrypt": os.getenv("MSSQL_ENCRYPT", "yes"),
17
+ "trust_server_certificate": os.getenv("MSSQL_TRUST_SERVER_CERTIFICATE", "no"),
18
+ "auth_mode": os.getenv("MSSQL_AUTH_MODE", "sql"), # sql, windows, azure
19
+ }
20
+
21
+ # Blocked SQL statement types when in readonly mode
22
+ BLOCKED_STATEMENT_TYPES = {
23
+ "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER",
24
+ "TRUNCATE", "MERGE", "EXEC", "EXECUTE", "GRANT", "REVOKE", "DENY",
25
+ }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ """MSSQL MCP Server - Main entry point.
3
+
4
+ Provides tools for interacting with Microsoft SQL Server databases,
5
+ including stored procedure management and SQL Server Agent job management.
6
+ """
7
+
8
+ from fastmcp import FastMCP
9
+
10
+ from .tools import database as db_tools
11
+ from .tools import jobs as job_tools
12
+ from .tools import procedures as proc_tools
13
+
14
+ # Create the FastMCP server instance
15
+ mcp = FastMCP(
16
+ "mssql-agent-mcp",
17
+ instructions="""This MCP server provides tools for interacting with Microsoft SQL Server databases.
18
+
19
+ It supports:
20
+ - Database queries and schema inspection
21
+ - Stored procedure management with export/import workflows
22
+ - SQL Server Agent job management with jobs-as-code workflow
23
+
24
+ Use `query` for SELECT statements, `execute` for write operations.
25
+ Jobs and procedures can be exported to files, edited, and pushed back to the server.
26
+ """,
27
+ )
28
+
29
+ # Register database tools
30
+ mcp.tool()(db_tools.query)
31
+ mcp.tool()(db_tools.execute)
32
+ mcp.tool()(db_tools.list_tables)
33
+ mcp.tool()(db_tools.describe_table)
34
+ mcp.tool()(db_tools.list_databases)
35
+ mcp.tool()(db_tools.get_table_sample)
36
+ mcp.tool()(db_tools.get_table_indexes)
37
+ mcp.tool()(db_tools.get_foreign_keys)
38
+
39
+ # Register stored procedure tools
40
+ mcp.tool()(proc_tools.list_procedures)
41
+ mcp.tool()(proc_tools.list_all_procedures)
42
+ mcp.tool()(proc_tools.get_procedure_details)
43
+ mcp.tool()(proc_tools.get_procedure_parameters)
44
+ mcp.tool()(proc_tools.export_procedures_to_files)
45
+ mcp.tool()(proc_tools.update_procedure_from_file)
46
+
47
+ # Register SQL Server Agent job tools
48
+ mcp.tool()(job_tools.list_agent_jobs)
49
+ mcp.tool()(job_tools.get_job_steps)
50
+ mcp.tool()(job_tools.get_job_details)
51
+ mcp.tool()(job_tools.get_job_schedules)
52
+ mcp.tool()(job_tools.get_job_history)
53
+ mcp.tool()(job_tools.export_enabled_jobs_to_files)
54
+ mcp.tool()(job_tools.update_job_step_from_file)
55
+ mcp.tool()(job_tools.create_job_step_from_file)
56
+
57
+
58
+ def main():
59
+ """Run the MCP server."""
60
+ mcp.run()
61
+
62
+
63
+ if __name__ == "__main__":
64
+ main()
@@ -0,0 +1,58 @@
1
+ """MCP tools for MSSQL Server operations."""
2
+
3
+ from .database import (
4
+ describe_table,
5
+ execute,
6
+ get_foreign_keys,
7
+ get_table_indexes,
8
+ get_table_sample,
9
+ list_databases,
10
+ list_tables,
11
+ query,
12
+ )
13
+ from .jobs import (
14
+ create_job_step_from_file,
15
+ export_enabled_jobs_to_files,
16
+ get_job_details,
17
+ get_job_history,
18
+ get_job_schedules,
19
+ get_job_steps,
20
+ list_agent_jobs,
21
+ update_job_step_from_file,
22
+ )
23
+ from .procedures import (
24
+ export_procedures_to_files,
25
+ get_procedure_details,
26
+ get_procedure_parameters,
27
+ list_all_procedures,
28
+ list_procedures,
29
+ update_procedure_from_file,
30
+ )
31
+
32
+ __all__ = [
33
+ # Database tools
34
+ "query",
35
+ "execute",
36
+ "list_tables",
37
+ "describe_table",
38
+ "list_databases",
39
+ "get_table_sample",
40
+ "get_table_indexes",
41
+ "get_foreign_keys",
42
+ # Procedure tools
43
+ "list_procedures",
44
+ "list_all_procedures",
45
+ "get_procedure_details",
46
+ "get_procedure_parameters",
47
+ "export_procedures_to_files",
48
+ "update_procedure_from_file",
49
+ # Job tools
50
+ "list_agent_jobs",
51
+ "get_job_steps",
52
+ "get_job_details",
53
+ "get_job_schedules",
54
+ "get_job_history",
55
+ "export_enabled_jobs_to_files",
56
+ "update_job_step_from_file",
57
+ "create_job_step_from_file",
58
+ ]
@@ -0,0 +1,193 @@
1
+ """Database query and schema inspection tools."""
2
+
3
+ from typing import Annotated
4
+
5
+ from ..config import READONLY_MODE
6
+ from ..utils import format_result, get_connection, rows_to_dicts, validate_query
7
+
8
+
9
+ def query(
10
+ sql: Annotated[str, "The SQL query to execute (SELECT statements)"]
11
+ ) -> str:
12
+ """Execute a SQL query and return results. Use for SELECT statements."""
13
+ is_valid, error_msg = validate_query(sql)
14
+ if not is_valid:
15
+ return format_result({"error": error_msg})
16
+
17
+ conn = get_connection()
18
+ try:
19
+ cursor = conn.cursor()
20
+ cursor.execute(sql)
21
+ if cursor.description:
22
+ rows = rows_to_dicts(cursor)
23
+ return format_result(rows) if rows else "Query executed successfully. No rows returned."
24
+ return "Query executed successfully. No rows returned."
25
+ finally:
26
+ conn.close()
27
+
28
+
29
+ def execute(
30
+ sql: Annotated[str, "The SQL statement to execute (INSERT, UPDATE, DELETE, CREATE, etc.)"]
31
+ ) -> str:
32
+ """Execute a SQL statement (INSERT, UPDATE, DELETE, CREATE, etc.) and return affected rows count."""
33
+ if READONLY_MODE:
34
+ return format_result({
35
+ "error": "Write operations are disabled in read-only mode. Set MSSQL_READONLY=false to enable."
36
+ })
37
+
38
+ conn = get_connection()
39
+ try:
40
+ cursor = conn.cursor()
41
+ cursor.execute(sql)
42
+ conn.commit()
43
+ return f"Statement executed successfully. Rows affected: {cursor.rowcount}"
44
+ finally:
45
+ conn.close()
46
+
47
+
48
+ def list_tables(
49
+ schema: Annotated[str | None, "Schema name to filter tables (optional, defaults to all schemas)"] = None
50
+ ) -> str:
51
+ """List all tables in the current database."""
52
+ conn = get_connection()
53
+ try:
54
+ cursor = conn.cursor()
55
+ if schema:
56
+ sql = """
57
+ SELECT TABLE_SCHEMA as [schema], TABLE_NAME as [table], TABLE_TYPE as [type]
58
+ FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?
59
+ ORDER BY TABLE_SCHEMA, TABLE_NAME
60
+ """
61
+ cursor.execute(sql, (schema,))
62
+ else:
63
+ sql = """
64
+ SELECT TABLE_SCHEMA as [schema], TABLE_NAME as [table], TABLE_TYPE as [type]
65
+ FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_SCHEMA, TABLE_NAME
66
+ """
67
+ cursor.execute(sql)
68
+ return format_result(rows_to_dicts(cursor))
69
+ finally:
70
+ conn.close()
71
+
72
+
73
+ def describe_table(
74
+ table: Annotated[str, "The table name to describe"],
75
+ schema: Annotated[str, "The schema name (defaults to 'dbo')"] = "dbo"
76
+ ) -> str:
77
+ """Get the schema/structure of a specific table including columns, data types, and constraints."""
78
+ sql = """
79
+ SELECT
80
+ c.COLUMN_NAME as [column], c.DATA_TYPE as [type],
81
+ c.CHARACTER_MAXIMUM_LENGTH as [max_length],
82
+ c.NUMERIC_PRECISION as [precision], c.NUMERIC_SCALE as [scale],
83
+ c.IS_NULLABLE as [nullable], c.COLUMN_DEFAULT as [default],
84
+ CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 'YES' ELSE 'NO' END as [primary_key]
85
+ FROM INFORMATION_SCHEMA.COLUMNS c
86
+ LEFT JOIN (
87
+ SELECT ku.TABLE_SCHEMA, ku.TABLE_NAME, ku.COLUMN_NAME
88
+ FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
89
+ JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku ON tc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME
90
+ WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
91
+ ) pk ON c.TABLE_SCHEMA = pk.TABLE_SCHEMA AND c.TABLE_NAME = pk.TABLE_NAME AND c.COLUMN_NAME = pk.COLUMN_NAME
92
+ WHERE c.TABLE_NAME = ? AND c.TABLE_SCHEMA = ?
93
+ ORDER BY c.ORDINAL_POSITION
94
+ """
95
+ conn = get_connection()
96
+ try:
97
+ cursor = conn.cursor()
98
+ cursor.execute(sql, (table, schema))
99
+ return format_result(rows_to_dicts(cursor))
100
+ finally:
101
+ conn.close()
102
+
103
+
104
+ def list_databases() -> str:
105
+ """List all databases on the SQL Server instance."""
106
+ sql = """
107
+ SELECT name as [database], state_desc as [state],
108
+ recovery_model_desc as [recovery_model], create_date as [created]
109
+ FROM sys.databases ORDER BY name
110
+ """
111
+ conn = get_connection()
112
+ try:
113
+ cursor = conn.cursor()
114
+ cursor.execute(sql)
115
+ return format_result(rows_to_dicts(cursor))
116
+ finally:
117
+ conn.close()
118
+
119
+
120
+ def get_table_sample(
121
+ table: Annotated[str, "The table name"],
122
+ schema: Annotated[str, "The schema name (defaults to 'dbo')"] = "dbo",
123
+ limit: Annotated[int, "Number of rows to return (defaults to 10, max 1000)"] = 10
124
+ ) -> str:
125
+ """Get a sample of rows from a table."""
126
+ safe_schema = "".join(c for c in schema if c.isalnum() or c == "_")
127
+ safe_table = "".join(c for c in table if c.isalnum() or c == "_")
128
+ safe_limit = min(max(1, int(limit)), 1000)
129
+
130
+ sql = f"SELECT TOP {safe_limit} * FROM [{safe_schema}].[{safe_table}]"
131
+ conn = get_connection()
132
+ try:
133
+ cursor = conn.cursor()
134
+ cursor.execute(sql)
135
+ return format_result(rows_to_dicts(cursor))
136
+ finally:
137
+ conn.close()
138
+
139
+
140
+ def get_table_indexes(
141
+ table: Annotated[str, "The table name"],
142
+ schema: Annotated[str, "The schema name (defaults to 'dbo')"] = "dbo"
143
+ ) -> str:
144
+ """Get indexes defined on a table."""
145
+ sql = """
146
+ SELECT i.name as [index_name], i.type_desc as [index_type],
147
+ i.is_unique as [is_unique], i.is_primary_key as [is_primary_key],
148
+ STRING_AGG(c.name, ', ') WITHIN GROUP (ORDER BY ic.key_ordinal) as [columns]
149
+ FROM sys.indexes i
150
+ JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
151
+ JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
152
+ JOIN sys.tables t ON i.object_id = t.object_id
153
+ JOIN sys.schemas s ON t.schema_id = s.schema_id
154
+ WHERE t.name = ? AND s.name = ?
155
+ GROUP BY i.name, i.type_desc, i.is_unique, i.is_primary_key
156
+ ORDER BY i.name
157
+ """
158
+ conn = get_connection()
159
+ try:
160
+ cursor = conn.cursor()
161
+ cursor.execute(sql, (table, schema))
162
+ return format_result(rows_to_dicts(cursor))
163
+ finally:
164
+ conn.close()
165
+
166
+
167
+ def get_foreign_keys(
168
+ table: Annotated[str, "The table name"],
169
+ schema: Annotated[str, "The schema name (defaults to 'dbo')"] = "dbo"
170
+ ) -> str:
171
+ """Get foreign key relationships for a table."""
172
+ sql = """
173
+ SELECT fk.name as [constraint_name],
174
+ OBJECT_SCHEMA_NAME(fk.parent_object_id) as [from_schema],
175
+ OBJECT_NAME(fk.parent_object_id) as [from_table],
176
+ COL_NAME(fkc.parent_object_id, fkc.parent_column_id) as [from_column],
177
+ OBJECT_SCHEMA_NAME(fk.referenced_object_id) as [to_schema],
178
+ OBJECT_NAME(fk.referenced_object_id) as [to_table],
179
+ COL_NAME(fkc.referenced_object_id, fkc.referenced_column_id) as [to_column]
180
+ FROM sys.foreign_keys fk
181
+ JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
182
+ JOIN sys.tables t ON fk.parent_object_id = t.object_id
183
+ JOIN sys.schemas s ON t.schema_id = s.schema_id
184
+ WHERE t.name = ? AND s.name = ?
185
+ ORDER BY fk.name
186
+ """
187
+ conn = get_connection()
188
+ try:
189
+ cursor = conn.cursor()
190
+ cursor.execute(sql, (table, schema))
191
+ return format_result(rows_to_dicts(cursor))
192
+ finally:
193
+ conn.close()