@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 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.
@@ -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
+ }