#
tokens: 7821/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .cursor
│   └── mcp.json
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
└── server.js
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependency directories
 2 | node_modules/
 3 | jspm_packages/
 4 | 
 5 | # Logs
 6 | logs
 7 | *.log
 8 | npm-debug.log*
 9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 | .pnpm-debug.log*
13 | 
14 | # Environment variables
15 | .env
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | 
21 | # Build outputs
22 | dist/
23 | build/
24 | out/
25 | 
26 | # Virtual environment
27 | venv/
28 | .venv/
29 | 
30 | # Coverage directory used by tools like istanbul
31 | coverage/
32 | *.lcov
33 | 
34 | # TypeScript cache
35 | *.tsbuildinfo
36 | 
37 | # Optional npm cache directory
38 | .npm
39 | 
40 | # Optional eslint cache
41 | .eslintcache
42 | 
43 | # Optional REPL history
44 | .node_repl_history
45 | 
46 | # Output of 'npm pack'
47 | *.tgz
48 | 
49 | # Yarn Integrity file
50 | .yarn-integrity
51 | 
52 | # dotenv environment variable files
53 | .env
54 | .env.development.local
55 | .env.test.local
56 | .env.production.local
57 | .env.local
58 | 
59 | # Mac OS
60 | .DS_Store
61 | 
62 | # IDE specific files
63 | .idea/
64 | .vscode/
65 | *.swp
66 | *.swo 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Model Context Protocol PostgreSQL Server
  2 | 
  3 | This project implements a Model Context Protocol (MCP) server that connects to a PostgreSQL database. It allows AI models to interact with your database through a standardized protocol.
  4 | 
  5 | ## Features
  6 | 
  7 | - Connects to a PostgreSQL database using connection pooling
  8 | - Implements the Model Context Protocol for AI model interaction
  9 | - Provides database schema information as resources
 10 | - Allows executing SQL queries with retry logic
 11 | - Handles connection errors gracefully
 12 | 
 13 | ## Prerequisites
 14 | 
 15 | - Node.js 20 or higher
 16 | - PostgreSQL database
 17 | - Access credentials for the database
 18 | 
 19 | ## Installation
 20 | 
 21 | 1. Clone this repository
 22 | 2. Install dependencies:
 23 | 
 24 | ```bash
 25 | npm install
 26 | ```
 27 | 
 28 | ## Configuration
 29 | 
 30 | The server reads database credentials from a `.env` file in the project root directory. You need to add your database credentials as a JSON string in the `DB_CREDENTIALS` environment variable:
 31 | 
 32 | 1. Create a `.env` file in the project root:
 33 | 
 34 | ```bash
 35 | touch .env
 36 | ```
 37 | 
 38 | 2. Add the following line with your actual database credentials:
 39 | 
 40 | ```bash
 41 | export DB_CREDENTIALS='{"DB_USER":"your-username","DB_PASSWORD":"your-password","DB_HOST":"your-host","DB_PORT":"5433","DB_NAME":"your-database"}'
 42 | ```
 43 | 
 44 | ### Fallback to Shell Config Files
 45 | 
 46 | If the `.env` file is not present or the credentials variable is not found, the server will automatically look for the credentials in your shell configuration files in the following order:
 47 | 
 48 | 1. `~/.zshrc`
 49 | 2. `~/.bashrc` 
 50 | 3. `~/.bash_profile`
 51 | 4. `~/.profile`
 52 | 
 53 | This is especially useful in environments where shell config files are not automatically sourced, such as the Cursor MCP environment.
 54 | 
 55 | To set up credentials in any of your shell config files:
 56 | 
 57 | 1. Open your preferred shell config file, for example:
 58 | 
 59 | ```bash
 60 | nano ~/.zshrc
 61 | # or
 62 | nano ~/.bashrc
 63 | ```
 64 | 
 65 | 2. Add the following line with your actual database credentials:
 66 | 
 67 | ```bash
 68 | export DB_CREDENTIALS='{"DB_USER":"your-username","DB_PASSWORD":"your-password","DB_HOST":"your-host","DB_PORT":"5433","DB_NAME":"your-database"}'
 69 | ```
 70 | 
 71 | The server will automatically detect and use these credentials when the `.env` file is not available.
 72 | 
 73 | ### Custom Credentials Variable
 74 | 
 75 | You can also use a custom environment variable name instead of `DB_CREDENTIALS` by using the `--credentials-var` flag when starting the server:
 76 | 
 77 | ```bash
 78 | node server.js --credentials-var MY_CUSTOM_DB_CREDS
 79 | ```
 80 | 
 81 | In this case, you would define `MY_CUSTOM_DB_CREDS` in your `.env` file instead.
 82 | 
 83 | ### Combining Options
 84 | 
 85 | You can combine different command-line options as needed:
 86 | 
 87 | ```bash
 88 | # Use custom credentials and enable verbose mode
 89 | node server.js --credentials-var MY_CUSTOM_DB_CREDS --verbose
 90 | 
 91 | # Short form also works
 92 | node server.js -c MY_CUSTOM_DB_CREDS -v
 93 | ```
 94 | 
 95 | ## Usage
 96 | 
 97 | Start the MCP server:
 98 | 
 99 | ```bash
100 | # Directly with Node.js
101 | node server.js
102 | 
103 | # Or with npm
104 | npm start
105 | ```
106 | 
107 | ### Logging Options
108 | 
109 | By default, the server runs in silent mode, displaying only error messages. If you want to see all log messages, you can use the verbose flag:
110 | 
111 | ```bash
112 | # With verbose logging
113 | node server.js --verbose
114 | 
115 | # Or with npm
116 | npm start -- --verbose
117 | ```
118 | 
119 | You can also use the short flag `-v`:
120 | 
121 | ```bash
122 | node server.js -v
123 | ```
124 | 
125 | The server will:
126 | 1. Test the database connection
127 | 2. Start the MCP server using stdio transport
128 | 3. Handle requests from AI models
129 | 
130 | ## Integration with Cursor
131 | 
132 | This server supports the Model Context Protocol (MCP) and integrates with Cursor AI. 
133 | 
134 | ### Automatic Configuration
135 | 
136 | This project includes a pre-configured `.cursor/mcp.json` file for automatic setup within Cursor.
137 | 
138 | ### Manual Configuration
139 | 
140 | To manually add this server to Cursor:
141 | 
142 | 1. Go to Cursor Settings → Features → MCP
143 | 2. Click "+ Add New MCP Server"
144 | 3. Enter the following details:
145 |    - **Name**: Postgres MCP
146 |    - **Type**: stdio
147 |    - **Command**: `node /full/path/to/server.js`
148 |    
149 | For more information on MCP integration with Cursor, see the [official documentation](https://cursor.sh/docs/mcp).
150 | 
151 | ## Available Tools
152 | 
153 | The server provides the following tools to AI models:
154 | 
155 | - `query`: Execute SQL queries with retry logic
156 | 
157 | ## Resources
158 | 
159 | The server exposes database tables as resources, allowing AI models to:
160 | 
161 | - List all tables in the database
162 | - View schema information for each table
163 | 
164 | ## Error Handling
165 | 
166 | The server includes:
167 | 
168 | - Connection retry logic
169 | - Detailed error logging
170 | - Graceful shutdown handling
171 | 
172 | ## Troubleshooting
173 | 
174 | ### Connection Issues
175 | 
176 | 1. **Database Connection Failed**
177 |    - Check if PostgreSQL is running: `pg_isready -h localhost -p 5433`
178 |    - Verify your credentials in the `.env` file are correct
179 |    - Make sure your IP address has access to the database (check pg_hba.conf)
180 |    - Try connecting with another tool like `psql` to verify credentials
181 | 
182 | 2. **Environment Variable Problems**
183 |    - Make sure your `.env` file is in the project root directory
184 |    - Check that the JSON structure in `DB_CREDENTIALS` is valid
185 |    - Verify there are no extra spaces or line breaks in the JSON string
186 |    - Test with: `node -e "console.log(JSON.parse(process.env.DB_CREDENTIALS))" < .env`
187 | 
188 | 3. **Node.js Version Issues**
189 |    - Check your Node.js version: `node -v`
190 |    - This server requires Node.js 20+
191 |    - If using an older version, install Node.js 20: `nvm install 20 && nvm use 20`
192 | 
193 | ### Cursor Integration
194 | 
195 | 1. **Server Not Showing in Cursor**
196 |    - Make sure the `.cursor/mcp.json` file exists and is properly formatted
197 |    - Try restarting Cursor to detect the project-specific configuration
198 |    - Check Cursor logs for any error messages
199 | 
200 | 2. **"Failed to create client" Error**
201 |    - This usually indicates the server crashed during startup
202 |    - Run the server manually with verbose logging to see the error: `node server.js -v`
203 |    - Check if the database credentials are accessible in the Cursor environment
204 | 
205 | 3. **No Tools Available in Cursor**
206 |    - Ensure the server is running properly (check logs)
207 |    - Try clicking the refresh button in the MCP tool panel
208 |    - Restart Cursor and try again
209 | 
210 | ### PostgreSQL Specific Issues
211 | 
212 | 1. **Permission Denied Errors**
213 |    - Make sure the database user has appropriate permissions for the tables
214 |    - Try granting required permissions: `GRANT SELECT ON ALL TABLES IN SCHEMA public TO username;`
215 | 
216 | 2. **"Relation does not exist" Errors**
217 |    - Verify that the table exists: `\dt tablename` in psql
218 |    - Check if you're connecting to the correct database
219 |    - Ensure the user has access to the schema where the table is located
220 | 
221 | 3. **Performance Issues**
222 |    - Large query results may cause lag, consider adding LIMIT clauses
223 |    - Check if your database needs optimization (indexes, vacuuming)
224 | 
225 | For additional help, you can run the server with verbose logging (`-v` flag) to see detailed error messages and operation logs.
226 | 
227 | ## License
228 | 
229 | MIT
230 | 
```

--------------------------------------------------------------------------------
/.cursor/mcp.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "ndao-db-prod": {
 4 |       "command": "node",
 5 |       "args": [
 6 |         "./server.js"
 7 |       ],
 8 |       "env": {
 9 |         "CURSOR_MCP": "1"
10 |       }
11 |     }
12 |   }
13 | } 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-gcloud-mysql",
 3 |   "version": "1.0.0",
 4 |   "description": "Model Context Protocol server for PostgreSQL database",
 5 |   "main": "server.js",
 6 |   "type": "module",
 7 |   "scripts": {
 8 |     "start": "node server.js"
 9 |   },
10 |   "dependencies": {
11 |     "@modelcontextprotocol/sdk": "^1.6.1",
12 |     "body-parser": "^1.20.2",
13 |     "cors": "^2.8.5",
14 |     "dotenv": "^16.3.1",
15 |     "express": "^4.18.2",
16 |     "pg": "^8.11.3"
17 |   },
18 |   "engines": {
19 |     "node": ">=20.0.0"
20 |   }
21 | }
22 | 
```

--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  3 | import {
  4 |   CallToolRequestSchema,
  5 |   ListResourcesRequestSchema,
  6 |   ListToolsRequestSchema,
  7 |   ReadResourceRequestSchema,
  8 | } from "@modelcontextprotocol/sdk/types.js";
  9 | import pg from "pg";
 10 | // Import and configure dotenv to load .env file
 11 | import dotenv from 'dotenv';
 12 | import path from 'path';
 13 | import { fileURLToPath } from 'url';
 14 | import fs from 'fs';
 15 | import os from 'os';
 16 | 
 17 | // Get the directory of this file
 18 | const __filename = fileURLToPath(import.meta.url);
 19 | const __dirname = path.dirname(__filename);
 20 | 
 21 | // Load environment variables from .env file
 22 | dotenv.config({ path: path.join(__dirname, '.env') });
 23 | 
 24 | // Parse command line arguments
 25 | const parseArgs = () => {
 26 |   const args = process.argv.slice(2);
 27 |   const options = {
 28 |     credentialsVar: 'DB_CREDENTIALS', // Default value
 29 |     silent: true // Default to silent mode
 30 |   };
 31 | 
 32 |   for (let i = 0; i < args.length; i++) {
 33 |     if (args[i] === '--credentials-var' || args[i] === '-c') {
 34 |       if (i + 1 < args.length) {
 35 |         options.credentialsVar = args[i + 1];
 36 |         i++; // Skip the next argument as it's the value
 37 |       } else {
 38 |         console.error('Error: --credentials-var flag requires a value');
 39 |         process.exit(1);
 40 |       }
 41 |     } else if (args[i] === '--verbose' || args[i] === '-v') {
 42 |       options.silent = false;
 43 |     } else if (args[i] === '--help' || args[i] === '-h') {
 44 |       console.log(`
 45 | Usage: node server.js [options]
 46 | 
 47 | Options:
 48 |   -c, --credentials-var VAR  Specify environment variable name containing DB credentials
 49 |                              (default: DB_CREDENTIALS)
 50 |   -v, --verbose              Enable console logs (silent by default)
 51 |   -h, --help                 Show this help message
 52 | 
 53 | Environment Variables:
 54 |   The specified environment variable (or DB_CREDENTIALS as fallback) should contain
 55 |   a JSON string with the following structure:
 56 |   {
 57 |     "DB_USER": "username",
 58 |     "DB_PASSWORD": "password",
 59 |     "DB_HOST": "hostname",
 60 |     "DB_PORT": "5432",
 61 |     "DB_NAME": "database_name"
 62 |   }
 63 | 
 64 | Fallback Logic:
 65 |   If the environment variable is not found in the .env file, the server will
 66 |   look for it in shell config files (~/.zshrc, ~/.bashrc, ~/.bash_profile, ~/.profile).
 67 |   This is especially useful for Cursor MCP environments where shell config files
 68 |   are not automatically sourced.
 69 | 
 70 | Examples:
 71 |   1. Using environment variable:
 72 |      node server.js --credentials-var MY_CUSTOM_DB_CREDS
 73 |   
 74 |   2. Using .env file (default):
 75 |      node server.js
 76 |       `);
 77 |       process.exit(0);
 78 |     }
 79 |   }
 80 |   
 81 |   return options;
 82 | };
 83 | 
 84 | // Check if we're being run under Cursor MCP
 85 | const isCursorMCP = process.env.CURSOR_MCP === '1';
 86 | 
 87 | const options = parseArgs();
 88 | 
 89 | // Create custom logger to respect silent mode
 90 | const logger = {
 91 |   log: (...args) => {
 92 |     if (!options.silent) {
 93 |       console.log(...args);
 94 |     }
 95 |   },
 96 |   error: (...args) => {
 97 |     // Always show errors, even in silent mode
 98 |     console.error(...args);
 99 |   }
100 | };
101 | 
102 | // Special handling for Cursor environment
103 | if (isCursorMCP) {
104 |   logger.log('Running in Cursor MCP environment');
105 | }
106 | 
107 | // Function to parse DB_CREDENTIALS from shell config files
108 | const parseShellConfigForCredentials = () => {
109 |   // List of common shell config files to check
110 |   const configFiles = [
111 |     '.zshrc',
112 |     '.bashrc',
113 |     '.bash_profile',
114 |     '.profile'
115 |   ];
116 |   
117 |   for (const configFile of configFiles) {
118 |     try {
119 |       const configPath = path.join(os.homedir(), configFile);
120 |       
121 |       if (!fs.existsSync(configPath)) {
122 |         logger.log(`~/${configFile} file not found`);
123 |         continue;
124 |       }
125 |       
126 |       const configContent = fs.readFileSync(configPath, 'utf8');
127 |       
128 |       // Enhanced regex to handle different export formats:
129 |       // - export VAR='value'
130 |       // - export VAR="value"
131 |       // - export VAR=value
132 |       const patterns = [
133 |         // Single quotes
134 |         new RegExp(`export\\s+(${options.credentialsVar}|[A-Z_]+DB[A-Z_]*CREDENTIALS[A-Z_]*)\\s*=\\s*'(.+?)'`, 's'),
135 |         // Double quotes
136 |         new RegExp(`export\\s+(${options.credentialsVar}|[A-Z_]+DB[A-Z_]*CREDENTIALS[A-Z_]*)\\s*=\\s*"(.+?)"`, 's'),
137 |         // No quotes
138 |         new RegExp(`export\\s+(${options.credentialsVar}|[A-Z_]+DB[A-Z_]*CREDENTIALS[A-Z_]*)\\s*=\\s*({.+?})`, 's')
139 |       ];
140 |       
141 |       for (const regex of patterns) {
142 |         const match = configContent.match(regex);
143 |         
144 |         if (match && match.length >= 3) {
145 |           const varName = match[1];
146 |           const credentialsJson = match[2];
147 |           
148 |           logger.log(`Found ${varName} in ~/${configFile}`);
149 |           
150 |           try {
151 |             const credentials = JSON.parse(credentialsJson);
152 |             return credentials;
153 |           } catch (parseError) {
154 |             logger.error(`Failed to parse JSON from ${varName} in ~/${configFile}: ${parseError.message}`);
155 |             // Continue trying other patterns or files
156 |           }
157 |         }
158 |       }
159 |     } catch (error) {
160 |       logger.error(`Error reading ~/${configFile}: ${error.message}`);
161 |       // Continue with next config file
162 |     }
163 |   }
164 |   
165 |   return null;
166 | };
167 | 
168 | // Parse DB credentials from environment variable, .env file, or shell config files
169 | let dbCredentials;
170 | try {
171 |   // Try to get credentials from environment variable
172 |   const credentialsJson = process.env[options.credentialsVar];
173 |   
174 |   if (!credentialsJson) {
175 |     logger.log(`${options.credentialsVar} environment variable not found. Checking shell config files as fallback...`);
176 |     
177 |     // Try to get credentials from shell config files
178 |     dbCredentials = parseShellConfigForCredentials();
179 |     
180 |     if (!dbCredentials) {
181 |       throw new Error(`${options.credentialsVar} not found in environment variables or shell config files.`);
182 |     }
183 |     
184 |     logger.log('Successfully parsed database credentials from shell config file');
185 |   } else {
186 |     try {
187 |       dbCredentials = JSON.parse(credentialsJson);
188 |       if (!options.silent) {
189 |         logger.log(`Successfully parsed database credentials from ${options.credentialsVar} environment variable`);
190 |       }
191 |     } catch (parseError) {
192 |       throw new Error(`Failed to parse JSON from ${options.credentialsVar}: ${parseError.message}. Ensure it contains valid JSON.`);
193 |     }
194 |   }
195 | } catch (error) {
196 |   logger.error('Error with database credentials:', error.message);
197 |   logger.error('Make sure your .env file is set up correctly with the DB_CREDENTIALS variable,');
198 |   logger.error('or ensure DB_CREDENTIALS is properly exported in your shell config file (~/.zshrc, ~/.bashrc, etc.).');
199 |   logger.error('Run with --help for more information on the required format.');
200 |   process.exit(1);
201 | }
202 | 
203 | // Validate required database credentials
204 | const validateCredentials = () => {
205 |   const requiredFields = ['DB_USER', 'DB_PASSWORD', 'DB_HOST', 'DB_PORT', 'DB_NAME'];
206 |   const missingFields = requiredFields.filter(field => !dbCredentials[field]);
207 |   
208 |   if (missingFields.length > 0) {
209 |     logger.error(`Error: Missing required fields in ${options.credentialsVar}: ${missingFields.join(', ')}`);
210 |     logger.error(`The ${options.credentialsVar} environment variable must contain a JSON object with these fields.`);
211 |     logger.error('Run with --help for more information on the required format.');
212 |     return false;
213 |   }
214 |   
215 |   // Validate that port is a number
216 |   const port = parseInt(dbCredentials.DB_PORT, 10);
217 |   if (isNaN(port)) {
218 |     logger.error(`Error: DB_PORT must be a valid number, got: "${dbCredentials.DB_PORT}"`);
219 |     return false;
220 |   }
221 |   
222 |   return true;
223 | };
224 | 
225 | if (!validateCredentials()) {
226 |   logger.error('Database credentials validation failed. Exiting.');
227 |   process.exit(1);
228 | }
229 | 
230 | // Database connection configuration
231 | const config = {
232 |   user: dbCredentials.DB_USER,
233 |   password: dbCredentials.DB_PASSWORD,
234 |   host: dbCredentials.DB_HOST,
235 |   port: parseInt(dbCredentials.DB_PORT, 10),
236 |   database: dbCredentials.DB_NAME,
237 |   // Add connection timeout and retry settings
238 |   connectionTimeoutMillis: 10000,
239 |   idleTimeoutMillis: 30000,
240 |   max: 10, // Maximum number of clients in the pool
241 | };
242 | 
243 | // Ensure localhost is using IPv4
244 | if (config.host === 'localhost') {
245 |   config.host = '127.0.0.1';
246 |   logger.log('Changed localhost to 127.0.0.1 to ensure IPv4 connection');
247 | }
248 | 
249 | // Log connection details (without password)
250 | if (!options.silent) {
251 |   logger.log('Database configuration:', {
252 |     user: config.user,
253 |     host: config.host,
254 |     port: config.port,
255 |     database: config.database,
256 |   });
257 | }
258 | 
259 | // Create a connection pool
260 | const pool = new pg.Pool(config);
261 | 
262 | // Add event listeners for pool errors
263 | pool.on('error', (err) => {
264 |   logger.error('Unexpected error on idle client', err);
265 | });
266 | 
267 | // Create the MCP server
268 | const server = new Server(
269 |   {
270 |     name: "cursor-mcp-postgres",
271 |     version: "0.1.0",
272 |   },
273 |   {
274 |     capabilities: {
275 |       resources: {},
276 |       tools: {},
277 |     },
278 |   },
279 | );
280 | 
281 | // Construct a resource base URL for the database
282 | const resourceBaseUrl = new URL(`postgres://${config.user}@${config.host}:${config.port}/${config.database}`);
283 | resourceBaseUrl.protocol = "postgres:";
284 | resourceBaseUrl.password = "";
285 | 
286 | const SCHEMA_PATH = "schema";
287 | 
288 | // Test database connection
289 | async function testDatabaseConnection() {
290 |   let testClient = null;
291 |   try {
292 |     logger.log('Testing connection to PostgreSQL database...');
293 |     testClient = await pool.connect();
294 |     const result = await testClient.query('SELECT 1 AS connection_test');
295 |     logger.log('Database connection test successful:', result.rows[0]);
296 |     return true;
297 |   } catch (err) {
298 |     logger.error('Database connection test failed:', err.message);
299 |     
300 |     // Provide more specific error messages based on error codes
301 |     if (err.code === 'ECONNREFUSED') {
302 |       logger.error('Connection refused. Please check if the database server is running and accessible.');
303 |     } else if (err.code === '28P01') {
304 |       logger.error('Authentication failed. Please check your database credentials.');
305 |     } else if (err.code === '3D000') {
306 |       logger.error('Database does not exist. Please check the database name.');
307 |     }
308 |     
309 |     logger.error('Error details:', err);
310 |     
311 |     // Don't exit immediately in Cursor environment to allow showing the error
312 |     if (!isCursorMCP) {
313 |       process.exit(1);
314 |     }
315 |     return false;
316 |   } finally {
317 |     if (testClient) {
318 |       testClient.release();
319 |     }
320 |   }
321 | }
322 | 
323 | // Execute a query with retry logic
324 | async function executeQueryWithRetry(sql, maxRetries = 3) {
325 |   let retries = 0;
326 |   let lastError = null;
327 | 
328 |   while (retries < maxRetries) {
329 |     const client = await pool.connect();
330 |     try {
331 |       logger.log(`Executing query (attempt ${retries + 1}/${maxRetries}):`, sql);
332 |       const result = await client.query(sql);
333 |       return result;
334 |     } catch (err) {
335 |       lastError = err;
336 |       logger.error(`Query error (attempt ${retries + 1}/${maxRetries}):`, err.message);
337 |       
338 |       // Check if this is a connection-related error that might be resolved by retrying
339 |       if (err.code === 'ECONNREFUSED' || err.code === '57P01' || err.code === '08006' || err.code === '08001') {
340 |         retries++;
341 |         await new Promise(resolve => setTimeout(resolve, 1000 * retries)); // Exponential backoff
342 |       } else {
343 |         // For other errors, don't retry
344 |         throw err;
345 |       }
346 |     } finally {
347 |       client.release();
348 |     }
349 |   }
350 |   
351 |   // If we've exhausted all retries
352 |   throw lastError;
353 | }
354 | 
355 | // Handler for listing database resources (tables)
356 | server.setRequestHandler(ListResourcesRequestSchema, async () => {
357 |   const client = await pool.connect();
358 |   try {
359 |     const result = await client.query(
360 |       "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",
361 |     );
362 |     return {
363 |       resources: result.rows.map((row) => ({
364 |         uri: new URL(`${row.table_name}/${SCHEMA_PATH}`, resourceBaseUrl).href,
365 |         mimeType: "application/json",
366 |         name: `"${row.table_name}" database schema`,
367 |       })),
368 |     };
369 |   } catch (error) {
370 |     logger.error('Error listing resources:', error.message);
371 |     throw error;
372 |   } finally {
373 |     client.release();
374 |   }
375 | });
376 | 
377 | // Handler for reading resource details (table schema)
378 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
379 |   const resourceUrl = new URL(request.params.uri);
380 | 
381 |   const pathComponents = resourceUrl.pathname.split("/");
382 |   const schema = pathComponents.pop();
383 |   const tableName = pathComponents.pop();
384 | 
385 |   if (schema !== SCHEMA_PATH) {
386 |     throw new Error("Invalid resource URI");
387 |   }
388 | 
389 |   const client = await pool.connect();
390 |   try {
391 |     const result = await client.query(
392 |       "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1",
393 |       [tableName],
394 |     );
395 | 
396 |     return {
397 |       contents: [
398 |         {
399 |           uri: request.params.uri,
400 |           mimeType: "application/json",
401 |           text: JSON.stringify(result.rows, null, 2),
402 |         },
403 |       ],
404 |     };
405 |   } catch (error) {
406 |     logger.error('Error reading resource:', error.message);
407 |     throw error;
408 |   } finally {
409 |     client.release();
410 |   }
411 | });
412 | 
413 | // Handler for listing available tools
414 | server.setRequestHandler(ListToolsRequestSchema, async () => {
415 |   return {
416 |     tools: [
417 |       {
418 |         name: "query",
419 |         description: "Run a SQL query against the PostgreSQL database",
420 |         inputSchema: {
421 |           type: "object",
422 |           properties: {
423 |             sql: { type: "string" },
424 |           },
425 |           required: ["sql"],
426 |         },
427 |       },
428 |     ],
429 |   };
430 | });
431 | 
432 | // Handler for executing tool calls (SQL queries)
433 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
434 |   if (request.params.name === "query") {
435 |     // Use type assertion in a way that works in JavaScript
436 |     const sql = request.params.arguments?.sql;
437 |     
438 |     if (!sql || typeof sql !== 'string') {
439 |       throw new Error("SQL query is required and must be a string");
440 |     }
441 | 
442 |     try {
443 |       // Use our retry logic for query execution
444 |       const result = await executeQueryWithRetry(sql);
445 |       
446 |       return {
447 |         content: [{ type: "text", text: JSON.stringify(result.rows, null, 2) }],
448 |         isError: false,
449 |       };
450 |     } catch (error) {
451 |       logger.error('Query execution error:', error.message);
452 |       return {
453 |         content: [{ type: "text", text: `Error executing query: ${error.message}` }],
454 |         isError: true,
455 |       };
456 |     }
457 |   }
458 |   throw new Error(`Unknown tool: ${request.params.name}`);
459 | });
460 | 
461 | // Main function to run the server
462 | async function runServer() {
463 |   try {
464 |     // Test database connection before starting
465 |     const connectionSuccess = await testDatabaseConnection();
466 |     
467 |     // In Cursor, we proceed even if the connection test fails to show errors
468 |     if (!isCursorMCP || connectionSuccess) {
469 |       logger.log('Starting MCP server for PostgreSQL...');
470 |       
471 |       // Create the transport
472 |       const transport = new StdioServerTransport();
473 |       
474 |       try {
475 |         // Connect to the transport
476 |         await server.connect(transport);
477 |         logger.log('MCP server connected and ready to use');
478 |       } catch (transportError) {
479 |         logger.error('Failed to connect to MCP transport:', transportError.message);
480 |         if (!isCursorMCP) {
481 |           process.exit(1);
482 |         }
483 |       }
484 |     }
485 |   } catch (error) {
486 |     logger.error('Server startup error:', error.message);
487 |     logger.error('Error details:', error);
488 |     if (!isCursorMCP) {
489 |       process.exit(1);
490 |     }
491 |   }
492 | }
493 | 
494 | // Handle graceful shutdown
495 | process.on('SIGINT', async () => {
496 |   logger.log('Closing database connection pool...');
497 |   try {
498 |     await pool.end();
499 |     logger.log('Database connection pool closed.');
500 |   } catch (err) {
501 |     logger.error('Error closing pool:', err.message);
502 |   }
503 |   process.exit(0);
504 | });
505 | 
506 | // Start the server
507 | runServer().catch(err => {
508 |   logger.error('Unhandled error:', err);
509 |   logger.error('Error details:', err);
510 |   if (!isCursorMCP) {
511 |     process.exit(1);
512 |   }
513 | }); 
```