Skip to content

Commit 17971b9

Browse files
committed
feat: allow using a local schema for introspection
This helps when an endpoint does not allow introspection but you have a schema ready for it.
1 parent 0bbeebe commit 17971b9

File tree

2 files changed

+61
-38
lines changed

2 files changed

+61
-38
lines changed

src/helpers/introspection.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { buildClientSchema, getIntrospectionQuery, printSchema } from "graphql";
2+
import { readFile } from "node:fs/promises";
3+
/**
4+
* Introspect a GraphQL endpoint and return the schema as the GraphQL SDL
5+
* @param endpoint - The endpoint to introspect
6+
* @returns The schema
7+
*/
8+
export async function introspectEndpoint(endpoint: string) {
9+
const response = await fetch(endpoint, {
10+
method: "POST",
11+
headers: {
12+
"Content-Type": "application/json",
13+
},
14+
body: JSON.stringify({
15+
query: getIntrospectionQuery(),
16+
}),
17+
});
18+
19+
if (!response.ok) {
20+
throw new Error(`GraphQL request failed: ${response.statusText}`);
21+
}
22+
23+
const responseJson = await response.json();
24+
// Transform to a schema object
25+
const schema = buildClientSchema(responseJson.data);
26+
27+
// Print the schema SDL
28+
return printSchema(schema);
29+
}
30+
31+
/**
32+
* Introspect a local GraphQL schema file and return the schema as the GraphQL SDL
33+
* @param path - The path to the local schema file
34+
* @returns The schema
35+
*/
36+
export async function introspectLocalSchema(path: string) {
37+
const schema = await readFile(path, "utf8");
38+
return schema;
39+
}

src/index.ts

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
44
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
55
import { parse } from "graphql/language";
6-
import {
7-
buildClientSchema,
8-
getIntrospectionQuery,
9-
printSchema,
10-
} from "graphql/utilities";
116
import yargs from "yargs";
127
import { hideBin } from "yargs/helpers";
138
import { z } from "zod";
9+
import {
10+
introspectEndpoint,
11+
introspectLocalSchema,
12+
} from "./helpers/introspection.js";
1413
import { getVersion } from "./helpers/package.js" with { type: "macro" };
1514

1615
const graphQLSchema = z.object({
@@ -23,6 +22,7 @@ const ConfigSchema = z.object({
2322
allowMutations: z.boolean().default(false),
2423
endpoint: z.string().url().default("http://localhost:4000/graphql"),
2524
headers: z.record(z.string()).default({}),
25+
schema: z.string().optional(),
2626
});
2727

2828
type Config = z.infer<typeof ConfigSchema>;
@@ -49,6 +49,10 @@ function parseArgs(): Config {
4949
description: "JSON string of headers to send with requests",
5050
default: "{}",
5151
})
52+
.option("schema", {
53+
type: "string",
54+
description: "Path to a local GraphQL schema file",
55+
})
5256
.help()
5357
.parseSync();
5458

@@ -85,33 +89,23 @@ server.resource(
8589
new URL(config.endpoint).href,
8690
async (uri) => {
8791
try {
88-
const response = await fetch(uri, {
89-
method: "POST",
90-
headers: {
91-
"Content-Type": "application/json",
92-
},
93-
body: JSON.stringify({
94-
query: getIntrospectionQuery(),
95-
}),
96-
});
97-
98-
if (!response.ok) {
99-
throw new Error(`GraphQL request failed: ${response.statusText}`);
92+
let schema: string;
93+
if (config.schema) {
94+
schema = await introspectLocalSchema(config.schema);
95+
} else {
96+
schema = await introspectEndpoint(config.endpoint);
10097
}
10198

102-
const responseJson = await response.json();
103-
const schema = buildClientSchema(responseJson.data);
104-
10599
return {
106100
contents: [
107101
{
108102
uri: uri.href,
109-
text: printSchema(schema),
103+
text: schema,
110104
},
111105
],
112106
};
113107
} catch (error) {
114-
throw new Error(`Failed to fetch GraphQL schema: ${error}`);
108+
throw new Error(`Failed to get GraphQL schema: ${error}`);
115109
}
116110
},
117111
);
@@ -122,28 +116,18 @@ server.tool(
122116
{},
123117
async () => {
124118
try {
125-
const response = await fetch(config.endpoint, {
126-
method: "POST",
127-
headers: {
128-
"Content-Type": "application/json",
129-
},
130-
body: JSON.stringify({
131-
query: getIntrospectionQuery(),
132-
}),
133-
});
134-
135-
if (!response.ok) {
136-
throw new Error(`GraphQL request failed: ${response.statusText}`);
119+
let schema: string;
120+
if (config.schema) {
121+
schema = await introspectLocalSchema(config.schema);
122+
} else {
123+
schema = await introspectEndpoint(config.endpoint);
137124
}
138125

139-
const responseJson = await response.json();
140-
const schema = buildClientSchema(responseJson.data);
141-
142126
return {
143127
content: [
144128
{
145129
type: "text",
146-
text: printSchema(schema),
130+
text: schema,
147131
},
148132
],
149133
};

0 commit comments

Comments
 (0)