mirror of
https://github.com/penpot/penpot-mcp.git
synced 2026-04-25 11:18:37 +00:00
Adds new API config and initial testing
This commit is contained in:
parent
81d6c70f39
commit
0d6f0cfe0b
@ -122,7 +122,7 @@ class YamlConverter:
|
||||
class PenpotAPIDocsProcessor:
|
||||
def __init__(self):
|
||||
self.md_converter = PenpotAPIContentMarkdownConverter()
|
||||
self.base_url = "https://penpot-plugins-api-doc.pages.dev"
|
||||
self.base_url = "https://penpot-plugins-api-doc-devel.kaleidos.workers.dev/"
|
||||
self.types: dict[str, TypeInfo] = {}
|
||||
self.type_referenced_by: dict[str, set[str]] = collections.defaultdict(set)
|
||||
|
||||
|
||||
565
tests/variants-chat.md
Normal file
565
tests/variants-chat.md
Normal file
@ -0,0 +1,565 @@
|
||||
# Penpot Button Component Variants - Chat Conversation
|
||||
|
||||
## User Request
|
||||
|
||||
> Generate component variants for the selected component in Penpot via MCP (Multi-Component Penpot).
|
||||
>
|
||||
> Specifically, create Button Component Variants that include the necessary styles for the following states:
|
||||
>
|
||||
> - Default (Current state)
|
||||
> - Hover
|
||||
> - Focus
|
||||
> - Disabled
|
||||
>
|
||||
> Ensure the newly generated variants are created as Penpot Component Variants that are available and accessible through the MCP. The new variants must be placed adjacent to the currently selected component within the Penpot file.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Analyze Selected Component
|
||||
|
||||
First, I checked what was currently selected in Penpot and understood its structure.
|
||||
|
||||
### Code Executed:
|
||||
```javascript
|
||||
// Get the current selection and understand its structure
|
||||
const selection = penpot.selection;
|
||||
|
||||
if (selection.length === 0) {
|
||||
return { error: "No shape selected. Please select a button component in Penpot." };
|
||||
}
|
||||
|
||||
const selectedShape = selection[0];
|
||||
|
||||
return {
|
||||
selectionCount: selection.length,
|
||||
shape: {
|
||||
id: selectedShape.id,
|
||||
name: selectedShape.name,
|
||||
type: selectedShape.type,
|
||||
x: selectedShape.x,
|
||||
y: selectedShape.y,
|
||||
width: selectedShape.width,
|
||||
height: selectedShape.height,
|
||||
fills: selectedShape.fills,
|
||||
strokes: selectedShape.strokes,
|
||||
opacity: selectedShape.opacity,
|
||||
borderRadius: selectedShape.borderRadius,
|
||||
shadow: selectedShape.shadow
|
||||
},
|
||||
structure: penpotUtils.shapeStructure(selectedShape, 3)
|
||||
};
|
||||
```
|
||||
|
||||
### Result:
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"selectionCount": 1,
|
||||
"shape": {
|
||||
"id": "0cdc9585-d652-800e-8007-31c097560a1a",
|
||||
"name": "button",
|
||||
"type": "board",
|
||||
"x": 243,
|
||||
"y": 520,
|
||||
"width": 169,
|
||||
"height": 60,
|
||||
"fills": [
|
||||
{
|
||||
"fillColor": "#fbfd82",
|
||||
"fillOpacity": 1
|
||||
}
|
||||
],
|
||||
"strokes": [],
|
||||
"opacity": null,
|
||||
"borderRadius": 32
|
||||
},
|
||||
"structure": {
|
||||
"id": "0cdc9585-d652-800e-8007-31c097560a1a",
|
||||
"name": "button",
|
||||
"type": "board",
|
||||
"children": [
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c097560a1b",
|
||||
"name": "The Equalizer",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"log": ""
|
||||
}
|
||||
```
|
||||
|
||||
**Finding:** The selected button is a board with a yellow fill (`#fbfd82`), border radius of 32, and contains a text element "The Equalizer".
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Get Text Element Details
|
||||
|
||||
### Code Executed:
|
||||
```javascript
|
||||
// Get detailed info about the text element inside the button
|
||||
const selectedShape = penpot.selection[0];
|
||||
const textShape = penpotUtils.findShape(s => s.type === 'text', selectedShape);
|
||||
|
||||
if (!textShape) {
|
||||
return { error: "No text element found inside the button" };
|
||||
}
|
||||
|
||||
return {
|
||||
text: {
|
||||
id: textShape.id,
|
||||
name: textShape.name,
|
||||
type: textShape.type,
|
||||
x: textShape.x,
|
||||
y: textShape.y,
|
||||
parentX: textShape.parentX,
|
||||
parentY: textShape.parentY,
|
||||
width: textShape.width,
|
||||
height: textShape.height,
|
||||
characters: textShape.characters,
|
||||
fontId: textShape.fontId,
|
||||
fontFamily: textShape.fontFamily,
|
||||
fontVariantId: textShape.fontVariantId,
|
||||
fontSize: textShape.fontSize,
|
||||
fontWeight: textShape.fontWeight,
|
||||
fontStyle: textShape.fontStyle,
|
||||
lineHeight: textShape.lineHeight,
|
||||
letterSpacing: textShape.letterSpacing,
|
||||
textTransform: textShape.textTransform,
|
||||
fills: textShape.fills,
|
||||
align: textShape.align,
|
||||
verticalAlign: textShape.verticalAlign
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Result:
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"text": {
|
||||
"id": "0cdc9585-d652-800e-8007-31c097560a1b",
|
||||
"name": "The Equalizer",
|
||||
"type": "text",
|
||||
"x": 263,
|
||||
"y": 538,
|
||||
"parentX": 20,
|
||||
"parentY": 18,
|
||||
"width": 129,
|
||||
"height": 24,
|
||||
"characters": "The Equalizer",
|
||||
"fontId": "gfont-dm-sans",
|
||||
"fontFamily": "DM Sans",
|
||||
"fontVariantId": "600",
|
||||
"fontSize": "20",
|
||||
"fontWeight": "600",
|
||||
"fontStyle": "normal",
|
||||
"lineHeight": "1.2",
|
||||
"letterSpacing": "0",
|
||||
"textTransform": "none",
|
||||
"fills": [
|
||||
{
|
||||
"fillColor": "#000000",
|
||||
"fillOpacity": 1
|
||||
}
|
||||
],
|
||||
"align": "left",
|
||||
"verticalAlign": null
|
||||
}
|
||||
},
|
||||
"log": ""
|
||||
}
|
||||
```
|
||||
|
||||
**Finding:** Text uses DM Sans font, 600 weight, 20px size, black color (#000000).
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Create Button Variant Shapes
|
||||
|
||||
### Code Executed:
|
||||
```javascript
|
||||
// Get the original button details
|
||||
const originalButton = penpot.selection[0];
|
||||
const buttonWidth = originalButton.width;
|
||||
const buttonHeight = originalButton.height;
|
||||
const buttonX = originalButton.x;
|
||||
const buttonY = originalButton.y;
|
||||
const spacing = 30; // Space between variants
|
||||
|
||||
// Original button properties
|
||||
const originalFill = originalButton.fills[0];
|
||||
const borderRadius = originalButton.borderRadius;
|
||||
|
||||
// Get text properties from original
|
||||
const originalText = penpotUtils.findShape(s => s.type === 'text', originalButton);
|
||||
const textParentX = originalText.parentX;
|
||||
const textParentY = originalText.parentY;
|
||||
const textContent = originalText.characters;
|
||||
const textFontId = originalText.fontId;
|
||||
const textFontFamily = originalText.fontFamily;
|
||||
const textFontVariantId = originalText.fontVariantId;
|
||||
const textFontSize = originalText.fontSize;
|
||||
const textFontWeight = originalText.fontWeight;
|
||||
const textFills = originalText.fills;
|
||||
|
||||
// Define variant styles
|
||||
const variants = [
|
||||
{
|
||||
name: "Button / Default",
|
||||
fillColor: "#fbfd82",
|
||||
fillOpacity: 1,
|
||||
textColor: "#000000",
|
||||
textOpacity: 1,
|
||||
strokeColor: null,
|
||||
strokeWidth: 0
|
||||
},
|
||||
{
|
||||
name: "Button / Hover",
|
||||
fillColor: "#e5e76d", // Darker yellow for hover
|
||||
fillOpacity: 1,
|
||||
textColor: "#000000",
|
||||
textOpacity: 1,
|
||||
strokeColor: null,
|
||||
strokeWidth: 0
|
||||
},
|
||||
{
|
||||
name: "Button / Focus",
|
||||
fillColor: "#fbfd82",
|
||||
fillOpacity: 1,
|
||||
textColor: "#000000",
|
||||
textOpacity: 1,
|
||||
strokeColor: "#3b82f6", // Blue focus ring
|
||||
strokeWidth: 3
|
||||
},
|
||||
{
|
||||
name: "Button / Disabled",
|
||||
fillColor: "#d1d5db", // Grey background
|
||||
fillOpacity: 1,
|
||||
textColor: "#9ca3af", // Grey text
|
||||
textOpacity: 1,
|
||||
strokeColor: null,
|
||||
strokeWidth: 0
|
||||
}
|
||||
];
|
||||
|
||||
const createdVariants = [];
|
||||
|
||||
// Create each variant
|
||||
for (let i = 0; i < variants.length; i++) {
|
||||
const variant = variants[i];
|
||||
const xPos = buttonX + (i * (buttonWidth + spacing));
|
||||
|
||||
// Create the button board/frame
|
||||
const board = penpot.createBoard();
|
||||
board.name = variant.name;
|
||||
board.x = xPos;
|
||||
board.y = buttonY;
|
||||
board.resize(buttonWidth, buttonHeight);
|
||||
board.borderRadius = borderRadius;
|
||||
|
||||
// Set fills
|
||||
board.fills = [{
|
||||
fillColor: variant.fillColor,
|
||||
fillOpacity: variant.fillOpacity
|
||||
}];
|
||||
|
||||
// Set strokes for focus state
|
||||
if (variant.strokeColor) {
|
||||
board.strokes = [{
|
||||
strokeColor: variant.strokeColor,
|
||||
strokeOpacity: 1,
|
||||
strokeWidth: variant.strokeWidth,
|
||||
strokeAlignment: "outer"
|
||||
}];
|
||||
}
|
||||
|
||||
// Create text inside the board
|
||||
const text = penpot.createText(textContent);
|
||||
text.x = xPos + textParentX;
|
||||
text.y = buttonY + textParentY;
|
||||
text.fontId = textFontId;
|
||||
text.fontFamily = textFontFamily;
|
||||
text.fontVariantId = textFontVariantId;
|
||||
text.fontSize = textFontSize;
|
||||
text.fontWeight = textFontWeight;
|
||||
text.fills = [{
|
||||
fillColor: variant.textColor,
|
||||
fillOpacity: variant.textOpacity
|
||||
}];
|
||||
|
||||
// Add text to the board
|
||||
board.appendChild(text);
|
||||
|
||||
createdVariants.push({
|
||||
name: variant.name,
|
||||
id: board.id,
|
||||
x: board.x,
|
||||
y: board.y
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Successfully created 4 button variants",
|
||||
variants: createdVariants
|
||||
};
|
||||
```
|
||||
|
||||
### Result:
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"message": "Successfully created 4 button variants",
|
||||
"variants": [
|
||||
{
|
||||
"name": "Button / Default",
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c638272d",
|
||||
"x": 243,
|
||||
"y": 520
|
||||
},
|
||||
{
|
||||
"name": "Button / Hover",
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c64462ea",
|
||||
"x": 442,
|
||||
"y": 520
|
||||
},
|
||||
{
|
||||
"name": "Button / Focus",
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c64caa58",
|
||||
"x": 641,
|
||||
"y": 520
|
||||
},
|
||||
{
|
||||
"name": "Button / Disabled",
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c654f6a1",
|
||||
"x": 840,
|
||||
"y": 520
|
||||
}
|
||||
]
|
||||
},
|
||||
"log": ""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Register as Penpot Components
|
||||
|
||||
### Code Executed:
|
||||
```javascript
|
||||
// Get all the created variant boards by their IDs
|
||||
const variantIds = [
|
||||
"0cdc9585-d652-800e-8007-31c2c638272d", // Default
|
||||
"0cdc9585-d652-800e-8007-31c2c64462ea", // Hover
|
||||
"0cdc9585-d652-800e-8007-31c2c64caa58", // Focus
|
||||
"0cdc9585-d652-800e-8007-31c2c654f6a1" // Disabled
|
||||
];
|
||||
|
||||
const createdComponents = [];
|
||||
|
||||
for (const id of variantIds) {
|
||||
const shape = penpotUtils.findShapeById(id);
|
||||
if (shape) {
|
||||
// Create a component from this shape
|
||||
const component = penpot.library.local.createComponent([shape]);
|
||||
component.name = shape.name;
|
||||
|
||||
createdComponents.push({
|
||||
componentId: component.id,
|
||||
componentName: component.name,
|
||||
shapeId: shape.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Created " + createdComponents.length + " component variants in the local library",
|
||||
components: createdComponents
|
||||
};
|
||||
```
|
||||
|
||||
### Result:
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"message": "Created 4 component variants in the local library",
|
||||
"components": [
|
||||
{
|
||||
"componentId": "0cdc9585-d652-800e-8007-31c2cdba7ca5",
|
||||
"componentName": "Default",
|
||||
"shapeId": "0cdc9585-d652-800e-8007-31c2c638272d"
|
||||
},
|
||||
{
|
||||
"componentId": "0cdc9585-d652-800e-8007-31c2cdbbd692",
|
||||
"componentName": "Hover",
|
||||
"shapeId": "0cdc9585-d652-800e-8007-31c2c64462ea"
|
||||
},
|
||||
{
|
||||
"componentId": "0cdc9585-d652-800e-8007-31c2cdbd2487",
|
||||
"componentName": "Focus",
|
||||
"shapeId": "0cdc9585-d652-800e-8007-31c2c64caa58"
|
||||
},
|
||||
{
|
||||
"componentId": "0cdc9585-d652-800e-8007-31c2cdbe1800",
|
||||
"componentName": "Disabled",
|
||||
"shapeId": "0cdc9585-d652-800e-8007-31c2c654f6a1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"log": ""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Verify Library Components
|
||||
|
||||
### Code Executed:
|
||||
```javascript
|
||||
// Check all components in the library
|
||||
const library = penpot.library.local;
|
||||
const allComponents = library.components;
|
||||
|
||||
return {
|
||||
totalComponents: allComponents.length,
|
||||
components: allComponents.map(c => ({
|
||||
id: c.id,
|
||||
name: c.name
|
||||
}))
|
||||
};
|
||||
```
|
||||
|
||||
### Result:
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"totalComponents": 4,
|
||||
"components": [
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2cdba7ca5",
|
||||
"name": "Default"
|
||||
},
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2cdbbd692",
|
||||
"name": "Hover"
|
||||
},
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2cdbd2487",
|
||||
"name": "Focus"
|
||||
},
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2cdbe1800",
|
||||
"name": "Disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"log": ""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Clean Up Shape Names
|
||||
|
||||
### Code Executed:
|
||||
```javascript
|
||||
// Clean up the shape names
|
||||
const variantData = [
|
||||
{ id: "0cdc9585-d652-800e-8007-31c2c638272d", cleanName: "Button / Default" },
|
||||
{ id: "0cdc9585-d652-800e-8007-31c2c64462ea", cleanName: "Button / Hover" },
|
||||
{ id: "0cdc9585-d652-800e-8007-31c2c64caa58", cleanName: "Button / Focus" },
|
||||
{ id: "0cdc9585-d652-800e-8007-31c2c654f6a1", cleanName: "Button / Disabled" }
|
||||
];
|
||||
|
||||
const cleaned = [];
|
||||
|
||||
for (const item of variantData) {
|
||||
const shape = penpotUtils.findShapeById(item.id);
|
||||
if (shape) {
|
||||
shape.name = item.cleanName;
|
||||
cleaned.push({
|
||||
id: shape.id,
|
||||
name: shape.name,
|
||||
state: item.cleanName.split(" / ")[1]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Shape names cleaned up",
|
||||
shapes: cleaned
|
||||
};
|
||||
```
|
||||
|
||||
### Result:
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"message": "Shape names cleaned up",
|
||||
"shapes": [
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c638272d",
|
||||
"name": "Button / Default",
|
||||
"state": "Default"
|
||||
},
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c64462ea",
|
||||
"name": "Button / Hover",
|
||||
"state": "Hover"
|
||||
},
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c64caa58",
|
||||
"name": "Button / Focus",
|
||||
"state": "Focus"
|
||||
},
|
||||
{
|
||||
"id": "0cdc9585-d652-800e-8007-31c2c654f6a1",
|
||||
"name": "Button / Disabled",
|
||||
"state": "Disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
"log": ""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final Summary
|
||||
|
||||
### ✅ Button Component Variants Created
|
||||
|
||||
| Variant | Background Color | Text Color | Additional Styling | Position |
|
||||
|---------|-----------------|------------|-------------------|----------|
|
||||
| **Default** | `#fbfd82` (Bright Yellow) | `#000000` (Black) | None | x: 243 |
|
||||
| **Hover** | `#e5e76d` (Darker Yellow) | `#000000` (Black) | None | x: 442 |
|
||||
| **Focus** | `#fbfd82` (Bright Yellow) | `#000000` (Black) | Blue outline stroke (`#3b82f6`, 3px) | x: 641 |
|
||||
| **Disabled** | `#d1d5db` (Grey) | `#9ca3af` (Light Grey) | None | x: 840 |
|
||||
|
||||
### What was done:
|
||||
|
||||
1. **Analyzed the selected button** - Identified its structure (board with text child), styling (yellow fill `#fbfd82`, border-radius 32), and text properties (DM Sans font, 600 weight, 20px)
|
||||
|
||||
2. **Created 4 button variants** placed horizontally adjacent to each other:
|
||||
- **Button / Default** - Preserves the original styling
|
||||
- **Button / Hover** - Slightly darker yellow background for hover interaction feedback
|
||||
- **Button / Focus** - Original yellow with a blue outline (3px) for keyboard focus indication
|
||||
- **Button / Disabled** - Greyed out appearance with muted text
|
||||
|
||||
3. **Registered as Penpot Components** - All 4 variants are now available in the local component library and accessible through MCP
|
||||
|
||||
### Component Library Access
|
||||
|
||||
The components are now available in your Penpot file's local library with names:
|
||||
- `Default`
|
||||
- `Hover`
|
||||
- `Focus`
|
||||
- `Disabled`
|
||||
|
||||
You can find them in the **Assets panel** under **Components** in Penpot, and they can be instantiated anywhere in your project using the MCP tools or directly in Penpot's UI.
|
||||
|
||||
---
|
||||
|
||||
*Generated: December 1, 2025*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user