@uemcp/server 1.5.0 → 1.6.1

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 (2) hide show
  1. package/dist/index.mjs +99 -18
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -153,10 +153,13 @@ const projectIdSchema = z.string().trim().transform((value) => {
153
153
  return HYPHENATED_PROJECT_ID_PATTERN.test(normalizedValue) ? normalizedValue.replaceAll("-", "") : normalizedValue;
154
154
  }).refine((value) => UNREAL_PROJECT_ID_PATTERN.test(value), "Expected a 32-character Unreal ProjectID");
155
155
  const upsertProjectRequestSchema = z.object({
156
+ engineVersion: z.string().trim().min(1).optional(),
157
+ pid: z.number().int().min(1),
158
+ pluginVersion: z.string().trim().min(1).optional(),
159
+ port: z.number().int().min(1).max(65535),
156
160
  projectName: z.string().trim().min(1),
157
161
  projectPath: z.string().trim().min(1),
158
- port: z.number().int().min(1).max(65535),
159
- pid: z.number().int().min(1)
162
+ protocolVersion: z.number().int().min(1).optional()
160
163
  });
161
164
  const projectParamsSchema = z.object({ projectId: projectIdSchema });
162
165
  const mcpPingResponseSchema = z.object({ pong: z.literal(true) });
@@ -8140,6 +8143,9 @@ function createBridgeSetDefaultGamemodeRequest(scope, gameModePath, options = {}
8140
8143
  });
8141
8144
  }
8142
8145
  //#endregion
8146
+ //#region src/version.ts
8147
+ const SERVER_VERSION = "1.6.1";
8148
+ //#endregion
8143
8149
  //#region src/bridgeSocketClient.ts
8144
8150
  var BridgeSocketError = class extends Error {
8145
8151
  code;
@@ -8149,6 +8155,18 @@ var BridgeSocketError = class extends Error {
8149
8155
  this.name = new.target.name;
8150
8156
  }
8151
8157
  };
8158
+ var BridgeProtocolVersionMismatchError = class extends BridgeSocketError {
8159
+ pluginProtocolVersion;
8160
+ serverProtocolVersion = 2;
8161
+ constructor(pluginProtocolVersion, options) {
8162
+ super("protocol_version_mismatch", describeProtocolVersionMismatch(pluginProtocolVersion), options);
8163
+ this.pluginProtocolVersion = pluginProtocolVersion;
8164
+ }
8165
+ };
8166
+ function describeProtocolVersionMismatch(pluginProtocolVersion) {
8167
+ const remedy = pluginProtocolVersion < 2 ? `Update the MCPBridge plugin in this Unreal project to a release that speaks bridge protocol v${String(2)}.` : `Update the MCP server (npx -y @uemcp/server@latest) to a release that speaks bridge protocol v${String(pluginProtocolVersion)}.`;
8168
+ return `Plugin/server version skew: the MCPBridge plugin speaks bridge protocol v${String(pluginProtocolVersion)}, but MCP server ${SERVER_VERSION} speaks v${String(2)}. ${remedy}`;
8169
+ }
8152
8170
  async function sendBridgePingRequest(request, options) {
8153
8171
  return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgePingResponseSchema, request.requestId);
8154
8172
  }
@@ -8456,6 +8474,8 @@ function encodeBridgeFrame(payload) {
8456
8474
  }
8457
8475
  function parseBridgeResponse(responsePayload, schema, expectedRequestId) {
8458
8476
  const parsedResponse = parseBridgePayload(responsePayload);
8477
+ const pluginProtocolVersion = readForeignProtocolVersion(parsedResponse);
8478
+ if (pluginProtocolVersion !== null) throw new BridgeProtocolVersionMismatchError(pluginProtocolVersion);
8459
8479
  const parsed = schema.safeParse(parsedResponse);
8460
8480
  const payloadBytes = Buffer.byteLength(responsePayload, "utf8");
8461
8481
  if (!parsed.success) throw new BridgeSocketError("invalid_response", `Bridge response did not match schema: ${parsed.error.message}. Payload bytes: ${String(payloadBytes)}.`, { cause: parsed.error });
@@ -8651,6 +8671,12 @@ var BridgeSessionClient = class {
8651
8671
  });
8652
8672
  }
8653
8673
  };
8674
+ function readForeignProtocolVersion(payload) {
8675
+ if (typeof payload !== "object" || payload === null || !("version" in payload)) return null;
8676
+ const { version } = payload;
8677
+ if (typeof version !== "number" || !Number.isInteger(version) || version === 2) return null;
8678
+ return version;
8679
+ }
8654
8680
  function parseBridgePayload(responsePayload) {
8655
8681
  try {
8656
8682
  return JSON.parse(responsePayload);
@@ -8997,9 +9023,24 @@ async function pingRegistryEntry$1(entry, options) {
8997
9023
  };
8998
9024
  return {
8999
9025
  alive: true,
9000
- lastSeenAt: /* @__PURE__ */ new Date()
9026
+ lastSeenAt: /* @__PURE__ */ new Date(),
9027
+ pluginProtocolVersion: responseBody.version
9001
9028
  };
9002
9029
  } catch (error) {
9030
+ if (error instanceof BridgeProtocolVersionMismatchError) {
9031
+ options.logger.warn({
9032
+ pluginProtocolVersion: error.pluginProtocolVersion,
9033
+ port: entry.port,
9034
+ projectId: entry.projectId,
9035
+ serverProtocolVersion: error.serverProtocolVersion
9036
+ }, "Engine ping found a plugin/server protocol version mismatch");
9037
+ return {
9038
+ alive: true,
9039
+ lastSeenAt: /* @__PURE__ */ new Date(),
9040
+ pluginProtocolVersion: error.pluginProtocolVersion,
9041
+ reason: error.message
9042
+ };
9043
+ }
9003
9044
  options.logger.warn({
9004
9045
  error,
9005
9046
  port: entry.port,
@@ -9111,12 +9152,21 @@ function createHttpApi(options) {
9111
9152
  projectPath: entry.projectPath
9112
9153
  }, "Project registration changed path for an existing projectId");
9113
9154
  options.logger.info({
9155
+ engineVersion: parsedBody.data.engineVersion,
9114
9156
  pid: entry.pid,
9157
+ pluginVersion: parsedBody.data.pluginVersion,
9115
9158
  port: entry.port,
9116
9159
  projectId: entry.projectId,
9117
9160
  projectName: entry.projectName,
9118
- projectPath: entry.projectPath
9161
+ projectPath: entry.projectPath,
9162
+ protocolVersion: parsedBody.data.protocolVersion
9119
9163
  }, "Upserted Unreal Editor project registration");
9164
+ if (parsedBody.data.protocolVersion !== void 0 && parsedBody.data.protocolVersion !== 2) options.logger.warn({
9165
+ pluginProtocolVersion: parsedBody.data.protocolVersion,
9166
+ pluginVersion: parsedBody.data.pluginVersion,
9167
+ projectId: entry.projectId,
9168
+ serverProtocolVersion: 2
9169
+ }, "Registered Unreal Editor speaks a different bridge protocol version; its bridge calls will fail");
9120
9170
  notifyRegistryChanged();
9121
9171
  return {
9122
9172
  projectId: entry.projectId,
@@ -10184,7 +10234,7 @@ function buildDiagnoseBridgeDisconnectPrompt(args) {
10184
10234
  "",
10185
10235
  `1. Read ${REGISTRY_RESOURCE_URI}. No entry at all means registration never landed or was evicted. An entry with a stale lastSeenAt means registration landed but health checks are failing.`,
10186
10236
  projectPathStep,
10187
- "3. Call ping_project with the registered projectId. reachable: false with registrationRemoved: true means the editor’s TCP listener is gone — the editor died, or the plugin lost its port. Note that this call evicts a stale registration, so read the registry resource before calling it, not after.",
10237
+ "3. Call ping_project with the registered projectId. reachable: false with registrationRemoved: true means the editor’s TCP listener is gone — the editor died, or the plugin lost its port. Note that this call evicts a stale registration, so read the registry resource before calling it, not after. reachable: true with compatible: false is version skew, not a disconnect: the plugin and server speak different bridge protocol versions, reason names which one to update, and no other tool will work until they match.",
10188
10238
  "4. If the registration looks healthy but one specific tool fails, call stream_output_log and drain with the returned cursor. Plugin-side faults are logged under the LogMCPBridgeRuntime category: failures resolving the ProjectID, failures binding the bridge socket, and rejected registrations all name themselves there.",
10189
10239
  "",
10190
10240
  "Common causes, in rough order of likelihood: the editor was closed or crashed and the registration is stale; the editor is open on a different project than the one being asked about; the project directory was copied, so two directories share one ProjectID and fight over one registration; the plugin is pointed at a different server address.",
@@ -12017,7 +12067,7 @@ function registerMcpTools(server, dependencies) {
12017
12067
  }, dependencies));
12018
12068
  });
12019
12069
  server.registerTool("ping_project", {
12020
- description: "Ping the registered Unreal engine bridge for a projectId and evict stale registrations after retries.",
12070
+ description: "Ping the registered Unreal engine bridge for a projectId and evict stale registrations after retries. Reports the plugin, engine and server versions; compatible: false means the plugin and server speak different bridge protocol versions, and reason names which one to update.",
12021
12071
  inputSchema: pingProjectInputSchema,
12022
12072
  title: "Ping Project"
12023
12073
  }, async ({ projectId }) => {
@@ -15356,7 +15406,8 @@ async function pingProject(projectId, dependencies) {
15356
15406
  projectId: normalizedProjectId,
15357
15407
  reason: "Registration was removed while ping_project was running.",
15358
15408
  reachable: false,
15359
- registrationRemoved: false
15409
+ registrationRemoved: false,
15410
+ versions: buildPingProjectVersions(startedEntry)
15360
15411
  };
15361
15412
  attemptsUsed = retryIndex + 1;
15362
15413
  const pingResult = await pingRegistryEntry$1(currentEntry, {
@@ -15379,12 +15430,18 @@ async function pingProject(projectId, dependencies) {
15379
15430
  projectId: currentEntry.projectId
15380
15431
  }, "Failed to persist registry snapshot after successful ping_project");
15381
15432
  }
15382
- return {
15433
+ const versions = buildPingProjectVersions(currentEntry, pingResult.pluginProtocolVersion);
15434
+ const compatible = versions.pluginProtocolVersion === 2;
15435
+ const result = {
15383
15436
  attemptsUsed,
15437
+ compatible,
15384
15438
  projectId: currentEntry.projectId,
15385
15439
  reachable: true,
15386
- registrationRemoved: false
15440
+ registrationRemoved: false,
15441
+ versions
15387
15442
  };
15443
+ if (!compatible && pingResult.reason !== void 0) result.reason = pingResult.reason;
15444
+ return result;
15388
15445
  }
15389
15446
  lastFailureReason = pingResult.reason ?? "Unknown ping failure.";
15390
15447
  if (retryIndex < 3) await sleep(getPingRetryDelayMs(retryIndex));
@@ -15401,7 +15458,8 @@ async function pingProject(projectId, dependencies) {
15401
15458
  projectId: normalizedProjectId,
15402
15459
  reason: lastFailureReason,
15403
15460
  reachable: false,
15404
- registrationRemoved: false
15461
+ registrationRemoved: false,
15462
+ versions: buildPingProjectVersions(startedEntry)
15405
15463
  };
15406
15464
  }
15407
15465
  const removalResult = await runQueuedRegistryWrite(dependencies, async () => {
@@ -15441,7 +15499,8 @@ async function pingProject(projectId, dependencies) {
15441
15499
  projectId: normalizedProjectId,
15442
15500
  reason: lastFailureReason,
15443
15501
  reachable: false,
15444
- registrationRemoved: false
15502
+ registrationRemoved: false,
15503
+ versions: buildPingProjectVersions(startedEntry)
15445
15504
  };
15446
15505
  }
15447
15506
  dependencies.logger.warn({
@@ -15454,9 +15513,21 @@ async function pingProject(projectId, dependencies) {
15454
15513
  projectId: currentEntry.projectId,
15455
15514
  reason: lastFailureReason,
15456
15515
  reachable: false,
15457
- registrationRemoved: true
15516
+ registrationRemoved: true,
15517
+ versions: buildPingProjectVersions(currentEntry)
15458
15518
  };
15459
15519
  }
15520
+ function buildPingProjectVersions(entry, observedPluginProtocolVersion) {
15521
+ const versions = {
15522
+ serverProtocolVersion: 2,
15523
+ serverVersion: SERVER_VERSION
15524
+ };
15525
+ const pluginProtocolVersion = observedPluginProtocolVersion ?? entry.protocolVersion;
15526
+ if (pluginProtocolVersion !== void 0) versions.pluginProtocolVersion = pluginProtocolVersion;
15527
+ if (entry.pluginVersion !== void 0) versions.pluginVersion = entry.pluginVersion;
15528
+ if (entry.engineVersion !== void 0) versions.engineVersion = entry.engineVersion;
15529
+ return versions;
15530
+ }
15460
15531
  async function listRegisteredProjects(input, dependencies) {
15461
15532
  const maxResults = input.maxResults ?? 50;
15462
15533
  const entries = dependencies.registry.snapshot().sort(compareRegistryEntries);
@@ -15487,10 +15558,17 @@ async function verifyRegisteredProjects(entries, dependencies) {
15487
15558
  timeoutMs: dependencies.pingTimeoutMs
15488
15559
  }), budgetExpiry]);
15489
15560
  const summary = toRegisteredProjectSummary(entry);
15490
- if (pingResult.alive) return {
15491
- ...summary,
15492
- reachable: true
15493
- };
15561
+ if (pingResult.alive) {
15562
+ if (pingResult.pluginProtocolVersion !== void 0 && pingResult.reason !== void 0) return {
15563
+ ...summary,
15564
+ reachable: true,
15565
+ reason: pingResult.reason
15566
+ };
15567
+ return {
15568
+ ...summary,
15569
+ reachable: true
15570
+ };
15571
+ }
15494
15572
  return {
15495
15573
  ...summary,
15496
15574
  reachable: false,
@@ -15502,7 +15580,7 @@ async function verifyRegisteredProjects(entries, dependencies) {
15502
15580
  }
15503
15581
  }
15504
15582
  function toRegisteredProjectSummary(entry) {
15505
- return {
15583
+ const summary = {
15506
15584
  host: BRIDGE_LOOPBACK_HOST,
15507
15585
  lastSeenAt: entry.lastSeenAt,
15508
15586
  pid: entry.pid,
@@ -15512,6 +15590,10 @@ function toRegisteredProjectSummary(entry) {
15512
15590
  projectPath: entry.projectPath,
15513
15591
  registeredAt: entry.registeredAt
15514
15592
  };
15593
+ if (entry.pluginVersion !== void 0) summary.pluginVersion = entry.pluginVersion;
15594
+ if (entry.engineVersion !== void 0) summary.engineVersion = entry.engineVersion;
15595
+ if (entry.protocolVersion !== void 0) summary.protocolVersion = entry.protocolVersion;
15596
+ return summary;
15515
15597
  }
15516
15598
  function compareRegistryEntries(left, right) {
15517
15599
  if (left.projectName !== right.projectName) return left.projectName < right.projectName ? -1 : 1;
@@ -17744,7 +17826,6 @@ function isMissingFileError(error) {
17744
17826
  //#endregion
17745
17827
  //#region src/server.ts
17746
17828
  const SERVER_NAME = "ue-mcp-server";
17747
- const SERVER_VERSION = "1.5.0";
17748
17829
  const logger = createModuleLogger("bootstrap");
17749
17830
  const enginePingLogger = createModuleLogger("engine-ping");
17750
17831
  const httpApiLogger = createModuleLogger("http-api");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uemcp/server",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "MCP server for making Unreal Engine and C++ development easier",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",