Skip to content

feat(mcp): glob-search over all modules - #8776

Open
matejcerny wants to merge 1 commit into
scalameta:mainfrom
matejcerny:glob-search
Open

feat(mcp): glob-search over all modules#8776
matejcerny wants to merge 1 commit into
scalameta:mainfrom
matejcerny:glob-search

Conversation

@matejcerny

@matejcerny matejcerny commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes the issue with the glob-search: #8769


  • glob-search and typed-glob-search now search all modules
  • the fileInFocus parameter is removed
  • results are deduplicated, ranked by relevance and capped (configurable via -Dmetals.max-mcp-search-results, default 100)
  • the response tells the caller when matches were dropped

Summary by CodeRabbit

  • New Features

    • Improved MCP symbol search with case-insensitive, name-boundary matching across workspace modules and dependencies.
    • Added typed search filtering for symbol categories, including packages.
    • Added configurable result limits, defaulting to 100, with clear partial-result indicators and warnings.
    • Simplified searches by removing the file-in-focus requirement.
  • Documentation

    • Expanded MCP search behavior, configuration, and release history documentation.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

MCP glob searches now use path-independent name matching across workspace and dependency scopes. Results are ranked, deduplicated, capped, and marked partial when searches are incomplete or truncated. Configuration, MCP interfaces, formatting, tests, and documentation were updated.

Changes

MCP search behavior

Layer / File(s) Summary
Search limit configuration and wiring
metals/src/main/scala/scala/meta/internal/metals/MetalsServerConfig.scala, metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala
Added maxMcpSearchResults with a default of 100. Shared integer-property parsing now handles related settings.
Workspace and dependency search
metals/src/main/scala/scala/meta/internal/metals/WorkspaceSymbolProvider.scala, metals/src/main/scala/scala/meta/internal/metals/mcp/McpSymbolSearch.scala
Name searches no longer use a focus path. Workspace package searches cover all build targets, while targeted searches retain dependency traversal and budget tracking.
Query normalization and MCP rendering
metals/src/main/scala/scala/meta/internal/metals/mcp/McpQueryEngine.scala, metals/src/main/scala/scala/meta/internal/metals/mcp/McpPrinter.scala, metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala
Results are ranked, deduplicated, display-sorted, capped, and returned with partial-result metadata. Both MCP tools remove fileInFocus and use formatted result output.
Behavior validation and documentation
tests/unit/src/test/scala/scala/meta/internal/metals/mcp/tests/McpGlobSearchNormalizeSuite.scala, tests/unit/src/test/scala/tests/mcp/McpGlobSearchCapLspSuite.scala, tests/unit/src/test/scala/tests/mcp/McpQueryLspSuite.scala, tests/unit/src/main/scala/tests/mcp/TestMcpStdioClient.scala, tests/slow/src/test/scala/tests/feature/McpStdioSuite.scala, project/TestGroups.scala, docs/features/mcp.md, docs/integrations/new-editor.md
Added coverage for matching, workspace and dependency searches, duplicate handling, result caps, and partial responses. Updated MCP configuration and release documentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • scalameta/metals#8323 — This PR extends the MCP documentation introduced there with search behavior and result-limit details.

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant MetalsMcpTools
  participant McpQueryEngine
  participant McpSymbolSearch
  MCPClient->>MetalsMcpTools: Send glob-search query
  MetalsMcpTools->>McpQueryEngine: Call globSearch
  McpQueryEngine->>McpSymbolSearch: Run workspace name search
  McpSymbolSearch-->>McpQueryEngine: Return results and budget state
  McpQueryEngine-->>MetalsMcpTools: Return normalized GlobSearchResult
  MetalsMcpTools-->>MCPClient: Return formatted result and partial notice
Loading

Suggested reviewers: tgodzik

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: MCP glob search now spans all modules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

"type": "string",
"description": "Substring of the symbol to search for"
},
"fileInFocus": {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed. It's radical, but I haven't found any real use-case for it. Please correct me if I'm wrong.

@matejcerny

Copy link
Copy Markdown
Contributor Author

I tried not to make overly verbose comments, but the logic is pretty complex and defined in many places.

@matejcerny
matejcerny marked this pull request as ready for review August 6, 2026 16:58

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala`:
- Around line 480-483: Update the descriptions in MetalsMcpTools.scala at lines
480-483 and 513 so the documented name-boundary matching rule explicitly applies
to symbols, while accurately describing workspace-package matching as
case-insensitive substring matching; keep both untyped and typed tool
descriptions consistent.

In `@metals/src/main/scala/scala/meta/internal/metals/MetalsServerConfig.scala`:
- Around line 195-200: Update intProperty to reject empty property values and
safely handle NumberFormatException from out-of-range digit strings, returning
default for both malformed and unparseable values while preserving valid integer
parsing.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ebbdda8-3b38-4eb7-b2ec-28f56366b434

📥 Commits

Reviewing files that changed from the base of the PR and between 0905ff7 and f87cb7a.

📒 Files selected for processing (15)
  • docs/features/mcp.md
  • docs/integrations/new-editor.md
  • metals/src/main/scala/scala/meta/internal/metals/MetalsServerConfig.scala
  • metals/src/main/scala/scala/meta/internal/metals/ProjectMetalsLspService.scala
  • metals/src/main/scala/scala/meta/internal/metals/WorkspaceSymbolProvider.scala
  • metals/src/main/scala/scala/meta/internal/metals/mcp/McpPrinter.scala
  • metals/src/main/scala/scala/meta/internal/metals/mcp/McpQueryEngine.scala
  • metals/src/main/scala/scala/meta/internal/metals/mcp/McpSymbolSearch.scala
  • metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala
  • project/TestGroups.scala
  • tests/slow/src/test/scala/tests/feature/McpStdioSuite.scala
  • tests/unit/src/main/scala/tests/mcp/TestMcpStdioClient.scala
  • tests/unit/src/test/scala/scala/meta/internal/metals/mcp/tests/McpGlobSearchNormalizeSuite.scala
  • tests/unit/src/test/scala/tests/mcp/McpGlobSearchCapLspSuite.scala
  • tests/unit/src/test/scala/tests/mcp/McpQueryLspSuite.scala

Comment on lines +480 to +483
"""|Search symbols by name across all modules and their dependency classpaths.
|Matches the last part of a fully qualified name at name boundaries, ignoring
|case, so `idWordProbe` does not find `MidWordProbe`.
|Use `inspect` for details of a result.""".stripMargin

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the workspace-package matching rule.

The descriptions claim that all matches use name boundaries. McpQueryEngine.globSearch documents that workspace packages use case-insensitive substring matching. This gives different results for packages and symbols.

  • metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala#L480-L483: State that the name-boundary rule applies to symbols, or apply it to workspace packages.
  • metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala#L513-L513: Make the typed tool description use the same corrected rule.
📍 Affects 1 file
  • metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala#L480-L483 (this comment)
  • metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala#L513-L513
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@metals/src/main/scala/scala/meta/internal/metals/mcp/MetalsMcpTools.scala`
around lines 480 - 483, Update the descriptions in MetalsMcpTools.scala at lines
480-483 and 513 so the documented name-boundary matching rule explicitly applies
to symbols, while accurately describing workspace-package matching as
case-insensitive substring matching; keep both untyped and typed tool
descriptions consistent.

Comment on lines +195 to +200
def intProperty(key: String, default: Int): Int =
sys.props
.get(key)
.filter(_.forall(Character.isDigit(_)))
.map(_.toInt)
.getOrElse(default)

@coderabbitai coderabbitai Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate MetalsServerConfig.scala:"
fd -a 'MetalsServerConfig\.scala$' . | sed 's#^\./##'

file="$(fd 'MetalsServerConfig\.scala$' . | head -n 1)"
echo "File: $file"
wc -l "$file"

echo "Outline:"
ast-grep outline "$file" --view compact || true

echo "Relevant intProperty usage/definition:"
rg -n "def intProperty|sys\.props|intProperty\(" "$file" -C 8

echo "Relevant lines:"
cat -n "$file" | sed -n '170,220p'

echo "Programmatic Scala property behavior via Scala REPL availability:"
if command -v scala >/dev/null 2>&1; then
  scala -e 'println("forall empty:", "".forall(java.lang.Character.isDigit(_: Char))); println("String#toInt fails throws NumberFormatException", true ); try { println("toInt empty:", "".toInt) } catch { case e: java.lang.NumberFormatException => println("catch NumberFormatException empty", e.getMessage) }; try { println("toInt 2147483648:", "2147483648".toInt) } catch { case e: java.lang.NumberFormatException => println("catch NumberFormatException 2147483648", e.getMessage) }'
elif command -v scalac >/dev/null 2>&1; then
  echo "scala runtime unavailable; scalac available"
else
  echo "No Scala runtime/compiler available"
fi

Repository: scalameta/metals

Length of output: 6779


🌐 Web query:

Scala String.forall empty string behavior String.toInt NumberFormatException

💡 Result:

In Scala, the behavior of String.forall and String.toInt are well-defined but often lead to confusion regarding empty strings and exception handling [1][2][3]. String.forall and Empty Strings The forall method on a String returns true if the string is empty [4][5]. This is because it follows the principle of vacuous truth in formal logic: if a collection is empty, then any predicate holds true for all of its (non-existent) elements [1][6]. If you need forall to return false for an empty string (for example, when validating that a string contains only digits), you must explicitly check for emptiness [1][2]: str.nonEmpty && str.forall(_.isDigit) String.toInt and NumberFormatException The toInt method on a String (provided by StringOps) attempts to parse the string as an integer [7][3]. If the string is not a valid representation of an integer (such as "abc" or an empty string ""), it throws a java.lang.NumberFormatException [7][3][8]. To avoid exceptions, it is recommended to use the toIntOption method (available since Scala 2.13), which returns an Option[Int]—Some(value) if successful, or None if the conversion fails [3][8]. Summary of best practices: - For validation: Always consider if you want to permit empty strings when using forall [1][2]. - For parsing: Prefer.toIntOption over.toInt to safely handle malformed input without relying on exception handling for control flow [9][8].

Citations:


Prevent malformed integer properties from failing MetalsServerConfig construction.

An empty property value passes String.forall, and any all-digit value outside the Int range also reaches toInt and throws. Reject empty strings and handle conversion failures before returning the default value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@metals/src/main/scala/scala/meta/internal/metals/MetalsServerConfig.scala`
around lines 195 - 200, Update intProperty to reject empty property values and
safely handle NumberFormatException from out-of-range digit strings, returning
default for both malformed and unparseable values while preserving valid integer
parsing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is intentional, I've just consolidated it into one method. Worth a follow-up though.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@matejcerny Understood. The consolidation preserves the existing behavior. The empty-value and integer-overflow handling can be addressed in a follow-up.

Would you like me to open a GitHub issue for that follow-up?

You are interacting with an AI system.

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.

1 participant