11import { EventEmitter } from "events" ;
2+ import { MongoServerError } from "mongodb" ;
23import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver" ;
34import { generateConnectionInfoFromCliArgs , type ConnectionInfo } from "@mongosh/arg-parser" ;
45import type { DeviceId } from "../helpers/deviceId.js" ;
@@ -29,7 +30,10 @@ export interface ConnectionState {
2930 connectedAtlasCluster ?: AtlasClusterConnectionInfo ;
3031}
3132
32- const MCP_TEST_DATABASE = "#mongodb-mcp" ;
33+ const SEARCH_PROBE_COLLECTION_NAME = "test" ;
34+
35+ /** See https://github.com/mongodb/mongo/blob/master/src/mongo/base/error_codes.yml (SearchNotEnabled). */
36+ const MONGODB_SEARCH_NOT_ENABLED_ERROR_CODE = 31082 ;
3337
3438export const defaultDriverOptions : ConnectionInfo [ "driverOptions" ] = {
3539 readConcern : {
@@ -55,21 +59,104 @@ export class ConnectionStateConnected implements ConnectionState {
5559
5660 private _isSearchSupported ?: boolean ;
5761
58- public async isSearchSupported ( ) : Promise < boolean > {
62+ public async isSearchSupported ( logger : LoggerBase ) : Promise < boolean > {
5963 if ( this . _isSearchSupported === undefined ) {
64+ this . _isSearchSupported = await this . probeSearchCapability ( logger ) ;
65+ }
66+
67+ return this . _isSearchSupported ;
68+ }
69+
70+ private async probeSearchCapability ( logger : LoggerBase ) : Promise < boolean > {
71+ const databases = await this . buildSearchProbeDatabaseCandidates ( logger ) ;
72+
73+ for ( const databaseName of databases ) {
6074 try {
61- // If a cluster supports search indexes, the call below will succeed
62- // with a cursor otherwise will throw an Error.
63- // the Search Index Management Service might not be ready yet, but
64- // we assume that the agent can retry in that situation.
65- await this . serviceProvider . getSearchIndexes ( MCP_TEST_DATABASE , "test" ) ;
66- this . _isSearchSupported = true ;
67- } catch {
68- this . _isSearchSupported = false ;
75+ await this . serviceProvider . getSearchIndexes ( databaseName , SEARCH_PROBE_COLLECTION_NAME ) ;
76+ logger . debug ( {
77+ id : LogId . searchCapabilityProbe ,
78+ context : "ConnectionStateConnected" ,
79+ message : "Atlas Search capability probe succeeded" ,
80+ } ) ;
81+ return true ;
82+ } catch ( probeError : unknown ) {
83+ if (
84+ probeError instanceof MongoServerError &&
85+ ( probeError . code === MONGODB_SEARCH_NOT_ENABLED_ERROR_CODE ||
86+ probeError . codeName === "SearchNotEnabled" )
87+ ) {
88+ logger . debug ( {
89+ id : LogId . searchCapabilityProbe ,
90+ context : "ConnectionStateConnected" ,
91+ message : "Atlas Search capability probe: search not enabled on cluster" ,
92+ } ) ;
93+
94+ return false ;
95+ }
96+
97+ logger . debug ( {
98+ id : LogId . searchCapabilityProbe ,
99+ context : "ConnectionStateConnected" ,
100+ message : "Atlas Search capability probe: inconclusive error for database candidate, trying next" ,
101+ } ) ;
69102 }
70103 }
71104
72- return this . _isSearchSupported ;
105+ logger . debug ( {
106+ id : LogId . searchCapabilityProbe ,
107+ context : "ConnectionStateConnected" ,
108+ message : "Atlas Search capability probe: no success and no SearchNotEnabled; assuming search is supported" ,
109+ } ) ;
110+
111+ return true ;
112+ }
113+
114+ /**
115+ * Build an ordered list of database names to try for the search index probe.
116+ * Prefers the driver's initial database from the connection string (when not
117+ * a system DB), then other non-system databases from listDatabases, then the
118+ * fallback #mongodb-mcp database.
119+ */
120+ private async buildSearchProbeDatabaseCandidates ( logger : LoggerBase ) : Promise < string [ ] > {
121+ type ListDatabasesDocument = { databases ?: { name ?: string } [ ] } ;
122+ let listedNames : string [ ] = [ ] ;
123+ try {
124+ const raw = ( await this . serviceProvider . listDatabases ( "" ) ) as ListDatabasesDocument ;
125+ const rows = raw . databases ;
126+ if ( Array . isArray ( rows ) ) {
127+ listedNames = rows
128+ . map ( ( row ) => row . name )
129+ . filter ( ( name ) : name is string => typeof name === "string" && name . length > 0 ) ;
130+ }
131+ } catch {
132+ logger . debug ( {
133+ id : LogId . searchCapabilityProbe ,
134+ context : "ConnectionStateConnected" ,
135+ message : "listDatabases failed while building Atlas Search probe candidates" ,
136+ } ) ;
137+ }
138+
139+ // System databases that should be skipped when searching for accessible databases
140+ const SYSTEM_DATABASES = new Set ( [ "admin" , "local" , "config" ] ) ;
141+
142+ const nonSystem = listedNames
143+ . filter ( ( name ) => ! SYSTEM_DATABASES . has ( name ) )
144+ . slice ( 0 , 10 )
145+ . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
146+
147+ const result = new Set < string > ( ) ;
148+ const initialDb = this . serviceProvider . initialDb ;
149+ if ( initialDb . length > 0 && ! SYSTEM_DATABASES . has ( initialDb ) ) {
150+ result . add ( initialDb ) ;
151+ }
152+
153+ for ( const name of nonSystem ) {
154+ result . add ( name ) ;
155+ }
156+
157+ result . add ( "#mongodb-mcp" ) ;
158+
159+ return [ ...result ] ;
73160 }
74161}
75162
0 commit comments