@@ -2,13 +2,21 @@ import { z } from "zod";
22import { AtlasToolBase } from "../atlasTool.js" ;
33import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js" ;
44import type { OperationType , ToolArgs } from "../../tool.js" ;
5+ import { formatUntrustedData } from "../../tool.js" ;
56import {
67 getSuggestedIndexes ,
78 getDropIndexSuggestions ,
89 getSchemaAdvice ,
910 getSlowQueries ,
1011 PerformanceAdvisorOperation ,
1112 type PerformanceAdvisorData ,
13+ type SuggestedIndex ,
14+ type DropIndexSuggestion ,
15+ type SlowQueryLog ,
16+ type SchemaRecommendation ,
17+ SCHEMA_RECOMMENDATION_DESCRIPTIONS ,
18+ SCHEMA_TRIGGER_DESCRIPTIONS ,
19+ type SchemaTriggerType ,
1220} from "../../../common/atlas/performanceAdvisorUtils.js" ;
1321
1422export class ListPerformanceAdvisorTool extends AtlasToolBase {
@@ -20,12 +28,120 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
2028 clusterName : z . string ( ) . describe ( "Atlas cluster name to list performance advisor recommendations" ) ,
2129 operations : z
2230 . array ( z . nativeEnum ( PerformanceAdvisorOperation ) )
31+ . default ( Object . values ( PerformanceAdvisorOperation ) )
2332 . describe ( "Operations to list performance advisor recommendations" ) ,
24- since : z . number ( ) . describe ( "Date to list slow query logs since" ) . optional ( ) ,
33+ since : z . date ( ) . describe ( "Date to list slow query logs since" ) . optional ( ) ,
2534 processId : z . string ( ) . describe ( "Process ID to list slow query logs" ) . optional ( ) ,
2635 namespaces : z . array ( z . string ( ) ) . describe ( "Namespaces to list slow query logs" ) . optional ( ) ,
2736 } ;
2837
38+ private formatSuggestedIndexesTable ( suggestedIndexes : Array < SuggestedIndex > ) : string {
39+ if ( suggestedIndexes . length === 0 ) return "No suggested indexes found." ;
40+
41+ const rows = suggestedIndexes
42+ . map ( ( index , i ) => {
43+ const namespace = index . namespace ?? "N/A" ;
44+ const weight = index . weight ?? "N/A" ;
45+ const avgObjSize = index . avgObjSize ?? "N/A" ;
46+ const indexKeys = index . index ? index . index . map ( ( key ) => Object . keys ( key ) [ 0 ] ) . join ( ", " ) : "N/A" ;
47+ return `${ i + 1 } | ${ namespace } | ${ weight } | ${ avgObjSize } | ${ indexKeys } ` ;
48+ } )
49+ . join ( "\n" ) ;
50+
51+ return `# | Namespace | Weight | Avg Obj Size | Index Keys
52+ ---|-----------|--------|--------------|------------
53+ ${ rows } `;
54+ }
55+
56+ private formatDropIndexesTable ( dropIndexSuggestions : {
57+ hiddenIndexes : Array < DropIndexSuggestion > ;
58+ redundantIndexes : Array < DropIndexSuggestion > ;
59+ unusedIndexes : Array < DropIndexSuggestion > ;
60+ } ) : string {
61+ const allIndexes = [
62+ ...dropIndexSuggestions . hiddenIndexes . map ( ( idx ) => ( { ...idx , type : "Hidden" } ) ) ,
63+ ...dropIndexSuggestions . redundantIndexes . map ( ( idx ) => ( { ...idx , type : "Redundant" } ) ) ,
64+ ...dropIndexSuggestions . unusedIndexes . map ( ( idx ) => ( { ...idx , type : "Unused" } ) ) ,
65+ ] ;
66+
67+ if ( allIndexes . length === 0 ) return "No drop index suggestions found." ;
68+
69+ const rows = allIndexes
70+ . map ( ( index , i ) => {
71+ const name = index . name ?? "N/A" ;
72+ const namespace = index . namespace ?? "N/A" ;
73+ const type = index . type ?? "N/A" ;
74+ const sizeBytes = index . sizeBytes ?? "N/A" ;
75+ const accessCount = index . accessCount ?? "N/A" ;
76+ return `${ i + 1 } | ${ name } | ${ namespace } | ${ type } | ${ sizeBytes } | ${ accessCount } ` ;
77+ } )
78+ . join ( "\n" ) ;
79+
80+ return `# | Index Name | Namespace | Type | Size (bytes) | Access Count
81+ ---|------------|-----------|------|--------------|-------------
82+ ${ rows } `;
83+ }
84+
85+ private formatSlowQueriesTable ( slowQueryLogs : Array < SlowQueryLog > ) : string {
86+ if ( slowQueryLogs . length === 0 ) return "No slow query logs found." ;
87+
88+ const rows = slowQueryLogs
89+ . map ( ( log , i ) => {
90+ const namespace = log . namespace ?? "N/A" ;
91+ const opType = log . opType ?? "N/A" ;
92+ const executionTime = log . metrics ?. operationExecutionTime ?? "N/A" ;
93+ const docsExamined = log . metrics ?. docsExamined ?? "N/A" ;
94+ const docsReturned = log . metrics ?. docsReturned ?? "N/A" ;
95+ return `${ i + 1 } | ${ namespace } | ${ opType } | ${ executionTime } ms | ${ docsExamined } | ${ docsReturned } ` ;
96+ } )
97+ . join ( "\n" ) ;
98+
99+ return `# | Namespace | Operation | Execution Time | Docs Examined | Docs Returned
100+ ---|-----------|-----------|---------------|---------------|---------------
101+ ${ rows } `;
102+ }
103+
104+ private getTriggerDescription ( triggerType : SchemaTriggerType | undefined ) : string {
105+ if ( ! triggerType ) return "N/A" ;
106+ return SCHEMA_TRIGGER_DESCRIPTIONS [ triggerType ] ?? triggerType ;
107+ }
108+
109+ private getNamespaceTriggerDescriptions ( namespace : {
110+ triggers ?: Array < { triggerType ?: SchemaTriggerType } > ;
111+ } ) : string {
112+ if ( ! namespace . triggers ) return "N/A" ;
113+
114+ return namespace . triggers . map ( ( trigger ) => this . getTriggerDescription ( trigger . triggerType ) ) . join ( ", " ) ;
115+ }
116+
117+ private getTriggerDescriptions ( suggestion : SchemaRecommendation ) : string {
118+ if ( ! suggestion . affectedNamespaces ) return "N/A" ;
119+
120+ return suggestion . affectedNamespaces
121+ . map ( ( namespace ) => this . getNamespaceTriggerDescriptions ( namespace ) )
122+ . join ( ", " ) ;
123+ }
124+
125+ private formatSchemaSuggestionsTable ( schemaSuggestions : Array < SchemaRecommendation > ) : string {
126+ if ( schemaSuggestions . length === 0 ) return "No schema suggestions found." ;
127+
128+ const rows = schemaSuggestions
129+ . map ( ( suggestion : SchemaRecommendation , i ) => {
130+ const recommendation = suggestion . recommendation
131+ ? ( SCHEMA_RECOMMENDATION_DESCRIPTIONS [ suggestion . recommendation ] ?? suggestion . recommendation )
132+ : "N/A" ;
133+ const description = suggestion . description ?? "N/A" ;
134+ const triggeredBy = this . getTriggerDescriptions ( suggestion ) ;
135+ const affectedNamespaces = suggestion . affectedNamespaces ?. length ?? 0 ;
136+ return `${ i + 1 } | ${ recommendation } | ${ description } | ${ triggeredBy } | ${ affectedNamespaces } namespaces` ;
137+ } )
138+ . join ( "\n" ) ;
139+
140+ return `# | Recommendation | Description | Triggered By | Affected Namespaces
141+ ---|---------------|-------------|----------|-------------------
142+ ${ rows } `;
143+ }
144+
29145 protected async execute ( {
30146 projectId,
31147 clusterName,
@@ -41,41 +157,46 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
41157 schemaSuggestions : [ ] ,
42158 } ;
43159
44- // If operations is empty, get all performance advisor recommendations
45- // Otherwise, get only the specified operations
46- const operationsToExecute = operations . length === 0 ? Object . values ( PerformanceAdvisorOperation ) : operations ;
47-
48160 try {
49- if ( operationsToExecute . includes ( PerformanceAdvisorOperation . SUGGESTED_INDEXES ) ) {
50- const { suggestedIndexes } = await getSuggestedIndexes ( this . session . apiClient , projectId , clusterName ) ;
51- data . suggestedIndexes = suggestedIndexes ;
161+ const performanceAdvisorPromises = [ ] ;
162+
163+ if ( operations . includes ( PerformanceAdvisorOperation . SUGGESTED_INDEXES ) ) {
164+ performanceAdvisorPromises . push (
165+ getSuggestedIndexes ( this . session . apiClient , projectId , clusterName ) . then ( ( { suggestedIndexes } ) => {
166+ data . suggestedIndexes = suggestedIndexes ;
167+ } )
168+ ) ;
52169 }
53170
54- if ( operationsToExecute . includes ( PerformanceAdvisorOperation . DROP_INDEX_SUGGESTIONS ) ) {
55- const { hiddenIndexes, redundantIndexes, unusedIndexes } = await getDropIndexSuggestions (
56- this . session . apiClient ,
57- projectId ,
58- clusterName
171+ if ( operations . includes ( PerformanceAdvisorOperation . DROP_INDEX_SUGGESTIONS ) ) {
172+ performanceAdvisorPromises . push (
173+ getDropIndexSuggestions ( this . session . apiClient , projectId , clusterName ) . then (
174+ ( { hiddenIndexes, redundantIndexes, unusedIndexes } ) => {
175+ data . dropIndexSuggestions = { hiddenIndexes, redundantIndexes, unusedIndexes } ;
176+ }
177+ )
59178 ) ;
60- data . dropIndexSuggestions = { hiddenIndexes, redundantIndexes, unusedIndexes } ;
61179 }
62180
63- if ( operationsToExecute . includes ( PerformanceAdvisorOperation . SLOW_QUERY_LOGS ) ) {
64- const { slowQueryLogs } = await getSlowQueries (
65- this . session . apiClient ,
66- projectId ,
67- clusterName ,
68- since ,
69- processId ,
70- namespaces
181+ if ( operations . includes ( PerformanceAdvisorOperation . SLOW_QUERY_LOGS ) ) {
182+ performanceAdvisorPromises . push (
183+ getSlowQueries ( this . session . apiClient , projectId , clusterName , since , processId , namespaces ) . then (
184+ ( { slowQueryLogs } ) => {
185+ data . slowQueryLogs = slowQueryLogs ;
186+ }
187+ )
71188 ) ;
72- data . slowQueryLogs = slowQueryLogs ;
73189 }
74190
75- if ( operationsToExecute . includes ( PerformanceAdvisorOperation . SCHEMA_SUGGESTIONS ) ) {
76- const { recommendations } = await getSchemaAdvice ( this . session . apiClient , projectId , clusterName ) ;
77- data . schemaSuggestions = recommendations ;
191+ if ( operations . includes ( PerformanceAdvisorOperation . SCHEMA_SUGGESTIONS ) ) {
192+ performanceAdvisorPromises . push (
193+ getSchemaAdvice ( this . session . apiClient , projectId , clusterName ) . then ( ( { recommendations } ) => {
194+ data . schemaSuggestions = recommendations ;
195+ } )
196+ ) ;
78197 }
198+
199+ await Promise . all ( performanceAdvisorPromises ) ;
79200 } catch ( error ) {
80201 return {
81202 content : [
@@ -87,8 +208,49 @@ export class ListPerformanceAdvisorTool extends AtlasToolBase {
87208 } ;
88209 }
89210
211+ // Format the data as tables
212+ let formattedOutput = "" ;
213+ let totalItems = 0 ;
214+
215+ if ( data . suggestedIndexes . length > 0 ) {
216+ const suggestedIndexesTable = this . formatSuggestedIndexesTable ( data . suggestedIndexes ) ;
217+ formattedOutput += `\n## Suggested Indexes\n${ suggestedIndexesTable } \n` ;
218+ totalItems += data . suggestedIndexes . length ;
219+ }
220+
221+ if (
222+ data . dropIndexSuggestions . hiddenIndexes . length > 0 ||
223+ data . dropIndexSuggestions . redundantIndexes . length > 0 ||
224+ data . dropIndexSuggestions . unusedIndexes . length > 0
225+ ) {
226+ const dropIndexesTable = this . formatDropIndexesTable ( data . dropIndexSuggestions ) ;
227+ formattedOutput += `\n## Drop Index Suggestions\n${ dropIndexesTable } \n` ;
228+ totalItems +=
229+ data . dropIndexSuggestions . hiddenIndexes . length +
230+ data . dropIndexSuggestions . redundantIndexes . length +
231+ data . dropIndexSuggestions . unusedIndexes . length ;
232+ }
233+
234+ if ( data . slowQueryLogs . length > 0 ) {
235+ const slowQueriesTable = this . formatSlowQueriesTable ( data . slowQueryLogs ) ;
236+ formattedOutput += `\n## Slow Query Logs\n${ slowQueriesTable } \n` ;
237+ totalItems += data . slowQueryLogs . length ;
238+ }
239+
240+ if ( data . schemaSuggestions . length > 0 ) {
241+ const schemaTable = this . formatSchemaSuggestionsTable ( data . schemaSuggestions ) ;
242+ formattedOutput += `\n## Schema Suggestions\n${ schemaTable } \n` ;
243+ totalItems += data . schemaSuggestions . length ;
244+ }
245+
246+ if ( totalItems === 0 ) {
247+ return {
248+ content : [ { type : "text" , text : "No performance advisor recommendations found." } ] ,
249+ } ;
250+ }
251+
90252 return {
91- content : [ { type : "text" , text : JSON . stringify ( data , null , 2 ) } ] ,
253+ content : formatUntrustedData ( `Found ${ totalItems } performance advisor recommendations` , formattedOutput ) ,
92254 } ;
93255 }
94256}
0 commit comments