11import { EventEmitter } from "events" ;
2- import type { MongoClientOptions } from "mongodb" ;
2+ import type { MongoClientOptions , MongoServerError } from "mongodb" ;
33import { ConnectionString } from "mongodb-connection-string-url" ;
44import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver" ;
55import { generateConnectionInfoFromCliArgs , type ConnectionInfo } from "@mongosh/arg-parser" ;
@@ -33,6 +33,8 @@ export interface ConnectionState {
3333}
3434
3535const MCP_TEST_DATABASE = "#mongodb-mcp" ;
36+ const MCP_TEST_VECTOR_SEARCH_INDEX_FIELD = "__mongodb-mcp-field" ;
37+ const MCP_TEST_VECTOR_SEARCH_INDEX_NAME = "mongodb-mcp-vector-search-index" ;
3638
3739export const defaultDriverOptions : ConnectionInfo [ "driverOptions" ] = {
3840 readConcern : {
@@ -56,23 +58,72 @@ export class ConnectionStateConnected implements ConnectionState {
5658 public connectedAtlasCluster ?: AtlasClusterConnectionInfo
5759 ) { }
5860
59- private _isSearchSupported ?: boolean ;
61+ private connectionMetadata ?: { searchSupported : boolean ; autoEmbeddingIndexSupported : boolean } ;
6062
61- public async isSearchSupported ( ) : Promise < boolean > {
62- if ( this . _isSearchSupported === undefined ) {
63- try {
64- // If a cluster supports search indexes, the call below will succeed
65- // with a cursor otherwise will throw an Error.
66- // the Search Index Management Service might not be ready yet, but
67- // we assume that the agent can retry in that situation.
68- await this . serviceProvider . getSearchIndexes ( MCP_TEST_DATABASE , "test" ) ;
69- this . _isSearchSupported = true ;
70- } catch {
71- this . _isSearchSupported = false ;
63+ private async populateConnectionMetadata ( ) : Promise < void > {
64+ try {
65+ await this . serviceProvider . createCollection ( MCP_TEST_DATABASE , "test" ) ;
66+ // If a cluster supports search indexes, the call below will succeed
67+ // with a cursor otherwise will throw an Error. The Search Index
68+ // Management Service might not be ready yet, but we assume that the
69+ // agent can retry in that situation.
70+ await this . serviceProvider . createSearchIndexes ( MCP_TEST_DATABASE , "test" , [
71+ {
72+ name : MCP_TEST_VECTOR_SEARCH_INDEX_NAME ,
73+ type : "vectorSearch" ,
74+ definition : {
75+ fields : [
76+ {
77+ // TODO: Before public preview this needs to be
78+ // changed to either "autoEmbedText" or "autoEmbed"
79+ // depending on which syntax is finalized.
80+ type : "text" ,
81+ path : MCP_TEST_VECTOR_SEARCH_INDEX_FIELD ,
82+ model : "voyage-3-large" ,
83+ } ,
84+ ] ,
85+ } ,
86+ } ,
87+ ] ) ;
88+ this . connectionMetadata = {
89+ searchSupported : true ,
90+ autoEmbeddingIndexSupported : true ,
91+ } ;
92+ } catch ( error ) {
93+ if ( ( error as MongoServerError ) . codeName === "SearchNotEnabled" ) {
94+ this . connectionMetadata = {
95+ searchSupported : false ,
96+ autoEmbeddingIndexSupported : false ,
97+ } ;
7298 }
99+ // If the error if because we tried creating an index with autoEmbed
100+ // field definition then we can safely assume that search is
101+ // supported but auto-embeddings are not.
102+ else if ( ( error as Error ) . message . includes ( '"userCommand.indexes[0].fields[0].type" must be one of' ) ) {
103+ this . connectionMetadata = {
104+ searchSupported : true ,
105+ autoEmbeddingIndexSupported : false ,
106+ } ;
107+ }
108+ } finally {
109+ await this . serviceProvider . dropDatabase ( MCP_TEST_DATABASE ) ;
110+ }
111+ }
112+
113+ public async isSearchSupported ( ) : Promise < boolean > {
114+ if ( this . connectionMetadata === undefined ) {
115+ await this . populateConnectionMetadata ( ) ;
116+ }
117+
118+ return this . connectionMetadata ?. searchSupported ?? false ;
119+ }
120+
121+ public async isAutoEmbeddingIndexSupported ( ) : Promise < boolean > {
122+ if ( this . connectionMetadata === undefined ) {
123+ await this . populateConnectionMetadata ( ) ;
73124 }
74125
75- return this . _isSearchSupported ;
126+ return this . connectionMetadata ?. autoEmbeddingIndexSupported ?? false ;
76127 }
77128}
78129
0 commit comments