Skip to content

feat: Add includeStackTrace option to reduce LLM token usage by 80-90% #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
25 changes: 21 additions & 4 deletions Editor/Resources/GetConsoleLogsResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class GetConsoleLogsResource : McpResourceBase
public GetConsoleLogsResource(IConsoleLogsService consoleLogsService)
{
Name = "get_console_logs";
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.";
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. Set includeStackTrace=false to exclude stack traces and reduce token usage. Recommended: limit=20-50 for optimal performance.";
Uri = "unity://logs/{logType}";

_consoleLogsService = consoleLogsService;
Expand All @@ -32,17 +32,20 @@ public override JObject Fetch(JObject parameters)

int offset = Math.Max(0, GetIntParameter(parameters, "offset", 0));
int limit = Math.Max(1, Math.Min(1000, GetIntParameter(parameters, "limit", 100)));
bool includeStackTrace = GetBoolParameter(parameters, "includeStackTrace", true);

// Debug logging - temporarily remove to avoid console clutter

// Use the new paginated method
JObject result = _consoleLogsService.GetLogsAsJson(logType, offset, limit);
// Use the new paginated method with stack trace option
JObject result = _consoleLogsService.GetLogsAsJson(logType, offset, limit, includeStackTrace);

// Add formatted message with pagination info
string typeFilter = logType != null ? $" of type '{logType}'" : "";
int returnedCount = result["_returnedCount"]?.Value<int>() ?? 0;
int filteredCount = result["_filteredCount"]?.Value<int>() ?? 0;
int totalCount = result["_totalCount"]?.Value<int>() ?? 0;

result["message"] = $"Retrieved {returnedCount} of {filteredCount} log entries{typeFilter} (offset: {offset}, limit: {limit}, total: {totalCount})";
result["message"] = $"Retrieved {returnedCount} of {filteredCount} log entries{typeFilter} (offset: {offset}, limit: {limit}, includeStackTrace: {includeStackTrace}, total: {totalCount})";
result["success"] = true;

// Remove internal count fields (they're now in the message)
Expand All @@ -67,6 +70,20 @@ private static int GetIntParameter(JObject parameters, string key, int defaultVa
return defaultValue;
}

/// <summary>
/// Helper method to safely extract boolean parameters with default values
/// </summary>
/// <param name="parameters">JObject containing parameters</param>
/// <param name="key">Parameter key to extract</param>
/// <param name="defaultValue">Default value if parameter is missing or invalid</param>
/// <returns>Extracted boolean value or default</returns>
private static bool GetBoolParameter(JObject parameters, string key, bool defaultValue)
{
if (parameters?[key] != null && bool.TryParse(parameters[key].ToString(), out bool value))
return value;
return defaultValue;
}


}
}
16 changes: 12 additions & 4 deletions Editor/Services/ConsoleLogsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ public void StopListening()
/// <param name="logType">Filter by log type (empty for all)</param>
/// <param name="offset">Starting index (0-based)</param>
/// <param name="limit">Maximum number of logs to return (default: 100)</param>
/// <param name="includeStackTrace">Whether to include stack trace in logs (default: true)</param>
/// <returns>JObject containing logs array and pagination info</returns>
public JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100)
public JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100, bool includeStackTrace = true)
{
// Convert log entries to a JSON array, filtering by logType if provided
JArray logsArray = new JArray();
Expand Down Expand Up @@ -127,13 +128,20 @@ public JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 10
// Check if we're in the offset range and haven't reached the limit yet
if (currentIndex >= offset && logsArray.Count < limit)
{
logsArray.Add(new JObject
var logObject = new JObject
{
["message"] = entry.Message,
["stackTrace"] = entry.StackTrace,
["type"] = entry.Type.ToString(),
["timestamp"] = entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
});
};

// Only include stack trace if requested
if (includeStackTrace)
{
logObject["stackTrace"] = entry.StackTrace;
}

logsArray.Add(logObject);
}

currentIndex++;
Expand Down
3 changes: 2 additions & 1 deletion Editor/Services/IConsoleLogsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ public interface IConsoleLogsService
/// <param name="logType">Filter by log type (empty for all)</param>
/// <param name="offset">Starting index (0-based)</param>
/// <param name="limit">Maximum number of logs to return (default: 100)</param>
/// <param name="includeStackTrace">Whether to include stack trace in logs (default: true)</param>
/// <returns>JObject containing logs array and pagination info</returns>
JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100);
JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100, bool includeStackTrace = true);

/// <summary>
/// Start listening for logs
Expand Down
40 changes: 27 additions & 13 deletions Server~/build/resources/getConsoleLogsResource.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,35 @@ 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}?offset={offset}&limit={limit}';
const resourceUri = 'unity://logs/{logType}?offset={offset}&limit={limit}&includeStackTrace={includeStackTrace}';
const resourceTemplate = new ResourceTemplate(resourceUri, {
list: () => listLogTypes(resourceMimeType)
});
function listLogTypes(resourceMimeType) {
return {
resources: [
{
uri: `unity://logs/`,
uri: `unity://logs/?offset=0&limit=50&includeStackTrace=true`,
name: "All 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.",
description: "All Unity console logs (newest first). ⚠️ Set includeStackTrace=false to save 80-90% tokens. Use limit=50 to avoid token limits.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/error`,
uri: `unity://logs/error?offset=0&limit=20&includeStackTrace=true`,
name: "Error logs",
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.",
description: "Error logs only. ⚠️ Start with includeStackTrace=false for quick overview, then true only if debugging specific errors.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/warning`,
uri: `unity://logs/warning?offset=0&limit=30&includeStackTrace=true`,
name: "Warning logs",
description: "Retrieve only warning logs from Unity console (newest first). Use pagination ?offset=0&limit=30 to manage token usage effectively.",
description: "Warning logs only. ⚠️ Use includeStackTrace=false by default to save tokens.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/info`,
uri: `unity://logs/info?offset=0&limit=25&includeStackTrace=false`,
name: "Info logs",
description: "Retrieve only info logs from Unity console (newest first). Use smaller limits like ?limit=25 to prevent token overflow in LLM responses.",
description: "Info logs only. Stack traces excluded by default to minimize tokens.",
mimeType: resourceMimeType
}
]
Expand All @@ -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 (newest first). IMPORTANT: Use pagination parameters ?offset=0&limit=50 to avoid LLM token limits. Default limit=100 may exceed context window.',
description: 'Retrieve Unity console logs by type with pagination support. See individual log type descriptions for optimal settings.',
mimeType: resourceMimeType
}, async (uri, variables) => {
try {
Expand All @@ -63,24 +63,38 @@ async function resourceHandler(mcpUnity, uri, variables, logger) {
let logType = variables["logType"] ? decodeURIComponent(variables["logType"]) : undefined;
if (logType === '')
logType = undefined;
// Extract pagination parameters
// Extract pagination parameters with validation
const offset = variables["offset"] ? parseInt(variables["offset"], 10) : 0;
const limit = variables["limit"] ? parseInt(variables["limit"], 10) : 100;
// Extract includeStackTrace parameter
let includeStackTrace = true; // Default to true for backward compatibility
if (variables["includeStackTrace"] !== undefined) {
const value = variables["includeStackTrace"];
includeStackTrace = value === 'true' || value === '1' || value === 'yes';
}
// 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,
offset: offset,
limit: limit
limit: limit,
includeStackTrace: includeStackTrace
}
});
if (!response.success) {
throw new McpUnityError(ErrorType.RESOURCE_FETCH, response.message || 'Failed to fetch logs from Unity');
}
return {
contents: [{
uri: `unity://logs/${logType ?? ''}?offset=${offset}&limit=${limit}`,
uri: `unity://logs/${logType ?? ''}?offset=${offset}&limit=${limit}&includeStackTrace=${includeStackTrace}`,
mimeType: resourceMimeType,
text: JSON.stringify(response, null, 2)
}]
Expand Down
9 changes: 7 additions & 2 deletions Server~/build/tools/getConsoleLogsTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ const paramsSchema = z.object({
.min(1)
.max(500)
.optional()
.describe("Maximum number of logs to return (defaults to 50, max 500 to avoid token limits)")
.describe("Maximum number of logs to return (defaults to 50, max 500 to avoid token limits)"),
includeStackTrace: z
.boolean()
.optional()
.describe("Whether to include stack trace in logs. ⚠️ ALWAYS SET TO FALSE to save 80-90% tokens, unless you specifically need stack traces for debugging. Default: true (except info logs in resource)")
});
/**
* Creates and registers the Get Console Logs tool with the MCP server
Expand Down Expand Up @@ -55,7 +59,7 @@ export function registerGetConsoleLogsTool(server, mcpUnity, logger) {
* @throws McpUnityError if the request to Unity fails
*/
async function toolHandler(mcpUnity, params) {
const { logType, offset = 0, limit = 50 } = params;
const { logType, offset = 0, limit = 50, includeStackTrace = true } = 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({
Expand All @@ -64,6 +68,7 @@ async function toolHandler(mcpUnity, params) {
logType: logType,
offset: offset,
limit: limit,
includeStackTrace: includeStackTrace,
},
});
if (!response.success) {
Expand Down
32 changes: 20 additions & 12 deletions Server~/src/resources/getConsoleLogsResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}?offset={offset}&limit={limit}';
const resourceUri = 'unity://logs/{logType}?offset={offset}&limit={limit}&includeStackTrace={includeStackTrace}';
const resourceTemplate = new ResourceTemplate(resourceUri, {
list: () => listLogTypes(resourceMimeType)
});
Expand All @@ -17,27 +17,27 @@ function listLogTypes(resourceMimeType: string) {
return {
resources: [
{
uri: `unity://logs/?offset=0&limit=50`,
uri: `unity://logs/?offset=0&limit=50&includeStackTrace=true`,
name: "All logs",
description: "Retrieve Unity console logs (newest first). Default pagination offset=0&limit=50 to avoid token limits.",
description: "All Unity console logs (newest first). ⚠️ Set includeStackTrace=false to save 80-90% tokens. Use limit=50 to avoid token limits.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/error?offset=0&limit=20`,
uri: `unity://logs/error?offset=0&limit=20&includeStackTrace=true`,
name: "Error logs",
description: "Retrieve only error logs from Unity console (newest first). Default pagination offset=0&limit=20.",
description: "Error logs only. ⚠️ Start with includeStackTrace=false for quick overview, then true only if debugging specific errors.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/warning?offset=0&limit=30`,
uri: `unity://logs/warning?offset=0&limit=30&includeStackTrace=true`,
name: "Warning logs",
description: "Retrieve only warning logs from Unity console (newest first). Default pagination offset=0&limit=30.",
description: "Warning logs only. ⚠️ Use includeStackTrace=false by default to save tokens.",
mimeType: resourceMimeType
},
{
uri: `unity://logs/info?offset=0&limit=25`,
uri: `unity://logs/info?offset=0&limit=25&includeStackTrace=false`,
name: "Info logs",
description: "Retrieve only info logs from Unity console (newest first). Default pagination offset=0&limit=25.",
description: "Info logs only. Stack traces excluded by default to minimize tokens.",
mimeType: resourceMimeType
}
]
Expand All @@ -54,7 +54,7 @@ export function registerGetConsoleLogsResource(server: McpServer, mcpUnity: McpU
resourceName,
resourceTemplate,
{
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.',
description: 'Retrieve Unity console logs by type with pagination support. See individual log type descriptions for optimal settings.',
mimeType: resourceMimeType
},
async (uri, variables) => {
Expand All @@ -80,6 +80,13 @@ async function resourceHandler(mcpUnity: McpUnity, uri: URL, variables: Variable
const offset = variables["offset"] ? parseInt(variables["offset"] as string, 10) : 0;
const limit = variables["limit"] ? parseInt(variables["limit"] as string, 10) : 100;

// Extract includeStackTrace parameter
let includeStackTrace = true; // Default to true for backward compatibility
if (variables["includeStackTrace"] !== undefined) {
const value = variables["includeStackTrace"] as string;
includeStackTrace = value === 'true' || value === '1' || value === 'yes';
}

// Validate pagination parameters
if (isNaN(offset) || offset < 0) {
throw new McpUnityError(ErrorType.VALIDATION, 'Invalid offset parameter: must be a non-negative integer');
Expand All @@ -94,7 +101,8 @@ async function resourceHandler(mcpUnity: McpUnity, uri: URL, variables: Variable
params: {
logType: logType,
offset: offset,
limit: limit
limit: limit,
includeStackTrace: includeStackTrace
}
});

Expand All @@ -107,7 +115,7 @@ async function resourceHandler(mcpUnity: McpUnity, uri: URL, variables: Variable

return {
contents: [{
uri: `unity://logs/${logType ?? ''}?offset=${offset}&limit=${limit}`,
uri: `unity://logs/${logType ?? ''}?offset=${offset}&limit=${limit}&includeStackTrace=${includeStackTrace}`,
mimeType: resourceMimeType,
text: JSON.stringify(response, null, 2)
}]
Expand Down
9 changes: 7 additions & 2 deletions Server~/src/tools/getConsoleLogsTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ const paramsSchema = z.object({
.min(1)
.max(500)
.optional()
.describe("Maximum number of logs to return (defaults to 50, max 500 to avoid token limits)")
.describe("Maximum number of logs to return (defaults to 50, max 500 to avoid token limits)"),
includeStackTrace: z
.boolean()
.optional()
.describe("Whether to include stack trace in logs. ⚠️ ALWAYS SET TO FALSE to save 80-90% tokens, unless you specifically need stack traces for debugging. Default: true (except info logs in resource)")
});

/**
Expand Down Expand Up @@ -76,7 +80,7 @@ async function toolHandler(
mcpUnity: McpUnity,
params: z.infer<typeof paramsSchema>
): Promise<CallToolResult> {
const { logType, offset = 0, limit = 50 } = params;
const { logType, offset = 0, limit = 50, includeStackTrace = true } = params;

// Send request to Unity using the same method name as the resource
// This allows reusing the existing Unity-side implementation
Expand All @@ -86,6 +90,7 @@ async function toolHandler(
logType: logType,
offset: offset,
limit: limit,
includeStackTrace: includeStackTrace,
},
});

Expand Down