@vizejs/musea-mcp-server 0.81.0 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as startServer } from "./src-Cp5GZpzj.mjs";
2
+ import { n as startServer } from "./src-BrRHhfXh.mjs";
3
3
  //#region src/cli.ts
4
4
  /**
5
5
  * Musea MCP Server CLI.
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as startServer, t as createMuseaServer } from "./src-Cp5GZpzj.mjs";
1
+ import { n as startServer, t as createMuseaServer } from "./src-BrRHhfXh.mjs";
2
2
  export { createMuseaServer, createMuseaServer as default, startServer };
@@ -495,9 +495,36 @@ function tokenize(query) {
495
495
  return Array.from(new Set(query.toLowerCase().split(/[\s/,_-]+/).map((term) => term.trim()).filter(Boolean)));
496
496
  }
497
497
  function toProjectPath(projectRoot, absolutePath) {
498
- const relativePath = path.relative(projectRoot, absolutePath);
499
- if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) return absolutePath;
500
- return relativePath;
498
+ const root = realpathNearest(projectRoot);
499
+ const resolved = realpathNearest(absolutePath);
500
+ const relativePath = path.relative(root, resolved);
501
+ return isProjectPath(root, resolved) ? relativePath || "." : resolved;
502
+ }
503
+ function realpathNearest(targetPath) {
504
+ let current = path.resolve(targetPath);
505
+ const missingParts = [];
506
+ while (true) try {
507
+ const real = fs.realpathSync.native(current);
508
+ return missingParts.length > 0 ? path.join(real, ...missingParts.reverse()) : real;
509
+ } catch {
510
+ const parent = path.dirname(current);
511
+ if (parent === current) return path.resolve(targetPath);
512
+ missingParts.push(path.basename(current));
513
+ current = parent;
514
+ }
515
+ }
516
+ function isProjectPath(projectRoot, candidatePath) {
517
+ const root = realpathNearest(projectRoot);
518
+ const candidate = realpathNearest(candidatePath);
519
+ const relativePath = path.relative(root, candidate);
520
+ return relativePath === "" || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
521
+ }
522
+ function resolveProjectPath(projectRoot, inputPath, label = "path") {
523
+ if (inputPath.includes("\0")) throw new McpError(ErrorCode.InvalidParams, `${label} contains an invalid character`);
524
+ const root = path.resolve(projectRoot);
525
+ const resolvedPath = path.isAbsolute(inputPath) ? path.resolve(inputPath) : path.resolve(root, inputPath);
526
+ if (!isProjectPath(root, resolvedPath)) throw new McpError(ErrorCode.InvalidParams, `${label} must stay inside the project root`);
527
+ return resolvedPath;
501
528
  }
502
529
  function buildResourceUris$1(relativePath, variantNames, hasComponentSource) {
503
530
  const encodedPath = encodeURIComponent(relativePath);
@@ -628,7 +655,7 @@ async function resolveArtReference(ctx, args) {
628
655
  const queryArg = typeof args?.query === "string" ? args.query : void 0;
629
656
  const refArg = typeof args?.ref === "string" ? args.ref : void 0;
630
657
  if (pathArg) {
631
- const resolvedPath = path.isAbsolute(pathArg) ? pathArg : path.resolve(ctx.projectRoot, pathArg);
658
+ const resolvedPath = resolveProjectPath(ctx.projectRoot, pathArg, "path");
632
659
  const normalizedResolvedPath = normalizePathLike(resolvedPath);
633
660
  const normalizedRelativePath = normalizePathLike(path.relative(ctx.projectRoot, resolvedPath));
634
661
  const directMatch = arts.find((info) => {
@@ -719,6 +746,13 @@ async function getComponentSourceDescriptor(ctx, resolved) {
719
746
  exists: false,
720
747
  error: "This art file does not declare a component source."
721
748
  };
749
+ if (!isProjectPath(ctx.projectRoot, componentPath)) return {
750
+ reference: resolved.info.component,
751
+ absolutePath: componentPath,
752
+ path: componentPath,
753
+ exists: false,
754
+ error: "Component source is outside the project root."
755
+ };
722
756
  try {
723
757
  await fs.promises.access(componentPath, fs.constants.R_OK);
724
758
  return {
@@ -977,7 +1011,7 @@ async function handleAnalyzeComponent(ctx, binding, args) {
977
1011
  const directPath = args?.path;
978
1012
  if (directPath?.endsWith(".vue") && !directPath.endsWith(".art.vue")) {
979
1013
  if (!binding.analyzeSfc) throw new McpError(ErrorCode.InternalError, "analyzeSfc not available in native binding");
980
- const absolutePath = path.resolve(ctx.projectRoot, directPath);
1014
+ const absolutePath = resolveProjectPath(ctx.projectRoot, directPath, "path");
981
1015
  const source = await fs.promises.readFile(absolutePath, "utf-8");
982
1016
  const analysis = binding.analyzeSfc(source, { filename: absolutePath });
983
1017
  return { content: [{
@@ -1344,7 +1378,7 @@ async function handleGenerateVariants(ctx, binding, args) {
1344
1378
  if (!componentRelPath) throw new McpError(ErrorCode.InvalidParams, "componentPath is required");
1345
1379
  if (!binding.analyzeSfc) throw new McpError(ErrorCode.InternalError, "analyzeSfc not available in native binding");
1346
1380
  if (!binding.generateVariants) throw new McpError(ErrorCode.InternalError, "generateVariants not available in native binding");
1347
- const absolutePath = path.resolve(ctx.projectRoot, componentRelPath);
1381
+ const absolutePath = resolveProjectPath(ctx.projectRoot, componentRelPath, "componentPath");
1348
1382
  const source = await fs.promises.readFile(absolutePath, "utf-8");
1349
1383
  const props = binding.analyzeSfc(source, { filename: absolutePath }).props.map((prop) => ({
1350
1384
  name: prop.name,
@@ -1469,7 +1503,7 @@ async function handleGetTokens(ctx, args) {
1469
1503
  const inputPath = args?.tokensPath;
1470
1504
  const format = args?.format ?? "json";
1471
1505
  let resolvedPath;
1472
- if (inputPath) resolvedPath = path.resolve(ctx.projectRoot, inputPath);
1506
+ if (inputPath) resolvedPath = resolveProjectPath(ctx.projectRoot, inputPath, "tokensPath");
1473
1507
  else resolvedPath = await ctx.resolveTokensPath();
1474
1508
  if (!resolvedPath) throw new McpError(ErrorCode.InvalidParams, "No tokens path provided and none auto-detected. Looked for: tokens/, design-tokens/, style-dictionary/ directories.");
1475
1509
  const categories = await parseTokensFromPath(resolvedPath);
@@ -1494,7 +1528,7 @@ async function handleSearchTokens(ctx, args) {
1494
1528
  const inputPath = args?.tokensPath;
1495
1529
  const typeFilter = typeof args?.type === "string" ? args.type.toLowerCase() : void 0;
1496
1530
  const limit = typeof args?.limit === "number" ? args.limit : 20;
1497
- const resolvedPath = inputPath ? path.resolve(ctx.projectRoot, inputPath) : await ctx.resolveTokensPath();
1531
+ const resolvedPath = inputPath ? resolveProjectPath(ctx.projectRoot, inputPath, "tokensPath") : await ctx.resolveTokensPath();
1498
1532
  if (!resolvedPath) throw new McpError(ErrorCode.InvalidParams, "No tokens path provided and none auto-detected. Looked for: tokens/, design-tokens/, style-dictionary/ directories.");
1499
1533
  const flattened = flattenTokenCategories(await parseTokensFromPath(resolvedPath));
1500
1534
  const normalizedQuery = query.toLowerCase();
@@ -1672,7 +1706,7 @@ async function readResource(ctx, uri) {
1672
1706
  }
1673
1707
  if (uri.startsWith("musea://source/")) {
1674
1708
  const relativePath = decodeURIComponent(uri.slice(15));
1675
- const absolutePath = path.resolve(ctx.projectRoot, relativePath);
1709
+ const absolutePath = resolveProjectPath(ctx.projectRoot, relativePath, "source path");
1676
1710
  return { contents: [{
1677
1711
  uri,
1678
1712
  mimeType: "text/plain",
@@ -1687,7 +1721,7 @@ async function readResource(ctx, uri) {
1687
1721
  includeDocumentation: false
1688
1722
  })).componentSource;
1689
1723
  if (!componentSource?.path || componentSource.exists !== true) throw new McpError(ErrorCode.InvalidRequest, componentSource?.error ?? "Component source not available for this art file");
1690
- const absolutePath = path.resolve(ctx.projectRoot, componentSource.path);
1724
+ const absolutePath = resolveProjectPath(ctx.projectRoot, componentSource.path, "component source path");
1691
1725
  return { contents: [{
1692
1726
  uri,
1693
1727
  mimeType: "text/plain",
@@ -1766,7 +1800,7 @@ function createMuseaServer(config) {
1766
1800
  resources: {},
1767
1801
  tools: {}
1768
1802
  } });
1769
- const projectRoot = config.projectRoot;
1803
+ const projectRoot = path.resolve(config.projectRoot);
1770
1804
  const include = config.include ?? ["**/*.art.vue"];
1771
1805
  const exclude = config.exclude ?? ["node_modules/**", "dist/**"];
1772
1806
  const tokensPath = config.tokensPath;
@@ -1801,7 +1835,7 @@ function createMuseaServer(config) {
1801
1835
  return artCache;
1802
1836
  }
1803
1837
  async function resolveTokensPath() {
1804
- if (tokensPath) return path.resolve(projectRoot, tokensPath);
1838
+ if (tokensPath) return resolveProjectPath(projectRoot, tokensPath, "tokensPath");
1805
1839
  for (const dir of [
1806
1840
  "tokens",
1807
1841
  "design-tokens",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizejs/musea-mcp-server",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "description": "MCP server for building Vue.js design systems - component analysis, documentation, variant generation, and design tokens",
5
5
  "keywords": [
6
6
  "ai",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "1.29.0",
41
- "@vizejs/native": "0.81.0"
41
+ "@vizejs/native": "0.83.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@tsdown/css": "0.22.0",