@cubicecho/graphql-mcp 0.1.0

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.
Files changed (2) hide show
  1. package/README.md +199 -0
  2. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # @cubicecho/graphql-mcp
2
+
3
+ Turn a GraphQL schema into a [Model Context Protocol](https://modelcontextprotocol.io/)
4
+ server. Point it at a `GraphQLSchema` and every `Query`/`Mutation` root field
5
+ becomes an MCP **tool**, described from your SDL — field and argument
6
+ descriptions, types — so an AI can discover and call your API.
7
+
8
+ It's a thin wrapper meant to run **side-by-side** with your GraphQL server:
9
+ mount the returned HTTP handler on a route in the same app, or run it as its own
10
+ process and forward to a remote GraphQL endpoint.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @cubicecho/graphql-mcp
16
+ # peer deps
17
+ npm install @modelcontextprotocol/sdk graphql
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ Run the MCP endpoint beside your GraphQL endpoint in the same Express app:
23
+
24
+ ```ts
25
+ import express from 'express';
26
+ import { createHttpHandler } from '@cubicecho/graphql-mcp';
27
+ import { schema } from './schema.js'; // your executable GraphQLSchema
28
+
29
+ const app = express();
30
+ app.use(express.json());
31
+
32
+ app.post('/graphql', /* your existing GraphQL handler */);
33
+ app.post('/mcp', createHttpHandler({ schema })); // ← the MCP server
34
+
35
+ app.listen(4000);
36
+ ```
37
+
38
+ Given the schema from the brief:
39
+
40
+ ```graphql
41
+ "A user in the system"
42
+ type User {
43
+ "The unique id for the user, a UUID"
44
+ id: String!
45
+ "The list of todos this user has created."
46
+ todos: [Todo!]!
47
+ }
48
+
49
+ "A todo entity, able to be marked as completed"
50
+ type Todo {
51
+ "The unique id for the todo, a UUID"
52
+ id: String!
53
+ "If the todo is complete or not."
54
+ completed: Boolean!
55
+ "A textual description of what the todo is."
56
+ description: String!
57
+ "The user who created this todo."
58
+ createdBy: User!
59
+ }
60
+
61
+ type Query {
62
+ todo(id: String!): Todo
63
+ todos: [Todo!]!
64
+ }
65
+
66
+ type Mutation {
67
+ "Create a new todo for a user."
68
+ createTodo(input: CreateTodoInput!): Todo!
69
+ setCompleted(id: String!, completed: Boolean!): Todo
70
+ }
71
+ ```
72
+
73
+ …you get four tools — `todo`, `todos`, `createTodo`, `setCompleted` — each with
74
+ an input schema derived from the field's arguments and a description built from
75
+ the SDL docstrings. Calling `createTodo` runs the equivalent of:
76
+
77
+ ```graphql
78
+ mutation createTodo($input: CreateTodoInput!) {
79
+ createTodo(input: $input) { id completed description __typename }
80
+ }
81
+ ```
82
+
83
+ ## Concepts
84
+
85
+ | Export | What it does |
86
+ |---|---|
87
+ | `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. |
88
+ | `createMcpServer(options)` | Returns a single `McpServer` with all tools registered. Use for stdio or one long-lived connection. |
89
+ | `createServerFactory(options)` | Builds the tool descriptors once and returns a `() => McpServer` factory. |
90
+ | `createLocalExecutor(schema, opts?)` | Executor that runs operations in-process via graphql-js (the default). |
91
+ | `createHttpExecutor(endpoint, opts?)` | Executor that forwards operations to a remote GraphQL HTTP endpoint. |
92
+ | `buildTools(schema, opts?)` | The pure core: schema → `ToolDescriptor[]` (no SDK, no executor). |
93
+
94
+ Lower-level helpers (`buildOperation`, `buildSelectionSet`, `argsToZodShape`,
95
+ `registerGraphqlTools`) and all types are exported too.
96
+
97
+ ## How fields become tools
98
+
99
+ - **Both queries and mutations become tools.** MCP has no query/mutation
100
+ distinction; queries are annotated `readOnlyHint`, mutations `destructiveHint`.
101
+ - **Arguments → input schema.** Each field's args are converted to a Zod schema
102
+ (the MCP input-schema format): non-null args are required, scalars/enums/lists/
103
+ input-objects map across, custom scalars fall back to an opaque value.
104
+ - **Return type → selection set.** A selection set is auto-generated: every
105
+ scalar/enum leaf plus nested objects up to `selectionDepth` (default 2), always
106
+ including `__typename`. Fields that require arguments and cyclic types are skipped.
107
+ - **Descriptions come from the SDL** — the field docstring, its signature, and a
108
+ per-argument list.
109
+
110
+ ## Choosing where GraphQL runs
111
+
112
+ The single seam is the **executor**. The default runs in-process against the
113
+ schema you pass:
114
+
115
+ ```ts
116
+ import { createMcpServer, createLocalExecutor } from '@cubicecho/graphql-mcp';
117
+
118
+ const server = createMcpServer({
119
+ schema,
120
+ executor: createLocalExecutor(schema, { rootValue, contextValue }),
121
+ });
122
+ ```
123
+
124
+ To run the MCP server as a separate process and forward to a GraphQL HTTP server:
125
+
126
+ ```ts
127
+ import { createHttpHandler, createHttpExecutor } from '@cubicecho/graphql-mcp';
128
+
129
+ const handler = createHttpHandler({
130
+ schema, // used only to derive the tools
131
+ executor: createHttpExecutor('http://localhost:4000/graphql', {
132
+ // forward auth derived from the per-request context
133
+ headers: (ctx) => ({ authorization: (ctx as { token: string }).token }),
134
+ }),
135
+ });
136
+ ```
137
+
138
+ ## Per-request context (auth)
139
+
140
+ Derive the GraphQL context from the incoming HTTP request — e.g. to forward an
141
+ auth token into resolvers or the forwarding executor:
142
+
143
+ ```ts
144
+ const handler = createHttpHandler({
145
+ schema,
146
+ contextFromRequest: (req) => ({ token: req.headers.authorization }),
147
+ });
148
+ ```
149
+
150
+ For non-HTTP setups, pass `context` as a static value or a factory of the MCP
151
+ request `extra`.
152
+
153
+ ## Custom tools & overrides
154
+
155
+ Add bespoke tools, or override a generated one by reusing its name (the surface
156
+ stays the same; only that tool's behaviour changes):
157
+
158
+ ```ts
159
+ const server = createMcpServer({
160
+ schema,
161
+ tools: [
162
+ {
163
+ name: 'createTodo', // overrides the generated createTodo tool
164
+ description: 'Create a todo, with extra validation.',
165
+ inputSchema: { description: z.string().min(1) },
166
+ handler: async (args) => ({
167
+ content: [{ type: 'text', text: `created: ${args.description}` }],
168
+ }),
169
+ },
170
+ ],
171
+ });
172
+ ```
173
+
174
+ ## Other HTTP servers
175
+
176
+ `createHttpHandler` returns a framework-agnostic handler: it only needs a Node
177
+ `IncomingMessage` with a parsed JSON body on `req.body` (as `express.json()`
178
+ provides) and a Node `ServerResponse`. Express is assumed for the MVP; adapters
179
+ for other frameworks/runtimes are tracked in [TODO.md](./TODO.md).
180
+
181
+ ## Development
182
+
183
+ ```bash
184
+ npm test # node --test (built-in runner, type stripping)
185
+ npm run coverage # node --test with built-in coverage + thresholds
186
+ npm run typecheck # tsc --noEmit
187
+ npm run typecheck:tests # type-check the test files too
188
+ npm run build # compile to dist/
189
+ npm run check # biome lint + format check
190
+ ```
191
+
192
+ The source uses `.ts` import specifiers so it runs unbuilt under Node's type
193
+ stripping; `tsc` rewrites them to `.js` on build. Requires Node ≥ 22 and
194
+ TypeScript ≥ 5.7.
195
+
196
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org/) and
197
+ drive automated releases: pushes to `main` run the **Test** workflow, and on
198
+ success the **Release** workflow runs [semantic-release](https://semantic-release.gitbook.io/)
199
+ to version, update the changelog, publish to npm, and tag a GitHub release.
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@cubicecho/graphql-mcp",
3
+ "version": "0.1.0",
4
+ "description": "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.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/cubicecho/graphql-mcp.git"
9
+ },
10
+ "homepage": "https://github.com/cubicecho/graphql-mcp#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/cubicecho/graphql-mcp/issues"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "typecheck": "tsc --noEmit",
29
+ "typecheck:tests": "tsc -p tsconfig.tests.json",
30
+ "test": "node --test --experimental-strip-types \"src/**/*.test.ts\"",
31
+ "test:watch": "node --test --watch --experimental-strip-types \"src/**/*.test.ts\"",
32
+ "coverage": "node --test --experimental-strip-types --experimental-test-coverage --test-coverage-exclude=\"**/*.test.ts\" --test-coverage-lines=90 --test-coverage-branches=80 --test-coverage-functions=85 \"src/**/*.test.ts\"",
33
+ "format": "biome format --write .",
34
+ "lint": "biome lint .",
35
+ "check": "biome check ."
36
+ },
37
+ "peerDependencies": {
38
+ "@modelcontextprotocol/sdk": ">=1.12",
39
+ "graphql": ">=16"
40
+ },
41
+ "dependencies": {
42
+ "zod": "^3.25.0"
43
+ },
44
+ "devDependencies": {
45
+ "@biomejs/biome": "^2.5.0",
46
+ "@modelcontextprotocol/sdk": "^1.12.0",
47
+ "@semantic-release/changelog": "^6.0.3",
48
+ "@semantic-release/commit-analyzer": "^13.0.1",
49
+ "@semantic-release/git": "^10.0.1",
50
+ "@semantic-release/github": "^12.0.8",
51
+ "@semantic-release/npm": "^13.1.5",
52
+ "@semantic-release/release-notes-generator": "^14.1.1",
53
+ "@types/node": "^24.0.0",
54
+ "graphql": "^16.14.0",
55
+ "semantic-release": "^25.0.5",
56
+ "typescript": "^5.7.0"
57
+ }
58
+ }