This is page 1 of 3. Use http://codebase.md/davidamom/snowflake-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── __init__.py ├── .env.template ├── .gitignore ├── assets │ ├── communication_flow_sequence_bright.svg │ ├── communication_flow_sequence.svg │ ├── key_components_bright.svg │ └── key_components.svg ├── Dockerfile ├── LICENSE ├── pyproject.toml ├── README.md ├── requirements.txt ├── server.py ├── src │ ├── __init__.py │ └── snowflake_mcp │ ├── __init__.py │ ├── auth │ │ └── __init__.py │ ├── config │ │ └── __init__.py │ ├── connection │ │ └── __init__.py │ ├── server │ │ └── __init__.py │ └── utils │ └── __init__.py └── tests └── __init__.py ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Environment variables 2 | .env 3 | .venv/ 4 | 5 | # Python cache 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # Distribution / packaging 11 | dist/ 12 | build/ 13 | *.egg-info/ 14 | 15 | # Virtual environments 16 | venv/ 17 | env/ 18 | ENV/ 19 | 20 | # IDE files 21 | .vscode/ 22 | .idea/ 23 | *.swp 24 | *.swo 25 | 26 | # Logs 27 | *.log 28 | 29 | rsa_key.pub 30 | rsa_key.p8 31 | _venv ``` -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- ``` 1 | # Snowflake Configuration - Basic Info 2 | SNOWFLAKE_USER=your_username # Your Snowflake username 3 | SNOWFLAKE_ACCOUNT=your_account # Your Snowflake account 4 | SNOWFLAKE_DATABASE=your_database # Your Snowflake database 5 | SNOWFLAKE_WAREHOUSE=your_warehouse # Your Snowflake warehouse 6 | 7 | # Authentication Method - Choose one method 8 | 9 | # Option 1: Password Authentication 10 | SNOWFLAKE_PASSWORD=your_password # Your Snowflake password 11 | 12 | # Option 2: Key Pair Authentication - Choose A or B: 13 | 14 | # A) For private key WITHOUT passphrase: 15 | # SNOWFLAKE_PRIVATE_KEY_FILE=/path/to/rsa_key.p8 16 | # Leave SNOWFLAKE_PRIVATE_KEY_PASSPHRASE commented out or empty 17 | 18 | # B) For private key WITH passphrase: 19 | # SNOWFLAKE_PRIVATE_KEY_FILE=/path/to/rsa_key.p8 20 | # SNOWFLAKE_PRIVATE_KEY_PASSPHRASE=your_passphrase ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Snowflake MCP Service 2 | 3 | A Model Context Protocol (MCP) server that provides access to Snowflake databases for any MCP-compatible client. 4 | 5 |  6 |  7 | 8 | This server implements the Model Context Protocol to allow any MCP client to: 9 | - Execute SQL queries on Snowflake databases 10 | - Automatically handle database connection lifecycle (connect, reconnect on timeout, close) 11 | - Handle query results and errors 12 | - Perform database operations safely 13 | - Connect using either password or key pair authentication 14 | 15 | ## Architecture Overview 16 | 17 | ### What is MCP (Model Context Protocol)? 18 | 19 | MCP is a standard protocol that allows applications to communicate with AI models and external services. It enables AI models to access tools and data sources beyond their training data, expanding their capabilities through a standardized communication interface. Key features include: 20 | 21 | - Based on stdio communication (standard input/output) 22 | - Structured tool definition and discovery 23 | - Standardized tool call mechanism 24 | - Structured results transmission 25 | 26 | ### System Components 27 | 28 | The Snowflake-MCP server consists of several key components: 29 | 30 | 1. **MCP Server** - Central component that implements the MCP protocol and handles client requests 31 | 2. **Snowflake Connection Manager** - Manages database connections, including creation, maintenance, and cleanup 32 | 3. **Query Processor** - Executes SQL queries on Snowflake and processes the results 33 | 4. **Authentication Manager** - Handles different authentication methods (password or private key) 34 | 35 |  36 | 37 | ### Communication Flow 38 | 39 | The system works through the following communication flow: 40 | 41 | 1. An MCP Client (such as Claude or other MCP-compatible application) sends a request to the MCP Server 42 | 2. The MCP Server authenticates with Snowflake using credentials from the `.env` file 43 | 3. The MCP Server executes SQL queries on Snowflake 44 | 4. Snowflake returns results to the MCP Server 45 | 5. The MCP Server formats and sends the results back to the MCP Client 46 | 47 |  48 | 49 | This architecture allows for seamless integration between AI applications and Snowflake databases while maintaining security and efficient connection management. 50 | 51 | ## Installation 52 | 53 | 1. Clone this repository 54 | ```bash 55 | git clone https://github.com/davidamom/snowflake-mcp.git 56 | ``` 57 | 58 | 2. Install dependencies 59 | ```bash 60 | pip install -r requirements.txt 61 | ``` 62 | 63 | ## Configuration 64 | 65 | ### MCP Client Configuration Example 66 | 67 | Below is an example configuration for Claude Desktop, but this server works with any MCP-compatible client. Each client may have its own configuration method: 68 | 69 | ```json 70 | { 71 | "mcpServers": { 72 | "snowflake": { 73 | "command": "C:\\Users\\YourUsername\\path\\to\\python.exe", 74 | "args": ["C:\\path\\to\\snowflake-mcp\\server.py"] 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | Configuration parameters: 81 | - `command`: Full path to your Python interpreter. Please modify this according to your Python installation location. 82 | - `args`: Full path to the server script. Please modify this according to where you cloned the repository. 83 | 84 | Example paths for different operating systems: 85 | 86 | Windows: 87 | ```json 88 | { 89 | "mcpServers": { 90 | "snowflake": { 91 | "command": "C:\\Users\\YourUsername\\anaconda3\\python.exe", 92 | "args": ["C:\\Path\\To\\snowflake-mcp\\server.py"] 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | MacOS/Linux: 99 | ```json 100 | { 101 | "mcpServers": { 102 | "snowflake": { 103 | "command": "/usr/bin/python3", 104 | "args": ["/path/to/snowflake-mcp/server.py"] 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | ### Snowflake Configuration 111 | 112 | Create a `.env` file in the project root directory and add the following configuration: 113 | 114 | ```env 115 | # Snowflake Configuration - Basic Info 116 | SNOWFLAKE_USER=your_username # Your Snowflake username 117 | SNOWFLAKE_ACCOUNT=YourAccount.Region # Example: MyOrg.US-WEST-2 118 | SNOWFLAKE_DATABASE=your_database # Your database 119 | SNOWFLAKE_WAREHOUSE=your_warehouse # Your warehouse 120 | SNOWFLAKE_ROLE=your_role # Your role 121 | 122 | # Authentication - Choose one method 123 | ``` 124 | 125 | #### Authentication Options 126 | 127 | This MCP server supports two authentication methods: 128 | 129 | 1. **Password Authentication** 130 | ```env 131 | SNOWFLAKE_PASSWORD=your_password # Your Snowflake password 132 | ``` 133 | 134 | 2. **Key Pair Authentication** 135 | ```env 136 | SNOWFLAKE_PRIVATE_KEY_FILE=/path/to/rsa_key.p8 # Path to private key file 137 | SNOWFLAKE_PRIVATE_KEY_PASSPHRASE=your_passphrase # Optional: passphrase if key is encrypted 138 | ``` 139 | 140 | For key pair authentication, you must first set up key pair authentication with Snowflake: 141 | - Generate a key pair and register the public key with Snowflake 142 | - Store the private key file securely on your machine 143 | - Provide the full path to the private key file in the configuration 144 | 145 | For instructions on setting up key pair authentication, refer to [Snowflake documentation on key pair authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth). 146 | 147 | If both authentication methods are configured, the server will prioritize key pair authentication. 148 | 149 | ## Connection Management 150 | 151 | The server provides automatic connection management features: 152 | 153 | - Automatic connection initialization 154 | - Creates connection when first query is received 155 | - Validates connection parameters 156 | 157 | - Connection maintenance 158 | - Keeps track of connection state 159 | - Handles connection timeouts 160 | - Automatically reconnects if connection is lost 161 | 162 | - Connection cleanup 163 | - Properly closes connections when server stops 164 | - Releases resources appropriately 165 | 166 | ## Usage 167 | 168 | ### Standard Usage 169 | 170 | The server will start automatically when configured with your MCP client. No manual startup is required in normal operation. Once the server is running, your MCP client will be able to execute Snowflake queries. 171 | 172 | For development testing, you can start the server manually using: 173 | 174 | ```bash 175 | python server.py 176 | ``` 177 | 178 | Note: Manual server startup is not needed for normal use. The MCP client will typically manage server startup and shutdown based on the configuration. 179 | 180 | ### Docker Usage 181 | 182 | You can also run the server using Docker. This method is recommended for production environments and ensures consistent execution across different platforms. 183 | 184 | 1. Build the Docker image: 185 | ```bash 186 | docker build -t snowflake-mcp . 187 | ``` 188 | 189 | 2. Configure your MCP client to use Docker. Example configuration: 190 | ```json 191 | { 192 | "mcpServers": { 193 | "snowflake-docker": { 194 | "command": "docker", 195 | "args": [ 196 | "run", 197 | "-i", 198 | "snowflake-mcp" 199 | ], 200 | "env": { 201 | "SNOWFLAKE_USER": "your_username", 202 | "SNOWFLAKE_ACCOUNT": "your_account", 203 | "SNOWFLAKE_DATABASE": "your_database", 204 | "SNOWFLAKE_WAREHOUSE": "your_warehouse", 205 | "SNOWFLAKE_PASSWORD": "your_password", 206 | "SNOWFLAKE_ROLE": "your_role" 207 | 208 | } 209 | } 210 | } 211 | } 212 | ``` 213 | 214 | Note: The Docker implementation uses stdio for communication, so no ports need to be exposed. 215 | 216 | If using key pair authentication with Docker, you'll need to mount your private key file: 217 | ```bash 218 | docker run -i -v /path/to/your/key.p8:/app/rsa_key.p8:ro snowflake-mcp 219 | ``` 220 | 221 | And update your configuration accordingly: 222 | ```json 223 | { 224 | "mcpServers": { 225 | "Snowflake-Docker": { 226 | "command": "docker", 227 | "args": [ 228 | "run", 229 | "-i", 230 | "-v", 231 | "/path/to/your/key.p8:/app/rsa_key.p8:ro", 232 | //optional 233 | "-v", 234 | "/path/to/export/dir/:/export/" 235 | "snowflake-mcp" 236 | ], 237 | "env": { 238 | "SNOWFLAKE_USER": "your_username", 239 | "SNOWFLAKE_ACCOUNT": "your_account", 240 | "SNOWFLAKE_DATABASE": "your_database", 241 | "SNOWFLAKE_WAREHOUSE": "your_warehouse", 242 | "SNOWFLAKE_ROLE": "your_role", 243 | "SNOWFLAKE_PRIVATE_KEY_FILE": "path_for_your_private_key", 244 | "SNOWFLAKE_PRIVATE_KEY_PASSPHRASE": "your_password_for_private_key" 245 | } 246 | } 247 | } 248 | } 249 | ``` 250 | 251 | ## Features 252 | 253 | - Secure Snowflake database access 254 | - Flexible authentication (password or key pair authentication) 255 | - Robust error handling and reporting 256 | - Automatic connection management 257 | - Query execution and result processing 258 | - Compatible with any MCP-compliant client 259 | 260 | ## Technical Details 261 | 262 | ### Core Components 263 | 264 | The implementation consists of several key classes and modules: 265 | 266 | - **server.py** - The main entry point containing the MCP server implementation. 267 | - **SnowflakeConnection** - Class that handles all Snowflake database operations, including: 268 | - Connection establishment and reconnection 269 | - Query execution and transaction management 270 | - Connection maintenance and cleanup 271 | - **SnowflakeMCPServer** - The main server class that implements the MCP protocol: 272 | - Registers available tools with the MCP framework 273 | - Handles tool call requests from clients 274 | - Manages the lifecycle of connections 275 | 276 | ### Connection Lifecycle 277 | 278 | The connection lifecycle is carefully managed to ensure reliability: 279 | 280 | 1. **Initialization** - Connections are created lazily when the first query is received 281 | 2. **Validation** - Connection parameters are validated before attempting to connect 282 | 3. **Monitoring** - Connections are regularly tested for validity 283 | 4. **Recovery** - Automatic reconnection if the connection is lost or times out 284 | 5. **Cleanup** - Proper resource release when the server shuts down 285 | 286 | ### MCP Tool Interface 287 | 288 | The server exposes the following tool to MCP clients: 289 | 290 | - **execute_query** - Executes a SQL query on Snowflake and returns the results 291 | - Input: SQL query string 292 | - Output: Query results in a structured format 293 | 294 | - **export_to_csv** - Executes a SQL query on Snowflake and returns the results 295 | - Input: SQL query string 296 | - Output: Num rows exported. File path of the output file 297 | 298 | This implementation follows best practices for both MCP protocol implementation and Snowflake database interaction. 299 | 300 | ## License 301 | 302 | [](https://opensource.org/licenses/MIT) 303 | 304 | This project is licensed under the [MIT License](LICENSE). See the [LICENSE](LICENSE) file for details. 305 | 306 | Copyright (c) 2025 David Amom 307 | ``` -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /src/snowflake_mcp/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /src/snowflake_mcp/auth/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /src/snowflake_mcp/config/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /src/snowflake_mcp/connection/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /src/snowflake_mcp/server/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /src/snowflake_mcp/utils/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- ```python 1 | """Snowflake MCP Service""" 2 | 3 | __version__ = "0.1.0" ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` 1 | snowflake-connector-python[pandas]>=2.7.0 2 | python-dotenv>=0.19.0 3 | mcp>=1.0.0 4 | cryptography>=36.0.0 ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt . 6 | 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | COPY . . 10 | 11 | ENV PYTHONUNBUFFERED=1 12 | 13 | CMD ["python", "server.py"] ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | # Python project configuration 2 | [project] 3 | name = "snowflake-mcp" 4 | version = "0.1.0" 5 | description = "MCP server for interacting with Snowflake databases" 6 | readme = "README.md" 7 | requires-python = ">=3.10" # Required Python version 8 | dependencies = [ # Project dependencies 9 | "mcp>=1.0.0", # MCP SDK 10 | "snowflake-connector-python", # Snowflake connector 11 | "python-dotenv" # Environment variable management 12 | ] 13 | 14 | # Build system configuration 15 | [build-system] 16 | requires = ["hatchling"] # Build tool requirement 17 | build-backend = "hatchling.build" # Build backend 18 | 19 | # Build configuration 20 | [tool.hatch.build.targets.wheel] 21 | packages = ["src/snowflake_mcp"] # Package location 22 | 23 | # Entry points 24 | [project.scripts] 25 | snowflake-mcp = "snowflake_mcp.server:main" # Command line entry ``` -------------------------------------------------------------------------------- /assets/key_components_bright.svg: -------------------------------------------------------------------------------- ``` 1 | <?xml version="1.0" encoding="utf-8" standalone="yes"?> 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 3 | <svg width="770" height="530" viewBox="0 0 770 530" style="fill:none;stroke:none;fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style class="text-font-style fontImports" data-font-family="Roboto">@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=block');</style><g id="items" style="isolation: isolate"><g id="blend" style="mix-blend-mode: normal"><g id="g-root-0.g-0_fr_13z9kvs1fxylfy-fill" data-item-order="-311030" transform="translate(255, 159)"><g id="0.g-0_fr_13z9kvs1fxylfy-fill" stroke="none" fill="#f5f5f5"><g><path d="M 250 130C 250 196.274 196.274 250 130 250C 63.7258 250 10 196.274 10 130C 10 63.7258 63.7258 10 130 10C 196.274 10 250 63.7258 250 130Z"></path></g></g></g><g id="g-root-4.g-4_fr_zk2jug1fxyj2g-fill" data-item-order="-311026" transform="translate(183, 87)"><g id="4.g-4_fr_zk2jug1fxyj2g-fill" stroke="none" fill="#fffbda"><g><path d="M 94 10C 47.6081 10 10 47.6081 10 94C 10 137.183 42.5856 172.756 84.5093 177.47C 94.1869 130.879 130.879 94.1869 177.47 84.5093C 172.756 42.5856 137.183 10 94 10Z"></path></g></g></g><g id="g-root-3.g-3_fr_v491q01fxyjuq-fill" data-item-order="-311020" transform="translate(183, 303)"><g id="3.g-3_fr_v491q01fxyjuq-fill" stroke="none" fill="#f4ffdc"><g><path d="M 10 93.999097C 10 140.391297 47.6081 177.999297 94 177.999297C 137.183 177.999297 172.756 145.413297 177.47 103.489797C 130.879 93.812197 94.1869 57.119897 84.5093 10.529297C 42.5856 15.243497 10 50.815897 10 93.999097Z"></path></g></g></g><g id="g-root-2.g-2_fr_m8m1h41fxykng-fill" data-item-order="-311014" transform="translate(399, 303)"><g id="2.g-2_fr_m8m1h41fxykng-fill" stroke="none" fill="#e3fff2"><g><path d="M 94.000073 177.999297C 140.392273 177.999297 178.000273 140.391297 178.000273 93.999097C 178.000273 50.815897 145.414273 15.243597 103.490773 10.529297C 93.813173 57.119897 57.120873 93.812197 10.530273 103.489797C 15.244473 145.413297 50.816873 177.999297 94.000073 177.999297Z"></path></g></g></g><g id="g-root-1.g-1_fr_8xs06w1fxyj2m-fill" data-item-order="-311008" transform="translate(399, 87)"><g id="1.g-1_fr_8xs06w1fxyj2m-fill" stroke="none" fill="#e8f9ff"><g><path d="M 178.000212 94C 178.000212 47.6081 140.392212 10 94.000012 10C 50.816812 10 15.244412 42.5856 10.530212 84.5093C 57.120812 94.1869 93.813112 130.879 103.490712 177.47C 145.414212 172.756 178.000212 137.183 178.000212 94Z"></path></g></g></g><g id="g-root-tx_keycompo_1lsf0mw1fxyiv5-fill" data-item-order="0" transform="translate(99, 39)"><g id="tx_keycompo_1lsf0mw1fxyiv5-fill" stroke="none" fill="#484848"><g><text style="font: 20px Roboto, sans-serif; white-space: pre;" font-size="20px" font-family="Roboto, sans-serif"><tspan x="12.67" y="34" dominant-baseline="ideographic">Key Components Enhancing Snowflake-MCP Server Efficiency</tspan></text></g></g></g><g id="g-root-tx_authenti_hznraw1fy8fm7-fill" data-item-order="0" transform="translate(15, 93)"><g id="tx_authenti_hznraw1fy8fm7-fill" stroke="none" fill="#e0cb15"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="19.73" y="34" dominant-baseline="ideographic">Authentication </tspan><tspan x="72.31" y="58" dominant-baseline="ideographic">Manager</tspan></text></g></g></g><g id="g-root-tx_mcpserve_1q6d3i01fy9szx-fill" data-item-order="0" transform="translate(591, 99)"><g id="tx_mcpserve_1q6d3i01fy9szx-fill" stroke="none" fill="#1eabda"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="12" y="34" dominant-baseline="ideographic">MCP Server</tspan></text></g></g></g><g id="g-root-key_91iupk1fy8cul-fill" data-item-order="0" transform="translate(225, 129)"></g><g id="g-root-hier_8z0yd41fxyl8s-fill" data-item-order="0" transform="translate(477, 129)"></g><g id="g-root-tx_centralc_1cvj27s1fy9rf2-fill" data-item-order="0" transform="translate(591, 135)"><g id="tx_centralc_1cvj27s1fy9rf2-fill" stroke="none" fill="#484848"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="12" y="28" dominant-baseline="ideographic">Central component </tspan><tspan x="12" y="46" dominant-baseline="ideographic">managing protocol </tspan><tspan x="12" y="64" dominant-baseline="ideographic">and client </tspan><tspan x="12" y="82" dominant-baseline="ideographic">interactions</tspan></text></g></g></g><g id="g-root-tx_managesa_907w81fy8etk-fill" data-item-order="0" transform="translate(15, 153)"><g id="tx_managesa_907w81fy8etk-fill" stroke="none" fill="#484848"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="78.17" y="31" dominant-baseline="ideographic">Manages </tspan><tspan x="44.2" y="49" dominant-baseline="ideographic">authentication </tspan><tspan x="21.78" y="67" dominant-baseline="ideographic">methods securely</tspan></text></g></g></g><g id="g-root-tx_snowflak_mahgqg1fxyjnv-fill" data-item-order="0" transform="translate(315, 255)"><g id="tx_snowflak_mahgqg1fxyjnv-fill" stroke="none" fill="#969696"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="19.18" y="34" dominant-baseline="ideographic">Snowflake-</tspan><tspan x="16.4" y="58" dominant-baseline="ideographic">MCP Server</tspan></text></g></g></g><g id="g-root-tx_querypro_18ntq7s1fy8emh-fill" data-item-order="0" transform="translate(15, 345)"><g id="tx_querypro_18ntq7s1fy8emh-fill" stroke="none" fill="#92bd39"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="50.64" y="34" dominant-baseline="ideographic">Query </tspan><tspan x="12.41" y="58" dominant-baseline="ideographic">Processor</tspan></text></g></g></g><g id="g-root-tx_connecti_dbq3201fy9tz7-fill" data-item-order="0" transform="translate(591, 345)"><g id="tx_connecti_dbq3201fy9tz7-fill" stroke="none" fill="#3cc583"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="12" y="34" dominant-baseline="ideographic">Connection </tspan><tspan x="12" y="58" dominant-baseline="ideographic">Manager</tspan></text></g></g></g><g id="g-root-sear_dj7s3c1fy5kb0-fill" data-item-order="0" transform="translate(225, 381)"></g><g id="g-root-brid_zp2cjc1fy8f0m-fill" data-item-order="0" transform="translate(477, 381)"></g><g id="g-root-tx_executes_v303js1fy9rm2-fill" data-item-order="0" transform="translate(15, 405)"><g id="tx_executes_v303js1fy9rm2-fill" stroke="none" fill="#484848"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="26.88" y="31" dominant-baseline="ideographic">Executes and </tspan><tspan x="16.06" y="49" dominant-baseline="ideographic">processes SQL </tspan><tspan x="66.81" y="67" dominant-baseline="ideographic">queries</tspan></text></g></g></g><g id="g-root-tx_oversees_w1rs1fy9sec-fill" data-item-order="0" transform="translate(591, 405)"><g id="tx_oversees_w1rs1fy9sec-fill" stroke="none" fill="#484848"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="12" y="31" dominant-baseline="ideographic">Oversees database </tspan><tspan x="12" y="49" dominant-baseline="ideographic">connections </tspan><tspan x="12" y="67" dominant-baseline="ideographic">lifecycle</tspan></text></g></g></g><g id="g-root-0.g-0_fr_13z9kvs1fxylfy-stroke" data-item-order="-311030" transform="translate(255, 159)"><g id="0.g-0_fr_13z9kvs1fxylfy-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#969696" stroke-width="2"><g><path d="M 250 130C 250 196.2742 196.2742 250 130 250C 63.7258 250 10 196.2742 10 130C 10 63.7258 63.7258 10 130 10C 196.2742 10 250 63.7258 250 130Z"></path></g></g></g><g id="g-root-4.g-4_fr_zk2jug1fxyj2g-stroke" data-item-order="-311026" transform="translate(183, 87)"><g id="4.g-4_fr_zk2jug1fxyj2g-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#e0cb15" stroke-width="2"><g><path d="M 94 10C 47.6081 10 10 47.6081 10 94C 10 137.1832 42.5856 172.7555 84.5093 177.4698C 94.1869 130.8792 130.8792 94.1869 177.4698 84.5093C 172.7555 42.5856 137.1832 10 94 10ZM 93.999977 178.000466C 140.391877 178.000466 177.999977 140.392366 177.999977 94.000466C 177.999977 90.791666 177.820077 87.624966 177.469777 84.509766C 130.879177 94.187366 94.186877 130.879666 84.509277 177.470266C 87.624477 177.820566 90.791177 178.000466 93.999977 178.000466Z"></path></g></g></g><g id="g-root-3.g-3_fr_v491q01fxyjuq-stroke" data-item-order="-311020" transform="translate(183, 303)"><g id="3.g-3_fr_v491q01fxyjuq-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#92bd39" stroke-width="2"><g><path d="M 10 93.999097C 10 140.390997 47.6081 177.999097 94 177.999097C 137.1832 177.999097 172.7555 145.413497 177.4698 103.489797C 130.8792 93.812197 94.1869 57.119897 84.5093 10.529297C 42.5856 15.243497 10 50.815897 10 93.999097ZM 178.000015 94C 178.000015 47.6081 140.391915 10 94.000015 10C 90.791215 10 87.624515 10.1799 84.509315 10.5302C 94.186915 57.1208 130.879215 93.8131 177.469815 103.4907C 177.820115 100.3755 178.000015 97.2088 178.000015 94Z"></path></g></g></g><g id="g-root-2.g-2_fr_m8m1h41fxykng-stroke" data-item-order="-311014" transform="translate(399, 303)"><g id="2.g-2_fr_m8m1h41fxykng-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#3cc583" stroke-width="2"><g><path d="M 94.000073 177.999097C 140.391973 177.999097 178.000073 140.390997 178.000073 93.999097C 178.000073 50.815897 145.414473 15.243497 103.490773 10.529297C 93.813173 57.119897 57.120873 93.812197 10.530273 103.489797C 15.244473 145.413497 50.816873 177.999097 94.000073 177.999097ZM 94 10C 47.6081 10 10 47.6081 10 94C 10 97.2088 10.1799 100.3755 10.5302 103.4907C 57.1208 93.8131 93.8131 57.1208 103.4907 10.5302C 100.3755 10.1799 97.2088 10 94 10Z"></path></g></g></g><g id="g-root-1.g-1_fr_8xs06w1fxyj2m-stroke" data-item-order="-311008" transform="translate(399, 87)"><g id="1.g-1_fr_8xs06w1fxyj2m-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#1eabda" stroke-width="2"><g><path d="M 178.000012 94C 178.000012 47.6081 140.391912 10 94.000012 10C 50.816812 10 15.244412 42.5856 10.530212 84.5093C 57.120812 94.1869 93.813112 130.8792 103.490712 177.4698C 145.414412 172.7555 178.000012 137.1832 178.000012 94ZM 10 94.000466C 10 140.392366 47.6081 178.000466 94 178.000466C 97.2088 178.000466 100.3755 177.820566 103.4907 177.470266C 93.8131 130.879666 57.1208 94.187366 10.5302 84.509766C 10.1799 87.624966 10 90.791666 10 94.000466Z"></path></g></g></g><g id="g-root-tx_keycompo_1lsf0mw1fxyiv5-stroke" data-item-order="0" transform="translate(99, 39)"></g><g id="g-root-tx_authenti_hznraw1fy8fm7-stroke" data-item-order="0" transform="translate(15, 93)"></g><g id="g-root-tx_mcpserve_1q6d3i01fy9szx-stroke" data-item-order="0" transform="translate(591, 99)"></g><g id="g-root-key_91iupk1fy8cul-stroke" data-item-order="0" transform="translate(225, 129)"><g id="key_91iupk1fy8cul-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#e0cb15" stroke-width="2"><g><path d="M 42.810001 50.462002C 41.833622 51.438122 40.250832 51.437984 39.27462 50.461689C 38.298416 49.485397 38.298416 47.902603 39.27462 46.926311C 40.250832 45.95002 41.833622 45.949879 42.810001 46.925999C 43.785519 47.902821 43.785519 49.48518 42.810001 50.462002ZM 10 10M 48.096001 45.883999L 56.706001 37.293999C 56.894161 37.106426 56.999943 36.851685 57 36.585999L 57 34C 57 33.447716 56.552284 33 56 33L 53.400002 33C 53.144054 32.999924 52.89782 33.097984 52.712002 33.274002L 43.854 41.641998C 40.328316 40.148323 36.238579 41.362339 34.098885 44.53775C 31.959192 47.713158 32.369568 51.959496 35.077744 54.666393C 37.785919 57.373291 42.032452 57.78167 45.206848 55.64048C 48.381248 53.49929 49.593338 49.408978 48.098 45.883999ZM 16.5 19.5C 16.5 24.19442 20.30558 28 25 28C 29.69442 28 33.5 24.19442 33.5 19.5C 33.5 14.80558 29.69442 11 25 11C 20.30558 11 16.5 14.80558 16.5 19.5ZM 27 45L 11 45C 10.999902 39.114559 14.680733 33.857452 20.211155 31.844276C 25.741575 29.831099 31.940599 31.491751 35.723999 36"></path></g></g></g><g id="g-root-hier_8z0yd41fxyl8s-stroke" data-item-order="0" transform="translate(477, 129)"><g id="hier_8z0yd41fxyl8s-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#1eabda" stroke-width="2"><g><path d="M 23 26.002001C 23 28.763424 27.924868 31.002001 34 31.002001C 40.075134 31.002001 45 28.763424 45 26.002001C 45 23.240578 40.075134 21.001999 34 21.001999C 27.924868 21.001999 23 23.240578 23 26.002001ZM 47 14.002C 47 15.658854 49.238575 17.002001 52 17.002001C 54.761425 17.002001 57 15.658854 57 14.002001C 57 12.345146 54.761425 11.002 52 11.002C 49.238575 11.002 47 12.345146 47 14.002001ZM 57 14L 57 20C 57 21.656 54.762001 23 52 23C 49.237999 23 47 21.658001 47 20L 47 14M 11 14.002C 11 15.658854 13.238577 17.002001 16 17.002001C 18.761425 17.002001 21 15.658854 21 14.002001C 21 12.345146 18.761425 11.002 16 11.002C 13.238577 11.002 11 12.345146 11 14.002001ZM 21 14L 21 20C 21 21.656 18.762001 23 16 23C 13.238 23 11 21.658001 11 20L 11 14M 47 48.001999C 47 49.658855 49.238575 51.002003 52 51.002003C 54.761425 51.002003 57 49.658855 57 48.002003C 57 46.345146 54.761425 45.002003 52 45.002003C 49.238575 45.002003 47 46.345146 47 48.002003ZM 57 48L 57 54C 57 55.655998 54.762001 57 52 57C 49.237999 57 47 55.655998 47 54L 47 48M 11 48.001999C 11 49.658855 13.238577 51.002003 16 51.002003C 18.761425 51.002003 21 49.658855 21 48.002003C 21 46.345146 18.761425 45.002003 16 45.002003C 13.238577 45.002003 11 46.345146 11 48.002003ZM 21 48L 21 54C 21 55.655998 18.762001 57 16 57C 13.238 57 11 55.658001 11 54L 11 48M 45 34C 45 36.762001 40.073997 39 34 39C 27.926001 39 23 36.764 23 34M 23 26L 23 42C 23 44.762001 27.926001 47 34 47C 40.073997 47 45 44.762001 45 42L 45 26M 20.618 21.156L 23.702 24.24M 47.382 21.156L 44.298 24.24M 20.709999 46.993999L 23.812 43.889999M 47.290001 46.993999L 44.188 43.889999"></path></g></g></g><g id="g-root-tx_centralc_1cvj27s1fy9rf2-stroke" data-item-order="0" transform="translate(591, 135)"></g><g id="g-root-tx_managesa_907w81fy8etk-stroke" data-item-order="0" transform="translate(15, 153)"></g><g id="g-root-tx_snowflak_mahgqg1fxyjnv-stroke" data-item-order="0" transform="translate(315, 255)"></g><g id="g-root-tx_querypro_18ntq7s1fy8emh-stroke" data-item-order="0" transform="translate(15, 345)"></g><g id="g-root-tx_connecti_dbq3201fy9tz7-stroke" data-item-order="0" transform="translate(591, 345)"></g><g id="g-root-sear_dj7s3c1fy5kb0-stroke" data-item-order="0" transform="translate(225, 381)"><g id="sear_dj7s3c1fy5kb0-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#92bd39" stroke-width="2"><g><path d="M 35 44.057999C 35 49.060593 39.055405 53.115997 44.057999 53.115997C 49.060593 53.115997 53.115997 49.060593 53.115997 44.057999C 53.115997 39.055405 49.060593 35 44.057999 35C 39.055405 35 35 39.055405 35 44.057999ZM 57 57L 50.504002 50.504002M 19 27L 38 27M 19 33L 31 33M 19 21L 33 21M 19 39L 27 39M 19 45L 27 45M 35 57L 13 57C 11.895431 57 11 56.104568 11 55L 11 13C 11 11.895431 11.895431 11 13 11L 39.585999 11C 40.11639 11.000113 40.625015 11.210901 41 11.586L 48.414001 19C 48.789101 19.374985 48.999886 19.88361 49 20.414L 49 29"></path></g></g></g><g id="g-root-brid_zp2cjc1fy8f0m-stroke" data-item-order="0" transform="translate(477, 381)"><g id="brid_zp2cjc1fy8f0m-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#3cc583" stroke-width="2"><g><path d="M 56.987999 22.633999C 51.417332 21.449936 47.327156 16.685707 46.999996 11C 47 17.628 40.627998 23 34 23C 27.372 23 21 17.628 21 11C 20.672398 16.703152 16.559757 21.477453 10.968 22.646M 11 33L 57 33M 23 45L 23 33M 19 33L 19 43M 45 43L 45 33.001999M 49 33L 49 43M 15.002 20.966L 15.002 33M 21.001999 11L 21.001999 33M 27.002001 20.968L 27.002001 33M 35.001999 22.959999L 35.001999 33M 41.001999 21.001999L 41.001999 33M 47 11L 47.001999 33M 53.001999 20.966L 53.001999 32.996002M 56.981998 56.776001C 51.981998 57.818001 48.862 52.818001 48.862 52.818001L 48.862 52.818001C 47.290386 55.316471 44.57085 56.860619 41.620003 56.930004C 38.693253 56.786953 36.009048 55.260334 34.389999 52.818001L 34.389999 52.818001C 32.818771 55.315887 30.100136 56.859955 27.150002 56.930004C 24.222607 56.789177 21.537462 55.262028 19.919998 52.817997L 19.92 52.818001C 19.92 52.818001 15.92 57.902 11.01 56.858002M 56.981998 49.776001C 51.981998 50.818001 48.862 45.818001 48.862 45.818001L 48.862 45.818001C 47.290386 48.316471 44.57085 49.860619 41.620003 49.93C 38.693253 49.786953 36.009048 48.260334 34.389999 45.818001L 34.389999 45.818001C 32.818771 48.315887 30.100136 49.859955 27.150002 49.93C 24.222607 49.789177 21.537462 48.262028 19.919998 45.817997L 19.92 45.818001C 19.92 45.818001 15.92 50.902 11.01 49.858002"></path></g></g></g><g id="g-root-tx_executes_v303js1fy9rm2-stroke" data-item-order="0" transform="translate(15, 405)"></g><g id="g-root-tx_oversees_w1rs1fy9sec-stroke" data-item-order="0" transform="translate(591, 405)"></g></g></g></svg> ``` -------------------------------------------------------------------------------- /assets/key_components.svg: -------------------------------------------------------------------------------- ``` 1 | <?xml version="1.0" encoding="utf-8" standalone="yes"?> 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 3 | <svg width="770" height="530" viewBox="0 0 770 530" style="fill:none;stroke:none;fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style class="text-font-style fontImports" data-font-family="Roboto">@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=block');</style><g id="items" style="isolation: isolate"><g id="blend" style="mix-blend-mode: normal"><g id="g-root-0.g-0_fr_13z9kvs1fxylfy-fill" data-item-order="-311030" transform="translate(255, 159)"><g id="0.g-0_fr_13z9kvs1fxylfy-fill" stroke="none" fill="#545454"><g><path d="M 250 130C 250 196.274 196.274 250 130 250C 63.7258 250 10 196.274 10 130C 10 63.7258 63.7258 10 130 10C 196.274 10 250 63.7258 250 130Z"></path></g></g></g><g id="g-root-4.g-4_fr_zk2jug1fxyj2g-fill" data-item-order="-311026" transform="translate(183, 87)"><g id="4.g-4_fr_zk2jug1fxyj2g-fill" stroke="none" fill="#54502f"><g><path d="M 94 10C 47.6081 10 10 47.6081 10 94C 10 137.183 42.5856 172.756 84.5093 177.47C 94.1869 130.879 130.879 94.1869 177.47 84.5093C 172.756 42.5856 137.183 10 94 10Z"></path></g></g></g><g id="g-root-3.g-3_fr_v491q01fxyjuq-fill" data-item-order="-311020" transform="translate(183, 303)"><g id="3.g-3_fr_v491q01fxyjuq-fill" stroke="none" fill="#4b533a"><g><path d="M 10 93.999097C 10 140.391297 47.6081 177.999297 94 177.999297C 137.183 177.999297 172.756 145.413297 177.47 103.489797C 130.879 93.812197 94.1869 57.119897 84.5093 10.529297C 42.5856 15.243497 10 50.815897 10 93.999097Z"></path></g></g></g><g id="g-root-2.g-2_fr_m8m1h41fxykng-fill" data-item-order="-311014" transform="translate(399, 303)"><g id="2.g-2_fr_m8m1h41fxykng-fill" stroke="none" fill="#3b5649"><g><path d="M 94.000073 177.999297C 140.392273 177.999297 178.000273 140.391297 178.000273 93.999097C 178.000273 50.815897 145.414273 15.243597 103.490773 10.529297C 93.813173 57.119897 57.120873 93.812197 10.530273 103.489797C 15.244473 145.413297 50.816873 177.999297 94.000073 177.999297Z"></path></g></g></g><g id="g-root-1.g-1_fr_8xs06w1fxyj2m-fill" data-item-order="-311008" transform="translate(399, 87)"><g id="1.g-1_fr_8xs06w1fxyj2m-fill" stroke="none" fill="#344e57"><g><path d="M 178.000212 94C 178.000212 47.6081 140.392212 10 94.000012 10C 50.816812 10 15.244412 42.5856 10.530212 84.5093C 57.120812 94.1869 93.813112 130.879 103.490712 177.47C 145.414212 172.756 178.000212 137.183 178.000212 94Z"></path></g></g></g><g id="g-root-tx_keycompo_1lsf0mw1fxyiv5-fill" data-item-order="0" transform="translate(99, 39)"><g id="tx_keycompo_1lsf0mw1fxyiv5-fill" stroke="none" fill="#f4f4f4"><g><text style="font: 20px Roboto, sans-serif; white-space: pre;" font-size="20px" font-family="Roboto, sans-serif"><tspan x="12.67" y="34" dominant-baseline="ideographic">Key Components Enhancing Snowflake-MCP Server Efficiency</tspan></text></g></g></g><g id="g-root-tx_authenti_hznraw1fy8fm7-fill" data-item-order="0" transform="translate(15, 93)"><g id="tx_authenti_hznraw1fy8fm7-fill" stroke="none" fill="#ffe711"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="19.73" y="34" dominant-baseline="ideographic">Authentication </tspan><tspan x="72.31" y="58" dominant-baseline="ideographic">Manager</tspan></text></g></g></g><g id="g-root-tx_mcpserve_1q6d3i01fy9szx-fill" data-item-order="0" transform="translate(591, 99)"><g id="tx_mcpserve_1q6d3i01fy9szx-fill" stroke="none" fill="#1ac3fb"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="12" y="34" dominant-baseline="ideographic">MCP Server</tspan></text></g></g></g><g id="g-root-key_91iupk1fy8cul-fill" data-item-order="0" transform="translate(225, 129)"></g><g id="g-root-hier_8z0yd41fxyl8s-fill" data-item-order="0" transform="translate(477, 129)"></g><g id="g-root-tx_centralc_1cvj27s1fy9rf2-fill" data-item-order="0" transform="translate(591, 135)"><g id="tx_centralc_1cvj27s1fy9rf2-fill" stroke="none" fill="#f4f4f4"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="12" y="28" dominant-baseline="ideographic">Central component </tspan><tspan x="12" y="46" dominant-baseline="ideographic">managing protocol </tspan><tspan x="12" y="64" dominant-baseline="ideographic">and client </tspan><tspan x="12" y="82" dominant-baseline="ideographic">interactions</tspan></text></g></g></g><g id="g-root-tx_managesa_907w81fy8etk-fill" data-item-order="0" transform="translate(15, 153)"><g id="tx_managesa_907w81fy8etk-fill" stroke="none" fill="#f4f4f4"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="78.17" y="31" dominant-baseline="ideographic">Manages </tspan><tspan x="44.2" y="49" dominant-baseline="ideographic">authentication </tspan><tspan x="21.78" y="67" dominant-baseline="ideographic">methods securely</tspan></text></g></g></g><g id="g-root-tx_snowflak_mahgqg1fxyjnv-fill" data-item-order="0" transform="translate(315, 255)"><g id="tx_snowflak_mahgqg1fxyjnv-fill" stroke="none" fill="#b7b7b7"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="19.18" y="34" dominant-baseline="ideographic">Snowflake-</tspan><tspan x="16.4" y="58" dominant-baseline="ideographic">MCP Server</tspan></text></g></g></g><g id="g-root-tx_querypro_18ntq7s1fy8emh-fill" data-item-order="0" transform="translate(15, 345)"><g id="tx_querypro_18ntq7s1fy8emh-fill" stroke="none" fill="#a6da37"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="50.64" y="34" dominant-baseline="ideographic">Query </tspan><tspan x="12.41" y="58" dominant-baseline="ideographic">Processor</tspan></text></g></g></g><g id="g-root-tx_connecti_dbq3201fy9tz7-fill" data-item-order="0" transform="translate(591, 345)"><g id="tx_connecti_dbq3201fy9tz7-fill" stroke="none" fill="#43dd93"><g><text style="font: bold 20px Roboto, sans-serif; white-space: pre;" font-weight="bold" font-size="20px" font-family="Roboto, sans-serif"><tspan x="12" y="34" dominant-baseline="ideographic">Connection </tspan><tspan x="12" y="58" dominant-baseline="ideographic">Manager</tspan></text></g></g></g><g id="g-root-sear_dj7s3c1fy5kb0-fill" data-item-order="0" transform="translate(225, 381)"></g><g id="g-root-brid_zp2cjc1fy8f0m-fill" data-item-order="0" transform="translate(477, 381)"></g><g id="g-root-tx_executes_v303js1fy9rm2-fill" data-item-order="0" transform="translate(15, 405)"><g id="tx_executes_v303js1fy9rm2-fill" stroke="none" fill="#f4f4f4"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="26.88" y="31" dominant-baseline="ideographic">Executes and </tspan><tspan x="16.06" y="49" dominant-baseline="ideographic">processes SQL </tspan><tspan x="66.81" y="67" dominant-baseline="ideographic">queries</tspan></text></g></g></g><g id="g-root-tx_oversees_w1rs1fy9sec-fill" data-item-order="0" transform="translate(591, 405)"><g id="tx_oversees_w1rs1fy9sec-fill" stroke="none" fill="#f4f4f4"><g><text style="font: 15px Roboto, sans-serif; white-space: pre;" font-size="15px" font-family="Roboto, sans-serif"><tspan x="12" y="31" dominant-baseline="ideographic">Oversees database </tspan><tspan x="12" y="49" dominant-baseline="ideographic">connections </tspan><tspan x="12" y="67" dominant-baseline="ideographic">lifecycle</tspan></text></g></g></g><g id="g-root-0.g-0_fr_13z9kvs1fxylfy-stroke" data-item-order="-311030" transform="translate(255, 159)"><g id="0.g-0_fr_13z9kvs1fxylfy-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#b7b7b7" stroke-width="2"><g><path d="M 250 130C 250 196.2742 196.2742 250 130 250C 63.7258 250 10 196.2742 10 130C 10 63.7258 63.7258 10 130 10C 196.2742 10 250 63.7258 250 130Z"></path></g></g></g><g id="g-root-4.g-4_fr_zk2jug1fxyj2g-stroke" data-item-order="-311026" transform="translate(183, 87)"><g id="4.g-4_fr_zk2jug1fxyj2g-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#ffe711" stroke-width="2"><g><path d="M 94 10C 47.6081 10 10 47.6081 10 94C 10 137.1832 42.5856 172.7555 84.5093 177.4698C 94.1869 130.8792 130.8792 94.1869 177.4698 84.5093C 172.7555 42.5856 137.1832 10 94 10ZM 93.999977 178.000466C 140.391877 178.000466 177.999977 140.392366 177.999977 94.000466C 177.999977 90.791666 177.820077 87.624966 177.469777 84.509766C 130.879177 94.187366 94.186877 130.879666 84.509277 177.470266C 87.624477 177.820566 90.791177 178.000466 93.999977 178.000466Z"></path></g></g></g><g id="g-root-3.g-3_fr_v491q01fxyjuq-stroke" data-item-order="-311020" transform="translate(183, 303)"><g id="3.g-3_fr_v491q01fxyjuq-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#a6da37" stroke-width="2"><g><path d="M 10 93.999097C 10 140.390997 47.6081 177.999097 94 177.999097C 137.1832 177.999097 172.7555 145.413497 177.4698 103.489797C 130.8792 93.812197 94.1869 57.119897 84.5093 10.529297C 42.5856 15.243497 10 50.815897 10 93.999097ZM 178.000015 94C 178.000015 47.6081 140.391915 10 94.000015 10C 90.791215 10 87.624515 10.1799 84.509315 10.5302C 94.186915 57.1208 130.879215 93.8131 177.469815 103.4907C 177.820115 100.3755 178.000015 97.2088 178.000015 94Z"></path></g></g></g><g id="g-root-2.g-2_fr_m8m1h41fxykng-stroke" data-item-order="-311014" transform="translate(399, 303)"><g id="2.g-2_fr_m8m1h41fxykng-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#43dd93" stroke-width="2"><g><path d="M 94.000073 177.999097C 140.391973 177.999097 178.000073 140.390997 178.000073 93.999097C 178.000073 50.815897 145.414473 15.243497 103.490773 10.529297C 93.813173 57.119897 57.120873 93.812197 10.530273 103.489797C 15.244473 145.413497 50.816873 177.999097 94.000073 177.999097ZM 94 10C 47.6081 10 10 47.6081 10 94C 10 97.2088 10.1799 100.3755 10.5302 103.4907C 57.1208 93.8131 93.8131 57.1208 103.4907 10.5302C 100.3755 10.1799 97.2088 10 94 10Z"></path></g></g></g><g id="g-root-1.g-1_fr_8xs06w1fxyj2m-stroke" data-item-order="-311008" transform="translate(399, 87)"><g id="1.g-1_fr_8xs06w1fxyj2m-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#1ac3fb" stroke-width="2"><g><path d="M 178.000012 94C 178.000012 47.6081 140.391912 10 94.000012 10C 50.816812 10 15.244412 42.5856 10.530212 84.5093C 57.120812 94.1869 93.813112 130.8792 103.490712 177.4698C 145.414412 172.7555 178.000012 137.1832 178.000012 94ZM 10 94.000466C 10 140.392366 47.6081 178.000466 94 178.000466C 97.2088 178.000466 100.3755 177.820566 103.4907 177.470266C 93.8131 130.879666 57.1208 94.187366 10.5302 84.509766C 10.1799 87.624966 10 90.791666 10 94.000466Z"></path></g></g></g><g id="g-root-tx_keycompo_1lsf0mw1fxyiv5-stroke" data-item-order="0" transform="translate(99, 39)"></g><g id="g-root-tx_authenti_hznraw1fy8fm7-stroke" data-item-order="0" transform="translate(15, 93)"></g><g id="g-root-tx_mcpserve_1q6d3i01fy9szx-stroke" data-item-order="0" transform="translate(591, 99)"></g><g id="g-root-key_91iupk1fy8cul-stroke" data-item-order="0" transform="translate(225, 129)"><g id="key_91iupk1fy8cul-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#ffe711" stroke-width="2"><g><path d="M 42.810001 50.462002C 41.833622 51.438122 40.250832 51.437984 39.27462 50.461689C 38.298416 49.485397 38.298416 47.902603 39.27462 46.926311C 40.250832 45.95002 41.833622 45.949879 42.810001 46.925999C 43.785519 47.902821 43.785519 49.48518 42.810001 50.462002ZM 10 10M 48.096001 45.883999L 56.706001 37.293999C 56.894161 37.106426 56.999943 36.851685 57 36.585999L 57 34C 57 33.447716 56.552284 33 56 33L 53.400002 33C 53.144054 32.999924 52.89782 33.097984 52.712002 33.274002L 43.854 41.641998C 40.328316 40.148323 36.238579 41.362339 34.098885 44.53775C 31.959192 47.713158 32.369568 51.959496 35.077744 54.666393C 37.785919 57.373291 42.032452 57.78167 45.206848 55.64048C 48.381248 53.49929 49.593338 49.408978 48.098 45.883999ZM 16.5 19.5C 16.5 24.19442 20.30558 28 25 28C 29.69442 28 33.5 24.19442 33.5 19.5C 33.5 14.80558 29.69442 11 25 11C 20.30558 11 16.5 14.80558 16.5 19.5ZM 27 45L 11 45C 10.999902 39.114559 14.680733 33.857452 20.211155 31.844276C 25.741575 29.831099 31.940599 31.491751 35.723999 36"></path></g></g></g><g id="g-root-hier_8z0yd41fxyl8s-stroke" data-item-order="0" transform="translate(477, 129)"><g id="hier_8z0yd41fxyl8s-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#1ac3fb" stroke-width="2"><g><path d="M 23 26.002001C 23 28.763424 27.924868 31.002001 34 31.002001C 40.075134 31.002001 45 28.763424 45 26.002001C 45 23.240578 40.075134 21.001999 34 21.001999C 27.924868 21.001999 23 23.240578 23 26.002001ZM 47 14.002C 47 15.658854 49.238575 17.002001 52 17.002001C 54.761425 17.002001 57 15.658854 57 14.002001C 57 12.345146 54.761425 11.002 52 11.002C 49.238575 11.002 47 12.345146 47 14.002001ZM 57 14L 57 20C 57 21.656 54.762001 23 52 23C 49.237999 23 47 21.658001 47 20L 47 14M 11 14.002C 11 15.658854 13.238577 17.002001 16 17.002001C 18.761425 17.002001 21 15.658854 21 14.002001C 21 12.345146 18.761425 11.002 16 11.002C 13.238577 11.002 11 12.345146 11 14.002001ZM 21 14L 21 20C 21 21.656 18.762001 23 16 23C 13.238 23 11 21.658001 11 20L 11 14M 47 48.001999C 47 49.658855 49.238575 51.002003 52 51.002003C 54.761425 51.002003 57 49.658855 57 48.002003C 57 46.345146 54.761425 45.002003 52 45.002003C 49.238575 45.002003 47 46.345146 47 48.002003ZM 57 48L 57 54C 57 55.655998 54.762001 57 52 57C 49.237999 57 47 55.655998 47 54L 47 48M 11 48.001999C 11 49.658855 13.238577 51.002003 16 51.002003C 18.761425 51.002003 21 49.658855 21 48.002003C 21 46.345146 18.761425 45.002003 16 45.002003C 13.238577 45.002003 11 46.345146 11 48.002003ZM 21 48L 21 54C 21 55.655998 18.762001 57 16 57C 13.238 57 11 55.658001 11 54L 11 48M 45 34C 45 36.762001 40.073997 39 34 39C 27.926001 39 23 36.764 23 34M 23 26L 23 42C 23 44.762001 27.926001 47 34 47C 40.073997 47 45 44.762001 45 42L 45 26M 20.618 21.156L 23.702 24.24M 47.382 21.156L 44.298 24.24M 20.709999 46.993999L 23.812 43.889999M 47.290001 46.993999L 44.188 43.889999"></path></g></g></g><g id="g-root-tx_centralc_1cvj27s1fy9rf2-stroke" data-item-order="0" transform="translate(591, 135)"></g><g id="g-root-tx_managesa_907w81fy8etk-stroke" data-item-order="0" transform="translate(15, 153)"></g><g id="g-root-tx_snowflak_mahgqg1fxyjnv-stroke" data-item-order="0" transform="translate(315, 255)"></g><g id="g-root-tx_querypro_18ntq7s1fy8emh-stroke" data-item-order="0" transform="translate(15, 345)"></g><g id="g-root-tx_connecti_dbq3201fy9tz7-stroke" data-item-order="0" transform="translate(591, 345)"></g><g id="g-root-sear_dj7s3c1fy5kb0-stroke" data-item-order="0" transform="translate(225, 381)"><g id="sear_dj7s3c1fy5kb0-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#a6da37" stroke-width="2"><g><path d="M 35 44.057999C 35 49.060593 39.055405 53.115997 44.057999 53.115997C 49.060593 53.115997 53.115997 49.060593 53.115997 44.057999C 53.115997 39.055405 49.060593 35 44.057999 35C 39.055405 35 35 39.055405 35 44.057999ZM 57 57L 50.504002 50.504002M 19 27L 38 27M 19 33L 31 33M 19 21L 33 21M 19 39L 27 39M 19 45L 27 45M 35 57L 13 57C 11.895431 57 11 56.104568 11 55L 11 13C 11 11.895431 11.895431 11 13 11L 39.585999 11C 40.11639 11.000113 40.625015 11.210901 41 11.586L 48.414001 19C 48.789101 19.374985 48.999886 19.88361 49 20.414L 49 29"></path></g></g></g><g id="g-root-brid_zp2cjc1fy8f0m-stroke" data-item-order="0" transform="translate(477, 381)"><g id="brid_zp2cjc1fy8f0m-stroke" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" stroke="#43dd93" stroke-width="2"><g><path d="M 56.987999 22.633999C 51.417332 21.449936 47.327156 16.685707 46.999996 11C 47 17.628 40.627998 23 34 23C 27.372 23 21 17.628 21 11C 20.672398 16.703152 16.559757 21.477453 10.968 22.646M 11 33L 57 33M 23 45L 23 33M 19 33L 19 43M 45 43L 45 33.001999M 49 33L 49 43M 15.002 20.966L 15.002 33M 21.001999 11L 21.001999 33M 27.002001 20.968L 27.002001 33M 35.001999 22.959999L 35.001999 33M 41.001999 21.001999L 41.001999 33M 47 11L 47.001999 33M 53.001999 20.966L 53.001999 32.996002M 56.981998 56.776001C 51.981998 57.818001 48.862 52.818001 48.862 52.818001L 48.862 52.818001C 47.290386 55.316471 44.57085 56.860619 41.620003 56.930004C 38.693253 56.786953 36.009048 55.260334 34.389999 52.818001L 34.389999 52.818001C 32.818771 55.315887 30.100136 56.859955 27.150002 56.930004C 24.222607 56.789177 21.537462 55.262028 19.919998 52.817997L 19.92 52.818001C 19.92 52.818001 15.92 57.902 11.01 56.858002M 56.981998 49.776001C 51.981998 50.818001 48.862 45.818001 48.862 45.818001L 48.862 45.818001C 47.290386 48.316471 44.57085 49.860619 41.620003 49.93C 38.693253 49.786953 36.009048 48.260334 34.389999 45.818001L 34.389999 45.818001C 32.818771 48.315887 30.100136 49.859955 27.150002 49.93C 24.222607 49.789177 21.537462 48.262028 19.919998 45.817997L 19.92 45.818001C 19.92 45.818001 15.92 50.902 11.01 49.858002"></path></g></g></g><g id="g-root-tx_executes_v303js1fy9rm2-stroke" data-item-order="0" transform="translate(15, 405)"></g><g id="g-root-tx_oversees_w1rs1fy9sec-stroke" data-item-order="0" transform="translate(591, 405)"></g></g></g></svg> ``` -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python 2 | import os 3 | import asyncio 4 | import logging 5 | import json 6 | import time 7 | import snowflake.connector 8 | from dotenv import load_dotenv 9 | import mcp.server.stdio 10 | from mcp.server import Server 11 | from mcp.types import Tool, TextContent 12 | from typing import Optional, Any, List, Dict 13 | import pandas as pd 14 | 15 | # Configure logging 16 | logging.basicConfig( 17 | level=logging.DEBUG, 18 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' 19 | ) 20 | logger = logging.getLogger('snowflake_mcp') 21 | 22 | # Load environment variables from .env file 23 | load_dotenv() 24 | 25 | # Define the allowed base path for CSV exports, defaulting to /export 26 | # This directory MUST be mounted as a volume in Docker. 27 | EXPORT_BASE_PATH = os.getenv("EXPORT_BASE_PATH", "/export") 28 | if not os.path.exists(EXPORT_BASE_PATH): 29 | try: 30 | os.makedirs(EXPORT_BASE_PATH) 31 | logger.info(f"Created export directory: {EXPORT_BASE_PATH}") 32 | except OSError as e: 33 | logger.error(f"Failed to create export directory {EXPORT_BASE_PATH}: {e}. CSV exports will likely fail.") 34 | elif not os.path.isdir(EXPORT_BASE_PATH): 35 | logger.error(f"Configured EXPORT_BASE_PATH '{EXPORT_BASE_PATH}' exists but is not a directory. CSV exports will likely fail.") 36 | else: 37 | logger.info(f"Using export base path: {EXPORT_BASE_PATH}") 38 | 39 | class SnowflakeConnection: 40 | """ 41 | Snowflake database connection management class 42 | """ 43 | def __init__(self): 44 | # Initialize configuration from environment variables 45 | self.config = { 46 | "user": os.getenv("SNOWFLAKE_USER"), 47 | "account": os.getenv("SNOWFLAKE_ACCOUNT"), 48 | "database": os.getenv("SNOWFLAKE_DATABASE"), 49 | "warehouse": os.getenv("SNOWFLAKE_WAREHOUSE"), 50 | "role": os.getenv("SNOWFLAKE_ROLE"), 51 | } 52 | 53 | # Log authentication configuration start 54 | logger.info("=== CONFIGURING AUTHENTICATION ===") 55 | 56 | # Check authentication methods available 57 | private_key_file = os.getenv("SNOWFLAKE_PRIVATE_KEY_FILE") 58 | private_key_passphrase = os.getenv("SNOWFLAKE_PRIVATE_KEY_PASSPHRASE") 59 | 60 | # Treat empty string passphrase as None 61 | if private_key_passphrase == "" or private_key_passphrase is None or (isinstance(private_key_passphrase, str) and private_key_passphrase.strip() == ""): 62 | private_key_passphrase = None 63 | logger.info("No passphrase provided or empty passphrase detected, treating as None") 64 | 65 | password = os.getenv("SNOWFLAKE_PASSWORD") 66 | 67 | # Priority 1: Key pair authentication (always preferred if available) 68 | if private_key_file: 69 | # First, check if the file exists 70 | if not os.path.exists(private_key_file): 71 | logger.error(f"Private key file does not exist: {private_key_file}") 72 | logger.info("Will try password authentication instead") 73 | 74 | # Fallback to password authentication 75 | if password: 76 | self.config["password"] = password 77 | logger.info("SELECTED AUTH: PASSWORD (fallback)") 78 | logger.info("Using password authentication as fallback") 79 | else: 80 | logger.error("No password available for fallback. Authentication will fail.") 81 | else: 82 | # Private key file exists, use key pair authentication 83 | if private_key_passphrase is not None: 84 | logger.info("SELECTED AUTH: KEY PAIR WITH PASSPHRASE") 85 | logger.info(f"Key file: {private_key_file}") 86 | else: 87 | logger.info("SELECTED AUTH: KEY PAIR WITHOUT PASSPHRASE") 88 | logger.info(f"Key file: {private_key_file}") 89 | # Ensure passphrase is None 90 | private_key_passphrase = None 91 | 92 | # Try to setup key pair authentication 93 | auth_success = self._setup_key_pair_auth(private_key_file, private_key_passphrase) 94 | 95 | # If key pair auth failed for any reason, try password as fallback 96 | if not auth_success: 97 | logger.warning("Failed to set up key pair authentication") 98 | 99 | # Only use password fallback if available 100 | if password: 101 | logger.info("FALLBACK AUTH: PASSWORD") 102 | logger.info("Using password authentication as fallback") 103 | self.config["password"] = password 104 | else: 105 | logger.error("No password available for fallback after key auth failure") 106 | logger.error("Authentication will likely fail") 107 | 108 | # Priority 2: Password authentication (if no key is available) 109 | elif password: 110 | self.config["password"] = password 111 | logger.info("SELECTED AUTH: PASSWORD") 112 | logger.info("Using password authentication (no key file configured)") 113 | 114 | # No authentication method available 115 | else: 116 | logger.error("NO AUTHENTICATION METHOD AVAILABLE") 117 | logger.error("Please configure either a private key or password") 118 | 119 | # Log authentication configuration end 120 | logger.info("=== AUTHENTICATION CONFIGURED ===") 121 | 122 | self.conn: Optional[snowflake.connector.SnowflakeConnection] = None 123 | 124 | # Log config (excluding sensitive info) 125 | safe_config = {k: v for k, v in self.config.items() 126 | if k not in ['password', 'private_key', 'private_key_passphrase']} 127 | logger.info(f"Initialized with config: {json.dumps(safe_config)}") 128 | # Store the base path for exports 129 | self.export_base_path = EXPORT_BASE_PATH 130 | if not self.export_base_path: 131 | logger.warning("EXPORT_BASE_PATH is not set. CSV export functionality might be limited or fail.") 132 | elif not os.path.isdir(self.export_base_path): 133 | logger.warning(f"Export base path '{self.export_base_path}' is not a valid directory. CSV exports might fail.") 134 | 135 | def _setup_key_pair_auth(self, private_key_file: str, passphrase: str = None) -> bool: 136 | """ 137 | Set up key pair authentication 138 | 139 | Args: 140 | private_key_file (str): Path to private key file 141 | passphrase (str, optional): Passphrase for the private key (NOT the Snowflake password) 142 | 143 | Returns: 144 | bool: True if key pair authentication was set up successfully, False otherwise 145 | """ 146 | try: 147 | # Read private key file 148 | with open(private_key_file, "rb") as key_file: 149 | private_key = key_file.read() 150 | 151 | # Try to load the key using snowflake's recommended approach 152 | from cryptography.hazmat.backends import default_backend 153 | from cryptography.hazmat.primitives.serialization import load_pem_private_key 154 | 155 | logger.info(f"Loading private key from {private_key_file}") 156 | 157 | # Only pass passphrase if it's not None and not empty 158 | if passphrase is not None and passphrase.strip() != "": 159 | logger.info("Using passphrase to decrypt private key") 160 | p_key = load_pem_private_key( 161 | private_key, 162 | password=passphrase.encode(), 163 | backend=default_backend() 164 | ) 165 | # Add passphrase to config for encrypted keys 166 | self.config["private_key_passphrase"] = passphrase 167 | logger.info("Private key with passphrase loaded successfully") 168 | else: 169 | logger.info("Using private key without passphrase") 170 | p_key = load_pem_private_key( 171 | private_key, 172 | password=None, 173 | backend=default_backend() 174 | ) 175 | logger.info("Private key without passphrase loaded successfully") 176 | 177 | # Convert key to DER format 178 | from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption 179 | pkb = p_key.private_bytes( 180 | encoding=Encoding.DER, 181 | format=PrivateFormat.PKCS8, 182 | encryption_algorithm=NoEncryption() 183 | ) 184 | 185 | # Add private key to config (required for Snowflake key pair auth) 186 | self.config["private_key"] = pkb 187 | 188 | return True 189 | 190 | except Exception as e: 191 | logger.error(f"Error setting up key pair authentication: {str(e)}") 192 | logger.error("Details:", exc_info=True) 193 | return False 194 | 195 | def ensure_connection(self) -> snowflake.connector.SnowflakeConnection: 196 | """ 197 | Ensure database connection is available, create new connection if it doesn't exist or is disconnected 198 | """ 199 | try: 200 | # Check if connection needs to be re-established 201 | if self.conn is None: 202 | logger.info("Creating new Snowflake connection...") 203 | 204 | # Determine the auth method we're actually using 205 | auth_method = "UNKNOWN" 206 | if "private_key" in self.config: 207 | auth_method = "KEY_PAIR" 208 | if "private_key_passphrase" in self.config and self.config.get("private_key_passphrase") is not None: 209 | auth_method += "_WITH_PASSPHRASE" 210 | else: 211 | auth_method += "_WITHOUT_PASSPHRASE" 212 | elif "password" in self.config: 213 | auth_method = "PASSWORD" 214 | 215 | logger.info(f"Connecting with authentication method: {auth_method}") 216 | 217 | # Attempt connection 218 | self.conn = snowflake.connector.connect( 219 | **self.config, 220 | client_session_keep_alive=True, 221 | network_timeout=15, 222 | login_timeout=15 223 | ) 224 | 225 | self.conn.cursor().execute("ALTER SESSION SET TIMEZONE = 'UTC'") 226 | 227 | # Log successful connection details 228 | logger.info(f"=== CONNECTION ESTABLISHED ===") 229 | logger.info(f"Authentication Method: {auth_method}") 230 | logger.info(f"Connected to: {self.config['account']}") 231 | logger.info(f"User: {self.config['user']}") 232 | logger.info(f"Database: {self.config['database']}") 233 | logger.info(f"Warehouse: {self.config['warehouse']}") 234 | logger.info(f"==============================") 235 | 236 | logger.info("New connection established and configured") 237 | 238 | # Test if connection is valid 239 | try: 240 | self.conn.cursor().execute("SELECT 1") 241 | except: 242 | logger.info("Connection lost, reconnecting...") 243 | self.conn = None 244 | return self.ensure_connection() 245 | 246 | return self.conn 247 | except Exception as e: 248 | logger.error(f"Connection error: {str(e)}") 249 | raise 250 | 251 | def execute_query(self, query: str) -> List[Dict[str, Any]]: 252 | """ 253 | Execute SQL query and return results 254 | 255 | Args: 256 | query (str): SQL query statement 257 | 258 | Returns: 259 | List[Dict[str, Any]]: List of query results 260 | """ 261 | start_time = time.time() 262 | logger.info(f"Executing query: {query[:200]}...") # Log only first 200 characters 263 | 264 | try: 265 | conn = self.ensure_connection() 266 | with conn.cursor() as cursor: 267 | # For write operations use transaction 268 | if any(query.strip().upper().startswith(word) for word in ['INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER']): 269 | cursor.execute("BEGIN") 270 | try: 271 | cursor.execute(query) 272 | conn.commit() 273 | logger.info(f"Write query executed in {time.time() - start_time:.2f}s") 274 | return [{"affected_rows": cursor.rowcount}] 275 | except Exception as e: 276 | conn.rollback() 277 | raise 278 | else: 279 | # Read operations 280 | cursor.execute(query) 281 | if cursor.description: 282 | columns = [col[0] for col in cursor.description] 283 | rows = cursor.fetchall() 284 | results = [dict(zip(columns, row)) for row in rows] 285 | logger.info(f"Read query returned {len(results)} rows in {time.time() - start_time:.2f}s") 286 | return results 287 | return [] 288 | 289 | except snowflake.connector.errors.ProgrammingError as e: 290 | logger.error(f"SQL Error: {str(e)}") 291 | logger.error(f"Error Code: {getattr(e, 'errno', 'unknown')}") 292 | raise 293 | except Exception as e: 294 | logger.error(f"Query error: {str(e)}") 295 | logger.error(f"Error type: {type(e).__name__}") 296 | raise 297 | 298 | def export_to_csv(self, query: str, relative_file_path: str) -> Dict[str, Any]: 299 | """ 300 | Execute SQL query and export results to a CSV file within the configured base path. 301 | 302 | Args: 303 | query (str): SQL query statement. 304 | relative_file_path (str): The relative path and filename for the CSV file (e.g., 'my_report.csv' or 'subdir/data.csv'). 305 | This will be joined with the EXPORT_BASE_PATH. 306 | 307 | Returns: 308 | Dict[str, Any]: A dictionary containing the full path to the exported file and the number of rows exported. 309 | 310 | Raises: 311 | ValueError: If the relative_file_path attempts to escape the export base path or is invalid. 312 | FileNotFoundError: If the export base path does not exist or is not a directory. 313 | Exception: Propagates exceptions from database query or file writing. 314 | """ 315 | start_time = time.time() 316 | logger.info(f"Exporting query to CSV: {query[:200]}...") 317 | logger.info(f"Target relative path: {relative_file_path}") 318 | 319 | if not self.export_base_path or not os.path.isdir(self.export_base_path): 320 | error_msg = f"Export base path '{self.export_base_path}' is not configured or not a valid directory." 321 | logger.error(error_msg) 322 | raise FileNotFoundError(error_msg) 323 | 324 | # Prevent path traversal attacks and ensure the path is relative 325 | if relative_file_path.startswith('/') or '..' in relative_file_path: 326 | error_msg = f"Invalid relative file path: '{relative_file_path}'. Must not start with '/' or contain '..'." 327 | logger.error(error_msg) 328 | raise ValueError(error_msg) 329 | 330 | # Construct the full path safely 331 | full_path = os.path.abspath(os.path.join(self.export_base_path, relative_file_path)) 332 | 333 | # Double-check it's still within the base path after resolving 334 | if not full_path.startswith(os.path.abspath(self.export_base_path)): 335 | error_msg = f"Resolved path '{full_path}' is outside the allowed export directory '{self.export_base_path}'." 336 | logger.error(error_msg) 337 | raise ValueError(error_msg) 338 | 339 | # Ensure the target directory exists 340 | target_dir = os.path.dirname(full_path) 341 | if not os.path.exists(target_dir): 342 | try: 343 | os.makedirs(target_dir) 344 | logger.info(f"Created directory for export: {target_dir}") 345 | except OSError as e: 346 | logger.error(f"Failed to create directory {target_dir}: {e}") 347 | raise 348 | 349 | logger.info(f"Full export path: {full_path}") 350 | 351 | try: 352 | conn = self.ensure_connection() 353 | with conn.cursor() as cursor: 354 | # Execute the query (read operations only for export) 355 | if any(query.strip().upper().startswith(word) for word in ['INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER']): 356 | raise ValueError("Only SELECT queries can be exported to CSV.") 357 | 358 | cursor.execute(query) 359 | 360 | if cursor.description: 361 | # Fetch data using pandas for efficient CSV writing 362 | df = cursor.fetch_pandas_all() 363 | row_count = len(df) 364 | 365 | # Write DataFrame to CSV 366 | df.to_csv(full_path, index=False) 367 | 368 | execution_time = time.time() - start_time 369 | logger.info(f"Exported {row_count} rows to '{full_path}' in {execution_time:.2f}s") 370 | return { 371 | "message": f"Successfully exported {row_count} rows.", 372 | "file_path": full_path, # Return the full path on the server's filesystem 373 | "rows_exported": row_count 374 | } 375 | else: 376 | # Handle queries that return no description (e.g., USE DATABASE) 377 | logger.info("Query did not return results to export.") 378 | # Create an empty file or return specific message? Let's return 0 rows. 379 | # Create an empty file to signify the query ran but had no output columns/rows 380 | pd.DataFrame().to_csv(full_path, index=False) 381 | execution_time = time.time() - start_time 382 | logger.info(f"Exported 0 rows (query returned no data/columns) to '{full_path}' in {execution_time:.2f}s") 383 | 384 | return { 385 | "message": "Query executed but returned no data/columns to export.", 386 | "file_path": full_path, 387 | "rows_exported": 0 388 | } 389 | 390 | except snowflake.connector.errors.ProgrammingError as e: 391 | logger.error(f"SQL Error during export: {str(e)}") 392 | logger.error(f"Error Code: {getattr(e, 'errno', 'unknown')}") 393 | raise 394 | except Exception as e: 395 | logger.error(f"Error during CSV export: {str(e)}") 396 | logger.error(f"Error type: {type(e).__name__}") 397 | raise 398 | 399 | def close(self): 400 | """ 401 | Close database connection 402 | """ 403 | if self.conn: 404 | try: 405 | self.conn.close() 406 | logger.info("Connection closed") 407 | except Exception as e: 408 | logger.error(f"Error closing connection: {str(e)}") 409 | finally: 410 | self.conn = None 411 | 412 | class SnowflakeMCPServer(Server): 413 | """ 414 | Snowflake MCP server class, handles client interactions 415 | """ 416 | def __init__(self): 417 | super().__init__(name="snowflake-mcp-server") 418 | self.db = SnowflakeConnection() 419 | logger.info("SnowflakeMCPServer initialized") 420 | 421 | @self.list_tools() 422 | async def handle_tools(): 423 | """ 424 | Return list of available tools 425 | """ 426 | return [ 427 | Tool( 428 | name="execute_query", 429 | description="Execute a SQL query on Snowflake and return results directly.", 430 | inputSchema={ 431 | "type": "object", 432 | "properties": { 433 | "query": { 434 | "type": "string", 435 | "description": "SQL query to execute (SELECT, INSERT, UPDATE, etc.)." 436 | } 437 | }, 438 | "required": ["query"] 439 | } 440 | ), 441 | Tool( # Add the new tool definition 442 | name="export_query_to_csv", 443 | description=f"Execute a SELECT SQL query on Snowflake and export the results to a CSV file within the designated export directory ('{EXPORT_BASE_PATH}' on the server).", 444 | inputSchema={ 445 | "type": "object", 446 | "properties": { 447 | "query": { 448 | "type": "string", 449 | "description": "SELECT SQL query to execute." 450 | }, 451 | "relative_file_path": { 452 | "type": "string", 453 | "description": f"The relative path and filename for the CSV file (e.g., 'my_data.csv' or 'reports/quarterly.csv'). This will be saved relative to the server's base export path: {EXPORT_BASE_PATH}. Do not use '..' or start with '/'." 454 | } 455 | }, 456 | "required": ["query", "relative_file_path"] 457 | } 458 | ) 459 | ] 460 | 461 | @self.call_tool() 462 | async def handle_call_tool(name: str, arguments: dict): 463 | """ 464 | Handle tool call requests 465 | 466 | Args: 467 | name (str): Tool name 468 | arguments (dict): Tool arguments 469 | 470 | Returns: 471 | list[TextContent]: Execution results 472 | """ 473 | if name == "execute_query": 474 | start_time = time.time() 475 | try: 476 | result = self.db.execute_query(arguments["query"]) 477 | execution_time = time.time() - start_time 478 | 479 | return [TextContent( 480 | type="text", 481 | text=f"Results (execution time: {execution_time:.2f}s):\n{result}" 482 | )] 483 | except Exception as e: 484 | error_message = f"Error executing query: {str(e)}" 485 | logger.error(error_message) 486 | return [TextContent( 487 | type="text", 488 | text=error_message 489 | )] 490 | elif name == "export_query_to_csv": # Handle the new tool call 491 | start_time = time.time() 492 | try: 493 | query = arguments["query"] 494 | relative_path = arguments["relative_file_path"] 495 | result = self.db.export_to_csv(query, relative_path) 496 | execution_time = time.time() - start_time 497 | 498 | return [TextContent( 499 | type="text", 500 | text=f"Export successful (execution time: {execution_time:.2f}s):\n{json.dumps(result, indent=2)}" 501 | )] 502 | except (ValueError, FileNotFoundError) as e: # Catch specific configuration/path errors 503 | error_message = f"Error preparing export: {str(e)}" 504 | logger.error(error_message) 505 | return [TextContent(type="text", text=error_message)] 506 | except Exception as e: # Catch database or file writing errors 507 | error_message = f"Error exporting query to CSV: {str(e)}" 508 | logger.error(error_message) 509 | return [TextContent( 510 | type="text", 511 | text=error_message 512 | )] 513 | 514 | def __del__(self): 515 | """ 516 | Clean up resources, close database connection 517 | """ 518 | if hasattr(self, 'db'): 519 | self.db.close() 520 | 521 | async def main(): 522 | """ 523 | Main function, starts server and handles requests 524 | """ 525 | try: 526 | server = SnowflakeMCPServer() 527 | initialization_options = server.create_initialization_options() 528 | logger.info("Starting server") 529 | 530 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): 531 | await server.run( 532 | read_stream, 533 | write_stream, 534 | initialization_options 535 | ) 536 | except Exception as e: 537 | logger.critical(f"Server failed: {str(e)}", exc_info=True) 538 | raise 539 | finally: 540 | logger.info("Server shutting down") 541 | 542 | if __name__ == "__main__": 543 | asyncio.run(main()) ```