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

```
├── .gitignore
├── cursorSettings.png
├── gel_llm.txt
├── header.jpg
├── package.json
├── README.md
├── src
│   └── index.ts
├── tsconfig.build.json
└── tsconfig.json
```

# Files

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

```
1 | dbschema/
2 | gel.toml
3 | yarn.lock
4 | build/
5 | node_modules/
6 | src/edgeql-js/
```

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

```markdown
  1 | # Gel Database MCP Server [Unofficial/Old]
  2 | This was created before Gel had an official MCP server which is a lot easier to setup. Leaving this one up though since it was an example of an MCP server I made.
  3 | Official one here: https://github.com/geldata/gel-mcp
  4 | 
  5 | A TypeScript-based Model Context Protocol (MCP) server designed to streamline Gel database operations with EdgeQL queries. This project provides Tools for LLM Agents (Cursor Agent, Claude Code, etc) to automate learning about your schema, and writing, validating, and executing database queries. Easily interact with your Gel database through natural language. Vibe coders rejoice! 
  6 | 
  7 | 
  8 | Note: Query generation is not included since LLMs can write more flexible queries. Tested this with Cursor agent using Claude-3.7-sonnet-thinking and had good results after providing Gel docs by linking the relevant webpages. 
  9 | 
 10 | ![Project Architecture Diagram](header.jpg)
 11 | 
 12 | ## Quick Start Guide
 13 | 
 14 | ```bash
 15 | # 1. Install dependencies
 16 | yarn install
 17 | 
 18 | # 2. Copy your dbschema folder into the project if you have one already 
 19 | # cp -r /path/to/your/dbschema ./
 20 | # or just copy and paste
 21 | 
 22 | # 3. Initialize a Gel project
 23 | npx gel project init
 24 | # Follow prompts to set up a new project 
 25 | # Can point to an existing gel instance by providing the name of your instance
 26 | #   -Import migrations if it asks
 27 | 
 28 | # 4. Generate EdgeQL JavaScript query builder files
 29 | npx @gel/generate edgeql-js
 30 | # Note: Re-run this command after any schema changes
 31 | 
 32 | # 5. Update connection settings
 33 | # Edit src/index_gel.ts lines 19-25 with your database, host, port, user, password
 34 | # Edit src/index_gel.ts line 37 with your branch name
 35 | 
 36 | # 6. Build the project
 37 | yarn build
 38 | 
 39 | # 7. (optional) Test the server runs without errors
 40 | node build/index.js
 41 | 
 42 | # 7.1 (if you have errors) Test server with a UI that provides more clear error logs using: 
 43 | npx @modelcontextprotocol/inspector node build/index.js
 44 | 
 45 | # 8. (Recommended) Include the gel_llm.txt documentation file
 46 | # Download the Gel documentation file and place it in your project root
 47 | # This allows both the search tool and direct file access for your LLM agent
 48 | # curl -o gel_llm.txt https://raw.githubusercontent.com/yourorg/gel-docs/main/gel_llm.txt
 49 | # Note: Replace the URL with the actual source of your gel_llm.txt file
 50 | ```
 51 | # Connect MCP Server in Cursor
 52 | 1. Click on the gear icon on the top right > MCP > +Add a new server
 53 | 2. Name it whatever you want
 54 | 3. Select type: Command
 55 | 4. Enter this: node your/full/path/to/build/index.js
 56 | 
 57 | ![Screenshot of Cursor MCP Settings](cursorSettings.png)
 58 | 
 59 | **Note:** While this server has been primarily tested with Cursor's agent, it should work with other agents and LLMs that support the Model Context Protocol. If you test with other agents, please feel free to contribute your findings!
 60 | 
 61 | 
 62 | ## Available Tools
 63 | 
 64 | The Gel Database MCP Server provides the following tools:
 65 | 
 66 | ### describe-schema
 67 | This helps your LLM agent learn and understand your database structure without having to manually inspect the code. The agent can discover available entity types, their properties, relationships, and constraints to generate more accurate queries.
 68 | 
 69 | **When to use:** When your agent needs to understand the structure of a database entity before querying it.
 70 | ![image](https://github.com/user-attachments/assets/e48b0da7-cd95-4416-820a-2a5c870c8e73)
 71 | 
 72 | ### validate-query
 73 | This helps your LLM agent verify raw EdgeQL query syntax without executing it, allowing safe validation of generated queries before they're run against your database.
 74 | 
 75 | **When to use:** During query development to check syntax without risking execution side effects.
 76 | ![image](https://github.com/user-attachments/assets/1d54c8a5-6f5c-4f7c-904c-93f664e23718)
 77 | 
 78 | ### execute-edgeql
 79 | This helps your LLM agent directly interact with your database by running raw EdgeQL queries, retrieving data, and performing operations based on your instructions. Your LLM can generate EdgeQL queries and execute them autonomously.
 80 | 
 81 | **Example:**
 82 | ```edgeql
 83 | SELECT Product { name, price } FILTER .price > 100;
 84 | ```
 85 | ![image](https://github.com/user-attachments/assets/79bbabab-aa3e-42e8-bd9f-92ba03cd18c0)
 86 | 
 87 | ### search-gel-docs
 88 | This tool allows your LLM agent to search through the Gel documentation to find relevant information about EdgeQL syntax, features, or examples. It returns comprehensive results with context to help the agent better understand Gel database concepts.
 89 | 
 90 | **When to use:** When your agent needs to learn about specific Gel/EdgeQL features, understand syntax, or find examples for implementing database operations.
 91 | 
 92 | **Example:**
 93 | ```
 94 | search_term: "for loop"
 95 | context_lines: 10  # Optional: Number of context lines to show (default: 5)
 96 | match_all_terms: true  # Optional: Require all terms to match (default: false)
 97 | ```
 98 | 
 99 | **Note on Documentation Hybrid Approach:** For optimal results, we recommend both:
100 | 1. Including the `gel_llm.txt` file in your project root (for direct file access)
101 | 2. Using the search-gel-docs tool for targeted queries
102 | 
103 | This hybrid approach gives your LLM agent the flexibility to search for specific terms while also accessing the complete documentation when needed for broader context.
104 | 
105 | ### execute-typescript
106 | Similar to execute-edgeql but can use this for testing and running Typescript Gel queries made with the query builder syntax. 
107 | 
108 | Instructions are included in the tool, but still a good idea to ask the agent what instructions it has so it loads them up in context. This makes sure it doesn't skip them. 
109 | 
110 | Note: General JavaScript syntax errors can crash the server, so if the connection is appearing as closed you will have to refresh the crashed server in Cursor MCP settings or restart the server. 
111 | 
112 | **Tell the LLM these are the Best practices:**
113 | - Use `await gelClient.query()` with console.log to display results
114 | - Use ORDER BY with THEN, not commas (e.g., ORDER BY .field1 THEN .field2)
115 | - Keep code simple and focused on a single operation
116 | 
117 | **Example:**
118 | ```typescript
119 | console.log(await gelClient.query(`
120 |   SELECT Product { 
121 |     name, 
122 |     price 
123 |   } 
124 |   FILTER .price > 100 
125 |   ORDER BY .price DESC 
126 |   LIMIT 5;
127 | `));
128 | ```
129 | 
130 | **When to use:** For complex queries that require programmatic logic or when you need to process query results with JavaScript.
131 | 
132 | ![image](https://github.com/user-attachments/assets/aed79dc8-d2ba-45d5-830b-1d73c04a5614)
133 | 
134 | ## Learn More
135 | 
136 | For more information about the Model Context Protocol, visit [modelcontextprotocol.io/quickstart](https://modelcontextprotocol.io/quickstart).
137 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "CommonJS",
 5 |     "moduleResolution": "Node",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "allowJs": true
13 |   },
14 |   "include": ["src/**/*"],
15 |   "exclude": ["node_modules", "dbschema"]
16 | }
17 | 
```

--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "CommonJS",
 5 |     "moduleResolution": "Node",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "allowJs": true,
13 |     "noEmitOnError": false
14 |   },
15 |   "include": ["src/**/*"],
16 |   "exclude": ["node_modules", "dbschema"]
17 | } 
```

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

```json
 1 | {
 2 |   "name": "mcp-quickstart-ts",
 3 |   "version": "1.0.0",
 4 |   "main": "index.js",
 5 |   "bin": {
 6 |     "weather": "./build/index.js"
 7 |   },
 8 |   "scripts": {
 9 |     "build": "node -e \"const fs = require('fs-extra'); const path = require('path'); const srcDir = './src/edgeql-js'; const dbDir = './dbschema/edgeql-js'; try { if (fs.existsSync(dbDir)) { fs.ensureDirSync(path.dirname(srcDir)); fs.copySync(dbDir, srcDir, {overwrite: true}); console.log('Successfully copied edgeql-js files'); } else { console.warn('Warning: dbschema/edgeql-js directory not found, skipping copy step'); } } catch(err) { console.error('Error during copy:', err); process.exit(1); }\" && tsc -p tsconfig.build.json && node -e \"const fs = require('fs'); const indexPath = './build/index.js'; try { if (fs.existsSync(indexPath)) { fs.chmodSync(indexPath, '755'); console.log('Successfully made index.js executable'); } else { console.warn('Warning: build/index.js not found, cannot make executable'); } } catch(err) { console.error('Error setting permissions:', err); }\""
10 |   },
11 |   "files": [
12 |     "build"
13 |   ],
14 |   "keywords": [],
15 |   "author": "",
16 |   "license": "ISC",
17 |   "description": "",
18 |   "devDependencies": {
19 |     "@types/node": "^22.10.0",
20 |     "fs-extra": "^11.2.0",
21 |     "typescript": "^5.7.2"
22 |   },
23 |   "dependencies": {
24 |     "@babel/runtime": "^7.26.9",
25 |     "@modelcontextprotocol/sdk": "^1.4.0",
26 |     "gel": "^2.0.0"
27 |   }
28 | }
29 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  3 | import { z } from "zod";
  4 | import { createClient } from "gel"; // The Gel client library
  5 | import * as gelLib from "gel"; // Import all exports from Gel
  6 | import e from "./edgeql-js/index.js";
  7 | // Import the EdgeQL query builder
  8 | // import { createClient as createEdgeQLClient } from "../dbschema/edgeql-js/index.js";
  9 | 
 10 | let gelClient: any; // Explicitly type as any for now
 11 | 
 12 | // Note: Due to TypeScript ESM compatibility issues with the generated EdgeQL query builder,
 13 | // we're using 'any' type here. In a production setting, you would properly integrate
 14 | // the EdgeQL query builder types.
 15 | 
 16 | 
 17 | async function getBranchClient(branchName: string) {
 18 |   console.error(`[getBranchClient] Starting to get client for branch: ${branchName}`);
 19 |   const branchClient = createClient({
 20 |     tlsSecurity: "insecure",
 21 |     database: branchName,
 22 |     host: "localhost",
 23 |     port: 10700,
 24 |     user: "edgedb",
 25 |     password: "6GmB7hZI5NTziQ1xEdGaGoQe",
 26 |     // Add other connection parameters if needed (host, user, password, etc.)
 27 |   });
 28 |   console.error(`[getBranchClient] Successfully created client for branch: ${branchName}`);
 29 |   return branchClient;
 30 | }
 31 | 
 32 | // Initialize Gel client
 33 | async function initGelClient() {
 34 |   try {
 35 |     // You can configure this with environment variables or directly here
 36 |     // Use the branch ID from environment or default to example
 37 |     const branchId = process.env.GEL_BRANCH_ID || "3802d36f-f2e8-45e5-abe4-8f5d2ecdff0e";
 38 |     gelClient = await getBranchClient(branchId);
 39 |     
 40 |     // Test connection
 41 |     const result = await gelClient.query('SELECT "Gel MCP Server connection test"');
 42 |     console.error('Gel database connection successful:', result);
 43 |     return true;
 44 |   } catch (error) {
 45 |     console.error('Error connecting to Gel database:', error);
 46 |     return false;
 47 |   }
 48 | }
 49 | 
 50 | // Create server instance
 51 | const server = new McpServer({
 52 |   name: "gel-database",
 53 |   version: "1.0.0",
 54 | });
 55 | 
 56 | // ===== GEL DATABASE QUERY TOOLS =====
 57 | 
 58 | // Tool to execute raw EdgeQL queries
 59 | server.tool(
 60 |   "execute-edgeql",
 61 |   "Execute a raw EdgeQL query on the Gel database",
 62 |   {
 63 |     query: z.string().describe("The EdgeQL query to execute"),
 64 |     args: z.record(z.any()).optional().describe("Optional query arguments"),
 65 |   },
 66 |   async (args, extra) => {
 67 |     try {
 68 |       console.error(`[execute-edgeql] Executing query: ${args.query}`);
 69 |       
 70 |       if (!gelClient) {
 71 |         return {
 72 |           content: [
 73 |             {
 74 |               type: "text",
 75 |               text: "Database client is not initialized.",
 76 |             },
 77 |           ],
 78 |         };
 79 |       }
 80 |       
 81 |       // Execute query with or without arguments based on whether args are provided
 82 |       let result;
 83 |       if (args.args && Object.keys(args.args).length > 0) {
 84 |         result = await gelClient.query(args.query, args.args);
 85 |       } else {
 86 |         result = await gelClient.query(args.query);
 87 |       }
 88 |       
 89 |       return {
 90 |         content: [
 91 |           {
 92 |             type: "text",
 93 |             text: "Query executed successfully:",
 94 |           },
 95 |           {
 96 |             type: "text",
 97 |             text: JSON.stringify(result, null, 2),
 98 |           },
 99 |         ],
100 |       };
101 |     } catch (error: any) {
102 |       console.error("Error executing EdgeQL query:", error);
103 |       return {
104 |         content: [
105 |           {
106 |             type: "text",
107 |             text: `Error executing query: ${error?.message || "Unknown error"}`,
108 |           },
109 |         ],
110 |       };
111 |     }
112 |   },
113 | );
114 | 
115 | // Tool to describe the database schema for a type
116 | server.tool(
117 |   "describe-schema",
118 |   "Get schema information for a specific type",
119 |   {
120 |     typeName: z.string().describe("Name of the type to describe"),
121 |   },
122 |   async (args, extra) => {
123 |     try {
124 |       if (!gelClient) {
125 |         return {
126 |           content: [
127 |             {
128 |               type: "text",
129 |               text: "Database client is not initialized.",
130 |             },
131 |           ],
132 |         };
133 |       }
134 | 
135 |       // Use introspection query to get schema information
136 |       const query = `
137 |         WITH module schema
138 |         SELECT ObjectType {
139 |           name,
140 |           properties: {
141 |             name,
142 |             target: { name },
143 |             cardinality,
144 |             required
145 |           },
146 |           links: {
147 |             name,
148 |             target: { name },
149 |             cardinality,
150 |             required
151 |           }
152 |         }
153 |         FILTER .name = <str>$typeName
154 |       `;
155 | 
156 |       const result = await gelClient.query(query, { typeName: `default::${args.typeName}` });
157 | 
158 |       if (!result || result.length === 0) {
159 |         return {
160 |           content: [
161 |             {
162 |               type: "text",
163 |               text: `Type '${args.typeName}' not found in the schema.`,
164 |             },
165 |           ],
166 |         };
167 |       }
168 | 
169 |       return {
170 |         content: [
171 |           {
172 |             type: "text",
173 |             text: `Schema for type '${args.typeName}':`,
174 |           },
175 |           {
176 |             type: "text",
177 |             text: JSON.stringify(result, null, 2),
178 |           },
179 |         ],
180 |       };
181 |     } catch (error: any) {
182 |       console.error("Error describing schema:", error);
183 |       return {
184 |         content: [
185 |           {
186 |             type: "text",
187 |             text: `Error describing schema: ${error?.message || "Unknown error"}`,
188 |           },
189 |         ],
190 |       };
191 |     }
192 |   },
193 | );
194 | 
195 | // Tool to validate EdgeQL query syntax
196 | server.tool(
197 |   "validate-query",
198 |   "Validate EdgeQL query syntax without executing it",
199 |   {
200 |     query: z.string().describe("The EdgeQL query to validate"),
201 |   },
202 |   async (args, extra) => {
203 |     try {
204 |       if (!gelClient) {
205 |         return {
206 |           content: [
207 |             {
208 |               type: "text",
209 |               text: "Database client is not initialized.",
210 |             },
211 |           ],
212 |         };
213 |       }
214 | 
215 |       // Using a query with ANALYZE to validate syntax
216 |       const analyzeQuery = `ANALYZE ${args.query};`;
217 |       await gelClient.query(analyzeQuery);
218 | 
219 |       return {
220 |         content: [
221 |           {
222 |             type: "text",
223 |             text: "Query syntax is valid.",
224 |           },
225 |         ],
226 |       };
227 |     } catch (error: any) {
228 |       console.error("Error validating query:", error);
229 |       return {
230 |         content: [
231 |           {
232 |             type: "text",
233 |             text: "Query validation failed:",
234 |           },
235 |           {
236 |             type: "text",
237 |             text: error?.message || "Unknown error",
238 |           },
239 |         ],
240 |       };
241 |     }
242 |   },
243 | );
244 | 
245 | // Tool to execute TypeScript code with access to the EdgeQL query builder
246 | server.tool(
247 |   "execute-typescript",
248 |   "Execute TypeScript with EdgeDB query builder access. USAGE: 1) The gelClient is ALREADY SET UP - use it directly. 2) Query builder pattern: 'const query = e.select(...); const result = await query.run(gelClient);'. 3) For direct EdgeQL: 'const result = await gelClient.query(`SELECT...`);'. 4) Common patterns: e.select(e.Type, obj => ({properties, filter: e.op(...)})), e.insert(e.Type, {properties}), e.update(e.Type, obj => ({filter_single: {id: '...'}, set: {prop: value}})), e.delete(e.Type, obj => ({filter: ...})). 5) Always use try/catch for error handling. 6) Check schema first with mcp__describe_schema if unsure about types. 7) Use e.cast() for proper type conversion when needed.",
249 |   {
250 |     code: z.string().describe("The TypeScript code to execute"),
251 |     timeout: z.number().optional().describe("Maximum execution time in milliseconds (default: 5000)"),
252 |     use_gel_client: z.boolean().optional().describe("Automatically inject the configured Gel client (default: true)"),
253 |     memory_limit: z.number().optional().describe("Maximum memory usage in MB (default: 512)"),
254 |   },
255 |   async (args, extra) => {
256 |     try {
257 |       console.error("[execute-typescript] Starting execution");
258 |       
259 |       // Initialize metrics for monitoring execution
260 |       const startTime = Date.now();
261 |       let isExecutionCompleted = false;
262 |       let hasConnectionError = false;
263 |       
264 |       // Check if gelClient is required but not initialized
265 |       if (args.use_gel_client !== false) {
266 |         if (!gelClient) {
267 |           console.error("[execute-typescript] Database client not initialized");
268 |           return {
269 |             content: [
270 |               {
271 |                 type: "text",
272 |                 text: "Database client is not initialized. Please initialize the database connection first.",
273 |               },
274 |             ],
275 |           };
276 |         }
277 |         
278 |         // Test the connection before executing
279 |         try {
280 |           await gelClient.query('SELECT 1;');
281 |         } catch (connError) {
282 |           console.error("[execute-typescript] Connection test failed:", connError);
283 |           hasConnectionError = true;
284 |           
285 |           // Try to reinitialize the connection
286 |           try {
287 |             console.error("[execute-typescript] Attempting to reconnect to database");
288 |             await initGelClient();
289 |             
290 |             // Test the connection again
291 |             await gelClient.query('SELECT 1;');
292 |             hasConnectionError = false;
293 |             console.error("[execute-typescript] Successfully reconnected to database");
294 |           } catch (reconnectError) {
295 |             console.error("[execute-typescript] Failed to reconnect:", reconnectError);
296 |           }
297 |           
298 |           // If still having connection issues
299 |           if (hasConnectionError) {
300 |             return {
301 |               content: [
302 |                 {
303 |                   type: "text",
304 |                   text: "Database connection error. The server has attempted to reconnect but was unsuccessful.",
305 |                 },
306 |                 {
307 |                   type: "text",
308 |                   text: "Please try again later or check your database connection configuration.",
309 |                 },
310 |               ],
311 |             };
312 |           }
313 |         }
314 |       }
315 | 
316 |       // Log the code for debugging (but sanitize it first to avoid exposing sensitive data)
317 |       const sanitizedCode = args.code.replace(/password\s*[:=]\s*["'].*?["']/gi, 'password: "[REDACTED]"');
318 |       console.error(`[execute-typescript] Executing code:\n${sanitizedCode}`);
319 |       
320 |       // Set memory limit (default to 512MB)
321 |       const memoryLimitMB = args.memory_limit || 512;
322 |       const memoryLimitBytes = memoryLimitMB * 1024 * 1024;
323 | 
324 |       // Set up the evaluation context with explicit log capture and error handling
325 |       let capturedLogs: string[] = [];
326 |       let hasUncaughtError = false;
327 |       
328 |       // Create a sandboxed context with enhanced utilities
329 |       const context = {
330 |         e, // EdgeQL query builder
331 |         gelClient: args.use_gel_client !== false ? gelClient : undefined,
332 |         console: {
333 |           log: (...args: any[]) => {
334 |             try {
335 |               const logMsg = args.map(a => {
336 |                 try {
337 |                   return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a);
338 |                 } catch (jsonError) {
339 |                   return `[Unserializable object: ${typeof a}]`;
340 |                 }
341 |               }).join(' ');
342 |               
343 |               // Truncate very long log messages to prevent memory issues
344 |               const maxLogLength = 10000;
345 |               const truncatedMsg = logMsg.length > maxLogLength 
346 |                 ? logMsg.substring(0, maxLogLength) + `... [truncated, ${logMsg.length - maxLogLength} more characters]` 
347 |                 : logMsg;
348 |               
349 |               capturedLogs.push(truncatedMsg);
350 |               process.stderr.write(`[TS Script Log]: ${truncatedMsg.substring(0, 1000)}${truncatedMsg.length > 1000 ? '...' : ''}\n`);
351 |             } catch (logError) {
352 |               process.stderr.write(`[TS Script Log Error]: Failed to process log message\n`);
353 |             }
354 |           },
355 |           error: (...args: any[]) => {
356 |             try {
357 |               const logMsg = args.map(a => {
358 |                 try {
359 |                   return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a);
360 |                 } catch (jsonError) {
361 |                   return `[Unserializable object: ${typeof a}]`;
362 |                 }
363 |               }).join(' ');
364 |               
365 |               const truncatedMsg = logMsg.length > 10000 
366 |                 ? logMsg.substring(0, 10000) + `... [truncated, ${logMsg.length - 10000} more characters]` 
367 |                 : logMsg;
368 |               
369 |               capturedLogs.push(`ERROR: ${truncatedMsg}`);
370 |               process.stderr.write(`[TS Script Error]: ${truncatedMsg.substring(0, 1000)}${truncatedMsg.length > 1000 ? '...' : ''}\n`);
371 |             } catch (logError) {
372 |               process.stderr.write(`[TS Script Error]: Failed to process error message\n`);
373 |             }
374 |           },
375 |         },
376 |         // Utilities with safety guards
377 |         setTimeout: (fn: Function, ms: number) => {
378 |           // Ensure timeouts don't exceed the overall execution timeout
379 |           const safeMs = Math.min(ms, args.timeout || 5000);
380 |           return setTimeout(fn, safeMs);
381 |         },
382 |         clearTimeout,
383 |         setInterval,
384 |         clearInterval,
385 |         Promise,
386 |         String,
387 |         Number,
388 |         Boolean,
389 |         Array,
390 |         Object,
391 |         JSON,
392 |         Date,
393 |         Math,
394 |         Error,
395 |         // Add utility function for safe database queries
396 |         safeQuery: async (queryStr: string, params: any = {}) => {
397 |           try {
398 |             if (!gelClient) throw new Error("Database client not available");
399 |             return { success: true, data: await gelClient.query(queryStr, params), error: null };
400 |           } catch (queryError: any) {
401 |             return { 
402 |               success: false, 
403 |               data: null, 
404 |               error: queryError?.message || String(queryError),
405 |               code: queryError?.code
406 |             };
407 |           }
408 |         }
409 |       };
410 | 
411 |       // Prepare the code for execution with a well-instrumented IIFE
412 |       // Include memory usage monitoring, timeouts, and comprehensive error handling
413 |       const scriptToExecute = `
414 |         (async () => {
415 |           console.log("[Inside] Starting script");
416 |           
417 |           // Define safeguards
418 |           let executionStartTime = Date.now();
419 |           let executionStats = {
420 |             executionTime: 0,
421 |             errorCount: 0
422 |           };
423 |           
424 |           // Simple monitoring function
425 |           const checkExecution = () => {
426 |             try {
427 |               // Update execution time
428 |               executionStats.executionTime = Date.now() - executionStartTime;
429 |             } catch (checkError) {
430 |               console.error("Execution check failed:", checkError.message);
431 |             }
432 |           };
433 |           
434 |           // Set up periodic checks
435 |           const checkInterval = setInterval(checkExecution, 500);
436 |           
437 |           try {
438 |             // Execute the user code in a controlled manner
439 |             let __result;
440 |             __result = await (async () => {
441 |               // Insert user code here, wrapped in extra try/catch
442 |               try {
443 |                 ${args.code}
444 |               } catch (userCodeError) {
445 |                 console.error("User code error:", userCodeError.message);
446 |                 if (userCodeError.stack) console.error(userCodeError.stack);
447 |                 throw userCodeError;
448 |               }
449 |             })();
450 |             
451 |             // Clean up
452 |             clearInterval(checkInterval);
453 |             console.log("[Inside] Code executed");
454 |             
455 |             // Final check
456 |             checkExecution();
457 |             
458 |             return {
459 |               result: __result,
460 |               stats: executionStats
461 |             };
462 |           } catch (error) {
463 |             // Clean up
464 |             clearInterval(checkInterval);
465 |             
466 |             // Check if this is a database connection error
467 |             const isConnectionError = error.message && (
468 |               error.message.includes("Not connected") || 
469 |               error.message.includes("Connection closed") ||
470 |               error.message.includes("network") ||
471 |               error.message.includes("socket") ||
472 |               error.message.includes("ECONNRESET") ||
473 |               error.message.includes("timeout")
474 |             );
475 |             
476 |             // Log error details
477 |             console.error("Script execution error:", error.message);
478 |             if (error.stack) console.error(error.stack);
479 |             
480 |             executionStats.errorCount++;
481 |             
482 |             return { 
483 |               error: error.message || "Unknown error occurred", 
484 |               stack: error.stack,
485 |               isConnectionError: isConnectionError,
486 |               stats: executionStats
487 |             };
488 |           }
489 |         })()
490 |       `;
491 | 
492 |       // Set up timeout mechanism
493 |       const timeoutMs = args.timeout || 5000;
494 |       let timeoutId: NodeJS.Timeout;
495 | 
496 |       const timeoutPromise = new Promise((_, reject) => {
497 |         timeoutId = setTimeout(() => {
498 |           isExecutionCompleted = true;
499 |           reject(new Error(`Execution timed out after ${timeoutMs}ms`));
500 |         }, timeoutMs);
501 |       });
502 | 
503 |       console.error("[execute-typescript] Creating execution script");
504 | 
505 |       // Use vm module for execution with resource constraints
506 |       const vm = require('vm');
507 |       try {
508 |         const script = new vm.Script(scriptToExecute, {
509 |           filename: 'user-script.js',
510 |           timeout: timeoutMs,  // Apply the timeout limit to compilation as well
511 |         });
512 |         
513 |         // Create isolated context
514 |         const contextObject = vm.createContext(context);
515 | 
516 |         console.error("[execute-typescript] Starting execution with timeout:", timeoutMs);
517 | 
518 |         const executionPromise = Promise.resolve(script.runInContext(contextObject, {
519 |           timeout: timeoutMs,
520 |           displayErrors: true,
521 |         }));
522 | 
523 |         // Race between execution and timeout
524 |         const result = await Promise.race([executionPromise, timeoutPromise])
525 |           .finally(() => {
526 |             clearTimeout(timeoutId);
527 |             isExecutionCompleted = true;
528 |           });
529 | 
530 |         // Calculate execution metrics
531 |         const executionTime = Date.now() - startTime;
532 |         console.error(`[execute-typescript] Execution completed in ${executionTime}ms, result type:`, typeof result);
533 | 
534 |         // Check if there was a connection error in the result
535 |         if (result && result.isConnectionError) {
536 |           hasConnectionError = true;
537 |           console.error("[execute-typescript] Database connection error detected in script execution");
538 |         }
539 | 
540 |         // Add execution stats to the output
541 |         const executionStats = result && result.stats ? result.stats : { 
542 |           executionTime,
543 |           errorCount: result && result.error ? 1 : 0 
544 |         };
545 | 
546 |         // Handle potential connection errors
547 |         if (hasConnectionError) {
548 |           // Add a note about connection issues
549 |           capturedLogs.push("NOTE: A database connection error occurred. The server might need to be restarted if this persists.");
550 |         }
551 | 
552 |         // If result is too large, truncate it
553 |         let resultStr;
554 |         try {
555 |           resultStr = JSON.stringify(result && result.result ? result.result : result, null, 2);
556 |           if (resultStr.length > 50000) {
557 |             resultStr = resultStr.substring(0, 50000) + `... [truncated, ${resultStr.length - 50000} more characters]`;
558 |           }
559 |         } catch (stringifyError) {
560 |           resultStr = `[Unserializable result of type ${typeof result}]`;
561 |         }
562 | 
563 |         // Return the result, captured logs, and execution stats
564 |         return {
565 |           content: [
566 |             {
567 |               type: "text",
568 |               text: `Execution result:\n${resultStr}`,
569 |             },
570 |             {
571 |               type: "text",
572 |               text: capturedLogs.length > 0 ? `Console output:\n${capturedLogs.join('\n')}` : "No console output.",
573 |             },
574 |             {
575 |               type: "text",
576 |               text: `Execution stats: Time ${executionStats.executionTime}ms | Errors ${executionStats.errorCount}`,
577 |             },
578 |           ],
579 |         };
580 |       } catch (vmError: any) {
581 |         console.error("[execute-typescript] VM execution error:", vmError);
582 |         
583 |         // Handle specific types of VM errors
584 |         let errorMessage = vmError?.message || "Unknown VM error";
585 |         let errorType = "VM Error";
586 |         
587 |         // Check for specific error types
588 |         if (errorMessage.includes("timed out")) {
589 |           errorType = "Timeout Error";
590 |           errorMessage = `Script execution timed out after ${timeoutMs}ms. Please simplify your code or increase the timeout limit.`;
591 |         } else if (errorMessage.includes("memory")) {
592 |           errorType = "Memory Error";
593 |           errorMessage = `Script exceeded memory limits. Please reduce memory usage in your code.`;
594 |         } else if (errorMessage.includes("Unexpected token")) {
595 |           errorType = "Syntax Error";
596 |         }
597 |         
598 |         // Include any captured logs to help with debugging
599 |         return {
600 |           content: [
601 |             {
602 |               type: "text",
603 |               text: `${errorType}: ${errorMessage}`,
604 |             },
605 |             {
606 |               type: "text",
607 |               text: vmError?.stack || "",
608 |             },
609 |             {
610 |               type: "text",
611 |               text: capturedLogs.length > 0 ? `Console output (before error):\n${capturedLogs.join('\n')}` : "No console output before error.",
612 |             },
613 |           ],
614 |         };
615 |       }
616 |     } catch (error: any) {
617 |       console.error("[execute-typescript] Top level error:", error);
618 |       
619 |       // Categorize the error
620 |       let errorCategory = "Uncategorized Error";
621 |       const errorMsg = error?.message || "Unknown error";
622 |       
623 |       if (errorMsg.includes("out of memory") || errorMsg.includes("heap") || errorMsg.includes("memory")) {
624 |         errorCategory = "Out of Memory Error";
625 |       } else if (errorMsg.includes("timeout") || errorMsg.includes("timed out")) {
626 |         errorCategory = "Timeout Error";
627 |       } else if (errorMsg.includes("connection") || errorMsg.includes("network") || errorMsg.includes("socket")) {
628 |         errorCategory = "Connection Error";
629 |       } else if (errorMsg.includes("syntax") || errorMsg.includes("unexpected token")) {
630 |         errorCategory = "Syntax Error";
631 |       }
632 |       
633 |       return {
634 |         content: [
635 |           {
636 |             type: "text",
637 |             text: `${errorCategory} executing TypeScript: ${errorMsg}`,
638 |           },
639 |           {
640 |             type: "text",
641 |             text: error?.stack || "",
642 |           },
643 |           {
644 |             type: "text",
645 |             text: "Consider simplifying your code, checking for syntax errors, or trying again later.",
646 |           },
647 |         ],
648 |       };
649 |     }
650 |   },
651 | );
652 | 
653 | // Tool to search the Gel documentation file
654 | server.tool(
655 |   "search_gel_docs",
656 |   "Search the Gel documentation for specific terms or patterns. Returns relevant sections with context. Use this tool to find information about EdgeQL syntax, features, or examples in the documentation.",
657 |   {
658 |     search_term: z.string().describe("The term or pattern to search for in the documentation"),
659 |     context_lines: z.number().optional().describe("Number of lines of context to show before and after matches (default: 5)"),
660 |     match_all_terms: z.boolean().optional().describe("If true, search must match all terms in a multi-word query (default: false)"),
661 |   },
662 |   async (args, extra) => {
663 |     try {
664 |       const { execSync } = require('child_process');
665 |       const fs = require('fs');
666 |       const path = require('path');
667 |       
668 |       // Try multiple possible locations for the documentation file
669 |       const possiblePaths = [
670 |         path.join(__dirname, '..', 'gel_llm.txt'),         // Relative to script dir
671 |         path.join(process.cwd(), 'gel_llm.txt'),           // Relative to current working dir
672 |         './gel_llm.txt'                                    // Directly in workspace root
673 |       ];
674 |       
675 |       let docFilePath = null;
676 |       for (const potentialPath of possiblePaths) {
677 |         if (fs.existsSync(potentialPath)) {
678 |           docFilePath = potentialPath;
679 |           console.error(`[search-gel-docs] Found documentation at: ${docFilePath}`);
680 |           break;
681 |         }
682 |       }
683 |       
684 |       // Validate that the file exists
685 |       if (!docFilePath) {
686 |         return {
687 |           content: [
688 |             {
689 |               type: "text" as const,
690 |               text: "Documentation file not found. Checked paths: " + possiblePaths.join(', '),
691 |             },
692 |           ],
693 |         };
694 |       }
695 | 
696 |       // Set default values
697 |       const contextLines = args.context_lines || 20;
698 |       const matchAllTerms = args.match_all_terms || false;
699 |       const searchTerm = args.search_term;
700 |       
701 |       console.error("[search-gel-docs] Starting search for:", searchTerm);
702 |       
703 |       // Build the grep command
704 |       let grepCommand;
705 |       
706 |       if (matchAllTerms && searchTerm.includes(' ')) {
707 |         // For multi-term search with matchAllTerms, we need to perform multiple grep operations
708 |         const terms = searchTerm.split(/\s+/).filter(t => t.trim().length > 0);
709 |         grepCommand = `grep -n -i "${terms[0]}" "${docFilePath}"`;
710 |         
711 |         for (let i = 1; i < terms.length; i++) {
712 |           grepCommand += ` | grep -i "${terms[i]}"`;
713 |         }
714 |       } else {
715 |         // For single term search, we can use grep with line numbers
716 |         grepCommand = `grep -n -i "${searchTerm}" "${docFilePath}"`;
717 |       }
718 |       
719 |       try {
720 |         // Execute the command
721 |         const grepOutput = execSync(grepCommand, { encoding: 'utf8' });
722 |         const matches = grepOutput.trim().split('\n').filter(Boolean);
723 |         
724 |         console.error(`[search-gel-docs] Found ${matches.length} matching lines`);
725 |         
726 |         if (matches.length === 0) {
727 |           return {
728 |             content: [{ type: "text" as const, text: "No matches found." }]
729 |           };
730 |         }
731 |         
732 |         // Parse match lines into line numbers and content
733 |         const parsedMatches = matches.map((match: string) => {
734 |           const colonIndex = match.indexOf(':');
735 |           if (colonIndex !== -1) {
736 |             return {
737 |               lineNumber: parseInt(match.substring(0, colonIndex), 10),
738 |               lineContent: match.substring(colonIndex + 1)
739 |             };
740 |           }
741 |           return null;
742 |         }).filter(Boolean);
743 |         
744 |         // Read the file for context
745 |         const fileLines = fs.readFileSync(docFilePath, 'utf8').split('\n');
746 |         
747 |         // Generate output with context
748 |         let output = `Found ${parsedMatches.length} matches for "${searchTerm}":\n\n`;
749 |         
750 |         // Track sections to group results
751 |         const sectionMatches: Record<string, {
752 |           matches: Array<{lineNumber: number, lineContent: string}>;
753 |           context?: string[];
754 |         }> = {};
755 |         
756 |         // Identify sections
757 |         const sectionBoundaries: Array<{lineNumber: number, title: string}> = [];
758 |         
759 |         // Find file boundaries and major section headers
760 |         for (let i = 0; i < fileLines.length; i++) {
761 |           const line = fileLines[i].trim();
762 |           
763 |           // Check for file declarations
764 |           if (line.startsWith('.. File:')) {
765 |             sectionBoundaries.push({
766 |               lineNumber: i,
767 |               title: line.substring('.. File:'.length).trim()
768 |             });
769 |             continue;
770 |           }
771 |           
772 |           // Check for section headers (underlined titles)
773 |           if (i > 0 && i < fileLines.length - 1) {
774 |             const prevLine = fileLines[i-1].trim();
775 |             
776 |             // Section markers are lines of repeated characters
777 |             if (line.length > 0 && /^[=\-~"'`.+_^*]+$/.test(line) && prevLine.length > 0) {
778 |               sectionBoundaries.push({
779 |                 lineNumber: i-1,
780 |                 title: prevLine
781 |               });
782 |             }
783 |           }
784 |         }
785 |         
786 |         // Sort section boundaries by line number
787 |         sectionBoundaries.sort((a, b) => a.lineNumber - b.lineNumber);
788 |         
789 |         // Group matches by section
790 |         for (const match of parsedMatches) {
791 |           // Find the section this match belongs to
792 |           let sectionTitle = "Unknown";
793 |           
794 |           for (let i = sectionBoundaries.length - 1; i >= 0; i--) {
795 |             if (match.lineNumber >= sectionBoundaries[i].lineNumber) {
796 |               sectionTitle = sectionBoundaries[i].title;
797 |               break;
798 |             }
799 |           }
800 |           
801 |           if (!sectionMatches[sectionTitle]) {
802 |             sectionMatches[sectionTitle] = { matches: [] };
803 |           }
804 |           
805 |           sectionMatches[sectionTitle].matches.push(match);
806 |         }
807 |         
808 |         // Format output by section
809 |         for (const [sectionTitle, { matches }] of Object.entries(sectionMatches)) {
810 |           output += `## ${sectionTitle}\n\n`;
811 |           
812 |           // Get context for the first match in each section
813 |           const firstMatch = matches[0];
814 |           const startLine = Math.max(0, firstMatch.lineNumber - contextLines);
815 |           const endLine = Math.min(fileLines.length - 1, firstMatch.lineNumber + contextLines);
816 |           
817 |           // Show context with line numbers, highlighting the matching line
818 |           for (let i = startLine; i <= endLine; i++) {
819 |             const isMatchLine = matches.some(m => m.lineNumber === i);
820 |             const prefix = isMatchLine ? '> ' : '  ';
821 |             output += `${prefix}${i+1}: ${fileLines[i]}\n`;
822 |           }
823 |           
824 |           if (matches.length > 1) {
825 |             output += `\n... and ${matches.length - 1} more match(es) in this section.\n`;
826 |           }
827 |           
828 |           output += '\n---\n\n';
829 |         }
830 |         
831 |         return {
832 |           content: [{ type: "text" as const, text: output }]
833 |         };
834 |         
835 |       } catch (err) {
836 |         // If grep exits with code 1, it means no matches were found (not an error)
837 |         const error = err as {status?: number; stderr?: string};
838 |         if (error.status === 1 && !error.stderr) {
839 |           return {
840 |             content: [{ type: "text" as const, text: "No matches found." }]
841 |           };
842 |         }
843 |         
844 |         throw err;
845 |       }
846 |     } catch (error: unknown) {
847 |       console.error("Error searching documentation:", error);
848 |       return {
849 |         content: [
850 |           {
851 |             type: "text" as const,
852 |             text: `Error searching documentation: ${error instanceof Error ? error.message : String(error)}`,
853 |           },
854 |         ],
855 |       };
856 |     }
857 |   }
858 | );
859 | 
860 | // Start the server
861 | async function main() {
862 |   const transport = new StdioServerTransport();
863 |   await server.connect(transport);
864 | 
865 |   const connected = await initGelClient();
866 |   console.error("Gel MCP Server running on stdio");
867 | }
868 | 
869 | main().catch((error) => {
870 |   console.error("Fatal error in main():", error);
871 |   process.exit(1);
872 | });
873 | 
```