Skip to content

Commit 0f6c707

Browse files
chore: identify if autoembeddings are supported or not
1 parent cced0c7 commit 0f6c707

File tree

6 files changed

+88
-21
lines changed

6 files changed

+88
-21
lines changed

src/common/connectionManager.ts

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EventEmitter } from "events";
2-
import type { MongoClientOptions } from "mongodb";
2+
import type { MongoClientOptions, MongoServerError } from "mongodb";
33
import { ConnectionString } from "mongodb-connection-string-url";
44
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
55
import { generateConnectionInfoFromCliArgs, type ConnectionInfo } from "@mongosh/arg-parser";
@@ -33,6 +33,8 @@ export interface ConnectionState {
3333
}
3434

3535
const 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

3739
export 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

src/common/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export enum ErrorCodes {
88
AtlasVectorSearchIndexNotFound = 1_000_006,
99
AtlasVectorSearchInvalidQuery = 1_000_007,
1010
Unexpected = 1_000_008,
11+
AutoEmbeddingIndexNotSupported = 1_000_009,
1112
}
1213

1314
export class MongoDBError<ErrorCode extends ErrorCodes = ErrorCodes> extends Error {

src/common/session.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,25 @@ export class Session extends EventEmitter<SessionEvents> {
179179
}
180180
}
181181

182+
async isAutoEmbeddingIndexSupported(): Promise<boolean> {
183+
const state = this.connectionManager.currentConnectionState;
184+
if (state.tag === "connected") {
185+
return await state.isAutoEmbeddingIndexSupported();
186+
}
187+
188+
return false;
189+
}
190+
191+
async assertAutoEmbeddingIndexSupported(): Promise<void> {
192+
const isSearchSupported = await this.isSearchSupported();
193+
if (!isSearchSupported) {
194+
throw new MongoDBError(
195+
ErrorCodes.AutoEmbeddingIndexNotSupported,
196+
"The connected search management service does not support creating auto-embedding indexes."
197+
);
198+
}
199+
}
200+
182201
get serviceProvider(): NodeDriverServiceProvider {
183202
if (this.isConnectedToMongoDB) {
184203
const state = this.connectionManager.currentConnectionState as ConnectionStateConnected;

src/tools/mongodb/create/createIndex.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class CreateIndexTool extends MongoDBToolBase {
183183
break;
184184
case "vectorSearch":
185185
{
186-
await this.ensureSearchIsSupported();
186+
await this.session.assertSearchSupported();
187187
indexes = await provider.createSearchIndexes(database, collection, [
188188
{
189189
name,
@@ -204,7 +204,7 @@ export class CreateIndexTool extends MongoDBToolBase {
204204
break;
205205
case "search":
206206
{
207-
await this.ensureSearchIsSupported();
207+
await this.session.assertSearchSupported();
208208
indexes = await provider.createSearchIndexes(database, collection, [
209209
{
210210
name,

src/tools/mongodb/delete/dropIndex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class DropIndexTool extends MongoDBToolBase {
5858
provider: NodeDriverServiceProvider,
5959
{ database, collection, indexName }: ToolArgs<typeof this.argsShape>
6060
): Promise<CallToolResult> {
61-
await this.ensureSearchIsSupported();
61+
await this.session.assertSearchSupported();
6262
const indexes = await provider.getSearchIndexes(database, collection, indexName);
6363
if (indexes.length === 0) {
6464
return {

src/tools/mongodb/mongodbTool.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ export abstract class MongoDBToolBase extends ToolBase {
4747
return this.session.serviceProvider;
4848
}
4949

50-
protected ensureSearchIsSupported(): Promise<void> {
51-
return this.session.assertSearchSupported();
52-
}
53-
5450
public register(server: Server): boolean {
5551
this.server = server;
5652
return super.register(server);

0 commit comments

Comments
 (0)