1515import java .util .Map ;
1616import java .util .Set ;
1717import lombok .extern .slf4j .Slf4j ;
18+ import org .openmetadata .mcp .util .McpParams ;
19+ import org .openmetadata .mcp .util .McpResponseTrim ;
1820import org .openmetadata .schema .api .lineage .LineageDirection ;
1921import org .openmetadata .schema .api .lineage .SearchLineageRequest ;
2022import org .openmetadata .schema .api .lineage .SearchLineageResult ;
@@ -36,13 +38,10 @@ public class RootCauseAnalysisTool implements McpTool {
3638
3739 private static final int DEFAULT_DEPTH = 3 ;
3840 private static final int MAX_DEPTH = 10 ;
39- // Slimming budgets mirror GetLineageTool so RCA's lineage-derived payload stays within
41+ // Slimming budgets come from McpResponseTrim so RCA's lineage-derived payload stays within
4042 // LLM/MCP context limits. The backend (searchDataQualityLineage / searchLineageWithDirection)
4143 // is shared with the UI LineageResource and is never touched — we only transform the
4244 // in-memory result before returning it to the MCP client.
43- private static final int SQL_MAX_LENGTH = 500 ;
44- private static final int TEXT_MAX_LENGTH = 500 ;
45- private static final int MAX_RESPONSE_CHARS = 100_000 ;
4645 private static final String RELATIONSHIP_SQL = "sql" ;
4746
4847 @ Override
@@ -52,12 +51,12 @@ public Map<String, Object> execute(
5251 Map <String , Object > parameters ) {
5352 String fqn = (String ) parameters .get ("fqn" );
5453 String entityType = (String ) parameters .getOrDefault ("entityType" , "table" );
55- int upstreamDepth = clampDepth (parseIntParam ( parameters . get ( "upstreamDepth" ) , DEFAULT_DEPTH ));
54+ int upstreamDepth = clampDepth (McpParams . getInt ( parameters , "upstreamDepth" , DEFAULT_DEPTH ));
5655 int downstreamDepth =
57- clampDepth (parseIntParam ( parameters . get ( "downstreamDepth" ) , DEFAULT_DEPTH ));
56+ clampDepth (McpParams . getInt ( parameters , "downstreamDepth" , DEFAULT_DEPTH ));
5857 String queryFilter = (String ) parameters .get ("queryFilter" );
59- boolean includeDeleted = parseBooleanParam ( parameters . get ( "includeDeleted" ) , false );
60- boolean includeColumns = parseBooleanParam ( parameters . get ( "includeColumnLineage" ) , false );
58+ boolean includeDeleted = McpParams . getBoolean ( parameters , "includeDeleted" , false );
59+ boolean includeColumns = McpParams . getBoolean ( parameters , "includeColumnLineage" , false );
6160
6261 if (fqn == null || fqn .trim ().isEmpty ()) {
6362 throw new IllegalArgumentException ("Parameter 'fqn' is required and cannot be empty" );
@@ -81,11 +80,12 @@ public Map<String, Object> execute(
8180 return analyze (request );
8281 } catch (IOException e ) {
8382 LOG .error ("IOException during root cause analysis for entity: {}" , fqn , e );
84- throw new RuntimeException ("Failed to perform root cause analysis: " + e .getMessage (), e );
83+ throw new RuntimeException (
84+ "Failed to perform root cause analysis: " + McpResponseTrim .safeMessage (e ), e );
8585 } catch (Exception e ) {
8686 LOG .error ("Unexpected error during root cause analysis for entity: {}" , fqn , e );
8787 throw new RuntimeException (
88- "Unexpected error during root cause analysis: " + e . getMessage ( ), e );
88+ "Unexpected error during root cause analysis: " + McpResponseTrim . safeMessage ( e ), e );
8989 }
9090 }
9191
@@ -172,7 +172,8 @@ private Map<String, Object> buildDownstreamAnalysis(RcaRequest request) {
172172 addDownstreamEdges (downstreamAnalysis , downstreamResult , request .includeColumns ());
173173 } catch (Exception e ) {
174174 LOG .warn ("Failed to perform downstream impact analysis for entity: {}" , request .fqn (), e );
175- downstreamAnalysis .put ("error" , "Failed to analyze downstream impact: " + e .getMessage ());
175+ downstreamAnalysis .put (
176+ "error" , "Failed to analyze downstream impact: " + McpResponseTrim .safeMessage (e ));
176177 }
177178 return downstreamAnalysis ;
178179 }
@@ -315,34 +316,30 @@ private static String relationshipType(Object pipeline) {
315316
316317 private static void applyDescription (Map <String , Object > slim , Object description ) {
317318 if (description instanceof String text && !text .isEmpty ()) {
318- slim .put ("description" , truncate (text , TEXT_MAX_LENGTH ));
319+ slim .put ("description" , McpResponseTrim . truncate (text , McpResponseTrim . TEXT_MAX_LENGTH ));
319320 }
320321 }
321322
322323 private static void applySqlQuery (Map <String , Object > slim , Object sqlQuery ) {
323324 if (sqlQuery instanceof String sql && !sql .isEmpty ()) {
324- slim .put ("sqlQuery" , truncate (sql , SQL_MAX_LENGTH ));
325- if (sql .length () > SQL_MAX_LENGTH ) {
325+ slim .put ("sqlQuery" , McpResponseTrim . truncate (sql , McpResponseTrim . SQL_MAX_LENGTH ));
326+ if (sql .length () > McpResponseTrim . SQL_MAX_LENGTH ) {
326327 slim .put ("sqlTruncated" , Boolean .TRUE );
327328 }
328329 }
329330 }
330331
331332 private static void truncateDescriptionInPlace (Map <String , Object > map ) {
332333 Object description = map .get ("description" );
333- if (description instanceof String text && text .length () > TEXT_MAX_LENGTH ) {
334- map .put ("description" , truncate (text , TEXT_MAX_LENGTH ));
334+ if (description instanceof String text && text .length () > McpResponseTrim . TEXT_MAX_LENGTH ) {
335+ map .put ("description" , McpResponseTrim . truncate (text , McpResponseTrim . TEXT_MAX_LENGTH ));
335336 }
336337 }
337338
338- private static String truncate (String value , int maxLength ) {
339- return value .length () > maxLength ? value .substring (0 , maxLength ) + "..." : value ;
340- }
341-
342339 @ VisibleForTesting
343340 static Map <String , Object > enforceSizeBudget (Map <String , Object > result ) {
344341 Map <String , Object > output = result ;
345- if (JsonUtils . pojoToJson (result ). length () > MAX_RESPONSE_CHARS ) {
342+ if (McpResponseTrim . serializedLength (result ) > McpResponseTrim . MAX_RESPONSE_CHARS ) {
346343 output = oversizedHint (result );
347344 }
348345 return output ;
@@ -417,36 +414,6 @@ private static void putIfPresent(Map<String, Object> map, String key, Object val
417414 }
418415 }
419416
420- private static int parseIntParam (Object value , int defaultValue ) {
421- if (value == null ) {
422- return defaultValue ;
423- }
424- if (value instanceof Number number ) {
425- return number .intValue ();
426- }
427- if (value instanceof String string ) {
428- try {
429- return Integer .parseInt (string );
430- } catch (NumberFormatException e ) {
431- return defaultValue ;
432- }
433- }
434- return defaultValue ;
435- }
436-
437- private static boolean parseBooleanParam (Object value , boolean defaultValue ) {
438- if (value == null ) {
439- return defaultValue ;
440- }
441- if (value instanceof Boolean bool ) {
442- return bool ;
443- }
444- if (value instanceof String string ) {
445- return "true" .equalsIgnoreCase (string );
446- }
447- return defaultValue ;
448- }
449-
450417 @ Override
451418 public Map <String , Object > execute (
452419 Authorizer authorizer ,
0 commit comments