@telvok/librarian-mcp 1.3.0 → 1.5.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/dist/library/parsers/index.d.ts +2 -0
- package/dist/library/parsers/index.js +2 -0
- package/dist/library/parsers/json.d.ts +11 -0
- package/dist/library/parsers/json.js +95 -0
- package/dist/library/parsers/sqlite.d.ts +8 -0
- package/dist/library/parsers/sqlite.js +123 -0
- package/dist/library/parsers/types.d.ts +1 -1
- package/dist/tools/import-memories.js +17 -3
- package/package.json +3 -1
|
@@ -2,3 +2,5 @@ export type { ParsedEntry, ParseResult } from './types.js';
|
|
|
2
2
|
export { parseJSONL } from './jsonl.js';
|
|
3
3
|
export { parseMarkdown } from './markdown.js';
|
|
4
4
|
export { parseCursorMemory } from './cursor.js';
|
|
5
|
+
export { parseJSON } from './json.js';
|
|
6
|
+
export { parseSQLite } from './sqlite.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ParseResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a JSON file containing an array of memory entries.
|
|
4
|
+
*
|
|
5
|
+
* Supports various common structures:
|
|
6
|
+
* - Array of objects with title/content
|
|
7
|
+
* - Array of objects with name/description
|
|
8
|
+
* - Object with entries array
|
|
9
|
+
* - Object with memories array
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseJSON(filePath: string): Promise<ParseResult>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a JSON file containing an array of memory entries.
|
|
4
|
+
*
|
|
5
|
+
* Supports various common structures:
|
|
6
|
+
* - Array of objects with title/content
|
|
7
|
+
* - Array of objects with name/description
|
|
8
|
+
* - Object with entries array
|
|
9
|
+
* - Object with memories array
|
|
10
|
+
*/
|
|
11
|
+
export async function parseJSON(filePath) {
|
|
12
|
+
const entries = [];
|
|
13
|
+
const errors = [];
|
|
14
|
+
let skipped = 0;
|
|
15
|
+
try {
|
|
16
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
17
|
+
const data = JSON.parse(content);
|
|
18
|
+
// Handle different JSON structures
|
|
19
|
+
let items = [];
|
|
20
|
+
if (Array.isArray(data)) {
|
|
21
|
+
// Direct array of entries
|
|
22
|
+
items = data;
|
|
23
|
+
}
|
|
24
|
+
else if (typeof data === 'object' && data !== null) {
|
|
25
|
+
// Object with entries/memories/items array
|
|
26
|
+
if (Array.isArray(data.entries)) {
|
|
27
|
+
items = data.entries;
|
|
28
|
+
}
|
|
29
|
+
else if (Array.isArray(data.memories)) {
|
|
30
|
+
items = data.memories;
|
|
31
|
+
}
|
|
32
|
+
else if (Array.isArray(data.items)) {
|
|
33
|
+
items = data.items;
|
|
34
|
+
}
|
|
35
|
+
else if (Array.isArray(data.data)) {
|
|
36
|
+
items = data.data;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Single object - treat as one entry
|
|
40
|
+
items = [data];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (let i = 0; i < items.length; i++) {
|
|
44
|
+
const item = items[i];
|
|
45
|
+
if (typeof item !== 'object' || item === null) {
|
|
46
|
+
skipped++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Extract title
|
|
50
|
+
const title = item.title || item.name || item.key || `Entry ${i + 1}`;
|
|
51
|
+
// Extract content
|
|
52
|
+
let entryContent = item.content ||
|
|
53
|
+
item.text ||
|
|
54
|
+
item.description ||
|
|
55
|
+
item.value ||
|
|
56
|
+
item.memory ||
|
|
57
|
+
item.observation;
|
|
58
|
+
// Handle observations array
|
|
59
|
+
if (!entryContent && item.observations && Array.isArray(item.observations)) {
|
|
60
|
+
entryContent = item.observations.join('\n\n');
|
|
61
|
+
}
|
|
62
|
+
if (!entryContent) {
|
|
63
|
+
skipped++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Extract context
|
|
67
|
+
let context;
|
|
68
|
+
if (item.context) {
|
|
69
|
+
context = item.context;
|
|
70
|
+
}
|
|
71
|
+
else if (item.category) {
|
|
72
|
+
context = item.category;
|
|
73
|
+
}
|
|
74
|
+
else if (item.type) {
|
|
75
|
+
context = item.type;
|
|
76
|
+
}
|
|
77
|
+
else if (item.tags && Array.isArray(item.tags)) {
|
|
78
|
+
context = item.tags.join(', ');
|
|
79
|
+
}
|
|
80
|
+
entries.push({
|
|
81
|
+
title: String(title),
|
|
82
|
+
content: String(entryContent),
|
|
83
|
+
context,
|
|
84
|
+
intent: item.intent ? String(item.intent) : undefined,
|
|
85
|
+
reasoning: item.reasoning ? String(item.reasoning) : undefined,
|
|
86
|
+
example: item.example ? String(item.example) : undefined,
|
|
87
|
+
source: 'json',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
errors.push(`Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
93
|
+
}
|
|
94
|
+
return { entries, skipped, errors };
|
|
95
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParseResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a SQLite database file for memory entries.
|
|
4
|
+
*
|
|
5
|
+
* Auto-detects table and column names from common patterns.
|
|
6
|
+
* Supports mcp-memory-service, SQLite-vec, and custom schemas.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseSQLite(filePath: string): Promise<ParseResult>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// SQLite Parser - mcp-memory-service, SQLite-vec, custom databases
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Common table names for memory storage
|
|
6
|
+
const TABLE_CANDIDATES = ['memories', 'entries', 'knowledge', 'notes', 'items', 'documents'];
|
|
7
|
+
// Common column names for different fields
|
|
8
|
+
const TITLE_CANDIDATES = ['title', 'name', 'key', 'id', 'subject', 'heading'];
|
|
9
|
+
const CONTENT_CANDIDATES = ['content', 'text', 'value', 'memory', 'observation', 'body', 'description', 'note'];
|
|
10
|
+
const CONTEXT_CANDIDATES = ['context', 'category', 'type', 'tags', 'topic', 'area'];
|
|
11
|
+
/**
|
|
12
|
+
* Parse a SQLite database file for memory entries.
|
|
13
|
+
*
|
|
14
|
+
* Auto-detects table and column names from common patterns.
|
|
15
|
+
* Supports mcp-memory-service, SQLite-vec, and custom schemas.
|
|
16
|
+
*/
|
|
17
|
+
export async function parseSQLite(filePath) {
|
|
18
|
+
const entries = [];
|
|
19
|
+
const errors = [];
|
|
20
|
+
let skipped = 0;
|
|
21
|
+
let db = null;
|
|
22
|
+
try {
|
|
23
|
+
db = new Database(filePath, { readonly: true });
|
|
24
|
+
// Find a suitable table
|
|
25
|
+
const tables = db
|
|
26
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table'")
|
|
27
|
+
.all();
|
|
28
|
+
const tableNames = tables.map((t) => t.name.toLowerCase());
|
|
29
|
+
const targetTable = TABLE_CANDIDATES.find((candidate) => tableNames.includes(candidate));
|
|
30
|
+
if (!targetTable) {
|
|
31
|
+
errors.push(`No memory table found. Available tables: ${tableNames.join(', ')}. ` +
|
|
32
|
+
`Expected one of: ${TABLE_CANDIDATES.join(', ')}`);
|
|
33
|
+
return { entries, skipped, errors };
|
|
34
|
+
}
|
|
35
|
+
// Get column info for the target table
|
|
36
|
+
const columns = db.prepare(`PRAGMA table_info(${targetTable})`).all();
|
|
37
|
+
const columnNames = columns.map((c) => c.name.toLowerCase());
|
|
38
|
+
// Find content column (required)
|
|
39
|
+
const contentColumn = CONTENT_CANDIDATES.find((candidate) => columnNames.includes(candidate));
|
|
40
|
+
if (!contentColumn) {
|
|
41
|
+
errors.push(`No content column found in table "${targetTable}". ` +
|
|
42
|
+
`Available columns: ${columnNames.join(', ')}. ` +
|
|
43
|
+
`Expected one of: ${CONTENT_CANDIDATES.join(', ')}`);
|
|
44
|
+
return { entries, skipped, errors };
|
|
45
|
+
}
|
|
46
|
+
// Find optional columns
|
|
47
|
+
const titleColumn = TITLE_CANDIDATES.find((candidate) => columnNames.includes(candidate));
|
|
48
|
+
const contextColumn = CONTEXT_CANDIDATES.find((candidate) => columnNames.includes(candidate));
|
|
49
|
+
// Build and execute query
|
|
50
|
+
const selectColumns = [
|
|
51
|
+
contentColumn,
|
|
52
|
+
titleColumn,
|
|
53
|
+
contextColumn,
|
|
54
|
+
].filter(Boolean);
|
|
55
|
+
const query = `SELECT ${selectColumns.join(', ')} FROM ${targetTable}`;
|
|
56
|
+
const rows = db.prepare(query).all();
|
|
57
|
+
for (let i = 0; i < rows.length; i++) {
|
|
58
|
+
const row = rows[i];
|
|
59
|
+
// Extract content
|
|
60
|
+
const content = row[contentColumn];
|
|
61
|
+
if (!content || typeof content !== 'string' || !content.trim()) {
|
|
62
|
+
skipped++;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Extract title
|
|
66
|
+
let title;
|
|
67
|
+
if (titleColumn && row[titleColumn]) {
|
|
68
|
+
title = String(row[titleColumn]);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Generate title from content
|
|
72
|
+
title = content.slice(0, 60).replace(/\s+/g, ' ').trim();
|
|
73
|
+
if (content.length > 60)
|
|
74
|
+
title += '...';
|
|
75
|
+
}
|
|
76
|
+
// Extract context
|
|
77
|
+
let context;
|
|
78
|
+
if (contextColumn && row[contextColumn]) {
|
|
79
|
+
const contextValue = row[contextColumn];
|
|
80
|
+
if (typeof contextValue === 'string') {
|
|
81
|
+
context = contextValue;
|
|
82
|
+
}
|
|
83
|
+
else if (Array.isArray(contextValue)) {
|
|
84
|
+
context = contextValue.join(', ');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
entries.push({
|
|
88
|
+
title,
|
|
89
|
+
content: content.trim(),
|
|
90
|
+
context,
|
|
91
|
+
source: 'sqlite',
|
|
92
|
+
originalPath: `${filePath}:${targetTable}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error instanceof Error) {
|
|
98
|
+
if (error.message.includes('SQLITE_CANTOPEN')) {
|
|
99
|
+
errors.push(`Cannot open database file: ${filePath}`);
|
|
100
|
+
}
|
|
101
|
+
else if (error.message.includes('file is not a database')) {
|
|
102
|
+
errors.push(`File is not a valid SQLite database: ${filePath}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
errors.push(`SQLite error: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
errors.push(`Unknown error: ${String(error)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
if (db) {
|
|
114
|
+
try {
|
|
115
|
+
db.close();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Ignore close errors
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { entries, skipped, errors };
|
|
123
|
+
}
|
|
@@ -2,7 +2,7 @@ import * as fs from 'fs/promises';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { getLibraryPath, getLocalPath } from '../library/storage.js';
|
|
4
4
|
import { loadIndex, saveIndex, addToIndex } from '../library/vector-index.js';
|
|
5
|
-
import { parseJSONL, parseMarkdown, parseCursorMemory } from '../library/parsers/index.js';
|
|
5
|
+
import { parseJSONL, parseMarkdown, parseCursorMemory, parseJSON, parseSQLite } from '../library/parsers/index.js';
|
|
6
6
|
// ============================================================================
|
|
7
7
|
// Tool Definition
|
|
8
8
|
// ============================================================================
|
|
@@ -14,19 +14,23 @@ Supported formats:
|
|
|
14
14
|
- jsonl: Anthropic MCP Memory, mcp-knowledge-graph (.jsonl files)
|
|
15
15
|
- markdown: Basic Memory, Obsidian, any .md files
|
|
16
16
|
- cursor: Cursor Memory Bank (.cursor-memory/)
|
|
17
|
+
- json: Simple memory servers, knowledge stores (.json files)
|
|
18
|
+
- sqlite: mcp-memory-service, SQLite-vec (.db, .sqlite files)
|
|
17
19
|
|
|
18
20
|
Imports go to .librarian/local/[source-name]/ and are automatically indexed for semantic search.
|
|
19
21
|
|
|
20
22
|
Examples:
|
|
21
23
|
- import_memories({ format: "jsonl", path: "~/.aim/memory.jsonl", source_name: "anthropic-memory" })
|
|
22
24
|
- import_memories({ format: "markdown", path: "~/basic-memory/", source_name: "basic-memory" })
|
|
23
|
-
- import_memories({ format: "cursor", path: ".cursor-memory/", source_name: "cursor-memory" })
|
|
25
|
+
- import_memories({ format: "cursor", path: ".cursor-memory/", source_name: "cursor-memory" })
|
|
26
|
+
- import_memories({ format: "json", path: "~/memories.json", source_name: "json-memory" })
|
|
27
|
+
- import_memories({ format: "sqlite", path: "~/memory.db", source_name: "sqlite-memory" })`,
|
|
24
28
|
inputSchema: {
|
|
25
29
|
type: 'object',
|
|
26
30
|
properties: {
|
|
27
31
|
format: {
|
|
28
32
|
type: 'string',
|
|
29
|
-
enum: ['jsonl', 'markdown', 'cursor'],
|
|
33
|
+
enum: ['jsonl', 'markdown', 'cursor', 'json', 'sqlite'],
|
|
30
34
|
description: 'Format of the source memories',
|
|
31
35
|
},
|
|
32
36
|
path: {
|
|
@@ -74,6 +78,12 @@ Examples:
|
|
|
74
78
|
case 'cursor':
|
|
75
79
|
parseResult = await parseCursorMemory(expandedPath);
|
|
76
80
|
break;
|
|
81
|
+
case 'json':
|
|
82
|
+
parseResult = await parseJSON(expandedPath);
|
|
83
|
+
break;
|
|
84
|
+
case 'sqlite':
|
|
85
|
+
parseResult = await parseSQLite(expandedPath);
|
|
86
|
+
break;
|
|
77
87
|
default:
|
|
78
88
|
throw new Error(`Unknown format: ${format}`);
|
|
79
89
|
}
|
|
@@ -133,6 +143,10 @@ function generateSourceName(format, inputPath) {
|
|
|
133
143
|
return 'cursor-memory';
|
|
134
144
|
case 'markdown':
|
|
135
145
|
return basename.replace(/[^a-z0-9-]/gi, '-').toLowerCase() || 'imported-markdown';
|
|
146
|
+
case 'json':
|
|
147
|
+
return basename.replace(/[^a-z0-9-]/gi, '-').toLowerCase() || 'imported-json';
|
|
148
|
+
case 'sqlite':
|
|
149
|
+
return basename.replace(/[^a-z0-9-]/gi, '-').toLowerCase() || 'imported-sqlite';
|
|
136
150
|
default:
|
|
137
151
|
return `imported-${format}`;
|
|
138
152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telvok/librarian-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Knowledge capture MCP server - remember what you learn with AI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -33,12 +33,14 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@huggingface/transformers": "^3.0.0",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
+
"better-sqlite3": "^11.0.0",
|
|
36
37
|
"glob": "^11.0.0",
|
|
37
38
|
"gray-matter": "^4.0.3",
|
|
38
39
|
"uuid": "^11.0.0",
|
|
39
40
|
"zod": "^3.24.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
43
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
42
44
|
"@types/node": "^22.0.0",
|
|
43
45
|
"@types/uuid": "^10.0.0",
|
|
44
46
|
"typescript": "^5.7.0"
|