composter-cli 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -4,44 +4,564 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/composter-cli.svg)](https://www.npmjs.com/package/composter-cli)
6
6
  [![license](https://img.shields.io/npm/l/composter-cli.svg)](https://github.com/binit2-1/Composter/blob/main/LICENSE)
7
+ [![node](https://img.shields.io/node/v/composter-cli.svg)](https://nodejs.org)
7
8
 
8
9
  **Composter** is like your personal shadcn/ui — but for YOUR components. Push components from any project, pull them into new ones, and let AI assistants access your vault via MCP.
9
10
 
11
+ ---
12
+
13
+ ## 🎯 Why Composter?
14
+
15
+ Ever built an amazing component and wanted to reuse it in another project? Tired of copy-pasting files and forgetting dependencies? Composter solves this:
16
+
17
+ - **No more copy-paste chaos** — Push once, pull anywhere
18
+ - **Dependencies tracked automatically** — Never forget to install packages again
19
+ - **Smart bundling** — Relative imports are bundled, npm packages are tracked
20
+ - **AI-ready** — Let Claude, Cursor, or Copilot access your vault via MCP
21
+ - **Private by default** — Your components, your vault, your control
22
+
23
+ ---
24
+
10
25
  ## ✨ Features
11
26
 
12
- - **📤 Push** Upload components with all their local dependencies auto-bundled
13
- - **📥 Pull** — Download components into any project with dependency detection
14
- - **📁 Categories** Organize components into logical groups
15
- - **🔗 Smart Bundling** Automatically crawls and bundles relative imports
16
- - **📦 Dependency Tracking** Detects npm packages and alerts you to missing ones
17
- - **🤖 MCP Ready** Works with Claude, Cursor, and other AI assistants
27
+ | Feature | Description |
28
+ |---------|-------------|
29
+ | 📤 **Push** | Upload components with all local dependencies auto-bundled |
30
+ | 📥 **Pull** | Download components with original folder structure preserved |
31
+ | 📁 **Categories** | Organize components into logical groups (UI, Hooks, Utils) |
32
+ | 🔗 **Smart Bundling** | Automatically crawls `./` and `@/` imports |
33
+ | 📦 **Dependency Detection** | Reads your `package.json` and tracks npm packages |
34
+ | ⚠️ **Missing Dep Alerts** | Tells you exactly what to `npm install` after pulling |
35
+ | 🤖 **MCP Integration** | Works with AI assistants via Model Context Protocol |
36
+
37
+ ---
18
38
 
19
39
  ## 📦 Installation
20
40
 
21
- npm install -g composter-cli## 🚀 Quick Start
41
+ ### Global Install (Recommended)
42
+
43
+ ```bash
44
+ npm install -g composter-cli
45
+ ```
46
+
47
+ ### Using npx
48
+
49
+ ```bash
50
+ npx composter-cli login
51
+ ```
52
+
53
+ ### Verify Installation
54
+
55
+ ```bash
56
+ composter --version
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 🚀 Quick Start
62
+
63
+ ### 1. Create an Account
64
+
65
+ Visit **[composter.vercel.app](https://composter.vercel.app)** and sign up.
66
+
67
+ ### 2. Login via CLI
68
+
69
+ ```bash
70
+ composter login
71
+ ```
72
+
73
+ ```
74
+ === Composter Login ===
75
+ ? Email: you@example.com
76
+ ? Password: ********
22
77
 
23
- # 1. Create an account at https://composter.vercel.app
78
+ Logged in successfully!
79
+ Session expires: 2025-01-09T12:00:00.000Z
80
+ ```
24
81
 
25
- # 2. Login to your vault
82
+ ### 3. Create a Category
83
+
84
+ ```bash
85
+ composter mkcat ui
86
+ ```
87
+
88
+ ### 4. Push Your First Component
89
+
90
+ ```bash
91
+ composter push ui "Fancy Button" ./src/components/FancyButton.tsx
92
+ ```
93
+
94
+ ```
95
+ Scanning FancyButton.tsx and its dependencies...
96
+ 📦 Bundled 3 file(s) and detected 2 external package(s).
97
+ ✅ Success! Component 'Fancy Button' pushed to 'ui'.
98
+ ```
99
+
100
+ ### 5. Pull It In Another Project
101
+
102
+ ```bash
103
+ composter pull ui "Fancy Button" ./src/components/
104
+ ```
105
+
106
+ ```
107
+ ⏳ Fetching 'Fancy Button' from 'ui'...
108
+ 📦 Unpacking 3 file(s) into: /Users/you/new-project/src/components
109
+ + FancyButton.tsx
110
+ + FancyButton.module.css
111
+ + useButtonAnimation.ts
112
+
113
+ ⚠️ Missing Dependencies (Run this to fix):
114
+ npm install framer-motion clsx
115
+
116
+ ✅ Component 'Fancy Button' pulled successfully!
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 📖 Commands Reference
122
+
123
+ ### `composter login`
124
+
125
+ Authenticate with your Composter account.
126
+
127
+ ```bash
26
128
  composter login
129
+ ```
130
+
131
+ **What happens:**
132
+ - Prompts for email and password
133
+ - Creates a JWT session token
134
+ - Stores session at `~/.config/composter/session.json`
135
+ - Session lasts 30 days
136
+
137
+ ---
138
+
139
+ ### `composter mkcat <category-name>`
140
+
141
+ Create a new category to organize your components.
142
+
143
+ ```bash
144
+ composter mkcat hooks
145
+ ```
146
+
147
+ **Output:**
148
+ ```
149
+ Category 'hooks' created successfully!
150
+ ID: clx1234567890
151
+ ```
152
+
153
+ **Rules:**
154
+ | Rule | Example |
155
+ |------|---------|
156
+ | No spaces | ❌ `my buttons` → ✅ `buttons` |
157
+ | Max 10 characters | ❌ `superlongname` → ✅ `utils` |
158
+ | Unique per user | Each category name must be unique |
159
+
160
+ ---
161
+
162
+ ### `composter ls`
163
+
164
+ List all your categories.
165
+
166
+ ```bash
167
+ composter ls
168
+ ```
169
+
170
+ **Output:**
171
+ ```
172
+ ui hooks utils layouts forms
173
+ ```
174
+
175
+ ---
176
+
177
+ ### `composter push <category> <title> <file-path>`
178
+
179
+ Push a component to your vault.
180
+
181
+ ```bash
182
+ composter push ui "Data Table" ./src/components/DataTable/index.tsx
183
+ ```
184
+
185
+ **Arguments:**
186
+
187
+ | Argument | Description | Example |
188
+ |----------|-------------|---------|
189
+ | `category` | Target category name | `ui` |
190
+ | `title` | Human-readable component name | `"Data Table"` |
191
+ | `file-path` | Entry file path | `./src/components/DataTable.tsx` |
192
+
193
+ **What gets bundled:**
194
+ ```
195
+ Your Entry File
196
+
197
+ ├── ./relative/imports → ✅ Bundled into package
198
+ ├── @/alias/imports → ✅ Bundled (assumes @/ = src/)
199
+ ├── ./styles.css → ✅ Bundled
200
+
201
+ └── External packages → 📦 Tracked as dependencies
202
+ ├── react
203
+ ├── framer-motion
204
+ └── @tanstack/react-table
205
+ ```
206
+
207
+ **Example Output:**
208
+ ```
209
+ Scanning DataTable.tsx and its dependencies...
210
+ 📦 Bundled 5 file(s) and detected 4 external package(s).
211
+ ✅ Success! Component 'Data Table' pushed to 'ui'.
212
+ ```
213
+
214
+ ---
215
+
216
+ ### `composter pull <category> <title> <target-directory>`
217
+
218
+ Pull a component from your vault.
219
+
220
+ ```bash
221
+ composter pull ui "Data Table" ./src/components/
222
+ ```
223
+
224
+ **Arguments:**
225
+
226
+ | Argument | Description | Example |
227
+ |----------|-------------|---------|
228
+ | `category` | Source category name | `ui` |
229
+ | `title` | Component name to pull | `"Data Table"` |
230
+ | `target-directory` | Where to save files | `./src/components/` |
231
+
232
+ **What happens:**
233
+ 1. Fetches component bundle from your vault
234
+ 2. Recreates original folder structure
235
+ 3. Writes all files to target directory
236
+ 4. Checks your `package.json` for missing dependencies
237
+ 5. Suggests `npm install` command if needed
238
+
239
+ **Example Output:**
240
+ ```
241
+ ⏳ Fetching 'Data Table' from 'ui'...
242
+ 📦 Unpacking 5 file(s) into: /Users/you/project/src/components
243
+ + DataTable/index.tsx
244
+ + DataTable/DataTableHeader.tsx
245
+ + DataTable/DataTableRow.tsx
246
+ + DataTable/DataTablePagination.tsx
247
+ + DataTable/data-table.css
248
+
249
+ ⚠️ Missing Dependencies (Run this to fix):
250
+ npm install @tanstack/react-table lucide-react
251
+
252
+ ✅ Component 'Data Table' pulled successfully!
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 🔧 How Smart Bundling Works
258
+
259
+ When you push a component, the CLI performs intelligent dependency crawling:
260
+
261
+ ### Import Resolution
262
+
263
+ ```tsx
264
+ // Entry: src/components/Button.tsx
265
+
266
+ import { cn } from "@/lib/utils" // ✅ Bundled (alias import)
267
+ import { useToggle } from "./hooks/toggle" // ✅ Bundled (relative import)
268
+ import "./button.css" // ✅ Bundled (CSS import)
269
+ import { motion } from "framer-motion" // 📦 Tracked as npm dependency
270
+ import React from "react" // 📦 Tracked as npm dependency
271
+ ```
272
+
273
+ ### Alias Support
274
+
275
+ The `@/` alias is automatically resolved to your `src/` directory:
276
+
277
+ ```
278
+ @/components/Button → src/components/Button
279
+ @/lib/utils → src/lib/utils
280
+ @/hooks/useAuth → src/hooks/useAuth
281
+ ```
282
+
283
+ ### File Extensions
284
+
285
+ The crawler automatically resolves these extensions:
286
+
287
+ ```
288
+ import { Button } from "./Button"
289
+
290
+ Tries: Button.tsx → Button.ts → Button.jsx → Button.js → Button/index.tsx
291
+ ```
292
+
293
+ ### Package Version Detection
294
+
295
+ npm dependencies are tracked with versions from your `package.json`:
296
+
297
+ ```json
298
+ // Your package.json
299
+ {
300
+ "dependencies": {
301
+ "framer-motion": "^10.16.0",
302
+ "clsx": "^2.0.0"
303
+ }
304
+ }
27
305
 
28
- # 3. Create a category
29
- composter mkcat buttons
306
+ // Stored with component
307
+ {
308
+ "framer-motion": "^10.16.0",
309
+ "clsx": "^2.0.0"
310
+ }
311
+ ```
30
312
 
31
- # 4. Push a component
32
- composter push buttons "Animated Button" ./src/components/Button.tsx
313
+ ---
33
314
 
34
- # 5. Pull it in another project
35
- composter pull buttons "Animated Button" ./src/components/## 📖 Commands
315
+ ## 🤖 AI Integration (MCP)
316
+
317
+ Composter includes a built-in Model Context Protocol (MCP) server that lets AI assistants interact with your vault directly.
318
+
319
+ ### Quick Setup
320
+
321
+ ```bash
322
+ # One command to configure your AI assistant
323
+ npx composter-cli@latest mcp init --client claude
324
+ ```
325
+
326
+ **Supported clients:**
327
+ - `claude` — Claude Desktop
328
+ - `cursor` — Cursor IDE
329
+ - `vscode` — VS Code with Copilot
330
+ - `windsurf` — Windsurf IDE
331
+
332
+ ### MCP Commands
36
333
 
37
334
  | Command | Description |
38
335
  |---------|-------------|
39
- | `composter login` | Authenticate with your account |
40
- | `composter mkcat <name>` | Create a new category |
41
- | `composter ls` | List all categories |
42
- | `composter push <category> <title> <file>` | Push a component |
43
- | `composter pull <category> <title> <dir>` | Pull a component |
336
+ | `composter mcp init --client <name>` | Configure MCP for an AI assistant |
337
+ | `composter mcp serve` | Start MCP server directly (testing) |
338
+ | `composter mcp info` | Show MCP configuration info |
339
+
340
+ ### Options
341
+
342
+ ```bash
343
+ # Configure for Claude Desktop
344
+ composter mcp init --client claude
345
+
346
+ # Configure for Cursor
347
+ composter mcp init --client cursor
348
+
349
+ # Use localhost backend (development)
350
+ composter mcp init --client claude --dev
351
+
352
+ # Show manual config instructions
353
+ composter mcp info
354
+ ```
355
+
356
+ ### Available AI Tools
357
+
358
+ Once configured, your AI assistant can use these tools:
359
+
360
+ | Tool | Description |
361
+ |------|-------------|
362
+ | `search_components` | Search your vault by name or category |
363
+ | `list_categories` | List all your categories |
364
+ | `list_components` | List components in a category |
365
+ | `read_component` | Read full source code of a component |
366
+
367
+ ### Example AI Prompts
368
+
369
+ After setup, you can ask your AI:
370
+ - *"Search my Composter vault for button components"*
371
+ - *"What categories do I have in Composter?"*
372
+ - *"Read the DataTable component from my ui category"*
373
+ - *"List all components in my hooks category"*
374
+
375
+ ### Development Mode
376
+
377
+ For local development with `localhost:3000` backend:
378
+
379
+ ```bash
380
+ # Initialize with dev flag
381
+ composter mcp init --client claude --dev
382
+
383
+ # Or set environment variable
384
+ COMPOSTER_DEV=true composter mcp serve
385
+ ```
386
+
387
+ ---
388
+
389
+ ## 📂 File Locations
390
+
391
+ | File | Location | Purpose |
392
+ |------|----------|---------|
393
+ | Session | `~/.config/composter/session.json` | JWT auth token |
394
+ | Config Dir | `~/.config/composter/` | All CLI data |
395
+
396
+ ### Session File Structure
397
+
398
+ ```json
399
+ {
400
+ "jwt": "eyJhbGciOiJSUzI1NiIs...",
401
+ "cookies": "session_token=...",
402
+ "createdAt": "2024-12-10T10:00:00.000Z",
403
+ "expiresAt": "2025-01-09T10:00:00.000Z"
404
+ }
405
+ ```
406
+
407
+ ---
408
+
409
+ ## 🐛 Troubleshooting
410
+
411
+ ### "You must be logged in"
412
+
413
+ Your session doesn't exist or has expired.
414
+
415
+ ```bash
416
+ composter login
417
+ ```
418
+
419
+ ### "Session expired"
420
+
421
+ Sessions last 30 days. Re-authenticate:
422
+
423
+ ```bash
424
+ composter login
425
+ ```
426
+
427
+ ### "Invalid category name"
428
+
429
+ Category names have strict rules:
430
+
431
+ ```bash
432
+ # ❌ These will fail
433
+ composter mkcat "My Components" # No spaces
434
+ composter mkcat verylongcatname # Max 10 chars
435
+
436
+ # ✅ These work
437
+ composter mkcat ui
438
+ composter mkcat hooks
439
+ composter mkcat utils
440
+ ```
441
+
442
+ ### "File not found" when pushing
443
+
444
+ Check your file path:
445
+
446
+ ```bash
447
+ # Make sure you're in the right directory
448
+ pwd
449
+
450
+ # Use relative path from current directory
451
+ composter push ui "Button" ./src/components/Button.tsx
452
+
453
+ # Or use absolute path
454
+ composter push ui "Button" /Users/me/project/src/components/Button.tsx
455
+ ```
456
+
457
+ ### "Component not found" when pulling
458
+
459
+ - Check the exact component title (case-sensitive)
460
+ - Verify the category name
461
+ - List your components on the web dashboard
462
+
463
+ ### Missing dependencies after pull
464
+
465
+ The CLI tells you what's missing. Just run:
466
+
467
+ ```bash
468
+ npm install package1 package2 package3
469
+ ```
470
+
471
+ ### Network errors
472
+
473
+ - Check your internet connection
474
+ - Verify the backend is accessible: `https://composter.onrender.com/api/health`
475
+ - Try logging in again
476
+
477
+ ---
478
+
479
+ ## 🌐 Web Dashboard
480
+
481
+ Manage your components visually at **[composter.vercel.app](https://composter.vercel.app)**
482
+
483
+ - 📋 Browse all components
484
+ - 👁️ Live code preview with Sandpack
485
+ - ✏️ Edit component metadata
486
+ - 📊 View dependency graphs
487
+ - 📋 Copy code snippets
488
+ - 🗑️ Delete components
489
+
490
+ ---
491
+
492
+ ## 🔒 Security
493
+
494
+ | Feature | Description |
495
+ |---------|-------------|
496
+ | 🔐 JWT Auth | Secure token-based authentication |
497
+ | 🏠 Local Storage | Tokens stored locally, never shared |
498
+ | 🔒 User Isolation | Each vault is completely private |
499
+ | 🌐 HTTPS | All API traffic encrypted |
500
+ | ⏰ Expiring Sessions | 30-day token lifetime |
501
+
502
+ ---
503
+
504
+ ## 📋 Examples
505
+
506
+ ### Push a Shadcn-style Component
507
+
508
+ ```bash
509
+ # Push a button with all its dependencies
510
+ composter push ui "Shadcn Button" ./src/components/ui/button.tsx
511
+ ```
512
+
513
+ ### Push a Custom Hook
514
+
515
+ ```bash
516
+ composter push hooks "useLocalStorage" ./src/hooks/useLocalStorage.ts
517
+ ```
518
+
519
+ ### Push a Full Feature
520
+
521
+ ```bash
522
+ # Push a data table that imports multiple files
523
+ composter push features "User Table" ./src/features/users/UserTable.tsx
524
+ ```
525
+
526
+ ### Pull Into a New Project
527
+
528
+ ```bash
529
+ # Create components folder if needed
530
+ mkdir -p src/components
531
+
532
+ # Pull your button
533
+ composter pull ui "Shadcn Button" ./src/components/ui/
534
+
535
+ # Install any missing deps
536
+ npm install
537
+ ```
538
+
539
+ ---
540
+
541
+ ## 🔗 Links
542
+
543
+ | Resource | URL |
544
+ |----------|-----|
545
+ | 🌐 Web App | [composter.vercel.app](https://composter.vercel.app) |
546
+ | 📦 npm | [npmjs.com/package/composter-cli](https://www.npmjs.com/package/composter-cli) |
547
+ | 💻 GitHub | [github.com/binit2-1/Composter](https://github.com/binit2-1/Composter) |
548
+ | 🐛 Issues | [Report a bug](https://github.com/binit2-1/Composter/issues) |
549
+ | 🤖 MCP Docs | [MCP Setup Guide](https://github.com/binit2-1/Composter/tree/main/mcp) |
550
+
551
+ ---
552
+
553
+ ## 🤝 Contributing
554
+
555
+ Contributions welcome! See [CONTRIBUTING.md](https://github.com/binit2-1/Composter/blob/main/CONTRIBUTING.md)
556
+
557
+ ---
558
+
559
+ ## 📄 License
560
+
561
+ MIT © [binit2-1](https://github.com/binit2-1)
44
562
 
45
- ## 🔧 Smart Bundling
563
+ ---
46
564
 
47
- When you push, the CLI automatically bundles all local imports:
565
+ <p align="center">
566
+ <b>Built with ❤️ for developers who hate copy-pasting components</b>
567
+ </p>
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "composter-cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
- "description": "Your personal vault for React components. Push, pull, and sync reusable components across projects — like shadcn/ui but for YOUR code.",
5
+ "description": "Your personal vault for React components. Push, pull, and sync reusable components across projects — like shadcn/ui but for YOUR code. Includes MCP server for AI assistants.",
6
6
  "main": "src/index.js",
7
7
  "bin": {
8
8
  "composter": "./bin/composter.js"
@@ -22,7 +22,11 @@
22
22
  "developer-tools",
23
23
  "productivity",
24
24
  "mcp",
25
- "ai-tools"
25
+ "ai-tools",
26
+ "claude",
27
+ "cursor",
28
+ "copilot",
29
+ "model-context-protocol"
26
30
  ],
27
31
  "author": "binit2-1",
28
32
  "license": "MIT",
@@ -38,9 +42,11 @@
38
42
  "node": ">=18.0.0"
39
43
  },
40
44
  "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.23.0",
41
46
  "commander": "^14.0.2",
42
47
  "dotenv": "^16.4.5",
43
48
  "inquirer": "^13.0.1",
44
- "node-fetch": "^3.3.2"
49
+ "node-fetch": "^3.3.2",
50
+ "zod": "^3.23.0"
45
51
  }
46
52
  }
@@ -0,0 +1,238 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import { loadSession } from "../utils/session.js";
5
+ import { fileURLToPath } from "url";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ // Get the path to the MCP server script
11
+ function getMcpServerPath() {
12
+ return path.resolve(__dirname, "../mcp/server.js");
13
+ }
14
+
15
+ // Client configuration templates
16
+ const CLIENT_CONFIGS = {
17
+ claude: {
18
+ name: "Claude Desktop",
19
+ configPath: () => {
20
+ if (process.platform === "darwin") {
21
+ return path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
22
+ } else if (process.platform === "win32") {
23
+ return path.join(process.env.APPDATA, "Claude", "claude_desktop_config.json");
24
+ } else {
25
+ return path.join(os.homedir(), ".config", "claude", "claude_desktop_config.json");
26
+ }
27
+ },
28
+ generateConfig: (serverPath, cwd) => ({
29
+ mcpServers: {
30
+ composter: {
31
+ command: "node",
32
+ args: [serverPath],
33
+ cwd: cwd,
34
+ },
35
+ },
36
+ }),
37
+ },
38
+ cursor: {
39
+ name: "Cursor",
40
+ configPath: () => {
41
+ return path.join(process.cwd(), ".cursor", "mcp.json");
42
+ },
43
+ generateConfig: (serverPath, cwd) => ({
44
+ composter: {
45
+ command: "node",
46
+ args: [serverPath],
47
+ cwd: cwd,
48
+ },
49
+ }),
50
+ },
51
+ vscode: {
52
+ name: "VS Code (Copilot)",
53
+ configPath: () => {
54
+ return path.join(process.cwd(), ".vscode", "mcp.json");
55
+ },
56
+ generateConfig: (serverPath, cwd) => ({
57
+ servers: {
58
+ composter: {
59
+ command: "node",
60
+ args: [serverPath],
61
+ cwd: cwd,
62
+ },
63
+ },
64
+ }),
65
+ },
66
+ windsurf: {
67
+ name: "Windsurf",
68
+ configPath: () => {
69
+ return path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
70
+ },
71
+ generateConfig: (serverPath, cwd) => ({
72
+ mcpServers: {
73
+ composter: {
74
+ command: "node",
75
+ args: [serverPath],
76
+ cwd: cwd,
77
+ },
78
+ },
79
+ }),
80
+ },
81
+ };
82
+
83
+ // Initialize MCP for a specific client
84
+ export async function mcpInit(client, options = {}) {
85
+ // Validate client
86
+ const clientConfig = CLIENT_CONFIGS[client?.toLowerCase()];
87
+ if (!clientConfig) {
88
+ console.log(`❌ Unknown client: ${client}`);
89
+ console.log(`\nSupported clients:`);
90
+ Object.entries(CLIENT_CONFIGS).forEach(([key, val]) => {
91
+ console.log(` - ${key} (${val.name})`);
92
+ });
93
+ return;
94
+ }
95
+
96
+ // Check if user is logged in
97
+ const session = loadSession();
98
+ if (!session?.jwt) {
99
+ console.log("❌ You must be logged in first. Run: composter login");
100
+ return;
101
+ }
102
+
103
+ const serverPath = getMcpServerPath();
104
+ const cwd = options.global ? path.dirname(serverPath) : process.cwd();
105
+ const configPath = clientConfig.configPath();
106
+ const configDir = path.dirname(configPath);
107
+
108
+ console.log(`\n🔧 Setting up Composter MCP for ${clientConfig.name}...\n`);
109
+
110
+ // Generate the config snippet
111
+ const newConfig = clientConfig.generateConfig(serverPath, cwd);
112
+
113
+ // Check if config file exists
114
+ let existingConfig = {};
115
+ if (fs.existsSync(configPath)) {
116
+ try {
117
+ existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
118
+ console.log(`📄 Found existing config at: ${configPath}`);
119
+ } catch {
120
+ console.log(`⚠️ Existing config is invalid, will create new one.`);
121
+ }
122
+ }
123
+
124
+ // Merge configs
125
+ let mergedConfig;
126
+ if (client === "claude" || client === "windsurf") {
127
+ mergedConfig = {
128
+ ...existingConfig,
129
+ mcpServers: {
130
+ ...existingConfig.mcpServers,
131
+ ...newConfig.mcpServers,
132
+ },
133
+ };
134
+ } else if (client === "vscode") {
135
+ mergedConfig = {
136
+ ...existingConfig,
137
+ servers: {
138
+ ...existingConfig.servers,
139
+ ...newConfig.servers,
140
+ },
141
+ };
142
+ } else {
143
+ // Cursor - direct merge
144
+ mergedConfig = {
145
+ ...existingConfig,
146
+ ...newConfig,
147
+ };
148
+ }
149
+
150
+ // Create directory if needed
151
+ if (!fs.existsSync(configDir)) {
152
+ fs.mkdirSync(configDir, { recursive: true });
153
+ console.log(`📁 Created directory: ${configDir}`);
154
+ }
155
+
156
+ // Write config
157
+ fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), "utf-8");
158
+ console.log(`✅ Config written to: ${configPath}`);
159
+
160
+ // Print success message
161
+ console.log(`
162
+ ╔════════════════════════════════════════════════════════════════╗
163
+ ║ 🎉 MCP Setup Complete! ║
164
+ ╠════════════════════════════════════════════════════════════════╣
165
+ ║ ║
166
+ ║ Composter MCP server has been configured for ${clientConfig.name.padEnd(14)} ║
167
+ ║ ║
168
+ ║ Next steps: ║
169
+ ║ 1. Restart ${clientConfig.name.padEnd(50)} ║
170
+ ║ 2. Look for "Composter" in your MCP tools ║
171
+ ║ ║
172
+ ║ Available tools: ║
173
+ ║ • search_components - Search your component vault ║
174
+ ║ • list_categories - List all your categories ║
175
+ ║ • list_components - List components in a category ║
176
+ ║ • read_component - Read full source code ║
177
+ ║ ║
178
+ ╚════════════════════════════════════════════════════════════════╝
179
+ `);
180
+
181
+ // Dev mode hint
182
+ if (options.dev) {
183
+ console.log(`\n💡 Dev mode: Set COMPOSTER_DEV=true to use localhost:3000`);
184
+ }
185
+ }
186
+
187
+ // Serve MCP server directly (for testing)
188
+ export async function mcpServe(options = {}) {
189
+ const session = loadSession();
190
+ if (!session?.jwt) {
191
+ console.log("❌ You must be logged in first. Run: composter login");
192
+ return;
193
+ }
194
+
195
+ console.log("🚀 Starting Composter MCP server...");
196
+ console.log(" Press Ctrl+C to stop\n");
197
+
198
+ // Set dev mode if requested
199
+ if (options.dev) {
200
+ process.env.COMPOSTER_DEV = "true";
201
+ console.log("📡 Dev mode: Using localhost:3000");
202
+ }
203
+
204
+ // Dynamic import and run the server
205
+ const serverPath = getMcpServerPath();
206
+ await import(serverPath);
207
+ }
208
+
209
+ // Show MCP configuration info
210
+ export function mcpInfo() {
211
+ const serverPath = getMcpServerPath();
212
+ const session = loadSession();
213
+
214
+ console.log(`
215
+ ╔════════════════════════════════════════════════════════════════╗
216
+ ║ Composter MCP Information ║
217
+ ╠════════════════════════════════════════════════════════════════╣
218
+
219
+ Server path: ${serverPath}
220
+ Logged in: ${session?.jwt ? "✅ Yes" : "❌ No (run 'composter login')"}
221
+
222
+ Manual configuration:
223
+
224
+ {
225
+ "composter": {
226
+ "command": "node",
227
+ "args": ["${serverPath}"]
228
+ }
229
+ }
230
+
231
+ Environment variables:
232
+ • COMPOSTER_DEV=true - Use localhost:3000 backend
233
+ • COMPOSTER_API_URL=... - Custom API URL
234
+
235
+ ╚════════════════════════════════════════════════════════════════╝
236
+ `);
237
+ }
238
+
package/src/index.js CHANGED
@@ -6,6 +6,7 @@ import { mkcat } from "./commands/mkcat.js";
6
6
  import { listCategories } from "./commands/listCat.js";
7
7
  import { pushComponent } from "./commands/push.js";
8
8
  import { pullComponent } from "./commands/pull.js";
9
+ import { mcpInit, mcpServe, mcpInfo } from "./commands/mcp.js";
9
10
  import { createRequire } from "module";
10
11
 
11
12
  const require = createRequire(import.meta.url);
@@ -49,4 +50,34 @@ program
49
50
  pullComponent(category, title, filepath);
50
51
  });
51
52
 
53
+ // MCP Commands
54
+ const mcp = program
55
+ .command("mcp")
56
+ .description("Manage MCP (Model Context Protocol) server for AI assistants");
57
+
58
+ mcp
59
+ .command("init")
60
+ .description("Initialize MCP server for an AI assistant")
61
+ .option("-c, --client <client>", "AI client to configure (claude, cursor, vscode, windsurf)", "claude")
62
+ .option("-g, --global", "Configure globally instead of project-specific")
63
+ .option("-d, --dev", "Use localhost:3000 backend (development mode)")
64
+ .action((options) => {
65
+ mcpInit(options.client, options);
66
+ });
67
+
68
+ mcp
69
+ .command("serve")
70
+ .description("Start the MCP server directly (for testing)")
71
+ .option("-d, --dev", "Use localhost:3000 backend (development mode)")
72
+ .action((options) => {
73
+ mcpServe(options);
74
+ });
75
+
76
+ mcp
77
+ .command("info")
78
+ .description("Show MCP configuration information")
79
+ .action(() => {
80
+ mcpInfo();
81
+ });
82
+
52
83
  program.parse(process.argv);
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { z } from "zod";
5
+ import { loadSession } from "../utils/session.js";
6
+ import fetch from "node-fetch";
7
+
8
+ // Redirect console.log to stderr (MCP uses stdout for protocol)
9
+ const originalLog = console.log;
10
+ console.log = (...args) => console.error(...args);
11
+
12
+ // Determine base URL - supports both dev and production
13
+ function getBaseUrl() {
14
+ // Check for explicit env var first
15
+ if (process.env.COMPOSTER_API_URL) {
16
+ return process.env.COMPOSTER_API_URL;
17
+ }
18
+ // Check for dev mode
19
+ if (process.env.COMPOSTER_DEV === "true" || process.env.NODE_ENV === "development") {
20
+ return "http://localhost:3000/api";
21
+ }
22
+ // Default to production
23
+ return "https://composter.onrender.com/api";
24
+ }
25
+
26
+ const BASE_URL = getBaseUrl();
27
+
28
+ // API request helper with JWT auth
29
+ async function apiRequest(path, options = {}) {
30
+ const session = loadSession();
31
+
32
+ if (!session?.jwt) {
33
+ throw new Error("Not authenticated. Run 'composter login' first.");
34
+ }
35
+
36
+ const headers = {
37
+ "Content-Type": "application/json",
38
+ "Authorization": `Bearer ${session.jwt}`,
39
+ ...options.headers,
40
+ };
41
+
42
+ const res = await fetch(`${BASE_URL}${path}`, {
43
+ ...options,
44
+ headers,
45
+ });
46
+
47
+ if (res.status === 401) {
48
+ throw new Error("Session expired. Run 'composter login' again.");
49
+ }
50
+
51
+ return res;
52
+ }
53
+
54
+ // Create the MCP server with tools
55
+ function createComposterMcpServer() {
56
+ const server = new McpServer({
57
+ name: "Composter",
58
+ version: "1.0.0",
59
+ });
60
+
61
+ // Tool: Search components
62
+ server.tool(
63
+ "search_components",
64
+ "Search for React components in your Composter vault by title or category name. Returns matching components with IDs and categories.",
65
+ {
66
+ query: z.string().describe("Search term for component title or category name"),
67
+ },
68
+ async ({ query }) => {
69
+ try {
70
+ const res = await apiRequest(`/components/search?q=${encodeURIComponent(query)}`, { method: "GET" });
71
+
72
+ if (!res.ok) {
73
+ const error = await res.json().catch(() => ({}));
74
+ return {
75
+ content: [{ type: "text", text: `Error searching: ${error.message || res.statusText}` }],
76
+ };
77
+ }
78
+
79
+ const data = await res.json();
80
+ const components = data.components || [];
81
+
82
+ if (components.length === 0) {
83
+ return {
84
+ content: [{ type: "text", text: "No components found matching that query." }],
85
+ };
86
+ }
87
+
88
+ const formatted = components.map((c) =>
89
+ `- **${c.title}** (Category: ${c.category?.name || "unknown"}) [ID: ${c.id}]`
90
+ ).join("\n");
91
+
92
+ return {
93
+ content: [{ type: "text", text: `Found ${components.length} component(s):\n\n${formatted}` }],
94
+ };
95
+ } catch (err) {
96
+ return {
97
+ content: [{ type: "text", text: `Error: ${err.message}` }],
98
+ };
99
+ }
100
+ }
101
+ );
102
+
103
+ // Tool: List categories
104
+ server.tool(
105
+ "list_categories",
106
+ "List all categories in your Composter vault.",
107
+ {},
108
+ async () => {
109
+ try {
110
+ const res = await apiRequest("/categories");
111
+
112
+ if (!res.ok) {
113
+ const error = await res.json().catch(() => ({}));
114
+ return {
115
+ content: [{ type: "text", text: `Error: ${error.message || res.statusText}` }],
116
+ };
117
+ }
118
+
119
+ const data = await res.json();
120
+ const categories = data.categories || [];
121
+
122
+ if (categories.length === 0) {
123
+ return {
124
+ content: [{ type: "text", text: "No categories found. Create one with 'composter mkcat <name>'." }],
125
+ };
126
+ }
127
+
128
+ const formatted = categories.map((c) => `- ${c.name}`).join("\n");
129
+
130
+ return {
131
+ content: [{ type: "text", text: `Your categories:\n\n${formatted}` }],
132
+ };
133
+ } catch (err) {
134
+ return {
135
+ content: [{ type: "text", text: `Error: ${err.message}` }],
136
+ };
137
+ }
138
+ }
139
+ );
140
+
141
+ // Tool: Read component
142
+ server.tool(
143
+ "read_component",
144
+ "Read the full source code of a React component from your vault. Returns the code, category, dependencies, and creation date.",
145
+ {
146
+ category: z.string().describe("The category name the component belongs to"),
147
+ title: z.string().describe("The title/name of the component to read"),
148
+ },
149
+ async ({ category, title }) => {
150
+ try {
151
+ const res = await apiRequest(
152
+ `/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`
153
+ );
154
+
155
+ if (res.status === 404) {
156
+ return {
157
+ content: [{ type: "text", text: `Component "${title}" not found in category "${category}".` }],
158
+ };
159
+ }
160
+
161
+ if (!res.ok) {
162
+ const error = await res.json().catch(() => ({}));
163
+ return {
164
+ content: [{ type: "text", text: `Error: ${error.message || res.statusText}` }],
165
+ };
166
+ }
167
+
168
+ const data = await res.json();
169
+ const component = data.component;
170
+
171
+ if (!component) {
172
+ return {
173
+ content: [{ type: "text", text: `Component "${title}" not found.` }],
174
+ };
175
+ }
176
+
177
+ // Parse code - could be JSON (multi-file) or string (single file)
178
+ let codeOutput = "";
179
+ try {
180
+ const files = JSON.parse(component.code);
181
+ codeOutput = Object.entries(files)
182
+ .map(([path, content]) => `### ${path}\n\`\`\`tsx\n${content}\n\`\`\``)
183
+ .join("\n\n");
184
+ } catch {
185
+ codeOutput = `\`\`\`tsx\n${component.code}\n\`\`\``;
186
+ }
187
+
188
+ // Format dependencies
189
+ let depsOutput = "";
190
+ if (component.dependencies && Object.keys(component.dependencies).length > 0) {
191
+ const deps = Object.entries(component.dependencies)
192
+ .map(([pkg, ver]) => `- ${pkg}: ${ver}`)
193
+ .join("\n");
194
+ depsOutput = `\n\n**Dependencies:**\n${deps}`;
195
+ }
196
+
197
+ const output = `# ${component.title}
198
+
199
+ **Category:** ${category}
200
+ **Created:** ${new Date(component.createdAt).toLocaleDateString()}
201
+ ${depsOutput}
202
+
203
+ ## Source Code
204
+
205
+ ${codeOutput}`;
206
+
207
+ return {
208
+ content: [{ type: "text", text: output }],
209
+ };
210
+ } catch (err) {
211
+ return {
212
+ content: [{ type: "text", text: `Error: ${err.message}` }],
213
+ };
214
+ }
215
+ }
216
+ );
217
+
218
+ // Tool: List components in category
219
+ server.tool(
220
+ "list_components",
221
+ "List all components in a specific category.",
222
+ {
223
+ category: z.string().describe("The category name to list components from"),
224
+ },
225
+ async ({ category }) => {
226
+ try {
227
+ const res = await apiRequest(`/components/list-by-category?category=${encodeURIComponent(category)}`, { method: "GET" });
228
+
229
+ if (!res.ok) {
230
+ const error = await res.json().catch(() => ({}));
231
+ return {
232
+ content: [{ type: "text", text: `Error: ${error.message || res.statusText}` }],
233
+ };
234
+ }
235
+
236
+ const data = await res.json();
237
+ const components = data.components || [];
238
+
239
+ if (components.length === 0) {
240
+ return {
241
+ content: [{ type: "text", text: `No components found in category "${category}".` }],
242
+ };
243
+ }
244
+
245
+ const formatted = components.map((c) =>
246
+ `- **${c.title}** (created: ${new Date(c.createdAt).toLocaleDateString()})`
247
+ ).join("\n");
248
+
249
+ return {
250
+ content: [{ type: "text", text: `Components in "${category}":\n\n${formatted}` }],
251
+ };
252
+ } catch (err) {
253
+ return {
254
+ content: [{ type: "text", text: `Error: ${err.message}` }],
255
+ };
256
+ }
257
+ }
258
+ );
259
+
260
+ return server;
261
+ }
262
+
263
+ // Main entry point
264
+ async function main() {
265
+ try {
266
+ // Verify session exists
267
+ const session = loadSession();
268
+ if (!session?.jwt) {
269
+ console.error("❌ No session found. Please run 'composter login' first.");
270
+ process.exit(1);
271
+ }
272
+
273
+ console.error(`🚀 Composter MCP Server starting...`);
274
+ console.error(`📡 API: ${BASE_URL}`);
275
+
276
+ const server = createComposterMcpServer();
277
+ const transport = new StdioServerTransport();
278
+ await server.connect(transport);
279
+
280
+ console.error("✅ Composter MCP server running on stdio");
281
+ } catch (error) {
282
+ console.error("❌ Fatal Error:", error.message);
283
+ process.exit(1);
284
+ }
285
+ }
286
+
287
+ // Run if called directly
288
+ main();
289
+