codevault 1.6.1 → 1.7.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 (123) hide show
  1. package/dist/chunking/token-counter.d.ts.map +1 -1
  2. package/dist/chunking/token-counter.js +2 -31
  3. package/dist/chunking/token-counter.js.map +1 -1
  4. package/dist/cli.js +3 -5
  5. package/dist/cli.js.map +1 -1
  6. package/dist/codemap/io.d.ts +1 -0
  7. package/dist/codemap/io.d.ts.map +1 -1
  8. package/dist/codemap/io.js +16 -0
  9. package/dist/codemap/io.js.map +1 -1
  10. package/dist/config/apply-env.d.ts +8 -0
  11. package/dist/config/apply-env.d.ts.map +1 -1
  12. package/dist/config/apply-env.js +54 -56
  13. package/dist/config/apply-env.js.map +1 -1
  14. package/dist/config/resolver.d.ts +30 -0
  15. package/dist/config/resolver.d.ts.map +1 -0
  16. package/dist/config/resolver.js +29 -0
  17. package/dist/config/resolver.js.map +1 -0
  18. package/dist/core/IndexerEngine.d.ts +24 -0
  19. package/dist/core/IndexerEngine.d.ts.map +1 -0
  20. package/dist/core/IndexerEngine.js +372 -0
  21. package/dist/core/IndexerEngine.js.map +1 -0
  22. package/dist/core/SearchService.d.ts +25 -0
  23. package/dist/core/SearchService.d.ts.map +1 -0
  24. package/dist/core/SearchService.js +455 -0
  25. package/dist/core/SearchService.js.map +1 -0
  26. package/dist/core/batch-indexer.d.ts.map +1 -1
  27. package/dist/core/batch-indexer.js +7 -4
  28. package/dist/core/batch-indexer.js.map +1 -1
  29. package/dist/core/indexer.d.ts +1 -1
  30. package/dist/core/indexer.d.ts.map +1 -1
  31. package/dist/core/indexer.js +4 -598
  32. package/dist/core/indexer.js.map +1 -1
  33. package/dist/core/indexing/chunk-pipeline.d.ts +39 -0
  34. package/dist/core/indexing/chunk-pipeline.d.ts.map +1 -0
  35. package/dist/core/indexing/chunk-pipeline.js +210 -0
  36. package/dist/core/indexing/chunk-pipeline.js.map +1 -0
  37. package/dist/core/indexing/file-scanner.d.ts +11 -0
  38. package/dist/core/indexing/file-scanner.d.ts.map +1 -0
  39. package/dist/core/indexing/file-scanner.js +49 -0
  40. package/dist/core/indexing/file-scanner.js.map +1 -0
  41. package/dist/core/search.d.ts +1 -1
  42. package/dist/core/search.d.ts.map +1 -1
  43. package/dist/core/search.js +11 -540
  44. package/dist/core/search.js.map +1 -1
  45. package/dist/database/db.d.ts +2 -3
  46. package/dist/database/db.d.ts.map +1 -1
  47. package/dist/database/db.js +0 -40
  48. package/dist/database/db.js.map +1 -1
  49. package/dist/indexer/watch.d.ts.map +1 -1
  50. package/dist/indexer/watch.js +3 -1
  51. package/dist/indexer/watch.js.map +1 -1
  52. package/dist/mcp/handlers/context.d.ts +15 -0
  53. package/dist/mcp/handlers/context.d.ts.map +1 -0
  54. package/dist/mcp/handlers/context.js +31 -0
  55. package/dist/mcp/handlers/context.js.map +1 -0
  56. package/dist/mcp/handlers/index.d.ts +5 -0
  57. package/dist/mcp/handlers/index.d.ts.map +1 -0
  58. package/dist/mcp/handlers/index.js +5 -0
  59. package/dist/mcp/handlers/index.js.map +1 -0
  60. package/dist/mcp/handlers/project.d.ts +41 -0
  61. package/dist/mcp/handlers/project.d.ts.map +1 -0
  62. package/dist/mcp/handlers/project.js +76 -0
  63. package/dist/mcp/handlers/project.js.map +1 -0
  64. package/dist/mcp/handlers/search.d.ts +27 -0
  65. package/dist/mcp/handlers/search.d.ts.map +1 -0
  66. package/dist/mcp/handlers/search.js +108 -0
  67. package/dist/mcp/handlers/search.js.map +1 -0
  68. package/dist/mcp/handlers/synthesis.d.ts +15 -0
  69. package/dist/mcp/handlers/synthesis.d.ts.map +1 -0
  70. package/dist/mcp/handlers/synthesis.js +58 -0
  71. package/dist/mcp/handlers/synthesis.js.map +1 -0
  72. package/dist/mcp-server.d.ts +10 -0
  73. package/dist/mcp-server.d.ts.map +1 -1
  74. package/dist/mcp-server.js +223 -471
  75. package/dist/mcp-server.js.map +1 -1
  76. package/dist/providers/chat-llm.d.ts +7 -2
  77. package/dist/providers/chat-llm.d.ts.map +1 -1
  78. package/dist/providers/chat-llm.js +23 -10
  79. package/dist/providers/chat-llm.js.map +1 -1
  80. package/dist/providers/index.d.ts +2 -1
  81. package/dist/providers/index.d.ts.map +1 -1
  82. package/dist/providers/index.js +2 -2
  83. package/dist/providers/index.js.map +1 -1
  84. package/dist/providers/openai.d.ts +5 -1
  85. package/dist/providers/openai.d.ts.map +1 -1
  86. package/dist/providers/openai.js +16 -6
  87. package/dist/providers/openai.js.map +1 -1
  88. package/dist/ranking/api-reranker.d.ts.map +1 -1
  89. package/dist/ranking/api-reranker.js +4 -1
  90. package/dist/ranking/api-reranker.js.map +1 -1
  91. package/dist/ranking/symbol-boost.js +2 -2
  92. package/dist/ranking/symbol-boost.js.map +1 -1
  93. package/dist/synthesis/conversational-synthesizer.d.ts.map +1 -1
  94. package/dist/synthesis/conversational-synthesizer.js +5 -2
  95. package/dist/synthesis/conversational-synthesizer.js.map +1 -1
  96. package/dist/synthesis/synthesizer.d.ts.map +1 -1
  97. package/dist/synthesis/synthesizer.js +5 -2
  98. package/dist/synthesis/synthesizer.js.map +1 -1
  99. package/dist/tests/rate-limiter.test.d.ts +2 -0
  100. package/dist/tests/rate-limiter.test.d.ts.map +1 -0
  101. package/dist/tests/rate-limiter.test.js +11 -0
  102. package/dist/tests/rate-limiter.test.js.map +1 -0
  103. package/dist/tests/search-normalization.test.d.ts +2 -0
  104. package/dist/tests/search-normalization.test.d.ts.map +1 -0
  105. package/dist/tests/search-normalization.test.js +9 -0
  106. package/dist/tests/search-normalization.test.js.map +1 -0
  107. package/dist/tests/semantic-chunker.test.d.ts +2 -0
  108. package/dist/tests/semantic-chunker.test.d.ts.map +1 -0
  109. package/dist/tests/semantic-chunker.test.js +48 -0
  110. package/dist/tests/semantic-chunker.test.js.map +1 -0
  111. package/dist/tests/simple-lru.test.d.ts +2 -0
  112. package/dist/tests/simple-lru.test.d.ts.map +1 -0
  113. package/dist/tests/simple-lru.test.js +21 -0
  114. package/dist/tests/simple-lru.test.js.map +1 -0
  115. package/dist/tests/symbol-boost.test.d.ts +2 -0
  116. package/dist/tests/symbol-boost.test.d.ts.map +1 -0
  117. package/dist/tests/symbol-boost.test.js +21 -0
  118. package/dist/tests/symbol-boost.test.js.map +1 -0
  119. package/dist/utils/simple-lru.d.ts +10 -0
  120. package/dist/utils/simple-lru.d.ts.map +1 -0
  121. package/dist/utils/simple-lru.js +38 -0
  122. package/dist/utils/simple-lru.js.map +1 -0
  123. package/package.json +3 -2
@@ -6,506 +6,258 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { fileURLToPath } from 'url';
9
- import { searchCode, getChunk, getOverview } from './core/search.js';
10
- import { indexProject } from './core/indexer.js';
11
- import { resolveScopeWithPack } from './context/packs.js';
12
- import { synthesizeAnswer } from './synthesis/synthesizer.js';
13
- import { formatSynthesisResult, formatErrorMessage, formatNoResultsMessage } from './synthesis/markdown-formatter.js';
14
- import { MAX_CHUNK_SIZE } from './config/constants.js';
15
- import { resolveProjectRoot } from './utils/path-helpers.js';
9
+ import * as handlers from './mcp/handlers/index.js';
10
+ import { SearchCodeArgsSchema, SearchCodeWithChunksArgsSchema, GetCodeChunkArgsSchema, IndexProjectArgsSchema, UpdateProjectArgsSchema, GetProjectStatsArgsSchema, UseContextPackArgsSchema, AskCodebaseArgsSchema } from './mcp/schemas.js';
11
+ import { CACHE_CONSTANTS } from './config/constants.js';
12
+ import { clearSearchCaches } from './core/search.js';
13
+ import { clearTokenCache } from './chunking/token-counter.js';
14
+ import { logger } from './utils/logger.js';
16
15
  const __filename = fileURLToPath(import.meta.url);
17
16
  const __dirname = path.dirname(__filename);
18
17
  const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
19
- // Note: working path is resolved per request via resolveProjectRoot()
20
- let sessionContextPack = null;
21
- // FIX: Add periodic cache clearing to prevent memory leaks in long-running MCP server
22
- const CACHE_CLEAR_INTERVAL_MS = Number.parseInt(process.env.CODEVAULT_CACHE_CLEAR_INTERVAL || '3600000', 10); // Default: 1 hour
23
- let cacheCleanupTimer = null;
24
- async function scheduleCacheCleanup() {
25
- if (cacheCleanupTimer) {
26
- clearInterval(cacheCleanupTimer);
27
- }
28
- cacheCleanupTimer = setInterval(async () => {
29
- try {
30
- // Clear search caches
31
- const searchModule = await import('./core/search.js').catch(() => ({ clearSearchCaches: undefined }));
32
- if (typeof searchModule.clearSearchCaches === 'function') {
33
- searchModule.clearSearchCaches();
34
- }
35
- // Clear token counter cache
36
- const tokenModule = await import('./chunking/token-counter.js').catch(() => ({ clearTokenCache: undefined }));
37
- if (typeof tokenModule.clearTokenCache === 'function') {
38
- tokenModule.clearTokenCache();
39
- }
40
- console.error(JSON.stringify({
41
- event: 'cache_cleared',
42
- timestamp: new Date().toISOString()
43
- }));
44
- }
45
- catch (error) {
46
- // Ignore errors during cleanup
47
- }
48
- }, CACHE_CLEAR_INTERVAL_MS);
49
- }
50
- const server = new Server({
51
- name: 'codevault-code-memory',
52
- version: packageJson.version,
53
- }, {
54
- capabilities: {
55
- tools: {},
56
- },
57
- });
58
- // List all available tools
59
- server.setRequestHandler(ListToolsRequestSchema, async () => {
60
- const tools = [
61
- {
62
- name: 'search_code',
63
- description: 'Search code semantically using embeddings',
64
- inputSchema: {
65
- type: 'object',
66
- properties: {
67
- query: { type: 'string', description: 'Search query' },
68
- limit: { type: 'number', description: 'Max results (default: 50, max: 200)', default: 50 },
69
- provider: { type: 'string', description: 'Embedding provider (auto|openai)', default: 'auto' },
70
- path: { type: 'string', description: 'Project root directory', default: '.' },
71
- path_glob: { type: ['string', 'array'], description: 'File patterns to filter' },
72
- tags: { type: ['string', 'array'], description: 'Tags to filter' },
73
- lang: { type: ['string', 'array'], description: 'Languages to filter' },
74
- reranker: { type: 'string', enum: ['off', 'api'], default: 'off' },
75
- hybrid: { type: 'string', enum: ['on', 'off'], default: 'on' },
76
- bm25: { type: 'string', enum: ['on', 'off'], default: 'on' },
77
- symbol_boost: { type: 'string', enum: ['on', 'off'], default: 'on' },
78
- },
79
- required: ['query'],
18
+ export class McpServer {
19
+ server;
20
+ sessionContextPack = null;
21
+ cacheCleanupTimer = null;
22
+ constructor() {
23
+ this.server = new Server({
24
+ name: 'codevault-code-memory',
25
+ version: packageJson.version,
26
+ }, {
27
+ capabilities: {
28
+ tools: {},
80
29
  },
81
- },
82
- {
83
- name: 'search_code_with_chunks',
84
- description: 'Search code and return full code chunks',
85
- inputSchema: {
86
- type: 'object',
87
- properties: {
88
- query: { type: 'string', description: 'Search query' },
89
- limit: { type: 'number', description: 'Max results', default: 10 },
90
- provider: { type: 'string', default: 'auto' },
91
- path: { type: 'string', default: '.' },
92
- path_glob: { type: ['string', 'array'] },
93
- tags: { type: ['string', 'array'] },
94
- lang: { type: ['string', 'array'] },
95
- reranker: { type: 'string', enum: ['off', 'api'], default: 'off' },
96
- hybrid: { type: 'string', enum: ['on', 'off'], default: 'on' },
97
- bm25: { type: 'string', enum: ['on', 'off'], default: 'on' },
98
- symbol_boost: { type: 'string', enum: ['on', 'off'], default: 'on' },
30
+ });
31
+ this.setupHandlers();
32
+ }
33
+ setupHandlers() {
34
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
35
+ const tools = [
36
+ {
37
+ name: 'search_code',
38
+ description: 'Search code semantically using embeddings',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ query: { type: 'string', description: 'Search query' },
43
+ limit: { type: 'number', description: 'Max results (default: 50, max: 200)', default: 50 },
44
+ provider: { type: 'string', description: 'Embedding provider (auto|openai)', default: 'auto' },
45
+ path: { type: 'string', description: 'Project root directory', default: '.' },
46
+ path_glob: { type: ['string', 'array'], description: 'File patterns to filter' },
47
+ tags: { type: ['string', 'array'], description: 'Tags to filter' },
48
+ lang: { type: ['string', 'array'], description: 'Languages to filter' },
49
+ reranker: { type: 'string', enum: ['off', 'api'], default: 'off' },
50
+ hybrid: { type: 'string', enum: ['on', 'off'], default: 'on' },
51
+ bm25: { type: 'string', enum: ['on', 'off'], default: 'on' },
52
+ symbol_boost: { type: 'string', enum: ['on', 'off'], default: 'on' },
53
+ },
54
+ required: ['query'],
55
+ },
99
56
  },
100
- required: ['query'],
101
- },
102
- },
103
- {
104
- name: 'get_code_chunk',
105
- description: 'Get code chunk by SHA',
106
- inputSchema: {
107
- type: 'object',
108
- properties: {
109
- sha: { type: 'string', description: 'SHA of code chunk' },
110
- path: { type: 'string', description: 'Project root directory', default: '.' },
57
+ {
58
+ name: 'search_code_with_chunks',
59
+ description: 'Search code and return full code chunks',
60
+ inputSchema: {
61
+ type: 'object',
62
+ properties: {
63
+ query: { type: 'string', description: 'Search query' },
64
+ limit: { type: 'number', description: 'Max results', default: 10 },
65
+ provider: { type: 'string', default: 'auto' },
66
+ path: { type: 'string', default: '.' },
67
+ path_glob: { type: ['string', 'array'] },
68
+ tags: { type: ['string', 'array'] },
69
+ lang: { type: ['string', 'array'] },
70
+ reranker: { type: 'string', enum: ['off', 'api'], default: 'off' },
71
+ hybrid: { type: 'string', enum: ['on', 'off'], default: 'on' },
72
+ bm25: { type: 'string', enum: ['on', 'off'], default: 'on' },
73
+ symbol_boost: { type: 'string', enum: ['on', 'off'], default: 'on' },
74
+ },
75
+ required: ['query'],
76
+ },
111
77
  },
112
- required: ['sha'],
113
- },
114
- },
115
- {
116
- name: 'index_project',
117
- description: 'Index a project for semantic search',
118
- inputSchema: {
119
- type: 'object',
120
- properties: {
121
- path: { type: 'string', description: 'Project root directory', default: '.' },
122
- provider: { type: 'string', description: 'Embedding provider', default: 'auto' },
78
+ {
79
+ name: 'get_code_chunk',
80
+ description: 'Get code chunk by SHA',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: {
84
+ sha: { type: 'string', description: 'SHA of code chunk' },
85
+ path: { type: 'string', description: 'Project root directory', default: '.' },
86
+ },
87
+ required: ['sha'],
88
+ },
123
89
  },
124
- },
125
- },
126
- {
127
- name: 'update_project',
128
- description: 'Update project index',
129
- inputSchema: {
130
- type: 'object',
131
- properties: {
132
- path: { type: 'string', description: 'Project root directory', default: '.' },
133
- provider: { type: 'string', default: 'auto' },
90
+ {
91
+ name: 'index_project',
92
+ description: 'Index a project for semantic search',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ path: { type: 'string', description: 'Project root directory', default: '.' },
97
+ provider: { type: 'string', description: 'Embedding provider', default: 'auto' },
98
+ },
99
+ },
134
100
  },
135
- },
136
- },
137
- {
138
- name: 'get_project_stats',
139
- description: 'Get project statistics',
140
- inputSchema: {
141
- type: 'object',
142
- properties: {
143
- path: { type: 'string', description: 'Project root directory', default: '.' },
101
+ {
102
+ name: 'update_project',
103
+ description: 'Update project index',
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {
107
+ path: { type: 'string', description: 'Project root directory', default: '.' },
108
+ provider: { type: 'string', default: 'auto' },
109
+ },
110
+ },
144
111
  },
145
- },
146
- },
147
- {
148
- name: 'use_context_pack',
149
- description: 'Activate a context pack for scoped search',
150
- inputSchema: {
151
- type: 'object',
152
- properties: {
153
- name: { type: 'string', description: 'Context pack name or "clear"' },
154
- path: { type: 'string', description: 'Project root directory', default: '.' },
112
+ {
113
+ name: 'get_project_stats',
114
+ description: 'Get project statistics',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ path: { type: 'string', description: 'Project root directory', default: '.' },
119
+ },
120
+ },
155
121
  },
156
- required: ['name'],
157
- },
158
- },
159
- {
160
- name: 'ask_codebase',
161
- description: 'Ask a question and get LLM-synthesized answer with code citations',
162
- inputSchema: {
163
- type: 'object',
164
- properties: {
165
- question: { type: 'string', description: 'Natural language question about the codebase' },
166
- provider: { type: 'string', description: 'Embedding provider (auto|openai)', default: 'auto' },
167
- chat_provider: { type: 'string', description: 'Chat LLM provider (auto|openai)', default: 'auto' },
168
- path: { type: 'string', description: 'Project root directory', default: '.' },
169
- max_chunks: { type: 'number', description: 'Max code chunks to analyze', default: 10 },
170
- path_glob: { type: ['string', 'array'], description: 'File patterns to filter' },
171
- tags: { type: ['string', 'array'], description: 'Tags to filter' },
172
- lang: { type: ['string', 'array'], description: 'Languages to filter' },
173
- reranker: { type: 'string', enum: ['on', 'off'], default: 'on', description: 'Use API reranking' },
174
- multi_query: { type: 'boolean', default: false, description: 'Break complex questions into sub-queries' },
175
- temperature: { type: 'number', minimum: 0, maximum: 2, default: 0.7, description: 'LLM temperature' },
122
+ {
123
+ name: 'use_context_pack',
124
+ description: 'Activate a context pack for scoped search',
125
+ inputSchema: {
126
+ type: 'object',
127
+ properties: {
128
+ name: { type: 'string', description: 'Context pack name or "clear"' },
129
+ path: { type: 'string', description: 'Project root directory', default: '.' },
130
+ },
131
+ required: ['name'],
132
+ },
176
133
  },
177
- required: ['question'],
178
- },
179
- },
180
- ];
181
- return { tools };
182
- });
183
- // Handle tool calls
184
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
185
- const { name, arguments: args } = request.params;
186
- const typedArgs = args; // MCP SDK doesn't provide strict typing for arguments
187
- try {
188
- switch (name) {
189
- case 'search_code': {
190
- const cleanPath = resolveProjectRoot(typedArgs);
191
- const { scope: scopeFilters } = resolveScopeWithPack({
192
- path_glob: typedArgs.path_glob,
193
- tags: typedArgs.tags,
194
- lang: typedArgs.lang,
195
- reranker: typedArgs.reranker,
196
- hybrid: typedArgs.hybrid,
197
- bm25: typedArgs.bm25,
198
- symbol_boost: typedArgs.symbol_boost,
199
- }, { basePath: cleanPath, sessionPack: sessionContextPack });
200
- const results = await searchCode(typedArgs.query, typedArgs.limit || 50, typedArgs.provider || 'auto', cleanPath, scopeFilters);
201
- if (!results.success) {
202
- return {
203
- content: [
204
- {
205
- type: 'text',
206
- text: results.error === 'database_not_found'
207
- ? `📋 Project not indexed!\n\n🔍 Database not found: ${cleanPath}/.codevault/codevault.db\n\n💡 Use index_project tool`
208
- : `No results: ${results.message}\n${results.suggestion || ''}`,
209
- },
210
- ],
211
- };
212
- }
213
- const resultText = results.results
214
- .map((result, index) => `${index + 1}. ${result.path}\n Symbol: ${result.meta.symbol} (${result.lang})\n Similarity: ${result.meta.score}\n SHA: ${result.sha}`)
215
- .join('\n\n');
216
- return {
217
- content: [
218
- {
219
- type: 'text',
220
- text: `Found ${results.results.length} results for: "${typedArgs.query}"\nProvider: ${results.provider}\n\n${resultText}`,
134
+ {
135
+ name: 'ask_codebase',
136
+ description: 'Ask a question and get LLM-synthesized answer with code citations',
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {
140
+ question: { type: 'string', description: 'Natural language question about the codebase' },
141
+ provider: { type: 'string', description: 'Embedding provider (auto|openai)', default: 'auto' },
142
+ chat_provider: { type: 'string', description: 'Chat LLM provider (auto|openai)', default: 'auto' },
143
+ path: { type: 'string', description: 'Project root directory', default: '.' },
144
+ max_chunks: { type: 'number', description: 'Max code chunks to analyze', default: 10 },
145
+ path_glob: { type: ['string', 'array'], description: 'File patterns to filter' },
146
+ tags: { type: ['string', 'array'], description: 'Tags to filter' },
147
+ lang: { type: ['string', 'array'], description: 'Languages to filter' },
148
+ reranker: { type: 'string', enum: ['on', 'off'], default: 'on', description: 'Use API reranking' },
149
+ multi_query: { type: 'boolean', default: false, description: 'Break complex questions into sub-queries' },
150
+ temperature: { type: 'number', minimum: 0, maximum: 2, default: 0.7, description: 'LLM temperature' },
221
151
  },
222
- ],
223
- };
224
- }
225
- case 'search_code_with_chunks': {
226
- const cleanPath = resolveProjectRoot(typedArgs);
227
- const { scope: scopeFilters } = resolveScopeWithPack({
228
- path_glob: typedArgs.path_glob,
229
- tags: typedArgs.tags,
230
- lang: typedArgs.lang,
231
- reranker: typedArgs.reranker,
232
- hybrid: typedArgs.hybrid,
233
- bm25: typedArgs.bm25,
234
- symbol_boost: typedArgs.symbol_boost,
235
- }, { basePath: cleanPath, sessionPack: sessionContextPack });
236
- const searchResults = await searchCode(typedArgs.query, typedArgs.limit || 10, typedArgs.provider || 'auto', cleanPath, scopeFilters);
237
- if (!searchResults.success) {
238
- return {
239
- content: [{ type: 'text', text: searchResults.message || 'Search failed' }],
240
- };
241
- }
242
- const resultsWithCode = [];
243
- for (const result of searchResults.results) {
244
- const chunkResult = await getChunk(result.sha, cleanPath);
245
- let code = '';
246
- let truncated = false;
247
- if (chunkResult.success && chunkResult.code) {
248
- code = chunkResult.code;
249
- if (code.length > MAX_CHUNK_SIZE) {
250
- code = code.substring(0, MAX_CHUNK_SIZE);
251
- truncated = true;
252
- }
152
+ required: ['question'],
153
+ },
154
+ },
155
+ ];
156
+ return { tools };
157
+ });
158
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
159
+ const { name, arguments: args } = request.params;
160
+ const rawArgs = args || {};
161
+ try {
162
+ switch (name) {
163
+ case 'search_code': {
164
+ const validArgs = SearchCodeArgsSchema.parse(rawArgs);
165
+ return await handlers.handleSearchCode(validArgs, this.sessionContextPack);
253
166
  }
254
- else {
255
- code = `[Error retrieving code: ${chunkResult.error}]`;
167
+ case 'search_code_with_chunks': {
168
+ const validArgs = SearchCodeWithChunksArgsSchema.parse(rawArgs);
169
+ return await handlers.handleSearchCodeWithChunks(validArgs, this.sessionContextPack);
256
170
  }
257
- resultsWithCode.push({ ...result, code, truncated });
258
- }
259
- const resultText = resultsWithCode
260
- .map((result, index) => `${index + 1}. ${result.path}\n Symbol: ${result.meta.symbol} (${result.lang})\n Similarity: ${result.meta.score}\n SHA: ${result.sha}${result.truncated ? '\n ⚠️ Code truncated' : ''}\n\n${'─'.repeat(80)}\n${result.code}\n${'─'.repeat(80)}`)
261
- .join('\n\n');
262
- return {
263
- content: [
264
- { type: 'text', text: `Found ${resultsWithCode.length} results with code\n\n${resultText}` },
265
- ],
266
- };
267
- }
268
- case 'get_code_chunk': {
269
- const cleanPath = resolveProjectRoot(typedArgs);
270
- const result = await getChunk(typedArgs.sha, cleanPath);
271
- if (!result.success) {
272
- return {
273
- content: [{ type: 'text', text: `Error: ${result.error}` }],
274
- isError: true,
275
- };
276
- }
277
- const codeText = result.code || '';
278
- if (codeText.length > MAX_CHUNK_SIZE) {
279
- return {
280
- content: [
281
- {
282
- type: 'text',
283
- text: `⚠️ CODE CHUNK TOO LARGE - TRUNCATED\n\nSHA: ${typedArgs.sha}\nFull size: ${codeText.length} characters\n\n${codeText.substring(0, MAX_CHUNK_SIZE)}\n\n[TRUNCATED]`,
284
- },
285
- ],
286
- };
287
- }
288
- return {
289
- content: [{ type: 'text', text: codeText }],
290
- };
291
- }
292
- case 'index_project': {
293
- const cleanPath = resolveProjectRoot(typedArgs);
294
- if (!fs.existsSync(cleanPath)) {
295
- throw new Error(`Directory ${cleanPath} does not exist`);
296
- }
297
- const result = await indexProject({ repoPath: cleanPath, provider: typedArgs.provider || 'auto' });
298
- if (!result.success) {
299
- return {
300
- content: [
301
- { type: 'text', text: `Indexing failed: ${result.errors[0]?.error || 'Unknown error'}` },
302
- ],
303
- isError: true,
304
- };
305
- }
306
- return {
307
- content: [
308
- {
309
- type: 'text',
310
- text: `✅ Project indexed successfully!\n\n📊 Statistics:\n- Processed chunks: ${result.processedChunks}\n- Total chunks: ${result.totalChunks}\n- Provider: ${result.provider}\n\n🔍 Ready to search!\n- Quick search: search_code with path="${cleanPath}"\n- With code: search_code_with_chunks with path="${cleanPath}"`,
311
- },
312
- ],
313
- };
314
- }
315
- case 'update_project': {
316
- const cleanPath = resolveProjectRoot(typedArgs);
317
- const result = await indexProject({ repoPath: cleanPath, provider: typedArgs.provider || 'auto' });
318
- if (!result.success) {
319
- return {
320
- content: [
321
- { type: 'text', text: `Update failed: ${result.errors[0]?.error || 'Unknown error'}` },
322
- ],
323
- isError: true,
324
- };
171
+ case 'get_code_chunk': {
172
+ const validArgs = GetCodeChunkArgsSchema.parse(rawArgs);
173
+ return await handlers.handleGetCodeChunk(validArgs);
174
+ }
175
+ case 'index_project': {
176
+ const validArgs = IndexProjectArgsSchema.parse(rawArgs);
177
+ return await handlers.handleIndexProject(validArgs);
178
+ }
179
+ case 'update_project': {
180
+ const validArgs = UpdateProjectArgsSchema.parse(rawArgs);
181
+ return await handlers.handleUpdateProject(validArgs);
182
+ }
183
+ case 'get_project_stats': {
184
+ const validArgs = GetProjectStatsArgsSchema.parse(rawArgs);
185
+ return await handlers.handleGetProjectStats(validArgs);
186
+ }
187
+ case 'use_context_pack': {
188
+ const validArgs = UseContextPackArgsSchema.parse(rawArgs);
189
+ return await handlers.handleUseContextPack(validArgs, (pack) => {
190
+ this.sessionContextPack = pack;
191
+ });
192
+ }
193
+ case 'ask_codebase': {
194
+ const validArgs = AskCodebaseArgsSchema.parse(rawArgs);
195
+ return await handlers.handleAskCodebase(validArgs, this.sessionContextPack);
196
+ }
197
+ default:
198
+ throw new Error(`Unknown tool: ${name}`);
325
199
  }
326
- return {
327
- content: [
328
- {
329
- type: 'text',
330
- text: `🔄 Project updated!\n📊 Processed: ${result.processedChunks} chunks\n📁 Total: ${result.totalChunks} chunks`,
331
- },
332
- ],
333
- };
334
200
  }
335
- case 'get_project_stats': {
336
- const cleanPath = resolveProjectRoot(typedArgs);
337
- const overviewResult = await getOverview(50, cleanPath);
338
- if (!overviewResult.success) {
201
+ catch (error) {
202
+ if (error && typeof error === 'object' && 'issues' in error && Array.isArray(error.issues)) {
203
+ const validationError = `Validation Error: ${error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', ')}`;
339
204
  return {
340
- content: [
341
- { type: 'text', text: `Error: ${overviewResult.message || 'Failed to get stats'}` },
342
- ],
205
+ content: [{ type: 'text', text: validationError }],
343
206
  isError: true,
344
207
  };
345
208
  }
346
- if (overviewResult.results.length === 0) {
347
- return {
348
- content: [{ type: 'text', text: '📋 Project not indexed or empty' }],
349
- };
350
- }
351
- const overview = overviewResult.results
352
- .map((result) => `- ${result.path} :: ${result.meta.symbol} (${result.lang})`)
353
- .join('\n');
354
209
  return {
355
- content: [
356
- {
357
- type: 'text',
358
- text: `📊 Project overview (${overviewResult.results.length} main functions):\n\n${overview}`,
359
- },
360
- ],
210
+ content: [{ type: 'text', text: `ERROR: ${error.message}` }],
211
+ isError: true,
361
212
  };
362
213
  }
363
- case 'use_context_pack': {
364
- const cleanPath = resolveProjectRoot(typedArgs);
365
- const name = typedArgs.name;
366
- if (name === 'default' || name === 'none' || name === 'clear') {
367
- sessionContextPack = null;
368
- return {
369
- content: [{ type: 'text', text: 'Cleared active context pack for this session' }],
370
- };
371
- }
372
- try {
373
- const { loadContextPack } = await import('./context/packs.js');
374
- const pack = loadContextPack(name, cleanPath);
375
- sessionContextPack = { ...pack, basePath: cleanPath };
376
- return {
377
- content: [
378
- {
379
- type: 'text',
380
- text: `Context pack "${pack.key}" activated for session\n\nScope: ${JSON.stringify(pack.scope, null, 2)}`,
381
- },
382
- ],
383
- };
384
- }
385
- catch (error) {
386
- return {
387
- content: [{ type: 'text', text: `Error: ${error.message}` }],
388
- isError: true,
389
- };
390
- }
391
- }
392
- case 'ask_codebase': {
393
- const cleanPath = resolveProjectRoot(typedArgs);
394
- // Use resolveScopeWithPack like other search tools for consistency
395
- const { scope: scopeFilters } = resolveScopeWithPack({
396
- path_glob: typedArgs.path_glob,
397
- tags: typedArgs.tags,
398
- lang: typedArgs.lang,
399
- reranker: typedArgs.reranker,
400
- }, { basePath: cleanPath, sessionPack: sessionContextPack });
401
- try {
402
- const result = await synthesizeAnswer(typedArgs.question, {
403
- provider: typedArgs.provider || 'auto',
404
- chatProvider: typedArgs.chat_provider || 'auto',
405
- workingPath: cleanPath,
406
- scope: scopeFilters,
407
- maxChunks: typedArgs.max_chunks || 10,
408
- useReranking: typedArgs.reranker !== 'off',
409
- useMultiQuery: typedArgs.multi_query || false,
410
- temperature: typedArgs.temperature || 0.7
411
- });
412
- if (!result.success) {
413
- let errorText;
414
- if (result.error === 'no_results') {
415
- errorText = formatNoResultsMessage(result.query, result.queriesUsed);
416
- }
417
- else {
418
- errorText = formatErrorMessage(result.error || 'Unknown error', result.query);
419
- }
420
- return {
421
- content: [{ type: 'text', text: errorText }],
422
- };
423
- }
424
- const formattedResult = formatSynthesisResult(result, {
425
- includeMetadata: true,
426
- includeStats: true
427
- });
428
- return {
429
- content: [
430
- {
431
- type: 'text',
432
- text: formattedResult
433
- }
434
- ],
435
- };
436
- }
437
- catch (error) {
438
- const errorText = formatErrorMessage(error.message, typedArgs.question);
439
- return {
440
- content: [{ type: 'text', text: errorText }],
441
- isError: true,
442
- };
443
- }
444
- }
445
- default:
446
- throw new Error(`Unknown tool: ${name}`);
447
- }
214
+ });
448
215
  }
449
- catch (error) {
450
- return {
451
- content: [{ type: 'text', text: `ERROR: ${error.message}` }],
452
- isError: true,
453
- };
216
+ async start() {
217
+ const transport = new StdioServerTransport();
218
+ await this.server.connect(transport);
219
+ logger.info('CodeVault MCP Server started', { version: packageJson.version });
220
+ this.scheduleCacheCleanup();
221
+ this.setupShutdownHandlers();
454
222
  }
455
- });
456
- async function main() {
457
- const transport = new StdioServerTransport();
458
- await server.connect(transport);
459
- console.error(JSON.stringify({
460
- start: 'CodeVault MCP Server started',
461
- version: packageJson.version,
462
- }));
463
- // FIX: Start periodic cache cleanup
464
- scheduleCacheCleanup();
465
- // FIX: Add cleanup handlers for graceful shutdown
466
- const cleanup = async () => {
467
- // Stop cache cleanup timer
468
- if (cacheCleanupTimer) {
469
- clearInterval(cacheCleanupTimer);
470
- cacheCleanupTimer = null;
223
+ scheduleCacheCleanup() {
224
+ if (this.cacheCleanupTimer) {
225
+ clearInterval(this.cacheCleanupTimer);
471
226
  }
472
- // Clear session context pack
473
- sessionContextPack = null;
474
- // Clear search caches to free memory
475
- try {
476
- const searchModule = await import('./core/search.js').catch(() => ({ clearSearchCaches: undefined }));
477
- if (typeof searchModule.clearSearchCaches === 'function') {
478
- searchModule.clearSearchCaches();
227
+ this.cacheCleanupTimer = setInterval(() => {
228
+ try {
229
+ clearSearchCaches();
230
+ clearTokenCache();
231
+ logger.debug('Cache cleared periodically');
479
232
  }
480
- }
481
- catch (error) {
482
- // Ignore if module doesn't export the function yet
483
- }
484
- // Clear token counter cache
485
- try {
486
- const tokenModule = await import('./chunking/token-counter.js').catch(() => ({ clearTokenCache: undefined }));
487
- if (typeof tokenModule.clearTokenCache === 'function') {
488
- tokenModule.clearTokenCache();
233
+ catch (error) {
234
+ // Ignore errors during cleanup
489
235
  }
490
- }
491
- catch (error) {
492
- // Ignore if module doesn't export the function yet
493
- }
494
- };
495
- process.on('SIGINT', async () => {
496
- await cleanup();
497
- process.exit(0);
498
- });
499
- process.on('SIGTERM', async () => {
500
- await cleanup();
501
- process.exit(0);
502
- });
503
- process.on('exit', () => {
504
- // Note: Cannot use async in exit handler, but cleanup is best-effort
505
- });
236
+ }, CACHE_CONSTANTS.CACHE_CLEAR_INTERVAL_MS);
237
+ }
238
+ setupShutdownHandlers() {
239
+ const cleanup = async () => {
240
+ if (this.cacheCleanupTimer) {
241
+ clearInterval(this.cacheCleanupTimer);
242
+ this.cacheCleanupTimer = null;
243
+ }
244
+ this.sessionContextPack = null;
245
+ clearSearchCaches();
246
+ clearTokenCache();
247
+ };
248
+ process.on('SIGINT', async () => {
249
+ await cleanup();
250
+ process.exit(0);
251
+ });
252
+ process.on('SIGTERM', async () => {
253
+ await cleanup();
254
+ process.exit(0);
255
+ });
256
+ }
506
257
  }
507
- main().catch((error) => {
508
- console.error('Fatal error:', error);
258
+ const server = new McpServer();
259
+ server.start().catch((error) => {
260
+ logger.error('Fatal error', error);
509
261
  process.exit(1);
510
262
  });
511
263
  //# sourceMappingURL=mcp-server.js.map