Skip to content

Commit 19a333c

Browse files
committed
chore: simplify, assume search indexes are available just by listing them
1 parent 3b104b5 commit 19a333c

File tree

9 files changed

+21
-140
lines changed

9 files changed

+21
-140
lines changed

src/common/connectionManager.ts

Lines changed: 5 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export interface ConnectionSettings {
2525
type ConnectionTag = "connected" | "connecting" | "disconnected" | "errored";
2626
type OIDCConnectionAuthType = "oidc-auth-flow" | "oidc-device-flow";
2727
export type ConnectionStringAuthType = "scram" | "ldap" | "kerberos" | OIDCConnectionAuthType | "x.509";
28-
export type SearchAvailability = false | "not-available-yet" | "available";
2928

3029
export interface ConnectionState {
3130
tag: ConnectionTag;
@@ -34,38 +33,24 @@ export interface ConnectionState {
3433
}
3534

3635
const MCP_TEST_DATABASE = "#mongodb-mcp";
37-
const SEARCH_AVAILABILITY_CHECK_TIMEOUT_MS = 500;
3836
export class ConnectionStateConnected implements ConnectionState {
3937
public tag = "connected" as const;
4038

4139
constructor(
4240
public serviceProvider: NodeDriverServiceProvider,
4341
public connectionStringAuthType?: ConnectionStringAuthType,
4442
public connectedAtlasCluster?: AtlasClusterConnectionInfo
45-
) {
46-
this.#isSearchAvailable = false;
47-
}
43+
) {}
4844

4945
#isSearchSupported?: boolean;
50-
#isSearchAvailable: boolean;
51-
52-
public async getSearchAvailability(): Promise<SearchAvailability> {
53-
if ((await this.isSearchSupported()) === true) {
54-
if ((await this.isSearchAvailable()) === true) {
55-
return "available";
56-
}
57-
58-
return "not-available-yet";
59-
}
6046

61-
return false;
62-
}
63-
64-
private async isSearchSupported(): Promise<boolean> {
47+
public async isSearchSupported(): Promise<boolean> {
6548
if (this.#isSearchSupported === undefined) {
6649
try {
6750
// If a cluster supports search indexes, the call below will succeed
68-
// with a cursor otherwise will throw an Error
51+
// with a cursor otherwise will throw an Error.
52+
// the Search Index Management Service might not be ready yet, but
53+
// we assume that the agent can retry in that situation.
6954
await this.serviceProvider.getSearchIndexes(MCP_TEST_DATABASE, "test");
7055
this.#isSearchSupported = true;
7156
} catch {
@@ -75,57 +60,6 @@ export class ConnectionStateConnected implements ConnectionState {
7560

7661
return this.#isSearchSupported;
7762
}
78-
79-
private async isSearchAvailable(): Promise<boolean> {
80-
if (this.#isSearchAvailable === true) {
81-
return true;
82-
}
83-
84-
const timeoutPromise = new Promise<boolean>((_resolve, reject) =>
85-
setTimeout(
86-
() =>
87-
reject(
88-
new MongoDBError(
89-
ErrorCodes.AtlasSearchNotAvailable,
90-
"Atlas Search is supported in your environment but is not available yet. Retry again later."
91-
)
92-
),
93-
SEARCH_AVAILABILITY_CHECK_TIMEOUT_MS
94-
)
95-
);
96-
97-
const checkPromise = new Promise<boolean>((resolve) => {
98-
void this.doCheckSearchIndexIsAvailable(resolve);
99-
});
100-
101-
return await Promise.race([checkPromise, timeoutPromise]);
102-
}
103-
104-
private async doCheckSearchIndexIsAvailable(resolve: (result: boolean) => void): Promise<void> {
105-
for (let i = 0; i < 100; i++) {
106-
try {
107-
try {
108-
await this.serviceProvider.insertOne(MCP_TEST_DATABASE, "test", { search: "search is available" });
109-
} catch (err) {
110-
// if inserting one document fails, it means we are in readOnly mode. We can't verify reliably if
111-
// Search is available, so assume it is.
112-
void err;
113-
resolve(true);
114-
return;
115-
}
116-
await this.serviceProvider.createSearchIndexes(MCP_TEST_DATABASE, "test", [
117-
{ definition: { mappings: { dynamic: true } } },
118-
]);
119-
await this.serviceProvider.dropDatabase(MCP_TEST_DATABASE);
120-
resolve(true);
121-
return;
122-
} catch (err) {
123-
void err;
124-
}
125-
}
126-
127-
resolve(false);
128-
}
12963
}
13064

13165
export interface ConnectionStateConnecting extends ConnectionState {

src/common/search/vectorSearchEmbeddings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export class VectorSearchEmbeddings {
9393
private async assertAtlasSearchIsAvailable(): Promise<NodeDriverServiceProvider | null> {
9494
const connectionState = this.connectionManager.currentConnectionState;
9595
if (connectionState.tag === "connected") {
96-
if ((await connectionState.getSearchAvailability()) === "available") {
96+
if (await connectionState.isSearchSupported()) {
9797
return connectionState.serviceProvider;
9898
}
9999
}

src/common/session.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type {
1111
ConnectionSettings,
1212
ConnectionStateConnected,
1313
ConnectionStateErrored,
14-
SearchAvailability,
1514
} from "./connectionManager.js";
1615
import type { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
1716
import { ErrorCodes, MongoDBError } from "./errors.js";
@@ -147,31 +146,24 @@ export class Session extends EventEmitter<SessionEvents> {
147146
return this.connectionManager.currentConnectionState.tag === "connected";
148147
}
149148

150-
async isSearchAvailable(): Promise<SearchAvailability> {
149+
async isSearchSupported(): Promise<boolean> {
151150
const state = this.connectionManager.currentConnectionState;
152151
if (state.tag === "connected") {
153-
return await state.getSearchAvailability();
152+
return await state.isSearchSupported();
154153
}
155154

156155
return false;
157156
}
158157

159-
async assertSearchAvailable(): Promise<void> {
160-
const availability = await this.isSearchAvailable();
158+
async assertSearchSupported(): Promise<void> {
159+
const availability = await this.isSearchSupported();
161160
if (!availability) {
162161
throw new MongoDBError(
163162
ErrorCodes.AtlasSearchNotSupported,
164163
"Atlas Search is not supported in the current cluster."
165164
);
166165
}
167166

168-
if (availability === "not-available-yet") {
169-
throw new MongoDBError(
170-
ErrorCodes.AtlasSearchNotAvailable,
171-
"Atlas Search is supported in the current cluster but not available yet."
172-
);
173-
}
174-
175167
return;
176168
}
177169

src/resources/common/debug.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ export class DebugResource extends ReactiveResource<
6161

6262
switch (this.current.tag) {
6363
case "connected": {
64-
const searchAvailability = await this.session.isSearchAvailable();
65-
const searchIndexesSupported = searchAvailability !== false;
64+
const searchIndexesSupported = await this.session.isSearchSupported();
6665
result += `The user is connected to the MongoDB cluster${searchIndexesSupported ? " with support for search indexes" : " without any support for search indexes"}.`;
6766
break;
6867
}

src/tools/mongodb/create/createIndex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class CreateIndexTool extends MongoDBToolBase {
112112
break;
113113
case "vectorSearch":
114114
{
115-
await this.ensureSearchIsAvailable();
115+
await this.ensureSearchIsSupported();
116116
indexes = await provider.createSearchIndexes(database, collection, [
117117
{
118118
name,

src/tools/mongodb/mongodbTool.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export abstract class MongoDBToolBase extends ToolBase {
4646
return this.session.serviceProvider;
4747
}
4848

49-
protected async ensureSearchIsAvailable(): Promise<void> {
50-
return await this.session.assertSearchAvailable();
49+
protected async ensureSearchIsSupported(): Promise<void> {
50+
return await this.session.assertSearchSupported();
5151
}
5252

5353
public register(server: Server): boolean {

src/tools/mongodb/search/listSearchIndexes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class ListSearchIndexesTool extends MongoDBToolBase {
2020

2121
protected async execute({ database, collection }: ToolArgs<typeof DbOperationArgs>): Promise<CallToolResult> {
2222
const provider = await this.ensureConnected();
23-
await this.session.assertSearchAvailable();
23+
await this.ensureSearchIsSupported();
2424

2525
const indexes = await provider.getSearchIndexes(database, collection);
2626
const trimmedIndexDefinitions = this.pickRelevantInformation(indexes);

tests/unit/common/session.test.ts

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -151,29 +151,7 @@ describe("Session", () => {
151151
connectionString: "mongodb://localhost:27017",
152152
});
153153

154-
expect(await session.isSearchAvailable()).toEqual("available");
155-
});
156-
157-
it("should return 'available' if listing search indexes succeed and we don't have write permissions", async () => {
158-
getSearchIndexesMock.mockResolvedValue([]);
159-
insertOneMock.mockRejectedValue(new Error("Read only mode"));
160-
createSearchIndexesMock.mockResolvedValue([]);
161-
162-
await session.connectToMongoDB({
163-
connectionString: "mongodb://localhost:27017",
164-
});
165-
166-
expect(await session.isSearchAvailable()).toEqual("available");
167-
});
168-
169-
it("should return 'not-available-yet' if listing search indexes work but can not create an index", async () => {
170-
getSearchIndexesMock.mockResolvedValue([]);
171-
insertOneMock.mockResolvedValue([]);
172-
createSearchIndexesMock.mockRejectedValue(new Error("SearchNotAvailable"));
173-
await session.connectToMongoDB({
174-
connectionString: "mongodb://localhost:27017",
175-
});
176-
expect(await session.isSearchAvailable()).toEqual("not-available-yet");
154+
expect(await session.isSearchSupported()).toBeTruthy();
177155
});
178156

179157
it("should return false if listing search indexes fail with search error", async () => {
@@ -182,51 +160,29 @@ describe("Session", () => {
182160
await session.connectToMongoDB({
183161
connectionString: "mongodb://localhost:27017",
184162
});
185-
expect(await session.isSearchAvailable()).toEqual(false);
163+
expect(await session.isSearchSupported()).toEqual(false);
186164
});
187165
});
188166

189-
describe("assertSearchAvailable", () => {
167+
describe("assertSearchSupported", () => {
190168
let getSearchIndexesMock: MockedFunction<() => unknown>;
191-
let createSearchIndexesMock: MockedFunction<() => unknown>;
192169

193170
beforeEach(() => {
194171
getSearchIndexesMock = vi.fn();
195-
createSearchIndexesMock = vi.fn();
196172

197173
MockNodeDriverServiceProvider.connect = vi.fn().mockResolvedValue({
198174
getSearchIndexes: getSearchIndexesMock,
199-
createSearchIndexes: createSearchIndexesMock,
200-
insertOne: vi.fn().mockResolvedValue({}),
201-
dropDatabase: vi.fn().mockResolvedValue({}),
202175
} as unknown as NodeDriverServiceProvider);
203176
});
204177

205178
it("should not throw if it is available", async () => {
206179
getSearchIndexesMock.mockResolvedValue([]);
207-
createSearchIndexesMock.mockResolvedValue([]);
208-
209-
await session.connectToMongoDB({
210-
connectionString: "mongodb://localhost:27017",
211-
});
212-
213-
await expect(session.assertSearchAvailable()).resolves.not.toThrowError();
214-
});
215-
216-
it("should throw if it is supported but not available", async () => {
217-
getSearchIndexesMock.mockResolvedValue([]);
218-
createSearchIndexesMock.mockRejectedValue(new Error("Not ready yet"));
219180

220181
await session.connectToMongoDB({
221182
connectionString: "mongodb://localhost:27017",
222183
});
223184

224-
await expect(session.assertSearchAvailable()).rejects.toThrowError(
225-
new MongoDBError(
226-
ErrorCodes.AtlasSearchNotAvailable,
227-
"Atlas Search is supported in the current cluster but not available yet."
228-
)
229-
);
185+
await expect(session.assertSearchSupported()).resolves.not.toThrowError();
230186
});
231187

232188
it("should throw if it is not supported", async () => {
@@ -236,7 +192,7 @@ describe("Session", () => {
236192
connectionString: "mongodb://localhost:27017",
237193
});
238194

239-
await expect(session.assertSearchAvailable()).rejects.toThrowError(
195+
await expect(session.assertSearchSupported()).rejects.toThrowError(
240196
new MongoDBError(
241197
ErrorCodes.AtlasSearchNotSupported,
242198
"Atlas Search is not supported in the current cluster."

tests/unit/resources/common/debug.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ describe("debug resource", () => {
108108
});
109109

110110
it("should notify if a cluster supports search indexes", async () => {
111-
vi.spyOn(session, "isSearchAvailable").mockImplementation(() => Promise.resolve("available"));
111+
vi.spyOn(session, "isSearchSupported").mockImplementation(() => Promise.resolve(true));
112112
debugResource.reduceApply("connect", undefined);
113113
const output = await debugResource.toOutput();
114114

0 commit comments

Comments
 (0)