@democratize-quality/mcp-server 1.1.2 → 1.1.3

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 CHANGED
@@ -13,7 +13,7 @@
13
13
  **The fastest way to get started** - Install with intelligent testing agents for GitHub Copilot:
14
14
 
15
15
  ```bash
16
- npx @democratize-quality/mcp-server --agents
16
+ npx @democratize-quality/mcp-server@latest --agents
17
17
  ```
18
18
 
19
19
  **What this does:**
@@ -929,6 +929,7 @@ MCP_FEATURES_ENABLEDEBUGMODE=true node mcpServer.js
929
929
  - 📖 [Getting Started Guide](docs/getting-started.md) - Complete setup walkthrough
930
930
  - 🔧 [Tool Reference](docs/api/tool-reference.md) - Detailed tool documentation
931
931
  - 🎯 [API Tools Usage Guide](docs/api_tools_usage.md) - Advanced examples and patterns
932
+ - 🔷 [GraphQL Support Guide](GRAPHQL-SUPPORT.md) - GraphQL testing capabilities and features
932
933
  - �‍💻 [Developer Guide](docs/development/adding-tools.md) - Extend the server
933
934
  - ⚙️ [Configuration Guide](docs/development/configuration.md) - Advanced settings
934
935
  - � [Examples](docs/examples/) - Real-world usage examples
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@democratize-quality/mcp-server",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "main": "mcpServer.js",
5
5
  "bin": {
6
6
  "democratize-quality-mcp": "cli.js",
@@ -75,6 +75,7 @@
75
75
  "chrome-launcher": "^1.2.0",
76
76
  "chrome-remote-interface": "^0.33.3",
77
77
  "express": "^5.1.0",
78
+ "graphql": "^16.11.0",
78
79
  "json-rpc-2.0": "^1.7.1",
79
80
  "yaml": "^2.8.1",
80
81
  "zod": "^4.0.10"
@@ -200,7 +200,106 @@ await tools.api_planner({
200
200
 
201
201
  ---
202
202
 
203
- ## 🔄 Quality Check Loop (Playwright-Style)
203
+ ## Working with Schema Files
204
+
205
+ ### ⚠️ CRITICAL: GraphQL SDL Files (.graphql, .gql)
206
+
207
+ **For GraphQL Schema Definition Language (SDL) files, ALWAYS use `schemaPath` parameter:**
208
+
209
+ ```javascript
210
+ // ✅ CORRECT: Use schemaPath
211
+ await tools.api_planner({
212
+ schemaPath: "./schema.graphql", // File path
213
+ apiBaseUrl: "https://api.github.com/graphql",
214
+ outputPath: "./test-plan.md"
215
+ })
216
+
217
+ // ❌ WRONG: Don't try to read and pass file content
218
+ // The tool ONLY accepts schemaPath or schemaUrl parameters
219
+ ```
220
+
221
+ **Why schemaPath is Required for SDL:**
222
+ 1. **Reads full file** - No truncation or summarization issues
223
+ 2. **Automatic conversion** - Tool converts SDL → introspection JSON automatically
224
+ 3. **Creates reusable file** - Saves `schema.json` alongside `schema.graphql` for future use
225
+ 4. **No parse errors** - Full file is read without truncation
226
+
227
+ **What Happens:**
228
+ ```
229
+ User provides: schema.graphql (SDL file)
230
+
231
+ Tool reads full file via schemaPath
232
+
233
+ Detects SDL format automatically
234
+
235
+ Converts SDL → Introspection JSON using graphql library
236
+
237
+ Saves as schema.json (same directory)
238
+
239
+ Uses introspection to generate test plan
240
+
241
+ Both files available for future use
242
+ ```
243
+
244
+ **Example with User:**
245
+ ```
246
+ User: "Generate test plan from schema.graphql"
247
+ Assistant: "I'll use schemaPath to read your GraphQL SDL file and convert it automatically."
248
+
249
+ await tools.api_planner({
250
+ schemaPath: "./schema.graphql", // ← Use path, not content!
251
+ apiBaseUrl: "https://api.github.com/graphql",
252
+ outputPath: "./github-test-plan.md"
253
+ })
254
+
255
+ // Tool automatically:
256
+ // ✓ Reads full file (no truncation)
257
+ // ✓ Converts SDL to introspection JSON
258
+ // ✓ Saves schema.json
259
+ // ✓ Generates test plan
260
+ ```
261
+
262
+ ### 📂 Other Schema File Types
263
+
264
+ **OpenAPI/Swagger Files (.json, .yaml, .yml):**
265
+ - Use `schemaPath` for local files
266
+ - Use `schemaUrl` for remote URLs
267
+
268
+ ```javascript
269
+ // Local OpenAPI file:
270
+ await tools.api_planner({
271
+ schemaPath: "./openapi.json"
272
+ })
273
+ ```
274
+ ```
275
+
276
+ ### 🔍 Detecting Schema Type
277
+
278
+ **When user mentions file:**
279
+ - `*.graphql` or `*.gql` → **MUST use schemaPath** (SDL conversion)
280
+ - `*.json` → Use schemaPath (JSON)
281
+ - `*.yaml` or `*.yml` → Use schemaPath (YAML)
282
+ - URL ending in `/graphql` → Use schemaUrl (introspection)
283
+ - URL ending in `.json` or `.yaml` → Use schemaUrl (fetch)
284
+
285
+ ### 🎯 Best Practice Guidelines
286
+
287
+ **DO:**
288
+ - ✅ Use `schemaPath` for all local schema files
289
+ - ✅ Use `schemaPath` for GraphQL SDL files (`.graphql`, `.gql`)
290
+ - ✅ Use `schemaPath` for large files (>1MB)
291
+ - ✅ Use `schemaUrl` for remote APIs with introspection
292
+ - ✅ Let tool auto-convert SDL to introspection JSON
293
+ - ✅ Reuse generated `.json` files in future runs
294
+
295
+ **DON'T:**
296
+ - ❌ Try to read file content and pass it to the tool
297
+ - ❌ Manually convert SDL before using tool (tool does it automatically)
298
+ - ❌ Delete generated `.json` files (they're reusable)
299
+
300
+ ---
301
+
302
+ ## �🔄 Quality Check Loop (Playwright-Style)
204
303
 
205
304
  ### 1. Generate Plan
206
305
  ```javascript
@@ -265,14 +364,9 @@ User asks about the output?
265
364
 
266
365
  ### Scenario 1: Local Schema File
267
366
  ```javascript
268
- // Read schema from file first
269
- const schemaContent = await tools.readFile({
270
- path: "./openapi.json"
271
- })
272
-
273
- // Then pass content to api_planner
367
+ // Use schemaPath for local files
274
368
  await tools.api_planner({
275
- schemaContent: schemaContent,
369
+ schemaPath: "./openapi.json",
276
370
  apiBaseUrl: "https://api.example.com",
277
371
  validateEndpoints: true
278
372
  })
@@ -350,8 +444,9 @@ for (const service of services) {
350
444
 
351
445
  ## 🎯 Parameter Reference
352
446
 
353
- ### Required Parameters:
354
- - `schemaUrl` OR `schemaContent` - OpenAPI/Swagger schema source
447
+ ### Required Parameters (One of):
448
+ - `schemaUrl` - URL to fetch schema (e.g., "https://api.example.com/swagger.json")
449
+ - `schemaPath` - Local file path (e.g., "./schema.graphql", "./openapi.json")
355
450
 
356
451
  ### Optional Parameters (Common):
357
452
  - `apiBaseUrl` - **FULL URL** to override base URL from schema (e.g., `"https://api-staging.example.com/v2"` for staging environment)
@@ -403,8 +498,8 @@ for (const service of services) {
403
498
  **Symptoms:** "Failed to parse schema" error
404
499
  **Solution:**
405
500
  1. Verify schema URL is accessible
406
- 2. Check schema format (OpenAPI 3.0/Swagger 2.0)
407
- 3. Try using `schemaContent` with file contents directly
501
+ 2. Check schema format (OpenAPI 3.0/Swagger 2.0/GraphQL SDL)
502
+ 3. Try using `schemaPath` for local files instead of schemaUrl
408
503
 
409
504
  ---
410
505
 
@@ -842,12 +937,12 @@ await tools.api_planner({
842
937
 
843
938
  <example>
844
939
  Context: Developer wants to create a test plan from API schema content.
845
- user: 'Generate a test plan from this OpenAPI schema: [schema content]'
940
+ user: 'Generate a test plan from this OpenAPI schema file: openapi.json'
846
941
  assistant: 'I'll generate a comprehensive test plan from your OpenAPI schema using the api_planner tool.'
847
942
 
848
- // IMMEDIATE RESPONSE - Generate from schema content:
943
+ // IMMEDIATE RESPONSE - Generate from schema file:
849
944
  await tools.api_planner({
850
- schemaContent: `[schema content]`,
945
+ schemaPath: "./openapi.json",
851
946
  schemaType: "auto",
852
947
  includeAuth: true,
853
948
  includeSecurity: true,
@@ -856,4 +951,4 @@ await tools.api_planner({
856
951
  })
857
952
  </example>
858
953
 
859
- **Key Principle: Use api_planner tool first, always. Only use manual API exploration if schema analysis needs assistance.**
954
+ **Key Principle: Use api_planner tool first, always. Use schemaPath for local files, schemaUrl for remote schemas.**
@@ -252,7 +252,8 @@ class ApiGeneratorTool extends ToolBase {
252
252
  title: '',
253
253
  description: '',
254
254
  baseUrl: '',
255
- sections: []
255
+ sections: [],
256
+ isGraphQL: false // NEW: Track if this is a GraphQL test plan
256
257
  };
257
258
 
258
259
  const lines = content.split('\n');
@@ -371,6 +372,11 @@ class ApiGeneratorTool extends ToolBase {
371
372
  }
372
373
  }
373
374
  }
375
+
376
+ // NEW: Detect if test plan is GraphQL-based
377
+ testPlan.isGraphQL = testPlan.sections.some(section =>
378
+ section.scenarios.some(scenario => scenario.isGraphQL)
379
+ );
374
380
 
375
381
  return testPlan;
376
382
  }
@@ -379,6 +385,18 @@ class ApiGeneratorTool extends ToolBase {
379
385
  try {
380
386
  const data = JSON.parse(content);
381
387
 
388
+ // NEW: Detect GraphQL structure
389
+ if (data.query || data.mutation || data.subscription) {
390
+ scenario.isGraphQL = true;
391
+ scenario.graphql = {
392
+ query: data.query || data.mutation || data.subscription,
393
+ variables: data.variables || {}
394
+ };
395
+ // Also store in data for backward compatibility
396
+ scenario.data = data;
397
+ return;
398
+ }
399
+
382
400
  if (context === 'request') {
383
401
  // This is request body data
384
402
  scenario.data = data;
@@ -439,7 +457,9 @@ class ApiGeneratorTool extends ToolBase {
439
457
  expectedStatus: scenario.expectedStatus,
440
458
  requestBody: scenario.requestBody,
441
459
  expectedBody: scenario.expectedBody,
442
- steps: scenario.steps
460
+ steps: scenario.steps,
461
+ isGraphQL: scenario.isGraphQL, // NEW: GraphQL flag
462
+ graphql: scenario.graphql // NEW: GraphQL query/variables
443
463
  })),
444
464
  language: options.language,
445
465
  isTypeScript: isTS,
@@ -516,7 +536,9 @@ class ApiGeneratorTool extends ToolBase {
516
536
  expectedStatus: scenario.expectedStatus,
517
537
  requestBody: scenario.requestBody,
518
538
  expectedBody: scenario.expectedBody,
519
- steps: scenario.steps
539
+ steps: scenario.steps,
540
+ isGraphQL: scenario.isGraphQL, // NEW: GraphQL flag
541
+ graphql: scenario.graphql // NEW: GraphQL query/variables
520
542
  })),
521
543
  language: options.language,
522
544
  isTypeScript: isTS,
@@ -699,8 +721,34 @@ ${this._generatePlaywrightScenarioTest(scenario, options, testPlan.baseUrl)}
699
721
  }
700
722
  testCode += '\n';
701
723
  });
724
+ } else if (scenario.isGraphQL) {
725
+ // NEW: Handle GraphQL requests
726
+ const endpoint = scenario.endpoint || '/graphql';
727
+ const fullUrl = endpoint.startsWith('http') ? endpoint : `\${baseUrl}${endpoint}`;
728
+
729
+ testCode += ` // GraphQL Request\n`;
730
+ testCode += ` const response = await request.post('${fullUrl}', {\n`;
731
+ testCode += ` headers: ${JSON.stringify(scenario.headers, null, 8)},\n`;
732
+ testCode += ` data: {\n`;
733
+ testCode += ` query: \`${scenario.graphql.query.replace(/`/g, '\\`')}\`,\n`;
734
+ if (scenario.graphql.variables && Object.keys(scenario.graphql.variables).length > 0) {
735
+ testCode += ` variables: ${JSON.stringify(scenario.graphql.variables, null, 10)}\n`;
736
+ }
737
+ testCode += ` }\n`;
738
+ testCode += ` });\n\n`;
739
+
740
+ // Add validations for GraphQL
741
+ if (scenario.expect.status) {
742
+ testCode += ` expect(response.status()).toBe(${scenario.expect.status});\n`;
743
+ }
744
+
745
+ if (scenario.expect.body) {
746
+ testCode += ` \n`;
747
+ testCode += ` const responseData = await response.json();\n`;
748
+ testCode += ` ${this._generateGraphQLPlaywrightValidation('responseData', scenario.expect.body)}\n`;
749
+ }
702
750
  } else {
703
- // Single request
751
+ // Single REST request
704
752
  let endpoint = scenario.endpoint;
705
753
 
706
754
  // Replace path parameters if any
@@ -776,6 +824,52 @@ ${this._generatePlaywrightScenarioTest(scenario, options, testPlan.baseUrl)}
776
824
  return validation.trimEnd(); // Remove trailing newline
777
825
  }
778
826
 
827
+ _generateGraphQLPlaywrightValidation(dataVar, expectedBody) {
828
+ let validation = '';
829
+
830
+ // GraphQL responses have either data or errors
831
+ validation += ` // GraphQL response structure validation\n`;
832
+ validation += ` expect(${dataVar}).toBeDefined();\n`;
833
+
834
+ // Check if we're expecting success (data) or error (errors)
835
+ if (expectedBody && typeof expectedBody === 'object') {
836
+ if (expectedBody.data !== undefined) {
837
+ validation += ` expect(${dataVar}).toHaveProperty('data');\n`;
838
+ validation += ` expect(${dataVar}.errors).toBeUndefined();\n`;
839
+
840
+ // Validate data structure
841
+ if (typeof expectedBody.data === 'object' && expectedBody.data !== null) {
842
+ Object.entries(expectedBody.data).forEach(([key, value]) => {
843
+ validation += ` expect(${dataVar}.data).toHaveProperty('${key}');\n`;
844
+
845
+ // Type checks for nested values
846
+ if (value === 'string') {
847
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('string');\n`;
848
+ } else if (value === 'number') {
849
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('number');\n`;
850
+ } else if (value === 'boolean') {
851
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('boolean');\n`;
852
+ } else if (Array.isArray(value)) {
853
+ validation += ` expect(Array.isArray(${dataVar}.data.${key})).toBe(true);\n`;
854
+ } else if (typeof value === 'object' && value !== null) {
855
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('object');\n`;
856
+ }
857
+ });
858
+ }
859
+ } else if (expectedBody.errors !== undefined) {
860
+ validation += ` expect(${dataVar}).toHaveProperty('errors');\n`;
861
+ validation += ` expect(Array.isArray(${dataVar}.errors)).toBe(true);\n`;
862
+ validation += ` expect(${dataVar}.errors.length).toBeGreaterThan(0);\n`;
863
+ }
864
+ } else {
865
+ // Generic validation when no specific expectation
866
+ validation += ` // Verify GraphQL response has either data or errors\n`;
867
+ validation += ` expect(${dataVar}.data !== undefined || ${dataVar}.errors !== undefined).toBe(true);\n`;
868
+ }
869
+
870
+ return validation.trimEnd();
871
+ }
872
+
779
873
  _generatePlaywrightHelpers(testPlan, options) {
780
874
  const isTS = options.language === 'typescript';
781
875
 
@@ -1274,8 +1368,33 @@ describe('${testPlan.title} API Tests', () => {
1274
1368
  }
1275
1369
  testCode += '\n';
1276
1370
  });
1371
+ } else if (scenario.isGraphQL) {
1372
+ // NEW: Handle GraphQL requests
1373
+ const endpoint = scenario.endpoint || '/graphql';
1374
+
1375
+ testCode += ` // GraphQL Request\n`;
1376
+ testCode += ` const response = await apiUtils.makeRequest({\n`;
1377
+ testCode += ` method: 'POST',\n`;
1378
+ testCode += ` url: '${endpoint}',\n`;
1379
+ testCode += ` headers: ${JSON.stringify(scenario.headers, null, 8)},\n`;
1380
+ testCode += ` data: {\n`;
1381
+ testCode += ` query: \`${scenario.graphql.query.replace(/`/g, '\\`')}\`,\n`;
1382
+ if (scenario.graphql.variables && Object.keys(scenario.graphql.variables).length > 0) {
1383
+ testCode += ` variables: ${JSON.stringify(scenario.graphql.variables, null, 10)}\n`;
1384
+ }
1385
+ testCode += ` }\n`;
1386
+ testCode += ` });\n\n`;
1387
+
1388
+ // Add validations for GraphQL
1389
+ if (scenario.expect.status) {
1390
+ testCode += ` expect(response.status).toBe(${scenario.expect.status});\n`;
1391
+ }
1392
+
1393
+ if (scenario.expect.body) {
1394
+ testCode += ` ${this._generateGraphQLJestValidation('response.data', scenario.expect.body)}\n`;
1395
+ }
1277
1396
  } else {
1278
- // Single request
1397
+ // Single REST request
1279
1398
  testCode += ` const response = await apiUtils.makeRequest({\n`;
1280
1399
  testCode += ` method: '${scenario.method}',\n`;
1281
1400
  testCode += ` url: '${scenario.endpoint}',\n`;
@@ -1323,6 +1442,52 @@ describe('${testPlan.title} API Tests', () => {
1323
1442
  return validation;
1324
1443
  }
1325
1444
 
1445
+ _generateGraphQLJestValidation(dataVar, expectedBody) {
1446
+ let validation = '';
1447
+
1448
+ // GraphQL responses have either data or errors
1449
+ validation += ` // GraphQL response structure validation\n`;
1450
+ validation += ` expect(${dataVar}).toBeDefined();\n`;
1451
+
1452
+ // Check if we're expecting success (data) or error (errors)
1453
+ if (expectedBody && typeof expectedBody === 'object') {
1454
+ if (expectedBody.data !== undefined) {
1455
+ validation += ` expect(${dataVar}).toHaveProperty('data');\n`;
1456
+ validation += ` expect(${dataVar}.errors).toBeUndefined();\n`;
1457
+
1458
+ // Validate data structure
1459
+ if (typeof expectedBody.data === 'object' && expectedBody.data !== null) {
1460
+ Object.entries(expectedBody.data).forEach(([key, value]) => {
1461
+ validation += ` expect(${dataVar}.data).toHaveProperty('${key}');\n`;
1462
+
1463
+ // Type checks for nested values
1464
+ if (value === 'string') {
1465
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('string');\n`;
1466
+ } else if (value === 'number') {
1467
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('number');\n`;
1468
+ } else if (value === 'boolean') {
1469
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('boolean');\n`;
1470
+ } else if (Array.isArray(value)) {
1471
+ validation += ` expect(Array.isArray(${dataVar}.data.${key})).toBe(true);\n`;
1472
+ } else if (typeof value === 'object' && value !== null) {
1473
+ validation += ` expect(typeof ${dataVar}.data.${key}).toBe('object');\n`;
1474
+ }
1475
+ });
1476
+ }
1477
+ } else if (expectedBody.errors !== undefined) {
1478
+ validation += ` expect(${dataVar}).toHaveProperty('errors');\n`;
1479
+ validation += ` expect(Array.isArray(${dataVar}.errors)).toBe(true);\n`;
1480
+ validation += ` expect(${dataVar}.errors.length).toBeGreaterThan(0);\n`;
1481
+ }
1482
+ } else {
1483
+ // Generic validation when no specific expectation
1484
+ validation += ` // Verify GraphQL response has either data or errors\n`;
1485
+ validation += ` expect(${dataVar}.data !== undefined || ${dataVar}.errors !== undefined).toBe(true);\n`;
1486
+ }
1487
+
1488
+ return validation;
1489
+ }
1490
+
1326
1491
  _generateJestUtils(testPlan, options) {
1327
1492
  const isTS = options.language === 'typescript';
1328
1493