@decocms/bindings 0.2.4 → 1.0.0-test.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.
Files changed (54) hide show
  1. package/README.md +3 -3
  2. package/package.json +9 -35
  3. package/src/core/binder.ts +241 -0
  4. package/src/core/client/README.md +3 -0
  5. package/{dist/core/client/http-client-transport.js → src/core/client/http-client-transport.ts} +24 -12
  6. package/src/core/client/index.ts +1 -0
  7. package/src/core/client/mcp-client.ts +149 -0
  8. package/src/core/client/mcp.ts +93 -0
  9. package/src/core/client/proxy.ts +151 -0
  10. package/src/core/connection.ts +38 -0
  11. package/src/core/subset.ts +514 -0
  12. package/src/index.ts +15 -0
  13. package/src/well-known/agent.ts +60 -0
  14. package/src/well-known/collections.ts +416 -0
  15. package/src/well-known/language-model.ts +383 -0
  16. package/test/index.test.ts +942 -0
  17. package/tsconfig.json +11 -0
  18. package/vitest.config.ts +8 -0
  19. package/dist/core/binder.d.ts +0 -3
  20. package/dist/core/binder.js +0 -77
  21. package/dist/core/binder.js.map +0 -1
  22. package/dist/core/client/http-client-transport.d.ts +0 -12
  23. package/dist/core/client/http-client-transport.js.map +0 -1
  24. package/dist/core/client/index.d.ts +0 -3
  25. package/dist/core/client/index.js +0 -5
  26. package/dist/core/client/index.js.map +0 -1
  27. package/dist/core/client/mcp-client.d.ts +0 -233
  28. package/dist/core/client/mcp-client.js +0 -99
  29. package/dist/core/client/mcp-client.js.map +0 -1
  30. package/dist/core/client/mcp.d.ts +0 -3
  31. package/dist/core/client/mcp.js +0 -29
  32. package/dist/core/client/mcp.js.map +0 -1
  33. package/dist/core/client/proxy.d.ts +0 -10
  34. package/dist/core/client/proxy.js +0 -104
  35. package/dist/core/client/proxy.js.map +0 -1
  36. package/dist/core/connection.d.ts +0 -30
  37. package/dist/core/connection.js +0 -1
  38. package/dist/core/connection.js.map +0 -1
  39. package/dist/core/subset.d.ts +0 -17
  40. package/dist/core/subset.js +0 -319
  41. package/dist/core/subset.js.map +0 -1
  42. package/dist/index-D0aUdNls.d.ts +0 -153
  43. package/dist/index.d.ts +0 -3
  44. package/dist/index.js +0 -7
  45. package/dist/index.js.map +0 -1
  46. package/dist/well-known/agent.d.ts +0 -903
  47. package/dist/well-known/agent.js +0 -27
  48. package/dist/well-known/agent.js.map +0 -1
  49. package/dist/well-known/collections.d.ts +0 -537
  50. package/dist/well-known/collections.js +0 -134
  51. package/dist/well-known/collections.js.map +0 -1
  52. package/dist/well-known/language-model.d.ts +0 -2836
  53. package/dist/well-known/language-model.js +0 -209
  54. package/dist/well-known/language-model.js.map +0 -1
@@ -0,0 +1,151 @@
1
+ /* oxlint-disable no-explicit-any */
2
+ import { convertJsonSchemaToZod } from "zod-from-json-schema";
3
+ import type { CreateStubAPIOptions } from "./mcp";
4
+ import { createServerClient } from "./mcp-client";
5
+
6
+ const safeParse = (content: string) => {
7
+ try {
8
+ return JSON.parse(content as string);
9
+ } catch {
10
+ return content;
11
+ }
12
+ };
13
+
14
+ const toolsMap = new Map<
15
+ string,
16
+ Promise<
17
+ Array<{
18
+ name: string;
19
+ inputSchema: any;
20
+ outputSchema?: any;
21
+ description: string;
22
+ }>
23
+ >
24
+ >();
25
+
26
+ /**
27
+ * The base fetcher used to fetch the MCP from API.
28
+ */
29
+ export function createMCPClientProxy<T extends Record<string, unknown>>(
30
+ options: CreateStubAPIOptions,
31
+ ): T {
32
+ return new Proxy<T>({} as T, {
33
+ get(_, name) {
34
+ if (name === "toJSON") {
35
+ return null;
36
+ }
37
+ if (typeof name !== "string") {
38
+ throw new Error("Name must be a string");
39
+ }
40
+ async function callToolFn(args: unknown) {
41
+ const debugId = options?.debugId?.();
42
+ const extraHeaders = debugId
43
+ ? { "x-trace-debug-id": debugId }
44
+ : undefined;
45
+
46
+ const { client, callStreamableTool } = await createServerClient(
47
+ { connection: options.connection },
48
+ undefined,
49
+ extraHeaders,
50
+ );
51
+
52
+ if (options?.streamable?.[String(name)]) {
53
+ return callStreamableTool(String(name), args);
54
+ }
55
+
56
+ const { structuredContent, isError, content } = await client.callTool(
57
+ {
58
+ name: String(name),
59
+ arguments: args as Record<string, unknown>,
60
+ },
61
+ undefined,
62
+ {
63
+ timeout: 3000000,
64
+ },
65
+ );
66
+
67
+ if (isError) {
68
+ const maybeErrorMessage = (content as { text: string }[])?.[0]?.text;
69
+ const error =
70
+ typeof maybeErrorMessage === "string"
71
+ ? safeParse(maybeErrorMessage)
72
+ : null;
73
+
74
+ const throwableError =
75
+ error?.code && typeof options?.getErrorByStatusCode === "function"
76
+ ? options.getErrorByStatusCode(
77
+ error.code,
78
+ error.message,
79
+ error.traceId,
80
+ )
81
+ : null;
82
+
83
+ if (throwableError) {
84
+ throw throwableError;
85
+ }
86
+
87
+ throw new Error(
88
+ `Tool ${String(name)} returned an error: ${JSON.stringify(
89
+ structuredContent ?? content,
90
+ )}`,
91
+ );
92
+ }
93
+ return structuredContent;
94
+ }
95
+
96
+ const listToolsFn = async () => {
97
+ const { client } = await createServerClient({
98
+ connection: options.connection,
99
+ });
100
+ const { tools } = await client.listTools();
101
+
102
+ return tools as {
103
+ name: string;
104
+ inputSchema: any;
105
+ outputSchema?: any;
106
+ description: string;
107
+ }[];
108
+ };
109
+
110
+ async function listToolsOnce() {
111
+ const conn = options.connection;
112
+ const key = JSON.stringify(conn);
113
+
114
+ try {
115
+ if (!toolsMap.has(key)) {
116
+ toolsMap.set(key, listToolsFn());
117
+ }
118
+
119
+ return await toolsMap.get(key)!;
120
+ } catch (error) {
121
+ console.error("Failed to list tools", error);
122
+
123
+ toolsMap.delete(key);
124
+ return;
125
+ }
126
+ }
127
+ callToolFn.asTool = async () => {
128
+ const tools = (await listToolsOnce()) ?? [];
129
+ const tool = tools.find((t) => t.name === name);
130
+ if (!tool) {
131
+ throw new Error(`Tool ${name} not found`);
132
+ }
133
+
134
+ return {
135
+ ...tool,
136
+ id: tool.name,
137
+ inputSchema: tool.inputSchema
138
+ ? convertJsonSchemaToZod(tool.inputSchema)
139
+ : undefined,
140
+ outputSchema: tool.outputSchema
141
+ ? convertJsonSchemaToZod(tool.outputSchema)
142
+ : undefined,
143
+ execute: (input: any) => {
144
+ return callToolFn(input.context);
145
+ },
146
+ };
147
+ };
148
+ return callToolFn;
149
+ },
150
+ });
151
+ }
@@ -0,0 +1,38 @@
1
+ export type SSEConnection = {
2
+ type: "SSE";
3
+ url: string;
4
+ token?: string;
5
+ headers?: Record<string, string>;
6
+ };
7
+
8
+ export type WebsocketConnection = {
9
+ type: "Websocket";
10
+ url: string;
11
+ token?: string;
12
+ };
13
+
14
+ export type DecoConnection = {
15
+ type: "Deco";
16
+ tenant: string;
17
+ token?: string;
18
+ };
19
+
20
+ export type InnateConnection = {
21
+ type: "INNATE";
22
+ name: string;
23
+ workspace?: string;
24
+ };
25
+
26
+ export type HTTPConnection = {
27
+ type: "HTTP";
28
+ url: string;
29
+ headers?: Record<string, string>;
30
+ token?: string;
31
+ };
32
+
33
+ export type MCPConnection =
34
+ | SSEConnection
35
+ | WebsocketConnection
36
+ | InnateConnection
37
+ | DecoConnection
38
+ | HTTPConnection;
@@ -0,0 +1,514 @@
1
+ /**
2
+ * Structural JSON Schema Subset Check
3
+ *
4
+ * This module implements an algorithm to determine if one JSON Schema (A)
5
+ * is a subset of another (B). A ⊆ B means every value valid under A is also
6
+ * valid under B (A is more restrictive or equally restrictive).
7
+ *
8
+ * Core Axiom: A ⊆ B ⟺ Constraints(A) ⊇ Constraints(B)
9
+ */
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ type JSONSchema = Record<string, any>;
13
+
14
+ /**
15
+ * Deep equality check for JSON values (for const/enum comparison)
16
+ */
17
+ function deepEqual(x: unknown, y: unknown): boolean {
18
+ if (x === y) return true;
19
+ if (typeof x !== typeof y) return false;
20
+ if (x === null || y === null) return x === y;
21
+ if (typeof x !== "object") return false;
22
+
23
+ if (Array.isArray(x)) {
24
+ if (!Array.isArray(y)) return false;
25
+ if (x.length !== y.length) return false;
26
+ for (let i = 0; i < x.length; i++) {
27
+ if (!deepEqual(x[i], y[i])) return false;
28
+ }
29
+ return true;
30
+ }
31
+
32
+ if (Array.isArray(y)) return false;
33
+
34
+ const xObj = x as Record<string, unknown>;
35
+ const yObj = y as Record<string, unknown>;
36
+ const xKeys = Object.keys(xObj);
37
+ const yKeys = Object.keys(yObj);
38
+
39
+ if (xKeys.length !== yKeys.length) return false;
40
+
41
+ for (const key of xKeys) {
42
+ if (!Object.prototype.hasOwnProperty.call(yObj, key)) return false;
43
+ if (!deepEqual(xObj[key], yObj[key])) return false;
44
+ }
45
+
46
+ return true;
47
+ }
48
+
49
+ /**
50
+ * Phase 1: Normalization
51
+ * Convert syntactic sugar to canonical form
52
+ */
53
+ function normalize(schema: JSONSchema | boolean): JSONSchema {
54
+ // Boolean schemas
55
+ if (schema === true) return {};
56
+ if (schema === false) return { not: {} };
57
+
58
+ // Already an object
59
+ const s = { ...schema };
60
+
61
+ // Type arrays -> anyOf
62
+ if (Array.isArray(s.type)) {
63
+ const types = s.type as string[];
64
+ if (types.length === 1) {
65
+ s.type = types[0];
66
+ } else {
67
+ const { type: _type, ...rest } = s;
68
+ return {
69
+ anyOf: types.map((t) => normalize({ ...rest, type: t })),
70
+ };
71
+ }
72
+ }
73
+
74
+ // Integer is number with multipleOf: 1
75
+ if (s.type === "integer") {
76
+ s.type = "number";
77
+ if (s.multipleOf === undefined) {
78
+ s.multipleOf = 1;
79
+ }
80
+ }
81
+
82
+ return s;
83
+ }
84
+
85
+ /**
86
+ * Check if set A is a subset of set B (for required fields)
87
+ */
88
+ function isSetSubset(a: string[], b: string[]): boolean {
89
+ const setB = new Set(b);
90
+ return a.every((item) => setB.has(item));
91
+ }
92
+
93
+ /**
94
+ * Get effective minimum value from schema
95
+ */
96
+ function getEffectiveMin(schema: JSONSchema): number {
97
+ const min = schema.minimum ?? -Infinity;
98
+ const exMin = schema.exclusiveMinimum;
99
+
100
+ if (typeof exMin === "number") {
101
+ return Math.max(min, exMin);
102
+ }
103
+ if (exMin === true && typeof schema.minimum === "number") {
104
+ return schema.minimum;
105
+ }
106
+ return min;
107
+ }
108
+
109
+ /**
110
+ * Get effective maximum value from schema
111
+ */
112
+ function getEffectiveMax(schema: JSONSchema): number {
113
+ const max = schema.maximum ?? Infinity;
114
+ const exMax = schema.exclusiveMaximum;
115
+
116
+ if (typeof exMax === "number") {
117
+ return Math.min(max, exMax);
118
+ }
119
+ if (exMax === true && typeof schema.maximum === "number") {
120
+ return schema.maximum;
121
+ }
122
+ return max;
123
+ }
124
+
125
+ /**
126
+ * Check if multipleOfA is a multiple of multipleOfB
127
+ * (i.e., A's constraint is tighter)
128
+ */
129
+ function isMultipleOf(a: number, b: number): boolean {
130
+ if (b === 0) return false;
131
+ // Handle floating point precision
132
+ const ratio = a / b;
133
+ return Math.abs(ratio - Math.round(ratio)) < 1e-10;
134
+ }
135
+
136
+ /**
137
+ * Check if A's enum values are all valid in B
138
+ */
139
+ function isEnumSubset(enumA: unknown[], schemaB: JSONSchema): boolean {
140
+ // If B has enum, check set inclusion
141
+ if (schemaB.enum) {
142
+ return enumA.every((val) =>
143
+ schemaB.enum.some((bVal: unknown) => deepEqual(val, bVal)),
144
+ );
145
+ }
146
+
147
+ // If B has const, check if A's enum only contains that value
148
+ if (schemaB.const !== undefined) {
149
+ return enumA.length === 1 && deepEqual(enumA[0], schemaB.const);
150
+ }
151
+
152
+ // Otherwise, enum values must match B's type constraints
153
+ // This is a simplified check - full validation would require more
154
+ return true;
155
+ }
156
+
157
+ /**
158
+ * Main subset check: isSubset(A, B)
159
+ * Returns true if A ⊆ B (every value valid under A is valid under B)
160
+ */
161
+ export function isSubset(
162
+ schemaA: JSONSchema | boolean,
163
+ schemaB: JSONSchema | boolean,
164
+ ): boolean {
165
+ // Phase 1: Normalize
166
+ const a = normalize(schemaA);
167
+ const b = normalize(schemaB);
168
+
169
+ // Phase 2: Meta Logic - Universal Terminators
170
+
171
+ // If B is {} (Any), everything is a subset
172
+ if (Object.keys(b).length === 0) return true;
173
+
174
+ // If A is false (Never), empty set is subset of everything
175
+ if (a.not && Object.keys(a.not).length === 0) return true;
176
+
177
+ // Deep equality check
178
+ if (deepEqual(a, b)) return true;
179
+
180
+ // Phase 2: Unions and Intersections
181
+
182
+ // Left-side union (anyOf in A): all options must fit in B
183
+ if (a.anyOf) {
184
+ return (a.anyOf as JSONSchema[]).every((optA) => isSubset(optA, b));
185
+ }
186
+
187
+ // Left-side union (oneOf in A): all options must fit in B
188
+ if (a.oneOf) {
189
+ return (a.oneOf as JSONSchema[]).every((optA) => isSubset(optA, b));
190
+ }
191
+
192
+ // Right-side union (anyOf in B): A must fit in at least one option
193
+ if (b.anyOf) {
194
+ return (b.anyOf as JSONSchema[]).some((optB) => isSubset(a, optB));
195
+ }
196
+
197
+ // Right-side union (oneOf in B): A must fit in at least one option
198
+ if (b.oneOf) {
199
+ return (b.oneOf as JSONSchema[]).some((optB) => isSubset(a, optB));
200
+ }
201
+
202
+ // Right-side intersection (allOf in B): A must satisfy all
203
+ if (b.allOf) {
204
+ return (b.allOf as JSONSchema[]).every((optB) => isSubset(a, optB));
205
+ }
206
+
207
+ // Left-side intersection (allOf in A): merge and compare
208
+ if (a.allOf) {
209
+ // Simplified: check if any single branch satisfies B
210
+ return (a.allOf as JSONSchema[]).some((optA) => isSubset(optA, b));
211
+ }
212
+
213
+ // Phase 3: Type-specific logic
214
+
215
+ // Handle const in A
216
+ if (a.const !== undefined) {
217
+ if (b.const !== undefined) {
218
+ return deepEqual(a.const, b.const);
219
+ }
220
+ if (b.enum) {
221
+ return b.enum.some((v: unknown) => deepEqual(a.const, v));
222
+ }
223
+ // const must match B's type constraints
224
+ return isValueValidForType(a.const, b);
225
+ }
226
+
227
+ // Handle enum in A
228
+ if (a.enum) {
229
+ return isEnumSubset(a.enum, b);
230
+ }
231
+
232
+ // Type mismatch check
233
+ if (a.type && b.type && a.type !== b.type) {
234
+ return false;
235
+ }
236
+
237
+ // If B has a type but A doesn't, A might allow more types
238
+ if (b.type && !a.type) {
239
+ // A is more permissive (no type restriction) so it's not a subset
240
+ // unless A has other constraints that limit it
241
+ if (!a.enum && a.const === undefined) {
242
+ return false;
243
+ }
244
+ }
245
+
246
+ const type = a.type || b.type;
247
+
248
+ switch (type) {
249
+ case "object":
250
+ return isObjectSubset(a, b);
251
+ case "array":
252
+ return isArraySubset(a, b);
253
+ case "number":
254
+ return isNumberSubset(a, b);
255
+ case "string":
256
+ return isStringSubset(a, b);
257
+ case "boolean":
258
+ case "null":
259
+ // These types have no additional constraints
260
+ return true;
261
+ default:
262
+ // Unknown type or no type specified
263
+ return true;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Check if a value would be valid for a schema's type
269
+ */
270
+ function isValueValidForType(value: unknown, schema: JSONSchema): boolean {
271
+ if (!schema.type) return true;
272
+
273
+ const valueType = typeof value;
274
+ switch (schema.type) {
275
+ case "string":
276
+ return valueType === "string";
277
+ case "number":
278
+ return valueType === "number";
279
+ case "boolean":
280
+ return valueType === "boolean";
281
+ case "null":
282
+ return value === null;
283
+ case "object":
284
+ return valueType === "object" && value !== null && !Array.isArray(value);
285
+ case "array":
286
+ return Array.isArray(value);
287
+ default:
288
+ return true;
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Object subset check
294
+ */
295
+ function isObjectSubset(a: JSONSchema, b: JSONSchema): boolean {
296
+ // Required keys: A must require at least everything B requires
297
+ const aRequired = (a.required as string[]) || [];
298
+ const bRequired = (b.required as string[]) || [];
299
+
300
+ if (!isSetSubset(bRequired, aRequired)) {
301
+ return false;
302
+ }
303
+
304
+ // Property compatibility
305
+ const aProps = (a.properties as Record<string, JSONSchema>) || {};
306
+ const bProps = (b.properties as Record<string, JSONSchema>) || {};
307
+
308
+ // Check all properties defined in B
309
+ for (const key of Object.keys(bProps)) {
310
+ if (key in aProps) {
311
+ // Both have the property, check recursively
312
+ if (!isSubset(aProps[key], bProps[key])) {
313
+ return false;
314
+ }
315
+ } else {
316
+ // Property missing in A
317
+ // If A is closed (additionalProperties: false), A won't produce this key
318
+ // which means A values won't have this property, potentially violating B if B requires it
319
+ if (a.additionalProperties === false) {
320
+ // A is closed and doesn't have this property
321
+ // If B requires this property, A can't satisfy it
322
+ if (bRequired.includes(key)) {
323
+ return false;
324
+ }
325
+ // Otherwise, A just won't have this optional property, which is fine
326
+ } else {
327
+ // A is open, check if additionalProperties schema satisfies B's property
328
+ const aAdditional = a.additionalProperties;
329
+ if (aAdditional && typeof aAdditional === "object") {
330
+ if (!isSubset(aAdditional, bProps[key])) {
331
+ return false;
332
+ }
333
+ }
334
+ // If additionalProperties is true or undefined, any value is allowed
335
+ // which might not satisfy B's property schema
336
+ }
337
+ }
338
+ }
339
+
340
+ // Check all properties defined in A (that A requires)
341
+ // If A requires a property, B must also define it (or have compatible additionalProperties)
342
+ for (const key of aRequired) {
343
+ if (key in aProps && !(key in bProps)) {
344
+ // A requires and defines this property, but B doesn't define it
345
+ // B must have additionalProperties that accepts A's property schema
346
+ if (b.additionalProperties === false) {
347
+ // B doesn't allow additional properties, so A's required property would be rejected
348
+ return false;
349
+ } else if (
350
+ b.additionalProperties &&
351
+ typeof b.additionalProperties === "object"
352
+ ) {
353
+ // B has a schema for additional properties, check compatibility
354
+ if (!isSubset(aProps[key], b.additionalProperties)) {
355
+ return false;
356
+ }
357
+ }
358
+ // If B's additionalProperties is true or undefined, any value is allowed
359
+ }
360
+ }
361
+
362
+ // Additional properties constraint
363
+ if (b.additionalProperties === false) {
364
+ // B is closed, A must also be closed or not have extra properties
365
+ const aHasExtraProps = Object.keys(aProps).some((key) => !(key in bProps));
366
+ if (aHasExtraProps) {
367
+ return false;
368
+ }
369
+ // If A is open and B is closed, A could produce extra properties
370
+ if (a.additionalProperties !== false) {
371
+ return false;
372
+ }
373
+ } else if (
374
+ b.additionalProperties &&
375
+ typeof b.additionalProperties === "object"
376
+ ) {
377
+ // B has a schema for additional properties
378
+ const aAdditional = a.additionalProperties;
379
+ if (aAdditional && typeof aAdditional === "object") {
380
+ if (!isSubset(aAdditional, b.additionalProperties)) {
381
+ return false;
382
+ }
383
+ }
384
+ }
385
+
386
+ return true;
387
+ }
388
+
389
+ /**
390
+ * Array subset check
391
+ */
392
+ function isArraySubset(a: JSONSchema, b: JSONSchema): boolean {
393
+ // Items schema
394
+ if (a.items && b.items) {
395
+ if (Array.isArray(a.items) && Array.isArray(b.items)) {
396
+ // Both are tuples
397
+ if (a.items.length !== b.items.length) {
398
+ return false;
399
+ }
400
+ for (let i = 0; i < a.items.length; i++) {
401
+ if (!isSubset(a.items[i], b.items[i])) {
402
+ return false;
403
+ }
404
+ }
405
+ } else if (Array.isArray(a.items) && !Array.isArray(b.items)) {
406
+ // A is tuple, B is list
407
+ for (const itemSchema of a.items) {
408
+ if (!isSubset(itemSchema, b.items)) {
409
+ return false;
410
+ }
411
+ }
412
+ } else if (!Array.isArray(a.items) && Array.isArray(b.items)) {
413
+ // A is list, B is tuple - A is more permissive
414
+ return false;
415
+ } else {
416
+ // Both are lists
417
+ if (!isSubset(a.items, b.items)) {
418
+ return false;
419
+ }
420
+ }
421
+ } else if (b.items && !a.items) {
422
+ // B has items constraint but A doesn't
423
+ return false;
424
+ }
425
+
426
+ // Length constraints
427
+ const aMinItems = a.minItems ?? 0;
428
+ const bMinItems = b.minItems ?? 0;
429
+ if (aMinItems < bMinItems) {
430
+ return false;
431
+ }
432
+
433
+ const aMaxItems = a.maxItems ?? Infinity;
434
+ const bMaxItems = b.maxItems ?? Infinity;
435
+ if (aMaxItems > bMaxItems) {
436
+ return false;
437
+ }
438
+
439
+ // Uniqueness
440
+ if (b.uniqueItems && !a.uniqueItems) {
441
+ return false;
442
+ }
443
+
444
+ return true;
445
+ }
446
+
447
+ /**
448
+ * Number subset check
449
+ */
450
+ function isNumberSubset(a: JSONSchema, b: JSONSchema): boolean {
451
+ // Minimum
452
+ const aMin = getEffectiveMin(a);
453
+ const bMin = getEffectiveMin(b);
454
+ if (aMin < bMin) {
455
+ return false;
456
+ }
457
+
458
+ // Maximum
459
+ const aMax = getEffectiveMax(a);
460
+ const bMax = getEffectiveMax(b);
461
+ if (aMax > bMax) {
462
+ return false;
463
+ }
464
+
465
+ // MultipleOf
466
+ if (b.multipleOf !== undefined) {
467
+ if (a.multipleOf === undefined) {
468
+ // A doesn't have multipleOf constraint, so it's more permissive
469
+ return false;
470
+ }
471
+ if (!isMultipleOf(a.multipleOf, b.multipleOf)) {
472
+ return false;
473
+ }
474
+ }
475
+
476
+ return true;
477
+ }
478
+
479
+ /**
480
+ * String subset check
481
+ */
482
+ function isStringSubset(a: JSONSchema, b: JSONSchema): boolean {
483
+ // Length constraints
484
+ const aMinLength = a.minLength ?? 0;
485
+ const bMinLength = b.minLength ?? 0;
486
+ if (aMinLength < bMinLength) {
487
+ return false;
488
+ }
489
+
490
+ const aMaxLength = a.maxLength ?? Infinity;
491
+ const bMaxLength = b.maxLength ?? Infinity;
492
+ if (aMaxLength > bMaxLength) {
493
+ return false;
494
+ }
495
+
496
+ // Pattern (regex)
497
+ if (b.pattern) {
498
+ if (!a.pattern) {
499
+ // A has no pattern constraint, more permissive
500
+ return false;
501
+ }
502
+ // Exact match only (full regex subset check is computationally expensive)
503
+ if (a.pattern !== b.pattern) {
504
+ return false;
505
+ }
506
+ }
507
+
508
+ // Format (treat as informational, exact match required)
509
+ if (b.format && a.format !== b.format) {
510
+ return false;
511
+ }
512
+
513
+ return true;
514
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @decocms/bindings
3
+ *
4
+ * Core type definitions for the bindings system.
5
+ * Bindings define standardized interfaces that integrations (MCPs) can implement.
6
+ */
7
+
8
+ // Re-export core binder types and utilities
9
+ export {
10
+ createBindingChecker,
11
+ type Binder,
12
+ type BindingChecker,
13
+ type ToolBinder,
14
+ type ToolWithSchemas,
15
+ } from "./core/binder";