@decocms/bindings 0.1.0

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.
@@ -0,0 +1,403 @@
1
+ import { z } from "zod";
2
+ import type { ToolBinder } from "../core/binder";
3
+
4
+ /**
5
+ * Collection Bindings
6
+ *
7
+ * This module provides standardized tool bindings for Collections, representing
8
+ * SQL table-like structures with CRUD + Search operations compatible with TanStack DB.
9
+ *
10
+ * Key Features:
11
+ * - Generic collection bindings that work with any entity type
12
+ * - Standardized tool naming: `DECO_COLLECTION_{COLLECTION}_*`
13
+ * - Compatible with TanStack DB query-collection
14
+ * - Full TypeScript support with proper type constraints
15
+ * - Support for filtering, sorting, and pagination
16
+ * - Collection URI format: rsc://{connectionid}/{collection_name}/{item_id}
17
+ */
18
+
19
+ /**
20
+ * Collection URI format validation
21
+ * Format: rsc://{connectionid}/{collection_name}/{item_id}
22
+ */
23
+ export const CollectionUriSchema = z
24
+ .string()
25
+ .regex(
26
+ /^rsc:\/\/[^/]+\/[^/]+\/.+$/,
27
+ "Invalid collection URI format. Expected format: rsc://{connectionid}/{collection_name}/{item_id}",
28
+ );
29
+
30
+ /**
31
+ * Utility function to validate a collection URI format
32
+ *
33
+ * @param uri - The URI to validate
34
+ * @returns True if the URI is valid, false otherwise
35
+ */
36
+ export function validateCollectionUri(uri: string): boolean {
37
+ return CollectionUriSchema.safeParse(uri).success;
38
+ }
39
+
40
+ /**
41
+ * Utility function to parse a collection URI into its components
42
+ *
43
+ * @param uri - The URI to parse
44
+ * @returns Object containing the parsed components or null if invalid
45
+ */
46
+ export function parseCollectionUri(uri: string): {
47
+ connectionId: string;
48
+ collectionName: string;
49
+ itemId: string;
50
+ } | null {
51
+ const result = CollectionUriSchema.safeParse(uri);
52
+ if (!result.success) {
53
+ return null;
54
+ }
55
+
56
+ const match = result.data.match(/^rsc:\/\/([^/]+)\/([^/]+)\/(.+)$/);
57
+ if (!match) {
58
+ return null;
59
+ }
60
+
61
+ return {
62
+ connectionId: decodeURIComponent(match[1]),
63
+ collectionName: decodeURIComponent(match[2]),
64
+ itemId: decodeURIComponent(match[3]),
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Utility function to construct a collection URI from components
70
+ *
71
+ * @param connectionId - The connection identifier
72
+ * @param collectionName - The collection name
73
+ * @param itemId - The item identifier
74
+ * @returns The constructed collection URI
75
+ */
76
+ export function constructCollectionUri(
77
+ connectionId: string,
78
+ collectionName: string,
79
+ itemId: string,
80
+ ): string {
81
+ return `rsc://${encodeURIComponent(connectionId)}/${
82
+ encodeURIComponent(collectionName)
83
+ }/${encodeURIComponent(itemId)}`;
84
+ }
85
+
86
+ /**
87
+ * Base schema for collection entities
88
+ * All collection entities must have a uri field and audit trail fields
89
+ */
90
+ export const BaseCollectionEntitySchema = z.object({
91
+ uri: z.string(),
92
+ created_at: z.string().datetime(),
93
+ updated_at: z.string().datetime(),
94
+ created_by: z.string().optional(),
95
+ updated_by: z.string().optional(),
96
+ });
97
+
98
+ /**
99
+ * Type helper for BaseCollectionEntitySchema
100
+ */
101
+ export type BaseCollectionEntitySchemaType = typeof BaseCollectionEntitySchema;
102
+
103
+ /**
104
+ * Where expression type for filtering
105
+ * Supports TanStack DB predicate push-down patterns
106
+ */
107
+ export type WhereExpression =
108
+ | {
109
+ field: string[];
110
+ operator: "eq" | "gt" | "gte" | "lt" | "lte" | "in" | "like" | "contains";
111
+ value: unknown;
112
+ }
113
+ | {
114
+ operator: "and" | "or" | "not";
115
+ conditions: WhereExpression[];
116
+ };
117
+
118
+ /**
119
+ * Where expression schema for filtering
120
+ * Supports TanStack DB predicate push-down patterns
121
+ */
122
+ export const WhereExpressionSchema = z.lazy(() =>
123
+ z.union([
124
+ // Simple comparison
125
+ z.object({
126
+ field: z.array(z.string()),
127
+ operator: z.enum([
128
+ "eq",
129
+ "gt",
130
+ "gte",
131
+ "lt",
132
+ "lte",
133
+ "in",
134
+ "like",
135
+ "contains",
136
+ ]),
137
+ value: z.unknown(),
138
+ }),
139
+ // Logical operators
140
+ z.object({
141
+ operator: z.enum(["and", "or", "not"]),
142
+ conditions: z.array(WhereExpressionSchema),
143
+ }),
144
+ ])
145
+ ) as z.ZodType<WhereExpression>;
146
+
147
+ /**
148
+ * Order by expression for sorting
149
+ */
150
+ export const OrderByExpressionSchema = z.object({
151
+ field: z.array(z.string()),
152
+ direction: z.enum(["asc", "desc"]),
153
+ nulls: z.enum(["first", "last"]).optional(),
154
+ });
155
+
156
+ /**
157
+ * List/Query input schema for collections
158
+ * Compatible with TanStack DB LoadSubsetOptions
159
+ */
160
+ export const CollectionListInputSchema = z.object({
161
+ where: WhereExpressionSchema.optional().describe("Filter expression"),
162
+ orderBy: z
163
+ .array(OrderByExpressionSchema)
164
+ .optional()
165
+ .describe("Sort expressions"),
166
+ limit: z
167
+ .number()
168
+ .int()
169
+ .min(1)
170
+ .max(1000)
171
+ .optional()
172
+ .describe("Maximum number of items to return"),
173
+ offset: z
174
+ .number()
175
+ .int()
176
+ .min(0)
177
+ .optional()
178
+ .describe("Number of items to skip"),
179
+ });
180
+
181
+ /**
182
+ * Factory function to create list output schema for a specific collection type
183
+ */
184
+ export function createCollectionListOutputSchema<T extends z.ZodTypeAny>(
185
+ entitySchema: T,
186
+ ) {
187
+ return z.object({
188
+ items: z.array(entitySchema).describe("Array of collection items"),
189
+ totalCount: z
190
+ .number()
191
+ .int()
192
+ .min(0)
193
+ .optional()
194
+ .describe("Total number of matching items (if available)"),
195
+ hasMore: z
196
+ .boolean()
197
+ .optional()
198
+ .describe("Whether there are more items available"),
199
+ });
200
+ }
201
+
202
+ /**
203
+ * Get by URI input schema
204
+ */
205
+ export const CollectionGetInputSchema = z.object({
206
+ uri: CollectionUriSchema.describe("URI of the entity to retrieve"),
207
+ });
208
+
209
+ /**
210
+ * Factory function to create get output schema
211
+ */
212
+ export function createCollectionGetOutputSchema<T extends z.ZodTypeAny>(
213
+ entitySchema: T,
214
+ ) {
215
+ return z.object({
216
+ item: entitySchema
217
+ .nullable()
218
+ .describe("The retrieved item, or null if not found"),
219
+ });
220
+ }
221
+
222
+ /**
223
+ * Factory function to create insert input schema
224
+ */
225
+ export function createCollectionInsertInputSchema<T extends z.ZodTypeAny>(
226
+ entitySchema: T,
227
+ ) {
228
+ // Remove uri field since it's auto-generated from connectionId/collectionName/itemId
229
+ return z.object({
230
+ data: entitySchema.describe("Data for the new entity (without uri)"),
231
+ });
232
+ }
233
+
234
+ /**
235
+ * Factory function to create insert output schema
236
+ */
237
+ export function createCollectionInsertOutputSchema<T extends z.ZodTypeAny>(
238
+ entitySchema: T,
239
+ ) {
240
+ return z.object({
241
+ item: entitySchema.describe("The created entity with generated URI"),
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Factory function to create update input schema
247
+ */
248
+ export function createCollectionUpdateInputSchema<T extends z.ZodTypeAny>(
249
+ entitySchema: T,
250
+ ) {
251
+ return z.object({
252
+ uri: CollectionUriSchema.describe("URI of the entity to update"),
253
+ data: (entitySchema as unknown as z.AnyZodObject)
254
+ .partial()
255
+ .describe("Partial entity data to update"),
256
+ });
257
+ }
258
+
259
+ /**
260
+ * Factory function to create update output schema
261
+ */
262
+ export function createCollectionUpdateOutputSchema<T extends z.ZodTypeAny>(
263
+ entitySchema: T,
264
+ ) {
265
+ return z.object({
266
+ item: entitySchema.describe("The updated entity"),
267
+ });
268
+ }
269
+
270
+ /**
271
+ * Delete input schema
272
+ */
273
+ export const CollectionDeleteInputSchema = z.object({
274
+ uri: CollectionUriSchema.describe("URI of the entity to delete"),
275
+ });
276
+
277
+ /**
278
+ * Delete output schema
279
+ */
280
+ export const CollectionDeleteOutputSchema = z.object({
281
+ success: z.boolean().describe("Whether the deletion was successful"),
282
+ uri: CollectionUriSchema.describe("URI of the deleted entity"),
283
+ });
284
+
285
+ /**
286
+ * Options for creating collection bindings
287
+ */
288
+ export interface CollectionBindingOptions {
289
+ /**
290
+ * If true, only LIST and GET operations will be included (read-only collection)
291
+ * @default false
292
+ */
293
+ readOnly?: boolean;
294
+ }
295
+
296
+ /**
297
+ * Creates generic collection bindings for a specific entity type
298
+ *
299
+ * This function generates standardized tool bindings that work with any collection/table
300
+ * by accepting a custom entity schema and collection name. The bindings provide:
301
+ * - DECO_COLLECTION_{NAME}_LIST - Query/search entities with filtering and sorting (required)
302
+ * - DECO_COLLECTION_{NAME}_GET - Get a single entity by URI (required)
303
+ * - DECO_COLLECTION_{NAME}_INSERT - Create a new entity (optional, excluded if readOnly=true)
304
+ * - DECO_COLLECTION_{NAME}_UPDATE - Update an existing entity (optional, excluded if readOnly=true)
305
+ * - DECO_COLLECTION_{NAME}_DELETE - Delete an entity (optional, excluded if readOnly=true)
306
+ *
307
+ * @param collectionName - The name of the collection/table (e.g., "users", "products", "orders")
308
+ * @param entitySchema - The Zod schema for the entity type (must extend BaseCollectionEntitySchema)
309
+ * @param options - Optional configuration for the collection bindings
310
+ * @returns Array of tool bindings for Collection CRUD + Query operations
311
+ *
312
+ * @example
313
+ * ```typescript
314
+ * const UserSchema = z.object({
315
+ * uri: z.string(),
316
+ * created_at: z.string().datetime(),
317
+ * updated_at: z.string().datetime(),
318
+ * created_by: z.string().optional(),
319
+ * updated_by: z.string().optional(),
320
+ * name: z.string(),
321
+ * email: z.string().email(),
322
+ * });
323
+ *
324
+ * // Full CRUD collection
325
+ * const USER_COLLECTION_BINDING = createCollectionBindings("users", UserSchema);
326
+ *
327
+ * // Read-only collection (only LIST and GET)
328
+ * const READONLY_COLLECTION_BINDING = createCollectionBindings("products", ProductSchema, { readOnly: true });
329
+ * ```
330
+ */
331
+ export function createCollectionBindings<
332
+ TEntitySchema extends BaseCollectionEntitySchemaType,
333
+ >(
334
+ collectionName: string,
335
+ entitySchema: TEntitySchema,
336
+ options?: CollectionBindingOptions,
337
+ ) {
338
+ const upperName = collectionName.toUpperCase();
339
+ const readOnly = options?.readOnly ?? false;
340
+
341
+ const bindings: ToolBinder[] = [
342
+ {
343
+ name: `DECO_COLLECTION_${upperName}_LIST` as const,
344
+ inputSchema: CollectionListInputSchema,
345
+ outputSchema: createCollectionListOutputSchema(entitySchema),
346
+ },
347
+ {
348
+ name: `DECO_COLLECTION_${upperName}_GET` as const,
349
+ inputSchema: CollectionGetInputSchema,
350
+ outputSchema: createCollectionGetOutputSchema(entitySchema),
351
+ },
352
+ ];
353
+
354
+ // Only include mutation operations if not read-only
355
+ if (!readOnly) {
356
+ bindings.push(
357
+ {
358
+ name: `DECO_COLLECTION_${upperName}_INSERT` as const,
359
+ inputSchema: createCollectionInsertInputSchema(entitySchema),
360
+ outputSchema: createCollectionInsertOutputSchema(entitySchema),
361
+ opt: true,
362
+ },
363
+ {
364
+ name: `DECO_COLLECTION_${upperName}_UPDATE` as const,
365
+ inputSchema: createCollectionUpdateInputSchema(entitySchema),
366
+ outputSchema: createCollectionUpdateOutputSchema(entitySchema),
367
+ opt: true,
368
+ },
369
+ {
370
+ name: `DECO_COLLECTION_${upperName}_DELETE` as const,
371
+ inputSchema: CollectionDeleteInputSchema,
372
+ outputSchema: CollectionDeleteOutputSchema,
373
+ opt: true,
374
+ },
375
+ );
376
+ }
377
+
378
+ return bindings satisfies readonly ToolBinder[];
379
+ }
380
+
381
+ /**
382
+ * Type helper to extract the collection binding type
383
+ */
384
+ export type CollectionBinding<
385
+ TEntitySchema extends BaseCollectionEntitySchemaType,
386
+ > = ReturnType<typeof createCollectionBindings<TEntitySchema>>;
387
+
388
+ /**
389
+ * Type helper to extract tool names from a collection binding
390
+ */
391
+ export type CollectionTools<
392
+ TEntitySchema extends BaseCollectionEntitySchemaType,
393
+ > = CollectionBinding<TEntitySchema>[number]["name"];
394
+
395
+ // Export types for TypeScript usage
396
+ export type CollectionUri = z.infer<typeof CollectionUriSchema>;
397
+ export type CollectionListInput = z.infer<typeof CollectionListInputSchema>;
398
+ export type CollectionGetInput = z.infer<typeof CollectionGetInputSchema>;
399
+ export type CollectionDeleteInput = z.infer<typeof CollectionDeleteInputSchema>;
400
+ export type CollectionDeleteOutput = z.infer<
401
+ typeof CollectionDeleteOutputSchema
402
+ >;
403
+ export type OrderByExpression = z.infer<typeof OrderByExpressionSchema>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Models Well-Known Binding
3
+ *
4
+ * Defines the interface for AI model providers.
5
+ * Any MCP that implements this binding can provide AI models and streaming endpoints.
6
+ *
7
+ * This binding uses collection bindings for LIST and GET operations (read-only),
8
+ * and adds a GET_STREAM_ENDPOINT tool for streaming functionality.
9
+ */
10
+
11
+ import { z } from "zod";
12
+ import type { Binder } from "../core/binder";
13
+ import {
14
+ BaseCollectionEntitySchema,
15
+ createCollectionBindings,
16
+ } from "./collections";
17
+
18
+ /**
19
+ * Model entity schema for AI models
20
+ * Extends BaseCollectionEntitySchema with model-specific fields
21
+ */
22
+ const ModelSchema = BaseCollectionEntitySchema.extend({
23
+ // Model-specific fields
24
+ id: z.string(),
25
+ model: z.string(),
26
+ name: z.string(),
27
+ logo: z.string().nullable(),
28
+ capabilities: z.array(z.string()),
29
+ contextWindow: z.number().nullable(),
30
+ inputCost: z.number().nullable(),
31
+ outputCost: z.number().nullable(),
32
+ outputLimit: z.number().nullable(),
33
+ description: z.string().nullable(),
34
+ });
35
+
36
+ /**
37
+ * MODELS Collection Binding
38
+ *
39
+ * Collection bindings for models (read-only).
40
+ * Provides LIST and GET operations for AI models.
41
+ */
42
+ export const MODELS_COLLECTION_BINDING = createCollectionBindings(
43
+ "models",
44
+ ModelSchema,
45
+ { readOnly: true },
46
+ );
47
+
48
+ /**
49
+ * MODELS Binding
50
+ *
51
+ * Defines the interface for AI model providers.
52
+ * Any MCP that implements this binding can provide AI models and streaming endpoints.
53
+ *
54
+ * Required tools:
55
+ * - DECO_COLLECTION_MODELS_LIST: List available AI models with their capabilities
56
+ * - DECO_COLLECTION_MODELS_GET: Get a single model by URI
57
+ * - GET_STREAM_ENDPOINT: Get the streaming endpoint URL for chat completions
58
+ */
59
+ export const MODELS_BINDING = [
60
+ ...MODELS_COLLECTION_BINDING,
61
+ {
62
+ name: "GET_STREAM_ENDPOINT" as const,
63
+ inputSchema: z.object({}).passthrough(),
64
+ outputSchema: z.object({
65
+ url: z.string().url(),
66
+ }).partial(),
67
+ },
68
+ ] as const satisfies Binder;
69
+