import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { listChanges, pruneHistory, getChangeStats, readSnapshotById, } from "@mandujs/core/change"; export const historyToolDefinitions: Tool[] = [ { name: "mandu.history.list", description: "List the change history with snapshots", annotations: { readOnlyHint: true, }, inputSchema: { type: "object", properties: { limit: { type: "number", description: "Maximum number of entries to return (default: 10)", }, }, required: [], }, }, { name: "mandu.history.snapshot", description: "Get details of a specific snapshot", annotations: { readOnlyHint: true, }, inputSchema: { type: "object", properties: { snapshotId: { type: "string", description: "The snapshot ID to retrieve", }, }, required: ["snapshotId"], }, }, { name: "mandu.history.prune", description: "Remove old snapshots to free up space", annotations: { destructiveHint: true, readOnlyHint: false, }, inputSchema: { type: "object", properties: { keepCount: { type: "number", description: "Number of snapshots to keep (default: 5)", }, }, required: [], }, }, ]; export function historyTools(projectRoot: string) { const handlers: Record) => Promise> = { "mandu.history.list": async (args: Record) => { const { limit = 10 } = args as { limit?: number }; const changes = await listChanges(projectRoot); const stats = await getChangeStats(projectRoot); // Sort by createdAt descending and limit const sortedChanges = changes .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, limit); return { changes: sortedChanges.map((c) => ({ id: c.id, message: c.message, status: c.status, createdAt: c.createdAt, snapshotId: c.snapshotId, autoGenerated: c.autoGenerated, })), stats: { total: stats.total, active: stats.active, committed: stats.committed, rolledBack: stats.rolledBack, snapshotCount: stats.snapshotCount, }, }; }, "mandu.history.snapshot": async (args: Record) => { const { snapshotId } = args as { snapshotId: string }; const snapshot = await readSnapshotById(projectRoot, snapshotId); if (!snapshot) { return { error: `Snapshot not found: ${snapshotId}` }; } const slotFiles = Object.keys(snapshot.slotContents); return { id: snapshot.id, timestamp: snapshot.timestamp, manifest: { version: snapshot.manifest.version, routeCount: snapshot.manifest.routes.length, routes: snapshot.manifest.routes.map((r) => ({ id: r.id, pattern: r.pattern, kind: r.kind, })), }, hasLock: !!snapshot.lock, lock: snapshot.lock ? { routesHash: snapshot.lock.routesHash, updatedAt: snapshot.lock.updatedAt, } : null, slotFiles, slotCount: slotFiles.length, }; }, "mandu.history.prune": async (args: Record) => { const { keepCount = 5 } = args as { keepCount?: number }; const deletedIds = await pruneHistory(projectRoot, keepCount); const stats = await getChangeStats(projectRoot); return { success: true, deletedCount: deletedIds.length, deletedIds, remaining: { snapshotCount: stats.snapshotCount, changeCount: stats.total, }, }; }, }; // Backward-compatible aliases (deprecated) handlers["mandu_list_history"] = handlers["mandu.history.list"]; handlers["mandu_get_snapshot"] = handlers["mandu.history.snapshot"]; handlers["mandu_prune_history"] = handlers["mandu.history.prune"]; return handlers; }