@pinepaper.studio/mcp-server 1.6.24 → 1.6.26

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.
Files changed (159) hide show
  1. package/README.md +167 -20
  2. package/SECURITY.md +15 -1
  3. package/dist/browser/puppeteer-controller.d.ts +7 -1
  4. package/dist/browser/puppeteer-controller.d.ts.map +1 -1
  5. package/dist/browser/puppeteer-controller.js +151 -69
  6. package/dist/browser/puppeteer-controller.js.map +1 -1
  7. package/dist/build-info.json +2 -2
  8. package/dist/cli.js +7 -5
  9. package/dist/cli.js.map +1 -1
  10. package/dist/design/design-systems.js +2 -2
  11. package/dist/design/design-systems.js.map +1 -1
  12. package/dist/i18n/locales/en.js +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +51 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/mcp/index.d.ts +5 -0
  18. package/dist/mcp/index.d.ts.map +1 -0
  19. package/dist/mcp/index.js +5 -0
  20. package/dist/mcp/index.js.map +1 -0
  21. package/dist/mcp/server.d.ts +86 -0
  22. package/dist/mcp/server.d.ts.map +1 -0
  23. package/dist/mcp/server.js +159 -0
  24. package/dist/mcp/server.js.map +1 -0
  25. package/dist/mcp/stdio.d.ts +20 -0
  26. package/dist/mcp/stdio.d.ts.map +1 -0
  27. package/dist/mcp/stdio.js +82 -0
  28. package/dist/mcp/stdio.js.map +1 -0
  29. package/dist/mcp/types.d.ts +598 -0
  30. package/dist/mcp/types.d.ts.map +1 -0
  31. package/dist/mcp/types.js +140 -0
  32. package/dist/mcp/types.js.map +1 -0
  33. package/dist/metrics/performance-tracker.d.ts.map +1 -1
  34. package/dist/metrics/performance-tracker.js +4 -3
  35. package/dist/metrics/performance-tracker.js.map +1 -1
  36. package/dist/ontology/hints.d.ts.map +1 -1
  37. package/dist/ontology/hints.js +3 -1
  38. package/dist/ontology/hints.js.map +1 -1
  39. package/dist/ontology/upstream-vocabulary.d.ts +2 -2
  40. package/dist/ontology/upstream-vocabulary.d.ts.map +1 -1
  41. package/dist/ontology/upstream-vocabulary.js +8 -2
  42. package/dist/ontology/upstream-vocabulary.js.map +1 -1
  43. package/dist/server/index.d.ts +1 -1
  44. package/dist/server/index.d.ts.map +1 -1
  45. package/dist/server/index.js +1 -1
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/tools/batch-macros.d.ts.map +1 -1
  48. package/dist/tools/batch-macros.js +2 -1
  49. package/dist/tools/batch-macros.js.map +1 -1
  50. package/dist/tools/compact-descriptions.d.ts.map +1 -1
  51. package/dist/tools/compact-descriptions.js +7 -6
  52. package/dist/tools/compact-descriptions.js.map +1 -1
  53. package/dist/tools/craft-tools.d.ts +1 -1
  54. package/dist/tools/craft-tools.d.ts.map +1 -1
  55. package/dist/tools/create-known-keys.d.ts +2 -0
  56. package/dist/tools/create-known-keys.d.ts.map +1 -0
  57. package/dist/tools/create-known-keys.js +24 -0
  58. package/dist/tools/create-known-keys.js.map +1 -0
  59. package/dist/tools/definitions.d.ts +3 -1
  60. package/dist/tools/definitions.d.ts.map +1 -1
  61. package/dist/tools/definitions.js +43 -22
  62. package/dist/tools/definitions.js.map +1 -1
  63. package/dist/tools/effect-check.d.ts +1 -1
  64. package/dist/tools/effect-check.d.ts.map +1 -1
  65. package/dist/tools/effect-check.js +4 -0
  66. package/dist/tools/effect-check.js.map +1 -1
  67. package/dist/tools/engine-lists.d.ts +4 -1
  68. package/dist/tools/engine-lists.d.ts.map +1 -1
  69. package/dist/tools/engine-lists.js +9 -1
  70. package/dist/tools/engine-lists.js.map +1 -1
  71. package/dist/tools/engine-methods.d.ts.map +1 -1
  72. package/dist/tools/engine-methods.js +1 -0
  73. package/dist/tools/engine-methods.js.map +1 -1
  74. package/dist/tools/engine-surface.d.ts +1 -1
  75. package/dist/tools/engine-surface.d.ts.map +1 -1
  76. package/dist/tools/engine-surface.js +19 -4
  77. package/dist/tools/engine-surface.js.map +1 -1
  78. package/dist/tools/export-file-receipt.d.ts +12 -0
  79. package/dist/tools/export-file-receipt.d.ts.map +1 -1
  80. package/dist/tools/export-file-receipt.js +29 -2
  81. package/dist/tools/export-file-receipt.js.map +1 -1
  82. package/dist/tools/handlers/beats.d.ts +1 -1
  83. package/dist/tools/handlers/beats.d.ts.map +1 -1
  84. package/dist/tools/handlers/camera.d.ts +1 -1
  85. package/dist/tools/handlers/camera.d.ts.map +1 -1
  86. package/dist/tools/handlers/export.d.ts +1 -1
  87. package/dist/tools/handlers/export.d.ts.map +1 -1
  88. package/dist/tools/handlers/export.js +3 -2
  89. package/dist/tools/handlers/export.js.map +1 -1
  90. package/dist/tools/handlers/font.d.ts +1 -1
  91. package/dist/tools/handlers/font.d.ts.map +1 -1
  92. package/dist/tools/handlers/font.js +8 -7
  93. package/dist/tools/handlers/font.js.map +1 -1
  94. package/dist/tools/handlers/generate.d.ts +1 -1
  95. package/dist/tools/handlers/generate.d.ts.map +1 -1
  96. package/dist/tools/handlers/generate.js +4 -3
  97. package/dist/tools/handlers/generate.js.map +1 -1
  98. package/dist/tools/handlers/maps.d.ts +1 -1
  99. package/dist/tools/handlers/maps.d.ts.map +1 -1
  100. package/dist/tools/handlers/ontology.d.ts +1 -1
  101. package/dist/tools/handlers/ontology.d.ts.map +1 -1
  102. package/dist/tools/handlers/tool-guide.d.ts +1 -1
  103. package/dist/tools/handlers/tool-guide.d.ts.map +1 -1
  104. package/dist/tools/handlers.d.ts +15 -3
  105. package/dist/tools/handlers.d.ts.map +1 -1
  106. package/dist/tools/handlers.js +71 -31
  107. package/dist/tools/handlers.js.map +1 -1
  108. package/dist/tools/lane.d.ts +11 -6
  109. package/dist/tools/lane.d.ts.map +1 -1
  110. package/dist/tools/lane.js +46 -9
  111. package/dist/tools/lane.js.map +1 -1
  112. package/dist/tools/minimal-descriptions.js +1 -1
  113. package/dist/tools/minimal-descriptions.js.map +1 -1
  114. package/dist/tools/p5-compat/p5-helpers.d.ts +1 -1
  115. package/dist/tools/p5-compat/p5-helpers.js +1 -1
  116. package/dist/tools/shape-params.d.ts +2 -2
  117. package/dist/tools/shape-params.d.ts.map +1 -1
  118. package/dist/tools/shape-params.js +8 -2
  119. package/dist/tools/shape-params.js.map +1 -1
  120. package/dist/tools/stick-vocabulary.d.ts +2 -2
  121. package/dist/tools/stick-vocabulary.js +2 -2
  122. package/dist/tools/timeline.d.ts +2 -2
  123. package/dist/tools/timeline.d.ts.map +1 -1
  124. package/dist/tools/timeline.js +6 -1
  125. package/dist/tools/timeline.js.map +1 -1
  126. package/dist/tools/toolkits.d.ts +22 -7
  127. package/dist/tools/toolkits.d.ts.map +1 -1
  128. package/dist/tools/toolkits.js +102 -20
  129. package/dist/tools/toolkits.js.map +1 -1
  130. package/dist/types/code-generator.d.ts +25 -0
  131. package/dist/types/code-generator.d.ts.map +1 -1
  132. package/dist/types/code-generator.js +424 -157
  133. package/dist/types/code-generator.js.map +1 -1
  134. package/dist/types/schemas.d.ts +52 -47
  135. package/dist/types/schemas.d.ts.map +1 -1
  136. package/dist/types/schemas.js +7 -5
  137. package/dist/types/schemas.js.map +1 -1
  138. package/dist/utils/asset-paths.d.ts.map +1 -1
  139. package/dist/utils/asset-paths.js +4 -4
  140. package/dist/utils/asset-paths.js.map +1 -1
  141. package/dist/utils/env.d.ts +9 -0
  142. package/dist/utils/env.d.ts.map +1 -0
  143. package/dist/utils/env.js +11 -0
  144. package/dist/utils/env.js.map +1 -0
  145. package/dist/utils/export-dir.d.ts +36 -0
  146. package/dist/utils/export-dir.d.ts.map +1 -0
  147. package/dist/utils/export-dir.js +86 -0
  148. package/dist/utils/export-dir.js.map +1 -0
  149. package/dist/utils/js-literal.d.ts +4 -0
  150. package/dist/utils/js-literal.d.ts.map +1 -0
  151. package/dist/utils/js-literal.js +25 -0
  152. package/dist/utils/js-literal.js.map +1 -0
  153. package/dist/utils/mcp-log.d.ts +1 -1
  154. package/dist/utils/mcp-log.d.ts.map +1 -1
  155. package/dist/version.d.ts.map +1 -1
  156. package/dist/version.js +5 -12
  157. package/dist/version.js.map +1 -1
  158. package/manifest.json +42 -63
  159. package/package.json +8 -14
package/README.md CHANGED
@@ -80,11 +80,19 @@ shareable. It is in invite-only beta.
80
80
 
81
81
  ### Quick start
82
82
 
83
- Puppeteer is an **optional** install, so a plain `npx @pinepaper.studio/mcp-server`
84
- has no browser: every tool then returns the code it would run instead of running
85
- it (the result says so). To execute in a real browser, install Puppeteer **in the
86
- same `npx` call** — a separate `npm i puppeteer` in your project is not visible to
87
- `npx`'s copy of the server:
83
+ **Claude Desktop:** download
84
+ [pinepaper-mcp-server.mcpb](https://github.com/pinepaper/mcp-server/releases/latest/download/pinepaper-mcp-server.mcpb)
85
+ and open it. Desktop installs the server, with no config file to edit. It
86
+ drives your installed Google Chrome; its settings choose another Chromium-based
87
+ browser, the export folder and the tool list. Each
88
+ [release](https://github.com/pinepaper/mcp-server/releases) lists the file's
89
+ SHA-256.
90
+
91
+ For other MCP clients: this package does not install Puppeteer, so a plain
92
+ `npx @pinepaper.studio/mcp-server` has no browser, and every tool returns the
93
+ code it would run instead of running it (the result says so). To execute in a
94
+ real browser, install Puppeteer **in the same `npx` call**. A separate
95
+ `npm i puppeteer` in your project is not visible to `npx`'s copy of the server:
88
96
 
89
97
  ```json
90
98
  {
@@ -246,9 +254,149 @@ If you do not want an agent executing anything, `code` mode is a first-class pat
246
254
 
247
255
  - Code an agent writes runs with whatever that browser page has. Give this server the same trust you would give anything else you let write and run code on your machine — which is the trust you already extend to an MCP client with tool access.
248
256
  - Puppeteer mode launches Chrome with `--no-sandbox` and `--disable-setuid-sandbox`. That is routine for headless automation and it does weaken Chrome's own process sandbox. If that matters where you are running it, use `code` mode or put the server in a container.
249
- - Puppeteer itself is an **optional** peer dependency, kept out of the default tree precisely because a headless browser plus an install script is what scanners flag hardest. Install it only if you want the executing mode.
257
+ - Puppeteer itself is not a dependency at all (it was an optional peer until 1.6.25), kept out of the tree precisely because a headless browser plus an install script is what scanners flag hardest. Install it only if you want the executing mode. The server loads it only from where its own dependencies would be found (the same `npx` call, a global install, a project install) or from a folder you name in `PINEPAPER_PUPPETEER_FROM`; never from the folder it happens to be started in. puppeteer 22 or newer is required.
258
+ - The package starts one child process, and only when an export is saved: `ffprobe -show_entries … <file>` (via `execFile`, no shell, fixed binary and argument list, an absolute path) to check the saved video against what the studio encoded. Without `ffprobe` on `PATH` the check is skipped and said so; `PINEPAPER_FFPROBE=off` turns it off.
250
259
  - The server itself fetches the images, SVGs and media a tool call names by URL, and reads local files named by path. URLs that resolve to loopback, private, link-local or reserved addresses are refused, redirects included, and so are cloud metadata addresses. Local files are read only from the working directory, the export folder, and folders listed in `PINEPAPER_ASSET_DIRS`. `PINEPAPER_ALLOW_PRIVATE_NETWORK=1` allows local and LAN hosts. Metadata addresses stay refused even with it set.
251
260
 
261
+ ## What's new in 1.6.26
262
+
263
+ ### Changed
264
+
265
+ - The package installs two dependencies, zod and undici. Puppeteer is no
266
+ longer declared, not even as an optional peer; install it yourself for the
267
+ browser tools, as before (`-p puppeteer` in the `npx` call, a global install
268
+ or a project install). The Desktop extension includes what it needs.
269
+ puppeteer 22 or newer is required. With pnpm `hoist=false` or Yarn PnP
270
+ strict, set `PINEPAPER_PUPPETEER_FROM` to the project folder that has it.
271
+
272
+ ### Fixed
273
+
274
+ - A video whose motion comes from relations (an orbit, an item attached to a
275
+ moving one, a generator, the camera) reported `animated: false` in its export
276
+ result, although the frames moved. When the studio says not animated, the
277
+ export now compares frames from across the video. If they differ, it reports
278
+ `animated: true` and says how it knows (`fidelity.checked.motion`). The
279
+ frozen-video check, which runs only on animated exports, now covers these
280
+ scenes too.
281
+
282
+ ## What's new in 1.6.25
283
+
284
+ ### Added
285
+
286
+ - `pinepaper_pattern` kinds `ring` and `orbit`: members spaced evenly round a
287
+ turning circle, with a fixed `center` or a `target` item the centre follows.
288
+ The list of kinds now comes from the studio, so a kind it adds appears here
289
+ too.
290
+
291
+ ### Changed
292
+
293
+ - The server speaks MCP itself instead of through the MCP SDK, so installing
294
+ it pulls in 2 packages (`zod` and `undici`) instead of 78. It behaves the
295
+ same on the wire: the same protocol versions (2024-10-07 to 2025-11-25),
296
+ errors, progress, cancellation and logging, checked against recordings of
297
+ the previous server. `createServer()` still accepts any MCP transport,
298
+ including the SDK's.
299
+ - The published code no longer uses `eval` or runs `git`, and it reads
300
+ environment variables in one place.
301
+ - New sessions start with a leaner tool list: the `core` toolkit, about half
302
+ the context of the previous default, with every craft tool the server
303
+ recommends. Other tools load when needed with `pinepaper_set_toolkit`
304
+ `{"load": ["map"]}` (a group) or a tool's name, and the list updates at
305
+ once. Claude Desktop, Cursor and Windsurf used to get all 178 tools.
306
+ `PINEPAPER_TOOLKIT=agent` or `full` restores a larger list.
307
+ - Exports are saved to `~/Downloads/PinePaper` instead of a temporary folder,
308
+ so Desktop users can find them (`PINEPAPER_EXPORT_DIR` still overrides it,
309
+ and machines without a Downloads folder keep the temporary folder). Each
310
+ saved file also comes back as a link the client can open, and
311
+ `resources/read` returns files up to 10 MB from the export folder.
312
+ - A Claude Desktop extension (`.mcpb`) can be built with `bun run
313
+ build:mcpb`: one file to open, no config to edit. It uses your installed
314
+ Chrome for the canvas tools. Without full `puppeteer`, the server now also
315
+ works with `puppeteer-core` and your installed Chrome, or another browser
316
+ set in `PINEPAPER_CHROME_PATH`.
317
+
318
+ ### Fixed
319
+
320
+ - `pinepaper_pattern` no longer warns "NO CHANGE" after a `create` or
321
+ `modify` that has not moved anything yet, such as a wave at time 0 or one
322
+ whose window starts later.
323
+ - A failed batch step addressed by `$N` names the item that `$N` resolved to,
324
+ not `"$N"`.
325
+ - Batch `animate` and `relation` operations quote their `animationType` and
326
+ `relationType` correctly in the generated code.
327
+ - The scene lint that batches and custom code run on every call no longer
328
+ checks every pair of items, which made each call on a large scene slower.
329
+ It reports the same warnings.
330
+ - A video export that goes over the bitrate you asked for says so: the studio
331
+ reports `bitrate_exceeded` with the bitrate the file actually has, and how
332
+ to land under the target.
333
+ - The agent toolkit's export description now says what each format returns:
334
+ only MP4 and WebM carry a frame digest, and a small SVG comes back inline
335
+ rather than as a file.
336
+ - `on_event_fire_if` is documented: its predicate is an ExpressionIR object
337
+ (comparisons, and/or/not, reads of an item's data, active or visible
338
+ state), and it fires the event in `params.event`, not its target. A
339
+ predicate written as a string, or a relation with no event to fire, is
340
+ refused with the shape that works, wherever the relation comes from:
341
+ `pinepaper_add_relation`, a batch `relation` step, `pinepaper_create_scene`,
342
+ `pinepaper_instantiate_ontology`, or an `on_event_add_relation` that adds
343
+ one later. Before, the relation silently never fired.
344
+ - An emitter given a `[min, max]` range for a key that takes one number
345
+ (such as `gravity`, `opacity` or `count`) now gets a warning: the key draws
346
+ one value for every particle, not a spread. Before, there was no warning.
347
+ - `pinepaper_import_image` takes a `name`, so a batch `patch` can address an
348
+ imported image by name.
349
+ - Custom code that returns nothing is reported as returning nothing, not as
350
+ `{}`, when the scene lint has warnings.
351
+ - An export whose file was saved but could not be hashed or read back says
352
+ so. It used to report the save as failed.
353
+ - A batch step other than `create` that carries a key it does not read
354
+ now gets an `IGNORED ARGUMENTS` note naming the step and the key. Before,
355
+ the key had no effect and nothing said so. The common mix-ups get a hint:
356
+ `params` on a `relation` step (it takes `relationOptions`), and `x`/`y` on
357
+ a `modify` step (changes go inside `properties`).
358
+ - Clearing the canvas starts item ids again at `item_1`
359
+ (`pinepaper_clear_canvas`, `pinepaper_agent_start_job` and
360
+ `pinepaper_agent_reset`), so a batch sent after a clear gets the same ids
361
+ every time. Before, the numbering carried on from the cleared items, and
362
+ only a server restart reset it.
363
+ - A `group` step in a batch now takes the next `$N`, as a `create` does, so
364
+ a later step can animate the group: in `[create, create, group, …]` the
365
+ group is `$2`. This renumbers batches that create items after a group:
366
+ in `[create, create, group, create]` the last item was `$2` and is now
367
+ `$3`. Before, a `$N` meant for the group failed with "it has created 2 so
368
+ far".
369
+ - An event reaction that shows or hides an item (`on_event_set_visibility`,
370
+ and its `on_enter_` and `on_exit_` forms) refuses `value` and asks for
371
+ `visible`, the field it reads. Before, `{"value": false}` showed the item
372
+ instead of hiding it.
373
+ - A PNG, JPG, WebP, SVG or PDF of a scene that is playing is its first frame
374
+ (time 0), the frame a video starts on, and the result says so. Before, it
375
+ was whatever frame playback had reached, so items moved by relations (an
376
+ orbit) were in a different place on every export. `time` still picks
377
+ another moment.
378
+ - A closed shape given a `strokeColor` and no fill is drawn as an outline.
379
+ Before, it got the default fill, or the active brand's colour.
380
+ - A batch no longer flags `ttl` or `bornAt` on a `line` as a likely typo.
381
+ - A line created with `from` and `to` reports where it actually is, and says
382
+ that `position` does not move it: `from` and `to` are canvas coordinates.
383
+ Before, the result reported the position asked for while the line was
384
+ drawn at the top left.
385
+ - `pinepaper_transform` `nudge` moves an item the way `pinepaper_modify_item`
386
+ does, so an item with a loop animation keeps its new place, and the
387
+ result no longer reports the move as no change.
388
+ - `create_item` documents `properties.id`: an id of your own, so replays
389
+ address the same items. An id with a quote, backslash or line break is
390
+ refused, since later calls could not address it.
391
+ - An item id containing a quote no longer breaks `pinepaper_modify_item`,
392
+ `pinepaper_delete_item`, `pinepaper_animate`,
393
+ `pinepaper_keyframe_animate`, `pinepaper_apply_effect` or
394
+ `pinepaper_query_relations`. Before, the call failed with a syntax error.
395
+ - The `lane` toolkit adds `pinepaper_event`, for things that happen at a
396
+ set time or on a click (a panel that hides at 3 s), and
397
+ `pinepaper_clear_canvas`, to start over. Its batch description shows how
398
+ to attach an event's reactions and triggers.
399
+
252
400
  ## What's new in 1.6.24
253
401
 
254
402
  ### Added
@@ -1717,27 +1865,20 @@ Fourteen new tools (121 → 135) and new actions across the surface — the rele
1717
1865
 
1718
1866
  | Profile | Contents |
1719
1867
  |---------|----------|
1868
+ | `core` | **The default.** The craft tools the server's instructions point to, the agent workflow, canvas and query tools, plus every tool those name, so no listed tool points at a missing one. About half the tokens of `agent`. Anything else loads on demand (below) |
1720
1869
  | `full` | Every tool, no filtering |
1721
- | `agent` | Every tool except the diagnostics (`pinepaper_get_performance_metrics`, `pinepaper_diagnostic_report`) and `pinepaper_register_item`. The default for a client not listed below |
1870
+ | `agent` | Every tool except the diagnostics (`pinepaper_get_performance_metrics`, `pinepaper_diagnostic_report`) and `pinepaper_register_item` |
1722
1871
  | `diagram` | Canvas + diagram + query/export |
1723
1872
  | `map` | Canvas + map + query/export |
1724
1873
  | `font` | Canvas + font + letter collage + export |
1725
1874
  | `minimal` | Agent, browser, canvas, and guide only |
1726
- | `lane` | **The agent lane**: exactly five tools — `pinepaper_agent_batch_execute`, `pinepaper_keyframe_animate`, `pinepaper_pattern`, `pinepaper_agent_export`, `pinepaper_lint_scene` — shown with descriptions that carry the rules an agent otherwise learns by failing (the keyframe shape, an item's words and look inside `properties`, a position being the item's centre, the export's 5 s default). The whole contract, instructions included, is ~3.3k tokens. For scripted builds of shapes, text, keyframes and relations; use `agent` for characters, styled scenes, maps, diagrams and sound |
1875
+ | `lane` | **The agent lane**: exactly seven tools — `pinepaper_agent_batch_execute`, `pinepaper_keyframe_animate`, `pinepaper_pattern`, `pinepaper_agent_export`, `pinepaper_lint_scene`, `pinepaper_event`, `pinepaper_clear_canvas` — shown with descriptions that carry the rules an agent otherwise learns by failing (the keyframe shape, an item's words and look inside `properties`, a position being the item's centre, the export's 5 s default). The whole contract, instructions included, is ~4.3k tokens. For scripted builds of shapes, text, keyframes and relations; use `core` for characters, styled scenes, maps, diagrams and sound |
1727
1876
 
1728
1877
  **Verbosity tiers** (`PINEPAPER_VERBOSITY`): `verbose`, `compact` (default), `minimal`.
1729
1878
 
1730
- **Client auto-detection.** When neither env var is set explicitly, the server picks a profile from the MCP `initialize` handshake:
1731
-
1732
- | Client | Toolkit | Verbosity |
1733
- |--------|---------|-----------|
1734
- | `claude-ai` | `minimal` | `compact` |
1735
- | `claude-desktop` | `full` | `compact` |
1736
- | `claude-code` | `agent` | `compact` |
1737
- | `cursor` | `full` | `compact` |
1738
- | `windsurf` | `full` | `compact` |
1879
+ **Load on demand.** Every client starts on `core`. When a task needs more, the model calls `pinepaper_set_toolkit` with `load`: a group (`{"load": ["map"]}`, `["diagram"]`, `["font"]`, `["physics"]`, …) or a tool's own name. The tools are added for the session, along with any tools they mention, and the client is told the list changed. The server logs each client's name at start-up.
1739
1880
 
1740
- Explicit env vars always win. You can also hand-pick tools with `PINEPAPER_TOOLS` (comma-separated names), or switch profiles at runtime with the `pinepaper_set_toolkit` tool. Start with `pinepaper_tool_guide` to have the server explain its own surface.
1881
+ `PINEPAPER_TOOLKIT` always wins. You can also hand-pick tools with `PINEPAPER_TOOLS` (comma-separated names), or switch profiles at runtime with the `pinepaper_set_toolkit` tool. Start with `pinepaper_tool_guide` to have the server explain its own surface.
1741
1882
 
1742
1883
  ## Features
1743
1884
 
@@ -2322,6 +2463,10 @@ bun install
2322
2463
  bun run build
2323
2464
  ```
2324
2465
 
2466
+ ### Claude Desktop extension (.mcpb)
2467
+
2468
+ Each release ships it ready-made ([latest](https://github.com/pinepaper/mcp-server/releases/latest/download/pinepaper-mcp-server.mcpb)). `bun run build:mcpb` builds `mcpb/pinepaper-mcp-server-<version>.mcpb`, a one-file Claude Desktop extension: open it and Desktop installs the server, with no config file to edit. The extension drives your installed Google Chrome (choose another Chromium-based browser in its settings), and its settings also set the export folder and the tool list.
2469
+
2325
2470
  ### Test with MCP Client (Local)
2326
2471
 
2327
2472
  1. Build the server:
@@ -2437,9 +2582,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
2437
2582
  | `PINEPAPER_API_KEY` | Service-account key (`pp_sa_…`) for the cloud-backed tools (`pinepaper_generate`) | — |
2438
2583
  | `PINEPAPER_CLOUD_URL` | The PinePaper cloud those tools call | `https://cloud.pinepaper.studio` |
2439
2584
  | `PINEPAPER_EXECUTION_MODE` | `puppeteer` (execute in a real browser) or `code` (return generated code only) | `puppeteer` |
2440
- | `PINEPAPER_EXPORT_DIR` | Directory for exported files | `<tmpdir>/pinepaper-exports` |
2585
+ | `PINEPAPER_PUPPETEER_FROM` | An absolute project folder to load `puppeteer` / `puppeteer-core` from, for a strict layout (pnpm `hoist=false`, Yarn PnP strict) where the server cannot see its project's packages | unset |
2586
+ | `PINEPAPER_CHROME_PATH` | A Chromium-based browser for the canvas tools when full `puppeteer` is not installed (the Desktop extension uses your installed Chrome by default) | installed Google Chrome |
2587
+ | `PINEPAPER_EXPORT_DIR` | Directory for exported files | `~/Downloads/PinePaper` (`<tmpdir>/pinepaper-exports` where there is no Downloads folder) |
2441
2588
  | `PINEPAPER_LOCALE` | Language locale code | `en` |
2442
- | `PINEPAPER_TOOLKIT` | Toolkit profile (`full`/`agent`/`diagram`/`map`/`font`/`minimal`/`lane`) | auto-detected |
2589
+ | `PINEPAPER_TOOLKIT` | Toolkit profile (`core`/`full`/`agent`/`diagram`/`map`/`font`/`minimal`/`lane`) | `core` |
2443
2590
  | `PINEPAPER_TOOLS` | Explicit comma-separated tool allowlist | unset |
2444
2591
  | `PINEPAPER_VERBOSITY` | Description verbosity (`verbose`/`compact`/`minimal`) | `compact` |
2445
2592
  | `PINEPAPER_TOOL_VERBOSITY` | Deprecated alias for `PINEPAPER_VERBOSITY` | unset |
package/SECURITY.md CHANGED
@@ -41,6 +41,20 @@ Examples of what is in scope:
41
41
  - reading or writing files outside the folders the server is allowed to use (path traversal);
42
42
  - tool input that escapes the generated snippet and runs in the server process instead of the page;
43
43
  - exposing a credential such as `PINEPAPER_API_KEY`, or sending it somewhere it should not go;
44
- - a crash or hang of the server caused by a single crafted tool call.
44
+ - a crash or hang of the server caused by a single crafted tool call;
45
+ - the server loading `puppeteer` (or any module) from somewhere other than where its own dependencies resolve or `PINEPAPER_PUPPETEER_FROM`, or running a child process other than the `ffprobe` read-back described below.
45
46
 
46
47
  The server's own defences, `PINEPAPER_ALLOW_PRIVATE_NETWORK` and `PINEPAPER_ASSET_DIRS`, are documented in the README. Bypassing either one is in scope.
48
+
49
+ ## Dependencies
50
+
51
+ The package installs two runtime dependencies and nothing else:
52
+
53
+ - `zod`, for tool input schemas;
54
+ - `undici`, used only by the guarded fetch (`src/utils/safe-fetch.ts`) to pin each connection to the addresses that passed the private-network check, which closes DNS rebinding. Node's built-in `fetch` cannot be given that pinned resolver, so undici stays.
55
+
56
+ **Puppeteer is not a dependency**, not even an optional peer (it was one up to 1.6.25). It brings a browser download with an install script, shell access and a large network tree, and the browser tools are a minority of what the server does. Install it yourself to use them (`npm i puppeteer`, or `-p puppeteer` in the `npx` call; see the README). The server imports it by name only from where its own dependencies resolve, or from an absolute folder you set in `PINEPAPER_PUPPETEER_FROM`; never from its working directory, which can be an untrusted checkout. puppeteer 22 or newer is required and an older one is refused.
57
+
58
+ **One child process.** When an exported video is saved, the server runs `ffprobe` through `execFile` (no shell) with a fixed argument list and the file's absolute path, to compare the file with what the studio encoded. It is skipped when `ffprobe` is not on `PATH`, and `PINEPAPER_FFPROBE=off` turns it off.
59
+
60
+ The source contains no `eval` or `new Function`.
@@ -8,7 +8,7 @@ import type { Page } from 'puppeteer';
8
8
  /**
9
9
  * What a navigation waits for. Puppeteer's own union, spelled out here so the
10
10
  * env var can be validated against it without importing puppeteer — which is
11
- * an optional peer, absent on most installs (see connect()).
11
+ * not a dependency, absent on most installs (see loadPuppeteer()).
12
12
  */
13
13
  export type PageWaitUntil = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
14
14
  export declare const PAGE_WAIT_UNTIL: readonly PageWaitUntil[];
@@ -305,6 +305,12 @@ export declare class PinePaperBrowserController {
305
305
  /**
306
306
  * Execute JavaScript code in PinePaper Studio
307
307
  */
308
+ /**
309
+ * The ungoverned path: the code as a CDP Runtime.evaluate (Puppeteer's string form awaits a promise and keeps the
310
+ * script's trailing value), then the in-page bridgeSafe the governed path uses, so a value holding live Paper items
311
+ * still crosses. A throw comes back as { success: false, error }, as it did from eval.
312
+ */
313
+ private runUngoverned;
308
314
  executeCode(code: string, takeScreenshot?: boolean, options?: ExecuteCodeOptions): Promise<ExecuteResult>;
309
315
  /**
310
316
  * Take a screenshot of the current canvas
@@ -1 +1 @@
1
- {"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,11 @@
6
6
  */
7
7
  import { mcpLog } from '../utils/mcp-log.js';
8
8
  import { LAZY_HEAVY_SUBSYSTEMS, LAZY_HEAVY_CLASSES, ENGINE_SURFACE } from '../tools/engine-surface.js';
9
+ import { env } from '../utils/env.js';
10
+ import { createRequire } from 'node:module';
11
+ import { readFileSync } from 'node:fs';
12
+ import { isAbsolute, join } from 'node:path';
13
+ import { pathToFileURL } from 'node:url';
9
14
  export const PAGE_WAIT_UNTIL = ['load', 'domcontentloaded', 'networkidle0', 'networkidle2'];
10
15
  /**
11
16
  * Is PinePaper Studio actually usable? Evaluated IN THE PAGE, so it closes over
@@ -74,6 +79,96 @@ const BOOTSTRAP_SUBSYSTEMS = Object.freeze(Object.entries(ENGINE_SURFACE)
74
79
  * not go through executeOrGenerate (gate A2, 1.6.19).
75
80
  */
76
81
  export const BROWSER_TIME = { executeMs: 0, screenshotMs: 0 };
82
+ /**
83
+ * The browser driver. Puppeteer is NOT a dependency of this package, not even an
84
+ * optional peer (it was one up to 1.6.25): browser automation is a handful of tools, and a
85
+ * headless-browser download with an install script is what supply-chain
86
+ * scanners flag hardest. A declared peer, optional or not, still puts its whole
87
+ * tree in a scanner's view of this package. The user installs it beside the
88
+ * server when they want the executing mode (README, "Install").
89
+ *
90
+ * Tried in order: `puppeteer` (its own Chrome), then `puppeteer-core` driving a
91
+ * browser already on the machine (1.6.25: the Claude Desktop bundle ships
92
+ * puppeteer-core, not a Chrome download): PINEPAPER_CHROME_PATH when set, else
93
+ * the installed Chrome. Neither present → the install instruction, not a crash.
94
+ */
95
+ const PUPPETEER_MIN_MAJOR = 22;
96
+ /**
97
+ * Import a package this server does not depend on, by a name held in a
98
+ * variable (so neither the build nor a scanner sees a dependency it does not
99
+ * have). Resolution starts from THIS package, as a declared dependency's would:
100
+ * `npx -p puppeteer -p @pinepaper.studio/mcp-server`, a global install of both,
101
+ * and a project install all put puppeteer where that finds it.
102
+ *
103
+ * It is never resolved from process.cwd(): an MCP client starts the server in
104
+ * whatever folder is open, which can be a cloned repository, and a
105
+ * node_modules/puppeteer planted there would then run as this process. The one
106
+ * other place looked in is a folder the user names in PINEPAPER_PUPPETEER_FROM
107
+ * (for a strict layout — pnpm with hoist=false, Yarn PnP strict — where a
108
+ * package cannot see its project's undeclared packages).
109
+ */
110
+ async function importOptional(name) {
111
+ const versionFrom = (base) => {
112
+ try {
113
+ const pkgPath = createRequire(base).resolve(`${name}/package.json`);
114
+ return String(JSON.parse(readFileSync(pkgPath, 'utf8')).version ?? '') || null;
115
+ }
116
+ catch {
117
+ return null; /* no exported package.json: version unknown, not refused */
118
+ }
119
+ };
120
+ try {
121
+ return { mod: await import(name), version: versionFrom(import.meta.url) };
122
+ }
123
+ catch { /* not beside this package (or present and failing to load: treated as absent, as before) */ }
124
+ const from = env('PINEPAPER_PUPPETEER_FROM')?.trim();
125
+ if (!from || !isAbsolute(from))
126
+ return null;
127
+ const base = pathToFileURL(join(from, 'package.json')).href;
128
+ try {
129
+ const entry = createRequire(base).resolve(name);
130
+ return { mod: await import(pathToFileURL(entry).href), version: versionFrom(base) };
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ }
136
+ function refuseOldPuppeteer(name, version) {
137
+ const major = version ? Number(version.split('.')[0]) : NaN;
138
+ if (Number.isFinite(major) && major < PUPPETEER_MIN_MAJOR) {
139
+ throw new Error(`The browser tools need ${name} ${PUPPETEER_MIN_MAJOR} or newer; ${version} is installed. Run \`npm i ${name}@latest\` (or add -p ${name}@latest to the npx call), then retry.`);
140
+ }
141
+ }
142
+ async function loadPuppeteer() {
143
+ const fullName = 'puppeteer'; // a variable: see importOptional
144
+ const full = await importOptional(fullName);
145
+ if (full) {
146
+ refuseOldPuppeteer(fullName, full.version);
147
+ return { launch: (opts) => full.mod.default.launch(opts) };
148
+ }
149
+ const coreName = 'puppeteer-core';
150
+ const coreLoaded = await importOptional(coreName);
151
+ if (coreLoaded)
152
+ refuseOldPuppeteer(coreName, coreLoaded.version);
153
+ const core = coreLoaded?.mod ?? null;
154
+ if (core) {
155
+ const executablePath = env('PINEPAPER_CHROME_PATH')?.trim();
156
+ return {
157
+ launch: async (opts) => {
158
+ try {
159
+ return await core.default.launch({ ...opts, ...(executablePath ? { executablePath } : { channel: 'chrome' }) });
160
+ }
161
+ catch (e) {
162
+ const why = e instanceof Error ? e.message.split('\n')[0] : String(e);
163
+ throw new Error(executablePath
164
+ ? `Could not start the browser at PINEPAPER_CHROME_PATH (${executablePath}): ${why}`
165
+ : `The canvas tools drive your installed Google Chrome, and it could not be started (${why}). Install Chrome from https://www.google.com/chrome/, or set PINEPAPER_CHROME_PATH to another Chromium-based browser (Edge, Brave, Chromium), then retry.`);
166
+ }
167
+ },
168
+ };
169
+ }
170
+ throw new Error('The browser tools need puppeteer, which this package does not install: install puppeteer to use the browser tools (npm i puppeteer). Under npx, add it to the same call — args ["-y", "-p", "puppeteer", "-p", "@pinepaper.studio/mcp-server", "pinepaper-mcp"] — since a separate `npm i puppeteer` is not visible to npx\'s copy. Installed globally: `npm i -g puppeteer`; in a project: `npm i puppeteer` (with pnpm hoist=false or Yarn PnP strict, also set PINEPAPER_PUPPETEER_FROM to the project folder). Then retry. It is kept out of the dependency tree for supply-chain hygiene.');
171
+ }
77
172
  export class PinePaperBrowserController {
78
173
  browser = null;
79
174
  page = null;
@@ -305,19 +400,9 @@ export class PinePaperBrowserController {
305
400
  this.browser = null;
306
401
  this.page = null;
307
402
  }
308
- // Puppeteer is an OPTIONAL peer (1.6.5): browser automation is 4 of 135
309
- // tools, and carrying a headless-browser + install-script dependency in
310
- // the default tree is what supply-chain scanners flag hardest. Absent →
311
- // a clear instruction, not a crash.
312
- let puppeteer;
313
- try {
314
- puppeteer = await import('puppeteer');
315
- }
316
- catch {
317
- throw new Error('The browser tools need puppeteer, which is an optional install. Under npx, add it to the same call — args ["-y", "-p", "puppeteer", "-p", "@pinepaper.studio/mcp-server", "pinepaper-mcp"] — since a separate `npm i puppeteer` is not visible to npx\'s copy. Installed globally: `npm i -g puppeteer`; in a project: `npm i puppeteer`. Then retry. It is kept out of the default dependency tree for supply-chain hygiene.');
318
- }
403
+ const puppeteer = await loadPuppeteer();
319
404
  console.error('[PinePaper] Launching browser...');
320
- this.browser = await puppeteer.default.launch(this.launchOptions(this.config.headless, this.launchArgs(this.config.viewportWidth, this.config.viewportHeight)));
405
+ this.browser = await puppeteer.launch(this.launchOptions(this.config.headless, this.launchArgs(this.config.viewportWidth, this.config.viewportHeight)));
321
406
  // Get existing pages first, reuse if available (avoid creating multiple tabs)
322
407
  const pages = await this.browser.pages();
323
408
  if (pages.length > 0) {
@@ -501,7 +586,7 @@ export class PinePaperBrowserController {
501
586
  async ensureStudioPage() {
502
587
  if (!this.page)
503
588
  return 'ok';
504
- let state = null;
589
+ let state;
505
590
  try {
506
591
  state = await this.page.evaluate(() => {
507
592
  const app = window.app || window.PinePaper;
@@ -548,6 +633,31 @@ export class PinePaperBrowserController {
548
633
  /**
549
634
  * Execute JavaScript code in PinePaper Studio
550
635
  */
636
+ /**
637
+ * The ungoverned path: the code as a CDP Runtime.evaluate (Puppeteer's string form awaits a promise and keeps the
638
+ * script's trailing value), then the in-page bridgeSafe the governed path uses, so a value holding live Paper items
639
+ * still crosses. A throw comes back as { success: false, error }, as it did from eval.
640
+ */
641
+ async runUngoverned(code) {
642
+ if (!this.page)
643
+ return { success: false, error: 'Browser not connected', executedVia: 'eval' };
644
+ let handle = null;
645
+ try {
646
+ handle = await this.page.evaluateHandle(code);
647
+ const value = await this.page.evaluate((v) => {
648
+ const safe = window.__ppBridgeSafe;
649
+ return safe ? safe(v) : v;
650
+ }, handle);
651
+ return { success: true, result: value, executedVia: 'eval' };
652
+ }
653
+ catch (e) {
654
+ return { success: false, error: e instanceof Error ? e.message : 'Execution error', executedVia: 'eval' };
655
+ }
656
+ finally {
657
+ if (handle)
658
+ await handle.dispose().catch(() => { });
659
+ }
660
+ }
551
661
  async executeCode(code, takeScreenshot = true, options = {}) {
552
662
  if (!this.page || !this.isConnected) {
553
663
  return {
@@ -611,7 +721,7 @@ export class PinePaperBrowserController {
611
721
  stage: options.stage ?? null,
612
722
  };
613
723
  const __evalStart = Date.now();
614
- const result = await this.page.evaluate(async (codeToRun, opts) => {
724
+ const evaluated = await this.page.evaluate(async (codeToRun, opts) => {
615
725
  // THE BRIDGE USES STRUCTURED CLONE, AND JSON.stringify DOES NOT PROVE IT.
616
726
  //
617
727
  // A result crosses CDP by structured clone, which refuses to carry a
@@ -715,25 +825,18 @@ export class PinePaperBrowserController {
715
825
  return { success: false, error: e instanceof Error ? e.message : 'Execution error', executedVia: 'governed' };
716
826
  }
717
827
  }
718
- // Fallback: raw eval — a pre-governor FxTool build, or an explicit
719
- // bypass. Both generated-code shapes are `(`-led expression statements,
720
- // so eval returns the same trailing value runGenerated captures.
721
- try {
722
- // eslint-disable-next-line no-eval
723
- const evalResult = eval(codeToRun);
724
- if (evalResult instanceof Promise) {
725
- return evalResult.then((r) => ({ success: true, result: bridgeSafe(r), executedVia: 'eval' }));
726
- }
727
- return { success: true, result: bridgeSafe(evalResult), executedVia: 'eval' };
728
- }
729
- catch (e) {
730
- return {
731
- success: false,
732
- error: e instanceof Error ? e.message : 'Execution error',
733
- executedVia: 'eval',
734
- };
735
- }
828
+ // Fallback: run ungoverned — a pre-governor FxTool build, or an explicit
829
+ // bypass. The code is run from the Node side as a CDP Runtime.evaluate
830
+ // (below), which returns the script's trailing value exactly as eval did:
831
+ // both generated-code shapes are `(`-led expression statements. It used
832
+ // to be evaluated here; the published package then carried an evaluating call
833
+ // that supply-chain scanners flag, for what CDP already does.
834
+ window.__ppBridgeSafe = bridgeSafe;
835
+ return { success: true, __ppRaw: true, executedVia: 'eval' };
736
836
  }, code, runOptions);
837
+ let result = evaluated;
838
+ if (evaluated.__ppRaw)
839
+ result = await this.runUngoverned(code);
737
840
  BROWSER_TIME.executeMs += Date.now() - __evalStart;
738
841
  // Take screenshot if requested
739
842
  let screenshot;
@@ -981,23 +1084,13 @@ export class PinePaperBrowserController {
981
1084
  this.browser = null;
982
1085
  this.page = null;
983
1086
  }
984
- // Puppeteer is an OPTIONAL peer (1.6.5): browser automation is 4 of 135
985
- // tools, and carrying a headless-browser + install-script dependency in
986
- // the default tree is what supply-chain scanners flag hardest. Absent →
987
- // a clear instruction, not a crash.
988
- let puppeteer;
989
- try {
990
- puppeteer = await import('puppeteer');
991
- }
992
- catch {
993
- throw new Error('The browser tools need puppeteer, which is an optional install. Under npx, add it to the same call — args ["-y", "-p", "puppeteer", "-p", "@pinepaper.studio/mcp-server", "pinepaper-mcp"] — since a separate `npm i puppeteer` is not visible to npx\'s copy. Installed globally: `npm i -g puppeteer`; in a project: `npm i puppeteer`. Then retry. It is kept out of the default dependency tree for supply-chain hygiene.');
994
- }
1087
+ const puppeteer = await loadPuppeteer();
995
1088
  // Agent mode defaults to visible browser so user can see PinePaper Studio
996
1089
  const useHeadless = options.headless ?? this.config.headless;
997
1090
  const viewportWidth = options.viewportWidth ?? this.config.viewportWidth;
998
1091
  const viewportHeight = options.viewportHeight ?? this.config.viewportHeight;
999
1092
  console.error(`[PinePaper] Launching browser in agent mode (headless: ${useHeadless})...`);
1000
- this.browser = await puppeteer.default.launch(this.launchOptions(useHeadless, this.launchArgs(viewportWidth, viewportHeight, [
1093
+ this.browser = await puppeteer.launch(this.launchOptions(useHeadless, this.launchArgs(viewportWidth, viewportHeight, [
1001
1094
  // '--disable-gpu' USED TO BE HERE, AND IT TURNED OFF WEBGL.
1002
1095
  //
1003
1096
  // It was carried in as a headless convention under "optimization
@@ -1134,24 +1227,13 @@ ${codes.map((code, i) => `
1134
1227
  })();
1135
1228
  `.trim();
1136
1229
  try {
1137
- const result = await this.page.evaluate((codeToRun) => {
1138
- try {
1139
- // eslint-disable-next-line no-eval
1140
- const evalResult = eval(codeToRun);
1141
- if (evalResult instanceof Promise) {
1142
- return evalResult;
1143
- }
1144
- return evalResult;
1145
- }
1146
- catch (e) {
1147
- return {
1148
- success: false,
1149
- error: e instanceof Error ? e.message : 'Batch execution error',
1150
- results: [],
1151
- itemIds: [],
1152
- };
1153
- }
1154
- }, batchCode);
1230
+ // A string is run as a CDP Runtime.evaluate: the script's value, awaited, by value (no eval in this package).
1231
+ const result = await this.page.evaluate(batchCode).catch((e) => ({
1232
+ success: false,
1233
+ error: e instanceof Error ? e.message : 'Batch execution error',
1234
+ results: [],
1235
+ itemIds: [],
1236
+ }));
1155
1237
  const executionTime = Date.now() - startTime;
1156
1238
  // Take screenshot if requested
1157
1239
  let screenshot;
@@ -1268,25 +1350,25 @@ export function getBrowserController(config) {
1268
1350
  // Env resolution lives here, not in the controller: explicit config wins,
1269
1351
  // then the env var, then the default — the shape PINEPAPER_HEADLESS and
1270
1352
  // PINEPAPER_STUDIO_URL already use.
1271
- const envHeadless = process.env.PINEPAPER_HEADLESS;
1353
+ const envHeadless = env('PINEPAPER_HEADLESS');
1272
1354
  const headless = config?.headless ?? (envHeadless !== undefined ? envHeadless !== 'false' : true);
1273
- const envWaitUntil = process.env.PINEPAPER_WAIT_UNTIL?.trim();
1355
+ const envWaitUntil = env('PINEPAPER_WAIT_UNTIL')?.trim();
1274
1356
  if (envWaitUntil && !PAGE_WAIT_UNTIL.includes(envWaitUntil)) {
1275
1357
  // Loudly, because a typo here silently reinstates the hang it was set to
1276
1358
  // avoid — and a 30s connect failure looks nothing like a bad env var.
1277
1359
  console.error(`[PinePaper] PINEPAPER_WAIT_UNTIL="${envWaitUntil}" is not one of ${PAGE_WAIT_UNTIL.join(', ')} — ignoring it.`);
1278
1360
  }
1279
- const envTimeout = Number(process.env.PINEPAPER_TIMEOUT);
1280
- if (process.env.PINEPAPER_TIMEOUT && !Number.isFinite(envTimeout)) {
1281
- console.error(`[PinePaper] PINEPAPER_TIMEOUT="${process.env.PINEPAPER_TIMEOUT}" is not a number — ignoring it.`);
1361
+ const envTimeout = Number(env('PINEPAPER_TIMEOUT'));
1362
+ if (env('PINEPAPER_TIMEOUT') && !Number.isFinite(envTimeout)) {
1363
+ console.error(`[PinePaper] PINEPAPER_TIMEOUT="${env('PINEPAPER_TIMEOUT')}" is not a number — ignoring it.`);
1282
1364
  }
1283
1365
  const mcpConfig = {
1284
1366
  ...config,
1285
1367
  headless,
1286
1368
  waitUntil: config?.waitUntil ?? (envWaitUntil && PAGE_WAIT_UNTIL.includes(envWaitUntil) ? envWaitUntil : undefined),
1287
1369
  timeout: config?.timeout ?? (Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : undefined),
1288
- proxy: config?.proxy ?? process.env.PINEPAPER_PROXY?.trim(),
1289
- governor: config?.governor ?? (process.env.PINEPAPER_GOVERNOR?.trim().toLowerCase() !== 'off'),
1370
+ proxy: config?.proxy ?? env('PINEPAPER_PROXY')?.trim(),
1371
+ governor: config?.governor ?? (env('PINEPAPER_GOVERNOR')?.trim().toLowerCase() !== 'off'),
1290
1372
  };
1291
1373
  globalController = new PinePaperBrowserController(mcpConfig);
1292
1374
  }