@znt/mcp 2.0.2 → 2.0.3

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 CHANGED
@@ -207,6 +207,10 @@ Windows Credential Manager, macOS Keychain или Linux Secret Service. В YAML
207
207
  Проверяет Core, YAML и credential. С `check_provider: true` дополнительно
208
208
  проверяет endpoint и настроенные модели. Значение секрета не возвращается.
209
209
 
210
+ Ключевые поля ответа:
211
+ - `config_managed` (boolean): `true`, если конфигурация управляется MCP-сервером, или `false`, если выбран внешний файл конфигурации через `ZNT_CONFIG`.
212
+ - `model`: имя генеративной LLM-модели. Если выбран `semantic_mode: "fast"` (локальные эвристики AST без вызовов LLM) и модель не задана, поле возвращает `"not_required (fast mode)"`.
213
+
210
214
  ### `search`
211
215
 
212
216
  Ищет компоненты в semantic index.
@@ -302,6 +306,14 @@ embedding не означает, что lexical symbol отсутствует.
302
306
  Допустимые форматы: `text`, `json`, `mermaid`. Для неоднозначного короткого
303
307
  имени core возвращает кандидатов; повторите запрос с квалифицированным `name`.
304
308
 
309
+ Дополнительные параметры направления:
310
+ - `direction` (`"down"` | `"up"` | `"both"`, по умолчанию `"down"`): в локальном режиме задает направление обхода вызовов:
311
+ - `"down"` (по умолчанию): исследует только исходящие вызовы (дерево вызовов / callees), исключая комбинаторный взрыв тестами и сторонними клиентами на `depth >= 2`;
312
+ - `"up"`: исследует иерархию вызывающих (callers / кто вызывает данный символ);
313
+ - `"both"`: двунаправленный обход (одновременно callers и callees).
314
+
315
+ > 💡 **Чистый граф проекта**: MCP автоматически отсекает методы стандартных библиотек и вендоров (JDK, Go stdlib, Spring boilerplate: `HashMap`, `put`, `get`, `ok`), поэтому граф содержит только реальный код проекта.
316
+
305
317
  ### `outline`
306
318
 
307
319
  Возвращает полное top-level оглавление проиндексированного файла:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znt/mcp",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Model Context Protocol server for znt-core through @znt/sdk-nodejs",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -79,6 +79,7 @@ export const TOOL_DEFINITIONS = Object.freeze([
79
79
  depth: { type: "integer", minimum: 1, maximum: 100, default: 1 },
80
80
  format: { type: "string", enum: ["text", "json", "mermaid"], default: "text" },
81
81
  edge_types: { type: "string", description: "Comma-separated contains,call,inherits,implements values." },
82
+ direction: { type: "string", enum: ["down", "up", "both"], default: "down", description: "Call direction: 'down' for call tree (callees, default), 'up' for callers (call hierarchy), 'both' for bidirectional context." },
82
83
  },
83
84
  required: ["from"],
84
85
  additionalProperties: false,
@@ -123,12 +124,11 @@ export const TOOL_DEFINITIONS = Object.freeze([
123
124
  },
124
125
  {
125
126
  name: "scan",
126
- description: "Scan a project only after setup is valid. If setup_required is returned, call setup before retrying.",
127
+ description: "Index the project workspace. Scans all supported source files, declarative schemas, and project resources.",
127
128
  inputSchema: {
128
129
  type: "object",
129
130
  properties: {
130
131
  project_path: { type: "string", minLength: 1, description: "Optional absolute project directory or a path relative to ZNT_PROJECT_ROOT. Defaults to ZNT_PROJECT_ROOT or cwd." },
131
- language: { type: "string", minLength: 1, default: "auto", description: "Parser name from info.languages, or auto." },
132
132
  },
133
133
  additionalProperties: false,
134
134
  },
package/src/znt-tools.js CHANGED
@@ -27,11 +27,13 @@ export class ZntTools {
27
27
  this.managedConfig = options.managedConfig ?? (explicitConfig === undefined);
28
28
  this.client = options.client ?? new ZntClient({
29
29
  configPath: this.configPath,
30
+ configManaged: this.managedConfig,
30
31
  ...(options.coreBinaryPath ? { coreBinaryPath: options.coreBinaryPath } : {}),
31
32
  });
32
33
  if (typeof this.client.setupManagedConfig !== "function") {
33
34
  this.client.setupManagedConfig = (opts) => setupManagedConfig(opts, {
34
35
  configPath: this.configPath,
36
+ configManaged: this.managedConfig,
35
37
  coreBinaryPath: this.customCoreBinary,
36
38
  client: this.client,
37
39
  });
@@ -39,6 +41,7 @@ export class ZntTools {
39
41
  if (typeof this.client.getSetupStatus !== "function") {
40
42
  this.client.getSetupStatus = (opts) => getSetupStatus(opts, {
41
43
  configPath: this.configPath,
44
+ configManaged: this.managedConfig,
42
45
  coreBinaryPath: this.customCoreBinary,
43
46
  client: this.client,
44
47
  });
@@ -145,10 +148,16 @@ export class ZntTools {
145
148
  }
146
149
 
147
150
  async setupStatus(checkProvider = false, signal) {
148
- const status = await this.client.getSetupStatus({ configPath: this.configPath, checkProvider, signal });
151
+ const status = await this.client.getSetupStatus({
152
+ configPath: this.configPath,
153
+ configManaged: this.managedConfig,
154
+ checkProvider,
155
+ signal,
156
+ });
149
157
  return {
150
158
  ...status,
151
- config_managed_by_mcp: this.managedConfig,
159
+ config_managed: this.managedConfig,
160
+ ...(status?.semantic_mode === "fast" && !status?.model ? { model: "not_required (fast mode)" } : {}),
152
161
  };
153
162
  }
154
163
 
@@ -179,7 +188,9 @@ export class ZntTools {
179
188
  "clear_api_key",
180
189
  ].some((key) => args[key] !== undefined);
181
190
 
182
- if (!hasExplicitSettings || args.interactive === true) {
191
+ const isInteractive = args.interactive !== false && args.interactive !== "false";
192
+
193
+ if (isInteractive && (!hasExplicitSettings || args.interactive === true || args.interactive === "true")) {
183
194
  return this.openSetupWizard(args, signal);
184
195
  }
185
196
 
@@ -266,43 +277,55 @@ export class ZntTools {
266
277
  !args.clear_api_key
267
278
  );
268
279
 
280
+ const isInteractive = args.interactive !== false && args.interactive !== "false";
269
281
  const mode = args.mode ?? previousStatus?.mode ?? "openapi";
270
282
  const isInteractiveOpenapi = mode === "openapi" && !args.api_key && args.credential_source !== "env" && !hasExistingCredential;
271
283
 
272
284
  const status = await this.client.setupManagedConfig({
273
285
  ...args,
274
286
  config_path: this.configPath,
275
- check_provider: isInteractiveOpenapi ? false : (args.check_provider !== false),
287
+ check_provider: (isInteractiveOpenapi && isInteractive) ? false : (args.check_provider !== false),
276
288
  });
277
289
 
278
290
  if (isInteractiveOpenapi || status.status === "credential_required") {
279
- const providerUrl = args.provider_url ?? status.provider_url ?? "https://api.openai.com/v1";
280
- const reference = credentialReference(providerUrl);
281
- const setupUrl = await this.credentialSetup.open({
282
- provider: providerUrl,
283
- onCredential: async (credential) => {
284
- if (typeof this.client.storeSecret === "function") {
285
- await this.client.storeSecret(reference, credential);
286
- }
287
- if (typeof this.client.validateConfig === "function") {
288
- await this.client.validateConfig(this.configPath, { checkProvider: true });
289
- }
290
- this.setupReady = true;
291
- },
292
- });
291
+ if (isInteractive) {
292
+ const providerUrl = args.provider_url ?? status.provider_url ?? "https://api.openai.com/v1";
293
+ const reference = credentialReference(providerUrl);
294
+ const setupUrl = await this.credentialSetup.open({
295
+ provider: providerUrl,
296
+ onCredential: async (credential) => {
297
+ if (typeof this.client.storeSecret === "function") {
298
+ await this.client.storeSecret(reference, credential);
299
+ }
300
+ if (typeof this.client.validateConfig === "function") {
301
+ await this.client.validateConfig(this.configPath, { checkProvider: true });
302
+ }
303
+ this.setupReady = true;
304
+ },
305
+ });
306
+ return {
307
+ status: "credential_required",
308
+ config_path: this.configPath,
309
+ credential_source: "keyring",
310
+ setup_url: setupUrl,
311
+ next: "Open setup_url, save the API key, then call setup_status with check_provider=true.",
312
+ };
313
+ }
314
+
293
315
  return {
316
+ ...status,
294
317
  status: "credential_required",
295
318
  config_path: this.configPath,
296
- credential_source: "keyring",
297
- setup_url: setupUrl,
298
- next: "Open setup_url, save the API key, then call setup_status with check_provider=true.",
319
+ instruction: "API key is required. Pass api_key directly in setup arguments or set interactive: true to open the browser setup wizard.",
299
320
  };
300
321
  }
301
322
 
323
+
302
324
  this.setupReady = status.status === "ready";
303
325
  return {
304
326
  ...status,
305
- config_managed_by_mcp: this.managedConfig,
327
+ config_managed: this.managedConfig,
328
+ ...(status?.semantic_mode === "fast" && !status?.model ? { model: "not_required (fast mode)" } : {}),
306
329
  scan_required: this.setupReady,
307
330
  };
308
331
  }
@@ -327,7 +350,7 @@ export class ZntTools {
327
350
  case "similar":
328
351
  return this.client.findSimilar(normalizeSimilarArgs(args), requestOptions);
329
352
  case "graph":
330
- return this.client.getSubgraph(copyDefined(args, ["from", "to", "depth", "format", "edge_types"]), requestOptions);
353
+ return this.client.getSubgraph(normalizeGraphArgs(args), requestOptions);
331
354
  case "outline":
332
355
  return this.client.fileOutline({ file_path: args.path, include_code: args.include_code }, requestOptions);
333
356
  case "logs":
@@ -457,10 +480,31 @@ export class ZntTools {
457
480
  }
458
481
  }
459
482
 
483
+ export const TYPE_BOOST_MAP = {
484
+ class: "declaration",
485
+ struct: "declaration",
486
+ interface: "declaration",
487
+ type: "declaration",
488
+ enum: "declaration",
489
+ declaration: "declaration",
490
+ method: "function",
491
+ function: "function",
492
+ constructor: "function",
493
+ field: "member",
494
+ property: "member",
495
+ member: "member",
496
+ };
497
+
498
+ export function normalizeAstType(type) {
499
+ if (!type || typeof type !== "string") return type;
500
+ const lower = type.trim().toLowerCase();
501
+ return TYPE_BOOST_MAP[lower] ?? type;
502
+ }
503
+
460
504
  function normalizeSearchArgs(args) {
461
505
  return copyDefined({
462
506
  ...args,
463
- type: args.type_boost,
507
+ type: normalizeAstType(args.type_boost),
464
508
  role: args.role_boost,
465
509
  file_path: args.file_pattern,
466
510
  }, ["query", "mode", "limit", "callers_level", "callees_level", "compact", "include_code", "max_code_lines", "type", "role", "file_path"]);
@@ -469,12 +513,20 @@ function normalizeSearchArgs(args) {
469
513
  function normalizeSimilarArgs(args) {
470
514
  return copyDefined({
471
515
  ...args,
472
- type: args.type_boost,
516
+ type: normalizeAstType(args.type_boost),
473
517
  role: args.role_boost,
474
518
  file_path: args.file_pattern,
475
519
  }, ["target", "query", "limit", "include_code", "max_code_lines", "type", "role", "file_path", "edge_types"]);
476
520
  }
477
521
 
522
+ function normalizeGraphArgs(args) {
523
+ return copyDefined({
524
+ ...args,
525
+ direction: args.direction ?? "down",
526
+ internal_only: true,
527
+ }, ["from", "to", "depth", "format", "edge_types", "direction", "internal_only"]);
528
+ }
529
+
478
530
  function copyDefined(source, keys) {
479
531
  return Object.fromEntries(keys.filter((key) => source[key] !== undefined).map((key) => [key, source[key]]));
480
532
  }