@eyejack-creator/mcp 0.4.0 → 0.5.0

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 (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +109 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -29,6 +29,7 @@ The token lives as long as your editor tab (the tab heartbeats it; closing the t
29
29
  |---|---|
30
30
  | `get_scene` | Full structured snapshot: metadata, parsed scene document (unity-space, degrees), `sceneRevision`, referenced files |
31
31
  | `list_assets` | The artwork's asset files with `usedInScene` resolution |
32
+ | `import_asset` | Upload a local media file (image/video/GLB) — the open editor ingests it through its normal transcode/upload pipeline and auto-places it in the scene |
32
33
  | `update_scene` | Whole-document, revision-checked scene write — applied **live** in the open editor with your `ops` action log; conflicts return the current scene to rebase on |
33
34
  | `publish_artwork` | Publish the artwork to its public launch URL (draft/disabled → published) |
34
35
  | `screenshot` | JPEG of the editor's current view, rendered by the open tab and relayed over AppSync (auto-degraded under the transport cap) — the agent's eyes |
package/dist/index.js CHANGED
@@ -728,6 +728,16 @@ var AGENT_LIST_ARTWORKS = `
728
728
  }
729
729
  }
730
730
  `;
731
+ var AGENT_CREATE_UPLOAD_URL = `
732
+ mutation AgentCreateUploadUrl($filename: String!, $contentType: String) {
733
+ agentCreateUploadUrl(filename: $filename, contentType: $contentType) {
734
+ uploadUrl
735
+ downloadUrl
736
+ key
737
+ expiresAt
738
+ }
739
+ }
740
+ `;
731
741
  var AGENT_ENSURE_SCENE_BACKUP = `
732
742
  mutation AgentEnsureSceneBackup($artworkId: ID!) {
733
743
  agentEnsureSceneBackup(artworkId: $artworkId) {
@@ -832,6 +842,15 @@ var AgentApi = class {
832
842
  const data = await this.client.execute(CREATE_ARTWORK, { input });
833
843
  return data.createArtwork;
834
844
  }
845
+ /**
846
+ * Presigned S3 staging pair for asset imports (P6): the agent PUTs local
847
+ * file bytes to uploadUrl, then hands downloadUrl to the browser (via the
848
+ * IMPORT_ASSET action) to pull into the normal asset pipeline.
849
+ */
850
+ async createUploadUrl(filename, contentType) {
851
+ const data = await this.client.execute(AGENT_CREATE_UPLOAD_URL, { filename, contentType: contentType ?? null });
852
+ return data.agentCreateUploadUrl;
853
+ }
835
854
  /**
836
855
  * Lazy pre-write snapshot (P6a): idempotent per (session, artwork) —
837
856
  * powers the editor's "Revert AI changes". saveScene calls this
@@ -1076,6 +1095,8 @@ function loadConfig() {
1076
1095
 
1077
1096
  // src/tools.ts
1078
1097
  import { randomUUID } from "node:crypto";
1098
+ import { readFile } from "node:fs/promises";
1099
+ import { basename, extname } from "node:path";
1079
1100
  import { z } from "zod";
1080
1101
  var SCENE_CONTRACT = `Scene document conventions (the stored EyeJack format):
1081
1102
  - Coordinate space is unity-style left-handed; rotations are in DEGREES.
@@ -1145,17 +1166,22 @@ function registerTools(server2, client2, options) {
1145
1166
  return waiter;
1146
1167
  };
1147
1168
  const NO_ARTWORK_HINT = "No artworkId given and no artwork is open in the editor tab. Pass artworkId explicitly (see list_artworks) or use open_artwork first.";
1148
- const resolveArtworkId = async (explicit) => {
1149
- if (explicit) return explicit;
1169
+ const queryActiveArtwork = async () => {
1150
1170
  await ensureResultListener();
1151
1171
  const pong = await requestOnce("PING", null, 6e3);
1152
1172
  if (pong?.ok && pong.payload) {
1153
1173
  try {
1154
- const parsed = JSON.parse(pong.payload);
1155
- if (parsed.activeArtworkId) return parsed.activeArtworkId;
1174
+ return JSON.parse(pong.payload).activeArtworkId ?? null;
1156
1175
  } catch {
1176
+ return null;
1157
1177
  }
1158
1178
  }
1179
+ return null;
1180
+ };
1181
+ const resolveArtworkId = async (explicit) => {
1182
+ if (explicit) return explicit;
1183
+ const active = await queryActiveArtwork();
1184
+ if (active) return active;
1159
1185
  throw new Error(NO_ARTWORK_HINT);
1160
1186
  };
1161
1187
  const artworkIdParam = {
@@ -1353,6 +1379,83 @@ function registerTools(server2, client2, options) {
1353
1379
  }
1354
1380
  }
1355
1381
  );
1382
+ const IMPORT_MIME = {
1383
+ ".png": "image/png",
1384
+ ".jpg": "image/jpeg",
1385
+ ".jpeg": "image/jpeg",
1386
+ ".webp": "image/webp",
1387
+ ".gif": "image/gif",
1388
+ ".mp4": "video/mp4",
1389
+ ".mov": "video/quicktime",
1390
+ ".glb": "model/gltf-binary"
1391
+ };
1392
+ const importAssetParams = {
1393
+ filePath: z.string().describe("Absolute path to a local media file: .png/.jpg/.jpeg/.webp image, .gif/.mp4/.mov video, or .glb model."),
1394
+ artworkId: z.string().optional().describe("Target artwork. Omit for the one open in the editor tab; if another id is given, the tab is steered there first (imports run in the open editor).")
1395
+ };
1396
+ server2.tool(
1397
+ "import_asset",
1398
+ "Bring NEW media into a project: upload a local file, which the user's open editor ingests through its normal asset pipeline (transcode, size limits, thumbnails) and auto-places in the scene. Returns the new asset's fileID \u2014 follow up with get_scene/update_scene to position it, and screenshot to verify. Requires the editor tab open; videos can take a minute or two to transcode. Per-project asset budget is ~3MB total, so prefer small optimized files.",
1399
+ importAssetParams,
1400
+ async (args) => {
1401
+ const { filePath, artworkId } = args;
1402
+ try {
1403
+ const ext = extname(filePath).toLowerCase();
1404
+ const contentType = IMPORT_MIME[ext];
1405
+ if (!contentType) {
1406
+ return asText(`Unsupported file type "${ext}". Supported: ${Object.keys(IMPORT_MIME).join(", ")}`);
1407
+ }
1408
+ const bytes = await readFile(filePath);
1409
+ if (bytes.length > 100 * 1024 * 1024) {
1410
+ return asText("File is larger than 100MB \u2014 far beyond the panels asset budget. Pick a smaller file.");
1411
+ }
1412
+ const filename = basename(filePath);
1413
+ const active = await queryActiveArtwork();
1414
+ let target = artworkId ?? active;
1415
+ if (!target) return asText(NO_ARTWORK_HINT);
1416
+ if (target !== active) {
1417
+ const opened = await requestOnce("OPEN_ARTWORK", JSON.stringify({ artworkId: target }), 15e3);
1418
+ if (!opened?.ok) {
1419
+ return asText(`Could not open ${target} in the editor first: ${opened?.error || "no response from the tab"}`);
1420
+ }
1421
+ await new Promise((resolve) => setTimeout(resolve, 4e3));
1422
+ }
1423
+ const staging = await client2.agent.createUploadUrl(filename, contentType);
1424
+ const put = await fetch(staging.uploadUrl, {
1425
+ method: "PUT",
1426
+ headers: { "content-type": contentType },
1427
+ body: new Uint8Array(bytes)
1428
+ });
1429
+ if (!put.ok) {
1430
+ return asText(`Staging upload failed (HTTP ${put.status}).`);
1431
+ }
1432
+ const result = await requestOnce(
1433
+ "IMPORT_ASSET",
1434
+ JSON.stringify({ artworkId: target, downloadUrl: staging.downloadUrl, filename, contentType }),
1435
+ 24e4
1436
+ );
1437
+ if (!result) {
1438
+ return asText("No response from the editor tab \u2014 is it open and visible? (Large videos can also exceed the wait; check the editor and re-read the scene.)");
1439
+ }
1440
+ if (!result.ok) {
1441
+ return asText(`Import failed: ${result.error || "unknown error"}`);
1442
+ }
1443
+ let fileID = null;
1444
+ try {
1445
+ fileID = result.payload ? JSON.parse(result.payload).fileID ?? null : null;
1446
+ } catch {
1447
+ }
1448
+ return asText({
1449
+ ok: true,
1450
+ artworkId: target,
1451
+ fileID,
1452
+ message: "Imported through the editor pipeline and auto-placed in the scene. Use get_scene to see it (assetID == fileID) and update_scene to reposition; screenshot to verify visually."
1453
+ });
1454
+ } catch (err) {
1455
+ return asError(err);
1456
+ }
1457
+ }
1458
+ );
1356
1459
  const screenshotParams = {
1357
1460
  width: z.number().int().min(64).max(2048).optional().describe("Image width in px (default 1280; height follows the editor aspect)."),
1358
1461
  quality: z.number().min(0.1).max(1).optional().describe("JPEG quality 0.1\u20131 (default 0.8; auto-degraded if the image exceeds the transport cap).")
@@ -1433,7 +1536,7 @@ var client = createCreatorClient({
1433
1536
  });
1434
1537
  var server = new McpServer({
1435
1538
  name: "eyejack-creator",
1436
- version: "0.4.0"
1539
+ version: "0.5.0"
1437
1540
  });
1438
1541
  registerTools(server, client, {
1439
1542
  launchBaseUrl: config.launchBaseUrl,
@@ -1442,4 +1545,4 @@ registerTools(server, client, {
1442
1545
  });
1443
1546
  var transport = new StdioServerTransport();
1444
1547
  await server.connect(transport);
1445
- console.error(`eyejack-mcp connected (stage: ${config.stage}) \u2014 account-scoped workspace tools ready.`);
1548
+ console.error(`eyejack-mcp connected (stage: ${config.stage}) \u2014 workspace + asset-import tools ready.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyejack-creator/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "EyeJack Creator MCP server — connect Claude Code / Codex / Cursor to a live EyeJack editing session.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",