mssql-agent-mcp 1.0.0__py3-none-any.whl → 1.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.
mssql_agent_mcp/config.py CHANGED
@@ -2,8 +2,23 @@
2
2
 
3
3
  import os
4
4
 
5
+
6
+ def _parse_bool(value: str, default: bool = False) -> bool:
7
+ """Parse a string value to boolean."""
8
+ if not value:
9
+ return default
10
+ return value.lower() in ("true", "1", "yes")
11
+
12
+
5
13
  # Security configuration
6
- READONLY_MODE = os.getenv("MSSQL_READONLY", "true").lower() in ("true", "1", "yes")
14
+ READONLY_MODE = _parse_bool(os.getenv("MSSQL_READONLY", "true"), default=True)
15
+
16
+ # Granular write permission controls (only effective when READONLY_MODE is False)
17
+ ALLOW_INSERT = _parse_bool(os.getenv("MSSQL_ALLOW_INSERT", "true"), default=True)
18
+ ALLOW_UPDATE = _parse_bool(os.getenv("MSSQL_ALLOW_UPDATE", "true"), default=True)
19
+ ALLOW_DELETE = _parse_bool(os.getenv("MSSQL_ALLOW_DELETE", "true"), default=True)
20
+ ALLOW_DDL = _parse_bool(os.getenv("MSSQL_ALLOW_DDL", "true"), default=True) # CREATE, ALTER, DROP
21
+ ALLOW_EXEC = _parse_bool(os.getenv("MSSQL_ALLOW_EXEC", "true"), default=True) # EXEC, EXECUTE
7
22
 
8
23
  # Database configuration from environment variables
9
24
  DB_CONFIG = {
@@ -18,8 +33,77 @@ DB_CONFIG = {
18
33
  "auth_mode": os.getenv("MSSQL_AUTH_MODE", "sql"), # sql, windows, azure
19
34
  }
20
35
 
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
- }
36
+ # Statement type categories for granular control
37
+ INSERT_STATEMENTS = {"INSERT", "MERGE"}
38
+ UPDATE_STATEMENTS = {"UPDATE"}
39
+ DELETE_STATEMENTS = {"DELETE", "TRUNCATE"}
40
+ DDL_STATEMENTS = {"CREATE", "ALTER", "DROP", "GRANT", "REVOKE", "DENY"}
41
+ EXEC_STATEMENTS = {"EXEC", "EXECUTE"}
42
+
43
+ # All blocked statement types when in full readonly mode
44
+ BLOCKED_STATEMENT_TYPES = (
45
+ INSERT_STATEMENTS | UPDATE_STATEMENTS | DELETE_STATEMENTS | DDL_STATEMENTS | EXEC_STATEMENTS
46
+ )
47
+
48
+
49
+ def is_readonly() -> bool:
50
+ """Check if the server is in read-only mode."""
51
+ return READONLY_MODE
52
+
53
+
54
+ def is_insert_allowed() -> bool:
55
+ """Check if INSERT operations are allowed."""
56
+ return not READONLY_MODE and ALLOW_INSERT
57
+
58
+
59
+ def is_update_allowed() -> bool:
60
+ """Check if UPDATE operations are allowed."""
61
+ return not READONLY_MODE and ALLOW_UPDATE
62
+
63
+
64
+ def is_delete_allowed() -> bool:
65
+ """Check if DELETE operations are allowed."""
66
+ return not READONLY_MODE and ALLOW_DELETE
67
+
68
+
69
+ def is_ddl_allowed() -> bool:
70
+ """Check if DDL operations (CREATE, ALTER, DROP) are allowed."""
71
+ return not READONLY_MODE and ALLOW_DDL
72
+
73
+
74
+ def is_exec_allowed() -> bool:
75
+ """Check if EXEC/EXECUTE operations are allowed."""
76
+ return not READONLY_MODE and ALLOW_EXEC
77
+
78
+
79
+ def get_blocked_statements() -> set[str]:
80
+ """Get the set of currently blocked statement types based on configuration."""
81
+ if READONLY_MODE:
82
+ return BLOCKED_STATEMENT_TYPES.copy()
83
+
84
+ blocked = set()
85
+ if not ALLOW_INSERT:
86
+ blocked.update(INSERT_STATEMENTS)
87
+ if not ALLOW_UPDATE:
88
+ blocked.update(UPDATE_STATEMENTS)
89
+ if not ALLOW_DELETE:
90
+ blocked.update(DELETE_STATEMENTS)
91
+ if not ALLOW_DDL:
92
+ blocked.update(DDL_STATEMENTS)
93
+ if not ALLOW_EXEC:
94
+ blocked.update(EXEC_STATEMENTS)
95
+
96
+ return blocked
97
+
98
+
99
+ def get_permission_summary() -> dict:
100
+ """Get a summary of current permission settings."""
101
+ return {
102
+ "readonly_mode": READONLY_MODE,
103
+ "allow_insert": is_insert_allowed(),
104
+ "allow_update": is_update_allowed(),
105
+ "allow_delete": is_delete_allowed(),
106
+ "allow_ddl": is_ddl_allowed(),
107
+ "allow_exec": is_exec_allowed(),
108
+ "blocked_statements": list(get_blocked_statements()),
109
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  from typing import Annotated
4
4
 
5
- from ..config import READONLY_MODE
5
+ from ..config import is_readonly
6
6
  from ..utils import format_result, get_connection, rows_to_dicts, validate_query
7
7
 
8
8
 
@@ -30,10 +30,10 @@ def execute(
30
30
  sql: Annotated[str, "The SQL statement to execute (INSERT, UPDATE, DELETE, CREATE, etc.)"]
31
31
  ) -> str:
32
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
- })
33
+ # Use validate_query for granular permission checking
34
+ is_valid, error_msg = validate_query(sql)
35
+ if not is_valid:
36
+ return format_result({"error": error_msg})
37
37
 
38
38
  conn = get_connection()
39
39
  try:
@@ -6,7 +6,7 @@ import shutil
6
6
  from pathlib import Path
7
7
  from typing import Annotated
8
8
 
9
- from ..config import READONLY_MODE
9
+ from ..config import is_exec_allowed
10
10
  from ..utils import (
11
11
  format_result,
12
12
  get_connection,
@@ -488,8 +488,8 @@ def update_job_step_from_file(
488
488
  if not validation_result["valid"]:
489
489
  return format_result({"success": False, "error": f"SQL validation failed: {validation_result['error']}"})
490
490
 
491
- if READONLY_MODE:
492
- return format_result({"success": False, "error": "Write operations are disabled in read-only mode."})
491
+ if not is_exec_allowed():
492
+ return format_result({"success": False, "error": "EXEC operations are disabled by configuration."})
493
493
 
494
494
  # Update job step
495
495
  try:
@@ -627,8 +627,8 @@ def create_job_step_from_file(
627
627
 
628
628
  db_name = database_name or "master"
629
629
 
630
- if READONLY_MODE:
631
- return format_result({"success": False, "error": "Write operations are disabled in read-only mode."})
630
+ if not is_exec_allowed():
631
+ return format_result({"success": False, "error": "EXEC operations are disabled by configuration."})
632
632
 
633
633
  # Create job step
634
634
  try:
@@ -4,7 +4,7 @@ import re
4
4
  from pathlib import Path
5
5
  from typing import Annotated
6
6
 
7
- from ..config import READONLY_MODE
7
+ from ..config import is_ddl_allowed
8
8
  from ..utils import (
9
9
  format_result,
10
10
  get_connection,
@@ -323,8 +323,8 @@ def update_procedure_from_file(
323
323
  elif not sql_upper.startswith("ALTER"):
324
324
  return format_result({"success": False, "error": "SQL must contain CREATE or ALTER PROCEDURE statement"})
325
325
 
326
- if READONLY_MODE:
327
- return format_result({"success": False, "error": "Write operations are disabled in read-only mode."})
326
+ if not is_ddl_allowed():
327
+ return format_result({"success": False, "error": "DDL operations (ALTER PROCEDURE) are disabled by configuration."})
328
328
 
329
329
  safe_db_name = "".join(c for c in database_name if c.isalnum() or c in "_-")
330
330
 
mssql_agent_mcp/utils.py CHANGED
@@ -7,27 +7,36 @@ import struct
7
7
  import pyodbc
8
8
  import sqlparse
9
9
 
10
- from .config import BLOCKED_STATEMENT_TYPES, DB_CONFIG, READONLY_MODE
10
+ from .config import DB_CONFIG, get_blocked_statements, is_readonly
11
11
 
12
12
 
13
13
  def validate_query(sql: str) -> tuple[bool, str]:
14
- """Validate SQL query against readonly restrictions."""
15
- if not READONLY_MODE:
14
+ """Validate SQL query against permission restrictions."""
15
+ blocked_statements = get_blocked_statements()
16
+
17
+ # If nothing is blocked, allow all queries
18
+ if not blocked_statements:
16
19
  return True, ""
17
20
 
18
21
  try:
19
22
  parsed = sqlparse.parse(sql)
20
23
  for statement in parsed:
21
24
  stmt_type = statement.get_type()
22
- if stmt_type and stmt_type.upper() in BLOCKED_STATEMENT_TYPES:
23
- return False, f"Blocked: {stmt_type} statements are not allowed in read-only mode."
25
+ if stmt_type and stmt_type.upper() in blocked_statements:
26
+ if is_readonly():
27
+ return False, f"Blocked: {stmt_type} statements are not allowed in read-only mode."
28
+ else:
29
+ return False, f"Blocked: {stmt_type} statements are disabled by configuration."
24
30
 
25
31
  tokens = [t for t in statement.tokens if not t.is_whitespace]
26
32
  if tokens:
27
33
  first_token = str(tokens[0]).upper().strip()
28
- for blocked in BLOCKED_STATEMENT_TYPES:
34
+ for blocked in blocked_statements:
29
35
  if first_token.startswith(blocked):
30
- return False, f"Blocked: {blocked} statements are not allowed in read-only mode."
36
+ if is_readonly():
37
+ return False, f"Blocked: {blocked} statements are not allowed in read-only mode."
38
+ else:
39
+ return False, f"Blocked: {blocked} statements are disabled by configuration."
31
40
  except Exception as e:
32
41
  return False, f"Query validation failed: {str(e)}"
33
42
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mssql-agent-mcp
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: MCP server for Microsoft SQL Server with SQL Agent job management and stored procedure version control
5
5
  Project-URL: Homepage, https://github.com/yyinhsu/mssql-agent-mcp
6
6
  Project-URL: Repository, https://github.com/yyinhsu/mssql-agent-mcp
@@ -37,6 +37,10 @@ Description-Content-Type: text/markdown
37
37
 
38
38
  # MSSQL Agent MCP
39
39
 
40
+ [![PyPI version](https://badge.fury.io/py/mssql-agent-mcp.svg)](https://badge.fury.io/py/mssql-agent-mcp)
41
+ [![Python](https://img.shields.io/pypi/pyversions/mssql-agent-mcp.svg)](https://pypi.org/project/mssql-agent-mcp/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
43
+
40
44
  A Model Context Protocol (MCP) server that provides tools for interacting with Microsoft SQL Server databases and managing SQL Server Agent Jobs.
41
45
  Beyond basic database query functionality, this server supports managing stored procedures and SQL Server Agent jobs as code. Users can easily export, edit, and update these objects, and integrate them into version control systems.
42
46
 
@@ -329,7 +333,9 @@ Download and install from [Microsoft ODBC Driver for SQL Server](https://docs.mi
329
333
 
330
334
  ## Installation
331
335
 
332
- ### Using pip
336
+ This package is available on [PyPI](https://pypi.org/project/mssql-agent-mcp/).
337
+
338
+ ### Using pip (Recommended)
333
339
 
334
340
  ```bash
335
341
  pip install mssql-agent-mcp
@@ -341,9 +347,16 @@ pip install mssql-agent-mcp
341
347
  pip install mssql-agent-mcp[azure]
342
348
  ```
343
349
 
350
+ ### Using uvx (No Installation Required)
351
+
352
+ ```bash
353
+ uvx mssql-agent-mcp
354
+ ```
355
+
344
356
  ### Development Installation
345
357
 
346
358
  ```bash
359
+ git clone https://github.com/yyinhsu/mssql-agent-mcp.git
347
360
  cd mssql-agent-mcp
348
361
  pip install -e .
349
362
  ```
@@ -366,6 +379,40 @@ MSSQL_READONLY=true
366
379
  MSSQL_READONLY=false
367
380
  ```
368
381
 
382
+ ### Granular Permission Control
383
+
384
+ When `MSSQL_READONLY=false`, you can further control which operations are allowed using these environment variables:
385
+
386
+ | Variable | Default | Controls |
387
+ |----------|---------|----------|
388
+ | `MSSQL_ALLOW_INSERT` | `true` | INSERT, MERGE statements |
389
+ | `MSSQL_ALLOW_UPDATE` | `true` | UPDATE statements |
390
+ | `MSSQL_ALLOW_DELETE` | `true` | DELETE, TRUNCATE statements |
391
+ | `MSSQL_ALLOW_DDL` | `true` | CREATE, ALTER, DROP, GRANT, REVOKE, DENY |
392
+ | `MSSQL_ALLOW_EXEC` | `true` | EXEC, EXECUTE (also controls job step updates) |
393
+
394
+ **Example: Allow only SELECT and INSERT:**
395
+ ```env
396
+ MSSQL_READONLY=false
397
+ MSSQL_ALLOW_INSERT=true
398
+ MSSQL_ALLOW_UPDATE=false
399
+ MSSQL_ALLOW_DELETE=false
400
+ MSSQL_ALLOW_DDL=false
401
+ MSSQL_ALLOW_EXEC=false
402
+ ```
403
+
404
+ **Example: Allow data modifications but block schema changes:**
405
+ ```env
406
+ MSSQL_READONLY=false
407
+ MSSQL_ALLOW_INSERT=true
408
+ MSSQL_ALLOW_UPDATE=true
409
+ MSSQL_ALLOW_DELETE=true
410
+ MSSQL_ALLOW_DDL=false
411
+ MSSQL_ALLOW_EXEC=false
412
+ ```
413
+
414
+ > **Note**: These granular settings only take effect when `MSSQL_READONLY=false`. When `MSSQL_READONLY=true`, all write operations are blocked regardless of other settings.
415
+
369
416
  ### Environment Variables
370
417
 
371
418
  | Variable | Default | Description |
@@ -379,7 +426,12 @@ MSSQL_READONLY=false
379
426
  | `MSSQL_ENCRYPT` | `yes` | Connection encryption (`yes`/`no`) |
380
427
  | `MSSQL_TRUST_SERVER_CERTIFICATE` | `no` | Trust self-signed certificates (`yes`/`no`) |
381
428
  | `MSSQL_AUTH_MODE` | `sql` | Authentication mode: `sql`, `windows`, or `azure` |
382
- | `MSSQL_READONLY` | `true` | Block write operations (`true`/`false`) |
429
+ | `MSSQL_READONLY` | `true` | Block all write operations (`true`/`false`) |
430
+ | `MSSQL_ALLOW_INSERT` | `true` | Allow INSERT/MERGE (when not readonly) |
431
+ | `MSSQL_ALLOW_UPDATE` | `true` | Allow UPDATE (when not readonly) |
432
+ | `MSSQL_ALLOW_DELETE` | `true` | Allow DELETE/TRUNCATE (when not readonly) |
433
+ | `MSSQL_ALLOW_DDL` | `true` | Allow DDL operations (when not readonly) |
434
+ | `MSSQL_ALLOW_EXEC` | `true` | Allow EXEC/EXECUTE (when not readonly) | |
383
435
 
384
436
  ### Authentication Modes
385
437
 
@@ -440,28 +492,26 @@ Add to your Claude Desktop configuration file:
440
492
  {
441
493
  "mcpServers": {
442
494
  "mssql": {
443
- "command": "python",
444
- "args": ["-m", "mssql_mcp.server"],
445
- "cwd": "/path/to/eagle_eye_sql_agent_job/mssql_db_mcp/src",
495
+ "command": "mssql-agent-mcp",
446
496
  "env": {
447
497
  "MSSQL_SERVER": "localhost",
448
498
  "MSSQL_DATABASE": "your_database",
449
499
  "MSSQL_USER": "your_username",
450
- "MSSQL_PASSWORD": "your_password",
451
- "MSSQL_PORT": "1433"
500
+ "MSSQL_PASSWORD": "your_password"
452
501
  }
453
502
  }
454
503
  }
455
504
  }
456
505
  ```
457
506
 
458
- Or if installed as a package:
507
+ Or using uvx (no installation required):
459
508
 
460
509
  ```json
461
510
  {
462
511
  "mcpServers": {
463
512
  "mssql": {
464
- "command": "mssql-agent-mcp",
513
+ "command": "uvx",
514
+ "args": ["mssql-agent-mcp"],
465
515
  "env": {
466
516
  "MSSQL_SERVER": "localhost",
467
517
  "MSSQL_DATABASE": "your_database",
@@ -481,9 +531,26 @@ Add to your VS Code MCP configuration (`.vscode/mcp.json`):
481
531
  {
482
532
  "servers": {
483
533
  "mssql": {
484
- "command": "python",
485
- "args": ["-m", "mssql_mcp.server"],
486
- "cwd": "${workspaceFolder}/mssql_db_mcp/src",
534
+ "command": "mssql-agent-mcp",
535
+ "env": {
536
+ "MSSQL_SERVER": "localhost",
537
+ "MSSQL_DATABASE": "your_database",
538
+ "MSSQL_USER": "your_username",
539
+ "MSSQL_PASSWORD": "your_password"
540
+ }
541
+ }
542
+ }
543
+ }
544
+ ```
545
+
546
+ Or using uvx:
547
+
548
+ ```json
549
+ {
550
+ "servers": {
551
+ "mssql": {
552
+ "command": "uvx",
553
+ "args": ["mssql-agent-mcp"],
487
554
  "env": {
488
555
  "MSSQL_SERVER": "localhost",
489
556
  "MSSQL_DATABASE": "your_database",
@@ -0,0 +1,14 @@
1
+ mssql_agent_mcp/__init__.py,sha256=MfmrU93VrH_hW9q2jUMDS2ZAVlWCNgtsFU7EYmNv2zg,238
2
+ mssql_agent_mcp/__main__.py,sha256=_Er170jrffogRV4UfASEsoITnb4yNZW3Z9pSkQaE7YY,129
3
+ mssql_agent_mcp/config.py,sha256=PhddzuTEVHeFYCBg0FmRL615BxVUnXsYDpE6jRSHMNQ,3728
4
+ mssql_agent_mcp/server.py,sha256=jneped9wQ0Awp1VAB9n5da6ppfnJXajSgUXHS-WzX18,1992
5
+ mssql_agent_mcp/utils.py,sha256=5ol1KOiK_Kc-LetwoYovYTuYTVqb_80W34gD1pWzfg8,4840
6
+ mssql_agent_mcp/tools/__init__.py,sha256=2qVsw2nYKa0KnbVQxBhwJVQ_chWyqHxqA62fyo1wGZU,1272
7
+ mssql_agent_mcp/tools/database.py,sha256=Atx_ufTXLU55-YLTGIKZ6annEC2taljxjZcjQakmMz0,7351
8
+ mssql_agent_mcp/tools/jobs.py,sha256=p92P-OeX9iuhxVoYRADR7WoAN-acQni4uJWDfiz6OUg,26713
9
+ mssql_agent_mcp/tools/procedures.py,sha256=k2GVBEmXEITRJgHr-n6nURX0k_RGgnmXZEr14_Tr-g8,14433
10
+ mssql_agent_mcp-1.1.0.dist-info/METADATA,sha256=y5yzbG6kuimutcx9ERslnECpD4JkLQdICm2lpbVpYCQ,20647
11
+ mssql_agent_mcp-1.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
+ mssql_agent_mcp-1.1.0.dist-info/entry_points.txt,sha256=u6w8znCIeZxC6bnBRGqtbE4JTVZ822eX0bW2uF38MuU,64
13
+ mssql_agent_mcp-1.1.0.dist-info/licenses/LICENSE,sha256=JD9Y1eaSlUIIYz8SZDUVAa6pe7wH6aNF87PlbavycNI,1063
14
+ mssql_agent_mcp-1.1.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- mssql_agent_mcp/__init__.py,sha256=MfmrU93VrH_hW9q2jUMDS2ZAVlWCNgtsFU7EYmNv2zg,238
2
- mssql_agent_mcp/__main__.py,sha256=_Er170jrffogRV4UfASEsoITnb4yNZW3Z9pSkQaE7YY,129
3
- mssql_agent_mcp/config.py,sha256=tIAKUwrF40R6lZWpgoDyP2ueHcBIdcTdF2yKOLusLKY,1001
4
- mssql_agent_mcp/server.py,sha256=jneped9wQ0Awp1VAB9n5da6ppfnJXajSgUXHS-WzX18,1992
5
- mssql_agent_mcp/utils.py,sha256=HnQnOy2XZWmf-Huw__cvUdFcB8LVmBl9Vjos-tCejAU,4406
6
- mssql_agent_mcp/tools/__init__.py,sha256=2qVsw2nYKa0KnbVQxBhwJVQ_chWyqHxqA62fyo1wGZU,1272
7
- mssql_agent_mcp/tools/database.py,sha256=Rj1ESc88UOguc_-AxyqUtjoIqFgiLW8uZG0UgcfvRy4,7349
8
- mssql_agent_mcp/tools/jobs.py,sha256=f-WsuWX_LsNNSQ9oLzD8ay-OYCrop-uMypksIwS-_tk,26699
9
- mssql_agent_mcp/tools/procedures.py,sha256=WAPg2BKMiOBso4_H8tAE1f7wI8IiOID3vdHf3dh2MAk,14410
10
- mssql_agent_mcp-1.0.0.dist-info/METADATA,sha256=LklUZR_77LCi9SjcbIkyaLtcG_qCoQlVVaU8Id3_Ch8,18443
11
- mssql_agent_mcp-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
- mssql_agent_mcp-1.0.0.dist-info/entry_points.txt,sha256=u6w8znCIeZxC6bnBRGqtbE4JTVZ822eX0bW2uF38MuU,64
13
- mssql_agent_mcp-1.0.0.dist-info/licenses/LICENSE,sha256=JD9Y1eaSlUIIYz8SZDUVAa6pe7wH6aNF87PlbavycNI,1063
14
- mssql_agent_mcp-1.0.0.dist-info/RECORD,,