pggm-mcp-snowflake-server 0.1.0__py3-none-any.whl → 0.1.1__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.
@@ -1,3 +1,111 @@
1
- from .server import main
1
+ import argparse
2
+ import asyncio
3
+ import os
2
4
 
3
- __all__ = ["main"]
5
+ import dotenv
6
+ import snowflake.connector
7
+
8
+ from . import server
9
+
10
+
11
+ def parse_args():
12
+ parser = argparse.ArgumentParser()
13
+
14
+ # Add arguments
15
+ parser.add_argument(
16
+ "--allow_write", required=False, default=False, action="store_true", help="Allow write operations on the database"
17
+ )
18
+ parser.add_argument("--log_dir", required=False, default=None, help="Directory to log to")
19
+ parser.add_argument("--log_level", required=False, default="INFO", help="Logging level")
20
+ parser.add_argument(
21
+ "--prefetch",
22
+ action="store_true",
23
+ dest="prefetch",
24
+ default=True,
25
+ help="Prefetch table descriptions (when enabled, list_tables and describe_table are disabled)",
26
+ )
27
+ parser.add_argument(
28
+ "--no-prefetch",
29
+ action="store_false",
30
+ dest="prefetch",
31
+ help="Don't prefetch table descriptions",
32
+ )
33
+ parser.add_argument(
34
+ "--exclude_tools",
35
+ required=False,
36
+ default=[],
37
+ nargs="+",
38
+ help="List of tools to exclude",
39
+ )
40
+
41
+ # First, get all the arguments we don't know about
42
+ args, unknown = parser.parse_known_args()
43
+
44
+ # Create a dictionary to store our key-value pairs
45
+ connection_args = {}
46
+
47
+ # Iterate through unknown args in pairs
48
+ for i in range(0, len(unknown), 2):
49
+ if i + 1 >= len(unknown):
50
+ break
51
+
52
+ key = unknown[i]
53
+ value = unknown[i + 1]
54
+
55
+ # Make sure it's a keyword argument (starts with --)
56
+ if key.startswith("--"):
57
+ key = key[2:] # Remove the '--'
58
+ connection_args[key] = value
59
+
60
+ # Now we can add the known args to kwargs
61
+ server_args = {
62
+ "allow_write": args.allow_write,
63
+ "log_dir": args.log_dir,
64
+ "log_level": args.log_level,
65
+ "prefetch": args.prefetch,
66
+ "exclude_tools": args.exclude_tools,
67
+ }
68
+
69
+ return server_args, connection_args
70
+
71
+
72
+ def main():
73
+ """Main entry point for the package."""
74
+
75
+ dotenv.load_dotenv()
76
+
77
+ default_connection_args = snowflake.connector.connection.DEFAULT_CONFIGURATION
78
+
79
+ connection_args_from_env = {
80
+ k: os.getenv("SNOWFLAKE_" + k.upper())
81
+ for k in default_connection_args
82
+ if os.getenv("SNOWFLAKE_" + k.upper()) is not None
83
+ }
84
+
85
+ server_args, connection_args = parse_args()
86
+
87
+ connection_args = {**connection_args_from_env, **connection_args}
88
+
89
+ assert (
90
+ "database" in connection_args
91
+ ), 'You must provide the account identifier as "--database" argument or "SNOWFLAKE_DATABASE" environment variable.'
92
+ assert (
93
+ "schema" in connection_args
94
+ ), 'You must provide the username as "--schema" argument or "SNOWFLAKE_SCHEMA" environment variable.'
95
+
96
+ asyncio.run(
97
+ server.main(
98
+ connection_args=connection_args,
99
+ allow_write=server_args["allow_write"],
100
+ log_dir=server_args["log_dir"],
101
+ log_level=server_args["log_level"],
102
+ exclude_tools=server_args["exclude_tools"],
103
+ )
104
+ )
105
+
106
+
107
+ # Optionally expose other important items at package level
108
+ __all__ = ["main", "server", "write_detector"]
109
+
110
+ if __name__ == "__main__":
111
+ main()
@@ -23,7 +23,7 @@ logging.basicConfig(
23
23
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
24
24
  handlers=[logging.StreamHandler()],
25
25
  )
26
- logger = logging.getLogger("pggm_mcp_snowflake_server")
26
+ logger = logging.getLogger("mcp_snowflake_server")
27
27
 
28
28
 
29
29
  def data_to_yaml(data: Any) -> str:
@@ -351,7 +351,7 @@ async def main(
351
351
  if log_dir:
352
352
  os.makedirs(log_dir, exist_ok=True)
353
353
  logger.handlers.append(
354
- logging.FileHandler(os.path.join(log_dir, "pggm_mcp_snowflake_server.log"))
354
+ logging.FileHandler(os.path.join(log_dir, "mcp_snowflake_server.log"))
355
355
  )
356
356
  if log_level:
357
357
  logger.setLevel(log_level)
@@ -614,10 +614,10 @@ async def main(
614
614
  write_stream,
615
615
  InitializationOptions(
616
616
  server_name="snowflake",
617
- server_version=importlib.metadata.version("pggm_mcp_snowflake_server"),
617
+ server_version=importlib.metadata.version("mcp_snowflake_server"),
618
618
  capabilities=server.get_capabilities(
619
619
  notification_options=NotificationOptions(),
620
620
  experimental_capabilities={},
621
621
  ),
622
622
  ),
623
- )
623
+ )
@@ -1,36 +1,98 @@
1
- class SQLWriteDetector:
2
- """Utility class to detect write operations in SQL queries."""
1
+ import sqlparse
2
+ from sqlparse.sql import Token, TokenList
3
+ from sqlparse.tokens import Keyword, DML, DDL
4
+ from typing import Dict, List, Set, Tuple
5
+
3
6
 
7
+ class SQLWriteDetector:
4
8
  def __init__(self):
5
- self.write_keywords = [
6
- "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE",
7
- "GRANT", "REVOKE", "MERGE", "UPSERT", "REPLACE"
8
- ]
9
-
10
- def analyze_query(self, query: str) -> dict:
9
+ # Define sets of keywords that indicate write operations
10
+ self.dml_write_keywords = {"INSERT", "UPDATE", "DELETE", "MERGE", "UPSERT", "REPLACE"}
11
+
12
+ self.ddl_keywords = {"CREATE", "ALTER", "DROP", "TRUNCATE", "RENAME"}
13
+
14
+ self.dcl_keywords = {"GRANT", "REVOKE"}
15
+
16
+ # Combine all write keywords
17
+ self.write_keywords = self.dml_write_keywords | self.ddl_keywords | self.dcl_keywords
18
+
19
+ def analyze_query(self, sql_query: str) -> Dict:
11
20
  """
12
- Analyze an SQL query to determine if it contains write operations.
13
-
21
+ Analyze a SQL query to determine if it contains write operations.
22
+
14
23
  Args:
15
- query: SQL query string to analyze
16
-
24
+ sql_query: The SQL query string to analyze
25
+
17
26
  Returns:
18
- Dictionary with analysis results
27
+ Dictionary containing analysis results
19
28
  """
20
- result = {
21
- "contains_write": False,
22
- "write_operations": [],
23
- "query_type": "READ"
29
+ # Parse the SQL query
30
+ parsed = sqlparse.parse(sql_query)
31
+ if not parsed:
32
+ return {"contains_write": False, "write_operations": set(), "has_cte_write": False}
33
+
34
+ # Initialize result tracking
35
+ found_operations = set()
36
+ has_cte_write = False
37
+
38
+ # Analyze each statement in the query
39
+ for statement in parsed:
40
+ # Check for write operations in CTEs (WITH clauses)
41
+ if self._has_cte(statement):
42
+ cte_write = self._analyze_cte(statement)
43
+ if cte_write:
44
+ has_cte_write = True
45
+ found_operations.add("CTE_WRITE")
46
+
47
+ # Analyze the main query
48
+ operations = self._find_write_operations(statement)
49
+ found_operations.update(operations)
50
+
51
+ return {
52
+ "contains_write": bool(found_operations) or has_cte_write,
53
+ "write_operations": found_operations,
54
+ "has_cte_write": has_cte_write,
24
55
  }
25
-
26
- # Convert to uppercase for case-insensitive comparison
27
- upper_query = query.upper()
28
-
29
- # Check for write keywords
30
- for keyword in self.write_keywords:
31
- if keyword in upper_query.split():
32
- result["contains_write"] = True
33
- result["write_operations"].append(keyword)
34
- result["query_type"] = "WRITE"
35
-
36
- return result
56
+
57
+ def _has_cte(self, statement: TokenList) -> bool:
58
+ """Check if the statement has a WITH clause."""
59
+ return any(token.is_keyword and token.normalized == "WITH" for token in statement.tokens)
60
+
61
+ def _analyze_cte(self, statement: TokenList) -> bool:
62
+ """
63
+ Analyze CTEs (WITH clauses) for write operations.
64
+ Returns True if any CTE contains a write operation.
65
+ """
66
+ in_cte = False
67
+ for token in statement.tokens:
68
+ if token.is_keyword and token.normalized == "WITH":
69
+ in_cte = True
70
+ elif in_cte:
71
+ if any(write_kw in token.normalized for write_kw in self.write_keywords):
72
+ return True
73
+ return False
74
+
75
+ def _find_write_operations(self, statement: TokenList) -> Set[str]:
76
+ """
77
+ Find all write operations in a statement.
78
+ Returns a set of found write operation keywords.
79
+ """
80
+ operations = set()
81
+
82
+ for token in statement.tokens:
83
+ # Skip comments and whitespace
84
+ if token.is_whitespace or token.ttype in (sqlparse.tokens.Comment,):
85
+ continue
86
+
87
+ # Check if token is a keyword
88
+ if token.ttype in (Keyword, DML, DDL):
89
+ normalized = token.normalized.upper()
90
+ if normalized in self.write_keywords:
91
+ operations.add(normalized)
92
+
93
+ # Recursively check child tokens
94
+ if isinstance(token, TokenList):
95
+ child_ops = self._find_write_operations(token)
96
+ operations.update(child_ops)
97
+
98
+ return operations
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: pggm-mcp-snowflake-server
3
+ Version: 0.1.1
4
+ Summary: Custom Model Context Protocol server for Snowflake
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: mcp
11
+ Requires-Dist: pydantic
12
+ Requires-Dist: snowflake-snowpark-python
13
+ Requires-Dist: pyyaml
14
+
15
+ # PGGM MCP Snowflake Server
16
+
17
+ A customized Model Context Protocol (MCP) server for Snowflake integration, allowing AI assistants to interact with Snowflake databases securely and efficiently.
18
+
19
+ ## Features
20
+
21
+ - Connect to Snowflake databases and execute queries
22
+ - Support for various SQL operations and schema exploration
23
+ - Data insights collection and memoization
24
+ - SQL write operation detection for enhanced security
25
+ - Customizable database, schema, and table filtering
26
+ - Support for authentication through environment variables or command-line arguments
27
+
28
+ ## Installation
29
+
30
+ ### Using pip
31
+
32
+ ```bash
33
+ pip install pggm-mcp-snowflake-server
34
+ ```
35
+
36
+
37
+ ### Tools Available to AI Assistants
38
+
39
+ The server provides the following tools for AI assistants:
40
+
41
+ - `list_databases` - List all available databases in Snowflake
42
+ - `list_schemas` - List all schemas in a database
43
+ - `list_tables` - List all tables in a specific database and schema
44
+ - `describe_table` - Get the schema information for a specific table
45
+ - `read_query` - Execute a SELECT query
46
+ - `append_insight` - Add a data insight to the memo
47
+ - `write_query` - Execute an INSERT, UPDATE, or DELETE query (if --allow_write is enabled)
48
+ - `create_table` - Create a new table in the Snowflake database (if --allow_write is enabled)
49
+
50
+ ## Security
51
+
52
+ By default, the server runs in read-only mode. To enable write operations, you must explicitly pass the `--allow_write` flag.
53
+
54
+ The server uses SQL parsing to detect and prevent write operations in `read_query` calls, ensuring only approved write operations can be executed.
55
+
56
+ ## Development
57
+
58
+ ### Setup Development Environment
59
+
60
+ ```bash
61
+ git clone https://github.com/yourusername/pggm-mcp-snowflake-server.git
62
+ cd pggm-mcp-snowflake-server
63
+ uv venv
64
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
65
+ uv pip install -e ".[dev]"
66
+ ```
67
+
@@ -0,0 +1,8 @@
1
+ pggm_mcp_snowflake_server/__init__.py,sha256=fBi0ejuNSFNA_q0h1Jo6zMlsS0R1_xOW8sne9BygAHQ,3304
2
+ pggm_mcp_snowflake_server/server.py,sha256=hfINhJuS2s44obtKWvxfLek6Y5P6GmBLUgkhnr3DuD0,21959
3
+ pggm_mcp_snowflake_server/write_detector.py,sha256=qLFxghERiZT7-DRT8sGPAqrACTgO61QE29QXtH4CN2w,3635
4
+ pggm_mcp_snowflake_server-0.1.1.dist-info/METADATA,sha256=Okb1U8raBISqfDIvOydkYhL2KJ8cf_Z4Fwc97lHRjHM,2328
5
+ pggm_mcp_snowflake_server-0.1.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
6
+ pggm_mcp_snowflake_server-0.1.1.dist-info/entry_points.txt,sha256=kbUmsaZT0hYi6naFrGUO-mIKlwqHi_HKO6HNQKdTJE4,84
7
+ pggm_mcp_snowflake_server-0.1.1.dist-info/top_level.txt,sha256=ouamdLwMWx5aSlAHI_mhoPvm9PEBtovD3qbDvR7x284,26
8
+ pggm_mcp_snowflake_server-0.1.1.dist-info/RECORD,,
@@ -1,34 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pggm-mcp-snowflake-server
3
- Version: 0.1.0
4
- Summary: Custom Model Context Protocol server for Snowflake
5
- Classifier: Programming Language :: Python :: 3
6
- Classifier: License :: OSI Approved :: MIT License
7
- Classifier: Operating System :: OS Independent
8
- Requires-Python: >=3.8
9
- Description-Content-Type: text/markdown
10
- Requires-Dist: mcp
11
- Requires-Dist: pydantic
12
- Requires-Dist: snowflake-snowpark-python
13
- Requires-Dist: pyyaml
14
-
15
- # PGGM MCP Snowflake Server
16
-
17
- A customized Model Context Protocol (MCP) server for Snowflake integration, allowing AI assistants to interact with Snowflake databases.
18
-
19
- ## Features
20
-
21
- - Connect to Snowflake databases and execute queries
22
- - Support for various SQL operations and schema exploration
23
- - Data insights collection
24
- - Customized filters and configurations
25
-
26
- ## Installation
27
-
28
- ```bash
29
- pip install pggm-mcp-snowflake-server
30
- ```
31
-
32
- ## Usage
33
-
34
- This package is designed to be used with MCP-compatible AI assistants for database interactions.
@@ -1,8 +0,0 @@
1
- pggm_mcp_snowflake_server/__init__.py,sha256=bq16COjhclXSrjQP18TV21vWwJV24hIBrf4I7OfwZkE,48
2
- pggm_mcp_snowflake_server/server.py,sha256=4_Jlr_o5i4v_39-LuT9CFcqNwGMlNCXSZNgiiSp2vwg,21976
3
- pggm_mcp_snowflake_server/write_detector.py,sha256=Zli_U5tnIlCzpVoSYT7jPDBlDUSv4nyS1PnIrXpwZYc,1204
4
- pggm_mcp_snowflake_server-0.1.0.dist-info/METADATA,sha256=B6Pgk0AKQdZgmgWK19TZ-Z6jAH1g4YiKkGskqp-skXg,1015
5
- pggm_mcp_snowflake_server-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
6
- pggm_mcp_snowflake_server-0.1.0.dist-info/entry_points.txt,sha256=kbUmsaZT0hYi6naFrGUO-mIKlwqHi_HKO6HNQKdTJE4,84
7
- pggm_mcp_snowflake_server-0.1.0.dist-info/top_level.txt,sha256=ouamdLwMWx5aSlAHI_mhoPvm9PEBtovD3qbDvR7x284,26
8
- pggm_mcp_snowflake_server-0.1.0.dist-info/RECORD,,