@znt/mcp 1.0.3 → 1.0.5
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 +97 -16
- package/index.js +63 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# Znt MCP Adapter (@znt/mcp)
|
|
1
|
+
# Znt MCP Adapter (@znt/mcp v1.1.0)
|
|
2
2
|
|
|
3
3
|
Официальный адаптер **Model Context Protocol (MCP)** для системы локального понимания кодовых баз и графового анализа **Знаток (Znt)**.
|
|
4
4
|
|
|
5
|
-
Позволяет внешним ИИ-агентам и LLM-клиентам (Claude Desktop, Antigravity IDE, Cursor, Windsurf, Zed и др.) осуществлять
|
|
5
|
+
Позволяет внешним ИИ-агентам и LLM-клиентам (Claude Desktop, Antigravity IDE, Cursor, Windsurf, Zed, Codex и др.) осуществлять гибридный и мультифакторный поиск по смыслу, получать семантические оглавления файлов, строить графы вызовов и трассировать зависимости с прозрачным трекингом экономии контекстных токенов.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## ⚡ Быстрый старт (npx)
|
|
10
10
|
|
|
11
|
-
Не требуется предварительная сборка или ручная установка. Для подключения добавьте адаптер в ваш конфигурационный файл MCP (например, `~/.gemini/config/mcp_config.json` или `claude_desktop_config.json`):
|
|
11
|
+
Не требуется предварительная сборка или ручная установка. Для подключения добавьте адаптер в ваш конфигурационный файл MCP (например, `~/.gemini/config/mcp_config.json`, `.cursor/mcp.json` или `claude_desktop_config.json`):
|
|
12
12
|
|
|
13
13
|
```json
|
|
14
14
|
{
|
|
@@ -30,25 +30,106 @@
|
|
|
30
30
|
|
|
31
31
|
Адаптер предоставляет 6 специализированных инструментов с автоматическим отслеживанием экономии контекста:
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
### 1. `znatok_semantic_search`
|
|
34
|
+
* **Назначение**: Семантический и гибридный поиск (BM25 + Векторы + RRF) по элементам кодовой базы.
|
|
35
|
+
* **Параметры**:
|
|
36
|
+
* `query` *(string, обязательный)*: Поисковый запрос на естественном языке.
|
|
37
|
+
* `limit` *(number)*: Максимальное число результатов (по умолчанию `10`, макс `100`).
|
|
38
|
+
* `callers_level` *(number)*: Глубина графа входящих вызовов (по умолчанию `3`).
|
|
39
|
+
* `callees_level` *(number)*: Глубина графа исходящих вызовов (по умолчанию `3`).
|
|
40
|
+
* `include_code` *(boolean)*: Включать ли фрагменты исходного кода узлов.
|
|
41
|
+
* `max_code_lines` *(number)*: Максимум строк кода на узел (по умолчанию `30`).
|
|
42
|
+
* `role` *(string)*: Фильтр по архитектурной роли (`"controller"`, `"service"`, `"repository"`, `"model"`).
|
|
43
|
+
* `type` *(string)*: Фильтр по типу узла AST (`"function"`, `"struct"`, `"class"`, `"interface"`).
|
|
44
|
+
* `file_pattern` *(string)*: Маска пути файла (например `"pkg/semantic/*"` или `"*.go"`).
|
|
45
|
+
* `hybrid` *(boolean)*: Использовать гибридный RRF поиск (BM25 + Векторы, по умолчанию `true`).
|
|
41
46
|
|
|
42
47
|
---
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
### 2. `znatok_find_similar`
|
|
50
|
+
* **Назначение**: Умный мультифакторный поиск аналогов и дубликатов кода (60% векторный косинус + 30% граф вызовов Jaccard + 10% AST-роли).
|
|
51
|
+
* **Параметры**:
|
|
52
|
+
* `target` *(string)*: Имя символа, функции или класса в проекте (например `"Server.runFullScan"`). При указании вектор берется из БД напрямую **без обращения к LLM**.
|
|
53
|
+
* `query` *(string)*: Текстовое описание или код для поиска аналогов (используется, если `target` не задан).
|
|
54
|
+
* `limit` *(number)*: Максимальное число результатов (по умолчанию `10`).
|
|
55
|
+
* `callers_level` / `callees_level` *(number)*: Глубина связанных вызовов (по умолчанию `3`).
|
|
56
|
+
* `include_code` *(boolean)* / `max_code_lines` *(number)*: Параметры включения кода (по умолчанию `30` строк).
|
|
57
|
+
* `role` / `type` / `file_pattern` *(string)*: Фильтры по архитектуре и путям файлов.
|
|
45
58
|
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### 3. `znatok_get_subgraph`
|
|
62
|
+
* **Назначение**: Построение подграфа окрестностей символа/файла или трассировка вызовов от точки `from` к точке `to`.
|
|
63
|
+
* **Параметры**:
|
|
64
|
+
* `target` *(string)*: Символ или относительный путь файла для вывода его окрестностей.
|
|
65
|
+
* `from` / `to` *(string)*: Точки трассировки цепочки вызовов (DFS по графу).
|
|
66
|
+
* `depth` *(number)*: Глубина обхода подграфа (по умолчанию `2`).
|
|
67
|
+
* `max_nodes` *(number)*: Максимальное количество узлов подграфа (по умолчанию `30`).
|
|
68
|
+
* `format` *(string)*: Формат ответа (`"mermaid"` для Markdown-диаграмм или `"json"`, по умолчанию `"mermaid"`).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### 4. `znatok_file_outline`
|
|
73
|
+
* **Назначение**: Анатомический семантический атлас (оглавление) файла за 1 запрос без замусоривания контекста.
|
|
74
|
+
* **Параметры**:
|
|
75
|
+
* `path` *(string, обязательный)*: Путь к целевому файлу (например `"internal/engine/server.go"`).
|
|
76
|
+
* `include_code` *(boolean)*: Включать ли фрагменты исходного кода для каждого символа.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 5. `znatok_server_logs`
|
|
81
|
+
* **Назначение**: Чтение системных логов и событий сервера Znt в реальном времени через WebSocket.
|
|
82
|
+
* **Параметры**: Отсутствуют.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### 6. `znatok_mcp_stats`
|
|
87
|
+
* **Назначение**: Детальная метрика работы MCP-сервера и статистика сэкономленных токенов из SQLite.
|
|
88
|
+
* **Параметры**: Отсутствуют.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 📊 Структура ответа и трекинг контекста (`_meta`)
|
|
93
|
+
|
|
94
|
+
Каждый инструмент возвращает обёртку со служебным объектом `_meta`:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"_meta": {
|
|
99
|
+
"tool": "znatok_semantic_search",
|
|
100
|
+
"response_tokens": 420,
|
|
101
|
+
"execution_ms": 15,
|
|
102
|
+
"last_call_saved_tokens": 12500,
|
|
103
|
+
"session_saved_tokens": 34000,
|
|
104
|
+
"alternative_cost": {
|
|
105
|
+
"tokens": 12920,
|
|
106
|
+
"description": "объем 3 затрагиваемых файлов целиком",
|
|
107
|
+
"savings_ratio": "30.8x"
|
|
108
|
+
},
|
|
109
|
+
"data_sources": ["core_api:search"]
|
|
110
|
+
},
|
|
111
|
+
"result": [ ... ]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## ⚙️ Переменные окружения
|
|
118
|
+
|
|
119
|
+
| Переменная | Назначение | Значение по умолчанию |
|
|
120
|
+
| :--- | :--- | :--- |
|
|
121
|
+
| `ZNT_API_URL` | Базовый URL запущенного сервера Znt (HTTP & WS) | `http://localhost:8080` |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 💾 Хранение данных
|
|
126
|
+
|
|
127
|
+
Статистика вызовов и экономии токенов сохраняется локально в базе данных SQLite:
|
|
128
|
+
* Путь к БД: `.znt/mcp_usage.db` в корневом каталоге проекта.
|
|
48
129
|
|
|
49
130
|
---
|
|
50
131
|
|
|
51
132
|
## 📋 Системные требования
|
|
52
133
|
|
|
53
|
-
* **Node.js** версии **v22.5.0** или
|
|
54
|
-
*
|
|
134
|
+
* **Node.js** версии **v22.5.0** или новее (используется встроенный модуль `node:sqlite`).
|
|
135
|
+
* Запущенный локальный сервер Znt (`http://localhost:8080`).
|
package/index.js
CHANGED
|
@@ -307,11 +307,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
307
307
|
},
|
|
308
308
|
{
|
|
309
309
|
name: 'znatok_find_similar',
|
|
310
|
-
description: 'Ищет элементы кода
|
|
310
|
+
description: 'Ищет аналогичные элементы кода и дубликаты на основе комбинированного мультифакторного анализа (векторное косинусное сходство, графовые соседи Jaccard и совпадение типов AST/ролей). Позволяет передавать как имя существующего символа (target), так и фрагмент текста/кода (query).',
|
|
311
311
|
inputSchema: {
|
|
312
312
|
type: 'object',
|
|
313
313
|
properties: {
|
|
314
|
-
|
|
314
|
+
target: { type: 'string', description: 'Имя существующего символа, функции или класса в проекте для поиска аналогов (например: "Server.runFullScan"). Если указано, вектор берется напрямую из БД без запроса к LLM.' },
|
|
315
|
+
query: { type: 'string', description: 'Текстовый фрагмент или описание функции/класса для поиска аналогов (используется, если target не задан)' },
|
|
315
316
|
limit: { type: 'number', description: 'Максимальное количество возвращаемых элементов (по умолчанию 10)' },
|
|
316
317
|
callers_level: { type: 'number', description: 'Глубина вложенности входящего графа вызовов ("где вызывается"), по умолчанию 3' },
|
|
317
318
|
callees_level: { type: 'number', description: 'Глубина вложенности исходящего графа вызовов ("где вызывают"), по умолчанию 3' },
|
|
@@ -319,22 +320,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
319
320
|
max_code_lines: { type: 'number', description: 'Максимальное количество строк исходного кода (по умолчанию 30)' },
|
|
320
321
|
role: { type: 'string', description: 'Фильтр по архитектурной роли компонента (например: "controller", "repository", "service", "model")' },
|
|
321
322
|
type: { type: 'string', description: 'Фильтр по типу узла AST (например: "function", "struct", "class", "interface")' },
|
|
322
|
-
file_pattern: { type: 'string', description: 'Шаблон/маска пути файла для фильтрации (например: "pkg/semantic/*" или "*.go")' }
|
|
323
|
-
|
|
324
|
-
},
|
|
325
|
-
required: ['query']
|
|
323
|
+
file_pattern: { type: 'string', description: 'Шаблон/маска пути файла для фильтрации (например: "pkg/semantic/*" или "*.go")' }
|
|
324
|
+
}
|
|
326
325
|
},
|
|
327
326
|
},
|
|
328
327
|
{
|
|
329
328
|
name: 'znatok_get_subgraph',
|
|
330
|
-
description: 'Возвращает ориентированный подграф (в формате Mermaid или JSON) вокруг заданного символа/файла (target) или
|
|
329
|
+
description: 'Возвращает ориентированный подграф (в формате Mermaid или JSON) вокруг заданного символа/файла (target) или ищет цепочку вызовов между двумя точками (from + to). Режим target: строит окрестности символа на заданную глубину. Режим трассировки (from + to): DFS запускается от `to` вверх по входящим рёбрам и ищет пути, в которых встречается `from`. Ограничение: если from вызывает to не напрямую, а через промежуточный узел, рёбра могут не отобразиться — в таком случае edges будет пустым и вернутся два изолированных узла.',
|
|
331
330
|
inputSchema: {
|
|
332
331
|
type: 'object',
|
|
333
332
|
properties: {
|
|
334
|
-
target: { type: 'string', description: 'Имя символа, функции, класса или относительный путь файла для построения подграфа окрестностей (например: "SemanticService" или "server.go")' },
|
|
335
|
-
from: { type: 'string', description: '
|
|
336
|
-
to: { type: 'string', description: '
|
|
337
|
-
depth: { type: 'number', description: 'Глубина обхода подграфа (по умолчанию 2)' },
|
|
333
|
+
target: { type: 'string', description: 'Имя символа, функции, класса или относительный путь файла для построения подграфа окрестностей (например: "SemanticService" или "server.go"). Используется только если from/to не заданы.' },
|
|
334
|
+
from: { type: 'string', description: 'Фильтр: символ/функция, которая должна встречаться в путях вызовов, найденных от `to`. Алгоритм ищет пути где from предшествует to по цепочке. Требует совместного указания с `to`.' },
|
|
335
|
+
to: { type: 'string', description: 'Точка старта трассировки — символ/функция, от которой DFS идёт вверх по входящим рёбрам (PredecessorMap). Именно `to` является началом обхода, а не концом. Требует совместного указания с `from`.' },
|
|
336
|
+
depth: { type: 'number', description: 'Глубина обхода подграфа (по умолчанию 2). В режиме трассировки используется как depth*2.' },
|
|
338
337
|
max_nodes: { type: 'number', description: 'Максимальное количество узлов подграфа (по умолчанию 30)' },
|
|
339
338
|
format: { type: 'string', description: 'Формат ответа: "mermaid" для диаграммы в Markdown или "json" (по умолчанию "mermaid")' }
|
|
340
339
|
}
|
|
@@ -437,7 +436,59 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
437
436
|
let sources = [];
|
|
438
437
|
|
|
439
438
|
switch (toolName) {
|
|
440
|
-
case 'znatok_find_similar':
|
|
439
|
+
case 'znatok_find_similar': {
|
|
440
|
+
const target = request.params.arguments?.target || request.params.arguments?.target_symbol || '';
|
|
441
|
+
const query = request.params.arguments?.query || request.params.arguments?.text || '';
|
|
442
|
+
if (!target && !query) {
|
|
443
|
+
result = 'Either target or query parameter is required.';
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
const limit = Math.min(Math.max(1, parseInt(request.params.arguments?.limit, 10) || 10), 100);
|
|
447
|
+
const callersLevel = Math.min(Math.max(0, parseInt(request.params.arguments?.callers_level ?? request.params.arguments?.caller_level, 10) ?? 3), 10);
|
|
448
|
+
const calleesLevel = Math.min(Math.max(0, parseInt(request.params.arguments?.callees_level ?? request.params.arguments?.callee_level, 10) ?? 3), 10);
|
|
449
|
+
const includeCode = Boolean(request.params.arguments?.include_code);
|
|
450
|
+
const maxCodeLines = parseInt(request.params.arguments?.max_code_lines, 10) || 30;
|
|
451
|
+
const role = request.params.arguments?.role || '';
|
|
452
|
+
const nodeType = request.params.arguments?.type || '';
|
|
453
|
+
const filePattern = request.params.arguments?.file_pattern || '';
|
|
454
|
+
|
|
455
|
+
const apiParams = new URLSearchParams({
|
|
456
|
+
limit: limit.toString(),
|
|
457
|
+
callers_level: callersLevel.toString(),
|
|
458
|
+
callees_level: calleesLevel.toString(),
|
|
459
|
+
include_code: includeCode.toString(),
|
|
460
|
+
max_code_lines: maxCodeLines.toString(),
|
|
461
|
+
});
|
|
462
|
+
if (target) apiParams.set('target', target);
|
|
463
|
+
if (query) apiParams.set('q', query);
|
|
464
|
+
if (role) apiParams.set('role', role);
|
|
465
|
+
if (nodeType) apiParams.set('type', nodeType);
|
|
466
|
+
if (filePattern) apiParams.set('file_pattern', filePattern);
|
|
467
|
+
|
|
468
|
+
const rawResults = await callZntApi(`/api/find_similar?${apiParams.toString()}`);
|
|
469
|
+
let processed = Array.isArray(rawResults) ? rawResults.map(item => {
|
|
470
|
+
const relFile = toRelativePath(item.file || item.file_path || item.filePath);
|
|
471
|
+
const resItem = {
|
|
472
|
+
...item,
|
|
473
|
+
file: relFile
|
|
474
|
+
};
|
|
475
|
+
if (includeCode && !resItem.code && resItem.file && resItem.start_line && resItem.end_line) {
|
|
476
|
+
let endLine = resItem.end_line;
|
|
477
|
+
if (maxCodeLines > 0 && (endLine - resItem.start_line + 1) > maxCodeLines) {
|
|
478
|
+
endLine = resItem.start_line + maxCodeLines - 1;
|
|
479
|
+
}
|
|
480
|
+
resItem.code = getSourceSnippet(resItem.file, resItem.start_line, endLine);
|
|
481
|
+
}
|
|
482
|
+
return resItem;
|
|
483
|
+
}) : rawResults;
|
|
484
|
+
if (Array.isArray(processed)) {
|
|
485
|
+
processed = processed.slice(0, limit);
|
|
486
|
+
}
|
|
487
|
+
result = processed;
|
|
488
|
+
sources = ['core_api:find_similar'];
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
|
|
441
492
|
case 'znatok_semantic_search': {
|
|
442
493
|
const query = request.params.arguments?.query || request.params.arguments?.text;
|
|
443
494
|
if (!query) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@znt/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Model Context Protocol adapter for Znt",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -31,4 +31,4 @@
|
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.0.1",
|
|
32
32
|
"ws": "^8.21.1"
|
|
33
33
|
}
|
|
34
|
-
}
|
|
34
|
+
}
|