|
16 | 16 |
|
17 | 17 | package com.google.adk.tools.mcp; |
18 | 18 |
|
19 | | -import static com.google.common.collect.ImmutableList.toImmutableList; |
| 19 | +import static java.util.concurrent.TimeUnit.MILLISECONDS; |
20 | 20 |
|
21 | 21 | import com.fasterxml.jackson.databind.ObjectMapper; |
22 | 22 | import com.google.adk.JsonBaseModel; |
@@ -198,73 +198,72 @@ public McpToolset(StreamableHttpServerParameters connectionParams) { |
198 | 198 |
|
199 | 199 | @Override |
200 | 200 | public Flowable<BaseTool> getTools(ReadonlyContext readonlyContext) { |
201 | | - return Flowable.fromCallable( |
| 201 | + return Flowable.defer( |
202 | 202 | () -> { |
203 | | - for (int i = 0; i < MAX_RETRIES; i++) { |
204 | | - try { |
205 | | - if (this.mcpSession == null) { |
206 | | - logger.info("MCP session is null or closed, initializing (attempt {}).", i + 1); |
207 | | - this.mcpSession = this.mcpSessionManager.createSession(); |
208 | | - } |
209 | | - |
210 | | - ListToolsResult toolsResponse = this.mcpSession.listTools(); |
211 | | - return toolsResponse.tools().stream() |
| 203 | + if (this.mcpSession == null) { |
| 204 | + logger.info("MCP session is null, initializing."); |
| 205 | + this.mcpSession = this.mcpSessionManager.createSession(); |
| 206 | + } |
| 207 | + |
| 208 | + // Retrieve tools from the MCP session, wrap them in McpTool, filter them, and return |
| 209 | + // as a Flowable. |
| 210 | + ListToolsResult toolsResponse = this.mcpSession.listTools(); |
| 211 | + return Flowable.fromStream( |
| 212 | + toolsResponse.tools().stream() |
212 | 213 | .map( |
213 | 214 | tool -> |
214 | 215 | new McpTool( |
215 | 216 | tool, this.mcpSession, this.mcpSessionManager, this.objectMapper)) |
216 | 217 | .filter( |
217 | 218 | tool -> |
218 | 219 | isToolSelected( |
219 | | - tool, toolFilter, Optional.ofNullable(readonlyContext))) |
220 | | - .collect(toImmutableList()); |
221 | | - } catch (IllegalArgumentException e) { |
222 | | - // This could happen if parameters for tool loading are somehow invalid. |
223 | | - // This is likely a fatal error and should not be retried. |
224 | | - logger.error("Invalid argument encountered during tool loading.", e); |
225 | | - throw new McpToolsetException.McpToolLoadingException( |
226 | | - "Invalid argument encountered during tool loading.", e); |
227 | | - } catch (RuntimeException e) { // Catch any other unexpected runtime exceptions |
228 | | - logger.error("Unexpected error during tool loading, retry attempt " + (i + 1), e); |
229 | | - if (i < MAX_RETRIES - 1) { |
230 | | - // For other general exceptions, we might still want to retry if they are |
231 | | - // potentially transient, or if we don't have more specific handling. But it's |
232 | | - // better to be specific. For now, we'll treat them as potentially retryable but |
233 | | - // log |
234 | | - // them at a higher level. |
235 | | - try { |
236 | | - logger.info( |
237 | | - "Reinitializing MCP session before next retry for unexpected error."); |
238 | | - this.mcpSession = this.mcpSessionManager.createSession(); |
239 | | - Thread.sleep(RETRY_DELAY_MILLIS); |
240 | | - } catch (InterruptedException ie) { |
241 | | - Thread.currentThread().interrupt(); |
242 | | - logger.error( |
243 | | - "Interrupted during retry delay for loadTools (unexpected error).", ie); |
244 | | - throw new McpToolsetException.McpToolLoadingException( |
245 | | - "Interrupted during retry delay (unexpected error)", ie); |
246 | | - } catch (RuntimeException reinitE) { |
247 | | - logger.error( |
248 | | - "Failed to reinitialize session during retry (unexpected error).", |
249 | | - reinitE); |
250 | | - throw new McpToolsetException.McpInitializationException( |
251 | | - "Failed to reinitialize session during tool loading retry (unexpected" |
252 | | - + " error).", |
253 | | - reinitE); |
254 | | - } |
255 | | - } else { |
256 | | - logger.error( |
257 | | - "Failed to load tools after multiple retries due to unexpected error.", e); |
258 | | - throw new McpToolsetException.McpToolLoadingException( |
259 | | - "Failed to load tools after multiple retries due to unexpected error.", e); |
260 | | - } |
261 | | - } |
262 | | - } |
263 | | - // This line should ideally not be reached if retries are handled correctly or an |
264 | | - // exception is always thrown. |
265 | | - throw new IllegalStateException("Unexpected state in getTools retry loop"); |
| 220 | + tool, toolFilter, Optional.ofNullable(readonlyContext)))); |
266 | 221 | }) |
267 | | - .flatMapIterable(tools -> tools); |
| 222 | + .retryWhen( |
| 223 | + errorObservable -> |
| 224 | + errorObservable.zipWith( |
| 225 | + Flowable.range(1, MAX_RETRIES), |
| 226 | + (error, retryCount) -> { |
| 227 | + if (error instanceof IllegalArgumentException) { |
| 228 | + // This could happen if parameters for tool loading are somehow invalid. |
| 229 | + // This is likely a fatal error and should not be retried. |
| 230 | + logger.error("Invalid argument encountered during tool loading.", error); |
| 231 | + throw new McpToolsetException.McpToolLoadingException( |
| 232 | + "Invalid argument encountered during tool loading.", error); |
| 233 | + } else if (error instanceof RuntimeException) { |
| 234 | + // Catch any other unexpected runtime exceptions |
| 235 | + logger.error( |
| 236 | + "Unexpected error during tool loading, retry attempt " + retryCount, |
| 237 | + error); |
| 238 | + logger.info( |
| 239 | + "Reinitializing MCP session before next retry for unexpected error."); |
| 240 | + this.mcpSession = null; |
| 241 | + |
| 242 | + if (retryCount < MAX_RETRIES) { |
| 243 | + // For other general exceptions, we might still want to retry if they are |
| 244 | + // potentially transient, or if we don't have more specific handling. But |
| 245 | + // it's better to be specific. For now, we'll treat them as potentially |
| 246 | + // retryable but log them at a higher level. |
| 247 | + |
| 248 | + // Delay before retrying |
| 249 | + return Flowable.timer(RETRY_DELAY_MILLIS, MILLISECONDS); |
| 250 | + } else { |
| 251 | + logger.error( |
| 252 | + "Failed to load tools after multiple retries due to unexpected" |
| 253 | + + " error.", |
| 254 | + error); |
| 255 | + throw new McpToolsetException.McpToolLoadingException( |
| 256 | + "Failed to load tools after multiple retries due to unexpected" |
| 257 | + + " error.", |
| 258 | + error); |
| 259 | + } |
| 260 | + } |
| 261 | + // This line should ideally not be reached if retries are handled correctly or |
| 262 | + // an exception is always thrown. |
| 263 | + // If an unhandled error type occurs, propagate it. |
| 264 | + return Flowable.error(error); |
| 265 | + })) |
| 266 | + .map(tools -> tools); |
268 | 267 | } |
269 | 268 |
|
270 | 269 | @Override |
|
0 commit comments