@convertcom/mcp-server 0.0.1-beta.6 → 0.0.1-beta.8
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/build/api-config.d.ts +1 -1
- package/build/api-config.js +10 -16
- package/build/api-config.js.map +1 -1
- package/build/executor.d.ts +1 -3
- package/build/executor.js +235 -51
- package/build/executor.js.map +1 -1
- package/build/index.d.ts +0 -8
- package/build/index.js +99 -45
- package/build/index.js.map +1 -1
- package/build/kb-hybrid.d.ts +20 -0
- package/build/kb-hybrid.js +69 -0
- package/build/kb-hybrid.js.map +1 -0
- package/build/kb-semantic.d.ts +16 -0
- package/build/kb-semantic.js +97 -0
- package/build/kb-semantic.js.map +1 -0
- package/build/namespaced.d.ts +6 -0
- package/build/namespaced.js +421 -51
- package/build/namespaced.js.map +1 -1
- package/build/shared.d.ts +6 -0
- package/build/shared.js +14 -0
- package/build/shared.js.map +1 -0
- package/build/tools.js +527 -816
- package/build/tools.js.map +1 -1
- package/package.json +27 -21
package/build/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
// Load environment variables from .env file
|
|
7
7
|
import dotenv from 'dotenv';
|
|
8
|
-
dotenv.config();
|
|
8
|
+
dotenv.config({ quiet: true });
|
|
9
9
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
11
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -14,36 +14,45 @@ import { getNamespacedTools, executeNamespacedTool, isNamespace } from './namesp
|
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { resolve, dirname } from 'node:path';
|
|
16
16
|
import { KbSearchService } from './kb-search.js';
|
|
17
|
+
import { SemanticKbSearchService } from './kb-semantic.js';
|
|
18
|
+
import { HybridKbSearchService } from './kb-hybrid.js';
|
|
17
19
|
if (process.env.NODE_ENV === 'development') {
|
|
18
20
|
import('mcps-logger/console');
|
|
19
21
|
}
|
|
20
|
-
import './api-config.js';
|
|
22
|
+
import { API_BASE_URL } from './api-config.js';
|
|
21
23
|
import { toolDefinitionMap } from './tools.js';
|
|
22
24
|
import { DebugLogger } from './debug-log.js';
|
|
25
|
+
import { JSON_SCHEMA_DRAFT_2020_12 } from './shared.js';
|
|
23
26
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
27
|
const knowledgeBasePath = resolve(__dirname, '..', 'data', 'convert-kb.json');
|
|
25
|
-
const
|
|
28
|
+
const vectorsDir = resolve(__dirname, '..', 'data', 'kb-vectors');
|
|
29
|
+
const keywordSearch = new KbSearchService(knowledgeBasePath);
|
|
30
|
+
const semanticSearch = new SemanticKbSearchService(knowledgeBasePath, vectorsDir);
|
|
31
|
+
const hybridSearch = new HybridKbSearchService(keywordSearch, semanticSearch);
|
|
26
32
|
const SERVER_INSTRUCTIONS = [
|
|
27
|
-
"You
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
33
|
+
"You have DIRECT API ACCESS to the user's Convert.com account.",
|
|
34
|
+
"This server provides 13 namespace tools (accounts, projects, domains, experiences, sections,",
|
|
35
|
+
"goals, tags, hypotheses, knowledgebases, observations, locations, audiences, features) that",
|
|
36
|
+
"call the Convert.com REST API on behalf of the authenticated user.",
|
|
37
|
+
"",
|
|
38
|
+
"WHEN THE USER ASKS ABOUT THEIR CONVERT DATA, USE THE NAMESPACE TOOLS DIRECTLY.",
|
|
39
|
+
"For example: 'what are my projects?' → call the 'projects' tool with action 'list'.",
|
|
40
|
+
"'show my A/B tests' → call the 'experiences' tool with action 'list'.",
|
|
41
|
+
"Each namespace tool accepts: { action, path, query, body }. Check _meta.available_actions for details.",
|
|
42
|
+
"",
|
|
43
|
+
"The 'search_knowledge_base' tool searches Convert's support documentation — use it when you need",
|
|
44
|
+
"help with Convert concepts, terminology, or setup guides. It does NOT access the user's account data.",
|
|
45
|
+
"",
|
|
46
|
+
"Key rules:",
|
|
47
|
+
"- Whenever an API response returns raw IDs (goals, projects, audiences, etc.), immediately enrich",
|
|
48
|
+
" those IDs by calling the appropriate namespace tool so the final answer contains descriptive names.",
|
|
49
|
+
" The human should never have to resolve IDs manually.",
|
|
50
|
+
"- When fetching resources with related data, ALWAYS use BOTH 'include' AND 'expand' query parameters",
|
|
51
|
+
" together: {\"query\": {\"include\": [\"goals\"], \"expand\": [\"goals\"]}}.",
|
|
52
|
+
" 'include' alone returns useless numeric IDs. 'expand' provides the full object data.",
|
|
53
|
+
"- Array parameters (include, expand) must always be arrays, even for single values.",
|
|
54
|
+
"- Pay attention to API response codes and any 'alerts' array in the payload."
|
|
55
|
+
].join('\n');
|
|
47
56
|
const debugLogger = new DebugLogger(process.env.CONVERT_DEBUG_LOG === '1' || process.env.CONVERT_DEBUG_LOG?.toLowerCase() === 'true');
|
|
48
57
|
function normalizeLooseJson(input) {
|
|
49
58
|
let output = input
|
|
@@ -100,18 +109,49 @@ function extractQueryArgument(rawArgs) {
|
|
|
100
109
|
return null;
|
|
101
110
|
}
|
|
102
111
|
const searchKnowledgeBaseTool = {
|
|
103
|
-
name: '
|
|
104
|
-
|
|
112
|
+
name: 'search_knowledge_base',
|
|
113
|
+
title: 'Search Knowledge Base',
|
|
114
|
+
description: 'Search Convert.com support documentation for product concepts, terminology, setup guides, and workflows. This does NOT access the user\'s account — use the namespace tools (projects, experiences, goals, etc.) to query live account data.',
|
|
105
115
|
inputSchema: {
|
|
116
|
+
$schema: JSON_SCHEMA_DRAFT_2020_12,
|
|
106
117
|
type: 'object',
|
|
107
118
|
properties: {
|
|
108
119
|
query: {
|
|
109
120
|
type: 'string',
|
|
110
|
-
description: '
|
|
121
|
+
description: 'Search query — use specific terms like product names, feature labels, or integration names (e.g., "install convert on shopify", "goal types", "GDPR consent").'
|
|
111
122
|
}
|
|
112
123
|
},
|
|
113
124
|
required: ['query'],
|
|
114
125
|
additionalProperties: false
|
|
126
|
+
},
|
|
127
|
+
outputSchema: {
|
|
128
|
+
$schema: JSON_SCHEMA_DRAFT_2020_12,
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
query: { type: 'string', description: 'The query that was searched.' },
|
|
132
|
+
results: {
|
|
133
|
+
type: 'array',
|
|
134
|
+
description: 'Matching knowledge base articles, ranked by relevance.',
|
|
135
|
+
items: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {
|
|
138
|
+
title: { type: 'string', description: 'Article title.' },
|
|
139
|
+
url: { type: 'string', description: 'Source URL of the support article.' },
|
|
140
|
+
snippet: { type: 'string', description: 'Relevant excerpt from the article.' },
|
|
141
|
+
score: { type: 'number', description: 'Relevance score normalized 0–1 (1 = best match).' }
|
|
142
|
+
},
|
|
143
|
+
required: ['title', 'url', 'snippet', 'score']
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
required: ['query', 'results']
|
|
148
|
+
},
|
|
149
|
+
annotations: {
|
|
150
|
+
title: 'Search Knowledge Base',
|
|
151
|
+
readOnlyHint: true,
|
|
152
|
+
destructiveHint: false,
|
|
153
|
+
idempotentHint: true,
|
|
154
|
+
openWorldHint: false
|
|
115
155
|
}
|
|
116
156
|
};
|
|
117
157
|
function getToolsForClient() {
|
|
@@ -123,11 +163,15 @@ function getToolsForClient() {
|
|
|
123
163
|
*/
|
|
124
164
|
export const SERVER_NAME = "convert-api";
|
|
125
165
|
export const SERVER_VERSION = "2.0.0";
|
|
126
|
-
export const API_BASE_URL = "https://api.convert.com/api/v2";
|
|
127
166
|
/**
|
|
128
167
|
* MCP Server instance
|
|
129
168
|
*/
|
|
130
|
-
const server = new Server({
|
|
169
|
+
const server = new Server({
|
|
170
|
+
name: SERVER_NAME,
|
|
171
|
+
version: SERVER_VERSION,
|
|
172
|
+
title: 'Convert.com',
|
|
173
|
+
description: 'Direct API access to the user\'s Convert.com A/B testing account — manage experiences, projects, goals, audiences, and more. Includes a knowledge base for product documentation.',
|
|
174
|
+
}, {
|
|
131
175
|
capabilities: { tools: {} },
|
|
132
176
|
instructions: SERVER_INSTRUCTIONS
|
|
133
177
|
});
|
|
@@ -137,20 +181,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
137
181
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
138
182
|
const { name: toolName, arguments: toolArgs } = request.params;
|
|
139
183
|
debugLogger.log('callToolRequest', { toolName, arguments: toolArgs });
|
|
140
|
-
if (toolName === '
|
|
184
|
+
if (toolName === 'search_knowledge_base') {
|
|
141
185
|
try {
|
|
142
186
|
const query = extractQueryArgument(toolArgs);
|
|
143
187
|
if (!query) {
|
|
144
|
-
debugLogger.log('
|
|
188
|
+
debugLogger.log('search_knowledge_base.invalidArguments', { toolArgs });
|
|
145
189
|
return {
|
|
146
190
|
isError: true,
|
|
147
|
-
content: [{ type: 'text', text: "Error: The 'query' argument is required for
|
|
148
|
-
structuredContent: { error: "The 'query' argument is required for
|
|
191
|
+
content: [{ type: 'text', text: "Error: The 'query' argument is required for search_knowledge_base." }],
|
|
192
|
+
structuredContent: { error: "The 'query' argument is required for search_knowledge_base." }
|
|
149
193
|
};
|
|
150
194
|
}
|
|
151
|
-
const results =
|
|
195
|
+
const results = await hybridSearch.search(query, 5);
|
|
152
196
|
if (results.length === 0) {
|
|
153
|
-
debugLogger.log('
|
|
197
|
+
debugLogger.log('search_knowledge_base.noResults', { query });
|
|
154
198
|
return {
|
|
155
199
|
content: [{ type: 'text', text: `No knowledge base entries were found for the query: "${query}".` }],
|
|
156
200
|
structuredContent: { query, results: [], message: `No knowledge base entries found for "${query}".` }
|
|
@@ -169,7 +213,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
169
213
|
const scoreText = Number.isFinite(result.score) ? ` (Relevance: ${(result.score * 100).toFixed(1)}%)` : '';
|
|
170
214
|
return `${index + 1}. ${result.title}${scoreText}\n${result.url}\n${result.snippet}`;
|
|
171
215
|
}).join('\n\n---\n\n');
|
|
172
|
-
debugLogger.log('
|
|
216
|
+
debugLogger.log('search_knowledge_base.success', { query, matchCount: results.length });
|
|
173
217
|
return {
|
|
174
218
|
content: [{ type: 'text', text: textSummary }],
|
|
175
219
|
structuredContent: { query, results: structuredResults }
|
|
@@ -177,7 +221,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
177
221
|
}
|
|
178
222
|
catch (error) {
|
|
179
223
|
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
-
debugLogger.log('
|
|
224
|
+
debugLogger.log('search_knowledge_base.error', { toolArgs, error: message });
|
|
181
225
|
return {
|
|
182
226
|
isError: true,
|
|
183
227
|
content: [{ type: 'text', text: `Unexpected error during knowledge base search: ${message}` }],
|
|
@@ -197,7 +241,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
197
241
|
}
|
|
198
242
|
console.error(`Error: Unknown tool requested: ${toolName}`);
|
|
199
243
|
debugLogger.log('unknownTool', { toolName });
|
|
200
|
-
return {
|
|
244
|
+
return {
|
|
245
|
+
isError: true,
|
|
246
|
+
content: [{ type: 'text', text: `Error: Unknown tool requested: ${toolName}` }],
|
|
247
|
+
structuredContent: { error: `Unknown tool: ${toolName}` },
|
|
248
|
+
};
|
|
201
249
|
});
|
|
202
250
|
/**
|
|
203
251
|
* Main function to start the server
|
|
@@ -219,7 +267,20 @@ async function main() {
|
|
|
219
267
|
*/
|
|
220
268
|
async function cleanup() {
|
|
221
269
|
console.error("Shutting down MCP server...");
|
|
222
|
-
|
|
270
|
+
await semanticSearch.waitReady().catch(() => { });
|
|
271
|
+
// Check BEFORE close() — close() clears references used by this check.
|
|
272
|
+
const loadedNativeRuntime = semanticSearch.hasNativeRuntimeLoaded();
|
|
273
|
+
hybridSearch.close();
|
|
274
|
+
// When both ONNX Runtime (HF Transformers) and Zvec native addons are
|
|
275
|
+
// loaded, their C++ atexit destructors conflict causing a libc++ abort.
|
|
276
|
+
// SIGKILL skips atexit entirely. The Zvec collection is read-only so
|
|
277
|
+
// skipping its close is safe (no write buffers to flush).
|
|
278
|
+
if (loadedNativeRuntime) {
|
|
279
|
+
process.kill(process.pid, 'SIGKILL');
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
223
284
|
}
|
|
224
285
|
// Register signal handlers
|
|
225
286
|
process.on('SIGINT', cleanup);
|
|
@@ -229,11 +290,4 @@ main().catch((error) => {
|
|
|
229
290
|
console.error("Fatal error in main execution:", error);
|
|
230
291
|
process.exit(1);
|
|
231
292
|
});
|
|
232
|
-
/**
|
|
233
|
-
* Formats API errors for better readability
|
|
234
|
-
*
|
|
235
|
-
* @param error Axios error
|
|
236
|
-
* @returns Formatted error message
|
|
237
|
-
*/
|
|
238
|
-
// Removed legacy per-tool executor from index; execution handled by executor.ts and namespaced.ts
|
|
239
293
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,4CAA4C;AAC5C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,MAAM,CAAC,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { KbSearchService, SearchResult } from './kb-search.js';
|
|
2
|
+
import type { SemanticKbSearchService } from './kb-semantic.js';
|
|
3
|
+
export declare class HybridKbSearchService {
|
|
4
|
+
private readonly keyword;
|
|
5
|
+
private readonly semantic;
|
|
6
|
+
constructor(keyword: KbSearchService, semantic: SemanticKbSearchService);
|
|
7
|
+
search(query: string, limit?: number): Promise<SearchResult[]>;
|
|
8
|
+
close(): void;
|
|
9
|
+
/**
|
|
10
|
+
* Reciprocal Rank Fusion (RRF) — merges two ranked result lists.
|
|
11
|
+
*
|
|
12
|
+
* For each result at rank r (0-indexed):
|
|
13
|
+
* rrfScore(chunk) += 1 / (k + r + 1) where k = 60
|
|
14
|
+
*
|
|
15
|
+
* Deduplicates by chunk URL. When a chunk appears in both sets,
|
|
16
|
+
* keeps the keyword SearchResult (it has query-highlighted snippets).
|
|
17
|
+
* Normalizes final scores to 0–1 range.
|
|
18
|
+
*/
|
|
19
|
+
static fuseRRF(keyword: SearchResult[], semantic: SearchResult[], limit: number): SearchResult[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const RRF_K = 60;
|
|
2
|
+
const OVERFETCH_FACTOR = 3;
|
|
3
|
+
export class HybridKbSearchService {
|
|
4
|
+
keyword;
|
|
5
|
+
semantic;
|
|
6
|
+
constructor(keyword, semantic) {
|
|
7
|
+
this.keyword = keyword;
|
|
8
|
+
this.semantic = semantic;
|
|
9
|
+
}
|
|
10
|
+
async search(query, limit = 5) {
|
|
11
|
+
if (!this.semantic.isReady()) {
|
|
12
|
+
return this.keyword.search(query, limit);
|
|
13
|
+
}
|
|
14
|
+
const fetchLimit = limit * OVERFETCH_FACTOR;
|
|
15
|
+
const [keywordResults, semanticResults] = await Promise.all([
|
|
16
|
+
Promise.resolve(this.keyword.search(query, fetchLimit)),
|
|
17
|
+
this.semantic.search(query, fetchLimit),
|
|
18
|
+
]);
|
|
19
|
+
if (semanticResults.length === 0) {
|
|
20
|
+
return keywordResults.slice(0, limit);
|
|
21
|
+
}
|
|
22
|
+
return HybridKbSearchService.fuseRRF(keywordResults, semanticResults, limit);
|
|
23
|
+
}
|
|
24
|
+
close() {
|
|
25
|
+
this.semantic.close();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reciprocal Rank Fusion (RRF) — merges two ranked result lists.
|
|
29
|
+
*
|
|
30
|
+
* For each result at rank r (0-indexed):
|
|
31
|
+
* rrfScore(chunk) += 1 / (k + r + 1) where k = 60
|
|
32
|
+
*
|
|
33
|
+
* Deduplicates by chunk URL. When a chunk appears in both sets,
|
|
34
|
+
* keeps the keyword SearchResult (it has query-highlighted snippets).
|
|
35
|
+
* Normalizes final scores to 0–1 range.
|
|
36
|
+
*/
|
|
37
|
+
static fuseRRF(keyword, semantic, limit) {
|
|
38
|
+
const scores = new Map();
|
|
39
|
+
const results = new Map();
|
|
40
|
+
// Accumulate keyword scores (preferred source for snippets)
|
|
41
|
+
for (let r = 0; r < keyword.length; r++) {
|
|
42
|
+
const key = keyword[r].chunk.url;
|
|
43
|
+
const rrfScore = 1 / (RRF_K + r + 1);
|
|
44
|
+
scores.set(key, (scores.get(key) ?? 0) + rrfScore);
|
|
45
|
+
results.set(key, keyword[r]);
|
|
46
|
+
}
|
|
47
|
+
// Accumulate semantic scores (don't overwrite keyword results)
|
|
48
|
+
for (let r = 0; r < semantic.length; r++) {
|
|
49
|
+
const key = semantic[r].chunk.url;
|
|
50
|
+
const rrfScore = 1 / (RRF_K + r + 1);
|
|
51
|
+
scores.set(key, (scores.get(key) ?? 0) + rrfScore);
|
|
52
|
+
if (!results.has(key)) {
|
|
53
|
+
results.set(key, semantic[r]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Sort by accumulated RRF score descending
|
|
57
|
+
const sorted = [...scores.entries()].sort((a, b) => b[1] - a[1]);
|
|
58
|
+
const topRrfScore = sorted.length > 0 ? sorted[0][1] : 1;
|
|
59
|
+
return sorted.slice(0, limit).map(([key, rrfScore]) => {
|
|
60
|
+
const result = results.get(key);
|
|
61
|
+
return {
|
|
62
|
+
chunk: result.chunk,
|
|
63
|
+
snippet: result.snippet,
|
|
64
|
+
score: Number((rrfScore / topRrfScore).toFixed(4)),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=kb-hybrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kb-hybrid.js","sourceRoot":"","sources":["../src/kb-hybrid.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SearchResult } from './kb-search.js';
|
|
2
|
+
export declare class SemanticKbSearchService {
|
|
3
|
+
private _isReady;
|
|
4
|
+
private _initPromise;
|
|
5
|
+
private _collection;
|
|
6
|
+
private _extractor;
|
|
7
|
+
private _chunks;
|
|
8
|
+
private _hasLoadedNativeRuntime;
|
|
9
|
+
constructor(kbJsonPath: string, vectorsDir: string);
|
|
10
|
+
isReady(): boolean;
|
|
11
|
+
waitReady(): Promise<boolean>;
|
|
12
|
+
hasNativeRuntimeLoaded(): boolean;
|
|
13
|
+
search(query: string, limit?: number): Promise<SearchResult[]>;
|
|
14
|
+
close(): void;
|
|
15
|
+
private _init;
|
|
16
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
const MODEL_ID = 'Xenova/all-MiniLM-L6-v2';
|
|
3
|
+
const EMBEDDING_DIM = 384;
|
|
4
|
+
export class SemanticKbSearchService {
|
|
5
|
+
_isReady = false;
|
|
6
|
+
_initPromise;
|
|
7
|
+
_collection = null;
|
|
8
|
+
_extractor = null;
|
|
9
|
+
_chunks = [];
|
|
10
|
+
_hasLoadedNativeRuntime = false;
|
|
11
|
+
constructor(kbJsonPath, vectorsDir) {
|
|
12
|
+
this._initPromise = this._init(kbJsonPath, vectorsDir);
|
|
13
|
+
}
|
|
14
|
+
isReady() {
|
|
15
|
+
return this._isReady;
|
|
16
|
+
}
|
|
17
|
+
waitReady() {
|
|
18
|
+
return this._initPromise;
|
|
19
|
+
}
|
|
20
|
+
hasNativeRuntimeLoaded() {
|
|
21
|
+
return this._hasLoadedNativeRuntime;
|
|
22
|
+
}
|
|
23
|
+
async search(query, limit = 5) {
|
|
24
|
+
if (!this._isReady || !this._collection || !this._extractor) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const output = await this._extractor(query, { pooling: 'mean', normalize: true });
|
|
29
|
+
const queryVec = new Float32Array(output.data);
|
|
30
|
+
if (queryVec.length !== EMBEDDING_DIM) {
|
|
31
|
+
console.error(`[SemanticKbSearch] Unexpected embedding dimension: ${queryVec.length}, expected ${EMBEDDING_DIM}`);
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const docs = this._collection.querySync({
|
|
35
|
+
fieldName: 'embedding',
|
|
36
|
+
vector: queryVec,
|
|
37
|
+
topk: limit,
|
|
38
|
+
outputFields: ['chunk_id'],
|
|
39
|
+
});
|
|
40
|
+
return docs.map((doc) => {
|
|
41
|
+
const chunkId = Number(doc.fields.chunk_id);
|
|
42
|
+
const chunk = this._chunks[chunkId];
|
|
43
|
+
if (!chunk)
|
|
44
|
+
return null;
|
|
45
|
+
const snippet = chunk.content.length > 360
|
|
46
|
+
? chunk.content.slice(0, 360) + '...'
|
|
47
|
+
: chunk.content;
|
|
48
|
+
return {
|
|
49
|
+
chunk,
|
|
50
|
+
score: doc.score,
|
|
51
|
+
snippet,
|
|
52
|
+
};
|
|
53
|
+
}).filter((r) => r !== null);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error('[SemanticKbSearch] Query error:', err instanceof Error ? err.message : err);
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
close() {
|
|
61
|
+
this._collection = null;
|
|
62
|
+
this._extractor = null;
|
|
63
|
+
this._isReady = false;
|
|
64
|
+
}
|
|
65
|
+
async _init(kbJsonPath, vectorsDir) {
|
|
66
|
+
try {
|
|
67
|
+
if (!existsSync(vectorsDir)) {
|
|
68
|
+
console.error(`[SemanticKbSearch] Vectors directory not found: ${vectorsDir} — falling back to keyword-only search.`);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
// Load chunks for content lookup by chunk_id
|
|
72
|
+
const raw = readFileSync(kbJsonPath, 'utf8');
|
|
73
|
+
this._chunks = JSON.parse(raw);
|
|
74
|
+
// Dynamic imports — missing packages throw catchable errors.
|
|
75
|
+
// Zvec is CJS, so dynamic import yields { default: { ZVecOpen, ... } }.
|
|
76
|
+
const [hf, zvecMod] = await Promise.all([
|
|
77
|
+
import('@huggingface/transformers'),
|
|
78
|
+
import('@zvec/zvec'),
|
|
79
|
+
]);
|
|
80
|
+
this._hasLoadedNativeRuntime = true;
|
|
81
|
+
const { ZVecOpen } = zvecMod.default ?? zvecMod;
|
|
82
|
+
this._extractor = await hf.pipeline('feature-extraction', MODEL_ID, {
|
|
83
|
+
dtype: 'fp32',
|
|
84
|
+
});
|
|
85
|
+
this._collection = ZVecOpen(vectorsDir, { readOnly: true });
|
|
86
|
+
this._isReady = true;
|
|
87
|
+
console.error(`[SemanticKbSearch] Ready — ${this._chunks.length} chunks, model: ${MODEL_ID}`);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error('[SemanticKbSearch] Init failed (keyword-only fallback):', err instanceof Error ? err.message : err);
|
|
92
|
+
this._isReady = false;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=kb-semantic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kb-semantic.js","sourceRoot":"","sources":["../src/kb-semantic.ts"],"names":[],"mappings":"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"}
|
package/build/namespaced.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Tool, CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Per-action agent guidance. Appended to generated descriptions as "Agent Notes:".
|
|
4
|
+
* Keys are "namespace.action" strings matching REGISTRY entries.
|
|
5
|
+
* This map survives `yarn generate` — it's hand-curated like REGISTRY.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ACTION_NOTES: Record<string, string>;
|
|
2
8
|
export declare function getNamespacedTools(toolSet: string): Tool[];
|
|
3
9
|
export declare function executeNamespacedTool(namespace: string, args: any): Promise<CallToolResult>;
|
|
4
10
|
export declare function isNamespace(name: string): boolean;
|