@iflow-mcp/joleyline-mcp-memory-libsql 0.0.14

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Scott Spence
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # mcp-memory-libsql
2
+
3
+ A high-performance, persistent memory system for the Model Context
4
+ Protocol (MCP) powered by libSQL. This server provides vector search
5
+ capabilities and efficient knowledge storage using libSQL as the
6
+ backing store.
7
+
8
+ <a href="https://glama.ai/mcp/servers/22lg4lq768">
9
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/22lg4lq768/badge" alt="Glama badge" />
10
+ </a>
11
+
12
+ ## Features
13
+
14
+ - 🚀 High-performance vector search using libSQL
15
+ - 💾 Persistent storage of entities and relations
16
+ - 🔍 Semantic search capabilities
17
+ - 🔄 Knowledge graph management
18
+ - 🌐 Compatible with local and remote libSQL databases
19
+ - 🔒 Secure token-based authentication for remote databases
20
+
21
+ ## Configuration
22
+
23
+ This server is designed to be used as part of an MCP configuration.
24
+ Here are examples for different environments:
25
+
26
+ ### Cline Configuration
27
+
28
+ Add this to your Cline MCP settings:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "mcp-memory-libsql": {
34
+ "command": "npx",
35
+ "args": ["-y", "mcp-memory-libsql"],
36
+ "env": {
37
+ "LIBSQL_URL": "file:/path/to/your/database.db"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### Claude Desktop with WSL Configuration
45
+
46
+ For a detailed guide on setting up this server with Claude Desktop in
47
+ WSL, see
48
+ [Getting MCP Server Working with Claude Desktop in WSL](https://scottspence.com/posts/getting-mcp-server-working-with-claude-desktop-in-wsl).
49
+
50
+ Add this to your Claude Desktop configuration for WSL environments:
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "mcp-memory-libsql": {
56
+ "command": "wsl.exe",
57
+ "args": [
58
+ "bash",
59
+ "-c",
60
+ "source ~/.nvm/nvm.sh && LIBSQL_URL=file:/path/to/database.db /home/username/.nvm/versions/node/v20.12.1/bin/npx mcp-memory-libsql"
61
+ ]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### Database Configuration
68
+
69
+ The server supports both local SQLite and remote libSQL databases
70
+ through the LIBSQL_URL environment variable:
71
+
72
+ For local SQLite databases:
73
+
74
+ ```json
75
+ {
76
+ "env": {
77
+ "LIBSQL_URL": "file:/path/to/database.db"
78
+ }
79
+ }
80
+ ```
81
+
82
+ For remote libSQL databases (e.g., Turso):
83
+
84
+ ```json
85
+ {
86
+ "env": {
87
+ "LIBSQL_URL": "libsql://your-database.turso.io",
88
+ "LIBSQL_AUTH_TOKEN": "your-auth-token"
89
+ }
90
+ }
91
+ ```
92
+
93
+ Note: When using WSL, ensure the database path uses the Linux
94
+ filesystem format (e.g., `/home/username/...`) rather than Windows
95
+ format.
96
+
97
+ By default, if no URL is provided, it will use `file:/memory-tool.db`
98
+ in the current directory.
99
+
100
+ ## API
101
+
102
+ The server implements the standard MCP memory interface with
103
+ additional vector search capabilities:
104
+
105
+ - Entity Management
106
+ - Create/Update entities with embeddings
107
+ - Delete entities
108
+ - Search entities by similarity
109
+ - Relation Management
110
+ - Create relations between entities
111
+ - Delete relations
112
+ - Query related entities
113
+
114
+ ## Architecture
115
+
116
+ The server uses a libSQL database with the following schema:
117
+
118
+ - Entities table: Stores entity information and embeddings
119
+ - Relations table: Stores relationships between entities
120
+ - Vector search capabilities implemented using libSQL's built-in
121
+ vector operations
122
+
123
+ ## Development
124
+
125
+ ### Publishing
126
+
127
+ Due to npm 2FA requirements, publishing needs to be done manually:
128
+
129
+ 1. Create a changeset (documents your changes):
130
+
131
+ ```bash
132
+ pnpm changeset
133
+ ```
134
+
135
+ 2. Version the package (updates version and CHANGELOG):
136
+
137
+ ```bash
138
+ pnpm changeset version
139
+ ```
140
+
141
+ 3. Publish to npm (will prompt for 2FA code):
142
+
143
+ ```bash
144
+ pnpm release
145
+ ```
146
+
147
+ ## Contributing
148
+
149
+ Contributions are welcome! Please read our contributing guidelines
150
+ before submitting pull requests.
151
+
152
+ ## License
153
+
154
+ MIT License - see the [LICENSE](LICENSE) file for details.
155
+
156
+ ## Acknowledgments
157
+
158
+ - Built on the
159
+ [Model Context Protocol](https://github.com/modelcontextprotocol)
160
+ - Powered by [libSQL](https://github.com/tursodatabase/libsql)
@@ -0,0 +1,89 @@
1
+ import { config } from 'dotenv';
2
+ import { join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+ import { readFileSync } from 'fs';
6
+ // Load environment variables from .env file
7
+ config();
8
+ // Get directory paths
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const rootDir = join(__dirname, '..', '..');
12
+ /**
13
+ * Default configuration values
14
+ */
15
+ const defaultConfig = {
16
+ server: {
17
+ name: 'mcp-memory-libsql',
18
+ version: '0.0.14', // This should be read from package.json
19
+ transport: 'stdio', // Default to stdio transport
20
+ sseOptions: {
21
+ port: 3000,
22
+ host: 'localhost',
23
+ cors: true,
24
+ },
25
+ },
26
+ database: {
27
+ url: 'file:memory.db',
28
+ },
29
+ embedding: {
30
+ model: 'Xenova/bge-small-en-v1.5',
31
+ dimension: 384,
32
+ cachePath: join(rootDir, 'models'),
33
+ },
34
+ logging: {
35
+ level: 'info',
36
+ includeTimestamps: true,
37
+ },
38
+ };
39
+ /**
40
+ * Load configuration from environment variables and defaults
41
+ */
42
+ export function loadConfig() {
43
+ try {
44
+ // Read package.json for name and version
45
+ const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8'));
46
+ // Create configuration with environment variables and defaults
47
+ const config = {
48
+ server: {
49
+ name: packageJson.name || defaultConfig.server.name,
50
+ version: packageJson.version || defaultConfig.server.version,
51
+ transport: process.env.TRANSPORT_TYPE || defaultConfig.server.transport,
52
+ sseOptions: defaultConfig.server.sseOptions,
53
+ },
54
+ database: {
55
+ url: process.env.DATABASE_URL || defaultConfig.database.url,
56
+ authToken: process.env.DATABASE_AUTH_TOKEN,
57
+ },
58
+ embedding: {
59
+ model: process.env.EMBEDDING_MODEL || defaultConfig.embedding.model,
60
+ dimension: parseInt(process.env.EMBEDDING_DIMENSION || String(defaultConfig.embedding.dimension), 10),
61
+ cachePath: process.env.MODEL_CACHE_PATH || defaultConfig.embedding.cachePath,
62
+ },
63
+ logging: {
64
+ level: process.env.LOG_LEVEL || defaultConfig.logging.level,
65
+ includeTimestamps: process.env.LOG_TIMESTAMPS === 'true' || defaultConfig.logging.includeTimestamps,
66
+ },
67
+ };
68
+ // Override SSE options if provided in environment variables
69
+ if (config.server.transport === 'sse') {
70
+ config.server.sseOptions = {
71
+ port: process.env.SSE_PORT ? parseInt(process.env.SSE_PORT, 10) : defaultConfig.server.sseOptions.port,
72
+ host: process.env.SSE_HOST || defaultConfig.server.sseOptions.host,
73
+ cors: process.env.SSE_CORS ? process.env.SSE_CORS === 'true' : defaultConfig.server.sseOptions.cors,
74
+ };
75
+ }
76
+ return config;
77
+ }
78
+ catch (error) {
79
+ console.error('Error loading configuration:', error);
80
+ return defaultConfig;
81
+ }
82
+ }
83
+ // Export the configuration
84
+ export const appConfig = loadConfig();
85
+ // Export specific configurations for convenience
86
+ export const serverConfig = appConfig.server;
87
+ export const databaseConfig = appConfig.database;
88
+ export const embeddingConfig = appConfig.embedding;
89
+ export const loggingConfig = appConfig.logging;
@@ -0,0 +1,234 @@
1
+ import { createClient } from '@libsql/client';
2
+ import { logger } from '../utils/logger.js';
3
+ import { EMBEDDING_DIMENSION } from '../services/embedding-service.js';
4
+ import { createEntities, getEntity, getRecentEntities, deleteEntity } from '../services/entity-service.js';
5
+ import { searchSimilar, searchEntities } from './index.js';
6
+ import { createRelations, deleteRelation, getRelationsForEntities } from '../services/relation-service.js';
7
+ import { readGraph, searchNodes } from '../services/graph-service.js';
8
+ /**
9
+ * Core DatabaseManager class that handles database connection and initialization
10
+ */
11
+ export class DatabaseManager {
12
+ /**
13
+ * Private constructor to enforce singleton pattern
14
+ * @param config - Database configuration
15
+ */
16
+ constructor(config) {
17
+ if (!config.url) {
18
+ throw new Error('Database URL is required');
19
+ }
20
+ this.client = createClient({
21
+ url: config.url,
22
+ authToken: config.authToken,
23
+ });
24
+ }
25
+ /**
26
+ * Gets the singleton instance of DatabaseManager
27
+ * @param config - Database configuration
28
+ * @returns DatabaseManager instance
29
+ */
30
+ static async getInstance(config) {
31
+ if (!DatabaseManager.instance) {
32
+ DatabaseManager.instance = new DatabaseManager(config);
33
+ await DatabaseManager.instance.initialize();
34
+ }
35
+ return DatabaseManager.instance;
36
+ }
37
+ /**
38
+ * Gets the singleton instance of DatabaseManager (snake_case alias for backward compatibility)
39
+ * @param config - Database configuration
40
+ * @returns DatabaseManager instance
41
+ */
42
+ static async get_instance(config) {
43
+ return DatabaseManager.getInstance(config);
44
+ }
45
+ /**
46
+ * Gets the database client
47
+ * @returns Database client
48
+ */
49
+ getClient() {
50
+ return this.client;
51
+ }
52
+ /**
53
+ * Gets the database client (snake_case alias for backward compatibility)
54
+ * @returns Database client
55
+ */
56
+ get_client() {
57
+ return this.client;
58
+ }
59
+ /**
60
+ * Initializes the database schema
61
+ */
62
+ async initialize() {
63
+ try {
64
+ logger.info(`Initializing database schema with vector dimension: ${EMBEDDING_DIMENSION}`);
65
+ // Create tables if they don't exist - each as a single statement
66
+ await this.client.execute({
67
+ sql: `
68
+ CREATE TABLE IF NOT EXISTS entities (
69
+ name TEXT PRIMARY KEY,
70
+ entity_type TEXT NOT NULL,
71
+ embedding F32_BLOB(${EMBEDDING_DIMENSION}), -- Using configurable dimension
72
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
73
+ )
74
+ `
75
+ });
76
+ await this.client.execute({
77
+ sql: `
78
+ CREATE TABLE IF NOT EXISTS observations (
79
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
80
+ entity_name TEXT NOT NULL,
81
+ content TEXT NOT NULL,
82
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
83
+ FOREIGN KEY (entity_name) REFERENCES entities(name)
84
+ )
85
+ `
86
+ });
87
+ await this.client.execute({
88
+ sql: `
89
+ CREATE TABLE IF NOT EXISTS relations (
90
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
91
+ source TEXT NOT NULL,
92
+ target TEXT NOT NULL,
93
+ relation_type TEXT NOT NULL,
94
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
95
+ FOREIGN KEY (source) REFERENCES entities(name),
96
+ FOREIGN KEY (target) REFERENCES entities(name)
97
+ )
98
+ `
99
+ });
100
+ // Create all indexes in a single batch transaction
101
+ await this.client.batch([
102
+ {
103
+ sql: 'CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name)',
104
+ args: [],
105
+ },
106
+ {
107
+ sql: 'CREATE INDEX IF NOT EXISTS idx_observations_entity ON observations(entity_name)',
108
+ args: [],
109
+ },
110
+ {
111
+ sql: 'CREATE INDEX IF NOT EXISTS idx_relations_source ON relations(source)',
112
+ args: [],
113
+ },
114
+ {
115
+ sql: 'CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target)',
116
+ args: [],
117
+ },
118
+ {
119
+ sql: 'CREATE INDEX IF NOT EXISTS idx_entities_embedding ON entities(libsql_vector_idx(embedding))',
120
+ args: [],
121
+ },
122
+ ], 'write');
123
+ }
124
+ catch (error) {
125
+ throw new Error(`Database initialization failed: ${error instanceof Error ? error.message : String(error)}`);
126
+ }
127
+ }
128
+ /**
129
+ * Creates or updates entities with observations and optional embeddings
130
+ * @param entities - Array of entities to create or update
131
+ */
132
+ async create_entities(entities) {
133
+ return createEntities(entities);
134
+ }
135
+ /**
136
+ * Searches for entities by similarity to the provided embedding vector
137
+ * @param embedding - Vector embedding to search with
138
+ * @param limit - Maximum number of results to return
139
+ * @param includeEmbeddings - Whether to include embeddings in the returned entities (default: false)
140
+ * @returns Array of search results with entities and distance scores
141
+ */
142
+ async search_similar(embedding, limit = 5, includeEmbeddings = false) {
143
+ const client = this.getClient();
144
+ return searchSimilar(client, embedding, limit, includeEmbeddings);
145
+ }
146
+ /**
147
+ * Gets an entity by name
148
+ * @param name - Name of the entity to retrieve
149
+ * @param includeEmbeddings - Whether to include embeddings in the returned entity (default: false)
150
+ * @returns Entity object with observations and optional embedding
151
+ */
152
+ async get_entity(name, includeEmbeddings = false) {
153
+ return getEntity(name, includeEmbeddings);
154
+ }
155
+ /**
156
+ * Searches for entities by text query in name, type, or observations
157
+ * @param query - Text query to search for
158
+ * @param includeEmbeddings - Whether to include embeddings in the returned entities (default: false)
159
+ * @returns Array of matching entities
160
+ */
161
+ async search_entities(query, includeEmbeddings = false) {
162
+ const client = this.getClient();
163
+ return searchEntities(client, query, includeEmbeddings);
164
+ }
165
+ /**
166
+ * Gets the most recently created entities
167
+ * @param limit - Maximum number of entities to return
168
+ * @param includeEmbeddings - Whether to include embeddings in the returned entities (default: false)
169
+ * @returns Array of recent entities
170
+ */
171
+ async get_recent_entities(limit = 10, includeEmbeddings = false) {
172
+ return getRecentEntities(limit, includeEmbeddings);
173
+ }
174
+ /**
175
+ * Creates relations between entities
176
+ * @param relations - Array of relations to create
177
+ */
178
+ async create_relations(relations) {
179
+ return createRelations(relations);
180
+ }
181
+ /**
182
+ * Deletes an entity and all its associated data
183
+ * @param name - Name of the entity to delete
184
+ */
185
+ async delete_entity(name) {
186
+ return deleteEntity(name);
187
+ }
188
+ /**
189
+ * Deletes a specific relation between entities
190
+ * @param source - Source entity name
191
+ * @param target - Target entity name
192
+ * @param type - Relation type
193
+ */
194
+ async delete_relation(source, target, type) {
195
+ return deleteRelation(source, target, type);
196
+ }
197
+ /**
198
+ * Gets relations for a set of entities
199
+ * @param entities - Array of entities to get relations for
200
+ * @returns Array of relations
201
+ */
202
+ async get_relations_for_entities(entities) {
203
+ const entityNames = entities.map(entity => entity.name);
204
+ return getRelationsForEntities(entityNames);
205
+ }
206
+ /**
207
+ * Reads the recent entities and their relations to form a graph
208
+ * @param includeEmbeddings - Whether to include embeddings in the returned entities (default: false)
209
+ * @returns Graph result with entities and relations
210
+ */
211
+ async read_graph(includeEmbeddings = false) {
212
+ return readGraph(10, includeEmbeddings);
213
+ }
214
+ /**
215
+ * Searches for nodes in the graph by text query or vector similarity
216
+ * @param query - Text query or vector embedding for search
217
+ * @param includeEmbeddings - Whether to include embeddings in the returned entities (default: false)
218
+ * @returns Graph result with matching entities and their relations
219
+ */
220
+ async search_nodes(query, includeEmbeddings = false) {
221
+ return searchNodes(query, includeEmbeddings);
222
+ }
223
+ /**
224
+ * Closes the database connection
225
+ */
226
+ async close() {
227
+ try {
228
+ await this.client.close();
229
+ }
230
+ catch (error) {
231
+ logger.error('Error closing database connection:', error);
232
+ }
233
+ }
234
+ }
@@ -0,0 +1,45 @@
1
+ // Re-export types
2
+ export * from './types.js';
3
+ // Re-export core database manager for backward compatibility
4
+ export { DatabaseManager } from './core.js';
5
+ // Re-export entity operations from services layer
6
+ export { createEntities, getEntity, getRecentEntities, deleteEntity, } from '../services/entity-service.js';
7
+ // Re-export relation operations from services layer
8
+ export { createRelations, deleteRelation, getRelationsForEntities, } from '../services/relation-service.js';
9
+ // Re-export graph operations from services layer
10
+ export { readGraph, searchNodes, } from '../services/graph-service.js';
11
+ // Re-export vector utilities from services layer
12
+ export { arrayToVectorString, extractVector, cosineSimilarity, euclideanDistance, } from '../services/vector-service.js';
13
+ // Re-export embedding service functions for backward compatibility
14
+ export { EMBEDDING_DIMENSION as DEFAULT_EMBEDDING_DIMENSION, } from '../services/embedding-service.js';
15
+ // Re-export embedding functions
16
+ export const generateEmbedding = async (input, modelName) => {
17
+ const { embeddingService } = await import('../services/embedding-service.js');
18
+ return embeddingService.generateEmbedding(input);
19
+ };
20
+ export const generateEmbeddings = async (inputs, modelName) => {
21
+ const { embeddingService } = await import('../services/embedding-service.js');
22
+ return embeddingService.generateEmbeddings(inputs);
23
+ };
24
+ // Re-export searchSimilar function for backward compatibility
25
+ export const searchSimilar = async (client, embedding, limit = 5, includeEmbeddings = false) => {
26
+ // This is a compatibility wrapper that adapts the new service API to the old function signature
27
+ const { databaseService } = await import('../services/database-service.js');
28
+ const { searchNodes } = await import('../services/graph-service.js');
29
+ // Use the searchNodes function with the vector embedding
30
+ const result = await searchNodes(embedding, includeEmbeddings);
31
+ // Convert the result to the format expected by the old API
32
+ return result.entities.map(entity => ({
33
+ entity,
34
+ distance: 0, // We don't have distance information in the new API
35
+ }));
36
+ };
37
+ // Re-export searchEntities function for backward compatibility
38
+ export const searchEntities = async (client, query, includeEmbeddings = false) => {
39
+ // This is a compatibility wrapper that adapts the new service API to the old function signature
40
+ const { searchNodes } = await import('../services/graph-service.js');
41
+ // Use the searchNodes function with the text query
42
+ const result = await searchNodes(query, includeEmbeddings);
43
+ // Return just the entities
44
+ return result.entities;
45
+ };