@vizejs/musea-mcp-server 0.81.0 → 0.82.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-DCb_Jsfy.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-DCb_Jsfy.mjs";
2
2
  export { createMuseaServer, createMuseaServer as default, startServer };
@@ -495,9 +495,23 @@ 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 = path.resolve(projectRoot);
499
+ const resolved = path.resolve(absolutePath);
500
+ const relativePath = path.relative(root, resolved);
501
+ return isProjectPath(root, resolved) ? relativePath || "." : resolved;
502
+ }
503
+ function isProjectPath(projectRoot, candidatePath) {
504
+ const root = path.resolve(projectRoot);
505
+ const candidate = path.resolve(candidatePath);
506
+ const relativePath = path.relative(root, candidate);
507
+ return relativePath === "" || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
508
+ }
509
+ function resolveProjectPath(projectRoot, inputPath, label = "path") {
510
+ if (inputPath.includes("\0")) throw new McpError(ErrorCode.InvalidParams, `${label} contains an invalid character`);
511
+ const root = path.resolve(projectRoot);
512
+ const resolvedPath = path.isAbsolute(inputPath) ? path.resolve(inputPath) : path.resolve(root, inputPath);
513
+ if (!isProjectPath(root, resolvedPath)) throw new McpError(ErrorCode.InvalidParams, `${label} must stay inside the project root`);
514
+ return resolvedPath;
501
515
  }
502
516
  function buildResourceUris$1(relativePath, variantNames, hasComponentSource) {
503
517
  const encodedPath = encodeURIComponent(relativePath);
@@ -628,7 +642,7 @@ async function resolveArtReference(ctx, args) {
628
642
  const queryArg = typeof args?.query === "string" ? args.query : void 0;
629
643
  const refArg = typeof args?.ref === "string" ? args.ref : void 0;
630
644
  if (pathArg) {
631
- const resolvedPath = path.isAbsolute(pathArg) ? pathArg : path.resolve(ctx.projectRoot, pathArg);
645
+ const resolvedPath = resolveProjectPath(ctx.projectRoot, pathArg, "path");
632
646
  const normalizedResolvedPath = normalizePathLike(resolvedPath);
633
647
  const normalizedRelativePath = normalizePathLike(path.relative(ctx.projectRoot, resolvedPath));
634
648
  const directMatch = arts.find((info) => {
@@ -719,6 +733,13 @@ async function getComponentSourceDescriptor(ctx, resolved) {
719
733
  exists: false,
720
734
  error: "This art file does not declare a component source."
721
735
  };
736
+ if (!isProjectPath(ctx.projectRoot, componentPath)) return {
737
+ reference: resolved.info.component,
738
+ absolutePath: componentPath,
739
+ path: componentPath,
740
+ exists: false,
741
+ error: "Component source is outside the project root."
742
+ };
722
743
  try {
723
744
  await fs.promises.access(componentPath, fs.constants.R_OK);
724
745
  return {
@@ -977,7 +998,7 @@ async function handleAnalyzeComponent(ctx, binding, args) {
977
998
  const directPath = args?.path;
978
999
  if (directPath?.endsWith(".vue") && !directPath.endsWith(".art.vue")) {
979
1000
  if (!binding.analyzeSfc) throw new McpError(ErrorCode.InternalError, "analyzeSfc not available in native binding");
980
- const absolutePath = path.resolve(ctx.projectRoot, directPath);
1001
+ const absolutePath = resolveProjectPath(ctx.projectRoot, directPath, "path");
981
1002
  const source = await fs.promises.readFile(absolutePath, "utf-8");
982
1003
  const analysis = binding.analyzeSfc(source, { filename: absolutePath });
983
1004
  return { content: [{
@@ -1344,7 +1365,7 @@ async function handleGenerateVariants(ctx, binding, args) {
1344
1365
  if (!componentRelPath) throw new McpError(ErrorCode.InvalidParams, "componentPath is required");
1345
1366
  if (!binding.analyzeSfc) throw new McpError(ErrorCode.InternalError, "analyzeSfc not available in native binding");
1346
1367
  if (!binding.generateVariants) throw new McpError(ErrorCode.InternalError, "generateVariants not available in native binding");
1347
- const absolutePath = path.resolve(ctx.projectRoot, componentRelPath);
1368
+ const absolutePath = resolveProjectPath(ctx.projectRoot, componentRelPath, "componentPath");
1348
1369
  const source = await fs.promises.readFile(absolutePath, "utf-8");
1349
1370
  const props = binding.analyzeSfc(source, { filename: absolutePath }).props.map((prop) => ({
1350
1371
  name: prop.name,
@@ -1469,7 +1490,7 @@ async function handleGetTokens(ctx, args) {
1469
1490
  const inputPath = args?.tokensPath;
1470
1491
  const format = args?.format ?? "json";
1471
1492
  let resolvedPath;
1472
- if (inputPath) resolvedPath = path.resolve(ctx.projectRoot, inputPath);
1493
+ if (inputPath) resolvedPath = resolveProjectPath(ctx.projectRoot, inputPath, "tokensPath");
1473
1494
  else resolvedPath = await ctx.resolveTokensPath();
1474
1495
  if (!resolvedPath) throw new McpError(ErrorCode.InvalidParams, "No tokens path provided and none auto-detected. Looked for: tokens/, design-tokens/, style-dictionary/ directories.");
1475
1496
  const categories = await parseTokensFromPath(resolvedPath);
@@ -1494,7 +1515,7 @@ async function handleSearchTokens(ctx, args) {
1494
1515
  const inputPath = args?.tokensPath;
1495
1516
  const typeFilter = typeof args?.type === "string" ? args.type.toLowerCase() : void 0;
1496
1517
  const limit = typeof args?.limit === "number" ? args.limit : 20;
1497
- const resolvedPath = inputPath ? path.resolve(ctx.projectRoot, inputPath) : await ctx.resolveTokensPath();
1518
+ const resolvedPath = inputPath ? resolveProjectPath(ctx.projectRoot, inputPath, "tokensPath") : await ctx.resolveTokensPath();
1498
1519
  if (!resolvedPath) throw new McpError(ErrorCode.InvalidParams, "No tokens path provided and none auto-detected. Looked for: tokens/, design-tokens/, style-dictionary/ directories.");
1499
1520
  const flattened = flattenTokenCategories(await parseTokensFromPath(resolvedPath));
1500
1521
  const normalizedQuery = query.toLowerCase();
@@ -1672,7 +1693,7 @@ async function readResource(ctx, uri) {
1672
1693
  }
1673
1694
  if (uri.startsWith("musea://source/")) {
1674
1695
  const relativePath = decodeURIComponent(uri.slice(15));
1675
- const absolutePath = path.resolve(ctx.projectRoot, relativePath);
1696
+ const absolutePath = resolveProjectPath(ctx.projectRoot, relativePath, "source path");
1676
1697
  return { contents: [{
1677
1698
  uri,
1678
1699
  mimeType: "text/plain",
@@ -1687,7 +1708,7 @@ async function readResource(ctx, uri) {
1687
1708
  includeDocumentation: false
1688
1709
  })).componentSource;
1689
1710
  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);
1711
+ const absolutePath = resolveProjectPath(ctx.projectRoot, componentSource.path, "component source path");
1691
1712
  return { contents: [{
1692
1713
  uri,
1693
1714
  mimeType: "text/plain",
@@ -1766,7 +1787,7 @@ function createMuseaServer(config) {
1766
1787
  resources: {},
1767
1788
  tools: {}
1768
1789
  } });
1769
- const projectRoot = config.projectRoot;
1790
+ const projectRoot = path.resolve(config.projectRoot);
1770
1791
  const include = config.include ?? ["**/*.art.vue"];
1771
1792
  const exclude = config.exclude ?? ["node_modules/**", "dist/**"];
1772
1793
  const tokensPath = config.tokensPath;
@@ -1801,7 +1822,7 @@ function createMuseaServer(config) {
1801
1822
  return artCache;
1802
1823
  }
1803
1824
  async function resolveTokensPath() {
1804
- if (tokensPath) return path.resolve(projectRoot, tokensPath);
1825
+ if (tokensPath) return resolveProjectPath(projectRoot, tokensPath, "tokensPath");
1805
1826
  for (const dir of [
1806
1827
  "tokens",
1807
1828
  "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.82.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.82.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@tsdown/css": "0.22.0",