@znt/mcp 1.0.6 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/index.js +50 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,7 +47,7 @@
47
47
  * `role` *(string)*: Фильтр по архитектурной роли (`"controller"`, `"service"`, `"repository"`, `"model"`).
48
48
  * `type` *(string)*: Фильтр по типу узла AST (`"function"`, `"struct"`, `"class"`, `"interface"`).
49
49
  * `file_pattern` *(string)*: Маска пути файла (например `"pkg/semantic/*"` или `"*.go"`).
50
- * `hybrid` *(boolean)*: Использовать гибридный RRF поиск (BM25 + Векторы, по умолчанию `true`).
50
+ * `mode` *(string)*: Режим поиска: `"hybrid"` (RRF гибридный), `"lexical"` (точный BM25/FTS5), `"vector"` (чисто векторный). По умолчанию `"hybrid"`.
51
51
 
52
52
  ---
53
53
 
package/index.js CHANGED
@@ -293,14 +293,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
293
293
  properties: {
294
294
  query: { type: 'string', description: 'Поисковый запрос на естественном языке (например: "обработка HTTP запросов в сервере")' },
295
295
  limit: { type: 'number', description: 'Максимальное количество результатов поиска (по умолчанию 10)' },
296
- callers_level: { type: 'number', description: 'Глубина вложенности графа входящих вызовов ("где вызывается"), по умолчанию 3' },
297
- callees_level: { type: 'number', description: 'Глубина вложенности графа исходящих вызовов ("где вызывают"), по умолчанию 3' },
296
+ callers_level: { type: 'number', description: 'Глубина вложенности графа входящих вызовов ("где вызывается"), по умолчанию 0' },
297
+ callees_level: { type: 'number', description: 'Глубина вложенности графа исходящих вызовов ("где вызывают"), по умолчанию 0' },
298
+ compact: { type: 'boolean', description: 'Возвращать только компактный DTO {name, file, start_line, end_line, summary, role, type, score} без графа связей' },
298
299
  include_code: { type: 'boolean', description: 'Включать ли исходный код (фрагмент узла) в результаты поиска' },
299
300
  max_code_lines: { type: 'number', description: 'Максимальное количество строк исходного кода (по умолчанию 30)' },
300
301
  role: { type: 'string', description: 'Фильтр по архитектурной роли компонента (например: "controller", "repository", "service", "model")' },
301
302
  type: { type: 'string', description: 'Фильтр по типу узла AST (например: "function", "struct", "class", "interface")' },
302
303
  file_pattern: { type: 'string', description: 'Шаблон/маска пути файла для фильтрации (например: "pkg/semantic/*" или "*.go")' },
303
- hybrid: { type: 'boolean', description: 'Использовать гибридный поиск (RRF: BM25/Лексика + Векторы, по умолчанию true)' }
304
+ mode: { type: 'string', description: 'Режим поиска: "hybrid" (RRF гибридный), "lexical" (точный BM25/FTS5), "vector" (чисто векторный). По умолчанию "hybrid"' }
304
305
  },
305
306
  required: ['query']
306
307
  },
@@ -346,7 +347,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
346
347
  type: 'object',
347
348
  properties: {
348
349
  path: { type: 'string', description: 'Относительный или абсолютный путь к целевому файлу (например: "internal/engine/server.go")' },
349
- include_code: { type: 'boolean', description: 'Включать ли фрагменты/сигнатуры исходного кода для каждого символа' }
350
+ include_code: { type: 'boolean', description: 'Включать ли фрагменты/сигнатуры исходного кода для каждого символа' },
351
+ format: { type: 'string', description: 'Формат ответа: "text" (сверхкомпактный атлас строк, экономит до 75% токенов) или "json"' }
350
352
  },
351
353
  required: ['path']
352
354
  },
@@ -409,9 +411,7 @@ async function callZntApi(endpoint, method = 'GET', body = null) {
409
411
  throw new Error(data.content);
410
412
  }
411
413
  } catch (err) {
412
- if (!err.message.includes('JSON')) {
413
- throw err;
414
- }
414
+ // Ignore parse errors
415
415
  }
416
416
  }
417
417
  }
@@ -419,6 +419,11 @@ async function callZntApi(endpoint, method = 'GET', body = null) {
419
419
  return { content: text };
420
420
  }
421
421
 
422
+ const contentType = response.headers.get('content-type') || '';
423
+ if (contentType.includes('text/plain')) {
424
+ return response.text();
425
+ }
426
+
422
427
  return response.json();
423
428
  }
424
429
 
@@ -496,14 +501,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
496
501
  break;
497
502
  }
498
503
  const limit = Math.min(Math.max(1, parseInt(request.params.arguments?.limit, 10) || 10), 100);
499
- const callersLevel = Math.min(Math.max(0, parseInt(request.params.arguments?.callers_level ?? request.params.arguments?.caller_level, 10) ?? 3), 10);
500
- const calleesLevel = Math.min(Math.max(0, parseInt(request.params.arguments?.callees_level ?? request.params.arguments?.callee_level, 10) ?? 3), 10);
504
+ const callersLevelArg = request.params.arguments?.callers_level ?? request.params.arguments?.caller_level;
505
+ const callersLevel = Math.min(Math.max(0, callersLevelArg !== undefined ? (parseInt(callersLevelArg, 10) || 0) : 0), 10);
506
+ const calleesLevelArg = request.params.arguments?.callees_level ?? request.params.arguments?.callee_level;
507
+ const calleesLevel = Math.min(Math.max(0, calleesLevelArg !== undefined ? (parseInt(calleesLevelArg, 10) || 0) : 0), 10);
508
+ const compact = Boolean(request.params.arguments?.compact);
501
509
  const includeCode = Boolean(request.params.arguments?.include_code);
502
510
  const maxCodeLines = parseInt(request.params.arguments?.max_code_lines, 10) || 30;
503
511
  const role = request.params.arguments?.role || '';
504
512
  const nodeType = request.params.arguments?.type || '';
505
513
  const filePattern = request.params.arguments?.file_pattern || '';
506
- const hybrid = request.params.arguments?.hybrid !== false;
514
+ const mode = request.params.arguments?.mode || (request.params.arguments?.hybrid === false ? 'lexical' : 'hybrid');
507
515
 
508
516
  const apiParams = new URLSearchParams({
509
517
  q: query,
@@ -512,8 +520,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
512
520
  callees_level: calleesLevel.toString(),
513
521
  include_code: includeCode.toString(),
514
522
  max_code_lines: maxCodeLines.toString(),
515
- hybrid: hybrid.toString(),
523
+ mode: mode,
524
+ format: 'text'
516
525
  });
526
+ if (compact) apiParams.set('compact', 'true');
517
527
  if (role) apiParams.set('role', role);
518
528
  if (nodeType) apiParams.set('type', nodeType);
519
529
  if (filePattern) apiParams.set('file_pattern', filePattern);
@@ -521,16 +531,35 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
521
531
  const rawResults = await callZntApi(`/api/search?${apiParams.toString()}`);
522
532
  let processed = Array.isArray(rawResults) ? rawResults.map(item => {
523
533
  const relFile = toRelativePath(item.file || item.file_path || item.filePath);
534
+ let codeSnippet = item.code;
535
+ if (includeCode && !codeSnippet && relFile && item.start_line && item.end_line) {
536
+ let endLine = item.end_line;
537
+ if (maxCodeLines > 0 && (endLine - item.start_line + 1) > maxCodeLines) {
538
+ endLine = item.start_line + maxCodeLines - 1;
539
+ }
540
+ codeSnippet = getSourceSnippet(relFile, item.start_line, endLine);
541
+ }
542
+
543
+ if (compact) {
544
+ return {
545
+ name: item.name,
546
+ file: relFile,
547
+ start_line: item.start_line,
548
+ end_line: item.end_line,
549
+ summary: item.summary,
550
+ role: item.role,
551
+ type: item.type,
552
+ score: item.score,
553
+ ...(codeSnippet ? { code: codeSnippet } : {})
554
+ };
555
+ }
556
+
524
557
  const resItem = {
525
558
  ...item,
526
559
  file: relFile
527
560
  };
528
- if (includeCode && !resItem.code && resItem.file && resItem.start_line && resItem.end_line) {
529
- let endLine = resItem.end_line;
530
- if (maxCodeLines > 0 && (endLine - resItem.start_line + 1) > maxCodeLines) {
531
- endLine = resItem.start_line + maxCodeLines - 1;
532
- }
533
- resItem.code = getSourceSnippet(resItem.file, resItem.start_line, endLine);
561
+ if (codeSnippet) {
562
+ resItem.code = codeSnippet;
534
563
  }
535
564
  return resItem;
536
565
  }) : rawResults;
@@ -571,11 +600,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
571
600
  break;
572
601
  }
573
602
  const includeCode = Boolean(request.params.arguments?.include_code);
603
+ const format = request.params.arguments?.format || '';
574
604
 
575
605
  const queryParams = new URLSearchParams({
576
606
  path: filePath,
577
607
  include_code: includeCode.toString(),
578
608
  });
609
+ if (format) {
610
+ queryParams.set('format', format);
611
+ }
579
612
 
580
613
  const resData = await callZntApi(`/api/file/outline?${queryParams.toString()}`);
581
614
  result = resData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znt/mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Model Context Protocol adapter for Znt",
5
5
  "main": "index.js",
6
6
  "type": "module",