conductor-figma 3.0.1 → 3.0.3

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/README.md CHANGED
@@ -80,22 +80,6 @@ Every tool queries the intelligence engine:
80
80
  - **Font Weights**: Auto-resolves "bold" → "Bold", "600" → "Semi Bold"
81
81
  - **Layout Intelligence**: 13 layout presets (row, column, center, spread, grid, sidebar, form)
82
82
 
83
- ## vs Competition
84
-
85
- |---------|-----------|--------|-----------|---------------|
86
- | Tools | 201 | 45 | 5 | 12 |
87
- | Categories | 17 | ~8 | 1 | ~4 |
88
- | Design intelligence | Yes | No | No | No |
89
- | Smart components | 18 types | No | No | No |
90
- | Accessibility | 12 tools | No | No | No |
91
- | Code export | 5 frameworks | No | No | Partial |
92
- | Batch operations | 17 tools | No | No | No |
93
- | Design system | 10 tools (free) | $9 add-on | No | Partial |
94
- | Responsive | 5 tools | No | No | No |
95
- | Typography | 10 tools | No | No | No |
96
- | Prototype | 10 tools | No | No | No |
97
- | Annotation | 10 tools | No | No | No |
98
-
99
83
  ## License
100
84
 
101
85
  MIT. Built by [0xDragoon](https://github.com/dragoon0x).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-figma",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Design-intelligent MCP server for Figma. 201 design-intelligent tools for Figma. Every tool knows typography, spacing, color, accessibility. Not a shape proxy — a design engine.",
5
5
  "author": "0xDragoon",
6
6
  "license": "MIT",
package/src/bridge.js CHANGED
@@ -4,57 +4,73 @@
4
4
 
5
5
  import { WebSocketServer } from 'ws'
6
6
 
7
- function log(...args) { process.stderr.write('[bridge] ' + args.join(' ') + '\n') }
7
+ function log() { process.stderr.write('[bridge] ' + Array.prototype.join.call(arguments, ' ') + '\n') }
8
8
 
9
9
  export function createBridge(port) {
10
10
  port = port || parseInt(process.env.CONDUCTOR_PORT) || 3055
11
- let wss = null
12
- let client = null
13
- let pending = new Map()
14
- let msgId = 0
11
+ var wss = null
12
+ var client = null
13
+ var pending = new Map()
14
+ var msgId = 0
15
+ var isOwner = false
15
16
 
16
17
  function start() {
17
- return new Promise((resolve) => {
18
- wss = new WebSocketServer({ port })
19
- wss.on('listening', () => { log(`WebSocket server on port ${port}`); resolve() })
20
- wss.on('connection', (ws) => {
21
- client = ws
22
- log('Figma plugin connected')
23
- ws.on('message', (data) => {
24
- try {
25
- const msg = JSON.parse(data.toString())
26
- if (msg.id && pending.has(msg.id)) {
27
- const { resolve, reject, timer } = pending.get(msg.id)
28
- clearTimeout(timer)
29
- pending.delete(msg.id)
30
- if (msg.error) reject(new Error(msg.error))
31
- else resolve(msg.result)
32
- }
33
- } catch (e) { log('Parse error:', e.message) }
18
+ return new Promise(function(resolve) {
19
+ try {
20
+ wss = new WebSocketServer({ port: port })
21
+ wss.on('listening', function() { isOwner = true; log('WebSocket server on port ' + port); resolve() })
22
+ wss.on('connection', function(ws) {
23
+ client = ws
24
+ log('Figma plugin connected')
25
+ ws.on('message', function(data) {
26
+ try {
27
+ var msg = JSON.parse(data.toString())
28
+ if (msg.id && pending.has(msg.id)) {
29
+ var p = pending.get(msg.id)
30
+ clearTimeout(p.timer)
31
+ pending.delete(msg.id)
32
+ if (msg.error) p.reject(new Error(msg.error))
33
+ else p.resolve(msg.result)
34
+ }
35
+ } catch (e) { log('Parse error:', e.message) }
36
+ })
37
+ ws.on('close', function() { client = null; log('Figma plugin disconnected') })
38
+ ws.on('error', function(e) { log('WS error:', e.message) })
34
39
  })
35
- ws.on('close', () => { client = null; log('Figma plugin disconnected') })
36
- ws.on('error', (e) => log('WS error:', e.message))
37
- })
38
- wss.on('error', (e) => { log('Server error:', e.message); resolve() })
40
+ wss.on('error', function(e) {
41
+ if (e.code === 'EADDRINUSE') {
42
+ log('Port ' + port + ' in use — another Conductor instance is running. This instance will handle MCP only.')
43
+ wss = null
44
+ resolve()
45
+ } else {
46
+ log('Server error:', e.message)
47
+ resolve()
48
+ }
49
+ })
50
+ } catch (e) {
51
+ log('Bridge start error:', e.message)
52
+ resolve()
53
+ }
39
54
  })
40
55
  }
41
56
 
42
- function stop() { if (wss) { wss.close(); wss = null } }
57
+ function stop() { if (wss && isOwner) { wss.close(); wss = null } }
43
58
 
44
59
  function isConnected() { return client !== null && client.readyState === 1 }
45
60
 
46
- function send(command, data, timeout = 15000) {
47
- return new Promise((resolve, reject) => {
48
- if (!isConnected()) return reject(new Error('Figma plugin not connected. Open Figma → Plugins → Development → Conductor'))
49
- const id = ++msgId
50
- const timer = setTimeout(() => {
61
+ function send(command, data, timeout) {
62
+ timeout = timeout || 15000
63
+ return new Promise(function(resolve, reject) {
64
+ if (!isConnected()) return reject(new Error('Figma plugin not connected. Run the Conductor plugin in Figma, and make sure the WebSocket server is running on port ' + port))
65
+ var id = ++msgId
66
+ var timer = setTimeout(function() {
51
67
  pending.delete(id)
52
- reject(new Error(`Timeout waiting for Figma response (${timeout}ms)`))
68
+ reject(new Error('Timeout waiting for Figma response (' + timeout + 'ms)'))
53
69
  }, timeout)
54
- pending.set(id, { resolve, reject, timer })
55
- client.send(JSON.stringify({ id, command, data }))
70
+ pending.set(id, { resolve: resolve, reject: reject, timer: timer })
71
+ client.send(JSON.stringify({ id: id, command: command, data: data }))
56
72
  })
57
73
  }
58
74
 
59
- return { start, stop, isConnected, send, getPort: () => port }
75
+ return { start: start, stop: stop, isConnected: isConnected, send: send, getPort: function() { return port } }
60
76
  }
package/src/server.js CHANGED
@@ -6,96 +6,110 @@ import { TOOL_LIST, TOOL_COUNT, getTool, CATEGORIES } from './tools/registry.js'
6
6
  import { handleTool } from './tools/handlers.js'
7
7
  import { createBridge } from './bridge.js'
8
8
 
9
- const VERSION = '3.0.0'
10
- const bridge = createBridge()
11
- bridge.start()
9
+ var VERSION = '3.0.2'
10
+ var bridge = null
11
+ var bridgeStarted = false
12
12
 
13
- function log(...args) { process.stderr.write('[conductor] ' + args.join(' ') + '\n') }
14
- log(`Conductor v${VERSION} starting — WebSocket on port ${bridge.getPort()}`)
13
+ function log() { process.stderr.write('[conductor] ' + Array.prototype.join.call(arguments, ' ') + '\n') }
14
+
15
+ function ensureBridge() {
16
+ if (!bridgeStarted) {
17
+ bridgeStarted = true
18
+ bridge = createBridge()
19
+ bridge.start()
20
+ }
21
+ return bridge
22
+ }
15
23
 
16
24
  // ─── JSON-RPC over stdio ───
17
- let buffer = ''
25
+ var buffer = ''
18
26
 
19
27
  process.stdin.setEncoding('utf8')
20
- process.stdin.on('data', chunk => {
28
+ process.stdin.on('data', function(chunk) {
21
29
  buffer += chunk
22
30
  while (true) {
23
- const headerEnd = buffer.indexOf('\r\n\r\n')
31
+ var headerEnd = buffer.indexOf('\r\n\r\n')
24
32
  if (headerEnd === -1) break
25
- const header = buffer.slice(0, headerEnd)
26
- const match = header.match(/Content-Length:\s*(\d+)/i)
33
+ var header = buffer.slice(0, headerEnd)
34
+ var match = header.match(/Content-Length:\s*(\d+)/i)
27
35
  if (!match) { buffer = buffer.slice(headerEnd + 4); continue }
28
- const len = parseInt(match[1])
29
- const bodyStart = headerEnd + 4
36
+ var len = parseInt(match[1])
37
+ var bodyStart = headerEnd + 4
30
38
  if (buffer.length < bodyStart + len) break
31
- const body = buffer.slice(bodyStart, bodyStart + len)
39
+ var body = buffer.slice(bodyStart, bodyStart + len)
32
40
  buffer = buffer.slice(bodyStart + len)
33
41
  try {
34
- const msg = JSON.parse(body)
42
+ var msg = JSON.parse(body)
35
43
  handleMessage(msg)
36
44
  } catch (e) { log('Parse error:', e.message) }
37
45
  }
38
46
  })
39
47
 
40
48
  function send(msg) {
41
- const json = JSON.stringify(msg)
42
- const out = `Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`
49
+ var json = JSON.stringify(msg)
50
+ var out = 'Content-Length: ' + Buffer.byteLength(json) + '\r\n\r\n' + json
43
51
  process.stdout.write(out)
44
52
  }
45
53
 
46
- function respond(id, result) { send({ jsonrpc: '2.0', id, result }) }
47
- function respondError(id, code, message) { send({ jsonrpc: '2.0', id, error: { code, message } }) }
54
+ function respond(id, result) { send({ jsonrpc: '2.0', id: id, result: result }) }
55
+ function respondError(id, code, message) { send({ jsonrpc: '2.0', id: id, error: { code: code, message: message } }) }
48
56
 
49
- async function handleMessage(msg) {
50
- const { id, method, params } = msg
57
+ function handleMessage(msg) {
58
+ var id = msg.id
59
+ var method = msg.method
60
+ var params = msg.params
51
61
 
52
62
  switch (method) {
53
63
  case 'initialize':
54
- return respond(id, {
64
+ // Start bridge in background, respond immediately
65
+ ensureBridge()
66
+ log('Conductor v' + VERSION + ' ready — ' + TOOL_COUNT + ' tools')
67
+ respond(id, {
55
68
  protocolVersion: '2024-11-05',
56
69
  capabilities: { tools: { listChanged: false } },
57
70
  serverInfo: { name: 'conductor-figma', version: VERSION },
58
71
  })
72
+ return
59
73
 
60
74
  case 'notifications/initialized':
61
- log(`Conductor v${VERSION} ready — ${TOOL_COUNT} tools across ${Object.keys(CATEGORIES).length} categories`)
62
75
  return
63
76
 
64
77
  case 'tools/list':
65
- return respond(id, {
66
- tools: TOOL_LIST.map(t => ({
67
- name: t.name,
68
- description: t.description,
69
- inputSchema: t.inputSchema,
70
- }))
78
+ respond(id, {
79
+ tools: TOOL_LIST.map(function(t) {
80
+ return { name: t.name, description: t.description, inputSchema: t.inputSchema }
81
+ })
71
82
  })
83
+ return
72
84
 
73
- case 'tools/call': {
74
- const { name, arguments: args } = params
75
- const tool = getTool(name)
76
- if (!tool) return respondError(id, -32601, `Unknown tool: ${name}`)
85
+ case 'tools/call':
86
+ var name = params.name
87
+ var args = params.arguments || {}
88
+ var tool = getTool(name)
89
+ if (!tool) { respondError(id, -32601, 'Unknown tool: ' + name); return }
77
90
 
78
- try {
79
- const result = await handleTool(name, args || {}, bridge)
80
- return respond(id, {
91
+ var b = ensureBridge()
92
+ handleTool(name, args, b).then(function(result) {
93
+ respond(id, {
81
94
  content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }]
82
95
  })
83
- } catch (e) {
84
- log(`Tool error [${name}]:`, e.message)
85
- return respond(id, {
86
- content: [{ type: 'text', text: `Error: ${e.message}` }],
96
+ }).catch(function(e) {
97
+ log('Tool error [' + name + ']:', e.message)
98
+ respond(id, {
99
+ content: [{ type: 'text', text: 'Error: ' + e.message }],
87
100
  isError: true,
88
101
  })
89
- }
90
- }
102
+ })
103
+ return
91
104
 
92
105
  case 'ping':
93
- return respond(id, {})
106
+ respond(id, {})
107
+ return
94
108
 
95
109
  default:
96
- if (id) respondError(id, -32601, `Unknown method: ${method}`)
110
+ if (id) respondError(id, -32601, 'Unknown method: ' + method)
97
111
  }
98
112
  }
99
113
 
100
- process.on('SIGINT', () => { if (bridge) bridge.stop(); process.exit(0) })
101
- process.on('SIGTERM', () => { if (bridge) bridge.stop(); process.exit(0) })
114
+ process.on('SIGINT', function() { if (bridge) bridge.stop(); process.exit(0) })
115
+ process.on('SIGTERM', function() { if (bridge) bridge.stop(); process.exit(0) })