#
tokens: 25937/50000 18/20 files (page 1/3)
lines: on (toggle) GitHub
raw markdown copy reset
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 | ![GitHub repo](https://img.shields.io/badge/GitHub-snowflake--mcp-blue)
  6 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
  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 | ![alt text](assets/key_components_bright.svg)
 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 | ![alt text](assets/communication_flow_sequence_bright.svg)
 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 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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&amp;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&amp;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())
```
Page 1/3FirstPrevNextLast