@j0hanz/memdb 1.0.11 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -120
- package/dist/config.d.ts +4 -0
- package/dist/config.js +13 -0
- package/dist/core/database-schema.d.ts +0 -1
- package/dist/core/database-schema.js +0 -1
- package/dist/core/database.d.ts +0 -1
- package/dist/core/database.js +0 -1
- package/dist/core/db.d.ts +16 -0
- package/dist/core/db.js +232 -0
- package/dist/core/memory-create.d.ts +0 -1
- package/dist/core/memory-create.js +13 -7
- package/dist/core/memory-db.d.ts +0 -1
- package/dist/core/memory-db.js +0 -1
- package/dist/core/memory-read.d.ts +2 -2
- package/dist/core/memory-read.js +32 -4
- package/dist/core/memory-relations.d.ts +0 -1
- package/dist/core/memory-relations.js +0 -1
- package/dist/core/memory-search.d.ts +8 -7
- package/dist/core/memory-search.js +15 -9
- package/dist/core/memory-stats.d.ts +0 -1
- package/dist/core/memory-stats.js +0 -1
- package/dist/core/memory-updates.d.ts +0 -1
- package/dist/core/memory-updates.js +1 -2
- package/dist/core/memory-write.d.ts +13 -0
- package/dist/core/memory-write.js +113 -0
- package/dist/core/relation-queries.d.ts +0 -1
- package/dist/core/relation-queries.js +0 -1
- package/dist/core/relations.d.ts +10 -0
- package/dist/core/relations.js +177 -0
- package/dist/core/row-mappers.d.ts +0 -1
- package/dist/core/row-mappers.js +0 -1
- package/dist/core/search-errors.d.ts +0 -1
- package/dist/core/search-errors.js +0 -1
- package/dist/core/search.d.ts +5 -12
- package/dist/core/search.js +77 -56
- package/dist/core/sqlite.d.ts +0 -1
- package/dist/core/sqlite.js +0 -3
- package/dist/core/tags.d.ts +0 -1
- package/dist/core/tags.js +1 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +58 -37
- package/dist/lib/errors.d.ts +0 -1
- package/dist/lib/errors.js +0 -1
- package/dist/logger.d.ts +5 -0
- package/dist/logger.js +17 -0
- package/dist/protocol-version-guard.d.ts +17 -0
- package/dist/protocol-version-guard.js +100 -0
- package/dist/schemas/inputs.d.ts +0 -1
- package/dist/schemas/inputs.js +0 -1
- package/dist/schemas/outputs.d.ts +0 -1
- package/dist/schemas/outputs.js +0 -1
- package/dist/schemas.d.ts +28 -0
- package/dist/schemas.js +65 -0
- package/dist/tools/definitions/memory-core.d.ts +0 -1
- package/dist/tools/definitions/memory-core.js +0 -1
- package/dist/tools/definitions/memory-relations.d.ts +0 -1
- package/dist/tools/definitions/memory-relations.js +0 -1
- package/dist/tools/definitions/memory-search.d.ts +0 -1
- package/dist/tools/definitions/memory-search.js +1 -11
- package/dist/tools/definitions/memory-stats.d.ts +0 -1
- package/dist/tools/definitions/memory-stats.js +0 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/tool-handlers.d.ts +0 -1
- package/dist/tools/tool-handlers.js +0 -1
- package/dist/tools/tool-types.d.ts +0 -1
- package/dist/tools/tool-types.js +0 -1
- package/dist/tools.d.ts +18 -0
- package/dist/tools.js +167 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types.d.ts +30 -0
- package/dist/types.js +1 -0
- package/dist/utils/config.d.ts +0 -1
- package/dist/utils/config.js +0 -1
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +0 -1
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +118 -0
- package/dist/worker/db-worker-client.d.ts +9 -0
- package/dist/worker/db-worker-client.js +93 -0
- package/dist/worker/db-worker.d.ts +1 -0
- package/dist/worker/db-worker.js +174 -0
- package/dist/worker/protocol.d.ts +9 -0
- package/dist/worker/protocol.js +14 -0
- package/package.json +8 -5
package/dist/core/search.js
CHANGED
|
@@ -1,78 +1,91 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { executeAll, prepareCached } from './sqlite.js';
|
|
1
|
+
import { executeAll, mapRowToSearchResult, prepareCached, } from './db.js';
|
|
3
2
|
const MAX_QUERY_TOKENS = 50;
|
|
3
|
+
const DEFAULT_LIMIT = 100;
|
|
4
4
|
const tokenizeQuery = (query) => {
|
|
5
5
|
const parts = query
|
|
6
6
|
.trim()
|
|
7
7
|
.split(/\s+/)
|
|
8
8
|
.filter((w) => w.length > 0);
|
|
9
9
|
if (parts.length === 0)
|
|
10
|
-
return
|
|
10
|
+
return [];
|
|
11
11
|
if (parts.length > MAX_QUERY_TOKENS) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
}
|
|
14
|
-
const tokens = [];
|
|
15
|
-
for (const part of parts) {
|
|
16
|
-
tokens.push(`"${part.replace(/"/g, '""')}"`);
|
|
12
|
+
throw new Error(`Query has too many terms (max ${MAX_QUERY_TOKENS})`);
|
|
17
13
|
}
|
|
18
|
-
return
|
|
14
|
+
return parts;
|
|
19
15
|
};
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
return
|
|
23
|
-
const
|
|
24
|
-
return
|
|
25
|
-
clause: ` AND m.id IN (SELECT memory_id FROM tags WHERE tag IN (${placeholders}))`,
|
|
26
|
-
params: [...tags],
|
|
27
|
-
};
|
|
16
|
+
const buildFtsQuery = (tokens) => {
|
|
17
|
+
if (tokens.length === 0)
|
|
18
|
+
return '""';
|
|
19
|
+
const escaped = tokens.map((t) => `"${t.replace(/"/g, '""')}"`);
|
|
20
|
+
return escaped.join(' OR ');
|
|
28
21
|
};
|
|
29
|
-
const
|
|
22
|
+
const buildTagPlaceholders = (count) => {
|
|
23
|
+
return Array.from({ length: count }, () => '?').join(', ');
|
|
24
|
+
};
|
|
25
|
+
// Search both content (FTS) and tags, deduplicated by memory id
|
|
26
|
+
const buildSearchQuery = (tokens) => {
|
|
27
|
+
const ftsQuery = buildFtsQuery(tokens);
|
|
28
|
+
const tagPlaceholders = buildTagPlaceholders(tokens.length);
|
|
30
29
|
const relevanceExpr = '1.0 / (1.0 + abs(bm25(memories_fts)))';
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
// Union of FTS content matches and tag matches, deduplicated
|
|
31
|
+
const sql = `
|
|
32
|
+
WITH content_matches AS (
|
|
33
33
|
SELECT m.*, ${relevanceExpr} as relevance
|
|
34
34
|
FROM memories m
|
|
35
35
|
JOIN memories_fts ON m.id = memories_fts.rowid
|
|
36
|
-
WHERE memories_fts MATCH
|
|
36
|
+
WHERE memories_fts MATCH ?
|
|
37
|
+
),
|
|
38
|
+
tag_matches AS (
|
|
39
|
+
SELECT DISTINCT m.*, 0.5 as relevance
|
|
40
|
+
FROM memories m
|
|
41
|
+
JOIN tags t ON m.id = t.memory_id
|
|
42
|
+
WHERE t.tag IN (${tagPlaceholders})
|
|
43
|
+
),
|
|
44
|
+
combined AS (
|
|
45
|
+
SELECT * FROM content_matches
|
|
46
|
+
UNION ALL
|
|
47
|
+
SELECT * FROM tag_matches
|
|
37
48
|
)
|
|
38
|
-
SELECT
|
|
49
|
+
SELECT id, content, summary, created_at, accessed_at, hash,
|
|
50
|
+
MAX(relevance) as relevance
|
|
51
|
+
FROM combined
|
|
52
|
+
GROUP BY id
|
|
53
|
+
ORDER BY relevance DESC
|
|
54
|
+
LIMIT ?
|
|
39
55
|
`;
|
|
56
|
+
return { sql, params: [ftsQuery, ...tokens, DEFAULT_LIMIT] };
|
|
40
57
|
};
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
const INDEX_MISSING_TOKENS = [
|
|
59
|
+
'no such module: fts5',
|
|
60
|
+
'no such table: memories_fts',
|
|
61
|
+
];
|
|
62
|
+
const QUERY_INVALID_TOKENS = ['fts5', 'syntax error'];
|
|
63
|
+
const isSearchIndexMissing = (message) => INDEX_MISSING_TOKENS.some((token) => message.includes(token));
|
|
64
|
+
const isSearchQueryInvalid = (message) => QUERY_INVALID_TOKENS.some((token) => message.includes(token));
|
|
65
|
+
const getErrorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
66
|
+
const SEARCH_ERROR_MAP = [
|
|
67
|
+
{
|
|
68
|
+
matches: isSearchIndexMissing,
|
|
69
|
+
build: () => new Error('Search index unavailable. Ensure FTS5 is enabled and the index is ' +
|
|
70
|
+
'initialized.'),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
matches: isSearchQueryInvalid,
|
|
74
|
+
build: (message) => new Error('Invalid search query syntax. Check for unbalanced quotes or special ' +
|
|
75
|
+
'characters. ' +
|
|
76
|
+
`Details: ${message}`),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
const toSearchError = (err) => {
|
|
80
|
+
const message = getErrorMessage(err);
|
|
81
|
+
for (const mapping of SEARCH_ERROR_MAP) {
|
|
82
|
+
if (mapping.matches(message)) {
|
|
83
|
+
return mapping.build(message);
|
|
84
|
+
}
|
|
55
85
|
}
|
|
56
|
-
return
|
|
57
|
-
};
|
|
58
|
-
export const buildSearchQuery = (input) => {
|
|
59
|
-
const sanitizedQuery = tokenizeQuery(input.query);
|
|
60
|
-
const tagFilter = buildTagFilter(input.tags);
|
|
61
|
-
const baseSql = buildBaseSql(tagFilter.clause);
|
|
62
|
-
const baseParams = [sanitizedQuery, ...tagFilter.params];
|
|
63
|
-
const baseQuery = { sql: baseSql, params: baseParams };
|
|
64
|
-
const withRelevance = input.minRelevance === undefined
|
|
65
|
-
? baseQuery
|
|
66
|
-
: appendMinRelevance({
|
|
67
|
-
...baseQuery,
|
|
68
|
-
minRelevance: input.minRelevance,
|
|
69
|
-
});
|
|
70
|
-
const paginatedQuery = input.offset === undefined
|
|
71
|
-
? { ...withRelevance, limit: input.limit }
|
|
72
|
-
: { ...withRelevance, limit: input.limit, offset: input.offset };
|
|
73
|
-
return appendPagination(paginatedQuery);
|
|
86
|
+
return undefined;
|
|
74
87
|
};
|
|
75
|
-
|
|
88
|
+
const executeSearch = (sql, params) => {
|
|
76
89
|
try {
|
|
77
90
|
const stmt = prepareCached(sql);
|
|
78
91
|
return executeAll(stmt, ...params);
|
|
@@ -85,4 +98,12 @@ export const executeSearch = (sql, params) => {
|
|
|
85
98
|
throw err;
|
|
86
99
|
}
|
|
87
100
|
};
|
|
88
|
-
|
|
101
|
+
export const searchMemories = (input) => {
|
|
102
|
+
const tokens = tokenizeQuery(input.query);
|
|
103
|
+
if (tokens.length === 0) {
|
|
104
|
+
throw new Error('Query cannot be empty');
|
|
105
|
+
}
|
|
106
|
+
const { sql, params } = buildSearchQuery(tokens);
|
|
107
|
+
const rows = executeSearch(sql, params);
|
|
108
|
+
return rows.map((row) => mapRowToSearchResult(row));
|
|
109
|
+
};
|
package/dist/core/sqlite.d.ts
CHANGED
package/dist/core/sqlite.js
CHANGED
|
@@ -8,9 +8,7 @@ const enforceStatementCacheLimit = () => {
|
|
|
8
8
|
const oldestSql = statementCacheOrder.shift();
|
|
9
9
|
if (!oldestSql)
|
|
10
10
|
return;
|
|
11
|
-
const toEvict = statementCache.get(oldestSql);
|
|
12
11
|
statementCache.delete(oldestSql);
|
|
13
|
-
void toEvict;
|
|
14
12
|
};
|
|
15
13
|
const isDbRow = (value) => {
|
|
16
14
|
return typeof value === 'object' && value !== null;
|
|
@@ -71,4 +69,3 @@ export const withImmediateTransaction = (operation) => {
|
|
|
71
69
|
throw err;
|
|
72
70
|
}
|
|
73
71
|
};
|
|
74
|
-
//# sourceMappingURL=sqlite.js.map
|
package/dist/core/tags.d.ts
CHANGED
package/dist/core/tags.js
CHANGED
|
@@ -8,7 +8,7 @@ const validateTag = (tag) => {
|
|
|
8
8
|
};
|
|
9
9
|
const validateTagCount = (tags, maxTags) => {
|
|
10
10
|
if (tags.length > maxTags) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error(`Too many tags (max ${maxTags})`);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
const dedupeTags = (tags) => {
|
|
@@ -25,4 +25,3 @@ export const normalizeTags = (tags, maxTags) => {
|
|
|
25
25
|
validateTagCount(tags, maxTags);
|
|
26
26
|
return dedupeTags(tags);
|
|
27
27
|
};
|
|
28
|
-
//# sourceMappingURL=tags.js.map
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -3,28 +3,46 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
import { SUPPORTED_PROTOCOL_VERSIONS, } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import { closeDb } from './core/db.js';
|
|
8
|
+
import { logger } from './logger.js';
|
|
9
|
+
import { ProtocolVersionGuardTransport } from './protocol-version-guard.js';
|
|
10
|
+
import { registerAllTools } from './tools.js';
|
|
11
|
+
const readPackageVersion = async () => {
|
|
12
|
+
const packageJsonText = await readFile(new URL('../package.json', import.meta.url), {
|
|
13
|
+
encoding: 'utf-8',
|
|
14
|
+
signal: AbortSignal.timeout(5000),
|
|
15
|
+
});
|
|
15
16
|
const parsed = JSON.parse(packageJsonText);
|
|
16
17
|
if (typeof parsed !== 'object' || parsed === null)
|
|
17
18
|
return undefined;
|
|
18
19
|
const version = Reflect.get(parsed, 'version');
|
|
19
20
|
return typeof version === 'string' ? version : undefined;
|
|
20
|
-
}
|
|
21
|
+
};
|
|
22
|
+
const packageVersion = await readPackageVersion();
|
|
21
23
|
const server = new McpServer({ name: 'memdb', version: packageVersion ?? '0.0.0' }, {
|
|
22
24
|
instructions: 'A Memory MCP Server for AI Assistants using node:sqlite',
|
|
23
25
|
capabilities: { logging: {}, tools: {} },
|
|
24
26
|
});
|
|
27
|
+
const patchToolErrorResults = (target) => {
|
|
28
|
+
const patched = target;
|
|
29
|
+
patched.createToolError = (message) => {
|
|
30
|
+
const structured = {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: { code: 'E_TOOL_ERROR', message },
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
content: [{ type: 'text', text: JSON.stringify(structured) }],
|
|
36
|
+
structuredContent: structured,
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
patchToolErrorResults(server);
|
|
25
42
|
registerAllTools(server);
|
|
26
43
|
let transport;
|
|
27
44
|
let shuttingDown = false;
|
|
45
|
+
const SHUTDOWN_TIMEOUT = 5000;
|
|
28
46
|
async function shutdown(signal) {
|
|
29
47
|
if (shuttingDown)
|
|
30
48
|
return;
|
|
@@ -33,45 +51,48 @@ async function shutdown(signal) {
|
|
|
33
51
|
const forceExitTimer = setTimeout(() => {
|
|
34
52
|
logger.warn('Shutdown timeout exceeded, forcing exit');
|
|
35
53
|
process.exit(1);
|
|
36
|
-
},
|
|
37
|
-
const exit = (code) => {
|
|
38
|
-
clearTimeout(forceExitTimer);
|
|
39
|
-
process.exit(code);
|
|
40
|
-
};
|
|
54
|
+
}, SHUTDOWN_TIMEOUT);
|
|
41
55
|
try {
|
|
42
56
|
closeDb();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
exit(0);
|
|
57
|
+
await transport?.close();
|
|
58
|
+
clearTimeout(forceExitTimer);
|
|
59
|
+
process.exit(0);
|
|
47
60
|
}
|
|
48
61
|
catch (err) {
|
|
49
62
|
logger.error('Error during shutdown:', err);
|
|
50
|
-
|
|
63
|
+
clearTimeout(forceExitTimer);
|
|
64
|
+
process.exit(1);
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
|
-
async
|
|
67
|
+
const main = async () => {
|
|
54
68
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
const stdioTransport = new StdioServerTransport();
|
|
70
|
+
const guardedTransport = new ProtocolVersionGuardTransport(stdioTransport, SUPPORTED_PROTOCOL_VERSIONS);
|
|
71
|
+
transport = guardedTransport;
|
|
72
|
+
await server.connect(guardedTransport);
|
|
58
73
|
logger.info('Memory MCP Server running on stdio');
|
|
59
74
|
}
|
|
60
75
|
catch (error) {
|
|
61
76
|
logger.error('Failed to start server', error);
|
|
62
77
|
process.exit(1);
|
|
63
78
|
}
|
|
64
|
-
}
|
|
79
|
+
};
|
|
80
|
+
const registerSignalHandlers = () => {
|
|
81
|
+
const signals = ['SIGTERM', 'SIGINT', 'SIGBREAK'];
|
|
82
|
+
for (const signal of signals) {
|
|
83
|
+
process.on(signal, () => void shutdown(signal));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const registerProcessHandlers = () => {
|
|
87
|
+
process.on('uncaughtException', (err, origin) => {
|
|
88
|
+
logger.error(`Uncaught exception (${origin}):`, err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
process.on('unhandledRejection', (reason) => {
|
|
92
|
+
logger.error('Unhandled rejection:', reason);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
65
96
|
void main();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
process.on('SIGBREAK', () => void shutdown('SIGBREAK'));
|
|
69
|
-
process.on('uncaughtException', (err, origin) => {
|
|
70
|
-
logger.error(`Uncaught exception (${origin}):`, err);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
});
|
|
73
|
-
process.on('unhandledRejection', (reason) => {
|
|
74
|
-
logger.error('Unhandled rejection:', reason);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
});
|
|
77
|
-
//# sourceMappingURL=index.js.map
|
|
97
|
+
registerSignalHandlers();
|
|
98
|
+
registerProcessHandlers();
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -17,4 +17,3 @@ type ErrorResponse = CallToolResult & {
|
|
|
17
17
|
export declare function getErrorMessage(error: unknown): string;
|
|
18
18
|
export declare function createErrorResponse(code: string, message: string, result?: unknown): ErrorResponse;
|
|
19
19
|
export {};
|
|
20
|
-
//# sourceMappingURL=errors.d.ts.map
|
package/dist/lib/errors.js
CHANGED
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { config } from './config.js';
|
|
2
|
+
const LEVELS = {
|
|
3
|
+
error: 0,
|
|
4
|
+
warn: 1,
|
|
5
|
+
info: 2,
|
|
6
|
+
};
|
|
7
|
+
const threshold = LEVELS[config.logLevel];
|
|
8
|
+
const createWriter = (level) => (msg, ...args) => {
|
|
9
|
+
if (LEVELS[level] > threshold)
|
|
10
|
+
return;
|
|
11
|
+
console.error(`[${level.toUpperCase()}] ${msg}`, ...args);
|
|
12
|
+
};
|
|
13
|
+
export const logger = {
|
|
14
|
+
info: createWriter('info'),
|
|
15
|
+
warn: createWriter('warn'),
|
|
16
|
+
error: createWriter('error'),
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Transport, TransportSendOptions } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
|
+
import { type JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
export declare class ProtocolVersionGuardTransport implements Transport {
|
|
4
|
+
readonly inner: Transport;
|
|
5
|
+
private readonly supportedVersions;
|
|
6
|
+
onclose: () => void;
|
|
7
|
+
onerror: (error: Error) => void;
|
|
8
|
+
onmessage: NonNullable<Transport['onmessage']>;
|
|
9
|
+
private sawInitialize;
|
|
10
|
+
private ready;
|
|
11
|
+
constructor(inner: Transport, supportedVersions: readonly string[]);
|
|
12
|
+
setProtocolVersion(version: string): void;
|
|
13
|
+
start(): Promise<void>;
|
|
14
|
+
send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
private handleMessage;
|
|
17
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { isInitializedNotification, isInitializeRequest, isJSONRPCErrorResponse, isJSONRPCRequest, isJSONRPCResultResponse, } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
const buildUnsupportedVersionMessage = (version, supportedVersions) => `Unsupported protocol version: ${version} (supported versions: ${supportedVersions.join(', ')})`;
|
|
3
|
+
const isObject = (value) => typeof value === 'object' && value !== null;
|
|
4
|
+
const getInitializeInfo = (message) => {
|
|
5
|
+
if (!isInitializeRequest(message))
|
|
6
|
+
return undefined;
|
|
7
|
+
const { params, id } = message;
|
|
8
|
+
if (typeof id !== 'string' && typeof id !== 'number')
|
|
9
|
+
return undefined;
|
|
10
|
+
if (!isObject(params))
|
|
11
|
+
return undefined;
|
|
12
|
+
const { protocolVersion } = params;
|
|
13
|
+
if (typeof protocolVersion !== 'string')
|
|
14
|
+
return undefined;
|
|
15
|
+
return { id, protocolVersion };
|
|
16
|
+
};
|
|
17
|
+
const createUnsupportedVersionError = (id, version, supportedVersions) => ({
|
|
18
|
+
jsonrpc: '2.0',
|
|
19
|
+
id,
|
|
20
|
+
error: {
|
|
21
|
+
code: -32000,
|
|
22
|
+
message: buildUnsupportedVersionMessage(version, supportedVersions),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const createLifecycleError = (id, message) => ({
|
|
26
|
+
jsonrpc: '2.0',
|
|
27
|
+
id,
|
|
28
|
+
error: {
|
|
29
|
+
code: -32600,
|
|
30
|
+
message,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
export class ProtocolVersionGuardTransport {
|
|
34
|
+
inner;
|
|
35
|
+
supportedVersions;
|
|
36
|
+
onclose = () => { };
|
|
37
|
+
onerror = () => { };
|
|
38
|
+
onmessage = () => { };
|
|
39
|
+
sawInitialize = false;
|
|
40
|
+
ready = false;
|
|
41
|
+
constructor(inner, supportedVersions) {
|
|
42
|
+
this.inner = inner;
|
|
43
|
+
this.supportedVersions = supportedVersions;
|
|
44
|
+
}
|
|
45
|
+
setProtocolVersion(version) {
|
|
46
|
+
this.inner.setProtocolVersion?.(version);
|
|
47
|
+
}
|
|
48
|
+
async start() {
|
|
49
|
+
this.inner.onmessage = (message, extra) => {
|
|
50
|
+
this.handleMessage(message, extra);
|
|
51
|
+
};
|
|
52
|
+
this.inner.onerror = (error) => {
|
|
53
|
+
this.onerror(error);
|
|
54
|
+
};
|
|
55
|
+
this.inner.onclose = () => {
|
|
56
|
+
this.onclose();
|
|
57
|
+
};
|
|
58
|
+
await this.inner.start();
|
|
59
|
+
}
|
|
60
|
+
async send(message, options) {
|
|
61
|
+
await this.inner.send(message, options);
|
|
62
|
+
}
|
|
63
|
+
async close() {
|
|
64
|
+
await this.inner.close();
|
|
65
|
+
}
|
|
66
|
+
handleMessage(message, extra) {
|
|
67
|
+
const initializeInfo = getInitializeInfo(message);
|
|
68
|
+
if (initializeInfo) {
|
|
69
|
+
if (this.sawInitialize) {
|
|
70
|
+
void this.inner.send(createLifecycleError(initializeInfo.id, 'Invalid request: initialize already received'), { relatedRequestId: initializeInfo.id });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!this.supportedVersions.includes(initializeInfo.protocolVersion)) {
|
|
74
|
+
void this.inner.send(createUnsupportedVersionError(initializeInfo.id, initializeInfo.protocolVersion, this.supportedVersions), { relatedRequestId: initializeInfo.id });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.sawInitialize = true;
|
|
78
|
+
this.onmessage(message, extra);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (isInitializedNotification(message)) {
|
|
82
|
+
if (this.sawInitialize && !this.ready) {
|
|
83
|
+
this.ready = true;
|
|
84
|
+
this.onmessage(message, extra);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!this.sawInitialize || !this.ready) {
|
|
89
|
+
if (isJSONRPCRequest(message)) {
|
|
90
|
+
void this.inner.send(createLifecycleError(message.id, 'Invalid request: initialize must be sent before other requests'), { relatedRequestId: message.id });
|
|
91
|
+
}
|
|
92
|
+
else if (isJSONRPCResultResponse(message) ||
|
|
93
|
+
isJSONRPCErrorResponse(message)) {
|
|
94
|
+
this.onmessage(message, extra);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.onmessage(message, extra);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/schemas/inputs.d.ts
CHANGED
package/dist/schemas/inputs.js
CHANGED
package/dist/schemas/outputs.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const StoreMemoryInputSchema: z.ZodObject<{
|
|
3
|
+
content: z.ZodString;
|
|
4
|
+
tags: z.ZodArray<z.ZodString>;
|
|
5
|
+
}, z.core.$strict>;
|
|
6
|
+
export declare const SearchMemoriesInputSchema: z.ZodObject<{
|
|
7
|
+
query: z.ZodString;
|
|
8
|
+
}, z.core.$strict>;
|
|
9
|
+
export declare const GetMemoryInputSchema: z.ZodObject<{
|
|
10
|
+
hash: z.ZodString;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export declare const DeleteMemoryInputSchema: z.ZodObject<{
|
|
13
|
+
hash: z.ZodString;
|
|
14
|
+
}, z.core.$strict>;
|
|
15
|
+
export declare const UpdateMemoryInputSchema: z.ZodObject<{
|
|
16
|
+
hash: z.ZodString;
|
|
17
|
+
content: z.ZodString;
|
|
18
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
19
|
+
}, z.core.$strict>;
|
|
20
|
+
export declare const MemoryStatsInputSchema: z.ZodObject<{}, z.core.$strict>;
|
|
21
|
+
export declare const DefaultOutputSchema: z.ZodObject<{
|
|
22
|
+
ok: z.ZodBoolean;
|
|
23
|
+
result: z.ZodOptional<z.ZodUnknown>;
|
|
24
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
25
|
+
code: z.ZodString;
|
|
26
|
+
message: z.ZodString;
|
|
27
|
+
}, z.core.$strict>>;
|
|
28
|
+
}, z.core.$strict>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const hashSchema = z.string().regex(/^[a-f0-9]{32}$/i);
|
|
3
|
+
const tagSchema = z
|
|
4
|
+
.string()
|
|
5
|
+
.min(1)
|
|
6
|
+
.max(50)
|
|
7
|
+
.regex(/^\S+$/, 'Tag must not contain whitespace');
|
|
8
|
+
const tagsSchema = z.array(tagSchema);
|
|
9
|
+
const contentSchema = z.string().min(1).max(100000);
|
|
10
|
+
const querySchema = z.string().trim().min(1).max(1000);
|
|
11
|
+
export const StoreMemoryInputSchema = z.strictObject({
|
|
12
|
+
content: contentSchema.meta({ description: 'The content of the memory' }),
|
|
13
|
+
tags: tagsSchema.min(1).max(100).meta({
|
|
14
|
+
description: 'Tags to categorize the memory (1-100 tags, no whitespace, max 50 chars each)',
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
export const SearchMemoriesInputSchema = z.strictObject({
|
|
18
|
+
query: querySchema.meta({
|
|
19
|
+
description: 'Search query (searches content and tags)',
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
export const GetMemoryInputSchema = z.strictObject({
|
|
23
|
+
hash: hashSchema.meta({ description: 'MD5 hash of the memory' }),
|
|
24
|
+
});
|
|
25
|
+
export const DeleteMemoryInputSchema = z.strictObject({
|
|
26
|
+
hash: hashSchema.meta({ description: 'MD5 hash of the memory' }),
|
|
27
|
+
});
|
|
28
|
+
export const UpdateMemoryInputSchema = z.strictObject({
|
|
29
|
+
hash: hashSchema.meta({ description: 'MD5 hash of the memory to update' }),
|
|
30
|
+
content: contentSchema.meta({ description: 'New content for the memory' }),
|
|
31
|
+
tags: tagsSchema
|
|
32
|
+
.max(100)
|
|
33
|
+
.optional()
|
|
34
|
+
.meta({ description: 'Replace tags (max 100 tags, each max 50 chars)' }),
|
|
35
|
+
});
|
|
36
|
+
export const MemoryStatsInputSchema = z
|
|
37
|
+
.strictObject({})
|
|
38
|
+
.meta({ description: 'No parameters required' });
|
|
39
|
+
const ErrorSchema = z.strictObject({
|
|
40
|
+
code: z.string(),
|
|
41
|
+
message: z.string(),
|
|
42
|
+
});
|
|
43
|
+
const DefaultOutputSchemaBase = z.strictObject({
|
|
44
|
+
ok: z.boolean(),
|
|
45
|
+
result: z.unknown().optional(),
|
|
46
|
+
error: ErrorSchema.optional(),
|
|
47
|
+
});
|
|
48
|
+
const addIssue = (ctx, path, message) => {
|
|
49
|
+
ctx.addIssue({
|
|
50
|
+
code: 'custom',
|
|
51
|
+
message,
|
|
52
|
+
path,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
export const DefaultOutputSchema = DefaultOutputSchemaBase.superRefine((value, ctx) => {
|
|
56
|
+
if (value.ok) {
|
|
57
|
+
if (value.error !== undefined) {
|
|
58
|
+
addIssue(ctx, ['error'], 'error must be absent when ok is true');
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (value.error === undefined) {
|
|
63
|
+
addIssue(ctx, ['error'], 'error is required when ok is false');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -14,17 +14,7 @@ export const searchTools = [
|
|
|
14
14
|
},
|
|
15
15
|
handler: wrapHandler('E_SEARCH_MEMORIES', (params) => {
|
|
16
16
|
const input = SearchMemoriesInputSchema.parse(params);
|
|
17
|
-
|
|
18
|
-
query: input.query,
|
|
19
|
-
limit: input.limit ?? 10,
|
|
20
|
-
tags: input.tags ?? [],
|
|
21
|
-
...(input.minRelevance !== undefined
|
|
22
|
-
? { minRelevance: input.minRelevance }
|
|
23
|
-
: {}),
|
|
24
|
-
...(input.offset !== undefined ? { offset: input.offset } : {}),
|
|
25
|
-
};
|
|
26
|
-
return ok(searchMemories(searchInput));
|
|
17
|
+
return ok(searchMemories(input));
|
|
27
18
|
}),
|
|
28
19
|
},
|
|
29
20
|
];
|
|
30
|
-
//# sourceMappingURL=memory-search.js.map
|