figma-prototype-mcp 0.30.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/LICENSE +25 -0
- package/README.md +169 -0
- package/dist/figma-plugin/code.js +1203 -0
- package/dist/figma-plugin/manifest.json +13 -0
- package/dist/figma-plugin/ui.html +123 -0
- package/dist/server/index.js +1155 -0
- package/package.json +61 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Figma Prototype MCP",
|
|
3
|
+
"id": "figma-prototype-mcp-local",
|
|
4
|
+
"api": "1.0.0",
|
|
5
|
+
"main": "code.js",
|
|
6
|
+
"ui": "ui.html",
|
|
7
|
+
"editorType": ["figma"],
|
|
8
|
+
"permissions": ["teamlibrary"],
|
|
9
|
+
"networkAccess": {
|
|
10
|
+
"allowedDomains": ["ws://localhost:3000"],
|
|
11
|
+
"reasoning": "Local WebSocket bridge to the figma-prototype-mcp unified server (Phase A v1.19+)"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<style>
|
|
6
|
+
body { font-family: -apple-system, system-ui, sans-serif; font-size: 13px; padding: 12px; margin: 0; }
|
|
7
|
+
button { margin-top: 8px; padding: 8px 12px; cursor: pointer; border-radius: 4px; border: 1px solid transparent; font-weight: 500; }
|
|
8
|
+
#connect { background: #18a058; color: #fff; border-color: #148e4d; }
|
|
9
|
+
#connect:hover { background: #148e4d; }
|
|
10
|
+
#disconnect { background: #d03050; color: #fff; border-color: #b02540; }
|
|
11
|
+
#disconnect:hover { background: #b02540; }
|
|
12
|
+
#status { margin-top: 12px; font-size: 12px; }
|
|
13
|
+
.ok { color: #18a058; }
|
|
14
|
+
.err { color: #d03050; }
|
|
15
|
+
.pending { color: #888; }
|
|
16
|
+
.url { color: #555; font-size: 11px; margin-bottom: 8px; }
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div class="url">Server: <code>ws://localhost:3000/ws</code></div>
|
|
21
|
+
<button id="connect">Connect</button>
|
|
22
|
+
<button id="disconnect" style="display:none">Disconnect</button>
|
|
23
|
+
<div id="status" class="pending">Not connected</div>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
const SERVER_URL = 'ws://localhost:3000/ws';
|
|
27
|
+
const connectBtn = document.getElementById('connect');
|
|
28
|
+
const disconnectBtn = document.getElementById('disconnect');
|
|
29
|
+
const statusEl = document.getElementById('status');
|
|
30
|
+
let ws = null;
|
|
31
|
+
let userDisconnected = false;
|
|
32
|
+
let retryTimer = null;
|
|
33
|
+
let retryDelay = 1000;
|
|
34
|
+
const MAX_RETRY_DELAY = 30000;
|
|
35
|
+
|
|
36
|
+
function setStatus(text, cls) {
|
|
37
|
+
statusEl.textContent = text;
|
|
38
|
+
statusEl.className = cls;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function setConnectedUI() {
|
|
42
|
+
setStatus('Connected', 'ok');
|
|
43
|
+
connectBtn.style.display = 'none';
|
|
44
|
+
disconnectBtn.style.display = 'inline-block';
|
|
45
|
+
retryDelay = 1000;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function setDisconnectedUI() {
|
|
49
|
+
connectBtn.style.display = 'inline-block';
|
|
50
|
+
disconnectBtn.style.display = 'none';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function connect() {
|
|
54
|
+
if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; }
|
|
55
|
+
userDisconnected = false;
|
|
56
|
+
if (ws) ws.close();
|
|
57
|
+
setStatus('Connecting…', 'pending');
|
|
58
|
+
ws = new WebSocket(SERVER_URL);
|
|
59
|
+
|
|
60
|
+
ws.onopen = () => { /* server will send {type:"ready"} */ };
|
|
61
|
+
ws.onerror = () => setStatus('Connection failed', 'err');
|
|
62
|
+
ws.onclose = () => {
|
|
63
|
+
if (userDisconnected) {
|
|
64
|
+
setStatus('Disconnected', 'err');
|
|
65
|
+
setDisconnectedUI();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
setStatus('Reconnecting in ' + (retryDelay / 1000) + 's…', 'pending');
|
|
69
|
+
retryTimer = setTimeout(() => {
|
|
70
|
+
retryTimer = null;
|
|
71
|
+
setStatus('Reconnecting…', 'pending');
|
|
72
|
+
retryDelay = Math.min(retryDelay * 2, MAX_RETRY_DELAY);
|
|
73
|
+
connect();
|
|
74
|
+
}, retryDelay);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
ws.onmessage = (e) => {
|
|
78
|
+
let data;
|
|
79
|
+
try { data = JSON.parse(e.data); } catch { return; }
|
|
80
|
+
|
|
81
|
+
if (data.type === 'ready') {
|
|
82
|
+
setConnectedUI();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (data.type === 'command') {
|
|
87
|
+
parent.postMessage({ pluginMessage: { type: 'command', envelope: data } }, '*');
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
connectBtn.onclick = connect;
|
|
93
|
+
|
|
94
|
+
disconnectBtn.onclick = () => {
|
|
95
|
+
userDisconnected = true;
|
|
96
|
+
if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; }
|
|
97
|
+
if (ws) ws.close();
|
|
98
|
+
setStatus('Disconnected', 'err');
|
|
99
|
+
setDisconnectedUI();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Forward responses from the plugin sandbox out to WebSocket.
|
|
103
|
+
window.onmessage = (event) => {
|
|
104
|
+
const msg = event.data.pluginMessage;
|
|
105
|
+
if (!msg) return;
|
|
106
|
+
|
|
107
|
+
if (msg.type === 'response' && ws && ws.readyState === WebSocket.OPEN) {
|
|
108
|
+
// msg.envelope shape from code.ts: { id, type:"response", status, result?, error? }
|
|
109
|
+
ws.send(JSON.stringify({
|
|
110
|
+
type: 'response',
|
|
111
|
+
id: msg.envelope.id,
|
|
112
|
+
status: msg.envelope.status,
|
|
113
|
+
result: msg.envelope.result,
|
|
114
|
+
error: msg.envelope.error,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Auto-connect on load — single-active model means we just try.
|
|
120
|
+
connect();
|
|
121
|
+
</script>
|
|
122
|
+
</body>
|
|
123
|
+
</html>
|