@sylphx/flow 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/package.json +1 -1
  3. package/src/commands/flow-command.ts +28 -0
  4. package/src/commands/hook-command.ts +10 -230
  5. package/src/composables/index.ts +0 -1
  6. package/src/config/servers.ts +35 -78
  7. package/src/core/interfaces.ts +0 -33
  8. package/src/domains/index.ts +0 -2
  9. package/src/index.ts +0 -4
  10. package/src/services/mcp-service.ts +0 -16
  11. package/src/targets/claude-code.ts +3 -9
  12. package/src/targets/functional/claude-code-logic.ts +4 -22
  13. package/src/targets/opencode.ts +0 -6
  14. package/src/types/mcp.types.ts +29 -38
  15. package/src/types/target.types.ts +0 -2
  16. package/src/types.ts +0 -1
  17. package/src/utils/sync-utils.ts +106 -0
  18. package/src/commands/codebase-command.ts +0 -168
  19. package/src/commands/knowledge-command.ts +0 -161
  20. package/src/composables/useTargetConfig.ts +0 -45
  21. package/src/core/formatting/bytes.test.ts +0 -115
  22. package/src/core/validation/limit.test.ts +0 -155
  23. package/src/core/validation/query.test.ts +0 -44
  24. package/src/domains/codebase/index.ts +0 -5
  25. package/src/domains/codebase/tools.ts +0 -139
  26. package/src/domains/knowledge/index.ts +0 -10
  27. package/src/domains/knowledge/resources.ts +0 -537
  28. package/src/domains/knowledge/tools.ts +0 -174
  29. package/src/services/search/base-indexer.ts +0 -156
  30. package/src/services/search/codebase-indexer-types.ts +0 -38
  31. package/src/services/search/codebase-indexer.ts +0 -647
  32. package/src/services/search/embeddings-provider.ts +0 -455
  33. package/src/services/search/embeddings.ts +0 -316
  34. package/src/services/search/functional-indexer.ts +0 -323
  35. package/src/services/search/index.ts +0 -27
  36. package/src/services/search/indexer.ts +0 -380
  37. package/src/services/search/knowledge-indexer.ts +0 -422
  38. package/src/services/search/semantic-search.ts +0 -244
  39. package/src/services/search/tfidf.ts +0 -559
  40. package/src/services/search/unified-search-service.ts +0 -888
  41. package/src/services/storage/cache-storage.ts +0 -487
  42. package/src/services/storage/drizzle-storage.ts +0 -581
  43. package/src/services/storage/index.ts +0 -15
  44. package/src/services/storage/lancedb-vector-storage.ts +0 -494
  45. package/src/services/storage/memory-storage.ts +0 -268
  46. package/src/services/storage/separated-storage.ts +0 -467
  47. package/src/services/storage/vector-storage.ts +0 -13
@@ -1,537 +0,0 @@
1
- /**
2
- * Knowledge Resources - 知識庫資源
3
- * 動態掃描文件夾並讀取 frontmatter 來提供知識庫內容
4
- */
5
-
6
- import fs from 'node:fs/promises';
7
- import path from 'node:path';
8
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
- import { z } from 'zod';
10
- import { logger } from '../../utils/logger.js';
11
- import { getKnowledgeDir } from '../../utils/paths.js';
12
-
13
- export interface KnowledgeResource {
14
- uri: string;
15
- title: string;
16
- description: string;
17
- content: string;
18
- category: 'stacks' | 'guides' | 'universal' | 'data';
19
- filePath?: string;
20
- lastModified?: string;
21
- }
22
-
23
- export interface KnowledgeFrontmatter {
24
- name: string;
25
- description: string;
26
- category?: string;
27
- tags?: string[];
28
- author?: string;
29
- lastUpdated?: string;
30
- }
31
-
32
- /**
33
- * 知識庫配置
34
- */
35
- interface KnowledgeConfig {
36
- knowledgeDir: string;
37
- supportedExtensions: string[];
38
- cacheTimeout: number; // milliseconds
39
- }
40
-
41
- const DEFAULT_CONFIG: KnowledgeConfig = {
42
- knowledgeDir: getKnowledgeDir(),
43
- supportedExtensions: ['.md'],
44
- cacheTimeout: 5 * 60 * 1000, // 5 minutes
45
- };
46
-
47
- /**
48
- * 知識庫掃描器
49
- */
50
- class KnowledgeScanner {
51
- private cache = new Map<string, { data: KnowledgeResource[]; timestamp: number }>();
52
- private config: KnowledgeConfig;
53
-
54
- constructor(config: Partial<KnowledgeConfig> = {}) {
55
- this.config = { ...DEFAULT_CONFIG, ...config };
56
- }
57
-
58
- /**
59
- * 掃描知識庫目錄並獲取所有資源
60
- */
61
- async scanKnowledgeResources(): Promise<KnowledgeResource[]> {
62
- const cacheKey = 'all_resources';
63
- const cached = this.cache.get(cacheKey);
64
-
65
- // 檢查緩存是否有效
66
- if (cached && Date.now() - cached.timestamp < this.config.cacheTimeout) {
67
- logger.debug('Returning cached knowledge resources');
68
- return cached.data;
69
- }
70
-
71
- try {
72
- logger.info('Scanning knowledge directory', { dir: this.config.knowledgeDir });
73
-
74
- const resources: KnowledgeResource[] = [];
75
- const categories = await this.getCategories();
76
-
77
- // 並行掃描所有類別
78
- const scanPromises = categories.map(async (category) => {
79
- const categoryResources = await this.scanCategory(category);
80
- return categoryResources;
81
- });
82
-
83
- const categoryResults = await Promise.all(scanPromises);
84
- categoryResults.forEach((categoryResources) => {
85
- resources.push(...categoryResources);
86
- });
87
-
88
- // 緩存結果
89
- this.cache.set(cacheKey, {
90
- data: resources,
91
- timestamp: Date.now(),
92
- });
93
-
94
- logger.info('Knowledge scan completed', {
95
- totalResources: resources.length,
96
- categories: categories.length,
97
- });
98
-
99
- return resources;
100
- } catch (error) {
101
- logger.error('Failed to scan knowledge resources', {
102
- error: (error as Error).message,
103
- dir: this.config.knowledgeDir,
104
- });
105
- throw error;
106
- }
107
- }
108
-
109
- /**
110
- * 獲取所有類別
111
- */
112
- private async getCategories(): Promise<string[]> {
113
- try {
114
- const entries = await fs.readdir(this.config.knowledgeDir, { withFileTypes: true });
115
- return entries
116
- .filter((entry) => entry.isDirectory())
117
- .map((entry) => entry.name)
118
- .filter((name) => !name.startsWith('.')); // 忽略隱藏文件夾
119
- } catch (error) {
120
- logger.warn('Failed to read knowledge directory', {
121
- error: (error as Error).message,
122
- dir: this.config.knowledgeDir,
123
- });
124
- return [];
125
- }
126
- }
127
-
128
- /**
129
- * 掃描特定類別
130
- */
131
- private async scanCategory(category: string): Promise<KnowledgeResource[]> {
132
- const categoryPath = path.join(this.config.knowledgeDir, category);
133
- const resources: KnowledgeResource[] = [];
134
-
135
- try {
136
- const files = await fs.readdir(categoryPath);
137
- const markdownFiles = files.filter((file) =>
138
- this.config.supportedExtensions.some((ext) => file.endsWith(ext))
139
- );
140
-
141
- // 並行處理所有文件
142
- const filePromises = markdownFiles.map(async (file) => {
143
- const filePath = path.join(categoryPath, file);
144
- return this.processFile(filePath, category);
145
- });
146
-
147
- const fileResults = await Promise.allSettled(filePromises);
148
- fileResults.forEach((result, index) => {
149
- if (result.status === 'fulfilled' && result.value) {
150
- resources.push(result.value);
151
- } else if (result.status === 'rejected') {
152
- logger.warn('Failed to process knowledge file', {
153
- file: markdownFiles[index],
154
- error: result.reason,
155
- });
156
- }
157
- });
158
-
159
- logger.debug('Category scan completed', {
160
- category,
161
- filesFound: markdownFiles.length,
162
- resourcesProcessed: resources.length,
163
- });
164
-
165
- return resources;
166
- } catch (error) {
167
- logger.warn('Failed to scan category', {
168
- category,
169
- error: (error as Error).message,
170
- });
171
- return [];
172
- }
173
- }
174
-
175
- /**
176
- * 處理單個文件
177
- */
178
- private async processFile(filePath: string, category: string): Promise<KnowledgeResource | null> {
179
- try {
180
- const content = await fs.readFile(filePath, 'utf-8');
181
- const frontmatter = this.parseFrontmatter(content);
182
-
183
- if (!frontmatter) {
184
- logger.warn('No frontmatter found in file', { filePath });
185
- return null;
186
- }
187
-
188
- // 生成 URI
189
- const fileName = path.basename(filePath, path.extname(filePath));
190
- const uri = `knowledge://${category}/${fileName}`;
191
-
192
- // 獲取文件統計信息
193
- const stats = await fs.stat(filePath);
194
-
195
- const resource: KnowledgeResource = {
196
- uri,
197
- title: frontmatter.name,
198
- description: frontmatter.description,
199
- content: this.extractMainContent(content),
200
- category: this.mapCategory(category),
201
- filePath,
202
- lastModified: stats.mtime.toISOString(),
203
- };
204
-
205
- logger.debug('Knowledge resource processed', {
206
- uri,
207
- title: resource.title,
208
- category: resource.category,
209
- });
210
-
211
- return resource;
212
- } catch (error) {
213
- logger.error('Failed to process knowledge file', {
214
- filePath,
215
- error: (error as Error).message,
216
- });
217
- return null;
218
- }
219
- }
220
-
221
- /**
222
- * 解析 frontmatter
223
- */
224
- private parseFrontmatter(content: string): KnowledgeFrontmatter | null {
225
- const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
226
- const match = content.match(frontmatterRegex);
227
-
228
- if (!match) {
229
- return null;
230
- }
231
-
232
- try {
233
- // 簡單的 YAML 解析(可以考慮使用 js-yaml 庫)
234
- const frontmatterText = match[1];
235
- const frontmatter: Partial<KnowledgeFrontmatter> = {};
236
-
237
- // 解析 key: value 格式
238
- frontmatterText.split('\n').forEach((line) => {
239
- const colonIndex = line.indexOf(':');
240
- if (colonIndex > 0) {
241
- const key = line.substring(0, colonIndex).trim();
242
- let value = line.substring(colonIndex + 1).trim();
243
-
244
- // 移除引號
245
- if (value.startsWith('"') && value.endsWith('"')) {
246
- value = value.slice(1, -1);
247
- } else if (value.startsWith("'") && value.endsWith("'")) {
248
- value = value.slice(1, -1);
249
- }
250
-
251
- (frontmatter as any)[key] = value;
252
- }
253
- });
254
-
255
- // 驗證必需字段
256
- if (!frontmatter.name || !frontmatter.description) {
257
- return null;
258
- }
259
-
260
- return frontmatter as KnowledgeFrontmatter;
261
- } catch (error) {
262
- logger.warn('Failed to parse frontmatter', {
263
- error: (error as Error).message,
264
- });
265
- return null;
266
- }
267
- }
268
-
269
- /**
270
- * 提取主要內容(移除 frontmatter)
271
- */
272
- private extractMainContent(content: string): string {
273
- const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
274
- return content.replace(frontmatterRegex, '').trim();
275
- }
276
-
277
- /**
278
- * 映射類別
279
- */
280
- private mapCategory(folderName: string): 'stacks' | 'guides' | 'universal' | 'data' {
281
- const categoryMap: Record<string, 'stacks' | 'guides' | 'universal' | 'data'> = {
282
- stacks: 'stacks',
283
- guides: 'guides',
284
- universal: 'universal',
285
- data: 'data',
286
- database: 'data',
287
- architecture: 'guides',
288
- patterns: 'guides',
289
- };
290
-
291
- return categoryMap[folderName.toLowerCase()] || 'guides';
292
- }
293
-
294
- /**
295
- * 根據 URI 獲取完整內容
296
- */
297
- async getKnowledgeContent(uri: string): Promise<string> {
298
- try {
299
- const resources = await this.scanKnowledgeResources();
300
- const resource = resources.find((r) => r.uri === uri);
301
-
302
- if (!resource) {
303
- throw new Error(`Knowledge resource not found: ${uri}`);
304
- }
305
-
306
- if (!resource.filePath) {
307
- throw new Error(`No file path for resource: ${uri}`);
308
- }
309
-
310
- // 讀取完整文件內容
311
- const fullContent = await fs.readFile(resource.filePath, 'utf-8');
312
- const mainContent = this.extractMainContent(fullContent);
313
-
314
- return mainContent;
315
- } catch (error) {
316
- logger.error('Failed to get knowledge content', {
317
- uri,
318
- error: (error as Error).message,
319
- });
320
- throw error;
321
- }
322
- }
323
-
324
- /**
325
- * 清除緩存
326
- */
327
- clearCache(): void {
328
- this.cache.clear();
329
- logger.info('Knowledge cache cleared');
330
- }
331
- }
332
-
333
- // 全局掃描器實例
334
- const knowledgeScanner = new KnowledgeScanner();
335
-
336
- /**
337
- * 獲取所有知識庫資源(動態掃描)
338
- */
339
- export async function getAllKnowledgeResources(): Promise<KnowledgeResource[]> {
340
- return knowledgeScanner.scanKnowledgeResources();
341
- }
342
-
343
- /**
344
- * 根據 URI 獲取知識庫內容
345
- */
346
- export async function getKnowledgeContent(uri: string): Promise<string> {
347
- return knowledgeScanner.getKnowledgeContent(uri);
348
- }
349
-
350
- /**
351
- * 根據類別獲取知識庫資源
352
- */
353
- export async function getKnowledgeResourcesByCategory(
354
- category: 'stacks' | 'guides' | 'universal' | 'data'
355
- ): Promise<KnowledgeResource[]> {
356
- const allResources = await getAllKnowledgeResources();
357
- return allResources.filter((resource) => resource.category === category);
358
- }
359
-
360
- /**
361
- * 搜索知識庫資源
362
- */
363
- export async function searchKnowledgeResources(
364
- query: string,
365
- options?: {
366
- category?: 'stacks' | 'guides' | 'universal' | 'data';
367
- limit?: number;
368
- }
369
- ): Promise<KnowledgeResource[]> {
370
- const allResources = await getAllKnowledgeResources();
371
- const queryLower = query.toLowerCase();
372
-
373
- let filtered = allResources.filter(
374
- (resource) =>
375
- resource.title.toLowerCase().includes(queryLower) ||
376
- resource.description.toLowerCase().includes(queryLower) ||
377
- resource.content.toLowerCase().includes(queryLower)
378
- );
379
-
380
- // 按類別過濾
381
- if (options?.category) {
382
- filtered = filtered.filter((resource) => resource.category === options.category);
383
- }
384
-
385
- // 限制結果數量
386
- if (options?.limit) {
387
- filtered = filtered.slice(0, options.limit);
388
- }
389
-
390
- return filtered;
391
- }
392
-
393
- /**
394
- * 清除知識庫緩存
395
- */
396
- export function clearKnowledgeCache(): void {
397
- knowledgeScanner.clearCache();
398
- }
399
-
400
- /**
401
- * 註冊 MCP 工具
402
- */
403
- export function registerKnowledgeTools(server: McpServer): void {
404
- // 獲取所有知識資源
405
- server.tool(
406
- 'get-all-knowledge-resources',
407
- 'Get all available knowledge resources',
408
- {
409
- category: z
410
- .enum(['stacks', 'guides', 'universal', 'data'])
411
- .optional()
412
- .describe('Filter by category'),
413
- },
414
- async ({ category }) => {
415
- try {
416
- const resources = category
417
- ? await getKnowledgeResourcesByCategory(category)
418
- : await getAllKnowledgeResources();
419
-
420
- return {
421
- content: [
422
- {
423
- type: 'text',
424
- text: JSON.stringify(
425
- {
426
- total: resources.length,
427
- resources: resources.map((r) => ({
428
- uri: r.uri,
429
- title: r.title,
430
- description: r.description,
431
- category: r.category,
432
- lastModified: r.lastModified,
433
- })),
434
- },
435
- null,
436
- 2
437
- ),
438
- },
439
- ],
440
- };
441
- } catch (error) {
442
- return {
443
- content: [
444
- {
445
- type: 'text',
446
- text: `Error: ${(error as Error).message}`,
447
- },
448
- ],
449
- };
450
- }
451
- }
452
- );
453
-
454
- // 獲取知識內容
455
- server.tool(
456
- 'get-knowledge-content',
457
- 'Get full content of a knowledge resource',
458
- {
459
- uri: z.string().describe('Knowledge resource URI'),
460
- },
461
- async ({ uri }) => {
462
- try {
463
- const content = await getKnowledgeContent(uri);
464
- return {
465
- content: [
466
- {
467
- type: 'text',
468
- text: content,
469
- },
470
- ],
471
- };
472
- } catch (error) {
473
- return {
474
- content: [
475
- {
476
- type: 'text',
477
- text: `Error: ${(error as Error).message}`,
478
- },
479
- ],
480
- };
481
- }
482
- }
483
- );
484
-
485
- // 搜索知識庫
486
- server.tool(
487
- 'search-knowledge',
488
- 'Search knowledge resources',
489
- {
490
- query: z.string().describe('Search query'),
491
- category: z
492
- .enum(['stacks', 'guides', 'universal', 'data'])
493
- .optional()
494
- .describe('Filter by category'),
495
- limit: z.number().optional().describe('Maximum results'),
496
- },
497
- async ({ query, category, limit }) => {
498
- try {
499
- const results = await searchKnowledgeResources(query, {
500
- category,
501
- limit,
502
- });
503
-
504
- return {
505
- content: [
506
- {
507
- type: 'text',
508
- text: JSON.stringify(
509
- {
510
- query,
511
- total: results.length,
512
- results: results.map((r) => ({
513
- uri: r.uri,
514
- title: r.title,
515
- description: r.description,
516
- category: r.category,
517
- })),
518
- },
519
- null,
520
- 2
521
- ),
522
- },
523
- ],
524
- };
525
- } catch (error) {
526
- return {
527
- content: [
528
- {
529
- type: 'text',
530
- text: `Error: ${(error as Error).message}`,
531
- },
532
- ],
533
- };
534
- }
535
- }
536
- );
537
- }
@@ -1,174 +0,0 @@
1
- /**
2
- * Knowledge tools
3
- * All tools for working with knowledge base, documentation, and guides
4
- */
5
-
6
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- import { z } from 'zod';
8
- import { getSearchService } from '../../services/search/unified-search-service.js';
9
- import { getKnowledgeContent } from './resources.js';
10
-
11
- /**
12
- * Register knowledge search tool
13
- */
14
- export function registerKnowledgeSearchTool(server: McpServer): void {
15
- server.registerTool(
16
- 'knowledge_search',
17
- {
18
- description: `Search knowledge base, documentation, guides, and reference materials. Use this for domain knowledge, best practices, setup instructions, and conceptual information.
19
-
20
- **IMPORTANT: Use this tool PROACTIVELY before starting work, not reactively when stuck.**
21
-
22
- This tool searches across all knowledge resources and returns the most relevant matches. Use include_content=false to reduce context usage, then use knowledge_get for specific documents.
23
-
24
- When to use this tool (BEFORE starting work):
25
- - **Before research/clarification**: Check relevant stack/universal knowledge to understand domain constraints
26
- - **Before design/architecture**: Review architecture patterns, security, and performance best practices
27
- - **Before implementation**: Consult framework-specific patterns, common pitfalls, and best practices
28
- - **Before testing/QA**: Review testing strategies, coverage requirements, and quality standards
29
- - **Before deployment**: Check deployment patterns, infrastructure, and monitoring guidance
30
-
31
- Available knowledge categories:
32
- - **stacks**: Framework-specific patterns (React, Next.js, Node.js)
33
- - **data**: Database patterns (SQL, indexing, migrations)
34
- - **guides**: Architecture guidance (SaaS, tech stack, UI/UX)
35
- - **universal**: Cross-cutting concerns (security, performance, testing, deployment)
36
-
37
- The knowledge is curated for LLM code generation - includes decision trees, common bugs, and practical patterns.
38
-
39
- **Best Practice**: Check relevant knowledge BEFORE making decisions or writing code, not after encountering issues.`,
40
- inputSchema: {
41
- query: z
42
- .string()
43
- .describe('Search query - use natural language, technology names, or topic keywords'),
44
- limit: z
45
- .number()
46
- .default(10)
47
- .optional()
48
- .describe('Maximum number of results to return (default: 10)'),
49
- include_content: z
50
- .boolean()
51
- .default(true)
52
- .optional()
53
- .describe(
54
- 'Include full content in results (default: true). Use false to reduce context, then knowledge_get for specific docs'
55
- ),
56
- },
57
- },
58
- async ({ query, limit = 10, include_content = true }) => {
59
- try {
60
- // Use unified search service - same logic as CLI
61
- const searchService = getSearchService();
62
- await searchService.initialize();
63
-
64
- // Check knowledge base status
65
- const status = await searchService.getStatus();
66
-
67
- if (status.knowledge.isIndexing) {
68
- const progressBar =
69
- '█'.repeat(Math.floor((status.knowledge.progress || 0) / 5)) +
70
- '░'.repeat(20 - Math.floor((status.knowledge.progress || 0) / 5));
71
- return {
72
- content: [
73
- {
74
- type: 'text',
75
- text: `⏳ **Knowledge Base Indexing In Progress**\n\nThe knowledge base is currently being indexed. Please wait...\n\n**Progress:** ${status.knowledge.progress || 0}%\n\`${progressBar}\`\n\n**Status:**\n- Documents: ${status.knowledge.documentCount || 0}\n- Building search index for knowledge resources\n\n**Estimated time:** ${status.knowledge.progress && status.knowledge.progress > 0 ? 'Less than 10 seconds' : 'Starting...'}\n\n💡 **Tip:** Knowledge base indexing is very fast. Try your search again in a few seconds.`,
76
- },
77
- ],
78
- };
79
- }
80
-
81
- if (!status.knowledge.indexed) {
82
- return {
83
- content: [
84
- {
85
- type: 'text',
86
- text: '📭 **No Knowledge Documents Available**\n\nThe knowledge base appears to be empty or not properly initialized.\n\n**To fix:**\n- Check if knowledge files exist in assets/knowledge/\n- Try restarting the MCP server to trigger indexing\n- Use CLI: `sylphx search status` for diagnostics\n\n**Expected knowledge files:**\n- stacks/ (framework-specific patterns)\n- guides/ (architecture guidance)\n- universal/ (cross-cutting concerns)\n- data/ (database patterns)',
87
- },
88
- ],
89
- };
90
- }
91
-
92
- // Search knowledge base using unified service
93
- const result = await searchService.searchKnowledge(query, {
94
- limit,
95
- include_content,
96
- });
97
-
98
- // Return MCP format using unified service formatter
99
- return searchService.formatResultsForMCP(result.results, query, result.totalIndexed);
100
- } catch (error) {
101
- return {
102
- content: [
103
- {
104
- type: 'text',
105
- text: `✗ Knowledge search error: ${(error as Error).message}`,
106
- },
107
- ],
108
- };
109
- }
110
- }
111
- );
112
- }
113
-
114
- /**
115
- * Register get_knowledge tool (for retrieving specific knowledge documents)
116
- */
117
- export function registerGetKnowledgeTool(server: McpServer): void {
118
- server.registerTool(
119
- 'knowledge_get',
120
- {
121
- description: `Get knowledge resource by exact URI.
122
-
123
- **NOTE: Prefer using 'knowledge_search' with include_content=false first, then use this tool for specific documents.**
124
-
125
- This tool retrieves a specific knowledge resource when you already know its exact URI from search results.
126
-
127
- The available URIs are dynamically generated from the indexed knowledge base. Use 'knowledge_search' to discover relevant URIs first.`,
128
- inputSchema: {
129
- uri: z.string().describe('Knowledge URI to access (e.g., "knowledge://stacks/react-app")'),
130
- },
131
- },
132
- async ({ uri }) => {
133
- try {
134
- const content = await getKnowledgeContent(uri);
135
- return {
136
- content: [
137
- {
138
- type: 'text',
139
- text: content,
140
- },
141
- ],
142
- };
143
- } catch (error: unknown) {
144
- const errorMessage = error instanceof Error ? error.message : String(error);
145
-
146
- // Dynamically get available URIs
147
- const searchService = getSearchService();
148
- const availableURIs = await searchService.getAvailableKnowledgeURIs();
149
- const uriList =
150
- availableURIs.length > 0
151
- ? availableURIs.map((uri) => `• ${uri}`).join('\n')
152
- : 'No knowledge documents available';
153
-
154
- return {
155
- content: [
156
- {
157
- type: 'text',
158
- text: `✗ Error: ${errorMessage}\n\nAvailable knowledge URIs:\n${uriList}`,
159
- },
160
- ],
161
- isError: true,
162
- };
163
- }
164
- }
165
- );
166
- }
167
-
168
- /**
169
- * Register all knowledge tools
170
- */
171
- export function registerKnowledgeTools(server: McpServer): void {
172
- registerKnowledgeSearchTool(server);
173
- registerGetKnowledgeTool(server);
174
- }