@famgia/omnify-mcp 0.0.2

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/dist/index.js ADDED
@@ -0,0 +1,675 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema,
9
+ ListResourcesRequestSchema,
10
+ ReadResourceRequestSchema
11
+ } from "@modelcontextprotocol/sdk/types.js";
12
+ import { z } from "zod";
13
+ import * as yaml from "yaml";
14
+ var PropertyTypeEnum = z.enum([
15
+ "String",
16
+ "Int",
17
+ "BigInt",
18
+ "Float",
19
+ "Decimal",
20
+ "Boolean",
21
+ "Text",
22
+ "LongText",
23
+ "Date",
24
+ "Time",
25
+ "Timestamp",
26
+ "Json",
27
+ "Email",
28
+ "Password",
29
+ "File",
30
+ "Point",
31
+ "Coordinates",
32
+ "Enum",
33
+ "EnumRef",
34
+ "Association"
35
+ ]);
36
+ var RelationTypeEnum = z.enum([
37
+ "OneToOne",
38
+ "OneToMany",
39
+ "ManyToOne",
40
+ "ManyToMany",
41
+ "MorphTo",
42
+ "MorphOne",
43
+ "MorphMany",
44
+ "MorphToMany",
45
+ "MorphedByMany"
46
+ ]);
47
+ var ReferentialActionEnum = z.enum([
48
+ "CASCADE",
49
+ "SET NULL",
50
+ "SET DEFAULT",
51
+ "RESTRICT",
52
+ "NO ACTION"
53
+ ]);
54
+ var LocalizedStringSchema = z.union([
55
+ z.string(),
56
+ z.record(z.string())
57
+ ]);
58
+ var CreateSchemaInputSchema = z.object({
59
+ name: z.string().describe("Schema name in PascalCase (e.g., User, BlogPost)"),
60
+ kind: z.enum(["object", "enum"]).default("object").describe("Schema kind"),
61
+ displayName: LocalizedStringSchema.optional().describe("Human-readable name (string or locale map)"),
62
+ group: z.string().optional().describe("Schema group for organization"),
63
+ properties: z.record(z.object({
64
+ type: PropertyTypeEnum,
65
+ displayName: LocalizedStringSchema.optional(),
66
+ nullable: z.boolean().optional(),
67
+ unique: z.boolean().optional(),
68
+ default: z.unknown().optional(),
69
+ length: z.number().optional(),
70
+ unsigned: z.boolean().optional(),
71
+ precision: z.number().optional(),
72
+ scale: z.number().optional(),
73
+ enum: z.union([z.string(), z.array(z.unknown())]).optional(),
74
+ relation: RelationTypeEnum.optional(),
75
+ target: z.string().optional(),
76
+ onDelete: ReferentialActionEnum.optional(),
77
+ joinTable: z.string().optional(),
78
+ multiple: z.boolean().optional()
79
+ })).optional(),
80
+ values: z.array(z.union([
81
+ z.string(),
82
+ z.object({
83
+ value: z.string(),
84
+ label: LocalizedStringSchema.optional()
85
+ })
86
+ ])).optional().describe("Enum values (only for kind: enum)"),
87
+ options: z.object({
88
+ softDelete: z.boolean().optional(),
89
+ timestamps: z.boolean().optional(),
90
+ authenticatable: z.boolean().optional(),
91
+ tableName: z.string().optional(),
92
+ idType: z.enum(["BigInt", "Int", "Uuid", "String"]).optional()
93
+ }).optional()
94
+ });
95
+ function getPropertyTypes() {
96
+ return `# Omnify Property Types
97
+
98
+ ## Primitive Types
99
+ - **String**: VARCHAR(255) - short text, use \`length\` for custom size
100
+ - **Int**: INTEGER - 32-bit integer
101
+ - **BigInt**: BIGINT - 64-bit integer (default for IDs)
102
+ - **Float**: FLOAT - floating point number
103
+ - **Decimal**: DECIMAL(precision, scale) - exact decimal, use for money
104
+ - **Boolean**: BOOLEAN - true/false
105
+
106
+ ## Text Types
107
+ - **Text**: TEXT (~65KB) - medium text content
108
+ - **LongText**: LONGTEXT (~4GB) - large text content
109
+
110
+ ## Date/Time Types
111
+ - **Date**: DATE - date only (YYYY-MM-DD)
112
+ - **Time**: TIME - time only (HH:MM:SS)
113
+ - **Timestamp**: TIMESTAMP - date and time
114
+
115
+ ## Special Types
116
+ - **Email**: VARCHAR(255) - validated email format
117
+ - **Password**: VARCHAR(255) - hashed password (auto-hidden)
118
+ - **Json**: JSON - structured data
119
+ - **File**: Polymorphic file attachment
120
+
121
+ ## Enum Types
122
+ - **Enum**: Inline enum values
123
+ - **EnumRef**: Reference to shared enum schema
124
+
125
+ ## Association Types (Relations)
126
+ - **Association**: Relationship to another schema
127
+ - relation: OneToOne, OneToMany, ManyToOne, ManyToMany
128
+ - relation: MorphTo, MorphOne, MorphMany (polymorphic)
129
+ - target: Target schema name
130
+ - onDelete: CASCADE, SET NULL, RESTRICT
131
+ `;
132
+ }
133
+ function getRelationshipGuide() {
134
+ return `# Omnify Relationship Guide
135
+
136
+ ## Standard Relations
137
+
138
+ ### ManyToOne (belongs to)
139
+ Most common - creates foreign key column.
140
+ \`\`\`yaml
141
+ author:
142
+ type: Association
143
+ relation: ManyToOne
144
+ target: User
145
+ onDelete: CASCADE
146
+ \`\`\`
147
+
148
+ ### OneToMany (has many)
149
+ Inverse of ManyToOne - no column created.
150
+ \`\`\`yaml
151
+ posts:
152
+ type: Association
153
+ relation: OneToMany
154
+ target: Post
155
+ \`\`\`
156
+
157
+ ### ManyToMany
158
+ Creates pivot table automatically.
159
+ \`\`\`yaml
160
+ tags:
161
+ type: Association
162
+ relation: ManyToMany
163
+ target: Tag
164
+ joinTable: post_tags # optional custom name
165
+ \`\`\`
166
+
167
+ ### OneToOne
168
+ Unique foreign key relationship.
169
+ \`\`\`yaml
170
+ profile:
171
+ type: Association
172
+ relation: OneToOne
173
+ target: UserProfile
174
+ \`\`\`
175
+
176
+ ## Polymorphic Relations
177
+
178
+ ### MorphTo (owning side)
179
+ Creates {name}_type and {name}_id columns.
180
+ \`\`\`yaml
181
+ commentable:
182
+ type: Association
183
+ relation: MorphTo
184
+ \`\`\`
185
+
186
+ ### MorphMany (inverse side)
187
+ No columns - defines inverse relationship.
188
+ \`\`\`yaml
189
+ comments:
190
+ type: Association
191
+ relation: MorphMany
192
+ target: Comment
193
+ \`\`\`
194
+ `;
195
+ }
196
+ function getExampleSchemas() {
197
+ return `# Omnify Schema Examples
198
+
199
+ ## User Schema (with authentication)
200
+ \`\`\`yaml
201
+ name: User
202
+ displayName:
203
+ ja: \u30E6\u30FC\u30B6\u30FC
204
+ en: User
205
+ options:
206
+ softDelete: true
207
+ authenticatable: true
208
+ properties:
209
+ name:
210
+ type: String
211
+ displayName:
212
+ ja: \u6C0F\u540D
213
+ en: Full Name
214
+ email:
215
+ type: Email
216
+ unique: true
217
+ displayName:
218
+ ja: \u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9
219
+ en: Email Address
220
+ password:
221
+ type: Password
222
+ posts:
223
+ type: Association
224
+ relation: OneToMany
225
+ target: Post
226
+ \`\`\`
227
+
228
+ ## Post Schema (with relations)
229
+ \`\`\`yaml
230
+ name: Post
231
+ displayName:
232
+ ja: \u6295\u7A3F
233
+ en: Post
234
+ options:
235
+ softDelete: true
236
+ indexes:
237
+ - columns: [published_at]
238
+ - columns: [status, published_at]
239
+ properties:
240
+ title:
241
+ type: String
242
+ displayName:
243
+ ja: \u30BF\u30A4\u30C8\u30EB
244
+ en: Title
245
+ slug:
246
+ type: String
247
+ unique: true
248
+ content:
249
+ type: LongText
250
+ displayName:
251
+ ja: \u672C\u6587
252
+ en: Content
253
+ status:
254
+ type: EnumRef
255
+ enum: PostStatus
256
+ default: draft
257
+ published_at:
258
+ type: Timestamp
259
+ nullable: true
260
+ author:
261
+ type: Association
262
+ relation: ManyToOne
263
+ target: User
264
+ onDelete: CASCADE
265
+ category:
266
+ type: Association
267
+ relation: ManyToOne
268
+ target: Category
269
+ tags:
270
+ type: Association
271
+ relation: ManyToMany
272
+ target: Tag
273
+ joinTable: post_tags
274
+ comments:
275
+ type: Association
276
+ relation: MorphMany
277
+ target: Comment
278
+ \`\`\`
279
+
280
+ ## Enum Schema
281
+ \`\`\`yaml
282
+ name: PostStatus
283
+ kind: enum
284
+ displayName:
285
+ ja: \u6295\u7A3F\u30B9\u30C6\u30FC\u30BF\u30B9
286
+ en: Post Status
287
+ values:
288
+ - value: draft
289
+ label:
290
+ ja: \u4E0B\u66F8\u304D
291
+ en: Draft
292
+ - value: pending
293
+ label:
294
+ ja: \u5BE9\u67FB\u4E2D
295
+ en: Pending Review
296
+ - value: published
297
+ label:
298
+ ja: \u516C\u958B\u6E08\u307F
299
+ en: Published
300
+ - value: archived
301
+ label:
302
+ ja: \u30A2\u30FC\u30AB\u30A4\u30D6
303
+ en: Archived
304
+ \`\`\`
305
+
306
+ ## Category Schema (self-referencing)
307
+ \`\`\`yaml
308
+ name: Category
309
+ displayName:
310
+ ja: \u30AB\u30C6\u30B4\u30EA
311
+ en: Category
312
+ properties:
313
+ name:
314
+ type: String
315
+ length: 100
316
+ slug:
317
+ type: String
318
+ unique: true
319
+ description:
320
+ type: Text
321
+ nullable: true
322
+ parent:
323
+ type: Association
324
+ relation: ManyToOne
325
+ target: Category
326
+ nullable: true
327
+ children:
328
+ type: Association
329
+ relation: OneToMany
330
+ target: Category
331
+ posts:
332
+ type: Association
333
+ relation: OneToMany
334
+ target: Post
335
+ \`\`\`
336
+ `;
337
+ }
338
+ function createSchema(input) {
339
+ const schema = {
340
+ name: input.name
341
+ };
342
+ if (input.kind && input.kind !== "object") {
343
+ schema.kind = input.kind;
344
+ }
345
+ if (input.displayName) {
346
+ schema.displayName = input.displayName;
347
+ }
348
+ if (input.group) {
349
+ schema.group = input.group;
350
+ }
351
+ if (input.options && Object.keys(input.options).length > 0) {
352
+ schema.options = input.options;
353
+ }
354
+ if (input.properties && Object.keys(input.properties).length > 0) {
355
+ schema.properties = input.properties;
356
+ }
357
+ if (input.values && input.values.length > 0) {
358
+ schema.values = input.values;
359
+ }
360
+ return yaml.stringify(schema);
361
+ }
362
+ function validateSchemaYaml(yamlContent) {
363
+ const errors = [];
364
+ const warnings = [];
365
+ try {
366
+ const schema = yaml.parse(yamlContent);
367
+ if (!schema.name) {
368
+ errors.push("Missing required field: name");
369
+ }
370
+ if (schema.kind && !["object", "enum"].includes(schema.kind)) {
371
+ errors.push(`Invalid kind: ${schema.kind}. Must be 'object' or 'enum'`);
372
+ }
373
+ if (schema.kind === "enum") {
374
+ if (!schema.values || schema.values.length === 0) {
375
+ errors.push("Enum schema must have values array");
376
+ }
377
+ if (schema.properties && Object.keys(schema.properties).length > 0) {
378
+ warnings.push("Enum schemas should not have properties");
379
+ }
380
+ }
381
+ if (schema.kind !== "enum") {
382
+ if (schema.values && schema.values.length > 0) {
383
+ warnings.push("Object schemas should not have values (use kind: enum for enums)");
384
+ }
385
+ if (schema.properties) {
386
+ for (const [propName, propDef] of Object.entries(schema.properties)) {
387
+ const prop = propDef;
388
+ if (!prop.type) {
389
+ errors.push(`Property '${propName}' missing required field: type`);
390
+ }
391
+ if (prop.type === "Association") {
392
+ if (!prop.relation) {
393
+ errors.push(`Property '${propName}' (Association) missing required field: relation`);
394
+ }
395
+ if (prop.relation !== "MorphTo" && !prop.target) {
396
+ errors.push(`Property '${propName}' (Association) missing required field: target`);
397
+ }
398
+ }
399
+ if (prop.type === "EnumRef" && !prop.enum) {
400
+ errors.push(`Property '${propName}' (EnumRef) missing required field: enum`);
401
+ }
402
+ if (prop.type === "Enum" && !prop.enum) {
403
+ errors.push(`Property '${propName}' (Enum) missing required field: enum`);
404
+ }
405
+ }
406
+ }
407
+ }
408
+ return { valid: errors.length === 0, errors, warnings };
409
+ } catch (e) {
410
+ return {
411
+ valid: false,
412
+ errors: [`Invalid YAML: ${e instanceof Error ? e.message : "Unknown error"}`],
413
+ warnings: []
414
+ };
415
+ }
416
+ }
417
+ var server = new Server(
418
+ {
419
+ name: "omnify-mcp",
420
+ version: "0.0.1"
421
+ },
422
+ {
423
+ capabilities: {
424
+ tools: {},
425
+ resources: {}
426
+ }
427
+ }
428
+ );
429
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
430
+ return {
431
+ tools: [
432
+ {
433
+ name: "omnify_create_schema",
434
+ description: "Create an Omnify schema YAML file. Use this to generate database models with Laravel and TypeScript support.",
435
+ inputSchema: {
436
+ type: "object",
437
+ properties: {
438
+ name: {
439
+ type: "string",
440
+ description: "Schema name in PascalCase (e.g., User, BlogPost)"
441
+ },
442
+ kind: {
443
+ type: "string",
444
+ enum: ["object", "enum"],
445
+ default: "object",
446
+ description: "Schema kind - object for tables, enum for enumeration types"
447
+ },
448
+ displayName: {
449
+ oneOf: [
450
+ { type: "string" },
451
+ { type: "object", additionalProperties: { type: "string" } }
452
+ ],
453
+ description: 'Human-readable name. Can be string or locale map like { ja: "\u65E5\u672C\u8A9E", en: "English" }'
454
+ },
455
+ group: {
456
+ type: "string",
457
+ description: "Schema group for organization (e.g., auth, blog, shop)"
458
+ },
459
+ properties: {
460
+ type: "object",
461
+ description: "Property definitions. Each property has type and optional modifiers.",
462
+ additionalProperties: {
463
+ type: "object",
464
+ properties: {
465
+ type: {
466
+ type: "string",
467
+ enum: ["String", "Int", "BigInt", "Float", "Decimal", "Boolean", "Text", "LongText", "Date", "Time", "Timestamp", "Json", "Email", "Password", "File", "Enum", "EnumRef", "Association"]
468
+ },
469
+ displayName: {
470
+ oneOf: [
471
+ { type: "string" },
472
+ { type: "object", additionalProperties: { type: "string" } }
473
+ ]
474
+ },
475
+ nullable: { type: "boolean" },
476
+ unique: { type: "boolean" },
477
+ default: {},
478
+ length: { type: "number" },
479
+ enum: { oneOf: [{ type: "string" }, { type: "array" }] },
480
+ relation: { type: "string", enum: ["OneToOne", "OneToMany", "ManyToOne", "ManyToMany", "MorphTo", "MorphOne", "MorphMany"] },
481
+ target: { type: "string" },
482
+ onDelete: { type: "string", enum: ["CASCADE", "SET NULL", "RESTRICT"] },
483
+ joinTable: { type: "string" }
484
+ }
485
+ }
486
+ },
487
+ values: {
488
+ type: "array",
489
+ description: "Enum values (only for kind: enum)",
490
+ items: {
491
+ oneOf: [
492
+ { type: "string" },
493
+ { type: "object", properties: { value: { type: "string" }, label: {} } }
494
+ ]
495
+ }
496
+ },
497
+ options: {
498
+ type: "object",
499
+ properties: {
500
+ softDelete: { type: "boolean", description: "Enable soft deletes" },
501
+ timestamps: { type: "boolean", description: "Add created_at/updated_at" },
502
+ authenticatable: { type: "boolean", description: "Enable for User model" },
503
+ tableName: { type: "string", description: "Custom table name" },
504
+ idType: { type: "string", enum: ["BigInt", "Int", "Uuid", "String"] }
505
+ }
506
+ }
507
+ },
508
+ required: ["name"]
509
+ }
510
+ },
511
+ {
512
+ name: "omnify_validate_schema",
513
+ description: "Validate an Omnify schema YAML content. Returns validation errors and warnings.",
514
+ inputSchema: {
515
+ type: "object",
516
+ properties: {
517
+ yaml_content: {
518
+ type: "string",
519
+ description: "YAML content to validate"
520
+ }
521
+ },
522
+ required: ["yaml_content"]
523
+ }
524
+ },
525
+ {
526
+ name: "omnify_get_types",
527
+ description: "Get documentation for all Omnify property types and their options.",
528
+ inputSchema: {
529
+ type: "object",
530
+ properties: {}
531
+ }
532
+ },
533
+ {
534
+ name: "omnify_get_relationships",
535
+ description: "Get documentation for Omnify relationship types (OneToMany, ManyToOne, ManyToMany, polymorphic, etc.)",
536
+ inputSchema: {
537
+ type: "object",
538
+ properties: {}
539
+ }
540
+ },
541
+ {
542
+ name: "omnify_get_examples",
543
+ description: "Get example Omnify schemas for common use cases (User, Post, Category, Enum)",
544
+ inputSchema: {
545
+ type: "object",
546
+ properties: {}
547
+ }
548
+ }
549
+ ]
550
+ };
551
+ });
552
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
553
+ const { name, arguments: args } = request.params;
554
+ switch (name) {
555
+ case "omnify_create_schema": {
556
+ try {
557
+ const input = CreateSchemaInputSchema.parse(args);
558
+ const yamlContent = createSchema(input);
559
+ return {
560
+ content: [
561
+ {
562
+ type: "text",
563
+ text: `# Generated Schema: ${input.name}
564
+
565
+ \`\`\`yaml
566
+ ${yamlContent}\`\`\`
567
+
568
+ Save this to: schemas/${input.group || "default"}/${input.name}.yaml`
569
+ }
570
+ ]
571
+ };
572
+ } catch (e) {
573
+ return {
574
+ content: [
575
+ {
576
+ type: "text",
577
+ text: `Error creating schema: ${e instanceof Error ? e.message : "Unknown error"}`
578
+ }
579
+ ],
580
+ isError: true
581
+ };
582
+ }
583
+ }
584
+ case "omnify_validate_schema": {
585
+ const yamlContent = args.yaml_content;
586
+ const result = validateSchemaYaml(yamlContent);
587
+ let response = result.valid ? "\u2705 Schema is valid!\n\n" : "\u274C Schema has errors:\n\n";
588
+ if (result.errors.length > 0) {
589
+ response += "**Errors:**\n" + result.errors.map((e) => `- ${e}`).join("\n") + "\n\n";
590
+ }
591
+ if (result.warnings.length > 0) {
592
+ response += "**Warnings:**\n" + result.warnings.map((w) => `- ${w}`).join("\n");
593
+ }
594
+ return {
595
+ content: [{ type: "text", text: response }]
596
+ };
597
+ }
598
+ case "omnify_get_types": {
599
+ return {
600
+ content: [{ type: "text", text: getPropertyTypes() }]
601
+ };
602
+ }
603
+ case "omnify_get_relationships": {
604
+ return {
605
+ content: [{ type: "text", text: getRelationshipGuide() }]
606
+ };
607
+ }
608
+ case "omnify_get_examples": {
609
+ return {
610
+ content: [{ type: "text", text: getExampleSchemas() }]
611
+ };
612
+ }
613
+ default:
614
+ return {
615
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
616
+ isError: true
617
+ };
618
+ }
619
+ });
620
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
621
+ return {
622
+ resources: [
623
+ {
624
+ uri: "omnify://schema/json-schema",
625
+ name: "Omnify JSON Schema",
626
+ description: "JSON Schema for validating Omnify schema files",
627
+ mimeType: "application/json"
628
+ },
629
+ {
630
+ uri: "omnify://docs/types",
631
+ name: "Property Types Documentation",
632
+ description: "Documentation for all Omnify property types",
633
+ mimeType: "text/markdown"
634
+ },
635
+ {
636
+ uri: "omnify://docs/relationships",
637
+ name: "Relationships Documentation",
638
+ description: "Documentation for Omnify relationship types",
639
+ mimeType: "text/markdown"
640
+ },
641
+ {
642
+ uri: "omnify://examples/all",
643
+ name: "Schema Examples",
644
+ description: "Example schemas for common use cases",
645
+ mimeType: "text/markdown"
646
+ }
647
+ ]
648
+ };
649
+ });
650
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
651
+ const { uri } = request.params;
652
+ switch (uri) {
653
+ case "omnify://docs/types":
654
+ return {
655
+ contents: [{ uri, mimeType: "text/markdown", text: getPropertyTypes() }]
656
+ };
657
+ case "omnify://docs/relationships":
658
+ return {
659
+ contents: [{ uri, mimeType: "text/markdown", text: getRelationshipGuide() }]
660
+ };
661
+ case "omnify://examples/all":
662
+ return {
663
+ contents: [{ uri, mimeType: "text/markdown", text: getExampleSchemas() }]
664
+ };
665
+ default:
666
+ throw new Error(`Unknown resource: ${uri}`);
667
+ }
668
+ });
669
+ async function main() {
670
+ const transport = new StdioServerTransport();
671
+ await server.connect(transport);
672
+ console.error("Omnify MCP Server running on stdio");
673
+ }
674
+ main().catch(console.error);
675
+ //# sourceMappingURL=index.js.map