cyrus-studio 1.0.6 → 1.0.8

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 (39) hide show
  1. package/README.md +107 -65
  2. package/bin/mcp-server.js +394 -0
  3. package/dist/index.cjs +1167 -0
  4. package/dist/index.d.ts +351 -0
  5. package/dist/index.js +41 -0
  6. package/dist/index.mjs +1137 -0
  7. package/dist/protocol.d.ts +35 -0
  8. package/dist/protocol.js +31 -0
  9. package/dist/src/index.d.ts +1176 -0
  10. package/dist/src/index.js +41 -0
  11. package/package.json +39 -50
  12. package/dist/assets/AuthDialog-bjTyuBr7.js +0 -1
  13. package/dist/assets/CanvaSaveModal-BfM3xRxS.js +0 -1
  14. package/dist/assets/CodingWorkspace-BKcqDIZx.js +0 -6
  15. package/dist/assets/CreateAppDialog-D8utCQ_b.js +0 -1236
  16. package/dist/assets/CreateProjectDialog-BVn3XyiE.js +0 -1
  17. package/dist/assets/HistoryTimelineDialog-B6LM34kT.js +0 -1
  18. package/dist/assets/ImportModal-B5pjYSjk.js +0 -83
  19. package/dist/assets/KeyboardShortcutsDialog-azy2kfng.js +0 -1
  20. package/dist/assets/LiveRuntimeWorkspace-CFPr1I0-.js +0 -1
  21. package/dist/assets/ShareExportDialog-BrQois88.js +0 -1
  22. package/dist/assets/UserProfileDialog-B2TENVlq.js +0 -1
  23. package/dist/assets/WelcomeOnboardingModal-Dyig1GK1.js +0 -1
  24. package/dist/assets/index-Cc_706Lp.css +0 -2
  25. package/dist/assets/index-DM7S-Aaa.js +0 -793
  26. package/dist/assets/reactTsGenerator-CpUD2_5K.js +0 -1330
  27. package/dist/assets/rolldown-runtime-Dd_uD5pT.js +0 -1
  28. package/dist/assets/themes-Iute1uX4.js +0 -1
  29. package/dist/assets/typechecker-BbGTunMD.js +0 -2
  30. package/dist/assets/vendor-firebase-DNMzpOGz.js +0 -32
  31. package/dist/assets/vendor-icons-CT-OIlA-.js +0 -13
  32. package/dist/assets/vendor-jszip-BhaZ3FnA.js +0 -2
  33. package/dist/assets/vendor-react-DVnpuVEx.js +0 -227
  34. package/dist/assets/vendor-utils-BqqQ8OQf.js +0 -1
  35. package/dist/favicon.svg +0 -1
  36. package/dist/icons.svg +0 -24
  37. package/dist/index.html +0 -50
  38. package/dist/login-bg.png +0 -0
  39. package/dist/test-sdk-sandbox.html +0 -125
package/README.md CHANGED
@@ -1,113 +1,155 @@
1
- # Cyrus Studio — AI Visual Product Builder & Multi-Framework Code Generator
1
+ # cyrus-studio
2
2
 
3
3
  <p align="center">
4
4
  <a href="https://cyrustudio.vercel.app"><img src="https://img.shields.io/badge/Live_App-cyrustudio.vercel.app-6366f1?style=for-the-badge&logo=vercel&logoColor=white" alt="Live Demo" /></a>
5
5
  <a href="https://www.npmjs.com/package/cyrus-studio"><img src="https://img.shields.io/npm/v/cyrus-studio?style=for-the-badge&color=blue" alt="NPM Version" /></a>
6
6
  <a href="https://www.npmjs.com/package/cyrus-studio"><img src="https://img.shields.io/npm/dm/cyrus-studio?style=for-the-badge&color=green" alt="NPM Downloads" /></a>
7
- <a href="https://github.com/cyrus-studio/cyrus"><img src="https://img.shields.io/badge/License-MIT-amber?style=for-the-badge" alt="License" /></a>
7
+ <img src="https://img.shields.io/badge/License-MIT-amber?style=for-the-badge" alt="License" />
8
8
  </p>
9
9
 
10
+ > **AI Visual Canvas Studio** — Embed Cyrus in any React app, generate full UIs from prompts, and export clean code for React, Flutter, SwiftUI & HTML.
11
+
12
+ 🚀 **Try the Live App**: **[https://cyrustudio.vercel.app](https://cyrustudio.vercel.app)**
13
+
10
14
  ---
11
15
 
12
- **Cyrus Studio** is a modern visual product builder that lets designers and developers create production-ready user interfaces on a visual canvas, preview them in real time, and export clean, maintainable source code for **React / Next.js**, **Flutter**, **SwiftUI**, and **HTML / Tailwind CSS**.
16
+ ## Install
13
17
 
14
- 🚀 **Try the Live Cloud App**: **[https://cyrustudio.vercel.app](https://cyrustudio.vercel.app)**
18
+ ```bash
19
+ npm install cyrus-studio
20
+ ```
21
+
22
+ > **Peer dependencies required**: `react >= 18` and `react-dom >= 18`
15
23
 
16
24
  ---
17
25
 
18
- ## ✨ Features
26
+ ## Usage
19
27
 
20
- - 🎨 **Visual Canvas & Drag-and-Drop Editor**: Build complex UIs with intuitive positioning, flex layouts, smart alignment guides, multi-selection, and layered hierarchy.
21
- - ⚡ **Multi-Framework Code Generation**:
22
- - ⚛️ **React / Next.js** (Tailwind CSS, TypeScript)
23
- - 📱 **Flutter / Dart** (Widget tree with theme binding)
24
- - 🍎 **SwiftUI** (Native declarative iOS/macOS views)
25
- - 🌐 **Clean HTML5 / CSS** (Semantic layout with Tailwind)
26
- - 🤖 **AI Design & Code Copilot**: Integrate OpenAI, Google Gemini, Anthropic Claude, or local Ollama models to generate layouts, copy, and components via natural language.
27
- - 👥 **Real-Time Multiplayer Collaboration**: Multiplayer room with live cursor tracking, conflict resolution, and commenting.
28
- - 📐 **Responsive Viewport Controls**: Instant switching between Desktop (1440px), Tablet (768px), and Mobile (390px) viewports with auto-scaling.
29
- - 🌗 **Design Systems & Theme Engine**: Global color palettes, typography tokens, component instances, and dynamic light/dark mode preview.
30
- - 📦 **One-Click Export**: Download structured ZIP bundles, export individual page source files, or save high-resolution PNG/PDF artboards.
28
+ ### 1. Embed the Cyrus Canvas in your React App
31
29
 
32
- ---
30
+ ```tsx
31
+ import { CyrusCanvasIframe } from 'cyrus-studio';
33
32
 
34
- ## 🚀 Quick Start
33
+ export default function App() {
34
+ return (
35
+ <CyrusCanvasIframe
36
+ hostUrl="https://cyrustudio.vercel.app"
37
+ projectId="my-project"
38
+ theme="dark"
39
+ style={{ height: '800px' }}
40
+ onStateChange={(state) => console.log('Canvas state:', state)}
41
+ />
42
+ );
43
+ }
44
+ ```
35
45
 
36
- ### Option 1: Use the Live Web App (No Setup Required)
37
- Open **[https://cyrustudio.vercel.app](https://cyrustudio.vercel.app)** in your browser and start designing immediately.
46
+ ### 2. Generate an Embed URL Programmatically
38
47
 
39
- ### Option 2: Run Locally via Git / NPM
48
+ ```ts
49
+ import { createCyrusEmbedUrl } from 'cyrus-studio';
40
50
 
41
- ```bash
42
- # 1. Clone the repository
43
- git clone https://github.com/cyrus-studio/cyrus.git
44
- cd cyrus
51
+ const url = createCyrusEmbedUrl({
52
+ hostUrl: 'https://cyrustudio.vercel.app',
53
+ projectId: 'landing-v2',
54
+ theme: 'light',
55
+ readOnly: true,
56
+ });
57
+
58
+ console.log(url);
59
+ // https://cyrustudio.vercel.app/#/embed/landing-v2?theme=light&readOnly=true
60
+ ```
61
+
62
+ ### 3. Use the Cyrus IR (Intermediate Representation) Protocol
63
+
64
+ The Cyrus IR is a universal JSON schema for AI-generated UIs. Use it to pass designs between your AI pipeline and the Cyrus canvas.
65
+
66
+ ```ts
67
+ import {
68
+ validateAndRepairCyrusIR,
69
+ getCyrusLLMSystemPrompt,
70
+ type CyrusIRDocument,
71
+ } from 'cyrus-studio';
45
72
 
46
- # 2. Install dependencies
47
- npm install
73
+ // Get the system prompt to paste into your LLM
74
+ const systemPrompt = getCyrusLLMSystemPrompt();
48
75
 
49
- # 3. Start development server
50
- npm run dev
76
+ // Validate and repair raw LLM output
77
+ const rawAiOutput = `{ "version": "1.0", "projectTitle": "My App", "pages": [...] }`;
78
+ const doc: CyrusIRDocument = validateAndRepairCyrusIR(rawAiOutput);
79
+
80
+ console.log(doc.pages[0].elements);
51
81
  ```
52
82
 
53
- Once started, open **`http://localhost:5173`** in your browser.
83
+ ---
84
+
85
+ ## API Reference
86
+
87
+ ### `CyrusCanvasIframe`
54
88
 
55
- - **Vite Web App**: Port `5173`
56
- - **Backend API Gateway**: Port `8787` (`/api` proxy)
89
+ React component that embeds the Cyrus Visual Canvas as an iframe.
90
+
91
+ | Prop | Type | Default | Description |
92
+ |---|---|---|---|
93
+ | `hostUrl` | `string` | `http://localhost:5173` | URL of the Cyrus app |
94
+ | `projectId` | `string` | `"demo"` | Project ID to load in canvas |
95
+ | `theme` | `"light" \| "dark"` | `"light"` | Canvas color theme |
96
+ | `onStateChange` | `(state: any) => void` | — | Callback fired when canvas state changes |
97
+ | `style` | `CSSProperties` | — | Inline styles for the iframe |
98
+ | `className` | `string` | — | CSS class for the iframe |
99
+
100
+ All standard `<iframe>` HTML attributes are also supported.
57
101
 
58
102
  ---
59
103
 
60
- ## ⚙️ Configuration & Environment Variables
104
+ ### `createCyrusEmbedUrl(options)`
61
105
 
62
- Copy `.env.example` to `.env` to configure optional integrations:
106
+ Returns an embeddable URL string.
63
107
 
64
- ```bash
65
- cp .env.example .env
108
+ ```ts
109
+ createCyrusEmbedUrl({
110
+ projectId?: string; // Project to load
111
+ hostUrl?: string; // Cyrus host (default: localhost:5173)
112
+ theme?: 'light' | 'dark';
113
+ readOnly?: boolean;
114
+ })
66
115
  ```
67
116
 
68
- | Variable | Description |
69
- | :--- | :--- |
70
- | `VITE_STRIPE_PUBLISHABLE_KEY` | Stripe Publishable Key for billing checkout. |
71
- | `STRIPE_SECRET_KEY` | Stripe Secret Key for serverless subscription sessions. |
72
- | `GEMINI_API_KEY` | Google Gemini API key for AI generation. |
73
- | `OPENAI_API_KEY` | OpenAI API key for GPT-4o design assistant. |
74
- | `ANTHROPIC_API_KEY` | Anthropic Claude API key. |
75
- | `JWT_SECRET` | Secret string for production session token signing. |
117
+ ---
76
118
 
77
- > **Note**: Cyrus Studio runs fully locally even without external API keys. Features requiring a specific provider will prompt for the key when used.
119
+ ### `validateAndRepairCyrusIR(rawInput)`
78
120
 
79
- ---
121
+ Parses, validates, and repairs raw AI output (string or object) into a valid `CyrusIRDocument`. Safe to call with any LLM response — never throws.
80
122
 
81
- ## 🛠️ Development & Testing Scripts
123
+ ---
82
124
 
83
- ```bash
84
- # Start local development server (Client + Server concurrently)
85
- npm run dev
125
+ ### `getCyrusLLMSystemPrompt()`
86
126
 
87
- # Run TypeScript type checking
88
- npm run typecheck
127
+ Returns the recommended system prompt string to paste into Claude, GPT-4, Gemini, or any other LLM so it outputs valid Cyrus IR JSON.
89
128
 
90
- # Run test suite with Vitest (90+ unit and integration tests)
91
- npm run test
129
+ ---
92
130
 
93
- # Build production bundle
94
- npm run build
131
+ ## TypeScript Types
95
132
 
96
- # Preview production build locally
97
- npm run preview
133
+ ```ts
134
+ import type {
135
+ CyrusIRDocument,
136
+ CyrusIRPage,
137
+ CyrusIRElement,
138
+ CyrusIframeProps,
139
+ } from 'cyrus-studio';
98
140
  ```
99
141
 
100
142
  ---
101
143
 
102
- ## 📦 Tech Stack
144
+ ## CommonJS Support
103
145
 
104
- - **Frontend**: React 19, TypeScript, Tailwind CSS v4, Zustand, Lucide Icons, JSZip
105
- - **Build System**: Vite, PostCSS, Oxlint
106
- - **Backend & APIs**: Node.js, Vercel Serverless Functions, Firebase Auth / Cloud Firestore
107
- - **Testing**: Vitest, React Testing Library
146
+ ```js
147
+ // Works with require() too
148
+ const { CyrusCanvasIframe, createCyrusEmbedUrl } = require('cyrus-studio');
149
+ ```
108
150
 
109
151
  ---
110
152
 
111
- ## 📄 License
153
+ ## License
112
154
 
113
- This project is licensed under the **[MIT License](LICENSE)**.
155
+ MIT © [Cyrus Studio Team](https://cyrustudio.vercel.app)
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cyrus Studio Model Context Protocol (MCP) CLI Entrypoint v1.0.8
5
+ *
6
+ * Standalone JSON-RPC 2.0 / MCP Server for:
7
+ * - Claude Desktop
8
+ * - Cursor
9
+ * - Antigravity / Gemini CLI
10
+ * - Autonomous AI Agents
11
+ */
12
+
13
+ import readline from 'node:readline';
14
+ import { randomBytes } from 'node:crypto';
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ import os from 'node:os';
18
+
19
+ function generateId(prefix = 'el') {
20
+ return `${prefix}_${randomBytes(5).toString('hex')}`;
21
+ }
22
+
23
+ // ─── LOCAL ATOMIC PERSISTENCE ───────────────────────────────────────
24
+ const DB_DIR = path.join(os.homedir(), '.cyrus-studio');
25
+ const DB_FILE = path.join(DB_DIR, 'db.json');
26
+
27
+ function loadMemoryDb() {
28
+ try {
29
+ if (!fs.existsSync(DB_DIR)) fs.mkdirSync(DB_DIR, { recursive: true });
30
+ if (fs.existsSync(DB_FILE)) {
31
+ return JSON.parse(fs.readFileSync(DB_FILE, 'utf8'));
32
+ }
33
+ } catch {}
34
+ return { version: 1, projects: [] };
35
+ }
36
+
37
+ function saveMemoryDb(db) {
38
+ try {
39
+ if (!fs.existsSync(DB_DIR)) fs.mkdirSync(DB_DIR, { recursive: true });
40
+ fs.writeFileSync(DB_FILE, JSON.stringify(db, null, 2), 'utf8');
41
+ } catch {}
42
+ }
43
+
44
+ // ─── MCP TOOL SCHEMAS ───────────────────────────────────────────────
45
+ export const MCP_TOOLS = [
46
+ {
47
+ name: 'cyrus_generate_ui',
48
+ description: 'Generates a complete, structured visual UI page layout on the Cyrus Canvas from a text prompt. Creates the project, saves it, and returns the interactive canvas URLs and clean React code.',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: {
52
+ prompt: { type: 'string', description: 'Description of the UI to generate' },
53
+ type: { type: 'string', enum: ['webapp', 'website', 'dashboard', 'landing', 'mobile'], default: 'webapp' },
54
+ theme: { type: 'string', enum: ['light', 'dark'], default: 'dark' },
55
+ preset: { type: 'string', enum: ['apple-hig', 'shadcn-zinc', 'shadcn-slate', 'shadcn-violet', 'shadcn-rose', 'tailwind-cyberpunk'], default: 'shadcn-zinc' }
56
+ },
57
+ required: ['prompt']
58
+ }
59
+ },
60
+ {
61
+ name: 'cyrus_create_element',
62
+ description: 'Creates and places a new high-fidelity UI widget/component onto the Cyrus Canvas (navbar, hero, button, pricingCard, metricCard, lineChart, searchBar, container, footer).',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ projectId: { type: 'string', description: 'Target project ID' },
67
+ type: { type: 'string', description: 'Component type' },
68
+ name: { type: 'string', description: 'Human-readable element label' },
69
+ x: { type: 'number' },
70
+ y: { type: 'number' },
71
+ width: { type: 'number' },
72
+ height: { type: 'number' },
73
+ styles: { type: 'object' },
74
+ content: { type: 'object' }
75
+ },
76
+ required: ['type']
77
+ }
78
+ },
79
+ {
80
+ name: 'cyrus_modify_element',
81
+ description: 'Updates specific visual styles, text content, or dimensions of an existing canvas element by ID.',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ projectId: { type: 'string' },
86
+ elementId: { type: 'string' },
87
+ updates: { type: 'object' }
88
+ },
89
+ required: ['projectId', 'elementId', 'updates']
90
+ }
91
+ },
92
+ {
93
+ name: 'cyrus_delete_element',
94
+ description: 'Deletes an element from the Cyrus canvas by its element ID.',
95
+ inputSchema: {
96
+ type: 'object',
97
+ properties: {
98
+ projectId: { type: 'string' },
99
+ elementId: { type: 'string' }
100
+ },
101
+ required: ['projectId', 'elementId']
102
+ }
103
+ },
104
+ {
105
+ name: 'cyrus_apply_design_system',
106
+ description: 'Applies global design system tokens (colors, typography, radii, theme presets) across the canvas.',
107
+ inputSchema: {
108
+ type: 'object',
109
+ properties: {
110
+ projectId: { type: 'string' },
111
+ preset: { type: 'string', enum: ['apple-hig', 'shadcn-zinc', 'shadcn-slate', 'shadcn-violet', 'shadcn-rose', 'tailwind-cyberpunk'] },
112
+ colors: { type: 'object' },
113
+ radii: { type: 'object' }
114
+ },
115
+ required: ['projectId']
116
+ }
117
+ },
118
+ {
119
+ name: 'cyrus_get_canvas_document',
120
+ description: 'Retrieves the complete structured Cyrus IR JSON document of a canvas project.',
121
+ inputSchema: {
122
+ type: 'object',
123
+ properties: {
124
+ projectId: { type: 'string' }
125
+ }
126
+ }
127
+ },
128
+ {
129
+ name: 'cyrus_list_projects',
130
+ description: 'Lists all Cyrus Studio projects available with IDs, names, and preview URLs.',
131
+ inputSchema: {
132
+ type: 'object',
133
+ properties: {}
134
+ }
135
+ },
136
+ {
137
+ name: 'cyrus_export_code',
138
+ description: 'Exports production-ready code (React + Tailwind CSS, Flutter, SwiftUI, or HTML + CSS) for the current visual canvas.',
139
+ inputSchema: {
140
+ type: 'object',
141
+ properties: {
142
+ projectId: { type: 'string' },
143
+ framework: { type: 'string', enum: ['react-tailwind', 'flutter', 'swiftui', 'html-css'], default: 'react-tailwind' }
144
+ },
145
+ required: ['projectId']
146
+ }
147
+ }
148
+ ];
149
+
150
+ // ─── PURE ESM CODE GENERATOR ─────────────────────────────────────────
151
+ function generateReactTailwindCode(page) {
152
+ const elements = page?.elements || [];
153
+ const componentName = (page?.name || 'Page').replace(/[^a-zA-Z0-9]/g, '') || 'CyrusApp';
154
+
155
+ const rendered = elements.map(el => {
156
+ const c = el.content || {};
157
+ return ` {/* ${el.name || el.type} */}
158
+ <div className="p-6 rounded-2xl border border-white/10 bg-neutral-900/60 shadow-xl">
159
+ <h3 className="text-xl font-bold text-white">${c.title || c.headline || c.text || el.name || 'Component'}</h3>
160
+ </div>`;
161
+ });
162
+
163
+ return `import React from 'react';
164
+
165
+ export default function ${componentName}() {
166
+ return (
167
+ <div className="min-h-screen bg-[#09090b] text-neutral-100 p-8 space-y-6">
168
+ ${rendered.join('\n\n')}
169
+ </div>
170
+ );
171
+ }`;
172
+ }
173
+
174
+ export async function handleToolCall(name, args = {}) {
175
+ const db = loadMemoryDb();
176
+
177
+ switch (name) {
178
+ case 'cyrus_generate_ui': {
179
+ const prompt = String(args.prompt || 'Modern Web Application').trim();
180
+ const theme = args.theme || 'dark';
181
+ const type = args.type || 'webapp';
182
+ const preset = args.preset || 'shadcn-zinc';
183
+ const projectId = `proj_${generateId()}`;
184
+
185
+ const elements = [
186
+ {
187
+ id: `el_${generateId()}`,
188
+ type: 'navbar',
189
+ name: 'Main Navigation Bar',
190
+ x: 40,
191
+ y: 30,
192
+ width: 1200,
193
+ height: 64,
194
+ styles: { backgroundColor: '#18181b', borderRadius: 12 },
195
+ content: { brandName: 'Cyrus Studio AI', buttonText: 'Launch App' }
196
+ },
197
+ {
198
+ id: `el_${generateId()}`,
199
+ type: 'hero',
200
+ name: 'Hero Section',
201
+ x: 40,
202
+ y: 110,
203
+ width: 1200,
204
+ height: 320,
205
+ styles: { backgroundColor: '#09090b', borderRadius: 16 },
206
+ content: {
207
+ badge: 'AI Generated Layout',
208
+ headline: prompt.slice(0, 60),
209
+ subtitle: `Generated for ${type.toUpperCase()} layout using ${preset} design tokens.`,
210
+ buttonText: 'Get Started'
211
+ }
212
+ },
213
+ {
214
+ id: `el_${generateId()}`,
215
+ type: 'pricingCard',
216
+ name: 'Subscription Plan',
217
+ x: 40,
218
+ y: 450,
219
+ width: 380,
220
+ height: 360,
221
+ styles: { backgroundColor: '#18181b', borderRadius: 16 },
222
+ content: {
223
+ badge: 'PRO TIER',
224
+ planName: 'Enterprise Engine',
225
+ price: '$49/mo',
226
+ features: ['Unlimited Canvas Projects', 'Real-time MCP Sync', 'React & Flutter Export']
227
+ }
228
+ }
229
+ ];
230
+
231
+ const page = { id: `page_${generateId()}`, name: 'Home', elements };
232
+ const projectDoc = {
233
+ id: projectId,
234
+ name: prompt.slice(0, 40),
235
+ version: '1.0.8',
236
+ theme,
237
+ type,
238
+ preset,
239
+ pages: [page],
240
+ createdAt: new Date().toISOString(),
241
+ updatedAt: new Date().toISOString()
242
+ };
243
+
244
+ db.projects.push({ document: projectDoc });
245
+ saveMemoryDb(db);
246
+
247
+ return {
248
+ success: true,
249
+ projectId,
250
+ title: projectDoc.name,
251
+ elementCount: elements.length,
252
+ editorUrl: `https://cyrustudio.vercel.app/#/editor/${projectId}`,
253
+ previewUrl: `https://cyrustudio.vercel.app/#/embed/${projectId}?theme=${theme}`,
254
+ document: projectDoc,
255
+ code: generateReactTailwindCode(page)
256
+ };
257
+ }
258
+
259
+ case 'cyrus_create_element': {
260
+ let targetProj = db.projects[db.projects.length - 1]?.document;
261
+ if (args.projectId) {
262
+ const found = db.projects.find(p => p.document?.id === args.projectId);
263
+ if (found) targetProj = found.document;
264
+ }
265
+ if (!targetProj) {
266
+ targetProj = {
267
+ id: `proj_${generateId()}`,
268
+ name: 'Cyrus Project',
269
+ pages: [{ id: `page_${generateId()}`, name: 'Home', elements: [] }]
270
+ };
271
+ db.projects.push({ document: targetProj });
272
+ }
273
+
274
+ const activePage = targetProj.pages[0];
275
+ const newEl = {
276
+ id: `el_${generateId()}`,
277
+ type: args.type || 'button',
278
+ name: args.name || args.type || 'New Element',
279
+ x: args.x || 100,
280
+ y: args.y || 120,
281
+ width: args.width || 320,
282
+ height: args.height || 120,
283
+ styles: args.styles || { backgroundColor: '#18181b', borderRadius: 12 },
284
+ content: args.content || { title: args.name || 'New Widget' }
285
+ };
286
+
287
+ activePage.elements.push(newEl);
288
+ saveMemoryDb(db);
289
+
290
+ return { success: true, projectId: targetProj.id, createdElement: newEl, totalElements: activePage.elements.length };
291
+ }
292
+
293
+ case 'cyrus_modify_element': {
294
+ const { projectId, elementId, updates = {} } = args;
295
+ const proj = db.projects.find(p => p.document?.id === projectId);
296
+ if (!proj) throw new Error(`Project "${projectId}" not found.`);
297
+
298
+ let targetEl = null;
299
+ for (const page of (proj.document.pages || [])) {
300
+ targetEl = (page.elements || []).find(e => e.id === elementId);
301
+ if (targetEl) break;
302
+ }
303
+ if (!targetEl) throw new Error(`Element "${elementId}" not found.`);
304
+
305
+ if (updates.styles) targetEl.styles = { ...targetEl.styles, ...updates.styles };
306
+ if (updates.content) targetEl.content = { ...targetEl.content, ...updates.content };
307
+ saveMemoryDb(db);
308
+
309
+ return { success: true, projectId, elementId, updatedElement: targetEl };
310
+ }
311
+
312
+ case 'cyrus_list_projects': {
313
+ const list = (db.projects || []).map(p => ({
314
+ id: p.document?.id,
315
+ name: p.document?.name || 'Untitled',
316
+ previewUrl: `https://cyrustudio.vercel.app/#/embed/${p.document?.id}`
317
+ }));
318
+ return { success: true, count: list.length, projects: list };
319
+ }
320
+
321
+ case 'cyrus_export_code': {
322
+ const { projectId, framework = 'react-tailwind' } = args;
323
+ const proj = db.projects.find(p => p.document?.id === projectId);
324
+ if (!proj) throw new Error(`Project "${projectId}" not found.`);
325
+ const page = proj.document.pages?.[0] || { name: 'Home', elements: [] };
326
+ return { success: true, projectId, framework, code: generateReactTailwindCode(page) };
327
+ }
328
+
329
+ default:
330
+ throw new Error(`Unknown Cyrus MCP tool: "${name}"`);
331
+ }
332
+ }
333
+
334
+ // ─── JSON-RPC 2.0 PROTOCOL HANDLER ───────────────────────────────────
335
+ export async function handleMcpJsonRpc(request) {
336
+ if (!request || typeof request !== 'object') {
337
+ return { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } };
338
+ }
339
+
340
+ const { id, method, params } = request;
341
+
342
+ if (method === 'initialize') {
343
+ return {
344
+ jsonrpc: '2.0',
345
+ id,
346
+ result: {
347
+ protocolVersion: '2024-11-05',
348
+ capabilities: { tools: { listChanged: false } },
349
+ serverInfo: { name: 'cyrus-studio', version: '1.0.8' }
350
+ }
351
+ };
352
+ }
353
+
354
+ if (method === 'notifications/initialized' || method === 'initialized') return null;
355
+ if (method === 'ping') return { jsonrpc: '2.0', id, result: {} };
356
+ if (method === 'tools/list') return { jsonrpc: '2.0', id, result: { tools: MCP_TOOLS } };
357
+
358
+ if (method === 'tools/call') {
359
+ try {
360
+ const data = await handleToolCall(params?.name, params?.arguments);
361
+ return {
362
+ jsonrpc: '2.0',
363
+ id,
364
+ result: { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], isError: false }
365
+ };
366
+ } catch (err) {
367
+ return {
368
+ jsonrpc: '2.0',
369
+ id,
370
+ result: { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true }
371
+ };
372
+ }
373
+ }
374
+
375
+ return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method "${method}" not found.` } };
376
+ }
377
+
378
+ // ─── STDIO STREAM ───────────────────────────────────────────────────
379
+ if (process.argv.includes('--stdio') || !process.stdin.isTTY || process.argv.includes('mcp-server')) {
380
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
381
+ rl.on('line', async (line) => {
382
+ const trimmed = line.trim();
383
+ if (!trimmed) return;
384
+ try {
385
+ const res = await handleMcpJsonRpc(JSON.parse(trimmed));
386
+ if (res) process.stdout.write(JSON.stringify(res) + '\n');
387
+ } catch (err) {
388
+ process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: null, error: { message: err.message } }) + '\n');
389
+ }
390
+ });
391
+ } else {
392
+ console.log('🤖 Cyrus Studio MCP Server v1.0.8');
393
+ console.log('Use with Claude Desktop or Cursor: npx cyrus-studio mcp-server');
394
+ }