ucn 3.7.38 → 3.7.39

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 (2) hide show
  1. package/mcp/server.js +44 -61
  2. package/package.json +1 -1
package/mcp/server.js CHANGED
@@ -257,13 +257,12 @@ server.registerTool(
257
257
  })
258
258
  },
259
259
  async (args) => {
260
- const { command, project_dir, name, file, exclude, include_tests,
261
- include_methods, include_uncertain, with_types, detailed,
262
- exact, in: inPath, top, depth, code_only, context: ctxLines,
263
- include_exported, include_decorated, calls_only, max_lines,
264
- direction, term, add_param, remove_param, rename_to,
265
- default_value, stack, item, range, base, staged,
266
- case_sensitive, regex, functions, all, top_level, class_name } = args;
260
+ const { command, project_dir } = args;
261
+
262
+ // Normalize ALL params once execute() handlers pick what they need.
263
+ // This eliminates per-case param selection and prevents CLI/MCP drift.
264
+ const { command: _c, project_dir: _p, ...rawParams } = args;
265
+ const ep = normalizeParams(rawParams);
267
266
 
268
267
  try {
269
268
  switch (command) {
@@ -276,7 +275,6 @@ server.registerTool(
276
275
 
277
276
  case 'about': {
278
277
  const index = getIndex(project_dir);
279
- const ep = normalizeParams({ name, file, exclude, with_types, all, include_methods, include_uncertain, top, class_name });
280
278
  const { ok, result, error } = execute(index, 'about', ep);
281
279
  if (!ok) return toolResult(error); // soft error — won't kill sibling calls
282
280
  return toolResult(output.formatAbout(result, {
@@ -287,19 +285,17 @@ server.registerTool(
287
285
 
288
286
  case 'context': {
289
287
  const index = getIndex(project_dir);
290
- const ep = normalizeParams({ name, file, exclude, include_methods, include_uncertain, class_name });
291
288
  const { ok, result: ctx, error } = execute(index, 'context', ep);
292
289
  if (!ok) return toolResult(error); // context uses soft error (not toolError)
293
290
  const { text, expandable } = output.formatContext(ctx, {
294
291
  expandHint: 'Use expand command with item number to see code for any item.'
295
292
  });
296
- expandCacheInstance.save(index.root, name, file, expandable);
293
+ expandCacheInstance.save(index.root, ep.name, ep.file, expandable);
297
294
  return toolResult(text);
298
295
  }
299
296
 
300
297
  case 'impact': {
301
298
  const index = getIndex(project_dir);
302
- const ep = normalizeParams({ name, file, exclude, top, class_name });
303
299
  const { ok, result, error } = execute(index, 'impact', ep);
304
300
  if (!ok) return toolResult(error); // soft error
305
301
  return toolResult(output.formatImpact(result));
@@ -307,7 +303,6 @@ server.registerTool(
307
303
 
308
304
  case 'smart': {
309
305
  const index = getIndex(project_dir);
310
- const ep = normalizeParams({ name, file, with_types, include_methods, include_uncertain });
311
306
  const { ok, result, error } = execute(index, 'smart', ep);
312
307
  if (!ok) return toolResult(error); // soft error
313
308
  return toolResult(output.formatSmart(result));
@@ -315,7 +310,6 @@ server.registerTool(
315
310
 
316
311
  case 'trace': {
317
312
  const index = getIndex(project_dir);
318
- const ep = normalizeParams({ name, file, depth, all, include_methods, include_uncertain });
319
313
  const { ok, result, error } = execute(index, 'trace', ep);
320
314
  if (!ok) return toolResult(error); // soft error
321
315
  return toolResult(output.formatTrace(result, {
@@ -326,19 +320,19 @@ server.registerTool(
326
320
 
327
321
  case 'example': {
328
322
  const index = getIndex(project_dir);
329
- const { ok, result, error } = execute(index, 'example', { name });
323
+ const { ok, result, error } = execute(index, 'example', ep);
330
324
  if (!ok) return toolResult(error);
331
- if (!result) return toolResult(`No usage examples found for "${name}".`);
332
- return toolResult(output.formatExample(result, name));
325
+ if (!result) return toolResult(`No usage examples found for "${ep.name}".`);
326
+ return toolResult(output.formatExample(result, ep.name));
333
327
  }
334
328
 
335
329
  case 'related': {
336
330
  const index = getIndex(project_dir);
337
- const { ok, result, error } = execute(index, 'related', { name, file, top, all });
331
+ const { ok, result, error } = execute(index, 'related', ep);
338
332
  if (!ok) return toolResult(error);
339
- if (!result) return toolResult(`Symbol "${name}" not found.`);
333
+ if (!result) return toolResult(`Symbol "${ep.name}" not found.`);
340
334
  return toolResult(output.formatRelated(result, {
341
- all: all || false, top,
335
+ all: ep.all || false, top: ep.top,
342
336
  allHint: 'Repeat with all=true to show all.'
343
337
  }));
344
338
  }
@@ -347,25 +341,22 @@ server.registerTool(
347
341
 
348
342
  case 'find': {
349
343
  const index = getIndex(project_dir);
350
- const ep = normalizeParams({ name, file, exclude, include_tests, exact, in: inPath });
351
344
  const { ok, result, error, note } = execute(index, 'find', ep);
352
345
  if (!ok) return toolResult(error); // soft error
353
- let text = output.formatFind(result, name, top);
346
+ let text = output.formatFind(result, ep.name, ep.top);
354
347
  if (note) text += '\n\n' + note;
355
348
  return toolResult(text);
356
349
  }
357
350
 
358
351
  case 'usages': {
359
352
  const index = getIndex(project_dir);
360
- const ep = normalizeParams({ name, exclude, include_tests, code_only, context: ctxLines, in: inPath });
361
353
  const { ok, result, error } = execute(index, 'usages', ep);
362
354
  if (!ok) return toolResult(error); // soft error
363
- return toolResult(output.formatUsages(result, name));
355
+ return toolResult(output.formatUsages(result, ep.name));
364
356
  }
365
357
 
366
358
  case 'toc': {
367
359
  const index = getIndex(project_dir);
368
- const ep = normalizeParams({ detailed, top_level, all, top });
369
360
  const { ok, result, error } = execute(index, 'toc', ep);
370
361
  if (!ok) return toolResult(error); // soft error
371
362
  return toolResult(output.formatToc(result, {
@@ -375,29 +366,26 @@ server.registerTool(
375
366
 
376
367
  case 'search': {
377
368
  const index = getIndex(project_dir);
378
- const ep = normalizeParams({ term, exclude, include_tests, code_only, context: ctxLines, case_sensitive, in: inPath, regex, top });
379
369
  const { ok, result, error } = execute(index, 'search', ep);
380
370
  if (!ok) return toolResult(error); // soft error
381
- return toolResult(output.formatSearch(result, term));
371
+ return toolResult(output.formatSearch(result, ep.term));
382
372
  }
383
373
 
384
374
  case 'tests': {
385
375
  const index = getIndex(project_dir);
386
- const ep = normalizeParams({ name, calls_only });
387
376
  const { ok, result, error } = execute(index, 'tests', ep);
388
377
  if (!ok) return toolResult(error); // soft error
389
- return toolResult(output.formatTests(result, name));
378
+ return toolResult(output.formatTests(result, ep.name));
390
379
  }
391
380
 
392
381
  case 'deadcode': {
393
382
  const index = getIndex(project_dir);
394
- const ep = normalizeParams({ exclude, in: inPath, include_exported, include_decorated, include_tests });
395
383
  const { ok, result, error } = execute(index, 'deadcode', ep);
396
384
  if (!ok) return toolResult(error); // soft error
397
385
  return toolResult(output.formatDeadcode(result, {
398
- top: top || 0,
399
- decoratedHint: !include_decorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use include_decorated=true to include them.` : undefined,
400
- exportedHint: !include_exported && result.excludedExported > 0 ? `${result.excludedExported} exported symbol(s) excluded (all have callers). Use include_exported=true to audit them.` : undefined
386
+ top: ep.top || 0,
387
+ decoratedHint: !ep.includeDecorated && result.excludedDecorated > 0 ? `${result.excludedDecorated} decorated/annotated symbol(s) hidden (framework-registered). Use include_decorated=true to include them.` : undefined,
388
+ exportedHint: !ep.includeExported && result.excludedExported > 0 ? `${result.excludedExported} exported symbol(s) excluded (all have callers). Use include_exported=true to audit them.` : undefined
401
389
  }));
402
390
  }
403
391
 
@@ -405,32 +393,32 @@ server.registerTool(
405
393
 
406
394
  case 'imports': {
407
395
  const index = getIndex(project_dir);
408
- const { ok, result, error } = execute(index, 'imports', { file });
396
+ const { ok, result, error } = execute(index, 'imports', ep);
409
397
  if (!ok) return toolResult(error); // soft error
410
- return toolResult(output.formatImports(result, file));
398
+ return toolResult(output.formatImports(result, ep.file));
411
399
  }
412
400
 
413
401
  case 'exporters': {
414
402
  const index = getIndex(project_dir);
415
- const { ok, result, error } = execute(index, 'exporters', { file });
403
+ const { ok, result, error } = execute(index, 'exporters', ep);
416
404
  if (!ok) return toolResult(error); // soft error
417
- return toolResult(output.formatExporters(result, file));
405
+ return toolResult(output.formatExporters(result, ep.file));
418
406
  }
419
407
 
420
408
  case 'file_exports': {
421
409
  const index = getIndex(project_dir);
422
- const { ok, result, error } = execute(index, 'fileExports', { file });
410
+ const { ok, result, error } = execute(index, 'fileExports', ep);
423
411
  if (!ok) return toolResult(error); // soft error
424
- return toolResult(output.formatFileExports(result, file));
412
+ return toolResult(output.formatFileExports(result, ep.file));
425
413
  }
426
414
 
427
415
  case 'graph': {
428
416
  const index = getIndex(project_dir);
429
- const { ok, result, error } = execute(index, 'graph', { file, direction, depth, all });
417
+ const { ok, result, error } = execute(index, 'graph', ep);
430
418
  if (!ok) return toolResult(error); // soft error
431
419
  return toolResult(output.formatGraph(result, {
432
- showAll: all || depth !== undefined,
433
- maxDepth: depth ?? 2, file,
420
+ showAll: ep.all || ep.depth !== undefined,
421
+ maxDepth: ep.depth ?? 2, file: ep.file,
434
422
  depthHint: 'Set depth parameter for deeper graph.',
435
423
  allHint: 'Set depth to expand all children.'
436
424
  }));
@@ -440,7 +428,6 @@ server.registerTool(
440
428
 
441
429
  case 'verify': {
442
430
  const index = getIndex(project_dir);
443
- const ep = normalizeParams({ name, file, class_name });
444
431
  const { ok, result, error } = execute(index, 'verify', ep);
445
432
  if (!ok) return toolResult(error); // soft error
446
433
  return toolResult(output.formatVerify(result));
@@ -448,7 +435,6 @@ server.registerTool(
448
435
 
449
436
  case 'plan': {
450
437
  const index = getIndex(project_dir);
451
- const ep = normalizeParams({ name, add_param, remove_param, rename_to, default_value, file, class_name });
452
438
  const { ok, result, error } = execute(index, 'plan', ep);
453
439
  if (!ok) return toolResult(error); // soft error
454
440
  return toolResult(output.formatPlan(result));
@@ -456,7 +442,7 @@ server.registerTool(
456
442
 
457
443
  case 'diff_impact': {
458
444
  const index = getIndex(project_dir);
459
- const { ok, result, error } = execute(index, 'diffImpact', { base, staged, file });
445
+ const { ok, result, error } = execute(index, 'diffImpact', ep);
460
446
  if (!ok) return toolResult(error); // soft error — e.g. "not a git repo"
461
447
  return toolResult(output.formatDiffImpact(result));
462
448
  }
@@ -465,39 +451,38 @@ server.registerTool(
465
451
 
466
452
  case 'typedef': {
467
453
  const index = getIndex(project_dir);
468
- const { ok, result, error } = execute(index, 'typedef', { name, exact });
454
+ const { ok, result, error } = execute(index, 'typedef', ep);
469
455
  if (!ok) return toolResult(error); // soft error
470
- return toolResult(output.formatTypedef(result, name));
456
+ return toolResult(output.formatTypedef(result, ep.name));
471
457
  }
472
458
 
473
459
  case 'stacktrace': {
474
460
  const index = getIndex(project_dir);
475
- const { ok, result, error } = execute(index, 'stacktrace', { stack });
461
+ const { ok, result, error } = execute(index, 'stacktrace', ep);
476
462
  if (!ok) return toolResult(error); // soft error
477
463
  return toolResult(output.formatStackTrace(result));
478
464
  }
479
465
 
480
466
  case 'api': {
481
467
  const index = getIndex(project_dir);
482
- const { ok, result, error } = execute(index, 'api', { file });
468
+ const { ok, result, error } = execute(index, 'api', ep);
483
469
  if (!ok) return toolResult(error); // soft error
484
- return toolResult(output.formatApi(result, file || '.'));
470
+ return toolResult(output.formatApi(result, ep.file || '.'));
485
471
  }
486
472
 
487
473
  case 'stats': {
488
474
  const index = getIndex(project_dir);
489
- const { ok, result, error } = execute(index, 'stats', { functions });
475
+ const { ok, result, error } = execute(index, 'stats', ep);
490
476
  if (!ok) return toolResult(error); // soft error
491
- return toolResult(output.formatStats(result, { top: top || 0 }));
477
+ return toolResult(output.formatStats(result, { top: ep.top || 0 }));
492
478
  }
493
479
 
494
480
  // ── Extracting Code (via execute) ────────────────────────────
495
481
 
496
482
  case 'fn': {
497
- const err = requireName(name);
483
+ const err = requireName(ep.name);
498
484
  if (err) return err;
499
485
  const index = getIndex(project_dir);
500
- const ep = normalizeParams({ name, file, all });
501
486
  const { ok, result, error } = execute(index, 'fn', ep);
502
487
  if (!ok) return toolResult(error); // soft error
503
488
  // MCP path security: validate all result files are within project root
@@ -510,13 +495,12 @@ server.registerTool(
510
495
  }
511
496
 
512
497
  case 'class': {
513
- const err = requireName(name);
498
+ const err = requireName(ep.name);
514
499
  if (err) return err;
515
- if (max_lines !== undefined && (!Number.isInteger(max_lines) || max_lines < 1)) {
516
- return toolError(`Invalid max_lines: ${max_lines}. Must be a positive integer.`);
500
+ if (ep.maxLines !== undefined && (!Number.isInteger(ep.maxLines) || ep.maxLines < 1)) {
501
+ return toolError(`Invalid max_lines: ${ep.maxLines}. Must be a positive integer.`);
517
502
  }
518
503
  const index = getIndex(project_dir);
519
- const ep = normalizeParams({ name, file, all, max_lines });
520
504
  const { ok, result, error } = execute(index, 'class', ep);
521
505
  if (!ok) return toolResult(error); // soft error (class not found)
522
506
  // MCP path security: validate all result files are within project root
@@ -530,7 +514,6 @@ server.registerTool(
530
514
 
531
515
  case 'lines': {
532
516
  const index = getIndex(project_dir);
533
- const ep = normalizeParams({ file, range });
534
517
  const { ok, result, error } = execute(index, 'lines', ep);
535
518
  if (!ok) return toolResult(error); // soft error
536
519
  // MCP path security: validate file is within project root
@@ -540,13 +523,13 @@ server.registerTool(
540
523
  }
541
524
 
542
525
  case 'expand': {
543
- if (item === undefined || item === null) {
526
+ if (ep.item === undefined || ep.item === null) {
544
527
  return toolError('Item number is required (e.g. item=1).');
545
528
  }
546
529
  const index = getIndex(project_dir);
547
- const lookup = expandCacheInstance.lookup(index.root, item);
530
+ const lookup = expandCacheInstance.lookup(index.root, ep.item);
548
531
  const { ok, result, error } = execute(index, 'expand', {
549
- match: lookup.match, itemNum: item,
532
+ match: lookup.match, itemNum: ep.item,
550
533
  itemCount: lookup.itemCount, symbolName: lookup.symbolName,
551
534
  validateRoot: true
552
535
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.7.38",
3
+ "version": "3.7.39",
4
4
  "mcpName": "io.github.mleoca/ucn",
5
5
  "description": "Universal Code Navigator — AST-based call graph analysis for AI agents. Find callers, trace impact, detect dead code across JS/TS, Python, Go, Rust, Java, and HTML. CLI, MCP server, and agent skill.",
6
6
  "main": "index.js",