@iflow-mcp/saewoohan-mcp-graphql-tools 0.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 +21 -0
- package/README.md +57 -0
- package/build/config.js +65 -0
- package/build/index.js +284 -0
- package/build/utils.js +92 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 saewoohan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# GraphQL MCP Tools
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server implementation that provides GraphQL API interaction capabilities. This server enables AI assistants to interact with GraphQL APIs through a set of standardized tools.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### Tools
|
|
8
|
+
|
|
9
|
+
- **graphql_query**
|
|
10
|
+
- Execute GraphQL queries against any endpoint
|
|
11
|
+
- Input:
|
|
12
|
+
- `query` (string): The GraphQL query to execute
|
|
13
|
+
- `variables` (object, optional): Variables for the query
|
|
14
|
+
- `endpoint` (string, optional): GraphQL endpoint URL
|
|
15
|
+
- `headers` (object, optional): HTTP headers for the request
|
|
16
|
+
- `timeout` (number, optional): Request timeout in milliseconds
|
|
17
|
+
- `allowMutations` (boolean, optional): Whether to allow mutation operations
|
|
18
|
+
|
|
19
|
+
- **graphql_introspect**
|
|
20
|
+
- Retrieve and explore GraphQL schema information
|
|
21
|
+
- Input:
|
|
22
|
+
- `endpoint` (string, optional): GraphQL endpoint URL
|
|
23
|
+
- `headers` (object, optional): HTTP headers for the request
|
|
24
|
+
- `includeDeprecated` (boolean, optional): Whether to include deprecated types/fields
|
|
25
|
+
|
|
26
|
+
## Usage with Claude Desktop
|
|
27
|
+
|
|
28
|
+
### NPX
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"graphql": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": [
|
|
36
|
+
"-y",
|
|
37
|
+
"mcp-graphql-tools",
|
|
38
|
+
"--endpoint=https://api.github.com/graphql",
|
|
39
|
+
"--headers={\"Authorization\":\"Bearer YOUR_GITHUB_TOKEN\"}",
|
|
40
|
+
"--timeout=30000",
|
|
41
|
+
"--maxComplexity=100"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration Options
|
|
49
|
+
The server accepts the following command-line arguments:
|
|
50
|
+
|
|
51
|
+
- --endpoint (-e): Default GraphQL endpoint URL (default: http://localhost:4000/graphql)
|
|
52
|
+
- --headers (-H): Default headers for all requests as JSON string
|
|
53
|
+
- --timeout (-t): Default request timeout in milliseconds (default: 30000)
|
|
54
|
+
- --maxComplexity (-m): Maximum allowed query complexity (default: 100)
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
package/build/config.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import yargs from "yargs";
|
|
2
|
+
import { hideBin } from "yargs/helpers";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
const packageVersion = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
9
|
+
export const parseArguments = () => {
|
|
10
|
+
return yargs(hideBin(process.argv))
|
|
11
|
+
.option("endpoint", {
|
|
12
|
+
alias: "e",
|
|
13
|
+
description: "Default GraphQL endpoint URL",
|
|
14
|
+
type: "string",
|
|
15
|
+
default: process.env.ENDPOINT ?? "http://localhost:4000/graphql",
|
|
16
|
+
})
|
|
17
|
+
.option("headers", {
|
|
18
|
+
alias: "H",
|
|
19
|
+
description: "Default headers for all requests (as JSON string)",
|
|
20
|
+
type: "string",
|
|
21
|
+
})
|
|
22
|
+
.option("timeout", {
|
|
23
|
+
alias: "t",
|
|
24
|
+
description: "Default request timeout in milliseconds",
|
|
25
|
+
type: "number",
|
|
26
|
+
default: Number(process.env.TIMEOUT) ?? 30000,
|
|
27
|
+
})
|
|
28
|
+
.option("maxComplexity", {
|
|
29
|
+
alias: "m",
|
|
30
|
+
description: "Maximum allowed query complexity",
|
|
31
|
+
type: "number",
|
|
32
|
+
default: Number(process.env.MAX_DEPTH) ?? 100,
|
|
33
|
+
})
|
|
34
|
+
.help()
|
|
35
|
+
.alias("help", "h")
|
|
36
|
+
.version(packageVersion)
|
|
37
|
+
.alias("version", "v")
|
|
38
|
+
.parseSync();
|
|
39
|
+
};
|
|
40
|
+
export class Config {
|
|
41
|
+
endpoint;
|
|
42
|
+
maxQueryComplexity;
|
|
43
|
+
timeout;
|
|
44
|
+
headers;
|
|
45
|
+
version;
|
|
46
|
+
constructor() {
|
|
47
|
+
const argv = parseArguments();
|
|
48
|
+
this.endpoint = argv.endpoint;
|
|
49
|
+
this.maxQueryComplexity = argv.maxComplexity;
|
|
50
|
+
this.timeout = argv.timeout;
|
|
51
|
+
this.version = packageVersion;
|
|
52
|
+
// Parse default headers
|
|
53
|
+
this.headers = {};
|
|
54
|
+
if (argv.headers) {
|
|
55
|
+
try {
|
|
56
|
+
Object.assign(this.headers, JSON.parse(argv.headers));
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
console.error("Error parsing default headers:", e);
|
|
60
|
+
console.error("Headers should be a valid JSON object string");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export const config = new Config();
|
package/build/index.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { parse, printSchema } from "graphql";
|
|
6
|
+
import { config } from "./config.js";
|
|
7
|
+
import { calculateQueryComplexity, executeGraphQLQuery, fetchGraphQLSchema, formatGraphQLQuery, isMutation, sanitizeErrorMessage, } from "./utils.js";
|
|
8
|
+
const DEFAULT_GRAPHQL_ENDPOINT = config.endpoint;
|
|
9
|
+
const MAX_QUERY_COMPLEXITY = config.maxQueryComplexity;
|
|
10
|
+
const DEFAULT_TIMEOUT = config.timeout;
|
|
11
|
+
const DEFAULT_HEADERS = config.headers;
|
|
12
|
+
const GRAPHQL_QUERY_TOOL = {
|
|
13
|
+
name: "graphql_query",
|
|
14
|
+
description: "Execute GraphQL queries using either a specified endpoint or the default endpoint configured during installation",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
endpoint: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "GraphQL endpoint URL (can be omitted to use default)",
|
|
21
|
+
},
|
|
22
|
+
query: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "GraphQL query to execute",
|
|
25
|
+
},
|
|
26
|
+
variables: {
|
|
27
|
+
type: "object",
|
|
28
|
+
description: "Variables to use with the query (JSON object)",
|
|
29
|
+
},
|
|
30
|
+
headers: {
|
|
31
|
+
type: "object",
|
|
32
|
+
description: "Additional headers to include in the request (will be merged with default headers)",
|
|
33
|
+
},
|
|
34
|
+
timeout: {
|
|
35
|
+
type: "number",
|
|
36
|
+
description: "Request timeout in milliseconds",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["query"],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const GRAPHQL_INTROSPECT_TOOL = {
|
|
43
|
+
name: "graphql_introspect",
|
|
44
|
+
description: "Introspect a GraphQL schema from an endpoint with configurable headers",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
endpoint: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "GraphQL endpoint URL (can be omitted to use default)",
|
|
51
|
+
},
|
|
52
|
+
headers: {
|
|
53
|
+
type: "object",
|
|
54
|
+
description: "Additional headers to include in the request (will be merged with default headers)",
|
|
55
|
+
},
|
|
56
|
+
includeDeprecated: {
|
|
57
|
+
type: "boolean",
|
|
58
|
+
description: "Whether to include deprecated fields",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const GRAPHQL_TOOLS = [GRAPHQL_QUERY_TOOL, GRAPHQL_INTROSPECT_TOOL];
|
|
64
|
+
const handleGraphQLQuery = async (query, variables, endpoint = DEFAULT_GRAPHQL_ENDPOINT, headers = {}, timeout = DEFAULT_TIMEOUT, allowMutations = false) => {
|
|
65
|
+
try {
|
|
66
|
+
// Validate query syntax
|
|
67
|
+
parse(query);
|
|
68
|
+
// Check for mutations if they're not allowed
|
|
69
|
+
if (!allowMutations && isMutation(query)) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: "Mutation operations are not allowed unless explicitly enabled with allowMutations=true",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Calculate query complexity
|
|
81
|
+
const queryComplexity = calculateQueryComplexity(query);
|
|
82
|
+
if (queryComplexity > MAX_QUERY_COMPLEXITY) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: `Query complexity (${queryComplexity}) exceeds maximum allowed (${MAX_QUERY_COMPLEXITY})`,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
isError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Process variables
|
|
94
|
+
let processedVariables = variables;
|
|
95
|
+
if (typeof variables === "string") {
|
|
96
|
+
try {
|
|
97
|
+
processedVariables = JSON.parse(variables);
|
|
98
|
+
}
|
|
99
|
+
catch (parseError) {
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: `Failed to parse variables as JSON: ${parseError.message}`,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Execute query
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
const response = await executeGraphQLQuery({
|
|
114
|
+
endpoint,
|
|
115
|
+
query,
|
|
116
|
+
variables: processedVariables,
|
|
117
|
+
headers,
|
|
118
|
+
timeout,
|
|
119
|
+
});
|
|
120
|
+
const executionTime = Date.now() - startTime;
|
|
121
|
+
// Check for GraphQL errors
|
|
122
|
+
if (response.data.errors) {
|
|
123
|
+
const errorMessages = response.data.errors
|
|
124
|
+
.map((e) => e.message)
|
|
125
|
+
.join(", ");
|
|
126
|
+
return {
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: `GraphQL server returned errors: ${errorMessages}`,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Return successful response
|
|
137
|
+
const formattedQuery = formatGraphQLQuery(query);
|
|
138
|
+
const formattedData = JSON.stringify(response.data.data, null, 2);
|
|
139
|
+
const hasDefaultHeaders = Object.keys(DEFAULT_HEADERS).length > 0;
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: `Query executed successfully in ${executionTime}ms at ${endpoint}`,
|
|
145
|
+
},
|
|
146
|
+
...(hasDefaultHeaders
|
|
147
|
+
? [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: `Using default headers: ${JSON.stringify(DEFAULT_HEADERS, null, 2)}`,
|
|
151
|
+
},
|
|
152
|
+
]
|
|
153
|
+
: []),
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `\nQuery:\n\`\`\`graphql\n${formattedQuery}\n\`\`\``,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: `\nResult:\n\`\`\`json\n${formattedData}\n\`\`\``,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const errorMessage = sanitizeErrorMessage(error);
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: `Error executing GraphQL query: ${errorMessage}`,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
isError: true,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const handleGraphQLIntrospect = async (endpoint = DEFAULT_GRAPHQL_ENDPOINT, headers = {}, includeDeprecated = true) => {
|
|
179
|
+
try {
|
|
180
|
+
// Fetch schema
|
|
181
|
+
const { schema } = await fetchGraphQLSchema({
|
|
182
|
+
endpoint,
|
|
183
|
+
headers,
|
|
184
|
+
includeDeprecated,
|
|
185
|
+
});
|
|
186
|
+
const schemaString = printSchema(schema);
|
|
187
|
+
const hasDefaultHeaders = Object.keys(DEFAULT_HEADERS).length > 0;
|
|
188
|
+
return {
|
|
189
|
+
content: [
|
|
190
|
+
{
|
|
191
|
+
type: "text",
|
|
192
|
+
text: `Schema introspection from ${endpoint} completed successfully`,
|
|
193
|
+
},
|
|
194
|
+
...(hasDefaultHeaders
|
|
195
|
+
? [
|
|
196
|
+
{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: `Using default headers: ${JSON.stringify(DEFAULT_HEADERS, null, 2)}`,
|
|
199
|
+
},
|
|
200
|
+
]
|
|
201
|
+
: []),
|
|
202
|
+
{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: `\nGraphQL Schema:\n\`\`\`graphql\n${schemaString}\n\`\`\``,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const errorMessage = sanitizeErrorMessage(error);
|
|
211
|
+
return {
|
|
212
|
+
isError: true,
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: "text",
|
|
216
|
+
text: `Error introspecting GraphQL schema: ${errorMessage}`,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const server = new Server({
|
|
223
|
+
name: "graphql-mcp-server",
|
|
224
|
+
version: config.version,
|
|
225
|
+
}, {
|
|
226
|
+
capabilities: {
|
|
227
|
+
tools: {},
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
231
|
+
tools: GRAPHQL_TOOLS,
|
|
232
|
+
}));
|
|
233
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
234
|
+
try {
|
|
235
|
+
switch (request.params.name) {
|
|
236
|
+
case "graphql_query": {
|
|
237
|
+
const { query, variables, endpoint, headers, timeout, allowMutations } = request.params.arguments;
|
|
238
|
+
return await handleGraphQLQuery(query, variables, endpoint, headers, timeout, allowMutations);
|
|
239
|
+
}
|
|
240
|
+
case "graphql_introspect": {
|
|
241
|
+
const { endpoint, headers, includeDeprecated } = request.params
|
|
242
|
+
.arguments;
|
|
243
|
+
return await handleGraphQLIntrospect(endpoint, headers, includeDeprecated);
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
return {
|
|
247
|
+
content: [
|
|
248
|
+
{
|
|
249
|
+
type: "text",
|
|
250
|
+
text: `Unknown tool: ${request.params.name}`,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
return {
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
type: "text",
|
|
262
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
isError: true,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
async function runServer() {
|
|
270
|
+
try {
|
|
271
|
+
console.error("Starting GraphQL MCP server...");
|
|
272
|
+
const transport = new StdioServerTransport();
|
|
273
|
+
await server.connect(transport);
|
|
274
|
+
console.error("GraphQL MCP server ready");
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
console.error("Error starting server:", error);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
runServer().catch((error) => {
|
|
282
|
+
console.error("Fatal error running server:", error);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
});
|
package/build/utils.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import axios, { AxiosError } from "axios";
|
|
2
|
+
import { parse, visit, Kind, OperationTypeNode, getIntrospectionQuery, buildClientSchema, } from "graphql";
|
|
3
|
+
import { config } from "./config.js";
|
|
4
|
+
const DEFAULT_GRAPHQL_ENDPOINT = config.endpoint;
|
|
5
|
+
const DEFAULT_TIMEOUT = config.timeout;
|
|
6
|
+
const DEFAULT_HEADERS = config.headers;
|
|
7
|
+
const MAX_QUERY_COMPLEXITY = config.maxQueryComplexity;
|
|
8
|
+
export const sanitizeErrorMessage = (error) => {
|
|
9
|
+
if (error instanceof AxiosError) {
|
|
10
|
+
if (error.response) {
|
|
11
|
+
return `Server responded with status ${error.response.status}: ${error.message}`;
|
|
12
|
+
}
|
|
13
|
+
if (error.request) {
|
|
14
|
+
return `No response received: ${error.message}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return error instanceof Error ? error.message : String(error);
|
|
18
|
+
};
|
|
19
|
+
export const formatGraphQLQuery = (query) => {
|
|
20
|
+
return query.replace(/\s+/g, " ").trim();
|
|
21
|
+
};
|
|
22
|
+
export const isMutation = (query) => {
|
|
23
|
+
try {
|
|
24
|
+
const document = parse(query);
|
|
25
|
+
for (const definition of document.definitions) {
|
|
26
|
+
if (definition.kind === Kind.OPERATION_DEFINITION &&
|
|
27
|
+
definition.operation === OperationTypeNode.MUTATION) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export const calculateQueryComplexity = (query) => {
|
|
38
|
+
try {
|
|
39
|
+
const document = parse(query);
|
|
40
|
+
let complexity = 0;
|
|
41
|
+
visit(document, {
|
|
42
|
+
Field(_) {
|
|
43
|
+
complexity += 1;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
return complexity;
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return MAX_QUERY_COMPLEXITY + 1;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
export const executeGraphQLQuery = async ({ endpoint = DEFAULT_GRAPHQL_ENDPOINT, query, variables, headers = {}, timeout = DEFAULT_TIMEOUT, }) => {
|
|
53
|
+
let processedVariables = variables;
|
|
54
|
+
if (typeof variables === "string") {
|
|
55
|
+
processedVariables = JSON.parse(variables);
|
|
56
|
+
}
|
|
57
|
+
const response = await axios.post(endpoint, {
|
|
58
|
+
query,
|
|
59
|
+
variables: processedVariables,
|
|
60
|
+
}, {
|
|
61
|
+
headers: {
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
Accept: "application/json",
|
|
64
|
+
...DEFAULT_HEADERS, // Apply default headers
|
|
65
|
+
...headers, // Request-specific headers override defaults
|
|
66
|
+
},
|
|
67
|
+
timeout,
|
|
68
|
+
});
|
|
69
|
+
return response;
|
|
70
|
+
};
|
|
71
|
+
export const fetchGraphQLSchema = async ({ endpoint = DEFAULT_GRAPHQL_ENDPOINT, headers = {}, includeDeprecated = true, timeout = DEFAULT_TIMEOUT, }) => {
|
|
72
|
+
const response = await axios.post(endpoint, {
|
|
73
|
+
query: getIntrospectionQuery({
|
|
74
|
+
descriptions: true,
|
|
75
|
+
inputValueDeprecation: includeDeprecated,
|
|
76
|
+
}),
|
|
77
|
+
}, {
|
|
78
|
+
headers: {
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
Accept: "application/json",
|
|
81
|
+
...DEFAULT_HEADERS, // Apply default headers
|
|
82
|
+
...headers, // Request-specific headers override defaults
|
|
83
|
+
},
|
|
84
|
+
timeout,
|
|
85
|
+
});
|
|
86
|
+
if (response.data.errors) {
|
|
87
|
+
throw new Error(`GraphQL server returned errors: ${JSON.stringify(response.data.errors)}`);
|
|
88
|
+
}
|
|
89
|
+
const introspectionResult = response.data.data;
|
|
90
|
+
const schema = buildClientSchema(introspectionResult);
|
|
91
|
+
return { schema, introspectionResult };
|
|
92
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iflow-mcp/saewoohan-mcp-graphql-tools",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "GraphQL MCP server for AI assistants",
|
|
5
|
+
"author": "saewoohan <hso2341@naver.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "build/index.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"mcp-graphql": "./build/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"build",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"format": "npx @biomejs/biome format --write src/",
|
|
20
|
+
"check": "npx @biomejs/biome check --apply src/",
|
|
21
|
+
"lint": "npx @biomejs/biome lint src/"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"graphql",
|
|
25
|
+
"mcp",
|
|
26
|
+
"claude",
|
|
27
|
+
"ai",
|
|
28
|
+
"assistant",
|
|
29
|
+
"modelcontextprotocol",
|
|
30
|
+
"llm"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/saewoohan/mcp-server-tools"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
41
|
+
"axios": "^1.8.4",
|
|
42
|
+
"graphql": "^16.10.0",
|
|
43
|
+
"yargs": "^17.7.2"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@biomejs/biome": "^1.9.4",
|
|
47
|
+
"@types/node": "^22.13.13",
|
|
48
|
+
"typescript": "^5.8.2"
|
|
49
|
+
}
|
|
50
|
+
}
|