mcp-dndgrid 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.
Files changed (45) hide show
  1. package/PROJECT_SUMMARY.md +482 -0
  2. package/QUICKSTART.md +223 -0
  3. package/README.md +365 -0
  4. package/STATUS.md +315 -0
  5. package/USAGE_GUIDE.md +547 -0
  6. package/dist/chunk-CMGEAPA5.js +157 -0
  7. package/dist/chunk-CMGEAPA5.js.map +1 -0
  8. package/dist/chunk-QZHBI6ZI.js +5281 -0
  9. package/dist/chunk-QZHBI6ZI.js.map +1 -0
  10. package/dist/chunk-SEGVTWSK.js +44 -0
  11. package/dist/chunk-SEGVTWSK.js.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +248012 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/stdio-FWYJXSU7.js +101 -0
  16. package/dist/stdio-FWYJXSU7.js.map +1 -0
  17. package/dist/template-JDMAVVX7.js +9 -0
  18. package/dist/template-JDMAVVX7.js.map +1 -0
  19. package/examples/claude_desktop_config.example.json +12 -0
  20. package/examples/example-complex-editor.tsx +107 -0
  21. package/examples/example-dashboard.tsx +65 -0
  22. package/examples/example-ide-layout.tsx +53 -0
  23. package/examples/test-generator.ts +37 -0
  24. package/examples/test-parser.ts +121 -0
  25. package/examples/test-scenarios.md +496 -0
  26. package/package.json +42 -0
  27. package/src/index.ts +16 -0
  28. package/src/server.ts +314 -0
  29. package/src/tools/analyze-layout.ts +193 -0
  30. package/src/tools/apply-template.ts +125 -0
  31. package/src/tools/generate-layout.ts +235 -0
  32. package/src/tools/interactive-builder.ts +100 -0
  33. package/src/tools/validate-layout.ts +113 -0
  34. package/src/types/layout.ts +48 -0
  35. package/src/types/template.ts +181 -0
  36. package/src/utils/ast-parser.ts +264 -0
  37. package/src/utils/code-generator.ts +123 -0
  38. package/src/utils/layout-analyzer.ts +105 -0
  39. package/src/utils/layout-builder.ts +127 -0
  40. package/src/utils/validator.ts +263 -0
  41. package/stderr.log +1 -0
  42. package/stdout.log +0 -0
  43. package/test-mcp.js +27 -0
  44. package/tsconfig.json +29 -0
  45. package/tsup.config.ts +16 -0
package/src/server.ts ADDED
@@ -0,0 +1,314 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import {
3
+ CallToolRequestSchema,
4
+ ListToolsRequestSchema,
5
+ ListResourcesRequestSchema,
6
+ ReadResourceRequestSchema,
7
+ } from '@modelcontextprotocol/sdk/types.js';
8
+ import {
9
+ validateLayout,
10
+ ValidateLayoutInputSchema,
11
+ } from './tools/validate-layout.js';
12
+ import {
13
+ analyzeLayout,
14
+ AnalyzeLayoutInputSchema,
15
+ } from './tools/analyze-layout.js';
16
+ import {
17
+ applyTemplate,
18
+ ApplyTemplateInputSchema,
19
+ } from './tools/apply-template.js';
20
+ import {
21
+ generateLayout,
22
+ GenerateLayoutInputSchema,
23
+ } from './tools/generate-layout.js';
24
+ import {
25
+ interactiveBuilder,
26
+ InteractiveBuilderInputSchema,
27
+ } from './tools/interactive-builder.js';
28
+
29
+ import { zodToJsonSchema } from 'zod-to-json-schema';
30
+
31
+ /**
32
+ * DndGrid MCP Server
33
+ * Provides tools and resources for generating and analyzing DndGrid layouts
34
+ */
35
+ export class DndGridMCPServer {
36
+ private server: Server;
37
+
38
+ constructor() {
39
+ this.server = new Server(
40
+ {
41
+ name: 'dndgrid-mcp',
42
+ version: '0.1.0',
43
+ },
44
+ {
45
+ capabilities: {
46
+ tools: {},
47
+ resources: {},
48
+ },
49
+ }
50
+ );
51
+
52
+ this.setupHandlers();
53
+ }
54
+
55
+ private setupHandlers(): void {
56
+ // List available tools
57
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
58
+ console.error('ListToolsRequest received');
59
+ return {
60
+ tools: [
61
+ {
62
+ name: 'validate-layout',
63
+ description: 'Validate DndGrid layout structure and constraints',
64
+ inputSchema: zodToJsonSchema(ValidateLayoutInputSchema) as any,
65
+ },
66
+ {
67
+ name: 'analyze-layout',
68
+ description: 'Analyze existing DndGrid code and suggest improvements',
69
+ inputSchema: zodToJsonSchema(AnalyzeLayoutInputSchema) as any,
70
+ },
71
+ {
72
+ name: 'apply-template',
73
+ description: 'Apply a pre-defined layout template',
74
+ inputSchema: zodToJsonSchema(ApplyTemplateInputSchema) as any,
75
+ },
76
+ {
77
+ name: 'generate-layout',
78
+ description: 'Generate DndGrid layout code from natural language requirements',
79
+ inputSchema: zodToJsonSchema(GenerateLayoutInputSchema) as any,
80
+ },
81
+ {
82
+ name: 'interactive-builder',
83
+ description: 'Build layout interactively through guided steps',
84
+ inputSchema: zodToJsonSchema(InteractiveBuilderInputSchema) as any,
85
+ },
86
+ ],
87
+ };
88
+ });
89
+
90
+ // Handle tool calls
91
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
92
+ const { name, arguments: args } = request.params;
93
+ console.error(`CallToolRequest received: ${name}`);
94
+
95
+ try {
96
+ switch (name) {
97
+ case 'validate-layout':
98
+ return await validateLayout(args as any);
99
+ case 'analyze-layout':
100
+ return await analyzeLayout(args as any);
101
+ case 'apply-template':
102
+ return await applyTemplate(args as any);
103
+ case 'generate-layout':
104
+ return await generateLayout(args as any);
105
+ case 'interactive-builder':
106
+ return await interactiveBuilder(args as any);
107
+ default:
108
+ throw new Error(`Unknown tool: ${name}`);
109
+ }
110
+ } catch (error) {
111
+ return {
112
+ content: [
113
+ {
114
+ type: 'text',
115
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
116
+ },
117
+ ],
118
+ };
119
+ }
120
+ });
121
+
122
+ // List available resources
123
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
124
+ return {
125
+ resources: [
126
+ {
127
+ uri: 'dndgrid://docs/architecture',
128
+ name: 'DndGrid Architecture',
129
+ description: 'Complete architecture documentation',
130
+ mimeType: 'text/markdown',
131
+ },
132
+ {
133
+ uri: 'dndgrid://templates/list',
134
+ name: 'Available Templates',
135
+ description: 'List of all available layout templates',
136
+ mimeType: 'application/json',
137
+ },
138
+ {
139
+ uri: 'dndgrid://docs/best-practices',
140
+ name: 'Best Practices',
141
+ description: 'Performance and integration guidelines',
142
+ mimeType: 'text/markdown',
143
+ },
144
+ ],
145
+ };
146
+ });
147
+
148
+ // Read resource content
149
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
150
+ const { uri } = request.params;
151
+
152
+ if (uri === 'dndgrid://docs/architecture') {
153
+ return {
154
+ contents: [
155
+ {
156
+ uri,
157
+ mimeType: 'text/markdown',
158
+ text: this.getArchitectureDoc(),
159
+ },
160
+ ],
161
+ };
162
+ }
163
+
164
+ if (uri === 'dndgrid://templates/list') {
165
+ const { BUILTIN_TEMPLATES } = await import('./types/template.js');
166
+ return {
167
+ contents: [
168
+ {
169
+ uri,
170
+ mimeType: 'application/json',
171
+ text: JSON.stringify(BUILTIN_TEMPLATES, null, 2),
172
+ },
173
+ ],
174
+ };
175
+ }
176
+
177
+ if (uri === 'dndgrid://docs/best-practices') {
178
+ return {
179
+ contents: [
180
+ {
181
+ uri,
182
+ mimeType: 'text/markdown',
183
+ text: this.getBestPracticesDoc(),
184
+ },
185
+ ],
186
+ };
187
+ }
188
+
189
+ throw new Error(`Unknown resource: ${uri}`);
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Get architecture documentation
195
+ */
196
+ private getArchitectureDoc(): string {
197
+ return `# DndGrid Architecture
198
+
199
+ ## Overview
200
+
201
+ DndGrid is a tree-based drag-and-drop grid system for React applications.
202
+
203
+ ## Core Components
204
+
205
+ ### DndGridContainer
206
+ - Root component
207
+ - Props: \`width\`, \`height\`, \`children\`
208
+ - Manages global state
209
+
210
+ ### DndGridSplit
211
+ - Divides space into two sections
212
+ - Props: \`direction\` ('horizontal' | 'vertical'), \`ratio\` (0-1), \`children\` (exactly 2)
213
+ - Creates nested layouts
214
+
215
+ ### DndGridItem
216
+ - Leaf node containing user components
217
+ - Props: \`children\`
218
+ - Can be dragged and rearranged
219
+
220
+ ## Key Concepts
221
+
222
+ ### Flat Rendering
223
+ - All items rendered at same depth
224
+ - Preserves React component state during DnD
225
+ - Logical tree != rendering tree
226
+
227
+ ### Tree Structure
228
+ - Binary tree: each Split has 2 children
229
+ - ID system: binary indexing
230
+ - Supports unlimited nesting (recommended < 4 levels)
231
+
232
+ ### Next.js Compatibility
233
+ - Requires \`"use client"\` directive for App Router
234
+ - All DndGrid components are client components
235
+
236
+ ## Performance
237
+
238
+ - **Recommended:** < 20 items, < 4 depth levels
239
+ - **Maximum:** < 50 items, < 6 depth levels
240
+ - Flat rendering optimizes reconciliation
241
+ `;
242
+ }
243
+
244
+ /**
245
+ * Get best practices documentation
246
+ */
247
+ private getBestPracticesDoc(): string {
248
+ return `# DndGrid Best Practices
249
+
250
+ ## Performance
251
+
252
+ ### Item Count
253
+ - Keep below 20 items for optimal performance
254
+ - Maximum 50 items
255
+
256
+ ### Tree Depth
257
+ - Recommended: < 4 levels
258
+ - Maximum: 6 levels
259
+
260
+ ### Split Ratios
261
+ - Avoid extreme ratios (< 0.1 or > 0.9)
262
+ - Use 0.2-0.8 range for better UX
263
+
264
+ ## Next.js Integration
265
+
266
+ ### App Router
267
+ \`\`\`tsx
268
+ "use client";
269
+
270
+ import { DndGridContainer, DndGridSplit, DndGridItem } from 'zerojin/components';
271
+ \`\`\`
272
+
273
+ ### Pages Router
274
+ No "use client" directive needed
275
+
276
+ ## Common Patterns
277
+
278
+ ### IDE Layout
279
+ - Sidebar: 20%
280
+ - Editor: 56%
281
+ - Terminal: 24%
282
+
283
+ ### Dashboard (2x2)
284
+ - Equal splits: 50/50
285
+
286
+ ### Three Column
287
+ - Left: 20%
288
+ - Center: 60%
289
+ - Right: 20%
290
+
291
+ ## Troubleshooting
292
+
293
+ ### State Reset on DnD
294
+ - Cause: Server Component in Next.js App Router
295
+ - Fix: Add "use client" to all DndGrid components
296
+
297
+ ### Performance Issues
298
+ - Check item count and depth
299
+ - Simplify nested structures
300
+ - Consider lazy loading
301
+ `;
302
+ }
303
+
304
+ /**
305
+ * Start the MCP server
306
+ */
307
+ async start(): Promise<void> {
308
+ const transport = new (
309
+ await import('@modelcontextprotocol/sdk/server/stdio.js')
310
+ ).StdioServerTransport();
311
+ await this.server.connect(transport);
312
+ console.error('DndGrid MCP Server started');
313
+ }
314
+ }
@@ -0,0 +1,193 @@
1
+ import { z } from 'zod';
2
+ import { ASTParser } from '../utils/ast-parser.js';
3
+ import { Validator } from '../utils/validator.js';
4
+ import { LayoutAnalyzer } from '../utils/layout-analyzer.js';
5
+
6
+ /**
7
+ * Input schema for analyze-layout tool
8
+ */
9
+ export const AnalyzeLayoutInputSchema = z.object({
10
+ code: z.string().describe('DndGrid code to analyze'),
11
+ analyzePerformance: z.boolean().optional().default(true).describe('Include performance analysis'),
12
+ checkBestPractices: z.boolean().optional().default(true).describe('Check against best practices'),
13
+ });
14
+
15
+ export type AnalyzeLayoutInput = z.infer<typeof AnalyzeLayoutInputSchema>;
16
+
17
+ /**
18
+ * analyze-layout tool implementation
19
+ */
20
+ export async function analyzeLayout(args: AnalyzeLayoutInput) {
21
+ const { code, analyzePerformance, checkBestPractices } = args;
22
+
23
+ let output = `# DndGrid Layout Analysis\n\n`;
24
+
25
+ // Parse the code
26
+ const parser = new ASTParser();
27
+ const layout = parser.parse(code);
28
+
29
+ if (!layout) {
30
+ output += `❌ **Failed to parse layout**\n\n`;
31
+ output += `The code does not contain a valid DndGrid structure.\n`;
32
+ return {
33
+ content: [{ type: 'text' as const, text: output }],
34
+ };
35
+ }
36
+
37
+ // Structure analysis
38
+ output += `## Structure\n\n`;
39
+ const metadata = LayoutAnalyzer.calculateMetadata(layout);
40
+ output += `- **Container:** ${layout.width}x${layout.height}px\n`;
41
+ output += `- **Items:** ${metadata.itemCount}\n`;
42
+ output += `- **Splits:** ${metadata.splitCount}\n`;
43
+ output += `- **Max Depth:** ${metadata.maxDepth} levels\n`;
44
+ output += `- **Components:** ${LayoutAnalyzer.collectComponents(layout.child).join(', ')}\n\n`;
45
+
46
+ // Performance analysis
47
+ if (analyzePerformance) {
48
+ output += `## Performance\n\n`;
49
+ output += `**Score:** ${metadata.estimatedPerformance}\n\n`;
50
+
51
+ const recommendations: string[] = [];
52
+
53
+ if (metadata.itemCount > 20) {
54
+ recommendations.push(`Reduce item count from ${metadata.itemCount} to < 20 for better performance`);
55
+ }
56
+
57
+ if (metadata.maxDepth > 4) {
58
+ recommendations.push(`Reduce tree depth from ${metadata.maxDepth} to < 4 levels`);
59
+ }
60
+
61
+ if (metadata.splitCount > metadata.itemCount) {
62
+ recommendations.push('More splits than items - consider simplifying structure');
63
+ }
64
+
65
+ if (recommendations.length > 0) {
66
+ output += `**Recommendations:**\n\n`;
67
+ recommendations.forEach((rec, i) => {
68
+ output += `${i + 1}. ${rec}\n`;
69
+ });
70
+ output += '\n';
71
+ } else {
72
+ output += `✅ No performance issues detected\n\n`;
73
+ }
74
+ }
75
+
76
+ // Best practices
77
+ if (checkBestPractices) {
78
+ output += `## Best Practices Check\n\n`;
79
+
80
+ const checks = [];
81
+
82
+ // Check for "use client"
83
+ if (code.includes('"use client"') || code.includes("'use client'")) {
84
+ checks.push({ rule: 'Next.js "use client" directive', passed: true });
85
+ } else {
86
+ checks.push({
87
+ rule: 'Next.js "use client" directive',
88
+ passed: false,
89
+ message: 'Add "use client"; for Next.js App Router compatibility',
90
+ });
91
+ }
92
+
93
+ // Check imports
94
+ if (code.includes('zerojin/components')) {
95
+ checks.push({ rule: 'Correct import path', passed: true });
96
+ } else {
97
+ checks.push({
98
+ rule: 'Correct import path',
99
+ passed: false,
100
+ message: 'Import from "zerojin/components"',
101
+ });
102
+ }
103
+
104
+ // Check for reasonable dimensions
105
+ if (layout.width >= 400 && layout.height >= 300) {
106
+ checks.push({ rule: 'Reasonable container dimensions', passed: true });
107
+ } else {
108
+ checks.push({
109
+ rule: 'Reasonable container dimensions',
110
+ passed: false,
111
+ message: 'Container should be at least 400x300px',
112
+ });
113
+ }
114
+
115
+ checks.forEach((check) => {
116
+ const icon = check.passed ? '✅' : '❌';
117
+ output += `${icon} ${check.rule}\n`;
118
+ if (!check.passed && check.message) {
119
+ output += ` - ${check.message}\n`;
120
+ }
121
+ });
122
+ output += '\n';
123
+ }
124
+
125
+ // Validation
126
+ const validator = new Validator();
127
+ const validation = validator.validate(layout, true);
128
+
129
+ if (validation.errors.length > 0 || validation.warnings.length > 0) {
130
+ output += `## Issues\n\n`;
131
+
132
+ if (validation.errors.length > 0) {
133
+ output += `**Errors:**\n\n`;
134
+ validation.errors.forEach((err, i) => {
135
+ output += `${i + 1}. [${err.type}] ${err.message}\n`;
136
+ });
137
+ output += '\n';
138
+ }
139
+
140
+ if (validation.warnings.length > 0) {
141
+ output += `**Warnings:**\n\n`;
142
+ validation.warnings.forEach((warn, i) => {
143
+ output += `${i + 1}. ${warn.message}\n`;
144
+ });
145
+ output += '\n';
146
+ }
147
+ }
148
+
149
+ // Refactoring opportunities
150
+ output += `## Refactoring Opportunities\n\n`;
151
+
152
+ const opportunities = findRefactoringOpportunities(layout);
153
+ if (opportunities.length > 0) {
154
+ opportunities.forEach((opp, i) => {
155
+ output += `${i + 1}. **${opp.description}** (${opp.impact})\n`;
156
+ if (opp.example) {
157
+ output += `\`\`\`\n${opp.example}\n\`\`\`\n`;
158
+ }
159
+ });
160
+ } else {
161
+ output += `✅ No obvious refactoring opportunities\n`;
162
+ }
163
+
164
+ return {
165
+ content: [{ type: 'text' as const, text: output }],
166
+ };
167
+ }
168
+
169
+ function findRefactoringOpportunities(layout: any) {
170
+ const opportunities: Array<{
171
+ description: string;
172
+ impact: 'high' | 'medium' | 'low';
173
+ example?: string;
174
+ }> = [];
175
+
176
+ const metadata = LayoutAnalyzer.calculateMetadata(layout);
177
+
178
+ if (metadata.maxDepth > 5) {
179
+ opportunities.push({
180
+ description: 'Deep nesting detected - consider flattening structure',
181
+ impact: 'high',
182
+ });
183
+ }
184
+
185
+ if (metadata.itemCount > 30) {
186
+ opportunities.push({
187
+ description: 'High item count - consider lazy loading or pagination',
188
+ impact: 'medium',
189
+ });
190
+ }
191
+
192
+ return opportunities;
193
+ }
@@ -0,0 +1,125 @@
1
+ import { z } from 'zod';
2
+ import { BUILTIN_TEMPLATES, type TemplateNode } from '../types/template.js';
3
+ import type { LayoutTree, LayoutNode } from '../types/layout.js';
4
+ import { CodeGenerator } from '../utils/code-generator.js';
5
+
6
+ /**
7
+ * Input schema for apply-template tool
8
+ */
9
+ export const ApplyTemplateInputSchema = z.object({
10
+ templateName: z
11
+ .enum(['ide-layout', 'dashboard-2x2', 'three-column', 'split-view'])
12
+ .describe('Template to apply'),
13
+ components: z
14
+ .record(z.string())
15
+ .describe('Mapping of template slots to component names'),
16
+ width: z.number().optional().default(1200).describe('Container width'),
17
+ height: z.number().optional().default(800).describe('Container height'),
18
+ framework: z
19
+ .enum(['react', 'nextjs-app', 'nextjs-pages'])
20
+ .optional()
21
+ .default('nextjs-app')
22
+ .describe('Target framework'),
23
+ });
24
+
25
+ export type ApplyTemplateInput = z.infer<typeof ApplyTemplateInputSchema>;
26
+
27
+ /**
28
+ * apply-template tool implementation
29
+ */
30
+ export async function applyTemplate(args: ApplyTemplateInput) {
31
+ const { templateName, components, width, height, framework } = args;
32
+
33
+ const template = BUILTIN_TEMPLATES[templateName];
34
+ if (!template) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: 'text' as const,
39
+ text: `❌ Template "${templateName}" not found.\n\nAvailable templates: ${Object.keys(BUILTIN_TEMPLATES).join(', ')}`,
40
+ },
41
+ ],
42
+ };
43
+ }
44
+
45
+ // Validate that all required slots are provided
46
+ const missingSlots = template.slots.filter((slot) => !components[slot]);
47
+ if (missingSlots.length > 0) {
48
+ return {
49
+ content: [
50
+ {
51
+ type: 'text' as const,
52
+ text: `❌ Missing component mappings for slots: ${missingSlots.join(', ')}\n\nRequired slots: ${template.slots.join(', ')}`,
53
+ },
54
+ ],
55
+ };
56
+ }
57
+
58
+ // Apply components to template slots
59
+ const layoutNode = applyComponentsToSlots(template.tree, components);
60
+
61
+ // Build LayoutTree
62
+ const layout: LayoutTree = {
63
+ type: 'container',
64
+ width,
65
+ height,
66
+ child: layoutNode,
67
+ };
68
+
69
+ // Generate code
70
+ const generator = new CodeGenerator({
71
+ framework,
72
+ width,
73
+ height,
74
+ });
75
+ const code = generator.generate(layout);
76
+
77
+ // Format output
78
+ let output = `# Applied Template: ${template.name}\n\n`;
79
+ output += `${template.description}\n\n`;
80
+ output += `## Preview\n\n\`\`\`\n${template.preview}\n\`\`\`\n\n`;
81
+ output += `## Component Mapping\n\n`;
82
+ template.slots.forEach((slot) => {
83
+ output += `- **${slot}:** ${components[slot]}\n`;
84
+ });
85
+ output += `\n## Generated Code\n\n\`\`\`tsx\n${code}\`\`\`\n`;
86
+
87
+ return {
88
+ content: [{ type: 'text' as const, text: output }],
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Apply component mappings to template slots
94
+ */
95
+ function applyComponentsToSlots(
96
+ templateNode: TemplateNode,
97
+ components: Record<string, string>
98
+ ): LayoutNode {
99
+ if (templateNode.type === 'item') {
100
+ if (!templateNode.slot) {
101
+ throw new Error('Template item node missing slot');
102
+ }
103
+ const component = components[templateNode.slot];
104
+ if (!component) {
105
+ throw new Error(`No component provided for slot: ${templateNode.slot}`);
106
+ }
107
+ return {
108
+ type: 'item',
109
+ component,
110
+ };
111
+ }
112
+
113
+ // Split node
114
+ if (!templateNode.primary || !templateNode.secondary) {
115
+ throw new Error('Template split node missing children');
116
+ }
117
+
118
+ return {
119
+ type: 'split',
120
+ direction: templateNode.direction!,
121
+ ratio: templateNode.ratio!,
122
+ primary: applyComponentsToSlots(templateNode.primary, components),
123
+ secondary: applyComponentsToSlots(templateNode.secondary, components),
124
+ };
125
+ }