#
tokens: 38781/50000 24/24 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .env.example
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── nodemon.json
├── package-lock.json
├── package.json
├── README.md
├── requirements.txt
├── scripts
│   ├── setup.js
│   └── start.js
├── src
│   ├── index.ts
│   ├── lib
│   │   ├── chunking-util.ts
│   │   ├── context-manager.ts
│   │   ├── embedding-service.ts
│   │   ├── errors.ts
│   │   ├── http-server.ts
│   │   ├── logger.ts
│   │   ├── namespace-manager.ts
│   │   ├── search-module.ts
│   │   ├── sqlite-store.ts
│   │   ├── tool-definitions.ts
│   │   └── tool-implementations.ts
│   └── python
│       └── embedding_service.py
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | # Path to SQLite database file
 2 | DB_PATH=./data/context.db
 3 | 
 4 | PORT=3000
 5 | 
 6 | # Use HTTP SSE or Stdio
 7 | USE_HTTP_SSE=true
 8 | 
 9 | # Logging Configuration: debug, info, warn, error
10 | LOG_LEVEL=info
```

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
 1 | {
 2 |   "singleQuote": true,
 3 |   "trailingComma": "es5",
 4 |   "printWidth": 100,
 5 |   "tabWidth": 2,
 6 |   "bracketSpacing": true,
 7 |   "jsxBracketSameLine": false,
 8 |   "semi": true,
 9 |   "useTabs": false,
10 |   "proseWrap": "always"
11 | }
```

--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------

```javascript
 1 | module.exports = {
 2 |     "env": {
 3 |         "browser": true,
 4 |         "es2021": true
 5 |     },
 6 |     "plugins": ["simple-import-sort", "prettier", "n", "promise"],
 7 |     "extends": "standard-with-typescript",
 8 |     "overrides": [
 9 |         {
10 |             "env": {
11 |                 "node": true
12 |             },
13 |             "files": [
14 |                 ".eslintrc.{js,cjs}"
15 |             ],
16 |             "parserOptions": {
17 |                 "sourceType": "script"
18 |             }
19 |         }
20 |     ],
21 |     "parserOptions": {
22 |         "ecmaVersion": "latest",
23 |         "sourceType": "module"
24 |     },
25 |     "rules": {
26 |         '@typescript-eslint/no-explicit-any': 'off',
27 |         '@typescript-eslint/explicit-module-boundary-types': 'off',
28 |         '@typescript-eslint/no-unused-vars': 'error',
29 |         'simple-import-sort/imports': 'warn',
30 |         'simple-import-sort/exports': 'warn',
31 |         'no-async-promise-executor': 'off',
32 |         'prefer-arrow-callback': 'error',
33 |         'no-prototype-builtins': 'off',
34 |         'prefer-const': 'error',
35 |         'no-var': 'error',
36 |         'prefer-template': 'error',
37 |         'no-useless-escape': 'off',
38 |         "indent": "off",
39 |         "no-use-before-define": "off",
40 |         '@typescript-eslint/indent': 'off',
41 |         '@typescript-eslint/restrict-template-expressions': 'off',
42 |         '@typescript-eslint/prefer-nullish-coalescing': 'off',
43 |         '@typescript-eslint/semi': 'off',
44 |         '@typescript-eslint/explicit-function-return-type': 'off',
45 |         '@typescript-eslint/space-before-function-paren': 'off',
46 |         '@typescript-eslint/no-floating-promises': 'off',
47 |         '@typescript-eslint/strict-boolean-expressions': 'off',
48 |         '@typescript-eslint/comma-dangle': 'off',
49 |         "@typescript-eslint/member-delimiter-style": "off"
50 |     }
51 | }
52 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Logs
  2 | logs
  3 | *.log
  4 | npm-debug.log*
  5 | yarn-debug.log*
  6 | yarn-error.log*
  7 | lerna-debug.log*
  8 | .pnpm-debug.log*
  9 | 
 10 | # Diagnostic reports (https://nodejs.org/api/report.html)
 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 12 | 
 13 | # Runtime data
 14 | pids
 15 | *.pid
 16 | *.seed
 17 | *.pid.lock
 18 | 
 19 | # Directory for instrumented libs generated by jscoverage/JSCover
 20 | lib-cov
 21 | 
 22 | # Coverage directory used by tools like istanbul
 23 | coverage
 24 | *.lcov
 25 | 
 26 | # nyc test coverage
 27 | .nyc_output
 28 | 
 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 30 | .grunt
 31 | 
 32 | # Bower dependency directory (https://bower.io/)
 33 | bower_components
 34 | 
 35 | # node-waf configuration
 36 | .lock-wscript
 37 | 
 38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 39 | build/Release
 40 | 
 41 | # Dependency directories
 42 | node_modules/
 43 | jspm_packages/
 44 | 
 45 | # Snowpack dependency directory (https://snowpack.dev/)
 46 | web_modules/
 47 | 
 48 | # TypeScript cache
 49 | *.tsbuildinfo
 50 | 
 51 | # Optional npm cache directory
 52 | .npm
 53 | 
 54 | # Optional eslint cache
 55 | .eslintcache
 56 | 
 57 | # Optional stylelint cache
 58 | .stylelintcache
 59 | 
 60 | # Microbundle cache
 61 | .rpt2_cache/
 62 | .rts2_cache_cjs/
 63 | .rts2_cache_es/
 64 | .rts2_cache_umd/
 65 | 
 66 | # Optional REPL history
 67 | .node_repl_history
 68 | 
 69 | # Output of 'npm pack'
 70 | *.tgz
 71 | 
 72 | # Yarn Integrity file
 73 | .yarn-integrity
 74 | 
 75 | # dotenv environment variable files
 76 | .env
 77 | .env.development.local
 78 | .env.test.local
 79 | .env.production.local
 80 | .env.local
 81 | 
 82 | # parcel-bundler cache (https://parceljs.org/)
 83 | .cache
 84 | .parcel-cache
 85 | 
 86 | # Next.js build output
 87 | .next
 88 | out
 89 | 
 90 | # Nuxt.js build / generate output
 91 | .nuxt
 92 | dist
 93 | 
 94 | # Gatsby files
 95 | .cache/
 96 | # Comment in the public line in if your project uses Gatsby and not Next.js
 97 | # https://nextjs.org/blog/next-9-1#public-directory-support
 98 | # public
 99 | 
100 | # vuepress build output
101 | .vuepress/dist
102 | 
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 | 
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 | 
110 | # Serverless directories
111 | .serverless/
112 | 
113 | # FuseBox cache
114 | .fusebox/
115 | 
116 | # DynamoDB Local files
117 | .dynamodb/
118 | 
119 | # TernJS port file
120 | .tern-port
121 | 
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 | 
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 | 
132 | # misc
133 | .idea
134 | venv
135 | /data/
136 | src/python/download_model.py
137 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Simple Memory Extension MCP Server
 2 | 
 3 | An MCP server to extend the context window / memory of agents. Useful when coding big features or vibe coding and need to store/recall progress, key moments or changes or anything worth remembering. Simply ask the agent to store memories and recall whenever you need or ask the agent to fully manage its memory (through cursor rules for example) however it sees fit.
 4 | 
 5 | ## Usage
 6 | 
 7 | ### Starting the Server
 8 | 
 9 | ```bash
10 | npm install
11 | npm start
12 | ```
13 | 
14 | ### Available Tools
15 | 
16 | #### Context Item Management
17 | - `store_context_item` - Store a value with key in namespace
18 | - `retrieve_context_item_by_key` - Get value by key
19 | - `delete_context_item` - Delete key-value pair
20 | 
21 | #### Namespace Management
22 | - `create_namespace` - Create new namespace
23 | - `delete_namespace` - Delete namespace and all contents
24 | - `list_namespaces` - List all namespaces
25 | - `list_context_item_keys` - List keys in a namespace
26 | 
27 | #### Semantic Search
28 | - `retrieve_context_items_by_semantic_search` - Find items by meaning
29 | 
30 | ### Semantic Search Implementation
31 | 
32 | 1. Query converted to vector using E5 model
33 | 2. Text automatically split into chunks for better matching
34 | 3. Cosine similarity calculated between query and stored chunks
35 | 4. Results filtered by threshold and sorted by similarity
36 | 5. Top matches returned with full item values
37 | 
38 | ## Development
39 | 
40 | ```bash
41 | # Dev server
42 | npm run dev
43 | 
44 | # Format code
45 | npm run format
46 | ```
47 | 
48 | ## .env
49 | 
50 | ```
51 | # Path to SQLite database file
52 | DB_PATH=./data/context.db
53 | 
54 | PORT=3000
55 | 
56 | # Use HTTP SSE or Stdio
57 | USE_HTTP_SSE=true
58 | 
59 | # Logging Configuration: debug, info, warn, error
60 | LOG_LEVEL=info
61 | ```
62 | 
63 | ## Semantic Search
64 | 
65 | This project includes semantic search capabilities using the E5 embedding model from Hugging Face. This allows you to find context items based on their meaning rather than just exact key matches.
66 | 
67 | ### Setup
68 | 
69 | The semantic search feature requires Python dependencies, but these *should be* automatically installed when you run: `npm run start`
70 | 
71 | ### Embedding Model
72 | 
73 | We use the [intfloat/multilingual-e5-large-instruct](https://huggingface.co/intfloat/multilingual-e5-large-instruct)
74 | 
75 | 
76 | ### Notes
77 | 
78 | Developed mostly while vibe coding, so don't expect much :D. But it works, and I found it helpful so w/e. Feel free to contribute or suggest improvements.
79 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
1 | torch>=2.0.0
2 | transformers>=4.30.0
3 | sentencepiece>=0.1.99 
```

--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "watch": ["src"],
3 |   "ext": "ts,json",
4 |   "ignore": ["src/**/*.spec.ts"],
5 |   "exec": "node --loader ts-node/esm src/index.ts"
6 | }
7 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "simple-memory-extension-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "description": "An MCP server providing a persistent key-value memory store with semantic search for your agents",
 5 |   "main": "dist/index.js",
 6 |   "start": "node dist/index.js",
 7 |   "bin": {
 8 |     "enhanced-kv-mcp-server": "./dist/index.js"
 9 |   },
10 |   "type": "module",
11 |   "scripts": {
12 |     "build": "tsc",
13 |     "start": "node scripts/start.js",
14 |     "setup": "node scripts/setup.js",
15 |     "dev": "nodemon",
16 |     "format": "prettier --write \"src/**/*.ts\""
17 |   },
18 |   "keywords": [
19 |     "mcp",
20 |     "key-value",
21 |     "sqlite",
22 |     "persistence",
23 |     "namespace",
24 |     "agents",
25 |     "llm",
26 |     "model-context-protocol",
27 |     "semantic-search"
28 |   ],
29 |   "author": "gmacev",
30 |   "dependencies": {
31 |     "@modelcontextprotocol/sdk": "^1.4.1",
32 |     "dotenv": "^16.4.5",
33 |     "sqlite3": "^5.1.7",
34 |     "zod": "^3.22.4"
35 |   },
36 |   "devDependencies": {
37 |     "@types/node": "^22.13.9",
38 |     "@types/sqlite3": "^3.1.11",
39 |     "@typescript-eslint/eslint-plugin": "^6.21.0",
40 |     "@typescript-eslint/parser": "^6.21.0",
41 |     "eslint": "^8.57.1",
42 |     "eslint-config-prettier": "^9.1.0",
43 |     "eslint-config-standard-with-typescript": "^39.1.1",
44 |     "eslint-plugin-import": "^2.31.0",
45 |     "eslint-plugin-n": "^16.6.2",
46 |     "eslint-plugin-prettier": "^5.2.3",
47 |     "eslint-plugin-promise": "^6.6.0",
48 |     "eslint-plugin-simple-import-sort": "^10.0.0",
49 |     "nodemon": "^3.0.1",
50 |     "prettier": "^3.5.3",
51 |     "ts-node": "^10.9.2",
52 |     "typescript": "^5.8.2"
53 |   },
54 |   "engines": {
55 |     "node": ">=18.0.0"
56 |   },
57 |   "license": "MIT"
58 | }
59 | 
```

--------------------------------------------------------------------------------
/src/lib/errors.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Custom error classes for better error classification and handling
 3 |  */
 4 | 
 5 | /**
 6 |  * Base error class for all MCP server errors
 7 |  */
 8 | export class McpServerError extends Error {
 9 |   constructor(message: string) {
10 |     super(message);
11 |     this.name = 'McpServerError';
12 |     // Ensure proper prototype chain for instanceof checks
13 |     Object.setPrototypeOf(this, McpServerError.prototype);
14 |   }
15 | }
16 | 
17 | /**
18 |  * Database-related errors
19 |  */
20 | export class DatabaseError extends McpServerError {
21 |   constructor(
22 |     message: string,
23 |     public readonly cause?: Error
24 |   ) {
25 |     super(message);
26 |     this.name = 'DatabaseError';
27 |     Object.setPrototypeOf(this, DatabaseError.prototype);
28 |   }
29 | }
30 | 
31 | /**
32 |  * Validation-related errors
33 |  */
34 | export class ValidationError extends McpServerError {
35 |   constructor(message: string) {
36 |     super(message);
37 |     this.name = 'ValidationError';
38 |     Object.setPrototypeOf(this, ValidationError.prototype);
39 |   }
40 | }
41 | 
42 | /**
43 |  * Error for invalid namespace names
44 |  */
45 | export class InvalidNamespaceError extends ValidationError {
46 |   constructor(message: string) {
47 |     super(message);
48 |     this.name = 'InvalidNamespaceError';
49 |     Object.setPrototypeOf(this, InvalidNamespaceError.prototype);
50 |   }
51 | }
52 | 
53 | /**
54 |  * Error for invalid key names
55 |  */
56 | export class InvalidKeyError extends ValidationError {
57 |   constructor(message: string) {
58 |     super(message);
59 |     this.name = 'InvalidKeyError';
60 |     Object.setPrototypeOf(this, InvalidKeyError.prototype);
61 |   }
62 | }
63 | 
64 | /**
65 |  * Helper function to convert unknown errors to typed errors
66 |  * @param error The original error
67 |  * @param defaultMessage Default message if error is not an Error object
68 |  * @returns A properly typed error
69 |  */
70 | export function normalizeError(error: unknown, defaultMessage = 'Unknown error'): Error {
71 |   if (error instanceof Error) {
72 |     return error;
73 |   }
74 | 
75 |   if (typeof error === 'string') {
76 |     return new Error(error);
77 |   }
78 | 
79 |   return new Error(defaultMessage);
80 | }
81 | 
```

--------------------------------------------------------------------------------
/src/lib/namespace-manager.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { SQLiteDataStore } from './sqlite-store.js';
 2 | import { InvalidNamespaceError } from './errors.js';
 3 | import { logger } from './logger.js';
 4 | 
 5 | /**
 6 |  * Creates a new namespace
 7 |  * @param db Database connection
 8 |  * @param namespace Name of the namespace to create
 9 |  * @returns Promise that resolves to true if a new namespace was created, false if it already existed
10 |  */
11 | export async function createNamespace(db: SQLiteDataStore, namespace: string): Promise<boolean> {
12 |   if (!namespace) {
13 |     logger.error('Cannot create namespace: namespace is required');
14 |     throw new InvalidNamespaceError('Namespace is required');
15 |   }
16 | 
17 |   logger.debug('Creating namespace', { namespace });
18 |   const created = await db.createNamespace(namespace);
19 | 
20 |   if (created) {
21 |     logger.info('Created new namespace', { namespace });
22 |   } else {
23 |     logger.debug('Namespace already exists', { namespace });
24 |   }
25 | 
26 |   return created;
27 | }
28 | 
29 | /**
30 |  * Deletes a namespace and all items within it
31 |  * @param db Database connection
32 |  * @param namespace Name of the namespace to delete
33 |  * @returns Promise that resolves to true if the namespace was deleted, false otherwise
34 |  */
35 | export async function deleteNamespace(db: SQLiteDataStore, namespace: string): Promise<boolean> {
36 |   if (!namespace) {
37 |     logger.error('Cannot delete namespace: namespace is required');
38 |     throw new InvalidNamespaceError('Namespace is required');
39 |   }
40 | 
41 |   logger.debug('Deleting namespace', { namespace });
42 |   const deleted = await db.deleteNamespace(namespace);
43 | 
44 |   if (deleted) {
45 |     logger.info('Deleted namespace', { namespace });
46 |   } else {
47 |     logger.debug('Namespace does not exist, nothing to delete', { namespace });
48 |   }
49 | 
50 |   return deleted;
51 | }
52 | 
53 | /**
54 |  * Lists all namespaces
55 |  * @param db Database connection
56 |  * @returns Array of namespace names
57 |  */
58 | export async function listNamespaces(db: SQLiteDataStore): Promise<string[]> {
59 |   logger.debug('Listing namespaces');
60 |   return await db.listAllNamespaces();
61 | }
62 | 
```

--------------------------------------------------------------------------------
/src/lib/context-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { SQLiteDataStore } from './sqlite-store.js';
  2 | import { InvalidNamespaceError, InvalidKeyError } from './errors.js';
  3 | import { logger } from './logger.js';
  4 | 
  5 | /**
  6 |  * Stores a context item
  7 |  * @param db Database connection
  8 |  * @param namespace Namespace for the context item
  9 |  * @param key Key for the context item
 10 |  * @param value Value to store
 11 |  * @returns Promise that resolves when the operation is complete
 12 |  */
 13 | export async function storeContextItem(
 14 |   db: SQLiteDataStore,
 15 |   namespace: string,
 16 |   key: string,
 17 |   value: any
 18 | ): Promise<void> {
 19 |   if (!namespace || namespace.trim() === '') {
 20 |     logger.warn('Attempted to store context item with empty namespace', { key });
 21 |     throw new InvalidNamespaceError('Namespace cannot be empty');
 22 |   }
 23 | 
 24 |   if (!key || key.trim() === '') {
 25 |     logger.warn('Attempted to store context item with empty key', { namespace });
 26 |     throw new InvalidKeyError('Key cannot be empty');
 27 |   }
 28 | 
 29 |   logger.debug('Storing context item', { namespace, key });
 30 |   // Store the item in the database
 31 |   await db.storeDataItem(namespace, key, value);
 32 | }
 33 | 
 34 | /**
 35 |  * Retrieves a context item by its key
 36 |  * @param db Database connection
 37 |  * @param namespace Namespace for the context item
 38 |  * @param key Key for the context item
 39 |  * @returns The context item with timestamps or null if not found
 40 |  */
 41 | export async function retrieveContextItemByKey(
 42 |   db: SQLiteDataStore,
 43 |   namespace: string,
 44 |   key: string
 45 | ): Promise<{ value: any; created_at: string; updated_at: string } | null> {
 46 |   if (!namespace || namespace.trim() === '') {
 47 |     logger.warn('Attempted to retrieve context item with empty namespace', { key });
 48 |     throw new InvalidNamespaceError('Namespace cannot be empty');
 49 |   }
 50 | 
 51 |   if (!key || key.trim() === '') {
 52 |     logger.warn('Attempted to retrieve context item with empty key', { namespace });
 53 |     throw new InvalidKeyError('Key cannot be empty');
 54 |   }
 55 | 
 56 |   logger.debug('Retrieving context item by key', { namespace, key });
 57 |   const item = await db.retrieveDataItem(namespace, key);
 58 | 
 59 |   if (!item) {
 60 |     logger.debug('Context item not found', { namespace, key });
 61 |     return null;
 62 |   }
 63 | 
 64 |   logger.debug('Context item retrieved successfully', { namespace, key });
 65 |   return {
 66 |     value: item.value,
 67 |     created_at: item.created_at,
 68 |     updated_at: item.updated_at,
 69 |   };
 70 | }
 71 | 
 72 | /**
 73 |  * Delete a context item
 74 |  * @param db Database connection
 75 |  * @param namespace Namespace to delete from
 76 |  * @param key Key to delete
 77 |  * @returns True if item was deleted, false if it did not exist
 78 |  */
 79 | export async function deleteContextItem(
 80 |   db: SQLiteDataStore,
 81 |   namespace: string,
 82 |   key: string
 83 | ): Promise<boolean> {
 84 |   if (!namespace || namespace.trim() === '') {
 85 |     logger.warn('Attempted to delete context item with empty namespace', { key });
 86 |     throw new InvalidNamespaceError('Namespace cannot be empty');
 87 |   }
 88 | 
 89 |   if (!key || key.trim() === '') {
 90 |     logger.warn('Attempted to delete context item with empty key', { namespace });
 91 |     throw new InvalidKeyError('Key cannot be empty');
 92 |   }
 93 | 
 94 |   logger.debug('Deleting context item', { namespace, key });
 95 |   const deleted = await db.deleteDataItem(namespace, key);
 96 |   logger.debug('Context item deletion result', { namespace, key, deleted });
 97 |   return deleted;
 98 | }
 99 | 
100 | /**
101 |  * List context item keys in a namespace
102 |  * @param db Database instance
103 |  * @param namespace Namespace to list items from
104 |  * @returns Array of key objects for all items in the namespace
105 |  */
106 | export async function listContextItemKeys(
107 |   db: SQLiteDataStore,
108 |   namespace: string
109 | ): Promise<
110 |   Array<{
111 |     key: string;
112 |     created_at: string;
113 |     updated_at: string;
114 |   }>
115 | > {
116 |   if (!namespace || namespace.trim() === '') {
117 |     logger.warn('Attempted to list context item keys with empty namespace');
118 |     throw new InvalidNamespaceError('Namespace cannot be empty');
119 |   }
120 | 
121 |   logger.debug('Listing context item keys', { namespace });
122 |   const keys = await db.listContextItemKeys(namespace);
123 |   logger.debug('Listed context item keys', { namespace, count: keys.length });
124 |   return keys;
125 | }
126 | 
```

--------------------------------------------------------------------------------
/src/lib/logger.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Structured logging utility for the MCP server
  3 |  * Provides consistent logging format across the application
  4 |  */
  5 | 
  6 | // Log levels
  7 | export enum LogLevel {
  8 |   DEBUG = 'debug',
  9 |   INFO = 'info',
 10 |   WARN = 'warn',
 11 |   ERROR = 'error',
 12 | }
 13 | 
 14 | // Log entry structure
 15 | export interface LogEntry {
 16 |   timestamp: string;
 17 |   level: LogLevel;
 18 |   message: string;
 19 |   context?: Record<string, any>;
 20 |   error?: Error | unknown;
 21 | }
 22 | 
 23 | /**
 24 |  * Logger class for structured logging
 25 |  */
 26 | export class Logger {
 27 |   private static instance: Logger;
 28 |   private logLevel: LogLevel = LogLevel.INFO;
 29 | 
 30 |   private constructor() {}
 31 | 
 32 |   /**
 33 |    * Get the singleton logger instance
 34 |    */
 35 |   public static getInstance(): Logger {
 36 |     if (!Logger.instance) {
 37 |       Logger.instance = new Logger();
 38 |     }
 39 |     return Logger.instance;
 40 |   }
 41 | 
 42 |   /**
 43 |    * Set the minimum log level
 44 |    * @param level Minimum log level to display
 45 |    */
 46 |   public setLogLevel(level: LogLevel): void {
 47 |     this.logLevel = level;
 48 |   }
 49 | 
 50 |   /**
 51 |    * Log a debug message
 52 |    * @param message Log message
 53 |    * @param context Optional context object
 54 |    */
 55 |   public debug(message: string, context?: Record<string, any>): void {
 56 |     this.log(LogLevel.DEBUG, message, context);
 57 |   }
 58 | 
 59 |   /**
 60 |    * Log an info message
 61 |    * @param message Log message
 62 |    * @param context Optional context object
 63 |    */
 64 |   public info(message: string, context?: Record<string, any>): void {
 65 |     this.log(LogLevel.INFO, message, context);
 66 |   }
 67 | 
 68 |   /**
 69 |    * Log a warning message
 70 |    * @param message Log message
 71 |    * @param context Optional context object
 72 |    */
 73 |   public warn(message: string, context?: Record<string, any>): void {
 74 |     this.log(LogLevel.WARN, message, context);
 75 |   }
 76 | 
 77 |   /**
 78 |    * Log an error message
 79 |    * @param message Log message
 80 |    * @param error Error object
 81 |    * @param context Optional context object
 82 |    */
 83 |   public error(message: string, error?: Error | unknown, context?: Record<string, any>): void {
 84 |     this.log(LogLevel.ERROR, message, context, error);
 85 |   }
 86 | 
 87 |   /**
 88 |    * Internal logging method
 89 |    * @param level Log level
 90 |    * @param message Log message
 91 |    * @param context Optional context object
 92 |    * @param error Optional error object
 93 |    */
 94 |   private log(
 95 |     level: LogLevel,
 96 |     message: string,
 97 |     context?: Record<string, any>,
 98 |     error?: Error | unknown
 99 |   ): void {
100 |     // Skip logging if level is below configured level
101 |     if (!this.shouldLog(level)) {
102 |       return;
103 |     }
104 | 
105 |     const entry: LogEntry = {
106 |       timestamp: new Date().toISOString(),
107 |       level,
108 |       message,
109 |       context,
110 |       error,
111 |     };
112 | 
113 |     // Format and output the log entry
114 |     this.output(entry);
115 |   }
116 | 
117 |   /**
118 |    * Check if a log level should be displayed
119 |    * @param level Log level to check
120 |    * @returns True if the log should be displayed
121 |    */
122 |   private shouldLog(level: LogLevel): boolean {
123 |     const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
124 |     const configuredIndex = levels.indexOf(this.logLevel);
125 |     const messageIndex = levels.indexOf(level);
126 | 
127 |     return messageIndex >= configuredIndex;
128 |   }
129 | 
130 |   /**
131 |    * Output a log entry
132 |    * @param entry Log entry to output
133 |    */
134 |   private output(entry: LogEntry): void {
135 |     // Format the log entry
136 |     let output = `[${entry.timestamp}] [${entry.level.toUpperCase()}] ${entry.message}`;
137 | 
138 |     // Add context if available
139 |     if (entry.context && Object.keys(entry.context).length > 0) {
140 |       output += ` | Context: ${JSON.stringify(entry.context)}`;
141 |     }
142 | 
143 |     // Add error details if available
144 |     if (entry.error) {
145 |       if (entry.error instanceof Error) {
146 |         output += ` | Error: ${entry.error.message}`;
147 |         if (entry.error.stack) {
148 |           output += `\n${entry.error.stack}`;
149 |         }
150 |       } else {
151 |         output += ` | Error: ${String(entry.error)}`;
152 |       }
153 |     }
154 | 
155 |     // Output to console based on log level
156 |     switch (entry.level) {
157 |       case LogLevel.DEBUG:
158 |       case LogLevel.INFO:
159 |         console.log(output);
160 |         break;
161 |       case LogLevel.WARN:
162 |         console.warn(output);
163 |         break;
164 |       case LogLevel.ERROR:
165 |         console.error(output);
166 |         break;
167 |     }
168 |   }
169 | }
170 | 
171 | // Export a singleton instance
172 | export const logger = Logger.getInstance();
173 | 
```

--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * This script orchestrates the complete startup process:
  5 |  * 1. Checks if Python setup is needed (first run)
  6 |  * 2. Builds the TypeScript code
  7 |  * 3. Sets up Python environment and pre-downloads model if needed
  8 |  * 4. Starts the server
  9 |  */
 10 | 
 11 | import { spawn } from 'child_process';
 12 | import path from 'path';
 13 | import fs from 'fs';
 14 | import os from 'os';
 15 | import { fileURLToPath } from 'url';
 16 | 
 17 | // Get the directory name in ES modules
 18 | const __filename = fileURLToPath(import.meta.url);
 19 | const __dirname = path.dirname(__filename);
 20 | 
 21 | // Important paths
 22 | const VENV_DIR = path.join(__dirname, '..', 'venv');
 23 | const PYTHON_SCRIPT_DIR = path.join(__dirname, '..', 'src', 'python');
 24 | const EMBEDDING_SCRIPT = path.join(PYTHON_SCRIPT_DIR, 'embedding_service.py');
 25 | const SERVER_PATH = path.join(__dirname, '..', 'dist', 'index.js');
 26 | 
 27 | // Colors for console output
 28 | const colors = {
 29 |   reset: '\x1b[0m',
 30 |   green: '\x1b[32m',
 31 |   yellow: '\x1b[33m',
 32 |   blue: '\x1b[34m',
 33 |   red: '\x1b[31m',
 34 | };
 35 | 
 36 | /**
 37 |  * Logs a message with color
 38 |  */
 39 | function log(message, color = colors.reset) {
 40 |   console.log(`${color}${message}${colors.reset}`);
 41 | }
 42 | 
 43 | /**
 44 |  * Runs a command and returns a promise
 45 |  */
 46 | function runCommand(command, args, options = {}) {
 47 |   return new Promise((resolve, reject) => {
 48 |     log(`Running: ${command} ${args.join(' ')}`, colors.blue);
 49 |     
 50 |     const proc = spawn(command, args, {
 51 |       stdio: 'inherit',
 52 |       shell: true,
 53 |       ...options
 54 |     });
 55 |     
 56 |     proc.on('close', (code) => {
 57 |       if (code === 0) {
 58 |         resolve();
 59 |       } else {
 60 |         reject(new Error(`Command failed with exit code ${code}`));
 61 |       }
 62 |     });
 63 |     
 64 |     proc.on('error', (err) => {
 65 |       reject(err);
 66 |     });
 67 |   });
 68 | }
 69 | 
 70 | /**
 71 |  * Checks if setup is needed
 72 |  */
 73 | function isSetupNeeded() {
 74 |   // Check if Python virtual environment exists
 75 |   const venvPython = os.platform() === 'win32' 
 76 |     ? path.join(VENV_DIR, 'Scripts', 'python.exe')
 77 |     : path.join(VENV_DIR, 'bin', 'python');
 78 |   
 79 |   const venvExists = fs.existsSync(venvPython);
 80 |   
 81 |   // Also make sure the embedding script exists
 82 |   const scriptExists = fs.existsSync(EMBEDDING_SCRIPT);
 83 |   
 84 |   // If either doesn't exist, setup is needed
 85 |   const setupNeeded = !venvExists || !scriptExists;
 86 |   
 87 |   if (setupNeeded) {
 88 |     log('First-time setup is needed', colors.yellow);
 89 |   } else {
 90 |     log('Setup already completed, skipping setup phase', colors.green);
 91 |   }
 92 |   
 93 |   return setupNeeded;
 94 | }
 95 | 
 96 | /**
 97 |  * Builds the TypeScript code
 98 |  */
 99 | async function buildTypeScript() {
100 |   log('Building TypeScript code...', colors.blue);
101 |   try {
102 |     // On Windows use npm directly with shell: true to handle the npm batch file
103 |     await runCommand('npm', ['run', 'build']);
104 |     log('TypeScript build completed successfully.', colors.green);
105 |   } catch (error) {
106 |     log(`TypeScript build failed: ${error.message}`, colors.red);
107 |     throw error;
108 |   }
109 | }
110 | 
111 | /**
112 |  * Runs the Python setup if needed
113 |  */
114 | async function setupIfNeeded() {
115 |   // Check if setup is needed
116 |   if (!isSetupNeeded()) {
117 |     return;
118 |   }
119 |   
120 |   log('Setting up Python environment...', colors.blue);
121 |   try {
122 |     const setupScript = path.join(__dirname, 'setup.js');
123 |     
124 |     // Make setup script executable on Unix systems
125 |     if (process.platform !== 'win32') {
126 |       try {
127 |         fs.chmodSync(setupScript, '755');
128 |       } catch (error) {
129 |         // Ignore errors here, the script will still run with Node
130 |       }
131 |     }
132 |     
133 |     // Use node with shell: true to handle scripts on Windows
134 |     await runCommand('node', [setupScript]);
135 |     log('Python setup completed successfully.', colors.green);
136 |   } catch (error) {
137 |     log(`Python setup failed: ${error.message}`, colors.red);
138 |     throw error;
139 |   }
140 | }
141 | 
142 | /**
143 |  * Starts the server
144 |  */
145 | async function startServer() {
146 |   log('Starting server...', colors.blue);
147 |   try {
148 |     // Use node with shell: true to run the built server
149 |     await runCommand('node', [SERVER_PATH]);
150 |   } catch (error) {
151 |     log(`Server failed to start: ${error.message}`, colors.red);
152 |     throw error;
153 |   }
154 | }
155 | 
156 | /**
157 |  * Main function
158 |  */
159 | async function main() {
160 |   log('Starting launch sequence...', colors.yellow);
161 |   
162 |   try {
163 |     // Step 1: Build TypeScript
164 |     await buildTypeScript();
165 |     
166 |     // Step 2: Setup Python if needed
167 |     await setupIfNeeded();
168 |     
169 |     // Step 3: Start server
170 |     await startServer();
171 |   } catch (error) {
172 |     log(`Launch sequence failed: ${error.message}`, colors.red);
173 |     process.exit(1);
174 |   }
175 | }
176 | 
177 | // Run the main function
178 | main(); 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  5 | import * as dotenv from 'dotenv';
  6 | import { SQLiteDataStore } from './lib/sqlite-store.js';
  7 | import { registerTools } from './lib/tool-implementations.js';
  8 | import { logger, LogLevel } from './lib/logger.js';
  9 | import { setupHttpServer, closeAllTransports } from './lib/http-server.js';
 10 | import http from 'http';
 11 | import path from 'path';
 12 | 
 13 | // Load environment variables from .env file
 14 | dotenv.config();
 15 | 
 16 | // Configure logging level from environment variable
 17 | const configuredLogLevel = process.env.LOG_LEVEL || 'info';
 18 | if (configuredLogLevel) {
 19 |   switch (configuredLogLevel.toLowerCase()) {
 20 |     case 'debug':
 21 |       logger.setLogLevel(LogLevel.DEBUG);
 22 |       break;
 23 |     case 'info':
 24 |       logger.setLogLevel(LogLevel.INFO);
 25 |       break;
 26 |     case 'warn':
 27 |       logger.setLogLevel(LogLevel.WARN);
 28 |       break;
 29 |     case 'error':
 30 |       logger.setLogLevel(LogLevel.ERROR);
 31 |       break;
 32 |     default:
 33 |       logger.warn(`Unknown log level: ${configuredLogLevel}, using default`);
 34 |   }
 35 | }
 36 | 
 37 | /**
 38 |  * Main entry point for the server
 39 |  */
 40 | async function main() {
 41 |   let db: SQLiteDataStore;
 42 |   let server: McpServer;
 43 | 
 44 |   try {
 45 |     logger.info('Initializing simple-memory-extension-mcp-server...');
 46 | 
 47 |     // Get database path from environment variable or use default path
 48 |     const dbPath = process.env.DB_PATH || path.join(process.cwd(), 'data', 'context.db');
 49 |     logger.info(`Using database at: ${dbPath}`);
 50 | 
 51 |     // Initialize the SQLite database
 52 |     try {
 53 |       db = new SQLiteDataStore({
 54 |         dbPath: dbPath,
 55 |         enableChunking: true,
 56 |       });
 57 |       logger.info('Database initialized successfully');
 58 |     } catch (dbError) {
 59 |       logger.error('Failed to initialize database', dbError);
 60 |       process.exit(1);
 61 |     }
 62 | 
 63 |     // Create an MCP server
 64 |     server = new McpServer({
 65 |       name: 'simple-memory-extension-mcp-server',
 66 |       version: '1.0.0',
 67 |     });
 68 | 
 69 |     // Register all tools with the server using schemas from tool-definitions.ts
 70 |     registerTools(server, db);
 71 |     logger.info('All tools registered with the server');
 72 | 
 73 |     // Check if we should use HTTP server
 74 |     const useHttpSSE = process.env.USE_HTTP_SSE === 'true';
 75 |     const port = parseInt(process.env.PORT || '3000', 10);
 76 | 
 77 |     if (useHttpSSE) {
 78 |       // Set up HTTP server SSE transport
 79 |       const httpServer = setupHttpServer(server, port);
 80 | 
 81 |       // Setup graceful shutdown for HTTP server
 82 |       setupGracefulShutdown(db, httpServer);
 83 |     } else {
 84 |       // Use standard stdio transport
 85 |       const transport = new StdioServerTransport();
 86 |       await server.connect(transport);
 87 |       logger.info('Server ready to receive requests via stdio');
 88 | 
 89 |       // Setup graceful shutdown
 90 |       setupGracefulShutdown(db);
 91 |     }
 92 |   } catch (error) {
 93 |     logger.error('Error initializing server', error);
 94 |     process.exit(1);
 95 |   }
 96 | }
 97 | 
 98 | /**
 99 |  * Set up graceful shutdown to properly close database connections
100 |  */
101 | function setupGracefulShutdown(db: SQLiteDataStore, httpServer?: http.Server) {
102 |   const shutdown = async () => {
103 |     logger.info('Shutting down server...');
104 | 
105 |     // Set a timeout to force exit if graceful shutdown takes too long
106 |     const forceExitTimeout = setTimeout(() => {
107 |       logger.error('Forced shutdown after timeout');
108 |       process.exit(1);
109 |     }, 5000); // Force exit after 5 seconds
110 | 
111 |     try {
112 |       // Close all active SSE transports
113 |       await closeAllTransports();
114 | 
115 |       if (httpServer) {
116 |         await new Promise<void>((resolve) => {
117 |           httpServer.close(() => {
118 |             logger.info('HTTP server closed');
119 |             resolve();
120 |           });
121 |         });
122 |       }
123 | 
124 |       if (db) {
125 |         // Get underlying DB and close it
126 |         const sqliteDb = await db.getDb();
127 |         if (sqliteDb) {
128 |           await new Promise<void>((resolve) => {
129 |             sqliteDb.close(() => {
130 |               logger.info('Database connection closed');
131 |               resolve();
132 |             });
133 |           });
134 |         }
135 |       }
136 | 
137 |       logger.info('Server shutdown complete');
138 |       clearTimeout(forceExitTimeout);
139 |       process.exit(0);
140 |     } catch (error) {
141 |       logger.error('Error during shutdown', error);
142 |       clearTimeout(forceExitTimeout);
143 |       process.exit(1);
144 |     }
145 |   };
146 | 
147 |   // Register shutdown handlers
148 |   process.on('SIGINT', shutdown);
149 |   process.on('SIGTERM', shutdown);
150 |   process.on('unhandledRejection', (reason, promise) => {
151 |     logger.error('Unhandled Promise rejection', { reason, promise });
152 |   });
153 | }
154 | 
155 | // Start the server
156 | main().catch((error) => {
157 |   logger.error('Unhandled error in main function', error);
158 |   process.exit(1);
159 | });
160 | 
```

--------------------------------------------------------------------------------
/src/python/embedding_service.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Embedding service using the E5 model from Hugging Face.
  4 | This script provides a bridge between Node.js and the E5 model.
  5 | """
  6 | 
  7 | import sys
  8 | import json
  9 | import torch
 10 | import torch.nn.functional as F
 11 | from transformers import AutoTokenizer, AutoModel
 12 | 
 13 | # Initialize model
 14 | MODEL_NAME = "intfloat/multilingual-e5-large-instruct"
 15 | tokenizer = None
 16 | model = None
 17 | 
 18 | def initialize_model():
 19 |     """Initialize the model and tokenizer."""
 20 |     global tokenizer, model
 21 |     
 22 |     if tokenizer is None or model is None:
 23 |         print("Initializing E5 model...", file=sys.stderr)
 24 |         tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
 25 |         model = AutoModel.from_pretrained(MODEL_NAME)
 26 |         print(f"Model initialized: {MODEL_NAME}", file=sys.stderr)
 27 | 
 28 | def average_pool(last_hidden_states, attention_mask):
 29 |     """Average pooling function for the model output."""
 30 |     last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
 31 |     return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]
 32 | 
 33 | def format_text_for_embedding(text, is_query=False):
 34 |     """
 35 |     Format text for embedding based on whether it's a query or passage.
 36 |     For queries, we add an instruction prefix.
 37 |     For passages, we use the text as is.
 38 |     """
 39 |     if is_query:
 40 |         # For queries, add instruction
 41 |         task_description = "Given a web search query, retrieve relevant passages that answer the query"
 42 |         return f'Instruct: {task_description}\nQuery: {text}'
 43 |     else:
 44 |         # For passages, use as is
 45 |         return text
 46 | 
 47 | def generate_embedding(text, is_query=False):
 48 |     """Generate embedding for a single text."""
 49 |     initialize_model()
 50 | 
 51 |     # Format text based on type
 52 |     input_text = format_text_for_embedding(text, is_query)
 53 | 
 54 |     # Tokenize
 55 |     encoded_input = tokenizer(
 56 |         input_text, 
 57 |         max_length=512, 
 58 |         padding=True, 
 59 |         truncation=True, 
 60 |         return_tensors='pt'
 61 |     )
 62 |                             
 63 |     # Generate embedding
 64 |     with torch.no_grad():
 65 |         model_output = model(**encoded_input)
 66 | 
 67 |     # Pool and normalize
 68 |     embedding = average_pool(model_output.last_hidden_state, encoded_input['attention_mask'])
 69 |     embedding = F.normalize(embedding, p=2, dim=1)
 70 | 
 71 |     # Convert to list
 72 |     return embedding[0].tolist()
 73 | 
 74 | def generate_embeddings(texts, is_query=False):
 75 |     """Generate embeddings for multiple texts."""
 76 |     initialize_model()
 77 | 
 78 |     if not texts or not isinstance(texts, list):
 79 |         raise ValueError("Texts must be a non-empty list of strings")
 80 |     
 81 |     # Format each text based on type
 82 |     input_texts = [format_text_for_embedding(text, is_query) for text in texts]
 83 | 
 84 |     # Tokenize
 85 |     encoded_input = tokenizer(
 86 |         input_texts, 
 87 |         max_length=512, 
 88 |         padding=True, 
 89 |         truncation=True, 
 90 |         return_tensors='pt'
 91 |     )
 92 |                             
 93 |     # Generate embeddings
 94 |     with torch.no_grad():
 95 |         model_output = model(**encoded_input)
 96 | 
 97 |     # Pool and normalize
 98 |     embeddings = average_pool(model_output.last_hidden_state, encoded_input['attention_mask'])
 99 |     embeddings = F.normalize(embeddings, p=2, dim=1)
100 | 
101 |     # Convert to list
102 |     return embeddings.tolist()
103 | 
104 | def process_command(command_json):
105 |     """Process a command from Node.js."""
106 |     try:
107 |         command = command_json.get("command")
108 | 
109 |         if command == "initialize":
110 |             initialize_model()
111 |             return {"status": "initialized"}
112 | 
113 |         elif command == "generate_embedding":
114 |             text = command_json.get("text")
115 |             is_query = command_json.get("is_query", False)
116 | 
117 |             if not text:
118 |                 return {"error": "No text provided"}
119 | 
120 |             embedding = generate_embedding(text, is_query)
121 |             return {"embedding": embedding}
122 | 
123 |         elif command == "generate_embeddings":
124 |             texts = command_json.get("texts")
125 |             is_query = command_json.get("is_query", False)
126 | 
127 |             if not texts or not isinstance(texts, list):
128 |                 return {"error": "No texts provided or invalid format"}
129 | 
130 |             embeddings = generate_embeddings(texts, is_query)
131 |             return {"embeddings": embeddings}
132 | 
133 |         else:
134 |             return {"error": f"Unknown command: {command}"}
135 | 
136 |     except Exception as e:
137 |         return {"error": str(e)}
138 | 
139 | def main():
140 |     """Main function to process commands from stdin."""
141 |     print("E5 Embedding Service started", file=sys.stderr)
142 |     initialize_model()
143 |     
144 |     for line in sys.stdin:
145 |         try:
146 |             command_json = json.loads(line)
147 |             result = process_command(command_json)
148 |             print(json.dumps(result), flush=True)
149 |         except json.JSONDecodeError:
150 |             print(json.dumps({"error": "Invalid JSON"}), flush=True)
151 |         except Exception as e:
152 |             print(json.dumps({"error": str(e)}), flush=True)
153 | 
154 | if __name__ == "__main__":
155 |     main() 
```

--------------------------------------------------------------------------------
/src/lib/tool-definitions.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | 
  3 | // Define Zod schemas for tool parameters
  4 | const namespaceSchema = z
  5 |   .string()
  6 |   .min(1, 'Namespace must not be empty')
  7 |   .describe(
  8 |     'A unique identifier for a collection of related key-value pairs. Use logical naming patterns like "user_preferences", "conversation_context", or "task_data" to organize information.'
  9 |   );
 10 | const keySchema = z
 11 |   .string()
 12 |   .min(1, 'Key must not be empty')
 13 |   .describe(
 14 |     'A unique identifier for a value within a namespace. Use descriptive keys that indicate the data\'s purpose, like "last_search_query", "user_location", or "current_task_id".'
 15 |   );
 16 | const nSchema = z
 17 |   .number()
 18 |   .int('Number of turns must be an integer')
 19 |   .positive('Number of turns must be greater than 0')
 20 |   .describe(
 21 |     'A positive integer specifying the quantity of items to retrieve. Use smaller values for recent context, larger values for more comprehensive history.'
 22 |   );
 23 | 
 24 | // Define tool schemas
 25 | export const toolSchemas = {
 26 |   // Context Item Management
 27 |   retrieve_context_item_by_key: {
 28 |     name: 'retrieve_context_item_by_key',
 29 |     description:
 30 |       'Retrieves a stored value by its key from a specific namespace. Although values are stored as strings, they are automatically parsed and returned with their original data types. For example, JSON objects/arrays will be returned as structured data, numbers as numeric values, booleans as true/false, and null as null.',
 31 |     schema: z.object({
 32 |       namespace: namespaceSchema,
 33 |       key: keySchema,
 34 |     }),
 35 |   },
 36 | 
 37 |   store_context_item: {
 38 |     name: 'store_context_item',
 39 |     description:
 40 |       'Stores a NEW value associated with a key in a specified namespace. IMPORTANT: All values must be passed as strings, but the system will intelligently parse them to preserve their data types. This tool will fail if the key already exists - use update_context_item for modifying existing items.',
 41 |     schema: z.object({
 42 |       namespace: namespaceSchema,
 43 |       key: keySchema,
 44 |       value: z
 45 |         .string()
 46 |         .describe(
 47 |           'The value to store as a string. Examples: "{\"name\":\"John\"}" for objects, "[1,2,3]" for arrays, "42.5" for numbers, "true"/"false" for booleans, "null" for null. The system will automatically detect and preserve the appropriate data type when retrieving.'
 48 |         ),
 49 |     }),
 50 |   },
 51 | 
 52 |   update_context_item: {
 53 |     name: 'update_context_item',
 54 |     description:
 55 |       'Updates an EXISTING value associated with a key in a specified namespace. This tool will fail if the key does not exist - use store_context_item for creating new items. All values must be passed as strings but will be intelligently parsed to preserve data types.',
 56 |     schema: z.object({
 57 |       namespace: namespaceSchema,
 58 |       key: keySchema,
 59 |       value: z
 60 |         .string()
 61 |         .describe(
 62 |           'The new value to store as a string. Examples: "{\"name\":\"John\"}" for objects, "[1,2,3]" for arrays, "42.5" for numbers, "true"/"false" for booleans, "null" for null. The system will automatically detect and preserve the appropriate data type when retrieving.'
 63 |         ),
 64 |     }),
 65 |   },
 66 | 
 67 |   delete_context_item: {
 68 |     name: 'delete_context_item',
 69 |     description:
 70 |       'Deletes a key-value pair from a namespace. Use this to remove data that is no longer needed or to clean up temporary storage.',
 71 |     schema: z.object({
 72 |       namespace: namespaceSchema,
 73 |       key: keySchema,
 74 |     }),
 75 |   },
 76 | 
 77 |   // Namespace Management
 78 |   create_namespace: {
 79 |     name: 'create_namespace',
 80 |     description:
 81 |       'Creates a new namespace for storing key-value pairs. Use this before storing items in a new namespace. If the namespace already exists, this is a no-op and returns success.',
 82 |     schema: z.object({
 83 |       namespace: namespaceSchema,
 84 |     }),
 85 |   },
 86 | 
 87 |   delete_namespace: {
 88 |     name: 'delete_namespace',
 89 |     description:
 90 |       'Deletes an entire namespace and all key-value pairs within it. Use this for cleanup when all data in a namespace is no longer needed.',
 91 |     schema: z.object({
 92 |       namespace: namespaceSchema,
 93 |     }),
 94 |   },
 95 | 
 96 |   list_namespaces: {
 97 |     name: 'list_namespaces',
 98 |     description:
 99 |       'Lists all available namespaces. Use this to discover what namespaces exist before retrieving or storing data.',
100 |     schema: z.object({}),
101 |   },
102 | 
103 |   // Semantic search
104 |   retrieve_context_items_by_semantic_search: {
105 |     name: 'retrieve_context_items_by_semantic_search',
106 |     description: 'Retrieves context items using semantic search based on query relevance',
107 |     schema: z.object({
108 |       namespace: namespaceSchema,
109 |       query: z.string().describe('The semantic query to search for'),
110 |       similarity_threshold: z
111 |         .number()
112 |         .optional()
113 |         .describe('Minimum similarity score (0-1) to include in results'),
114 |       limit: z.number().optional().describe('Maximum number of results to return'),
115 |     }),
116 |   },
117 | 
118 |   // Context Item Keys Listing
119 |   list_context_item_keys: {
120 |     name: 'list_context_item_keys',
121 |     description:
122 |       'Lists keys and timestamps for all items in a namespace without retrieving their values. Use this to discover what data is available before retrieving specific items.',
123 |     schema: z.object({
124 |       namespace: namespaceSchema,
125 |     }),
126 |   },
127 | };
128 | 
```

--------------------------------------------------------------------------------
/src/lib/search-module.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { SQLiteDataStore } from './sqlite-store.js';
  2 | import { ContextItem } from './sqlite-store.js';
  3 | import { logger } from './logger.js';
  4 | import { EmbeddingService } from './embedding-service.js';
  5 | 
  6 | // Singleton embedding service instance
  7 | let embeddingService: EmbeddingService | null = null;
  8 | 
  9 | /**
 10 |  * Gets or creates the embedding service
 11 |  */
 12 | async function getEmbeddingService(): Promise<EmbeddingService> {
 13 |   if (!embeddingService) {
 14 |     embeddingService = new EmbeddingService();
 15 |     await embeddingService.initialize();
 16 |   }
 17 |   return embeddingService;
 18 | }
 19 | 
 20 | /**
 21 |  * Retrieves context items using semantic search
 22 |  * @param db Database connection
 23 |  * @param namespace Namespace to search in
 24 |  * @param query The semantic query text
 25 |  * @param options Additional search options
 26 |  * @returns Array of matching context items with similarity scores
 27 |  */
 28 | export async function retrieveBySemantic(
 29 |   db: SQLiteDataStore,
 30 |   namespace: string,
 31 |   query: string,
 32 |   options: {
 33 |     limit?: number;
 34 |     threshold?: number;
 35 |     tags?: string[];
 36 |   } = {}
 37 | ): Promise<Array<ItemWithEmbedding & { similarity: number }>> {
 38 |   if (!namespace || namespace.trim() === '') {
 39 |     throw new Error('Namespace cannot be empty');
 40 |   }
 41 | 
 42 |   if (!query || query.trim() === '') {
 43 |     throw new Error('Query cannot be empty');
 44 |   }
 45 | 
 46 |   logger.debug('Performing semantic search', { namespace, query, options });
 47 | 
 48 |   try {
 49 |     // Generate embedding for the query
 50 |     const queryEmbedding = await generateEmbedding(query);
 51 | 
 52 |     // Get all items from the namespace with optional tag filtering
 53 |     const items = await getNamespaceItems(db, namespace);
 54 | 
 55 |     // For items with embeddings, calculate similarity and sort
 56 |     const results = await calculateSimilarities(items, queryEmbedding);
 57 | 
 58 |     // Filter by threshold and limit results
 59 |     return results
 60 |       .filter((item) => item.similarity >= (options.threshold || 0.7))
 61 |       .sort((a, b) => b.similarity - a.similarity)
 62 |       .slice(0, options.limit || 10);
 63 |   } catch (error: any) {
 64 |     logger.error('Error in semantic search', error, { namespace, query });
 65 |     throw new Error(`Failed to perform semantic search: ${error.message}`);
 66 |   }
 67 | }
 68 | 
 69 | /**
 70 |  * Generates embedding for text using the E5 model
 71 |  */
 72 | async function generateEmbedding(text: string): Promise<number[]> {
 73 |   try {
 74 |     logger.debug('Generating embedding for text', { textLength: text.length });
 75 |     const service = await getEmbeddingService();
 76 |     return service.generateEmbedding(text);
 77 |   } catch (error: any) {
 78 |     logger.error('Error generating embedding', error);
 79 |     // Fallback to random vector if embedding generation fails
 80 |     logger.debug('Using fallback random embedding');
 81 |     return Array.from({ length: 1024 }, () => Math.random() - 0.5);
 82 |   }
 83 | }
 84 | 
 85 | /**
 86 |  * Retrieve items from namespace with optional tag filtering
 87 |  */
 88 | async function getNamespaceItems(db: SQLiteDataStore, namespace: string): Promise<ContextItem[]> {
 89 |   logger.debug(`Getting all items in namespace: ${namespace}`);
 90 |   return await db.retrieveAllItemsInNamespace(namespace);
 91 | }
 92 | 
 93 | // Define a type for items with parsed embeddings
 94 | interface ItemWithEmbedding extends Omit<ContextItem, 'embedding'> {
 95 |   embedding: number[];
 96 | }
 97 | 
 98 | /**
 99 |  * Calculate similarity between query embedding and items
100 |  */
101 | async function calculateSimilarities(
102 |   items: ContextItem[],
103 |   queryEmbedding: number[]
104 | ): Promise<Array<ItemWithEmbedding & { similarity: number }>> {
105 |   logger.debug('Calculating similarities', { itemCount: items.length });
106 | 
107 |   // Generate embeddings for all items
108 |   const itemsWithEmbeddings = await generateEmbeddingsForItems(items);
109 | 
110 |   // Calculate cosine similarity for each item
111 |   return itemsWithEmbeddings.map((item) => ({
112 |     ...item,
113 |     similarity: calculateCosineSimilarity(queryEmbedding, item.embedding),
114 |   }));
115 | }
116 | 
117 | /**
118 |  * Generates embeddings for a list of items
119 |  */
120 | async function generateEmbeddingsForItems(items: ContextItem[]): Promise<ItemWithEmbedding[]> {
121 |   logger.debug(`Generating embeddings for ${items.length} items`);
122 | 
123 |   const service = await getEmbeddingService();
124 |   const results: ItemWithEmbedding[] = [];
125 | 
126 |   for (const item of items) {
127 |     try {
128 |       const text = extractTextFromItem(item);
129 |       const embedding = await service.generateEmbedding(text);
130 | 
131 |       results.push({
132 |         ...item,
133 |         embedding,
134 |       });
135 |     } catch (error: any) {
136 |       logger.error('Failed to generate embedding for item', {
137 |         namespace: item.namespace,
138 |         key: item.key,
139 |         error: error.message,
140 |       });
141 |       // Skip items that fail embedding generation
142 |       continue;
143 |     }
144 |   }
145 | 
146 |   return results;
147 | }
148 | 
149 | /**
150 |  * Extract text representation from an item
151 |  */
152 | function extractTextFromItem(item: ContextItem): string {
153 |   // Convert item value to text for embedding
154 |   if (typeof item.value === 'string') {
155 |     return item.value;
156 |   }
157 | 
158 |   if (typeof item.value === 'object') {
159 |     // For objects, concatenate string values
160 |     return Object.entries(item.value)
161 |       .filter(([_, v]) => typeof v === 'string')
162 |       .map(([k, v]) => `${k}: ${v}`)
163 |       .join('\n');
164 |   }
165 | 
166 |   return `${item.key}: ${JSON.stringify(item.value)}`;
167 | }
168 | 
169 | /**
170 |  * Calculate cosine similarity between two embeddings
171 |  */
172 | function calculateCosineSimilarity(vec1: number[], vec2: number[]): number {
173 |   if (vec1.length !== vec2.length) {
174 |     throw new Error('Vectors must have the same dimensions');
175 |   }
176 | 
177 |   const dotProduct = vec1.reduce((sum, val, i) => sum + val * vec2[i], 0);
178 |   const norm1 = Math.sqrt(vec1.reduce((sum, val) => sum + val * val, 0));
179 |   const norm2 = Math.sqrt(vec2.reduce((sum, val) => sum + val * val, 0));
180 | 
181 |   if (norm1 === 0 || norm2 === 0) {
182 |     return 0; // Avoid division by zero
183 |   }
184 | 
185 |   return dotProduct / (norm1 * norm2);
186 | }
187 | 
```

--------------------------------------------------------------------------------
/src/lib/chunking-util.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { logger } from './logger.js';
  2 | 
  3 | /**
  4 |  * Configuration for text chunking
  5 |  */
  6 | export interface ChunkingConfig {
  7 |   // Maximum tokens per chunk (default matches E5 model constraints)
  8 |   maxTokens: number;
  9 |   // Tokens to overlap between chunks for context preservation
 10 |   overlapTokens: number;
 11 |   // Rough estimation of characters per token (varies by language)
 12 |   charsPerToken: number;
 13 | }
 14 | 
 15 | // Default configuration optimized for the E5 model
 16 | export const DEFAULT_CHUNKING_CONFIG: ChunkingConfig = {
 17 |   maxTokens: 400, // Target 400 tokens to stay safely below 512 limit
 18 |   overlapTokens: 50, // ~12.5% overlap to maintain context
 19 |   charsPerToken: 4, // Rough estimate - varies by language
 20 | };
 21 | 
 22 | /**
 23 |  * Estimates token count from character count
 24 |  * This is a rough approximation - actual tokenization depends on model and language
 25 |  */
 26 | export function estimateTokenCount(
 27 |   text: string,
 28 |   charsPerToken: number = DEFAULT_CHUNKING_CONFIG.charsPerToken
 29 | ): number {
 30 |   return Math.ceil(text.length / charsPerToken);
 31 | }
 32 | 
 33 | /**
 34 |  * Chunk text by semantic boundaries like paragraphs and sentences
 35 |  * Tries to respect natural text boundaries while staying within token limits
 36 |  */
 37 | export function chunkTextBySemanticBoundaries(
 38 |   text: string,
 39 |   config: ChunkingConfig = DEFAULT_CHUNKING_CONFIG
 40 | ): string[] {
 41 |   logger.debug(`Chunking text of length ${text.length} characters`);
 42 | 
 43 |   // If text is already small enough, return as-is
 44 |   if (estimateTokenCount(text, config.charsPerToken) <= config.maxTokens) {
 45 |     logger.debug('Text fits in a single chunk, no chunking needed');
 46 |     return [text];
 47 |   }
 48 | 
 49 |   const chunks: string[] = [];
 50 | 
 51 |   // First split by double newlines (paragraphs)
 52 |   const paragraphs = text.split(/\n\s*\n/);
 53 |   logger.debug(`Split into ${paragraphs.length} paragraphs`);
 54 | 
 55 |   let currentChunk = '';
 56 |   let currentTokenCount = 0;
 57 | 
 58 |   // Process paragraph by paragraph
 59 |   for (let i = 0; i < paragraphs.length; i++) {
 60 |     const para = paragraphs[i];
 61 |     const paraTokens = estimateTokenCount(para, config.charsPerToken);
 62 | 
 63 |     // If this paragraph alone exceeds max tokens, split it into sentences
 64 |     if (paraTokens > config.maxTokens) {
 65 |       logger.debug(`Large paragraph found (est. ${paraTokens} tokens), splitting into sentences`);
 66 | 
 67 |       // If we have accumulated content in current chunk, save it first
 68 |       if (currentChunk) {
 69 |         chunks.push(currentChunk);
 70 |         currentChunk = '';
 71 |         currentTokenCount = 0;
 72 |       }
 73 | 
 74 |       // Split large paragraph into sentences and process them
 75 |       const sentences = para.split(/(?<=[.!?])\s+/);
 76 |       let sentenceChunk = '';
 77 |       let sentenceTokenCount = 0;
 78 | 
 79 |       for (const sentence of sentences) {
 80 |         const sentenceTokens = estimateTokenCount(sentence, config.charsPerToken);
 81 | 
 82 |         // If single sentence exceeds limit, we have to split it by character count
 83 |         if (sentenceTokens > config.maxTokens) {
 84 |           logger.debug(
 85 |             `Very long sentence found (est. ${sentenceTokens} tokens), splitting by character count`
 86 |           );
 87 | 
 88 |           // Save any accumulated content first
 89 |           if (sentenceChunk) {
 90 |             chunks.push(sentenceChunk);
 91 |             sentenceChunk = '';
 92 |             sentenceTokenCount = 0;
 93 |           }
 94 | 
 95 |           // Force split the long sentence into multiple chunks
 96 |           const maxChars = config.maxTokens * config.charsPerToken;
 97 |           for (let j = 0; j < sentence.length; j += maxChars) {
 98 |             const subChunk = sentence.substring(j, j + maxChars);
 99 |             chunks.push(subChunk);
100 |           }
101 |         }
102 |         // If adding this sentence exceeds limit, save current and start new
103 |         else if (sentenceTokenCount + sentenceTokens > config.maxTokens) {
104 |           chunks.push(sentenceChunk);
105 | 
106 |           // Start new chunk with overlap if possible
107 |           if (sentenceChunk && config.overlapTokens > 0) {
108 |             // Extract last N tokens worth of text as overlap
109 |             const overlapChars = config.overlapTokens * config.charsPerToken;
110 |             const overlapText = sentenceChunk.substring(
111 |               Math.max(0, sentenceChunk.length - overlapChars)
112 |             );
113 |             sentenceChunk = overlapText + ' ' + sentence;
114 |             sentenceTokenCount = estimateTokenCount(sentenceChunk, config.charsPerToken);
115 |           } else {
116 |             sentenceChunk = sentence;
117 |             sentenceTokenCount = sentenceTokens;
118 |           }
119 |         }
120 |         // Otherwise add to current sentence chunk
121 |         else {
122 |           sentenceChunk = sentenceChunk ? `${sentenceChunk} ${sentence}` : sentence;
123 |           sentenceTokenCount += sentenceTokens;
124 |         }
125 |       }
126 | 
127 |       // Add the last sentence chunk if not empty
128 |       if (sentenceChunk) {
129 |         chunks.push(sentenceChunk);
130 |       }
131 |     }
132 |     // If adding this paragraph would exceed the token limit
133 |     else if (currentTokenCount + paraTokens > config.maxTokens) {
134 |       // Save current chunk
135 |       chunks.push(currentChunk);
136 | 
137 |       // Start new chunk with overlap if possible
138 |       if (currentChunk && config.overlapTokens > 0) {
139 |         // Extract last N tokens worth of text as overlap
140 |         const overlapChars = config.overlapTokens * config.charsPerToken;
141 |         const overlapText = currentChunk.substring(Math.max(0, currentChunk.length - overlapChars));
142 |         currentChunk = overlapText + '\n\n' + para;
143 |         currentTokenCount = estimateTokenCount(currentChunk, config.charsPerToken);
144 |       } else {
145 |         currentChunk = para;
146 |         currentTokenCount = paraTokens;
147 |       }
148 |     }
149 |     // Otherwise add to current chunk
150 |     else {
151 |       if (currentChunk) {
152 |         currentChunk += '\n\n' + para;
153 |       } else {
154 |         currentChunk = para;
155 |       }
156 |       currentTokenCount += paraTokens;
157 |     }
158 |   }
159 | 
160 |   // Add the last chunk if not empty
161 |   if (currentChunk) {
162 |     chunks.push(currentChunk);
163 |   }
164 | 
165 |   logger.debug(`Text chunked into ${chunks.length} semantic chunks`);
166 |   return chunks;
167 | }
168 | 
```

--------------------------------------------------------------------------------
/scripts/setup.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * This script automates the setup process for the semantic search feature:
  5 |  * - Creates a Python virtual environment (if needed)
  6 |  * - Installs Python dependencies
  7 |  * - Pre-downloads the E5 model to avoid delays on first use
  8 |  */
  9 | 
 10 | import { spawn, execSync } from 'child_process';
 11 | import fs from 'fs';
 12 | import path from 'path';
 13 | import os from 'os';
 14 | import { fileURLToPath } from 'url';
 15 | 
 16 | // Get the directory name in ES modules
 17 | const __filename = fileURLToPath(import.meta.url);
 18 | const __dirname = path.dirname(__filename);
 19 | 
 20 | const PYTHON_COMMAND = os.platform() === 'win32' ? 'python' : 'python3';
 21 | const VENV_DIR = path.join(__dirname, '..', 'venv');
 22 | const REQUIREMENTS_FILE = path.join(__dirname, '..', 'requirements.txt');
 23 | const PYTHON_SCRIPT_DIR = path.join(__dirname, '..', 'src', 'python');
 24 | const MODEL_DIR = path.join(os.homedir(), '.cache', 'huggingface', 'transformers');
 25 | 
 26 | // Ensure the Python script directory exists
 27 | if (!fs.existsSync(PYTHON_SCRIPT_DIR)) {
 28 |   fs.mkdirSync(PYTHON_SCRIPT_DIR, { recursive: true });
 29 | }
 30 | 
 31 | // Colors for console output
 32 | const colors = {
 33 |   reset: '\x1b[0m',
 34 |   green: '\x1b[32m',
 35 |   yellow: '\x1b[33m',
 36 |   blue: '\x1b[34m',
 37 |   red: '\x1b[31m',
 38 | };
 39 | 
 40 | /**
 41 |  * Logs a message with color
 42 |  */
 43 | function log(message, color = colors.reset) {
 44 |   console.log(`${color}${message}${colors.reset}`);
 45 | }
 46 | 
 47 | /**
 48 |  * Checks if a command exists in PATH
 49 |  */
 50 | function commandExists(command) {
 51 |   try {
 52 |     const devNull = os.platform() === 'win32' ? 'NUL' : '/dev/null';
 53 |     execSync(`${command} --version`, { stdio: 'ignore' });
 54 |     return true;
 55 |   } catch (e) {
 56 |     return false;
 57 |   }
 58 | }
 59 | 
 60 | /**
 61 |  * Checks if virtual environment exists
 62 |  */
 63 | function venvExists() {
 64 |   const venvPython = os.platform() === 'win32' 
 65 |     ? path.join(VENV_DIR, 'Scripts', 'python.exe')
 66 |     : path.join(VENV_DIR, 'bin', 'python');
 67 |   return fs.existsSync(venvPython);
 68 | }
 69 | 
 70 | /**
 71 |  * Creates a Python virtual environment
 72 |  */
 73 | async function createVirtualEnv() {
 74 |   if (venvExists()) {
 75 |     log('Python virtual environment already exists.', colors.green);
 76 |     return;
 77 |   }
 78 | 
 79 |   log('Creating Python virtual environment...', colors.blue);
 80 |   return new Promise((resolve, reject) => {
 81 |     const proc = spawn(PYTHON_COMMAND, ['-m', 'venv', VENV_DIR]);
 82 |     
 83 |     proc.on('close', (code) => {
 84 |       if (code === 0) {
 85 |         log('Python virtual environment created successfully.', colors.green);
 86 |         resolve();
 87 |       } else {
 88 |         log(`Failed to create virtual environment (exit code ${code}).`, colors.red);
 89 |         reject(new Error(`Failed to create virtual environment (exit code ${code})`));
 90 |       }
 91 |     });
 92 |     
 93 |     proc.on('error', (err) => {
 94 |       log(`Error creating virtual environment: ${err.message}`, colors.red);
 95 |       reject(err);
 96 |     });
 97 |   });
 98 | }
 99 | 
100 | /**
101 |  * Installs Python dependencies
102 |  */
103 | async function installDependencies() {
104 |   const pipCmd = os.platform() === 'win32' 
105 |     ? path.join(VENV_DIR, 'Scripts', 'pip.exe')
106 |     : path.join(VENV_DIR, 'bin', 'pip');
107 |   
108 |   log('Installing Python dependencies...', colors.blue);
109 |   return new Promise((resolve, reject) => {
110 |     const proc = spawn(pipCmd, ['install', '-r', REQUIREMENTS_FILE]);
111 |     
112 |     proc.stdout.on('data', (data) => {
113 |       process.stdout.write(data.toString());
114 |     });
115 |     
116 |     proc.stderr.on('data', (data) => {
117 |       process.stderr.write(data.toString());
118 |     });
119 |     
120 |     proc.on('close', (code) => {
121 |       if (code === 0) {
122 |         log('Python dependencies installed successfully.', colors.green);
123 |         resolve();
124 |       } else {
125 |         log(`Failed to install dependencies (exit code ${code}).`, colors.red);
126 |         reject(new Error(`Failed to install dependencies (exit code ${code})`));
127 |       }
128 |     });
129 |     
130 |     proc.on('error', (err) => {
131 |       log(`Error installing dependencies: ${err.message}`, colors.red);
132 |       reject(err);
133 |     });
134 |   });
135 | }
136 | 
137 | /**
138 |  * Pre-downloads the E5 model
139 |  */
140 | async function preDownloadModel() {
141 |   const pythonCmd = os.platform() === 'win32'
142 |       ? path.join(VENV_DIR, 'Scripts', 'python.exe')
143 |       : path.join(VENV_DIR, 'bin', 'python');
144 | 
145 |   const downloadScriptPath = path.join(PYTHON_SCRIPT_DIR, 'download_model.py');
146 | 
147 |   // Check if the download script already exists
148 |   if (!fs.existsSync(downloadScriptPath)) {
149 |     // Create a simple script to download the model
150 |     const downloadScript = `
151 | import os
152 | from transformers import AutoTokenizer, AutoModel
153 | 
154 | # Set the model name
155 | MODEL_NAME = "intfloat/multilingual-e5-large-instruct"
156 | 
157 | print(f"Pre-downloading model: {MODEL_NAME}")
158 | print("This might take a few minutes...")
159 | 
160 | # Download the model and tokenizer
161 | tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
162 | model = AutoModel.from_pretrained(MODEL_NAME)
163 | 
164 | print(f"Model downloaded and cached at: {os.path.expanduser('~/.cache/huggingface/transformers')}")
165 | print("Setup completed successfully!")
166 | `;
167 | 
168 |     fs.writeFileSync(downloadScriptPath, downloadScript);
169 |   }
170 | 
171 |   log('Pre-downloading the E5 model (this may take a few minutes)...', colors.blue);
172 |   return new Promise((resolve, reject) => {
173 |     const proc = spawn(pythonCmd, [downloadScriptPath]);
174 | 
175 |     proc.stdout.on('data', (data) => {
176 |       process.stdout.write(data.toString());
177 |     });
178 | 
179 |     proc.stderr.on('data', (data) => {
180 |       process.stderr.write(data.toString());
181 |     });
182 | 
183 |     proc.on('close', (code) => {
184 |       if (code === 0) {
185 |         log('E5 model downloaded successfully.', colors.green);
186 |         resolve();
187 |       } else {
188 |         log(`Failed to download model (exit code ${code}).`, colors.red);
189 |         // Don't reject since this is not critical - the model will be downloaded on first use
190 |         resolve();
191 |       }
192 |     });
193 | 
194 |     proc.on('error', (err) => {
195 |       log(`Error downloading model: ${err.message}`, colors.red);
196 |       // Don't reject since this is not critical - the model will be downloaded on first use
197 |       resolve();
198 |     });
199 |   });
200 | }
201 | 
202 | /**
203 |  * Main function
204 |  */
205 | async function main() {
206 |   log('Starting setup for semantic search...', colors.blue);
207 |   
208 |   // Check if Python is installed
209 |   if (!commandExists(PYTHON_COMMAND)) {
210 |     log(`${PYTHON_COMMAND} not found. Please install Python 3.8 or later.`, colors.red);
211 |     process.exit(1);
212 |   }
213 |   
214 |   try {
215 |     // Create virtual environment
216 |     await createVirtualEnv();
217 |     
218 |     // Install dependencies
219 |     await installDependencies();
220 |     
221 |     // Pre-download model (optional)
222 |     await preDownloadModel();
223 |     
224 |     log('Setup completed successfully!', colors.green);
225 |   } catch (error) {
226 |     log(`Setup failed: ${error.message}`, colors.red);
227 |     process.exit(1);
228 |   }
229 | }
230 | 
231 | // Run the main function
232 | main(); 
```

--------------------------------------------------------------------------------
/src/lib/embedding-service.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { spawn } from 'child_process';
  2 | import path from 'path';
  3 | import os from 'os';
  4 | import { logger } from './logger.js';
  5 | 
  6 | /**
  7 |  * Service for generating embeddings using the E5 model
  8 |  */
  9 | export class EmbeddingService {
 10 |   private pythonProcess: any;
 11 |   private initialized: boolean = false;
 12 |   private initializationPromise: Promise<void> | null = null;
 13 |   private readonly pythonScriptPath: string;
 14 |   private readonly pythonCommand: string;
 15 |   private readonly taskDescription: string;
 16 | 
 17 |   /**
 18 |    * Creates a new embedding service
 19 |    * @param options Configuration options
 20 |    */
 21 |   constructor(
 22 |     options: {
 23 |       pythonScriptPath?: string;
 24 |       taskDescription?: string;
 25 |       useVenv?: boolean;
 26 |     } = {}
 27 |   ) {
 28 |     this.pythonScriptPath =
 29 |       options.pythonScriptPath || path.join(process.cwd(), 'src', 'python', 'embedding_service.py');
 30 |     this.taskDescription =
 31 |       options.taskDescription || 'Given a document, find semantically similar documents';
 32 | 
 33 |     // Determine which Python executable to use based on the useVenv option
 34 |     const useVenv = options.useVenv !== undefined ? options.useVenv : true;
 35 |     if (useVenv) {
 36 |       const venvDir = path.join(process.cwd(), 'venv');
 37 |       this.pythonCommand =
 38 |         os.platform() === 'win32'
 39 |           ? path.join(venvDir, 'Scripts', 'python.exe')
 40 |           : path.join(venvDir, 'bin', 'python');
 41 |     } else {
 42 |       this.pythonCommand = os.platform() === 'win32' ? 'python' : 'python3';
 43 |     }
 44 |   }
 45 | 
 46 |   /**
 47 |    * Initializes the embedding service
 48 |    */
 49 |   async initialize(): Promise<void> {
 50 |     if (this.initialized) {
 51 |       return;
 52 |     }
 53 | 
 54 |     if (this.initializationPromise) {
 55 |       return this.initializationPromise;
 56 |     }
 57 | 
 58 |     this.initializationPromise = new Promise<void>((resolve, reject) => {
 59 |       try {
 60 |         logger.debug('Initializing embedding service', {
 61 |           scriptPath: this.pythonScriptPath,
 62 |           pythonCommand: this.pythonCommand,
 63 |         });
 64 | 
 65 |         // Spawn Python process
 66 |         this.pythonProcess = spawn(this.pythonCommand, [this.pythonScriptPath]);
 67 | 
 68 |         // Handle process exit
 69 |         this.pythonProcess.on('exit', (code: number) => {
 70 |           logger.error('Embedding service process exited', { code });
 71 |           this.initialized = false;
 72 |           this.pythonProcess = null;
 73 |         });
 74 | 
 75 |         // Handle process errors
 76 |         this.pythonProcess.on('error', (error: Error) => {
 77 |           logger.error('Error in embedding service process', error);
 78 |           reject(error);
 79 |         });
 80 | 
 81 |         // Log stderr output
 82 |         this.pythonProcess.stderr.on('data', (data: Buffer) => {
 83 |           logger.debug('Embedding service stderr:', { message: data.toString().trim() });
 84 |         });
 85 | 
 86 |         // Initialize the model
 87 |         this.sendCommand({ command: 'initialize' })
 88 |           .then(() => {
 89 |             this.initialized = true;
 90 |             logger.debug('Embedding service initialized successfully');
 91 |             resolve();
 92 |           })
 93 |           .catch((error) => {
 94 |             logger.error('Failed to initialize embedding service', error);
 95 |             reject(error);
 96 |           });
 97 |       } catch (error) {
 98 |         logger.error('Error initializing embedding service', error);
 99 |         reject(error);
100 |       }
101 |     });
102 | 
103 |     return this.initializationPromise;
104 |   }
105 | 
106 |   /**
107 |    * Generates an embedding for a single text
108 |    * @param text The text to generate an embedding for
109 |    * @returns The embedding vector
110 |    */
111 |   async generateEmbedding(text: string): Promise<number[]> {
112 |     if (!this.initialized) {
113 |       await this.initialize();
114 |     }
115 | 
116 |     const result = await this.sendCommand({
117 |       command: 'generate_embedding',
118 |       text,
119 |       task: this.taskDescription,
120 |     });
121 | 
122 |     if (result.error) {
123 |       throw new Error(`Embedding generation failed: ${result.error}`);
124 |     }
125 | 
126 |     return result.embedding;
127 |   }
128 | 
129 |   /**
130 |    * Generate embeddings for multiple texts
131 |    * @param texts Array of texts to generate embeddings for
132 |    * @param options Options for embedding generation
133 |    * @returns Promise that resolves to an array of embedding vectors
134 |    */
135 |   async generateEmbeddings(
136 |     texts: string[],
137 |     options?: { input_type?: 'query' | 'passage' }
138 |   ): Promise<number[][]> {
139 |     if (!this.initialized) {
140 |       await this.initialize();
141 |     }
142 | 
143 |     if (!texts || texts.length === 0) {
144 |       throw new Error('No texts provided for embedding generation');
145 |     }
146 | 
147 |     logger.debug(`Generating embeddings for ${texts.length} texts`);
148 | 
149 |     try {
150 |       const command = {
151 |         command: 'generate_embeddings',
152 |         texts: texts,
153 |         is_query: options?.input_type === 'query',
154 |       };
155 | 
156 |       const result = await this.sendCommand(command);
157 | 
158 |       if (result.error) {
159 |         throw new Error(`Batch embedding generation failed: ${result.error}`);
160 |       }
161 | 
162 |       if (!result.embeddings || !Array.isArray(result.embeddings)) {
163 |         throw new Error('Invalid response from embedding service');
164 |       }
165 | 
166 |       return result.embeddings;
167 |     } catch (error: any) {
168 |       logger.error('Error generating embeddings', error);
169 |       throw new Error(`Batch embedding generation failed: ${error.message}`);
170 |     }
171 |   }
172 | 
173 |   /**
174 |    * Sends a command to the Python process
175 |    * @param command The command to send
176 |    * @returns The result from the Python process
177 |    */
178 |   private sendCommand(command: any): Promise<any> {
179 |     return new Promise((resolve, reject) => {
180 |       if (!this.pythonProcess) {
181 |         reject(new Error('Python process not initialized'));
182 |         return;
183 |       }
184 | 
185 |       // Buffer to accumulate response chunks
186 |       let responseBuffer = '';
187 | 
188 |       // Set up data handler that accumulates chunks
189 |       const responseHandler = (data: Buffer) => {
190 |         const chunk = data.toString();
191 |         responseBuffer += chunk;
192 | 
193 |         try {
194 |           // Try to parse the accumulated buffer
195 |           const response = JSON.parse(responseBuffer);
196 | 
197 |           // If successful parsing, clean up and resolve
198 |           this.pythonProcess.stdout.removeListener('data', responseHandler);
199 |           resolve(response);
200 |         } catch (error) {
201 |           // Incomplete JSON, continue accumulating
202 |           // This is expected for large responses split across chunks
203 |           // We'll keep collecting chunks until we get valid JSON
204 |         }
205 |       };
206 | 
207 |       // Handle error and end events
208 |       const errorHandler = (error: Error) => {
209 |         this.pythonProcess.stdout.removeListener('data', responseHandler);
210 |         reject(error);
211 |       };
212 | 
213 |       // Set up event listeners
214 |       this.pythonProcess.stdout.on('data', responseHandler);
215 |       this.pythonProcess.stdout.once('error', errorHandler);
216 | 
217 |       // Send command to Python process
218 |       this.pythonProcess.stdin.write(JSON.stringify(command) + '\n');
219 | 
220 |       // Set a reasonable timeout (30 seconds)
221 |       setTimeout(() => {
222 |         this.pythonProcess.stdout.removeListener('data', responseHandler);
223 |         reject(new Error('Timeout waiting for embedding service response'));
224 |       }, 30000);
225 |     });
226 |   }
227 | 
228 |   /**
229 |    * Closes the embedding service
230 |    */
231 |   async close(): Promise<void> {
232 |     if (this.pythonProcess) {
233 |       this.pythonProcess.kill();
234 |       this.pythonProcess = null;
235 |       this.initialized = false;
236 |       this.initializationPromise = null;
237 |       logger.debug('Embedding service closed');
238 |     }
239 |   }
240 | }
241 | 
```

--------------------------------------------------------------------------------
/src/lib/http-server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import http from 'http';
  2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  3 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
  4 | import { logger } from './logger.js';
  5 | import { URL } from 'url';
  6 | 
  7 | // Store active SSE transports by session ID
  8 | const activeTransports = new Map<string, SSEServerTransport>();
  9 | 
 10 | // Store heartbeat intervals by session ID
 11 | const heartbeatIntervals = new Map<string, NodeJS.Timeout>();
 12 | 
 13 | /**
 14 |  * Get transport by session ID - useful for reconnection
 15 |  */
 16 | export function getTransport(sessionId: string): SSEServerTransport | undefined {
 17 |   return activeTransports.get(sessionId);
 18 | }
 19 | 
 20 | /**
 21 |  * Set up an HTTP server with SSE transport for the MCP server
 22 |  */
 23 | export function setupHttpServer(server: McpServer, port: number): http.Server {
 24 |   // Create HTTP server
 25 |   const httpServer = http.createServer((req, res) => {
 26 |     res.setHeader('Access-Control-Allow-Origin', '*');
 27 |     res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
 28 |     res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
 29 | 
 30 |     // Handle preflight requests
 31 |     if (req.method === 'OPTIONS') {
 32 |       res.writeHead(204);
 33 |       res.end();
 34 |       return;
 35 |     }
 36 | 
 37 |     // Parse URL
 38 |     const reqUrl = new URL(req.url || '/', `http://localhost:${port}`);
 39 |     const path = reqUrl.pathname;
 40 | 
 41 |     // Health check endpoint
 42 |     if (path === '/health' && req.method === 'GET') {
 43 |       res.writeHead(200, { 'Content-Type': 'application/json' });
 44 |       res.end(JSON.stringify({ status: 'ok' }));
 45 |       return;
 46 |     }
 47 | 
 48 |     // SSE endpoint
 49 |     if (path === '/sse' && req.method === 'GET') {
 50 |       logger.info('New SSE connection established');
 51 | 
 52 |       // Prevent timeout for long-running connections
 53 |       req.socket.setTimeout(0);
 54 |       req.socket.setKeepAlive(true);
 55 |       req.socket.setMaxListeners(20);
 56 | 
 57 |       // Check if there's a session ID for reconnection
 58 |       const urlParams = new URLSearchParams(reqUrl.search);
 59 |       const existingSessionId = urlParams.get('sessionId');
 60 | 
 61 |       // Create SSE transport with a fixed endpoint path
 62 |       const sseTransport = new SSEServerTransport('message', res);
 63 | 
 64 |       // If reconnecting with an existing session ID, store it
 65 |       if (existingSessionId) {
 66 |         logger.debug(`Attempting to reconnect with existing session ID: ${existingSessionId}`);
 67 |       }
 68 | 
 69 |       // Store the transport in our map
 70 |       const sessionId = sseTransport.sessionId;
 71 |       activeTransports.set(sessionId, sseTransport);
 72 | 
 73 |       logger.debug(`Created new SSE transport with session ID: ${sessionId}`);
 74 | 
 75 |       // Set up heartbeat to keep connection alive
 76 |       // This is critical for preventing connection timeouts and ensuring we don't lose message correlation
 77 |       // The heartbeat sends an empty comment every 30 seconds to keep the connection open
 78 |       const heartbeatInterval = setInterval(() => {
 79 |         if (!res.writableEnded) {
 80 |           try {
 81 |             // Send a comment as heartbeat
 82 |             res.write(`:heartbeat\n\n`);
 83 |             logger.debug(`Sent heartbeat to session ${sessionId}`);
 84 |           } catch (err) {
 85 |             logger.error(`Failed to send heartbeat to session ${sessionId}`, err);
 86 |             clearInterval(heartbeatInterval);
 87 |             activeTransports.delete(sessionId);
 88 |           }
 89 |         } else {
 90 |           // Connection already ended
 91 |           clearInterval(heartbeatInterval);
 92 |           activeTransports.delete(sessionId);
 93 |         }
 94 |       }, 30000);
 95 | 
 96 |       // Store the heartbeat interval
 97 |       heartbeatIntervals.set(sessionId, heartbeatInterval);
 98 | 
 99 |       // Connect server to transport
100 |       server.connect(sseTransport).catch((error) => {
101 |         logger.error('Failed to connect to SSE transport', error);
102 |         clearInterval(heartbeatIntervals.get(sessionId)!);
103 |         heartbeatIntervals.delete(sessionId);
104 |         activeTransports.delete(sessionId);
105 |       });
106 | 
107 |       // Handle connection close
108 |       req.on('close', () => {
109 |         logger.info(`SSE connection closed for session: ${sessionId}`);
110 | 
111 |         // Clean up resources
112 |         const interval = heartbeatIntervals.get(sessionId);
113 |         if (interval) {
114 |           clearInterval(interval);
115 |           heartbeatIntervals.delete(sessionId);
116 |         }
117 | 
118 |         activeTransports.delete(sessionId);
119 |         sseTransport.close().catch((error) => {
120 |           logger.error('Error closing SSE transport', error);
121 |         });
122 |       });
123 | 
124 |       return;
125 |     }
126 | 
127 |     // Message endpoint
128 |     if (path === '/message' && req.method === 'POST') {
129 |       const urlParams = new URLSearchParams(reqUrl.search);
130 |       const sessionId = urlParams.get('sessionId');
131 | 
132 |       if (!sessionId) {
133 |         res.writeHead(400, { 'Content-Type': 'application/json' });
134 |         res.end(JSON.stringify({ error: 'Missing sessionId parameter' }));
135 |         return;
136 |       }
137 | 
138 |       const transport = activeTransports.get(sessionId);
139 |       if (!transport) {
140 |         res.writeHead(404, { 'Content-Type': 'application/json' });
141 |         res.end(JSON.stringify({
142 |           error: 'Session not found or expired',
143 |           reconnect: true
144 |         }));
145 |         return;
146 |       }
147 | 
148 |       // Pass the request to the SSE transport's handlePostMessage method
149 |       try {
150 |         transport.handlePostMessage(req, res);
151 |       } catch (error) {
152 |         logger.error(`Error handling message for session ${sessionId}`, error);
153 |         if (!res.headersSent) {
154 |           res.writeHead(500, { 'Content-Type': 'application/json' });
155 |           res.end(JSON.stringify({
156 |             error: 'Internal server error',
157 |             details: error instanceof Error ? error.message : String(error)
158 |           }));
159 |         }
160 |       }
161 | 
162 |       return;
163 |     }
164 | 
165 |     // Not found
166 |     res.writeHead(404, { 'Content-Type': 'application/json' });
167 |     res.end(JSON.stringify({ error: 'Not found' }));
168 |   });
169 | 
170 |   // Start HTTP server
171 |   httpServer.listen(port, () => {
172 |     logger.info(`HTTP server listening on port ${port}`);
173 |     logger.info('Server ready to receive requests via HTTP');
174 |     logger.info(`SSE endpoint: http://localhost:${port}/sse`);
175 |     logger.info(`Message endpoint: http://localhost:${port}/message?sessionId=<SESSION_ID>`);
176 |   });
177 | 
178 |   return httpServer;
179 | }
180 | 
181 | /**
182 |  * Close all active SSE transports
183 |  */
184 | export async function closeAllTransports(): Promise<void> {
185 |   logger.info(`Closing ${activeTransports.size} active SSE transports`);
186 | 
187 |   // Clear all heartbeat intervals
188 |   for (const [sessionId, interval] of heartbeatIntervals.entries()) {
189 |     clearInterval(interval);
190 |     heartbeatIntervals.delete(sessionId);
191 |   }
192 | 
193 |   // Use Promise.allSettled to ensure we attempt to close all transports
194 |   // even if some fail
195 |   const closePromises = Array.from(activeTransports.entries()).map(
196 |       async ([sessionId, transport]) => {
197 |         try {
198 |           logger.debug(`Closing SSE transport for session: ${sessionId}`);
199 |           await Promise.race([
200 |             transport.close(),
201 |             // Add a timeout to prevent hanging
202 |             new Promise((_, reject) =>
203 |                 setTimeout(() => reject(new Error('Transport close timeout')), 1000)
204 |             ),
205 |           ]);
206 |           return { sessionId, success: true };
207 |         } catch (error) {
208 |           logger.error(`Error closing SSE transport for session ${sessionId}`, error);
209 |           return { sessionId, success: false };
210 |         }
211 |       }
212 |   );
213 | 
214 |   await Promise.allSettled(closePromises);
215 | 
216 |   // Clear the map regardless of success/failure
217 |   activeTransports.clear();
218 |   logger.info('All SSE transports closed or timed out');
219 | }
220 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "compilerOptions": {
  3 |     /* Visit https://aka.ms/tsconfig to read more about this file */
  4 | 
  5 |     /* Projects */
  6 |     // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
  7 |     // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
  8 |     // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
  9 |     // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
 10 |     // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
 11 |     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 12 | 
 13 |     /* Language and Environment */
 14 |     "target": "ES2020",
 15 |     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
 16 |     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
 17 |     // "libReplacement": true,                           /* Enable lib replacement. */
 18 |     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
 19 |     // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
 20 |     // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
 21 |     // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
 22 |     // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
 23 |     // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
 24 |     // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
 25 |     // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
 26 |     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
 27 | 
 28 |     /* Modules */
 29 |     "module": "NodeNext",
 30 |     "moduleResolution": "NodeNext",
 31 |     // "rootDir": "./",                                  /* Specify the root folder within your source files. */
 32 |     // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
 33 |     // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
 34 |     // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
 35 |     // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
 36 |     // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
 37 |     // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
 38 |     // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
 39 |     // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
 40 |     // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
 41 |     // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
 42 |     // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
 43 |     // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
 44 |     // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
 45 |     "resolveJsonModule": true,
 46 |     // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
 47 |     // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
 48 | 
 49 |     /* JavaScript Support */
 50 |     // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
 51 |     // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
 52 |     // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
 53 | 
 54 |     /* Emit */
 55 |     "declaration": true,
 56 |     // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
 57 |     // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
 58 |     "sourceMap": true,
 59 |     // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
 60 |     // "noEmit": true,                                   /* Disable emitting files from a compilation. */
 61 |     // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
 62 |     "outDir": "./dist",
 63 |     // "removeComments": true,                           /* Disable emitting comments. */
 64 |     // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
 65 |     // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
 66 |     // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
 67 |     // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
 68 |     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
 69 |     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
 70 |     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
 71 |     // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
 72 |     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
 73 |     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
 74 |     // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
 75 |     // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
 76 | 
 77 |     /* Interop Constraints */
 78 |     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
 79 |     // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
 80 |     // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
 81 |     // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
 82 |     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
 83 |     "esModuleInterop": true,
 84 |     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
 85 |     "forceConsistentCasingInFileNames": true,
 86 | 
 87 |     /* Type Checking */
 88 |     "strict": true,
 89 |     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
 90 |     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
 91 |     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
 92 |     // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
 93 |     // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
 94 |     // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
 95 |     // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
 96 |     // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
 97 |     // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
 98 |     // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
 99 |     // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
100 |     // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
101 |     // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
102 |     // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
103 |     // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
104 |     // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
105 |     // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
106 |     // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
107 |     // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
108 | 
109 |     /* Completeness */
110 |     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
111 |     "skipLibCheck": true
112 |   },
113 |   "include": ["src/**/*"],
114 |   "exclude": ["node_modules", "dist"]
115 | }
116 | 
```

--------------------------------------------------------------------------------
/src/lib/tool-implementations.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import { SQLiteDataStore } from './sqlite-store.js';
  3 | import { createNamespace, deleteNamespace, listNamespaces } from './namespace-manager.js';
  4 | import {
  5 |   storeContextItem,
  6 |   retrieveContextItemByKey,
  7 |   deleteContextItem,
  8 |   listContextItemKeys,
  9 | } from './context-manager.js';
 10 | import { toolSchemas } from './tool-definitions.js';
 11 | import { logger } from './logger.js';
 12 | import { normalizeError } from './errors.js';
 13 | import { retrieveBySemantic } from './search-module.js';
 14 | 
 15 | /**
 16 |  * Registers all tool implementations with the MCP server
 17 |  * @param server MCP server instance
 18 |  * @param db Database connection
 19 |  */
 20 | export function registerTools(server: McpServer, db: SQLiteDataStore): void {
 21 |   server.tool(
 22 |     toolSchemas.retrieve_context_item_by_key.name,
 23 |     toolSchemas.retrieve_context_item_by_key.description,
 24 |     toolSchemas.retrieve_context_item_by_key.schema.shape,
 25 |     async ({ namespace, key }) => {
 26 |       try {
 27 |         logger.debug('Executing retrieve_context_item_by_key tool', { namespace, key });
 28 |         const result = await retrieveContextItemByKey(db, namespace, key);
 29 | 
 30 |         return {
 31 |           content: [{ type: 'text', text: JSON.stringify({ result }) }],
 32 |         };
 33 |       } catch (error: any) {
 34 |         const normalizedError = normalizeError(error);
 35 |         logger.error('Error retrieving context item', normalizedError, { namespace, key });
 36 |         return {
 37 |           isError: true,
 38 |           content: [
 39 |             { type: 'text', text: `Error retrieving context item: ${normalizedError.message}` },
 40 |           ],
 41 |         };
 42 |       }
 43 |     }
 44 |   );
 45 | 
 46 |   server.tool(
 47 |     toolSchemas.store_context_item.name,
 48 |     toolSchemas.store_context_item.description,
 49 |     toolSchemas.store_context_item.schema.shape,
 50 |     async ({ namespace, key, value }) => {
 51 |       try {
 52 |         logger.debug('Executing store_context_item tool', { namespace, key });
 53 | 
 54 |         // Try to parse the value as JSON or number before storing
 55 |         let parsedValue: any = value;
 56 | 
 57 |         // First try to parse as JSON
 58 |         try {
 59 |           // Only attempt to parse if the value looks like JSON (starts with { or [)
 60 |           if (value.trim().startsWith('{') || value.trim().startsWith('[')) {
 61 |             parsedValue = JSON.parse(value);
 62 |             logger.debug('Parsed value as JSON object', { namespace, key });
 63 |           }
 64 |         } catch (parseError: any) {
 65 |           // If JSON parsing fails, keep the original string value
 66 |           logger.debug('Failed to parse value as JSON, keeping as string', {
 67 |             namespace,
 68 |             key,
 69 |             error: parseError.message,
 70 |           });
 71 |         }
 72 | 
 73 |         // If it's still a string, try to parse as other types
 74 |         if (typeof parsedValue === 'string') {
 75 |           const trimmedValue = parsedValue.trim();
 76 | 
 77 |           // Check for number
 78 |           if (/^-?\d+(\.\d+)?$/.test(trimmedValue)) {
 79 |             const numValue = Number(trimmedValue);
 80 |             if (!isNaN(numValue)) {
 81 |               parsedValue = numValue;
 82 |               logger.debug('Parsed value as number', { namespace, key });
 83 |             }
 84 |           }
 85 |           // Check for boolean
 86 |           else if (trimmedValue === 'true' || trimmedValue === 'false') {
 87 |             parsedValue = trimmedValue === 'true';
 88 |             logger.debug('Parsed value as boolean', { namespace, key });
 89 |           }
 90 |           // Check for null
 91 |           else if (trimmedValue === 'null') {
 92 |             parsedValue = null;
 93 |             logger.debug('Parsed value as null', { namespace, key });
 94 |           }
 95 |         }
 96 | 
 97 |         // Check if the item already exists
 98 |         const existingItem = await retrieveContextItemByKey(db, namespace, key);
 99 |         if (existingItem) {
100 |           // Item already exists, return as an error
101 |           return {
102 |             isError: true,
103 |             content: [
104 |               {
105 |                 type: 'text',
106 |                 text: `Error storing context item: An item with the key "${key}" already exists in namespace "${namespace}". Use update_context_item to modify existing items.`,
107 |               },
108 |             ],
109 |           };
110 |         }
111 | 
112 |         // Store the parsed value
113 |         await storeContextItem(db, namespace, key, parsedValue);
114 |         return {
115 |           content: [{ type: 'text', text: JSON.stringify({ result: true }) }],
116 |         };
117 |       } catch (error: any) {
118 |         const normalizedError = normalizeError(error);
119 |         logger.error('Error storing context item', normalizedError, { namespace, key });
120 |         return {
121 |           isError: true,
122 |           content: [
123 |             { type: 'text', text: `Error storing context item: ${normalizedError.message}` },
124 |           ],
125 |         };
126 |       }
127 |     }
128 |   );
129 | 
130 |   server.tool(
131 |     toolSchemas.update_context_item.name,
132 |     toolSchemas.update_context_item.description,
133 |     toolSchemas.update_context_item.schema.shape,
134 |     async ({ namespace, key, value }) => {
135 |       try {
136 |         logger.debug('Executing update_context_item tool', { namespace, key });
137 | 
138 |         // Check if the item exists
139 |         const existingItem = await retrieveContextItemByKey(db, namespace, key);
140 |         if (!existingItem) {
141 |           // Item doesn't exist, return as an error
142 |           return {
143 |             isError: true,
144 |             content: [
145 |               {
146 |                 type: 'text',
147 |                 text: `Error updating context item: No item with the key "${key}" exists in namespace "${namespace}". Use store_context_item to create new items.`,
148 |               },
149 |             ],
150 |           };
151 |         }
152 | 
153 |         // Try to parse the value as JSON or number before storing
154 |         let parsedValue: any = value;
155 | 
156 |         // First try to parse as JSON
157 |         try {
158 |           // Only attempt to parse if the value looks like JSON (starts with { or [)
159 |           if (value.trim().startsWith('{') || value.trim().startsWith('[')) {
160 |             parsedValue = JSON.parse(value);
161 |             logger.debug('Parsed value as JSON object', { namespace, key });
162 |           }
163 |         } catch (parseError: any) {
164 |           // If JSON parsing fails, keep the original string value
165 |           logger.debug('Failed to parse value as JSON, keeping as string', {
166 |             namespace,
167 |             key,
168 |             error: parseError.message,
169 |           });
170 |         }
171 | 
172 |         // If it's still a string, try to parse as other types
173 |         if (typeof parsedValue === 'string') {
174 |           const trimmedValue = parsedValue.trim();
175 | 
176 |           // Check for number
177 |           if (/^-?\d+(\.\d+)?$/.test(trimmedValue)) {
178 |             const numValue = Number(trimmedValue);
179 |             if (!isNaN(numValue)) {
180 |               parsedValue = numValue;
181 |               logger.debug('Parsed value as number', { namespace, key });
182 |             }
183 |           }
184 |           // Check for boolean
185 |           else if (trimmedValue === 'true' || trimmedValue === 'false') {
186 |             parsedValue = trimmedValue === 'true';
187 |             logger.debug('Parsed value as boolean', { namespace, key });
188 |           }
189 |           // Check for null
190 |           else if (trimmedValue === 'null') {
191 |             parsedValue = null;
192 |             logger.debug('Parsed value as null', { namespace, key });
193 |           }
194 |         }
195 | 
196 |         // Update the item
197 |         await storeContextItem(db, namespace, key, parsedValue);
198 |         return {
199 |           content: [{ type: 'text', text: JSON.stringify({ result: true }) }],
200 |         };
201 |       } catch (error: any) {
202 |         const normalizedError = normalizeError(error);
203 |         logger.error('Error updating context item', normalizedError, { namespace, key });
204 |         return {
205 |           isError: true,
206 |           content: [
207 |             { type: 'text', text: `Error updating context item: ${normalizedError.message}` },
208 |           ],
209 |         };
210 |       }
211 |     }
212 |   );
213 | 
214 |   server.tool(
215 |     toolSchemas.delete_context_item.name,
216 |     toolSchemas.delete_context_item.description,
217 |     toolSchemas.delete_context_item.schema.shape,
218 |     async ({ namespace, key }) => {
219 |       try {
220 |         logger.debug('Executing delete_context_item tool', { namespace, key });
221 |         const deleted = await deleteContextItem(db, namespace, key);
222 | 
223 |         return {
224 |           content: [
225 |             {
226 |               type: 'text',
227 |               text: JSON.stringify({
228 |                 result: deleted, // Will be true if item was deleted, false if it didn't exist
229 |               }),
230 |             },
231 |           ],
232 |         };
233 |       } catch (error: any) {
234 |         const normalizedError = normalizeError(error);
235 |         logger.error('Error deleting context item', normalizedError, { namespace, key });
236 |         return {
237 |           isError: true,
238 |           content: [
239 |             { type: 'text', text: `Error deleting context item: ${normalizedError.message}` },
240 |           ],
241 |         };
242 |       }
243 |     }
244 |   );
245 | 
246 |   // Namespace Management
247 |   server.tool(
248 |     toolSchemas.create_namespace.name,
249 |     toolSchemas.create_namespace.description,
250 |     toolSchemas.create_namespace.schema.shape,
251 |     async ({ namespace }) => {
252 |       try {
253 |         logger.debug('Executing create_namespace tool', { namespace });
254 |         const created = await createNamespace(db, namespace);
255 | 
256 |         if (!created) {
257 |           // Namespace already exists, return as an error
258 |           return {
259 |             isError: true,
260 |             content: [
261 |               {
262 |                 type: 'text',
263 |                 text: `Error creating namespace: A namespace with the name "${namespace}" already exists.`,
264 |               },
265 |             ],
266 |           };
267 |         }
268 | 
269 |         return {
270 |           content: [
271 |             {
272 |               type: 'text',
273 |               text: JSON.stringify({
274 |                 result: true,
275 |               }),
276 |             },
277 |           ],
278 |         };
279 |       } catch (error: any) {
280 |         const normalizedError = normalizeError(error);
281 |         logger.error('Error creating namespace', normalizedError, { namespace });
282 |         return {
283 |           isError: true,
284 |           content: [{ type: 'text', text: `Error creating namespace: ${normalizedError.message}` }],
285 |         };
286 |       }
287 |     }
288 |   );
289 | 
290 |   server.tool(
291 |     toolSchemas.delete_namespace.name,
292 |     toolSchemas.delete_namespace.description,
293 |     toolSchemas.delete_namespace.schema.shape,
294 |     async ({ namespace }) => {
295 |       try {
296 |         logger.debug('Executing delete_namespace tool', { namespace });
297 |         const deleted = await deleteNamespace(db, namespace);
298 | 
299 |         return {
300 |           content: [
301 |             {
302 |               type: 'text',
303 |               text: JSON.stringify({
304 |                 result: deleted, // Will be true if namespace was deleted, false if it didn't exist
305 |               }),
306 |             },
307 |           ],
308 |         };
309 |       } catch (error: any) {
310 |         const normalizedError = normalizeError(error);
311 |         logger.error('Error deleting namespace', normalizedError, { namespace });
312 |         return {
313 |           isError: true,
314 |           content: [{ type: 'text', text: `Error deleting namespace: ${normalizedError.message}` }],
315 |         };
316 |       }
317 |     }
318 |   );
319 | 
320 |   server.tool(
321 |     toolSchemas.list_namespaces.name,
322 |     toolSchemas.list_namespaces.description,
323 |     toolSchemas.list_namespaces.schema.shape,
324 |     async () => {
325 |       try {
326 |         logger.debug('Executing list_namespaces tool');
327 |         const namespaces = await listNamespaces(db);
328 | 
329 |         return {
330 |           content: [{ type: 'text', text: JSON.stringify({ result: namespaces }) }],
331 |         };
332 |       } catch (error: any) {
333 |         const normalizedError = normalizeError(error);
334 |         logger.error('Error listing namespaces', normalizedError);
335 |         return {
336 |           isError: true,
337 |           content: [{ type: 'text', text: `Error listing namespaces: ${normalizedError.message}` }],
338 |         };
339 |       }
340 |     }
341 |   );
342 | 
343 |   // Keys Listing
344 |   server.tool(
345 |     toolSchemas.list_context_item_keys.name,
346 |     toolSchemas.list_context_item_keys.description,
347 |     toolSchemas.list_context_item_keys.schema.shape,
348 |     async ({ namespace }) => {
349 |       try {
350 |         logger.debug('Executing list_context_item_keys tool', { namespace });
351 |         const keys = await listContextItemKeys(db, namespace);
352 | 
353 |         return {
354 |           content: [{ type: 'text', text: JSON.stringify({ result: keys }) }],
355 |         };
356 |       } catch (error: any) {
357 |         const normalizedError = normalizeError(error);
358 |         logger.error('Error listing context item keys', normalizedError, { namespace });
359 |         return {
360 |           isError: true,
361 |           content: [
362 |             {
363 |               type: 'text',
364 |               text: `Error listing context item keys: ${normalizedError.message}`,
365 |             },
366 |           ],
367 |         };
368 |       }
369 |     }
370 |   );
371 | 
372 |   // Semantic search
373 |   server.tool(
374 |     'retrieve_context_items_by_semantic_search',
375 |     toolSchemas.retrieve_context_items_by_semantic_search.schema.shape,
376 |     async ({ namespace, query, limit, similarity_threshold }) => {
377 |       try {
378 |         logger.debug('Executing retrieve_context_items_by_semantic_search tool', {
379 |           namespace,
380 |           query,
381 |           limit,
382 |           similarity_threshold,
383 |         });
384 | 
385 |         const items = await retrieveBySemantic(db, namespace, query, {
386 |           limit: limit,
387 |           threshold: similarity_threshold,
388 |         });
389 | 
390 |         logger.debug('Retrieved items by semantic search', {
391 |           namespace,
392 |           query,
393 |           count: items.length,
394 |         });
395 | 
396 |         return {
397 |           content: [
398 |             {
399 |               type: 'text',
400 |               text: JSON.stringify({
401 |                 result: items.map((item) => ({
402 |                   value: item.value,
403 |                   similarity: item.similarity,
404 |                   created_at: item.created_at,
405 |                   updated_at: item.updated_at,
406 |                 })),
407 |               }),
408 |             },
409 |           ],
410 |         };
411 |       } catch (error: any) {
412 |         const normalizedError = normalizeError(error);
413 |         logger.error('Error retrieving items by semantic search', normalizedError, {
414 |           namespace,
415 |           query,
416 |         });
417 |         return {
418 |           isError: true,
419 |           content: [
420 |             {
421 |               type: 'text',
422 |               text: `Error retrieving items by semantic search: ${normalizedError.message}`,
423 |             },
424 |           ],
425 |         };
426 |       }
427 |     }
428 |   );
429 | }
430 | 
```

--------------------------------------------------------------------------------
/src/lib/sqlite-store.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import sqlite3 from 'sqlite3';
  2 | import { promisify } from 'util';
  3 | import fs from 'fs';
  4 | import path from 'path';
  5 | import { logger } from './logger.js';
  6 | import { DatabaseError, normalizeError } from './errors.js';
  7 | import { chunkTextBySemanticBoundaries } from './chunking-util.js';
  8 | 
  9 | // Define types for database operations
 10 | export interface ContextItem {
 11 |   namespace: string;
 12 |   key: string;
 13 |   value: any;
 14 |   created_at: string;
 15 |   updated_at: string;
 16 | }
 17 | 
 18 | export interface Database {
 19 |   run: (sql: string, ...params: any[]) => Promise<void>;
 20 |   get: (sql: string, ...params: any[]) => Promise<any>;
 21 |   all: (sql: string, ...params: any[]) => Promise<any[]>;
 22 |   close: () => Promise<void>;
 23 | }
 24 | 
 25 | export interface SQLiteDataStoreOptions {
 26 |   dbPath: string;
 27 |   // Add optional chunking flag
 28 |   enableChunking?: boolean;
 29 | }
 30 | 
 31 | // Define the DataStore interface
 32 | interface DataStore {
 33 |   storeDataItem(namespace: string, key: string, value: any): Promise<void>;
 34 |   retrieveDataItem(namespace: string, key: string): Promise<any | null>;
 35 |   deleteDataItem(namespace: string, key: string): Promise<boolean>;
 36 |   dataItemExists(namespace: string, key: string): Promise<boolean>;
 37 |   listAllNamespaces(): Promise<string[]>;
 38 |   listContextItemKeys(namespace: string): Promise<any[]>;
 39 |   createNamespace(namespace: string): Promise<boolean>;
 40 |   deleteNamespace(namespace: string): Promise<boolean>;
 41 |   // Optional method for non-chunked embeddings
 42 |   retrieveDataItemsByEmbeddingSimilarity?(
 43 |     namespace: string,
 44 |     queryText: string,
 45 |     embeddingService: any,
 46 |     options: any
 47 |   ): Promise<any[]>;
 48 |   retrieveAllItemsInNamespace(namespace: string): Promise<any[]>;
 49 | }
 50 | 
 51 | export class SQLiteDataStore implements DataStore {
 52 |   private dbPath: string;
 53 |   private db: sqlite3.Database | null = null;
 54 |   // Add chunking option
 55 |   private enableChunking: boolean;
 56 | 
 57 |   constructor(options: SQLiteDataStoreOptions) {
 58 |     this.dbPath = options.dbPath;
 59 |     // Default to false for backward compatibility
 60 |     this.enableChunking = options.enableChunking ?? false;
 61 |     logger.debug(
 62 |       `SQLite data store initialized, chunking ${this.enableChunking ? 'enabled' : 'disabled'}`
 63 |     );
 64 |   }
 65 | 
 66 |   /**
 67 |    * Get or create a database connection
 68 |    */
 69 |   async getDb(): Promise<sqlite3.Database> {
 70 |     if (!this.db) {
 71 |       logger.debug('Creating new database connection');
 72 |       this.db = new sqlite3.Database(this.dbPath);
 73 | 
 74 |       // Enable foreign key constraints
 75 |       const run = promisify(this.db.run.bind(this.db)) as (
 76 |         sql: string,
 77 |         ...params: any[]
 78 |       ) => Promise<void>;
 79 |       await run('PRAGMA foreign_keys = ON');
 80 |       logger.debug('Foreign key constraints enabled');
 81 | 
 82 |       await this.initializeDatabase();
 83 |     }
 84 |     return this.db;
 85 |   }
 86 | 
 87 |   /**
 88 |    * Check if a data item exists
 89 |    */
 90 |   async dataItemExists(namespace: string, key: string): Promise<boolean> {
 91 |     const db = await this.getDb();
 92 |     const get = promisify(db.get.bind(db)) as (sql: string, ...params: any[]) => Promise<any>;
 93 | 
 94 |     try {
 95 |       const result = await get(
 96 |         'SELECT 1 FROM context_items WHERE namespace = ? AND key = ?',
 97 |         namespace,
 98 |         key
 99 |       );
100 |       return !!result;
101 |     } catch (error) {
102 |       logger.error(
103 |         `Error checking if data item exists | Context: ${JSON.stringify({ namespace, key, error })}`
104 |       );
105 |       throw error;
106 |     }
107 |   }
108 | 
109 |   /**
110 |    * Initializes the SQLite database and creates necessary tables if they don't exist
111 |    * @param dbPath Path to the SQLite database file
112 |    * @returns A database connection object
113 |    */
114 |   async initializeDatabase(): Promise<Database> {
115 |     try {
116 |       logger.info('Initializing database', { dbPath: this.dbPath });
117 | 
118 |       // Ensure the directory exists
119 |       const dbDir = path.dirname(this.dbPath);
120 |       if (!fs.existsSync(dbDir)) {
121 |         logger.debug('Creating database directory', { dbDir });
122 |         fs.mkdirSync(dbDir, { recursive: true });
123 |       }
124 | 
125 |       // Create a new database connection
126 |       const db = new sqlite3.Database(this.dbPath);
127 |       logger.debug('Database connection established');
128 | 
129 |       // Promisify database methods with proper type assertions
130 |       const run = promisify(db.run.bind(db)) as (sql: string, ...params: any[]) => Promise<void>;
131 |       const get = promisify(db.get.bind(db)) as (sql: string, ...params: any[]) => Promise<any>;
132 |       const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
133 |       const close = promisify(db.close.bind(db)) as () => Promise<void>;
134 | 
135 |       // Create tables if they don't exist
136 |       logger.debug("Creating tables if they don't exist");
137 |       await run(`
138 |         CREATE TABLE IF NOT EXISTS namespaces (
139 |           namespace TEXT PRIMARY KEY,
140 |           created_at TEXT NOT NULL
141 |         )
142 |       `);
143 | 
144 |       await run(`
145 |         CREATE TABLE IF NOT EXISTS context_items (
146 |           namespace TEXT NOT NULL,
147 |           key TEXT NOT NULL,
148 |           value TEXT NOT NULL,
149 |           created_at TEXT NOT NULL,
150 |           updated_at TEXT NOT NULL,
151 |           PRIMARY KEY (namespace, key),
152 |           FOREIGN KEY (namespace) REFERENCES namespaces(namespace) ON DELETE CASCADE
153 |         )
154 |       `);
155 | 
156 |       // Create embeddings table for chunked items
157 |       await run(`
158 |         CREATE TABLE IF NOT EXISTS embeddings (
159 |           id INTEGER PRIMARY KEY AUTOINCREMENT,
160 |           namespace TEXT NOT NULL,
161 |           item_key TEXT NOT NULL,
162 |           chunk_index INTEGER NOT NULL,
163 |           chunk_text TEXT NOT NULL,
164 |           embedding TEXT,
165 |           created_at TEXT NOT NULL,
166 |           FOREIGN KEY (namespace, item_key) REFERENCES context_items(namespace, key) ON DELETE CASCADE
167 |         )
168 |       `);
169 | 
170 |       // Create indexes for embeddings table
171 |       await run(
172 |         'CREATE INDEX IF NOT EXISTS idx_embeddings_namespace_item_key ON embeddings(namespace, item_key)'
173 |       );
174 |       await run(
175 |         'CREATE INDEX IF NOT EXISTS idx_embeddings_has_embedding ON embeddings(namespace, embedding IS NOT NULL)'
176 |       );
177 | 
178 |       // Create indexes for performance
179 |       logger.debug("Creating indexes if they don't exist");
180 |       await run(
181 |         'CREATE INDEX IF NOT EXISTS idx_context_items_namespace ON context_items(namespace)'
182 |       );
183 | 
184 |       logger.info('Database initialization completed successfully');
185 |       return {
186 |         run,
187 |         get,
188 |         all,
189 |         close,
190 |       };
191 |     } catch (error) {
192 |       const normalizedError = normalizeError(error);
193 |       logger.error('Failed to initialize database', normalizedError, { dbPath: this.dbPath });
194 |       throw new DatabaseError(`Failed to initialize database: ${normalizedError.message}`);
195 |     }
196 |   }
197 | 
198 |   /**
199 |    * Store a data item with optional chunking support
200 |    */
201 |   async storeDataItem(namespace: string, key: string, value: any): Promise<void> {
202 |     const db = await this.getDb();
203 |     const run = promisify(db.run.bind(db)) as (sql: string, ...params: any[]) => Promise<void>;
204 |     const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
205 | 
206 |     logger.debug(`Storing data item | Context: ${JSON.stringify({ namespace, key })}`);
207 | 
208 |     // Check if the item already exists
209 |     const exists = await this.dataItemExists(namespace, key);
210 | 
211 |     const timestamp = new Date().toISOString();
212 | 
213 |     try {
214 |       logger.debug(
215 |         `Beginning transaction for storing data item | Context: ${JSON.stringify({ namespace, key })}`
216 |       );
217 |       await run('BEGIN TRANSACTION');
218 | 
219 |       if (exists) {
220 |         logger.debug(`Updating existing item | Context: ${JSON.stringify({ namespace, key })}`);
221 |         await run(
222 |           `UPDATE context_items
223 |            SET value = ?, updated_at = ?
224 |            WHERE namespace = ? AND key = ?`,
225 |           JSON.stringify(value),
226 |           timestamp,
227 |           namespace,
228 |           key
229 |         );
230 | 
231 |         // If chunking is enabled, delete existing chunks before adding new ones
232 |         if (this.enableChunking) {
233 |           logger.debug(
234 |             `Deleting existing chunks for updated item | Context: ${JSON.stringify({ namespace, key })}`
235 |           );
236 |           await run('DELETE FROM embeddings WHERE namespace = ? AND item_key = ?', namespace, key);
237 |         }
238 |       } else {
239 |         logger.debug(`Inserting new item | Context: ${JSON.stringify({ namespace, key })}`);
240 |         await run(
241 |           `INSERT INTO context_items
242 |            (namespace, key, value, created_at, updated_at)
243 |            VALUES (?, ?, ?, ?, ?)`,
244 |           namespace,
245 |           key,
246 |           JSON.stringify(value),
247 |           timestamp,
248 |           timestamp
249 |         );
250 |       }
251 | 
252 |       // Process chunking if enabled
253 |       if (this.enableChunking) {
254 |         // Extract the text value for chunking - stringify objects
255 |         const textValue = typeof value === 'string' ? value : JSON.stringify(value);
256 | 
257 |         // Create chunks based on semantic boundaries
258 |         const chunks = chunkTextBySemanticBoundaries(textValue);
259 | 
260 |         logger.debug(
261 |           `Storing ${chunks.length} chunks for item | Context: ${JSON.stringify({ namespace, key })}`
262 |         );
263 | 
264 |         // Store each chunk with its index
265 |         for (let i = 0; i < chunks.length; i++) {
266 |           await run(
267 |             `INSERT INTO embeddings
268 |              (namespace, item_key, chunk_index, chunk_text, created_at)
269 |              VALUES (?, ?, ?, ?, ?)`,
270 |             namespace,
271 |             key,
272 |             i,
273 |             chunks[i],
274 |             timestamp
275 |           );
276 |         }
277 | 
278 |         // Immediately generate embeddings for the newly created chunks
279 |         try {
280 |           // Import the embedding service dynamically to avoid circular dependencies
281 |           const { EmbeddingService } = await import('./embedding-service.js');
282 |           const embeddingService = new EmbeddingService();
283 |           await embeddingService.initialize();
284 | 
285 |           logger.debug(
286 |             `Generating embeddings for chunks | Context: ${JSON.stringify({ namespace, key, count: chunks.length })}`
287 |           );
288 | 
289 |           // Get the chunk IDs for the newly inserted chunks
290 |           const chunkIds = await all(
291 |             `SELECT id, chunk_text FROM embeddings 
292 |              WHERE namespace = ? AND item_key = ? AND embedding IS NULL
293 |              ORDER BY chunk_index`,
294 |             namespace,
295 |             key
296 |           );
297 | 
298 |           if (chunkIds.length > 0) {
299 |             // Extract just the text for embedding generation
300 |             const chunkTexts = chunkIds.map((chunk) => chunk.chunk_text);
301 | 
302 |             // Generate embeddings
303 |             const embeddings = await embeddingService.generateEmbeddings(chunkTexts);
304 | 
305 |             // Update each chunk with its embedding
306 |             for (let i = 0; i < chunkIds.length; i++) {
307 |               const embedding = JSON.stringify(embeddings[i]);
308 |               await run(
309 |                 `UPDATE embeddings SET embedding = ? WHERE id = ?`,
310 |                 embedding,
311 |                 chunkIds[i].id
312 |               );
313 |             }
314 | 
315 |             logger.debug(
316 |               `Embeddings generated and stored for ${chunkIds.length} chunks | Context: ${JSON.stringify({ namespace, key })}`
317 |             );
318 |           }
319 |         } catch (error) {
320 |           // Log the error but don't fail the store operation
321 |           logger.error(
322 |             `Error generating embeddings for chunks | Context: ${JSON.stringify({ namespace, key, error })}`
323 |           );
324 |         }
325 |       }
326 | 
327 |       await run('COMMIT');
328 |       logger.debug(
329 |         `Transaction committed successfully | Context: ${JSON.stringify({ namespace, key })}`
330 |       );
331 |     } catch (error) {
332 |       logger.error(
333 |         `Error storing data item | Context: ${JSON.stringify({ namespace, key, error })}`
334 |       );
335 |       await run('ROLLBACK');
336 |       throw error;
337 |     }
338 |   }
339 | 
340 |   /**
341 |    * Generate embeddings for all chunks that don't have them yet
342 |    */
343 |   async generateEmbeddingsForChunks(namespace: string, embeddingService: any): Promise<number> {
344 |     if (!this.enableChunking) {
345 |       logger.debug(`Chunking is disabled, skipping embedding generation`);
346 |       return 0;
347 |     }
348 | 
349 |     const db = await this.getDb();
350 |     const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
351 |     const run = promisify(db.run.bind(db)) as (sql: string, ...params: any[]) => Promise<void>;
352 | 
353 |     // Find chunks without embeddings
354 |     const chunksWithoutEmbeddings = await all(
355 |       `SELECT id, namespace, item_key, chunk_index, chunk_text
356 |        FROM embeddings
357 |        WHERE namespace = ? AND embedding IS NULL
358 |        ORDER BY item_key, chunk_index`,
359 |       namespace
360 |     );
361 | 
362 |     if (chunksWithoutEmbeddings.length === 0) {
363 |       logger.debug(`No chunks found without embeddings for namespace: ${namespace}`);
364 |       return 0;
365 |     }
366 | 
367 |     logger.debug(
368 |       `Generating embeddings for ${chunksWithoutEmbeddings.length} chunks | Context: { namespace: ${namespace} }`
369 |     );
370 | 
371 |     let processedCount = 0;
372 | 
373 |     // Process chunks in batches to avoid memory issues
374 |     const BATCH_SIZE = 10;
375 |     for (let i = 0; i < chunksWithoutEmbeddings.length; i += BATCH_SIZE) {
376 |       const batch = chunksWithoutEmbeddings.slice(i, i + BATCH_SIZE);
377 |       const chunkTexts = batch.map((chunk) => chunk.chunk_text);
378 | 
379 |       try {
380 |         // Generate embeddings for the batch
381 |         const embeddings = await embeddingService.generateEmbeddings(chunkTexts);
382 | 
383 |         // Update each chunk with its embedding
384 |         await run('BEGIN TRANSACTION');
385 |         for (let j = 0; j < batch.length; j++) {
386 |           const chunk = batch[j];
387 |           const embedding = JSON.stringify(embeddings[j]);
388 | 
389 |           await run(
390 |             `UPDATE embeddings
391 |              SET embedding = ?
392 |              WHERE id = ?`,
393 |             embedding,
394 |             chunk.id
395 |           );
396 |           processedCount++;
397 |         }
398 |         await run('COMMIT');
399 | 
400 |         logger.debug(
401 |           `Generated embeddings for batch ${i / BATCH_SIZE + 1} | Context: { count: ${batch.length} }`
402 |         );
403 |       } catch (error) {
404 |         logger.error(
405 |           `Error generating embeddings for batch ${i / BATCH_SIZE + 1} | Context: ${JSON.stringify(error)}`
406 |         );
407 |         await run('ROLLBACK');
408 |         throw error;
409 |       }
410 |     }
411 | 
412 |     logger.debug(`Completed embedding generation | Context: { processed: ${processedCount} }`);
413 |     return processedCount;
414 |   }
415 | 
416 |   /**
417 |    * Retrieve items by semantic search, using the chunked embeddings if enabled
418 |    */
419 |   async retrieveDataItemsBySemanticSearch(
420 |     namespace: string,
421 |     queryText: string,
422 |     embeddingService: any,
423 |     options: {
424 |       limit?: number;
425 |       similarityThreshold?: number;
426 |     } = {}
427 |   ): Promise<any[]> {
428 |     logger.debug(
429 |       `Performing semantic search | Context: ${JSON.stringify({ namespace, queryText, options })}`
430 |     );
431 | 
432 |     const { limit = 10, similarityThreshold = 0.5 } = options;
433 | 
434 |     // If chunking is not enabled, fall back to the legacy method if it exists
435 |     if (!this.enableChunking) {
436 |       logger.debug(`Chunking disabled, using legacy semantic search method`);
437 |       return this.retrieveDataItemsByEmbeddingSimilarity(
438 |         namespace,
439 |         queryText,
440 |         embeddingService,
441 |         options
442 |       );
443 |     }
444 | 
445 |     const db = await this.getDb();
446 |     const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
447 | 
448 |     try {
449 |       // Generate embedding for the query
450 |       logger.debug(
451 |         `Generating embedding for text | Context: ${JSON.stringify({ textLength: queryText.length })}`
452 |       );
453 |       const queryEmbedding = await embeddingService.generateEmbeddings([queryText], {
454 |         input_type: 'query',
455 |       });
456 | 
457 |       if (!queryEmbedding || !queryEmbedding[0] || queryEmbedding[0].length === 0) {
458 |         logger.error(`Failed to generate query embedding`);
459 |         return [];
460 |       }
461 | 
462 |       // Get all chunks with embeddings
463 |       const chunksWithEmbeddings = await all(
464 |         `SELECT c.namespace, c.item_key, c.chunk_index, c.chunk_text, c.embedding,
465 |                 i.value, i.tags, i.description, i.created_at, i.updated_at
466 |          FROM embeddings c
467 |          JOIN context_items i ON c.namespace = i.namespace AND c.item_key = i.key
468 |          WHERE c.namespace = ? AND c.embedding IS NOT NULL`,
469 |         namespace
470 |       );
471 | 
472 |       if (chunksWithEmbeddings.length === 0) {
473 |         logger.debug(`No chunks with embeddings found for namespace: ${namespace}`);
474 |         return [];
475 |       }
476 | 
477 |       logger.debug(
478 |         `Calculating similarities | Context: ${JSON.stringify({ itemCount: chunksWithEmbeddings.length })}`
479 |       );
480 | 
481 |       // Calculate similarity scores
482 |       const similarities = chunksWithEmbeddings.map((chunk) => {
483 |         const chunkEmbedding = JSON.parse(chunk.embedding);
484 |         const similarity = this.calculateCosineSimilarity(queryEmbedding[0], chunkEmbedding);
485 |         return {
486 |           ...chunk,
487 |           similarity,
488 |         };
489 |       });
490 | 
491 |       // Filter by similarity threshold and sort by similarity
492 |       const filteredResults = similarities
493 |         .filter((item) => item.similarity >= similarityThreshold)
494 |         .sort((a, b) => b.similarity - a.similarity);
495 | 
496 |       // Group by item to avoid duplicates, taking the highest similarity score
497 |       const itemMap = new Map();
498 |       for (const result of filteredResults) {
499 |         const itemKey = `${result.namespace}:${result.item_key}`;
500 | 
501 |         if (!itemMap.has(itemKey) || itemMap.get(itemKey).similarity < result.similarity) {
502 |           // Parse the value field
503 |           try {
504 |             result.value = JSON.parse(result.value);
505 |           } catch (e) {
506 |             // If parsing fails, keep the original value
507 |           }
508 | 
509 |           // Parse tags
510 |           try {
511 |             result.tags = JSON.parse(result.tags);
512 |           } catch (e) {
513 |             result.tags = [];
514 |           }
515 | 
516 |           itemMap.set(itemKey, result);
517 |         }
518 |       }
519 | 
520 |       // Convert back to array and take top results
521 |       const results = Array.from(itemMap.values())
522 |         .slice(0, limit)
523 |         .map((item) => ({
524 |           namespace: item.namespace,
525 |           key: item.item_key,
526 |           value: item.value,
527 |           tags: item.tags,
528 |           description: item.description,
529 |           matchedChunk: item.chunk_text,
530 |           similarity: item.similarity,
531 |           created_at: item.created_at,
532 |           updated_at: item.updated_at,
533 |         }));
534 | 
535 |       logger.debug(
536 |         `Retrieved items by semantic search | Context: ${JSON.stringify({ namespace, queryText, count: results.length })}`
537 |       );
538 | 
539 |       return results;
540 |     } catch (error) {
541 |       logger.error(
542 |         `Error retrieving items by semantic search | Context: ${JSON.stringify({ namespace, error })}`
543 |       );
544 |       throw error;
545 |     }
546 |   }
547 | 
548 |   /**
549 |    * Legacy method for retrieving items by embedding similarity (without chunking)
550 |    */
551 |   async retrieveDataItemsByEmbeddingSimilarity(
552 |     namespace: string,
553 |     queryText: string,
554 |     embeddingService: any,
555 |     options: {
556 |       limit?: number;
557 |       similarityThreshold?: number;
558 |     } = {}
559 |   ): Promise<any[]> {
560 |     logger.warn(`Legacy embedding similarity search called, but not implemented`);
561 |     return []; // Empty implementation - this would be implemented in your system if needed
562 |   }
563 | 
564 |   /**
565 |    * Retrieve a data item by namespace and key
566 |    */
567 |   async retrieveDataItem(namespace: string, key: string): Promise<any | null> {
568 |     const db = await this.getDb();
569 |     const get = promisify(db.get.bind(db)) as (sql: string, ...params: any[]) => Promise<any>;
570 | 
571 |     try {
572 |       logger.debug(`Retrieving data item | Context: ${JSON.stringify({ namespace, key })}`);
573 | 
574 |       const result = await get(
575 |         'SELECT * FROM context_items WHERE namespace = ? AND key = ?',
576 |         namespace,
577 |         key
578 |       );
579 | 
580 |       if (!result) {
581 |         logger.debug(`Data item not found | Context: ${JSON.stringify({ namespace, key })}`);
582 |         return null;
583 |       }
584 | 
585 |       try {
586 |         // Parse JSON value
587 |         result.value = JSON.parse(result.value);
588 |       } catch (e) {
589 |         // If parsing fails, keep the original value
590 |         logger.warn(
591 |           `Failed to parse value as JSON | Context: ${JSON.stringify({ namespace, key })}`
592 |         );
593 |       }
594 | 
595 |       logger.debug(
596 |         `Data item retrieved successfully | Context: ${JSON.stringify({ namespace, key })}`
597 |       );
598 |       return result;
599 |     } catch (error) {
600 |       logger.error(
601 |         `Error retrieving data item | Context: ${JSON.stringify({ namespace, key, error })}`
602 |       );
603 |       throw error;
604 |     }
605 |   }
606 | 
607 |   /**
608 |    * Delete a data item
609 |    */
610 |   async deleteDataItem(namespace: string, key: string): Promise<boolean> {
611 |     const db = await this.getDb();
612 |     const run = promisify(db.run.bind(db)) as (sql: string, ...params: any[]) => Promise<void>;
613 | 
614 |     try {
615 |       logger.debug(`Deleting data item | Context: ${JSON.stringify({ namespace, key })}`);
616 | 
617 |       await run('BEGIN TRANSACTION');
618 | 
619 |       // First delete any associated chunks if chunking is enabled
620 |       if (this.enableChunking) {
621 |         await run('DELETE FROM embeddings WHERE namespace = ? AND item_key = ?', namespace, key);
622 |       }
623 | 
624 |       // Then delete the main item
625 |       const result = await run(
626 |         'DELETE FROM context_items WHERE namespace = ? AND key = ?',
627 |         namespace,
628 |         key
629 |       );
630 | 
631 |       await run('COMMIT');
632 | 
633 |       logger.debug(`Data item deleted | Context: ${JSON.stringify({ namespace, key, result })}`);
634 |       return true; // SQLite doesn't return affected rows in the same way as other DBs
635 |     } catch (error) {
636 |       await run('ROLLBACK');
637 |       logger.error(
638 |         `Error deleting data item | Context: ${JSON.stringify({ namespace, key, error })}`
639 |       );
640 |       throw error;
641 |     }
642 |   }
643 | 
644 |   /**
645 |    * List all namespaces
646 |    */
647 |   async listAllNamespaces(): Promise<string[]> {
648 |     const db = await this.getDb();
649 |     const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
650 | 
651 |     try {
652 |       logger.debug('Listing all namespaces');
653 | 
654 |       const results = await all('SELECT namespace FROM namespaces ORDER BY namespace');
655 |       const namespaces = results.map((row) => row.namespace);
656 | 
657 |       logger.debug(`Retrieved ${namespaces.length} namespaces`);
658 |       return namespaces;
659 |     } catch (error) {
660 |       logger.error(`Error listing namespaces | Context: ${JSON.stringify(error)}`);
661 |       throw error;
662 |     }
663 |   }
664 | 
665 |   /**
666 |    * List context item keys
667 |    */
668 |   async listContextItemKeys(namespace: string): Promise<any[]> {
669 |     const db = await this.getDb();
670 |     const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
671 | 
672 |     try {
673 |       logger.debug(`Listing context item keys | Context: ${JSON.stringify({ namespace })}`);
674 | 
675 |       const results = await all(
676 |         'SELECT key, created_at, updated_at FROM context_items WHERE namespace = ? ORDER BY key',
677 |         namespace
678 |       );
679 | 
680 |       logger.debug(
681 |         `Retrieved keys for ${results.length} items | Context: ${JSON.stringify({ namespace })}`
682 |       );
683 |       return results;
684 |     } catch (error) {
685 |       logger.error(
686 |         `Error listing context item keys | Context: ${JSON.stringify({ namespace, error })}`
687 |       );
688 |       throw error;
689 |     }
690 |   }
691 | 
692 |   /**
693 |    * Create a namespace
694 |    */
695 |   async createNamespace(namespace: string): Promise<boolean> {
696 |     const db = await this.getDb();
697 |     const get = promisify(db.get.bind(db)) as (sql: string, ...params: any[]) => Promise<any>;
698 |     const run = promisify(db.run.bind(db)) as (sql: string, ...params: any[]) => Promise<void>;
699 | 
700 |     try {
701 |       logger.debug(`Creating namespace | Context: ${JSON.stringify({ namespace })}`);
702 | 
703 |       // Check if namespace already exists
704 |       const existingNamespace = await get(
705 |         'SELECT 1 FROM namespaces WHERE namespace = ?',
706 |         namespace
707 |       );
708 | 
709 |       if (existingNamespace) {
710 |         logger.debug(`Namespace already exists | Context: ${JSON.stringify({ namespace })}`);
711 |         return false;
712 |       }
713 | 
714 |       // Create the namespace
715 |       await run(
716 |         'INSERT INTO namespaces (namespace, created_at) VALUES (?, ?)',
717 |         namespace,
718 |         new Date().toISOString()
719 |       );
720 | 
721 |       logger.debug(`Namespace created successfully | Context: ${JSON.stringify({ namespace })}`);
722 |       return true;
723 |     } catch (error) {
724 |       logger.error(`Error creating namespace | Context: ${JSON.stringify({ namespace, error })}`);
725 |       throw error;
726 |     }
727 |   }
728 | 
729 |   /**
730 |    * Delete a namespace
731 |    */
732 |   async deleteNamespace(namespace: string): Promise<boolean> {
733 |     const db = await this.getDb();
734 |     const get = promisify(db.get.bind(db)) as (sql: string, ...params: any[]) => Promise<any>;
735 |     const run = promisify(db.run.bind(db)) as (sql: string, ...params: any[]) => Promise<void>;
736 | 
737 |     try {
738 |       logger.debug(`Attempting to delete namespace | Context: ${JSON.stringify({ namespace })}`);
739 | 
740 |       // Check if namespace exists
741 |       const existingNamespace = await get(
742 |         'SELECT 1 FROM namespaces WHERE namespace = ?',
743 |         namespace
744 |       );
745 | 
746 |       if (!existingNamespace) {
747 |         logger.debug(
748 |           `Namespace does not exist, nothing to delete | Context: ${JSON.stringify({ namespace })}`
749 |         );
750 |         return false;
751 |       }
752 | 
753 |       // Delete using a transaction to ensure atomicity
754 |       logger.debug(
755 |         `Beginning transaction for namespace deletion | Context: ${JSON.stringify({ namespace })}`
756 |       );
757 |       await run('BEGIN TRANSACTION');
758 | 
759 |       // Delete all context items in namespace (cascades to embeddings due to foreign key)
760 |       logger.debug(
761 |         `Deleting all context items in namespace | Context: ${JSON.stringify({ namespace })}`
762 |       );
763 |       await run('DELETE FROM context_items WHERE namespace = ?', namespace);
764 | 
765 |       // Delete the namespace
766 |       logger.debug(`Deleting namespace | Context: ${JSON.stringify({ namespace })}`);
767 |       await run('DELETE FROM namespaces WHERE namespace = ?', namespace);
768 | 
769 |       await run('COMMIT');
770 | 
771 |       logger.debug(`Namespace deleted successfully | Context: ${JSON.stringify({ namespace })}`);
772 |       return true;
773 |     } catch (error) {
774 |       await run('ROLLBACK');
775 |       logger.error(`Error deleting namespace | Context: ${JSON.stringify({ namespace, error })}`);
776 |       throw error;
777 |     }
778 |   }
779 | 
780 |   /**
781 |    * Calculate cosine similarity between two embedding vectors
782 |    */
783 |   private calculateCosineSimilarity(vec1: number[], vec2: number[]): number {
784 |     if (vec1.length !== vec2.length) {
785 |       throw new Error(`Vector dimensions don't match: ${vec1.length} vs ${vec2.length}`);
786 |     }
787 | 
788 |     let dotProduct = 0;
789 |     let mag1 = 0;
790 |     let mag2 = 0;
791 | 
792 |     for (let i = 0; i < vec1.length; i++) {
793 |       dotProduct += vec1[i] * vec2[i];
794 |       mag1 += vec1[i] * vec1[i];
795 |       mag2 += vec2[i] * vec2[i];
796 |     }
797 | 
798 |     mag1 = Math.sqrt(mag1);
799 |     mag2 = Math.sqrt(mag2);
800 | 
801 |     if (mag1 === 0 || mag2 === 0) {
802 |       return 0;
803 |     }
804 | 
805 |     return dotProduct / (mag1 * mag2);
806 |   }
807 | 
808 |   /**
809 |    * Execute a SQL query with parameters and return a single row
810 |    * (For compatibility with old Database interface)
811 |    */
812 |   async get(sql: string, ...params: any[]): Promise<any> {
813 |     const db = await this.getDb();
814 |     const get = promisify(db.get.bind(db)) as (sql: string, ...params: any[]) => Promise<any>;
815 | 
816 |     try {
817 |       logger.debug(`Executing SQL get query: ${sql}`);
818 |       const result = await get(sql, ...params);
819 |       return result;
820 |     } catch (error) {
821 |       logger.error(`Error executing SQL get query: ${error}`);
822 |       throw error;
823 |     }
824 |   }
825 | 
826 |   /**
827 |    * Execute a SQL query with parameters and return all rows
828 |    * (For compatibility with old Database interface)
829 |    */
830 |   async all(sql: string, ...params: any[]): Promise<any[]> {
831 |     const db = await this.getDb();
832 |     const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
833 | 
834 |     try {
835 |       logger.debug(`Executing SQL all query: ${sql}`);
836 |       const results = await all(sql, ...params);
837 |       return results;
838 |     } catch (error) {
839 |       logger.error(`Error executing SQL all query: ${error}`);
840 |       throw error;
841 |     }
842 |   }
843 | 
844 |   /**
845 |    * Execute a SQL query with parameters (no return value)
846 |    * (For compatibility with old Database interface)
847 |    */
848 |   async run(sql: string, ...params: any[]): Promise<void> {
849 |     const db = await this.getDb();
850 |     const run = promisify(db.run.bind(db)) as (sql: string, ...params: any[]) => Promise<void>;
851 | 
852 |     try {
853 |       logger.debug(`Executing SQL run query: ${sql}`);
854 |       await run(sql, ...params);
855 |     } catch (error) {
856 |       logger.error(`Error executing SQL run query: ${error}`);
857 |       throw error;
858 |     }
859 |   }
860 | 
861 |   /**
862 |    * Close the database connection
863 |    * (For compatibility with old Database interface)
864 |    */
865 |   async close(): Promise<void> {
866 |     if (this.db) {
867 |       const db = this.db;
868 |       await new Promise<void>((resolve, reject) => {
869 |         db.close((err) => {
870 |           if (err) {
871 |             reject(err);
872 |           } else {
873 |             this.db = null;
874 |             resolve();
875 |           }
876 |         });
877 |       });
878 |     }
879 |   }
880 | 
881 |   /**
882 |    * Retrieve all items in a namespace
883 |    */
884 |   async retrieveAllItemsInNamespace(namespace: string): Promise<any[]> {
885 |     const db = await this.getDb();
886 |     const all = promisify(db.all.bind(db)) as (sql: string, ...params: any[]) => Promise<any[]>;
887 | 
888 |     const results = await all(
889 |       'SELECT * FROM context_items WHERE namespace = ? ORDER BY key',
890 |       namespace
891 |     );
892 | 
893 |     return results.map((result) => {
894 |       try {
895 |         result.value = JSON.parse(result.value);
896 |       } catch (e) {
897 |         // If parsing fails, keep the original value
898 |       }
899 | 
900 |       return result;
901 |     });
902 |   }
903 | }
904 | 
```