Commit b5724ad
authored
feat: add progress notifications and async tree walker (#305)
* refactor!: switch HTTP transport to stateless mode
Each request creates its own McpServer and transport with
sessionIdGenerator: undefined. Removes the session registry,
SSE transport, /messages endpoint, and progress notification
interval. StreamableHTTP is served at both /mcp and /sse for
backward compatibility. GET and DELETE return 405.
* docs: update transport references after stateless HTTP refactor
* fix: catch async errors in Express 4 route handler
Express 4 does not catch rejected promises from async handlers.
Without a try/catch, a failure in connect() or handleRequest()
causes an unhandled rejection that crashes the process.
* chore: upgrade Express to v5
Aligns with the MCP SDK which already depends on Express 5.
Express 5 natively catches async route handler rejections,
so the manual try/catch added in the previous commit is no
longer needed.
* fix: add per-request cleanup and JSON-RPC error middleware
Close transport and McpServer when the response ends, matching
the SDK's recommended pattern for stateless servers. Add Express
error-handling middleware that returns a JSON-RPC error response
instead of Express's default HTML 500.
* fix: use SDK's createMcpExpressApp for DNS rebinding protection
Replace raw express() with the SDK's createMcpExpressApp(), which
applies localhost DNS rebinding protection middleware automatically.
This also handles express.json() globally, removing the need for
per-route body parsing.
* feat: add progress notifications to tool handlers
Thread the SDK's RequestHandlerExtra through registerTool wiring
to tool handlers. Add sendProgress helper that sends
notifications/progress when the client provides a progressToken.
get_figma_data reports progress after fetching from the Figma API
and after processing design data (2 phases).
download_figma_images reports progress after resolving download
items and after completing all downloads (2 phases).
* refactor: extract sendProgress helper into mcp/progress.ts
Break the circular dependency where tools imported from their
own registration module (mcp/index.ts -> tools -> mcp/index.ts).
* fix: send initial progress notification before slow I/O
Progress was only sent after the Figma API fetch completed,
which defeats the purpose — the timeout fires during the fetch.
Now sends progress at 0/3 before the slow operation starts, so
clients with resetTimeoutOnProgress get an immediate signal.
* feat: add progress heartbeat during long Figma API calls
Figma API calls can take up to ~55 seconds. A single progress
notification before the call isn't enough if the client timeout
is shorter. startProgressHeartbeat sends periodic notifications
every 5 seconds during slow I/O, keeping clients with
resetTimeoutOnProgress alive. The heartbeat stops as soon as
the operation completes or errors.
* fix: add progress between synchronous processing phases
The tree walker and YAML serializer are synchronous and block
the event loop, preventing heartbeat intervals from firing.
Send progress notifications between each phase so the client
gets a fresh timeout window before each synchronous step:
API fetch -> simplify -> serialize -> return.
* feat: make tree walker async to unblock event loop
The synchronous tree walk blocks the event loop for large Figma
files (75MB+), preventing progress heartbeats from firing, SIGINT
from being handled, and the HTTP server from shutting down cleanly.
Make extractFromDesign and simplifyRawFigmaObject async. The tree
walker yields the event loop every 500 nodes via setImmediate,
allowing heartbeats and signal handlers to run during processing.
Also fix progress total (3 -> 4), add diagnostic logging to
sendProgress, and force-close keep-alive connections on shutdown.
* fix: run heartbeat during simplification phase
The heartbeat was only active during the Figma API call but not
during the tree walk simplification, which is the actual slow
part for large files. Now that the tree walker yields the event
loop, the heartbeat can fire during simplification.
* feat: show node count in heartbeat during simplification
Report how many nodes have been processed in each heartbeat
message (e.g. "Simplifying design data (3500 nodes processed)").
The heartbeat message function is now evaluated dynamically on
each tick.
* fix: tighten yield interval and heartbeat frequency
Reduce yield interval from 500 to 100 nodes — later nodes in
large files are deeper and more complex, so 500 nodes could
take longer than the heartbeat interval. Reduce heartbeat from
5s to 3s for more margin against 10s client timeouts.
* fix: gracefully close MCP connections before server shutdown
Track active per-request transport/server pairs. On shutdown,
close each transport and server cleanly before terminating HTTP
connections. This gives clients a proper stream close instead of
an abrupt disconnect.
* chore: remove diagnostic logging from sendProgress
* test: add tree walker regression tests
Test extractFromDesign and simplifyRawFigmaObject with a
representative node tree covering visibility filtering, depth
limits, child recursion, VECTOR->IMAGE-SVG type mapping, and
global style variable accumulation. Verified against both the
original sync and new async implementations to confirm the
async conversion produces identical output.1 parent 9dfb1cb commit b5724ad
10 files changed
Lines changed: 317 additions & 32 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
12 | 30 | | |
13 | 31 | | |
14 | 32 | | |
| |||
18 | 36 | | |
19 | 37 | | |
20 | 38 | | |
21 | | - | |
| 39 | + | |
22 | 40 | | |
23 | 41 | | |
24 | 42 | | |
25 | 43 | | |
26 | | - | |
| 44 | + | |
27 | 45 | | |
28 | 46 | | |
29 | 47 | | |
30 | 48 | | |
31 | 49 | | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
36 | 58 | | |
37 | 59 | | |
38 | 60 | | |
| |||
43 | 65 | | |
44 | 66 | | |
45 | 67 | | |
46 | | - | |
| 68 | + | |
47 | 69 | | |
48 | 70 | | |
49 | 71 | | |
50 | 72 | | |
51 | | - | |
| 73 | + | |
52 | 74 | | |
53 | 75 | | |
54 | 76 | | |
55 | 77 | | |
| 78 | + | |
| 79 | + | |
56 | 80 | | |
57 | 81 | | |
58 | 82 | | |
| |||
75 | 99 | | |
76 | 100 | | |
77 | 101 | | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
82 | 108 | | |
83 | 109 | | |
84 | 110 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
60 | | - | |
61 | | - | |
| 61 | + | |
| 62 | + | |
62 | 63 | | |
63 | 64 | | |
64 | 65 | | |
| |||
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
73 | | - | |
74 | | - | |
| 74 | + | |
| 75 | + | |
75 | 76 | | |
76 | 77 | | |
77 | 78 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
88 | | - | |
| 89 | + | |
| 90 | + | |
89 | 91 | | |
90 | 92 | | |
91 | 93 | | |
| |||
109 | 111 | | |
110 | 112 | | |
111 | 113 | | |
| 114 | + | |
| 115 | + | |
112 | 116 | | |
113 | 117 | | |
114 | 118 | | |
| |||
171 | 175 | | |
172 | 176 | | |
173 | 177 | | |
174 | | - | |
175 | | - | |
176 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
177 | 189 | | |
178 | 190 | | |
| 191 | + | |
179 | 192 | | |
180 | 193 | | |
181 | 194 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
| |||
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
| 47 | + | |
45 | 48 | | |
46 | 49 | | |
47 | 50 | | |
| |||
55 | 58 | | |
56 | 59 | | |
57 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
58 | 64 | | |
59 | 65 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
64 | 74 | | |
65 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
66 | 82 | | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
71 | 92 | | |
72 | 93 | | |
73 | 94 | | |
| |||
77 | 98 | | |
78 | 99 | | |
79 | 100 | | |
| 101 | + | |
| 102 | + | |
80 | 103 | | |
81 | 104 | | |
82 | 105 | | |
| |||
88 | 111 | | |
89 | 112 | | |
90 | 113 | | |
| 114 | + | |
| 115 | + | |
91 | 116 | | |
92 | 117 | | |
93 | 118 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
14 | 20 | | |
15 | 21 | | |
16 | 22 | | |
| |||
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| 66 | + | |
| 67 | + | |
60 | 68 | | |
| 69 | + | |
61 | 70 | | |
62 | 71 | | |
63 | 72 | | |
| |||
114 | 123 | | |
115 | 124 | | |
116 | 125 | | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
117 | 133 | | |
118 | 134 | | |
119 | 135 | | |
120 | 136 | | |
121 | 137 | | |
122 | 138 | | |
| 139 | + | |
123 | 140 | | |
124 | 141 | | |
0 commit comments