@letoribo/mcp-graphql-enhanced 2.0.5 → 2.0.6
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 +19 -12
- package/dist/helpers/introspection.d.ts +1 -0
- package/dist/helpers/introspection.d.ts.map +1 -1
- package/dist/helpers/introspection.js +84 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# mcp-graphql-enhanced
|
|
2
2
|
|
|
3
|
-
[](https://smithery.ai/server/mcp-graphql-enhanced)
|
|
4
|
-
[](https://glama.ai/mcp/servers/@letoribo/mcp-graphql-enhanced)
|
|
5
|
-
|
|
3
|
+
[](https://smithery.ai/server/@letoribo/mcp-graphql-enhanced)
|
|
4
|
+
[](https://glama.ai/mcp/servers/@letoribo/mcp-graphql-enhanced)
|
|
6
5
|
An **enhanced MCP (Model Context Protocol) server for GraphQL** that fixes real-world interoperability issues between LLMs and GraphQL APIs.
|
|
7
6
|
|
|
8
7
|
> Drop-in replacement for `mcp-graphql` — with dynamic headers, robust variables parsing, and zero breaking changes.
|
|
@@ -11,9 +10,16 @@ An **enhanced MCP (Model Context Protocol) server for GraphQL** that fixes real-
|
|
|
11
10
|
|
|
12
11
|
- ✅ **Dynamic headers** — pass `Authorization`, `X-API-Key`, etc., via tool arguments (no config restarts)
|
|
13
12
|
- ✅ **Robust variables parsing** — fixes `“Query variables must be a null or an object”` error
|
|
13
|
+
- ✅ **Filtered introspection** — request only specific types (e.g., `typeNames: ["Query", "User"]`) to reduce LLM context noise
|
|
14
14
|
- ✅ **Full MCP compatibility** — works with **Claude Desktop**, **Cursor**, **Glama**, and **Smithery**
|
|
15
15
|
- ✅ **Secure by default** — mutations disabled unless explicitly enabled
|
|
16
16
|
|
|
17
|
+
## 🔍 Filtered Introspection (New!)
|
|
18
|
+
|
|
19
|
+
Avoid 50k-line schema dumps. Ask for only what you need:
|
|
20
|
+
|
|
21
|
+
@introspect-schema typeNames ["Query", "User"]
|
|
22
|
+
|
|
17
23
|
## 🔍 Debug & Inspect
|
|
18
24
|
|
|
19
25
|
Use the official MCP Inspector to test your server live:
|
|
@@ -21,7 +27,7 @@ Use the official MCP Inspector to test your server live:
|
|
|
21
27
|
```bash
|
|
22
28
|
npx @modelcontextprotocol/inspector \
|
|
23
29
|
-e ENDPOINT=https://api.example.com/graphql \
|
|
24
|
-
npx mcp-graphql-enhanced --debug
|
|
30
|
+
npx @letoribo/mcp-graphql-enhanced --debug
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
### Environment Variables (Breaking change in 1.0.0)
|
|
@@ -40,22 +46,22 @@ npx @modelcontextprotocol/inspector \
|
|
|
40
46
|
|
|
41
47
|
```bash
|
|
42
48
|
# Basic usage
|
|
43
|
-
ENDPOINT=http://localhost:3000/graphql npx mcp-graphql-enhanced
|
|
49
|
+
ENDPOINT=http://localhost:3000/graphql npx @letoribo/mcp-graphql-enhanced
|
|
44
50
|
|
|
45
51
|
# With auth header
|
|
46
52
|
ENDPOINT=https://api.example.com/graphql \
|
|
47
53
|
HEADERS='{"Authorization":"Bearer xyz"}' \
|
|
48
|
-
npx mcp-graphql-enhanced
|
|
54
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
49
55
|
|
|
50
56
|
# Enable mutations
|
|
51
57
|
ENDPOINT=http://localhost:3000/graphql \
|
|
52
58
|
ALLOW_MUTATIONS=true \
|
|
53
|
-
npx mcp-graphql-enhanced
|
|
59
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
54
60
|
|
|
55
61
|
# Use local schema file
|
|
56
62
|
ENDPOINT=http://localhost:3000/graphql \
|
|
57
63
|
SCHEMA=./schema.graphql \
|
|
58
|
-
npx mcp-graphql-enhanced
|
|
64
|
+
npx @letoribo/mcp-graphql-enhanced
|
|
59
65
|
```
|
|
60
66
|
|
|
61
67
|
## Resources
|
|
@@ -66,8 +72,9 @@ npx mcp-graphql-enhanced
|
|
|
66
72
|
|
|
67
73
|
The server provides two main tools:
|
|
68
74
|
|
|
69
|
-
1. **introspect-schema**: This tool retrieves the GraphQL schema. Use this first if you don't have access to the schema as a resource.
|
|
75
|
+
1. **introspect-schema**: This tool retrieves the GraphQL schema or a filtered subset (via typeNames). Use this first if you don't have access to the schema as a resource.
|
|
70
76
|
This uses either the local schema file, a schema file hosted at a URL, or an introspection query.
|
|
77
|
+
Filtered introspection (typeNames) is only available when using a live GraphQL endpoint (not with SCHEMA file or URL).
|
|
71
78
|
|
|
72
79
|
2. **query-graphql**: Execute GraphQL queries against the endpoint. By default, mutations are disabled unless `ALLOW_MUTATIONS` is set to `true`.
|
|
73
80
|
|
|
@@ -75,10 +82,10 @@ This uses either the local schema file, a schema file hosted at a URL, or an int
|
|
|
75
82
|
|
|
76
83
|
### Installing via Smithery
|
|
77
84
|
|
|
78
|
-
To install GraphQL MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-graphql-enhanced):
|
|
85
|
+
To install GraphQL MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@letoribo/mcp-graphql-enhanced):
|
|
79
86
|
|
|
80
87
|
```bash
|
|
81
|
-
npx -y @smithery/cli install mcp-graphql-enhanced --client claude
|
|
88
|
+
npx -y @smithery/cli install @letoribo/mcp-graphql-enhanced --client claude
|
|
82
89
|
```
|
|
83
90
|
|
|
84
91
|
### Installing Manually
|
|
@@ -89,7 +96,7 @@ It can be manually installed to Claude:
|
|
|
89
96
|
"mcpServers": {
|
|
90
97
|
"mcp-graphql": {
|
|
91
98
|
"command": "npx",
|
|
92
|
-
"args": ["mcp-graphql-enhanced"],
|
|
99
|
+
"args": ["@letoribo/mcp-graphql-enhanced"],
|
|
93
100
|
"env": {
|
|
94
101
|
"ENDPOINT": "https://your-api.com/graphql"
|
|
95
102
|
}
|
|
@@ -17,4 +17,5 @@ export declare function introspectSchemaFromUrl(url: string): Promise<string>;
|
|
|
17
17
|
* @returns The schema
|
|
18
18
|
*/
|
|
19
19
|
export declare function introspectLocalSchema(path: string): Promise<string>;
|
|
20
|
+
export declare function introspectTypes(endpoint: string, headers: Record<string, string> | undefined, typeNames: string[]): Promise<string>;
|
|
20
21
|
//# sourceMappingURL=introspection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../../src/helpers/introspection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../../src/helpers/introspection.ts"],"names":[],"mappings":"AAcA;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,mBAuBhC;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,mBASxD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,mBAGvD;AAkBD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAK,EACpC,SAAS,EAAE,MAAM,EAAE,mBA6EpB"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.introspectEndpoint = introspectEndpoint;
|
|
4
4
|
exports.introspectSchemaFromUrl = introspectSchemaFromUrl;
|
|
5
5
|
exports.introspectLocalSchema = introspectLocalSchema;
|
|
6
|
+
exports.introspectTypes = introspectTypes;
|
|
6
7
|
const graphql_1 = require("graphql");
|
|
7
8
|
const promises_1 = require("node:fs/promises");
|
|
8
9
|
/**
|
|
@@ -53,3 +54,86 @@ async function introspectLocalSchema(path) {
|
|
|
53
54
|
const schema = await (0, promises_1.readFile)(path, "utf8");
|
|
54
55
|
return schema;
|
|
55
56
|
}
|
|
57
|
+
function isObjectLikeType(type) {
|
|
58
|
+
return 'getFields' in type;
|
|
59
|
+
}
|
|
60
|
+
function isUnionType(type) {
|
|
61
|
+
return 'getTypes' in type;
|
|
62
|
+
}
|
|
63
|
+
function isEnumType(type) {
|
|
64
|
+
return 'getValues' in type;
|
|
65
|
+
}
|
|
66
|
+
function isInputObjectType(type) {
|
|
67
|
+
return 'getFields' in type;
|
|
68
|
+
}
|
|
69
|
+
async function introspectTypes(endpoint, headers = {}, typeNames) {
|
|
70
|
+
const response = await fetch(endpoint, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
73
|
+
body: JSON.stringify({ query: (0, graphql_1.getIntrospectionQuery)() }),
|
|
74
|
+
});
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
const schema = (0, graphql_1.buildClientSchema)(data.data);
|
|
77
|
+
const result = {};
|
|
78
|
+
for (const name of typeNames) {
|
|
79
|
+
const type = schema.getType(name);
|
|
80
|
+
if (!type)
|
|
81
|
+
continue;
|
|
82
|
+
// Handle object/interface types
|
|
83
|
+
if (isObjectLikeType(type)) {
|
|
84
|
+
result[name] = {
|
|
85
|
+
kind: type instanceof graphql_1.GraphQLObjectType ? "OBJECT" : "INTERFACE",
|
|
86
|
+
description: type.description,
|
|
87
|
+
fields: Object.fromEntries(Object.entries(type.getFields()).map(([fieldName, field]) => [
|
|
88
|
+
fieldName,
|
|
89
|
+
{
|
|
90
|
+
type: field.type.toString(),
|
|
91
|
+
description: field.description,
|
|
92
|
+
args: field.args.map(arg => ({
|
|
93
|
+
name: arg.name,
|
|
94
|
+
type: arg.type.toString(),
|
|
95
|
+
description: arg.description,
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
]))
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Handle union types
|
|
102
|
+
else if (isUnionType(type)) {
|
|
103
|
+
result[name] = {
|
|
104
|
+
kind: "UNION",
|
|
105
|
+
description: type.description,
|
|
106
|
+
possibleTypes: type.getTypes().map(t => t.name)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Handle enums
|
|
110
|
+
else if (isEnumType(type)) {
|
|
111
|
+
result[name] = {
|
|
112
|
+
kind: "ENUM",
|
|
113
|
+
description: type.description,
|
|
114
|
+
values: type.getValues().map(v => ({
|
|
115
|
+
name: v.name,
|
|
116
|
+
description: v.description
|
|
117
|
+
}))
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Handle scalars and input objects
|
|
121
|
+
else if (isInputObjectType(type)) {
|
|
122
|
+
result[name] = {
|
|
123
|
+
kind: "INPUT_OBJECT",
|
|
124
|
+
description: type.description,
|
|
125
|
+
fields: Object.fromEntries(Object.entries(type.getFields()).map(([fieldName, field]) => [
|
|
126
|
+
fieldName,
|
|
127
|
+
{ type: field.type.toString(), description: field.description }
|
|
128
|
+
]))
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
else if (type instanceof graphql_1.GraphQLScalarType) {
|
|
132
|
+
result[name] = {
|
|
133
|
+
kind: "SCALAR",
|
|
134
|
+
description: type.description
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return JSON.stringify(result, null, 2);
|
|
139
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,10 +4,15 @@ declare const StdioServerTransport: any;
|
|
|
4
4
|
declare const parse: any;
|
|
5
5
|
declare const z: any;
|
|
6
6
|
declare const checkDeprecatedArguments: any;
|
|
7
|
-
declare const introspectEndpoint: any, introspectLocalSchema: any, introspectSchemaFromUrl: any;
|
|
7
|
+
declare const introspectEndpoint: any, introspectLocalSchema: any, introspectSchemaFromUrl: any, introspectTypes: any;
|
|
8
8
|
declare const getVersion: () => any;
|
|
9
9
|
declare const EnvSchema: any;
|
|
10
10
|
declare const env: any;
|
|
11
11
|
declare const server: any;
|
|
12
|
+
interface IntrospectSchemaArgs {
|
|
13
|
+
typeNames?: string[];
|
|
14
|
+
descriptions?: boolean;
|
|
15
|
+
directives?: boolean;
|
|
16
|
+
}
|
|
12
17
|
declare function main(): Promise<void>;
|
|
13
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,QAAA,MAAQ,SAAS,KAAuD,CAAC;AACzE,QAAA,MAAQ,oBAAoB,KAAyD,CAAC;AACtF,QAAA,MAAQ,KAAK,KAAgC,CAAC;AAC9C,QAAA,MAAM,CAAC,KAAyB,CAAC;AACjC,QAAA,MAAQ,wBAAwB,KAAwC,CAAC;AACzE,QAAA,MACC,kBAAkB,OAClB,qBAAqB,OACrB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,QAAA,MAAQ,SAAS,KAAuD,CAAC;AACzE,QAAA,MAAQ,oBAAoB,KAAyD,CAAC;AACtF,QAAA,MAAQ,KAAK,KAAgC,CAAC;AAC9C,QAAA,MAAM,CAAC,KAAyB,CAAC;AACjC,QAAA,MAAQ,wBAAwB,KAAwC,CAAC;AACzE,QAAA,MACC,kBAAkB,OAClB,qBAAqB,OACrB,uBAAuB,OACvB,eAAe,KACyB,CAAC;AAG1C,QAAA,MAAM,UAAU,WAIf,CAAC;AAKF,QAAA,MAAM,SAAS,KAkBb,CAAC;AAEH,QAAA,MAAM,GAAG,KAA+B,CAAC;AAEzC,QAAA,MAAM,MAAM,KAIV,CAAC;AA+BH,UAAU,oBAAoB;IAC5B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAoKD,iBAAe,IAAI,kBAOlB"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio
|
|
|
5
5
|
const { parse } = require("graphql/language");
|
|
6
6
|
const z = require("zod").default;
|
|
7
7
|
const { checkDeprecatedArguments } = require("./helpers/deprecation.js");
|
|
8
|
-
const { introspectEndpoint, introspectLocalSchema, introspectSchemaFromUrl, } = require("./helpers/introspection.js");
|
|
8
|
+
const { introspectEndpoint, introspectLocalSchema, introspectSchemaFromUrl, introspectTypes, } = require("./helpers/introspection.js");
|
|
9
9
|
// Simulate macro import — since "with { type: 'macro' }" is not CommonJS compatible
|
|
10
10
|
const getVersion = () => {
|
|
11
11
|
// Replace with your actual version or read from package.json
|
|
@@ -68,38 +68,38 @@ server.resource("graphql-schema", new URL(env.ENDPOINT).href, async (uri) => {
|
|
|
68
68
|
throw new Error(`Failed to get GraphQL schema: ${error}`);
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
|
-
server.tool("introspect-schema", "Introspect the GraphQL schema
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}, async () => {
|
|
71
|
+
server.tool("introspect-schema", "Introspect the GraphQL schema. Optionally filter to specific types.", {
|
|
72
|
+
typeNames: z.array(z.string()).optional().describe("e.g., [\"Query\", \"User\"]"),
|
|
73
|
+
descriptions: z.boolean().optional().default(true),
|
|
74
|
+
directives: z.boolean().optional().default(true),
|
|
75
|
+
}, async ({ typeNames, descriptions = true, directives = true }) => {
|
|
77
76
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
if (typeNames && typeNames.length > 0) {
|
|
78
|
+
// ✅ Use your existing introspectTypes helper
|
|
79
|
+
const filtered = await introspectTypes(env.ENDPOINT, env.HEADERS, typeNames);
|
|
80
|
+
return { content: [{ type: "text", text: filtered }] };
|
|
81
81
|
}
|
|
82
82
|
else {
|
|
83
|
-
|
|
83
|
+
// Fallback to full schema
|
|
84
|
+
let schema;
|
|
85
|
+
if (env.SCHEMA) {
|
|
86
|
+
if (env.SCHEMA.startsWith("http://") || env.SCHEMA.startsWith("https://")) {
|
|
87
|
+
schema = await introspectSchemaFromUrl(env.SCHEMA);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
schema = await introspectLocalSchema(env.SCHEMA);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
schema = await introspectEndpoint(env.ENDPOINT, env.HEADERS);
|
|
95
|
+
}
|
|
96
|
+
return { content: [{ type: "text", text: schema }] };
|
|
84
97
|
}
|
|
85
|
-
return {
|
|
86
|
-
content: [
|
|
87
|
-
{
|
|
88
|
-
type: "text",
|
|
89
|
-
text: schema,
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
};
|
|
93
98
|
}
|
|
94
99
|
catch (error) {
|
|
95
100
|
return {
|
|
96
101
|
isError: true,
|
|
97
|
-
content: [
|
|
98
|
-
{
|
|
99
|
-
type: "text",
|
|
100
|
-
text: `Failed to introspect schema: ${error}`,
|
|
101
|
-
},
|
|
102
|
-
],
|
|
102
|
+
content: [{ type: "text", text: `Introspection failed: ${error}` }],
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
});
|
package/package.json
CHANGED