@wallarm-org/mcp 3.2.0 → 3.3.0-rc-fix-claude-code.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/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mc
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import fs from "fs";
5
5
  import path_0 from "path";
6
+ import { fileURLToPath } from "url";
6
7
  function getEnumValues(entries) {
7
8
  const numericValues = Object.values(entries).filter((v)=>"number" == typeof v);
8
9
  const values = Object.entries(entries).filter(([k, _])=>-1 === numericValues.indexOf(+k)).map(([_, v])=>v);
@@ -40,6 +41,13 @@ function cleanRegex(source) {
40
41
  const end = source.endsWith("$") ? source.length - 1 : source.length;
41
42
  return source.slice(start, end);
42
43
  }
44
+ function floatSafeRemainder(val, step) {
45
+ const ratio = val / step;
46
+ const roundedRatio = Math.round(ratio);
47
+ const tolerance = 4 * Number.EPSILON * Math.max(Math.abs(ratio), 1);
48
+ if (Math.abs(ratio - roundedRatio) < tolerance) return 0;
49
+ return ratio - roundedRatio;
50
+ }
43
51
  function util_assignProp(target, prop, value) {
44
52
  Object.defineProperty(target, prop, {
45
53
  value,
@@ -961,6 +969,7 @@ function datetime(args) {
961
969
  return new RegExp(`^${dateSource}T(?:${timeRegex})$`);
962
970
  }
963
971
  const anyString = /^[\s\S]{0,}$/;
972
+ const number = /^-?\d+(?:\.\d+)?$/;
964
973
  const regexes_boolean = /^(?:true|false)$/i;
965
974
  const lowercase = /^[^A-Z]*$/;
966
975
  const uppercase = /^[^a-z]*$/;
@@ -974,6 +983,120 @@ const _whenHasLength = (payload)=>{
974
983
  const val = payload.value;
975
984
  return !nullish(val) && void 0 !== val.length;
976
985
  };
986
+ const numericOriginMap = {
987
+ number: "number",
988
+ bigint: "bigint",
989
+ object: "date"
990
+ };
991
+ const $ZodCheckLessThan = /*@__PURE__*/ $constructor("$ZodCheckLessThan", (inst, def)=>{
992
+ $ZodCheck.init(inst, def);
993
+ const origin = numericOriginMap[typeof def.value];
994
+ inst._zod.check = (payload)=>{
995
+ if (def.inclusive ? payload.value <= def.value : payload.value < def.value) return;
996
+ payload.issues.push({
997
+ origin: numericOriginMap[typeof payload.value] ?? origin,
998
+ code: "too_big",
999
+ maximum: "object" == typeof def.value ? def.value.getTime() : def.value,
1000
+ input: payload.value,
1001
+ inclusive: def.inclusive,
1002
+ inst,
1003
+ continue: !def.abort
1004
+ });
1005
+ };
1006
+ });
1007
+ const $ZodCheckGreaterThan = /*@__PURE__*/ $constructor("$ZodCheckGreaterThan", (inst, def)=>{
1008
+ $ZodCheck.init(inst, def);
1009
+ const origin = numericOriginMap[typeof def.value];
1010
+ inst._zod.check = (payload)=>{
1011
+ if (def.inclusive ? payload.value >= def.value : payload.value > def.value) return;
1012
+ payload.issues.push({
1013
+ origin: numericOriginMap[typeof payload.value] ?? origin,
1014
+ code: "too_small",
1015
+ minimum: "object" == typeof def.value ? def.value.getTime() : def.value,
1016
+ input: payload.value,
1017
+ inclusive: def.inclusive,
1018
+ inst,
1019
+ continue: !def.abort
1020
+ });
1021
+ };
1022
+ });
1023
+ const $ZodCheckMultipleOf = /*@__PURE__*/ $constructor("$ZodCheckMultipleOf", (inst, def)=>{
1024
+ $ZodCheck.init(inst, def);
1025
+ inst._zod.check = (payload)=>{
1026
+ if (typeof payload.value !== typeof def.value) throw new Error("Cannot mix number and bigint in multiple_of check.");
1027
+ const isMultiple = "bigint" == typeof payload.value ? def.value !== BigInt(0) && payload.value % def.value === BigInt(0) : 0 === floatSafeRemainder(payload.value, def.value);
1028
+ if (isMultiple) return;
1029
+ payload.issues.push({
1030
+ origin: typeof payload.value,
1031
+ code: "not_multiple_of",
1032
+ divisor: def.value,
1033
+ input: payload.value,
1034
+ inst,
1035
+ continue: !def.abort
1036
+ });
1037
+ };
1038
+ });
1039
+ const $ZodCheckNumberFormat = /*@__PURE__*/ $constructor("$ZodCheckNumberFormat", (inst, def)=>{
1040
+ $ZodCheck.init(inst, def);
1041
+ def.format = def.format || "float64";
1042
+ const isInt = def.format?.includes("int");
1043
+ const origin = isInt ? "int" : "number";
1044
+ const [minimum, maximum] = NUMBER_FORMAT_RANGES[def.format];
1045
+ inst._zod.check = (payload)=>{
1046
+ const input = payload.value;
1047
+ if (isInt) {
1048
+ if (!Number.isInteger(input)) return void payload.issues.push({
1049
+ expected: origin,
1050
+ format: def.format,
1051
+ code: "invalid_type",
1052
+ continue: false,
1053
+ input,
1054
+ inst
1055
+ });
1056
+ if (!Number.isSafeInteger(input)) {
1057
+ if (input > 0) payload.issues.push({
1058
+ input,
1059
+ code: "too_big",
1060
+ maximum: Number.MAX_SAFE_INTEGER,
1061
+ note: "Integers must be within the safe integer range.",
1062
+ inst,
1063
+ origin,
1064
+ inclusive: true,
1065
+ continue: !def.abort
1066
+ });
1067
+ else payload.issues.push({
1068
+ input,
1069
+ code: "too_small",
1070
+ minimum: Number.MIN_SAFE_INTEGER,
1071
+ note: "Integers must be within the safe integer range.",
1072
+ inst,
1073
+ origin,
1074
+ inclusive: true,
1075
+ continue: !def.abort
1076
+ });
1077
+ return;
1078
+ }
1079
+ }
1080
+ if (input < minimum) payload.issues.push({
1081
+ origin: "number",
1082
+ input,
1083
+ code: "too_small",
1084
+ minimum,
1085
+ inclusive: true,
1086
+ inst,
1087
+ continue: !def.abort
1088
+ });
1089
+ if (input > maximum) payload.issues.push({
1090
+ origin: "number",
1091
+ input,
1092
+ code: "too_big",
1093
+ maximum,
1094
+ inclusive: true,
1095
+ inst,
1096
+ continue: !def.abort
1097
+ });
1098
+ };
1099
+ });
977
1100
  const $ZodCheckMaxLength = /*@__PURE__*/ $constructor("$ZodCheckMaxLength", (inst, def)=>{
978
1101
  var _a;
979
1102
  $ZodCheck.init(inst, def);
@@ -1630,6 +1753,32 @@ const $ZodJWT = /*@__PURE__*/ $constructor("$ZodJWT", (inst, def)=>{
1630
1753
  });
1631
1754
  };
1632
1755
  });
1756
+ const $ZodNumber = /*@__PURE__*/ $constructor("$ZodNumber", (inst, def)=>{
1757
+ $ZodType.init(inst, def);
1758
+ inst._zod.pattern = number;
1759
+ inst._zod.parse = (payload, _ctx)=>{
1760
+ if (def.coerce) try {
1761
+ payload.value = Number(payload.value);
1762
+ } catch (_) {}
1763
+ const input = payload.value;
1764
+ if ("number" == typeof input && !Number.isNaN(input) && Number.isFinite(input)) return payload;
1765
+ const received = "number" == typeof input ? Number.isNaN(input) ? "NaN" : Number.isFinite(input) ? void 0 : String(input) : void 0;
1766
+ payload.issues.push({
1767
+ expected: "number",
1768
+ code: "invalid_type",
1769
+ input,
1770
+ inst,
1771
+ ...received ? {
1772
+ received
1773
+ } : {}
1774
+ });
1775
+ return payload;
1776
+ };
1777
+ });
1778
+ const $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumberFormat", (inst, def)=>{
1779
+ $ZodCheckNumberFormat.init(inst, def);
1780
+ $ZodNumber.init(inst, def);
1781
+ });
1633
1782
  const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def)=>{
1634
1783
  $ZodType.init(inst, def);
1635
1784
  inst._zod.pattern = regexes_boolean;
@@ -2731,6 +2880,22 @@ function _isoDuration(Class, params) {
2731
2880
  ...normalizeParams(params)
2732
2881
  });
2733
2882
  }
2883
+ function _number(Class, params) {
2884
+ return new Class(snapshotChecks({
2885
+ type: "number",
2886
+ checks: [],
2887
+ ...normalizeParams(params)
2888
+ }));
2889
+ }
2890
+ function _int(Class, params) {
2891
+ return new Class({
2892
+ type: "number",
2893
+ check: "number_format",
2894
+ abort: false,
2895
+ format: "safeint",
2896
+ ...normalizeParams(params)
2897
+ });
2898
+ }
2734
2899
  function _boolean(Class, params) {
2735
2900
  return new Class({
2736
2901
  type: "boolean",
@@ -2748,6 +2913,45 @@ function _never(Class, params) {
2748
2913
  ...normalizeParams(params)
2749
2914
  });
2750
2915
  }
2916
+ function _lt(value, params) {
2917
+ return new $ZodCheckLessThan({
2918
+ check: "less_than",
2919
+ ...normalizeParams(params),
2920
+ value,
2921
+ inclusive: false
2922
+ });
2923
+ }
2924
+ function _lte(value, params) {
2925
+ return new $ZodCheckLessThan({
2926
+ check: "less_than",
2927
+ ...normalizeParams(params),
2928
+ value,
2929
+ inclusive: true
2930
+ });
2931
+ }
2932
+ function _gt(value, params) {
2933
+ return new $ZodCheckGreaterThan({
2934
+ check: "greater_than",
2935
+ ...normalizeParams(params),
2936
+ value,
2937
+ inclusive: false
2938
+ });
2939
+ }
2940
+ function _gte(value, params) {
2941
+ return new $ZodCheckGreaterThan({
2942
+ check: "greater_than",
2943
+ ...normalizeParams(params),
2944
+ value,
2945
+ inclusive: true
2946
+ });
2947
+ }
2948
+ function _multipleOf(value, params) {
2949
+ return new $ZodCheckMultipleOf({
2950
+ check: "multiple_of",
2951
+ ...normalizeParams(params),
2952
+ value
2953
+ });
2954
+ }
2751
2955
  function _maxLength(maximum, params) {
2752
2956
  const ch = new $ZodCheckMaxLength({
2753
2957
  check: "max_length",
@@ -3459,6 +3663,37 @@ const stringProcessor = (schema, ctx, _json, _params)=>{
3459
3663
  ];
3460
3664
  }
3461
3665
  };
3666
+ const numberProcessor = (schema, ctx, _json, params)=>{
3667
+ const json = _json;
3668
+ const { minimum, maximum, multipleOf, exclusiveMaximum, exclusiveMinimum, isInt } = aggregateChecks(schema);
3669
+ json.type = isInt ? "integer" : "number";
3670
+ const exMin = "number" == typeof exclusiveMinimum && exclusiveMinimum >= (minimum ?? -1 / 0);
3671
+ const exMax = "number" == typeof exclusiveMaximum && exclusiveMaximum <= (maximum ?? 1 / 0);
3672
+ const legacy = "draft-04" === ctx.target || "openapi-3.0" === ctx.target;
3673
+ if (exMin) if (legacy) {
3674
+ json.minimum = exclusiveMinimum;
3675
+ json.exclusiveMinimum = true;
3676
+ } else json.exclusiveMinimum = exclusiveMinimum;
3677
+ else if ("number" == typeof minimum) json.minimum = minimum;
3678
+ if (exMax) if (legacy) {
3679
+ json.maximum = exclusiveMaximum;
3680
+ json.exclusiveMaximum = true;
3681
+ } else json.exclusiveMaximum = exclusiveMaximum;
3682
+ else if ("number" == typeof maximum) json.maximum = maximum;
3683
+ if (multipleOf) {
3684
+ const divisors = new Set();
3685
+ for (const divisor of multipleOf)if (Number.isFinite(divisor) && 0 !== divisor) divisors.add(Math.abs(divisor));
3686
+ else to_json_schema_handleUnrepresentable(schema, ctx, json, params, `A multipleOf divisor of ${divisor} cannot be represented in JSON Schema`);
3687
+ const [first, ...rest] = divisors;
3688
+ if (void 0 !== first) json.multipleOf = first;
3689
+ if (rest.length) json.allOf = [
3690
+ ...json.allOf ?? [],
3691
+ ...rest.map((m)=>({
3692
+ multipleOf: m
3693
+ }))
3694
+ ];
3695
+ }
3696
+ };
3462
3697
  const booleanProcessor = (_schema, _ctx, json, _params)=>{
3463
3698
  json.type = "boolean";
3464
3699
  };
@@ -4645,6 +4880,82 @@ const ZodJWT = /*@__PURE__*/ $constructor("ZodJWT", (inst, def)=>{
4645
4880
  $ZodJWT.init(inst, def);
4646
4881
  ZodStringFormat.init(inst, def);
4647
4882
  });
4883
+ const ZodNumber = /*@__PURE__*/ $constructor("ZodNumber", (inst, def)=>{
4884
+ $ZodNumber.init(inst, def);
4885
+ ZodType.init(inst, def);
4886
+ inst._zod.processJSONSchema = (ctx, json, params)=>numberProcessor(inst, ctx, json, params);
4887
+ inst.isFinite = true;
4888
+ }, /*@__PURE__*/ derived({
4889
+ minValue: (inst)=>{
4890
+ const { minimum, exclusiveMinimum } = aggregateChecks(inst);
4891
+ return Math.max(minimum ?? -1 / 0, exclusiveMinimum ?? -1 / 0);
4892
+ },
4893
+ maxValue: (inst)=>{
4894
+ const { maximum, exclusiveMaximum } = aggregateChecks(inst);
4895
+ return Math.min(maximum ?? 1 / 0, exclusiveMaximum ?? 1 / 0);
4896
+ },
4897
+ isInt: (inst)=>{
4898
+ const { isInt, multipleOf } = aggregateChecks(inst);
4899
+ return !!isInt || !!multipleOf?.some(Number.isSafeInteger);
4900
+ },
4901
+ format: (inst)=>aggregateChecks(inst).format ?? null
4902
+ }, {
4903
+ gt (value, params) {
4904
+ return this.check(_gt(value, params));
4905
+ },
4906
+ gte (value, params) {
4907
+ return this.check(_gte(value, params));
4908
+ },
4909
+ min (value, params) {
4910
+ return this.check(_gte(value, params));
4911
+ },
4912
+ lt (value, params) {
4913
+ return this.check(_lt(value, params));
4914
+ },
4915
+ lte (value, params) {
4916
+ return this.check(_lte(value, params));
4917
+ },
4918
+ max (value, params) {
4919
+ return this.check(_lte(value, params));
4920
+ },
4921
+ int (params) {
4922
+ return this.check(schemas_int(params));
4923
+ },
4924
+ safe (params) {
4925
+ return this.check(schemas_int(params));
4926
+ },
4927
+ positive (params) {
4928
+ return this.check(_gt(0, params));
4929
+ },
4930
+ nonnegative (params) {
4931
+ return this.check(_gte(0, params));
4932
+ },
4933
+ negative (params) {
4934
+ return this.check(_lt(0, params));
4935
+ },
4936
+ nonpositive (params) {
4937
+ return this.check(_lte(0, params));
4938
+ },
4939
+ multipleOf (value, params) {
4940
+ return this.check(_multipleOf(value, params));
4941
+ },
4942
+ step (value, params) {
4943
+ return this.check(_multipleOf(value, params));
4944
+ },
4945
+ finite () {
4946
+ return this;
4947
+ }
4948
+ }));
4949
+ function schemas_number(params) {
4950
+ return _number(ZodNumber, params);
4951
+ }
4952
+ const ZodNumberFormat = /*@__PURE__*/ $constructor("ZodNumberFormat", (inst, def)=>{
4953
+ $ZodNumberFormat.init(inst, def);
4954
+ ZodNumber.init(inst, def);
4955
+ });
4956
+ function schemas_int(params) {
4957
+ return _int(ZodNumberFormat, params);
4958
+ }
4648
4959
  const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def)=>{
4649
4960
  $ZodBoolean.init(inst, def);
4650
4961
  ZodType.init(inst, def);
@@ -4997,6 +5308,7 @@ function refine(fn, _params = {}) {
4997
5308
  function superRefine(fn, params) {
4998
5309
  return _superRefine(fn, params);
4999
5310
  }
5311
+ const rulesraw_namespaceObject = "Every numeric utility is in px, not 4px steps: p-16 is 16px, p-4 is 4px. Translate Tailwind habits (p-4 → p-16, gap-6 → gap-24, w-64 → w-256).\n\nBefore composing a screen, call get_guideline for the pattern (app shell, list page, table, drawer, form, dialog, filters, empty state, settings, auth). Wrong → right:\n\n- p-5, gap-3, p-10 → stay on the scale: 1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 40, 48, 64…\n- space-y-4, margins per child → <VStack gap={16}>, <HStack gap={8}>\n- Padded page wrapper → Page > PageHeader / PageToolbar / PageContent own the 24px gutter\n- p-* on Card, DrawerBody, DialogBody, PageToolbar, Table → none; they own their padding\n- className on Text, Heading, Field, Drawer/Dialog Header/Body/Footer → wrap in your own element\n- Raw <h1>, <p>, <label> → <Heading as=\"h2\" size=\"lg\">, <Text size=\"sm\">, Field\n- <Text> default (16px) for body → size=\"sm\" (md is only an alias of sm)\n- leading-none (0px), leading-5 (5px) → leading-sm or leading-20\n- weight=\"semibold\" → weight is light|regular|medium|bold (medium = 450)\n- bg-primary, text-muted, bg-card, bg-white, text-gray-500 → bg-bg-primary, text-text-secondary, bg-bg-surface-1\n- text-error, bg-red-500 → danger tokens; categorical color → Badge color\n- dark: variant, setting data-theme → semantic tokens, useTheme().setTheme()\n- rounded-lg, rounded, rounded-[15px] → rounded-8 (controls), rounded-12 (panels)\n- shadow-* on DS components, z-* on overlays → never; they own both\n- focus:ring → focus-visible:ring-3 ring-focus-primary outline-none\n- size, DIALOG_SIZES, DRAWER_SIZES → width: Dialog 400 confirm, 560 default, 960 picker; Drawer 640 default, 700 Timeline form\n- Dialog, useToast from the root → @wallarm-org/design-system/Dialog, /Toast\n- Field invalid → error on the control + FieldError\n- Hand-built side menu → RemoteShell config={NavConfig}\n- ConfirmDialog, FilterBar, Combobox → compose Dialog parts, PageToolbar + FilterDropdown, Select + useSelectSearch\n- data-slot in tests → data-testid\n";
5000
5312
  const propMetadataSchema = schemas_object({
5001
5313
  name: schemas_string(),
5002
5314
  type: schemas_string(),
@@ -5022,6 +5334,10 @@ const exampleMetadataSchema = schemas_object({
5022
5334
  storyTitle: schemas_string().optional(),
5023
5335
  tags: schemas_array(schemas_string()).optional()
5024
5336
  });
5337
+ const componentDocSchema = schemas_object({
5338
+ name: schemas_string(),
5339
+ markdown: schemas_string()
5340
+ });
5025
5341
  const componentMetadataSchema = schemas_object({
5026
5342
  name: schemas_string(),
5027
5343
  description: schemas_string().optional(),
@@ -5029,7 +5345,8 @@ const componentMetadataSchema = schemas_object({
5029
5345
  props: schemas_array(propMetadataSchema),
5030
5346
  variants: schemas_array(variantMetadataSchema),
5031
5347
  subComponents: schemas_array(subComponentMetadataSchema),
5032
- examples: schemas_array(exampleMetadataSchema)
5348
+ examples: schemas_array(exampleMetadataSchema),
5349
+ docs: schemas_array(componentDocSchema).optional()
5033
5350
  });
5034
5351
  const tokenMetadataSchema = schemas_object({
5035
5352
  name: schemas_string(),
@@ -5046,6 +5363,39 @@ const designSystemMetadataSchema = schemas_object({
5046
5363
  components: schemas_array(componentMetadataSchema),
5047
5364
  tokens: schemas_array(tokenCategorySchema)
5048
5365
  });
5366
+ const guidelineSectionSchema = schemas_object({
5367
+ id: schemas_string(),
5368
+ heading: schemas_string(),
5369
+ markdown: schemas_string()
5370
+ });
5371
+ const guidelineStorySchema = schemas_object({
5372
+ name: schemas_string(),
5373
+ code: schemas_string(),
5374
+ description: schemas_string().optional()
5375
+ });
5376
+ const guidelineKindSchema = schemas_enum([
5377
+ 'foundation',
5378
+ 'pattern'
5379
+ ]);
5380
+ const guidelineSchema = schemas_object({
5381
+ slug: schemas_string(),
5382
+ kind: guidelineKindSchema,
5383
+ title: schemas_string(),
5384
+ description: schemas_string(),
5385
+ components: schemas_array(schemas_string()),
5386
+ keywords: schemas_array(schemas_string()),
5387
+ order: schemas_number(),
5388
+ markdown: schemas_string(),
5389
+ sections: schemas_array(guidelineSectionSchema),
5390
+ stories: schemas_array(guidelineStorySchema).optional()
5391
+ });
5392
+ const guidelinesMetadataSchema = schemas_object({
5393
+ version: schemas_string(),
5394
+ rules: schemas_string(),
5395
+ guidelines: schemas_array(guidelineSchema)
5396
+ });
5397
+ const DS_METADATA = path_0.join('@wallarm-org', 'design-system', 'dist', 'metadata', 'components.json');
5398
+ const describeSource = (source, version)=>'installed' === source.kind ? `Source: installed @wallarm-org/design-system v${version}` : `Source: bundled snapshot v${version} — may differ from your installed version (install @wallarm-org/design-system or pass --metadata-path)`;
5049
5399
  function parseMetadataPathArg(args) {
5050
5400
  const idx = args.indexOf('--metadata-path');
5051
5401
  if (-1 !== idx && idx + 1 < args.length) return args[idx + 1];
@@ -5054,11 +5404,27 @@ function findMetadataInNodeModules(startDir) {
5054
5404
  let dir = startDir;
5055
5405
  const root = path_0.parse(dir).root;
5056
5406
  while(dir !== root){
5057
- const candidate = path_0.join(dir, 'node_modules', '@wallarm-org', 'design-system', 'dist', 'metadata', 'components.json');
5407
+ const candidate = path_0.join(dir, 'node_modules', DS_METADATA);
5058
5408
  if (fs.existsSync(candidate)) return candidate;
5059
5409
  dir = path_0.dirname(dir);
5060
5410
  }
5061
5411
  }
5412
+ function findMetadataInWorkspaces(cwd) {
5413
+ for (const group of [
5414
+ 'apps',
5415
+ 'packages'
5416
+ ]){
5417
+ const groupDir = path_0.join(cwd, group);
5418
+ if (fs.existsSync(groupDir)) for (const entry of fs.readdirSync(groupDir, {
5419
+ withFileTypes: true
5420
+ })){
5421
+ if (!entry.isDirectory()) continue;
5422
+ const candidate = path_0.join(groupDir, entry.name, 'node_modules', DS_METADATA);
5423
+ if (fs.existsSync(candidate)) return candidate;
5424
+ }
5425
+ }
5426
+ }
5427
+ const defaultBundledPath = ()=>path_0.join(path_0.dirname(fileURLToPath(import.meta.url)), 'metadata', 'components.json');
5062
5428
  async function resolveViaImportMeta() {
5063
5429
  try {
5064
5430
  const resolved = import.meta.resolve('@wallarm-org/design-system/metadata');
@@ -5066,19 +5432,46 @@ async function resolveViaImportMeta() {
5066
5432
  if (fs.existsSync(filePath)) return filePath;
5067
5433
  } catch {}
5068
5434
  }
5069
- async function loadMetadata(args = process.argv) {
5435
+ async function loadMetadata(args = process.argv, { cwd = process.cwd(), bundledPath = defaultBundledPath(), resolveInstalled = resolveViaImportMeta } = {}) {
5070
5436
  let metadataPath;
5437
+ let kind = 'installed';
5071
5438
  metadataPath = parseMetadataPathArg(args);
5072
5439
  if (metadataPath) metadataPath = path_0.resolve(metadataPath);
5073
- if (!metadataPath) metadataPath = await resolveViaImportMeta();
5074
- if (!metadataPath) metadataPath = findMetadataInNodeModules(process.cwd());
5440
+ if (!metadataPath) metadataPath = await resolveInstalled();
5441
+ if (!metadataPath) metadataPath = findMetadataInNodeModules(cwd) ?? findMetadataInWorkspaces(cwd);
5442
+ if (!metadataPath && fs.existsSync(bundledPath)) {
5443
+ metadataPath = bundledPath;
5444
+ kind = 'bundled';
5445
+ }
5075
5446
  if (!metadataPath) throw new Error("Could not find Wallarm Design System metadata.\n\nMake sure @wallarm-org/design-system is installed and built, or provide the path explicitly:\n wallarm-ds-mcp --metadata-path ./path/to/components.json\n");
5076
5447
  if (!fs.existsSync(metadataPath)) throw new Error(`Metadata file not found at: ${metadataPath}`);
5077
5448
  const raw = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
5078
5449
  const result = designSystemMetadataSchema.safeParse(raw);
5079
5450
  if (!result.success) throw new Error(`Invalid metadata format in ${metadataPath}:\n${result.error.message}\n\nThis might mean the design system version is incompatible with this MCP server version.`);
5080
- return result.data;
5451
+ return {
5452
+ metadata: result.data,
5453
+ metadataPath,
5454
+ source: {
5455
+ kind,
5456
+ path: metadataPath
5457
+ }
5458
+ };
5459
+ }
5460
+ function loadGuidelines(metadataPath, warn = (message)=>console.error('[wallarm-ds-mcp]', message)) {
5461
+ const guidelinesPath = path_0.join(path_0.dirname(metadataPath), 'guidelines.json');
5462
+ if (!fs.existsSync(guidelinesPath)) return;
5463
+ try {
5464
+ const raw = JSON.parse(fs.readFileSync(guidelinesPath, 'utf-8'));
5465
+ if (raw && Array.isArray(raw.guidelines)) raw.guidelines = raw.guidelines.filter((g)=>guidelineKindSchema.safeParse(g?.kind).success);
5466
+ const result = guidelinesMetadataSchema.safeParse(raw);
5467
+ if (!result.success) throw new Error(result.error.message);
5468
+ return result.data;
5469
+ } catch (err) {
5470
+ warn(`Ignoring ${guidelinesPath} (guidelines unavailable): ${err instanceof Error ? err.message : String(err)}`);
5471
+ return;
5472
+ }
5081
5473
  }
5474
+ const loadBundledGuidelines = (bundledPath = defaultBundledPath())=>loadGuidelines(bundledPath);
5082
5475
  function getComponentListContent(metadata) {
5083
5476
  const lines = [
5084
5477
  `# Wallarm Design System Components (v${metadata.version})`,
@@ -5110,105 +5503,467 @@ function getTokensContent(metadata) {
5110
5503
  }
5111
5504
  return lines.join('\n');
5112
5505
  }
5506
+ const RESPONSE_LIMIT = 40000;
5507
+ const splitUnion = (type)=>{
5508
+ const parts = [];
5509
+ let depth = 0;
5510
+ let quote = '';
5511
+ let start = 0;
5512
+ for(let i = 0; i < type.length; i++){
5513
+ const ch = type[i];
5514
+ if (quote) {
5515
+ if (ch === quote) quote = '';
5516
+ } else if ('"' === ch || "'" === ch || '`' === ch) quote = ch;
5517
+ else if ('{(<['.includes(ch ?? '')) depth++;
5518
+ else if ('})>]'.includes(ch ?? '') && !('>' === ch && '=' === type[i - 1])) depth--;
5519
+ else if ('|' === ch && 0 === depth) {
5520
+ parts.push(type.slice(start, i).trim());
5521
+ start = i + 1;
5522
+ }
5523
+ }
5524
+ parts.push(type.slice(start).trim());
5525
+ return parts.filter(Boolean);
5526
+ };
5527
+ const formatType = (type, maxMembers = 8, maxLength = 160)=>{
5528
+ const members = splitUnion(type).filter((m)=>'undefined' !== m);
5529
+ let out = members.length > maxMembers ? `${members.slice(0, maxMembers).join(' | ')} | …(+${members.length - maxMembers})` : members.join(' | ') || type;
5530
+ if (out.length > maxLength) out = `${out.slice(0, maxLength)}…`;
5531
+ return out;
5532
+ };
5533
+ const cell = (text)=>text.replace(/\s*\n\s*/g, ' ').replace(/\|/g, '\\|');
5534
+ const oneLine = (text, max = 140)=>{
5535
+ const flat = text.replace(/\s*\n\s*/g, ' ').trim();
5536
+ const end = flat.search(/[.!?](\s|$)/);
5537
+ const sentence = -1 === end ? flat : flat.slice(0, end + 1);
5538
+ return sentence.length > max ? `${sentence.slice(0, max)}…` : sentence;
5539
+ };
5540
+ const formatPropsTable = (props, options = {})=>{
5541
+ if (0 === props.length) return '';
5542
+ const { brief = false, maxMembers = brief ? 8 : 30 } = options;
5543
+ const hasRequired = props.some((p)=>p.required);
5544
+ const hasDefault = props.some((p)=>void 0 !== p.defaultValue);
5545
+ const hasDescription = props.some((p)=>p.description);
5546
+ const head = [
5547
+ 'Prop',
5548
+ 'Type'
5549
+ ];
5550
+ if (hasRequired) head.push('Required');
5551
+ if (hasDefault) head.push('Default');
5552
+ if (hasDescription) head.push("Description");
5553
+ const rows = props.map((p)=>{
5554
+ const row = [
5555
+ `\`${p.name}\``,
5556
+ `\`${cell(formatType(p.type, maxMembers, brief ? 160 : 400))}\``
5557
+ ];
5558
+ if (hasRequired) row.push(p.required ? 'yes' : '');
5559
+ if (hasDefault) row.push(void 0 !== p.defaultValue ? `\`${cell(p.defaultValue)}\`` : '');
5560
+ if (hasDescription) {
5561
+ const desc = p.description ?? '';
5562
+ row.push(cell(brief ? oneLine(desc) : desc));
5563
+ }
5564
+ return `| ${row.join(' | ')} |`;
5565
+ });
5566
+ return [
5567
+ `| ${head.join(' | ')} |`,
5568
+ `|${head.map(()=>'---').join('|')}|`,
5569
+ ...rows
5570
+ ].join('\n');
5571
+ };
5572
+ const paginate = (text, page, nextCall, limit = RESPONSE_LIMIT)=>{
5573
+ const budget = limit - 1000;
5574
+ if (text.length <= budget && page <= 1) return text;
5575
+ const blocks = text.split(/\n(?=#{1,3} )/);
5576
+ const pages = [];
5577
+ let current = '';
5578
+ const push = (piece)=>{
5579
+ if (current && current.length + piece.length + 1 > budget) {
5580
+ pages.push(current);
5581
+ current = '';
5582
+ }
5583
+ current = current ? `${current}\n${piece}` : piece;
5584
+ };
5585
+ for (const block of blocks)if (block.length <= budget) push(block);
5586
+ else for (const line of block.split('\n'))push(line.slice(0, budget));
5587
+ if (current) pages.push(current);
5588
+ const total = pages.length;
5589
+ const index = Math.min(Math.max(page, 1), total) - 1;
5590
+ const footer = index + 1 < total ? `\n\n---\nPage ${index + 1}/${total}. Call again with ${nextCall(index + 2)} for the next page.` : `\n\n---\nPage ${index + 1}/${total} (last).`;
5591
+ return `${pages[index]}${footer}`;
5592
+ };
5113
5593
  function getComponent(metadata, name) {
5114
5594
  return metadata.components.find((c)=>c.name.toLowerCase() === name.toLowerCase());
5115
5595
  }
5116
- function formatComponentDetails(component) {
5117
- const lines = [];
5118
- lines.push(`# ${component.name}`);
5119
- if (component.description) lines.push('', component.description);
5120
- lines.push('', `**Import:** \`import { ${component.name} } from '${component.importPath}'\``);
5121
- if (component.props.length > 0) {
5122
- lines.push('', '## Props', '');
5123
- lines.push("| Prop | Type | Required | Default | Description |");
5124
- lines.push('|------|------|----------|---------|-------------|');
5125
- for (const prop of component.props){
5126
- const req = prop.required ? 'Yes' : 'No';
5127
- const def = prop.defaultValue ?? '-';
5128
- const desc = prop.description ?? '-';
5129
- lines.push(`| \`${prop.name}\` | \`${prop.type}\` | ${req} | \`${def}\` | ${desc} |`);
5130
- }
5596
+ function findSubComponentOwner(metadata, name) {
5597
+ const n = name.toLowerCase();
5598
+ for (const component of metadata.components){
5599
+ const sub = component.subComponents.find((s)=>s.name.toLowerCase() === n);
5600
+ if (sub) return {
5601
+ component,
5602
+ sub
5603
+ };
5131
5604
  }
5132
- if (component.variants.length > 0) {
5133
- lines.push('', '## Variants', '');
5134
- for (const variant of component.variants){
5135
- const defaultNote = variant.defaultValue ? ` (default: \`${variant.defaultValue}\`)` : '';
5136
- const options = variant.options.length > 0 ? variant.options.map((o)=>`\`${o}\``).join(', ') : '_dynamically generated_';
5137
- lines.push(`- **${variant.name}**: ${options}${defaultNote}`);
5138
- }
5605
+ }
5606
+ const storyName = (example)=>example.storyTitle?.split('/').at(-1) ?? 'Story';
5607
+ function exampleEntries(component) {
5608
+ const counts = new Map();
5609
+ for (const e of component.examples)counts.set(e.name, (counts.get(e.name) ?? 0) + 1);
5610
+ return component.examples.map((example)=>({
5611
+ key: (counts.get(example.name) ?? 0) > 1 ? `${storyName(example)}/${example.name}` : example.name,
5612
+ example
5613
+ }));
5614
+ }
5615
+ function sharedProps(subs) {
5616
+ const counts = new Map();
5617
+ for (const sub of subs)for (const p of sub.props)if (!p.required && !p.description) counts.set(p.name, (counts.get(p.name) ?? 0) + 1);
5618
+ const min = Math.max(3, Math.ceil(subs.length / 2));
5619
+ return new Set([
5620
+ ...counts
5621
+ ].filter(([, n])=>n >= min).map(([name])=>name));
5622
+ }
5623
+ const withoutShared = (props, shared)=>props.filter((p)=>p.required || p.description || !shared.has(p.name));
5624
+ const sharedNote = (shared)=>shared.size > 0 ? `_Not repeated per sub-component — most also accept: ${[
5625
+ ...shared
5626
+ ].map((n)=>`\`${n}\``).join(', ')}._` : '';
5627
+ const call = (args)=>`\`{ ${Object.entries(args).map(([k, v])=>`${k}: ${'number' == typeof v ? v : `'${v}'`}`).join(', ')} }\``;
5628
+ const formatVariantList = (c)=>c.variants.map((v)=>{
5629
+ const def = v.defaultValue ? ` (default: \`${v.defaultValue}\`)` : '';
5630
+ const options = v.options.length > 0 ? v.options.map((o)=>`\`${o}\``).join(', ') : '_dynamically generated_';
5631
+ return `- **${v.name}**: ${options}${def}`;
5632
+ }).join('\n');
5633
+ function formatOverview(c) {
5634
+ const lines = [
5635
+ `# ${c.name}`
5636
+ ];
5637
+ if (c.description) lines.push('', c.description);
5638
+ const exported = 0 === c.props.length && c.subComponents.length > 0 ? `${c.subComponents.slice(0, 4).map((x)=>x.name).join(', ')}${c.subComponents.length > 4 ? ', …' : ''}` : c.name;
5639
+ lines.push('', `**Import:** \`import { ${exported} } from '${c.importPath}'\``);
5640
+ if (c.props.length > 0) lines.push('', '## Props', '', formatPropsTable(c.props, {
5641
+ brief: true
5642
+ }));
5643
+ if (c.variants.length > 0) lines.push('', '## Variants', '', formatVariantList(c));
5644
+ if (c.subComponents.length > 0) {
5645
+ lines.push('', `## Sub-components (${c.subComponents.length})`, '');
5646
+ if (c.subComponents.some((s)=>s.description)) for (const s of c.subComponents)lines.push(`- **${s.name}**${s.description ? ` — ${oneLine(s.description)}` : ''}`);
5647
+ else lines.push(c.subComponents.map((s)=>s.name).join(', '));
5648
+ lines.push('', `Props of one: \`get_component\` ${call({
5649
+ name: c.name,
5650
+ section: c.subComponents[0]?.name ?? ''
5651
+ })}; all: section \`'subcomponents'\`.`);
5139
5652
  }
5140
- if (component.subComponents.length > 0) {
5141
- lines.push('', '## Sub-components', '');
5142
- for (const sub of component.subComponents){
5143
- lines.push(`### ${sub.name}`);
5144
- if (sub.props.length > 0) {
5145
- lines.push('');
5146
- lines.push("| Prop | Type | Required | Default | Description |");
5147
- lines.push('|------|------|----------|---------|-------------|');
5148
- for (const prop of sub.props){
5149
- const req = prop.required ? 'Yes' : 'No';
5150
- const def = prop.defaultValue ?? '-';
5151
- const desc = prop.description ?? '-';
5152
- lines.push(`| \`${prop.name}\` | \`${prop.type}\` | ${req} | \`${def}\` | ${desc} |`);
5153
- }
5154
- }
5155
- lines.push('');
5156
- }
5653
+ const entries = exampleEntries(c);
5654
+ if (entries.length > 0) {
5655
+ lines.push('', `## Examples (${entries.length})`, '');
5656
+ for (const { key, example } of entries)lines.push(`- \`${key}\`${example.description ? ` — ${oneLine(example.description, 120)}` : ''}`);
5657
+ lines.push('', `Code: \`get_example\` ${call({
5658
+ component: c.name,
5659
+ example: entries[0]?.key ?? ''
5660
+ })}; all code: section \`'examples'\`.`);
5157
5661
  }
5158
- if (component.examples && component.examples.length > 0) {
5159
- lines.push('', '## Usage Examples', '');
5160
- for (const example of component.examples){
5161
- lines.push(`### ${example.name}`);
5162
- if (example.description) lines.push('', example.description);
5163
- lines.push('', '```tsx', example.code, '```', '');
5164
- }
5662
+ if (c.docs && c.docs.length > 0) {
5663
+ lines.push('', '## Usage docs', '');
5664
+ for (const d of c.docs)lines.push(`- \`docs/${d.name}\` (${Math.round(d.markdown.length / 1000)}k chars)`);
5665
+ lines.push('', `Read: \`get_component\` ${call({
5666
+ name: c.name,
5667
+ section: `docs/${c.docs[0]?.name}`
5668
+ })}.`);
5165
5669
  }
5166
5670
  return lines.join('\n');
5167
5671
  }
5168
- function getTokenCategory(metadata, name) {
5169
- return metadata.tokens.find((c)=>c.name.toLowerCase() === name.toLowerCase());
5672
+ function formatSubComponent(c, sub) {
5673
+ const shared = sharedProps(c.subComponents);
5674
+ const lines = [
5675
+ `# ${c.name} › ${sub.name}`
5676
+ ];
5677
+ if (sub.description) lines.push('', sub.description);
5678
+ lines.push('', `**Import:** \`import { ${sub.name} } from '${c.importPath}'\``);
5679
+ const props = withoutShared(sub.props, shared);
5680
+ lines.push('', props.length > 0 ? formatPropsTable(props) : '_No own props._');
5681
+ const note = sharedNote(shared);
5682
+ if (note) lines.push('', note);
5683
+ const usedIn = exampleEntries(c).filter(({ example })=>new RegExp(`<${sub.name}\\b`).test(example.code)).map(({ key })=>`\`${key}\``);
5684
+ if (usedIn.length > 0) lines.push('', `Used in examples (\`get_example\`): ${usedIn.slice(0, 10).join(', ')}${usedIn.length > 10 ? ', …' : ''}`);
5685
+ return lines.join('\n');
5170
5686
  }
5171
- function formatTokenCategory(category) {
5172
- const lines = [];
5173
- lines.push(`# ${category.name}`);
5174
- if (category.description) lines.push('', category.description);
5175
- lines.push('', `Total: ${category.tokens.length} tokens`, '');
5176
- lines.push('| Token | Value |');
5177
- lines.push('|-------|-------|');
5178
- for (const token of category.tokens)lines.push(`| \`${token.name}\` | \`${token.value}\` |`);
5687
+ function formatSubComponents(c) {
5688
+ const shared = sharedProps(c.subComponents);
5689
+ const lines = [
5690
+ `# ${c.name} — sub-components`
5691
+ ];
5692
+ const note = sharedNote(shared);
5693
+ if (note) lines.push('', note);
5694
+ for (const sub of c.subComponents){
5695
+ const props = withoutShared(sub.props, shared);
5696
+ lines.push('', `### ${sub.name}`);
5697
+ if (sub.description) lines.push('', sub.description);
5698
+ lines.push('', props.length > 0 ? formatPropsTable(props) : '_No own props._');
5699
+ }
5179
5700
  return lines.join('\n');
5180
5701
  }
5181
- function scoreComponent(component, query) {
5182
- const q = query.toLowerCase();
5183
- const name = component.name.toLowerCase();
5184
- if (name === q) return 100;
5185
- if (name.startsWith(q)) return 80;
5186
- if (name.includes(q)) return 60;
5187
- if (component.description?.toLowerCase().includes(q)) return 40;
5188
- const propsMatch = component.props.some((p)=>p.name.toLowerCase().includes(q) || p.type.toLowerCase().includes(q));
5189
- if (propsMatch) return 30;
5190
- const variantsMatch = component.variants.some((v)=>v.name.toLowerCase().includes(q) || v.options.some((o)=>o.toLowerCase().includes(q)));
5191
- if (variantsMatch) return 20;
5192
- const subMatch = component.subComponents.some((sc)=>sc.name.toLowerCase().includes(q));
5193
- if (subMatch) return 15;
5194
- return 0;
5702
+ const formatExample = (key, example)=>[
5703
+ `### ${key}`,
5704
+ ...example.description ? [
5705
+ '',
5706
+ example.description
5707
+ ] : [],
5708
+ '',
5709
+ '```tsx',
5710
+ example.code,
5711
+ '```'
5712
+ ].join('\n');
5713
+ function formatExamples(c) {
5714
+ return [
5715
+ `# ${c.name} — examples`,
5716
+ ...exampleEntries(c).map(({ key, example })=>`\n${formatExample(key, example)}`)
5717
+ ].join('\n');
5718
+ }
5719
+ function formatProps(c) {
5720
+ return [
5721
+ `# ${c.name} — props`,
5722
+ '',
5723
+ c.props.length > 0 ? formatPropsTable(c.props) : '_No props._',
5724
+ ...c.variants.length > 0 ? [
5725
+ '',
5726
+ '## Variants',
5727
+ '',
5728
+ formatVariantList(c)
5729
+ ] : []
5730
+ ].join('\n');
5731
+ }
5732
+ function componentSections(c) {
5733
+ return [
5734
+ 'props',
5735
+ ...c.subComponents.length > 0 ? [
5736
+ 'subcomponents'
5737
+ ] : [],
5738
+ ...c.examples.length > 0 ? [
5739
+ 'examples'
5740
+ ] : [],
5741
+ ...(c.docs ?? []).map((d)=>`docs/${d.name}`),
5742
+ ...c.subComponents.map((s)=>s.name)
5743
+ ];
5195
5744
  }
5196
- function searchComponents(metadata, query) {
5197
- return metadata.components.map((c)=>({
5745
+ function formatComponent(c, options = {}) {
5746
+ const { section, page = 1, related = '', source } = options;
5747
+ const s = section?.trim().toLowerCase();
5748
+ let body;
5749
+ if (s && 'overview' !== s) if ('props' === s || 'variants' === s) body = formatProps(c);
5750
+ else if ('subcomponents' === s || 'sub-components' === s) body = formatSubComponents(c);
5751
+ else if ('examples' === s) body = formatExamples(c);
5752
+ else if (s.startsWith('docs/')) {
5753
+ const doc = c.docs?.find((d)=>d.name.toLowerCase() === s.slice(5));
5754
+ if (doc) body = doc.markdown;
5755
+ } else {
5756
+ const sub = c.subComponents.find((x)=>x.name.toLowerCase() === s);
5757
+ if (sub) body = formatSubComponent(c, sub);
5758
+ }
5759
+ else body = formatOverview(c) + related;
5760
+ if (void 0 === body) return `Section "${section}" not found in ${c.name}.\n\nAvailable sections: ${componentSections(c).join(', ')}`;
5761
+ const text = paginate(body, page, (next)=>call({
5198
5762
  name: c.name,
5199
- description: c.description,
5200
- importPath: c.importPath,
5201
- score: scoreComponent(c, query)
5202
- })).filter((r)=>r.score > 0).sort((a, b)=>b.score - a.score);
5763
+ ...section && {
5764
+ section
5765
+ },
5766
+ page: next
5767
+ }));
5768
+ const footer = s ? '' : `\n\n---\nMore: \`get_component\` with section ${componentSections(c).slice(0, 4).map((x)=>`\`'${x}'\``).join(', ')}${c.subComponents.length > 0 ? ' or a sub-component name' : ''}.`;
5769
+ return `${text}${footer}${source ? `\n\n_${source}_` : ''}`;
5770
+ }
5771
+ const ROUTING = "Tools: `search_component` / `get_component` for components — `get_component` returns an overview, pass `section` for full props, sub-components or examples, and `get_example` for one example's code; `search_token` / `get_token_category` for design tokens; `list_guidelines` / `get_guideline` for foundations (spacing, color, typography, …) and patterns (screens and flows). Read the matching pattern with `get_guideline` before composing a screen.";
5772
+ function buildInstructions(rules, source) {
5773
+ return `${rules.trim()}\n\n${ROUTING}${source ? `\n\n${source}.` : ''}`;
5203
5774
  }
5204
- function scoreToken(token, category, query) {
5775
+ function listGuidelines(guidelines, kind) {
5776
+ return guidelines.guidelines.filter((g)=>!kind || g.kind === kind);
5777
+ }
5778
+ function formatGuidelineList(list) {
5779
+ if (0 === list.length) return 'No guidelines found.';
5780
+ return list.map((g)=>{
5781
+ const components = g.components.length > 0 ? `\n Components: ${g.components.join(', ')}` : '';
5782
+ return `- **${g.slug}** — ${g.title}: ${g.description}${components}`;
5783
+ }).join('\n');
5784
+ }
5785
+ const splitLink = (link)=>{
5786
+ const [path = '', anchor] = link.trim().split('#');
5787
+ return {
5788
+ path,
5789
+ anchor: anchor || void 0
5790
+ };
5791
+ };
5792
+ const normalizeSlug = (slug)=>splitLink(slug).path.toLowerCase().replace(/^(\.{1,2}\/)+/, '').replace(/\.md$/, '').replace(/^\/+|\/+$/g, '');
5793
+ const baseName = (slug)=>slug.slice(slug.lastIndexOf('/') + 1);
5794
+ function findGuideline(guidelines, slug) {
5795
+ const s = normalizeSlug(slug);
5796
+ return guidelines.guidelines.find((g)=>g.slug === s) ?? guidelines.guidelines.find((g)=>baseName(g.slug) === s);
5797
+ }
5798
+ const levenshtein = (a, b)=>{
5799
+ let prev = Array.from({
5800
+ length: b.length + 1
5801
+ }, (_, i)=>i);
5802
+ for(let i = 1; i <= a.length; i++){
5803
+ const row = [
5804
+ i
5805
+ ];
5806
+ for(let j = 1; j <= b.length; j++){
5807
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
5808
+ row[j] = Math.min((prev[j] ?? 0) + 1, (row[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
5809
+ }
5810
+ prev = row;
5811
+ }
5812
+ return prev[b.length] ?? 0;
5813
+ };
5814
+ function nearestSlugs(guidelines, slug, limit = 3) {
5815
+ const s = baseName(normalizeSlug(slug));
5816
+ return guidelines.guidelines.map((g)=>{
5817
+ const keywordHit = g.keywords.some((k)=>k.toLowerCase().includes(s) || s.includes(k.toLowerCase()));
5818
+ return {
5819
+ slug: g.slug,
5820
+ distance: levenshtein(s, baseName(g.slug)) - (keywordHit ? 100 : 0)
5821
+ };
5822
+ }).sort((a, b)=>a.distance - b.distance).slice(0, limit).map((r)=>r.slug);
5823
+ }
5824
+ const STORIES = 'stories';
5825
+ const storiesFooter = (g)=>g.stories?.length ? `\n\n---\nLive stories (Storybook \`Patterns/${g.title}\`): ${g.stories.map((s)=>s.name).join(', ')}. Full, runnable code: \`get_guideline({ slug: '${g.slug}', section: '${STORIES}' })\`.` : '';
5826
+ const storiesText = (g, page)=>{
5827
+ const body = (g.stories ?? []).map((s)=>[
5828
+ `## ${s.name}`,
5829
+ s.description ?? '',
5830
+ `\`\`\`tsx\n${s.code}\n\`\`\``
5831
+ ].filter(Boolean).join('\n\n')).join('\n\n');
5832
+ return paginate(`# ${g.title} — Stories\n\n${body}`, page, (next)=>`\`{ slug: '${g.slug}', section: '${STORIES}', page: ${next} }\``);
5833
+ };
5834
+ function getGuidelineText(guidelines, slug, section, page = 1) {
5835
+ const guideline = findGuideline(guidelines, slug);
5836
+ if (!guideline) {
5837
+ const hint = nearestSlugs(guidelines, slug);
5838
+ return `Guideline "${slug}" not found.${hint.length > 0 ? `\n\nDid you mean: ${hint.join(', ')}?` : ''}`;
5839
+ }
5840
+ section ??= splitLink(slug).anchor;
5841
+ const pageCall = (next)=>`\`{ slug: '${guideline.slug}'${section ? `, section: '${section}'` : ''}, page: ${next} }\``;
5842
+ if (!section) return `${paginate(guideline.markdown, page, pageCall)}${storiesFooter(guideline)}`;
5843
+ const id = section.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
5844
+ if (id === STORIES && guideline.stories?.length) return storiesText(guideline, page);
5845
+ const match = guideline.sections.find((s)=>s.id === id);
5846
+ if (!match) {
5847
+ const ids = [
5848
+ ...guideline.sections.map((s)=>s.id),
5849
+ ...guideline.stories?.length ? [
5850
+ STORIES
5851
+ ] : []
5852
+ ];
5853
+ return `Section "${section}" not found in ${guideline.slug}.\n\nAvailable sections: ${ids.join(', ')}`;
5854
+ }
5855
+ return paginate(`# ${guideline.title} — ${match.heading}\n\n${match.markdown}`, page, pageCall);
5856
+ }
5857
+ function relatedGuidelines(guidelines, componentName) {
5858
+ const name = componentName.toLowerCase();
5859
+ return guidelines.guidelines.filter((g)=>g.components.some((c)=>c.toLowerCase() === name));
5860
+ }
5861
+ function formatRelatedGuidelines(related) {
5862
+ if (0 === related.length) return '';
5863
+ return `\n\nRelated guidelines (read with \`get_guideline\`): ${related.map((g)=>`${g.slug} (${g.title})`).join(', ')}`;
5864
+ }
5865
+ const normalize = (s)=>s.toLowerCase().replace(/[^a-z0-9/]/g, '');
5866
+ function getExampleText(c, example, page = 1) {
5867
+ const entries = exampleEntries(c);
5868
+ if (0 === entries.length) return `${c.name} has no examples.`;
5869
+ const q = normalize(example);
5870
+ const byName = entries.filter((e)=>normalize(e.example.name) === q);
5871
+ const candidates = [
5872
+ entries.filter((e)=>normalize(e.key) === q),
5873
+ byName,
5874
+ entries.filter((e)=>normalize(e.key).includes(q))
5875
+ ].find((list)=>list.length > 0) ?? [];
5876
+ const keys = entries.map((e)=>`\`${e.key}\``).join(', ');
5877
+ if (candidates.length > 1) return `"${example}" matches several ${c.name} examples: ${candidates.map((e)=>`\`${e.key}\``).join(', ')}. Pass one of them.`;
5878
+ const fuzzy = candidates[0] || q.length < 4 ? void 0 : entries.map((e)=>{
5879
+ const key = normalize(e.key);
5880
+ return {
5881
+ e,
5882
+ d: Math.min(levenshtein(q, key), levenshtein(q, key.split('/').at(-1) ?? ''))
5883
+ };
5884
+ }).filter(({ d })=>d <= Math.max(2, Math.floor(q.length / 4))).sort((a, b)=>a.d - b.d)[0]?.e;
5885
+ const match = candidates[0] ?? fuzzy;
5886
+ if (!match) return `Example "${example}" not found in ${c.name}.\n\nAvailable examples: ${keys}`;
5887
+ const closest = fuzzy ? `Closest match for "${example}": \`${fuzzy.key}\`.\n\n` : '';
5888
+ return closest + paginate(formatExample(match.key, match.example), page, (next)=>call({
5889
+ component: c.name,
5890
+ example: match.key,
5891
+ page: next
5892
+ }));
5893
+ }
5894
+ const COLOR_FAMILY_UTILITIES = {
5895
+ text: [
5896
+ 'text'
5897
+ ],
5898
+ bg: [
5899
+ 'bg'
5900
+ ],
5901
+ border: [
5902
+ 'border'
5903
+ ],
5904
+ icon: [
5905
+ 'text'
5906
+ ],
5907
+ states: [
5908
+ 'bg'
5909
+ ],
5910
+ focus: [
5911
+ 'ring'
5912
+ ],
5913
+ syntax: [
5914
+ 'text'
5915
+ ]
5916
+ };
5917
+ function tokenUtilities(tokenName) {
5918
+ const name = tokenName.replace(/^--/, '');
5919
+ const [, namespace = '', key = ''] = name.match(/^(color|spacing|radius|shadow|blur|icon|text|leading|tracking|font-weight|font)-(.+)$/) ?? [];
5920
+ switch(namespace){
5921
+ case 'color':
5922
+ {
5923
+ const family = key.split('-')[0] ?? '';
5924
+ return (COLOR_FAMILY_UTILITIES[family] ?? [
5925
+ 'bg',
5926
+ 'text',
5927
+ 'border'
5928
+ ]).map((u)=>`${u}-${key}`);
5929
+ }
5930
+ case 'spacing':
5931
+ return [
5932
+ 'p',
5933
+ 'm',
5934
+ 'gap',
5935
+ 'w',
5936
+ 'h'
5937
+ ].map((u)=>`${u}-${key}`);
5938
+ case 'radius':
5939
+ return [
5940
+ `rounded-${key}`
5941
+ ];
5942
+ case 'text':
5943
+ return key.endsWith('--line-height') ? [] : [
5944
+ `text-${key}`
5945
+ ];
5946
+ case 'font-weight':
5947
+ case 'font':
5948
+ return [
5949
+ `font-${key}`
5950
+ ];
5951
+ case '':
5952
+ return [];
5953
+ default:
5954
+ return [
5955
+ `${namespace}-${key}`
5956
+ ];
5957
+ }
5958
+ }
5959
+ function scoreToken(token, utilities, category, query) {
5205
5960
  const q = query.toLowerCase();
5206
5961
  const name = token.name.toLowerCase();
5207
5962
  const val = token.value.toLowerCase();
5208
5963
  const cat = category.toLowerCase();
5209
- if (name === q || name === `--${q}`) return 100;
5964
+ if (name === q || name === `--${q}` || utilities.includes(q)) return 100;
5210
5965
  if (name.startsWith(q) || name.startsWith(`--${q}`)) return 80;
5211
- if (name.includes(q)) return 60;
5966
+ if (name.includes(q) || utilities.some((u)=>u.includes(q))) return 60;
5212
5967
  if (val === q) return 50;
5213
5968
  if (val.includes(q)) return 40;
5214
5969
  if (cat === q) return 30;
@@ -5218,29 +5973,344 @@ function scoreToken(token, category, query) {
5218
5973
  function searchTokens(metadata, query) {
5219
5974
  const results = [];
5220
5975
  for (const category of metadata.tokens)for (const token of category.tokens){
5221
- const score = scoreToken(token, category.name, query);
5976
+ const utilities = tokenUtilities(token.name);
5977
+ const score = scoreToken(token, utilities, category.name, query);
5222
5978
  if (score > 0) results.push({
5223
5979
  name: token.name,
5224
5980
  value: token.value,
5225
5981
  category: category.name,
5982
+ utilities,
5226
5983
  score
5227
5984
  });
5228
5985
  }
5229
5986
  return results.sort((a, b)=>b.score - a.score);
5230
5987
  }
5988
+ function getTokenCategory(metadata, name) {
5989
+ return metadata.tokens.find((c)=>c.name.toLowerCase() === name.toLowerCase());
5990
+ }
5991
+ function formatTokenCategory(category) {
5992
+ const lines = [];
5993
+ lines.push(`# ${category.name}`);
5994
+ if (category.description) lines.push('', category.description);
5995
+ lines.push('', `Total: ${category.tokens.length} tokens`, '');
5996
+ lines.push('| Token | Value | Utility |');
5997
+ lines.push('|-------|-------|---------|');
5998
+ for (const token of category.tokens){
5999
+ const utility = tokenUtilities(token.name).map((u)=>`\`${u}\``).join(' ');
6000
+ lines.push(`| \`${token.name}\` | \`${token.value}\` | ${utility} |`);
6001
+ }
6002
+ return lines.join('\n');
6003
+ }
6004
+ const ALIASES = {
6005
+ modal: [
6006
+ 'Dialog'
6007
+ ],
6008
+ popup: [
6009
+ 'Dialog',
6010
+ 'Popover'
6011
+ ],
6012
+ confirm: [
6013
+ 'Dialog'
6014
+ ],
6015
+ confirmation: [
6016
+ 'Dialog'
6017
+ ],
6018
+ sidebar: [
6019
+ 'RemoteShell',
6020
+ 'NavRail',
6021
+ 'AppShell',
6022
+ 'Drawer'
6023
+ ],
6024
+ 'side panel': [
6025
+ 'Drawer'
6026
+ ],
6027
+ sheet: [
6028
+ 'Drawer'
6029
+ ],
6030
+ flyout: [
6031
+ 'Drawer',
6032
+ 'Popover'
6033
+ ],
6034
+ navigation: [
6035
+ 'NavRail',
6036
+ 'RemoteShell',
6037
+ 'TopHeader',
6038
+ 'Breadcrumbs'
6039
+ ],
6040
+ nav: [
6041
+ 'NavRail',
6042
+ 'RemoteShell'
6043
+ ],
6044
+ header: [
6045
+ 'TopHeader',
6046
+ 'Heading'
6047
+ ],
6048
+ layout: [
6049
+ 'Page',
6050
+ 'AppShell',
6051
+ 'Stack',
6052
+ 'Flex'
6053
+ ],
6054
+ toast: [
6055
+ 'Toast'
6056
+ ],
6057
+ snackbar: [
6058
+ 'Toast'
6059
+ ],
6060
+ notification: [
6061
+ 'Toast',
6062
+ 'Alert',
6063
+ 'Banner',
6064
+ 'NotificationCenter'
6065
+ ],
6066
+ callout: [
6067
+ 'Alert',
6068
+ 'Banner'
6069
+ ],
6070
+ dropdown: [
6071
+ 'Select',
6072
+ 'DropdownMenu'
6073
+ ],
6074
+ combobox: [
6075
+ 'Select'
6076
+ ],
6077
+ autocomplete: [
6078
+ 'Select'
6079
+ ],
6080
+ 'context menu': [
6081
+ 'DropdownMenu'
6082
+ ],
6083
+ menu: [
6084
+ 'DropdownMenu'
6085
+ ],
6086
+ 'empty state': [
6087
+ 'EmptyState'
6088
+ ],
6089
+ 'no data': [
6090
+ 'EmptyState'
6091
+ ],
6092
+ 'date picker': [
6093
+ 'DateInput',
6094
+ 'DateRangeInput',
6095
+ 'Calendar'
6096
+ ],
6097
+ datepicker: [
6098
+ 'DateInput',
6099
+ 'DateRangeInput',
6100
+ 'Calendar'
6101
+ ],
6102
+ 'date range': [
6103
+ 'DateRangeInput'
6104
+ ],
6105
+ 'time picker': [
6106
+ 'TimeInput'
6107
+ ],
6108
+ grid: [
6109
+ 'Table'
6110
+ ],
6111
+ bulk: [
6112
+ 'Table'
6113
+ ],
6114
+ 'action bar': [
6115
+ 'Table'
6116
+ ],
6117
+ 'row selection': [
6118
+ 'Table'
6119
+ ],
6120
+ 'data table': [
6121
+ 'Table'
6122
+ ],
6123
+ datagrid: [
6124
+ 'Table'
6125
+ ],
6126
+ chart: [
6127
+ 'SimpleCharts'
6128
+ ],
6129
+ graph: [
6130
+ 'SimpleCharts'
6131
+ ],
6132
+ spinner: [
6133
+ 'Loader'
6134
+ ],
6135
+ toggle: [
6136
+ 'Switch',
6137
+ 'ToggleButton'
6138
+ ],
6139
+ upload: [
6140
+ 'FileUpload'
6141
+ ],
6142
+ dropzone: [
6143
+ 'FileUpload'
6144
+ ],
6145
+ hint: [
6146
+ 'Tooltip'
6147
+ ],
6148
+ chip: [
6149
+ 'Tag',
6150
+ 'Badge'
6151
+ ],
6152
+ pill: [
6153
+ 'Tag',
6154
+ 'Badge'
6155
+ ],
6156
+ label: [
6157
+ 'Tag',
6158
+ 'Badge'
6159
+ ],
6160
+ form: [
6161
+ 'Field',
6162
+ 'Input'
6163
+ ],
6164
+ 'form field': [
6165
+ 'Field'
6166
+ ],
6167
+ 'text field': [
6168
+ 'Input'
6169
+ ],
6170
+ multiline: [
6171
+ 'Textarea'
6172
+ ],
6173
+ search: [
6174
+ 'SearchInput',
6175
+ 'SearchModal'
6176
+ ],
6177
+ 'command palette': [
6178
+ 'SearchModal'
6179
+ ],
6180
+ 'code block': [
6181
+ 'CodeSnippet',
6182
+ 'CodeEditor'
6183
+ ],
6184
+ copy: [
6185
+ 'Copyable'
6186
+ ],
6187
+ wizard: [
6188
+ 'Stepper'
6189
+ ],
6190
+ collapse: [
6191
+ 'Accordion'
6192
+ ],
6193
+ collapsible: [
6194
+ 'Accordion'
6195
+ ],
6196
+ disclosure: [
6197
+ 'Accordion'
6198
+ ],
6199
+ paginator: [
6200
+ 'Pagination'
6201
+ ],
6202
+ otp: [
6203
+ 'InputOTP'
6204
+ ],
6205
+ divider: [
6206
+ 'Separator'
6207
+ ],
6208
+ 'keyboard shortcut': [
6209
+ 'Kbd'
6210
+ ],
6211
+ hotkey: [
6212
+ 'Kbd'
6213
+ ],
6214
+ filter: [
6215
+ 'FilterInput',
6216
+ 'FilterDropdown',
6217
+ 'FilterCascade'
6218
+ ],
6219
+ status: [
6220
+ 'Badge',
6221
+ 'Indicator'
6222
+ ]
6223
+ };
6224
+ const STOP_WORDS = new Set([
6225
+ 'a',
6226
+ 'an',
6227
+ 'the',
6228
+ 'for',
6229
+ 'with',
6230
+ 'of',
6231
+ 'to',
6232
+ 'in',
6233
+ 'and',
6234
+ 'or',
6235
+ 'component',
6236
+ 'ui'
6237
+ ]);
6238
+ const singular = (w)=>w.length > 4 && w.endsWith('ies') ? `${w.slice(0, -3)}y` : w.length > 3 && w.endsWith('s') && !w.endsWith('ss') ? w.slice(0, -1) : w;
6239
+ const words = (text)=>text.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/).filter((w)=>w && !STOP_WORDS.has(w)).map(singular);
6240
+ const coverage = (query, field)=>{
6241
+ if (0 === query.length || 0 === field.length) return 0;
6242
+ const hits = query.filter((q)=>field.some((f)=>f === q || q.length >= 4 && f.startsWith(q)));
6243
+ return hits.length / query.length;
6244
+ };
6245
+ function scoreComponent(component, query, aliasHits) {
6246
+ const q = query.trim().toLowerCase();
6247
+ const name = component.name.toLowerCase();
6248
+ if (name === q || words(component.name).join(' ') === words(query).join(' ')) return 100;
6249
+ if (aliasHits.has(component.name)) return 90;
6250
+ if (q && name.startsWith(q)) return 80;
6251
+ if (q.length >= 3 && name.includes(q)) return 60;
6252
+ const qw = words(query);
6253
+ const nameScore = 60 * coverage(qw, words(component.name));
6254
+ const descScore = 40 * coverage(qw, words(component.description ?? ''));
6255
+ const propScore = 30 * coverage(qw, component.props.flatMap((p)=>words(p.name)));
6256
+ const variantScore = 20 * coverage(qw, component.variants.flatMap((v)=>[
6257
+ ...words(v.name),
6258
+ ...v.options.flatMap(words)
6259
+ ]));
6260
+ const subScore = 15 * coverage(qw, component.subComponents.flatMap((s)=>words(s.name)));
6261
+ return Math.round(Math.max(nameScore, descScore, propScore, variantScore, subScore));
6262
+ }
6263
+ function aliasMatches(query) {
6264
+ const q = ` ${words(query).join(' ')} `;
6265
+ const hits = new Set();
6266
+ for (const [phrase, targets] of Object.entries(ALIASES))if (q.includes(` ${words(phrase).join(' ')} `)) for (const t of targets)hits.add(t);
6267
+ return hits;
6268
+ }
6269
+ function searchComponents(metadata, query) {
6270
+ const aliasHits = aliasMatches(query);
6271
+ return metadata.components.map((c)=>({
6272
+ name: c.name,
6273
+ description: c.description,
6274
+ importPath: c.importPath,
6275
+ score: scoreComponent(c, query, aliasHits)
6276
+ })).filter((r)=>r.score > 0).sort((a, b)=>b.score - a.score);
6277
+ }
5231
6278
  async function main() {
5232
6279
  const log = (...args)=>console.error('[wallarm-ds-mcp]', ...args);
5233
6280
  log('Loading metadata...');
5234
- const metadata = await loadMetadata();
5235
- log(`Loaded v${metadata.version}: ${metadata.components.length} components, ${metadata.tokens.length} token categories`);
6281
+ const { metadata, metadataPath, source } = await loadMetadata();
6282
+ const sourceLine = describeSource(source, metadata.version);
6283
+ let guidelines = loadGuidelines(metadataPath);
6284
+ let guidelinesSource = '';
6285
+ if (!guidelines && 'installed' === source.kind) {
6286
+ guidelines = loadBundledGuidelines();
6287
+ if (guidelines) guidelinesSource = `\n\n_Guidelines: bundled snapshot v${guidelines.version} — your installed @wallarm-org/design-system v${metadata.version} ships no guidelines.json._`;
6288
+ }
6289
+ log(sourceLine, `(${metadataPath})`);
6290
+ log(`Loaded v${metadata.version}: ${metadata.components.length} components, ${metadata.tokens.length} token categories, ${guidelines ? `${guidelines.guidelines.length} guidelines` : 'no guidelines.json (bundled rules)'}`);
6291
+ const noGuidelines = `Guidelines are not available: @wallarm-org/design-system v${metadata.version} ships no guidelines.json. Upgrade the design system to get foundations and patterns.`;
5236
6292
  const server = new McpServer({
5237
6293
  name: 'wallarm-design-system',
5238
6294
  version: metadata.version
6295
+ }, {
6296
+ instructions: `${buildInstructions(guidelines?.rules ?? rulesraw_namespaceObject, sourceLine)}${guidelinesSource}`
5239
6297
  });
6298
+ const notFound = (name)=>{
6299
+ const suggestion = searchComponents(metadata, name).slice(0, 3);
6300
+ const hint = suggestion.length > 0 ? `\n\nDid you mean: ${suggestion.map((s)=>s.name).join(', ')}?` : '';
6301
+ return {
6302
+ content: [
6303
+ {
6304
+ type: 'text',
6305
+ text: `Component "${name}" not found.${hint}`
6306
+ }
6307
+ ]
6308
+ };
6309
+ };
5240
6310
  server.registerTool('search_component', {
5241
- description: "Search for components by name, description, props, or variants. Returns matched components sorted by relevance.",
6311
+ description: 'Use when you need a UI element and do not know which DS component provides it (e.g. "modal", "date picker", "empty state", "sidebar"). Word-based search over names, descriptions, prop names, variants and common aliases; returns up to 10 components by relevance.',
5242
6312
  inputSchema: {
5243
- query: schemas_string().describe('Search query — component name, prop name, variant, or keyword')
6313
+ query: schemas_string().describe('What you need — a component name, a UI concept ("toast", "dropdown") or a prop name')
5244
6314
  }
5245
6315
  }, async ({ query })=>{
5246
6316
  const results = searchComponents(metadata, query);
@@ -5266,35 +6336,68 @@ async function main() {
5266
6336
  };
5267
6337
  });
5268
6338
  server.registerTool('get_component', {
5269
- description: 'Get full details for a specific component: props, variants, sub-components, and import path.',
6339
+ description: "Use when you know the component and need its API before writing code. Without `section` returns an overview: import, props, variants, sub-component names, example names, usage docs and related guidelines. Pass `section` for full detail: 'props', 'subcomponents', 'examples', 'docs/<Name>' or a sub-component name (e.g. 'TableColumnMenu'). Long sections are paginated — pass `page`.",
5270
6340
  inputSchema: {
5271
- name: schemas_string().describe('Component name (case-insensitive), e.g. "Button", "Alert", "Select"')
6341
+ name: schemas_string().describe('Component name (case-insensitive), e.g. "Button", "Alert", "Select"'),
6342
+ section: schemas_string().optional().describe("Omit for the overview; or 'props', 'subcomponents', 'examples', 'docs/<Name>', or a sub-component name"),
6343
+ page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
5272
6344
  }
5273
- }, async ({ name })=>{
6345
+ }, async ({ name, section, page })=>{
5274
6346
  const component = getComponent(metadata, name);
5275
6347
  if (!component) {
5276
- const suggestion = searchComponents(metadata, name).slice(0, 3);
5277
- const hint = suggestion.length > 0 ? `\n\nDid you mean: ${suggestion.map((s)=>s.name).join(', ')}?` : '';
6348
+ const owner = findSubComponentOwner(metadata, name);
6349
+ if (!owner) return notFound(name);
6350
+ const text = formatComponent(owner.component, {
6351
+ section: owner.sub.name,
6352
+ page,
6353
+ source: sourceLine
6354
+ });
5278
6355
  return {
5279
6356
  content: [
5280
6357
  {
5281
6358
  type: 'text',
5282
- text: `Component "${name}" not found.${hint}`
6359
+ text
5283
6360
  }
5284
6361
  ]
5285
6362
  };
5286
6363
  }
6364
+ const related = guidelines ? formatRelatedGuidelines(relatedGuidelines(guidelines, component.name)) : '';
6365
+ const text = formatComponent(component, {
6366
+ section,
6367
+ page,
6368
+ related,
6369
+ source: sourceLine
6370
+ });
6371
+ return {
6372
+ content: [
6373
+ {
6374
+ type: 'text',
6375
+ text
6376
+ }
6377
+ ]
6378
+ };
6379
+ });
6380
+ server.registerTool('get_example', {
6381
+ description: 'Use when you want working code for one usage of a component — copy the pattern instead of guessing composition. Example names come from the `get_component` overview; close names are matched, and a miss lists every example.',
6382
+ inputSchema: {
6383
+ component: schemas_string().describe('Component name, e.g. "Table", "SimpleCharts"'),
6384
+ example: schemas_string().describe('Example name from get_component, e.g. "WithPagination" or "AreaChart/Default"'),
6385
+ page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
6386
+ }
6387
+ }, async ({ component: name, example, page })=>{
6388
+ const component = getComponent(metadata, name) ?? findSubComponentOwner(metadata, name)?.component;
6389
+ if (!component) return notFound(name);
5287
6390
  return {
5288
6391
  content: [
5289
6392
  {
5290
6393
  type: 'text',
5291
- text: formatComponentDetails(component)
6394
+ text: getExampleText(component, example, page)
5292
6395
  }
5293
6396
  ]
5294
6397
  };
5295
6398
  });
5296
6399
  server.registerTool('search_token', {
5297
- description: 'Search for design tokens by name, value, or category. Returns matched tokens sorted by relevance.',
6400
+ description: 'Use when styling needs a colour, spacing, radius, shadow or type value — find the token and the Tailwind class to write (e.g. "text-primary", "surface", "spacing-8", "#ff6633", "bg-bg-surface-2"). Returns up to 20 tokens with value and utility classes.',
5298
6401
  inputSchema: {
5299
6402
  query: schemas_string().describe('Search query — token name (e.g. "spacing-8"), value (e.g. "#ff6633"), or category (e.g. "colors-primary")')
5300
6403
  }
@@ -5308,7 +6411,10 @@ async function main() {
5308
6411
  }
5309
6412
  ]
5310
6413
  };
5311
- const text = results.slice(0, 20).map((r)=>`- \`${r.name}\`: \`${r.value}\` _(${r.category})_`).join('\n');
6414
+ const text = results.slice(0, 20).map((r)=>{
6415
+ const utilities = r.utilities.length > 0 ? ` → ${r.utilities.map((u)=>`\`${u}\``).join(' ')}` : '';
6416
+ return `- \`${r.name}\`: \`${r.value}\`${utilities} _(${r.category})_`;
6417
+ }).join('\n');
5312
6418
  return {
5313
6419
  content: [
5314
6420
  {
@@ -5319,11 +6425,12 @@ async function main() {
5319
6425
  };
5320
6426
  });
5321
6427
  server.registerTool('get_token_category', {
5322
- description: 'Get all tokens in a specific category (e.g. "spacing", "colors-primary", "typography").',
6428
+ description: 'Use when you need the whole scale of one token category (e.g. all spacing steps, all semantic colours) with values and Tailwind classes. Large categories are paginated — pass `page`.',
5323
6429
  inputSchema: {
5324
- name: schemas_string().describe('Category name (case-insensitive), e.g. "spacing", "colors-primary", "shadow"')
6430
+ name: schemas_string().describe('Category name (case-insensitive), e.g. "spacing", "colors-primary", "shadow"'),
6431
+ page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
5325
6432
  }
5326
- }, async ({ name })=>{
6433
+ }, async ({ name, page = 1 })=>{
5327
6434
  const category = getTokenCategory(metadata, name);
5328
6435
  if (!category) {
5329
6436
  const categories = metadata.tokens.map((c)=>c.name);
@@ -5336,15 +6443,47 @@ async function main() {
5336
6443
  ]
5337
6444
  };
5338
6445
  }
6446
+ const text = paginate(formatTokenCategory(category), page, (next)=>`\`{ name: '${category.name}', page: ${next} }\``);
5339
6447
  return {
5340
6448
  content: [
5341
6449
  {
5342
6450
  type: 'text',
5343
- text: formatTokenCategory(category)
6451
+ text
5344
6452
  }
5345
6453
  ]
5346
6454
  };
5347
6455
  });
6456
+ server.registerTool('list_guidelines', {
6457
+ description: "Use when starting a screen or flow, or unsure about spacing, colour or typography rules — lists foundations and patterns with slug, title, description and components.",
6458
+ inputSchema: {
6459
+ kind: schemas_enum([
6460
+ 'foundation',
6461
+ 'pattern'
6462
+ ]).optional().describe('Only list foundations or only patterns; omit for both')
6463
+ }
6464
+ }, async ({ kind })=>({
6465
+ content: [
6466
+ {
6467
+ type: 'text',
6468
+ text: guidelines ? `${formatGuidelineList(listGuidelines(guidelines, kind))}${guidelinesSource}` : noGuidelines
6469
+ }
6470
+ ]
6471
+ }));
6472
+ server.registerTool('get_guideline', {
6473
+ description: "Use when composing a screen (read the matching pattern first) or applying a foundation — returns the guideline as markdown. Pass `section` to get one section only; `section: 'stories'` returns a pattern's full live-story code (paginated — pass `page`).",
6474
+ inputSchema: {
6475
+ slug: schemas_string().describe('Guideline slug from list_guidelines, e.g. "patterns/entity-table" or "foundations/spacing"'),
6476
+ section: schemas_string().optional().describe('Section id or heading, e.g. "rules", "examples", "states", "stories"'),
6477
+ page: schemas_number().int().min(1).optional().describe('Page of a paginated response, from 1')
6478
+ }
6479
+ }, async ({ slug, section, page })=>({
6480
+ content: [
6481
+ {
6482
+ type: 'text',
6483
+ text: guidelines ? `${getGuidelineText(guidelines, slug, section, page)}${guidelinesSource}` : noGuidelines
6484
+ }
6485
+ ]
6486
+ }));
5348
6487
  server.registerResource('component-list', 'ds://components', {
5349
6488
  description: 'List of all Wallarm Design System components with import paths',
5350
6489
  mimeType: 'text/markdown'
@@ -5390,7 +6529,9 @@ async function main() {
5390
6529
  contents: [
5391
6530
  {
5392
6531
  uri: uri.href,
5393
- text: formatComponentDetails(component),
6532
+ text: formatComponent(component, {
6533
+ source: sourceLine
6534
+ }),
5394
6535
  mimeType: 'text/markdown'
5395
6536
  }
5396
6537
  ]