@pinepaper.studio/mcp-server 1.6.32 → 1.6.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +115 -1
  2. package/dist/agent/types.d.ts +3 -3
  3. package/dist/build-info.json +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +6 -5
  6. package/dist/index.js.map +1 -1
  7. package/dist/ontology/upstream-vocabulary.d.ts +1 -1
  8. package/dist/ontology/upstream-vocabulary.js +1 -1
  9. package/dist/tools/batch-macros.d.ts +20 -1
  10. package/dist/tools/batch-macros.d.ts.map +1 -1
  11. package/dist/tools/batch-macros.js +146 -7
  12. package/dist/tools/batch-macros.js.map +1 -1
  13. package/dist/tools/compact-descriptions.js +1 -1
  14. package/dist/tools/compact-descriptions.js.map +1 -1
  15. package/dist/tools/definitions.d.ts.map +1 -1
  16. package/dist/tools/definitions.js +82 -55
  17. package/dist/tools/definitions.js.map +1 -1
  18. package/dist/tools/engine-lists.d.ts +5 -3
  19. package/dist/tools/engine-lists.d.ts.map +1 -1
  20. package/dist/tools/engine-lists.js +11 -3
  21. package/dist/tools/engine-lists.js.map +1 -1
  22. package/dist/tools/engine-surface.d.ts +1 -1
  23. package/dist/tools/engine-surface.d.ts.map +1 -1
  24. package/dist/tools/engine-surface.js +11 -5
  25. package/dist/tools/engine-surface.js.map +1 -1
  26. package/dist/tools/handlers.d.ts.map +1 -1
  27. package/dist/tools/handlers.js +71 -16
  28. package/dist/tools/handlers.js.map +1 -1
  29. package/dist/tools/lane.d.ts +8 -1
  30. package/dist/tools/lane.d.ts.map +1 -1
  31. package/dist/tools/lane.js +56 -15
  32. package/dist/tools/lane.js.map +1 -1
  33. package/dist/tools/minimal-descriptions.js +1 -1
  34. package/dist/tools/minimal-descriptions.js.map +1 -1
  35. package/dist/tools/reply-fields.d.ts +15 -0
  36. package/dist/tools/reply-fields.d.ts.map +1 -0
  37. package/dist/tools/reply-fields.js +117 -0
  38. package/dist/tools/reply-fields.js.map +1 -0
  39. package/dist/tools/scene-cache-export.d.ts.map +1 -1
  40. package/dist/tools/scene-cache-export.js +2 -1
  41. package/dist/tools/scene-cache-export.js.map +1 -1
  42. package/dist/tools/shape-params.d.ts +2 -2
  43. package/dist/tools/shape-params.d.ts.map +1 -1
  44. package/dist/tools/shape-params.js +4 -2
  45. package/dist/tools/shape-params.js.map +1 -1
  46. package/dist/tools/stick-vocabulary.d.ts +1 -1
  47. package/dist/tools/stick-vocabulary.js +1 -1
  48. package/dist/tools/toolkits.d.ts +1 -1
  49. package/dist/tools/toolkits.d.ts.map +1 -1
  50. package/dist/tools/toolkits.js +7 -2
  51. package/dist/tools/toolkits.js.map +1 -1
  52. package/dist/types/code-generator.d.ts.map +1 -1
  53. package/dist/types/code-generator.js +142 -50
  54. package/dist/types/code-generator.js.map +1 -1
  55. package/dist/types/schemas.d.ts +40 -6
  56. package/dist/types/schemas.d.ts.map +1 -1
  57. package/dist/types/schemas.js +20 -7
  58. package/dist/types/schemas.js.map +1 -1
  59. package/manifest.json +1 -1
  60. package/package.json +1 -1
@@ -9,8 +9,9 @@ import { dropPrivateRelationParams, normalizeBatchOperationNames, renamedArgumen
9
9
  import { nestedIgnoredPaths } from './nested-ignored.js';
10
10
  import { CRAFT_FRAME_JS, generateDrawWeave, generateDrawKnots, generateDrawSashiko, generateDrawSubdivision, generateUiKit, uiKitCallProblems, } from './craft-tools.js';
11
11
  import { exportPerScene } from './scene-cache-export.js';
12
+ import { boundedStructured, reportedIgnoredPaths } from './reply-fields.js';
12
13
  import { codeGenerator, generateQueryMutationsCode, SHEET_RUBRIC } from '../types/code-generator.js';
13
- import { expandBatchMacros, BatchMacroError } from './batch-macros.js';
14
+ import { expandBatchMacros, BatchMacroError, batchPreflightView, withKeyColumns } from './batch-macros.js';
14
15
  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,
15
16
  // Diagram schemas
16
17
  CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
@@ -161,11 +162,11 @@ function exportGovernorTimeoutMs() {
161
162
  * size cap. This reads the user's disk on their instruction, the same as the
162
163
  * export side writes to it, and neither should be a wildcard.
163
164
  */
164
- const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.avif']);
165
+ const IMAGE_EXTENSIONS = new Set(['.png', '.apng', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.avif']);
165
166
  const MAX_LOCAL_IMAGE_BYTES = 32 * 1024 * 1024;
166
167
  const IMAGE_MIME = {
167
168
  '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
168
- '.webp': 'image/webp', '.svg': 'image/svg+xml', '.bmp': 'image/bmp', '.avif': 'image/avif',
169
+ '.webp': 'image/webp', '.apng': 'image/apng', '.svg': 'image/svg+xml', '.bmp': 'image/bmp', '.avif': 'image/avif',
169
170
  };
170
171
  /**
171
172
  * THE PAGE CANNOT FETCH A THIRD-PARTY IMAGE. THIS PROCESS CAN.
@@ -421,6 +422,7 @@ async function saveAdExport(r, input, platform) {
421
422
  if (warnings.length && cleanResult.fidelity.note)
422
423
  delete cleanResult.fidelity.note;
423
424
  return {
425
+ structuredContent: boundedStructured(cleanResult),
424
426
  content: [{
425
427
  type: 'text',
426
428
  text: `Ad saved:\n\n${Object.entries(files).map(([k, v]) => `${k}: ${v}`).join('\n')}\nFormat: ${r.format}\nSize: ${r.adSize.width}x${r.adSize.height}, upload ${(uploadBytes / 1024).toFixed(1)} KB\n\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
@@ -1909,22 +1911,60 @@ export function batchOpIgnoredPaths(args) {
1909
1911
  return out.slice(0, 50);
1910
1912
  }
1911
1913
  export const IGNORED_ARGUMENTS_META_KEY = 'pinepaper.studio/ignoredArguments';
1912
- /** Adds fields to a reply whose first block is a JSON object; false when it is not one (or there is nothing to add). */
1914
+ /**
1915
+ * Adds fields to a reply whose first block is a JSON object, and to its structuredContent when it already has one (a
1916
+ * saved export: prose first, data beside it). False when neither took them (or nothing to add is true).
1917
+ */
1913
1918
  function withReplyFields(result, fields) {
1914
1919
  if (!Object.keys(fields).length)
1915
1920
  return true;
1921
+ const hadStructured = !!result.structuredContent;
1922
+ if (result.structuredContent)
1923
+ result.structuredContent = { ...result.structuredContent, ...fields };
1916
1924
  const first = result.content?.[0];
1917
1925
  if (first?.type !== 'text')
1918
- return false;
1926
+ return hadStructured;
1919
1927
  try {
1920
1928
  const obj = JSON.parse(first.text);
1921
1929
  if (!obj || typeof obj !== 'object' || Array.isArray(obj))
1922
- return false;
1930
+ return hadStructured;
1923
1931
  result.content = [{ ...first, text: JSON.stringify({ ...obj, ...fields }, null, 2) }, ...result.content.slice(1)];
1924
1932
  return true;
1925
1933
  }
1926
1934
  catch {
1927
- return false;
1935
+ return hadStructured;
1936
+ }
1937
+ }
1938
+ /** How many ops a written batch op becomes; a macro that does not expand is refused before anything runs. */
1939
+ function expandedOpCount(op) {
1940
+ try {
1941
+ return expandBatchMacros([op]).length;
1942
+ }
1943
+ catch {
1944
+ return 1;
1945
+ }
1946
+ }
1947
+ /**
1948
+ * The tools that hand back something the studio rendered. Render behaviour follows the live studio, not this package,
1949
+ * so a result is traced by the build that made it. agent_export's generated code says so itself; the rest are asked
1950
+ * here, once, after the call.
1951
+ */
1952
+ const RENDERING_TOOLS = new Set(['pinepaper_agent_export', 'pinepaper_capture_frames', 'pinepaper_export_scene', 'pinepaper_export_svg',
1953
+ 'pinepaper_export_training_data', 'pinepaper_export_widget', 'pinepaper_export_widget_html', 'pinepaper_render_batch', 'pinepaper_sprite_sheet']);
1954
+ async function renderingBuildField(toolName, args, result, options) {
1955
+ if (!RENDERING_TOOLS.has(toolName) || args.estimateOnly === true || result.isError || !options.executeInBrowser || result.structuredContent?.studioBuild)
1956
+ return {};
1957
+ if ((options.executionMode ?? getExecutionMode()) === 'code')
1958
+ return {};
1959
+ const controller = (options.browserController ?? peekBrowserController());
1960
+ if (!controller?.connected || typeof controller.executeCode !== 'function')
1961
+ return {};
1962
+ try {
1963
+ const r = await controller.executeCode(`(function () { return typeof app !== 'undefined' && app.buildId ? app.buildId : null; })();`, false, { bypassGovernor: true });
1964
+ return r?.success && typeof r.result === 'string' ? { studioBuild: r.result } : {};
1965
+ }
1966
+ catch {
1967
+ return {}; // the export stands
1928
1968
  }
1929
1969
  }
1930
1970
  export async function handleToolCall(toolName, args, options = {}) {
@@ -1955,10 +1995,12 @@ export async function handleToolCall(toolName, args, options = {}) {
1955
1995
  }
1956
1996
  }
1957
1997
  const ignored = await ignoredArgumentsNoteFor(toolName, args);
1958
- if (ignored) {
1998
+ if (ignored)
1959
1999
  result.content = [...(result.content ?? []), { type: 'text', text: ignored.note }];
1960
- result._meta = { ...(result._meta ?? {}), [IGNORED_ARGUMENTS_META_KEY]: ignored.ignored };
1961
- }
2000
+ // What only the run can tell: an option or property the op's own warning named. Its sentence is already in the reply.
2001
+ const ignoredPaths = [...new Set([...(ignored?.ignored ?? []), ...reportedIgnoredPaths(toolName, args, result, expandedOpCount)])];
2002
+ if (ignoredPaths.length)
2003
+ result._meta = { ...(result._meta ?? {}), [IGNORED_ARGUMENTS_META_KEY]: ignoredPaths };
1962
2004
  if (renamed.length)
1963
2005
  result._meta = { ...(result._meta ?? {}), [RENAMED_ARGUMENTS_META_KEY]: renamed };
1964
2006
  // IN THE REPLY'S OWN JSON. A client that has structuredContent reads that, and it is built from the first block
@@ -1966,7 +2008,8 @@ export async function handleToolCall(toolName, args, options = {}) {
1966
2008
  const folded = withReplyFields(result, {
1967
2009
  ...(renamed.length ? { translatedArguments: renamed } : {}),
1968
2010
  ...(dropped.length ? { droppedArguments: dropped } : {}),
1969
- ...(ignored ? { ignoredArguments: ignored.ignored } : {}),
2011
+ ...(ignoredPaths.length ? { ignoredArguments: ignoredPaths } : {}),
2012
+ ...await renderingBuildField(toolName, args, result, options),
1970
2013
  });
1971
2014
  if (!folded) {
1972
2015
  const lines = [...(renamed.length ? [renamedArgumentsNote(renamed)] : []), ...(dropped.length ? [`Not sent to the studio, as its private state: ${dropped.join(', ')}.`] : [])];
@@ -2910,6 +2953,15 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
2910
2953
  return executeOrGenerate(code, what, options, 'pinepaper_import_motion_capture');
2911
2954
  }
2912
2955
  case 'pinepaper_keyframe_animate': {
2956
+ // keys: the same keyframes by column; the rest of this path never sees them.
2957
+ try {
2958
+ args = withKeyColumns(args, 'pinepaper_keyframe_animate');
2959
+ }
2960
+ catch (e) {
2961
+ if (e instanceof BatchMacroError)
2962
+ return errorResult(ErrorCodes.VALIDATION_ERROR, e.message, {}, { toolName: 'pinepaper_keyframe_animate' });
2963
+ throw e;
2964
+ }
2913
2965
  const input = KeyframeAnimateInputSchema.parse(args);
2914
2966
  const code = codeGenerator.generateKeyframeAnimate(input);
2915
2967
  return executeOrGenerate(code, `Applies keyframe animation with ${input.keyframes.length} keyframes to ${input.itemId}`, options, 'pinepaper_keyframe_animate');
@@ -3924,12 +3976,13 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3924
3976
  // Opt-out via skipValidation: true when the caller knows what they're
3925
3977
  // doing (e.g. experimental vocabulary).
3926
3978
  if (!input.skipValidation) {
3927
- const ops = input.operations;
3979
+ // A create_many item gets the checks a create gets, reported at its own path.
3980
+ const { ops, paths } = batchPreflightView(input.operations);
3928
3981
  // A relation registered with register_custom_relation this session is a valid relationType, as in add_relation.
3929
3982
  const violations = validateBatchVocabulary(ops, { relationType: CUSTOM_RELATION_NAMES });
3930
3983
  if (violations.length > 0) {
3931
3984
  return errorResult(ErrorCodes.VALIDATION_ERROR, `Batch preflight rejected ${violations.length} operation${violations.length > 1 ? 's' : ''} with unknown vocabulary value${violations.length > 1 ? 's' : ''}. Fix the offending fields and retry, or pass skipValidation: true to bypass.`, violations.map((v) => ({
3932
- path: `operations[${v.opIndex}].${v.field}`,
3985
+ path: `${paths[v.opIndex]}.${v.field}`,
3933
3986
  opType: v.opType,
3934
3987
  vocabulary: v.hint,
3935
3988
  })));
@@ -3941,7 +3994,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3941
3994
  const typos = detectBatchPropertyTypos(ops);
3942
3995
  if (typos.length > 0) {
3943
3996
  return errorResult(ErrorCodes.VALIDATION_ERROR, `Batch preflight detected ${typos.length} likely property-key typo${typos.length > 1 ? 's' : ''}. Each suggestion is a Levenshtein-≤2 match against the schema for that itemType — fix and retry, or pass skipValidation: true to bypass.`, typos.map((t) => ({
3944
- path: `operations[${t.opIndex}].properties.${t.property}`,
3997
+ path: `${paths[t.opIndex]}.properties.${t.property}`,
3945
3998
  opType: t.opType,
3946
3999
  itemType: t.itemType,
3947
4000
  received: t.property,
@@ -3969,7 +4022,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3969
4022
  }
3970
4023
  const code = codeGenerator.generateAgentBatchExecute(input);
3971
4024
  const description = `Batch executed ${input.operations.length} operations${input.atomic !== false ? ' (atomic)' : ''}`
3972
- + (expanded.length !== input.operations.length ? ` (${expanded.length} after expanding ring / grid / orbit / stagger)` : '');
4025
+ + (expanded.length !== input.operations.length ? ` (${expanded.length} after expanding ring / grid / orbit / stagger / create_many)` : '');
3973
4026
  // Check for out-of-bounds items before execution
3974
4027
  let boundsWarning = '';
3975
4028
  const sizeOp = input.operations.find((op) => op.type === 'set_canvas_size');
@@ -4229,6 +4282,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
4229
4282
  const receipt = await exportFileReceiptOrReason(filePath, exportResult);
4230
4283
  const cleanResult = { ...exportResult, data: undefined, filePath, fileSize, chunks, file: receipt };
4231
4284
  return withEngineReport({
4285
+ structuredContent: boundedStructured(cleanResult),
4232
4286
  content: [{
4233
4287
  type: 'text',
4234
4288
  text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\nPaged out of the studio's export store in ${chunks} chunk${chunks === 1 ? '' : 's'}\n${receiptErrorLine(receipt)}${'receiptError' in receipt ? '' : probeLines(receipt)}${lintLines(exportResult)}\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
@@ -4265,7 +4319,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
4265
4319
  const { sccCues: _c, ...rest } = exportResult;
4266
4320
  void _c;
4267
4321
  const cleanResult = { ...rest, filePath, fileSize, frameRate: '29.97 drop-frame', fidelity: { warnings } };
4268
- return withEngineReport({ content: [{ type: 'text', text: `Export saved to file:\n\nFile: ${filePath}\nFormat: scc (CEA-608, CC1, pop-on)\nSize: ${fileSize} bytes\n\nResult: ${JSON.stringify(cleanResult, null, 2)}` }, exportFileLink(filePath, fileSize)] }, exportBrowserResult.report);
4322
+ return withEngineReport({ structuredContent: boundedStructured(cleanResult), content: [{ type: 'text', text: `Export saved to file:\n\nFile: ${filePath}\nFormat: scc (CEA-608, CC1, pop-on)\nSize: ${fileSize} bytes\n\nResult: ${JSON.stringify(cleanResult, null, 2)}` }, exportFileLink(filePath, fileSize)] }, exportBrowserResult.report);
4269
4323
  }
4270
4324
  if ((format === 'html5-ad' || format === 'playable') && typeof exportResult?.adPage === 'string') {
4271
4325
  try {
@@ -4295,6 +4349,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
4295
4349
  // it has to be said out loud here rather than left in the JSON.
4296
4350
  const dropNote = exportResult.incomplete;
4297
4351
  return withEngineReport({
4352
+ structuredContent: boundedStructured(cleanResult),
4298
4353
  content: [{
4299
4354
  type: 'text',
4300
4355
  text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\n`