@ikuma.cloud/pix-mcp 0.0.1

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/src/schema.ts ADDED
@@ -0,0 +1,97 @@
1
+ import type { TSchema } from "@earendil-works/pi-ai";
2
+ import type { jsonSchemaValidator } from "@modelcontextprotocol/sdk/validation";
3
+ import { Ajv2020 } from "ajv/dist/2020.js";
4
+ import { Compile } from "typebox/compile";
5
+
6
+ const syntax = new Ajv2020({ strict: false, validateFormats: false });
7
+ const dialect = "https://json-schema.org/draft/2020-12/schema";
8
+ const schemaMaps = new Set([
9
+ "$defs",
10
+ "definitions",
11
+ "properties",
12
+ "patternProperties",
13
+ "dependentSchemas",
14
+ ]);
15
+ const schemaArrays = new Set(["allOf", "anyOf", "oneOf", "prefixItems"]);
16
+ const schemaValues = new Set([
17
+ "additionalProperties",
18
+ "unevaluatedProperties",
19
+ "propertyNames",
20
+ "not",
21
+ "if",
22
+ "then",
23
+ "else",
24
+ "contains",
25
+ "additionalItems",
26
+ "unevaluatedItems",
27
+ "contentSchema",
28
+ ]);
29
+
30
+ function checkSchemaSupport(value: unknown): void {
31
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
32
+ const schema = value as Record<string, unknown>;
33
+ // Pi validates the exposed schema with TypeBox before execute. A different
34
+ // private validator cannot repair legacy semantics there (e.g. draft-07 must
35
+ // ignore $ref siblings). Reject legacy dialects, including embedded resources,
36
+ // rather than misrepresent them as supported or silently relabeling the schema.
37
+ if (
38
+ schema.$schema !== undefined &&
39
+ (typeof schema.$schema !== "string" ||
40
+ schema.$schema.replace(/#$/, "") !== dialect)
41
+ ) {
42
+ throw new Error("Invalid or unsupported MCP JSON Schema dialect.");
43
+ }
44
+ for (const keyword of ["$ref", "$dynamicRef", "$recursiveRef"]) {
45
+ const reference = schema[keyword];
46
+ if (typeof reference === "string" && !reference.startsWith("#")) {
47
+ throw new Error("External MCP schema references are unsupported.");
48
+ }
49
+ }
50
+ // const/enum/default/examples and unknown annotation keywords contain instance
51
+ // data, not schemas: a literal {$ref: 'https://...'} there must remain legal.
52
+ for (const [keyword, child] of Object.entries(schema)) {
53
+ if (schemaMaps.has(keyword) || keyword === "dependencies") {
54
+ for (const nested of Object.values(child as Record<string, unknown>))
55
+ checkSchemaSupport(nested);
56
+ } else if (schemaArrays.has(keyword) || keyword === "items") {
57
+ if (Array.isArray(child)) child.forEach(checkSchemaSupport);
58
+ else checkSchemaSupport(child);
59
+ } else if (schemaValues.has(keyword)) checkSchemaSupport(child);
60
+ }
61
+ }
62
+
63
+ export function compileSchema(schema: Record<string, unknown>) {
64
+ const serialized = JSON.stringify(schema);
65
+ if (Buffer.byteLength(serialized) > 64 * 1024)
66
+ throw new Error("MCP schema exceeds 64 KiB.");
67
+ const checkDepth = (value: unknown, depth: number): void => {
68
+ if (depth > 64) throw new Error("MCP schema is too deeply nested.");
69
+ if (!value || typeof value !== "object") return;
70
+ for (const child of Object.values(value)) checkDepth(child, depth + 1);
71
+ };
72
+ checkDepth(schema, 0);
73
+ // TypeBox code generation is not schema syntax validation: unknown type names
74
+ // can compile into always-true checks. Validate the dialect's meta-schema first.
75
+ // Validate against the supported meta-schema directly, without consulting an
76
+ // untrusted $schema URI or attempting to load a remote meta-schema.
77
+ if (syntax.validate(dialect, schema) !== true)
78
+ throw new Error("Invalid or unsupported MCP JSON Schema.");
79
+ checkSchemaSupport(schema);
80
+ return Compile(schema as TSchema);
81
+ }
82
+
83
+ // Use Pi's validator family for SDK output validation too, rather than silently
84
+ // downgrading MCP's default 2020-12 schemas to the SDK's default draft-07 validator.
85
+ export const schemaValidator: jsonSchemaValidator = {
86
+ getValidator<T>(schema: Record<string, unknown>) {
87
+ const validator = compileSchema(schema);
88
+ return (input: unknown) =>
89
+ validator.Check(input)
90
+ ? { valid: true, data: input as T, errorMessage: undefined }
91
+ : {
92
+ valid: false,
93
+ data: undefined,
94
+ errorMessage: "Result does not match the advertised MCP schema.",
95
+ };
96
+ },
97
+ };