@uemcp/server 1.5.0 → 1.6.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/dist/index.mjs +99 -18
- 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
|
-
|
|
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.0";
|
|
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
|
-
|
|
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)
|
|
15491
|
-
|
|
15492
|
-
|
|
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
|
-
|
|
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");
|