@akiojin/unity-mcp-server
Version:
MCP server and Unity Editor bridge — enables AI assistants to control Unity for AI-assisted workflows
84 lines (78 loc) • 4.06 kB
JavaScript
import { BaseToolHandler } from '../base/BaseToolHandler.js';
import { LspRpcClient } from '../../lsp/LspRpcClient.js';
import { ProjectInfoProvider } from '../../core/projectInfo.js';
export class ScriptRefactorRenameToolHandler extends BaseToolHandler {
constructor(unityConnection) {
super(
'script_refactor_rename',
'Refactor: rename a symbol across the project using the bundled C# LSP. Required params: relative (file path starting with Assets/ or Packages/), namePath (container path like Outer/Nested/Member), newName. Guidance: resolve targets first (script_symbols_get/script_symbol_find), prefer fully-qualified namePath to avoid ambiguity, and use preview for diagnostics only (apply proceeds even if diagnostics exist; errors are returned in response). Responses are summarized (errors≤30, message≤200 chars, large text≤1000 chars).',
{
type: 'object',
properties: {
relative: { type: 'string', description: 'Project-relative file path (Assets/ or Packages/)' },
namePath: { type: 'string', description: 'Symbol path like Class/Method' },
newName: { type: 'string', description: 'New name' },
preview: { type: 'boolean', description: 'Preview changes before applying (default: true)' }
},
required: ['relative', 'namePath', 'newName']
}
);
this.projectInfo = new ProjectInfoProvider(unityConnection);
this.lsp = null;
}
validate(params) {
super.validate(params);
const { relative, namePath, newName } = params;
if (!relative || !namePath || !newName) throw new Error('relative, namePath, newName are required');
}
async execute(params) {
const { relative, namePath, newName, preview = true } = params;
const info = await this.projectInfo.get();
if (!this.lsp) this.lsp = new LspRpcClient(info.projectRoot);
const resp = await this.lsp.request('mcp/renameByNamePath', {
relative: String(relative).replace(/\\\\/g, '/'),
namePath: String(namePath),
newName: String(newName),
apply: !preview
});
const r = resp?.result ?? resp;
return this._summarizeResult(r);
}
_summarizeResult(res) {
if (!res || typeof res !== 'object') return res;
const MAX_ERRORS = 30;
const MAX_MSG_LEN = 200;
const MAX_TEXT_LEN = 1000;
const out = {};
if ('id' in res) out.id = res.id;
if ('success' in res) out.success = !!res.success;
if ('applied' in res) out.applied = !!res.applied;
if (Array.isArray(res.errors)) {
const trimmed = res.errors.slice(0, MAX_ERRORS).map(e => {
const o = {};
if (e && typeof e === 'object') {
if ('id' in e) o.id = e.id;
if ('message' in e) o.message = String(e.message).slice(0, MAX_MSG_LEN);
if ('file' in e) o.file = String(e.file).slice(0, 260);
if ('line' in e) o.line = e.line;
if ('column' in e) o.column = e.column;
} else { o.message = String(e).slice(0, MAX_MSG_LEN); }
return o;
});
out.errorCount = trimmed.length;
out.totalErrors = res.errors.length;
out.errors = trimmed;
}
// workspace情報は返さない(厳格: .sln必須のため)
for (const k of ['preview','diff','text','content']) {
if (typeof res[k] === 'string' && res[k].length > 0) {
out[k] = res[k].slice(0, MAX_TEXT_LEN);
if (res[k].length > MAX_TEXT_LEN) out[`${k}Truncated`] = true;
}
}
for (const k of ['operation','path','relative','symbolName']) {
if (res[k] !== undefined) out[k] = res[k];
}
return Object.keys(out).length ? out : res;
}
}