11using System ;
22using System . Collections . Generic ;
33using System . ComponentModel ;
4+ using System . Linq ;
5+ using System . Reflection ;
6+ using System . Text . RegularExpressions ;
7+ using System . Threading . Tasks ;
48using Cysharp . Threading . Tasks ;
59using ModelContextProtocol . Server ;
610using UnityEditor ;
711using UnityEngine ;
12+ using Assert = UnityEngine . Assertions . Assert ;
813
914namespace UnityNaturalMCP . Editor . McpTools
1015{
@@ -14,30 +19,119 @@ internal sealed class McpUnityEditorTool
1419 [ McpServerTool , Description ( "Execute AssetDatabase.Refresh" ) ]
1520 public async UniTask RefreshAssets ( )
1621 {
17- await UniTask . SwitchToMainThread ( ) ;
18- AssetDatabase . Refresh ( ) ;
22+ try
23+ {
24+ await UniTask . SwitchToMainThread ( ) ;
25+ AssetDatabase . Refresh ( ) ;
26+ }
27+ catch ( Exception e )
28+ {
29+ Debug . LogError ( e ) ;
30+ throw ;
31+ }
1932 }
20- [ McpServerTool , Description ( "Get log history." ) ]
21- public IReadOnlyList < LogEntry > GetLogHistory (
22- [ Description ( "Filter logs by type. Valid values: \" All\" , \" Error\" , \" Assert\" , \" Warning\" , \" Exception\" " ) ]
23- string logType )
33+
34+ [ McpServerTool , Description ( "Get current console logs. Recommend calling ClearConsoleLogs beforehand." ) ]
35+ public async Task < IReadOnlyList < LogEntry > > GetCurrentConsoleLogs (
36+ [ Description (
37+ "Filter logs by type. Valid values: \" \" (Maches all logs), \" error\" , \" warning\" , \" log\" , \" compile-error\" (This is all you need to check for compilation errors.), \" compile-warning\" " ) ]
38+ string logType = "" ,
39+ [ Description ( "Filter by regex. If empty, all logs are returned." ) ]
40+ string filter = "" ,
41+ [ Description ( "Log count limit. Set to 0 for no limit(Not recommended)." ) ]
42+ int maxCount = 20 ,
43+ [ Description ( "Get only first line of the log message. If false, the whole message is returned.(To save tokens, recommend calling this with true.)" ) ]
44+ bool onlyFirstLine = true ,
45+ [ Description (
46+ "If true, the logs will be sorted by time in chronological order(oldest first). If false, newest first." ) ]
47+ bool isChronological = false )
2448 {
25- if ( logType . ToLower ( ) == "all" )
49+ try
50+ {
51+ await UniTask . SwitchToMainThread ( ) ;
52+ var logTypeToLower = logType . ToLower ( ) ;
53+ var logs = new List < LogEntry > ( ) ;
54+ var logEntries = Type . GetType ( "UnityEditor.LogEntries,UnityEditor.dll" ) ;
55+ Assert . IsNotNull ( logEntries ) ;
56+
57+ var getCountMethod = logEntries . GetMethod ( "GetCount" , BindingFlags . Public | BindingFlags . Static ) ;
58+ var getEntryInternalMethod = logEntries . GetMethod ( "GetEntryInternal" , BindingFlags . Public | BindingFlags . Static ) ;
59+
60+ Assert . IsNotNull ( getCountMethod ) ;
61+ Assert . IsNotNull ( getEntryInternalMethod ) ;
62+
63+ var count = ( int ) getCountMethod . Invoke ( null , null ) ;
64+
65+ for ( var i = 0 ; i < count ; i ++ )
66+ {
67+ var logEntryType = Type . GetType ( "UnityEditor.LogEntry,UnityEditor.dll" ) ;
68+ Assert . IsNotNull ( logEntryType ) ;
69+
70+ var logEntry = Activator . CreateInstance ( logEntryType ) ;
71+
72+ getEntryInternalMethod . Invoke ( null , new [ ] { i , logEntry } ) ;
73+
74+ var message = logEntry . GetType ( ) . GetField ( "message" ) . GetValue ( logEntry ) as string ?? "" ;
75+ var file = logEntry . GetType ( ) . GetField ( "file" ) . GetValue ( logEntry ) as string ?? "" ;
76+ var mode = ( int ) logEntry . GetType ( ) . GetField ( "mode" ) . GetValue ( logEntry ) ;
77+ var logTypeValue = UnityInternalLogModeToLogType ( mode ) ;
78+
79+ if ( ( string . IsNullOrEmpty ( logTypeToLower ) || logTypeValue . Equals ( logTypeToLower ) )
80+ && ( string . IsNullOrEmpty ( filter ) || Regex . IsMatch ( message , filter ) ) )
81+ {
82+ logs . Add ( new LogEntry ( onlyFirstLine ? message . Split ( '\n ' ) [ 0 ] : message , logTypeValue ) ) ;
83+ }
84+ }
85+
86+ if ( ! isChronological )
87+ {
88+ logs = ( ( IEnumerable < LogEntry > ) logs ) . Reverse ( ) . ToList ( ) ;
89+ }
90+
91+ if ( maxCount > 0 )
92+ {
93+ logs = logs . Take ( maxCount ) . ToList ( ) ;
94+ }
95+
96+ return logs ;
97+ }
98+ catch ( Exception e )
2699 {
27- return LogCollector . LogHistory ;
100+ Debug . LogError ( e ) ;
101+ throw ;
28102 }
103+ }
104+
105+ [ McpServerTool , Description ( "Clear console logs. It is recommended to call it before GetCurrentConsoleLogs." ) ]
106+ public async Task ClearConsoleLogs ( )
107+ {
108+ try
109+ {
110+ await UniTask . SwitchToMainThread ( ) ;
111+ var logEntries = Type . GetType ( "UnityEditor.LogEntries,UnityEditor.dll" ) ;
112+ Assert . IsNotNull ( logEntries ) ;
113+
114+ var clearMethod = logEntries . GetMethod ( "Clear" , BindingFlags . Public | BindingFlags . Static ) ;
29115
30- var logTypeEnum = logType . ToLower ( ) switch
116+ Assert . IsNotNull ( clearMethod ) ;
117+
118+ clearMethod . Invoke ( null , null ) ;
119+ }
120+ catch ( Exception e )
31121 {
32- "error" => LogType . Error ,
33- "assert" => LogType . Assert ,
34- "warning" => LogType . Warning ,
35- "log" => LogType . Log ,
36- "exception" => LogType . Exception ,
37- _ => throw new ArgumentOutOfRangeException ( nameof ( logType ) , logType , null )
38- } ;
39-
40- return LogCollector . GetLogHistory ( logTypeEnum ) ;
122+ Debug . LogError ( e ) ;
123+ throw ;
124+ }
41125 }
126+
127+ private string UnityInternalLogModeToLogType ( int mode ) => mode switch
128+ {
129+ _ when ( mode & ( int ) LogMessageFlags . ScriptingError ) != 0 => "error" ,
130+ _ when ( mode & ( int ) LogMessageFlags . ScriptingWarning ) != 0 => "warning" ,
131+ _ when ( mode & ( int ) LogMessageFlags . ScriptingLog ) != 0 => "log" ,
132+ _ when ( mode & ( int ) LogMessageFlags . ScriptCompileError ) != 0 => "compile-error" ,
133+ _ when ( mode & ( int ) LogMessageFlags . ScriptCompileWarning ) != 0 => "compile-warning" ,
134+ _ => "unknown"
135+ } ;
42136 }
43137}
0 commit comments