unity-editor-mcp
Version:
MCP server for Unity Editor integration - enables AI assistants to control Unity Editor
252 lines (229 loc) • 7.09 kB
JavaScript
import { BaseToolHandler } from '../base/BaseToolHandler.js';
/**
* Handler for reading Unity Editor console logs with advanced filtering
*/
export class EnhancedReadLogsToolHandler extends BaseToolHandler {
constructor(unityConnection) {
super(
'enhanced_read_logs',
'Read Unity console logs with advanced filtering',
{
type: 'object',
properties: {
count: {
type: 'number',
description: 'Number of logs to retrieve (1-1000, default: 100)',
minimum: 1,
maximum: 1000,
default: 100
},
logTypes: {
type: 'array',
description: 'Filter by log types (default: ["All"])',
items: {
type: 'string',
enum: ['Log', 'Warning', 'Error', 'Assert', 'Exception', 'All']
},
default: ['All']
},
filterText: {
type: 'string',
description: 'Filter logs containing this text (case-insensitive)'
},
includeStackTrace: {
type: 'boolean',
description: 'Include stack traces in results',
default: true
},
format: {
type: 'string',
description: 'Output format for logs',
enum: ['detailed', 'compact', 'json', 'plain'],
default: 'detailed'
},
sinceTimestamp: {
type: 'string',
description: 'Only return logs after this timestamp (ISO 8601)'
},
untilTimestamp: {
type: 'string',
description: 'Only return logs before this timestamp (ISO 8601)'
},
sortOrder: {
type: 'string',
description: 'Sort order for logs',
enum: ['newest', 'oldest'],
default: 'newest'
},
groupBy: {
type: 'string',
description: 'Group logs by criteria',
enum: ['none', 'type', 'file', 'time'],
default: 'none'
}
},
required: []
}
);
this.unityConnection = unityConnection;
}
/**
* Validates the input parameters
* @param {Object} params - The input parameters
* @throws {Error} If validation fails
*/
validate(params) {
const {
count,
logTypes,
filterText,
includeStackTrace,
format,
sinceTimestamp,
untilTimestamp,
sortOrder,
groupBy
} = params;
// Validate count
if (count !== undefined) {
if (typeof count !== 'number' || count < 1 || count > 1000) {
throw new Error('count must be between 1 and 1000');
}
}
// Validate log types
if (logTypes !== undefined) {
if (!Array.isArray(logTypes)) {
throw new Error('logTypes must be an array');
}
const validTypes = ['Log', 'Warning', 'Error', 'Assert', 'Exception', 'All'];
for (const type of logTypes) {
if (!validTypes.includes(type)) {
throw new Error(`Invalid log type: ${type}. Must be one of: ${validTypes.join(', ')}`);
}
}
}
// Validate timestamps
if (sinceTimestamp !== undefined) {
if (!this.isValidISO8601(sinceTimestamp)) {
throw new Error('sinceTimestamp must be a valid ISO 8601 timestamp');
}
}
if (untilTimestamp !== undefined) {
if (!this.isValidISO8601(untilTimestamp)) {
throw new Error('untilTimestamp must be a valid ISO 8601 timestamp');
}
}
// Validate timestamp order
if (sinceTimestamp && untilTimestamp) {
const since = new Date(sinceTimestamp);
const until = new Date(untilTimestamp);
if (until <= since) {
throw new Error('untilTimestamp must be after sinceTimestamp');
}
}
// Validate format
if (format !== undefined) {
const validFormats = ['detailed', 'compact', 'json', 'plain'];
if (!validFormats.includes(format)) {
throw new Error(`format must be one of: ${validFormats.join(', ')}`);
}
}
// Validate sort order
if (sortOrder !== undefined) {
const validOrders = ['newest', 'oldest'];
if (!validOrders.includes(sortOrder)) {
throw new Error(`sortOrder must be one of: ${validOrders.join(', ')}`);
}
}
// Validate groupBy
if (groupBy !== undefined) {
const validGroups = ['none', 'type', 'file', 'time'];
if (!validGroups.includes(groupBy)) {
throw new Error(`groupBy must be one of: ${validGroups.join(', ')}`);
}
}
}
/**
* Checks if a string is a valid ISO 8601 timestamp
* @param {string} timestamp - The timestamp to validate
* @returns {boolean} True if valid
*/
isValidISO8601(timestamp) {
const regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
if (!regex.test(timestamp)) {
return false;
}
const date = new Date(timestamp);
return !isNaN(date.getTime());
}
/**
* Executes the enhanced log reading operation
* @param {Object} params - The input parameters
* @returns {Promise<Object>} The result of the log reading
*/
async execute(params) {
const {
count = 100,
logTypes = ['All'],
filterText,
includeStackTrace = true,
format = 'detailed',
sinceTimestamp,
untilTimestamp,
sortOrder = 'newest',
groupBy = 'none'
} = params;
// Ensure connection to Unity
if (!this.unityConnection.isConnected()) {
await this.unityConnection.connect();
}
// Prepare command parameters
const commandParams = {
count,
logTypes,
includeStackTrace,
format,
sortOrder,
groupBy
};
// Add optional parameters
if (filterText !== undefined) {
commandParams.filterText = filterText;
}
if (sinceTimestamp !== undefined) {
commandParams.sinceTimestamp = sinceTimestamp;
}
if (untilTimestamp !== undefined) {
commandParams.untilTimestamp = untilTimestamp;
}
// Send command to Unity
const response = await this.unityConnection.sendCommand('enhanced_read_logs', commandParams);
// Handle Unity response
if (response.success === false) {
throw new Error(response.error || 'Failed to read logs');
}
// Build result object
const result = {
logs: response.logs || [],
count: response.count || 0,
totalCaptured: response.totalCaptured || 0
};
// Include optional fields if available
if (response.filteredCount !== undefined) {
result.filteredCount = response.filteredCount;
}
if (response.statistics !== undefined) {
result.statistics = response.statistics;
}
if (response.groupedLogs !== undefined) {
result.groupedLogs = response.groupedLogs;
}
if (response.format !== undefined) {
result.format = response.format;
}
if (response.groupBy !== undefined) {
result.groupBy = response.groupBy;
}
return result;
}
}