#
tokens: 6407/50000 15/15 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitattributes
├── .gitignore
├── .npmrc
├── biome.json
├── bun.lockb
├── dev
│   └── graphql.ts
├── Dockerfile
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── helpers
│   │   ├── deprecation.ts
│   │   ├── header.ts
│   │   ├── introspection.ts
│   │   └── package.ts
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------

```
package-lock=false

```

--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------

```
*.ts linguist-language=TypeScript

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore

# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Caches

.cache

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store

dist/

# GraphQL schema for debugging
/schema.graphql

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# mcp-graphql

[![smithery badge](https://smithery.ai/badge/mcp-graphql)](https://smithery.ai/server/mcp-graphql)

A Model Context Protocol server that enables LLMs to interact with GraphQL APIs. This implementation provides schema introspection and query execution capabilities, allowing models to discover and use GraphQL APIs dynamically.

## Usage

Run `mcp-graphql` with the correct endpoint, it will automatically try to introspect your queries.

### Command Line Arguments

| Argument             | Description                                      | Default                         |
| -------------------- | ------------------------------------------------ | ------------------------------- |
| `--endpoint`         | GraphQL endpoint URL                             | `http://localhost:4000/graphql` |
| `--headers`          | JSON string containing headers for requests      | `{}`                            |
| `--enable-mutations` | Enable mutation operations (disabled by default) | `false`                         |
| `--name`             | Name of the MCP server                           | `mcp-graphql`                   |
| `--schema`           | Path to a local GraphQL schema file (optional)   | -                               |

### Examples

```bash
# Basic usage with a local GraphQL server
npx mcp-graphql --endpoint http://localhost:3000/graphql

# Using with custom headers
npx mcp-graphql --endpoint https://api.example.com/graphql --headers '{"Authorization":"Bearer token123"}'

# Enable mutation operations
npx mcp-graphql --endpoint http://localhost:3000/graphql --enable-mutations

# Using a local schema file instead of introspection
npx mcp-graphql --endpoint http://localhost:3000/graphql --schema ./schema.graphql
```

## Available Tools

The server provides two main tools:

1. **introspect-schema**: This tool retrieves the GraphQL schema. Use this first if you don't have access to the schema as a resource.
   This uses either the local schema file or an introspection query.

2. **query-graphql**: Execute GraphQL queries against the endpoint. By default, mutations are disabled unless `--enable-mutations` is specified.

## Resources

- **graphql-schema**: The server exposes the GraphQL schema as a resource that clients can access. This is either the local schema file or based on an introspection query.

## Installation

### Installing via Smithery

To install GraphQL MCP Toolkit for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-graphql):

```bash
npx -y @smithery/cli install mcp-graphql --client claude
```

### Installing Manually

It can be manually installed to Claude:

```json
{
  "mcpServers": {
    "mcp-graphql": {
      "command": "npx",
      "args": ["mcp-graphql", "--endpoint", "http://localhost:3000/graphql"]
    }
  }
}
```

## Security Considerations

Mutations are disabled by default as a security measure to prevent an LLM from modifying your database or service data. Consider carefully before enabling mutations in production environments.

## Customize for your own server

This is a very generic implementation where it allows for complete introspection and for your users to do whatever (including mutations). If you need a more specific implementation I'd suggest to just create your own MCP and lock down tool calling for clients to only input specific query fields and/or variables. You can use this as a reference.

```

--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------

```json
{
    "formatter": {
      "enabled": true,
      "indentStyle": "tab",
      "indentWidth": 2
    }
  }
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
      "target": "ES2022",
      "module": "Node16",
      "moduleResolution": "Node16",
      "outDir": "dist",
      "rootDir": "src",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "declaration": true
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/src/helpers/package.ts:
--------------------------------------------------------------------------------

```typescript
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Current package version so I only need to update it in one place
const { version } = JSON.parse(
	readFileSync(join(__dirname, "../../package.json"), "utf-8"),
);

export function getVersion() {
	return version;
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Use a Node.js base image that supports bun installation
FROM node:18-alpine as builder

# Install bun
RUN npm install -g bun

# Set the working directory
WORKDIR /app

# Copy package and lock files
COPY package.json bun.lockb ./

# Install dependencies using bun
RUN bun install

# Copy the rest of the application
COPY . .

# Build the application
RUN bun run build

# Create a release image
FROM node:18-alpine

# Install bun
RUN npm install -g bun

# Set the working directory
WORKDIR /app

# Copy built files and package.json from builder
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/package.json /app/

# Configure to use STDIO
ENV NODE_ENV=production
ENV MCP_TRANSPORT=stdio

# Run the server with STDIO transport
CMD ["node", "/app/dist/index.js"]

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - endpoint
    properties:
      headers:
        type: string
        description: Optional JSON string of headers to send with the GraphQL requests.
      endpoint:
        type: string
        description: The GraphQL server endpoint URL.
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({ 
      command: 'node', 
      args: ['/app/dist/index.js', '--endpoint', config.endpoint].concat(config.headers ? ['--headers', config.headers] : []), 
      env: { MCP_TRANSPORT: 'stdio', NODE_ENV: 'production' } 
    })

```

--------------------------------------------------------------------------------
/src/helpers/header.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Parse and merge headers from various sources
 * @param configHeaders - Default headers from configuration
 * @param inputHeaders - Headers provided by the user (string or object)
 * @returns Merged headers object
 */
export function parseAndMergeHeaders(
  configHeaders: Record<string, string>,
  inputHeaders?: string | Record<string, string>
): Record<string, string> {
  // Parse headers if they're provided as a string
  let parsedHeaders: Record<string, string> = {};

  if (typeof inputHeaders === "string") {
    try {
      parsedHeaders = JSON.parse(inputHeaders);
    } catch (e) {
      throw new Error(`Invalid headers JSON: ${e}`);
    }
  } else if (inputHeaders) {
    parsedHeaders = inputHeaders;
  }

  // Merge with config headers (config headers are overridden by input headers)
  return { ...configHeaders, ...parsedHeaders };
}

```

--------------------------------------------------------------------------------
/src/helpers/deprecation.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Helper module for handling deprecation warnings
 */

/**
 * Check for deprecated command line arguments and output warnings
 */
export function checkDeprecatedArguments(): void {
	const deprecatedArgs = [
		"--endpoint",
		"--headers",
		"--enable-mutations",
		"--name",
		"--schema",
	];
	const usedDeprecatedArgs = deprecatedArgs.filter((arg) =>
		process.argv.includes(arg),
	);

	if (usedDeprecatedArgs.length > 0) {
		console.error(
			`WARNING: Deprecated command line arguments detected: ${usedDeprecatedArgs.join(", ")}`,
		);
		console.error(
			"As of version 1.0.0, command line arguments have been replaced with environment variables.",
		);
		console.error("Please use environment variables instead. For example:");
		console.error(
			"  Instead of: npx mcp-graphql --endpoint http://example.com/graphql",
		);
		console.error("  Use: ENDPOINT=http://example.com/graphql npx mcp-graphql");
		console.error("");
	}
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@kailashg101/graphql-mcp-toolkit",
  "version": "1.0.2",
  "description": "MCP server for connecting to GraphQL servers",
  "module": "index.ts",
  "type": "module",
  "author": "Kailash G",
  "license": "MIT",
  "publishConfig": {
    "access": "public"
  },
  "bin": {
    "mcp-graphql": "./dist/index.js"
  },
  "files": [
    "dist"
  ],
  "devDependencies": {
    "@graphql-tools/schema": "^10.0.21",
    "@standard-schema/spec": "^1.0.0",
    "@types/bun": "latest",
    "@types/yargs": "17.0.33",
    "graphql-yoga": "^5.13.1",
    "typescript": "5.8.2"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.6.1",
    "graphql": "^16.10.0",
    "yargs": "17.7.2",
    "zod": "3.24.2",
    "zod-to-json-schema": "3.24.3"
  },
  "scripts": {
    "dev": "bun --watch src/index.ts",
    "build": "bun build src/index.ts --outdir dist --target node && bun -e \"require('fs').chmodSync('dist/index.js', '755')\"",
    "start": "bun run dist/index.js"
  },
  "packageManager": "[email protected]"
}

```

--------------------------------------------------------------------------------
/src/helpers/introspection.ts:
--------------------------------------------------------------------------------

```typescript
import { buildClientSchema, getIntrospectionQuery, printSchema } from "graphql";
import { readFile } from "node:fs/promises";
/**
 * Introspect a GraphQL endpoint and return the schema as the GraphQL SDL
 * @param endpoint - The endpoint to introspect
 * @returns The schema
 */
export async function introspectEndpoint(
	endpoint: string,
	headers?: Record<string, string>,
) {
	const response = await fetch(endpoint, {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			...headers,
		},
		body: JSON.stringify({
			query: getIntrospectionQuery(),
		}),
	});

	if (!response.ok) {
		throw new Error(`GraphQL request failed: ${response.statusText}`);
	}

	const responseJson = await response.json();
	// Transform to a schema object
	const schema = buildClientSchema(responseJson.data);

	// Print the schema SDL
	return printSchema(schema);
}

/**
 * Introspect a local GraphQL schema file and return the schema as the GraphQL SDL
 * @param path - The path to the local schema file
 * @returns The schema
 */
export async function introspectLocalSchema(path: string) {
	const schema = await readFile(path, "utf8");
	return schema;
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { parse } from "graphql/language";
import { z } from "zod";
import { checkDeprecatedArguments } from "./helpers/deprecation.js";
import { parseAndMergeHeaders } from "./helpers/header.js";
import {
  introspectEndpoint,
  introspectLocalSchema,
} from "./helpers/introspection.js";
import { getVersion } from "./helpers/package.js";

// Check for deprecated command line arguments
checkDeprecatedArguments();

const EnvSchema = z.object({
  NAME: z.string().default("mcp-graphql"),
  ENDPOINT: z.string().url().default("http://localhost:4000/graphql"),
  ALLOW_MUTATIONS: z.boolean().default(false),
  HEADERS: z
    .string()
    .default("{}")
    .transform((val) => {
      try {
        return JSON.parse(val);
      } catch (e) {
        throw new Error("HEADERS must be a valid JSON string");
      }
    }),
  SCHEMA: z.string().optional(),
});

const env = EnvSchema.parse(process.env);

const server = new McpServer({
  name: env.NAME,
  version: getVersion(),
  description: `GraphQL MCP server for ${env.ENDPOINT}`,
});

server.resource("graphql-schema", new URL(env.ENDPOINT).href, async (uri) => {
  try {
    let schema: string;
    if (env.SCHEMA) {
      schema = await introspectLocalSchema(env.SCHEMA);
    } else {
      schema = await introspectEndpoint(env.ENDPOINT, env.HEADERS);
    }

    return {
      contents: [
        {
          uri: uri.href,
          text: schema,
        },
      ],
    };
  } catch (error) {
    throw new Error(`Failed to get GraphQL schema: ${error}`);
  }
});

server.tool(
  "introspect-schema",
  "Introspect the GraphQL schema, use this tool before doing a query to get the schema information if you do not have it available as a resource already.",
  {
    endpoint: z
      .string()
      .url()
      .optional()
      .describe(
        `Optional: Override the default endpoint, the already used endpoint is: ${env.ENDPOINT}`
      ),
    headers: z
      .union([z.record(z.string()), z.string()])
      .optional()
      .describe(
        `Optional: Add additional headers, the already used headers are: ${JSON.stringify(
          env.HEADERS
        )}`
      ),
  },
  async ({ endpoint, headers }) => {
    try {
      let schema: string;
      if (env.SCHEMA) {
        schema = await introspectLocalSchema(env.SCHEMA);
      } else {
        const useEndpoint = endpoint || env.ENDPOINT;
        const useHeaders = parseAndMergeHeaders(env.HEADERS, headers);
        schema = await introspectEndpoint(useEndpoint, useHeaders);
      }

      return {
        content: [
          {
            type: "text",
            text: schema,
          },
        ],
      };
    } catch (error) {
      throw new Error(`Failed to introspect schema: ${error}`);
    }
  }
);

server.tool(
  "query-graphql",
  "Query a GraphQL endpoint with the given query and variables",
  {
    query: z.string(),
    variables: z.string().optional(),
    endpoint: z
      .string()
      .url()
      .optional()
      .describe(
        `Optional: Override the default endpoint, the already used endpoint is: ${env.ENDPOINT}`
      ),
    headers: z
      .union([z.record(z.string()), z.string()])
      .optional()
      .describe(
        `Optional: Add additional headers, the already used headers are: ${JSON.stringify(
          env.HEADERS
        )}`
      ),
  },
  async ({ query, variables, endpoint, headers }) => {
    try {
      const parsedQuery = parse(query);

      // Check if the query is a mutation
      const isMutation = parsedQuery.definitions.some(
        (def) =>
          def.kind === "OperationDefinition" && def.operation === "mutation"
      );

      if (isMutation && !env.ALLOW_MUTATIONS) {
        return {
          isError: true,
          content: [
            {
              type: "text",
              text: "Mutations are not allowed unless you enable them in the configuration. Please use a query operation instead.",
            },
          ],
        };
      }
    } catch (error) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Invalid GraphQL query: ${error}`,
          },
        ],
      };
    }

    try {
      const useEndpoint = endpoint || env.ENDPOINT;
      const useHeaders = parseAndMergeHeaders(env.HEADERS, headers);

      const response = await fetch(useEndpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...useHeaders,
        },
        body: JSON.stringify({
          query,
          variables,
        }),
      });

      if (!response.ok) {
        throw new Error(`GraphQL request failed: ${response.statusText}`);
      }

      const data = await response.json();

      if (data.errors && data.errors.length > 0) {
        // Contains GraphQL errors
        return {
          isError: true,
          content: [
            {
              type: "text",
              text: `The GraphQL response has errors, please fix the query: ${JSON.stringify(
                data,
                null,
                2
              )}`,
            },
          ],
        };
      }

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(data, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new Error(`Failed to execute GraphQL query: ${error}`);
    }
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);

  console.error(
    `Started graphql mcp server ${env.NAME} for endpoint: ${env.ENDPOINT}`
  );
}

main().catch((error) => {
  console.error(`Fatal error in main(): ${error}`);
  process.exit(1);
});

```

--------------------------------------------------------------------------------
/dev/graphql.ts:
--------------------------------------------------------------------------------

```typescript
import { makeExecutableSchema } from "@graphql-tools/schema";
import { createYoga } from "graphql-yoga";
import fs from "node:fs";

/**
 * Simple GraphQL server implementation for testing purposes
 *
 * This is a simple GraphQL server implementation for testing purposes.
 * It is not intended to be used in production.
 *
 * It is used to test the GraphQL schema and resolvers.
 *
 */

// Define types
interface User {
	id: string;
	name: string;
	email: string;
	createdAt: string;
	updatedAt: string | null;
}

interface Post {
	id: string;
	title: string;
	content: string;
	published: boolean;
	authorId: string;
	createdAt: string;
	updatedAt: string | null;
}

interface Comment {
	id: string;
	text: string;
	postId: string;
	authorId: string;
	createdAt: string;
}

interface CreateUserInput {
	name: string;
	email: string;
}

interface UpdateUserInput {
	name?: string;
	email?: string;
}

interface CreatePostInput {
	title: string;
	content: string;
	published?: boolean;
	authorId: string;
}

interface AddCommentInput {
	text: string;
	postId: string;
	authorId: string;
}

// Define resolver context type
type ResolverContext = Record<string, never>;

// Read schema from file
const typeDefs = fs.readFileSync("./schema-simple.graphql", "utf-8");

// Create mock data
const users: User[] = [
	{
		id: "1",
		name: "John Doe",
		email: "[email protected]",
		createdAt: new Date().toISOString(),
		updatedAt: null,
	},
	{
		id: "2",
		name: "Jane Smith",
		email: "[email protected]",
		createdAt: new Date().toISOString(),
		updatedAt: null,
	},
	{
		id: "3",
		name: "Bob Johnson",
		email: "[email protected]",
		createdAt: new Date().toISOString(),
		updatedAt: null,
	},
];

const posts: Post[] = [
	{
		id: "1",
		title: "First Post",
		content: "This is my first post",
		published: true,
		authorId: "1",
		createdAt: new Date().toISOString(),
		updatedAt: null,
	},
	{
		id: "2",
		title: "GraphQL is Awesome",
		content: "Here is why GraphQL is better than REST",
		published: true,
		authorId: "1",
		createdAt: new Date().toISOString(),
		updatedAt: null,
	},
	{
		id: "3",
		title: "Yoga Tutorial",
		content: "Learn how to use GraphQL Yoga",
		published: false,
		authorId: "2",
		createdAt: new Date().toISOString(),
		updatedAt: null,
	},
];

const comments: Comment[] = [
	{
		id: "1",
		text: "Great post!",
		postId: "1",
		authorId: "2",
		createdAt: new Date().toISOString(),
	},
	{
		id: "2",
		text: "I learned a lot",
		postId: "1",
		authorId: "3",
		createdAt: new Date().toISOString(),
	},
	{
		id: "3",
		text: "Looking forward to more content",
		postId: "2",
		authorId: "2",
		createdAt: new Date().toISOString(),
	},
];

// Define resolvers
const resolvers = {
	Query: {
		user: (
			_parent: unknown,
			{ id }: { id: string },
			_context: ResolverContext,
		) => users.find((user) => user.id === id),
		users: () => users,
		post: (
			_parent: unknown,
			{ id }: { id: string },
			_context: ResolverContext,
		) => posts.find((post) => post.id === id),
		posts: () => posts,
		commentsByPost: (
			_parent: unknown,
			{ postId }: { postId: string },
			_context: ResolverContext,
		) => comments.filter((comment) => comment.postId === postId),
	},
	Mutation: {
		createUser: (
			_parent: unknown,
			{ input }: { input: CreateUserInput },
			_context: ResolverContext,
		) => {
			const newUser: User = {
				id: String(users.length + 1),
				name: input.name,
				email: input.email,
				createdAt: new Date().toISOString(),
				updatedAt: null,
			};
			users.push(newUser);
			return newUser;
		},
		updateUser: (
			_parent: unknown,
			{ id, input }: { id: string; input: UpdateUserInput },
			_context: ResolverContext,
		) => {
			const userIndex = users.findIndex((user) => user.id === id);
			if (userIndex === -1) throw new Error(`User with ID ${id} not found`);

			users[userIndex] = {
				...users[userIndex],
				...input,
				updatedAt: new Date().toISOString(),
			};

			return users[userIndex];
		},
		deleteUser: (
			_parent: unknown,
			{ id }: { id: string },
			_context: ResolverContext,
		) => {
			const userIndex = users.findIndex((user) => user.id === id);
			if (userIndex === -1) return false;

			users.splice(userIndex, 1);
			return true;
		},
		createPost: (
			_parent: unknown,
			{ input }: { input: CreatePostInput },
			_context: ResolverContext,
		) => {
			const newPost: Post = {
				id: String(posts.length + 1),
				title: input.title,
				content: input.content,
				published: input.published ?? false,
				authorId: input.authorId,
				createdAt: new Date().toISOString(),
				updatedAt: null,
			};
			posts.push(newPost);
			return newPost;
		},
		addComment: (
			_parent: unknown,
			{ input }: { input: AddCommentInput },
			_context: ResolverContext,
		) => {
			const newComment: Comment = {
				id: String(comments.length + 1),
				text: input.text,
				postId: input.postId,
				authorId: input.authorId,
				createdAt: new Date().toISOString(),
			};
			comments.push(newComment);
			return newComment;
		},
	},
	User: {
		posts: (parent: User) =>
			posts.filter((post) => post.authorId === parent.id),
		comments: (parent: User) =>
			comments.filter((comment) => comment.authorId === parent.id),
	},
	Post: {
		author: (parent: Post) => users.find((user) => user.id === parent.authorId),
		comments: (parent: Post) =>
			comments.filter((comment) => comment.postId === parent.id),
	},
	Comment: {
		post: (parent: Comment) => posts.find((post) => post.id === parent.postId),
		author: (parent: Comment) =>
			users.find((user) => user.id === parent.authorId),
	},
};

// Create executable schema
const schema = makeExecutableSchema({
	typeDefs,
	resolvers,
});

// Create Yoga instance
const yoga = createYoga({ schema });

// Start server with proper request handler
const server = Bun.serve({
	port: 4000,
	fetch: (request) => {
		// Add dev logger for incoming requests
		console.log(
			`[${new Date().toISOString()}] Incoming request: ${request.method} ${
				request.url
			}`,
		);
		return yoga.fetch(request);
	},
});

console.info(
	`GraphQL server is running on ${new URL(
		yoga.graphqlEndpoint,
		`http://${server.hostname}:${server.port}`,
	)}`,
);

```