UNPKG

@akiojin/unity-editor-mcp

Version:

MCP server for Unity Editor integration - enables AI assistants to control Unity Editor

97 lines (86 loc) 3.2 kB
import fs from 'fs'; import path from 'path'; import Database from 'better-sqlite3'; import { logger } from './config.js'; let dbCache = new Map(); function getDbPath(projectRoot) { const dir = path.join(projectRoot, 'Library', 'UnityMCP', 'CodeIndex'); fs.mkdirSync(dir, { recursive: true }); return path.join(dir, 'index.db'); } export function openDb(projectRoot) { const key = path.resolve(projectRoot); if (dbCache.has(key)) return dbCache.get(key); const dbPath = getDbPath(projectRoot); const db = new Database(dbPath); db.pragma('journal_mode = WAL'); db.exec(` CREATE TABLE IF NOT EXISTS files ( path TEXT PRIMARY KEY, mtime INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS symbols ( path TEXT NOT NULL, name TEXT NOT NULL, kind TEXT, container TEXT, ns TEXT, line INTEGER, column INTEGER, FOREIGN KEY(path) REFERENCES files(path) ); CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name); CREATE TABLE IF NOT EXISTS refs ( path TEXT NOT NULL, name TEXT NOT NULL, line INTEGER, snippet TEXT, FOREIGN KEY(path) REFERENCES files(path) ); CREATE INDEX IF NOT EXISTS idx_refs_name ON refs(name); `); dbCache.set(key, db); return db; } export function upsertFile(db, filePath, mtimeMs) { const stmt = db.prepare('INSERT INTO files(path, mtime) VALUES(?, ?) ON CONFLICT(path) DO UPDATE SET mtime=excluded.mtime'); stmt.run(filePath, Math.floor(mtimeMs)); } export function replaceSymbols(db, filePath, symbols) { const del = db.prepare('DELETE FROM symbols WHERE path = ?'); del.run(filePath); const ins = db.prepare('INSERT INTO symbols(path, name, kind, container, ns, line, column) VALUES(?,?,?,?,?,?,?)'); const tr = db.transaction((rows) => { for (const s of rows) ins.run(filePath, s.name || '', s.kind || '', s.container || null, s.ns || null, s.line || 0, s.column || 0); }); tr(symbols || []); } export function replaceReferences(db, filePath, refs) { const del = db.prepare('DELETE FROM refs WHERE path = ?'); del.run(filePath); const ins = db.prepare('INSERT INTO refs(path, name, line, snippet) VALUES(?,?,?,?)'); const tr = db.transaction((rows) => { for (const r of rows) ins.run(filePath, r.name || '', r.line || 0, r.snippet || null); }); tr(refs || []); } export function querySymbolsByName(db, name, kind = null) { if (kind) { return db.prepare('SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? AND kind = ? LIMIT 500').all(name, kind); } return db.prepare('SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? LIMIT 500').all(name); } export function queryRefsByName(db, name) { return db.prepare('SELECT path,name,line,snippet FROM refs WHERE name = ? LIMIT 1000').all(name); } export function isFresh(projectRoot, filePath, db) { try { const row = db.prepare('SELECT mtime FROM files WHERE path = ?').get(filePath); if (!row) return false; const abs = path.join(projectRoot, filePath); const st = fs.statSync(abs); return Math.floor(st.mtimeMs) === row.mtime; } catch { return false; } }