unreal-engine-mcp-server 0.4.5 → 0.4.6
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/.env.production +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/response-validator.js +64 -9
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/index.ts +1 -1
- package/src/utils/response-validator.ts +67 -8
package/.env.production
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.6] - 2025-10-04
|
|
6
|
+
### Fixed
|
|
7
|
+
- Fixed duplicate response output issue where tool responses were being displayed twice in MCP content
|
|
8
|
+
- Response validator now emits concise summaries in text content instead of duplicating full JSON payloads
|
|
9
|
+
- Structured content is preserved for validation and tests while user-facing output is streamlined
|
|
10
|
+
|
|
5
11
|
## [0.4.5] - 2025-10-03
|
|
6
12
|
### Added
|
|
7
13
|
- Expose `UE_PROJECT_PATH` environment variable across runtime config, Smithery manifest, and client example configs. This allows tools that need an absolute .uproject path (e.g., engine_start) to work without additional manual configuration.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/modelcontextprotocol/sdk)
|
|
6
6
|
[](https://www.unrealengine.com/)
|
|
7
7
|
[](https://registry.modelcontextprotocol.io/)
|
|
8
|
-
[](https://smithery.ai/server/@ChiR24/unreal_mcp)
|
|
9
9
|
|
|
10
10
|
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine via Remote Control API. Built with TypeScript and designed for game development automation.
|
|
11
11
|
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,66 @@ import Ajv from 'ajv';
|
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
3
|
import { cleanObject } from './safe-json.js';
|
|
4
4
|
const log = new Logger('ResponseValidator');
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function normalizeText(text) {
|
|
9
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
10
|
+
}
|
|
11
|
+
function buildSummaryText(toolName, payload) {
|
|
12
|
+
if (typeof payload === 'string') {
|
|
13
|
+
const normalized = payload.trim();
|
|
14
|
+
return normalized || `${toolName} responded`;
|
|
15
|
+
}
|
|
16
|
+
if (typeof payload === 'number' || typeof payload === 'bigint' || typeof payload === 'boolean') {
|
|
17
|
+
return `${toolName} responded: ${payload}`;
|
|
18
|
+
}
|
|
19
|
+
if (!isRecord(payload)) {
|
|
20
|
+
return `${toolName} responded`;
|
|
21
|
+
}
|
|
22
|
+
const parts = [];
|
|
23
|
+
const message = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
|
|
24
|
+
const error = typeof payload.error === 'string' ? normalizeText(payload.error) : '';
|
|
25
|
+
const success = typeof payload.success === 'boolean' ? (payload.success ? 'success' : 'failed') : '';
|
|
26
|
+
const path = typeof payload.path === 'string' ? payload.path : '';
|
|
27
|
+
const name = typeof payload.name === 'string' ? payload.name : '';
|
|
28
|
+
const warningCount = Array.isArray(payload.warnings) ? payload.warnings.length : 0;
|
|
29
|
+
if (message)
|
|
30
|
+
parts.push(message);
|
|
31
|
+
if (error && (!message || !message.includes(error)))
|
|
32
|
+
parts.push(`error: ${error}`);
|
|
33
|
+
if (success)
|
|
34
|
+
parts.push(success);
|
|
35
|
+
if (path)
|
|
36
|
+
parts.push(`path: ${path}`);
|
|
37
|
+
if (name)
|
|
38
|
+
parts.push(`name: ${name}`);
|
|
39
|
+
if (warningCount > 0)
|
|
40
|
+
parts.push(`warnings: ${warningCount}`);
|
|
41
|
+
const summary = isRecord(payload.summary) ? payload.summary : undefined;
|
|
42
|
+
if (summary) {
|
|
43
|
+
const summaryParts = [];
|
|
44
|
+
for (const [key, value] of Object.entries(summary)) {
|
|
45
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
46
|
+
summaryParts.push(`${key}: ${value}`);
|
|
47
|
+
}
|
|
48
|
+
if (summaryParts.length >= 3)
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if (summaryParts.length) {
|
|
52
|
+
parts.push(`summary(${summaryParts.join(', ')})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (parts.length === 0) {
|
|
56
|
+
const keys = Object.keys(payload).slice(0, 3);
|
|
57
|
+
if (keys.length) {
|
|
58
|
+
return `${toolName} responded (${keys.join(', ')})`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return parts.length > 0
|
|
62
|
+
? parts.join(' | ')
|
|
63
|
+
: `${toolName} responded`;
|
|
64
|
+
}
|
|
5
65
|
/**
|
|
6
66
|
* Response Validator for MCP Tool Outputs
|
|
7
67
|
* Validates tool responses against their defined output schemas
|
|
@@ -127,15 +187,10 @@ export class ResponseValidator {
|
|
|
127
187
|
return response;
|
|
128
188
|
}
|
|
129
189
|
// Otherwise, wrap structured result into MCP content
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
text =
|
|
134
|
-
? response
|
|
135
|
-
: JSON.stringify(response ?? { success: true }, null, 2);
|
|
136
|
-
}
|
|
137
|
-
catch (_e) {
|
|
138
|
-
text = String(response);
|
|
190
|
+
const summarySource = structuredPayload !== undefined ? structuredPayload : response;
|
|
191
|
+
let text = buildSummaryText(toolName, summarySource);
|
|
192
|
+
if (!text || !text.trim()) {
|
|
193
|
+
text = buildSummaryText(toolName, response);
|
|
139
194
|
}
|
|
140
195
|
const wrapped = {
|
|
141
196
|
content: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unreal-engine-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"mcpName": "io.github.ChiR24/unreal-engine-mcp",
|
|
5
5
|
"description": "A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine via Remote Control API. Built with TypeScript and designed for game development automation.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
|
|
3
3
|
"name": "io.github.ChiR24/unreal-engine-mcp",
|
|
4
4
|
"description": "MCP server for Unreal Engine 5 with 13 tools for game development automation.",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.6",
|
|
6
6
|
"packages": [
|
|
7
7
|
{
|
|
8
8
|
"registryType": "npm",
|
|
9
9
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
10
10
|
"identifier": "unreal-engine-mcp-server",
|
|
11
|
-
"version": "0.4.
|
|
11
|
+
"version": "0.4.6",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
},
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,69 @@ import { cleanObject } from './safe-json.js';
|
|
|
4
4
|
|
|
5
5
|
const log = new Logger('ResponseValidator');
|
|
6
6
|
|
|
7
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeText(text: string): string {
|
|
12
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildSummaryText(toolName: string, payload: unknown): string {
|
|
16
|
+
if (typeof payload === 'string') {
|
|
17
|
+
const normalized = payload.trim();
|
|
18
|
+
return normalized || `${toolName} responded`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof payload === 'number' || typeof payload === 'bigint' || typeof payload === 'boolean') {
|
|
22
|
+
return `${toolName} responded: ${payload}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!isRecord(payload)) {
|
|
26
|
+
return `${toolName} responded`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const parts: string[] = [];
|
|
30
|
+
const message = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
|
|
31
|
+
const error = typeof payload.error === 'string' ? normalizeText(payload.error) : '';
|
|
32
|
+
const success = typeof payload.success === 'boolean' ? (payload.success ? 'success' : 'failed') : '';
|
|
33
|
+
const path = typeof payload.path === 'string' ? payload.path : '';
|
|
34
|
+
const name = typeof payload.name === 'string' ? payload.name : '';
|
|
35
|
+
const warningCount = Array.isArray(payload.warnings) ? payload.warnings.length : 0;
|
|
36
|
+
|
|
37
|
+
if (message) parts.push(message);
|
|
38
|
+
if (error && (!message || !message.includes(error))) parts.push(`error: ${error}`);
|
|
39
|
+
if (success) parts.push(success);
|
|
40
|
+
if (path) parts.push(`path: ${path}`);
|
|
41
|
+
if (name) parts.push(`name: ${name}`);
|
|
42
|
+
if (warningCount > 0) parts.push(`warnings: ${warningCount}`);
|
|
43
|
+
|
|
44
|
+
const summary = isRecord(payload.summary) ? payload.summary : undefined;
|
|
45
|
+
if (summary) {
|
|
46
|
+
const summaryParts: string[] = [];
|
|
47
|
+
for (const [key, value] of Object.entries(summary)) {
|
|
48
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
49
|
+
summaryParts.push(`${key}: ${value}`);
|
|
50
|
+
}
|
|
51
|
+
if (summaryParts.length >= 3) break;
|
|
52
|
+
}
|
|
53
|
+
if (summaryParts.length) {
|
|
54
|
+
parts.push(`summary(${summaryParts.join(', ')})`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (parts.length === 0) {
|
|
59
|
+
const keys = Object.keys(payload).slice(0, 3);
|
|
60
|
+
if (keys.length) {
|
|
61
|
+
return `${toolName} responded (${keys.join(', ')})`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return parts.length > 0
|
|
66
|
+
? parts.join(' | ')
|
|
67
|
+
: `${toolName} responded`;
|
|
68
|
+
}
|
|
69
|
+
|
|
7
70
|
/**
|
|
8
71
|
* Response Validator for MCP Tool Outputs
|
|
9
72
|
* Validates tool responses against their defined output schemas
|
|
@@ -148,14 +211,10 @@ export class ResponseValidator {
|
|
|
148
211
|
}
|
|
149
212
|
|
|
150
213
|
// Otherwise, wrap structured result into MCP content
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
text =
|
|
155
|
-
? response
|
|
156
|
-
: JSON.stringify(response ?? { success: true }, null, 2);
|
|
157
|
-
} catch (_e) {
|
|
158
|
-
text = String(response);
|
|
214
|
+
const summarySource = structuredPayload !== undefined ? structuredPayload : response;
|
|
215
|
+
let text = buildSummaryText(toolName, summarySource);
|
|
216
|
+
if (!text || !text.trim()) {
|
|
217
|
+
text = buildSummaryText(toolName, response);
|
|
159
218
|
}
|
|
160
219
|
|
|
161
220
|
const wrapped = {
|