mcp-app-studio 0.5.0 → 0.5.1

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
@@ -20,7 +20,7 @@ npm install
20
20
  npm run dev
21
21
  ```
22
22
 
23
- Open http://localhost:3000 — you're in the workbench.
23
+ Open http://localhost:3002 — you're in the workbench.
24
24
 
25
25
  ## Universal SDK
26
26
 
package/dist/cli/index.js CHANGED
@@ -3,22 +3,48 @@ import fs2 from "fs";
3
3
  import { createWriteStream } from "fs";
4
4
  import { Readable } from "stream";
5
5
  import { pipeline } from "stream/promises";
6
- import path2 from "path";
6
+ import path3 from "path";
7
7
  import os from "os";
8
8
  import { fileURLToPath } from "url";
9
9
  import * as p from "@clack/prompts";
10
10
  import pc from "picocolors";
11
11
  import { extract } from "tar";
12
12
 
13
+ // src/cli/template-utils.ts
14
+ import path from "path";
15
+ function getGithubArchiveTarballUrl(repo, ref) {
16
+ return `https://github.com/${repo}/archive/${ref}.tar.gz`;
17
+ }
18
+ function isTarLinkEntry(entry) {
19
+ if (!entry || typeof entry !== "object") return false;
20
+ if (!("type" in entry)) return false;
21
+ const t = entry.type;
22
+ return t === "SymbolicLink" || t === "Link";
23
+ }
24
+ function assertSafeTarEntryPath(rootDir, entryPath) {
25
+ if (path.isAbsolute(entryPath)) {
26
+ throw new Error(`Template tarball contains an absolute path: ${entryPath}`);
27
+ }
28
+ const resolved = path.resolve(rootDir, entryPath);
29
+ if (resolved !== rootDir && !resolved.startsWith(`${rootDir}${path.sep}`)) {
30
+ throw new Error(`Template tarball contains an unsafe path: ${entryPath}`);
31
+ }
32
+ }
33
+ function filterTemplateTarEntry(rootDir, entryPath, entry) {
34
+ if (isTarLinkEntry(entry)) return false;
35
+ assertSafeTarEntryPath(rootDir, entryPath);
36
+ return true;
37
+ }
38
+
13
39
  // src/cli/utils.ts
14
40
  import fs from "fs";
15
- import path from "path";
41
+ import path2 from "path";
16
42
  function isValidProjectPath(name) {
17
43
  if (!name || name.trim() === "") {
18
44
  return { valid: false, error: "Project name is required" };
19
45
  }
20
46
  const trimmed = name.trim();
21
- if (path.isAbsolute(trimmed)) {
47
+ if (path2.isAbsolute(trimmed)) {
22
48
  return {
23
49
  valid: false,
24
50
  error: "Absolute paths are not allowed. Use a relative path or project name."
@@ -31,9 +57,9 @@ function isValidProjectPath(name) {
31
57
  };
32
58
  }
33
59
  const cwd = process.cwd();
34
- const resolved = path.resolve(cwd, trimmed);
35
- const relative = path.relative(cwd, resolved);
36
- if (relative.startsWith("..") || path.isAbsolute(relative)) {
60
+ const resolved = path2.resolve(cwd, trimmed);
61
+ const relative = path2.relative(cwd, resolved);
62
+ if (relative.startsWith("..") || path2.isAbsolute(relative)) {
37
63
  return {
38
64
  valid: false,
39
65
  error: "Project must be created within current directory."
@@ -58,7 +84,7 @@ function emptyDir(dir) {
58
84
  if (!fs.existsSync(dir)) return;
59
85
  for (const file of fs.readdirSync(dir)) {
60
86
  if (file === ".git") continue;
61
- fs.rmSync(path.join(dir, file), { recursive: true, force: true });
87
+ fs.rmSync(path2.join(dir, file), { recursive: true, force: true });
62
88
  }
63
89
  }
64
90
  var DEPENDENCY_OVERRIDES = {
@@ -66,8 +92,8 @@ var DEPENDENCY_OVERRIDES = {
66
92
  "@assistant-ui/react-ai-sdk": "^1.3.3",
67
93
  "@assistant-ui/react-markdown": "^0.12.1"
68
94
  };
69
- function updatePackageJson(dir, name, description, includeServer) {
70
- const pkgPath = path.join(dir, "package.json");
95
+ function updatePackageJson(dir, name, description, opts) {
96
+ const pkgPath = path2.join(dir, "package.json");
71
97
  if (!fs.existsSync(pkgPath)) return;
72
98
  try {
73
99
  const content = fs.readFileSync(pkgPath, "utf-8");
@@ -78,12 +104,10 @@ function updatePackageJson(dir, name, description, includeServer) {
78
104
  if (description) {
79
105
  pkg["description"] = description;
80
106
  }
81
- if (includeServer) {
82
- const scripts = pkg["scripts"] ?? {};
83
- scripts["postinstall"] = "cd server && npm install";
84
- pkg["scripts"] = scripts;
85
- }
86
107
  const deps = pkg["dependencies"] ?? {};
108
+ if (opts?.mcpAppStudioVersion && opts.mcpAppStudioVersion !== "0.0.0") {
109
+ deps["mcp-app-studio"] = `^${opts.mcpAppStudioVersion}`;
110
+ }
87
111
  for (const [dep, version] of Object.entries(DEPENDENCY_OVERRIDES)) {
88
112
  if (deps[dep]) {
89
113
  deps[dep] = version;
@@ -107,9 +131,11 @@ function detectPackageManager() {
107
131
  }
108
132
 
109
133
  // src/cli/index.ts
110
- var __dirname = path2.dirname(fileURLToPath(import.meta.url));
111
- var TEMPLATE_REPO = "assistant-ui/mcp-app-studio-starter";
112
- var TEMPLATE_BRANCH = "main";
134
+ var __dirname = path3.dirname(fileURLToPath(import.meta.url));
135
+ var TEMPLATE_REPO = process.env["MCP_APP_STUDIO_TEMPLATE_REPO"] ?? "assistant-ui/mcp-app-studio-starter";
136
+ var TEMPLATE_REF = process.env["MCP_APP_STUDIO_TEMPLATE_REF"] ?? process.env["MCP_APP_STUDIO_TEMPLATE_BRANCH"] ?? "main";
137
+ var DOCS_URL = "https://github.com/assistant-ui/assistant-ui/tree/main/packages/mcp-app-studio";
138
+ var EXAMPLES_URL = "https://github.com/assistant-ui/mcp-app-studio-starter";
113
139
  var REQUIRED_NODE_VERSION = { major: 20, minor: 9, patch: 0 };
114
140
  function parseNodeVersion(version) {
115
141
  const [majorRaw, minorRaw, patchRaw] = version.split(".");
@@ -139,7 +165,7 @@ function ensureSupportedNodeVersion() {
139
165
  }
140
166
  function getVersion() {
141
167
  try {
142
- const pkgPath = path2.resolve(__dirname, "../package.json");
168
+ const pkgPath = path3.resolve(__dirname, "../package.json");
143
169
  const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
144
170
  return pkg.version || "0.0.0";
145
171
  } catch {
@@ -163,7 +189,7 @@ ${pc.bold("Options:")}
163
189
  --version, -v Show version number
164
190
  -y, --yes Non-interactive mode (use defaults or flag values)
165
191
  --template <name> Template to use: minimal, poi-map (default: minimal)
166
- --include-server Include MCP server (default when not using -y)
192
+ --include-server Include MCP server (default: true)
167
193
  --no-server Do not include MCP server
168
194
  --description <text> App description
169
195
 
@@ -173,8 +199,8 @@ ${pc.bold("Examples:")}
173
199
  npx mcp-app-studio my-app -y --template poi-map --include-server
174
200
 
175
201
  ${pc.bold("Learn more:")}
176
- Documentation: https://github.com/assistant-ui/mcp-app-studio
177
- Examples: https://github.com/assistant-ui/mcp-app-studio-starter
202
+ Documentation: ${DOCS_URL}
203
+ Examples: ${EXAMPLES_URL}
178
204
  `);
179
205
  }
180
206
  function quotePath(p2) {
@@ -201,6 +227,76 @@ var TEMPLATE_DEFAULT_COMPONENT = {
201
227
  minimal: "welcome",
202
228
  "poi-map": "poi-map"
203
229
  };
230
+ function writeStudioConfig(targetDir, template, appName) {
231
+ const exportConfig = TEMPLATE_EXPORT_CONFIG[template];
232
+ const config = {
233
+ widget: {
234
+ entryPoint: exportConfig.entryPoint,
235
+ exportName: exportConfig.exportName,
236
+ name: appName
237
+ }
238
+ };
239
+ fs2.writeFileSync(
240
+ path3.join(targetDir, "mcp-app-studio.config.json"),
241
+ `${JSON.stringify(config, null, 2)}
242
+ `
243
+ );
244
+ }
245
+ function updateServerPackageName(targetDir, projectPackageName) {
246
+ const serverPkgPath = path3.join(targetDir, "server", "package.json");
247
+ if (!fs2.existsSync(serverPkgPath)) return;
248
+ const raw = fs2.readFileSync(serverPkgPath, "utf-8");
249
+ const pkg = JSON.parse(raw);
250
+ const baseName = projectPackageName.includes("/") ? projectPackageName.split("/").pop() : projectPackageName;
251
+ if (!baseName) return;
252
+ pkg["name"] = `${baseName}-mcp-server`;
253
+ fs2.writeFileSync(serverPkgPath, `${JSON.stringify(pkg, null, 2)}
254
+ `, "utf-8");
255
+ }
256
+ function ensureServerPostinstall(targetDir) {
257
+ const pkgPath = path3.join(targetDir, "package.json");
258
+ const serverPkgPath = path3.join(targetDir, "server", "package.json");
259
+ if (!fs2.existsSync(pkgPath) || !fs2.existsSync(serverPkgPath)) return;
260
+ const raw = fs2.readFileSync(pkgPath, "utf-8");
261
+ const pkg = JSON.parse(raw);
262
+ const scripts = pkg["scripts"] ?? {};
263
+ if (scripts["postinstall"]) return;
264
+ const scriptPath = path3.join(
265
+ targetDir,
266
+ "scripts",
267
+ "mcp-app-studio-postinstall.cjs"
268
+ );
269
+ fs2.mkdirSync(path3.dirname(scriptPath), { recursive: true });
270
+ fs2.writeFileSync(
271
+ scriptPath,
272
+ `/* eslint-disable */
273
+ const { spawnSync } = require("node:child_process");
274
+ const { existsSync } = require("node:fs");
275
+ const { join } = require("node:path");
276
+
277
+ const ROOT = process.cwd();
278
+ const SERVER_DIR = join(ROOT, "server");
279
+
280
+ if (!existsSync(join(SERVER_DIR, "package.json"))) process.exit(0);
281
+ if (existsSync(join(SERVER_DIR, "node_modules")) || existsSync(join(SERVER_DIR, ".pnp.cjs"))) process.exit(0);
282
+
283
+ const ua = process.env.npm_config_user_agent || "";
284
+ let pm = "npm";
285
+ if (ua.includes("pnpm")) pm = "pnpm";
286
+ else if (ua.includes("yarn")) pm = "yarn";
287
+ else if (ua.includes("bun")) pm = "bun";
288
+
289
+ console.log("\\n\\x1b[2mInstalling server dependencies (" + pm + ")...\\x1b[0m\\n");
290
+ const result = spawnSync(pm, ["install"], { cwd: SERVER_DIR, stdio: "inherit", shell: process.platform === "win32" });
291
+ process.exit(result.status == null ? 1 : result.status);
292
+ `,
293
+ "utf-8"
294
+ );
295
+ scripts["postinstall"] = "node scripts/mcp-app-studio-postinstall.cjs";
296
+ pkg["scripts"] = scripts;
297
+ fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
298
+ `, "utf-8");
299
+ }
204
300
  function generateComponentRegistry(components) {
205
301
  const imports = [];
206
302
  const entries = [];
@@ -326,8 +422,10 @@ function generateExamplesIndex(components) {
326
422
  ` : "// No examples\n";
327
423
  }
328
424
  function updateExportScriptDefaults(targetDir, entryPoint, exportName) {
329
- const exportScriptPath = path2.join(targetDir, "scripts/export.ts");
425
+ const exportScriptPath = path3.join(targetDir, "scripts/export.ts");
426
+ if (!fs2.existsSync(exportScriptPath)) return;
330
427
  let content = fs2.readFileSync(exportScriptPath, "utf-8");
428
+ if (content.includes("mcp-app-studio.config.json")) return;
331
429
  content = content.replace(
332
430
  /entryPoint: "lib\/workbench\/wrappers\/[^"]+"/,
333
431
  `entryPoint: "${entryPoint}"`
@@ -358,19 +456,22 @@ function generateWorkbenchIndexExport(components) {
358
456
  return exports.length > 0 ? `export { ${exports.join(", ")} } from "./wrappers";` : "// No SDK exports";
359
457
  }
360
458
  function updateWorkbenchIndex(targetDir, components) {
361
- const indexPath = path2.join(targetDir, "lib/workbench/index.ts");
459
+ const indexPath = path3.join(targetDir, "lib/workbench/index.ts");
362
460
  let content = fs2.readFileSync(indexPath, "utf-8");
363
461
  const wrappersExportRegex = /export \{[^}]*\} from "\.\/wrappers";/;
364
462
  const newExport = generateWorkbenchIndexExport(components);
365
463
  if (wrappersExportRegex.test(content)) {
366
464
  content = content.replace(wrappersExportRegex, newExport);
367
465
  } else {
368
- content = content.trimEnd() + "\n\n" + newExport + "\n";
466
+ content = `${content.trimEnd()}
467
+
468
+ ${newExport}
469
+ `;
369
470
  }
370
471
  fs2.writeFileSync(indexPath, content);
371
472
  }
372
473
  function updateWorkbenchStoreDefault(targetDir, defaultComponent) {
373
- const storePath = path2.join(targetDir, "lib/workbench/store.ts");
474
+ const storePath = path3.join(targetDir, "lib/workbench/store.ts");
374
475
  let content = fs2.readFileSync(storePath, "utf-8");
375
476
  content = content.replace(
376
477
  /selectedComponent: "[^"]+"/,
@@ -380,17 +481,17 @@ function updateWorkbenchStoreDefault(targetDir, defaultComponent) {
380
481
  }
381
482
  function applyTemplate(targetDir, template) {
382
483
  const components = TEMPLATE_COMPONENTS[template];
383
- const registryPath = path2.join(
484
+ const registryPath = path3.join(
384
485
  targetDir,
385
486
  "lib/workbench/component-registry.tsx"
386
487
  );
387
488
  fs2.writeFileSync(registryPath, generateComponentRegistry(components));
388
- const wrappersIndexPath = path2.join(
489
+ const wrappersIndexPath = path3.join(
389
490
  targetDir,
390
491
  "lib/workbench/wrappers/index.ts"
391
492
  );
392
493
  fs2.writeFileSync(wrappersIndexPath, generateWrappersIndex(components));
393
- const examplesIndexPath = path2.join(
494
+ const examplesIndexPath = path3.join(
394
495
  targetDir,
395
496
  "components/examples/index.ts"
396
497
  );
@@ -398,23 +499,23 @@ function applyTemplate(targetDir, template) {
398
499
  updateWorkbenchIndex(targetDir, components);
399
500
  const defaultComponent = TEMPLATE_DEFAULT_COMPONENT[template];
400
501
  updateWorkbenchStoreDefault(targetDir, defaultComponent);
401
- const examplesDir = path2.join(targetDir, "components/examples");
502
+ const examplesDir = path3.join(targetDir, "components/examples");
402
503
  if (!components.includes("welcome")) {
403
- fs2.rmSync(path2.join(examplesDir, "welcome-card"), {
504
+ fs2.rmSync(path3.join(examplesDir, "welcome-card"), {
404
505
  recursive: true,
405
506
  force: true
406
507
  });
407
508
  fs2.rmSync(
408
- path2.join(targetDir, "lib/workbench/wrappers/welcome-card-sdk.tsx"),
509
+ path3.join(targetDir, "lib/workbench/wrappers/welcome-card-sdk.tsx"),
409
510
  { force: true }
410
511
  );
411
512
  }
412
513
  if (!components.includes("poi-map")) {
413
- fs2.rmSync(path2.join(examplesDir, "poi-map"), {
514
+ fs2.rmSync(path3.join(examplesDir, "poi-map"), {
414
515
  recursive: true,
415
516
  force: true
416
517
  });
417
- fs2.rmSync(path2.join(targetDir, "lib/workbench/wrappers/poi-map-sdk.tsx"), {
518
+ fs2.rmSync(path3.join(targetDir, "lib/workbench/wrappers/poi-map-sdk.tsx"), {
418
519
  force: true
419
520
  });
420
521
  }
@@ -425,16 +526,61 @@ function applyTemplate(targetDir, template) {
425
526
  exportConfig.exportName
426
527
  );
427
528
  }
428
- async function downloadTemplate(targetDir) {
429
- const tarballUrl = `https://github.com/${TEMPLATE_REPO}/archive/refs/heads/${TEMPLATE_BRANCH}.tar.gz`;
430
- const tempDir = path2.join(os.tmpdir(), `mcp-app-studio-${Date.now()}`);
431
- const tarballPath = path2.join(tempDir, "template.tar.gz");
529
+ var REQUIRED_TEMPLATE_PATHS = [
530
+ "package.json",
531
+ "scripts/dev.ts",
532
+ "scripts/export.ts",
533
+ "lib/workbench/component-registry.tsx",
534
+ "lib/workbench/wrappers/index.ts",
535
+ "components/examples/index.ts",
536
+ "lib/workbench/index.ts",
537
+ "lib/workbench/store.ts"
538
+ ];
539
+ function validateTemplateDir(templateDir) {
540
+ const missing = REQUIRED_TEMPLATE_PATHS.filter(
541
+ (p2) => !fs2.existsSync(path3.join(templateDir, p2))
542
+ );
543
+ if (missing.length > 0) {
544
+ throw new Error(
545
+ `Template validation failed. Missing expected files:
546
+ ${missing.map((p2) => `- ${p2}`).join(
547
+ "\n"
548
+ )}
549
+
550
+ This may indicate the starter template has changed. Try updating mcp-app-studio or set MCP_APP_STUDIO_TEMPLATE_REPO / MCP_APP_STUDIO_TEMPLATE_REF to a compatible template.`
551
+ );
552
+ }
553
+ }
554
+ function copyDirContents(srcDir, destDir) {
555
+ fs2.mkdirSync(destDir, { recursive: true });
556
+ const entries = fs2.readdirSync(srcDir, { withFileTypes: true });
557
+ for (const entry of entries) {
558
+ if (entry.name === ".git") continue;
559
+ const srcPath = path3.join(srcDir, entry.name);
560
+ const destPath = path3.join(destDir, entry.name);
561
+ if (entry.isDirectory()) {
562
+ copyDirContents(srcPath, destPath);
563
+ continue;
564
+ }
565
+ if (entry.isFile()) {
566
+ fs2.mkdirSync(path3.dirname(destPath), { recursive: true });
567
+ fs2.copyFileSync(srcPath, destPath);
568
+ }
569
+ }
570
+ }
571
+ async function downloadTemplateToTemp() {
572
+ const tarballUrl = getGithubArchiveTarballUrl(TEMPLATE_REPO, TEMPLATE_REF);
573
+ const tempDir = fs2.mkdtempSync(
574
+ path3.join(os.tmpdir(), "mcp-app-studio-template-")
575
+ );
576
+ const tarballPath = path3.join(tempDir, "template.tar.gz");
577
+ const extractDir = path3.join(tempDir, "extract");
432
578
  try {
433
- fs2.mkdirSync(tempDir, { recursive: true });
579
+ fs2.mkdirSync(extractDir, { recursive: true });
434
580
  const response = await fetch(tarballUrl);
435
581
  if (!response.ok) {
436
582
  throw new Error(
437
- `Failed to download template: ${response.status} ${response.statusText}`
583
+ `Failed to download template from ${tarballUrl}: ${response.status} ${response.statusText}`
438
584
  );
439
585
  }
440
586
  const fileStream = createWriteStream(tarballPath);
@@ -446,15 +592,33 @@ async function downloadTemplate(targetDir) {
446
592
  body
447
593
  );
448
594
  await pipeline(nodeStream, fileStream);
449
- fs2.mkdirSync(targetDir, { recursive: true });
450
595
  await extract({
451
596
  file: tarballPath,
452
- cwd: targetDir,
453
- strip: 1
454
- // Remove the top-level directory (e.g., mcp-app-studio-starter-main/)
597
+ cwd: extractDir,
598
+ strip: 0,
599
+ filter: (entryPath, entry) => filterTemplateTarEntry(extractDir, entryPath, entry)
455
600
  });
456
- } finally {
457
- fs2.rmSync(tempDir, { recursive: true, force: true });
601
+ const topLevelDirs = fs2.readdirSync(extractDir, { withFileTypes: true }).filter((d) => d.isDirectory());
602
+ if (topLevelDirs.length !== 1) {
603
+ throw new Error(
604
+ `Unexpected template archive layout. Expected a single top-level directory, found ${topLevelDirs.length}.`
605
+ );
606
+ }
607
+ const topLevelDir = topLevelDirs[0];
608
+ if (!topLevelDir) {
609
+ throw new Error(
610
+ "Unexpected template archive layout. No top-level directory found."
611
+ );
612
+ }
613
+ const templateDir = path3.join(extractDir, topLevelDir.name);
614
+ validateTemplateDir(templateDir);
615
+ return { tempDir, templateDir };
616
+ } catch (err) {
617
+ try {
618
+ fs2.rmSync(tempDir, { recursive: true, force: true });
619
+ } catch {
620
+ }
621
+ throw err;
458
622
  }
459
623
  }
460
624
  function parseArgs(args) {
@@ -599,8 +763,8 @@ async function main() {
599
763
  p.cancel("Operation cancelled.");
600
764
  process.exit(0);
601
765
  }
602
- const targetDir = path2.resolve(process.cwd(), projectName);
603
- const packageName = projectName === "." ? toValidPackageName(path2.basename(targetDir)) : toValidPackageName(projectName);
766
+ const targetDir = path3.resolve(process.cwd(), projectName);
767
+ const packageName = projectName === "." ? toValidPackageName(path3.basename(targetDir)) : toValidPackageName(projectName);
604
768
  const config = {
605
769
  name: projectName,
606
770
  packageName,
@@ -608,9 +772,11 @@ async function main() {
608
772
  template,
609
773
  includeServer
610
774
  };
611
- if (!isEmpty(targetDir)) {
775
+ const targetNotEmpty = !isEmpty(targetDir);
776
+ let shouldOverwrite = false;
777
+ if (targetNotEmpty) {
612
778
  if (nonInteractive) {
613
- emptyDir(targetDir);
779
+ shouldOverwrite = true;
614
780
  } else {
615
781
  const overwrite = await p.confirm({
616
782
  message: `Directory "${projectName}" is not empty. Remove existing files?`,
@@ -620,13 +786,14 @@ async function main() {
620
786
  p.cancel("Operation cancelled.");
621
787
  process.exit(0);
622
788
  }
623
- emptyDir(targetDir);
789
+ shouldOverwrite = true;
624
790
  }
625
791
  }
626
792
  const s = p.spinner();
627
793
  s.start("Downloading template...");
794
+ let downloaded;
628
795
  try {
629
- await downloadTemplate(targetDir);
796
+ downloaded = await downloadTemplateToTemp();
630
797
  } catch (error) {
631
798
  s.stop("Download failed");
632
799
  p.log.error(
@@ -635,16 +802,35 @@ async function main() {
635
802
  process.exit(1);
636
803
  }
637
804
  s.message("Creating project...");
638
- updatePackageJson(
639
- targetDir,
640
- config.packageName,
641
- config.description,
642
- config.includeServer
643
- );
805
+ try {
806
+ if (!downloaded) {
807
+ throw new Error(
808
+ "Internal error: template download did not return a result"
809
+ );
810
+ }
811
+ if (shouldOverwrite) {
812
+ emptyDir(targetDir);
813
+ }
814
+ fs2.mkdirSync(targetDir, { recursive: true });
815
+ copyDirContents(downloaded.templateDir, targetDir);
816
+ } finally {
817
+ if (downloaded?.tempDir) {
818
+ fs2.rmSync(downloaded.tempDir, { recursive: true, force: true });
819
+ }
820
+ }
821
+ updatePackageJson(targetDir, config.packageName, config.description, {
822
+ mcpAppStudioVersion: getVersion()
823
+ });
824
+ if (config.includeServer) {
825
+ ensureServerPostinstall(targetDir);
826
+ }
644
827
  s.message("Applying template...");
645
828
  applyTemplate(targetDir, config.template);
646
- if (!config.includeServer) {
647
- fs2.rmSync(path2.join(targetDir, "server"), { recursive: true, force: true });
829
+ writeStudioConfig(targetDir, config.template, path3.basename(targetDir));
830
+ if (config.includeServer) {
831
+ updateServerPackageName(targetDir, config.packageName);
832
+ } else {
833
+ fs2.rmSync(path3.join(targetDir, "server"), { recursive: true, force: true });
648
834
  }
649
835
  s.stop("Project created!");
650
836
  const pm = detectPackageManager();
@@ -694,12 +880,8 @@ async function main() {
694
880
  );
695
881
  p.log.message("");
696
882
  p.log.step(pc.bold("Learn more:"));
697
- p.log.message(
698
- ` ${pc.dim("\u2022")} SDK Docs: ${pc.cyan("https://github.com/assistant-ui/mcp-app-studio#sdk")}`
699
- );
700
- p.log.message(
701
- ` ${pc.dim("\u2022")} Examples: ${pc.cyan("https://github.com/assistant-ui/mcp-app-studio-starter")}`
702
- );
883
+ p.log.message(` ${pc.dim("\u2022")} SDK Docs: ${pc.cyan(DOCS_URL)}`);
884
+ p.log.message(` ${pc.dim("\u2022")} Examples: ${pc.cyan(EXAMPLES_URL)}`);
703
885
  if (config.includeServer) {
704
886
  p.log.message(
705
887
  ` ${pc.dim("\u2022")} MCP Guide: ${pc.cyan("https://modelcontextprotocol.io/quickstart")}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-app-studio",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Build interactive apps for AI assistants (ChatGPT, Claude, MCP hosts)",
5
5
  "keywords": [
6
6
  "chatgpt",
@@ -62,9 +62,9 @@
62
62
  ],
63
63
  "sideEffects": false,
64
64
  "dependencies": {
65
- "@clack/prompts": "^0.11.0",
65
+ "@clack/prompts": "^1.0.0",
66
66
  "picocolors": "^1.1.1",
67
- "tar": "^7.5.6"
67
+ "tar": "^7.5.7"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@modelcontextprotocol/ext-apps": ">=0.4.0",
@@ -79,12 +79,12 @@
79
79
  }
80
80
  },
81
81
  "devDependencies": {
82
- "@modelcontextprotocol/ext-apps": "^0.4.0",
83
- "@types/node": "^25.0.10",
84
- "@types/react": "^19.1.6",
82
+ "@modelcontextprotocol/ext-apps": "^1.0.1",
83
+ "@types/node": "^25.2.0",
84
+ "@types/react": "^19.2.10",
85
85
  "@types/tar": "^6.1.13",
86
86
  "@vitest/coverage-v8": "^4.0.18",
87
- "react": "^19.1.0",
87
+ "react": "^19.2.4",
88
88
  "tsup": "^8.5.1",
89
89
  "tsx": "^4.21.0",
90
90
  "typescript": "^5.9.3",