@letoribo/mcp-graphql-enhanced 3.2.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,14 +4,33 @@ An **enhanced MCP (Model Context Protocol) server for GraphQL** that fixes real-
4
4
  > Drop-in replacement for `mcp-graphql` — with dynamic headers, robust variables parsing, and zero breaking changes.
5
5
 
6
6
  ## ✨ Key Enhancements
7
+ * ✅ **Built-in GraphiQL IDE** — Visual playground at http://localhost:MCP_PORT/ (or /graphiql) with pre-configured headers.
7
8
  * ✅ **Dual Transport** — Supports both **STDIO** (for local CLI/client tools) and **HTTP/JSON-RPC** (for external/browser clients).
8
9
  * ✅ **Dynamic headers** — pass `Authorization`, `X-API-Key`, etc., via tool arguments (no config restarts)
9
10
  * ✅ **Robust variables parsing** — fixes `“Query variables must be a null or an object”` error
10
11
  * ✅ **Filtered introspection** — request only specific types (e.g., `typeNames: ["Query", "User"]`) to reduce LLM context noise
11
12
  * ✅ **Full MCP compatibility** — works with **Claude Desktop**, **Cursor**, **Glama**
12
13
  * ✅ **Secure by default** — mutations disabled unless explicitly enabled
14
+ * ✅ **Dynamic Schema Evolution** — Smart diagnostics and gap analysis for servers that regenerate GraphQL types on-the-fly (like Neo4j).
15
+ * ✅ **Deep Observability** — Automatic Cypher extraction and cleaning from GraphQL extensions.
16
+
17
+ ## 🔍 Advanced Observability & Cypher
18
+ The bridge provides deep insights into how the LLM interacts with your graph database.
19
+
20
+ ### 🕸️ Automated Cypher Extraction
21
+ For GraphQL server implementations that return query execution plans (like `@neo4j/graphql`), the bridge automatically:
22
+ 1. **Detects** `extensions.cypher` in the response.
23
+ 2. **Sanitizes** the output by stripping internal headers (like `CYPHER 5` or empty `PARAMS`).
24
+ 3. **Injects** a clean Cypher block directly into the tool's output for the AI to analyze.
25
+
26
+ > **Note:** This feature requires your GraphQL server to be configured to include debug information in the response extensions.
13
27
 
14
28
  ---
29
+ ## 🎨 Visual Command Center (GraphiQL)
30
+ Unlike standard MCP servers, this one provides a visual interface for humans. When running with `ENABLE_HTTP=true`, you can open a full-featured **GraphiQL IDE** in your browser.
31
+
32
+ * **Endpoint:** `http://localhost:6274/` (or `/graphiql`)
33
+ * **Header Sync:** Any headers set in your environment (like GitHub tokens) are automatically injected into the GraphiQL "Headers" tab for immediate testing.
15
34
 
16
35
  ## 💻 HTTP / Dual Transport
17
36
 
@@ -21,6 +40,7 @@ This allows external systems, web applications, and direct `curl` commands to ac
21
40
 
22
41
  | **Endpoint** | **Method** | **Description** |
23
42
  | :--- | :--- | :--- |
43
+ | `/graphiql` | `GET` | Human Interface: The visual GraphQL IDE. |
24
44
  | `/mcp` | `POST` | The main JSON-RPC 2.0 endpoint for tool execution. |
25
45
  | `/health` | `GET` | Simple health check, returns `{ status: 'ok' }`. |
26
46
 
@@ -46,21 +66,21 @@ curl http://localhost:6274/health
46
66
  # Example: Test the query tool via JSON-RPC (using port 6275 if 6274 was busy)
47
67
  curl -X POST http://localhost:6275/mcp -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"query-graphql","params":{"query":"query { __typename }"},"id":1}'
48
68
 
49
- ## 🔍 Filtered Introspection (New!)
69
+ ## 🔍 Filtered Introspection
50
70
  Avoid 50k-line schema dumps. Ask for only what you need:
51
- ```@introspect-schema typeNames ["Query", "User"]```
71
+ `@introspect-schema typeNames ["Query", "User"]`
52
72
  ## 🔍 Debug & Inspect
53
73
  Use the official MCP Inspector to test your server live:
54
74
  ```bash
55
75
  npx @modelcontextprotocol/inspector \
56
76
  -e ENDPOINT=https://api.example.com/graphql \
57
- npx @letoribo/mcp-graphql-enhanced --debug
77
+ npx @letoribo/mcp-graphql-enhanced
58
78
  ```
59
79
  ### Environment Variables (Breaking change in 1.0.0)
60
80
  > **Note:** As of version 1.0.0, command line arguments have been replaced with environment variables.
61
81
 
62
82
  | Environment Variable | Description | Default |
63
- |----------|-------------|---------|
83
+ | :--- | :--- | :--- |
64
84
  | `ENDPOINT` | GraphQL endpoint URL | `https://mcp-neo4j-discord.vercel.app/api/graphiql` |
65
85
  | `HEADERS` | JSON string containing headers for requests | `{}` |
66
86
  | `ALLOW_MUTATIONS` | Enable mutation operations (disabled by default) | `false` |
@@ -93,6 +113,13 @@ npx @letoribo/mcp-graphql-enhanced
93
113
  MCP_PORT=8080 npx @letoribo/mcp-graphql-enhanced
94
114
  # Disable HTTP transport (fastest, recommended for Claude Desktop)
95
115
  ENABLE_HTTP=false npx @letoribo/mcp-graphql-enhanced
116
+ # Test the surgical precision and the IDE immediately:
117
+ ENDPOINT=https://api.github.com/graphql \
118
+ HEADERS='{"Authorization":"Bearer YOUR_GITHUB_TOKEN"}' \
119
+ ENABLE_HTTP=true \
120
+ npx @letoribo/mcp-graphql-enhanced
121
+
122
+ # Then visit http://localhost:6274/graphiql
96
123
  ```
97
124
  ### 🖥️ Claude Desktop Configuration Examples
98
125
  You can connect Claude Desktop to your GraphQL API using either the npx package (recommended for simplicity) or the Docker image (ideal for reproducibility and isolation).
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Renders the GraphiQL IDE template with visible headers
3
+ * @param endpoint The GraphQL API endpoint to connect to
4
+ * @param headers Optional default headers to inject into the editor
5
+ */
6
+ export declare function renderGraphiQL(endpoint: string, headers?: object): string;
7
+ //# sourceMappingURL=graphiql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphiql.d.ts","sourceRoot":"","sources":["../../src/helpers/graphiql.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW,GAAG,MAAM,CAoE7E"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderGraphiQL = renderGraphiQL;
4
+ /**
5
+ * Renders the GraphiQL IDE template with visible headers
6
+ * @param endpoint The GraphQL API endpoint to connect to
7
+ * @param headers Optional default headers to inject into the editor
8
+ */
9
+ function renderGraphiQL(endpoint, headers = {}) {
10
+ // Stringify headers for the injection into the script
11
+ const stringifiedHeaders = JSON.stringify(headers, null, 2);
12
+ return `
13
+ <!DOCTYPE html>
14
+ <html lang="en">
15
+ <head>
16
+ <meta charset="utf-8" />
17
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
18
+ <title>GraphiQL Explorer | Surgical MCP</title>
19
+ <link href="https://unpkg.com/graphiql@3.8.2/graphiql.min.css" rel="stylesheet" />
20
+ <style>
21
+ body {
22
+ margin: 0;
23
+ height: 100vh;
24
+ width: 100vw;
25
+ overflow: hidden;
26
+ background: #0b1016;
27
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
28
+ }
29
+ #graphiql { height: 100vh; }
30
+ .loading-screen {
31
+ color: white;
32
+ display: flex;
33
+ justify-content: center;
34
+ align-items: center;
35
+ height: 100%;
36
+ font-size: 1.2rem;
37
+ }
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <div id="graphiql">
42
+ <div class="loading-screen">Initializing Surgical GraphiQL...</div>
43
+ </div>
44
+
45
+ <script src="https://unpkg.com/react@18.2.0/umd/react.production.min.js" crossorigin referrerpolicy="no-referrer"></script>
46
+ <script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js" crossorigin referrerpolicy="no-referrer"></script>
47
+ <script src="https://unpkg.com/graphiql@3.8.2/graphiql.min.js" crossorigin referrerpolicy="no-referrer"></script>
48
+
49
+ <script>
50
+ window.addEventListener('load', function() {
51
+ if (typeof GraphiQL !== 'undefined') {
52
+ const fetcher = GraphiQL.createFetcher({
53
+ url: '${endpoint}'
54
+ });
55
+
56
+ const root = ReactDOM.createRoot(document.getElementById('graphiql'));
57
+ root.render(
58
+ React.createElement(GraphiQL, {
59
+ fetcher: fetcher,
60
+ headerEditorEnabled: true,
61
+ shouldPersistHeaders: true,
62
+ // This makes the headers visible in the UI tab
63
+ defaultHeaders: \`${stringifiedHeaders}\`,
64
+ theme: 'dark',
65
+ defaultEditorToolsVisibility: true
66
+ })
67
+ );
68
+ } else {
69
+ document.getElementById('graphiql').innerHTML =
70
+ '<div class="loading-screen" style="color: #ff4d4d">Error: GraphiQL SDK failed to load.</div>';
71
+ }
72
+ });
73
+ </script>
74
+ </body>
75
+ </html>`;
76
+ }
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
9
9
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
10
10
  const language_1 = require("graphql/language");
11
11
  const zod_1 = __importDefault(require("zod"));
12
+ const graphiql_js_1 = require("./helpers/graphiql.js");
13
+ const graphql_1 = require("graphql");
12
14
  // Helper imports
13
15
  const deprecation_js_1 = require("./helpers/deprecation.js");
14
16
  const introspection_js_1 = require("./helpers/introspection.js");
@@ -61,48 +63,147 @@ const server = new mcp_js_1.McpServer({
61
63
  let cachedSDL = null;
62
64
  let cachedSchemaObject = null;
63
65
  let schemaLoadError = null;
64
- async function getSchema() {
65
- if (cachedSDL)
66
+ let isUpdating = false;
67
+ let updatePromise = null;
68
+ let lastKnownTypeCount = 0;
69
+ let expectEmptySchema = false; // Intent flag for intentional purges
70
+ /**
71
+ * Smart Hybrid Schema Fetcher (Zero-Error Version)
72
+ * @param force If true, blocks and waits for the new schema evolution.
73
+ * If false, returns cache immediately and updates in background.
74
+ */
75
+ async function getSchema(force = false, requestedTypes) {
76
+ // 1. Hook into existing update if in progress
77
+ if (isUpdating && updatePromise) {
78
+ if (force || !cachedSDL)
79
+ return await updatePromise;
66
80
  return cachedSDL;
81
+ }
82
+ // 2. Return cache if valid and not forcing
83
+ if (cachedSDL && !force) {
84
+ // Validation check: If user wants specific types but they aren't in the cache
85
+ if (requestedTypes && cachedSchemaObject) {
86
+ const typeMap = cachedSchemaObject.getTypeMap();
87
+ const missing = requestedTypes.filter(t => !typeMap[t]);
88
+ if (missing.length > 0) {
89
+ // Force a refresh if requested types are missing from current cache
90
+ return await (updatePromise = performUpdate(true));
91
+ }
92
+ }
93
+ return cachedSDL;
94
+ }
95
+ if (force)
96
+ schemaLoadError = null;
67
97
  if (schemaLoadError)
68
98
  throw schemaLoadError;
99
+ // 3. Trigger update
100
+ updatePromise = performUpdate(force);
101
+ try {
102
+ if (force || !cachedSDL) {
103
+ return await updatePromise;
104
+ }
105
+ else {
106
+ updatePromise.catch(err => console.error("[SCHEMA] Background update failed:", err));
107
+ return cachedSDL;
108
+ }
109
+ }
110
+ finally {
111
+ // Promise reference is cleared inside performUpdate's 'finally' block
112
+ }
113
+ }
114
+ /**
115
+ * Internal logic for schema introspection and building.
116
+ * This version uses universal business-type tracking and provides
117
+ * detailed diagnostic reports instead of generic error messages.
118
+ */
119
+ async function performUpdate(force) {
120
+ isUpdating = true;
121
+ const startTime = Date.now();
69
122
  try {
70
- const { buildClientSchema, getIntrospectionQuery, printSchema, buildASTSchema, parse: gqlParse } = require("graphql");
71
- let sdl;
123
+ const { buildClientSchema, getIntrospectionQuery, printSchema, buildASTSchema, parse: gqlParse, isObjectType } = require("graphql");
124
+ let tempSchema;
125
+ // --- FETCHING LOGIC (Unified Source) ---
72
126
  if (env.SCHEMA) {
73
- // Check if it's a URL or local path
127
+ let sdl;
74
128
  if (env.SCHEMA.startsWith("http")) {
129
+ // Remote SDL File: Fetch via HTTP
75
130
  const response = await fetch(env.SCHEMA);
131
+ if (!response.ok)
132
+ throw new Error(`Remote_SDL_Fetch_Failed: ${response.statusText}`);
76
133
  sdl = await response.text();
77
134
  }
78
135
  else {
136
+ // Local SDL File: Use your custom helper (readFile inside)
79
137
  sdl = await (0, introspection_js_1.introspectLocalSchema)(env.SCHEMA);
80
138
  }
81
- cachedSchemaObject = buildASTSchema(gqlParse(sdl));
82
- cachedSDL = sdl;
139
+ // Direct path: Convert raw SDL string to GraphQLSchema object
140
+ tempSchema = buildASTSchema(gqlParse(sdl));
83
141
  }
84
142
  else {
143
+ // Standard Path: Execute Introspection Query against live ENDPOINT
85
144
  const response = await fetch(env.ENDPOINT, {
86
145
  method: "POST",
87
146
  headers: { "Content-Type": "application/json", ...env.HEADERS },
88
147
  body: JSON.stringify({ query: getIntrospectionQuery() }),
89
148
  });
90
149
  if (!response.ok)
91
- throw new Error(`Fetch failed: ${response.statusText}`);
150
+ throw new Error(`HTTP_${response.status}: ${response.statusText}`);
92
151
  const result = await response.json();
93
- cachedSchemaObject = buildClientSchema(result.data);
94
- cachedSDL = printSchema(cachedSchemaObject);
152
+ if (!result.data)
153
+ throw new Error("Invalid GraphQL response: Missing 'data' field.");
154
+ // Build Schema object from introspection JSON
155
+ tempSchema = buildClientSchema(result.data);
156
+ }
157
+ // --- UNIFIED STRUCTURAL ANALYSIS (For AI Report) ---
158
+ const typeMap = tempSchema.getTypeMap();
159
+ // Filter "Business Labels" (Nodes) while ignoring internal scalars/system types
160
+ const businessTypes = Object.keys(typeMap).filter(typeName => {
161
+ const type = typeMap[typeName];
162
+ return (!typeName.startsWith('__') &&
163
+ !['Query', 'Mutation', 'Subscription'].includes(typeName) &&
164
+ !['String', 'Int', 'Float', 'Boolean', 'ID', 'BigInt', 'DateTime'].includes(typeName) &&
165
+ isObjectType(type));
166
+ });
167
+ // Maintain state for "Gap Analysis"
168
+ lastKnownTypeCount = businessTypes.length;
169
+ const currentSDL = printSchema(tempSchema);
170
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
171
+ // --- CACHE & NOTIFICATION ---
172
+ if (currentSDL !== cachedSDL) {
173
+ cachedSDL = currentSDL;
174
+ cachedSchemaObject = tempSchema; // Store the live Schema object for tool execution
175
+ return [
176
+ `✨ SCHEMA EVOLVED (${duration}s)`,
177
+ `📊 Source: ${env.SCHEMA ? 'Local/Remote SDL' : 'Live Endpoint'}`,
178
+ `🧬 Labels: ${businessTypes.join(', ') || 'None'}`,
179
+ `---`,
180
+ `The bridge has updated the graph model. New types are now queryable.`
181
+ ].join('\n');
182
+ }
183
+ else {
184
+ return `✅ Status: Schema stable (${lastKnownTypeCount} labels).`;
95
185
  }
96
- console.error(`[SCHEMA] Successfully loaded and cached GraphQL schema`);
97
- return cachedSDL;
98
186
  }
99
187
  catch (error) {
100
- schemaLoadError = error instanceof Error ? error : new Error(String(error));
101
- throw schemaLoadError;
188
+ // Informative error report to prevent "AI confusion"
189
+ return [
190
+ `❌ SCHEMA SYNC FAILED`,
191
+ `🔍 Reason: ${error.message}`,
192
+ `🛠️ Action: Verify your ${env.SCHEMA ? 'file path' : 'endpoint connection'} and retry.`
193
+ ].join('\n');
194
+ }
195
+ finally {
196
+ isUpdating = false;
197
+ updatePromise = null;
102
198
  }
103
199
  }
104
200
  // --- TOOL HANDLERS ---
105
201
  const toolHandlers = new Map();
202
+ /** * executionLogs stores the last 5 GraphQL operations.
203
+ * This allows the AI to "inspect" its own generated queries and the raw data
204
+ * for debugging or bridging to 3D visualization tools.
205
+ */
206
+ let executionLogs = [];
106
207
  // Tool: query-graphql
107
208
  const queryGraphqlHandler = async ({ query, variables, headers }) => {
108
209
  try {
@@ -120,9 +221,32 @@ const queryGraphqlHandler = async ({ query, variables, headers }) => {
120
221
  headers: allHeaders,
121
222
  body: JSON.stringify({ query, variables: parsedVariables }),
122
223
  });
123
- const data = await response.json();
224
+ const data = (await response.json());
225
+ // 1. Extract and sanitize Cypher if present in extensions
226
+ const rawCypher = data.extensions?.cypher || [];
227
+ const cleanCypher = rawCypher.map((c) => c.replace(/^CYPHER: /, '')
228
+ .replace(/^CYPHER 5\n/, '')
229
+ .replace(/\nPARAMS: \{\}$/, ''));
230
+ // 2. Update execution history for internal server state
231
+ executionLogs.push({
232
+ query,
233
+ variables: parsedVariables,
234
+ response: data,
235
+ timestamp: new Date().toISOString()
236
+ });
237
+ if (executionLogs.length > 5)
238
+ executionLogs.shift();
239
+ // 3. Prepare optimized response for Claude
240
+ const responseForClaude = {
241
+ result: data.data,
242
+ // Only add the cypher field if there's actual data to show
243
+ ...(cleanCypher.length > 0 ? { cypher: cleanCypher } : {})
244
+ };
124
245
  return {
125
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
246
+ content: [{
247
+ type: "text",
248
+ text: JSON.stringify(responseForClaude, null, 2)
249
+ }]
126
250
  };
127
251
  }
128
252
  catch (error) {
@@ -137,14 +261,56 @@ server.tool("query-graphql", "Execute a GraphQL query against the endpoint", {
137
261
  }, queryGraphqlHandler);
138
262
  // Tool: introspect-schema
139
263
  const introspectHandler = async ({ typeNames }) => {
140
- await getSchema();
264
+ // 1. Always pull the latest schema state
265
+ // The report from performUpdate is captured to show evolution details
266
+ const evolutionSummary = await getSchema(true);
267
+ const schema = cachedSchemaObject;
268
+ const typeMap = schema.getTypeMap();
269
+ // 2. Generate a structural fingerprint
270
+ const typeKeys = Object.keys(typeMap).filter(t => !t.startsWith('__'));
271
+ const schemaVersion = `v${typeKeys.length}.${Math.floor(Date.now() / 10000) % 1000}`;
272
+ // --- GAP ANALYSIS: Check if requested types are actually in the map ---
273
+ if (typeNames && typeNames.length > 0) {
274
+ const missing = typeNames.filter(name => !typeMap[name]);
275
+ if (missing.length > 0) {
276
+ return {
277
+ content: [{
278
+ type: "text",
279
+ text: `❌ PARTIAL RESULTS [ID: ${schemaVersion}]\n\n` +
280
+ `MISSING TYPES: ${missing.join(", ")}\n` +
281
+ `REASON: The database has been updated, but the GraphQL Schema is still regenerating these specific types.\n` +
282
+ `ACTION: Please wait 3 seconds and retry 'introspect-schema' to see the full graph.\n\n` +
283
+ `CURRENTLY AVAILABLE: ${typeKeys.filter(t => !['Query', 'Mutation', 'Report'].includes(t)).join(", ")}`
284
+ }]
285
+ };
286
+ }
287
+ }
141
288
  if (!typeNames || typeNames.length === 0) {
142
- const allTypeNames = Object.keys(cachedSchemaObject.getTypeMap()).filter(t => !t.startsWith('__'));
289
+ const queryType = schema.getQueryType();
290
+ const discoveredEntities = new Set();
291
+ if (queryType) {
292
+ const queryFields = queryType.getFields();
293
+ Object.values(queryFields).forEach((field) => {
294
+ const namedType = (0, graphql_1.getNamedType)(field.type);
295
+ if ((0, graphql_1.isObjectType)(namedType) && !namedType.name.startsWith('__')) {
296
+ discoveredEntities.add(namedType.name);
297
+ }
298
+ });
299
+ }
300
+ const coreEntities = Array.from(discoveredEntities).sort();
143
301
  return {
144
- content: [{ type: "text", text: `Schema is large. Available types: ${allTypeNames.join(", ")}` }]
302
+ content: [{
303
+ type: "text",
304
+ text: `${evolutionSummary}\n\n` + // Include the report from performUpdate
305
+ `GraphQL Schema Manifest [ID: ${schemaVersion}]\n\n` +
306
+ `ENTRY_POINT_ENTITIES: ${coreEntities.join(", ") || "None"}\n` +
307
+ `TOTAL_SCHEMA_TYPES: ${typeKeys.length}\n\n` +
308
+ `ALL_AVAILABLE_TYPES: ${typeKeys.join(", ")}`
309
+ }]
145
310
  };
146
311
  }
147
- const filtered = (0, introspection_js_1.introspectSpecificTypes)(cachedSchemaObject, typeNames);
312
+ // 3. Detailed introspection for specific types
313
+ const filtered = (0, introspection_js_1.introspectSpecificTypes)(schema, typeNames);
148
314
  return {
149
315
  content: [{ type: "text", text: JSON.stringify(filtered, null, 2) }]
150
316
  };
@@ -164,6 +330,12 @@ async function handleHttpRequest(req, res) {
164
330
  return;
165
331
  }
166
332
  const url = new URL(req.url || '', `http://${req.headers.host}`);
333
+ // NEW FEATURE: Web GUI for Humans
334
+ if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/graphiql')) {
335
+ res.writeHead(200, { 'Content-Type': 'text/html' });
336
+ // Pass env.HEADERS to the explorer
337
+ return res.end((0, graphiql_js_1.renderGraphiQL)(env.ENDPOINT, env.HEADERS));
338
+ }
167
339
  if (url.pathname === '/mcp' && req.method === 'POST') {
168
340
  let body = '';
169
341
  req.on('data', chunk => { body += chunk; });
@@ -201,6 +373,8 @@ async function main() {
201
373
  const serverHttp = node_http_1.default.createServer(handleHttpRequest);
202
374
  serverHttp.listen(env.MCP_PORT, () => {
203
375
  console.error(`[HTTP] Server started on http://localhost:${env.MCP_PORT}`);
376
+ console.error(`🎨 GraphiQL IDE: http://localhost:${env.MCP_PORT}/graphiql`);
377
+ console.error(`🤖 MCP Endpoint: http://localhost:${env.MCP_PORT}/mcp\n`);
204
378
  });
205
379
  }
206
380
  const transport = new stdio_js_1.StdioServerTransport();
package/package.json CHANGED
@@ -50,5 +50,5 @@
50
50
  "tsx": "^4.21.0",
51
51
  "typescript": "5.8.3"
52
52
  },
53
- "version": "3.2.1"
53
+ "version": "3.4.0"
54
54
  }