@evantahler/mcpx 0.21.4 → 0.21.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evantahler/mcpx",
3
- "version": "0.21.4",
3
+ "version": "0.21.5",
4
4
  "description": "A command-line interface for MCP servers. curl for MCP.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -26,7 +26,7 @@ function validateWithSchema(
26
26
 
27
27
  if (!validate) {
28
28
  try {
29
- validate = ajv.compile(schema);
29
+ validate = ajv.compile(normalizeSchema(schema));
30
30
  validatorCache.set(cacheKey, validate);
31
31
  } catch (err) {
32
32
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -60,6 +60,64 @@ export function validateElicitationResponse(
60
60
  return validateWithSchema(`__elicitation__${JSON.stringify(schema)}`, schema, input);
61
61
  }
62
62
 
63
+ type JsonPrimitive = string | number | boolean | null;
64
+
65
+ function isPrimitive(v: unknown): v is JsonPrimitive {
66
+ return v === null || ["string", "number", "boolean"].includes(typeof v);
67
+ }
68
+
69
+ function primitiveJsonType(v: JsonPrimitive): "string" | "number" | "boolean" | "null" {
70
+ if (v === null) return "null";
71
+ if (typeof v === "boolean") return "boolean";
72
+ if (typeof v === "number") return "number";
73
+ return "string";
74
+ }
75
+
76
+ /**
77
+ * Normalize a JSON Schema before handing it to Ajv. Rewrites the malformed
78
+ * shape `{ type: "array", enum: [<primitives>], items: { type: <matching> } }`
79
+ * — published by some real MCP servers — into `{ type: "array", items: { ..., enum: [...] } }`.
80
+ * Without this fix Ajv compares the whole array value against the primitive enum
81
+ * and rejects every input. Returns a deep clone; the input schema is untouched.
82
+ */
83
+ function normalizeSchema(schema: Record<string, unknown>): Record<string, unknown> {
84
+ return walk(schema) as Record<string, unknown>;
85
+ }
86
+
87
+ function walk(node: unknown): unknown {
88
+ if (Array.isArray(node)) {
89
+ return node.map(walk);
90
+ }
91
+ if (!node || typeof node !== "object") {
92
+ return node;
93
+ }
94
+
95
+ const out: Record<string, unknown> = {};
96
+ for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
97
+ out[key] = walk(value);
98
+ }
99
+
100
+ if (out.type === "array" && Array.isArray(out.enum) && out.enum.every(isPrimitive)) {
101
+ const items = out.items;
102
+ const enumValues = out.enum as JsonPrimitive[];
103
+ if (items && typeof items === "object" && !Array.isArray(items)) {
104
+ const itemsObj = items as Record<string, unknown>;
105
+ const enumType = primitiveJsonType(enumValues[0]!);
106
+ const allSameType = enumValues.every((v) => primitiveJsonType(v) === enumType);
107
+ const itemsTypeMatches =
108
+ itemsObj.type === undefined ||
109
+ itemsObj.type === enumType ||
110
+ (Array.isArray(itemsObj.type) && itemsObj.type.includes(enumType));
111
+ if (allSameType && itemsTypeMatches && itemsObj.enum === undefined) {
112
+ out.items = { ...itemsObj, enum: enumValues };
113
+ delete out.enum;
114
+ }
115
+ }
116
+ }
117
+
118
+ return out;
119
+ }
120
+
63
121
  function formatAjvError(err: ErrorObject): ValidationError {
64
122
  const path = err.instancePath ? err.instancePath.replace(/^\//, "").replace(/\//g, ".") : "(root)";
65
123