Skip to content

Commit 925431d

Browse files
Saqooshaclaude
andcommitted
feat: Add pagination support to console logs to prevent LLM token limits
- Add offset and limit parameters to GetConsoleLogsResource and GetConsoleLogsTool - Implement GetLogsAsJson method with pagination in ConsoleLogsService - Return logs in newest-first order for better debugging workflow - Add automatic memory management with configurable cleanup thresholds - Update resource descriptions with pagination usage recommendations - Enhance Unity-side and Node.js-side implementations consistently 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cb4b03c commit 925431d

File tree

7 files changed

+211
-44
lines changed

7 files changed

+211
-44
lines changed

Editor/Resources/GetConsoleLogsResource.cs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,58 @@ public class GetConsoleLogsResource : McpResourceBase
1313
public GetConsoleLogsResource(IConsoleLogsService consoleLogsService)
1414
{
1515
Name = "get_console_logs";
16-
Description = "Retrieves logs from the Unity console, optionally filtered by type (error, warning, info)";
16+
Description = "Retrieves logs from the Unity console (newest first), optionally filtered by type (error, warning, info). Use pagination parameters (offset, limit) to avoid LLM token limits. Recommended: limit=20-50 for optimal performance.";
1717
Uri = "unity://logs/{logType}";
1818

1919
_consoleLogsService = consoleLogsService;
2020
}
2121

2222
/// <summary>
23-
/// Fetch logs from the Unity console, optionally filtered by type
23+
/// Fetch logs from the Unity console, optionally filtered by type with pagination support
2424
/// </summary>
25-
/// <param name="parameters">Resource parameters as a JObject (may include 'logType')</param>
26-
/// <returns>A JObject containing the list of logs</returns>
25+
/// <param name="parameters">Resource parameters as a JObject (may include 'logType', 'offset', 'limit')</param>
26+
/// <returns>A JObject containing the list of logs with pagination info</returns>
2727
public override JObject Fetch(JObject parameters)
2828
{
2929
string logType = null;
30-
if (parameters != null && parameters.ContainsKey("logType") && parameters["logType"] != null)
30+
int offset = 0;
31+
int limit = 100;
32+
33+
if (parameters != null)
3134
{
32-
logType = parameters["logType"].ToString()?.ToLowerInvariant();
33-
if (string.IsNullOrWhiteSpace(logType))
35+
// Extract logType
36+
if (parameters.ContainsKey("logType") && parameters["logType"] != null)
37+
{
38+
logType = parameters["logType"].ToString()?.ToLowerInvariant();
39+
if (string.IsNullOrWhiteSpace(logType))
40+
{
41+
logType = null;
42+
}
43+
}
44+
45+
// Extract pagination parameters
46+
if (parameters.ContainsKey("offset") && parameters["offset"] != null)
47+
{
48+
int.TryParse(parameters["offset"].ToString(), out offset);
49+
}
50+
51+
if (parameters.ContainsKey("limit") && parameters["limit"] != null)
3452
{
35-
logType = null;
53+
int.TryParse(parameters["limit"].ToString(), out limit);
3654
}
3755
}
3856

39-
JArray logsArray = _consoleLogsService.GetAllLogsAsJson(logType);
57+
// Use the new paginated method
58+
JObject result = _consoleLogsService.GetLogsAsJson(logType, offset, limit);
59+
60+
// Add success info to the response
61+
result["success"] = true;
62+
63+
var pagination = result["pagination"] as JObject;
64+
string typeFilter = logType != null ? $" of type '{logType}'" : "";
65+
result["message"] = $"Retrieved {pagination["returnedCount"]} of {pagination["filteredCount"]} log entries{typeFilter} (offset: {offset}, limit: {limit})";
4066

41-
// Create the response
42-
return new JObject
43-
{
44-
["success"] = true,
45-
["message"] = $"Retrieved {logsArray.Count} log entries" + (logType != null ? $" of type '{logType}'" : ""),
46-
["logs"] = logsArray
47-
};
67+
return result;
4868
}
4969

5070

Editor/Services/ConsoleLogsService.cs

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ private class LogEntry
2121
public DateTime Timestamp { get; set; }
2222
}
2323

24+
// Constants for log management
25+
private const int MaxLogEntries = 1000;
26+
private const int CleanupThreshold = 200; // Remove oldest entries when exceeding max
27+
2428
// Collection to store all log messages
2529
private readonly List<LogEntry> _logEntries = new List<LogEntry>();
2630

@@ -71,28 +75,75 @@ public void StopListening()
7175
/// </summary>
7276
/// <returns>JArray containing all logs</returns>
7377
public JArray GetAllLogsAsJson(string logType = "")
78+
{
79+
var result = GetLogsAsJson(logType, 0, int.MaxValue);
80+
return result["logs"] as JArray;
81+
}
82+
83+
/// <summary>
84+
/// Get logs as a JSON array with pagination support
85+
/// </summary>
86+
/// <param name="logType">Filter by log type (empty for all)</param>
87+
/// <param name="offset">Starting index (0-based)</param>
88+
/// <param name="limit">Maximum number of logs to return (default: 100)</param>
89+
/// <returns>JObject containing logs array and pagination info</returns>
90+
public JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100)
7491
{
7592
// Convert log entries to a JSON array, filtering by logType if provided
7693
JArray logsArray = new JArray();
7794
bool filter = !string.IsNullOrEmpty(logType);
95+
int totalCount = 0;
96+
int filteredCount = 0;
97+
int currentIndex = 0;
7898

7999
lock (_logEntries)
80100
{
101+
// First pass: count total and filtered entries
81102
foreach (var entry in _logEntries)
82103
{
104+
totalCount++;
105+
if (!filter || entry.Type.ToString().Equals(logType, System.StringComparison.OrdinalIgnoreCase))
106+
{
107+
filteredCount++;
108+
}
109+
}
110+
111+
// Second pass: collect the requested page (newest first)
112+
for (int i = _logEntries.Count - 1; i >= 0; i--)
113+
{
114+
var entry = _logEntries[i];
83115
if (filter && !entry.Type.ToString().Equals(logType, System.StringComparison.OrdinalIgnoreCase))
84116
continue;
85-
logsArray.Add(new JObject
117+
118+
if (currentIndex >= offset && logsArray.Count < limit)
86119
{
87-
["message"] = entry.Message,
88-
["stackTrace"] = entry.StackTrace,
89-
["type"] = entry.Type.ToString(),
90-
["timestamp"] = entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
91-
});
120+
logsArray.Add(new JObject
121+
{
122+
["message"] = entry.Message,
123+
["stackTrace"] = entry.StackTrace,
124+
["type"] = entry.Type.ToString(),
125+
["timestamp"] = entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
126+
});
127+
}
128+
129+
currentIndex++;
130+
if (logsArray.Count >= limit) break;
92131
}
93132
}
94133

95-
return logsArray;
134+
return new JObject
135+
{
136+
["logs"] = logsArray,
137+
["pagination"] = new JObject
138+
{
139+
["offset"] = offset,
140+
["limit"] = limit,
141+
["totalCount"] = totalCount,
142+
["filteredCount"] = filteredCount,
143+
["returnedCount"] = logsArray.Count,
144+
["hasMore"] = offset + logsArray.Count < filteredCount
145+
}
146+
};
96147
}
97148

98149
/// <summary>
@@ -106,6 +157,34 @@ private void ClearLogs()
106157
}
107158
}
108159

160+
/// <summary>
161+
/// Manually clean up old log entries, keeping only the most recent ones
162+
/// </summary>
163+
/// <param name="keepCount">Number of recent entries to keep (default: 500)</param>
164+
public void CleanupOldLogs(int keepCount = 500)
165+
{
166+
lock (_logEntries)
167+
{
168+
if (_logEntries.Count > keepCount)
169+
{
170+
int removeCount = _logEntries.Count - keepCount;
171+
_logEntries.RemoveRange(0, removeCount);
172+
}
173+
}
174+
}
175+
176+
/// <summary>
177+
/// Get current log count
178+
/// </summary>
179+
/// <returns>Number of stored log entries</returns>
180+
public int GetLogCount()
181+
{
182+
lock (_logEntries)
183+
{
184+
return _logEntries.Count;
185+
}
186+
}
187+
109188
/// <summary>
110189
/// Check if console was cleared using reflection (for Unity 2022.3)
111190
/// </summary>
@@ -154,6 +233,12 @@ private void OnLogMessageReceived(string logString, string stackTrace, LogType t
154233
Type = type,
155234
Timestamp = DateTime.Now
156235
});
236+
237+
// Clean up old entries if we exceed the maximum
238+
if (_logEntries.Count > MaxLogEntries)
239+
{
240+
_logEntries.RemoveRange(0, CleanupThreshold);
241+
}
157242
}
158243
}
159244

Editor/Services/IConsoleLogsService.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ public interface IConsoleLogsService
1717
/// <returns>JArray containing filtered logs</returns>
1818
JArray GetAllLogsAsJson(string logType = "");
1919

20+
/// <summary>
21+
/// Get logs as a JSON object with pagination support
22+
/// </summary>
23+
/// <param name="logType">Filter by log type (empty for all)</param>
24+
/// <param name="offset">Starting index (0-based)</param>
25+
/// <param name="limit">Maximum number of logs to return (default: 100)</param>
26+
/// <returns>JObject containing logs array and pagination info</returns>
27+
JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100);
28+
2029
/// <summary>
2130
/// Start listening for logs
2231
/// </summary>
@@ -26,5 +35,17 @@ public interface IConsoleLogsService
2635
/// Stop listening for logs
2736
/// </summary>
2837
void StopListening();
38+
39+
/// <summary>
40+
/// Manually clean up old log entries, keeping only the most recent ones
41+
/// </summary>
42+
/// <param name="keepCount">Number of recent entries to keep (default: 500)</param>
43+
void CleanupOldLogs(int keepCount = 500);
44+
45+
/// <summary>
46+
/// Get current log count
47+
/// </summary>
48+
/// <returns>Number of stored log entries</returns>
49+
int GetLogCount();
2950
}
3051
}

Server~/build/resources/getConsoleLogsResource.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { McpUnityError, ErrorType } from '../utils/errors.js';
33
// Constants for the resource
44
const resourceName = 'get_console_logs';
55
const resourceMimeType = 'application/json';
6-
const resourceUri = 'unity://logs/{logType}';
6+
const resourceUri = 'unity://logs/{logType}?offset={offset}&limit={limit}';
77
const resourceTemplate = new ResourceTemplate(resourceUri, {
88
list: () => listLogTypes(resourceMimeType)
99
});
@@ -13,25 +13,25 @@ function listLogTypes(resourceMimeType) {
1313
{
1414
uri: `unity://logs/`,
1515
name: "All logs",
16-
description: "Retrieve all Unity console logs",
16+
description: "Retrieve Unity console logs (newest first). Use pagination to avoid token limits: ?offset=0&limit=50 for recent logs. Default limit=100 may be too large for LLM context.",
1717
mimeType: resourceMimeType
1818
},
1919
{
2020
uri: `unity://logs/error`,
2121
name: "Error logs",
22-
description: "Retrieve only error logs from the Unity console",
22+
description: "Retrieve only error logs from Unity console (newest first). Use ?offset=0&limit=20 to avoid token limits. Large log sets may exceed LLM context window.",
2323
mimeType: resourceMimeType
2424
},
2525
{
2626
uri: `unity://logs/warning`,
2727
name: "Warning logs",
28-
description: "Retrieve only warning logs from the Unity console",
28+
description: "Retrieve only warning logs from Unity console (newest first). Use pagination ?offset=0&limit=30 to manage token usage effectively.",
2929
mimeType: resourceMimeType
3030
},
3131
{
3232
uri: `unity://logs/info`,
3333
name: "Info logs",
34-
description: "Retrieve only info logs from the Unity console",
34+
description: "Retrieve only info logs from Unity console (newest first). Use smaller limits like ?limit=25 to prevent token overflow in LLM responses.",
3535
mimeType: resourceMimeType
3636
}
3737
]
@@ -43,7 +43,7 @@ function listLogTypes(resourceMimeType) {
4343
export function registerGetConsoleLogsResource(server, mcpUnity, logger) {
4444
logger.info(`Registering resource: ${resourceName}`);
4545
server.resource(resourceName, resourceTemplate, {
46-
description: 'Retrieve Unity console logs by type',
46+
description: 'Retrieve Unity console logs by type (newest first). IMPORTANT: Use pagination parameters ?offset=0&limit=50 to avoid LLM token limits. Default limit=100 may exceed context window.',
4747
mimeType: resourceMimeType
4848
}, async (uri, variables) => {
4949
try {
@@ -63,19 +63,24 @@ async function resourceHandler(mcpUnity, uri, variables, logger) {
6363
let logType = variables["logType"] ? decodeURIComponent(variables["logType"]) : undefined;
6464
if (logType === '')
6565
logType = undefined;
66+
// Extract pagination parameters
67+
const offset = variables["offset"] ? parseInt(variables["offset"], 10) : 0;
68+
const limit = variables["limit"] ? parseInt(variables["limit"], 10) : 100;
6669
// Send request to Unity
6770
const response = await mcpUnity.sendRequest({
6871
method: resourceName,
6972
params: {
70-
logType: logType
73+
logType: logType,
74+
offset: offset,
75+
limit: limit
7176
}
7277
});
7378
if (!response.success) {
7479
throw new McpUnityError(ErrorType.RESOURCE_FETCH, response.message || 'Failed to fetch logs from Unity');
7580
}
7681
return {
7782
contents: [{
78-
uri: `unity://logs/${logType ?? ''}`,
83+
uri: `unity://logs/${logType ?? ''}?offset=${offset}&limit=${limit}`,
7984
mimeType: resourceMimeType,
8085
text: JSON.stringify(response, null, 2)
8186
}]

Server~/build/tools/getConsoleLogsTool.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,25 @@ import * as z from "zod";
22
import { McpUnityError, ErrorType } from "../utils/errors.js";
33
// Constants for the tool
44
const toolName = "get_console_logs";
5-
const toolDescription = "Retrieves logs from the Unity console";
5+
const toolDescription = "Retrieves logs from the Unity console with pagination support to avoid token limits";
66
const paramsSchema = z.object({
77
logType: z
88
.enum(["info", "warning", "error"])
99
.optional()
1010
.describe("The type of logs to retrieve (info, warning, error) - defaults to all logs if not specified"),
11+
offset: z
12+
.number()
13+
.int()
14+
.min(0)
15+
.optional()
16+
.describe("Starting index for pagination (0-based, defaults to 0)"),
17+
limit: z
18+
.number()
19+
.int()
20+
.min(1)
21+
.max(500)
22+
.optional()
23+
.describe("Maximum number of logs to return (defaults to 50, max 500 to avoid token limits)")
1124
});
1225
/**
1326
* Creates and registers the Get Console Logs tool with the MCP server
@@ -42,13 +55,15 @@ export function registerGetConsoleLogsTool(server, mcpUnity, logger) {
4255
* @throws McpUnityError if the request to Unity fails
4356
*/
4457
async function toolHandler(mcpUnity, params) {
45-
const { logType } = params;
58+
const { logType, offset = 0, limit = 50 } = params;
4659
// Send request to Unity using the same method name as the resource
4760
// This allows reusing the existing Unity-side implementation
4861
const response = await mcpUnity.sendRequest({
4962
method: "get_console_logs",
5063
params: {
5164
logType: logType,
65+
offset: offset,
66+
limit: limit,
5267
},
5368
});
5469
if (!response.success) {

0 commit comments

Comments
 (0)