Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/prompts/mcp-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ SIMPLE TOOLS (positional arguments):
COMPLEX TOOLS (key:value arguments):
- mcp__codanna__search_symbols query:"parse" limit:10 (kind is optional)
- mcp__codanna__search_symbols query:"parse" kind:"function" limit:10
- mcp__codanna__search_symbols query:"Service" limit:20 summary_only:true (compact output, 25x fewer tokens)
- mcp__codanna__get_symbol_details symbol_name:"SendMessageToApi" file_path:"Processes/Helper.cs" (detailed info for specific symbol)
- mcp__codanna__semantic_search_docs query:"error handling" limit:5
- mcp__codanna__semantic_search_with_context query:"authentication flow" limit:3
</usage>
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,10 @@ Icon?
*.swp
*.swo
*.bak
*.tmp
*.tmp

# Local development notes (not for PR)
PR_DESCRIPTION.md
PR_NOTES_MCP_FIXES.md
IMPROVEMENTS_ROADMAP.md
.claude/settings.local.json
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ Available tools when using the MCP server. All tools support `--json` flag for s
| `find_callers` | Show functions that call a given function | `codanna mcp find_callers init` |
| `analyze_impact` | Analyze the impact radius of symbol changes | `codanna mcp analyze_impact Parser --json` |
| `get_index_info` | Get index statistics and metadata | `codanna mcp get_index_info --json` |
| `get_symbol_details` | Get detailed info for a specific symbol | `codanna mcp get_symbol_details symbol_name:Parser file_path:"src/main.rs"` |

#### Complex Tools (Key:Value Arguments)
| Tool | Description | Example |
Expand All @@ -471,17 +472,39 @@ codanna mcp semantic_search_with_context query:"parse config" lang:typescript li

Language filtering eliminates duplicate results when similar documentation exists across multiple languages, reducing result sets by up to 75% while maintaining identical similarity scores.

#### Summary Mode (Token Optimization)
For overview queries, use `summary_only:true` to get compact output with 25x fewer tokens:
```bash
# Compact output: just name, kind, location (200 tokens vs 5000 tokens)
codanna mcp search_symbols query:"Service" limit:20 summary_only:true

# Output:
# Found 20 result(s) for query 'Service':
# Service (Function) at .\Service.cs:10
# Service (Field) at .\Models\ServicesConfig.cs:58
# Service (Method) at .\Processes\Service.cs:25
# ...
```

Use summary mode for:
- Quick overviews and symbol discovery
- Large result sets (50+ results)
- When you only need to know what exists, not full details

Then use `find_symbol` or full search for specific symbols you want to explore.

#### Parameters Reference
| Tool | Parameters |
|------|------------|
| `find_symbol` | `name` (required) |
| `search_symbols` | `query`, `limit`, `kind`, `module` |
| `find_symbol` | `name` (required), `lang` |
| `search_symbols` | `query`, `limit`, `kind`, `module`, `lang`, `offset`, `summary_only` |
| `semantic_search_docs` | `query`, `limit`, `threshold`, `lang` |
| `semantic_search_with_context` | `query`, `limit`, `threshold`, `lang` |
| `get_calls` | `function_name` |
| `find_callers` | `function_name` |
| `analyze_impact` | `symbol_name`, `max_depth` |
| `get_index_info` | None |
| `get_symbol_details` | `symbol_name` (required), `file_path`, `module` |


### Performance
Expand Down
97 changes: 77 additions & 20 deletions src/indexing/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,10 +910,15 @@ impl SimpleIndexer {
module_path: &Option<String>,
behavior: &dyn crate::parsing::LanguageBehavior,
) {
// Delegate full configuration to the language behavior.
// This allows languages to preserve parser-derived visibility and
// apply custom module path rules.
behavior.configure_symbol(symbol, module_path.as_deref());
// Only configure if module_path is not already set by the parser
// This allows parsers to set the correct namespace directly
// (e.g., C# parser extracts namespace declarations and sets them on symbols)
if symbol.module_path.is_none() {
// Delegate full configuration to the language behavior.
// This allows languages to preserve parser-derived visibility and
// apply custom module path rules.
behavior.configure_symbol(symbol, module_path.as_deref());
}

debug_print!(
self,
Expand Down Expand Up @@ -2206,6 +2211,10 @@ impl SimpleIndexer {
self.resolve_cross_file_relationships()?;
}

// Stop timing and update final stats
stats.stop_timing();
stats.symbols_found = self.symbol_count();

Ok(stats)
}

Expand Down Expand Up @@ -2324,7 +2333,31 @@ impl SimpleIndexer {
let static_method = format!("{}::{}", receiver, method_call.method_name);
let result = context
.resolve(&static_method)
.or_else(|| context.resolve(&method_call.method_name));
.or_else(|| context.resolve(&method_call.method_name))
// Fallback: search entire index for methods with this name in the receiver class
.or_else(|| {
debug_print!(
self,
"Context resolution failed for {}::{}, searching entire index",
receiver,
method_call.method_name
);
// Find all symbols with this method name
let candidates = self.find_symbols_by_name(&method_call.method_name, None);
// Filter to only methods in the receiver class/module
candidates
.into_iter()
.find(|sym| {
// Check if this symbol's module path contains the receiver class name
if let Some(module) = sym.as_module_path() {
module.ends_with(receiver.as_str())
|| module.ends_with(&format!(".{receiver}"))
} else {
false
}
})
.map(|sym| sym.id)
});
debug_print!(
self,
"Static method resolution result for {}: {:?}",
Expand Down Expand Up @@ -2592,21 +2625,45 @@ impl SimpleIndexer {
// If unresolved call, try language behavior external mapping
if result.is_none() && rel.kind == RelationKind::Calls {
if let Some(behavior) = self.file_behaviors.get(&file_id) {
if let Some((module_path, symbol_name)) =
behavior.resolve_external_call_target(&rel.to_name, file_id)
{
// Skip external symbol creation for mapped external calls
debug_print!(
self,
"Skipping external symbol for mapped call: {} -> {}::{}",
rel.to_name,
module_path,
symbol_name
);
None
} else {
None
}
// Get all imports for this file
let imports = behavior.get_imports_for_file(file_id);
debug_print!(
self,
"Trying to resolve '{}' in {} imported namespaces",
rel.to_name,
imports.len()
);

// Try each imported namespace to find the symbol
self.document_index.find_symbols_by_name(&rel.to_name, None)
.ok()
.and_then(|candidates| {
// Look for symbol in any of the imported namespaces
candidates.into_iter().find_map(|candidate| {
if let Some(ref cand_module) = candidate.module_path {
let cand_module_str = cand_module.as_ref();
// Check if this symbol is in any imported namespace
for import in &imports {
let import_path = &import.path;
// Exact match or the candidate is in a sub-namespace
if cand_module_str == import_path
|| cand_module_str.starts_with(&format!("{import_path}.")) {
debug_print!(
self,
"Found external symbol: {} in {} (via import {})",
rel.to_name,
cand_module_str,
import_path
);
return Some(candidate.id);
}
}
None
} else {
None
}
})
})
} else {
None
}
Expand Down
37 changes: 34 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ enum Commands {
#[command(
about = "Execute MCP tools directly",
long_about = "Execute MCP tools directly without spawning a server.\n\nSupports positional arguments, key=value pairs, and JSON arguments.",
after_help = "Examples:\n codanna mcp find_symbol main\n codanna mcp get_calls process_file\n codanna mcp semantic_search_docs query:\"error handling\" limit:5\n codanna mcp search_symbols query:parse kind:function\n codanna mcp find_symbol Parser --json | jq '.data[].symbol.name'\n codanna mcp search_symbols query:Parser --json | jq '.data[].name'\n\nTools:\n find_symbol Find symbol by exact name\n search_symbols Full-text search with fuzzy matching\n semantic_search_docs Natural language search\n semantic_search_with_context Natural language search with relationships\n get_calls Functions called by a function\n find_callers Functions that call a function\n analyze_impact Impact radius of symbol changes\n get_index_info Index statistics"
after_help = "Examples:\n codanna mcp find_symbol main\n codanna mcp get_calls process_file\n codanna mcp semantic_search_docs query:\"error handling\" limit:5\n codanna mcp search_symbols query:parse kind:function\n codanna mcp find_symbol Parser --json | jq '.data[].symbol.name'\n codanna mcp search_symbols query:Parser --json | jq '.data[].name'\n codanna mcp get_symbol_details symbol_name:SendMessageToApi file_path:\"Processes/Helper.cs\"\n\nTools:\n find_symbol Find symbol by exact name\n search_symbols Full-text search with fuzzy matching\n semantic_search_docs Natural language search\n semantic_search_with_context Natural language search with relationships\n get_calls Functions called by a function\n find_callers Functions that call a function\n analyze_impact Impact radius of symbol changes\n get_index_info Index statistics\n get_symbol_details Get detailed info about a specific symbol"
)]
Mcp {
/// Tool to call
Expand Down Expand Up @@ -2073,6 +2073,10 @@ async fn main() {
kind,
module,
lang,
file_pattern: None,
exclude_pattern: None,
offset: 0,
summary_only: false,
}))
.await
}
Expand Down Expand Up @@ -2146,6 +2150,33 @@ async fn main() {
))
.await
}
"get_symbol_details" => {
let symbol_name = arguments
.as_ref()
.and_then(|m| m.get("symbol_name"))
.and_then(|v| v.as_str())
.unwrap_or_else(|| {
eprintln!("Error: get_symbol_details requires 'symbol_name' parameter");
std::process::exit(1);
});
let file_path = arguments
.as_ref()
.and_then(|m| m.get("file_path"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let module = arguments
.as_ref()
.and_then(|m| m.get("module"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
server
.get_symbol_details(Parameters(GetSymbolDetailsRequest {
symbol_name: symbol_name.to_string(),
file_path,
module,
}))
.await
}
_ => {
if json {
use codanna::io::exit_code::ExitCode;
Expand All @@ -2154,14 +2185,14 @@ async fn main() {
ExitCode::GeneralError,
&format!("Unknown tool: {tool}"),
vec![
"Available tools: find_symbol, get_calls, find_callers, analyze_impact, get_index_info, search_symbols, semantic_search_docs, semantic_search_with_context",
"Available tools: find_symbol, get_calls, find_callers, analyze_impact, get_index_info, search_symbols, semantic_search_docs, semantic_search_with_context, get_symbol_details",
],
);
println!("{}", serde_json::to_string_pretty(&response).unwrap());
} else {
eprintln!("Unknown tool: {tool}");
eprintln!(
"Available tools: find_symbol, get_calls, find_callers, analyze_impact, get_index_info, search_symbols, semantic_search_docs, semantic_search_with_context"
"Available tools: find_symbol, get_calls, find_callers, analyze_impact, get_index_info, search_symbols, semantic_search_docs, semantic_search_with_context, get_symbol_details"
);
}
std::process::exit(1);
Expand Down
Loading
Loading