Skip to content

Commit cbfecd4

Browse files
Merge pull request #35 from SamyNarrainen/env_port_fix
Env port fix
2 parents 4d0680b + 63622d8 commit cbfecd4

File tree

7 files changed

+92
-113
lines changed

7 files changed

+92
-113
lines changed

Editor/UnityBridge/McpUnitySettings.cs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public class McpUnitySettings
1919

2020
private const string EnvUnityPort = "UNITY_PORT";
2121
private const string EnvUnityRequestTimeout = "UNITY_REQUEST_TIMEOUT";
22+
/// <remarks>
23+
/// This file path is also read by the MCP server. Changes here will require updates to it. See mcpUnity.ts
24+
/// </remarks>
2225
private const string SettingsPath = "ProjectSettings/McpUnitySettings.json";
2326

2427
private static McpUnitySettings _instance;
@@ -72,18 +75,6 @@ public void LoadSettings()
7275
string json = File.ReadAllText(SettingsPath);
7376
JsonUtility.FromJsonOverwrite(json, this);
7477
}
75-
76-
// Check for environment variable PORT
77-
string envPort = System.Environment.GetEnvironmentVariable(EnvUnityPort);
78-
if (!string.IsNullOrEmpty(envPort) && int.TryParse(envPort, out int port))
79-
{
80-
Port = port;
81-
}
82-
string envTimeout = System.Environment.GetEnvironmentVariable(EnvUnityRequestTimeout);
83-
if (!string.IsNullOrEmpty(envTimeout) && int.TryParse(envTimeout, out int timeout))
84-
{
85-
RequestTimeoutSeconds = timeout;
86-
}
8778
}
8879
catch (Exception ex)
8980
{
@@ -95,21 +86,16 @@ public void LoadSettings()
9586
/// <summary>
9687
/// Save settings to disk
9788
/// </summary>
89+
/// <remarks>
90+
/// WARNING: This file is also read by the MCP server. Changes here will require updates to it. See mcpUnity.ts
91+
/// </remarks>
9892
public void SaveSettings()
9993
{
10094
try
10195
{
10296
// Save settings to McpUnitySettings.json
10397
string json = JsonUtility.ToJson(this, true);
10498
File.WriteAllText(SettingsPath, json);
105-
106-
// Set environment variable PORT for the Node.js process
107-
// EnvironmentVariableTarget.User and EnvironmentVariableTarget.Machine should be used on .NET implementations running on Windows systems only.
108-
// For non-Windows systems, User and Machine are treated as Process.
109-
// Using Process target for broader compatibility.
110-
// see: https://learn.microsoft.com/en-us/dotnet/api/system.environmentvariabletarget?view=net-8.0#remarks
111-
Environment.SetEnvironmentVariable(EnvUnityPort, Port.ToString(), EnvironmentVariableTarget.Process);
112-
Environment.SetEnvironmentVariable(EnvUnityRequestTimeout, RequestTimeoutSeconds.ToString(), EnvironmentVariableTarget.Process);
11399
}
114100
catch (Exception ex)
115101
{

Editor/Utils/McpConfigUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static string GenerateMcpConfigJson(bool useTabsIndentation)
6060
}
6161

6262
/// <summary>
63-
/// Gets the absolute path to the Server directory containing package.json
63+
/// Gets the absolute path to the Server directory containing package.json (root server dir).
6464
/// Works whether MCP Unity is installed via Package Manager or directly in the Assets folder
6565
/// </summary>
6666
public static string GetServerPath()

Server~/build/unity/mcpUnity.d.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@ export declare class McpUnity {
99
private port;
1010
private ws;
1111
private pendingRequests;
12-
private readonly REQUEST_TIMEOUT;
12+
private requestTimeout;
1313
private retryDelay;
1414
constructor(logger: Logger);
1515
/**
1616
* Start the Unity connection
1717
* @param clientName Optional name of the MCP client connecting to Unity
1818
*/
1919
start(clientName?: string): Promise<void>;
20+
/**
21+
* Reads our configuration file and sets parameters of the server based on them.
22+
*/
23+
private parseAndSetConfig;
2024
/**
2125
* Connect to the Unity WebSocket
2226
* @param clientName Optional name of the MCP client connecting to Unity
@@ -48,14 +52,9 @@ export declare class McpUnity {
4852
*/
4953
get isConnected(): boolean;
5054
/**
51-
* Retrieves the UNITY_PORT value from the Windows registry (HKCU\Environment)
52-
* @returns The port value as a string if found, otherwise an empty string
53-
*/
54-
private getUnityPortFromWindowsRegistry;
55-
/**
56-
* Retrieves the UNITY_PORT value from Unix-like system environment variables
57-
* @returns The port value as a string if found, otherwise an empty string
55+
* Read the McpUnitySettings.json file and return its contents as a JSON object.
56+
* @returns a JSON object with the contents of the McpUnitySettings.json file.
5857
*/
59-
private getUnityPortFromUnixRegistry;
58+
private readConfigFileAsJson;
6059
}
6160
export {};

Server~/build/unity/mcpUnity.js

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
11
import WebSocket from 'ws';
22
import { v4 as uuidv4 } from 'uuid';
33
import { McpUnityError, ErrorType } from '../utils/errors.js';
4-
import { execSync } from 'child_process';
5-
import { default as winreg } from 'winreg';
4+
import { promises as fs } from 'fs';
5+
import path from 'path';
66
export class McpUnity {
77
logger;
8-
port;
8+
port = null;
99
ws = null;
1010
pendingRequests = new Map();
11-
REQUEST_TIMEOUT;
11+
requestTimeout = 10000;
1212
retryDelay = 1000;
1313
constructor(logger) {
1414
this.logger = logger;
15-
// Initialize port from environment variable or use default
16-
const envRegistry = process.platform === 'win32'
17-
? this.getUnityPortFromWindowsRegistry()
18-
: this.getUnityPortFromUnixRegistry();
19-
const envPort = process.env.UNITY_PORT || envRegistry;
20-
this.port = envPort ? parseInt(envPort, 10) : 8090;
21-
this.logger.info(`Using port: ${this.port} for Unity WebSocket connection`);
22-
// Initialize timeout from environment variable (in seconds; it is the same as Cline) or use default (10 seconds)
23-
const envTimeout = process.env.UNITY_REQUEST_TIMEOUT;
24-
this.REQUEST_TIMEOUT = envTimeout ? parseInt(envTimeout, 10) * 1000 : 10000;
25-
this.logger.info(`Using request timeout: ${this.REQUEST_TIMEOUT / 1000} seconds`);
2615
}
2716
/**
2817
* Start the Unity connection
2918
* @param clientName Optional name of the MCP client connecting to Unity
3019
*/
3120
async start(clientName) {
3221
try {
22+
this.logger.info('Attempting to read startup parameters...');
23+
await this.parseAndSetConfig();
3324
this.logger.info('Attempting to connect to Unity WebSocket...');
3425
await this.connect(clientName); // Pass client name to connect
3526
this.logger.info('Successfully connected to Unity WebSocket');
@@ -45,6 +36,19 @@ export class McpUnity {
4536
}
4637
return Promise.resolve();
4738
}
39+
/**
40+
* Reads our configuration file and sets parameters of the server based on them.
41+
*/
42+
async parseAndSetConfig() {
43+
const config = await this.readConfigFileAsJson();
44+
const configPort = config.Port;
45+
this.port = configPort ? parseInt(configPort, 10) : 8090;
46+
this.logger.info(`Using port: ${this.port} for Unity WebSocket connection`);
47+
// Initialize timeout from environment variable (in seconds; it is the same as Cline) or use default (10 seconds)
48+
const configTimeout = config.RequestTimeoutSeconds;
49+
this.requestTimeout = configTimeout ? parseInt(configTimeout, 10) * 1000 : 10000;
50+
this.logger.info(`Using request timeout: ${this.requestTimeout / 1000} seconds`);
51+
}
4852
/**
4953
* Connect to the Unity WebSocket
5054
* @param clientName Optional name of the MCP client connecting to Unity
@@ -74,7 +78,7 @@ export class McpUnity {
7478
this.disconnect();
7579
reject(new McpUnityError(ErrorType.CONNECTION, 'Connection timeout'));
7680
}
77-
}, this.REQUEST_TIMEOUT);
81+
}, this.requestTimeout);
7882
this.ws.onopen = () => {
7983
clearTimeout(connectionTimeout);
8084
this.logger.debug('WebSocket connected');
@@ -193,12 +197,12 @@ export class McpUnity {
193197
// Create timeout for the request
194198
const timeout = setTimeout(() => {
195199
if (this.pendingRequests.has(requestId)) {
196-
this.logger.error(`Request ${requestId} timed out after ${this.REQUEST_TIMEOUT}ms`);
200+
this.logger.error(`Request ${requestId} timed out after ${this.requestTimeout}ms`);
197201
this.pendingRequests.delete(requestId);
198202
reject(new McpUnityError(ErrorType.TIMEOUT, 'Request timed out'));
199203
}
200204
this.reconnect();
201-
}, this.REQUEST_TIMEOUT);
205+
}, this.requestTimeout);
202206
// Store pending request
203207
this.pendingRequests.set(requestId, {
204208
resolve,
@@ -225,27 +229,20 @@ export class McpUnity {
225229
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
226230
}
227231
/**
228-
* Retrieves the UNITY_PORT value from the Windows registry (HKCU\Environment)
229-
* @returns The port value as a string if found, otherwise an empty string
230-
*/
231-
getUnityPortFromWindowsRegistry() {
232-
const regKey = new winreg({ hive: winreg.HKCU, key: '\\Environment' });
233-
let result = '';
234-
regKey.get('UNITY_PORT', (err, item) => {
235-
if (err) {
236-
this.logger.error(`Error getting registry value: ${err.message}`);
237-
}
238-
else {
239-
result = item.value;
240-
}
241-
});
242-
return result;
243-
}
244-
/**
245-
* Retrieves the UNITY_PORT value from Unix-like system environment variables
246-
* @returns The port value as a string if found, otherwise an empty string
232+
* Read the McpUnitySettings.json file and return its contents as a JSON object.
233+
* @returns a JSON object with the contents of the McpUnitySettings.json file.
247234
*/
248-
getUnityPortFromUnixRegistry() {
249-
return execSync('printenv UNITY_PORT', { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
235+
async readConfigFileAsJson() {
236+
const configPath = path.resolve(process.cwd(), '../ProjectSettings/McpUnitySettings.json');
237+
this.logger.debug(`Reading McpUnitySettings.json from ${configPath}`);
238+
try {
239+
const content = await fs.readFile(configPath, 'utf-8');
240+
const json = JSON.parse(content);
241+
return json;
242+
}
243+
catch (err) {
244+
this.logger.debug(`McpUnitySettings.json not found or unreadable: ${err instanceof Error ? err.message : String(err)}`);
245+
return {};
246+
}
250247
}
251248
}

Server~/build/utils/errors.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export var ErrorType;
66
ErrorType["VALIDATION"] = "validation_error";
77
ErrorType["INTERNAL"] = "internal_error";
88
ErrorType["TIMEOUT"] = "timeout_error";
9-
})(ErrorType || (ErrorType = {}));
9+
})(ErrorType = ErrorType || (ErrorType = {}));
1010
export class McpUnityError extends Error {
1111
type;
1212
details;

Server~/build/utils/logger.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export var LogLevel;
55
LogLevel[LogLevel["INFO"] = 1] = "INFO";
66
LogLevel[LogLevel["WARN"] = 2] = "WARN";
77
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
8-
})(LogLevel || (LogLevel = {}));
8+
})(LogLevel = LogLevel || (LogLevel = {}));
99
// Check environment variable for logging
1010
const isLoggingEnabled = process.env.LOGGING === 'true';
1111
// Check environment variable for logging in a file

Server~/src/unity/mcpUnity.ts

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import WebSocket from 'ws';
22
import { v4 as uuidv4 } from 'uuid';
33
import { Logger } from '../utils/logger.js';
44
import { McpUnityError, ErrorType } from '../utils/errors.js';
5-
import { execSync } from 'child_process';
6-
import { default as winreg } from 'winreg';
5+
import { promises as fs } from 'fs';
6+
import path from 'path';
77

88
interface PendingRequest {
99
resolve: (value: any) => void;
@@ -30,28 +30,14 @@ interface UnityResponse {
3030

3131
export class McpUnity {
3232
private logger: Logger;
33-
private port: number;
33+
private port: number | null = null;
3434
private ws: WebSocket | null = null;
3535
private pendingRequests: Map<string, PendingRequest> = new Map<string, PendingRequest>();
36-
private readonly REQUEST_TIMEOUT: number;
36+
private requestTimeout = 10000;
3737
private retryDelay = 1000;
3838

3939
constructor(logger: Logger) {
4040
this.logger = logger;
41-
42-
// Initialize port from environment variable or use default
43-
const envRegistry = process.platform === 'win32'
44-
? this.getUnityPortFromWindowsRegistry()
45-
: this.getUnityPortFromUnixRegistry();
46-
47-
const envPort = process.env.UNITY_PORT || envRegistry;
48-
this.port = envPort ? parseInt(envPort, 10) : 8090;
49-
this.logger.info(`Using port: ${this.port} for Unity WebSocket connection`);
50-
51-
// Initialize timeout from environment variable (in seconds; it is the same as Cline) or use default (10 seconds)
52-
const envTimeout = process.env.UNITY_REQUEST_TIMEOUT;
53-
this.REQUEST_TIMEOUT = envTimeout ? parseInt(envTimeout, 10) * 1000 : 10000;
54-
this.logger.info(`Using request timeout: ${this.REQUEST_TIMEOUT / 1000} seconds`);
5541
}
5642

5743
/**
@@ -60,6 +46,9 @@ export class McpUnity {
6046
*/
6147
public async start(clientName?: string): Promise<void> {
6248
try {
49+
this.logger.info('Attempting to read startup parameters...');
50+
await this.parseAndSetConfig();
51+
6352
this.logger.info('Attempting to connect to Unity WebSocket...');
6453
await this.connect(clientName); // Pass client name to connect
6554
this.logger.info('Successfully connected to Unity WebSocket');
@@ -77,6 +66,22 @@ export class McpUnity {
7766

7867
return Promise.resolve();
7968
}
69+
70+
/**
71+
* Reads our configuration file and sets parameters of the server based on them.
72+
*/
73+
private async parseAndSetConfig() {
74+
const config = await this.readConfigFileAsJson();
75+
76+
const configPort = config.Port;
77+
this.port = configPort ? parseInt(configPort, 10) : 8090;
78+
this.logger.info(`Using port: ${this.port} for Unity WebSocket connection`);
79+
80+
// Initialize timeout from environment variable (in seconds; it is the same as Cline) or use default (10 seconds)
81+
const configTimeout = config.RequestTimeoutSeconds;
82+
this.requestTimeout = configTimeout ? parseInt(configTimeout, 10) * 1000 : 10000;
83+
this.logger.info(`Using request timeout: ${this.requestTimeout / 1000} seconds`);
84+
}
8085

8186
/**
8287
* Connect to the Unity WebSocket
@@ -112,7 +117,7 @@ export class McpUnity {
112117
this.disconnect();
113118
reject(new McpUnityError(ErrorType.CONNECTION, 'Connection timeout'));
114119
}
115-
}, this.REQUEST_TIMEOUT);
120+
}, this.requestTimeout);
116121

117122
this.ws.onopen = () => {
118123
clearTimeout(connectionTimeout);
@@ -249,12 +254,12 @@ export class McpUnity {
249254
// Create timeout for the request
250255
const timeout = setTimeout(() => {
251256
if (this.pendingRequests.has(requestId)) {
252-
this.logger.error(`Request ${requestId} timed out after ${this.REQUEST_TIMEOUT}ms`);
257+
this.logger.error(`Request ${requestId} timed out after ${this.requestTimeout}ms`);
253258
this.pendingRequests.delete(requestId);
254259
reject(new McpUnityError(ErrorType.TIMEOUT, 'Request timed out'));
255260
}
256261
this.reconnect();
257-
}, this.REQUEST_TIMEOUT);
262+
}, this.requestTimeout);
258263

259264
// Store pending request
260265
this.pendingRequests.set(requestId, {
@@ -282,29 +287,21 @@ export class McpUnity {
282287
// Basic WebSocket connection check
283288
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
284289
}
285-
286-
/**
287-
* Retrieves the UNITY_PORT value from the Windows registry (HKCU\Environment)
288-
* @returns The port value as a string if found, otherwise an empty string
289-
*/
290-
private getUnityPortFromWindowsRegistry(): string {
291-
const regKey = new winreg({hive: winreg.HKCU, key: '\\Environment'});
292-
let result = '';
293-
regKey.get('UNITY_PORT', (err: Error | null, item: any) => {
294-
if (err) {
295-
this.logger.error(`Error getting registry value: ${err.message}`);
296-
} else {
297-
result = item.value;
298-
}
299-
});
300-
return result;
301-
}
302290

303291
/**
304-
* Retrieves the UNITY_PORT value from Unix-like system environment variables
305-
* @returns The port value as a string if found, otherwise an empty string
292+
* Read the McpUnitySettings.json file and return its contents as a JSON object.
293+
* @returns a JSON object with the contents of the McpUnitySettings.json file.
306294
*/
307-
private getUnityPortFromUnixRegistry(): string {
308-
return execSync('printenv UNITY_PORT', { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
295+
private async readConfigFileAsJson(): Promise<any> {
296+
const configPath = path.resolve(process.cwd(), '../ProjectSettings/McpUnitySettings.json');
297+
this.logger.debug(`Reading McpUnitySettings.json from ${configPath}`);
298+
try {
299+
const content = await fs.readFile(configPath, 'utf-8');
300+
const json = JSON.parse(content);
301+
return json;
302+
} catch (err) {
303+
this.logger.debug(`McpUnitySettings.json not found or unreadable: ${err instanceof Error ? err.message : String(err)}`);
304+
return {};
305+
}
309306
}
310307
}

0 commit comments

Comments
 (0)