@asd412id/mcp-context-manager 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # MCP Context Manager
2
+
3
+ MCP (Model Context Protocol) tools untuk manajemen context pada AI coding agents. Membantu mengatasi masalah out-of-context pada chat yang panjang.
4
+
5
+ ## Features
6
+
7
+ - **Memory Store** - Persistent key-value storage antar session
8
+ - **Context Summarizer** - Ringkas chat/text, extract key points, decisions, action items
9
+ - **Project Tracker** - Track decisions, changes, todos, notes, errors
10
+ - **Session Checkpoint** - Save/restore session state
11
+ - **Smart File Loader** - Load file dengan filter relevansi
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -g @asd412id/mcp-context-manager
17
+ ```
18
+
19
+ Atau jalankan langsung dengan npx:
20
+
21
+ ```bash
22
+ npx @asd412id/mcp-context-manager
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ ### Claude Desktop
28
+
29
+ Tambahkan ke `claude_desktop_config.json`:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "context-manager": {
35
+ "command": "npx",
36
+ "args": ["-y", "@asd412id/mcp-context-manager"],
37
+ "env": {
38
+ "MCP_CONTEXT_PATH": "/path/to/your/project/.context"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### Environment Variables
46
+
47
+ | Variable | Description | Default |
48
+ |----------|-------------|---------|
49
+ | `MCP_CONTEXT_PATH` | Path untuk menyimpan context data | `{cwd}/.context` |
50
+
51
+ ## Available Tools
52
+
53
+ ### Memory Store (6 tools)
54
+
55
+ | Tool | Description |
56
+ |------|-------------|
57
+ | `memory_set` | Simpan key-value ke memory |
58
+ | `memory_get` | Ambil value dari memory |
59
+ | `memory_search` | Cari memory by pattern/tags |
60
+ | `memory_delete` | Hapus memory entry |
61
+ | `memory_list` | List semua memory keys |
62
+ | `memory_clear` | Clear memory (all/by tags) |
63
+
64
+ ### Context Summarizer (4 tools)
65
+
66
+ | Tool | Description |
67
+ |------|-------------|
68
+ | `context_summarize` | Ringkas text, extract key points, decisions, action items |
69
+ | `context_get_summary` | Ambil summary by ID |
70
+ | `context_list_summaries` | List semua summaries |
71
+ | `context_merge_summaries` | Gabungkan beberapa summaries |
72
+
73
+ ### Project Tracker (6 tools)
74
+
75
+ | Tool | Description |
76
+ |------|-------------|
77
+ | `tracker_log` | Log decision/change/todo/note/error |
78
+ | `tracker_status` | Get project status overview |
79
+ | `tracker_todo_update` | Update todo status |
80
+ | `tracker_search` | Search tracker entries |
81
+ | `tracker_set_project` | Set project name |
82
+ | `tracker_export` | Export tracker as markdown |
83
+
84
+ ### Session Checkpoint (5 tools)
85
+
86
+ | Tool | Description |
87
+ |------|-------------|
88
+ | `checkpoint_save` | Save session state |
89
+ | `checkpoint_load` | Load checkpoint |
90
+ | `checkpoint_list` | List all checkpoints |
91
+ | `checkpoint_delete` | Delete checkpoint |
92
+ | `checkpoint_compare` | Compare 2 checkpoints |
93
+
94
+ ### Smart File Loader (4 tools)
95
+
96
+ | Tool | Description |
97
+ |------|-------------|
98
+ | `file_smart_read` | Read file with smart options (lines, keywords, structure) |
99
+ | `file_info` | Get file metadata |
100
+ | `file_search_content` | Search pattern in file |
101
+ | `file_list_dir` | List directory files |
102
+
103
+ ## Usage Examples
104
+
105
+ ### Memory Store
106
+
107
+ ```
108
+ // Save important context
109
+ memory_set({ key: "api_base_url", value: "https://api.example.com", tags: ["config"] })
110
+
111
+ // Retrieve later
112
+ memory_get({ key: "api_base_url" })
113
+
114
+ // Search by tags
115
+ memory_search({ tags: ["config"] })
116
+ ```
117
+
118
+ ### Context Summarizer
119
+
120
+ ```
121
+ // Summarize long conversation
122
+ context_summarize({
123
+ text: "..long conversation..",
124
+ maxLength: 2000,
125
+ sessionId: "session-123"
126
+ })
127
+
128
+ // Merge multiple summaries
129
+ context_merge_summaries({ ids: ["sum_1", "sum_2", "sum_3"] })
130
+ ```
131
+
132
+ ### Project Tracker
133
+
134
+ ```
135
+ // Log a decision
136
+ tracker_log({ type: "decision", content: "Using PostgreSQL for database", tags: ["database"] })
137
+
138
+ // Log a todo
139
+ tracker_log({ type: "todo", content: "Implement user authentication" })
140
+
141
+ // Get project status
142
+ tracker_status()
143
+ ```
144
+
145
+ ### Session Checkpoint
146
+
147
+ ```
148
+ // Save checkpoint before major changes
149
+ checkpoint_save({
150
+ name: "before-refactor",
151
+ description: "Completed user module, starting refactor",
152
+ state: { currentTask: "refactoring", completedFiles: ["user.ts"] },
153
+ files: ["src/user.ts", "src/auth.ts"]
154
+ })
155
+
156
+ // Load checkpoint in new session
157
+ checkpoint_load({ name: "before-refactor" })
158
+ ```
159
+
160
+ ### Smart File Loader
161
+
162
+ ```
163
+ // Read only code structure
164
+ file_smart_read({ path: "src/index.ts", structureOnly: true })
165
+
166
+ // Read specific lines
167
+ file_smart_read({ path: "src/index.ts", startLine: 50, endLine: 100 })
168
+
169
+ // Search by keywords
170
+ file_smart_read({ path: "src/index.ts", keywords: ["function", "export"] })
171
+ ```
172
+
173
+ ## Best Practices untuk Optimasi Context
174
+
175
+ 1. **Gunakan memory_set** untuk menyimpan informasi penting yang perlu diingat
176
+ 2. **Buat checkpoint** sebelum context terlalu panjang
177
+ 3. **Summarize conversation** secara berkala
178
+ 4. **Track decisions** agar tidak perlu explain ulang
179
+ 5. **Load file secara smart** - hanya bagian yang diperlukan
180
+
181
+ ## License
182
+
183
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import * as path from 'path';
5
+ import { initStore } from './storage/file-store.js';
6
+ import { registerMemoryTools } from './tools/memory.js';
7
+ import { registerSummarizerTools } from './tools/summarizer.js';
8
+ import { registerTrackerTools } from './tools/tracker.js';
9
+ import { registerCheckpointTools } from './tools/checkpoint.js';
10
+ import { registerLoaderTools } from './tools/loader.js';
11
+ import { registerPrompts } from './prompts.js';
12
+ const SERVER_NAME = 'mcp-context-manager';
13
+ const SERVER_VERSION = '1.0.0';
14
+ async function main() {
15
+ const contextPath = process.env.MCP_CONTEXT_PATH || path.join(process.cwd(), '.context');
16
+ initStore(contextPath);
17
+ const server = new McpServer({
18
+ name: SERVER_NAME,
19
+ version: SERVER_VERSION
20
+ });
21
+ registerMemoryTools(server);
22
+ registerSummarizerTools(server);
23
+ registerTrackerTools(server);
24
+ registerCheckpointTools(server);
25
+ registerLoaderTools(server);
26
+ registerPrompts(server);
27
+ const transport = new StdioServerTransport();
28
+ await server.connect(transport);
29
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} started`);
30
+ console.error(`Context path: ${contextPath}`);
31
+ }
32
+ main().catch((error) => {
33
+ console.error('Fatal error:', error);
34
+ process.exit(1);
35
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerPrompts(server: McpServer): void;
@@ -0,0 +1,127 @@
1
+ import * as z from 'zod';
2
+ export function registerPrompts(server) {
3
+ // ctx-init - Load previous context
4
+ server.registerPrompt('ctx-init', {
5
+ title: 'Init Session',
6
+ description: 'Load previous context at session start'
7
+ }, () => ({
8
+ messages: [{
9
+ role: 'user',
10
+ content: {
11
+ type: 'text',
12
+ text: `Load previous context:
13
+ 1. checkpoint_load() - get last state
14
+ 2. tracker_status() - see todos & decisions
15
+ 3. memory_list() - check saved data`
16
+ }
17
+ }]
18
+ }));
19
+ // ctx-save - Quick save current state
20
+ server.registerPrompt('ctx-save', {
21
+ title: 'Save State',
22
+ description: 'Quick save current session state',
23
+ argsSchema: {
24
+ name: z.string().optional().describe('Checkpoint name')
25
+ }
26
+ }, ({ name }) => ({
27
+ messages: [{
28
+ role: 'user',
29
+ content: {
30
+ type: 'text',
31
+ text: `Save current state to checkpoint "${name || 'auto'}". Include: current task, progress, important decisions, modified files.`
32
+ }
33
+ }]
34
+ }));
35
+ // ctx-remember - Save to memory
36
+ server.registerPrompt('ctx-remember', {
37
+ title: 'Remember',
38
+ description: 'Save important info to memory',
39
+ argsSchema: {
40
+ what: z.string().describe('What to remember')
41
+ }
42
+ }, ({ what }) => ({
43
+ messages: [{
44
+ role: 'user',
45
+ content: {
46
+ type: 'text',
47
+ text: `Save to memory: "${what}". Use descriptive key and appropriate tags.`
48
+ }
49
+ }]
50
+ }));
51
+ // ctx-todo - Add todo item
52
+ server.registerPrompt('ctx-todo', {
53
+ title: 'Add Todo',
54
+ description: 'Log a todo item',
55
+ argsSchema: {
56
+ task: z.string().describe('Task to do')
57
+ }
58
+ }, ({ task }) => ({
59
+ messages: [{
60
+ role: 'user',
61
+ content: {
62
+ type: 'text',
63
+ text: `Log todo: "${task}"`
64
+ }
65
+ }]
66
+ }));
67
+ // ctx-decide - Log decision
68
+ server.registerPrompt('ctx-decide', {
69
+ title: 'Log Decision',
70
+ description: 'Record a decision',
71
+ argsSchema: {
72
+ decision: z.string().describe('Decision made')
73
+ }
74
+ }, ({ decision }) => ({
75
+ messages: [{
76
+ role: 'user',
77
+ content: {
78
+ type: 'text',
79
+ text: `Log decision: "${decision}"`
80
+ }
81
+ }]
82
+ }));
83
+ // ctx-status - Get current status
84
+ server.registerPrompt('ctx-status', {
85
+ title: 'Status',
86
+ description: 'Show project status'
87
+ }, () => ({
88
+ messages: [{
89
+ role: 'user',
90
+ content: {
91
+ type: 'text',
92
+ text: `Show: tracker_status(), memory_list(), checkpoint_list()`
93
+ }
94
+ }]
95
+ }));
96
+ // ctx-compress - Summarize long context
97
+ server.registerPrompt('ctx-compress', {
98
+ title: 'Compress Context',
99
+ description: 'Summarize to save context space'
100
+ }, () => ({
101
+ messages: [{
102
+ role: 'user',
103
+ content: {
104
+ type: 'text',
105
+ text: `Context getting long. Summarize conversation with context_summarize(), save checkpoint, store key info to memory.`
106
+ }
107
+ }]
108
+ }));
109
+ // ctx-recall - Search memories
110
+ server.registerPrompt('ctx-recall', {
111
+ title: 'Recall',
112
+ description: 'Search saved memories',
113
+ argsSchema: {
114
+ query: z.string().optional().describe('Search term')
115
+ }
116
+ }, ({ query }) => ({
117
+ messages: [{
118
+ role: 'user',
119
+ content: {
120
+ type: 'text',
121
+ text: query
122
+ ? `Search memories for: "${query}"`
123
+ : `List all memories with memory_list()`
124
+ }
125
+ }]
126
+ }));
127
+ }
@@ -0,0 +1,18 @@
1
+ export interface StoreOptions {
2
+ basePath: string;
3
+ }
4
+ export declare class FileStore {
5
+ private basePath;
6
+ constructor(options: StoreOptions);
7
+ private ensureDir;
8
+ private getFilePath;
9
+ read<T>(filename: string, defaultValue: T): Promise<T>;
10
+ write<T>(filename: string, data: T): Promise<void>;
11
+ append<T>(filename: string, item: T): Promise<void>;
12
+ delete(filename: string): Promise<boolean>;
13
+ exists(filename: string): Promise<boolean>;
14
+ list(subdir?: string): Promise<string[]>;
15
+ getSubStore(subdir: string): FileStore;
16
+ }
17
+ export declare function getStore(basePath?: string): FileStore;
18
+ export declare function initStore(basePath: string): FileStore;
@@ -0,0 +1,84 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ export class FileStore {
4
+ basePath;
5
+ constructor(options) {
6
+ this.basePath = options.basePath;
7
+ this.ensureDir(this.basePath);
8
+ }
9
+ ensureDir(dirPath) {
10
+ if (!fs.existsSync(dirPath)) {
11
+ fs.mkdirSync(dirPath, { recursive: true });
12
+ }
13
+ }
14
+ getFilePath(filename) {
15
+ return path.join(this.basePath, filename);
16
+ }
17
+ async read(filename, defaultValue) {
18
+ const filePath = this.getFilePath(filename);
19
+ try {
20
+ if (fs.existsSync(filePath)) {
21
+ const content = fs.readFileSync(filePath, 'utf-8');
22
+ return JSON.parse(content);
23
+ }
24
+ }
25
+ catch (error) {
26
+ console.error(`Error reading ${filename}:`, error);
27
+ }
28
+ return defaultValue;
29
+ }
30
+ async write(filename, data) {
31
+ const filePath = this.getFilePath(filename);
32
+ const dir = path.dirname(filePath);
33
+ this.ensureDir(dir);
34
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
35
+ }
36
+ async append(filename, item) {
37
+ const existing = await this.read(filename, []);
38
+ existing.push(item);
39
+ await this.write(filename, existing);
40
+ }
41
+ async delete(filename) {
42
+ const filePath = this.getFilePath(filename);
43
+ try {
44
+ if (fs.existsSync(filePath)) {
45
+ fs.unlinkSync(filePath);
46
+ return true;
47
+ }
48
+ }
49
+ catch (error) {
50
+ console.error(`Error deleting ${filename}:`, error);
51
+ }
52
+ return false;
53
+ }
54
+ async exists(filename) {
55
+ return fs.existsSync(this.getFilePath(filename));
56
+ }
57
+ async list(subdir) {
58
+ const dirPath = subdir ? path.join(this.basePath, subdir) : this.basePath;
59
+ this.ensureDir(dirPath);
60
+ try {
61
+ return fs.readdirSync(dirPath).filter(f => f.endsWith('.json'));
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }
67
+ getSubStore(subdir) {
68
+ return new FileStore({
69
+ basePath: path.join(this.basePath, subdir)
70
+ });
71
+ }
72
+ }
73
+ let storeInstance = null;
74
+ export function getStore(basePath) {
75
+ if (!storeInstance) {
76
+ const defaultPath = path.join(process.cwd(), '.context');
77
+ storeInstance = new FileStore({ basePath: basePath || defaultPath });
78
+ }
79
+ return storeInstance;
80
+ }
81
+ export function initStore(basePath) {
82
+ storeInstance = new FileStore({ basePath });
83
+ return storeInstance;
84
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCheckpointTools(server: McpServer): void;
@@ -0,0 +1,192 @@
1
+ import * as z from 'zod';
2
+ import { getStore } from '../storage/file-store.js';
3
+ const CHECKPOINTS_DIR = 'checkpoints';
4
+ async function getCheckpointStore() {
5
+ const store = getStore().getSubStore(CHECKPOINTS_DIR);
6
+ return store.read('index.json', { checkpoints: [] });
7
+ }
8
+ async function saveCheckpointStore(data) {
9
+ const store = getStore().getSubStore(CHECKPOINTS_DIR);
10
+ await store.write('index.json', data);
11
+ }
12
+ async function saveCheckpointData(id, data) {
13
+ const store = getStore().getSubStore(CHECKPOINTS_DIR);
14
+ await store.write(`${id}.json`, data);
15
+ }
16
+ async function loadCheckpointData(id) {
17
+ const store = getStore().getSubStore(CHECKPOINTS_DIR);
18
+ if (await store.exists(`${id}.json`)) {
19
+ return store.read(`${id}.json`, {});
20
+ }
21
+ return null;
22
+ }
23
+ export function registerCheckpointTools(server) {
24
+ server.registerTool('checkpoint_save', {
25
+ title: 'Save Checkpoint',
26
+ description: 'Save current session state as a checkpoint. Use before context gets too long or at important milestones.',
27
+ inputSchema: {
28
+ name: z.string().describe('Checkpoint name (e.g., "before-refactor", "feature-complete")'),
29
+ description: z.string().optional().describe('Description of what was accomplished'),
30
+ state: z.record(z.unknown()).describe('State data to save (conversation summary, current task, etc.)'),
31
+ files: z.array(z.string()).optional().describe('List of relevant file paths')
32
+ }
33
+ }, async ({ name, description, state, files }) => {
34
+ const checkpointStore = await getCheckpointStore();
35
+ const checkpoint = {
36
+ id: `cp_${Date.now()}`,
37
+ name,
38
+ description,
39
+ state,
40
+ files: files || [],
41
+ createdAt: new Date().toISOString()
42
+ };
43
+ await saveCheckpointData(checkpoint.id, state);
44
+ checkpointStore.checkpoints.push({
45
+ ...checkpoint,
46
+ state: {}
47
+ });
48
+ await saveCheckpointStore(checkpointStore);
49
+ return {
50
+ content: [{
51
+ type: 'text',
52
+ text: `Checkpoint saved: "${name}" (ID: ${checkpoint.id})\nDescription: ${description || 'N/A'}\nFiles: ${files?.length || 0}`
53
+ }]
54
+ };
55
+ });
56
+ server.registerTool('checkpoint_load', {
57
+ title: 'Load Checkpoint',
58
+ description: 'Load a previously saved checkpoint to restore context.',
59
+ inputSchema: {
60
+ id: z.string().optional().describe('Checkpoint ID (loads latest if not specified)'),
61
+ name: z.string().optional().describe('Checkpoint name to search for')
62
+ }
63
+ }, async ({ id, name }) => {
64
+ const checkpointStore = await getCheckpointStore();
65
+ let checkpoint;
66
+ if (id) {
67
+ checkpoint = checkpointStore.checkpoints.find(cp => cp.id === id);
68
+ }
69
+ else if (name) {
70
+ checkpoint = checkpointStore.checkpoints
71
+ .filter(cp => cp.name.toLowerCase().includes(name.toLowerCase()))
72
+ .pop();
73
+ }
74
+ else {
75
+ checkpoint = checkpointStore.checkpoints[checkpointStore.checkpoints.length - 1];
76
+ }
77
+ if (!checkpoint) {
78
+ return {
79
+ content: [{ type: 'text', text: 'Checkpoint not found' }]
80
+ };
81
+ }
82
+ const stateData = await loadCheckpointData(checkpoint.id);
83
+ const output = {
84
+ id: checkpoint.id,
85
+ name: checkpoint.name,
86
+ description: checkpoint.description,
87
+ createdAt: checkpoint.createdAt,
88
+ files: checkpoint.files,
89
+ state: stateData
90
+ };
91
+ return {
92
+ content: [{ type: 'text', text: JSON.stringify(output, null, 2) }]
93
+ };
94
+ });
95
+ server.registerTool('checkpoint_list', {
96
+ title: 'List Checkpoints',
97
+ description: 'List all saved checkpoints.',
98
+ inputSchema: {
99
+ limit: z.number().optional().describe('Maximum checkpoints to return (default: 10)')
100
+ }
101
+ }, async ({ limit = 10 }) => {
102
+ const checkpointStore = await getCheckpointStore();
103
+ const checkpoints = checkpointStore.checkpoints.slice(-limit);
104
+ if (checkpoints.length === 0) {
105
+ return {
106
+ content: [{ type: 'text', text: 'No checkpoints saved' }]
107
+ };
108
+ }
109
+ const list = checkpoints.map(cp => ({
110
+ id: cp.id,
111
+ name: cp.name,
112
+ description: cp.description,
113
+ createdAt: cp.createdAt,
114
+ filesCount: cp.files.length
115
+ }));
116
+ return {
117
+ content: [{ type: 'text', text: JSON.stringify(list, null, 2) }]
118
+ };
119
+ });
120
+ server.registerTool('checkpoint_delete', {
121
+ title: 'Delete Checkpoint',
122
+ description: 'Delete a checkpoint by ID.',
123
+ inputSchema: {
124
+ id: z.string().describe('Checkpoint ID to delete')
125
+ }
126
+ }, async ({ id }) => {
127
+ const checkpointStore = await getCheckpointStore();
128
+ const index = checkpointStore.checkpoints.findIndex(cp => cp.id === id);
129
+ if (index === -1) {
130
+ return {
131
+ content: [{ type: 'text', text: `Checkpoint not found: ${id}` }]
132
+ };
133
+ }
134
+ const removed = checkpointStore.checkpoints.splice(index, 1)[0];
135
+ await saveCheckpointStore(checkpointStore);
136
+ const store = getStore().getSubStore(CHECKPOINTS_DIR);
137
+ await store.delete(`${id}.json`);
138
+ return {
139
+ content: [{ type: 'text', text: `Deleted checkpoint: "${removed.name}" (${id})` }]
140
+ };
141
+ });
142
+ server.registerTool('checkpoint_compare', {
143
+ title: 'Compare Checkpoints',
144
+ description: 'Compare two checkpoints to see what changed.',
145
+ inputSchema: {
146
+ id1: z.string().describe('First checkpoint ID'),
147
+ id2: z.string().describe('Second checkpoint ID')
148
+ }
149
+ }, async ({ id1, id2 }) => {
150
+ const checkpointStore = await getCheckpointStore();
151
+ const cp1 = checkpointStore.checkpoints.find(cp => cp.id === id1);
152
+ const cp2 = checkpointStore.checkpoints.find(cp => cp.id === id2);
153
+ if (!cp1 || !cp2) {
154
+ return {
155
+ content: [{ type: 'text', text: 'One or both checkpoints not found' }]
156
+ };
157
+ }
158
+ const state1 = await loadCheckpointData(id1) || {};
159
+ const state2 = await loadCheckpointData(id2) || {};
160
+ const allKeys = new Set([...Object.keys(state1), ...Object.keys(state2)]);
161
+ const changes = [];
162
+ for (const key of allKeys) {
163
+ const v1 = JSON.stringify(state1[key]);
164
+ const v2 = JSON.stringify(state2[key]);
165
+ if (v1 !== v2) {
166
+ if (!state1[key]) {
167
+ changes.push({ key, change: 'added' });
168
+ }
169
+ else if (!state2[key]) {
170
+ changes.push({ key, change: 'removed' });
171
+ }
172
+ else {
173
+ changes.push({ key, change: 'modified' });
174
+ }
175
+ }
176
+ }
177
+ const newFiles = cp2.files.filter(f => !cp1.files.includes(f));
178
+ const removedFiles = cp1.files.filter(f => !cp2.files.includes(f));
179
+ const comparison = {
180
+ checkpoint1: { id: cp1.id, name: cp1.name, date: cp1.createdAt },
181
+ checkpoint2: { id: cp2.id, name: cp2.name, date: cp2.createdAt },
182
+ stateChanges: changes,
183
+ fileChanges: {
184
+ added: newFiles,
185
+ removed: removedFiles
186
+ }
187
+ };
188
+ return {
189
+ content: [{ type: 'text', text: JSON.stringify(comparison, null, 2) }]
190
+ };
191
+ });
192
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerLoaderTools(server: McpServer): void;