Skip to content

Commit c910855

Browse files
committed
Added _score-based sorting support with order configuration, updated existing integration tests to include new sorting parameters, enhanced text normalization with TextCleaner utility, documented new query profiling and debugging features in README, and introduced DTOs for detailed query and scoring analysis.
1 parent 99aed01 commit c910855

26 files changed

Lines changed: 2980 additions & 210 deletions

.claude/skills/improvements/SKILL.md

Lines changed: 817 additions & 11 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,26 @@ A Model Context Protocol (MCP) server that exposes Apache Lucene fulltext search
1818
- Structured passages with quality metadata for LLM consumption
1919
- Paginated results with filter suggestions
2020

21+
🔬 **Query Profiling & Debugging**
22+
- Deep query analysis and profiling (`profileQuery` tool)
23+
- Understand why queries return certain results and how scoring works
24+
- Filter impact analysis showing document reduction per filter
25+
- Document scoring explanations with BM25 breakdown
26+
- Term statistics (IDF, rarity, document frequency)
27+
- Actionable optimization recommendations
28+
- LLM-optimized structured output for easy interpretation
29+
2130
📄 **Rich Metadata Extraction**
2231
- Automatic language detection
2332
- Author, title, creation date extraction
2433
- File type and size information
2534
- SHA-256 content hashing for change detection
2635

36+
🧹 **Text Normalization**
37+
- Automatic removal of broken/invalid characters (�, control chars, zero-width chars)
38+
- Whitespace normalization (multiple spaces collapsed to single space)
39+
- Ensures clean, readable search results and passages
40+
2741
**Performance Optimized**
2842
- Batch processing for efficient indexing
2943
- NRT (Near Real-Time) search with dynamic optimization
@@ -334,6 +348,15 @@ lucene:
334348

335349
## Available MCP Tools
336350

351+
**Quick Reference - Most Important Tools:**
352+
- 🔍 **`search`** - Search documents with full Lucene query syntax and structured filters
353+
- 🔬 **`profileQuery`** - Debug and optimize queries with detailed analysis and scoring explanations
354+
- 🗂️ **`indexAdmin`** - Visual UI for index maintenance (optimize, purge, unlock)
355+
- 📊 **`getIndexStats`** - View index statistics and document count
356+
- 🚀 **`startCrawl`** - Index documents from configured directories
357+
358+
---
359+
337360
### `indexAdmin`
338361

339362
An [MCP App](https://github.com/modelcontextprotocol/ext-apps) that provides a visual user interface for index maintenance tasks directly inside your MCP client (e.g. Claude Desktop). When invoked, the app is rendered inline in the conversation and offers one-click access to administrative operations without requiring manual tool calls.
@@ -362,6 +385,44 @@ Search the Lucene fulltext index using **lexical matching** (exact word forms on
362385
- `filters` (optional): Array of structured filters for precise field-level filtering (see **Structured Filters** below)
363386
- `page` (optional): Page number, 0-based (default: 0)
364387
- `pageSize` (optional): Results per page (default: 10, max: 100)
388+
- `sortBy` (optional): Sort field - `_score` (default), `modified_date`, `created_date`, or `file_size`
389+
- `sortOrder` (optional): Sort order - `asc` or `desc` (default: `desc`)
390+
391+
**Sorting Results:**
392+
393+
By default, results are sorted by relevance score (most relevant first). You can sort by metadata fields:
394+
395+
| Sort Field | Description | Default Order |
396+
|------------|-------------|---------------|
397+
| `_score` | Relevance score (default) | Descending (best match first) |
398+
| `modified_date` | Last modified date | Descending (most recent first) |
399+
| `created_date` | Creation date | Descending (most recent first) |
400+
| `file_size` | File size in bytes | Descending (largest first) |
401+
402+
**Sort Examples:**
403+
```json
404+
// Most recently modified documents
405+
{ "query": "contract", "sortBy": "modified_date", "sortOrder": "desc" }
406+
407+
// Oldest documents first
408+
{ "query": "contract", "sortBy": "created_date", "sortOrder": "asc" }
409+
410+
// Smallest files (for quick review)
411+
{ "query": "summary", "sortBy": "file_size", "sortOrder": "asc" }
412+
413+
// Combine sorting with filters
414+
{
415+
"query": "*",
416+
"sortBy": "modified_date",
417+
"sortOrder": "desc",
418+
"filters": [
419+
{ "field": "file_extension", "value": "pdf" },
420+
{ "field": "modified_date", "operator": "range", "from": "2024-01-01" }
421+
]
422+
}
423+
```
424+
425+
**Note:** When sorting by metadata fields, relevance scores are still computed and used as a secondary sort criterion for tie-breaking.
365426

366427
**Structured Filters:**
367428

@@ -495,6 +556,213 @@ Leading wildcard queries are optimised internally using a reverse token index (`
495556
]}
496557
```
497558

559+
### 🔬 `profileQuery` - Query Profiling & Debugging
560+
561+
> **🌟 Powerful debugging tool for understanding search behavior, scoring, and performance**
562+
563+
Analyze and profile a search query to understand its behavior, performance, and scoring characteristics. This tool provides detailed insights into how Lucene processes your query, which terms contribute to scoring, how filters affect results, and where optimization opportunities exist.
564+
565+
**✨ Key Benefits:**
566+
- 🐛 **Debug** why certain documents match or don't match
567+
- 📊 **Understand** why documents are ranked in a particular order
568+
-**Optimize** slow queries by identifying expensive operations
569+
- 🎯 **Analyze** filter effectiveness and selectivity
570+
- 📈 **Learn** which query terms are most/least discriminative
571+
- 🤖 **LLM-optimized** output format for AI-assisted query tuning
572+
573+
**Parameters:**
574+
- `query` (optional): The search query (same as `search` tool)
575+
- `filters` (optional): Array of structured filters (same as `search` tool)
576+
- `page` (optional): Page number, 0-based (default: 0)
577+
- `pageSize` (optional): Results per page (default: 10, max: 100)
578+
- `analyzeFilterImpact` (optional): If `true`, analyzes how each filter reduces result count. **WARNING:** Expensive operation requiring multiple queries. Default: `false`
579+
- `analyzeDocumentScoring` (optional): If `true`, provides detailed scoring explanations for top documents using Lucene's Explanation API. **WARNING:** Expensive operation. Default: `false`
580+
- `analyzeFacetCost` (optional): If `true`, measures faceting computation overhead. **WARNING:** Expensive operation. Default: `false`
581+
- `maxDocExplanations` (optional): Maximum number of documents to explain when `analyzeDocumentScoring=true` (default: 5, max: 10)
582+
583+
**Analysis Levels:**
584+
585+
**Level 1: Fast Analysis (Always Included)**
586+
- Query structure and component breakdown
587+
- Query type identification (BooleanQuery, TermQuery, WildcardQuery, etc.)
588+
- Estimated cost per query component
589+
- Term statistics (document frequency, IDF, rarity classification)
590+
- Search metrics (total hits, filter reduction percentage)
591+
592+
**Level 2: Filter Impact Analysis (Opt-in, Expensive)**
593+
- Shows how each filter affects result count
594+
- Calculates selectivity (low/medium/high/very high)
595+
- Measures execution time per filter
596+
- Helps identify redundant or ineffective filters
597+
598+
**Level 3: Document Scoring Explanations (Opt-in, Expensive)**
599+
- Detailed score breakdown for top-ranked documents
600+
- Shows which terms contribute most to each document's score
601+
- Provides human-readable scoring summaries
602+
- Uses Lucene's Explanation API but parsed into LLM-friendly format
603+
604+
**Level 4: Facet Cost Analysis (Opt-in, Expensive)**
605+
- Measures faceting computation overhead
606+
- Shows cost per facet dimension
607+
- Helps decide if faceting should be disabled for performance
608+
609+
**Returns:**
610+
611+
A structured analysis object containing:
612+
613+
```typescript
614+
{
615+
success: boolean,
616+
queryAnalysis: {
617+
originalQuery: string,
618+
parsedQueryType: string,
619+
components: [{
620+
type: string, // "TermQuery", "WildcardQuery", etc.
621+
field: string,
622+
value: string,
623+
occur: string, // "MUST", "SHOULD", "FILTER", "MUST_NOT"
624+
estimatedCost: number,
625+
costDescription: string // "~450 documents (moderate)"
626+
}],
627+
rewrites: [{ // Query optimizations performed by Lucene
628+
original: string,
629+
rewritten: string,
630+
reason: string
631+
}],
632+
warnings: string[]
633+
},
634+
searchMetrics: {
635+
totalIndexedDocuments: number,
636+
documentsMatchingQuery: number,
637+
documentsAfterFilters: number,
638+
filterReductionPercent: number,
639+
termStatistics: {
640+
[term: string]: {
641+
term: string,
642+
documentFrequency: number,
643+
totalTermFrequency: number,
644+
idf: number,
645+
rarity: string // "very common", "common", "uncommon", "rare"
646+
}
647+
}
648+
},
649+
filterImpact?: { // Only if analyzeFilterImpact=true
650+
baselineHits: number,
651+
finalHits: number,
652+
filterImpacts: [{
653+
filter: {...},
654+
hitsBeforeFilter: number,
655+
hitsAfterFilter: number,
656+
documentsRemoved: number,
657+
reductionPercent: number,
658+
selectivity: string, // "low", "medium", "high", "very high"
659+
executionTimeMs: number
660+
}],
661+
totalExecutionTimeMs: number
662+
},
663+
documentExplanations?: [{ // Only if analyzeDocumentScoring=true
664+
filePath: string,
665+
rank: number,
666+
score: number,
667+
scoringBreakdown: {
668+
totalScore: number,
669+
components: [{
670+
term: string,
671+
field: string,
672+
contribution: number,
673+
contributionPercent: number,
674+
details: {
675+
idf: number,
676+
tf: number,
677+
termFrequency: number,
678+
documentLength: number,
679+
averageDocumentLength: number,
680+
explanation: string
681+
}
682+
}],
683+
summary: string // "Score dominated by term 'contract' (60.8%)"
684+
},
685+
matchedTerms: string[]
686+
}],
687+
facetCost?: { // Only if analyzeFacetCost=true
688+
facetingOverheadMs: number,
689+
facetingOverheadPercent: number,
690+
dimensions: {
691+
[dimension: string]: {
692+
dimension: string,
693+
uniqueValues: number,
694+
totalCount: number,
695+
computationTimeMs: number
696+
}
697+
}
698+
},
699+
recommendations: string[] // Actionable optimization suggestions
700+
}
701+
```
702+
703+
**Example: Basic Query Analysis**
704+
705+
```
706+
Ask Claude: "Profile my search for 'contract AND signed' to understand its performance"
707+
```
708+
709+
This performs fast analysis showing:
710+
- Query structure (Boolean AND query with two terms)
711+
- Term statistics (how common "contract" and "signed" are)
712+
- Cost estimates (how many documents will be examined)
713+
- Optimization recommendations
714+
715+
**Example: Deep Analysis with Scoring**
716+
717+
```
718+
{
719+
"query": "(contract OR agreement) AND signed",
720+
"filters": [
721+
{ "field": "language", "value": "en" },
722+
{ "field": "modified_date", "operator": "range", "from": "2024-01-01" }
723+
],
724+
"analyzeDocumentScoring": true,
725+
"maxDocExplanations": 3
726+
}
727+
```
728+
729+
This provides detailed scoring explanations for the top 3 documents, showing:
730+
- Which terms matched in each document
731+
- How much each term contributed to the final score
732+
- Why document A ranked higher than document B
733+
734+
**Example: Filter Optimization**
735+
736+
```
737+
{
738+
"query": "*",
739+
"filters": [
740+
{ "field": "file_extension", "value": "pdf" },
741+
{ "field": "language", "value": "en" },
742+
{ "field": "file_type", "value": "application/pdf" }
743+
],
744+
"analyzeFilterImpact": true
745+
}
746+
```
747+
748+
This analyzes filter effectiveness, potentially revealing:
749+
- `file_extension=pdf` reduces results by 75% (high selectivity)
750+
- `file_type=application/pdf` reduces results by 0% (redundant with file_extension)
751+
- Recommendation: Remove redundant `file_type` filter
752+
753+
**Performance Notes:**
754+
- **Basic analysis** (default): Very fast, negligible overhead (~5-10ms)
755+
- **Filter impact analysis**: Requires N+1 queries where N is the number of filters. Can take seconds for complex filter sets.
756+
- **Document scoring analysis**: Requires Lucene to compute full Explanation objects. Cost grows with `maxDocExplanations`.
757+
- **Facet cost analysis**: Requires facet computation. Cost depends on number of unique facet values.
758+
759+
**💡 Best Practices:**
760+
1. Start with basic analysis (no optional flags) to get quick insights
761+
2. Enable expensive analysis only when debugging specific performance issues
762+
3. Use `analyzeDocumentScoring` to understand why certain documents rank highly
763+
4. Use `analyzeFilterImpact` to optimize filter order and remove redundant filters
764+
5. Pay attention to the `recommendations` array for actionable optimization tips
765+
498766
### `getIndexStats`
499767

500768
Get statistics about the Lucene index.

0 commit comments

Comments
 (0)