@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.
- package/README.md +167 -20
- package/SECURITY.md +15 -1
- package/dist/browser/puppeteer-controller.d.ts +7 -1
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +151 -69
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/cli.js +7 -5
- package/dist/cli.js.map +1 -1
- package/dist/design/design-systems.js +2 -2
- package/dist/design/design-systems.js.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +86 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +159 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +20 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/stdio.js +82 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/types.d.ts +598 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +140 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/metrics/performance-tracker.d.ts.map +1 -1
- package/dist/metrics/performance-tracker.js +4 -3
- package/dist/metrics/performance-tracker.js.map +1 -1
- package/dist/ontology/hints.d.ts.map +1 -1
- package/dist/ontology/hints.js +3 -1
- package/dist/ontology/hints.js.map +1 -1
- package/dist/ontology/upstream-vocabulary.d.ts +2 -2
- package/dist/ontology/upstream-vocabulary.d.ts.map +1 -1
- package/dist/ontology/upstream-vocabulary.js +8 -2
- package/dist/ontology/upstream-vocabulary.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/tools/batch-macros.d.ts.map +1 -1
- package/dist/tools/batch-macros.js +2 -1
- package/dist/tools/batch-macros.js.map +1 -1
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +7 -6
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/craft-tools.d.ts +1 -1
- package/dist/tools/craft-tools.d.ts.map +1 -1
- package/dist/tools/create-known-keys.d.ts +2 -0
- package/dist/tools/create-known-keys.d.ts.map +1 -0
- package/dist/tools/create-known-keys.js +24 -0
- package/dist/tools/create-known-keys.js.map +1 -0
- package/dist/tools/definitions.d.ts +3 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +43 -22
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/effect-check.d.ts +1 -1
- package/dist/tools/effect-check.d.ts.map +1 -1
- package/dist/tools/effect-check.js +4 -0
- package/dist/tools/effect-check.js.map +1 -1
- package/dist/tools/engine-lists.d.ts +4 -1
- package/dist/tools/engine-lists.d.ts.map +1 -1
- package/dist/tools/engine-lists.js +9 -1
- package/dist/tools/engine-lists.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +1 -0
- package/dist/tools/engine-methods.js.map +1 -1
- package/dist/tools/engine-surface.d.ts +1 -1
- package/dist/tools/engine-surface.d.ts.map +1 -1
- package/dist/tools/engine-surface.js +19 -4
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/export-file-receipt.d.ts +12 -0
- package/dist/tools/export-file-receipt.d.ts.map +1 -1
- package/dist/tools/export-file-receipt.js +29 -2
- package/dist/tools/export-file-receipt.js.map +1 -1
- package/dist/tools/handlers/beats.d.ts +1 -1
- package/dist/tools/handlers/beats.d.ts.map +1 -1
- package/dist/tools/handlers/camera.d.ts +1 -1
- package/dist/tools/handlers/camera.d.ts.map +1 -1
- package/dist/tools/handlers/export.d.ts +1 -1
- package/dist/tools/handlers/export.d.ts.map +1 -1
- package/dist/tools/handlers/export.js +3 -2
- package/dist/tools/handlers/export.js.map +1 -1
- package/dist/tools/handlers/font.d.ts +1 -1
- package/dist/tools/handlers/font.d.ts.map +1 -1
- package/dist/tools/handlers/font.js +8 -7
- package/dist/tools/handlers/font.js.map +1 -1
- package/dist/tools/handlers/generate.d.ts +1 -1
- package/dist/tools/handlers/generate.d.ts.map +1 -1
- package/dist/tools/handlers/generate.js +4 -3
- package/dist/tools/handlers/generate.js.map +1 -1
- package/dist/tools/handlers/maps.d.ts +1 -1
- package/dist/tools/handlers/maps.d.ts.map +1 -1
- package/dist/tools/handlers/ontology.d.ts +1 -1
- package/dist/tools/handlers/ontology.d.ts.map +1 -1
- package/dist/tools/handlers/tool-guide.d.ts +1 -1
- package/dist/tools/handlers/tool-guide.d.ts.map +1 -1
- package/dist/tools/handlers.d.ts +15 -3
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +71 -31
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/lane.d.ts +11 -6
- package/dist/tools/lane.d.ts.map +1 -1
- package/dist/tools/lane.js +46 -9
- package/dist/tools/lane.js.map +1 -1
- package/dist/tools/minimal-descriptions.js +1 -1
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/p5-compat/p5-helpers.d.ts +1 -1
- package/dist/tools/p5-compat/p5-helpers.js +1 -1
- package/dist/tools/shape-params.d.ts +2 -2
- package/dist/tools/shape-params.d.ts.map +1 -1
- package/dist/tools/shape-params.js +8 -2
- package/dist/tools/shape-params.js.map +1 -1
- package/dist/tools/stick-vocabulary.d.ts +2 -2
- package/dist/tools/stick-vocabulary.js +2 -2
- package/dist/tools/timeline.d.ts +2 -2
- package/dist/tools/timeline.d.ts.map +1 -1
- package/dist/tools/timeline.js +6 -1
- package/dist/tools/timeline.js.map +1 -1
- package/dist/tools/toolkits.d.ts +22 -7
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +102 -20
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +25 -0
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +424 -157
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +52 -47
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +7 -5
- package/dist/types/schemas.js.map +1 -1
- package/dist/utils/asset-paths.d.ts.map +1 -1
- package/dist/utils/asset-paths.js +4 -4
- package/dist/utils/asset-paths.js.map +1 -1
- package/dist/utils/env.d.ts +9 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +11 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/export-dir.d.ts +36 -0
- package/dist/utils/export-dir.d.ts.map +1 -0
- package/dist/utils/export-dir.js +86 -0
- package/dist/utils/export-dir.js.map +1 -0
- package/dist/utils/js-literal.d.ts +4 -0
- package/dist/utils/js-literal.d.ts.map +1 -0
- package/dist/utils/js-literal.js +25 -0
- package/dist/utils/js-literal.js.map +1 -0
- package/dist/utils/mcp-log.d.ts +1 -1
- package/dist/utils/mcp-log.d.ts.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +5 -12
- package/dist/version.js.map +1 -1
- package/manifest.json +42 -63
- 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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
| `
|
|
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`) |
|
|
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
|
-
*
|
|
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":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
719
|
-
// bypass.
|
|
720
|
-
//
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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 =
|
|
1353
|
+
const envHeadless = env('PINEPAPER_HEADLESS');
|
|
1272
1354
|
const headless = config?.headless ?? (envHeadless !== undefined ? envHeadless !== 'false' : true);
|
|
1273
|
-
const envWaitUntil =
|
|
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(
|
|
1280
|
-
if (
|
|
1281
|
-
console.error(`[PinePaper] PINEPAPER_TIMEOUT="${
|
|
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 ??
|
|
1289
|
-
governor: config?.governor ?? (
|
|
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
|
}
|