diff --git a/Editor/Resources/GetConsoleLogsResource.cs b/Editor/Resources/GetConsoleLogsResource.cs
index 029da3d4..863c50b6 100644
--- a/Editor/Resources/GetConsoleLogsResource.cs
+++ b/Editor/Resources/GetConsoleLogsResource.cs
@@ -1,3 +1,4 @@
+using System;
using Newtonsoft.Json.Linq;
using McpUnity.Services;
@@ -13,38 +14,57 @@ public class GetConsoleLogsResource : McpResourceBase
public GetConsoleLogsResource(IConsoleLogsService consoleLogsService)
{
Name = "get_console_logs";
- Description = "Retrieves logs from the Unity console, optionally filtered by type (error, warning, info)";
+ 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.";
Uri = "unity://logs/{logType}";
_consoleLogsService = consoleLogsService;
}
///
- /// Fetch logs from the Unity console, optionally filtered by type
+ /// Fetch logs from the Unity console, optionally filtered by type with pagination support
///
- /// Resource parameters as a JObject (may include 'logType')
- /// A JObject containing the list of logs
+ /// Resource parameters as a JObject (may include 'logType', 'offset', 'limit')
+ /// A JObject containing the list of logs with pagination info
public override JObject Fetch(JObject parameters)
{
- string logType = null;
- if (parameters != null && parameters.ContainsKey("logType") && parameters["logType"] != null)
- {
- logType = parameters["logType"].ToString()?.ToLowerInvariant();
- if (string.IsNullOrWhiteSpace(logType))
- {
- logType = null;
- }
- }
-
- JArray logsArray = _consoleLogsService.GetAllLogsAsJson(logType);
-
- // Create the response
- return new JObject
- {
- ["success"] = true,
- ["message"] = $"Retrieved {logsArray.Count} log entries" + (logType != null ? $" of type '{logType}'" : ""),
- ["logs"] = logsArray
- };
+ string logType = parameters?["logType"]?.ToString();
+ if (string.IsNullOrWhiteSpace(logType)) logType = null;
+
+ int offset = Math.Max(0, GetIntParameter(parameters, "offset", 0));
+ int limit = Math.Max(1, Math.Min(1000, GetIntParameter(parameters, "limit", 100)));
+
+ // Use the new paginated method
+ JObject result = _consoleLogsService.GetLogsAsJson(logType, offset, limit);
+
+ // Add formatted message with pagination info
+ string typeFilter = logType != null ? $" of type '{logType}'" : "";
+ int returnedCount = result["_returnedCount"]?.Value() ?? 0;
+ int filteredCount = result["_filteredCount"]?.Value() ?? 0;
+ int totalCount = result["_totalCount"]?.Value() ?? 0;
+
+ result["message"] = $"Retrieved {returnedCount} of {filteredCount} log entries{typeFilter} (offset: {offset}, limit: {limit}, total: {totalCount})";
+ result["success"] = true;
+
+ // Remove internal count fields (they're now in the message)
+ result.Remove("_totalCount");
+ result.Remove("_filteredCount");
+ result.Remove("_returnedCount");
+
+ return result;
+ }
+
+ ///
+ /// Helper method to safely extract integer parameters with default values
+ ///
+ /// JObject containing parameters
+ /// Parameter key to extract
+ /// Default value if parameter is missing or invalid
+ /// Extracted integer value or default
+ private static int GetIntParameter(JObject parameters, string key, int defaultValue)
+ {
+ if (parameters?[key] != null && int.TryParse(parameters[key].ToString(), out int value))
+ return value;
+ return defaultValue;
}
diff --git a/Editor/Services/ConsoleLogsService.cs b/Editor/Services/ConsoleLogsService.cs
index b0fac8b0..cbca7503 100644
--- a/Editor/Services/ConsoleLogsService.cs
+++ b/Editor/Services/ConsoleLogsService.cs
@@ -29,6 +29,10 @@ private class LogEntry
public DateTime Timestamp { get; set; }
}
+ // Constants for log management
+ private const int MaxLogEntries = 1000;
+ private const int CleanupThreshold = 200; // Remove oldest entries when exceeding max
+
// Collection to store all log messages
private readonly List _logEntries = new List();
@@ -73,16 +77,21 @@ public void StopListening()
EditorApplication.update -= CheckConsoleClearViaReflection;
#endif
}
-
///
- /// Get all logs as a JSON array
+ /// Get logs as a JSON array with pagination support
///
- /// JArray containing all logs
- public JArray GetAllLogsAsJson(string logType = "")
+ /// Filter by log type (empty for all)
+ /// Starting index (0-based)
+ /// Maximum number of logs to return (default: 100)
+ /// JObject containing logs array and pagination info
+ public JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100)
{
// Convert log entries to a JSON array, filtering by logType if provided
JArray logsArray = new JArray();
bool filter = !string.IsNullOrEmpty(logType);
+ int totalCount = 0;
+ int filteredCount = 0;
+ int currentIndex = 0;
// Map MCP log types to Unity log types outside the loop for better performance
HashSet unityLogTypes = null;
@@ -101,21 +110,46 @@ public JArray GetAllLogsAsJson(string logType = "")
lock (_logEntries)
{
- foreach (var entry in _logEntries)
+ totalCount = _logEntries.Count;
+
+ // Single pass: count filtered entries and collect the requested page (newest first)
+ for (int i = _logEntries.Count - 1; i >= 0; i--)
{
+ var entry = _logEntries[i];
+
+ // Skip if filtering and entry doesn't match the filter
if (filter && !unityLogTypes.Contains(entry.Type.ToString()))
continue;
- logsArray.Add(new JObject
+
+ // Count filtered entries
+ filteredCount++;
+
+ // Check if we're in the offset range and haven't reached the limit yet
+ if (currentIndex >= offset && logsArray.Count < limit)
{
- ["message"] = entry.Message,
- ["stackTrace"] = entry.StackTrace,
- ["type"] = entry.Type.ToString(),
- ["timestamp"] = entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
- });
+ logsArray.Add(new JObject
+ {
+ ["message"] = entry.Message,
+ ["stackTrace"] = entry.StackTrace,
+ ["type"] = entry.Type.ToString(),
+ ["timestamp"] = entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
+ });
+ }
+
+ currentIndex++;
+
+ // Early exit if we've collected enough logs
+ if (currentIndex >= offset + limit) break;
}
}
- return logsArray;
+ return new JObject
+ {
+ ["logs"] = logsArray,
+ ["_totalCount"] = totalCount,
+ ["_filteredCount"] = filteredCount,
+ ["_returnedCount"] = logsArray.Count
+ };
}
///
@@ -129,6 +163,34 @@ private void ClearLogs()
}
}
+ ///
+ /// Manually clean up old log entries, keeping only the most recent ones
+ ///
+ /// Number of recent entries to keep (default: 500)
+ public void CleanupOldLogs(int keepCount = 500)
+ {
+ lock (_logEntries)
+ {
+ if (_logEntries.Count > keepCount)
+ {
+ int removeCount = _logEntries.Count - keepCount;
+ _logEntries.RemoveRange(0, removeCount);
+ }
+ }
+ }
+
+ ///
+ /// Get current log count
+ ///
+ /// Number of stored log entries
+ public int GetLogCount()
+ {
+ lock (_logEntries)
+ {
+ return _logEntries.Count;
+ }
+ }
+
///
/// Check if console was cleared using reflection (for Unity 2022.3)
///
@@ -177,6 +239,12 @@ private void OnLogMessageReceived(string logString, string stackTrace, LogType t
Type = type,
Timestamp = DateTime.Now
});
+
+ // Clean up old entries if we exceed the maximum
+ if (_logEntries.Count > MaxLogEntries)
+ {
+ _logEntries.RemoveRange(0, CleanupThreshold);
+ }
}
}
diff --git a/Editor/Services/IConsoleLogsService.cs b/Editor/Services/IConsoleLogsService.cs
index ac96d50c..1c7795fb 100644
--- a/Editor/Services/IConsoleLogsService.cs
+++ b/Editor/Services/IConsoleLogsService.cs
@@ -11,11 +11,13 @@ namespace McpUnity.Services
public interface IConsoleLogsService
{
///
- /// Get all logs as a JSON array, optionally filtered by log type
+ /// Get logs as a JSON object with pagination support
///
- /// UnityEngine.LogType as string (e.g. "Error", "Warning", "Log"). Empty string for all logs.
- /// JArray containing filtered logs
- JArray GetAllLogsAsJson(string logType = "");
+ /// Filter by log type (empty for all)
+ /// Starting index (0-based)
+ /// Maximum number of logs to return (default: 100)
+ /// JObject containing logs array and pagination info
+ JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100);
///
/// Start listening for logs
@@ -26,5 +28,17 @@ public interface IConsoleLogsService
/// Stop listening for logs
///
void StopListening();
+
+ ///
+ /// Manually clean up old log entries, keeping only the most recent ones
+ ///
+ /// Number of recent entries to keep (default: 500)
+ void CleanupOldLogs(int keepCount = 500);
+
+ ///
+ /// Get current log count
+ ///
+ /// Number of stored log entries
+ int GetLogCount();
}
}
diff --git a/Server~/build/resources/getConsoleLogsResource.js b/Server~/build/resources/getConsoleLogsResource.js
index d8a1e29c..877f807f 100644
--- a/Server~/build/resources/getConsoleLogsResource.js
+++ b/Server~/build/resources/getConsoleLogsResource.js
@@ -3,7 +3,7 @@ import { McpUnityError, ErrorType } from '../utils/errors.js';
// Constants for the resource
const resourceName = 'get_console_logs';
const resourceMimeType = 'application/json';
-const resourceUri = 'unity://logs/{logType}';
+const resourceUri = 'unity://logs/{logType}?offset={offset}&limit={limit}';
const resourceTemplate = new ResourceTemplate(resourceUri, {
list: () => listLogTypes(resourceMimeType)
});
@@ -13,25 +13,25 @@ function listLogTypes(resourceMimeType) {
{
uri: `unity://logs/`,
name: "All logs",
- description: "Retrieve all Unity console logs",
+ 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.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/error`,
name: "Error logs",
- description: "Retrieve only error logs from the Unity console",
+ 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.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/warning`,
name: "Warning logs",
- description: "Retrieve only warning logs from the Unity console",
+ description: "Retrieve only warning logs from Unity console (newest first). Use pagination ?offset=0&limit=30 to manage token usage effectively.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/info`,
name: "Info logs",
- description: "Retrieve only info logs from the Unity console",
+ description: "Retrieve only info logs from Unity console (newest first). Use smaller limits like ?limit=25 to prevent token overflow in LLM responses.",
mimeType: resourceMimeType
}
]
@@ -43,7 +43,7 @@ function listLogTypes(resourceMimeType) {
export function registerGetConsoleLogsResource(server, mcpUnity, logger) {
logger.info(`Registering resource: ${resourceName}`);
server.resource(resourceName, resourceTemplate, {
- description: 'Retrieve Unity console logs by type',
+ 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.',
mimeType: resourceMimeType
}, async (uri, variables) => {
try {
@@ -63,11 +63,16 @@ async function resourceHandler(mcpUnity, uri, variables, logger) {
let logType = variables["logType"] ? decodeURIComponent(variables["logType"]) : undefined;
if (logType === '')
logType = undefined;
+ // Extract pagination parameters
+ const offset = variables["offset"] ? parseInt(variables["offset"], 10) : 0;
+ const limit = variables["limit"] ? parseInt(variables["limit"], 10) : 100;
// Send request to Unity
const response = await mcpUnity.sendRequest({
method: resourceName,
params: {
- logType: logType
+ logType: logType,
+ offset: offset,
+ limit: limit
}
});
if (!response.success) {
@@ -75,7 +80,7 @@ async function resourceHandler(mcpUnity, uri, variables, logger) {
}
return {
contents: [{
- uri: `unity://logs/${logType ?? ''}`,
+ uri: `unity://logs/${logType ?? ''}?offset=${offset}&limit=${limit}`,
mimeType: resourceMimeType,
text: JSON.stringify(response, null, 2)
}]
diff --git a/Server~/build/tools/getConsoleLogsTool.js b/Server~/build/tools/getConsoleLogsTool.js
index 7cff61fc..d4f3a01a 100644
--- a/Server~/build/tools/getConsoleLogsTool.js
+++ b/Server~/build/tools/getConsoleLogsTool.js
@@ -2,12 +2,25 @@ import * as z from "zod";
import { McpUnityError, ErrorType } from "../utils/errors.js";
// Constants for the tool
const toolName = "get_console_logs";
-const toolDescription = "Retrieves logs from the Unity console";
+const toolDescription = "Retrieves logs from the Unity console with pagination support to avoid token limits";
const paramsSchema = z.object({
logType: z
.enum(["info", "warning", "error"])
.optional()
.describe("The type of logs to retrieve (info, warning, error) - defaults to all logs if not specified"),
+ offset: z
+ .number()
+ .int()
+ .min(0)
+ .optional()
+ .describe("Starting index for pagination (0-based, defaults to 0)"),
+ limit: z
+ .number()
+ .int()
+ .min(1)
+ .max(500)
+ .optional()
+ .describe("Maximum number of logs to return (defaults to 50, max 500 to avoid token limits)")
});
/**
* Creates and registers the Get Console Logs tool with the MCP server
@@ -42,13 +55,15 @@ export function registerGetConsoleLogsTool(server, mcpUnity, logger) {
* @throws McpUnityError if the request to Unity fails
*/
async function toolHandler(mcpUnity, params) {
- const { logType } = params;
+ const { logType, offset = 0, limit = 50 } = params;
// Send request to Unity using the same method name as the resource
// This allows reusing the existing Unity-side implementation
const response = await mcpUnity.sendRequest({
method: "get_console_logs",
params: {
logType: logType,
+ offset: offset,
+ limit: limit,
},
});
if (!response.success) {
diff --git a/Server~/src/resources/getConsoleLogsResource.ts b/Server~/src/resources/getConsoleLogsResource.ts
index 78ed84e3..f804c5b2 100644
--- a/Server~/src/resources/getConsoleLogsResource.ts
+++ b/Server~/src/resources/getConsoleLogsResource.ts
@@ -8,7 +8,7 @@ import { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js';
// Constants for the resource
const resourceName = 'get_console_logs';
const resourceMimeType = 'application/json';
-const resourceUri = 'unity://logs/{logType}';
+const resourceUri = 'unity://logs/{logType}?offset={offset}&limit={limit}';
const resourceTemplate = new ResourceTemplate(resourceUri, {
list: () => listLogTypes(resourceMimeType)
});
@@ -17,27 +17,27 @@ function listLogTypes(resourceMimeType: string) {
return {
resources: [
{
- uri: `unity://logs/`,
+ uri: `unity://logs/?offset=0&limit=50`,
name: "All logs",
- description: "Retrieve all Unity console logs",
+ description: "Retrieve Unity console logs (newest first). Default pagination offset=0&limit=50 to avoid token limits.",
mimeType: resourceMimeType
},
{
- uri: `unity://logs/error`,
+ uri: `unity://logs/error?offset=0&limit=20`,
name: "Error logs",
- description: "Retrieve only error logs from the Unity console",
+ description: "Retrieve only error logs from Unity console (newest first). Default pagination offset=0&limit=20.",
mimeType: resourceMimeType
},
{
- uri: `unity://logs/warning`,
- name: "Warning logs",
- description: "Retrieve only warning logs from the Unity console",
+ uri: `unity://logs/warning?offset=0&limit=30`,
+ name: "Warning logs",
+ description: "Retrieve only warning logs from Unity console (newest first). Default pagination offset=0&limit=30.",
mimeType: resourceMimeType
},
{
- uri: `unity://logs/info`,
+ uri: `unity://logs/info?offset=0&limit=25`,
name: "Info logs",
- description: "Retrieve only info logs from the Unity console",
+ description: "Retrieve only info logs from Unity console (newest first). Default pagination offset=0&limit=25.",
mimeType: resourceMimeType
}
]
@@ -54,7 +54,7 @@ export function registerGetConsoleLogsResource(server: McpServer, mcpUnity: McpU
resourceName,
resourceTemplate,
{
- description: 'Retrieve Unity console logs by type',
+ 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.',
mimeType: resourceMimeType
},
async (uri, variables) => {
@@ -75,12 +75,26 @@ async function resourceHandler(mcpUnity: McpUnity, uri: URL, variables: Variable
// Extract and convert the parameter from the template variables
let logType = variables["logType"] ? decodeURIComponent(variables["logType"] as string) : undefined;
if (logType === '') logType = undefined;
+
+ // Extract pagination parameters with validation
+ const offset = variables["offset"] ? parseInt(variables["offset"] as string, 10) : 0;
+ const limit = variables["limit"] ? parseInt(variables["limit"] as string, 10) : 100;
+
+ // Validate pagination parameters
+ if (isNaN(offset) || offset < 0) {
+ throw new McpUnityError(ErrorType.VALIDATION, 'Invalid offset parameter: must be a non-negative integer');
+ }
+ if (isNaN(limit) || limit <= 0) {
+ throw new McpUnityError(ErrorType.VALIDATION, 'Invalid limit parameter: must be a positive integer');
+ }
// Send request to Unity
const response = await mcpUnity.sendRequest({
method: resourceName,
params: {
- logType: logType
+ logType: logType,
+ offset: offset,
+ limit: limit
}
});
@@ -93,7 +107,7 @@ async function resourceHandler(mcpUnity: McpUnity, uri: URL, variables: Variable
return {
contents: [{
- uri: `unity://logs/${logType ?? ''}`,
+ uri: `unity://logs/${logType ?? ''}?offset=${offset}&limit=${limit}`,
mimeType: resourceMimeType,
text: JSON.stringify(response, null, 2)
}]
diff --git a/Server~/src/tools/getConsoleLogsTool.ts b/Server~/src/tools/getConsoleLogsTool.ts
index a521aed4..d50c5e47 100644
--- a/Server~/src/tools/getConsoleLogsTool.ts
+++ b/Server~/src/tools/getConsoleLogsTool.ts
@@ -7,7 +7,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
// Constants for the tool
const toolName = "get_console_logs";
-const toolDescription = "Retrieves logs from the Unity console";
+const toolDescription = "Retrieves logs from the Unity console with pagination support to avoid token limits";
const paramsSchema = z.object({
logType: z
.enum(["info", "warning", "error"])
@@ -15,6 +15,19 @@ const paramsSchema = z.object({
.describe(
"The type of logs to retrieve (info, warning, error) - defaults to all logs if not specified"
),
+ offset: z
+ .number()
+ .int()
+ .min(0)
+ .optional()
+ .describe("Starting index for pagination (0-based, defaults to 0)"),
+ limit: z
+ .number()
+ .int()
+ .min(1)
+ .max(500)
+ .optional()
+ .describe("Maximum number of logs to return (defaults to 50, max 500 to avoid token limits)")
});
/**
@@ -63,7 +76,7 @@ async function toolHandler(
mcpUnity: McpUnity,
params: z.infer
): Promise {
- const { logType } = params;
+ const { logType, offset = 0, limit = 50 } = params;
// Send request to Unity using the same method name as the resource
// This allows reusing the existing Unity-side implementation
@@ -71,6 +84,8 @@ async function toolHandler(
method: "get_console_logs",
params: {
logType: logType,
+ offset: offset,
+ limit: limit,
},
});