@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.
- package/README.md +199 -0
- 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
|
+
}
|