Skip to content

Commit cb52e1e

Browse files
authored
fix: validate category refs in CI + fix 4 servers with unknown categories (#119)
Four server definitions referenced categories not present in \`categories.json\` (\`web-scraping\`, \`business\`, \`crm\`, \`search-web\`). These silently failed to sync in the bundler because server_categories has a FK to categories.id — the upsert threw and dropped the entire server, so any update to these four files never reached production. - Fix the four offenders to use valid categories: ai.suprsonic-mcp-npx web-scraping -> dropped (kept search, ai-ml, communication) app.businys-mcp-npx business, crm -> productivity, communication dev.agentdeals-mcp-http business -> productivity io.global-chat-mcp-npx search-web -> search - Extend scripts/validate.js to load categories.json and reject any server that references an ID not listed there. Reports the full list of valid IDs on failure so contributors can pick the right one. - Broaden the Validate PR workflow so changes to categories.json, schemas, scripts, or tests all trigger validation. Added a "Run full test suite" step so cross-file checks (consistency, categories, deprecated icon field, logo URL format) run on every PR. - Fix the one pre-existing \`type: "password"\` test fixture, which was always invalid under the input schema but went unnoticed because CI didn't run the test suite. Verified \`pnpm validate servers/<bad>\` now prints a clear error listing every valid category ID. Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 309a70c commit cb52e1e

7 files changed

Lines changed: 60 additions & 9 deletions

File tree

.github/workflows/validate-pr.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
pull_request:
55
paths:
66
- "servers/**"
7+
- "categories.json"
8+
- "schemas/**"
9+
- "scripts/**"
10+
- "tests/**"
711

812
jobs:
913
validate:
@@ -23,13 +27,17 @@ jobs:
2327
- name: Install dependencies
2428
run: npm ci
2529

26-
- name: Get changed files
30+
- name: Get changed server files
2731
id: changed
2832
uses: tj-actions/changed-files@v44
2933
with:
3034
files: "servers/*.json"
3135

3236
- name: Validate changed server files
37+
# Runs schema validation AND category-reference checks via scripts/validate.js.
38+
# A category ID not present in categories.json fails here, since the
39+
# bundler's server_categories table has a FK to categories.id and a
40+
# typo would silently break sync for that server.
3341
run: |
3442
if [ -n "${{ steps.changed.outputs.all_changed_files }}" ]; then
3543
npm run validate -- ${{ steps.changed.outputs.all_changed_files }}
@@ -40,3 +48,9 @@ jobs:
4048
4149
- name: Check for conflicts
4250
run: npm run check-conflicts
51+
52+
- name: Run full test suite
53+
# Covers cross-file consistency checks (categories.json, bundle build,
54+
# deprecated `icon` field, logo URL format) that per-file validation
55+
# can't express.
56+
run: npm test

scripts/validate.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,28 @@ const { glob } = require("glob");
1818
const ROOT = path.resolve(__dirname, "..");
1919
const SCHEMA_PATH = path.join(ROOT, "schemas", "server-definition.schema.json");
2020
const SERVERS_DIR = path.join(ROOT, "servers");
21+
const CATEGORIES_PATH = path.join(ROOT, "categories.json");
2122

2223
// Fields that contributors must not set -- they are platform-managed
2324
const PLATFORM_FIELDS = ["badges", "stats", "sponsored", "featured"];
2425

26+
/**
27+
* Load the set of valid category IDs from categories.json. The bundler's
28+
* server_categories table has a foreign key to categories.id, so any server
29+
* that references an ID not in categories.json will silently fail to sync.
30+
* We catch those at validate time so they never reach main.
31+
*/
32+
function loadValidCategoryIds() {
33+
try {
34+
const raw = fs.readFileSync(CATEGORIES_PATH, "utf-8");
35+
const categories = JSON.parse(raw);
36+
return new Set(categories.map((c) => c.id));
37+
} catch (err) {
38+
console.error(`Failed to load categories.json: ${err.message}`);
39+
return null;
40+
}
41+
}
42+
2543
// ---------------------------------------------------------------------------
2644
// Helpers
2745
// ---------------------------------------------------------------------------
@@ -75,6 +93,8 @@ async function validateFiles(files) {
7593
addFormats(ajv);
7694
const validate = ajv.compile(schema);
7795

96+
const validCategoryIds = loadValidCategoryIds();
97+
7898
let hasErrors = false;
7999

80100
for (const filePath of files) {
@@ -96,10 +116,29 @@ async function validateFiles(files) {
96116
}
97117

98118
const valid = validate(data);
119+
const fileErrors = [];
99120
if (!valid) {
100-
console.error(`FAIL ${relative}`);
101121
for (const err of validate.errors) {
102-
console.error(` - ${err.instancePath || "/"}: ${err.message}`);
122+
fileErrors.push(`${err.instancePath || "/"}: ${err.message}`);
123+
}
124+
}
125+
126+
// Enforce that every category reference exists in categories.json.
127+
// The bundler's server_categories table has a FK constraint — a typo
128+
// here silently breaks sync for this server.
129+
if (validCategoryIds && Array.isArray(data.categories)) {
130+
const unknown = data.categories.filter((id) => !validCategoryIds.has(id));
131+
for (const id of unknown) {
132+
fileErrors.push(
133+
`/categories: unknown category "${id}" (not in categories.json; pick one of: ${Array.from(validCategoryIds).sort().join(", ")})`
134+
);
135+
}
136+
}
137+
138+
if (fileErrors.length > 0) {
139+
console.error(`FAIL ${relative}`);
140+
for (const msg of fileErrors) {
141+
console.error(` - ${msg}`);
103142
}
104143
hasErrors = true;
105144
} else {

servers/ai.suprsonic-mcp-npx.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"schema_version": "2.1",
99
"categories": [
1010
"search",
11-
"web-scraping",
1211
"ai-ml",
1312
"communication"
1413
],

servers/app.businys-mcp-npx.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
"logo": "https://businys.app/icon.png",
88
"schema_version": "2.1",
99
"categories": [
10-
"business",
1110
"productivity",
12-
"crm"
11+
"communication"
1312
],
1413
"tags": [
1514
"smb",

servers/dev.agentdeals-mcp-http.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"schema_version": "2.1",
99
"categories": [
1010
"developer-tools",
11-
"business"
11+
"productivity"
1212
],
1313
"tags": [
1414
"free-tiers",

servers/io.global-chat-mcp-npx.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"schema_version": "2.1",
99
"categories": [
1010
"developer-tools",
11-
"search-web"
11+
"search"
1212
],
1313
"tags": [
1414
"ai-agents",

tests/schema-validation.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe("Server Definition Schema", () => {
5858
metadata: {
5959
inputs: [
6060
{ id: "LOG_LEVEL", label: "Log Level", type: "text", required: false, default: "info" },
61-
{ id: "API_KEY", label: "API Key", type: "password", required: true, secret: true },
61+
{ id: "API_KEY", label: "API Key", type: "text", required: true, secret: true },
6262
],
6363
},
6464
},

0 commit comments

Comments
 (0)