npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cubicecho/graphql-mcp

v0.1.1

Published

Turn a GraphQL schema into a Model Context Protocol (MCP) server: each query/mutation becomes a tool, runnable side-by-side with your GraphQL server.

Downloads

355

Readme

@cubicecho/graphql-mcp

Turn a GraphQL schema into a Model Context Protocol server. Point it at a GraphQLSchema and every Query/Mutation root field becomes an MCP tool, described from your SDL — field and argument descriptions, types — so an AI can discover and call your API.

It's a thin wrapper meant to run side-by-side with your GraphQL server: mount the returned HTTP handler on a route in the same app, or run it as its own process and forward to a remote GraphQL endpoint.

Install

npm install @cubicecho/graphql-mcp
# peer deps
npm install @modelcontextprotocol/sdk graphql

Quick start

Run the MCP endpoint beside your GraphQL endpoint in the same Express app:

import express from 'express';
import { createHttpHandler } from '@cubicecho/graphql-mcp';
import { schema } from './schema.js'; // your executable GraphQLSchema

const app = express();
app.use(express.json());

app.post('/graphql', /* your existing GraphQL handler */);
app.post('/mcp', createHttpHandler({ schema })); // ← the MCP server

app.listen(4000);

Given the schema from the brief:

"A user in the system"
type User {
  "The unique id for the user, a UUID"
  id: String!
  "The list of todos this user has created."
  todos: [Todo!]!
}

"A todo entity, able to be marked as completed"
type Todo {
  "The unique id for the todo, a UUID"
  id: String!
  "If the todo is complete or not."
  completed: Boolean!
  "A textual description of what the todo is."
  description: String!
  "The user who created this todo."
  createdBy: User!
}

type Query {
  todo(id: String!): Todo
  todos: [Todo!]!
}

type Mutation {
  "Create a new todo for a user."
  createTodo(input: CreateTodoInput!): Todo!
  setCompleted(id: String!, completed: Boolean!): Todo
}

…you get four tools — todo, todos, createTodo, setCompleted — each with an input schema derived from the field's arguments and a description built from the SDL docstrings. Calling createTodo runs the equivalent of:

mutation createTodo($input: CreateTodoInput!) {
  createTodo(input: $input) { id completed description __typename }
}

Concepts

| Export | What it does | |---|---| | createHttpHandler(options) | Returns an Express/Node (req, res) handler serving the tools over the MCP Streamable HTTP transport. A fresh server is created per request. | | createMcpServer(options) | Returns a single McpServer with all tools registered. Use for stdio or one long-lived connection. | | createServerFactory(options) | Builds the tool descriptors once and returns a () => McpServer factory. | | createLocalExecutor(schema, opts?) | Executor that runs operations in-process via graphql-js (the default). | | createHttpExecutor(endpoint, opts?) | Executor that forwards operations to a remote GraphQL HTTP endpoint. | | buildTools(schema, opts?) | The pure core: schema → ToolDescriptor[] (no SDK, no executor). |

Lower-level helpers (buildOperation, buildSelectionSet, argsToZodShape, registerGraphqlTools) and all types are exported too.

How fields become tools

  • Both queries and mutations become tools. MCP has no query/mutation distinction; queries are annotated readOnlyHint, mutations destructiveHint.
  • Arguments → input schema. Each field's args are converted to a Zod schema (the MCP input-schema format): non-null args are required, scalars/enums/lists/ input-objects map across, custom scalars fall back to an opaque value.
  • Return type → selection set. A selection set is auto-generated: every scalar/enum leaf plus nested objects up to selectionDepth (default 2), always including __typename. Fields that require arguments and cyclic types are skipped.
  • Descriptions come from the SDL — the field docstring, its signature, and a per-argument list.

Choosing where GraphQL runs

The single seam is the executor. The default runs in-process against the schema you pass:

import { createMcpServer, createLocalExecutor } from '@cubicecho/graphql-mcp';

const server = createMcpServer({
  schema,
  executor: createLocalExecutor(schema, { rootValue, contextValue }),
});

To run the MCP server as a separate process and forward to a GraphQL HTTP server:

import { createHttpHandler, createHttpExecutor } from '@cubicecho/graphql-mcp';

const handler = createHttpHandler({
  schema, // used only to derive the tools
  executor: createHttpExecutor('http://localhost:4000/graphql', {
    // forward auth derived from the per-request context
    headers: (ctx) => ({ authorization: (ctx as { token: string }).token }),
  }),
});

Per-request context (auth)

Derive the GraphQL context from the incoming HTTP request — e.g. to forward an auth token into resolvers or the forwarding executor:

const handler = createHttpHandler({
  schema,
  contextFromRequest: (req) => ({ token: req.headers.authorization }),
});

For non-HTTP setups, pass context as a static value or a factory of the MCP request extra.

Custom tools & overrides

Add bespoke tools, or override a generated one by reusing its name (the surface stays the same; only that tool's behaviour changes):

const server = createMcpServer({
  schema,
  tools: [
    {
      name: 'createTodo', // overrides the generated createTodo tool
      description: 'Create a todo, with extra validation.',
      inputSchema: { description: z.string().min(1) },
      handler: async (args) => ({
        content: [{ type: 'text', text: `created: ${args.description}` }],
      }),
    },
  ],
});

Other HTTP servers

createHttpHandler returns a framework-agnostic handler: it only needs a Node IncomingMessage with a parsed JSON body on req.body (as express.json() provides) and a Node ServerResponse. Express is assumed for the MVP; adapters for other frameworks/runtimes are tracked in TODO.md.

Development

npm test                # node --test (built-in runner, type stripping)
npm run coverage        # node --test with built-in coverage + thresholds
npm run typecheck       # tsc --noEmit
npm run typecheck:tests # type-check the test files too
npm run build           # compile to dist/
npm run check           # biome lint + format check

The source uses .ts import specifiers so it runs unbuilt under Node's type stripping; tsc rewrites them to .js on build. Requires Node ≥ 22 and TypeScript ≥ 5.7.

Commits follow Conventional Commits and drive automated releases: pushes to main run the Test workflow, and on success the Release workflow runs semantic-release to version, update the changelog, publish to npm, and tag a GitHub release.