vendure-mcp-graphql 1.3.1 → 1.5.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/dist/index.js +66 -12
- package/dist/src/client.d.ts +1 -1
- package/dist/src/client.js +12 -2
- package/dist/src/tools/graphql-operations.d.ts +24 -6
- package/dist/src/tools/graphql-operations.js +341 -14
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7,20 +7,22 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { config } from "dotenv";
|
|
10
|
-
import { resolve } from "path";
|
|
10
|
+
import { resolve, dirname } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
11
12
|
import { existsSync } from "fs";
|
|
12
|
-
// Load environment variables
|
|
13
|
+
// Load environment variables
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
15
|
// 1. Try to load from project root .env.local
|
|
14
|
-
config({ path: resolve(
|
|
16
|
+
config({ path: resolve(__dirname, "../../.env.local") });
|
|
15
17
|
// 2. Also try to load from specific vendure-api-key.env if it exists
|
|
16
|
-
const apiKeyPath = resolve(
|
|
18
|
+
const apiKeyPath = resolve(__dirname, "../../vendure-api-key.env");
|
|
17
19
|
if (existsSync(apiKeyPath)) {
|
|
18
20
|
const envConfig = config({ path: apiKeyPath });
|
|
19
21
|
if (envConfig.parsed?.VENDURE_API_KEY) {
|
|
20
22
|
process.env.VENDURE_API_KEY = envConfig.parsed.VENDURE_API_KEY;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
|
-
import { adminQuery, adminMutation, adminBatchMutation, getAdminSchema, getAdminOperations, shopQuery, shopMutation, shopBatchMutation, getShopSchema, getShopOperations, } from "./src/tools/index.js";
|
|
25
|
+
import { adminQuery, adminMutation, adminBatchMutation, getAdminSchema, getAdminOperations, shopQuery, shopMutation, shopBatchMutation, getShopSchema, getShopOperations, describeOperation, } from "./src/tools/index.js";
|
|
24
26
|
// Singleton pattern for shared resources
|
|
25
27
|
class SharedResources {
|
|
26
28
|
static instance = null;
|
|
@@ -47,7 +49,7 @@ class SharedResources {
|
|
|
47
49
|
}
|
|
48
50
|
const server = new Server({
|
|
49
51
|
name: "vendure-mcp-graphql",
|
|
50
|
-
version: "1.
|
|
52
|
+
version: "1.0.0",
|
|
51
53
|
}, {
|
|
52
54
|
capabilities: {
|
|
53
55
|
tools: {},
|
|
@@ -64,6 +66,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
64
66
|
properties: {
|
|
65
67
|
query: { type: "string", description: "The GraphQL query string" },
|
|
66
68
|
variables: { type: "object", description: "Optional variables" },
|
|
69
|
+
channelToken: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Optional channel token to set channel context (e.g., 'default-channel', 'test-channel')",
|
|
72
|
+
},
|
|
67
73
|
},
|
|
68
74
|
required: ["query"],
|
|
69
75
|
},
|
|
@@ -79,6 +85,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
79
85
|
description: "The GraphQL mutation string",
|
|
80
86
|
},
|
|
81
87
|
variables: { type: "object", description: "Optional variables" },
|
|
88
|
+
channelToken: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Optional channel token to set channel context (e.g., 'default-channel', 'test-channel')",
|
|
91
|
+
},
|
|
82
92
|
},
|
|
83
93
|
required: ["mutation"],
|
|
84
94
|
},
|
|
@@ -112,6 +122,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
112
122
|
type: "number",
|
|
113
123
|
description: "Max number of concurrent mutations. Default: 5.",
|
|
114
124
|
},
|
|
125
|
+
channelToken: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Optional channel token to set channel context (e.g., 'default-channel', 'test-channel')",
|
|
128
|
+
},
|
|
115
129
|
},
|
|
116
130
|
required: ["mutation", "ids"],
|
|
117
131
|
},
|
|
@@ -134,6 +148,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
134
148
|
properties: {
|
|
135
149
|
query: { type: "string", description: "The GraphQL query string" },
|
|
136
150
|
variables: { type: "object", description: "Optional variables" },
|
|
151
|
+
channelToken: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "Optional channel token to set channel context (e.g., 'default-channel', 'test-channel')",
|
|
154
|
+
},
|
|
137
155
|
},
|
|
138
156
|
required: ["query"],
|
|
139
157
|
},
|
|
@@ -149,6 +167,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
149
167
|
description: "The GraphQL mutation string",
|
|
150
168
|
},
|
|
151
169
|
variables: { type: "object", description: "Optional variables" },
|
|
170
|
+
channelToken: {
|
|
171
|
+
type: "string",
|
|
172
|
+
description: "Optional channel token to set channel context (e.g., 'default-channel', 'test-channel')",
|
|
173
|
+
},
|
|
152
174
|
},
|
|
153
175
|
required: ["mutation"],
|
|
154
176
|
},
|
|
@@ -182,6 +204,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
182
204
|
type: "number",
|
|
183
205
|
description: "Max number of concurrent mutations. Default: 5.",
|
|
184
206
|
},
|
|
207
|
+
channelToken: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "Optional channel token to set channel context (e.g., 'default-channel', 'test-channel')",
|
|
210
|
+
},
|
|
185
211
|
},
|
|
186
212
|
required: ["mutation", "ids"],
|
|
187
213
|
},
|
|
@@ -196,6 +222,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
196
222
|
description: "List all available queries and mutations for the Shop API with descriptions.",
|
|
197
223
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
198
224
|
},
|
|
225
|
+
{
|
|
226
|
+
name: "describe_operation",
|
|
227
|
+
description: "Describe a GraphQL query or mutation in detail: shows which API(s) it's available on, arguments with input types, and the return type with all its fields. Checks both Admin and Shop APIs by default.",
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
operation: {
|
|
232
|
+
type: "string",
|
|
233
|
+
description: "The name of the query or mutation to describe (e.g. productGroups, createProduct)",
|
|
234
|
+
},
|
|
235
|
+
api: {
|
|
236
|
+
type: "string",
|
|
237
|
+
enum: ["admin", "shop"],
|
|
238
|
+
description: "Optional: restrict lookup to a specific API. If omitted, checks both Admin and Shop APIs.",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
required: ["operation"],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
199
244
|
],
|
|
200
245
|
}));
|
|
201
246
|
// Handle tool calls
|
|
@@ -208,7 +253,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
208
253
|
content: [
|
|
209
254
|
{
|
|
210
255
|
type: "text",
|
|
211
|
-
text: JSON.stringify(await adminQuery(args?.query, args?.variables), null, 2),
|
|
256
|
+
text: JSON.stringify(await adminQuery(args?.query, args?.variables, args?.channelToken), null, 2),
|
|
212
257
|
},
|
|
213
258
|
],
|
|
214
259
|
};
|
|
@@ -217,7 +262,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
217
262
|
content: [
|
|
218
263
|
{
|
|
219
264
|
type: "text",
|
|
220
|
-
text: JSON.stringify(await adminMutation(args?.mutation, args?.variables), null, 2),
|
|
265
|
+
text: JSON.stringify(await adminMutation(args?.mutation, args?.variables, args?.channelToken), null, 2),
|
|
221
266
|
},
|
|
222
267
|
],
|
|
223
268
|
};
|
|
@@ -226,7 +271,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
226
271
|
content: [
|
|
227
272
|
{
|
|
228
273
|
type: "text",
|
|
229
|
-
text: JSON.stringify(await adminBatchMutation(args?.mutation, args?.ids, args?.variableName || "id", args?.extraVariables, args?.concurrency || 5), null, 2),
|
|
274
|
+
text: JSON.stringify(await adminBatchMutation(args?.mutation, args?.ids, args?.variableName || "id", args?.extraVariables, args?.concurrency || 5, args?.channelToken), null, 2),
|
|
230
275
|
},
|
|
231
276
|
],
|
|
232
277
|
};
|
|
@@ -253,7 +298,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
253
298
|
content: [
|
|
254
299
|
{
|
|
255
300
|
type: "text",
|
|
256
|
-
text: JSON.stringify(await shopQuery(args?.query, args?.variables), null, 2),
|
|
301
|
+
text: JSON.stringify(await shopQuery(args?.query, args?.variables, args?.channelToken), null, 2),
|
|
257
302
|
},
|
|
258
303
|
],
|
|
259
304
|
};
|
|
@@ -262,7 +307,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
262
307
|
content: [
|
|
263
308
|
{
|
|
264
309
|
type: "text",
|
|
265
|
-
text: JSON.stringify(await shopMutation(args?.mutation, args?.variables), null, 2),
|
|
310
|
+
text: JSON.stringify(await shopMutation(args?.mutation, args?.variables, args?.channelToken), null, 2),
|
|
266
311
|
},
|
|
267
312
|
],
|
|
268
313
|
};
|
|
@@ -271,7 +316,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
271
316
|
content: [
|
|
272
317
|
{
|
|
273
318
|
type: "text",
|
|
274
|
-
text: JSON.stringify(await shopBatchMutation(args?.mutation, args?.ids, args?.variableName || "id", args?.extraVariables, args?.concurrency || 5), null, 2),
|
|
319
|
+
text: JSON.stringify(await shopBatchMutation(args?.mutation, args?.ids, args?.variableName || "id", args?.extraVariables, args?.concurrency || 5, args?.channelToken), null, 2),
|
|
275
320
|
},
|
|
276
321
|
],
|
|
277
322
|
};
|
|
@@ -293,6 +338,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
293
338
|
},
|
|
294
339
|
],
|
|
295
340
|
};
|
|
341
|
+
case "describe_operation":
|
|
342
|
+
return {
|
|
343
|
+
content: [
|
|
344
|
+
{
|
|
345
|
+
type: "text",
|
|
346
|
+
text: JSON.stringify(await describeOperation(args?.operation, args?.api), null, 2),
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
};
|
|
296
350
|
default:
|
|
297
351
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
298
352
|
}
|
package/dist/src/client.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface GraphQLResponse<T = any> {
|
|
|
12
12
|
export declare class GraphQLClient {
|
|
13
13
|
private apiKey;
|
|
14
14
|
constructor(apiKey?: string | null);
|
|
15
|
-
request<T = any>(url: string, queryString: string, variables?: Record<string, any
|
|
15
|
+
request<T = any>(url: string, queryString: string, variables?: Record<string, any>, channelToken?: string): Promise<T>;
|
|
16
16
|
}
|
|
17
17
|
export declare function getClient(): GraphQLClient;
|
|
18
18
|
export declare function getAdminUrl(): string;
|
package/dist/src/client.js
CHANGED
|
@@ -7,13 +7,16 @@ export class GraphQLClient {
|
|
|
7
7
|
constructor(apiKey = null) {
|
|
8
8
|
this.apiKey = apiKey;
|
|
9
9
|
}
|
|
10
|
-
async request(url, queryString, variables) {
|
|
10
|
+
async request(url, queryString, variables, channelToken) {
|
|
11
11
|
const headers = {
|
|
12
12
|
"Content-Type": "application/json",
|
|
13
13
|
};
|
|
14
14
|
if (this.apiKey) {
|
|
15
15
|
headers["vendure-api-key"] = this.apiKey;
|
|
16
16
|
}
|
|
17
|
+
if (channelToken) {
|
|
18
|
+
headers["vendure-token"] = channelToken;
|
|
19
|
+
}
|
|
17
20
|
const response = await fetch(url, {
|
|
18
21
|
method: "POST",
|
|
19
22
|
headers,
|
|
@@ -33,8 +36,15 @@ export class GraphQLClient {
|
|
|
33
36
|
return result.data;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
// Singleton instance
|
|
40
|
+
let clientInstance = null;
|
|
36
41
|
export function getClient() {
|
|
37
|
-
|
|
42
|
+
if (clientInstance) {
|
|
43
|
+
return clientInstance;
|
|
44
|
+
}
|
|
45
|
+
const apiKey = process.env.VENDURE_API_KEY || null;
|
|
46
|
+
clientInstance = new GraphQLClient(apiKey);
|
|
47
|
+
return clientInstance;
|
|
38
48
|
}
|
|
39
49
|
export function getAdminUrl() {
|
|
40
50
|
return process.env.ADMIN_API_URL || "http://localhost:3000/admin-api";
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Execute a GraphQL query on the Admin API
|
|
3
3
|
*/
|
|
4
|
-
export declare function adminQuery(queryString: string, variables?: Record<string, any
|
|
4
|
+
export declare function adminQuery(queryString: string, variables?: Record<string, any>, channelToken?: string): Promise<any>;
|
|
5
5
|
/**
|
|
6
6
|
* Execute a GraphQL mutation on the Admin API
|
|
7
7
|
*/
|
|
8
|
-
export declare function adminMutation(mutationString: string, variables?: Record<string, any
|
|
8
|
+
export declare function adminMutation(mutationString: string, variables?: Record<string, any>, channelToken?: string): Promise<any>;
|
|
9
9
|
/**
|
|
10
10
|
* Execute a GraphQL query on the Shop API
|
|
11
11
|
*/
|
|
12
|
-
export declare function shopQuery(queryString: string, variables?: Record<string, any
|
|
12
|
+
export declare function shopQuery(queryString: string, variables?: Record<string, any>, channelToken?: string): Promise<any>;
|
|
13
13
|
/**
|
|
14
14
|
* Execute a GraphQL mutation on the Shop API
|
|
15
15
|
*/
|
|
16
|
-
export declare function shopMutation(mutationString: string, variables?: Record<string, any
|
|
16
|
+
export declare function shopMutation(mutationString: string, variables?: Record<string, any>, channelToken?: string): Promise<any>;
|
|
17
17
|
/**
|
|
18
18
|
* Fetch the Admin GraphQL schema introspection
|
|
19
19
|
*/
|
|
@@ -22,6 +22,14 @@ export declare function getAdminSchema(): Promise<any>;
|
|
|
22
22
|
* Fetch the Shop GraphQL schema introspection
|
|
23
23
|
*/
|
|
24
24
|
export declare function getShopSchema(): Promise<any>;
|
|
25
|
+
/**
|
|
26
|
+
* Fetch details for a single type from the Admin API schema
|
|
27
|
+
*/
|
|
28
|
+
export declare function getAdminTypeDetails(name: string): Promise<any>;
|
|
29
|
+
/**
|
|
30
|
+
* Fetch details for a single type from the Shop API schema
|
|
31
|
+
*/
|
|
32
|
+
export declare function getShopTypeDetails(name: string): Promise<any>;
|
|
25
33
|
/**
|
|
26
34
|
* Fetch a summary of available queries and mutations for the Admin API
|
|
27
35
|
*/
|
|
@@ -30,6 +38,16 @@ export declare function getAdminOperations(): Promise<any>;
|
|
|
30
38
|
* Fetch a summary of available queries and mutations for the Shop API
|
|
31
39
|
*/
|
|
32
40
|
export declare function getShopOperations(): Promise<any>;
|
|
41
|
+
/**
|
|
42
|
+
* Describe a GraphQL operation (query or mutation) with full details.
|
|
43
|
+
*
|
|
44
|
+
* Checks both Admin and Shop APIs (or just one if `api` is specified).
|
|
45
|
+
* Returns the operation's arguments with input types, and its return type with fields.
|
|
46
|
+
*
|
|
47
|
+
* @param operationName - The name of the query or mutation
|
|
48
|
+
* @param api - Optional: restrict to "admin" or "shop" only
|
|
49
|
+
*/
|
|
50
|
+
export declare function describeOperation(operationName: string, api?: "admin" | "shop"): Promise<any>;
|
|
33
51
|
export interface BatchResult {
|
|
34
52
|
total: number;
|
|
35
53
|
succeeded: number;
|
|
@@ -45,9 +63,9 @@ export interface BatchResult {
|
|
|
45
63
|
* Execute a GraphQL mutation in batch for multiple IDs on the Admin API.
|
|
46
64
|
* Runs mutations concurrently with configurable concurrency.
|
|
47
65
|
*/
|
|
48
|
-
export declare function adminBatchMutation(mutationString: string, ids: string[], variableName?: string, extraVariables?: Record<string, any>, concurrency?: number): Promise<BatchResult>;
|
|
66
|
+
export declare function adminBatchMutation(mutationString: string, ids: string[], variableName?: string, extraVariables?: Record<string, any>, concurrency?: number, channelToken?: string): Promise<BatchResult>;
|
|
49
67
|
/**
|
|
50
68
|
* Execute a GraphQL mutation in batch for multiple IDs on the Shop API.
|
|
51
69
|
* Runs mutations concurrently with configurable concurrency.
|
|
52
70
|
*/
|
|
53
|
-
export declare function shopBatchMutation(mutationString: string, ids: string[], variableName?: string, extraVariables?: Record<string, any>, concurrency?: number): Promise<BatchResult>;
|
|
71
|
+
export declare function shopBatchMutation(mutationString: string, ids: string[], variableName?: string, extraVariables?: Record<string, any>, concurrency?: number, channelToken?: string): Promise<BatchResult>;
|
|
@@ -2,30 +2,30 @@ import { getClient, getAdminUrl, getShopUrl } from "../client.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Execute a GraphQL query on the Admin API
|
|
4
4
|
*/
|
|
5
|
-
export async function adminQuery(queryString, variables) {
|
|
5
|
+
export async function adminQuery(queryString, variables, channelToken) {
|
|
6
6
|
const client = getClient();
|
|
7
|
-
return client.request(getAdminUrl(), queryString, variables);
|
|
7
|
+
return client.request(getAdminUrl(), queryString, variables, channelToken);
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Execute a GraphQL mutation on the Admin API
|
|
11
11
|
*/
|
|
12
|
-
export async function adminMutation(mutationString, variables) {
|
|
12
|
+
export async function adminMutation(mutationString, variables, channelToken) {
|
|
13
13
|
const client = getClient();
|
|
14
|
-
return client.request(getAdminUrl(), mutationString, variables);
|
|
14
|
+
return client.request(getAdminUrl(), mutationString, variables, channelToken);
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Execute a GraphQL query on the Shop API
|
|
18
18
|
*/
|
|
19
|
-
export async function shopQuery(queryString, variables) {
|
|
19
|
+
export async function shopQuery(queryString, variables, channelToken) {
|
|
20
20
|
const client = getClient();
|
|
21
|
-
return client.request(getShopUrl(), queryString, variables);
|
|
21
|
+
return client.request(getShopUrl(), queryString, variables, channelToken);
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Execute a GraphQL mutation on the Shop API
|
|
25
25
|
*/
|
|
26
|
-
export async function shopMutation(mutationString, variables) {
|
|
26
|
+
export async function shopMutation(mutationString, variables, channelToken) {
|
|
27
27
|
const client = getClient();
|
|
28
|
-
return client.request(getShopUrl(), mutationString, variables);
|
|
28
|
+
return client.request(getShopUrl(), mutationString, variables, channelToken);
|
|
29
29
|
}
|
|
30
30
|
const introspectionQuery = `
|
|
31
31
|
query IntrospectionQuery {
|
|
@@ -138,6 +138,102 @@ const operationsSummaryQuery = `
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
`;
|
|
141
|
+
/**
|
|
142
|
+
* Query that fetches all operations with their return type chain and args.
|
|
143
|
+
* Used by describeOperation to find a specific operation's details.
|
|
144
|
+
*/
|
|
145
|
+
const allOperationsWithTypesQuery = `
|
|
146
|
+
query AllOperationsWithTypes {
|
|
147
|
+
__schema {
|
|
148
|
+
queryType {
|
|
149
|
+
fields {
|
|
150
|
+
name
|
|
151
|
+
description
|
|
152
|
+
args {
|
|
153
|
+
name
|
|
154
|
+
description
|
|
155
|
+
defaultValue
|
|
156
|
+
type {
|
|
157
|
+
name
|
|
158
|
+
kind
|
|
159
|
+
ofType {
|
|
160
|
+
name
|
|
161
|
+
kind
|
|
162
|
+
ofType {
|
|
163
|
+
name
|
|
164
|
+
kind
|
|
165
|
+
ofType {
|
|
166
|
+
name
|
|
167
|
+
kind
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
type {
|
|
174
|
+
name
|
|
175
|
+
kind
|
|
176
|
+
ofType {
|
|
177
|
+
name
|
|
178
|
+
kind
|
|
179
|
+
ofType {
|
|
180
|
+
name
|
|
181
|
+
kind
|
|
182
|
+
ofType {
|
|
183
|
+
name
|
|
184
|
+
kind
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
mutationType {
|
|
192
|
+
fields {
|
|
193
|
+
name
|
|
194
|
+
description
|
|
195
|
+
args {
|
|
196
|
+
name
|
|
197
|
+
description
|
|
198
|
+
defaultValue
|
|
199
|
+
type {
|
|
200
|
+
name
|
|
201
|
+
kind
|
|
202
|
+
ofType {
|
|
203
|
+
name
|
|
204
|
+
kind
|
|
205
|
+
ofType {
|
|
206
|
+
name
|
|
207
|
+
kind
|
|
208
|
+
ofType {
|
|
209
|
+
name
|
|
210
|
+
kind
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
type {
|
|
217
|
+
name
|
|
218
|
+
kind
|
|
219
|
+
ofType {
|
|
220
|
+
name
|
|
221
|
+
kind
|
|
222
|
+
ofType {
|
|
223
|
+
name
|
|
224
|
+
kind
|
|
225
|
+
ofType {
|
|
226
|
+
name
|
|
227
|
+
kind
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
`;
|
|
141
237
|
/**
|
|
142
238
|
* Fetch the Admin GraphQL schema introspection
|
|
143
239
|
*/
|
|
@@ -152,6 +248,93 @@ export async function getShopSchema() {
|
|
|
152
248
|
const client = getClient();
|
|
153
249
|
return client.request(getShopUrl(), introspectionQuery);
|
|
154
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Fetch a summary of available queries and mutations for the Admin API
|
|
253
|
+
*/
|
|
254
|
+
const typeDetailsQuery = `
|
|
255
|
+
query GetTypeDetails($name: String!) {
|
|
256
|
+
__type(name: $name) {
|
|
257
|
+
kind
|
|
258
|
+
name
|
|
259
|
+
description
|
|
260
|
+
fields(includeDeprecated: false) {
|
|
261
|
+
name
|
|
262
|
+
description
|
|
263
|
+
args {
|
|
264
|
+
name
|
|
265
|
+
description
|
|
266
|
+
type {
|
|
267
|
+
name
|
|
268
|
+
kind
|
|
269
|
+
ofType {
|
|
270
|
+
name
|
|
271
|
+
kind
|
|
272
|
+
ofType {
|
|
273
|
+
name
|
|
274
|
+
kind
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
defaultValue
|
|
279
|
+
}
|
|
280
|
+
type {
|
|
281
|
+
name
|
|
282
|
+
kind
|
|
283
|
+
ofType {
|
|
284
|
+
name
|
|
285
|
+
kind
|
|
286
|
+
ofType {
|
|
287
|
+
name
|
|
288
|
+
kind
|
|
289
|
+
ofType {
|
|
290
|
+
name
|
|
291
|
+
kind
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
inputFields {
|
|
298
|
+
name
|
|
299
|
+
description
|
|
300
|
+
type {
|
|
301
|
+
name
|
|
302
|
+
kind
|
|
303
|
+
ofType {
|
|
304
|
+
name
|
|
305
|
+
kind
|
|
306
|
+
ofType {
|
|
307
|
+
name
|
|
308
|
+
kind
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
defaultValue
|
|
313
|
+
}
|
|
314
|
+
enumValues(includeDeprecated: false) {
|
|
315
|
+
name
|
|
316
|
+
description
|
|
317
|
+
}
|
|
318
|
+
interfaces {
|
|
319
|
+
name
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
`;
|
|
324
|
+
/**
|
|
325
|
+
* Fetch details for a single type from the Admin API schema
|
|
326
|
+
*/
|
|
327
|
+
export async function getAdminTypeDetails(name) {
|
|
328
|
+
const client = getClient();
|
|
329
|
+
return client.request(getAdminUrl(), typeDetailsQuery, { name });
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Fetch details for a single type from the Shop API schema
|
|
333
|
+
*/
|
|
334
|
+
export async function getShopTypeDetails(name) {
|
|
335
|
+
const client = getClient();
|
|
336
|
+
return client.request(getShopUrl(), typeDetailsQuery, { name });
|
|
337
|
+
}
|
|
155
338
|
/**
|
|
156
339
|
* Fetch a summary of available queries and mutations for the Admin API
|
|
157
340
|
*/
|
|
@@ -166,21 +349,165 @@ export async function getShopOperations() {
|
|
|
166
349
|
const client = getClient();
|
|
167
350
|
return client.request(getShopUrl(), operationsSummaryQuery);
|
|
168
351
|
}
|
|
352
|
+
// ─── Helpers for describeOperation ───────────────────────────────
|
|
353
|
+
/**
|
|
354
|
+
* Extract the named type name from a type chain (through NonNull/List wrappers).
|
|
355
|
+
* e.g., `[ProductGroup!]!` → `ProductGroup`
|
|
356
|
+
*/
|
|
357
|
+
function extractNamedTypeName(type) {
|
|
358
|
+
if (!type)
|
|
359
|
+
return null;
|
|
360
|
+
if (type.kind !== "NON_NULL" && type.kind !== "LIST") {
|
|
361
|
+
return type.name || null;
|
|
362
|
+
}
|
|
363
|
+
return extractNamedTypeName(type.ofType);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Format a GraphQL type chain into a readable string like `[ProductGroup!]!`
|
|
367
|
+
*/
|
|
368
|
+
function formatTypeString(type) {
|
|
369
|
+
if (!type)
|
|
370
|
+
return "unknown";
|
|
371
|
+
switch (type.kind) {
|
|
372
|
+
case "NON_NULL":
|
|
373
|
+
return `${formatTypeString(type.ofType)}!`;
|
|
374
|
+
case "LIST":
|
|
375
|
+
return `[${formatTypeString(type.ofType)}]`;
|
|
376
|
+
default:
|
|
377
|
+
return type.name || "unknown";
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Describe a GraphQL operation (query or mutation) with full details.
|
|
382
|
+
*
|
|
383
|
+
* Checks both Admin and Shop APIs (or just one if `api` is specified).
|
|
384
|
+
* Returns the operation's arguments with input types, and its return type with fields.
|
|
385
|
+
*
|
|
386
|
+
* @param operationName - The name of the query or mutation
|
|
387
|
+
* @param api - Optional: restrict to "admin" or "shop" only
|
|
388
|
+
*/
|
|
389
|
+
export async function describeOperation(operationName, api) {
|
|
390
|
+
const apisToCheck = api ? [api] : ["admin", "shop"];
|
|
391
|
+
const client = getClient();
|
|
392
|
+
const details = [];
|
|
393
|
+
for (const targetApi of apisToCheck) {
|
|
394
|
+
const url = targetApi === "admin" ? getAdminUrl() : getShopUrl();
|
|
395
|
+
try {
|
|
396
|
+
// Step 1: Fetch all operations with type chains to find the one we need
|
|
397
|
+
const schemaData = await client.request(url, allOperationsWithTypesQuery);
|
|
398
|
+
const queryFields = schemaData.__schema.queryType.fields;
|
|
399
|
+
const mutationFields = schemaData.__schema.mutationType.fields;
|
|
400
|
+
const queryField = queryFields.find((f) => f.name === operationName);
|
|
401
|
+
const mutationField = mutationFields.find((f) => f.name === operationName);
|
|
402
|
+
if (!queryField && !mutationField)
|
|
403
|
+
continue;
|
|
404
|
+
const field = queryField || mutationField;
|
|
405
|
+
const kind = queryField ? "query" : "mutation";
|
|
406
|
+
// Step 2: Resolve the return type's fields
|
|
407
|
+
const returnTypeName = extractNamedTypeName(field.type);
|
|
408
|
+
let returnType = {
|
|
409
|
+
name: formatTypeString(field.type),
|
|
410
|
+
namedType: returnTypeName,
|
|
411
|
+
};
|
|
412
|
+
if (returnTypeName) {
|
|
413
|
+
try {
|
|
414
|
+
const result = await client.request(url, typeDetailsQuery, {
|
|
415
|
+
name: returnTypeName,
|
|
416
|
+
});
|
|
417
|
+
if (result.__type) {
|
|
418
|
+
returnType = {
|
|
419
|
+
name: returnTypeName,
|
|
420
|
+
kind: result.__type.kind,
|
|
421
|
+
description: result.__type.description,
|
|
422
|
+
fields: (result.__type.fields || []).map((f) => ({
|
|
423
|
+
name: f.name,
|
|
424
|
+
type: formatTypeString(f.type),
|
|
425
|
+
description: f.description,
|
|
426
|
+
args: f.args?.length
|
|
427
|
+
? f.args.map((a) => ({
|
|
428
|
+
name: a.name,
|
|
429
|
+
type: formatTypeString(a.type),
|
|
430
|
+
description: a.description,
|
|
431
|
+
defaultValue: a.defaultValue,
|
|
432
|
+
}))
|
|
433
|
+
: undefined,
|
|
434
|
+
})),
|
|
435
|
+
enumValues: result.__type.enumValues,
|
|
436
|
+
inputFields: result.__type.inputFields,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
// Type might be a built-in scalar - skip
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// Step 3: Resolve argument input types
|
|
445
|
+
const argDetails = await Promise.all((field.args || []).map(async (arg) => {
|
|
446
|
+
const argTypeName = extractNamedTypeName(arg.type);
|
|
447
|
+
const detail = {
|
|
448
|
+
name: arg.name,
|
|
449
|
+
type: formatTypeString(arg.type),
|
|
450
|
+
description: arg.description,
|
|
451
|
+
defaultValue: arg.defaultValue,
|
|
452
|
+
};
|
|
453
|
+
if (argTypeName) {
|
|
454
|
+
try {
|
|
455
|
+
const result = await client.request(url, typeDetailsQuery, {
|
|
456
|
+
name: argTypeName,
|
|
457
|
+
});
|
|
458
|
+
if (result.__type) {
|
|
459
|
+
detail.inputFields = (result.__type.inputFields || []).map((f) => ({
|
|
460
|
+
name: f.name,
|
|
461
|
+
type: formatTypeString(f.type),
|
|
462
|
+
description: f.description,
|
|
463
|
+
defaultValue: f.defaultValue,
|
|
464
|
+
}));
|
|
465
|
+
detail.inputKind = result.__type.kind;
|
|
466
|
+
detail.inputDescription = result.__type.description;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
// ignore
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return detail;
|
|
474
|
+
}));
|
|
475
|
+
details.push({
|
|
476
|
+
api: targetApi,
|
|
477
|
+
kind,
|
|
478
|
+
description: field.description,
|
|
479
|
+
arguments: argDetails,
|
|
480
|
+
returnType,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
details.push({
|
|
485
|
+
api: targetApi,
|
|
486
|
+
error: error instanceof Error ? error.message : String(error),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
operation: operationName,
|
|
492
|
+
availableOn: details.filter((d) => !d.error).map((d) => d.api),
|
|
493
|
+
details,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
169
496
|
/**
|
|
170
497
|
* Execute a GraphQL mutation in batch for multiple IDs on the Admin API.
|
|
171
498
|
* Runs mutations concurrently with configurable concurrency.
|
|
172
499
|
*/
|
|
173
|
-
export async function adminBatchMutation(mutationString, ids, variableName = "id", extraVariables, concurrency = 5) {
|
|
174
|
-
return executeBatch(getAdminUrl(), mutationString, ids, variableName, extraVariables, concurrency);
|
|
500
|
+
export async function adminBatchMutation(mutationString, ids, variableName = "id", extraVariables, concurrency = 5, channelToken) {
|
|
501
|
+
return executeBatch(getAdminUrl(), mutationString, ids, variableName, extraVariables, concurrency, channelToken);
|
|
175
502
|
}
|
|
176
503
|
/**
|
|
177
504
|
* Execute a GraphQL mutation in batch for multiple IDs on the Shop API.
|
|
178
505
|
* Runs mutations concurrently with configurable concurrency.
|
|
179
506
|
*/
|
|
180
|
-
export async function shopBatchMutation(mutationString, ids, variableName = "id", extraVariables, concurrency = 5) {
|
|
181
|
-
return executeBatch(getShopUrl(), mutationString, ids, variableName, extraVariables, concurrency);
|
|
507
|
+
export async function shopBatchMutation(mutationString, ids, variableName = "id", extraVariables, concurrency = 5, channelToken) {
|
|
508
|
+
return executeBatch(getShopUrl(), mutationString, ids, variableName, extraVariables, concurrency, channelToken);
|
|
182
509
|
}
|
|
183
|
-
async function executeBatch(url, mutationString, ids, variableName, extraVariables, concurrency) {
|
|
510
|
+
async function executeBatch(url, mutationString, ids, variableName, extraVariables, concurrency, channelToken) {
|
|
184
511
|
const client = getClient();
|
|
185
512
|
const results = [];
|
|
186
513
|
const queue = [...ids];
|
|
@@ -191,7 +518,7 @@ async function executeBatch(url, mutationString, ids, variableName, extraVariabl
|
|
|
191
518
|
const id = queue.shift();
|
|
192
519
|
const variables = { ...extraVariables, [variableName]: id };
|
|
193
520
|
try {
|
|
194
|
-
const data = await client.request(url, mutationString, variables);
|
|
521
|
+
const data = await client.request(url, mutationString, variables, channelToken);
|
|
195
522
|
succeeded++;
|
|
196
523
|
results.push({ id, success: true, data });
|
|
197
524
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vendure-mcp-graphql",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "MCP server for Vendure Admin and Shop GraphQL APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"vendure-mcp-graphql": "dist/index.js"
|
|
7
|
+
"vendure-mcp-graphql": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"files": [
|