ebade 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,32 @@
1
+ {
2
+ "name": "@ebade/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP Server for ebade - Enable AI agents to build with the essence of code",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "ebade-mcp": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "ebade",
18
+ "ai",
19
+ "agent",
20
+ "framework"
21
+ ],
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.25.1",
25
+ "yaml": "^2.3.4",
26
+ "zod": "^3.22.4"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.10.0",
30
+ "typescript": "^5.3.0"
31
+ }
32
+ }
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ebade MCP Server
5
+ *
6
+ * This server enables AI agents to use ebade for building web applications.
7
+ * It exposes tools that allow agents to:
8
+ * - Scaffold projects from ebade definitions
9
+ * - Validate ebade files
10
+ * - Compile ebade to framework-specific code
11
+ * - Generate components from natural language descriptions
12
+ */
13
+
14
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
15
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import {
17
+ CallToolRequestSchema,
18
+ ListToolsRequestSchema,
19
+ ListResourcesRequestSchema,
20
+ ReadResourceRequestSchema,
21
+ } from "@modelcontextprotocol/sdk/types.js";
22
+
23
+ import { scaffoldProject } from "./tools/scaffold.js";
24
+ import { validateIntent } from "./tools/validate.js";
25
+ import { compileIntent } from "./tools/compile.js";
26
+ import { generateComponent } from "./tools/generate.js";
27
+
28
+ // Create the MCP server
29
+ const server = new Server(
30
+ {
31
+ name: "ebade",
32
+ version: "0.1.0",
33
+ },
34
+ {
35
+ capabilities: {
36
+ tools: {},
37
+ resources: {},
38
+ },
39
+ }
40
+ );
41
+
42
+ // List available tools
43
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
44
+ return {
45
+ tools: [
46
+ {
47
+ name: "ebade_scaffold",
48
+ description: `Scaffold a new project from an ebade definition.
49
+ This creates a complete Next.js project structure based on the ebade specification.
50
+
51
+ Use this when the user wants to:
52
+ - Start a new web project
53
+ - Create an e-commerce site, SaaS dashboard, blog, etc.
54
+ - Generate a full application structure
55
+
56
+ The ebade file uses YAML format with pages, components, data models, and API definitions.`,
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {
60
+ projectName: {
61
+ type: "string",
62
+ description: "Name of the project (kebab-case, e.g., 'my-store')",
63
+ },
64
+ projectType: {
65
+ type: "string",
66
+ enum: [
67
+ "e-commerce",
68
+ "saas-dashboard",
69
+ "blog",
70
+ "landing-page",
71
+ "portfolio",
72
+ "api-only",
73
+ ],
74
+ description: "Type of project to scaffold",
75
+ },
76
+ features: {
77
+ type: "array",
78
+ items: { type: "string" },
79
+ description:
80
+ "Features to include (e.g., 'user-auth', 'shopping-cart', 'dark-mode')",
81
+ },
82
+ outputDir: {
83
+ type: "string",
84
+ description: "Output directory path (absolute)",
85
+ },
86
+ },
87
+ required: ["projectName", "projectType", "outputDir"],
88
+ },
89
+ },
90
+ {
91
+ name: "ebade_validate",
92
+ description: `Validate an ebade definition file.
93
+ Checks for:
94
+ - Correct syntax and structure
95
+ - Valid decorator usage
96
+ - Proper data model relationships
97
+ - Missing required fields
98
+
99
+ Use this before scaffolding to ensure the ebade is valid.`,
100
+ inputSchema: {
101
+ type: "object",
102
+ properties: {
103
+ intentContent: {
104
+ type: "string",
105
+ description: "The ebade file content (YAML or TypeScript)",
106
+ },
107
+ },
108
+ required: ["intentContent"],
109
+ },
110
+ },
111
+ {
112
+ name: "ebade_compile",
113
+ description: `Compile a specific ebade definition into framework-specific code.
114
+ Returns the generated code for a specific page, component, or API route.
115
+
116
+ Use this when:
117
+ - User wants to see what code an ebade produces
118
+ - Building a specific component
119
+ - Iterating on a single ebade definition`,
120
+ inputSchema: {
121
+ type: "object",
122
+ properties: {
123
+ intent: {
124
+ type: "object",
125
+ description: "The ebade definition object",
126
+ properties: {
127
+ type: {
128
+ type: "string",
129
+ enum: ["page", "component", "api", "layout"],
130
+ },
131
+ name: { type: "string" },
132
+ path: { type: "string" },
133
+ components: {
134
+ type: "array",
135
+ items: { type: "string" },
136
+ },
137
+ auth: {
138
+ type: "string",
139
+ enum: ["none", "required", "optional"],
140
+ },
141
+ data: {
142
+ type: "array",
143
+ items: { type: "string" },
144
+ },
145
+ outcomes: {
146
+ type: "object",
147
+ },
148
+ },
149
+ },
150
+ target: {
151
+ type: "string",
152
+ enum: ["nextjs", "react", "vue", "svelte"],
153
+ description: "Target framework (default: nextjs)",
154
+ },
155
+ },
156
+ required: ["intent"],
157
+ },
158
+ },
159
+ {
160
+ name: "ebade_generate",
161
+ description: `Generate a component from a natural language description.
162
+ AI-powered ebade inference - describe what you want and get the ebade + code.
163
+
164
+ Use this when:
165
+ - User describes a component in natural language
166
+ - Quick prototyping
167
+ - Exploring ebade syntax and capabilities`,
168
+ inputSchema: {
169
+ type: "object",
170
+ properties: {
171
+ description: {
172
+ type: "string",
173
+ description:
174
+ "Natural language description of the component (e.g., 'a product card with image, title, price, and add to cart button')",
175
+ },
176
+ style: {
177
+ type: "string",
178
+ enum: [
179
+ "minimal-modern",
180
+ "bold-vibrant",
181
+ "dark-premium",
182
+ "glassmorphism",
183
+ ],
184
+ description: "Design style preference",
185
+ },
186
+ },
187
+ required: ["description"],
188
+ },
189
+ },
190
+ ],
191
+ };
192
+ });
193
+
194
+ // Handle tool calls
195
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
196
+ const { name, arguments: args } = request.params;
197
+
198
+ try {
199
+ switch (name) {
200
+ case "ebade_scaffold":
201
+ return {
202
+ content: [
203
+ {
204
+ type: "text",
205
+ text: await scaffoldProject(args as any),
206
+ },
207
+ ],
208
+ };
209
+
210
+ case "ebade_validate":
211
+ return await validateIntent({
212
+ intentContent: args?.intentContent as string,
213
+ });
214
+
215
+ case "ebade_compile":
216
+ return {
217
+ content: [
218
+ {
219
+ type: "text",
220
+ text: await compileIntent({
221
+ intent: args?.intent as any,
222
+ target:
223
+ (args?.target as "nextjs" | "react" | "vue" | "svelte") ||
224
+ "nextjs",
225
+ }),
226
+ },
227
+ ],
228
+ };
229
+
230
+ case "ebade_generate":
231
+ return await generateComponent({
232
+ description: args?.description as string,
233
+ style: args?.style as any,
234
+ });
235
+
236
+ default:
237
+ throw new Error(`Unknown tool: ${name}`);
238
+ }
239
+ } catch (error) {
240
+ return {
241
+ content: [
242
+ {
243
+ type: "text",
244
+ text: `Error: ${
245
+ error instanceof Error ? error.message : String(error)
246
+ }`,
247
+ },
248
+ ],
249
+ isError: true,
250
+ };
251
+ }
252
+ });
253
+
254
+ // List available resources (ebade templates, examples)
255
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
256
+ return {
257
+ resources: [
258
+ {
259
+ uri: "ebade://syntax",
260
+ name: "ebade Syntax Reference",
261
+ description: "The complete syntax guide for ebade definitions",
262
+ mimeType: "text/markdown",
263
+ },
264
+ {
265
+ uri: "ebade://examples/ecommerce",
266
+ name: "E-commerce ebade Example",
267
+ description: "A comprehensive ebade example for an e-commerce platform",
268
+ mimeType: "text/x-yaml",
269
+ },
270
+ {
271
+ uri: "ebade://examples/saas",
272
+ name: "SaaS Dashboard ebade Example",
273
+ description: "SaaS dashboard with auth, billing, user management",
274
+ mimeType: "text/x-yaml",
275
+ },
276
+ ],
277
+ };
278
+ });
279
+
280
+ // Read resources
281
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
282
+ const { uri } = request.params;
283
+
284
+ const resources: Record<string, string> = {
285
+ "ebade://syntax": `# ebade Syntax Reference\\n\\n@page, @ebade, @requires, @outcomes, @data, @validate, @style, @compose, @on, @expects\\n\\nCode = f(ebade)`,
286
+ "ebade://examples/ecommerce": `# E-commerce ebade\nname: my-store\ntype: e-commerce\nfeatures:\n - product-catalog\n - shopping-cart\n - checkout`,
287
+ "ebade://examples/saas": `# SaaS ebade\nname: my-saas\ntype: saas-dashboard\nfeatures:\n - user-auth\n - billing\n - analytics`,
288
+ };
289
+
290
+ const content = resources[uri];
291
+ if (!content) {
292
+ throw new Error(`Resource not found: ${uri}`);
293
+ }
294
+
295
+ return {
296
+ contents: [
297
+ {
298
+ uri,
299
+ mimeType: "text/markdown",
300
+ text: content,
301
+ },
302
+ ],
303
+ };
304
+ });
305
+
306
+ // Start the server
307
+ async function main() {
308
+ const transport = new StdioServerTransport();
309
+ await server.connect(transport);
310
+ console.error("ebade MCP Server running on stdio");
311
+ }
312
+
313
+ main().catch((error) => {
314
+ console.error("Failed to start server:", error);
315
+ process.exit(1);
316
+ });
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Compile Tool
3
+ *
4
+ * Compiles a single ebade definition into framework-specific code.
5
+ */
6
+
7
+ interface CompileArgs {
8
+ intent: {
9
+ type: "page" | "component" | "api" | "layout";
10
+ name: string;
11
+ path?: string;
12
+ components?: string[];
13
+ auth?: "none" | "required" | "optional";
14
+ data?: string[];
15
+ outcomes?: Record<string, any>;
16
+ style?: string | Record<string, any>;
17
+ };
18
+ target?: "nextjs" | "react" | "vue" | "svelte";
19
+ }
20
+
21
+ // Code generators for different targets
22
+ const generators = {
23
+ nextjs: {
24
+ page: (intent: CompileArgs["intent"]) => {
25
+ const name = intent.name
26
+ .split("-")
27
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
28
+ .join("");
29
+
30
+ const imports: string[] = [];
31
+ const bodyParts: string[] = [];
32
+
33
+ // Auth handling
34
+ if (intent.auth === "required") {
35
+ imports.push(`import { redirect } from "next/navigation";`);
36
+ imports.push(`import { auth } from "@/lib/auth";`);
37
+ bodyParts.push(` const session = await auth();
38
+ if (!session) {
39
+ redirect("/login");
40
+ }`);
41
+ }
42
+
43
+ // Data fetching
44
+ if (intent.data && intent.data.length > 0) {
45
+ imports.push(`import { db } from "@/lib/db";`);
46
+ for (const dataName of intent.data) {
47
+ const varName = dataName.toLowerCase();
48
+ bodyParts.push(
49
+ ` const ${varName} = await db.${varName}.findMany();`
50
+ );
51
+ }
52
+ }
53
+
54
+ // Component imports
55
+ if (intent.components && intent.components.length > 0) {
56
+ for (const comp of intent.components) {
57
+ const compName = comp
58
+ .split("-")
59
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
60
+ .join("");
61
+ imports.push(`import { ${compName} } from "@/components/${comp}";`);
62
+ }
63
+ }
64
+
65
+ // Component usage
66
+ const componentJSX = (intent.components || [])
67
+ .map((comp) => {
68
+ const compName = comp
69
+ .split("-")
70
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
71
+ .join("");
72
+ return ` <${compName} />`;
73
+ })
74
+ .join("\n");
75
+
76
+ return `/**
77
+ * @page('${intent.path || "/"}')
78
+ * @ebade('${intent.name}')
79
+ * Built with ebade
80
+ * ${intent.auth ? `@requires({ auth: '${intent.auth}' })` : ""}
81
+ * ${
82
+ intent.data ? `@data([${intent.data.map((d) => `'${d}'`).join(", ")}])` : ""
83
+ }
84
+ */
85
+
86
+ ${imports.join("\n")}
87
+
88
+ export default async function ${name}Page() {
89
+ ${bodyParts.join("\n\n")}
90
+
91
+ return (
92
+ <main className="page ${intent.name}">
93
+ <div className="container">
94
+ ${componentJSX || " {/* Page content */}"}
95
+ </div>
96
+ </main>
97
+ );
98
+ }
99
+
100
+ // Metadata
101
+ export const metadata = {
102
+ title: "${name}",
103
+ description: "Page for ${intent.name} (Built with ebade)",
104
+ };
105
+ `;
106
+ },
107
+
108
+ component: (intent: CompileArgs["intent"]) => {
109
+ const name = intent.name
110
+ .split("-")
111
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
112
+ .join("");
113
+
114
+ const style =
115
+ typeof intent.style === "string"
116
+ ? intent.style
117
+ : JSON.stringify(intent.style);
118
+
119
+ return `/* ebade Generated Component */
120
+ /**
121
+ * @ebade('${name}')
122
+ * ${intent.style ? `@style(${style})` : ""}
123
+ */
124
+
125
+ import { useState } from "react";
126
+
127
+ interface ${name}Props {
128
+ className?: string;
129
+ }
130
+
131
+ export function ${name}({ className }: ${name}Props) {
132
+ return (
133
+ <section className={\`${intent.name} \${className || ""}\`}>
134
+ <div className="container">
135
+ <h2>${name}</h2>
136
+ {/* Component content */}
137
+ </div>
138
+ </section>
139
+ );
140
+ }
141
+ `;
142
+ },
143
+
144
+ api: (intent: CompileArgs["intent"]) => {
145
+ const authCheck =
146
+ intent.auth === "required"
147
+ ? `
148
+ // Auth check
149
+ const session = await auth();
150
+ if (!session) {
151
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
152
+ }
153
+ `
154
+ : "";
155
+
156
+ return `/**
157
+ * @api('${intent.path || "/api/" + intent.name}')
158
+ * @ebade('${intent.name}')
159
+ * Built with ebade
160
+ * ${intent.auth ? `@requires({ auth: '${intent.auth}' })` : ""}
161
+ */
162
+
163
+ import { NextRequest, NextResponse } from "next/server";
164
+ ${intent.auth === "required" ? `import { auth } from "@/lib/auth";` : ""}
165
+
166
+ export async function GET(request: NextRequest) {
167
+ ${authCheck}
168
+ try {
169
+ // TODO: Implement GET handler
170
+ return NextResponse.json({ message: "GET ${intent.name}" });
171
+ } catch (error) {
172
+ return NextResponse.json(
173
+ { error: "Internal server error" },
174
+ { status: 500 }
175
+ );
176
+ }
177
+ }
178
+
179
+ export async function POST(request: NextRequest) {
180
+ ${authCheck}
181
+ try {
182
+ const body = await request.json();
183
+ // TODO: Implement POST handler
184
+ return NextResponse.json({ message: "POST ${intent.name}", data: body });
185
+ } catch (error) {
186
+ return NextResponse.json(
187
+ { error: "Internal server error" },
188
+ { status: 500 }
189
+ );
190
+ }
191
+ }
192
+ `;
193
+ },
194
+
195
+ layout: (intent: CompileArgs["intent"]) => {
196
+ const name = intent.name
197
+ .split("-")
198
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
199
+ .join("");
200
+
201
+ return `/**
202
+ * ${name} Layout
203
+ *
204
+ * @layout('${intent.name}')
205
+ * ${
206
+ intent.components
207
+ ? `@compose([${intent.components.map((c) => `'${c}'`).join(", ")}])`
208
+ : ""
209
+ }
210
+ */
211
+
212
+ ${(intent.components || [])
213
+ .map((comp) => {
214
+ const compName = comp
215
+ .split("-")
216
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
217
+ .join("");
218
+ return `import { ${compName} } from "@/components/${comp}";`;
219
+ })
220
+ .join("\n")}
221
+
222
+ export default function ${name}Layout({
223
+ children,
224
+ }: {
225
+ children: React.ReactNode;
226
+ }) {
227
+ return (
228
+ <div className="layout ${intent.name}">
229
+ ${intent.components?.includes("header") ? "<Header />" : ""}
230
+ <main>{children}</main>
231
+ ${intent.components?.includes("footer") ? "<Footer />" : ""}
232
+ </div>
233
+ );
234
+ }
235
+ `;
236
+ },
237
+ },
238
+ };
239
+
240
+ export async function compileIntent(args: CompileArgs) {
241
+ const { intent, target = "nextjs" } = args;
242
+
243
+ if (target !== "nextjs") {
244
+ return `⚠️ Target "${target}" is not yet supported. Currently only "nextjs" is available.
245
+
246
+ Coming soon: vue, svelte, react (standalone)`;
247
+ }
248
+
249
+ const generator = generators[target];
250
+ const typeGenerator = (generator as any)[intent.type];
251
+
252
+ if (!typeGenerator) {
253
+ return `❌ Unknown ebade type: ${intent.type}. Available: page, component, api, layout`;
254
+ }
255
+
256
+ const code = typeGenerator(intent);
257
+
258
+ return `✅ Compiled ${intent.type}: ${intent.name}
259
+
260
+ \`\`\`typescript
261
+ ${code}
262
+ \`\`\`
263
+
264
+ 📋 ebade definition used:
265
+ \`\`\`json
266
+ ${JSON.stringify(intent, null, 2)}
267
+ \`\`\`
268
+ `;
269
+ }