@letoribo/mcp-graphql-enhanced 2.3.0 → 2.3.1

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
@@ -42,7 +42,7 @@ curl http://localhost:6274/health
42
42
 
43
43
  # Example: Test the query tool via JSON-RPC (using port 6275 if 6274 was busy)
44
44
  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}'
45
-
45
+ ```
46
46
  ## 🔍 Filtered Introspection (New!)
47
47
  Avoid 50k-line schema dumps. Ask for only what you need:
48
48
  ```@introspect-schema typeNames ["Query", "User"]```
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
2
- export {};
1
+ declare const app: import("express-serve-static-core").Express;
2
+ export default app;
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA2UA,QAAA,MAAM,GAAG,6CAAY,CAAC;AA0CtB,eAAe,GAAG,CAAC"}
package/dist/index.js CHANGED
@@ -1,361 +1,349 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
3
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
37
  };
6
38
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const node_http_1 = __importDefault(require("node:http"));
8
- const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
9
- const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
10
- const { parse } = require("graphql/language");
11
- const z = require("zod").default;
12
- const { checkDeprecatedArguments } = require("./helpers/deprecation.js");
13
- const { introspectEndpoint, introspectLocalSchema, introspectSchemaFromUrl, introspectTypes, } = require("./helpers/introspection.js");
14
- const getVersion = () => {
15
- const pkg = require("../package.json");
16
- return pkg.version;
17
- };
18
- checkDeprecatedArguments();
19
- const EnvSchema = z.object({
20
- NAME: z.string().default("mcp-graphql-enhanced"),
21
- ENDPOINT: z.preprocess((val) => (typeof val === 'string' ? val.trim() : val), z.string().url("ENDPOINT must be a valid URL (e.g., https://example.com/graphql)")).default("https://mcp-neo4j-discord.vercel.app/api/graphiql"),
22
- ALLOW_MUTATIONS: z
23
- .enum(["true", "false"])
24
- .transform((value) => value === "true")
25
- .default("false"),
26
- HEADERS: z
27
- .string()
28
- .default("{}")
29
- .transform((val) => {
30
- try {
31
- return JSON.parse(val);
32
- }
33
- catch (e) {
34
- throw new Error("HEADERS must be a valid JSON string");
35
- }
36
- }),
37
- SCHEMA: z.string().optional(),
38
- MCP_PORT: z.preprocess((val) => (val ? parseInt(val) : 6274), z.number().int().min(1024).max(65535)).default(6274),
39
- });
40
- const env = EnvSchema.parse(process.env);
41
- const server = new McpServer({
42
- name: env.NAME,
43
- version: getVersion(),
44
- description: `GraphQL MCP server for ${env.ENDPOINT}`,
45
- });
46
- server.resource("graphql-schema", new URL(env.ENDPOINT).href, async (uri) => {
47
- try {
48
- let schema;
49
- if (env.SCHEMA) {
50
- if (env.SCHEMA.startsWith("http://") ||
51
- env.SCHEMA.startsWith("https://")) {
52
- schema = await introspectSchemaFromUrl(env.SCHEMA);
53
- }
54
- else {
55
- schema = await introspectLocalSchema(env.SCHEMA);
56
- }
57
- }
58
- else {
59
- schema = await introspectEndpoint(env.ENDPOINT, env.HEADERS);
60
- }
61
- return {
62
- contents: [
63
- {
64
- uri: uri.href,
65
- text: schema,
66
- },
67
- ],
68
- };
39
+ const express_1 = __importDefault(require("express"));
40
+ // CRITICAL FIX: Explicitly import the CJS file path and use HttpLink for Vercel/NodeNext environments.
41
+ // NOTE: We import it as 'any' to temporarily bypass the missing type declaration warning
42
+ // for '@apollo/client/core/core.cjs', but you must install the @types/express package.
43
+ const apolloModule = __importStar(require("@apollo/client/core/core.cjs"));
44
+ const graphql_1 = require("graphql");
45
+ // -----------------------------------------------------------------------------------
46
+ // 1. GraphQL Endpoint Configuration
47
+ const MCP_GRAPHQL_ENDPOINT = process.env.ENDPOINT ||
48
+ process.env.MCP_GRAPHQL_ENDPOINT ||
49
+ 'https://countries.trevorblades.com/';
50
+ // Initialize the client immediately
51
+ let client;
52
+ let coreExports; // Variable to hold the correctly resolved Apollo components
53
+ try {
54
+ // Dynamically determine the correct namespace for Apollo components.
55
+ // This is necessary because CJS module formats often wrap exports differently.
56
+ if (apolloModule.HttpLink) {
57
+ coreExports = apolloModule;
58
+ console.log('[Setup] Apollo exports resolved at the root level.');
69
59
  }
70
- catch (error) {
71
- throw new Error(`Failed to get GraphQL schema: ${error}`);
60
+ else if (apolloModule.default && apolloModule.default.HttpLink) {
61
+ coreExports = apolloModule.default;
62
+ console.log('[Setup] Apollo exports resolved nested under .default.');
72
63
  }
73
- });
74
- const toolHandlers = new Map();
75
- const introspectSchemaHandler = async ({ typeNames, descriptions = true, directives = true }) => {
64
+ else {
65
+ throw new Error('Could not resolve Apollo Client components (HttpLink, ApolloClient) from imported module.');
66
+ }
67
+ // Now use coreExports for all constructors and functions
68
+ const httpLink = new coreExports.HttpLink({
69
+ uri: MCP_GRAPHQL_ENDPOINT,
70
+ // Pass the global fetch function
71
+ fetch: fetch,
72
+ });
73
+ client = new coreExports.ApolloClient({
74
+ link: httpLink,
75
+ cache: new coreExports.InMemoryCache(),
76
+ });
77
+ console.log('[Setup] Apollo Client Initialized.');
78
+ }
79
+ catch (error) {
80
+ console.error('[Setup] CRITICAL RUNTIME ERROR: Failed to initialize Apollo Client:', error instanceof Error ? error.message : String(error));
81
+ }
82
+ // --- GraphQL Introspection Helpers (Using global fetch) ---
83
+ /**
84
+ * Executes a GraphQL introspection query against a remote endpoint.
85
+ */
86
+ const executeIntrospection = async (endpoint, headers, query) => {
87
+ let response;
76
88
  try {
77
- if (typeNames && typeNames.length > 0) {
78
- const filtered = await introspectTypes(env.ENDPOINT, env.HEADERS, typeNames);
79
- return { content: [{ type: "text", text: filtered }] };
80
- }
81
- else {
82
- let schema;
83
- if (env.SCHEMA) {
84
- if (env.SCHEMA.startsWith("http://") || env.SCHEMA.startsWith("https://")) {
85
- schema = await introspectSchemaFromUrl(env.SCHEMA);
86
- }
87
- else {
88
- schema = await introspectLocalSchema(env.SCHEMA);
89
- }
90
- }
91
- else {
92
- schema = await introspectEndpoint(env.ENDPOINT, env.HEADERS);
93
- }
94
- return { content: [{ type: "text", text: schema }] };
95
- }
89
+ // Use the global fetch function
90
+ response = await fetch(endpoint, {
91
+ method: "POST",
92
+ headers: {
93
+ "Content-Type": "application/json",
94
+ ...headers,
95
+ },
96
+ body: JSON.stringify({ query }),
97
+ });
96
98
  }
97
- catch (error) {
98
- return {
99
- isError: true,
100
- content: [{ type: "text", text: `Introspection failed: ${error}` }],
101
- };
99
+ catch (networkError) {
100
+ console.error('NETWORK ERROR during introspection:', networkError);
101
+ throw new Error(`Failed to connect to GraphQL endpoint at ${endpoint}. Network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`);
102
+ }
103
+ if (!response.ok) {
104
+ const responseText = await response.text();
105
+ throw new Error(`Introspection query failed with status ${response.status} (${response.statusText}). Response: ${responseText.substring(0, 200)}...`);
106
+ }
107
+ const json = await response.json();
108
+ if (json.errors) {
109
+ throw new Error(`GraphQL errors during introspection: ${JSON.stringify(json.errors)}`);
102
110
  }
111
+ return json.data;
103
112
  };
104
- toolHandlers.set("introspect-schema", introspectSchemaHandler);
105
- server.tool("introspect-schema", "Introspect the GraphQL schema. Optionally filter to specific types.", {
106
- typeNames: z.array(z.string()).optional().describe("e.g., [\"Query\", \"User\"]"),
107
- descriptions: z.boolean().optional().default(true),
108
- directives: z.boolean().optional().default(true),
109
- }, introspectSchemaHandler);
110
- const queryGraphqlHandler = async ({ query, variables, headers }) => {
111
- try {
112
- const parsedQuery = parse(query);
113
- const isMutation = parsedQuery.definitions.some((def) => def.kind === "OperationDefinition" && def.operation === "mutation");
114
- if (isMutation && !env.ALLOW_MUTATIONS) {
115
- return {
116
- isError: true,
117
- content: [
118
- {
119
- type: "text",
120
- text: "Mutations are not allowed unless you enable them in the configuration. Please use a query operation instead.",
121
- },
122
- ],
123
- };
113
+ /**
114
+ * Introspects the endpoint and returns the full schema string (SDL).
115
+ */
116
+ const introspectEndpoint = async (endpoint, headers) => {
117
+ const data = await executeIntrospection(endpoint, headers, (0, graphql_1.getIntrospectionQuery)());
118
+ const schema = (0, graphql_1.buildClientSchema)(data);
119
+ return (0, graphql_1.printSchema)(schema);
120
+ };
121
+ /**
122
+ * Introspects the endpoint and returns an annotated schema, optionally filtering root fields.
123
+ */
124
+ const introspectTypes = async (endpoint, headers, typeNames) => {
125
+ const data = await executeIntrospection(endpoint, headers, (0, graphql_1.getIntrospectionQuery)());
126
+ const schema = (0, graphql_1.buildClientSchema)(data);
127
+ // Fallback to full schema if no typeNames are provided or if filtering is not feasible
128
+ if (!typeNames || typeNames.length === 0) {
129
+ return `--- Full Schema Introspection ---\nEndpoint: ${endpoint}\n\n${(0, graphql_1.printSchema)(schema)}`;
130
+ }
131
+ const requestedTypeSet = new Set(typeNames.map(name => name.trim()));
132
+ let filteredSchema = `--- Annotated Schema Introspection ---\nEndpoint: ${endpoint}\nRequested Types: ${typeNames.join(', ')}\n\n`;
133
+ // 1. Prune the Query type fields to only include those that resolve to requested types
134
+ const queryType = schema.getQueryType();
135
+ if (queryType) {
136
+ const fields = queryType.getFields();
137
+ const keptQueryFields = [];
138
+ // Flag: If the user requested the "Query" type itself, include all root fields for utility.
139
+ const includeAllQueryFields = requestedTypeSet.has(queryType.name);
140
+ Object.keys(fields).forEach(fieldName => {
141
+ const field = fields[fieldName];
142
+ const returnType = (0, graphql_1.getNamedType)(field.type);
143
+ // Keep the field if:
144
+ // 1. Its return type is one of the requested types, OR
145
+ // 2. The user explicitly requested the "Query" type itself.
146
+ if (includeAllQueryFields || requestedTypeSet.has(returnType.name)) {
147
+ // Build a representation of the field including arguments and full type string
148
+ const args = field.args.map(arg => `${arg.name}: ${arg.type.toString()}`).join(', ');
149
+ keptQueryFields.push(` ${fieldName}${args ? `(${args})` : ''}: ${field.type.toString()}`);
150
+ }
151
+ });
152
+ // CRITICAL FIX: Ensure the Annotated Query block is output if fields were kept.
153
+ if (keptQueryFields.length > 0) {
154
+ filteredSchema += `type Query { \n${keptQueryFields.join('\n')}\n\n`;
124
155
  }
125
156
  }
126
- catch (error) {
157
+ // 2. Add the definition of every requested type (and their dependencies for robustness)
158
+ filteredSchema += `--- Full Type Definitions (Including Dependencies) ---\n\n`;
159
+ filteredSchema += (0, graphql_1.printSchema)(schema);
160
+ return filteredSchema;
161
+ };
162
+ /**
163
+ * Fetches a schema file from a remote URL.
164
+ */
165
+ const introspectSchemaFromUrl = async (schemaUrl) => {
166
+ // Use the global fetch function
167
+ const response = await fetch(schemaUrl);
168
+ if (!response.ok) {
169
+ throw new Error(`Failed to fetch schema from ${schemaUrl}: ${response.statusText}`);
170
+ }
171
+ return response.text();
172
+ };
173
+ /**
174
+ * Handles local file loading (not supported on Vercel).
175
+ */
176
+ const introspectLocalSchema = async (filePath) => {
177
+ throw new Error("Local schema file loading is not supported in the Vercel environment.");
178
+ };
179
+ // --- JSON-RPC Handler ---
180
+ /**
181
+ * Handles incoming JSON-RPC 2.0 requests.
182
+ */
183
+ async function handleJsonRpc(reqBody) {
184
+ const { method, params, id } = reqBody;
185
+ // Guard clause to ensure Apollo Client was initialized successfully
186
+ if (!client) {
187
+ console.error('Apollo Client is unavailable.');
127
188
  return {
128
- isError: true,
129
- content: [
130
- {
131
- type: "text",
132
- text: `Invalid GraphQL query: ${error}`,
133
- },
134
- ],
189
+ jsonrpc: '2.0',
190
+ id: id || null,
191
+ error: {
192
+ code: -32000, // Server error
193
+ message: 'Server failed to initialize Apollo Client.',
194
+ },
135
195
  };
136
196
  }
137
- try {
138
- const toolHeaders = headers
139
- ? JSON.parse(headers)
140
- : {};
141
- const allHeaders = {
142
- "Content-Type": "application/json",
143
- ...env.HEADERS,
144
- ...toolHeaders,
145
- };
146
- let parsedVariables = null;
147
- if (variables) {
148
- if (typeof variables === 'string') {
149
- parsedVariables = JSON.parse(variables);
197
+ // --- 1. Query GraphQL ---
198
+ if (method === 'query-graphql' && params && params.query) {
199
+ try {
200
+ const { query } = params;
201
+ // Execute the GraphQL query against the configured endpoint
202
+ const result = await client.query({
203
+ // Note: Using 'gql' via coreExports
204
+ query: coreExports.gql(query),
205
+ fetchPolicy: 'no-cache', // Ensure fresh data on every call
206
+ });
207
+ // CRITICAL FIX:
208
+ // A successful JSON-RPC response must contain the actual data under the 'result' key.
209
+ // The actual GraphQL data is located in the Apollo result's 'data' property.
210
+ if (result.data) {
211
+ return {
212
+ jsonrpc: '2.0',
213
+ id: id,
214
+ // Map the raw GraphQL data to the JSON-RPC 'result' field
215
+ result: result.data,
216
+ };
150
217
  }
151
- else {
152
- parsedVariables = variables;
218
+ // Handle GraphQL errors returned in the result object (e.g., validation/execution errors)
219
+ if (result.errors) {
220
+ console.error('GraphQL Execution Errors Found:', result.errors);
221
+ // Return a JSON-RPC Error object if GraphQL execution failed
222
+ return {
223
+ jsonrpc: "2.0",
224
+ error: {
225
+ code: -32000, // Standard JSON-RPC error code for Server Error
226
+ message: "GraphQL Execution Error",
227
+ data: result.errors
228
+ },
229
+ id: id
230
+ };
153
231
  }
154
- }
155
- const response = await fetch(env.ENDPOINT, {
156
- method: "POST",
157
- headers: allHeaders,
158
- body: JSON.stringify({
159
- query,
160
- variables: parsedVariables,
161
- }),
162
- });
163
- if (!response.ok) {
164
- const responseText = await response.text();
232
+ // Fallback for unexpected empty result
165
233
  return {
166
- isError: true,
167
- content: [
168
- {
169
- type: "text",
170
- text: `GraphQL request failed: ${response.statusText}\n${responseText}`,
171
- },
172
- ],
234
+ jsonrpc: '2.0',
235
+ id: id,
236
+ error: {
237
+ code: -32603, // Internal error
238
+ message: 'Error executing GraphQL query: Response was empty.',
239
+ },
173
240
  };
174
241
  }
175
- const rawData = await response.json();
176
- const data = rawData;
177
- if (data.errors && data.errors.length > 0) {
242
+ catch (error) {
243
+ console.error('GraphQL Execution Error (Catch Block):', error);
178
244
  return {
179
- isError: true,
180
- content: [
181
- {
182
- type: "text",
183
- text: `GraphQL errors: ${JSON.stringify(data.errors, null, 2)}`,
184
- },
185
- ],
245
+ jsonrpc: '2.0',
246
+ id: id,
247
+ error: {
248
+ code: -32603, // Internal error
249
+ message: 'Error executing GraphQL query',
250
+ data: error.message,
251
+ },
186
252
  };
187
253
  }
188
- return {
189
- content: [
190
- {
191
- type: "text",
192
- text: JSON.stringify(data, null, 2),
193
- },
194
- ],
195
- };
196
254
  }
197
- catch (error) {
198
- return {
199
- isError: true,
200
- content: [
201
- {
202
- type: "text",
203
- text: `Failed to execute GraphQL query: ${error}`,
204
- },
205
- ],
206
- };
207
- }
208
- };
209
- toolHandlers.set("query-graphql", queryGraphqlHandler);
210
- server.tool("query-graphql", "Query a GraphQL endpoint with the given query and variables. Optionally pass headers (e.g., for Authorization).", {
211
- query: z.string(),
212
- variables: z.string().optional(),
213
- headers: z
214
- .string()
215
- .optional()
216
- .describe("Optional JSON string of headers to include, e.g., {\"Authorization\": \"Bearer ...\"}"),
217
- }, queryGraphqlHandler);
218
- function readBody(req) {
219
- return new Promise((resolve, reject) => {
220
- let body = '';
221
- req.on('data', (chunk) => {
222
- body += chunk.toString();
223
- });
224
- req.on('end', () => {
225
- resolve(body);
226
- });
227
- req.on('error', reject);
228
- });
229
- }
230
- async function handleHttpRequest(req, res) {
231
- res.setHeader('Access-Control-Allow-Origin', '*');
232
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
233
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
234
- if (req.method === 'OPTIONS') {
235
- res.writeHead(204);
236
- res.end();
237
- return;
238
- }
239
- const url = new URL(req.url, `http://${req.headers.host}`);
240
- if (url.pathname === '/health' && req.method === 'GET') {
241
- res.writeHead(200, { 'Content-Type': 'application/json' });
242
- res.end(JSON.stringify({ status: 'ok', server: env.NAME }));
243
- return;
244
- }
245
- if (url.pathname === '/mcp' && req.method === 'POST') {
246
- let rawBody;
247
- let request;
248
- try {
249
- rawBody = await readBody(req);
250
- request = JSON.parse(rawBody);
251
- }
252
- catch (e) {
253
- console.error("HTTP MCP Parse Error:", e);
254
- res.writeHead(400, { 'Content-Type': 'application/json' });
255
- res.end(JSON.stringify({
256
- jsonrpc: '2.0',
257
- id: null,
258
- error: { code: -32700, message: 'Parse Error: Invalid JSON received in request body.' }
259
- }));
260
- return;
261
- }
255
+ // --- 2. Introspect Schema ---
256
+ if (method === 'introspect-schema') {
262
257
  try {
263
- const { method, params, id } = request;
264
- if (!method || typeof id === 'undefined') {
265
- res.writeHead(400, { 'Content-Type': 'application/json' });
266
- res.end(JSON.stringify({
267
- jsonrpc: '2.0',
268
- id: id || null,
269
- error: { code: -32600, message: 'Invalid JSON-RPC Request structure (missing method or id).' }
270
- }));
271
- return;
258
+ // Default to using the main endpoint
259
+ let schemaSource = MCP_GRAPHQL_ENDPOINT;
260
+ let resultText;
261
+ const headers = {};
262
+ // Check if a remote schema URL is provided
263
+ if (params?.schemaUrl) {
264
+ resultText = await introspectSchemaFromUrl(params.schemaUrl);
272
265
  }
273
- const handler = toolHandlers.get(method);
274
- if (!handler) {
275
- res.writeHead(404, { 'Content-Type': 'application/json' });
276
- res.end(JSON.stringify({
277
- jsonrpc: '2.0',
278
- id: id,
279
- error: { code: -32601, message: `Method not found: ${method}` }
280
- }));
281
- return;
266
+ // Check if typeNames are provided for filtered introspection
267
+ else if (params?.typeNames) {
268
+ resultText = await introspectTypes(schemaSource, headers, params.typeNames);
269
+ }
270
+ // Default: Full introspection
271
+ else {
272
+ resultText = await introspectEndpoint(schemaSource, headers);
282
273
  }
283
- const result = await handler(params);
284
- res.writeHead(200, { 'Content-Type': 'application/json' });
285
- res.end(JSON.stringify({
274
+ // Introspection methods return the schema as a string, which is correct
275
+ // for the 'text' type output used here.
276
+ return {
286
277
  jsonrpc: '2.0',
287
278
  id: id,
288
- result: result
289
- }));
279
+ result: {
280
+ content: [
281
+ {
282
+ type: 'text',
283
+ text: resultText,
284
+ },
285
+ ],
286
+ },
287
+ };
290
288
  }
291
289
  catch (error) {
292
- console.error("HTTP MCP Execution Error:", error);
293
- res.writeHead(500, { 'Content-Type': 'application/json' });
294
- res.end(JSON.stringify({
290
+ console.error('Introspection Error:', error);
291
+ return {
295
292
  jsonrpc: '2.0',
296
- id: request?.id || null,
297
- error: { code: -32603, message: 'Internal server error during tool execution.' }
298
- }));
293
+ id: id,
294
+ error: {
295
+ code: -32603, // Internal error
296
+ message: 'Error during schema introspection',
297
+ data: error.message,
298
+ },
299
+ };
299
300
  }
300
- return;
301
301
  }
302
- res.writeHead(404, { 'Content-Type': 'text/plain' });
303
- res.end('Not Found. Use POST /mcp for JSON-RPC or GET /health.');
304
- }
305
- /**
306
- * Tries to listen on a given port, automatically retrying on the next port if EADDRINUSE occurs.
307
- * @param server - The HTTP server instance.
308
- * @param port - The port to attempt binding to.
309
- * @param maxRetries - Maximum number of retries.
310
- * @param attempt - Current attempt number.
311
- * @returns Resolves with the bound server instance.
312
- */
313
- function tryListen(server, port, maxRetries = 5, attempt = 0) {
314
- return new Promise((resolve, reject) => {
315
- if (attempt >= maxRetries) {
316
- reject(new Error(`Failed to bind HTTP server after ${maxRetries} attempts, starting from port ${env.MCP_PORT}.`));
317
- return;
318
- }
319
- if (port > 65535) {
320
- reject(new Error(`Exceeded maximum port number (65535) during retry.`));
321
- return;
322
- }
323
- const errorHandler = (err) => {
324
- server.removeListener('error', errorHandler); // Remove listener to prevent memory leak
325
- if (err.code === 'EADDRINUSE') {
326
- const nextPort = port + 1;
327
- // Use console.error so it appears in the Inspector log
328
- console.error(`[HTTP] Port ${port} is in use (EADDRINUSE). Retrying on ${nextPort}...`);
329
- server.close(() => {
330
- // Recursively call tryListen with the next port
331
- resolve(tryListen(server, nextPort, maxRetries, attempt + 1));
332
- });
333
- }
334
- else {
335
- reject(err);
336
- }
337
- };
338
- server.on('error', errorHandler);
339
- server.listen(port, () => {
340
- server.removeListener('error', errorHandler); // success, remove the error listener
341
- console.error(`[HTTP] Started server on http://localhost:${port}. Listening for POST /mcp requests.`);
342
- resolve(server);
343
- });
344
- });
345
- }
346
- function startHttpTransport() {
347
- const serverInstance = node_http_1.default.createServer(handleHttpRequest);
348
- tryListen(serverInstance, env.MCP_PORT).catch((error) => {
349
- console.error(`[HTTP] Failed to start HTTP transport: ${error.message}`);
350
- });
302
+ // --- 3. Handle unsupported method ---
303
+ return {
304
+ jsonrpc: '2.0',
305
+ id: id || null,
306
+ error: {
307
+ code: -32601, // Method not found
308
+ message: `Method not found: ${method}`,
309
+ },
310
+ };
351
311
  }
352
- async function main() {
353
- const stdioTransport = new StdioServerTransport();
354
- await server.connect(stdioTransport);
355
- startHttpTransport();
356
- console.error(`[STDIO] Started graphql mcp server ${env.NAME} for endpoint: ${env.ENDPOINT}`);
357
- }
358
- main().catch((error) => {
359
- console.error(`Fatal error in main(): ${error}`);
360
- process.exit(1);
312
+ // --- Express App Setup ---
313
+ const app = (0, express_1.default)();
314
+ // Use express.json()
315
+ app.use(express_1.default.json());
316
+ // **CRITICAL FIX FOR LOCAL DEVELOPMENT (vercel dev):**
317
+ // When running locally, the full path /mcp is passed to Express.
318
+ app.post('/mcp', async (req, res) => {
319
+ const response = await handleJsonRpc(req.body);
320
+ res.json(response);
321
+ });
322
+ // **CRITICAL FIX FOR VERCEL PRODUCTION:**
323
+ // When deployed, Vercel strips the /mcp prefix, so the function receives the path as '/'.
324
+ app.post('/', async (req, res) => {
325
+ const response = await handleJsonRpc(req.body);
326
+ res.json(response);
327
+ });
328
+ // Health check endpoints for both environments
329
+ // FIX: Changed unused 'req' parameters to '_req' to silence the linting warning.
330
+ app.get('/health', (_req, res) => {
331
+ // Note: Checking client status (it will be an instance of ApolloClient if successful)
332
+ if (client && client instanceof coreExports.ApolloClient) {
333
+ res.status(200).send('OK');
334
+ }
335
+ else {
336
+ res.status(500).send('Initialization Failed');
337
+ }
338
+ });
339
+ app.get('/mcp/health', (_req, res) => {
340
+ // This route is primarily for local testing convenience
341
+ if (client && client instanceof coreExports.ApolloClient) {
342
+ res.status(200).send('OK');
343
+ }
344
+ else {
345
+ res.status(500).send('Initialization Failed');
346
+ }
361
347
  });
348
+ // Export the app instance for Vercel
349
+ exports.default = app;
package/package.json CHANGED
@@ -43,6 +43,8 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@graphql-tools/schema": "^10.0.23",
46
+ "@types/express": "^5.0.5",
47
+ "@types/graphql": "^14.2.3",
46
48
  "@types/node": "^24.7.2",
47
49
  "@types/yargs": "17.0.33",
48
50
  "graphql-yoga": "^5.13.5",
@@ -50,5 +52,5 @@
50
52
  "ts-node": "^10.9.2",
51
53
  "typescript": "5.8.3"
52
54
  },
53
- "version": "2.3.0"
55
+ "version": "2.3.1"
54
56
  }