11from mcp .server .fastmcp import FastMCP , Context , Image
22import logging
3+ import os
34from dataclasses import dataclass
45from contextlib import asynccontextmanager
56from typing import AsyncIterator , Dict , Any , List
67from config import config
78from tools import register_all_tools
89from unity_connection import get_unity_connection , UnityConnection
9- from telemetry import record_telemetry , record_milestone , RecordType , MilestoneType
1010import time
1111
1212# Configure logging using settings from config
1313logging .basicConfig (
1414 level = getattr (logging , config .log_level ),
15- format = config .log_format
15+ format = config .log_format ,
16+ stream = None , # None -> defaults to sys.stderr; avoid stdout used by MCP stdio
17+ force = True # Ensure our handler replaces any prior stdout handlers
1618)
1719logger = logging .getLogger ("mcp-for-unity-server" )
20+ # Quieten noisy third-party loggers to avoid clutter during stdio handshake
21+ for noisy in ("httpx" , "urllib3" ):
22+ try :
23+ logging .getLogger (noisy ).setLevel (max (logging .WARNING , getattr (logging , config .log_level )))
24+ except Exception :
25+ pass
26+
27+ # Import telemetry only after logging is configured to ensure its logs use stderr and proper levels
28+ from telemetry import record_telemetry , record_milestone , RecordType , MilestoneType
1829
1930# Global connection state
2031_unity_connection : UnityConnection = None
@@ -34,42 +45,52 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
3445 server_version = ver_path .read_text (encoding = "utf-8" ).strip ()
3546 except Exception :
3647 server_version = "unknown"
37- record_telemetry (RecordType .STARTUP , {
38- "server_version" : server_version ,
39- "startup_time" : start_time
40- })
48+ # Defer telemetry for first second to avoid interfering with stdio handshake
49+ if (time .perf_counter () - start_clk ) > 1.0 :
50+ record_telemetry (RecordType .STARTUP , {
51+ "server_version" : server_version ,
52+ "startup_time" : start_time
53+ })
4154
4255 # Record first startup milestone
43- record_milestone (MilestoneType .FIRST_STARTUP )
56+ if (time .perf_counter () - start_clk ) > 1.0 :
57+ record_milestone (MilestoneType .FIRST_STARTUP )
4458
4559 try :
46- _unity_connection = get_unity_connection ()
47- logger .info ("Connected to Unity on startup" )
48-
49- # Record successful Unity connection
50- record_telemetry (RecordType .UNITY_CONNECTION , {
51- "status" : "connected" ,
52- "connection_time_ms" : (time .time () - start_time ) * 1000
53- })
54-
60+ skip_connect = os .environ .get ("UNITY_MCP_SKIP_STARTUP_CONNECT" , "" ).lower () in ("1" , "true" , "yes" , "on" )
61+ if skip_connect :
62+ logger .info ("Skipping Unity connection on startup (UNITY_MCP_SKIP_STARTUP_CONNECT=1)" )
63+ else :
64+ _unity_connection = get_unity_connection ()
65+ logger .info ("Connected to Unity on startup" )
66+
67+ # Record successful Unity connection
68+ if (time .perf_counter () - start_clk ) > 1.0 :
69+ record_telemetry (RecordType .UNITY_CONNECTION , {
70+ "status" : "connected" ,
71+ "connection_time_ms" : (time .time () - start_time ) * 1000
72+ })
73+
5574 except ConnectionError as e :
5675 logger .warning ("Could not connect to Unity on startup: %s" , e )
5776 _unity_connection = None
5877
5978 # Record connection failure
60- record_telemetry (RecordType .UNITY_CONNECTION , {
61- "status" : "failed" ,
62- "error" : str (e )[:200 ],
63- "connection_time_ms" : (time .perf_counter () - start_clk ) * 1000
64- })
79+ if (time .perf_counter () - start_clk ) > 1.0 :
80+ record_telemetry (RecordType .UNITY_CONNECTION , {
81+ "status" : "failed" ,
82+ "error" : str (e )[:200 ],
83+ "connection_time_ms" : (time .perf_counter () - start_clk ) * 1000
84+ })
6585 except Exception as e :
6686 logger .warning ("Unexpected error connecting to Unity on startup: %s" , e )
6787 _unity_connection = None
68- record_telemetry (RecordType .UNITY_CONNECTION , {
69- "status" : "failed" ,
70- "error" : str (e )[:200 ],
71- "connection_time_ms" : (time .perf_counter () - start_clk ) * 1000
72- })
88+ if (time .perf_counter () - start_clk ) > 1.0 :
89+ record_telemetry (RecordType .UNITY_CONNECTION , {
90+ "status" : "failed" ,
91+ "error" : str (e )[:200 ],
92+ "connection_time_ms" : (time .perf_counter () - start_clk ) * 1000
93+ })
7394
7495 try :
7596 # Yield the connection object so it can be attached to the context
@@ -97,18 +118,18 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
97118def asset_creation_strategy () -> str :
98119 """Guide for discovering and using MCP for Unity tools effectively."""
99120 return (
100- "Available MCP for Unity Server Tools:\\ n \ \ n"
101- "- `manage_editor`: Controls editor state and queries info.\\ n"
102- "- `execute_menu_item`: Executes Unity Editor menu items by path.\\ n"
103- "- `read_console`: Reads or clears Unity console messages, with filtering options.\\ n"
104- "- `manage_scene`: Manages scenes.\\ n"
105- "- `manage_gameobject`: Manages GameObjects in the scene.\\ n"
106- "- `manage_script`: Manages C# script files.\\ n"
107- "- `manage_asset`: Manages prefabs and assets.\\ n"
108- "- `manage_shader`: Manages shaders.\\ n \ \ n"
109- "Tips:\\ n"
110- "- Create prefabs for reusable GameObjects.\\ n"
111- "- Always include a camera and main light in your scenes.\\ n"
121+ "Available MCP for Unity Server Tools:\n \n "
122+ "- `manage_editor`: Controls editor state and queries info.\n "
123+ "- `execute_menu_item`: Executes Unity Editor menu items by path.\n "
124+ "- `read_console`: Reads or clears Unity console messages, with filtering options.\n "
125+ "- `manage_scene`: Manages scenes.\n "
126+ "- `manage_gameobject`: Manages GameObjects in the scene.\n "
127+ "- `manage_script`: Manages C# script files.\n "
128+ "- `manage_asset`: Manages prefabs and assets.\n "
129+ "- `manage_shader`: Manages shaders.\n \n "
130+ "Tips:\n "
131+ "- Create prefabs for reusable GameObjects.\n "
132+ "- Always include a camera and main light in your scenes.\n "
112133 )
113134
114135# Run the server
0 commit comments