@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.
- package/README.md +1 -0
- package/dist/index.js +109 -6
- 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
|
|
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
|
-
|
|
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.
|
|
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
|
|
1548
|
+
console.error(`eyejack-mcp connected (stage: ${config.stage}) \u2014 workspace + asset-import tools ready.`);
|
package/package.json
CHANGED