@letoribo/mcp-graphql-enhanced 2.3.3 → 3.0.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/LICENSE +17 -1
- package/README.md +8 -1
- package/README.md-draft +444 -0
- package/dist/index.js +117 -56
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2024 Boris Besemer
|
|
3
|
+
Copyright (c) 2024 Boris Besemer (Original Work)
|
|
4
|
+
Copyright (c) 2025-2026 [letoribo] (Enhanced Architecture, NPM Refactoring & Discord Graph Infrastructure)
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -19,3 +20,18 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
20
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
21
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
22
|
SOFTWARE.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
PROJECT EVOLUTION & SOVEREIGNTY NOTE:
|
|
26
|
+
As of mid-2025, this project has undergone a fundamental architectural shift.
|
|
27
|
+
The legacy Bun-based implementation has been decommissioned and replaced with
|
|
28
|
+
a surgical, production-ready NPM/Node.js core for maximum stability.
|
|
29
|
+
|
|
30
|
+
This "Enhanced" version was battle-tested as the architectural engine for the
|
|
31
|
+
"Neo4j Discord-as-a-Database" project (mcp-neo4j-discord.vercel.app), handling
|
|
32
|
+
large-scale graph datasets.
|
|
33
|
+
|
|
34
|
+
While active real-time data ingestion is currently throttled due to persistent
|
|
35
|
+
infrastructure challenges and blackouts in Odesa, the codebase remains the
|
|
36
|
+
sovereign standard for high-performance GraphRAG, 3D visualization interface,
|
|
37
|
+
and resilient AI intent parsing. Developed under pressure in Odesa, Ukraine.
|
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ This allows external systems, web applications, and direct `curl` commands to ac
|
|
|
26
26
|
|
|
27
27
|
### Resolving Port Conflicts (EADDRINUSE) and Automatic Port Selection
|
|
28
28
|
|
|
29
|
-
The server defaults to port `6274`. If you encounter an `EADDRINUSE: address already in use :::6274` error (common in local development due to stale processes), the server will automatically
|
|
29
|
+
The server defaults to port `6274`. If you encounter an `EADDRINUSE: address already in use :::6274` error (common in local development due to stale processes), the server will automatically find the next available port (up to 10 attempts, not spawning multiple servers).
|
|
30
30
|
|
|
31
31
|
This ensures the server starts successfully even when the default is blocked. **Always check the server logs for the final bound port** (e.g., `[HTTP] Started server on http://localhost:6275`) if your `curl` or client tool fails on the default `6274`.
|
|
32
32
|
|
|
@@ -64,6 +64,11 @@ npx @modelcontextprotocol/inspector \
|
|
|
64
64
|
| `NAME` | Name of the MCP server | `mcp-graphql-enhanced` |
|
|
65
65
|
| `SCHEMA` | Path to a local GraphQL schema file or URL (optional) | - |
|
|
66
66
|
| `MCP_PORT` | Port for the HTTP/JSON-RPC server. | `6274` |
|
|
67
|
+
| `ENABLE_HTTP` | Enable HTTP transport: `auto` (default), `true`, or `false` | `auto` |
|
|
68
|
+
**Note on `ENABLE_HTTP`:**
|
|
69
|
+
- `auto` (default): Automatically enables HTTP only when running in MCP Inspector...
|
|
70
|
+
- `true`: Always enable HTTP server
|
|
71
|
+
- `false`: Disable HTTP server completely
|
|
67
72
|
### Examples
|
|
68
73
|
```bash
|
|
69
74
|
# Basic usage
|
|
@@ -82,6 +87,8 @@ SCHEMA=./schema.graphql \
|
|
|
82
87
|
npx @letoribo/mcp-graphql-enhanced
|
|
83
88
|
# Change the HTTP port
|
|
84
89
|
MCP_PORT=8080 npx @letoribo/mcp-graphql-enhanced
|
|
90
|
+
# Disable HTTP transport (fastest, recommended for Claude Desktop)
|
|
91
|
+
ENABLE_HTTP=false npx @letoribo/mcp-graphql-enhanced
|
|
85
92
|
```
|
|
86
93
|
### 🖥️ Claude Desktop Configuration Examples
|
|
87
94
|
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).
|
package/README.md-draft
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# mcp-graphql-enhanced
|
|
2
|
+
[](https://glama.ai/mcp/servers/@letoribo/mcp-graphql-enhanced)
|
|
3
|
+
|
|
4
|
+
An **enhanced MCP (Model Context Protocol) server for GraphQL** that fixes real-world interoperability issues between LLMs and GraphQL APIs.
|
|
5
|
+
|
|
6
|
+
> Drop-in replacement for `mcp-graphql` — with dynamic headers, robust variables parsing, schema caching for performance, and zero breaking changes.
|
|
7
|
+
|
|
8
|
+
## ✨ Key Enhancements
|
|
9
|
+
* ✅ **Dual Transport** — Supports both **STDIO** (for local CLI/client tools) and **HTTP/JSON-RPC** (for external/browser clients)
|
|
10
|
+
* ✅ **Schema Caching** — Intelligent caching eliminates repeated introspection, dramatically improving response times
|
|
11
|
+
* ✅ **Auto-detect Mode** — Automatically enables HTTP only when running in MCP Inspector, optimizing performance for production use
|
|
12
|
+
* ✅ **Dynamic headers** — Pass `Authorization`, `X-API-Key`, etc., via tool arguments (no config restarts)
|
|
13
|
+
* ✅ **Robust variables parsing** — Fixes `"Query variables must be a null or an object"` error
|
|
14
|
+
* ✅ **Filtered introspection** — Request only specific types (e.g., `typeNames: ["Query", "User"]`) to reduce LLM context noise
|
|
15
|
+
* ✅ **Full MCP compatibility** — Works with **Claude Desktop**, **Cursor**, **Glama**, **MCP Inspector**
|
|
16
|
+
* ✅ **Secure by default** — Mutations disabled unless explicitly enabled
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 💻 HTTP / Dual Transport
|
|
21
|
+
|
|
22
|
+
This server runs in **dual transport mode**, supporting both the standard **STDIO** communication (used by most MCP clients) and an optional **HTTP JSON-RPC** endpoint.
|
|
23
|
+
|
|
24
|
+
### Smart HTTP Transport
|
|
25
|
+
|
|
26
|
+
The HTTP server is **automatically controlled** via the `ENABLE_HTTP` environment variable:
|
|
27
|
+
|
|
28
|
+
- **`auto`** (default): Automatically detects if running in MCP Inspector and enables HTTP only when needed
|
|
29
|
+
- **`true`**: Always enable HTTP server (useful for debugging or external clients)
|
|
30
|
+
- **`false`**: Disable HTTP server completely (fastest, recommended for production Claude Desktop usage)
|
|
31
|
+
|
|
32
|
+
| **Endpoint** | **Method** | **Description** |
|
|
33
|
+
| :--- | :--- | :--- |
|
|
34
|
+
| `/mcp` | `POST` | The main JSON-RPC endpoint for tool execution |
|
|
35
|
+
| `/health` | `GET` | Simple health check, returns `{ status: 'ok', server: 'mcp-graphql-enhanced' }` |
|
|
36
|
+
|
|
37
|
+
### Intelligent Port Management
|
|
38
|
+
|
|
39
|
+
The server defaults to port `6274`. If this port is in use, the server will **automatically find the next available port** (up to 10 attempts), ensuring reliable startup even in development environments with multiple services.
|
|
40
|
+
|
|
41
|
+
**The server logs will show the actual bound port:**
|
|
42
|
+
```
|
|
43
|
+
[HTTP] Server started on http://localhost:6275
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
To **force a specific port**, set the `MCP_PORT` environment variable:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
MCP_PORT=8080 npx @letoribo/mcp-graphql-enhanced
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Testing the HTTP Endpoint
|
|
53
|
+
|
|
54
|
+
You can test the endpoint using `curl` when the server is running:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Test the health check
|
|
58
|
+
curl http://localhost:6274/health
|
|
59
|
+
|
|
60
|
+
# Example: Query via JSON-RPC
|
|
61
|
+
curl -X POST http://localhost:6274/mcp \
|
|
62
|
+
-H "Content-Type: application/json" \
|
|
63
|
+
-d '{
|
|
64
|
+
"jsonrpc": "2.0",
|
|
65
|
+
"method": "query-graphql",
|
|
66
|
+
"params": {
|
|
67
|
+
"query": "query { __typename }"
|
|
68
|
+
},
|
|
69
|
+
"id": 1
|
|
70
|
+
}'
|
|
71
|
+
|
|
72
|
+
# Example: Introspect schema
|
|
73
|
+
curl -X POST http://localhost:6274/mcp \
|
|
74
|
+
-H "Content-Type: application/json" \
|
|
75
|
+
-d '{
|
|
76
|
+
"jsonrpc": "2.0",
|
|
77
|
+
"method": "introspect-schema",
|
|
78
|
+
"params": {},
|
|
79
|
+
"id": 2
|
|
80
|
+
}'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🔍 Filtered Introspection
|
|
86
|
+
|
|
87
|
+
Avoid 50k-line schema dumps. Ask for only what you need:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
@introspect-schema typeNames ["Query", "User"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This dramatically reduces context size and improves LLM performance.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🔍 Debug & Inspect
|
|
98
|
+
|
|
99
|
+
Use the official MCP Inspector to test your server live:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx @modelcontextprotocol/inspector \
|
|
103
|
+
-e ENDPOINT=https://api.example.com/graphql \
|
|
104
|
+
-e ENABLE_HTTP=true \
|
|
105
|
+
npx @letoribo/mcp-graphql-enhanced --debug
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Note:** When using the Inspector, `ENABLE_HTTP=auto` will automatically detect and enable the HTTP server.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## ⚙️ Environment Variables
|
|
113
|
+
|
|
114
|
+
> **Note:** As of version 1.0.0, command line arguments have been replaced with environment variables.
|
|
115
|
+
|
|
116
|
+
| Environment Variable | Description | Default |
|
|
117
|
+
|----------|-------------|---------|
|
|
118
|
+
| `ENDPOINT` | GraphQL endpoint URL | `http://localhost:4000/graphql` |
|
|
119
|
+
| `HEADERS` | JSON string containing headers for requests | `{}` |
|
|
120
|
+
| `ALLOW_MUTATIONS` | Enable mutation operations | `true` |
|
|
121
|
+
| `NAME` | Name of the MCP server | `mcp-graphql-enhanced` |
|
|
122
|
+
| `SCHEMA` | Path to a local GraphQL schema file or URL (optional) | - |
|
|
123
|
+
| `MCP_PORT` | Port for the HTTP/JSON-RPC server | `6274` |
|
|
124
|
+
| `ENABLE_HTTP` | Enable HTTP transport: `auto`, `true`, or `false` | `auto` |
|
|
125
|
+
|
|
126
|
+
### ENABLE_HTTP Modes
|
|
127
|
+
|
|
128
|
+
- **`auto`** (recommended): Automatically enables HTTP only when running in MCP Inspector, disabled otherwise for optimal performance
|
|
129
|
+
- **`true`**: Always run HTTP server (useful for debugging or external API access)
|
|
130
|
+
- **`false`**: Never run HTTP server (fastest, use for production Claude Desktop)
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 📝 Examples
|
|
135
|
+
|
|
136
|
+
### Basic usage
|
|
137
|
+
```bash
|
|
138
|
+
ENDPOINT=http://localhost:3000/graphql \
|
|
139
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### With authentication header
|
|
143
|
+
```bash
|
|
144
|
+
ENDPOINT=https://api.example.com/graphql \
|
|
145
|
+
HEADERS='{"Authorization":"Bearer xyz"}' \
|
|
146
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Enable mutations
|
|
150
|
+
```bash
|
|
151
|
+
ENDPOINT=http://localhost:3000/graphql \
|
|
152
|
+
ALLOW_MUTATIONS=true \
|
|
153
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Use local schema file
|
|
157
|
+
```bash
|
|
158
|
+
ENDPOINT=http://localhost:3000/graphql \
|
|
159
|
+
SCHEMA=./schema.graphql \
|
|
160
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Force HTTP on specific port
|
|
164
|
+
```bash
|
|
165
|
+
ENDPOINT=http://localhost:3000/graphql \
|
|
166
|
+
ENABLE_HTTP=true \
|
|
167
|
+
MCP_PORT=8080 \
|
|
168
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Disable HTTP completely (fastest)
|
|
172
|
+
```bash
|
|
173
|
+
ENDPOINT=http://localhost:3000/graphql \
|
|
174
|
+
ENABLE_HTTP=false \
|
|
175
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 🖥️ Claude Desktop Configuration Examples
|
|
181
|
+
|
|
182
|
+
You can connect Claude Desktop to your GraphQL API using npx (recommended), Docker, or a local build.
|
|
183
|
+
|
|
184
|
+
### ✅ Option 1: Using npx (Recommended - Auto-optimized)
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"mcpServers": {
|
|
189
|
+
"mcp-graphql-enhanced": {
|
|
190
|
+
"command": "npx",
|
|
191
|
+
"args": ["@letoribo/mcp-graphql-enhanced"],
|
|
192
|
+
"env": {
|
|
193
|
+
"ENDPOINT": "https://your-api.com/graphql",
|
|
194
|
+
"ENABLE_HTTP": "auto"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**With authentication:**
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"mcpServers": {
|
|
205
|
+
"mcp-graphql-enhanced": {
|
|
206
|
+
"command": "npx",
|
|
207
|
+
"args": ["@letoribo/mcp-graphql-enhanced"],
|
|
208
|
+
"env": {
|
|
209
|
+
"ENDPOINT": "https://your-api.com/graphql",
|
|
210
|
+
"HEADERS": "{\"Authorization\": \"Bearer YOUR_TOKEN\"}",
|
|
211
|
+
"ALLOW_MUTATIONS": "true",
|
|
212
|
+
"ENABLE_HTTP": "auto"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 🐳 Option 2: Using Docker (auto-pull supported)
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"mcpServers": {
|
|
224
|
+
"mcp-graphql-enhanced": {
|
|
225
|
+
"command": "sh",
|
|
226
|
+
"args": [
|
|
227
|
+
"-c",
|
|
228
|
+
"docker run --rm -i -e ENDPOINT=$ENDPOINT -e HEADERS=$HEADERS -e ALLOW_MUTATIONS=$ALLOW_MUTATIONS -e ENABLE_HTTP=$ENABLE_HTTP ghcr.io/letoribo/mcp-graphql-enhanced:main"
|
|
229
|
+
],
|
|
230
|
+
"env": {
|
|
231
|
+
"ENDPOINT": "https://your-api.com/graphql",
|
|
232
|
+
"HEADERS": "{\"Authorization\": \"Bearer YOUR_TOKEN\"}",
|
|
233
|
+
"ALLOW_MUTATIONS": "false",
|
|
234
|
+
"ENABLE_HTTP": "auto"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 🧪 Option 3: Using local build (for development)
|
|
242
|
+
|
|
243
|
+
If you've cloned the repo and built the project (`npm run build` → outputs to `dist/`):
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"mcpServers": {
|
|
248
|
+
"mcp-graphql-enhanced": {
|
|
249
|
+
"command": "node",
|
|
250
|
+
"args": ["/path/to/mcp-graphql-enhanced/dist/index.js"],
|
|
251
|
+
"env": {
|
|
252
|
+
"ENDPOINT": "https://your-api.com/graphql",
|
|
253
|
+
"ALLOW_MUTATIONS": "true",
|
|
254
|
+
"ENABLE_HTTP": "auto"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 🔍 Debug Configuration (for MCP Inspector)
|
|
262
|
+
|
|
263
|
+
For development and debugging with the MCP Inspector:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"mcpServers": {
|
|
268
|
+
"mcp-graphql-debug": {
|
|
269
|
+
"command": "npx",
|
|
270
|
+
"args": ["@letoribo/mcp-graphql-enhanced", "--debug"],
|
|
271
|
+
"env": {
|
|
272
|
+
"ENDPOINT": "http://localhost:4000/graphql",
|
|
273
|
+
"ALLOW_MUTATIONS": "true",
|
|
274
|
+
"ENABLE_HTTP": "true",
|
|
275
|
+
"MCP_PORT": "6274"
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 📚 Resources
|
|
285
|
+
|
|
286
|
+
- **graphql-schema**: The server exposes the GraphQL schema as a resource that clients can access. This is either:
|
|
287
|
+
- The local schema file (specified via `SCHEMA`)
|
|
288
|
+
- A schema file hosted at a URL (specified via `SCHEMA`)
|
|
289
|
+
- Retrieved via introspection query from the endpoint
|
|
290
|
+
|
|
291
|
+
**Schema caching**: The schema is loaded once on startup and cached in memory for instant access on subsequent requests.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 🛠️ Available Tools
|
|
296
|
+
|
|
297
|
+
The server provides two main tools:
|
|
298
|
+
|
|
299
|
+
### 1. **introspect-schema**
|
|
300
|
+
|
|
301
|
+
Retrieves the GraphQL schema or a filtered subset.
|
|
302
|
+
|
|
303
|
+
**Parameters:**
|
|
304
|
+
- `typeNames` (optional): Array of type names to filter (e.g., `["Query", "User"]`)
|
|
305
|
+
- `descriptions` (optional, default: `true`): Include field descriptions
|
|
306
|
+
- `directives` (optional, default: `true`): Include directive information
|
|
307
|
+
|
|
308
|
+
**Usage:**
|
|
309
|
+
```graphql
|
|
310
|
+
# Get full schema (cached after first load)
|
|
311
|
+
@introspect-schema
|
|
312
|
+
|
|
313
|
+
# Get specific types only
|
|
314
|
+
@introspect-schema typeNames ["Query", "Mutation", "User"]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Note:** Filtered introspection (`typeNames`) is only available when using a live GraphQL endpoint (not with `SCHEMA` file or URL).
|
|
318
|
+
|
|
319
|
+
### 2. **query-graphql**
|
|
320
|
+
|
|
321
|
+
Execute GraphQL queries and mutations against the endpoint.
|
|
322
|
+
|
|
323
|
+
**Parameters:**
|
|
324
|
+
- `query` (required): GraphQL query or mutation string
|
|
325
|
+
- `variables` (optional): JSON string or object containing query variables
|
|
326
|
+
- `headers` (optional): JSON string containing additional headers (e.g., `{"Authorization": "Bearer token"}`)
|
|
327
|
+
|
|
328
|
+
**Usage:**
|
|
329
|
+
```graphql
|
|
330
|
+
# Simple query
|
|
331
|
+
query { users { id name } }
|
|
332
|
+
|
|
333
|
+
# With variables
|
|
334
|
+
query($id: ID!) { user(id: $id) { name } }
|
|
335
|
+
# variables: {"id": "123"}
|
|
336
|
+
|
|
337
|
+
# With custom headers
|
|
338
|
+
query { me { email } }
|
|
339
|
+
# headers: {"Authorization": "Bearer xyz"}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Security:** Mutations are controlled by the `ALLOW_MUTATIONS` environment variable.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 🚀 Performance Optimizations
|
|
347
|
+
|
|
348
|
+
### Schema Caching
|
|
349
|
+
The schema is introspected once on startup and cached in memory. This provides:
|
|
350
|
+
- **Instant responses** for schema requests (no repeated introspection)
|
|
351
|
+
- **Faster tool initialization** (especially with large schemas)
|
|
352
|
+
- **Reduced load** on your GraphQL endpoint
|
|
353
|
+
|
|
354
|
+
### Auto-optimized HTTP Transport
|
|
355
|
+
When `ENABLE_HTTP=auto` (default):
|
|
356
|
+
- HTTP server is **disabled** in Claude Desktop (fastest performance)
|
|
357
|
+
- HTTP server is **enabled** automatically in MCP Inspector (for debugging)
|
|
358
|
+
- No manual configuration needed
|
|
359
|
+
|
|
360
|
+
### Smart Port Allocation
|
|
361
|
+
Automatic port discovery (6274-6284) prevents startup failures due to port conflicts, with clear logging of the actual bound port.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 🔒 Security Considerations
|
|
366
|
+
|
|
367
|
+
- **Mutations disabled by default** to prevent unintended data changes
|
|
368
|
+
- Always **validate `HEADERS` and `SCHEMA` inputs** in production
|
|
369
|
+
- Use **HTTPS endpoints** and **short-lived tokens** where possible
|
|
370
|
+
- Consider **filtering introspection** (`typeNames`) to limit schema exposure
|
|
371
|
+
- For production: Set `ENABLE_HTTP=false` to disable the HTTP endpoint entirely
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 🛠️ Customize for Your Own Server
|
|
376
|
+
|
|
377
|
+
This is a generic implementation that allows complete introspection and flexible querying (including mutations when enabled).
|
|
378
|
+
|
|
379
|
+
If you need a more specific implementation:
|
|
380
|
+
- Create your own MCP server
|
|
381
|
+
- Lock down tool calling to specific query fields and/or variables
|
|
382
|
+
- Use this repository as a reference implementation
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 📊 Performance Benchmarks
|
|
387
|
+
|
|
388
|
+
Typical startup times (on a standard development machine):
|
|
389
|
+
|
|
390
|
+
- **With HTTP disabled** (`ENABLE_HTTP=false`): ~1-2 seconds
|
|
391
|
+
- **With HTTP auto-detect** (`ENABLE_HTTP=auto` in Claude): ~2-3 seconds
|
|
392
|
+
- **With HTTP enabled** (`ENABLE_HTTP=true`): ~3-4 seconds
|
|
393
|
+
- **Legacy version** (no caching): ~50+ seconds ❌
|
|
394
|
+
|
|
395
|
+
Schema requests after startup: **Instant** (cached)
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 🐛 Troubleshooting
|
|
400
|
+
|
|
401
|
+
### Port conflicts (EADDRINUSE)
|
|
402
|
+
The server automatically finds an available port. Check the logs for the actual port:
|
|
403
|
+
```
|
|
404
|
+
[HTTP] Server started on http://localhost:6275
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Slow startup
|
|
408
|
+
- Ensure your GraphQL endpoint is responsive
|
|
409
|
+
- Consider using a local schema file with `SCHEMA=./schema.graphql`
|
|
410
|
+
- Set `ENABLE_HTTP=false` if you don't need the HTTP endpoint
|
|
411
|
+
|
|
412
|
+
### Schema changes not reflected
|
|
413
|
+
Restart the MCP server to reload and re-cache the schema.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## 📝 Changelog
|
|
418
|
+
|
|
419
|
+
### v3.1.0 (Latest)
|
|
420
|
+
- ✨ Added schema caching for dramatic performance improvements
|
|
421
|
+
- ✨ Added `ENABLE_HTTP` with auto-detect mode
|
|
422
|
+
- ✨ Improved port allocation with automatic retry logic
|
|
423
|
+
- 🐛 Fixed multiple HTTP server spawn issue
|
|
424
|
+
- 🐛 Fixed 50+ second initialization delay
|
|
425
|
+
- 📚 Updated documentation with performance benchmarks
|
|
426
|
+
|
|
427
|
+
### v3.0.0
|
|
428
|
+
- ✨ Added dual transport support (STDIO + HTTP)
|
|
429
|
+
- ✨ Added filtered introspection support
|
|
430
|
+
- ✨ Improved variable parsing robustness
|
|
431
|
+
|
|
432
|
+
### v1.0.0
|
|
433
|
+
- 🔄 Migrated from CLI arguments to environment variables
|
|
434
|
+
- ✨ Initial release with dynamic headers support
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 📄 License
|
|
439
|
+
|
|
440
|
+
MIT
|
|
441
|
+
|
|
442
|
+
## 🤝 Contributing
|
|
443
|
+
|
|
444
|
+
Contributions welcome! Please open an issue or PR on GitHub.
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,17 @@ const EnvSchema = z.object({
|
|
|
36
36
|
}),
|
|
37
37
|
SCHEMA: z.string().optional(),
|
|
38
38
|
MCP_PORT: z.preprocess((val) => (val ? parseInt(val) : 6274), z.number().int().min(1024).max(65535)).default(6274),
|
|
39
|
+
ENABLE_HTTP: z
|
|
40
|
+
.enum(["true", "false", "auto"])
|
|
41
|
+
.transform((value) => {
|
|
42
|
+
if (value === "auto") {
|
|
43
|
+
// Auto-detect: enable HTTP if running in MCP Inspector
|
|
44
|
+
// Inspector sets specific environment variables
|
|
45
|
+
return !!(process.env.MCP_INSPECTOR || process.env.INSPECTOR_PORT);
|
|
46
|
+
}
|
|
47
|
+
return value === "true";
|
|
48
|
+
})
|
|
49
|
+
.default("auto"), // Auto-detect by default
|
|
39
50
|
});
|
|
40
51
|
const env = EnvSchema.parse(process.env);
|
|
41
52
|
const server = new McpServer({
|
|
@@ -43,7 +54,18 @@ const server = new McpServer({
|
|
|
43
54
|
version: getVersion(),
|
|
44
55
|
description: `GraphQL MCP server for ${env.ENDPOINT}`,
|
|
45
56
|
});
|
|
46
|
-
|
|
57
|
+
// Cache schema to avoid repeated introspection
|
|
58
|
+
let cachedSchema = null;
|
|
59
|
+
let schemaLoadError = null;
|
|
60
|
+
async function getSchema() {
|
|
61
|
+
// Return cached schema if available
|
|
62
|
+
if (cachedSchema) {
|
|
63
|
+
return cachedSchema;
|
|
64
|
+
}
|
|
65
|
+
// Return cached error if schema failed to load
|
|
66
|
+
if (schemaLoadError) {
|
|
67
|
+
throw schemaLoadError;
|
|
68
|
+
}
|
|
47
69
|
try {
|
|
48
70
|
let schema;
|
|
49
71
|
if (env.SCHEMA) {
|
|
@@ -58,6 +80,19 @@ server.resource("graphql-schema", new URL(env.ENDPOINT).href, async (uri) => {
|
|
|
58
80
|
else {
|
|
59
81
|
schema = await introspectEndpoint(env.ENDPOINT, env.HEADERS);
|
|
60
82
|
}
|
|
83
|
+
// Cache the schema
|
|
84
|
+
cachedSchema = schema;
|
|
85
|
+
console.error(`[SCHEMA] Successfully loaded and cached GraphQL schema`);
|
|
86
|
+
return schema;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
schemaLoadError = error;
|
|
90
|
+
throw new Error(`Failed to get GraphQL schema: ${error}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
server.resource("graphql-schema", new URL(env.ENDPOINT).href, async (uri) => {
|
|
94
|
+
try {
|
|
95
|
+
const schema = await getSchema();
|
|
61
96
|
return {
|
|
62
97
|
contents: [
|
|
63
98
|
{
|
|
@@ -68,7 +103,7 @@ server.resource("graphql-schema", new URL(env.ENDPOINT).href, async (uri) => {
|
|
|
68
103
|
};
|
|
69
104
|
}
|
|
70
105
|
catch (error) {
|
|
71
|
-
throw
|
|
106
|
+
throw error;
|
|
72
107
|
}
|
|
73
108
|
});
|
|
74
109
|
const toolHandlers = new Map();
|
|
@@ -82,18 +117,7 @@ const introspectSchemaHandler = async ({ typeNames, descriptions = true, directi
|
|
|
82
117
|
return { content: [{ type: "text", text: filtered }] };
|
|
83
118
|
}
|
|
84
119
|
else {
|
|
85
|
-
|
|
86
|
-
if (env.SCHEMA) {
|
|
87
|
-
if (env.SCHEMA.startsWith("http://") || env.SCHEMA.startsWith("https://")) {
|
|
88
|
-
schema = await introspectSchemaFromUrl(env.SCHEMA);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
schema = await introspectLocalSchema(env.SCHEMA);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
schema = await introspectEndpoint(env.ENDPOINT, env.HEADERS);
|
|
96
|
-
}
|
|
120
|
+
const schema = await getSchema();
|
|
97
121
|
return { content: [{ type: "text", text: schema }] };
|
|
98
122
|
}
|
|
99
123
|
}
|
|
@@ -305,59 +329,96 @@ async function handleHttpRequest(req, res) {
|
|
|
305
329
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
306
330
|
res.end('Not Found. Use POST /mcp for JSON-RPC or GET /health.');
|
|
307
331
|
}
|
|
332
|
+
// Single HTTP server instance
|
|
333
|
+
let httpServer = null;
|
|
308
334
|
/**
|
|
309
|
-
* Tries to listen on a given port
|
|
310
|
-
*
|
|
311
|
-
* @param port - The port to attempt binding to.
|
|
312
|
-
* @param maxRetries - Maximum number of retries.
|
|
313
|
-
* @param attempt - Current attempt number.
|
|
314
|
-
* @returns Resolves with the bound server instance.
|
|
335
|
+
* Tries to listen on a given port with a single retry attempt.
|
|
336
|
+
* Returns the port it successfully bound to.
|
|
315
337
|
*/
|
|
316
|
-
function
|
|
338
|
+
async function startHttpServer(initialPort) {
|
|
317
339
|
return new Promise((resolve, reject) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
const errorHandler = (err) => {
|
|
327
|
-
server.removeListener('error', errorHandler); // Remove listener to prevent memory leak
|
|
328
|
-
if (err.code === 'EADDRINUSE') {
|
|
329
|
-
const nextPort = port + 1;
|
|
330
|
-
// Use console.error so it appears in the Inspector log
|
|
331
|
-
console.error(`[HTTP] Port ${port} is in use (EADDRINUSE). Retrying on ${nextPort}...`);
|
|
332
|
-
server.close(() => {
|
|
333
|
-
// Recursively call tryListen with the next port
|
|
334
|
-
resolve(tryListen(server, nextPort, maxRetries, attempt + 1));
|
|
335
|
-
});
|
|
340
|
+
let currentPort = initialPort;
|
|
341
|
+
const maxAttempts = 10;
|
|
342
|
+
let attempts = 0;
|
|
343
|
+
function tryPort(port) {
|
|
344
|
+
if (attempts >= maxAttempts) {
|
|
345
|
+
reject(new Error(`Failed to bind HTTP server after ${maxAttempts} attempts`));
|
|
346
|
+
return;
|
|
336
347
|
}
|
|
337
|
-
|
|
338
|
-
reject(
|
|
348
|
+
if (port > 65535) {
|
|
349
|
+
reject(new Error(`Exceeded maximum port number (65535)`));
|
|
350
|
+
return;
|
|
339
351
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
352
|
+
attempts++;
|
|
353
|
+
const server = node_http_1.default.createServer(handleHttpRequest);
|
|
354
|
+
server.once('error', (err) => {
|
|
355
|
+
if (err.code === 'EADDRINUSE') {
|
|
356
|
+
console.error(`[HTTP] Port ${port} in use, trying ${port + 1}...`);
|
|
357
|
+
server.close();
|
|
358
|
+
tryPort(port + 1);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
reject(err);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
server.listen(port, () => {
|
|
365
|
+
httpServer = server;
|
|
366
|
+
console.error(`[HTTP] Server started on http://localhost:${port}`);
|
|
367
|
+
resolve(port);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
tryPort(currentPort);
|
|
353
371
|
});
|
|
354
372
|
}
|
|
355
373
|
async function main() {
|
|
356
374
|
const stdioTransport = new StdioServerTransport();
|
|
357
375
|
await server.connect(stdioTransport);
|
|
358
|
-
|
|
359
|
-
|
|
376
|
+
// Only start HTTP server if explicitly enabled
|
|
377
|
+
if (env.ENABLE_HTTP) {
|
|
378
|
+
try {
|
|
379
|
+
const port = await startHttpServer(env.MCP_PORT);
|
|
380
|
+
console.error(`[HTTP] Listening on port ${port} for POST /mcp requests`);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
console.error(`[HTTP] Failed to start HTTP server: ${error}`);
|
|
384
|
+
// Don't exit - STDIO transport is more important
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.error(`[HTTP] HTTP transport disabled (ENABLE_HTTP=auto|true to enable)`);
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
await getSchema();
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
console.error(`[SCHEMA] Warning: Failed to pre-load schema: ${error}`);
|
|
395
|
+
}
|
|
360
396
|
}
|
|
397
|
+
// Graceful shutdown
|
|
398
|
+
process.on('SIGINT', () => {
|
|
399
|
+
console.error('\n[SHUTDOWN] Received SIGINT, closing server...');
|
|
400
|
+
if (httpServer) {
|
|
401
|
+
httpServer.close(() => {
|
|
402
|
+
console.error('[SHUTDOWN] HTTP server closed');
|
|
403
|
+
process.exit(0);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
process.exit(0);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
process.on('SIGTERM', () => {
|
|
411
|
+
console.error('\n[SHUTDOWN] Received SIGTERM, closing server...');
|
|
412
|
+
if (httpServer) {
|
|
413
|
+
httpServer.close(() => {
|
|
414
|
+
console.error('[SHUTDOWN] HTTP server closed');
|
|
415
|
+
process.exit(0);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
process.exit(0);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
361
422
|
main().catch((error) => {
|
|
362
423
|
console.error(`Fatal error in main(): ${error}`);
|
|
363
424
|
process.exit(1);
|
package/package.json
CHANGED