ucn 3.8.10 → 3.8.11

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 +22 -16
  2. package/package.json +1 -1
package/mcp/server.js CHANGED
@@ -114,12 +114,17 @@ const server = new McpServer({
114
114
  // TOOL HELPERS
115
115
  // ============================================================================
116
116
 
117
- const DEFAULT_OUTPUT_CHARS = 10000; // ~2.5K tokens — compact default for AI agents
117
+ const DEFAULT_OUTPUT_CHARS = 10000; // ~2.5K tokens — targeted commands (about, context, smart, etc.)
118
+ const BROAD_OUTPUT_CHARS = 3000; // ~750 tokens — broad commands where truncated listings are useless
118
119
  const MAX_OUTPUT_CHARS = 100000; // hard ceiling even with max_chars override
119
120
 
121
+ // Broad commands: output is project-wide, truncation means you need a filter, not more text
122
+ const BROAD_COMMANDS = new Set(['toc', 'entrypoints', 'diff_impact', 'affected_tests', 'deadcode', 'usages']);
123
+
120
124
  function toolResult(text, command, maxChars) {
121
125
  if (!text) return { content: [{ type: 'text', text: '(no output)' }] };
122
- const limit = Math.min(maxChars || DEFAULT_OUTPUT_CHARS, MAX_OUTPUT_CHARS);
126
+ const defaultLimit = BROAD_COMMANDS.has(command) ? BROAD_OUTPUT_CHARS : DEFAULT_OUTPUT_CHARS;
127
+ const limit = Math.min(maxChars || defaultLimit, MAX_OUTPUT_CHARS);
123
128
  if (text.length > limit) {
124
129
  const fullSize = text.length;
125
130
  const fullTokens = Math.round(fullSize / 4);
@@ -128,14 +133,15 @@ function toolResult(text, command, maxChars) {
128
133
  const lastNewline = truncated.lastIndexOf('\n');
129
134
  const cleanCut = lastNewline > limit * 0.8 ? truncated.substring(0, lastNewline) : truncated;
130
135
  // Command-specific narrowing hints
131
- let narrow = 'Use file=/in=/exclude= to narrow scope.';
132
- if (command === 'toc') {
133
- narrow = 'Use in= to scope to a subdirectory, or detailed=false for compact view.';
134
- } else if (command === 'diff_impact') {
135
- narrow = 'Use file= to scope to specific files/directories.';
136
- } else if (command === 'affected_tests') {
137
- narrow = 'Use file= to scope, exclude= to skip patterns.';
138
- }
136
+ const hints = {
137
+ toc: 'Use in= to scope to a subdirectory, or detailed=false for compact view.',
138
+ entrypoints: 'Use framework= to filter by framework, exclude= to skip patterns.',
139
+ diff_impact: 'Use file= to scope to specific files/directories.',
140
+ affected_tests: 'Use file= to scope, exclude= to skip patterns.',
141
+ deadcode: 'Use file= to scope, exclude= to skip patterns.',
142
+ usages: 'Use file= to scope to specific files.',
143
+ };
144
+ const narrow = hints[command] || 'Use file=/in=/exclude= to narrow scope.';
139
145
  return { content: [{ type: 'text', text: cleanCut + `\n\n... OUTPUT TRUNCATED: showing ${limit} of ${fullSize} chars. Full output would be ~${fullTokens} tokens. ${narrow} Or use all=true to see everything (warning: ~${fullTokens} tokens).` }] };
140
146
  }
141
147
  return { content: [{ type: 'text', text }] };
@@ -285,7 +291,7 @@ server.registerTool(
285
291
  class_name: z.string().optional().describe('Class name to scope method analysis (e.g. "MarketDataFetcher" for close)'),
286
292
  limit: z.number().optional().describe('Max results to return (default: 500). Caps find, usages, search, deadcode, api, toc --detailed.'),
287
293
  max_files: z.number().optional().describe('Max files to index (default: 10000). Use for very large codebases.'),
288
- max_chars: z.number().optional().describe('Max output chars before truncation (default: 30000 ~7.5K tokens, max: 100000 ~25K tokens). Use all=true to bypass all caps, or set this for fine-grained control. Truncation message shows full size.'),
294
+ max_chars: z.number().optional().describe('Max output chars before truncation. Targeted commands (about, context, smart, etc.): 10K default. Broad commands (toc, entrypoints, deadcode, etc.): 3K default. Max: 100K. Use all=true to bypass all caps.'),
289
295
  // Structural search flags (search command)
290
296
  type: z.string().optional().describe('Symbol type filter for structural search: function, class, call, method, type. Triggers index-based search.'),
291
297
  param: z.string().optional().describe('Filter by parameter name or type (structural search). E.g. "Request", "ctx".'),
@@ -308,8 +314,8 @@ server.registerTool(
308
314
  // all=true bypasses both formatter caps AND char truncation (parity with CLI --all)
309
315
  const maxChars = ep.all ? MAX_OUTPUT_CHARS : ep.maxChars;
310
316
 
311
- // Wrap toolResult to auto-inject maxChars from this request
312
- const tr = (text, cmd) => toolResult(text, cmd, maxChars);
317
+ // Wrap toolResult to auto-inject command + maxChars from this request
318
+ const tr = (text) => toolResult(text, command, maxChars);
313
319
 
314
320
  try {
315
321
  switch (command) {
@@ -432,7 +438,7 @@ server.registerTool(
432
438
  topHint: 'Set top=N or use detailed=false for compact view.'
433
439
  });
434
440
  if (note) text += '\n\n' + note;
435
- return tr(text, 'toc');
441
+ return tr(text);
436
442
  }
437
443
 
438
444
  case 'search': {
@@ -456,7 +462,7 @@ server.registerTool(
456
462
  const index = getIndex(project_dir, ep);
457
463
  const { ok, result, error } = execute(index, 'affectedTests', ep);
458
464
  if (!ok) return tr(error);
459
- return tr(output.formatAffectedTests(result, { all: ep.all }), 'affected_tests');
465
+ return tr(output.formatAffectedTests(result, { all: ep.all }));
460
466
  }
461
467
 
462
468
  case 'deadcode': {
@@ -542,7 +548,7 @@ server.registerTool(
542
548
  const index = getIndex(project_dir, ep);
543
549
  const { ok, result, error } = execute(index, 'diffImpact', ep);
544
550
  if (!ok) return tr(error); // soft error — e.g. "not a git repo"
545
- return tr(output.formatDiffImpact(result, { all: ep.all }), 'diff_impact');
551
+ return tr(output.formatDiffImpact(result, { all: ep.all }));
546
552
  }
547
553
 
548
554
  // ── Other ───────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucn",
3
- "version": "3.8.10",
3
+ "version": "3.8.11",
4
4
  "mcpName": "io.github.mleoca/ucn",
5
5
  "description": "Code intelligence toolkit for AI agents — extract functions, trace call chains, find callers, detect dead code without reading entire files. Works as MCP server, CLI, or agent skill. Supports JS/TS, Python, Go, Rust, Java.",
6
6
  "main": "index.js",