@leanmcp/cli 0.2.9 → 0.2.11

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 (2) hide show
  1. package/dist/index.js +280 -137
  2. package/package.json +4 -1
package/dist/index.js CHANGED
@@ -59,14 +59,33 @@ function parseUIAppDecorators(content, filePath) {
59
59
  while ((match = uiAppRegex.exec(content)) !== null) {
60
60
  const decoratorBody = match[1];
61
61
  const methodName = match[2];
62
- const componentMatch = decoratorBody.match(/component\s*:\s*(\w+)/);
63
- if (!componentMatch) continue;
64
- const componentName = componentMatch[1];
65
- const componentPath = importMap[componentName];
66
- if (!componentPath) {
67
- console.warn(`[scanUIApp] Could not resolve import for component: ${componentName}`);
68
- continue;
62
+ let componentPath;
63
+ let componentName;
64
+ const stringMatch = decoratorBody.match(/component\s*:\s*['"]([^'"]+)['"]/);
65
+ if (stringMatch) {
66
+ const relativePath = stringMatch[1];
67
+ const dir = path.dirname(filePath);
68
+ let resolvedPath = path.resolve(dir, relativePath);
69
+ if (!resolvedPath.endsWith(".tsx") && !resolvedPath.endsWith(".ts")) {
70
+ if (fs.existsSync(resolvedPath + ".tsx")) {
71
+ resolvedPath += ".tsx";
72
+ } else if (fs.existsSync(resolvedPath + ".ts")) {
73
+ resolvedPath += ".ts";
74
+ }
75
+ }
76
+ componentPath = resolvedPath;
77
+ componentName = path.basename(relativePath).replace(/\.(tsx?|jsx?)$/, "");
78
+ } else {
79
+ const identifierMatch = decoratorBody.match(/component\s*:\s*(\w+)/);
80
+ if (!identifierMatch) continue;
81
+ componentName = identifierMatch[1];
82
+ componentPath = importMap[componentName];
83
+ if (!componentPath) {
84
+ console.warn(`[scanUIApp] Could not resolve import for component: ${componentName}`);
85
+ continue;
86
+ }
69
87
  }
88
+ if (!componentPath) continue;
70
89
  const servicePrefix = serviceName.replace(/Service$/i, "").toLowerCase();
71
90
  const resourceUri = `ui://${servicePrefix}/${methodName}`;
72
91
  results.push({
@@ -134,11 +153,137 @@ async function buildUIComponent(uiApp, projectDir, isDev = false) {
134
153
  <script type="module" src="./entry.tsx"></script>
135
154
  </body>
136
155
  </html>`);
156
+ const tailwindConfig = path2.join(tempDir, "tailwind.config.js");
157
+ await fs2.writeFile(tailwindConfig, `
158
+ /** @type {import('tailwindcss').Config} */
159
+ module.exports = {
160
+ content: [
161
+ '${path2.join(projectDir, "**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/")}',
162
+ '${path2.join(projectDir, "mcp/**/*.{ts,tsx,js,jsx}").replace(/\\/g, "/")}',
163
+ '${path2.join(projectDir, "node_modules/@leanmcp/ui/**/*.{js,mjs}").replace(/\\/g, "/")}',
164
+ '${path2.join(projectDir, "../../packages/ui/src/**/*.{ts,tsx}").replace(/\\/g, "/")}',
165
+ ],
166
+ darkMode: ['class'],
167
+ theme: {
168
+ container: {
169
+ center: true,
170
+ padding: '2rem',
171
+ screens: {
172
+ '2xl': '1400px',
173
+ },
174
+ },
175
+ extend: {
176
+ colors: {
177
+ border: 'hsl(var(--border))',
178
+ input: 'hsl(var(--input))',
179
+ ring: 'hsl(var(--ring))',
180
+ background: 'hsl(var(--background))',
181
+ foreground: 'hsl(var(--foreground))',
182
+ primary: {
183
+ DEFAULT: 'hsl(var(--primary))',
184
+ foreground: 'hsl(var(--primary-foreground))',
185
+ },
186
+ secondary: {
187
+ DEFAULT: 'hsl(var(--secondary))',
188
+ foreground: 'hsl(var(--secondary-foreground))',
189
+ },
190
+ destructive: {
191
+ DEFAULT: 'hsl(var(--destructive))',
192
+ foreground: 'hsl(var(--destructive-foreground))',
193
+ },
194
+ muted: {
195
+ DEFAULT: 'hsl(var(--muted))',
196
+ foreground: 'hsl(var(--muted-foreground))',
197
+ },
198
+ accent: {
199
+ DEFAULT: 'hsl(var(--accent))',
200
+ foreground: 'hsl(var(--accent-foreground))',
201
+ },
202
+ popover: {
203
+ DEFAULT: 'hsl(var(--popover))',
204
+ foreground: 'hsl(var(--popover-foreground))',
205
+ },
206
+ card: {
207
+ DEFAULT: 'hsl(var(--card))',
208
+ foreground: 'hsl(var(--card-foreground))',
209
+ },
210
+ },
211
+ borderRadius: {
212
+ lg: 'var(--radius)',
213
+ md: 'calc(var(--radius) - 2px)',
214
+ sm: 'calc(var(--radius) - 4px)',
215
+ },
216
+ },
217
+ },
218
+ plugins: [],
219
+ }
220
+ `);
221
+ const stylesCss = path2.join(tempDir, "styles.css");
222
+ await fs2.writeFile(stylesCss, `
223
+ @tailwind base;
224
+ @tailwind components;
225
+ @tailwind utilities;
226
+
227
+ @layer base {
228
+ :root {
229
+ --background: 0 0% 100%;
230
+ --foreground: 222.2 84% 4.9%;
231
+ --card: 0 0% 100%;
232
+ --card-foreground: 222.2 84% 4.9%;
233
+ --popover: 0 0% 100%;
234
+ --popover-foreground: 222.2 84% 4.9%;
235
+ --primary: 222.2 47.4% 11.2%;
236
+ --primary-foreground: 210 40% 98%;
237
+ --secondary: 210 40% 96.1%;
238
+ --secondary-foreground: 222.2 47.4% 11.2%;
239
+ --muted: 210 40% 96.1%;
240
+ --muted-foreground: 215.4 16.3% 46.9%;
241
+ --accent: 210 40% 96.1%;
242
+ --accent-foreground: 222.2 47.4% 11.2%;
243
+ --destructive: 0 84.2% 60.2%;
244
+ --destructive-foreground: 210 40% 98%;
245
+ --border: 214.3 31.8% 91.4%;
246
+ --input: 214.3 31.8% 91.4%;
247
+ --ring: 222.2 84% 4.9%;
248
+ --radius: 0.5rem;
249
+ }
250
+
251
+ .dark {
252
+ --background: 222.2 84% 4.9%;
253
+ --foreground: 210 40% 98%;
254
+ --card: 222.2 84% 4.9%;
255
+ --card-foreground: 210 40% 98%;
256
+ --popover: 222.2 84% 4.9%;
257
+ --popover-foreground: 210 40% 98%;
258
+ --primary: 210 40% 98%;
259
+ --primary-foreground: 222.2 47.4% 11.2%;
260
+ --secondary: 217.2 32.6% 17.5%;
261
+ --secondary-foreground: 210 40% 98%;
262
+ --muted: 217.2 32.6% 17.5%;
263
+ --muted-foreground: 215 20.2% 65.1%;
264
+ --accent: 217.2 32.6% 17.5%;
265
+ --accent-foreground: 210 40% 98%;
266
+ --destructive: 0 62.8% 30.6%;
267
+ --destructive-foreground: 210 40% 98%;
268
+ --border: 217.2 32.6% 17.5%;
269
+ --input: 217.2 32.6% 17.5%;
270
+ --ring: 212.7 26.8% 83.9%;
271
+ }
272
+
273
+ body {
274
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
275
+ background-color: hsl(var(--background));
276
+ color: hsl(var(--foreground));
277
+ }
278
+ }
279
+ `);
137
280
  const relativeComponentPath = path2.relative(tempDir, componentPath).replace(/\\/g, "/");
138
281
  await fs2.writeFile(entryJs, `
139
282
  import React, { StrictMode } from 'react';
140
283
  import { createRoot } from 'react-dom/client';
141
- import { AppProvider } from '@leanmcp/ui';
284
+ import { AppProvider, Toaster } from '@leanmcp/ui';
285
+ import '@leanmcp/ui/styles.css';
286
+ import './styles.css';
142
287
  import { ${componentName} } from '${relativeComponentPath.replace(/\.tsx?$/, "")}';
143
288
 
144
289
  const APP_INFO = {
@@ -150,6 +295,7 @@ function App() {
150
295
  return (
151
296
  <AppProvider appInfo={APP_INFO}>
152
297
  <${componentName} />
298
+ <Toaster />
153
299
  </AppProvider>
154
300
  );
155
301
  }
@@ -167,6 +313,16 @@ createRoot(document.getElementById('root')!).render(
167
313
  react(),
168
314
  viteSingleFile()
169
315
  ],
316
+ css: {
317
+ postcss: {
318
+ plugins: [
319
+ (await import("tailwindcss")).default({
320
+ config: tailwindConfig
321
+ }),
322
+ (await import("autoprefixer")).default
323
+ ]
324
+ }
325
+ },
170
326
  build: {
171
327
  outDir,
172
328
  emptyOutDir: false,
@@ -400,7 +556,7 @@ program.name("leanmcp").description("LeanMCP CLI \u2014 create production-ready
400
556
  Examples:
401
557
  $ leanmcp create my-app --allow-all # Scaffold without interactive prompts
402
558
  `);
403
- program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").action(async (projectName, options) => {
559
+ program.command("create <projectName>").description("Create a new LeanMCP project with Streamable HTTP transport").option("--allow-all", "Skip interactive confirmations and assume Yes").option("--no-dashboard", "Disable dashboard UI at / and /mcp GET endpoints").action(async (projectName, options) => {
404
560
  const spinner = ora3(`Creating project ${projectName}...`).start();
405
561
  const targetDir = path5.join(process.cwd(), projectName);
406
562
  if (fs5.existsSync(targetDir)) {
@@ -465,152 +621,139 @@ program.command("create <projectName>").description("Create a new LeanMCP projec
465
621
  await fs5.writeJSON(path5.join(targetDir, "tsconfig.json"), tsconfig, {
466
622
  spaces: 2
467
623
  });
624
+ const dashboardLine = options.dashboard === false ? `
625
+ dashboard: false, // Dashboard disabled via --no-dashboard` : "";
468
626
  const mainTs = `import dotenv from "dotenv";
469
- import { createHTTPServer, MCPServer } from "@leanmcp/core";
627
+ import { createHTTPServer } from "@leanmcp/core";
470
628
 
471
629
  // Load environment variables
472
630
  dotenv.config();
473
631
 
474
- const PORT = Number(process.env.PORT) || 3001;
475
-
476
- /**
477
- * Create and configure the MCP server
478
- * Services are automatically discovered from ./mcp directory
479
- */
480
- const serverFactory = async () => {
481
- const server = new MCPServer({
482
- name: "${projectName}",
483
- version: "1.0.0",
484
- logging: true
485
- });
486
-
487
- // Services are automatically discovered and registered from ./mcp
488
- return server.getServer();
489
- };
490
-
491
- // Start the HTTP server
492
- await createHTTPServer(serverFactory, {
493
- port: PORT,
632
+ // Services are automatically discovered from ./mcp directory
633
+ await createHTTPServer({
634
+ name: "${projectName}",
635
+ version: "1.0.0",
636
+ port: 3001,
494
637
  cors: true,
495
- logging: true // Log HTTP requests
638
+ logging: true${dashboardLine}
496
639
  });
497
640
 
498
- console.log(\`\\n${projectName} MCP Server\`);
641
+ console.log("\\n${projectName} MCP Server");
499
642
  `;
500
643
  await fs5.writeFile(path5.join(targetDir, "main.ts"), mainTs);
501
644
  const exampleServiceTs = `import { Tool, Resource, Prompt, SchemaConstraint, Optional } from "@leanmcp/core";
502
645
 
503
- /**
504
- * Example service demonstrating LeanMCP SDK decorators
505
- *
506
- * This is a simple example to get you started. Add your own tools, resources, and prompts here!
507
- */
508
-
509
- // Input schema with validation decorators
510
- class CalculateInput {
511
- @SchemaConstraint({ description: "First number" })
512
- a!: number;
513
-
514
- @SchemaConstraint({ description: "Second number" })
515
- b!: number;
516
-
517
- @Optional()
518
- @SchemaConstraint({
519
- description: "Operation to perform",
520
- enum: ["add", "subtract", "multiply", "divide"],
521
- default: "add"
522
- })
523
- operation?: string;
524
- }
525
-
526
- class EchoInput {
527
- @SchemaConstraint({
528
- description: "Message to echo back",
529
- minLength: 1
530
- })
531
- message!: string;
532
- }
646
+ /**
647
+ * Example service demonstrating LeanMCP SDK decorators
648
+ *
649
+ * This is a simple example to get you started. Add your own tools, resources, and prompts here!
650
+ */
651
+
652
+ // Input schema with validation decorators
653
+ class CalculateInput {
654
+ @SchemaConstraint({ description: "First number" })
655
+ a!: number;
656
+
657
+ @SchemaConstraint({ description: "Second number" })
658
+ b!: number;
659
+
660
+ @Optional()
661
+ @SchemaConstraint({
662
+ description: "Operation to perform",
663
+ enum: ["add", "subtract", "multiply", "divide"],
664
+ default: "add"
665
+ })
666
+ operation?: string;
667
+ }
533
668
 
534
- export class ExampleService {
535
- @Tool({
536
- description: "Perform arithmetic operations with automatic schema validation",
537
- inputClass: CalculateInput
538
- })
539
- async calculate(input: CalculateInput) {
540
- // Ensure numerical operations by explicitly converting to numbers
541
- const a = Number(input.a);
542
- const b = Number(input.b);
543
- let result: number;
544
-
545
- switch (input.operation || "add") {
546
- case "add":
547
- result = a + b;
548
- break;
549
- case "subtract":
550
- result = a - b;
551
- break;
552
- case "multiply":
553
- result = a * b;
554
- break;
555
- case "divide":
556
- if (b === 0) throw new Error("Cannot divide by zero");
557
- result = a / b;
558
- break;
559
- default:
560
- throw new Error("Invalid operation");
669
+ class EchoInput {
670
+ @SchemaConstraint({
671
+ description: "Message to echo back",
672
+ minLength: 1
673
+ })
674
+ message!: string;
561
675
  }
562
676
 
563
- return {
564
- content: [{
565
- type: "text" as const,
566
- text: JSON.stringify({
567
- operation: input.operation || "add",
568
- operands: { a: input.a, b: input.b },
569
- result
570
- }, null, 2)
571
- }]
572
- };
573
- }
677
+ export class ExampleService {
678
+ @Tool({
679
+ description: "Perform arithmetic operations with automatic schema validation",
680
+ inputClass: CalculateInput
681
+ })
682
+ async calculate(input: CalculateInput) {
683
+ // Ensure numerical operations by explicitly converting to numbers
684
+ const a = Number(input.a);
685
+ const b = Number(input.b);
686
+ let result: number;
687
+
688
+ switch (input.operation || "add") {
689
+ case "add":
690
+ result = a + b;
691
+ break;
692
+ case "subtract":
693
+ result = a - b;
694
+ break;
695
+ case "multiply":
696
+ result = a * b;
697
+ break;
698
+ case "divide":
699
+ if (b === 0) throw new Error("Cannot divide by zero");
700
+ result = a / b;
701
+ break;
702
+ default:
703
+ throw new Error("Invalid operation");
704
+ }
574
705
 
575
- @Tool({
576
- description: "Echo a message back",
577
- inputClass: EchoInput
578
- })
579
- async echo(input: EchoInput) {
580
- return {
581
- content: [{
582
- type: "text" as const,
583
- text: JSON.stringify({
584
- echoed: input.message,
585
- timestamp: new Date().toISOString()
586
- }, null, 2)
587
- }]
588
- };
589
- }
706
+ return {
707
+ content: [{
708
+ type: "text" as const,
709
+ text: JSON.stringify({
710
+ operation: input.operation || "add",
711
+ operands: { a: input.a, b: input.b },
712
+ result
713
+ }, null, 2)
714
+ }]
715
+ };
716
+ }
590
717
 
591
- @Resource({ description: "Get server information" })
592
- async serverInfo() {
593
- return {
594
- contents: [{
595
- uri: "server://info",
596
- mimeType: "application/json",
597
- text: JSON.stringify({
598
- name: "${projectName}",
599
- version: "1.0.0",
600
- uptime: process.uptime()
601
- }, null, 2)
602
- }]
603
- };
604
- }
718
+ @Tool({
719
+ description: "Echo a message back",
720
+ inputClass: EchoInput
721
+ })
722
+ async echo(input: EchoInput) {
723
+ return {
724
+ content: [{
725
+ type: "text" as const,
726
+ text: JSON.stringify({
727
+ echoed: input.message,
728
+ timestamp: new Date().toISOString()
729
+ }, null, 2)
730
+ }]
731
+ };
732
+ }
605
733
 
606
- @Prompt({ description: "Generate a greeting prompt" })
607
- async greeting(args: { name?: string }) {
608
- return {
609
- messages: [{
610
- role: "user" as const,
611
- content: {
612
- type: "text" as const,
613
- text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
734
+ @Resource({ description: "Get server information" })
735
+ async serverInfo() {
736
+ return {
737
+ contents: [{
738
+ uri: "server://info",
739
+ mimeType: "application/json",
740
+ text: JSON.stringify({
741
+ name: "${projectName}",
742
+ version: "1.0.0",
743
+ uptime: process.uptime()
744
+ }, null, 2)
745
+ }]
746
+ };
747
+ }
748
+
749
+ @Prompt({ description: "Generate a greeting prompt" })
750
+ async greeting(args: { name?: string }) {
751
+ return {
752
+ messages: [{
753
+ role: "user" as const,
754
+ content: {
755
+ type: "text" as const,
756
+ text: \`Hello \${args.name || 'there'}! Welcome to ${projectName}.\`
614
757
  }
615
758
  }]
616
759
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Command-line interface for scaffolding LeanMCP projects",
5
5
  "bin": {
6
6
  "leanmcp": "bin/leanmcp.js"
@@ -29,12 +29,15 @@
29
29
  "dependencies": {
30
30
  "@inquirer/prompts": "^7.0.1",
31
31
  "@vitejs/plugin-react": "^4.3.0",
32
+ "autoprefixer": "^10.4.16",
32
33
  "chalk": "^5.3.0",
33
34
  "chokidar": "^4.0.0",
34
35
  "commander": "^12.0.0",
35
36
  "fs-extra": "^11.2.0",
36
37
  "glob": "^11.0.0",
37
38
  "ora": "^8.1.0",
39
+ "postcss": "^8.4.32",
40
+ "tailwindcss": "^3.4.0",
38
41
  "vite": "^5.4.0",
39
42
  "vite-plugin-singlefile": "^2.3.0"
40
43
  },