Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions MCPForUnity/Editor/Helpers/McpLog.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using MCPForUnity.Editor.Constants;
using UnityEditor;
using UnityEngine;
Expand All @@ -11,6 +12,15 @@ internal static class McpLog
private const string WarnPrefix = "<b><color=#cc7a00>MCP-FOR-UNITY</color></b>:";
private const string ErrorPrefix = "<b><color=#cc3333>MCP-FOR-UNITY</color></b>:";

internal static bool IsMcpLogMessage(string message)
{
return message.StartsWith(InfoPrefix, StringComparison.Ordinal)
|| message.StartsWith(DebugPrefix, StringComparison.Ordinal)
|| message.StartsWith(WarnPrefix, StringComparison.Ordinal)
|| message.StartsWith(ErrorPrefix, StringComparison.Ordinal)
|| message.StartsWith("MCP-FOR-UNITY:", StringComparison.Ordinal);
}

private static volatile bool _debugEnabled = ReadDebugPreference();

private static bool IsDebugEnabled() => _debugEnabled;
Expand Down
12 changes: 10 additions & 2 deletions MCPForUnity/Editor/Tools/ReadConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public static object HandleCommand(JObject @params)
string filterText = p.Get("filterText");
string format = p.Get("format", "plain").ToLower();
bool includeStacktrace = p.GetBool("includeStacktrace", false);
bool includeMcpLogs = p.GetBool("includeMcpLogs", true);

if (types.Contains("all"))
{
Expand All @@ -205,7 +206,8 @@ public static object HandleCommand(JObject @params)
cursor,
filterText,
format,
includeStacktrace
includeStacktrace,
includeMcpLogs
);
}
else
Expand Down Expand Up @@ -349,7 +351,8 @@ private static object GetConsoleEntries(
int? cursor,
string filterText,
string format,
bool includeStacktrace
bool includeStacktrace,
bool includeMcpLogs
)
{
List<object> formattedEntries = new List<object>();
Expand Down Expand Up @@ -409,6 +412,11 @@ bool includeStacktrace
continue; // Skip empty messages
}

if (!includeMcpLogs && McpLog.IsMcpLogMessage(message))
{
continue;
}

// (Calibration removed)

// --- Filtering ---
Expand Down
9 changes: 8 additions & 1 deletion Server/src/cli/commands/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,26 @@ def stop():
is_flag=True,
help="Include stack traces."
)
@click.option(
"--mcp-logs/--no-mcp-logs",
default=True,
help="Include MCP for Unity's own diagnostics (default: included)."
)
@click.option(
"--clear",
is_flag=True,
help="Clear the console instead of reading."
)
@handle_unity_errors
def console(log_types: tuple, count: int, filter_text: Optional[str], stacktrace: bool, clear: bool):
def console(log_types: tuple, count: int, filter_text: Optional[str], stacktrace: bool, mcp_logs: bool, clear: bool):
"""Read or clear the Unity console.

\b
Examples:
unity-mcp editor console
unity-mcp editor console --type error --count 20
unity-mcp editor console --filter "NullReference" --stacktrace
unity-mcp editor console --type error --no-mcp-logs
unity-mcp editor console --clear
"""
config = get_config()
Expand All @@ -106,6 +112,7 @@ def console(log_types: tuple, count: int, filter_text: Optional[str], stacktrace
"types": list(log_types),
"count": count,
"include_stacktrace": stacktrace,
"include_mcp_logs": mcp_logs,
}

if filter_text:
Expand Down
6 changes: 5 additions & 1 deletion Server/src/services/tools/read_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ async def read_console(
'json'], "Output format"] | None = None,
include_stacktrace: Annotated[bool | str,
"Include stack traces in output (accepts true/false or 'true'/'false')"] | None = None,
include_mcp_logs: Annotated[bool | str,
"Include MCP for Unity's own diagnostics (default true). Set false to exclude them before count and paging limits."] | None = None,
) -> dict[str, Any]:
# Get active instance from session state
# Removed session_state import
Expand Down Expand Up @@ -96,6 +98,7 @@ async def read_console(
# Coerce booleans defensively (strings like 'true'/'false')

include_stacktrace = coerce_bool(include_stacktrace, default=False)
include_mcp_logs = coerce_bool(include_mcp_logs, default=True)
coerced_page_size = coerce_int(page_size, default=None)
coerced_cursor = coerce_int(cursor, default=None)

Expand Down Expand Up @@ -125,7 +128,8 @@ async def read_console(
"pageSize": coerced_page_size,
"cursor": coerced_cursor,
"format": format.lower() if isinstance(format, str) else format,
"includeStacktrace": include_stacktrace
"includeStacktrace": include_stacktrace,
"includeMcpLogs": include_mcp_logs
}

# Remove None values unless it's 'count' (as None might mean 'all')
Expand Down
22 changes: 21 additions & 1 deletion Server/tests/integration/test_read_console_truncate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ def setup_console_tools():
return mcp.tools


@pytest.mark.asyncio
@pytest.mark.parametrize("value, expected", [(None, True), (True, True), (False, False), ("true", True), ("false", False)])
async def test_read_console_include_mcp_logs(monkeypatch, value, expected):
read_console = setup_console_tools()["read_console"]
captured = {}

async def fake_send(_cmd, params, **_kwargs):
captured.update(params)
return {"success": True, "data": []}

import services.tools.read_console as read_console_mod
monkeypatch.setattr(read_console_mod, "async_send_command_with_retry", fake_send)

kwargs = {} if value is None else {"include_mcp_logs": value}
result = await read_console(ctx=DummyContext(), **kwargs)

assert result["success"]
assert captured["includeMcpLogs"] is expected


@pytest.mark.asyncio
async def test_read_console_full_default(monkeypatch):
tools = setup_console_tools()
Expand Down Expand Up @@ -245,4 +265,4 @@ async def fake_send_with_unity_instance(_send_fn, _unity_instance, _command_type
resp = await read_console(ctx=DummyContext(), action="get", types='[1, "error"]')
assert resp["success"] is False
assert "types entries must be strings" in resp["message"]
assert captured == {}
assert captured == {}
7 changes: 7 additions & 0 deletions Server/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,13 @@ def test_editor_console_clear(self, runner, mock_unity_response):
result = runner.invoke(cli, ["editor", "console", "--clear"])
assert result.exit_code == 0

@pytest.mark.parametrize("options, expected", [([], True), (["--no-mcp-logs"], False), (["--mcp-logs"], True)])
def test_editor_console_mcp_logs(self, runner, mock_unity_response, options, expected):
with patch("cli.commands.editor.run_command", return_value=mock_unity_response) as command:
result = runner.invoke(cli, ["editor", "console", *options])
assert result.exit_code == 0, result.output
assert command.call_args.args[1]["include_mcp_logs"] is expected

def test_editor_add_tag(self, runner, mock_unity_response):
"""Test editor add-tag command."""
with patch("cli.commands.editor.run_command", return_value=mock_unity_response):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,126 @@
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Tools;
using MCPForUnity.Editor.Helpers;
using static MCPForUnityTests.Editor.TestUtilities;

namespace MCPForUnityTests.Editor.Tools
{
public class ReadConsoleTests
{
[TestCase("plain")]
[TestCase("detailed")]
public void HandleCommand_Get_ExcludesMcpLogsBeforeCount(string format)
{
string id = Guid.NewGuid().ToString();
McpLog.Warn($"Transport diagnostic {id}");
Debug.LogWarning($"Project warning {id}");

var result = ToJObject(ReadConsole.HandleCommand(new JObject
{
["types"] = new JArray { "warning" },
["filterText"] = id,
["format"] = format,
["count"] = 1,
["includeMcpLogs"] = false
}));

Assert.IsTrue(result.Value<bool>("success"), result.ToString());
var entries = (JArray)result["data"];
Assert.AreEqual(1, entries.Count);
string message = format == "plain" ? entries[0].ToString() : entries[0].Value<string>("message");
Assert.AreEqual($"Project warning {id}", message);
}

[Test]
public void HandleCommand_Get_ExcludesMcpLogsBeforePaging()
{
string id = Guid.NewGuid().ToString();
McpLog.Info($"Transport diagnostic {id}");
Debug.Log($"First project log {id}");
McpLog.Warn($"Transport warning {id}");
Debug.Log($"Second project log {id}");

var parameters = new JObject
{
["types"] = new JArray { "all" },
["filterText"] = id,
["format"] = "plain",
["pageSize"] = 1,
["include_mcp_logs"] = false
};
var first = ToJObject(ReadConsole.HandleCommand(parameters));
Assert.IsTrue(first.Value<bool>("success"), first.ToString());
Assert.AreEqual($"First project log {id}", first["data"]["items"][0].ToString());
Assert.AreEqual("1", first["data"].Value<string>("nextCursor"));

parameters["cursor"] = 1;
var second = ToJObject(ReadConsole.HandleCommand(parameters));
Assert.IsTrue(second.Value<bool>("success"), second.ToString());
Assert.AreEqual($"Second project log {id}", second["data"]["items"][0].ToString());
Assert.IsFalse(second["data"].Value<bool>("truncated"));
Assert.AreEqual(JTokenType.Null, second["data"]["nextCursor"].Type);
}

[TestCase(false)]
[TestCase(true)]
public void HandleCommand_Get_IncludesMcpLogsByDefaultAndWhenRequested(bool explicitOption)
{
string id = Guid.NewGuid().ToString();
McpLog.Warn($"Transport diagnostic {id}");
var parameters = new JObject
{
["types"] = new JArray { "warning" },
["filterText"] = id,
["format"] = "plain"
};
if (explicitOption) parameters["includeMcpLogs"] = true;

var result = ToJObject(ReadConsole.HandleCommand(parameters));
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
Assert.AreEqual(1, ((JArray)result["data"]).Count);
StringAssert.Contains($"Transport diagnostic {id}", result["data"][0].ToString());
}

[Test]
public void HandleCommand_Get_KeepsProjectMessagesMentioningMcp()
{
string id = Guid.NewGuid().ToString();
string message = $"Project warning {id}: MCP-FOR-UNITY: connection unavailable";
Debug.LogWarning(message);
var result = ToJObject(ReadConsole.HandleCommand(new JObject
{
["types"] = new JArray { "warning" },
["filterText"] = id,
["format"] = "plain",
["includeMcpLogs"] = false
}));

Assert.IsTrue(result.Value<bool>("success"), result.ToString());
Assert.AreEqual(1, ((JArray)result["data"]).Count);
Assert.AreEqual(message, result["data"][0].ToString());
}

[TestCase("MCP-FOR-UNITY:")]
[TestCase("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>:")]
[TestCase("<b><color=#6AA84F>MCP-FOR-UNITY</color></b>:")]
[TestCase("<b><color=#cc7a00>MCP-FOR-UNITY</color></b>:")]
[TestCase("<b><color=#cc3333>MCP-FOR-UNITY</color></b>:")]
public void HandleCommand_Get_ExcludesKnownMcpPrefixes(string prefix)
{
string id = Guid.NewGuid().ToString();
Debug.Log($"{prefix} Transport diagnostic {id}");
var result = ToJObject(ReadConsole.HandleCommand(new JObject
{
["types"] = new JArray { "all" },
["filterText"] = id,
["includeMcpLogs"] = false
}));

Assert.IsTrue(result.Value<bool>("success"), result.ToString());
Assert.AreEqual(0, ((JArray)result["data"]).Count);
}

[Test]
public void HandleCommand_Clear_Works()
{
Expand Down
18 changes: 17 additions & 1 deletion website/docs/reference/tools/core/read_console.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Gets messages from or clears the Unity Editor console. Defaults to 10 most recen
| `cursor` | `int \| str \| None` | — | Opaque cursor for paging (0-based offset). Defaults to 0. |
| `format` | `Literal['plain', 'detailed', 'json'] \| None` | — | Output format |
| `include_stacktrace` | `bool \| str \| None` | — | Include stack traces in output (accepts true/false or 'true'/'false') |
| `include_mcp_logs` | `bool \| str \| None` | — | Include MCP for Unity's own diagnostics (default true). Set false to exclude them before count and paging limits. |

## Returns

Expand All @@ -34,6 +35,21 @@ A `dict` containing the Unity response. The exact shape depends on the action.
## Examples

<!-- examples:start -->
*No examples yet. Add usage examples here — they will be preserved across regenerations.*
Read project warnings and errors without MCP for Unity's transport diagnostics
consuming the result limit:

```json
{"action":"get","types":["error","warning"],"count":10,"include_mcp_logs":false}
```

MCP diagnostics are included by default. Leave `include_mcp_logs` unset or set it
to `true` when troubleshooting the bridge itself. Exclusion happens before count
and paging limits; project messages that merely mention MCP for Unity are retained.

The CLI exposes the same filter:

```bash
unity-mcp editor console --type error --type warning --no-mcp-logs
```
<!-- examples:end -->