@pinepaper.studio/mcp-server 1.6.20 → 1.6.22
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.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.hi.md +1 -1
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +96 -5
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/build-info.json +3 -3
- package/dist/ontology/upstream-vocabulary.d.ts +2 -2
- package/dist/ontology/upstream-vocabulary.js +3 -3
- package/dist/ontology/upstream-vocabulary.js.map +1 -1
- package/dist/ontology/vocabulary.js +1 -1
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/tools/choreograph.d.ts.map +1 -1
- package/dist/tools/choreograph.js +10 -0
- package/dist/tools/choreograph.js.map +1 -1
- package/dist/tools/craft-tools.d.ts +55 -0
- package/dist/tools/craft-tools.d.ts.map +1 -0
- package/dist/tools/craft-tools.js +139 -0
- package/dist/tools/craft-tools.js.map +1 -0
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +117 -7
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/effect-class.d.ts.map +1 -1
- package/dist/tools/effect-class.js +6 -0
- package/dist/tools/effect-class.js.map +1 -1
- package/dist/tools/engine-lists.d.ts +2 -2
- package/dist/tools/engine-lists.js +2 -2
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +4 -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 +55 -7
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/brand-intake.d.ts.map +1 -1
- package/dist/tools/handlers/brand-intake.js +42 -1
- package/dist/tools/handlers/brand-intake.js.map +1 -1
- package/dist/tools/handlers/generate.d.ts +28 -28
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +161 -10
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +7 -0
- package/dist/tools/minimal-descriptions.js.map +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 +9 -2
- package/dist/tools/shape-params.js.map +1 -1
- package/dist/tools/stick-vocabulary.d.ts +1 -1
- package/dist/tools/stick-vocabulary.js +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +9 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +10 -1
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +605 -107
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +1745 -582
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +144 -4
- package/dist/types/schemas.js.map +1 -1
- package/dist/utils/asset-paths.d.ts +11 -0
- package/dist/utils/asset-paths.d.ts.map +1 -0
- package/dist/utils/asset-paths.js +67 -0
- package/dist/utils/asset-paths.js.map +1 -0
- package/dist/utils/safe-fetch.d.ts +28 -0
- package/dist/utils/safe-fetch.d.ts.map +1 -0
- package/dist/utils/safe-fetch.js +221 -0
- package/dist/utils/safe-fetch.js.map +1 -0
- package/dist/vendor/design/bauhaus-generator.d.ts.map +1 -1
- package/dist/vendor/design/bauhaus-generator.js +20 -5
- package/dist/vendor/design/bauhaus-generator.js.map +1 -1
- package/dist/vendor/design/design-style.d.ts +2 -2
- package/dist/vendor/design/design-style.d.ts.map +1 -1
- package/dist/vendor/design/design-style.js +7 -2
- package/dist/vendor/design/design-style.js.map +1 -1
- package/dist/vendor/design/subdivision-tool-schema.d.ts +16 -0
- package/dist/vendor/design/subdivision-tool-schema.d.ts.map +1 -0
- package/dist/vendor/design/subdivision-tool-schema.js +104 -0
- package/dist/vendor/design/subdivision-tool-schema.js.map +1 -0
- package/dist/vendor/design/textile-tool-schema.d.ts +14 -0
- package/dist/vendor/design/textile-tool-schema.d.ts.map +1 -0
- package/dist/vendor/design/textile-tool-schema.js +195 -0
- package/dist/vendor/design/textile-tool-schema.js.map +1 -0
- package/dist/vendor/design/ui-kit-runtime.d.ts +49 -0
- package/dist/vendor/design/ui-kit-runtime.d.ts.map +1 -0
- package/dist/vendor/design/ui-kit-runtime.js +761 -0
- package/dist/vendor/design/ui-kit-runtime.js.map +1 -0
- package/dist/vendor/design/ui-kit-tool-schema.d.ts +280 -0
- package/dist/vendor/design/ui-kit-tool-schema.d.ts.map +1 -0
- package/dist/vendor/design/ui-kit-tool-schema.js +209 -0
- package/dist/vendor/design/ui-kit-tool-schema.js.map +1 -0
- package/manifest.json +29 -1
- package/package.json +7 -4
- package/dist/assets/repositories/openclipart.d.ts +0 -41
- package/dist/assets/repositories/openclipart.d.ts.map +0 -1
- package/dist/assets/repositories/openclipart.js +0 -139
- package/dist/assets/repositories/openclipart.js.map +0 -1
- package/dist/assets/repositories/svgrepo.d.ts +0 -61
- package/dist/assets/repositories/svgrepo.d.ts.map +0 -1
- package/dist/assets/repositories/svgrepo.js +0 -276
- package/dist/assets/repositories/svgrepo.js.map +0 -1
package/dist/tools/handlers.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Supports i18n for localized error and success messages.
|
|
6
6
|
* Supports browser execution via Puppeteer.
|
|
7
7
|
*/
|
|
8
|
+
import { CRAFT_FRAME_JS, generateDrawWeave, generateDrawKnots, generateDrawSashiko, generateDrawSubdivision, generateUiKit, uiKitCallProblems, } from './craft-tools.js';
|
|
8
9
|
import { codeGenerator, generateQueryMutationsCode, SHEET_RUBRIC } from '../types/code-generator.js';
|
|
9
10
|
import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, ImportMotionCaptureInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, GetItemsInputSchema, PlayTimelineInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, ImportSVGInputSchema, ImportMermaidInputSchema, AddFilterInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, GeometryInputSchema, EquationPathInputSchema, EventInputSchema, ConstructionSequenceInputSchema, ValidateSceneInputSchema, CaptureFramesInputSchema, InstantiateOntologyInputSchema, LintSceneInputSchema, MediaInputSchema, TextStyleInputSchema, TextEffectInputSchema, DesignMediumInputSchema, RelightInputSchema, ChoreographInputSchema, EmitterInputSchema, MorphSequenceInputSchema, OriginalCharacterInputSchema, LookInputSchema, RenderHookInputSchema, ShaderGraphInputSchema, ShatterImageInputSchema, ImportLayeredCharacterInputSchema, CharacterInputSchema, GameInputSchema, World3DInputSchema, BrandKitInputSchema, ComponentInputSchema, ArtboardInputSchema, CommentInputSchema, ProvenanceInputSchema, SceneDiffInputSchema, AudioBeatsInputSchema, TemplateParamsInputSchema, RenderBatchInputSchema, AccessibilityCheckInputSchema, QueryMutationsInputSchema, ComposeInputSchema, CropImageInputSchema, PathOpInputSchema, MotionInputSchema, SoundInputSchema, InterchangeInputSchema, StickInputSchema, DesignSystemInputSchema, StoryInputSchema, ChromaKeyInputSchema, RiggingInputSchema, GroupInputSchema, CameraDirectorInputSchema, DetectObjectsInputSchema, ExtractObjectInputSchema, ArrangeInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, DiagnosticReportInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema,
|
|
10
11
|
// Diagram schemas
|
|
@@ -27,7 +28,7 @@ ImportImageInputSchema,
|
|
|
27
28
|
// Scene management schemas
|
|
28
29
|
ManageScenesInputSchema, ScenePlaybackInputSchema, SceneGraphInputSchema, SequenceInputSchema, StaggerInputSchema, FlipInputSchema,
|
|
29
30
|
// New consolidated tool schemas
|
|
30
|
-
SelectionInputSchema, TransformInputSchema, HistoryInputSchema, ImageFilterInputSchema, LassoInputSchema, CutoutStyleInputSchema, PrecompInputSchema, BackgroundInputSchema, QueryInputSchema, QueryCapabilitiesInputSchema, DeformInputSchema, SpriteSheetInputSchema, InteractionInputSchema, ExportWidgetInputSchema, ExportWidgetHtmlInputSchema, CreateChartInputSchema, MagicInputSchema, PhysicsInputSchema, MeasurementInputSchema, PlaceOnSurfaceInputSchema, StyledSceneInputSchema, ErrorCodes, } from '../types/schemas.js';
|
|
31
|
+
SelectionInputSchema, TransformInputSchema, HistoryInputSchema, ImageFilterInputSchema, LassoInputSchema, CutoutStyleInputSchema, PrecompInputSchema, BackgroundInputSchema, QueryInputSchema, QueryCapabilitiesInputSchema, DeformInputSchema, HandDrawnInputSchema, PenFigureInputSchema, CreateCharacterInputSchema, CharacterActionInputSchema, CharacterPoseInputSchema, SpriteSheetInputSchema, InteractionInputSchema, ExportWidgetInputSchema, ExportWidgetHtmlInputSchema, CreateChartInputSchema, MagicInputSchema, PhysicsInputSchema, MeasurementInputSchema, PlaceOnSurfaceInputSchema, DrawWeaveInputSchema, DrawKnotsInputSchema, DrawSashikoInputSchema, DrawSubdivisionInputSchema, UiKitInputSchema, StyledSceneInputSchema, ErrorCodes, } from '../types/schemas.js';
|
|
31
32
|
import { ZodError, z } from 'zod';
|
|
32
33
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
33
34
|
import { effectClass } from './effect-class.js';
|
|
@@ -57,6 +58,8 @@ import { buildZip } from '../utils/zip.js';
|
|
|
57
58
|
import { buildScc } from '../utils/scc.js';
|
|
58
59
|
import { checkContrast, checkFlashes, parseCssColor } from '../utils/a11y.js';
|
|
59
60
|
import { buildHtml5Ad, buildPlayable, externalRequests, pinSceneBackground } from '../utils/ad-package.js';
|
|
61
|
+
import { safeFetch, checkUrl, UnsafeUrlError } from '../utils/safe-fetch.js';
|
|
62
|
+
import { confineAssetPath } from '../utils/asset-paths.js';
|
|
60
63
|
/**
|
|
61
64
|
* Registry of per-domain handler maps. Tools listed here short-circuit the
|
|
62
65
|
* main switch at dispatch time (see handleToolCallInner). New domains are
|
|
@@ -175,9 +178,11 @@ const MAX_REMOTE_IMAGE_BYTES = 32 * 1024 * 1024;
|
|
|
175
178
|
export async function fetchImageAsDataUrl(url) {
|
|
176
179
|
let res;
|
|
177
180
|
try {
|
|
178
|
-
res = await
|
|
181
|
+
res = await safeFetch(url);
|
|
179
182
|
}
|
|
180
183
|
catch (e) {
|
|
184
|
+
if (e instanceof UnsafeUrlError)
|
|
185
|
+
return { error: e.message };
|
|
181
186
|
const why = e instanceof Error ? e.message : 'network request failed';
|
|
182
187
|
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.` };
|
|
183
188
|
}
|
|
@@ -231,14 +236,18 @@ export async function resolveImageSource(input) {
|
|
|
231
236
|
return { src: raw };
|
|
232
237
|
if (/^https?:/i.test(raw))
|
|
233
238
|
return fetchImageAsDataUrl(raw);
|
|
234
|
-
const
|
|
235
|
-
const ext = extname(
|
|
239
|
+
const asked = raw.startsWith('file://') ? fileURLToPath(raw) : raw;
|
|
240
|
+
const ext = extname(asked).toLowerCase();
|
|
236
241
|
if (!IMAGE_EXTENSIONS.has(ext)) {
|
|
237
242
|
return {
|
|
238
243
|
error: `"${input}" is neither an http(s) URL nor a path to an image file. `
|
|
239
244
|
+ `Recognised image extensions: ${[...IMAGE_EXTENSIONS].join(', ')}.`,
|
|
240
245
|
};
|
|
241
246
|
}
|
|
247
|
+
const confined = await confineAssetPath(asked);
|
|
248
|
+
if ('error' in confined)
|
|
249
|
+
return confined;
|
|
250
|
+
const path = confined.path;
|
|
242
251
|
let bytes;
|
|
243
252
|
try {
|
|
244
253
|
const info = await stat(path);
|
|
@@ -286,9 +295,11 @@ export async function resolveMediaSource(input) {
|
|
|
286
295
|
if (/^https?:/i.test(raw)) {
|
|
287
296
|
let res;
|
|
288
297
|
try {
|
|
289
|
-
res = await
|
|
298
|
+
res = await safeFetch(raw, { headers: { 'User-Agent': 'PinePaper-MCP (+https://pinepaper.studio)' } });
|
|
290
299
|
}
|
|
291
300
|
catch (e) {
|
|
301
|
+
if (e instanceof UnsafeUrlError)
|
|
302
|
+
return { error: e.message };
|
|
292
303
|
const why = e instanceof Error ? e.message : 'network request failed';
|
|
293
304
|
return { error: `could not reach ${raw} — ${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.` };
|
|
294
305
|
}
|
|
@@ -308,11 +319,15 @@ export async function resolveMediaSource(input) {
|
|
|
308
319
|
return tooBig(raw, buf.length);
|
|
309
320
|
return { src: `data:${type};base64,${buf.toString('base64')}` };
|
|
310
321
|
}
|
|
311
|
-
const
|
|
312
|
-
const type = MEDIA_MIME[extname(
|
|
322
|
+
const asked = raw.startsWith('file://') ? fileURLToPath(raw) : raw;
|
|
323
|
+
const type = MEDIA_MIME[extname(asked).toLowerCase()];
|
|
313
324
|
if (!type) {
|
|
314
325
|
return { error: `"${input}" is neither an http(s) URL, a data: URL, nor a path to an audio/video file. Recognised extensions: ${Object.keys(MEDIA_MIME).join(', ')}.` };
|
|
315
326
|
}
|
|
327
|
+
const confined = await confineAssetPath(asked);
|
|
328
|
+
if ('error' in confined)
|
|
329
|
+
return confined;
|
|
330
|
+
const path = confined.path;
|
|
316
331
|
try {
|
|
317
332
|
const info = await stat(path);
|
|
318
333
|
if (!info.isFile())
|
|
@@ -983,6 +998,15 @@ export async function customCodeRollback(controller, error) {
|
|
|
983
998
|
* isolated context, so code mode cannot do it and says so.
|
|
984
999
|
*/
|
|
985
1000
|
async function brandFromUrl(url, shots, options) {
|
|
1001
|
+
// Same address rules as every server-side fetch, before a browser is started.
|
|
1002
|
+
// The page's own requests are checked as it loads (see intakeBrand).
|
|
1003
|
+
try {
|
|
1004
|
+
await checkUrl(url);
|
|
1005
|
+
}
|
|
1006
|
+
catch (e) {
|
|
1007
|
+
const why = e instanceof Error ? e.message : String(e);
|
|
1008
|
+
return errorResult(e instanceof UnsafeUrlError ? ErrorCodes.INVALID_PARAMS : ErrorCodes.EXECUTION_ERROR, e instanceof UnsafeUrlError ? why : `could not reach ${url}: ${why}`, {}, { toolName: 'pinepaper_brand_kit' });
|
|
1009
|
+
}
|
|
986
1010
|
const effectiveExecMode = options.executionMode ?? getExecutionMode();
|
|
987
1011
|
if (effectiveExecMode === 'code' || !options.executeInBrowser) {
|
|
988
1012
|
return errorResult(ErrorCodes.EXECUTION_ERROR, 'brand_kit from_url reads the site in the MCP server\'s browser, so it needs browser mode; it cannot run as generated code.', {}, { toolName: 'pinepaper_brand_kit' });
|
|
@@ -1099,6 +1123,24 @@ async function captureSheet(input, options) {
|
|
|
1099
1123
|
}],
|
|
1100
1124
|
};
|
|
1101
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* The canvas the craft tools fill when the call gives no bounds. Asked of the
|
|
1128
|
+
* studio, so a portrait or square canvas is filled rather than a 1280x720
|
|
1129
|
+
* corner of it; undefined (the generators' own default) without a studio.
|
|
1130
|
+
*/
|
|
1131
|
+
async function craftFrame(options) {
|
|
1132
|
+
const ctl = options.browserController;
|
|
1133
|
+
if (!options.executeInBrowser || !ctl || !ctl.connected)
|
|
1134
|
+
return undefined;
|
|
1135
|
+
try {
|
|
1136
|
+
const r = await ctl.executeCode(CRAFT_FRAME_JS, false);
|
|
1137
|
+
const f = r.success ? r.result : null;
|
|
1138
|
+
return f && f.width > 0 && f.height > 0 ? f : undefined;
|
|
1139
|
+
}
|
|
1140
|
+
catch {
|
|
1141
|
+
return undefined;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1102
1144
|
export async function executeOrGenerate(code, description, options, toolName,
|
|
1103
1145
|
/**
|
|
1104
1146
|
* Values too large to live inside the generated code. Written to
|
|
@@ -1788,6 +1830,17 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
1788
1830
|
const code = codeGenerator.generateDeform(input);
|
|
1789
1831
|
return executeOrGenerate(code, `Deform: ${input.action}`, options, 'pinepaper_deform');
|
|
1790
1832
|
}
|
|
1833
|
+
case 'pinepaper_hand_drawn': {
|
|
1834
|
+
const input = HandDrawnInputSchema.parse(args);
|
|
1835
|
+
const code = codeGenerator.generateHandDrawn(input);
|
|
1836
|
+
return executeOrGenerate(code, `Hand-drawn style: ${input.style ?? input.mode ?? 'sketchy'}`, options, 'pinepaper_hand_drawn');
|
|
1837
|
+
}
|
|
1838
|
+
case 'pinepaper_pen_figure': {
|
|
1839
|
+
const input = PenFigureInputSchema.parse(args);
|
|
1840
|
+
const code = codeGenerator.generatePenFigure(input);
|
|
1841
|
+
const what = input.action === 'list' ? 'List the pen figures' : `Draw ${typeof input.figure === 'string' ? input.figure : 'a figure'} with a pen`;
|
|
1842
|
+
return executeOrGenerate(code, what, options, 'pinepaper_pen_figure');
|
|
1843
|
+
}
|
|
1791
1844
|
case 'pinepaper_sprite_sheet': {
|
|
1792
1845
|
const input = SpriteSheetInputSchema.parse(args);
|
|
1793
1846
|
const code = codeGenerator.generateSpriteSheet(input);
|
|
@@ -1856,6 +1909,60 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
1856
1909
|
const description = `Places ${input.concept} as ${plan.parts.length} parts with ${plan.tracks} keyframe tracks`;
|
|
1857
1910
|
return executeOrGenerate(code, description, options, 'pinepaper_character');
|
|
1858
1911
|
}
|
|
1912
|
+
case 'pinepaper_create_character': {
|
|
1913
|
+
const input = CreateCharacterInputSchema.parse(args);
|
|
1914
|
+
// If layered bundle provided, use importLayeredCharacter
|
|
1915
|
+
if (input.bundle) {
|
|
1916
|
+
const bundleInfo = input.bundle.info || input.bundle;
|
|
1917
|
+
const bundleImages = input.bundle.images || {};
|
|
1918
|
+
const code = codeGenerator.generateImportLayeredCharacter({ info: bundleInfo, images: bundleImages });
|
|
1919
|
+
return executeOrGenerate(code, `Import layered character: ${input.name || input.characterId || 'bundle'}`, options, 'pinepaper_create_character');
|
|
1920
|
+
}
|
|
1921
|
+
// If concept/preset matches a planCharacter concept, plan it
|
|
1922
|
+
const conceptName = input.concept || input.preset;
|
|
1923
|
+
if (conceptName) {
|
|
1924
|
+
const formattedConcept = conceptName.startsWith('pp:') ? conceptName : `pp:${conceptName.charAt(0).toUpperCase() + conceptName.slice(1)}`;
|
|
1925
|
+
const plan = planCharacter({
|
|
1926
|
+
concept: formattedConcept,
|
|
1927
|
+
at: { x: input.x, y: input.y },
|
|
1928
|
+
scale: input.scale,
|
|
1929
|
+
});
|
|
1930
|
+
if (plan.ok) {
|
|
1931
|
+
const code = generateCharacterCode(plan, {
|
|
1932
|
+
generateCreateItem: (i) => codeGenerator.generateCreateItem(i),
|
|
1933
|
+
generateKeyframeAnimate: (i) => codeGenerator.generateKeyframeAnimate(i),
|
|
1934
|
+
}, {
|
|
1935
|
+
createItem: (a) => CreateItemInputSchema.parse(a),
|
|
1936
|
+
keyframeAnimate: (a) => KeyframeAnimateInputSchema.parse(a),
|
|
1937
|
+
});
|
|
1938
|
+
return executeOrGenerate(code, `Places character ${formattedConcept}`, options, 'pinepaper_create_character');
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
const code = codeGenerator.generateCreateCharacter(input);
|
|
1942
|
+
return executeOrGenerate(code, `Create character: ${input.name || input.characterId || 'Character'}`, options, 'pinepaper_create_character');
|
|
1943
|
+
}
|
|
1944
|
+
case 'pinepaper_character_action': {
|
|
1945
|
+
const input = CharacterActionInputSchema.parse(args);
|
|
1946
|
+
const act = String(input.action || 'walk').toLowerCase();
|
|
1947
|
+
const FACES = ['blink', 'frown', 'smile', 'surprise', 'talk', 'wink'];
|
|
1948
|
+
if (FACES.includes(act)) {
|
|
1949
|
+
return {
|
|
1950
|
+
content: [{
|
|
1951
|
+
type: 'text',
|
|
1952
|
+
text: `"${act}" is a facial expression — use pinepaper_character_pose with { expression: "${act}" }. Actions: walk, run, locomotion, wave, dance, jump, idle`,
|
|
1953
|
+
}],
|
|
1954
|
+
isError: true,
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
const code = codeGenerator.generateCharacterAction(input);
|
|
1958
|
+
return executeOrGenerate(code, `Character action: ${act}`, options, 'pinepaper_character_action');
|
|
1959
|
+
}
|
|
1960
|
+
case 'pinepaper_character_pose': {
|
|
1961
|
+
const input = CharacterPoseInputSchema.parse(args);
|
|
1962
|
+
const expr = String(input.expression || 'smile').toLowerCase();
|
|
1963
|
+
const code = codeGenerator.generateCharacterPose(input);
|
|
1964
|
+
return executeOrGenerate(code, `Character pose/expression: ${expr}`, options, 'pinepaper_character_pose');
|
|
1965
|
+
}
|
|
1859
1966
|
case 'pinepaper_batch_create': {
|
|
1860
1967
|
const input = BatchCreateInputSchema.parse(args);
|
|
1861
1968
|
const code = codeGenerator.generateBatchCreate(input);
|
|
@@ -2061,6 +2168,44 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
2061
2168
|
const input = StyledSceneInputSchema.parse(args);
|
|
2062
2169
|
return executeOrGenerate(codeGenerator.generateStyledScene(input), `Styled scene${input.style ? `: ${input.style}` : ''}`, options, 'pinepaper_styled_scene');
|
|
2063
2170
|
}
|
|
2171
|
+
// The cloud's craft tools, shared (craft-tools.ts says what differs locally).
|
|
2172
|
+
case 'pinepaper_draw_weave': {
|
|
2173
|
+
const input = DrawWeaveInputSchema.parse(args);
|
|
2174
|
+
const code = generateDrawWeave(input, await craftFrame(options));
|
|
2175
|
+
return executeOrGenerate(code, `Draw a ${input.weave ?? 'plain'} weave`, options, 'pinepaper_draw_weave');
|
|
2176
|
+
}
|
|
2177
|
+
case 'pinepaper_draw_knots': {
|
|
2178
|
+
const input = DrawKnotsInputSchema.parse(args);
|
|
2179
|
+
if (!input.points?.length) {
|
|
2180
|
+
return {
|
|
2181
|
+
content: [{ type: 'text', text: 'pinepaper_draw_knots: give points — [{x, y}, …], one per knot. The tool places knots where you say and draws nothing without them.' }],
|
|
2182
|
+
isError: true,
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
return executeOrGenerate(generateDrawKnots(input), `Draw ${input.points.length} embroidered knot(s)`, options, 'pinepaper_draw_knots');
|
|
2186
|
+
}
|
|
2187
|
+
case 'pinepaper_draw_sashiko': {
|
|
2188
|
+
const input = DrawSashikoInputSchema.parse(args);
|
|
2189
|
+
const code = generateDrawSashiko(input, await craftFrame(options));
|
|
2190
|
+
return executeOrGenerate(code, `Draw ${input.pattern ?? 'seigaiha'} sashiko`, options, 'pinepaper_draw_sashiko');
|
|
2191
|
+
}
|
|
2192
|
+
case 'pinepaper_draw_subdivision': {
|
|
2193
|
+
const input = DrawSubdivisionInputSchema.parse(args);
|
|
2194
|
+
const code = generateDrawSubdivision(input, await craftFrame(options));
|
|
2195
|
+
return executeOrGenerate(code, `Draw a ${input.preset ?? 'mondrian'} subdivision`, options, 'pinepaper_draw_subdivision');
|
|
2196
|
+
}
|
|
2197
|
+
case 'pinepaper_ui_kit': {
|
|
2198
|
+
const input = UiKitInputSchema.parse(args);
|
|
2199
|
+
const problems = uiKitCallProblems(input);
|
|
2200
|
+
const code = problems.length ? null : generateUiKit(input);
|
|
2201
|
+
if (!code) {
|
|
2202
|
+
return {
|
|
2203
|
+
content: [{ type: 'text', text: `pinepaper_ui_kit: ${problems.join(' ') || 'nothing to draw'}` }],
|
|
2204
|
+
isError: true,
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
return executeOrGenerate(code, `UI kit: ${input.action}`, options, 'pinepaper_ui_kit');
|
|
2208
|
+
}
|
|
2064
2209
|
case 'pinepaper_place_on_surface': {
|
|
2065
2210
|
const input = PlaceOnSurfaceInputSchema.parse(args);
|
|
2066
2211
|
const code = codeGenerator.generatePlaceOnSurface(input);
|
|
@@ -2575,13 +2720,15 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2575
2720
|
let svgWarnings = [];
|
|
2576
2721
|
if (input.url) {
|
|
2577
2722
|
try {
|
|
2578
|
-
const res = await
|
|
2723
|
+
const res = await safeFetch(input.url);
|
|
2579
2724
|
if (!res.ok) {
|
|
2580
2725
|
return errorResult(ErrorCodes.EXECUTION_ERROR, `the server refused ${input.url} — HTTP ${res.status} ${res.statusText || ''}`.trim());
|
|
2581
2726
|
}
|
|
2582
2727
|
svgText = await res.text();
|
|
2583
2728
|
}
|
|
2584
2729
|
catch (e) {
|
|
2730
|
+
if (e instanceof UnsafeUrlError)
|
|
2731
|
+
return errorResult(ErrorCodes.INVALID_PARAMS, e.message);
|
|
2585
2732
|
const why = e instanceof Error ? e.message : 'network request failed';
|
|
2586
2733
|
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.`);
|
|
2587
2734
|
}
|
|
@@ -2593,7 +2740,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2593
2740
|
}
|
|
2594
2741
|
const code = codeGenerator.generateImportSVG(svgText, svgText ? undefined : input.url, input.position, input.scale, input.source);
|
|
2595
2742
|
const description = svgWarnings.length
|
|
2596
|
-
? `Imports SVG onto the canvas (${svgWarnings.length} unreachable image(s) removed)`
|
|
2743
|
+
? `Imports SVG onto the canvas (${svgWarnings.length} unreachable image(s) removed: ${svgWarnings.map((w) => w.message).join('; ')})`
|
|
2597
2744
|
: 'Imports SVG onto the canvas';
|
|
2598
2745
|
return executeOrGenerate(code, description, options, 'pinepaper_import_svg');
|
|
2599
2746
|
}
|
|
@@ -3161,9 +3308,11 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
3161
3308
|
// third-party host under the studio's CSP and this process can.
|
|
3162
3309
|
let res;
|
|
3163
3310
|
try {
|
|
3164
|
-
res = await
|
|
3311
|
+
res = await safeFetch(input.url);
|
|
3165
3312
|
}
|
|
3166
3313
|
catch (e) {
|
|
3314
|
+
if (e instanceof UnsafeUrlError)
|
|
3315
|
+
return errorResult(ErrorCodes.INVALID_PARAMS, e.message);
|
|
3167
3316
|
const why = e instanceof Error ? e.message : 'network request failed';
|
|
3168
3317
|
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.`);
|
|
3169
3318
|
}
|
|
@@ -4054,6 +4203,8 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4054
4203
|
'pinepaper_query_capabilities',
|
|
4055
4204
|
// Deformation, sprite sheets, interaction
|
|
4056
4205
|
'pinepaper_deform',
|
|
4206
|
+
'pinepaper_hand_drawn',
|
|
4207
|
+
'pinepaper_pen_figure',
|
|
4057
4208
|
'pinepaper_sprite_sheet',
|
|
4058
4209
|
'pinepaper_interaction',
|
|
4059
4210
|
// Widget export
|