Skip to content

Improve MCP search intelligence - #246

Merged
kentcdodds merged 3 commits into
mainfrom
cursor/search-intelligence-plan-5025
Apr 21, 2026
Merged

kentcdodds merged 3 commits into
mainfrom
cursor/search-intelligence-plan-5025

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Apr 21, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • refactor MCP search into query understanding, candidate generation, and deterministic task-aware reranking
  • add registry-driven search intent extraction, richer result metadata, and structured search telemetry/phase timings
  • expand focused regression coverage for search ranking, formatting, handler timing, and observability behavior
  • preserve candidate-count telemetry correctly in structured search responses after review feedback

Testing

  • npm run test -- packages/worker/src/mcp/tools/search.node.test.ts packages/worker/src/mcp/tools/search-handler.node.test.ts packages/worker/src/mcp/tools/search-format.node.test.ts packages/worker/src/mcp/observability.workers.test.ts
  • npm run typecheck
  • npx oxfmt --check packages/worker/src/mcp/tools/search.ts packages/worker/src/mcp/tools/search-format.ts packages/worker/src/mcp/tools/understand-search-query.ts packages/worker/src/mcp/tools/search.node.test.ts packages/worker/src/mcp/tools/search-handler.node.test.ts packages/worker/src/mcp/tools/search-format.node.test.ts
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Search results now include entity references and performance telemetry information
    • Intelligent search query interpretation provides better understanding of user intent
    • Enhanced multi-stage search ranking system for improved result relevance and accuracy
  • Improvements

    • Refined candidate scoring mechanisms and result reranking for better search quality
    • Search telemetry now tracks query processing phases and timing metrics

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Apr 21, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@cursor[bot] has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 45 minutes and 32 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 45 minutes and 32 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d206ca5-6fac-401f-a2fd-10b63c727d24

📥 Commits

Reviewing files that changed from the base of the PR and between ec7218b and 2725c55.

📒 Files selected for processing (2)
  • packages/worker/src/mcp/tools/search.ts
  • packages/worker/src/mcp/tools/understand-search-query.ts
📝 Walkthrough

Walkthrough

A comprehensive refactor of the search system introducing a multi-phase pipeline: query understanding to extract intent/entities/constraints, candidate generation with improved scoring, and reranking with entity/action matching signals. Telemetry and entity references are now tracked throughout the pipeline, with a new module for structured query parsing.

Changes

Cohort / File(s) Summary
Query Understanding
packages/worker/src/mcp/tools/understand-search-query.ts
New module that normalizes and tokenizes search queries, infers task/action/constraint signals, and matches tokens against entity aliases to produce structured SearchIntent with confidence scores.
Search Core Pipeline
packages/worker/src/mcp/tools/search.ts
Replaced unified scoring with three-phase flow: query understanding via understandSearchQuery, candidate generation with lexical+vector base scoring, and reranking with entity/action/task-affinity signals. Added SearchScoreComponents and SearchTelemetry types; changed searchUnified return to include intent, telemetry, phaseTimings; delegated searchPackages to searchUnified.
Result Formatting & Schema
packages/worker/src/mcp/tools/search-format.ts
Extended SearchResultStructuredContent with optional telemetry (intent, candidateCounts, topResultTypes, trim indicators) and phaseTimings; added required entityRef: string to all SlimSearchMatch variants; updated markdown formatting for entity details; changed package UI calls to use open_generated_ui({ kody_id }) when available.
Test Updates
packages/worker/src/mcp/tools/search-format.node.test.ts, packages/worker/src/mcp/tools/search-handler.node.test.ts
Updated expected structured output to include entityRef fields; relaxed timing assertions in handler tests to use toMatchObject and separate non-negative duration checks.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SearchTool as Search Tool
    participant QueryParser as Query Parser
    participant CandidateGen as Candidate<br/>Generator
    participant Reranker as Reranker
    participant Formatter as Result<br/>Formatter

    User->>SearchTool: search({ query, entity })
    SearchTool->>QueryParser: understandSearchQuery(query, entities)
    QueryParser->>QueryParser: normalize & tokenize
    QueryParser->>QueryParser: match tokens against<br/>entity aliases
    QueryParser-->>SearchTool: SearchIntent<br/>(task, entities, actions)
    
    SearchTool->>CandidateGen: generate candidates<br/>(lexical + vector)
    CandidateGen->>CandidateGen: score base matches<br/>for capabilities,<br/>packages, values,<br/>connectors, secrets
    CandidateGen-->>SearchTool: scored candidates
    
    SearchTool->>Reranker: rerankCandidates<br/>(base scores + signals)
    Reranker->>Reranker: apply entity match,<br/>action match,<br/>task affinity,<br/>constraint signals
    Reranker-->>SearchTool: final ranked<br/>candidates
    
    SearchTool->>Formatter: formatSearchMarkdown<br/>(matches, intent, telemetry)
    Formatter->>Formatter: add entityRef,<br/>markdown details,<br/>telemetry/timings
    Formatter-->>SearchTool: formatted result
    
    SearchTool-->>User: { matches, intent,<br/>telemetry, phaseTimings }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Whiskers twitch with query delight,
Intent parsed, entities bright,
Candidates ranked through signals true,
Scores reweighed with task and view,
Telemetry traces each phase with care—
Search now dances through structured air! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve MCP search intelligence' directly aligns with the PR's main objective of refactoring MCP search into three stages with enhanced query understanding, candidate generation, and deterministic reranking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/search-intelligence-plan-5025

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kentcdodds
kentcdodds marked this pull request as ready for review April 21, 2026 04:43
@github-actions

github-actions Bot commented Apr 21, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-246.kentcdodds.workers.dev

Worker: kody-pr-246
D1: kody-pr-246-db
KV: kody-pr-246-oauth-kv

Mocks:

Comment thread packages/worker/src/mcp/tools/search.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ec7218b. Configure here.

]
const confidence =
confidenceInputs.reduce((sum, value) => sum + value, 0) /
confidenceInputs.length

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall confidence double-counts task signal via actions

Medium Severity

The overall confidence averages task.confidence, entities[0]?.confidence, and actions[0]?.confidence. However, actions[0]?.confidence is always identical to task.confidence because inferTaskSignals returns the strongest task as both task and as the first element of actions (sorted descending by score). This double-counts the task signal, inflating intent confidence, which in turn inflates rerankWeight in rerankCandidates and amplifies all reranking adjustments (entity match, task affinity, app availability, constraints).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ec7218b. Configure here.

Comment thread packages/worker/src/mcp/tools/search.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/worker/src/mcp/tools/search.ts (1)

877-897: ⚠️ Potential issue | 🟡 Minor

Keep app-opening help text in sync with the new kody_id guidance.

The structured search output now recommends open_generated_ui({ kody_id }), but the tool description and example still only show package_id.

📝 Proposed fix
-Open package apps with `open_generated_ui({ package_id })` or hosted package URLs.
+Open package apps with `open_generated_ui({ kody_id })`, `open_generated_ui({ package_id })`, or hosted package URLs.
@@
-- To open a saved package app: `open_generated_ui({ "package_id": "<id>" })`
+- To open a saved package app: `open_generated_ui({ "kody_id": "<kody-id>" })`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/tools/search.ts` around lines 877 - 897, Update the
help text and examples so they reference the new kody_id guidance instead of
package_id: replace occurrences of "open_generated_ui({ package_id })" and
example strings that use "package_id" with "open_generated_ui({ kody_id })" and
corresponding example JSON; ensure descriptive sentences mentioning package_id
are updated to mention kody_id as the recommended identifier (search output,
example arguments, and any sentences referencing opening a saved package app).
Verify the symbol open_generated_ui and keys kody_id/package_id in this comment
block are consistent and update all example argument lines accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/worker/src/mcp/tools/search.ts`:
- Around line 684-717: The code currently calls understandSearchQuery twice and
measures query understanding only for the non-empty path; move the creation of
emptyIntent into the if (!query) branch so you only call understandSearchQuery
once for empty queries, and for non-empty queries call understandSearchQuery
after building entityDescriptors (buildSearchableEntityDescriptors) and after
recording queryUnderstandingStart (performance.now()) so queryUnderstandingMs =
performance.now() - queryUnderstandingStart is accurate; reference the symbols
emptyIntent, understandSearchQuery, queryUnderstandingStart, performance.now(),
buildSearchableEntityDescriptors, entityDescriptors, and queryUnderstandingMs
when making this change.

In `@packages/worker/src/mcp/tools/understand-search-query.ts`:
- Around line 278-293: The issue is that the special-case match for the phrase
"set up" (when term === 'setup') only contributes one hit so queries like "set
up github" tie with the "operate" match and lose by lexicon order; in
inferTaskSignals, after computing matchedTerms for each [task, terms] entry,
detect if terms includes 'setup' and normalizedQuery.includes('set up') and if
so increment the matchedTerms count (or treat it as an extra match) before
computing score so the setup task gets a boost; update the score calculation (in
inferTaskSignals using tokenSet, matchedTerms, normalizedQuery and taskLexicon)
to use matchedTerms.length + (setupBoost ? 1 : 0) when computing score.

---

Outside diff comments:
In `@packages/worker/src/mcp/tools/search.ts`:
- Around line 877-897: Update the help text and examples so they reference the
new kody_id guidance instead of package_id: replace occurrences of
"open_generated_ui({ package_id })" and example strings that use "package_id"
with "open_generated_ui({ kody_id })" and corresponding example JSON; ensure
descriptive sentences mentioning package_id are updated to mention kody_id as
the recommended identifier (search output, example arguments, and any sentences
referencing opening a saved package app). Verify the symbol open_generated_ui
and keys kody_id/package_id in this comment block are consistent and update all
example argument lines accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 73efe349-3f70-4d72-aab3-dd8181c3725b

📥 Commits

Reviewing files that changed from the base of the PR and between d4ab497 and ec7218b.

📒 Files selected for processing (5)
  • packages/worker/src/mcp/tools/search-format.node.test.ts
  • packages/worker/src/mcp/tools/search-format.ts
  • packages/worker/src/mcp/tools/search-handler.node.test.ts
  • packages/worker/src/mcp/tools/search.ts
  • packages/worker/src/mcp/tools/understand-search-query.ts

Comment on lines +684 to +717
const offline = isCapabilitySearchOffline(input.env)
const query = input.query.trim()
const emptyIntent = understandSearchQuery({
query,
entities: [],
})
if (!query) {
return {
matches: [],
offline,
intent: emptyIntent,
telemetry: buildCandidateTelemetry({
intent: emptyIntent,
candidates: [],
matches: [],
}),
phaseTimings: {
queryUnderstandingMs: 0,
candidateGenerationMs: 0,
rerankingMs: 0,
},
}
}

const limit = Math.max(1, input.limit)
const queryUnderstandingStart = performance.now()
const entityDescriptors = buildSearchableEntityDescriptors({
registry: input.registry,
optionalRows: input.optionalRows,
})
const intent = understandSearchQuery({
query,
entities: entityDescriptors,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Move emptyIntent inside the empty-query branch.

Non-empty searches currently call understandSearchQuery twice, and the first call is outside queryUnderstandingMs, so phase timing under-reports the work.

♻️ Proposed fix
 	const offline = isCapabilitySearchOffline(input.env)
 	const query = input.query.trim()
-	const emptyIntent = understandSearchQuery({
-		query,
-		entities: [],
-	})
 	if (!query) {
+		const emptyIntent = understandSearchQuery({
+			query,
+			entities: [],
+		})
 		return {
 			matches: [],
 			offline,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const offline = isCapabilitySearchOffline(input.env)
const query = input.query.trim()
const emptyIntent = understandSearchQuery({
query,
entities: [],
})
if (!query) {
return {
matches: [],
offline,
intent: emptyIntent,
telemetry: buildCandidateTelemetry({
intent: emptyIntent,
candidates: [],
matches: [],
}),
phaseTimings: {
queryUnderstandingMs: 0,
candidateGenerationMs: 0,
rerankingMs: 0,
},
}
}
const limit = Math.max(1, input.limit)
const queryUnderstandingStart = performance.now()
const entityDescriptors = buildSearchableEntityDescriptors({
registry: input.registry,
optionalRows: input.optionalRows,
})
const intent = understandSearchQuery({
query,
entities: entityDescriptors,
})
const offline = isCapabilitySearchOffline(input.env)
const query = input.query.trim()
if (!query) {
const emptyIntent = understandSearchQuery({
query,
entities: [],
})
return {
matches: [],
offline,
intent: emptyIntent,
telemetry: buildCandidateTelemetry({
intent: emptyIntent,
candidates: [],
matches: [],
}),
phaseTimings: {
queryUnderstandingMs: 0,
candidateGenerationMs: 0,
rerankingMs: 0,
},
}
}
const limit = Math.max(1, input.limit)
const queryUnderstandingStart = performance.now()
const entityDescriptors = buildSearchableEntityDescriptors({
registry: input.registry,
optionalRows: input.optionalRows,
})
const intent = understandSearchQuery({
query,
entities: entityDescriptors,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/tools/search.ts` around lines 684 - 717, The code
currently calls understandSearchQuery twice and measures query understanding
only for the non-empty path; move the creation of emptyIntent into the if
(!query) branch so you only call understandSearchQuery once for empty queries,
and for non-empty queries call understandSearchQuery after building
entityDescriptors (buildSearchableEntityDescriptors) and after recording
queryUnderstandingStart (performance.now()) so queryUnderstandingMs =
performance.now() - queryUnderstandingStart is accurate; reference the symbols
emptyIntent, understandSearchQuery, queryUnderstandingStart, performance.now(),
buildSearchableEntityDescriptors, entityDescriptors, and queryUnderstandingMs
when making this change.

Comment thread packages/worker/src/mcp/tools/understand-search-query.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds merged commit ec920ef into main Apr 21, 2026
9 checks passed
@kentcdodds
kentcdodds deleted the cursor/search-intelligence-plan-5025 branch April 21, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants