ac-framework 1.8.0 → 1.9.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/bin/postinstall.js +13 -0
- package/package.json +4 -2
- package/src/commands/init.js +98 -0
- package/src/commands/memory.js +107 -46
- package/src/mcp/server.js +345 -0
- package/src/mcp/server.js.bak +727 -0
- package/src/services/mcp-installer.js +194 -0
package/bin/postinstall.js
CHANGED
|
@@ -29,6 +29,10 @@ console.log(' acfm memory search Search memories');
|
|
|
29
29
|
console.log(' acfm memory save Save memory manually');
|
|
30
30
|
console.log(' acfm memory stats View memory statistics');
|
|
31
31
|
console.log();
|
|
32
|
+
console.log(' MCP (Model Context Protocol) Servers');
|
|
33
|
+
console.log(' acfm memory install-mcps Install MCP servers for AI assistants');
|
|
34
|
+
console.log(' acfm memory uninstall-mcps Uninstall MCP servers from AI assistants');
|
|
35
|
+
console.log();
|
|
32
36
|
console.log(' Tip: Add --json to any command for machine-readable output.');
|
|
33
37
|
console.log();
|
|
34
38
|
|
|
@@ -44,3 +48,12 @@ if (isWin) {
|
|
|
44
48
|
console.log(' 4. Restart your terminal');
|
|
45
49
|
console.log();
|
|
46
50
|
}
|
|
51
|
+
|
|
52
|
+
// Auto-detect and install MCPs for supported assistants
|
|
53
|
+
try {
|
|
54
|
+
const { detectAndInstallMCPs } = require('../src/services/mcp-installer');
|
|
55
|
+
detectAndInstallMCPs();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Silently fail if MCP installer is not available yet
|
|
58
|
+
// This allows the framework to work without MCP dependencies during early development
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ac-framework",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Agentic Coding Framework - Multi-assistant configuration system with OpenSpec workflows",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/b4san/AC-framework#readme",
|
|
44
44
|
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
45
46
|
"better-sqlite3": "^11.8.1",
|
|
46
47
|
"chalk": "^5.3.0",
|
|
47
48
|
"commander": "^12.1.0",
|
|
@@ -49,7 +50,8 @@
|
|
|
49
50
|
"inquirer": "^12.3.2",
|
|
50
51
|
"js-yaml": "^4.1.1",
|
|
51
52
|
"nanospinner": "^1.2.2",
|
|
52
|
-
"tar": "^7.5.7"
|
|
53
|
+
"tar": "^7.5.7",
|
|
54
|
+
"zod": "^4.3.6"
|
|
53
55
|
},
|
|
54
56
|
"type": "module",
|
|
55
57
|
"engines": {
|
package/src/commands/init.js
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
import chalk from 'chalk';
|
|
15
15
|
import gradient from 'gradient-string';
|
|
16
16
|
import inquirer from 'inquirer';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
17
20
|
import { DESCRIPTIONS, ASSISTANT_ICONS, BUNDLED } from '../config/constants.js';
|
|
18
21
|
import { IDE_MD_MAP, AVAILABLE_MD_FILES, MD_DESCRIPTIONS } from '../config/ide-mapping.js';
|
|
19
22
|
import { formatFolderName, sleep } from '../utils/helpers.js';
|
|
@@ -40,6 +43,100 @@ import {
|
|
|
40
43
|
|
|
41
44
|
const acGradient = gradient(['#6C5CE7', '#00CEC9', '#0984E3']);
|
|
42
45
|
|
|
46
|
+
// ── Persistent Memory Setup ───────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* First-time persistent memory setup.
|
|
50
|
+
* Only shown when ~/.acfm/memory.db does not yet exist.
|
|
51
|
+
*
|
|
52
|
+
* The memory store is named "NexusVault" — a unique, memorable name
|
|
53
|
+
* for the embedded knowledge base that persists context across sessions.
|
|
54
|
+
*/
|
|
55
|
+
async function setupPersistentMemory() {
|
|
56
|
+
const memoryDbPath = join(homedir(), '.acfm', 'memory.db');
|
|
57
|
+
|
|
58
|
+
// Only ask on first run
|
|
59
|
+
if (existsSync(memoryDbPath)) return;
|
|
60
|
+
|
|
61
|
+
console.log();
|
|
62
|
+
await animatedSeparator(60);
|
|
63
|
+
console.log();
|
|
64
|
+
|
|
65
|
+
const vaultBadge = chalk.hex('#2D3436').bgHex('#6C5CE7').bold(' NexusVault ');
|
|
66
|
+
console.log(` ${vaultBadge} ${chalk.hex('#B2BEC3').bold('Persistent Memory System')}`);
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(
|
|
69
|
+
chalk.hex('#636E72')(
|
|
70
|
+
' NexusVault is an embedded SQLite knowledge base that lives at\n' +
|
|
71
|
+
` ${chalk.hex('#DFE6E9')('~/.acfm/memory.db')} on your machine.\n\n` +
|
|
72
|
+
' When enabled, your AI assistants will automatically:\n' +
|
|
73
|
+
` ${chalk.hex('#00CEC9')('◆')} Remember architectural decisions, bugfixes & patterns\n` +
|
|
74
|
+
` ${chalk.hex('#00CEC9')('◆')} Recall relevant context when you start a new task\n` +
|
|
75
|
+
` ${chalk.hex('#00CEC9')('◆')} Connect to assistants via MCP (Model Context Protocol)\n\n` +
|
|
76
|
+
' Your data never leaves your machine — fully offline & private.'
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
console.log();
|
|
80
|
+
|
|
81
|
+
const { enableMemory } = await inquirer.prompt([{
|
|
82
|
+
type: 'confirm',
|
|
83
|
+
name: 'enableMemory',
|
|
84
|
+
message: chalk.hex('#B2BEC3')('Enable NexusVault persistent memory?'),
|
|
85
|
+
default: true,
|
|
86
|
+
}]);
|
|
87
|
+
|
|
88
|
+
if (!enableMemory) {
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(chalk.hex('#636E72')(' Skipped. You can enable it later with: acfm memory init'));
|
|
91
|
+
console.log();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk.hex('#B2BEC3')(' Initializing NexusVault...'));
|
|
97
|
+
|
|
98
|
+
// Init the SQLite database
|
|
99
|
+
const { initDatabase, isDatabaseInitialized } = await import('../memory/database.js');
|
|
100
|
+
if (!isDatabaseInitialized()) {
|
|
101
|
+
initDatabase();
|
|
102
|
+
}
|
|
103
|
+
console.log(
|
|
104
|
+
chalk.hex('#00CEC9')(' ◆ ') +
|
|
105
|
+
chalk.hex('#DFE6E9')('NexusVault database created at ~/.acfm/memory.db')
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Install MCP server into detected assistants
|
|
109
|
+
console.log();
|
|
110
|
+
console.log(chalk.hex('#B2BEC3')(' Connecting NexusVault to your AI assistants via MCP...'));
|
|
111
|
+
console.log();
|
|
112
|
+
|
|
113
|
+
const { detectAndInstallMCPs, ASSISTANTS, isAssistantInstalled } = await import('../services/mcp-installer.js');
|
|
114
|
+
const { installed, success } = detectAndInstallMCPs();
|
|
115
|
+
|
|
116
|
+
if (installed === 0) {
|
|
117
|
+
console.log(chalk.hex('#636E72')(' No AI assistants detected yet.'));
|
|
118
|
+
console.log(chalk.hex('#636E72')(' Run ' + chalk.hex('#DFE6E9')('acfm memory install-mcps') + ' after installing an assistant.'));
|
|
119
|
+
} else {
|
|
120
|
+
for (const assistant of ASSISTANTS) {
|
|
121
|
+
if (isAssistantInstalled(assistant)) {
|
|
122
|
+
console.log(
|
|
123
|
+
chalk.hex('#00CEC9')(' ◆ ') +
|
|
124
|
+
chalk.hex('#DFE6E9').bold(assistant.name) +
|
|
125
|
+
chalk.hex('#636E72')(` · MCP config → ${assistant.configPath}`)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
console.log();
|
|
130
|
+
const successBadge = chalk.hex('#2D3436').bgHex('#00CEC9').bold(` ${success}/${installed} `);
|
|
131
|
+
console.log(` ${successBadge} ${chalk.hex('#B2BEC3')('assistants connected to NexusVault')}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(chalk.hex('#6C5CE7').bold(' NexusVault is active.'));
|
|
136
|
+
console.log(chalk.hex('#636E72')(' Use acfm memory --help to manage your knowledge base.'));
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
|
|
43
140
|
// ── Helpers ──────────────────────────────────────────────────────
|
|
44
141
|
|
|
45
142
|
function buildChoices(folders) {
|
|
@@ -423,6 +520,7 @@ export async function initCommand(options = {}) {
|
|
|
423
520
|
// ── Final result ──────────────────────────────────────────────
|
|
424
521
|
if (errors.length === 0) {
|
|
425
522
|
await celebrateSuccess(installed, targetDir);
|
|
523
|
+
await setupPersistentMemory();
|
|
426
524
|
} else {
|
|
427
525
|
await showFailureSummary(installed, errors);
|
|
428
526
|
}
|
package/src/commands/memory.js
CHANGED
|
@@ -706,61 +706,122 @@ export function memoryCommand() {
|
|
|
706
706
|
|
|
707
707
|
// ─── acfm memory session ───────────────────────────────────────────────────
|
|
708
708
|
const sessionCmd = new Command('session')
|
|
709
|
-
|
|
709
|
+
.description('Gestión de sesiones de memoria');
|
|
710
710
|
|
|
711
711
|
sessionCmd
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
} catch (err) {
|
|
730
|
-
output({ error: err.message }, opts.json);
|
|
731
|
-
if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
|
|
732
|
-
process.exit(1);
|
|
712
|
+
.command('start')
|
|
713
|
+
.description('Inicia nueva sesión')
|
|
714
|
+
.option('-p, --project <path>', 'Proyecto', process.cwd())
|
|
715
|
+
.option('-c, --change <name>', 'Change')
|
|
716
|
+
.option('--json', 'Output as JSON')
|
|
717
|
+
.action((opts) => {
|
|
718
|
+
try {
|
|
719
|
+
ensureInitialized();
|
|
720
|
+
|
|
721
|
+
const sessionId = startSession(opts.project, opts.change);
|
|
722
|
+
|
|
723
|
+
output({ sessionId, project: opts.project, change: opts.change }, opts.json);
|
|
724
|
+
|
|
725
|
+
if (!opts.json) {
|
|
726
|
+
console.log(chalk.green('✓ Session started'));
|
|
727
|
+
console.log(chalk.dim(` ID: ${sessionId}`));
|
|
733
728
|
}
|
|
734
|
-
})
|
|
729
|
+
} catch (err) {
|
|
730
|
+
output({ error: err.message }, opts.json);
|
|
731
|
+
if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
735
|
|
|
736
736
|
sessionCmd
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
737
|
+
.command('end <sessionId>')
|
|
738
|
+
.description('Finaliza sesión')
|
|
739
|
+
.option('-s, --summary <text>', 'Resumen de la sesión')
|
|
740
|
+
.option('--json', 'Output as JSON')
|
|
741
|
+
.action((sessionId, opts) => {
|
|
742
|
+
try {
|
|
743
|
+
ensureInitialized();
|
|
744
|
+
|
|
745
|
+
endSession(sessionId, opts.summary);
|
|
746
|
+
|
|
747
|
+
output({ ended: true, sessionId }, opts.json);
|
|
748
|
+
|
|
749
|
+
if (!opts.json) {
|
|
750
|
+
console.log(chalk.green('✓ Session ended'));
|
|
751
|
+
if (opts.summary) {
|
|
752
|
+
console.log(chalk.dim('Summary saved as memory'));
|
|
754
753
|
}
|
|
755
|
-
} catch (err) {
|
|
756
|
-
output({ error: err.message }, opts.json);
|
|
757
|
-
if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
|
|
758
|
-
process.exit(1);
|
|
759
754
|
}
|
|
760
|
-
})
|
|
755
|
+
} catch (err) {
|
|
756
|
+
output({ error: err.message }, opts.json);
|
|
757
|
+
if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
|
|
758
|
+
process.exit(1);
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
761
|
|
|
762
762
|
memory.addCommand(sessionCmd);
|
|
763
|
-
|
|
763
|
+
|
|
764
|
+
// ─── acfm memory install-mcps ───────────────────────────────────────────────
|
|
765
|
+
memory
|
|
766
|
+
.command('install-mcps')
|
|
767
|
+
.description('Instala servidores MCP para asistentes de IA detectados')
|
|
768
|
+
.option('--all', 'Instalar para todos (sin requerir detección)', false)
|
|
769
|
+
.option('--json', 'Output as JSON')
|
|
770
|
+
.action(async (opts) => {
|
|
771
|
+
try {
|
|
772
|
+
const { detectAndInstallMCPs, installAllMCPs, ASSISTANTS, isAssistantInstalled } = await import('../services/mcp-installer.js');
|
|
773
|
+
|
|
774
|
+
const result = opts.all ? installAllMCPs() : detectAndInstallMCPs();
|
|
775
|
+
|
|
776
|
+
output({ total: result.total ?? result.installed, success: result.success }, opts.json);
|
|
777
|
+
|
|
778
|
+
if (!opts.json) {
|
|
779
|
+
if (!opts.all) {
|
|
780
|
+
if (result.installed === 0) {
|
|
781
|
+
console.log(chalk.yellow('No AI assistants detected.'));
|
|
782
|
+
console.log(chalk.dim('Use --all to install for all supported assistants.'));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
for (const assistant of ASSISTANTS) {
|
|
786
|
+
if (isAssistantInstalled(assistant)) {
|
|
787
|
+
console.log(
|
|
788
|
+
chalk.cyan('◆ ') + chalk.bold(assistant.name) +
|
|
789
|
+
chalk.dim(` → ${assistant.configPath}`)
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
console.log(chalk.green(`\n✓ MCP servers installed (${result.success}/${result.total ?? result.installed})`));
|
|
795
|
+
}
|
|
796
|
+
} catch (err) {
|
|
797
|
+
output({ error: err.message }, opts.json);
|
|
798
|
+
if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
|
|
799
|
+
process.exit(1);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// ─── acfm memory uninstall-mcps ───────────────────────────────────────────
|
|
804
|
+
memory
|
|
805
|
+
.command('uninstall-mcps')
|
|
806
|
+
.description('Desinstala servidores MCP de asistentes de IA')
|
|
807
|
+
.option('--json', 'Output as JSON')
|
|
808
|
+
.action(async (opts) => {
|
|
809
|
+
try {
|
|
810
|
+
const { uninstallAllMCPs } = await import('../services/mcp-installer.js');
|
|
811
|
+
const result = uninstallAllMCPs();
|
|
812
|
+
|
|
813
|
+
output({ success: result.success }, opts.json);
|
|
814
|
+
|
|
815
|
+
if (!opts.json) {
|
|
816
|
+
console.log(chalk.green(`✓ MCP servers uninstalled (${result.success})`));
|
|
817
|
+
}
|
|
818
|
+
} catch (err) {
|
|
819
|
+
output({ error: err.message }, opts.json);
|
|
820
|
+
if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
764
825
|
return memory;
|
|
765
826
|
}
|
|
766
827
|
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for AC Framework Memory System
|
|
5
|
+
* Exposes memory system functionality via Model Context Protocol
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import {
|
|
12
|
+
initDatabase,
|
|
13
|
+
isDatabaseInitialized,
|
|
14
|
+
saveMemory,
|
|
15
|
+
searchMemories,
|
|
16
|
+
getContext,
|
|
17
|
+
getTimeline,
|
|
18
|
+
getMemory,
|
|
19
|
+
updateMemory,
|
|
20
|
+
deleteMemory,
|
|
21
|
+
startSession,
|
|
22
|
+
endSession,
|
|
23
|
+
getStats,
|
|
24
|
+
findPatterns,
|
|
25
|
+
getConnections,
|
|
26
|
+
anticipateNeeds,
|
|
27
|
+
exportMemories,
|
|
28
|
+
importMemories,
|
|
29
|
+
pruneMemories,
|
|
30
|
+
MEMORY_TYPES,
|
|
31
|
+
IMPORTANCE_LEVELS
|
|
32
|
+
} from "../memory/index.js";
|
|
33
|
+
|
|
34
|
+
function getMemoryTypeDescription(type) {
|
|
35
|
+
const descriptions = {
|
|
36
|
+
architectural_decision: "Major design decisions about system architecture",
|
|
37
|
+
bugfix_pattern: "Solutions to bugs and issues encountered",
|
|
38
|
+
api_pattern: "Patterns and best practices for API design",
|
|
39
|
+
performance_insight: "Learnings from performance optimization work",
|
|
40
|
+
security_fix: "Security vulnerability fixes and patches",
|
|
41
|
+
refactor_technique: "Successful code refactoring patterns",
|
|
42
|
+
dependency_note: "Notes about project dependencies and versions",
|
|
43
|
+
workaround: "Temporary solutions to problems",
|
|
44
|
+
convention: "Established project conventions and standards",
|
|
45
|
+
context_boundary: "Defined system boundaries and limitations",
|
|
46
|
+
general_insight: "General insights and learnings",
|
|
47
|
+
session_summary: "Summary of work completed in a session"
|
|
48
|
+
};
|
|
49
|
+
return descriptions[type] || "Memory type description not available";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class MCPMemoryServer {
|
|
53
|
+
constructor() {
|
|
54
|
+
// Initialize database if not already done
|
|
55
|
+
if (!isDatabaseInitialized()) {
|
|
56
|
+
initDatabase();
|
|
57
|
+
console.error("Memory system initialized for MCP server");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.server = new McpServer({
|
|
61
|
+
name: "ac-framework-memory",
|
|
62
|
+
version: "1.0.0",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.setupTools();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setupTools() {
|
|
69
|
+
// ── memory_save ──────────────────────────────────────────────
|
|
70
|
+
this.server.tool(
|
|
71
|
+
"memory_save",
|
|
72
|
+
"Save a memory observation to the persistent knowledge base",
|
|
73
|
+
{
|
|
74
|
+
content: z.string().describe("Content to save in memory"),
|
|
75
|
+
type: z.enum(MEMORY_TYPES).default("general_insight").describe("Type of memory"),
|
|
76
|
+
importance: z.enum(IMPORTANCE_LEVELS).default("medium").describe("Importance level"),
|
|
77
|
+
tags: z.array(z.string()).optional().describe("Tags for categorization"),
|
|
78
|
+
projectPath: z.string().optional().describe("Associated project path"),
|
|
79
|
+
changeName: z.string().optional().describe("Associated change name"),
|
|
80
|
+
confidence: z.number().min(0).max(1).default(0.8).describe("Confidence score (0-1)")
|
|
81
|
+
},
|
|
82
|
+
async ({ content, type, importance, tags, projectPath, changeName, confidence }) => {
|
|
83
|
+
try {
|
|
84
|
+
const result = saveMemory({ content, type, importance, tags, projectPath, changeName, confidence });
|
|
85
|
+
return {
|
|
86
|
+
content: [{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: JSON.stringify({ success: true, id: result.id, operation: result.operation, revisionCount: result.revisionCount }, null, 2)
|
|
89
|
+
}]
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// ── memory_search ─────────────────────────────────────────────
|
|
98
|
+
this.server.tool(
|
|
99
|
+
"memory_search",
|
|
100
|
+
"Search memories using full-text search (FTS5)",
|
|
101
|
+
{
|
|
102
|
+
query: z.string().describe("Search query"),
|
|
103
|
+
limit: z.number().int().positive().default(10).describe("Maximum results"),
|
|
104
|
+
type: z.enum(MEMORY_TYPES).optional().describe("Filter by memory type"),
|
|
105
|
+
importance: z.enum(IMPORTANCE_LEVELS).optional().describe("Filter by importance"),
|
|
106
|
+
projectPath: z.string().optional().describe("Filter by project path"),
|
|
107
|
+
minConfidence: z.number().min(0).max(1).default(0).describe("Minimum confidence score")
|
|
108
|
+
},
|
|
109
|
+
async ({ query, limit, type, importance, projectPath, minConfidence }) => {
|
|
110
|
+
try {
|
|
111
|
+
const results = searchMemories(query, { limit, type, importance, projectPath, minConfidence });
|
|
112
|
+
return {
|
|
113
|
+
content: [{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: JSON.stringify({ query, count: results.length, results }, null, 2)
|
|
116
|
+
}]
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// ── memory_recall ─────────────────────────────────────────────
|
|
125
|
+
this.server.tool(
|
|
126
|
+
"memory_recall",
|
|
127
|
+
"Recall relevant context for a task or project",
|
|
128
|
+
{
|
|
129
|
+
task: z.string().optional().describe("Specific task to recall context for"),
|
|
130
|
+
projectPath: z.string().optional().describe("Project path"),
|
|
131
|
+
changeName: z.string().optional().describe("Change name"),
|
|
132
|
+
limit: z.number().int().positive().default(5).describe("Number of memories to return"),
|
|
133
|
+
days: z.number().int().positive().default(30).describe("Days to look back")
|
|
134
|
+
},
|
|
135
|
+
async ({ task, projectPath, changeName, limit, days }) => {
|
|
136
|
+
try {
|
|
137
|
+
const results = getContext({ projectPath, changeName, currentTask: task, limit, lookbackDays: days });
|
|
138
|
+
return {
|
|
139
|
+
content: [{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: JSON.stringify({ task: task || null, projectPath, count: results.length, memories: results }, null, 2)
|
|
142
|
+
}]
|
|
143
|
+
};
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// ── memory_get ────────────────────────────────────────────────
|
|
151
|
+
this.server.tool(
|
|
152
|
+
"memory_get",
|
|
153
|
+
"Get a specific memory by ID",
|
|
154
|
+
{
|
|
155
|
+
id: z.number().int().positive().describe("Memory ID")
|
|
156
|
+
},
|
|
157
|
+
async ({ id }) => {
|
|
158
|
+
try {
|
|
159
|
+
const memory = getMemory(id);
|
|
160
|
+
if (!memory) {
|
|
161
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Memory not found" }) }], isError: true };
|
|
162
|
+
}
|
|
163
|
+
return { content: [{ type: "text", text: JSON.stringify({ memory }, null, 2) }] };
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// ── memory_timeline ───────────────────────────────────────────
|
|
171
|
+
this.server.tool(
|
|
172
|
+
"memory_timeline",
|
|
173
|
+
"Get chronological timeline around a specific memory",
|
|
174
|
+
{
|
|
175
|
+
id: z.number().int().positive().describe("Memory ID"),
|
|
176
|
+
window: z.number().int().positive().default(3).describe("Number of memories before/after")
|
|
177
|
+
},
|
|
178
|
+
async ({ id, window }) => {
|
|
179
|
+
try {
|
|
180
|
+
const timeline = getTimeline(id, { window });
|
|
181
|
+
if (!timeline) {
|
|
182
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Memory not found" }) }], isError: true };
|
|
183
|
+
}
|
|
184
|
+
return { content: [{ type: "text", text: JSON.stringify(timeline, null, 2) }] };
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// ── memory_stats ──────────────────────────────────────────────
|
|
192
|
+
this.server.tool(
|
|
193
|
+
"memory_stats",
|
|
194
|
+
"Get memory system statistics",
|
|
195
|
+
{
|
|
196
|
+
projectPath: z.string().optional().describe("Filter by project path"),
|
|
197
|
+
since: z.string().optional().describe("ISO date string to filter from")
|
|
198
|
+
},
|
|
199
|
+
async ({ projectPath, since }) => {
|
|
200
|
+
try {
|
|
201
|
+
const stats = getStats({ projectPath, since });
|
|
202
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// ── memory_patterns ───────────────────────────────────────────
|
|
210
|
+
this.server.tool(
|
|
211
|
+
"memory_patterns",
|
|
212
|
+
"Find patterns and clusters in the memory system",
|
|
213
|
+
{
|
|
214
|
+
type: z.enum(MEMORY_TYPES).optional().describe("Filter by memory type"),
|
|
215
|
+
minFrequency: z.number().int().positive().default(2).describe("Minimum frequency for pattern detection")
|
|
216
|
+
},
|
|
217
|
+
async ({ type, minFrequency }) => {
|
|
218
|
+
try {
|
|
219
|
+
const patterns = findPatterns({ type, minFrequency });
|
|
220
|
+
return { content: [{ type: "text", text: JSON.stringify({ patterns }, null, 2) }] };
|
|
221
|
+
} catch (error) {
|
|
222
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// ── memory_anticipate ─────────────────────────────────────────
|
|
228
|
+
this.server.tool(
|
|
229
|
+
"memory_anticipate",
|
|
230
|
+
"Anticipate memories that will be needed for a future task",
|
|
231
|
+
{
|
|
232
|
+
task: z.string().describe("Task to anticipate needs for"),
|
|
233
|
+
projectPath: z.string().optional().describe("Project path"),
|
|
234
|
+
limit: z.number().int().positive().default(5).describe("Number of suggestions")
|
|
235
|
+
},
|
|
236
|
+
async ({ task, projectPath, limit }) => {
|
|
237
|
+
try {
|
|
238
|
+
const memories = anticipateNeeds(task, projectPath);
|
|
239
|
+
return {
|
|
240
|
+
content: [{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: JSON.stringify({ task, suggestions: memories.slice(0, limit) }, null, 2)
|
|
243
|
+
}]
|
|
244
|
+
};
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// ── memory_export ─────────────────────────────────────────────
|
|
252
|
+
this.server.tool(
|
|
253
|
+
"memory_export",
|
|
254
|
+
"Export memories to JSON (to file or as response)",
|
|
255
|
+
{
|
|
256
|
+
filePath: z.string().optional().describe("File path to export to (optional, returns inline if omitted)"),
|
|
257
|
+
shareableOnly: z.boolean().default(true).describe("Only export shareable memories"),
|
|
258
|
+
projectPath: z.string().optional().describe("Filter by project path"),
|
|
259
|
+
since: z.string().optional().describe("ISO date string to filter from")
|
|
260
|
+
},
|
|
261
|
+
async ({ filePath, shareableOnly, projectPath, since }) => {
|
|
262
|
+
try {
|
|
263
|
+
const memories = exportMemories({ shareableOnly, projectPath, since });
|
|
264
|
+
const exportData = { exportedAt: new Date().toISOString(), count: memories.length, memories };
|
|
265
|
+
if (filePath) {
|
|
266
|
+
const { writeFileSync } = await import("node:fs");
|
|
267
|
+
writeFileSync(filePath, JSON.stringify(exportData, null, 2));
|
|
268
|
+
return {
|
|
269
|
+
content: [{
|
|
270
|
+
type: "text",
|
|
271
|
+
text: JSON.stringify({ success: true, message: `Exported ${memories.length} memories to ${filePath}` }, null, 2)
|
|
272
|
+
}]
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return { content: [{ type: "text", text: JSON.stringify(exportData, null, 2) }] };
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// ── memory_import ─────────────────────────────────────────────
|
|
283
|
+
this.server.tool(
|
|
284
|
+
"memory_import",
|
|
285
|
+
"Import memories from a JSON file",
|
|
286
|
+
{
|
|
287
|
+
filePath: z.string().describe("File path to import from"),
|
|
288
|
+
merge: z.boolean().default(true).describe("Merge with existing memories")
|
|
289
|
+
},
|
|
290
|
+
async ({ filePath, merge }) => {
|
|
291
|
+
try {
|
|
292
|
+
const { readFileSync } = await import("node:fs");
|
|
293
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
294
|
+
const memories = data.memories || data;
|
|
295
|
+
const results = importMemories(memories, { merge });
|
|
296
|
+
const success = results.filter(r => r.success);
|
|
297
|
+
const failed = results.filter(r => !r.success);
|
|
298
|
+
return {
|
|
299
|
+
content: [{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: JSON.stringify({ imported: success.length, failed: failed.length, results }, null, 2)
|
|
302
|
+
}]
|
|
303
|
+
};
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// ── memory_prune ──────────────────────────────────────────────
|
|
311
|
+
this.server.tool(
|
|
312
|
+
"memory_prune",
|
|
313
|
+
"Prune obsolete or low-confidence memories",
|
|
314
|
+
{
|
|
315
|
+
olderThanDays: z.number().int().positive().default(90).describe("Delete memories older than this many days"),
|
|
316
|
+
dryRun: z.boolean().default(false).describe("Show what would be pruned without actually deleting")
|
|
317
|
+
},
|
|
318
|
+
async ({ olderThanDays, dryRun }) => {
|
|
319
|
+
try {
|
|
320
|
+
const result = pruneMemories({ olderThanDays, dryRun });
|
|
321
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
322
|
+
} catch (error) {
|
|
323
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], isError: true };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async run() {
|
|
330
|
+
const transport = new StdioServerTransport();
|
|
331
|
+
await this.server.connect(transport);
|
|
332
|
+
console.error("AC Framework Memory MCP Server running on stdio");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Start server if called directly
|
|
337
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
338
|
+
const server = new MCPMemoryServer();
|
|
339
|
+
server.run().catch(error => {
|
|
340
|
+
console.error("Failed to start MCP server:", error);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export default MCPMemoryServer;
|