@eyejack-creator/mcp 0.2.0 → 0.3.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 +2 -1
- package/dist/index.js +177 -6
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @eyejack-creator/mcp
|
|
2
2
|
|
|
3
|
-
EyeJack Creator MCP server — connect **Claude Code / Codex / Cursor** to a live EyeJack editing session. Your AI reads the scene **
|
|
3
|
+
EyeJack Creator MCP server — connect **Claude Code / Codex / Cursor** to a live EyeJack editing session. Your AI reads the scene, **edits it** (revision-checked writes land in the open editor live, with a human-readable action log) and **sees it** — the screenshot tool renders the editor's current view back to the model, closing the read → edit → inspect loop.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
Claude Code ──stdio──► @eyejack/mcp ──► @eyejack/creator-api ──► AppSync (agent session token)
|
|
@@ -31,6 +31,7 @@ The token lives as long as your editor tab (the tab heartbeats it; closing the t
|
|
|
31
31
|
| `list_assets` | The artwork's asset files with `usedInScene` resolution |
|
|
32
32
|
| `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
33
|
| `publish_artwork` | Publish the artwork to its public launch URL (draft/disabled → published) |
|
|
34
|
+
| `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 |
|
|
34
35
|
| `get_session_status` | Session scope, active state, expiry |
|
|
35
36
|
|
|
36
37
|
Tool descriptions embed the scene-format contract so models handle coordinates/conventions correctly without external docs.
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { WebSocket as WsWebSocket } from "ws";
|
|
6
7
|
|
|
7
8
|
// ../creator-api/dist/transport.js
|
|
8
9
|
var GraphQLRequestError = class extends Error {
|
|
@@ -669,6 +670,50 @@ var GET_ARTWORK_SCENE_BACKUP = `
|
|
|
669
670
|
}
|
|
670
671
|
}
|
|
671
672
|
`;
|
|
673
|
+
var REQUEST_AGENT_ACTION = `
|
|
674
|
+
mutation RequestAgentAction($sessionId: ID!, $requestId: ID!, $actionType: String!, $payload: String) {
|
|
675
|
+
requestAgentAction(sessionId: $sessionId, requestId: $requestId, actionType: $actionType, payload: $payload) {
|
|
676
|
+
sessionId
|
|
677
|
+
requestId
|
|
678
|
+
actionType
|
|
679
|
+
payload
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
`;
|
|
683
|
+
var POST_AGENT_ACTION_RESULT = `
|
|
684
|
+
mutation PostAgentActionResult($sessionId: ID!, $requestId: ID!, $ok: Boolean!, $resultType: String, $payload: String, $error: String) {
|
|
685
|
+
postAgentActionResult(sessionId: $sessionId, requestId: $requestId, ok: $ok, resultType: $resultType, payload: $payload, error: $error) {
|
|
686
|
+
sessionId
|
|
687
|
+
requestId
|
|
688
|
+
ok
|
|
689
|
+
resultType
|
|
690
|
+
payload
|
|
691
|
+
error
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
`;
|
|
695
|
+
var ON_AGENT_ACTION = `
|
|
696
|
+
subscription OnAgentAction($sessionId: ID!) {
|
|
697
|
+
onAgentAction(sessionId: $sessionId) {
|
|
698
|
+
sessionId
|
|
699
|
+
requestId
|
|
700
|
+
actionType
|
|
701
|
+
payload
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
`;
|
|
705
|
+
var ON_AGENT_ACTION_RESULT = `
|
|
706
|
+
subscription OnAgentActionResult($sessionId: ID!) {
|
|
707
|
+
onAgentActionResult(sessionId: $sessionId) {
|
|
708
|
+
sessionId
|
|
709
|
+
requestId
|
|
710
|
+
ok
|
|
711
|
+
resultType
|
|
712
|
+
payload
|
|
713
|
+
error
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
`;
|
|
672
717
|
var AGENT_GET_SCENE = `
|
|
673
718
|
query AgentGetScene {
|
|
674
719
|
agentGetScene {
|
|
@@ -692,8 +737,9 @@ var AGENT_GET_SCENE = `
|
|
|
692
737
|
}
|
|
693
738
|
`;
|
|
694
739
|
var AgentApi = class {
|
|
695
|
-
constructor(client2) {
|
|
740
|
+
constructor(client2, realtime) {
|
|
696
741
|
this.client = client2;
|
|
742
|
+
this.realtime = realtime;
|
|
697
743
|
}
|
|
698
744
|
// --- Browser-side (Cognito) session lifecycle ---
|
|
699
745
|
async createSession(artworkId) {
|
|
@@ -774,6 +820,46 @@ var AgentApi = class {
|
|
|
774
820
|
});
|
|
775
821
|
return data.publishArtwork;
|
|
776
822
|
}
|
|
823
|
+
// --- Runtime action channel (P5) ---
|
|
824
|
+
/** AGENT side: fire an action request into the session's channel. */
|
|
825
|
+
async requestAction(params) {
|
|
826
|
+
const data = await this.client.execute(REQUEST_AGENT_ACTION, { ...params, payload: params.payload ?? null });
|
|
827
|
+
return data.requestAgentAction;
|
|
828
|
+
}
|
|
829
|
+
/** BROWSER side: answer an action request (session-owner-checked server-side). */
|
|
830
|
+
async postActionResult(params) {
|
|
831
|
+
const data = await this.client.execute(POST_AGENT_ACTION_RESULT, {
|
|
832
|
+
...params,
|
|
833
|
+
resultType: params.resultType ?? null,
|
|
834
|
+
payload: params.payload ?? null,
|
|
835
|
+
error: params.error ?? null
|
|
836
|
+
});
|
|
837
|
+
return data.postAgentActionResult;
|
|
838
|
+
}
|
|
839
|
+
/** BROWSER side: listen for the agent's action requests (subscribe-time owner check). */
|
|
840
|
+
watchActions(sessionId, handlers) {
|
|
841
|
+
if (!this.realtime)
|
|
842
|
+
throw new Error("AgentApi.watchActions: realtime client not configured");
|
|
843
|
+
return this.realtime().subscribe(ON_AGENT_ACTION, { sessionId }, {
|
|
844
|
+
next: (data) => {
|
|
845
|
+
if (data?.onAgentAction)
|
|
846
|
+
handlers.next(data.onAgentAction);
|
|
847
|
+
},
|
|
848
|
+
error: handlers.error
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
/** AGENT side: listen for the browser's results (subscribe-time session pin). */
|
|
852
|
+
watchActionResults(sessionId, handlers) {
|
|
853
|
+
if (!this.realtime)
|
|
854
|
+
throw new Error("AgentApi.watchActionResults: realtime client not configured");
|
|
855
|
+
return this.realtime().subscribe(ON_AGENT_ACTION_RESULT, { sessionId }, {
|
|
856
|
+
next: (data) => {
|
|
857
|
+
if (data?.onAgentActionResult)
|
|
858
|
+
handlers.next(data.onAgentActionResult);
|
|
859
|
+
},
|
|
860
|
+
error: handlers.error
|
|
861
|
+
});
|
|
862
|
+
}
|
|
777
863
|
};
|
|
778
864
|
|
|
779
865
|
// ../creator-api/dist/domains/artworks.js
|
|
@@ -868,7 +954,7 @@ function createCreatorClient(options) {
|
|
|
868
954
|
}
|
|
869
955
|
return realtimeInstance;
|
|
870
956
|
};
|
|
871
|
-
const agent = new AgentApi(graphql);
|
|
957
|
+
const agent = new AgentApi(graphql, getRealtime);
|
|
872
958
|
const artworks = new ArtworksApi(graphql, getRealtime);
|
|
873
959
|
const scene = new SceneApi(graphql, artworks, getRealtime);
|
|
874
960
|
return {
|
|
@@ -914,6 +1000,7 @@ function loadConfig() {
|
|
|
914
1000
|
}
|
|
915
1001
|
|
|
916
1002
|
// src/tools.ts
|
|
1003
|
+
import { randomUUID } from "node:crypto";
|
|
917
1004
|
import { z } from "zod";
|
|
918
1005
|
var SCENE_CONTRACT = `Scene document conventions (the stored EyeJack format):
|
|
919
1006
|
- Coordinate space is unity-style left-handed; rotations are in DEGREES.
|
|
@@ -1065,6 +1152,83 @@ function registerTools(server2, client2, options) {
|
|
|
1065
1152
|
}
|
|
1066
1153
|
}
|
|
1067
1154
|
);
|
|
1155
|
+
let resultSub = null;
|
|
1156
|
+
const pendingResults = /* @__PURE__ */ new Map();
|
|
1157
|
+
const ensureResultListener = async () => {
|
|
1158
|
+
if (resultSub) return;
|
|
1159
|
+
resultSub = client2.agent.watchActionResults(options.sessionId, {
|
|
1160
|
+
next: (result) => {
|
|
1161
|
+
const resolve = pendingResults.get(result.requestId);
|
|
1162
|
+
if (resolve) {
|
|
1163
|
+
pendingResults.delete(result.requestId);
|
|
1164
|
+
resolve(result);
|
|
1165
|
+
}
|
|
1166
|
+
},
|
|
1167
|
+
error: (err) => {
|
|
1168
|
+
console.error("[eyejack-mcp] action result channel error:", err);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
1172
|
+
};
|
|
1173
|
+
const requestOnce = async (payload, timeoutMs) => {
|
|
1174
|
+
const requestId = randomUUID();
|
|
1175
|
+
const waiter = new Promise((resolve) => {
|
|
1176
|
+
const timer = setTimeout(() => {
|
|
1177
|
+
pendingResults.delete(requestId);
|
|
1178
|
+
resolve(null);
|
|
1179
|
+
}, timeoutMs);
|
|
1180
|
+
pendingResults.set(requestId, (result) => {
|
|
1181
|
+
clearTimeout(timer);
|
|
1182
|
+
resolve(result);
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
await client2.agent.requestAction({
|
|
1186
|
+
sessionId: options.sessionId,
|
|
1187
|
+
requestId,
|
|
1188
|
+
actionType: "SCREENSHOT",
|
|
1189
|
+
payload
|
|
1190
|
+
});
|
|
1191
|
+
return waiter;
|
|
1192
|
+
};
|
|
1193
|
+
const screenshotParams = {
|
|
1194
|
+
width: z.number().int().min(64).max(2048).optional().describe("Image width in px (default 1280; height follows the editor aspect)."),
|
|
1195
|
+
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).")
|
|
1196
|
+
};
|
|
1197
|
+
server2.tool(
|
|
1198
|
+
"screenshot",
|
|
1199
|
+
"See the scene: capture a rendered JPEG of the connected artwork exactly as the user's open editor shows it. Use it to visually verify your update_scene edits (read \u2192 edit \u2192 screenshot \u2192 correct). Requires the editor tab open in a desktop browser; if nobody answers, the tab is closed, hidden, or on a touch device.",
|
|
1200
|
+
screenshotParams,
|
|
1201
|
+
async (args) => {
|
|
1202
|
+
const { width, quality } = args;
|
|
1203
|
+
try {
|
|
1204
|
+
await ensureResultListener();
|
|
1205
|
+
const payload = JSON.stringify({ width: width ?? 1280, quality: quality ?? 0.8 });
|
|
1206
|
+
let result = await requestOnce(payload, 2e4);
|
|
1207
|
+
if (!result) {
|
|
1208
|
+
result = await requestOnce(payload, 2e4);
|
|
1209
|
+
}
|
|
1210
|
+
if (!result) {
|
|
1211
|
+
return asText(
|
|
1212
|
+
"No response from the editor tab (timed out twice). Ask the user to check the EyeJack editor tab is open, visible, and showing this artwork, then try again."
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
if (!result.ok || !result.payload) {
|
|
1216
|
+
return asText(`The editor could not capture a screenshot: ${result.error || "unknown error"}`);
|
|
1217
|
+
}
|
|
1218
|
+
return {
|
|
1219
|
+
content: [
|
|
1220
|
+
{
|
|
1221
|
+
type: "image",
|
|
1222
|
+
data: result.payload,
|
|
1223
|
+
mimeType: result.resultType || "image/jpeg"
|
|
1224
|
+
}
|
|
1225
|
+
]
|
|
1226
|
+
};
|
|
1227
|
+
} catch (err) {
|
|
1228
|
+
return asError(err);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
);
|
|
1068
1232
|
server2.tool(
|
|
1069
1233
|
"get_session_status",
|
|
1070
1234
|
"Inspect the agent session itself: which artwork it is scoped to, whether it is active, and when it expires (the editor tab heartbeats it while open; closing the tab lets it lapse).",
|
|
@@ -1090,13 +1254,20 @@ function registerTools(server2, client2, options) {
|
|
|
1090
1254
|
var config = loadConfig();
|
|
1091
1255
|
var client = createCreatorClient({
|
|
1092
1256
|
endpoint: config.endpoint,
|
|
1093
|
-
auth: new StaticTokenProvider(config.token)
|
|
1257
|
+
auth: new StaticTokenProvider(config.token),
|
|
1258
|
+
// Node ≥22 ships a global WebSocket; older runtimes use `ws`. Needed for
|
|
1259
|
+
// the screenshot tool's result subscription.
|
|
1260
|
+
WebSocketImpl: globalThis.WebSocket ?? WsWebSocket
|
|
1094
1261
|
});
|
|
1095
1262
|
var server = new McpServer({
|
|
1096
1263
|
name: "eyejack-creator",
|
|
1097
|
-
version: "0.
|
|
1264
|
+
version: "0.3.0"
|
|
1265
|
+
});
|
|
1266
|
+
registerTools(server, client, {
|
|
1267
|
+
launchBaseUrl: config.launchBaseUrl,
|
|
1268
|
+
// The token is "<sessionId>.<secret>" — the channel tools need the id.
|
|
1269
|
+
sessionId: config.token.split(".")[0]
|
|
1098
1270
|
});
|
|
1099
|
-
registerTools(server, client, { launchBaseUrl: config.launchBaseUrl });
|
|
1100
1271
|
var transport = new StdioServerTransport();
|
|
1101
1272
|
await server.connect(transport);
|
|
1102
|
-
console.error(`eyejack-mcp connected (stage: ${config.stage}) \u2014 scene read/write tools ready.`);
|
|
1273
|
+
console.error(`eyejack-mcp connected (stage: ${config.stage}) \u2014 scene read/write + screenshot tools ready.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eyejack-creator/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
23
|
+
"ws": "^8.18.0",
|
|
23
24
|
"zod": "3.24.2"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
},
|
|
30
31
|
"scripts": {
|
|
31
32
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
-
"build": "npm run typecheck && rm -rf dist && esbuild src/index.ts --bundle --platform=node --format=esm --target=node18 --outfile=dist/index.js --external:@modelcontextprotocol/sdk --external:zod --banner:js=\"#!/usr/bin/env node\"",
|
|
33
|
+
"build": "npm run typecheck && rm -rf dist && esbuild src/index.ts --bundle --platform=node --format=esm --target=node18 --outfile=dist/index.js --external:@modelcontextprotocol/sdk --external:zod --external:ws --banner:js=\"#!/usr/bin/env node\"",
|
|
33
34
|
"smoke": "node examples/smoke-client.mjs"
|
|
34
35
|
}
|
|
35
36
|
}
|