@gethopp/figma-mcp-bridge 0.0.11 → 0.0.13
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 +48 -4
- package/dist/bridge.d.ts +13 -4
- package/dist/bridge.js +90 -20
- package/dist/bridge.js.map +1 -1
- package/dist/follower.d.ts +4 -3
- package/dist/follower.js +21 -3
- package/dist/follower.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/leader.js +16 -3
- package/dist/leader.js.map +1 -1
- package/dist/node.d.ts +4 -3
- package/dist/node.js +12 -5
- package/dist/node.js.map +1 -1
- package/dist/schema.d.ts +1889 -1
- package/dist/schema.js +550 -6
- package/dist/schema.js.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +311 -21
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,10 @@ The limit for free accounts is 6 requests per month, yes **per month**.
|
|
|
21
21
|
|
|
22
22
|
Figma MCP Bridge is a solution to this problem. It is a plugin + MCP server that streams live Figma document data to AI tools without hitting Figma API rate limits, so its Figma MCP for the rest of us ✊
|
|
23
23
|
|
|
24
|
+
It supports **multiple Figma files connected simultaneously**; open the plugin in each file and your AI agent can query any of them by `fileKey`. Single-file setups work exactly as before with no changes required.
|
|
25
|
+
|
|
26
|
+
It also includes a small, opt-in set of **write tools** for safe agent-driven edits — see [Editing Notes](#editing-notes) below.
|
|
27
|
+
|
|
24
28
|
## Demo
|
|
25
29
|
|
|
26
30
|
[Watch a demo of building a UI in Cursor with Figma MCP Bridge](https://youtu.be/ouygIhFBx0g)
|
|
@@ -53,12 +57,15 @@ Download the plugin from the [latest release](https://github.com/gethopp/figma-m
|
|
|
53
57
|
|
|
54
58
|
Open a Figma file, run the plugin, and start prompting your AI tool. The MCP server will automatically connect to the plugin.
|
|
55
59
|
|
|
60
|
+
To work across multiple files, just open the plugin in each Figma file. The bridge keeps all connections active and your AI agent can target any of them by `fileKey`.
|
|
61
|
+
|
|
56
62
|
If you want to know more about how it works, read the [How it works](#how-it-works) section.
|
|
57
63
|
|
|
58
64
|
## Available Tools
|
|
59
65
|
|
|
60
66
|
| Tool | Description |
|
|
61
67
|
|------|-------------|
|
|
68
|
+
| `list_files` | List all connected Figma files (supports multi-file workflows) |
|
|
62
69
|
| `get_document` | Get the current Figma page document tree |
|
|
63
70
|
| `get_selection` | Get the currently selected nodes in Figma |
|
|
64
71
|
| `get_node` | Get a specific Figma node by ID (colon format, e.g. `4029:12345`) |
|
|
@@ -68,6 +75,43 @@ If you want to know more about how it works, read the [How it works](#how-it-wor
|
|
|
68
75
|
| `get_variable_defs` | Get all variable collections, modes, and values (design tokens) |
|
|
69
76
|
| `get_screenshot` | Export nodes as PNG/SVG/JPG/PDF (base64-encoded) |
|
|
70
77
|
| `save_screenshots` | Export and save screenshots directly to the local filesystem |
|
|
78
|
+
| `set_node_visibility` | Show or hide specific nodes |
|
|
79
|
+
| `set_text_content` | Replace the contents of a text node |
|
|
80
|
+
| `set_text_properties` | Patch font, size, alignment, auto-resize, color, and bounds on a text node |
|
|
81
|
+
| `set_node_properties` | Patch common node properties: name, position, size, visibility, opacity, corner radius |
|
|
82
|
+
| `set_solid_fill` | Replace a node's fill or stroke with a single solid paint |
|
|
83
|
+
| `set_gradient_fill` | Replace a node's fill or stroke with a linear/radial/angular/diamond gradient |
|
|
84
|
+
| `set_effects` | Replace a node's effects list (drop/inner shadows, layer/background blurs) |
|
|
85
|
+
| `set_stroke_properties` | Patch stroke weight, align, dash pattern, cap, and join |
|
|
86
|
+
| `set_auto_layout` | Configure auto-layout direction, padding, gap, alignment, sizing, and wrap |
|
|
87
|
+
| `create_frame` | Create a new frame, optionally under a parent |
|
|
88
|
+
| `create_text` | Create a new text node |
|
|
89
|
+
| `create_shape` | Create a rectangle, ellipse, or line |
|
|
90
|
+
| `create_image` | Create an image-backed rectangle from a local path, URL, or data URI |
|
|
91
|
+
| `duplicate_nodes` | Duplicate nodes in place |
|
|
92
|
+
| `reparent_nodes` | Move nodes into another parent |
|
|
93
|
+
| `group_nodes` | Wrap a list of nodes (sharing a parent) in a new group |
|
|
94
|
+
| `ungroup_node` | Ungroup a group or frame — children move up to its parent |
|
|
95
|
+
| `set_selection` | Set the page selection to a list of node IDs (works in Dev Mode) |
|
|
96
|
+
| `scroll_and_zoom_into_view` | Frame the viewport around the given nodes (works in Dev Mode) |
|
|
97
|
+
| `delete_nodes` | Delete nodes with explicit confirmation |
|
|
98
|
+
|
|
99
|
+
All tools accept an optional `fileKey` parameter when multiple Figma files are connected. Use `list_files` to discover connected files and their keys.
|
|
100
|
+
|
|
101
|
+
### Editing Notes
|
|
102
|
+
|
|
103
|
+
- Edit tools work only when the plugin is opened in Figma's design editor (Dev Mode is read-only — they will return a clear error there).
|
|
104
|
+
- The current user must have permission to edit the target file.
|
|
105
|
+
- `delete_nodes` is intentionally gated behind `confirm: true`.
|
|
106
|
+
- Text edits automatically load the fonts currently used by the target text node before applying the new content.
|
|
107
|
+
- New text nodes default to `Inter Regular` unless a font is provided.
|
|
108
|
+
- `create_image` reads local paths relative to the MCP server working directory unless you pass an absolute path.
|
|
109
|
+
|
|
110
|
+
### What You Can Build
|
|
111
|
+
|
|
112
|
+
With the current write surface, an agent can build a basic slide deck in a new empty Figma file: create slide frames, style titles and body copy, lay out rectangles/ellipses/lines for cards and dividers, duplicate slide templates, reparent content into the right frame, and adjust common geometry/visual properties — including solid/gradient paints, shadows and blurs, stroke geometry, and auto-layout configuration.
|
|
113
|
+
|
|
114
|
+
The current version is intentionally limited — no components/instances, no variables/styles authoring, no per-segment text styling, and no vector boolean operations yet.
|
|
71
115
|
|
|
72
116
|
## Local development
|
|
73
117
|
|
|
@@ -129,10 +173,10 @@ The Figma plugin is the user interface for the Figma MCP Bridge. You run this in
|
|
|
129
173
|
|
|
130
174
|
### 2. The MCP Server
|
|
131
175
|
|
|
132
|
-
The MCP server is the core of the Figma MCP Bridge.
|
|
133
|
-
- Handling WebSocket connections from
|
|
134
|
-
-
|
|
135
|
-
-
|
|
176
|
+
The MCP server is the core of the Figma MCP Bridge. It maintains a registry of WebSocket connections keyed by `fileKey`, so multiple Figma files can be connected simultaneously. The server is responsible for:
|
|
177
|
+
- Handling WebSocket connections from one or more Figma plugin instances
|
|
178
|
+
- Routing tool calls to the correct file based on `fileKey`
|
|
179
|
+
- Forwarding responses back to the AI client
|
|
136
180
|
- Handling leader election (as we can have only one WS connection to an MCP server at a time)
|
|
137
181
|
|
|
138
182
|
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
2
|
import type { Duplex } from "node:stream";
|
|
3
|
-
import type { BridgeResponse } from "./types.js";
|
|
3
|
+
import type { BridgeResponse, ConnectedFile } from "./types.js";
|
|
4
4
|
export declare class Bridge {
|
|
5
5
|
private wss;
|
|
6
|
-
private
|
|
6
|
+
private connections;
|
|
7
7
|
private pending;
|
|
8
8
|
private counter;
|
|
9
9
|
constructor();
|
|
10
10
|
handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer): void;
|
|
11
11
|
private handleConnection;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
private rejectPendingForSocket;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve which connection to use.
|
|
15
|
+
* - If fileKey is provided, use that specific connection.
|
|
16
|
+
* - If only one file is connected and no fileKey given, use it (backward compat).
|
|
17
|
+
* - If multiple files connected and no fileKey, throw with a helpful message.
|
|
18
|
+
*/
|
|
19
|
+
private resolveConnection;
|
|
20
|
+
listConnectedFiles(): ConnectedFile[];
|
|
21
|
+
send(requestType: string, nodeIds?: string[], fileKey?: string): Promise<BridgeResponse>;
|
|
22
|
+
sendWithParams(requestType: string, nodeIds?: string[], params?: Record<string, unknown>, fileKey?: string): Promise<BridgeResponse>;
|
|
14
23
|
private nextId;
|
|
15
24
|
close(): void;
|
|
16
25
|
}
|
package/dist/bridge.js
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import { WebSocketServer, WebSocket } from "ws";
|
|
2
2
|
export class Bridge {
|
|
3
3
|
wss;
|
|
4
|
-
|
|
4
|
+
connections = new Map();
|
|
5
5
|
pending = new Map();
|
|
6
6
|
counter = 0;
|
|
7
7
|
constructor() {
|
|
8
8
|
this.wss = new WebSocketServer({ noServer: true });
|
|
9
9
|
}
|
|
10
10
|
handleUpgrade(request, socket, head) {
|
|
11
|
+
if (request.url == undefined) {
|
|
12
|
+
console.error("Plugin connected without url, rejecting");
|
|
13
|
+
socket.destroy();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const url = new URL(request.url, "http://localhost");
|
|
17
|
+
const { fileKey, fileName = "Unknown" } = Object.fromEntries(url.searchParams);
|
|
18
|
+
if (!fileKey) {
|
|
19
|
+
console.error("Plugin connected without fileKey, rejecting");
|
|
20
|
+
socket.destroy();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
11
23
|
this.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
12
|
-
this.handleConnection(ws);
|
|
24
|
+
this.handleConnection(ws, fileKey, fileName);
|
|
13
25
|
});
|
|
14
26
|
}
|
|
15
|
-
handleConnection(ws) {
|
|
16
|
-
// Replace existing connection
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
handleConnection(ws, fileKey, fileName) {
|
|
28
|
+
// Replace existing connection for the same file
|
|
29
|
+
const existing = this.connections.get(fileKey);
|
|
30
|
+
if (existing) {
|
|
31
|
+
existing.ws.close();
|
|
19
32
|
}
|
|
20
|
-
this.
|
|
33
|
+
this.connections.set(fileKey, { ws, fileKey, fileName });
|
|
34
|
+
console.error(`Plugin connected: ${fileName} (${fileKey})`);
|
|
21
35
|
ws.on("message", (data) => {
|
|
22
36
|
try {
|
|
23
37
|
const resp = JSON.parse(data.toString());
|
|
@@ -33,23 +47,79 @@ export class Bridge {
|
|
|
33
47
|
}
|
|
34
48
|
});
|
|
35
49
|
ws.on("close", () => {
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
const current = this.connections.get(fileKey);
|
|
51
|
+
if (current?.ws === ws) {
|
|
52
|
+
this.connections.delete(fileKey);
|
|
53
|
+
console.error(`Plugin disconnected: ${fileName} (${fileKey})`);
|
|
38
54
|
}
|
|
55
|
+
this.rejectPendingForSocket(ws, `Plugin disconnected: ${fileName} (${fileKey})`);
|
|
39
56
|
});
|
|
40
57
|
ws.on("error", (err) => {
|
|
41
58
|
console.error("WebSocket error:", err.message);
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
const current = this.connections.get(fileKey);
|
|
60
|
+
if (current?.ws === ws) {
|
|
61
|
+
this.connections.delete(fileKey);
|
|
44
62
|
}
|
|
63
|
+
this.rejectPendingForSocket(ws, `Plugin connection error (${fileName}): ${err.message}`);
|
|
45
64
|
});
|
|
46
65
|
}
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
rejectPendingForSocket(ws, reason) {
|
|
67
|
+
for (const [id, p] of this.pending) {
|
|
68
|
+
if (p.ws === ws) {
|
|
69
|
+
clearTimeout(p.timeout);
|
|
70
|
+
this.pending.delete(id);
|
|
71
|
+
p.reject(new Error(reason));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
49
74
|
}
|
|
50
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Resolve which connection to use.
|
|
77
|
+
* - If fileKey is provided, use that specific connection.
|
|
78
|
+
* - If only one file is connected and no fileKey given, use it (backward compat).
|
|
79
|
+
* - If multiple files connected and no fileKey, throw with a helpful message.
|
|
80
|
+
*/
|
|
81
|
+
resolveConnection(fileKey) {
|
|
82
|
+
if (fileKey) {
|
|
83
|
+
const entry = this.connections.get(fileKey);
|
|
84
|
+
if (!entry) {
|
|
85
|
+
const available = this.listConnectedFiles();
|
|
86
|
+
const hint = available.length > 0
|
|
87
|
+
? ` Connected files: ${available.map((f) => `"${f.fileName}" (fileKey: ${f.fileKey})`).join(", ")}`
|
|
88
|
+
: " No files are currently connected.";
|
|
89
|
+
throw new Error(`No plugin connected for fileKey "${fileKey}".${hint}`);
|
|
90
|
+
}
|
|
91
|
+
return entry.ws;
|
|
92
|
+
}
|
|
93
|
+
if (this.connections.size === 0) {
|
|
94
|
+
throw new Error("No plugin connected. Open a Figma file and run the bridge plugin.");
|
|
95
|
+
}
|
|
96
|
+
if (this.connections.size === 1) {
|
|
97
|
+
const entry = this.connections.values().next().value;
|
|
98
|
+
return entry.ws;
|
|
99
|
+
}
|
|
100
|
+
const files = this.listConnectedFiles();
|
|
101
|
+
throw new Error(`Multiple files connected. Specify a fileKey to choose which file to query. Connected files: ${files.map((f) => `"${f.fileName}" (fileKey: ${f.fileKey})`).join(", ")}. Use the list_files tool to see all connected files.`);
|
|
102
|
+
}
|
|
103
|
+
listConnectedFiles() {
|
|
104
|
+
return [...this.connections.values()].map((entry) => ({
|
|
105
|
+
fileKey: entry.fileKey,
|
|
106
|
+
fileName: entry.fileName,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
send(requestType, nodeIds, fileKey) {
|
|
110
|
+
return this.sendWithParams(requestType, nodeIds, undefined, fileKey);
|
|
111
|
+
}
|
|
112
|
+
sendWithParams(requestType, nodeIds, params, fileKey) {
|
|
51
113
|
return new Promise((resolve, reject) => {
|
|
52
|
-
|
|
114
|
+
let conn;
|
|
115
|
+
try {
|
|
116
|
+
conn = this.resolveConnection(fileKey);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
reject(err);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (conn.readyState !== WebSocket.OPEN) {
|
|
53
123
|
reject(new Error("Plugin not connected"));
|
|
54
124
|
return;
|
|
55
125
|
}
|
|
@@ -68,8 +138,8 @@ export class Bridge {
|
|
|
68
138
|
this.pending.delete(requestId);
|
|
69
139
|
reject(new Error("Request timed out"));
|
|
70
140
|
}, 30_000);
|
|
71
|
-
this.pending.set(requestId, { resolve, reject, timeout });
|
|
72
|
-
|
|
141
|
+
this.pending.set(requestId, { resolve, reject, timeout, ws: conn });
|
|
142
|
+
conn.send(JSON.stringify(request), (err) => {
|
|
73
143
|
if (err) {
|
|
74
144
|
clearTimeout(timeout);
|
|
75
145
|
this.pending.delete(requestId);
|
|
@@ -93,10 +163,10 @@ export class Bridge {
|
|
|
93
163
|
reject(new Error("Bridge closed"));
|
|
94
164
|
}
|
|
95
165
|
this.pending.clear();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.conn = null;
|
|
166
|
+
for (const [, entry] of this.connections) {
|
|
167
|
+
entry.ws.close();
|
|
99
168
|
}
|
|
169
|
+
this.connections.clear();
|
|
100
170
|
this.wss.close();
|
|
101
171
|
}
|
|
102
172
|
}
|
package/dist/bridge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAkBhD,MAAM,OAAO,MAAM;IACT,GAAG,CAAkB;IACrB,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;IACjD,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAC5C,OAAO,GAAG,CAAC,CAAC;IAEpB;QACE,IAAI,CAAC,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,aAAa,CAAC,OAAwB,EAAE,MAAc,EAAE,IAAY;QAClE,IAAI,OAAO,CAAC,GAAG,IAAI,SAAS,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACzD,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACrD,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,SAAS,EAAE,GAAG,MAAM,CAAC,WAAW,CAC1D,GAAG,CAAC,YAAY,CACjB,CAAC;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;YAC7D,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE;YACnD,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,gBAAgB,CACtB,EAAa,EACb,OAAe,EACf,QAAgB;QAEhB,gDAAgD;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,KAAK,CAAC,qBAAqB,QAAQ,KAAK,OAAO,GAAG,CAAC,CAAC;QAE5D,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAmB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACjD,IAAI,OAAO,EAAE,CAAC;oBACZ,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;oBAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACpC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;YAChD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;gBACvB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACjC,OAAO,CAAC,KAAK,CAAC,wBAAwB,QAAQ,KAAK,OAAO,GAAG,CAAC,CAAC;YACjE,CAAC;YACD,IAAI,CAAC,sBAAsB,CACzB,EAAE,EACF,wBAAwB,QAAQ,KAAK,OAAO,GAAG,CAChD,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACrB,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;gBACvB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC;YACD,IAAI,CAAC,sBAAsB,CACzB,EAAE,EACF,4BAA4B,QAAQ,MAAM,GAAG,CAAC,OAAO,EAAE,CACxD,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,sBAAsB,CAAC,EAAa,EAAE,MAAc;QAC1D,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACnC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBAChB,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBACxB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACxB,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,iBAAiB,CAAC,OAAgB;QACxC,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,SAAS,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC5C,MAAM,IAAI,GACR,SAAS,CAAC,MAAM,GAAG,CAAC;oBAClB,CAAC,CAAC,qBAAqB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,eAAe,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACnG,CAAC,CAAC,oCAAoC,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC;YAC1E,CAAC;YACD,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,CAAC;QAED,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC;YACtD,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,+FAA+F,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,eAAe,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,uDAAuD,CAC7N,CAAC;IACJ,CAAC;IAED,kBAAkB;QAChB,OAAO,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACpD,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC,CAAC,CAAC;IACN,CAAC;IAED,IAAI,CACF,WAAmB,EACnB,OAAkB,EAClB,OAAgB;QAEhB,OAAO,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;IAED,cAAc,CACZ,WAAmB,EACnB,OAAkB,EAClB,MAAgC,EAChC,OAAgB;QAEhB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,IAAe,CAAC;YACpB,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,OAAO;YACT,CAAC;YAED,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACvC,MAAM,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;gBAC1C,OAAO;YACT,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,MAAM,OAAO,GAAkB;gBAC7B,IAAI,EAAE,WAAW;gBACjB,SAAS;aACV,CAAC;YACF,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;YAC5B,CAAC;YACD,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7C,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;YAC1B,CAAC;YAED,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC/B,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;YACzC,CAAC,EAAE,MAAM,CAAC,CAAC;YAEX,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;YAEpE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE;gBACzC,IAAI,GAAG,EAAE,CAAC;oBACR,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;oBAC/B,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACnD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACrD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACrD,OAAO,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;IAC/C,CAAC;IAED,KAAK;QACH,8BAA8B;QAC9B,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrD,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAErB,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACzC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF"}
|
package/dist/follower.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { BridgeResponse } from "./types.js";
|
|
1
|
+
import type { BridgeResponse, ConnectedFile } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Follower proxies MCP tool calls to the leader via HTTP /rpc.
|
|
4
4
|
*/
|
|
5
5
|
export declare class Follower {
|
|
6
6
|
private leaderUrl;
|
|
7
7
|
constructor(leaderUrl: string);
|
|
8
|
-
send(requestType: string, nodeIds?: string[]): Promise<BridgeResponse>;
|
|
9
|
-
sendWithParams(requestType: string, nodeIds?: string[], params?: Record<string, unknown
|
|
8
|
+
send(requestType: string, nodeIds?: string[], fileKey?: string): Promise<BridgeResponse>;
|
|
9
|
+
sendWithParams(requestType: string, nodeIds?: string[], params?: Record<string, unknown>, fileKey?: string): Promise<BridgeResponse>;
|
|
10
|
+
listConnectedFiles(): Promise<ConnectedFile[]>;
|
|
10
11
|
ping(): Promise<boolean>;
|
|
11
12
|
}
|
package/dist/follower.js
CHANGED
|
@@ -6,15 +6,17 @@ export class Follower {
|
|
|
6
6
|
constructor(leaderUrl) {
|
|
7
7
|
this.leaderUrl = leaderUrl;
|
|
8
8
|
}
|
|
9
|
-
send(requestType, nodeIds) {
|
|
10
|
-
return this.sendWithParams(requestType, nodeIds);
|
|
9
|
+
send(requestType, nodeIds, fileKey) {
|
|
10
|
+
return this.sendWithParams(requestType, nodeIds, undefined, fileKey);
|
|
11
11
|
}
|
|
12
|
-
async sendWithParams(requestType, nodeIds, params) {
|
|
12
|
+
async sendWithParams(requestType, nodeIds, params, fileKey) {
|
|
13
13
|
const rpcReq = { tool: requestType };
|
|
14
14
|
if (nodeIds && nodeIds.length > 0)
|
|
15
15
|
rpcReq.nodeIds = nodeIds;
|
|
16
16
|
if (params && Object.keys(params).length > 0)
|
|
17
17
|
rpcReq.params = params;
|
|
18
|
+
if (fileKey)
|
|
19
|
+
rpcReq.fileKey = fileKey;
|
|
18
20
|
const response = await fetch(`${this.leaderUrl}/rpc`, {
|
|
19
21
|
method: "POST",
|
|
20
22
|
headers: { "Content-Type": "application/json" },
|
|
@@ -34,6 +36,22 @@ export class Follower {
|
|
|
34
36
|
data: rpcResp.data,
|
|
35
37
|
};
|
|
36
38
|
}
|
|
39
|
+
async listConnectedFiles() {
|
|
40
|
+
const response = await fetch(`${this.leaderUrl}/rpc`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { "Content-Type": "application/json" },
|
|
43
|
+
body: JSON.stringify({ tool: "list_files" }),
|
|
44
|
+
signal: AbortSignal.timeout(5_000),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`Leader returned status ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
const rpcResp = (await response.json());
|
|
50
|
+
if (rpcResp.error) {
|
|
51
|
+
throw new Error(rpcResp.error);
|
|
52
|
+
}
|
|
53
|
+
return rpcResp.data ?? [];
|
|
54
|
+
}
|
|
37
55
|
async ping() {
|
|
38
56
|
try {
|
|
39
57
|
const response = await fetch(`${this.leaderUrl}/ping`, {
|
package/dist/follower.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"follower.js","sourceRoot":"","sources":["../src/follower.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,QAAQ;IACC;IAApB,YAAoB,SAAiB;QAAjB,cAAS,GAAT,SAAS,CAAQ;IAAG,CAAC;IAEzC,IAAI,CACF,WAAmB,EACnB,OAAkB;
|
|
1
|
+
{"version":3,"file":"follower.js","sourceRoot":"","sources":["../src/follower.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ async function main() {
|
|
|
24
24
|
name: "figma-bridge",
|
|
25
25
|
version: VERSION,
|
|
26
26
|
});
|
|
27
|
-
registerTools(server, node);
|
|
27
|
+
registerTools(server, node, PORT);
|
|
28
28
|
console.error(`Starting MCP server (role: ${node.roleName})`);
|
|
29
29
|
const transport = new StdioServerTransport();
|
|
30
30
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,IAAI,GAAG,IAAI,CAAC;AAElB,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEvB,oBAAoB;IACpB,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;QAClC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAEhC,sCAAsC;IACtC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,IAAI,GAAG,IAAI,CAAC;AAElB,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEvB,oBAAoB;IACpB,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;QAClC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAEhC,sCAAsC;IACtC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAElC,OAAO,CAAC,KAAK,CAAC,8BAA8B,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;IAE9D,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/leader.js
CHANGED
|
@@ -37,7 +37,8 @@ export class Leader {
|
|
|
37
37
|
res.end("Not found");
|
|
38
38
|
});
|
|
39
39
|
server.on("upgrade", (req, socket, head) => {
|
|
40
|
-
|
|
40
|
+
const pathname = new URL(req.url ?? "", "http://localhost").pathname;
|
|
41
|
+
if (pathname === "/ws") {
|
|
41
42
|
this.bridge.handleUpgrade(req, socket, head);
|
|
42
43
|
}
|
|
43
44
|
else {
|
|
@@ -65,20 +66,32 @@ export class Leader {
|
|
|
65
66
|
req.on("end", async () => {
|
|
66
67
|
try {
|
|
67
68
|
const rpcReq = JSON.parse(body);
|
|
69
|
+
// Handle list_files as a special RPC (not forwarded to plugin)
|
|
70
|
+
if (rpcReq.tool === "list_files") {
|
|
71
|
+
this.sendJSON(res, 200, {
|
|
72
|
+
data: this.bridge.listConnectedFiles(),
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
68
76
|
const validationError = validateRpc(rpcReq.tool, rpcReq.nodeIds, rpcReq.params);
|
|
69
77
|
if (validationError) {
|
|
70
78
|
this.sendJSON(res, 400, { error: validationError });
|
|
71
79
|
return;
|
|
72
80
|
}
|
|
81
|
+
const fileKey = rpcReq.fileKey;
|
|
73
82
|
// Currently the only tool that is not forwarded to the plugin is save_screenshots
|
|
74
83
|
// If more are added we need to refactor to a better abstraction.
|
|
75
84
|
if (rpcReq.tool === "save_screenshots") {
|
|
76
85
|
const params = rpcReq.params ?? {};
|
|
77
|
-
|
|
86
|
+
// Create a sender bound to the specific fileKey
|
|
87
|
+
const sender = {
|
|
88
|
+
sendWithParams: (requestType, nodeIds, sendParams) => this.bridge.sendWithParams(requestType, nodeIds, sendParams, fileKey),
|
|
89
|
+
};
|
|
90
|
+
const result = await executeSaveScreenshots(sender, params.items, params.format, params.scale);
|
|
78
91
|
this.sendJSON(res, 200, { data: result });
|
|
79
92
|
return;
|
|
80
93
|
}
|
|
81
|
-
const resp = await this.bridge.sendWithParams(rpcReq.tool, rpcReq.nodeIds, rpcReq.params);
|
|
94
|
+
const resp = await this.bridge.sendWithParams(rpcReq.tool, rpcReq.nodeIds, rpcReq.params, fileKey);
|
|
82
95
|
this.sendJSON(res, 200, resp.error ? { error: resp.error } : { data: resp.data });
|
|
83
96
|
}
|
|
84
97
|
catch (err) {
|
package/dist/leader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leader.js","sourceRoot":"","sources":["../src/leader.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,OAAO,MAAM;IAIG;IAHZ,MAAM,CAAS;IACf,MAAM,GAAuB,IAAI,CAAC;IAE1C,YAAoB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC7B,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,KAAK;QACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBAC5C,IAAI,GAAG,CAAC,GAAG,KAAK,OAAO,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;oBAChD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;oBAC5D,OAAO;gBACT,CAAC;gBAED,IAAI,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;oBAChD,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;oBACzB,OAAO;gBACT,CAAC;gBAED,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACvB,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CACP,SAAS,EACT,CAAC,GAAyB,EAAE,MAAc,EAAE,IAAY,EAAE,EAAE;gBAC1D,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"leader.js","sourceRoot":"","sources":["../src/leader.ts"],"names":[],"mappings":"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"}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Role } from "./types.js";
|
|
2
|
-
import type { BridgeResponse } from "./types.js";
|
|
2
|
+
import type { BridgeResponse, ConnectedFile } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Node is the dynamic handler that switches between leader and follower roles.
|
|
5
5
|
* It routes MCP tool calls to the appropriate backend based on its current role.
|
|
@@ -12,8 +12,9 @@ export declare class Node {
|
|
|
12
12
|
constructor(port: number);
|
|
13
13
|
get role(): Role;
|
|
14
14
|
get roleName(): string;
|
|
15
|
-
send(requestType: string, nodeIds?: string[]): Promise<BridgeResponse>;
|
|
16
|
-
sendWithParams(requestType: string, nodeIds?: string[], params?: Record<string, unknown
|
|
15
|
+
send(requestType: string, nodeIds?: string[], fileKey?: string): Promise<BridgeResponse>;
|
|
16
|
+
sendWithParams(requestType: string, nodeIds?: string[], params?: Record<string, unknown>, fileKey?: string): Promise<BridgeResponse>;
|
|
17
|
+
listConnectedFiles(): ConnectedFile[] | undefined;
|
|
17
18
|
becomeLeader(): Promise<void>;
|
|
18
19
|
becomeFollower(): void;
|
|
19
20
|
stop(): void;
|
package/dist/node.js
CHANGED
|
@@ -27,16 +27,23 @@ export class Node {
|
|
|
27
27
|
return "UNKNOWN";
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
send(requestType, nodeIds) {
|
|
31
|
-
return this.sendWithParams(requestType, nodeIds);
|
|
30
|
+
send(requestType, nodeIds, fileKey) {
|
|
31
|
+
return this.sendWithParams(requestType, nodeIds, undefined, fileKey);
|
|
32
32
|
}
|
|
33
|
-
sendWithParams(requestType, nodeIds, params) {
|
|
33
|
+
sendWithParams(requestType, nodeIds, params, fileKey) {
|
|
34
34
|
if (this._role === Role.Leader && this.leader) {
|
|
35
35
|
return this.leader
|
|
36
36
|
.getBridge()
|
|
37
|
-
.sendWithParams(requestType, nodeIds, params);
|
|
37
|
+
.sendWithParams(requestType, nodeIds, params, fileKey);
|
|
38
38
|
}
|
|
39
|
-
return this.follower.sendWithParams(requestType, nodeIds, params);
|
|
39
|
+
return this.follower.sendWithParams(requestType, nodeIds, params, fileKey);
|
|
40
|
+
}
|
|
41
|
+
listConnectedFiles() {
|
|
42
|
+
if (this._role === Role.Leader && this.leader) {
|
|
43
|
+
return this.leader.getBridge().listConnectedFiles();
|
|
44
|
+
}
|
|
45
|
+
// Followers return undefined — the tool handler falls back to RPC
|
|
46
|
+
return undefined;
|
|
40
47
|
}
|
|
41
48
|
async becomeLeader() {
|
|
42
49
|
if (this._role === Role.Leader)
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGlC;;;GAGG;AACH,MAAM,OAAO,IAAI;IAKK;IAJZ,KAAK,GAAS,IAAI,CAAC,OAAO,CAAC;IAC3B,MAAM,GAAkB,IAAI,CAAC;IAC7B,QAAQ,CAAW;IAE3B,YAAoB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,QAAQ;QACV,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,KAAK,IAAI,CAAC,MAAM;gBACd,OAAO,QAAQ,CAAC;YAClB,KAAK,IAAI,CAAC,QAAQ;gBAChB,OAAO,UAAU,CAAC;YACpB;gBACE,OAAO,SAAS,CAAC;QACrB,CAAC;IACH,CAAC;IAED,IAAI,CACF,WAAmB,EACnB,OAAkB;
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGlC;;;GAGG;AACH,MAAM,OAAO,IAAI;IAKK;IAJZ,KAAK,GAAS,IAAI,CAAC,OAAO,CAAC;IAC3B,MAAM,GAAkB,IAAI,CAAC;IAC7B,QAAQ,CAAW;IAE3B,YAAoB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,QAAQ;QACV,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,KAAK,IAAI,CAAC,MAAM;gBACd,OAAO,QAAQ,CAAC;YAClB,KAAK,IAAI,CAAC,QAAQ;gBAChB,OAAO,UAAU,CAAC;YACpB;gBACE,OAAO,SAAS,CAAC;QACrB,CAAC;IACH,CAAC;IAED,IAAI,CACF,WAAmB,EACnB,OAAkB,EAClB,OAAgB;QAEhB,OAAO,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;IAED,cAAc,CACZ,WAAmB,EACnB,OAAkB,EAClB,MAAgC,EAChC,OAAgB;QAEhB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9C,OAAO,IAAI,CAAC,MAAM;iBACf,SAAS,EAAE;iBACX,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7E,CAAC;IAED,kBAAkB;QAChB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9C,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,kBAAkB,EAAE,CAAC;QACtD,CAAC;QACD,kEAAkE;QAClE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO;QAEvC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QAErB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACjC,CAAC;IAED,cAAc;QACZ,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEzC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACnC,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC;IAC5B,CAAC;CACF"}
|