@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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +177 -6
  3. 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 **and edits it**: revision-checked writes land in the open editor live, with a human-readable action log. Screenshots arrive in a later phase.
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.2.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.2.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
  }