@metacells/mcellui-mcp-server 0.1.3 → 0.1.4

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
@@ -121,7 +121,21 @@ var tools = [
121
121
  },
122
122
  {
123
123
  name: "nativeui_get_component",
124
- description: "Get the full source code for a specific component",
124
+ description: "Get concise documentation for a component: description, props, examples, and installation. Use this first to understand a component.",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ name: {
129
+ type: "string",
130
+ description: 'Component name (e.g., "button", "card", "input")'
131
+ }
132
+ },
133
+ required: ["name"]
134
+ }
135
+ },
136
+ {
137
+ name: "nativeui_get_component_source",
138
+ description: "Get the full source code for a component. Only use this when you need to see the complete implementation details.",
125
139
  inputSchema: {
126
140
  type: "object",
127
141
  properties: {
@@ -368,32 +382,46 @@ Available: ${registry.components.map((c) => c.name).join(", ")}`
368
382
  content: [
369
383
  {
370
384
  type: "text",
371
- text: `Error: Could not load source code for "${componentName}".`
385
+ text: `Error: Could not load documentation for "${componentName}".`
372
386
  }
373
387
  ]
374
388
  };
375
389
  }
376
- const output = `# ${component.name}
377
-
378
- ${component.description}
379
-
380
- **Category:** ${component.category}
381
- **Status:** ${component.status}
382
- ${component.dependencies?.length ? `**Dependencies:** ${component.dependencies.join(", ")}` : ""}
390
+ const docs = parseComponentDocs(code);
391
+ const output = formatComponentDocs(component, docs);
392
+ return { content: [{ type: "text", text: output }] };
393
+ }
394
+ case "nativeui_get_component_source": {
395
+ const componentName = args?.name;
396
+ const component = registry.components.find((c) => c.name === componentName);
397
+ if (!component) {
398
+ return {
399
+ content: [
400
+ {
401
+ type: "text",
402
+ text: `Component "${componentName}" not found.
383
403
 
384
- ## Source Code
404
+ Available: ${registry.components.map((c) => c.name).join(", ")}`
405
+ }
406
+ ]
407
+ };
408
+ }
409
+ const code = await loadComponentCode(component);
410
+ if (!code) {
411
+ return {
412
+ content: [
413
+ {
414
+ type: "text",
415
+ text: `Error: Could not load source code for "${componentName}".`
416
+ }
417
+ ]
418
+ };
419
+ }
420
+ const output = `# ${toPascalCase(component.name)} - Source Code
385
421
 
386
422
  \`\`\`tsx
387
423
  ${code}
388
424
  \`\`\`
389
-
390
- ## Usage
391
-
392
- \`\`\`tsx
393
- import { ${toPascalCase(component.name)} } from '@/components/ui/${component.name}';
394
-
395
- <${toPascalCase(component.name)} />
396
- \`\`\`
397
425
  `;
398
426
  return { content: [{ type: "text", text: output }] };
399
427
  }
@@ -415,7 +443,7 @@ import { ${toPascalCase(component.name)} } from '@/components/ui/${component.nam
415
443
  Run this command:
416
444
 
417
445
  \`\`\`bash
418
- npx nativeui add ${component.name}
446
+ npx @metacells/mcellui-cli add ${component.name}
419
447
  \`\`\`
420
448
  `;
421
449
  if (component.dependencies?.length) {
@@ -434,7 +462,7 @@ npx expo install ${component.dependencies.join(" ")}
434
462
  This component depends on: ${component.registryDependencies.join(", ")}
435
463
 
436
464
  \`\`\`bash
437
- npx nativeui add ${component.registryDependencies.join(" ")}
465
+ npx @metacells/mcellui-cli add ${component.registryDependencies.join(" ")}
438
466
  \`\`\`
439
467
  `;
440
468
  }
@@ -510,7 +538,7 @@ Based on: "${description}"
510
538
  **Add all suggested components:**
511
539
 
512
540
  \`\`\`bash
513
- npx nativeui add ${componentNames}
541
+ npx @metacells/mcellui-cli add ${componentNames}
514
542
  \`\`\``;
515
543
  return { content: [{ type: "text", text: output }] };
516
544
  }
@@ -527,7 +555,7 @@ npx nativeui add ${componentNames}
527
555
  The easiest way to create a new component:
528
556
 
529
557
  \`\`\`bash
530
- npx nativeui create ${kebabName} --template ${template}${withForwardRef ? " --forward-ref" : ""}
558
+ npx @metacells/mcellui-cli create ${kebabName} --template ${template}${withForwardRef ? " --forward-ref" : ""}
531
559
  \`\`\`
532
560
 
533
561
  This creates \`components/ui/${kebabName}.tsx\` with the ${template} template.
@@ -779,7 +807,7 @@ Checking project at: \`${projectPath}\`
779
807
  Run the CLI doctor command for a full diagnostic:
780
808
 
781
809
  \`\`\`bash
782
- npx nativeui doctor
810
+ npx @metacells/mcellui-cli doctor
783
811
  \`\`\`
784
812
 
785
813
  ## Common Issues & Fixes
@@ -787,7 +815,7 @@ npx nativeui doctor
787
815
  ### 1. "Project not initialized"
788
816
 
789
817
  \`\`\`bash
790
- npx nativeui init
818
+ npx @metacells/mcellui-cli init
791
819
  \`\`\`
792
820
 
793
821
  ### 2. Missing dependencies
@@ -900,6 +928,120 @@ Found ${results.length} component(s):
900
928
  };
901
929
  }
902
930
  }
931
+ function parseComponentDocs(code) {
932
+ const result = {
933
+ description: "",
934
+ example: "",
935
+ props: [],
936
+ exports: []
937
+ };
938
+ const docblockMatch = code.match(/\/\*\*\s*\n([\s\S]*?)\*\//);
939
+ if (docblockMatch) {
940
+ const docblock = docblockMatch[1];
941
+ const descLines = [];
942
+ const lines = docblock.split("\n");
943
+ for (const line of lines) {
944
+ const cleaned = line.replace(/^\s*\*\s?/, "").trim();
945
+ if (cleaned.startsWith("@example")) break;
946
+ if (cleaned) descLines.push(cleaned);
947
+ }
948
+ result.description = descLines.join(" ").trim();
949
+ const exampleMatch = docblock.match(/@example\s*\n\s*\*\s*```tsx?\s*\n([\s\S]*?)```/);
950
+ if (exampleMatch) {
951
+ result.example = exampleMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "")).join("\n").trim();
952
+ }
953
+ }
954
+ const propsInterfaces = code.matchAll(/export\s+(?:interface|type)\s+(\w+Props)\s*(?:=\s*)?{([^}]+)}/g);
955
+ for (const match of propsInterfaces) {
956
+ const interfaceName = match[1];
957
+ const propsBody = match[2];
958
+ const propPattern = /(?:\/\*\*\s*(.*?)\s*\*\/\s*)?([\w]+)(\?)?:\s*([^;]+);/g;
959
+ let propMatch;
960
+ while ((propMatch = propPattern.exec(propsBody)) !== null) {
961
+ const [, docComment, propName, optional, propType] = propMatch;
962
+ result.props.push({
963
+ name: propName,
964
+ type: propType.trim(),
965
+ required: !optional,
966
+ description: docComment?.replace(/\s+/g, " ").trim() || ""
967
+ });
968
+ }
969
+ }
970
+ const exportMatches = code.matchAll(/export\s+(?:function|const)\s+(\w+)/g);
971
+ for (const match of exportMatches) {
972
+ result.exports.push(match[1]);
973
+ }
974
+ const typeExports = code.matchAll(/export\s+(?:type|interface)\s+(\w+)/g);
975
+ for (const match of typeExports) {
976
+ result.exports.push(match[1]);
977
+ }
978
+ return result;
979
+ }
980
+ function formatComponentDocs(component, docs) {
981
+ const pascalName = toPascalCase(component.name);
982
+ let output = `# ${pascalName}
983
+
984
+ `;
985
+ output += `${docs.description || component.description}
986
+
987
+ `;
988
+ output += `**Category:** ${component.category} \xB7 **Status:** ${component.status}
989
+
990
+ `;
991
+ output += `## Installation
992
+
993
+ `;
994
+ output += `\`\`\`bash
995
+ npx @metacells/mcellui-cli add ${component.name}
996
+ \`\`\`
997
+
998
+ `;
999
+ if (component.dependencies?.length) {
1000
+ output += `**Peer Dependencies:** ${component.dependencies.join(", ")}
1001
+
1002
+ `;
1003
+ }
1004
+ if (component.registryDependencies?.length) {
1005
+ output += `**Required Components:** ${component.registryDependencies.join(", ")}
1006
+
1007
+ `;
1008
+ }
1009
+ if (docs.example) {
1010
+ output += `## Example
1011
+
1012
+ `;
1013
+ output += `\`\`\`tsx
1014
+ ${docs.example}
1015
+ \`\`\`
1016
+
1017
+ `;
1018
+ }
1019
+ if (docs.props.length > 0) {
1020
+ output += `## Props
1021
+
1022
+ `;
1023
+ output += `| Prop | Type | Required | Description |
1024
+ `;
1025
+ output += `|------|------|----------|-------------|
1026
+ `;
1027
+ for (const prop of docs.props) {
1028
+ const typeStr = prop.type.length > 40 ? prop.type.slice(0, 37) + "..." : prop.type;
1029
+ output += `| \`${prop.name}\` | \`${typeStr}\` | ${prop.required ? "\u2713" : ""} | ${prop.description} |
1030
+ `;
1031
+ }
1032
+ output += "\n";
1033
+ }
1034
+ if (docs.exports.length > 0) {
1035
+ output += `## Exports
1036
+
1037
+ `;
1038
+ output += `\`\`\`tsx
1039
+ import { ${docs.exports.join(", ")} } from '@/components/ui/${component.name}';
1040
+ \`\`\`
1041
+ `;
1042
+ }
1043
+ return output;
1044
+ }
903
1045
  function toPascalCase(str) {
904
1046
  return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
905
1047
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metacells/mcellui-mcp-server",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "MCP server for mcellui - AI assistant integration for React Native development",
5
5
  "author": "metacells",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@ export function Avatar({
42
42
  size = 'md',
43
43
  style,
44
44
  }: AvatarProps) {
45
- const { colors, components, platformShadow } = useTheme();
45
+ const { colors, components, componentRadius, platformShadow } = useTheme();
46
46
  const tokens = components.avatar[size];
47
47
  const [imageError, setImageError] = useState(false);
48
48
  const showFallback = !source || imageError;
@@ -54,7 +54,7 @@ export function Avatar({
54
54
  {
55
55
  width: tokens.size,
56
56
  height: tokens.size,
57
- borderRadius: tokens.borderRadius,
57
+ borderRadius: componentRadius.avatar,
58
58
  backgroundColor: colors.backgroundMuted,
59
59
  },
60
60
  platformShadow('sm'),
@@ -68,7 +68,7 @@ export function Avatar({
68
68
  style={[
69
69
  styles.fallback,
70
70
  {
71
- borderRadius: tokens.borderRadius,
71
+ borderRadius: componentRadius.avatar,
72
72
  backgroundColor: colors.primary,
73
73
  },
74
74
  ]}
@@ -89,7 +89,7 @@ export function Avatar({
89
89
  ) : (
90
90
  <Image
91
91
  source={source}
92
- style={[styles.image, { borderRadius: tokens.borderRadius }]}
92
+ style={[styles.image, { borderRadius: componentRadius.avatar }]}
93
93
  onError={() => setImageError(true)}
94
94
  />
95
95
  )}
@@ -40,7 +40,7 @@ export function Badge({
40
40
  style,
41
41
  textStyle,
42
42
  }: BadgeProps) {
43
- const { colors, components, platformShadow } = useTheme();
43
+ const { colors, components, componentRadius, platformShadow } = useTheme();
44
44
  const tokens = components.badge[size];
45
45
  const variantStyles = getVariantStyles(variant, colors);
46
46
 
@@ -54,7 +54,7 @@ export function Badge({
54
54
  {
55
55
  paddingHorizontal: tokens.paddingHorizontal,
56
56
  paddingVertical: tokens.paddingVertical,
57
- borderRadius: tokens.borderRadius,
57
+ borderRadius: componentRadius.badge,
58
58
  },
59
59
  variantStyles.container,
60
60
  shadowStyle,
@@ -77,11 +77,16 @@ export const Button = forwardRef(function Button(
77
77
  }: ButtonProps,
78
78
  ref: React.ForwardedRef<View>
79
79
  ) {
80
- const { colors, components, platformShadow, springs } = useTheme();
80
+ const { colors, components, componentRadius, platformShadow, springs } = useTheme();
81
81
  const tokens = components.button[size];
82
82
  const isDisabled = disabled || loading;
83
83
  const scale = useSharedValue(1);
84
84
 
85
+ // Get size-specific border radius from dynamic componentRadius
86
+ const borderRadius = size === 'sm' ? componentRadius.buttonSm
87
+ : size === 'lg' ? componentRadius.buttonLg
88
+ : componentRadius.button;
89
+
85
90
  const handlePressIn = useCallback(
86
91
  (e: any) => {
87
92
  scale.value = withSpring(BUTTON_CONSTANTS.pressScale, springs.snappy);
@@ -128,7 +133,7 @@ export const Button = forwardRef(function Button(
128
133
  minHeight: tokens.height,
129
134
  paddingHorizontal: tokens.paddingHorizontal,
130
135
  paddingVertical: tokens.paddingVertical,
131
- borderRadius: tokens.borderRadius,
136
+ borderRadius,
132
137
  gap: tokens.gap,
133
138
  },
134
139
  variantStyles.container,
@@ -63,7 +63,7 @@ export interface CardProps {
63
63
  }
64
64
 
65
65
  export function Card({ children, onPress, disabled, style }: CardProps) {
66
- const { colors, components, platformShadow, springs } = useTheme();
66
+ const { colors, components, componentRadius, platformShadow, springs } = useTheme();
67
67
  const tokens = components.card;
68
68
  const scale = useSharedValue(1);
69
69
  const isInteractive = !!onPress && !disabled;
@@ -97,7 +97,7 @@ export function Card({ children, onPress, disabled, style }: CardProps) {
97
97
  const cardStyle = [
98
98
  styles.card,
99
99
  {
100
- borderRadius: tokens.borderRadius,
100
+ borderRadius: componentRadius.card,
101
101
  borderWidth: tokens.borderWidth,
102
102
  backgroundColor: colors.card,
103
103
  borderColor: colors.border,
@@ -151,10 +151,10 @@ export function CardImage({
151
151
  overlay = false,
152
152
  style,
153
153
  }: CardImageProps) {
154
- const { colors, components } = useTheme();
154
+ const { colors, components, componentRadius } = useTheme();
155
155
  const tokens = components.card;
156
156
  // Calculate inner border radius (outer - border width)
157
- const innerRadius = tokens.borderRadius - tokens.borderWidth;
157
+ const innerRadius = componentRadius.card - tokens.borderWidth;
158
158
 
159
159
  return (
160
160
  <View style={[styles.imageContainer, { borderTopLeftRadius: innerRadius, borderTopRightRadius: innerRadius }]}>
@@ -341,7 +341,7 @@ export function ImageCard({
341
341
  textPosition = 'bottom',
342
342
  style,
343
343
  }: ImageCardProps) {
344
- const { colors, components, platformShadow, springs, spacing } = useTheme();
344
+ const { colors, components, componentRadius, platformShadow, springs, spacing } = useTheme();
345
345
  const tokens = components.card;
346
346
  const scale = useSharedValue(1);
347
347
  const isInteractive = !!onPress;
@@ -415,7 +415,7 @@ export function ImageCard({
415
415
 
416
416
  const cardStyle = [
417
417
  styles.imageCard,
418
- { borderRadius: tokens.borderRadius + 4 },
418
+ { borderRadius: componentRadius.card + 4 },
419
419
  platformShadow('md'),
420
420
  style,
421
421
  ];
@@ -471,7 +471,7 @@ export function MediaCard({
471
471
  onPress,
472
472
  style,
473
473
  }: MediaCardProps) {
474
- const { colors, components, platformShadow, springs, spacing } = useTheme();
474
+ const { colors, components, componentRadius, platformShadow, springs, spacing } = useTheme();
475
475
  const tokens = components.card;
476
476
  const scale = useSharedValue(1);
477
477
  const isInteractive = !!onPress;
@@ -505,7 +505,7 @@ export function MediaCard({
505
505
  const content = (
506
506
  <>
507
507
  {/* Image container */}
508
- <View style={[styles.mediaCardImageContainer, { borderRadius: tokens.borderRadius }, platformShadow('sm')]}>
508
+ <View style={[styles.mediaCardImageContainer, { borderRadius: componentRadius.card }, platformShadow('sm')]}>
509
509
  <Image
510
510
  source={source}
511
511
  style={[
@@ -70,7 +70,7 @@ export function Checkbox({
70
70
  size = 'md',
71
71
  style,
72
72
  }: CheckboxProps) {
73
- const { colors, components, platformShadow, springs } = useTheme();
73
+ const { colors, components, componentRadius, platformShadow, springs } = useTheme();
74
74
  const tokens = components.checkbox[size];
75
75
  const progress = useSharedValue(checked || indeterminate ? 1 : 0);
76
76
  const scale = useSharedValue(1);
@@ -170,7 +170,7 @@ export function Checkbox({
170
170
  {
171
171
  width: tokens.size,
172
172
  height: tokens.size,
173
- borderRadius: tokens.borderRadius,
173
+ borderRadius: componentRadius.checkbox,
174
174
  borderWidth: tokens.borderWidth,
175
175
  },
176
176
  boxAnimatedStyle,
@@ -75,7 +75,7 @@ export const Input = forwardRef<TextInput, InputProps>(
75
75
  },
76
76
  ref
77
77
  ) => {
78
- const { colors, components, timing, spacing } = useTheme();
78
+ const { colors, components, componentRadius, timing, spacing } = useTheme();
79
79
  const tokens = components.input[size];
80
80
  const focusProgress = useSharedValue(0);
81
81
 
@@ -157,7 +157,7 @@ export const Input = forwardRef<TextInput, InputProps>(
157
157
  minHeight: tokens.height,
158
158
  paddingHorizontal: tokens.paddingHorizontal,
159
159
  paddingVertical: tokens.paddingVertical,
160
- borderRadius: tokens.borderRadius,
160
+ borderRadius: componentRadius.input,
161
161
  fontSize: tokens.fontSize,
162
162
  backgroundColor: isDisabled ? colors.backgroundMuted : colors.background,
163
163
  color: isDisabled ? colors.foregroundMuted : colors.foreground,
@@ -65,7 +65,7 @@ export function Switch({
65
65
  size = 'md',
66
66
  style,
67
67
  }: SwitchProps) {
68
- const { colors, components, platformShadow, springs } = useTheme();
68
+ const { colors, components, componentRadius, platformShadow, springs } = useTheme();
69
69
  const tokens = components.switch[size];
70
70
  const progress = useSharedValue(checked ? 1 : 0);
71
71
  const scale = useSharedValue(1);
@@ -193,7 +193,7 @@ export function Switch({
193
193
  {
194
194
  width: tokens.trackWidth,
195
195
  height: tokens.trackHeight,
196
- borderRadius: tokens.borderRadius,
196
+ borderRadius: componentRadius.switch,
197
197
  paddingHorizontal: tokens.thumbOffset,
198
198
  },
199
199
  trackAnimatedStyle,
@@ -206,7 +206,7 @@ export function Switch({
206
206
  {
207
207
  width: tokens.thumbSize,
208
208
  height: tokens.thumbSize,
209
- borderRadius: tokens.borderRadius,
209
+ borderRadius: componentRadius.switch,
210
210
  backgroundColor: colors.background,
211
211
  },
212
212
  platformShadow('sm'),