adityamparikh opened a new pull request, #273:
URL: https://github.com/apache/solr-mcp/pull/273

   Closes #213.
   
   Stacked on #269 (`./gradlew build` runs `buildSrc`'s tests) and #266 
(google-java-format): the first two commits here are those PRs, and only the 
top commit (`fix(mcp): report checked tool exceptions as readable tool errors`) 
is new.
   
   ## Problem
   
   #213 was written against Spring AI 1.1, which reported only the root cause's 
message. On 2.0.1 the wrapper message and the root cause both reach the client, 
so the four parse sites the issue lists already show their detail; this PR 
leaves them alone.
   
   What is still broken is the checked-exception path. Spring AI 2.0.1 turns a 
`RuntimeException` from an `@McpTool` method into an `isError` result, but 
wraps a checked exception in `UndeclaredThrowableException` and rethrows it 
(`AbstractMcpToolMethodCallback`, `SyncStatelessMcpToolMethodCallback`; 
unchanged on Spring AI `main` and `2.1.0-M1`). The MCP SDK then answers with a 
protocol error instead of a tool result:
   
   ```
   {"jsonRpcError":{"code":-32603,"message":"Failed to handle request. Check 
server logs for details."}}
   ```
   
   14 of the 15 tools declare `SolrServerException`/`IOException`, so with Solr 
unreachable the model gets no text it can act on. `SearchService`'s own "unable 
to communicate with Solr" hint was rethrown as a checked exception and never 
reached the client either.
   
   ## Changes
   
   - **`McpToolExceptionAspect`** (new, `config/`): `@Around` every `@McpTool` 
method.
     - Runtime exceptions and errors are rethrown unchanged. Spring AI already 
reports them as tool errors, and method security's `AccessDeniedException` is 
one of them.
     - Any checked exception is logged at WARN and replaced by an 
`IllegalStateException`: `<tool> failed: unable to communicate with Solr. Retry 
later; if it persists, ask the operator to check Solr availability and 
connection settings.` It has no cause, because Spring AI appends the root 
cause's message to the tool error and SolrJ's text names the internal Solr URL.
     - Spring AI has no hook for this (spring-projects/spring-ai#4488); 
`toolCallExceptionClass` is deprecated and ignored.
   - **`SearchService`**: drop the `SolrServerException`/`IOException` catches 
and `CONNECTION_ERROR`, which the aspect replaces. Correct the comments that 
said MCP reports only the root cause.
   - **`CollectionService.getCollectionStats`**: log the Solr 404 and throw the 
`list-collections` hint without it as the cause, as 
`SearchService.withRemediationHint` already does.
   
   Every checked exception a tool throws today comes from SolrJ, which is what 
the message assumes. A future tool with a non-Solr checked exception 
(index-url's HTTP fetch, #208) has to translate it before it reaches the aspect.
   
   ## Tests
   
   - `McpToolBackendUnavailableIntegrationTest` (new): HTTP server with 
`solr.url=http://127.0.0.1:1/solr/`, calling all 14 Solr-backed tools. Each 
must return `isError=true`, start with `<tool> failed`, and not contain the 
Solr address. Before the fix, all 14 fail with the `-32603` above.
   - `McpToolExceptionAspectTest` (new): the aspect in a minimal Spring context 
with fixture tools, so the proxy is generated by AOT and the test runs under 
`nativeTest` too. Covers checked → translated, runtime and 
`AccessDeniedException` → same instance, normal return, and methods without 
`@McpTool` left alone.
   - `McpClientIntegrationTestBase` (HTTP and stdio): malformed XML and invalid 
YAML front matter return their parser detail; `get-collection-stats` on an 
unknown collection returns the `list-collections` hint without `Error from 
server`.
   - `SearchServiceTest`, `SearchServiceIntegrationTest`, 
`CollectionServiceTest`: drop the checked cases from search's backend-failure 
loop, assert no cause on the not-found error, and reword the "MCP must not 
unwrap" messages.
   
   Until spring-projects/spring-ai#6949 ships, a cause-less error message 
appears twice in the tool error (spring-projects/spring-ai#6948). Assertions 
use `contains`/`startsWith`, so that fix needs no test changes.
   
   `./gradlew build`: 490 tests, 0 failures, 0 skipped. `./gradlew nativeTest 
-Pnative`: 329 successful, 0 failed, including `McpToolExceptionAspectTest` and 
`McpToolBackendUnavailableIntegrationTest`.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to