mirror of
https://github.com/penpot/penpot-mcp.git
synced 2026-04-25 11:18:37 +00:00
Establish websocket connection between plugin and MCP server
This commit is contained in:
parent
740750fbd8
commit
7faca70aa7
52
README.md
Normal file
52
README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# WebSocket Connection Setup
|
||||
|
||||
This document explains how to test the basic WebSocket connection between the MCP server and the Penpot plugin.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The system consists of two main components:
|
||||
|
||||
1. **MCP Server** (`mcp-server/`):
|
||||
- Runs as a traditional MCP server (stdio transport)
|
||||
- Also runs a WebSocket server on port 8080
|
||||
- Basic WebSocket connection handling (protocol to be defined later)
|
||||
|
||||
2. **Penpot Plugin** (`penpot-plugin/`):
|
||||
- Contains a "Connect to MCP server" button in the UI
|
||||
- Establishes WebSocket connection to `ws://localhost:8080`
|
||||
- Basic connection status feedback
|
||||
|
||||
## Testing the Connection
|
||||
|
||||
### Step 1: Start the MCP Server
|
||||
```bash
|
||||
cd mcp-server
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
The server will output:
|
||||
```
|
||||
Penpot MCP Server started successfully
|
||||
WebSocket server is listening on ws://localhost:8080
|
||||
```
|
||||
|
||||
### Step 2: Build and Run the Plugin
|
||||
```bash
|
||||
cd penpot-plugin
|
||||
npm run build
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Step 3: Load Plugin in Penpot
|
||||
1. Open Penpot in your browser
|
||||
2. Navigate to a design file
|
||||
3. Go to Plugins menu
|
||||
4. Load the plugin using the development URL (typically `http://localhost:4400/manifest.json`)
|
||||
|
||||
### Step 4: Test the Connection
|
||||
1. In the plugin UI, click "Connect to MCP server"
|
||||
2. The connection status should change from "Not connected" to "Connected to MCP server"
|
||||
3. Check the browser's developer console for WebSocket connection logs
|
||||
4. Check the MCP server terminal for WebSocket connection messages
|
||||
|
||||
35
mcp-server/package-lock.json
generated
35
mcp-server/package-lock.json
generated
@ -12,10 +12,12 @@
|
||||
"@modelcontextprotocol/sdk": "^0.4.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/ws": "^8.5.10",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.0",
|
||||
"typescript": "^5.0.0"
|
||||
@ -107,6 +109,16 @@
|
||||
"integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
@ -387,6 +399,27 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
||||
@ -22,10 +22,12 @@
|
||||
"@modelcontextprotocol/sdk": "^0.4.0",
|
||||
"class-validator": "^0.14.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/ws": "^8.5.10",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.0",
|
||||
"typescript": "^5.0.0"
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { WebSocketServer, WebSocket } from "ws";
|
||||
|
||||
import { Tool } from "./interfaces/Tool.js";
|
||||
import { HelloWorldTool } from "./tools/HelloWorldTool.js";
|
||||
@ -16,6 +17,8 @@ import { HelloWorldTool } from "./tools/HelloWorldTool.js";
|
||||
class PenpotMcpServer {
|
||||
private readonly server: Server;
|
||||
private readonly tools: Map<string, Tool>;
|
||||
private readonly wsServer: WebSocketServer;
|
||||
private readonly connectedClients: Set<WebSocket> = new Set();
|
||||
|
||||
/**
|
||||
* Creates a new Penpot MCP server instance.
|
||||
@ -30,7 +33,9 @@ class PenpotMcpServer {
|
||||
});
|
||||
|
||||
this.tools = new Map<string, Tool>();
|
||||
this.wsServer = new WebSocketServer({ port: 8080 });
|
||||
this.setupHandlers();
|
||||
this.setupWebSocketHandlers();
|
||||
this.registerTools();
|
||||
}
|
||||
|
||||
@ -80,6 +85,37 @@ class PenpotMcpServer {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up WebSocket connection handlers for plugin communication.
|
||||
*
|
||||
* Manages client connections and provides bidirectional communication
|
||||
* channel between the MCP server and Penpot plugin instances.
|
||||
*/
|
||||
private setupWebSocketHandlers(): void {
|
||||
this.wsServer.on("connection", (ws: WebSocket) => {
|
||||
console.error("New WebSocket connection established");
|
||||
this.connectedClients.add(ws);
|
||||
|
||||
ws.on("message", (data: Buffer) => {
|
||||
console.error("Received WebSocket message:", data.toString());
|
||||
// Protocol will be defined later
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.error("WebSocket connection closed");
|
||||
this.connectedClients.delete(ws);
|
||||
});
|
||||
|
||||
ws.on("error", (error) => {
|
||||
console.error("WebSocket connection error:", error);
|
||||
this.connectedClients.delete(ws);
|
||||
});
|
||||
});
|
||||
|
||||
console.error("WebSocket server started on port 8080");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the MCP server using stdio transport.
|
||||
*
|
||||
@ -90,6 +126,7 @@ class PenpotMcpServer {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
console.error("Penpot MCP Server started successfully");
|
||||
console.error("WebSocket server is listening on ws://localhost:8080");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,14 @@
|
||||
Create text
|
||||
</button>
|
||||
|
||||
<button type="button" data-appearance="secondary" data-handler="connect-mcp">
|
||||
Connect to MCP server
|
||||
</button>
|
||||
|
||||
<div id="connection-status" style="margin-top: 10px; font-size: 12px; color: #666;">
|
||||
Not connected
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -4,11 +4,70 @@ import "./style.css";
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
document.body.dataset.theme = searchParams.get("theme") ?? "light";
|
||||
|
||||
// WebSocket connection management
|
||||
let ws: WebSocket | null = null;
|
||||
const statusElement = document.getElementById("connection-status");
|
||||
|
||||
/**
|
||||
* Updates the connection status display element.
|
||||
*/
|
||||
function updateConnectionStatus(status: string, isConnectedState: boolean): void {
|
||||
if (statusElement) {
|
||||
statusElement.textContent = status;
|
||||
statusElement.style.color = isConnectedState ? "#4CAF50" : "#666";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a WebSocket connection to the MCP server.
|
||||
*/
|
||||
function connectToMcpServer(): void {
|
||||
if (ws?.readyState === WebSocket.OPEN) {
|
||||
updateConnectionStatus("Already connected", true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket("ws://localhost:8080");
|
||||
updateConnectionStatus("Connecting...", false);
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("Connected to MCP server");
|
||||
updateConnectionStatus("Connected to MCP server", true);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
console.log("Received from MCP server:", event.data);
|
||||
// Protocol will be defined later
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log("Disconnected from MCP server");
|
||||
updateConnectionStatus("Disconnected", false);
|
||||
ws = null;
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
updateConnectionStatus("Connection error", false);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to connect to MCP server:", error);
|
||||
updateConnectionStatus("Connection failed", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Event handlers
|
||||
document.querySelector("[data-handler='create-text']")?.addEventListener("click", () => {
|
||||
// send message to plugin.ts
|
||||
parent.postMessage("create-text", "*");
|
||||
});
|
||||
|
||||
document.querySelector("[data-handler='connect-mcp']")?.addEventListener("click", () => {
|
||||
connectToMcpServer();
|
||||
});
|
||||
|
||||
// Listen plugin.ts messages
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data.source === "penpot") {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user