chrometools-mcp 2.4.0 → 2.5.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/index.js CHANGED
@@ -63,7 +63,8 @@ import {
63
63
  listFigmaPages,
64
64
  normalizeFigmaNodeId,
65
65
  parseFigmaUrl,
66
- searchFigmaFrames
66
+ searchFigmaFrames,
67
+ simplifyNode
67
68
  } from './figma-tools.js';
68
69
 
69
70
  // Debug mode - only use stderr for actual errors, not debug info
@@ -908,6 +909,240 @@ async function executeToolInternal(name, args) {
908
909
  };
909
910
  }
910
911
 
912
+ if (name === "selectOption") {
913
+ const validatedArgs = schemas.SelectOptionSchema.parse(args);
914
+ const page = await getLastOpenPage();
915
+
916
+ // Select option with priority: value > text > index
917
+ const result = await page.evaluate((selector, value, text, index) => {
918
+ const selectElement = document.querySelector(selector);
919
+ if (!selectElement || selectElement.tagName !== 'SELECT') {
920
+ return { success: false, error: `Select element not found: ${selector}` };
921
+ }
922
+
923
+ let selectedOption = null;
924
+
925
+ // Priority 1: Select by value
926
+ if (value !== undefined && value !== null) {
927
+ const option = Array.from(selectElement.options).find(opt => opt.value === value);
928
+ if (option) {
929
+ selectElement.value = value;
930
+ selectedOption = option;
931
+ }
932
+ }
933
+
934
+ // Priority 2: Select by text
935
+ if (!selectedOption && text !== undefined && text !== null) {
936
+ const option = Array.from(selectElement.options).find(opt => opt.textContent.trim() === text);
937
+ if (option) {
938
+ selectElement.value = option.value;
939
+ selectedOption = option;
940
+ }
941
+ }
942
+
943
+ // Priority 3: Select by index
944
+ if (!selectedOption && index !== undefined && index !== null) {
945
+ if (index >= 0 && index < selectElement.options.length) {
946
+ selectElement.selectedIndex = index;
947
+ selectedOption = selectElement.options[index];
948
+ }
949
+ }
950
+
951
+ if (!selectedOption) {
952
+ return { success: false, error: 'No matching option found' };
953
+ }
954
+
955
+ // Trigger events for React and other frameworks
956
+ selectElement.dispatchEvent(new Event('input', { bubbles: true }));
957
+ selectElement.dispatchEvent(new Event('change', { bubbles: true }));
958
+
959
+ return {
960
+ success: true,
961
+ selectedValue: selectElement.value,
962
+ selectedText: selectedOption.textContent.trim(),
963
+ selectedIndex: selectElement.selectedIndex
964
+ };
965
+ }, validatedArgs.selector, validatedArgs.value, validatedArgs.text, validatedArgs.index);
966
+
967
+ if (!result.success) {
968
+ throw new Error(result.error);
969
+ }
970
+
971
+ return {
972
+ content: [{
973
+ type: "text",
974
+ text: `Selected option in ${validatedArgs.selector}:\n` +
975
+ ` Value: ${result.selectedValue}\n` +
976
+ ` Text: ${result.selectedText}\n` +
977
+ ` Index: ${result.selectedIndex}`
978
+ }],
979
+ };
980
+ }
981
+
982
+ if (name === "drag") {
983
+ const validatedArgs = schemas.DragSchema.parse(args);
984
+ const page = await getLastOpenPage();
985
+
986
+ const distance = validatedArgs.distance || 100;
987
+ const duration = validatedArgs.duration || 500;
988
+
989
+ // Calculate drag deltas based on direction
990
+ let deltaX = 0;
991
+ let deltaY = 0;
992
+
993
+ switch (validatedArgs.direction) {
994
+ case 'up':
995
+ deltaY = -distance;
996
+ break;
997
+ case 'down':
998
+ deltaY = distance;
999
+ break;
1000
+ case 'left':
1001
+ deltaX = -distance;
1002
+ break;
1003
+ case 'right':
1004
+ deltaX = distance;
1005
+ break;
1006
+ case 'up-left':
1007
+ deltaY = -distance;
1008
+ deltaX = -distance;
1009
+ break;
1010
+ case 'up-right':
1011
+ deltaY = -distance;
1012
+ deltaX = distance;
1013
+ break;
1014
+ case 'down-left':
1015
+ deltaY = distance;
1016
+ deltaX = -distance;
1017
+ break;
1018
+ case 'down-right':
1019
+ deltaY = distance;
1020
+ deltaX = distance;
1021
+ break;
1022
+ }
1023
+
1024
+ // Get element center position for drag start
1025
+ const elementInfo = await page.evaluate((selector) => {
1026
+ const element = document.querySelector(selector);
1027
+ if (!element) {
1028
+ return { success: false, error: `Element not found: ${selector}` };
1029
+ }
1030
+
1031
+ const rect = element.getBoundingClientRect();
1032
+ return {
1033
+ success: true,
1034
+ centerX: rect.left + rect.width / 2,
1035
+ centerY: rect.top + rect.height / 2,
1036
+ width: rect.width,
1037
+ height: rect.height
1038
+ };
1039
+ }, validatedArgs.selector);
1040
+
1041
+ if (!elementInfo.success) {
1042
+ throw new Error(elementInfo.error);
1043
+ }
1044
+
1045
+ // Perform drag: mousedown → mousemove → mouseup
1046
+ const startX = elementInfo.centerX;
1047
+ const startY = elementInfo.centerY;
1048
+ const endX = startX + deltaX;
1049
+ const endY = startY + deltaY;
1050
+
1051
+ // Move to start position
1052
+ await page.mouse.move(startX, startY);
1053
+
1054
+ // Press mouse button (start drag)
1055
+ await page.mouse.down();
1056
+
1057
+ // Wait a bit to ensure drag is registered
1058
+ await new Promise(resolve => setTimeout(resolve, 50));
1059
+
1060
+ // Move mouse to end position (drag)
1061
+ const steps = Math.max(10, Math.floor(duration / 20)); // Smooth movement
1062
+ await page.mouse.move(endX, endY, { steps });
1063
+
1064
+ // Wait for duration
1065
+ await new Promise(resolve => setTimeout(resolve, Math.max(0, duration - steps * 20)));
1066
+
1067
+ // Release mouse button (end drag)
1068
+ await page.mouse.up();
1069
+
1070
+ return {
1071
+ content: [{
1072
+ type: "text",
1073
+ text: `Dragged ${validatedArgs.selector} ${validatedArgs.direction} by ${distance}px:\n` +
1074
+ ` Start position: (${Math.round(startX)}, ${Math.round(startY)})\n` +
1075
+ ` End position: (${Math.round(endX)}, ${Math.round(endY)})\n` +
1076
+ ` Delta: (${deltaX}px, ${deltaY}px)\n` +
1077
+ ` Duration: ${duration}ms`
1078
+ }],
1079
+ };
1080
+ }
1081
+
1082
+ if (name === "scrollHorizontal") {
1083
+ const validatedArgs = schemas.ScrollHorizontalSchema.parse(args);
1084
+ const page = await getLastOpenPage();
1085
+
1086
+ const behavior = validatedArgs.behavior || 'auto';
1087
+
1088
+ const result = await page.evaluate((selector, direction, amount, behavior) => {
1089
+ const element = document.querySelector(selector);
1090
+ if (!element) {
1091
+ return { success: false, error: `Element not found: ${selector}` };
1092
+ }
1093
+
1094
+ // Determine scroll amount
1095
+ let scrollAmount;
1096
+ if (amount === 'full') {
1097
+ // Scroll to the end
1098
+ scrollAmount = direction === 'right'
1099
+ ? element.scrollWidth - element.clientWidth
1100
+ : 0;
1101
+ } else {
1102
+ // Relative scroll
1103
+ scrollAmount = direction === 'right'
1104
+ ? element.scrollLeft + amount
1105
+ : element.scrollLeft - amount;
1106
+ }
1107
+
1108
+ // Perform scroll
1109
+ element.scrollTo({
1110
+ left: scrollAmount,
1111
+ behavior: behavior
1112
+ });
1113
+
1114
+ // Wait a bit for scroll to complete (if smooth)
1115
+ return new Promise(resolve => {
1116
+ setTimeout(() => {
1117
+ resolve({
1118
+ success: true,
1119
+ scrollLeft: element.scrollLeft,
1120
+ scrollWidth: element.scrollWidth,
1121
+ clientWidth: element.clientWidth,
1122
+ canScrollRight: element.scrollLeft < (element.scrollWidth - element.clientWidth),
1123
+ canScrollLeft: element.scrollLeft > 0
1124
+ });
1125
+ }, behavior === 'smooth' ? 300 : 50);
1126
+ });
1127
+ }, validatedArgs.selector, validatedArgs.direction, validatedArgs.amount, behavior);
1128
+
1129
+ if (!result.success) {
1130
+ throw new Error(result.error);
1131
+ }
1132
+
1133
+ return {
1134
+ content: [{
1135
+ type: "text",
1136
+ text: `Scrolled ${validatedArgs.selector} ${validatedArgs.direction}:\n` +
1137
+ ` Scroll position: ${result.scrollLeft}px\n` +
1138
+ ` Total width: ${result.scrollWidth}px\n` +
1139
+ ` Visible width: ${result.clientWidth}px\n` +
1140
+ ` Can scroll right: ${result.canScrollRight}\n` +
1141
+ ` Can scroll left: ${result.canScrollLeft}`
1142
+ }],
1143
+ };
1144
+ }
1145
+
911
1146
  if (name === "setStyles") {
912
1147
  const validatedArgs = schemas.SetStylesSchema.parse(args);
913
1148
  const page = await getLastOpenPage();
@@ -1464,6 +1699,111 @@ async function executeToolInternal(name, args) {
1464
1699
  };
1465
1700
  }
1466
1701
 
1702
+ if (name === "convertFigmaToCode") {
1703
+ const validatedArgs = schemas.ConvertFigmaToCodeSchema.parse(args);
1704
+ const token = validatedArgs.figmaToken || FIGMA_TOKEN;
1705
+ if (!token) {
1706
+ throw new Error('Figma token is required. Pass it as parameter or set FIGMA_TOKEN environment variable in MCP config.');
1707
+ }
1708
+
1709
+ // Normalize node ID
1710
+ const nodeId = normalizeFigmaNodeId(validatedArgs.nodeId);
1711
+ const framework = validatedArgs.framework || 'react';
1712
+ const includeComments = validatedArgs.includeComments !== false; // default true
1713
+
1714
+ // Fetch node structure
1715
+ const nodesData = await fetchFigmaAPI(
1716
+ `files/${validatedArgs.fileKey}/nodes?ids=${encodeURIComponent(nodeId)}`,
1717
+ token
1718
+ );
1719
+
1720
+ if (!nodesData.nodes || !nodesData.nodes[nodeId]) {
1721
+ throw new Error(`Node ${nodeId} not found in Figma file ${validatedArgs.fileKey}`);
1722
+ }
1723
+
1724
+ // Fetch rendered image at 2x scale
1725
+ const exportData = await fetchFigmaAPI(
1726
+ `images/${validatedArgs.fileKey}?ids=${nodeId}&scale=2&format=png`,
1727
+ token
1728
+ );
1729
+
1730
+ if (!exportData.images || !exportData.images[nodeId]) {
1731
+ throw new Error(`Failed to export image for node ${nodeId}`);
1732
+ }
1733
+
1734
+ const imageUrl = exportData.images[nodeId];
1735
+
1736
+ // Simplify node structure
1737
+ const nodeInfo = nodesData.nodes[nodeId];
1738
+ const simplifiedNode = simplifyNode(nodeInfo.document);
1739
+
1740
+ // Build AI instruction based on framework
1741
+ const frameworkInstructions = {
1742
+ 'react': 'React (JavaScript) with Tailwind CSS',
1743
+ 'react-typescript': 'React (TypeScript) with Tailwind CSS',
1744
+ 'html': 'Pure HTML with Tailwind CSS classes'
1745
+ };
1746
+
1747
+ const instruction = `# Figma to Code Conversion
1748
+
1749
+ ## Design Image
1750
+ ![Design](${imageUrl})
1751
+
1752
+ ## Task
1753
+ Convert this Figma design to ${frameworkInstructions[framework]}.
1754
+
1755
+ ## Design Structure (Simplified)
1756
+ \`\`\`json
1757
+ ${JSON.stringify(simplifiedNode, null, 2)}
1758
+ \`\`\`
1759
+
1760
+ ## Instructions
1761
+
1762
+ ### Framework: ${framework.toUpperCase()}
1763
+ ${framework.startsWith('react') ? `
1764
+ - Create a functional React component
1765
+ - Use Tailwind CSS for all styling
1766
+ - Props: Accept any necessary data as props
1767
+ - Use semantic HTML elements (div, section, button, h1-h6, p, etc.)
1768
+ ${framework === 'react-typescript' ? '- Add TypeScript type definitions for props' : ''}
1769
+ ` : `
1770
+ - Create clean, semantic HTML structure
1771
+ - Use Tailwind CSS classes for styling
1772
+ - No JavaScript required unless interactive elements present
1773
+ `}
1774
+
1775
+ ### Styling Guidelines
1776
+ 1. **Colors**: Convert RGB values to Tailwind colors or use arbitrary values: \`bg-[rgb(r,g,b)]\`
1777
+ 2. **Spacing**: Use Tailwind spacing scale (p-4, m-2, gap-4) matching design padding/gaps
1778
+ 3. **Layout**:
1779
+ - HORIZONTAL → \`flex flex-row\`
1780
+ - VERTICAL → \`flex flex-col\`
1781
+ - Use \`justify-*\` and \`items-*\` for alignment
1782
+ 4. **Typography**: Match font families, weights, sizes from textStyle properties
1783
+ 5. **Border Radius**: \`rounded-[Npx]\` for exact values
1784
+ 6. **Shadows**: Use Tailwind shadow utilities or arbitrary values
1785
+ 7. **Responsive**: Add responsive variants if design suggests multiple breakpoints
1786
+
1787
+ ### Quality Requirements
1788
+ - **Clean code**: No unnecessary divs, proper semantic structure
1789
+ - **Accurate spacing**: Match design padding, gaps, and margins
1790
+ - **Proper hierarchy**: Respect component nesting from design structure
1791
+ ${includeComments ? '- **Comments**: Add brief comments explaining complex layout decisions' : ''}
1792
+ - **Accessibility**: Use proper ARIA labels where needed
1793
+
1794
+ ### Output Format
1795
+ Return ONLY the code, no explanations. ${framework.startsWith('react') ? 'Export the component as default.' : 'Provide complete HTML structure.'}
1796
+
1797
+ Start coding now.`;
1798
+
1799
+ return {
1800
+ content: [{
1801
+ type: "text",
1802
+ text: instruction
1803
+ }],
1804
+ };
1805
+ }
1806
+
1467
1807
  // New AI optimization tools
1468
1808
  if (name === "smartFindElement") {
1469
1809
  const validatedArgs = schemas.SmartFindElementSchema.parse(args);
@@ -1595,7 +1935,7 @@ async function executeToolInternal(name, args) {
1595
1935
  }
1596
1936
 
1597
1937
  // Perform comprehensive analysis
1598
- const analysis = await page.evaluate((utilsCode) => {
1938
+ const analysis = await page.evaluate((includeAll, utilsCode) => {
1599
1939
  // Inject utilities
1600
1940
  eval(utilsCode);
1601
1941
 
@@ -1610,6 +1950,11 @@ async function executeToolInternal(name, args) {
1610
1950
  navigation: [],
1611
1951
  };
1612
1952
 
1953
+ // Add allElements array if includeAll is true
1954
+ if (includeAll) {
1955
+ result.allElements = [];
1956
+ }
1957
+
1613
1958
  // Analyze forms
1614
1959
  document.querySelectorAll('form').forEach((form, idx) => {
1615
1960
  const formData = {
@@ -1624,9 +1969,9 @@ async function executeToolInternal(name, args) {
1624
1969
  form.querySelectorAll('input, textarea, select').forEach(field => {
1625
1970
  if (field.type === 'submit' || field.type === 'button') return;
1626
1971
 
1627
- formData.fields.push({
1972
+ const fieldData = {
1628
1973
  selector: getUniqueSelectorInPage(field),
1629
- type: field.type || 'text',
1974
+ type: field.type || (field.tagName === 'SELECT' ? 'select' : 'text'),
1630
1975
  name: field.name,
1631
1976
  id: field.id,
1632
1977
  placeholder: field.placeholder,
@@ -1635,7 +1980,23 @@ async function executeToolInternal(name, args) {
1635
1980
  return label ? label.textContent.trim() : null;
1636
1981
  })(),
1637
1982
  required: field.required,
1638
- });
1983
+ };
1984
+
1985
+ // Add select-specific information
1986
+ if (field.tagName === 'SELECT') {
1987
+ fieldData.options = Array.from(field.options).map((opt, idx) => ({
1988
+ value: opt.value,
1989
+ text: opt.textContent.trim(),
1990
+ index: idx,
1991
+ selected: opt.selected,
1992
+ disabled: opt.disabled
1993
+ }));
1994
+ fieldData.selectedIndex = field.selectedIndex;
1995
+ fieldData.selectedValue = field.value;
1996
+ fieldData.selectedText = field.options[field.selectedIndex]?.textContent.trim() || null;
1997
+ }
1998
+
1999
+ formData.fields.push(fieldData);
1639
2000
  });
1640
2001
 
1641
2002
  // Find submit button
@@ -1667,12 +2028,28 @@ async function executeToolInternal(name, args) {
1667
2028
  if (input.type === 'submit' || input.type === 'button' || input.type === 'hidden') return;
1668
2029
  if (input.offsetWidth === 0 && input.offsetHeight === 0) return;
1669
2030
 
1670
- result.inputs.push({
2031
+ const inputData = {
1671
2032
  selector: getUniqueSelectorInPage(input),
1672
- type: input.type || 'text',
2033
+ type: input.type || (input.tagName === 'SELECT' ? 'select' : 'text'),
1673
2034
  name: input.name,
1674
2035
  placeholder: input.placeholder,
1675
- });
2036
+ };
2037
+
2038
+ // Add select-specific information
2039
+ if (input.tagName === 'SELECT') {
2040
+ inputData.options = Array.from(input.options).map((opt, idx) => ({
2041
+ value: opt.value,
2042
+ text: opt.textContent.trim(),
2043
+ index: idx,
2044
+ selected: opt.selected,
2045
+ disabled: opt.disabled
2046
+ }));
2047
+ inputData.selectedIndex = input.selectedIndex;
2048
+ inputData.selectedValue = input.value;
2049
+ inputData.selectedText = input.options[input.selectedIndex]?.textContent.trim() || null;
2050
+ }
2051
+
2052
+ result.inputs.push(inputData);
1676
2053
  });
1677
2054
 
1678
2055
  // All links
@@ -1712,8 +2089,52 @@ async function executeToolInternal(name, args) {
1712
2089
  });
1713
2090
  });
1714
2091
 
2092
+ // Collect all elements if requested
2093
+ if (includeAll) {
2094
+ const skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK', 'HEAD', 'TITLE'];
2095
+
2096
+ document.querySelectorAll('body *').forEach(el => {
2097
+ if (skipTags.includes(el.tagName)) return;
2098
+
2099
+ // Skip elements with no dimensions (hidden)
2100
+ if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
2101
+
2102
+ // Get text content (own text, not children)
2103
+ let ownText = '';
2104
+ for (const node of el.childNodes) {
2105
+ if (node.nodeType === Node.TEXT_NODE) {
2106
+ ownText += node.textContent;
2107
+ }
2108
+ }
2109
+ ownText = ownText.trim();
2110
+
2111
+ result.allElements.push({
2112
+ selector: getUniqueSelectorInPage(el),
2113
+ tag: el.tagName.toLowerCase(),
2114
+ text: ownText.substring(0, 100),
2115
+ classes: (() => {
2116
+ // Handle both string className (HTML) and SVGAnimatedString (SVG)
2117
+ if (!el.className) return [];
2118
+ if (typeof el.className === 'string') {
2119
+ return el.className.split(' ').filter(c => c);
2120
+ }
2121
+ // SVG elements have className.baseVal
2122
+ if (el.className.baseVal) {
2123
+ return el.className.baseVal.split(' ').filter(c => c);
2124
+ }
2125
+ return [];
2126
+ })(),
2127
+ id: el.id || null,
2128
+ attributes: {
2129
+ role: el.getAttribute('role') || null,
2130
+ 'aria-label': el.getAttribute('aria-label') || null,
2131
+ }
2132
+ });
2133
+ });
2134
+ }
2135
+
1715
2136
  return result;
1716
- }, elementFinderUtils);
2137
+ }, validatedArgs.includeAll || false, elementFinderUtils);
1717
2138
 
1718
2139
  // Cache the result
1719
2140
  pageAnalysisCache.set(pageUrl, analysis);
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrometools-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "MCP (Model Context Protocol) server for Chrome automation using Puppeteer. Persistent browser sessions, visual testing, Figma comparison, and design validation. Works seamlessly in WSL, Linux, and macOS.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -28,7 +28,7 @@ export const toolDefinitions = [
28
28
  },
29
29
  {
30
30
  name: "click",
31
- description: "Click element. Waits for animations. Optional screenshot parameter.",
31
+ description: "PRIMARY tool for clicking elements. Works correctly with React/Vue/Angular synthetic events. DO NOT use executeScript for clicks - use this tool instead. Waits for animations and navigation.",
32
32
  inputSchema: {
33
33
  type: "object",
34
34
  properties: {
@@ -42,7 +42,7 @@ export const toolDefinitions = [
42
42
  },
43
43
  {
44
44
  name: "type",
45
- description: "Type text into input field. Optional clear and typing delay.",
45
+ description: "PRIMARY tool for filling input fields. Works correctly with React/Vue/Angular state management. DO NOT use executeScript for typing - use this tool instead. Automatically updates framework state (React hooks, Vue reactive data).",
46
46
  inputSchema: {
47
47
  type: "object",
48
48
  properties: {
@@ -159,7 +159,7 @@ export const toolDefinitions = [
159
159
  },
160
160
  {
161
161
  name: "executeScript",
162
- description: "Execute JavaScript. Use only when specialized tools insufficient. Prefer analyzePage or findElementsByText.",
162
+ description: "⚠️ LAST RESORT tool - use ONLY when ALL specialized tools failed. NEVER use for: clicking (use click), typing (use type), reading page (use analyzePage), finding elements (use findElementsByText). May break React/Vue/Angular synthetic events. ALWAYS try specialized tools first.",
163
163
  inputSchema: {
164
164
  type: "object",
165
165
  properties: {
@@ -231,6 +231,48 @@ export const toolDefinitions = [
231
231
  required: ["selector"],
232
232
  },
233
233
  },
234
+ {
235
+ name: "selectOption",
236
+ description: "Select option in dropdown. Works with HTML select elements. Specify value, text, or index to choose option.",
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: {
240
+ selector: { type: "string", description: "CSS selector for select element" },
241
+ value: { type: "string", description: "Option value attribute (priority 1)" },
242
+ text: { type: "string", description: "Option text content (priority 2)" },
243
+ index: { type: "number", description: "Option index, 0-based (priority 3)" },
244
+ },
245
+ required: ["selector"],
246
+ },
247
+ },
248
+ {
249
+ name: "drag",
250
+ description: "Drag element by mouse (click-hold-move-release). Simulates real mouse drag in any direction. Works with interactive maps, Gantt charts, SVG diagrams, canvas, sliders. Does NOT work with standard overflow scrollbars - use scrollTo/scrollHorizontal instead.",
251
+ inputSchema: {
252
+ type: "object",
253
+ properties: {
254
+ selector: { type: "string", description: "CSS selector for element to drag" },
255
+ direction: { type: "string", enum: ["up", "down", "left", "right", "up-left", "up-right", "down-left", "down-right"], description: "Drag direction" },
256
+ distance: { type: "number", description: "Distance in pixels (default: 100)" },
257
+ duration: { type: "number", description: "Drag duration in ms (default: 500)" },
258
+ },
259
+ required: ["selector", "direction"],
260
+ },
261
+ },
262
+ {
263
+ name: "scrollHorizontal",
264
+ description: "Scroll element horizontally. For tables, carousels, and horizontally scrollable containers. Can scroll by pixels or to the end.",
265
+ inputSchema: {
266
+ type: "object",
267
+ properties: {
268
+ selector: { type: "string", description: "CSS selector for element to scroll" },
269
+ direction: { type: "string", enum: ["left", "right"], description: "Scroll direction" },
270
+ amount: { description: "Pixels to scroll or 'full' for end" },
271
+ behavior: { type: "string", enum: ["auto", "smooth"], description: "Scroll behavior (default: auto)" },
272
+ },
273
+ required: ["selector", "direction", "amount"],
274
+ },
275
+ },
234
276
  {
235
277
  name: "setStyles",
236
278
  description: "Apply inline CSS to element. For live editing and prototyping.",
@@ -403,6 +445,21 @@ export const toolDefinitions = [
403
445
  required: ["fileKey"],
404
446
  },
405
447
  },
448
+ {
449
+ name: "convertFigmaToCode",
450
+ description: "Convert Figma design to React/Tailwind code. Fetches node structure and rendered image, returns simplified design data with AI instructions for generating clean, semantic code. Focuses on React components with Tailwind CSS styling.",
451
+ inputSchema: {
452
+ type: "object",
453
+ properties: {
454
+ figmaToken: { type: "string", description: "API token (optional)" },
455
+ fileKey: { type: "string", description: "File key" },
456
+ nodeId: { type: "string", description: "Frame/component ID (formats: '123:456' or '123-456')" },
457
+ framework: { type: "string", enum: ["react", "react-typescript", "html"], description: "Target framework (default: react)" },
458
+ includeComments: { type: "boolean", description: "Include comments (default: true)" },
459
+ },
460
+ required: ["fileKey", "nodeId"],
461
+ },
462
+ },
406
463
  {
407
464
  name: "smartFindElement",
408
465
  description: "Find elements with natural language. Returns ranked candidates. Prefer analyzePage for better performance.",
@@ -429,11 +486,12 @@ export const toolDefinitions = [
429
486
  },
430
487
  {
431
488
  name: "analyzePage",
432
- description: "Get page state: forms, inputs, buttons, links with values. Use refresh:true after interactions. Cached per URL. 2-5k tokens vs screenshot 15-25k.",
489
+ description: "PRIMARY tool for reading page state (forms, inputs, buttons, links, values). Use this INSTEAD of executeScript for reading page content. Use refresh:true after clicks/submissions to see updated state. Efficient: 2-5k tokens vs screenshot 15-25k. includeAll:true gets ALL elements including non-interactive.",
433
490
  inputSchema: {
434
491
  type: "object",
435
492
  properties: {
436
493
  refresh: { type: "boolean", description: "Refresh cache (default: false)" },
494
+ includeAll: { type: "boolean", description: "Include all elements on page, not just interactive ones (default: false)" },
437
495
  },
438
496
  },
439
497
  },
@@ -449,7 +507,7 @@ export const toolDefinitions = [
449
507
  },
450
508
  {
451
509
  name: "findElementsByText",
452
- description: "Find elements by text. Returns elements with selectors. Optional actions on first match.",
510
+ description: "Find elements by visible text content and get their selectors. Use this INSTEAD of executeScript when you need to find elements. Returns working selectors that can be used with click/type tools. Can optionally perform actions directly.",
453
511
  inputSchema: {
454
512
  type: "object",
455
513
  properties: {
@@ -5,9 +5,9 @@
5
5
  */
6
6
 
7
7
  export const toolGroups = {
8
- core: ['ping', 'openBrowser'],
8
+ core: ['ping', 'openBrowser', 'executeScript', 'navigateTo'],
9
9
 
10
- interaction: ['click', 'type', 'scrollTo', 'waitForElement', 'hover'],
10
+ interaction: ['click', 'type', 'scrollTo', 'waitForElement', 'hover', 'selectOption', 'drag', 'scrollHorizontal'],
11
11
 
12
12
  inspection: ['getElement', 'getComputedCss', 'getBoxModel', 'screenshot', 'saveScreenshot'],
13
13
 
@@ -19,11 +19,9 @@ export const toolGroups = {
19
19
  ],
20
20
 
21
21
  advanced: [
22
- 'executeScript',
23
22
  'setStyles',
24
23
  'setViewport',
25
24
  'getViewport',
26
- 'navigateTo',
27
25
  'smartFindElement',
28
26
  'analyzePage',
29
27
  'getAllInteractiveElements',
@@ -51,7 +49,8 @@ export const toolGroups = {
51
49
  'searchFigmaFrames',
52
50
  'getFigmaComponents',
53
51
  'getFigmaStyles',
54
- 'getFigmaColorPalette'
52
+ 'getFigmaColorPalette',
53
+ 'convertFigmaToCode'
55
54
  ]
56
55
  };
57
56