@pinepaper.studio/mcp-server 1.6.11 → 1.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/dist/agent/export-manager.d.ts.map +1 -1
- package/dist/agent/export-manager.js +9 -7
- package/dist/agent/export-manager.js.map +1 -1
- package/dist/assets/repositories/iconify.d.ts +0 -5
- package/dist/assets/repositories/iconify.d.ts.map +1 -1
- package/dist/assets/repositories/iconify.js +17 -5
- package/dist/assets/repositories/iconify.js.map +1 -1
- package/dist/browser/puppeteer-controller.d.ts +16 -0
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +32 -2
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/design/design-systems.d.ts +27 -2
- package/dist/design/design-systems.d.ts.map +1 -1
- package/dist/design/design-systems.js +20 -0
- package/dist/design/design-systems.js.map +1 -1
- package/dist/tools/compact-descriptions.js +1 -1
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +175 -52
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +8 -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 +32 -12
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers.d.ts +13 -2
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +219 -13
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.js +1 -1
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +36 -0
- package/dist/tools/shape-params.d.ts.map +1 -0
- package/dist/tools/shape-params.js +159 -0
- package/dist/tools/shape-params.js.map +1 -0
- package/dist/tools/stick-vocabulary.d.ts +26 -0
- package/dist/tools/stick-vocabulary.d.ts.map +1 -0
- package/dist/tools/stick-vocabulary.js +102 -0
- package/dist/tools/stick-vocabulary.js.map +1 -0
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +10 -1
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +865 -122
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +191 -35
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +32 -16
- package/dist/types/schemas.js.map +1 -1
- package/dist/vendor/design/design-systems-dtcg.d.ts +9 -0
- package/dist/vendor/design/design-systems-dtcg.d.ts.map +1 -1
- package/dist/vendor/design/design-systems-dtcg.js +53 -1
- package/dist/vendor/design/design-systems-dtcg.js.map +1 -1
- package/dist/vendor/design/device-mockup-generator.d.ts +83 -0
- package/dist/vendor/design/device-mockup-generator.d.ts.map +1 -0
- package/dist/vendor/design/device-mockup-generator.js +634 -0
- package/dist/vendor/design/device-mockup-generator.js.map +1 -0
- package/dist/vendor/design/recursive-subdivision-generator.d.ts +79 -0
- package/dist/vendor/design/recursive-subdivision-generator.d.ts.map +1 -0
- package/dist/vendor/design/recursive-subdivision-generator.js +319 -0
- package/dist/vendor/design/recursive-subdivision-generator.js.map +1 -0
- package/dist/vendor/design/textile-weave-generator.d.ts +140 -0
- package/dist/vendor/design/textile-weave-generator.d.ts.map +1 -0
- package/dist/vendor/design/textile-weave-generator.js +568 -0
- package/dist/vendor/design/textile-weave-generator.js.map +1 -0
- package/manifest.json +1 -1
- package/package.json +8 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/tools/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAA6B,MAAM,oCAAoC,CAAC;AAgJ/F,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,0BAA0B,EAG3B,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,YAAY,EAA0C,MAAM,uBAAuB,CAAC;AAgC7F;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAOlD;AAaD,eAAO,MAAM,mBAAmB,aAAgD,CAAC;
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/tools/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAA6B,MAAM,oCAAoC,CAAC;AAgJ/F,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,0BAA0B,EAG3B,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAAE,YAAY,EAA0C,MAAM,uBAAuB,CAAC;AAgC7F;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAOlD;AAaD,eAAO,MAAM,mBAAmB,aAAgD,CAAC;AA6EjF,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBnG;AAoCD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA0BpG;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGvD;AAsLD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,MAAM,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAOhD;AAMD,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mEAAmE;IACnE,iBAAiB,CAAC,EAAE,0BAA0B,CAAC;IAC/C,oEAAoE;IACpE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,sEAAsE;IACtE,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AA8HD,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,OAAO,EACpB,OAAO,CAAC,EAAE,YAAY,GACrB,cAAc,CAoChB;AAyBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,MAAM;AAChB;;;;GAIG;AACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,cAAc,CAAC,CA4NzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAS1D;AAMD,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GACtC,MAAM,CASR;AAkFD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAkBzB"}
|
package/dist/tools/handlers.js
CHANGED
|
@@ -84,7 +84,13 @@ function getExportDir() {
|
|
|
84
84
|
// minutes the schema allows is ~77 MB. Deliverable across the bridge, useless
|
|
85
85
|
// pasted into a response.
|
|
86
86
|
export const ALWAYS_SAVE_FORMATS = new Set(['mp4', 'webm', 'gif', 'pdf', 'wav']);
|
|
87
|
-
|
|
87
|
+
// 500_000 was chosen against the bridge's limits, not the CALLER's. A pilot
|
|
88
|
+
// session hit a 263K-character end_job result — comfortably under this, so it
|
|
89
|
+
// was returned inline, and over the tool-result limit of the client reading
|
|
90
|
+
// it, so the agent could not read its own verification step. A value that
|
|
91
|
+
// lets a result through to be rejected downstream is the wrong value; saving
|
|
92
|
+
// to a file and naming the path costs one line of text and always works.
|
|
93
|
+
const SAVE_THRESHOLD_BYTES = 180_000; // ~180KB base64 ≈ 135KB decoded
|
|
88
94
|
/**
|
|
89
95
|
* Governor budget for the export run itself.
|
|
90
96
|
*
|
|
@@ -131,10 +137,82 @@ const IMAGE_MIME = {
|
|
|
131
137
|
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
|
|
132
138
|
'.webp': 'image/webp', '.svg': 'image/svg+xml', '.bmp': 'image/bmp', '.avif': 'image/avif',
|
|
133
139
|
};
|
|
140
|
+
/**
|
|
141
|
+
* THE PAGE CANNOT FETCH A THIRD-PARTY IMAGE. THIS PROCESS CAN.
|
|
142
|
+
*
|
|
143
|
+
* pinepaper.studio serves `connect-src 'self' https://cloud.pinepaper.studio`,
|
|
144
|
+
* so an in-page fetch() of ANY other host fails outright. `img-src` still
|
|
145
|
+
* allows https:, which is why the old path could LOAD a remote image — and
|
|
146
|
+
* tainted the canvas doing it, killing every later export. The taint fix
|
|
147
|
+
* replaced that load with a fetch, which is safe and, under this CSP, can
|
|
148
|
+
* never succeed: import by URL stopped being broken-and-dangerous and became
|
|
149
|
+
* simply impossible.
|
|
150
|
+
*
|
|
151
|
+
* Node has no CSP. Fetching here and handing the page a data: URL is the only
|
|
152
|
+
* shape that both works and cannot taint, so a URL is resolved BEFORE it
|
|
153
|
+
* reaches the browser. Verified end to end by the session that found it: a
|
|
154
|
+
* 157KB data: URL imports and exports cleanly.
|
|
155
|
+
*/
|
|
156
|
+
const MAX_REMOTE_IMAGE_BYTES = 32 * 1024 * 1024;
|
|
157
|
+
export async function fetchImageAsDataUrl(url) {
|
|
158
|
+
let res;
|
|
159
|
+
try {
|
|
160
|
+
res = await fetch(url, { redirect: 'follow' });
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
const why = e instanceof Error ? e.message : 'network request failed';
|
|
164
|
+
return { error: `could not reach ${url} — ${why}. This fetch runs in the MCP server, not the page, so it is not a CSP problem: check the host resolves and any proxy is reachable.` };
|
|
165
|
+
}
|
|
166
|
+
if (!res.ok) {
|
|
167
|
+
return { error: `the server refused ${url} — HTTP ${res.status} ${res.statusText || ''}`.trim() };
|
|
168
|
+
}
|
|
169
|
+
const type = (res.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();
|
|
170
|
+
if (!type.startsWith('image/')) {
|
|
171
|
+
return { error: `${url} served "${type || 'no content-type'}", not an image. A URL that returns an HTML page around the image will do this — link the file itself.` };
|
|
172
|
+
}
|
|
173
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
174
|
+
if (buf.length > MAX_REMOTE_IMAGE_BYTES) {
|
|
175
|
+
return { error: `${url} is ${(buf.length / 1024 / 1024).toFixed(1)} MB, above the ${MAX_REMOTE_IMAGE_BYTES / 1024 / 1024} MB import cap.` };
|
|
176
|
+
}
|
|
177
|
+
return { src: `data:${type};base64,${buf.toString('base64')}` };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Resolve an SVG to a string with every remote <image> already inlined.
|
|
181
|
+
*
|
|
182
|
+
* The emitted code does this in the page as a backstop, and under
|
|
183
|
+
* pinepaper.studio's CSP that backstop can only ever DROP images — the page
|
|
184
|
+
* may not fetch a third-party host. Doing it here means the page receives a
|
|
185
|
+
* document with no remote references at all: nothing to fetch, nothing to
|
|
186
|
+
* taint, and the pictures actually arrive.
|
|
187
|
+
*
|
|
188
|
+
* An href that cannot be fetched is still removed rather than left in, on the
|
|
189
|
+
* same reasoning as the in-page pass: a missing picture is visible and
|
|
190
|
+
* recoverable, a tainted canvas is neither.
|
|
191
|
+
*/
|
|
192
|
+
async function inlineSvgImages(svg) {
|
|
193
|
+
const warnings = [];
|
|
194
|
+
const hrefs = [...new Set([...svg.matchAll(/(?:xlink:)?href\s*=\s*("|')(https?:\/\/[^"']+)\1/gi)].map((m) => m[2]))];
|
|
195
|
+
let out = svg;
|
|
196
|
+
for (const href of hrefs) {
|
|
197
|
+
const got = await fetchImageAsDataUrl(href);
|
|
198
|
+
if ('src' in got) {
|
|
199
|
+
out = out.split(href).join(got.src);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const esc = href.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
203
|
+
out = out
|
|
204
|
+
.replace(new RegExp(`<image\\b[^>]*${esc}[^>]*\\/?>`, 'gi'), '')
|
|
205
|
+
.replace(new RegExp(`<image\\b[^>]*${esc}[\\s\\S]*?<\\/image>`, 'gi'), '');
|
|
206
|
+
warnings.push({ url: href, action: 'removed', reason: 'unfetchable', message: got.error });
|
|
207
|
+
}
|
|
208
|
+
return { svg: out, warnings };
|
|
209
|
+
}
|
|
134
210
|
export async function resolveImageSource(input) {
|
|
135
211
|
const raw = input.trim();
|
|
136
|
-
if (/^
|
|
212
|
+
if (/^data:/i.test(raw))
|
|
137
213
|
return { src: raw };
|
|
214
|
+
if (/^https?:/i.test(raw))
|
|
215
|
+
return fetchImageAsDataUrl(raw);
|
|
138
216
|
const path = raw.startsWith('file://') ? fileURLToPath(raw) : raw;
|
|
139
217
|
const ext = extname(path).toLowerCase();
|
|
140
218
|
if (!IMAGE_EXTENSIONS.has(ext)) {
|
|
@@ -416,7 +494,44 @@ function screenshotResult(screenshot) {
|
|
|
416
494
|
],
|
|
417
495
|
};
|
|
418
496
|
}
|
|
419
|
-
|
|
497
|
+
/**
|
|
498
|
+
* Swap a `code` field carrying the emitted script for a description of it.
|
|
499
|
+
*
|
|
500
|
+
* Only a long string is touched: a short `code` is usually a status or an
|
|
501
|
+
* error code, not a program, and stripping those would take information away.
|
|
502
|
+
*/
|
|
503
|
+
function redactGeneratedCode(details) {
|
|
504
|
+
if (process.env.PINEPAPER_ECHO_CODE === '1')
|
|
505
|
+
return details;
|
|
506
|
+
if (!details || typeof details !== 'object' || Array.isArray(details))
|
|
507
|
+
return details;
|
|
508
|
+
const d = details;
|
|
509
|
+
const script = d.code;
|
|
510
|
+
if (typeof script !== 'string' || script.length < 400)
|
|
511
|
+
return details;
|
|
512
|
+
const { code: _dropped, ...rest } = d;
|
|
513
|
+
return {
|
|
514
|
+
...rest,
|
|
515
|
+
generatedCode: {
|
|
516
|
+
bytes: script.length,
|
|
517
|
+
lines: script.split('\n').length,
|
|
518
|
+
firstLine: script.split('\n').find((l) => l.trim()) ?? '',
|
|
519
|
+
hint: 'omitted — set PINEPAPER_ECHO_CODE=1 to return the generated script with the error',
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
export function errorResult(code, message, rawDetails, context) {
|
|
524
|
+
// THE GENERATED SCRIPT IS NOT THE ERROR.
|
|
525
|
+
//
|
|
526
|
+
// Most error paths pass the emitted code in `details` as evidence, and for a
|
|
527
|
+
// long script that is ~12KB of JavaScript in front of a one-line failure —
|
|
528
|
+
// measured from a pilot session. An agent reading the result pays for all of
|
|
529
|
+
// it and learns nothing the message does not already say, and on a big
|
|
530
|
+
// export it can crowd out the result entirely.
|
|
531
|
+
//
|
|
532
|
+
// Replaced with its shape and a way to get it back. PINEPAPER_ECHO_CODE=1
|
|
533
|
+
// returns the script in full, the same switch that governs it on success.
|
|
534
|
+
const details = redactGeneratedCode(rawDetails);
|
|
420
535
|
// Build error object
|
|
421
536
|
const errorObj = {
|
|
422
537
|
success: false,
|
|
@@ -470,7 +585,13 @@ function handleValidationError(error, i18n) {
|
|
|
470
585
|
*
|
|
471
586
|
* Default is 'on_request' for optimal performance per best practices.
|
|
472
587
|
*/
|
|
473
|
-
export async function executeOrGenerate(code, description, options, toolName
|
|
588
|
+
export async function executeOrGenerate(code, description, options, toolName,
|
|
589
|
+
/**
|
|
590
|
+
* Values too large to live inside the generated code. Written to
|
|
591
|
+
* window.__ppStage before the run and read back by key — see
|
|
592
|
+
* ExecuteCodeOptions.stage for why inlining them breaks the governor.
|
|
593
|
+
*/
|
|
594
|
+
stage) {
|
|
474
595
|
const { executeInBrowser, browserController, screenshotMode, executionMode } = options;
|
|
475
596
|
const effectiveExecutionMode = executionMode ?? getExecutionMode();
|
|
476
597
|
const effectiveScreenshotMode = screenshotMode ?? getScreenshotMode();
|
|
@@ -604,7 +725,7 @@ ${code}
|
|
|
604
725
|
if (shouldTakeScreenshot) {
|
|
605
726
|
tracker.startTimer(`${timerId}_screenshot`);
|
|
606
727
|
}
|
|
607
|
-
const result = await controller.executeCode(code, shouldTakeScreenshot);
|
|
728
|
+
const result = await controller.executeCode(code, shouldTakeScreenshot, stage ? { stage } : {});
|
|
608
729
|
const browserDuration = tracker.endTimer(`${timerId}_browser_execution`);
|
|
609
730
|
tracker.recordMetric({
|
|
610
731
|
toolName,
|
|
@@ -963,6 +1084,11 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
963
1084
|
}
|
|
964
1085
|
case 'pinepaper_query_capabilities': {
|
|
965
1086
|
const input = QueryCapabilitiesInputSchema.parse(args);
|
|
1087
|
+
// `query` is what callers reach for on an action named 'find'; `key`
|
|
1088
|
+
// is what the emitter reads. Accepted as an alias rather than left as
|
|
1089
|
+
// a validation dead end.
|
|
1090
|
+
if (!input.key && input.query)
|
|
1091
|
+
input.key = input.query;
|
|
966
1092
|
const code = codeGenerator.generateQueryCapabilities(input);
|
|
967
1093
|
return executeOrGenerate(code, `Capabilities: ${input.action || 'list'}`, options, 'pinepaper_query_capabilities');
|
|
968
1094
|
}
|
|
@@ -1148,7 +1274,12 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
1148
1274
|
return executeOrGenerate(code, `Media: ${input.action}`, options, 'pinepaper_media');
|
|
1149
1275
|
}
|
|
1150
1276
|
case 'pinepaper_text_style': {
|
|
1277
|
+
// 'list' is an alias for 'list_styles'. pinepaper_text_effect spells
|
|
1278
|
+
// the same action 'list', so an agent moving between the two guesses
|
|
1279
|
+
// wrong — and a rejected enum value is a dead end rather than a hint.
|
|
1151
1280
|
const input = TextStyleInputSchema.parse(args);
|
|
1281
|
+
if (input.action === 'list')
|
|
1282
|
+
input.action = 'list_styles';
|
|
1152
1283
|
const code = codeGenerator.generateTextStyle(input);
|
|
1153
1284
|
return executeOrGenerate(code, `Text style: ${input.action}`, options, 'pinepaper_text_style');
|
|
1154
1285
|
}
|
|
@@ -1468,8 +1599,39 @@ You can now start creating new items on a clean canvas.`,
|
|
|
1468
1599
|
// -----------------------------------------------------------------------
|
|
1469
1600
|
case 'pinepaper_import_svg': {
|
|
1470
1601
|
const input = ImportSVGInputSchema.parse(args);
|
|
1471
|
-
|
|
1472
|
-
|
|
1602
|
+
// Resolve the document HERE, so the page never has to fetch anything.
|
|
1603
|
+
//
|
|
1604
|
+
// Under pinepaper.studio's CSP (connect-src 'self' + the cloud origin)
|
|
1605
|
+
// the page cannot fetch a third-party host at all, so the in-page
|
|
1606
|
+
// inlining could only ever DROP remote images. Fetching the SVG and
|
|
1607
|
+
// its images in this process means the browser receives a document
|
|
1608
|
+
// with no remote references: nothing to fetch, nothing to taint, and
|
|
1609
|
+
// the pictures arrive. The in-page pass stays as a backstop for an
|
|
1610
|
+
// svgString handed straight to the generator elsewhere.
|
|
1611
|
+
let svgText = input.svgString;
|
|
1612
|
+
let svgWarnings = [];
|
|
1613
|
+
if (input.url) {
|
|
1614
|
+
try {
|
|
1615
|
+
const res = await fetch(input.url, { redirect: 'follow' });
|
|
1616
|
+
if (!res.ok) {
|
|
1617
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, `the server refused ${input.url} — HTTP ${res.status} ${res.statusText || ''}`.trim());
|
|
1618
|
+
}
|
|
1619
|
+
svgText = await res.text();
|
|
1620
|
+
}
|
|
1621
|
+
catch (e) {
|
|
1622
|
+
const why = e instanceof Error ? e.message : 'network request failed';
|
|
1623
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, `could not reach ${input.url} — ${why}. This fetch runs in the MCP server rather than the page, so it is not a CSP problem.`);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
if (svgText && /(?:xlink:)?href\s*=\s*["']https?:/i.test(svgText)) {
|
|
1627
|
+
const inlined = await inlineSvgImages(svgText);
|
|
1628
|
+
svgText = inlined.svg;
|
|
1629
|
+
svgWarnings = inlined.warnings;
|
|
1630
|
+
}
|
|
1631
|
+
const code = codeGenerator.generateImportSVG(svgText, svgText ? undefined : input.url, input.position, input.scale, input.source);
|
|
1632
|
+
const description = svgWarnings.length
|
|
1633
|
+
? `Imports SVG onto the canvas (${svgWarnings.length} unreachable image(s) removed)`
|
|
1634
|
+
: 'Imports SVG onto the canvas';
|
|
1473
1635
|
return executeOrGenerate(code, description, options, 'pinepaper_import_svg');
|
|
1474
1636
|
}
|
|
1475
1637
|
case 'pinepaper_import_mermaid': {
|
|
@@ -1987,9 +2149,34 @@ You can now start creating new items on a clean canvas.`,
|
|
|
1987
2149
|
metadata = result.metadata;
|
|
1988
2150
|
}
|
|
1989
2151
|
else if (input.url) {
|
|
1990
|
-
//
|
|
1991
|
-
//
|
|
1992
|
-
|
|
2152
|
+
// IMPLEMENTED NOW, rather than redirected.
|
|
2153
|
+
//
|
|
2154
|
+
// This used to refuse with "URL imports not yet implemented" while
|
|
2155
|
+
// the agent guide documented the url parameter — the redirect
|
|
2156
|
+
// named a working alternative, which made it a good refusal, but
|
|
2157
|
+
// it was still a documented parameter that did nothing. The reason
|
|
2158
|
+
// it could not be done here was that fetching belonged to the
|
|
2159
|
+
// page; it does not any more, because the page cannot fetch a
|
|
2160
|
+
// third-party host under the studio's CSP and this process can.
|
|
2161
|
+
let res;
|
|
2162
|
+
try {
|
|
2163
|
+
res = await fetch(input.url, { redirect: 'follow' });
|
|
2164
|
+
}
|
|
2165
|
+
catch (e) {
|
|
2166
|
+
const why = e instanceof Error ? e.message : 'network request failed';
|
|
2167
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, `could not reach ${input.url} — ${why}. This fetch runs in the MCP server rather than the page, so it is not a CSP problem.`);
|
|
2168
|
+
}
|
|
2169
|
+
if (!res.ok) {
|
|
2170
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, `the server refused ${input.url} — HTTP ${res.status} ${res.statusText || ''}`.trim());
|
|
2171
|
+
}
|
|
2172
|
+
const body = await res.text();
|
|
2173
|
+
if (!/<svg[\s>]/i.test(body)) {
|
|
2174
|
+
const type = (res.headers.get('content-type') ?? '').split(';')[0].trim();
|
|
2175
|
+
return errorResult(ErrorCodes.INVALID_PARAMS, `${input.url} served "${type || 'no content-type'}" and its body is not SVG. `
|
|
2176
|
+
+ 'For a raster image use pinepaper_import_image, which accepts a url and inlines it.');
|
|
2177
|
+
}
|
|
2178
|
+
svg = body;
|
|
2179
|
+
metadata = { source: 'url', url: input.url };
|
|
1993
2180
|
}
|
|
1994
2181
|
// Import the SVG onto canvas using existing import_svg tool
|
|
1995
2182
|
const code = codeGenerator.generateImportSVG(svg, undefined, input.position, input.scale);
|
|
@@ -2131,7 +2318,12 @@ You can now start creating new items on a clean canvas.`,
|
|
|
2131
2318
|
const previewScreenshot = await endJobController.takeScreenshot();
|
|
2132
2319
|
const content = [{
|
|
2133
2320
|
type: 'text',
|
|
2134
|
-
|
|
2321
|
+
// The script is not part of the answer here either — same reason
|
|
2322
|
+
// errors no longer carry it. PINEPAPER_ECHO_CODE=1 brings it back.
|
|
2323
|
+
text: (process.env.PINEPAPER_ECHO_CODE === '1'
|
|
2324
|
+
? `Executed PinePaper code:\n\n\`\`\`javascript\n${code}\n\`\`\`\n\n`
|
|
2325
|
+
: '')
|
|
2326
|
+
+ `Full screenshot saved to: ${filePath} (${(fileSize / 1024).toFixed(1)} KB)\n\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
|
|
2135
2327
|
}];
|
|
2136
2328
|
if (previewScreenshot) {
|
|
2137
2329
|
content.push({
|
|
@@ -2638,8 +2830,22 @@ You can now start creating new items on a clean canvas.`,
|
|
|
2638
2830
|
if ('error' in resolved) {
|
|
2639
2831
|
return errorResult(ErrorCodes.INVALID_PARAMS, resolved.error, { url: parsed.url }, { toolName: 'pinepaper_import_image' });
|
|
2640
2832
|
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2833
|
+
// THE BYTES GO BESIDE THE CODE, NOT INSIDE IT.
|
|
2834
|
+
//
|
|
2835
|
+
// Inlining the data: URL turned a 3000x800 photo into ~630KB of
|
|
2836
|
+
// generated JavaScript. app.runGenerated rewrites what it runs, and its
|
|
2837
|
+
// loop-guard transform bails at that size — "Loop-guard transform
|
|
2838
|
+
// bailed (unusual syntax)" — after which the run reported
|
|
2839
|
+
// itemsCreated: 0 and lost the return value, so the import answered
|
|
2840
|
+
// success with no itemId. Smaller photos were fine, which is exactly
|
|
2841
|
+
// what made it look like a registration bug rather than a size one.
|
|
2842
|
+
const staged = resolved.src.startsWith('data:') && resolved.src.length > 64_000;
|
|
2843
|
+
const stageKey = staged ? `img_${Date.now().toString(36)}` : undefined;
|
|
2844
|
+
const code = codeGenerator.generateImportImage({
|
|
2845
|
+
...parsed,
|
|
2846
|
+
url: staged ? `__ppStage:${stageKey}` : resolved.src,
|
|
2847
|
+
});
|
|
2848
|
+
return executeOrGenerate(code, 'Imports image', options, 'pinepaper_import_image', staged ? { [stageKey]: resolved.src } : undefined);
|
|
2643
2849
|
}
|
|
2644
2850
|
// -----------------------------------------------------------------------
|
|
2645
2851
|
// ONTOLOGY TOOLS — extracted to src/tools/handlers/ontology.ts
|