diff --git a/scripts/apply.ts b/scripts/apply.ts index 7ab36b97..c051c4ee 100755 --- a/scripts/apply.ts +++ b/scripts/apply.ts @@ -58,11 +58,11 @@ async function main() { const httpCode = parseInt(code, 10); if (httpCode >= 200 && httpCode < 300) { const response = operation.responses[code]; - const responseObject = findObjectFromRef(response, openapi); - if (responseObject.content) { + const responseObject = findObjectFromRef(response, openapi) as OpenAPIV3_1.ResponseObject; + if (responseObject && responseObject.content) { for (const contentType in responseObject.content) { const content = responseObject.content[contentType]; - hasResponseBody = !!content.schema; + hasResponseBody = !!content?.schema; } } } @@ -84,7 +84,7 @@ async function main() { operationId: operation.operationId || "", requiredParams, hasResponseBody, - tag: operation.tags[0], + tag: operation.tags?.[0] ?? "", }); } } diff --git a/src/common/atlas/cluster.ts b/src/common/atlas/cluster.ts index b2bbd172..793cd99b 100644 --- a/src/common/atlas/cluster.ts +++ b/src/common/atlas/cluster.ts @@ -50,8 +50,7 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster { }; }); - const instanceSize = (regionConfigs.length <= 0 ? undefined : regionConfigs[0].instanceSize) || "UNKNOWN"; - + const instanceSize = regionConfigs[0]?.instanceSize ?? "UNKNOWN"; const clusterInstanceType = instanceSize == "M0" ? "FREE" : "DEDICATED"; return { diff --git a/src/logger.ts b/src/logger.ts index 73cf0103..8157324b 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -32,6 +32,8 @@ export const LogId = { mongodbConnectFailure: mongoLogId(1_004_001), mongodbDisconnectFailure: mongoLogId(1_004_002), + + toolUpdateFailure: mongoLogId(1_005_001), } as const; abstract class LoggerBase { diff --git a/src/tools/atlas/create/createProject.ts b/src/tools/atlas/create/createProject.ts index 8cb6e1a9..cdf71b9c 100644 --- a/src/tools/atlas/create/createProject.ts +++ b/src/tools/atlas/create/createProject.ts @@ -28,7 +28,13 @@ export class CreateProjectTool extends AtlasToolBase { "No organizations were found in your MongoDB Atlas account. Please create an organization first." ); } - organizationId = organizations.results[0].id; + const firstOrg = organizations.results[0]; + if (!firstOrg?.id) { + throw new Error( + "The first organization found does not have an ID. Please check your Atlas account." + ); + } + organizationId = firstOrg.id; assumedOrg = true; } catch { throw new Error( diff --git a/src/tools/atlas/read/listProjects.ts b/src/tools/atlas/read/listProjects.ts index 3e30d38d..1a9ab523 100644 --- a/src/tools/atlas/read/listProjects.ts +++ b/src/tools/atlas/read/listProjects.ts @@ -21,7 +21,8 @@ export class ListProjectsTool extends AtlasToolBase { const orgs: Record = orgData.results .map((org) => [org.id || "", org.name]) - .reduce((acc, [id, name]) => ({ ...acc, [id]: name }), {}); + .filter(([id]) => id) + .reduce((acc, [id, name]) => ({ ...acc, [id as string]: name }), {}); const data = orgId ? await this.session.apiClient.listOrganizationProjects({ @@ -41,7 +42,8 @@ export class ListProjectsTool extends AtlasToolBase { const rows = data.results .map((project) => { const createdAt = project.created ? new Date(project.created).toLocaleString() : "N/A"; - return `${project.name} | ${project.id} | ${orgs[project.orgId]} | ${project.orgId} | ${createdAt}`; + const orgName = orgs[project.orgId] ?? "N/A"; + return `${project.name} | ${project.id} | ${orgName} | ${project.orgId} | ${createdAt}`; }) .join("\n"); const formattedProjects = `Project Name | Project ID | Organization Name | Organization ID | Created At diff --git a/src/tools/tool.ts b/src/tools/tool.ts index d04fbda8..bcf886ea 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -93,6 +93,12 @@ export abstract class ToolBase { this.update = (updates: { name?: string; description?: string; inputSchema?: AnyZodObject }) => { const tools = server["_registeredTools"] as { [toolName: string]: RegisteredTool }; const existingTool = tools[this.name]; + + if (!existingTool) { + logger.warning(LogId.toolUpdateFailure, "tool", `Tool ${this.name} not found in update`); + return; + } + existingTool.annotations = this.annotations; if (updates.name && updates.name !== this.name) { diff --git a/tests/integration/tools/atlas/accessLists.test.ts b/tests/integration/tools/atlas/accessLists.test.ts index a194a351..d2dc219c 100644 --- a/tests/integration/tools/atlas/accessLists.test.ts +++ b/tests/integration/tools/atlas/accessLists.test.ts @@ -67,7 +67,7 @@ describeWithAtlas("ip access lists", (integration) => { })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - expect(response.content[0].text).toContain("IP/CIDR ranges added to access list"); + expect(response.content[0]?.text).toContain("IP/CIDR ranges added to access list"); }); }); @@ -90,7 +90,7 @@ describeWithAtlas("ip access lists", (integration) => { expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); for (const value of values) { - expect(response.content[0].text).toContain(value); + expect(response.content[0]?.text).toContain(value); } }); }); diff --git a/tests/integration/tools/atlas/alerts.test.ts b/tests/integration/tools/atlas/alerts.test.ts index cf8a675e..3e95cced 100644 --- a/tests/integration/tools/atlas/alerts.test.ts +++ b/tests/integration/tools/atlas/alerts.test.ts @@ -23,7 +23,7 @@ describeWithAtlas("alerts", (integration) => { expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - const data = parseTable(response.content[0].text as string); + const data = parseTable(response.content[0]?.text as string); expect(data).toBeArray(); // Since we can't guarantee alerts will exist, we just verify the table structure diff --git a/tests/integration/tools/atlas/atlasHelpers.ts b/tests/integration/tools/atlas/atlasHelpers.ts index aecf0479..f03e1dc7 100644 --- a/tests/integration/tools/atlas/atlasHelpers.ts +++ b/tests/integration/tools/atlas/atlasHelpers.ts @@ -75,7 +75,9 @@ export function parseTable(text: string): Record[] { .map((cells) => { const row: Record = {}; cells.forEach((cell, index) => { - row[headers[index]] = cell; + if (headers) { + row[headers[index] ?? ""] = cell; + } }); return row; }); @@ -87,14 +89,14 @@ async function createProject(apiClient: ApiClient): Promise { const projectName: string = `testProj-` + randomId; const orgs = await apiClient.listOrganizations(); - if (!orgs?.results?.length || !orgs.results[0].id) { + if (!orgs?.results?.length || !orgs.results[0]?.id) { throw new Error("No orgs found"); } const group = await apiClient.createProject({ body: { name: projectName, - orgId: orgs.results[0].id, + orgId: orgs.results[0]?.id ?? "", } as Group, }); diff --git a/tests/integration/tools/atlas/clusters.test.ts b/tests/integration/tools/atlas/clusters.test.ts index f9e07943..166ee637 100644 --- a/tests/integration/tools/atlas/clusters.test.ts +++ b/tests/integration/tools/atlas/clusters.test.ts @@ -88,7 +88,7 @@ describeWithAtlas("clusters", (integration) => { })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(2); - expect(response.content[0].text).toContain("has been created"); + expect(response.content[0]?.text).toContain("has been created"); }); }); @@ -113,7 +113,7 @@ describeWithAtlas("clusters", (integration) => { })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - expect(response.content[0].text).toContain(`${clusterName} | `); + expect(response.content[0]?.text).toContain(`${clusterName} | `); }); }); @@ -135,7 +135,7 @@ describeWithAtlas("clusters", (integration) => { .callTool({ name: "atlas-list-clusters", arguments: { projectId } })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(2); - expect(response.content[1].text).toContain(`${clusterName} | `); + expect(response.content[1]?.text).toContain(`${clusterName} | `); }); }); @@ -178,7 +178,7 @@ describeWithAtlas("clusters", (integration) => { })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - expect(response.content[0].text).toContain(`Connected to cluster "${clusterName}"`); + expect(response.content[0]?.text).toContain(`Connected to cluster "${clusterName}"`); }); }); }); diff --git a/tests/integration/tools/atlas/dbUsers.test.ts b/tests/integration/tools/atlas/dbUsers.test.ts index 2bcb95fa..3bfb979e 100644 --- a/tests/integration/tools/atlas/dbUsers.test.ts +++ b/tests/integration/tools/atlas/dbUsers.test.ts @@ -65,18 +65,18 @@ describeWithAtlas("db users", (integration) => { const elements = getResponseElements(response); expect(elements).toHaveLength(1); - expect(elements[0].text).toContain("created successfully"); - expect(elements[0].text).toContain(userName); - expect(elements[0].text).not.toContain("testpassword"); + expect(elements[0]?.text).toContain("created successfully"); + expect(elements[0]?.text).toContain(userName); + expect(elements[0]?.text).not.toContain("testpassword"); }); it("should create a database user with generated password", async () => { const response = await createUserWithMCP(); const elements = getResponseElements(response); expect(elements).toHaveLength(1); - expect(elements[0].text).toContain("created successfully"); - expect(elements[0].text).toContain(userName); - expect(elements[0].text).toContain("with password: `"); + expect(elements[0]?.text).toContain("created successfully"); + expect(elements[0]?.text).toContain(userName); + expect(elements[0]?.text).toContain("with password: `"); }); }); describe("atlas-list-db-users", () => { @@ -98,7 +98,7 @@ describeWithAtlas("db users", (integration) => { .callTool({ name: "atlas-list-db-users", arguments: { projectId } })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - expect(response.content[0].text).toContain(userName); + expect(response.content[0]?.text).toContain(userName); }); }); }); diff --git a/tests/integration/tools/atlas/orgs.test.ts b/tests/integration/tools/atlas/orgs.test.ts index 83143404..246a37db 100644 --- a/tests/integration/tools/atlas/orgs.test.ts +++ b/tests/integration/tools/atlas/orgs.test.ts @@ -16,9 +16,9 @@ describeWithAtlas("orgs", (integration) => { .callTool({ name: "atlas-list-orgs", arguments: {} })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - const data = parseTable(response.content[0].text as string); + const data = parseTable(response.content[0]?.text as string); expect(data).toHaveLength(1); - expect(data[0]["Organization Name"]).toEqual("MongoDB MCP Test"); + expect(data[0]?.["Organization Name"]).toEqual("MongoDB MCP Test"); }); }); }); diff --git a/tests/integration/tools/atlas/projects.test.ts b/tests/integration/tools/atlas/projects.test.ts index 7d773c7e..d7258fa1 100644 --- a/tests/integration/tools/atlas/projects.test.ts +++ b/tests/integration/tools/atlas/projects.test.ts @@ -43,7 +43,7 @@ describeWithAtlas("projects", (integration) => { })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - expect(response.content[0].text).toContain(projName); + expect(response.content[0]?.text).toContain(projName); }); }); describe("atlas-list-projects", () => { @@ -62,8 +62,8 @@ describeWithAtlas("projects", (integration) => { .callTool({ name: "atlas-list-projects", arguments: {} })) as CallToolResult; expect(response.content).toBeArray(); expect(response.content).toHaveLength(1); - expect(response.content[0].text).toContain(projName); - const data = parseTable(response.content[0].text as string); + expect(response.content[0]?.text).toContain(projName); + const data = parseTable(response.content[0]?.text as string); expect(data).toBeArray(); expect(data.length).toBeGreaterThan(0); let found = false; diff --git a/tests/integration/tools/mongodb/create/createCollection.test.ts b/tests/integration/tools/mongodb/create/createCollection.test.ts index ef8da5f1..3e0d1689 100644 --- a/tests/integration/tools/mongodb/create/createCollection.test.ts +++ b/tests/integration/tools/mongodb/create/createCollection.test.ts @@ -34,7 +34,7 @@ describeWithMongoDB("createCollection tool", (integration) => { collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray(); expect(collections).toHaveLength(1); - expect(collections[0].name).toEqual("bar"); + expect(collections[0]?.name).toEqual("bar"); }); }); @@ -78,7 +78,7 @@ describeWithMongoDB("createCollection tool", (integration) => { expect(content).toEqual(`Collection "collection1" created in database "${integration.randomDbName()}".`); collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray(); expect(collections).toHaveLength(1); - expect(collections[0].name).toEqual("collection1"); + expect(collections[0]?.name).toEqual("collection1"); // Make sure we didn't drop the existing collection documents = await mongoClient.db(integration.randomDbName()).collection("collection1").find({}).toArray(); diff --git a/tests/integration/tools/mongodb/create/createIndex.test.ts b/tests/integration/tools/mongodb/create/createIndex.test.ts index b1a2a5df..f4929b92 100644 --- a/tests/integration/tools/mongodb/create/createIndex.test.ts +++ b/tests/integration/tools/mongodb/create/createIndex.test.ts @@ -38,10 +38,10 @@ describeWithMongoDB("createIndex tool", (integration) => { const mongoClient = integration.mongoClient(); const collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray(); expect(collections).toHaveLength(1); - expect(collections[0].name).toEqual("coll1"); + expect(collections[0]?.name).toEqual("coll1"); const indexes = await mongoClient.db(integration.randomDbName()).collection(collection).indexes(); expect(indexes).toHaveLength(expected.length + 1); - expect(indexes[0].name).toEqual("_id_"); + expect(indexes[0]?.name).toEqual("_id_"); for (const index of expected) { const foundIndex = indexes.find((i) => i.name === index.name); expectDefined(foundIndex); diff --git a/tests/integration/tools/mongodb/create/insertMany.test.ts b/tests/integration/tools/mongodb/create/insertMany.test.ts index a49c3a4e..eb8e1ef4 100644 --- a/tests/integration/tools/mongodb/create/insertMany.test.ts +++ b/tests/integration/tools/mongodb/create/insertMany.test.ts @@ -82,7 +82,7 @@ describeWithMongoDB("insertMany tool", (integration) => { const content = getResponseContent(response.content); expect(content).toContain("Error running insert-many"); expect(content).toContain("duplicate key error"); - expect(content).toContain(insertedIds[0].toString()); + expect(content).toContain(insertedIds[0]?.toString()); }); validateAutoConnectBehavior(integration, "insert-many", () => { diff --git a/tests/integration/tools/mongodb/delete/dropCollection.test.ts b/tests/integration/tools/mongodb/delete/dropCollection.test.ts index 1dcaa218..48707156 100644 --- a/tests/integration/tools/mongodb/delete/dropCollection.test.ts +++ b/tests/integration/tools/mongodb/delete/dropCollection.test.ts @@ -54,7 +54,7 @@ describeWithMongoDB("dropCollection tool", (integration) => { ); const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray(); expect(collections).toHaveLength(1); - expect(collections[0].name).toBe("coll2"); + expect(collections[0]?.name).toBe("coll2"); }); validateAutoConnectBehavior(integration, "drop-collection", () => { diff --git a/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts b/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts index 1b7481a2..e67b0ce5 100644 --- a/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +++ b/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts @@ -132,11 +132,11 @@ describeWithMongoDB("collectionSchema tool", (integration) => { expect(items).toHaveLength(2); // Expect to find _id, name, age - expect(items[0].text).toEqual( + expect(items[0]?.text).toEqual( `Found ${Object.entries(testCase.expectedSchema).length} fields in the schema for "${integration.randomDbName()}.foo"` ); - const schema = JSON.parse(items[1].text) as SimplifiedSchema; + const schema = JSON.parse(items[1]?.text ?? "{}") as SimplifiedSchema; expect(schema).toEqual(testCase.expectedSchema); }); } diff --git a/tests/integration/tools/mongodb/metadata/dbStats.test.ts b/tests/integration/tools/mongodb/metadata/dbStats.test.ts index b26a2cf2..2ce4a84c 100644 --- a/tests/integration/tools/mongodb/metadata/dbStats.test.ts +++ b/tests/integration/tools/mongodb/metadata/dbStats.test.ts @@ -28,9 +28,9 @@ describeWithMongoDB("dbStats tool", (integration) => { }); const elements = getResponseElements(response.content); expect(elements).toHaveLength(2); - expect(elements[0].text).toBe(`Statistics for database ${integration.randomDbName()}`); + expect(elements[0]?.text).toBe(`Statistics for database ${integration.randomDbName()}`); - const stats = JSON.parse(elements[1].text) as { + const stats = JSON.parse(elements[1]?.text ?? "{}") as { db: string; collections: number; storageSize: number; @@ -75,9 +75,9 @@ describeWithMongoDB("dbStats tool", (integration) => { }); const elements = getResponseElements(response.content); expect(elements).toHaveLength(2); - expect(elements[0].text).toBe(`Statistics for database ${integration.randomDbName()}`); + expect(elements[0]?.text).toBe(`Statistics for database ${integration.randomDbName()}`); - const stats = JSON.parse(elements[1].text) as { + const stats = JSON.parse(elements[1]?.text ?? "{}") as { db: string; collections: unknown; storageSize: unknown; diff --git a/tests/integration/tools/mongodb/metadata/explain.test.ts b/tests/integration/tools/mongodb/metadata/explain.test.ts index 0aeb92ea..44cfe6ff 100644 --- a/tests/integration/tools/mongodb/metadata/explain.test.ts +++ b/tests/integration/tools/mongodb/metadata/explain.test.ts @@ -89,12 +89,12 @@ describeWithMongoDB("explain tool", (integration) => { const content = getResponseElements(response.content); expect(content).toHaveLength(2); - expect(content[0].text).toEqual( + expect(content[0]?.text).toEqual( `Here is some information about the winning plan chosen by the query optimizer for running the given \`${testCase.method}\` operation in "${integration.randomDbName()}.coll1". This information can be used to understand how the query was executed and to optimize the query performance.` ); - expect(content[1].text).toContain("queryPlanner"); - expect(content[1].text).toContain("winningPlan"); + expect(content[1]?.text).toContain("queryPlanner"); + expect(content[1]?.text).toContain("winningPlan"); }); } }); @@ -139,22 +139,22 @@ describeWithMongoDB("explain tool", (integration) => { const content = getResponseElements(response.content); expect(content).toHaveLength(2); - expect(content[0].text).toEqual( + expect(content[0]?.text).toEqual( `Here is some information about the winning plan chosen by the query optimizer for running the given \`${testCase.method}\` operation in "${integration.randomDbName()}.people". This information can be used to understand how the query was executed and to optimize the query performance.` ); - expect(content[1].text).toContain("queryPlanner"); - expect(content[1].text).toContain("winningPlan"); + expect(content[1]?.text).toContain("queryPlanner"); + expect(content[1]?.text).toContain("winningPlan"); if (indexed) { if (testCase.method === "count") { - expect(content[1].text).toContain("COUNT_SCAN"); + expect(content[1]?.text).toContain("COUNT_SCAN"); } else { - expect(content[1].text).toContain("IXSCAN"); + expect(content[1]?.text).toContain("IXSCAN"); } - expect(content[1].text).toContain("name_1"); + expect(content[1]?.text).toContain("name_1"); } else { - expect(content[1].text).toContain("COLLSCAN"); + expect(content[1]?.text).toContain("COLLSCAN"); } }); } diff --git a/tests/integration/tools/mongodb/metadata/listCollections.test.ts b/tests/integration/tools/mongodb/metadata/listCollections.test.ts index cef0a59d..c975d8de 100644 --- a/tests/integration/tools/mongodb/metadata/listCollections.test.ts +++ b/tests/integration/tools/mongodb/metadata/listCollections.test.ts @@ -45,7 +45,7 @@ describeWithMongoDB("listCollections tool", (integration) => { }); const items = getResponseElements(response.content); expect(items).toHaveLength(1); - expect(items[0].text).toContain('Name: "collection-1"'); + expect(items[0]?.text).toContain('Name: "collection-1"'); await mongoClient.db(integration.randomDbName()).createCollection("collection-2"); diff --git a/tests/integration/tools/mongodb/metadata/listDatabases.test.ts b/tests/integration/tools/mongodb/metadata/listDatabases.test.ts index de803c6c..b828d743 100644 --- a/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +++ b/tests/integration/tools/mongodb/metadata/listDatabases.test.ts @@ -65,9 +65,13 @@ describeWithMongoDB("listDatabases tool", (integration) => { function getDbNames(content: unknown): (string | null)[] { const responseItems = getResponseElements(content); - - return responseItems.map((item) => { - const match = item.text.match(/Name: (.*), Size: \d+ bytes/); - return match ? match[1] : null; - }); + return responseItems + .map((item) => { + if (item && typeof item.text === "string") { + const match = item.text.match(/Name: ([^,]+), Size: \d+ bytes/); + return match ? match[1] : null; + } + return null; + }) + .filter((item): item is string | null => item !== undefined); } diff --git a/tests/integration/tools/mongodb/metadata/logs.test.ts b/tests/integration/tools/mongodb/metadata/logs.test.ts index bc7f79bc..debbb1ae 100644 --- a/tests/integration/tools/mongodb/metadata/logs.test.ts +++ b/tests/integration/tools/mongodb/metadata/logs.test.ts @@ -37,11 +37,11 @@ describeWithMongoDB("logs tool", (integration) => { // Default limit is 50 expect(elements.length).toBeLessThanOrEqual(51); - expect(elements[0].text).toMatch(/Found: \d+ messages/); + expect(elements[0]?.text).toMatch(/Found: \d+ messages/); for (let i = 1; i < elements.length; i++) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const log = JSON.parse(elements[i].text); + const log = JSON.parse(elements[i]?.text ?? "{}"); expect(log).toHaveProperty("t"); expect(log).toHaveProperty("msg"); } @@ -59,7 +59,7 @@ describeWithMongoDB("logs tool", (integration) => { const elements = getResponseElements(response); expect(elements.length).toBeLessThanOrEqual(51); for (let i = 1; i < elements.length; i++) { - const log = JSON.parse(elements[i].text) as { tags: string[] }; + const log = JSON.parse(elements[i]?.text ?? "{}") as { tags: string[] }; expect(log).toHaveProperty("t"); expect(log).toHaveProperty("msg"); expect(log).toHaveProperty("tags"); @@ -76,7 +76,7 @@ describeWithMongoDB("logs tool", (integration) => { validate: (content) => { const elements = getResponseElements(content); expect(elements.length).toBeLessThanOrEqual(51); - expect(elements[0].text).toMatch(/Found: \d+ messages/); + expect(elements[0]?.text).toMatch(/Found: \d+ messages/); }, }; }); diff --git a/tests/integration/tools/mongodb/read/aggregate.test.ts b/tests/integration/tools/mongodb/read/aggregate.test.ts index 65d243d9..6cf6aff0 100644 --- a/tests/integration/tools/mongodb/read/aggregate.test.ts +++ b/tests/integration/tools/mongodb/read/aggregate.test.ts @@ -35,7 +35,7 @@ describeWithMongoDB("aggregate tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(1); - expect(elements[0].text).toEqual('Found 0 documents in the collection "people":'); + expect(elements[0]?.text).toEqual('Found 0 documents in the collection "people":'); }); it("can run aggragation on an empty collection", async () => { @@ -53,7 +53,7 @@ describeWithMongoDB("aggregate tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(1); - expect(elements[0].text).toEqual('Found 0 documents in the collection "people":'); + expect(elements[0]?.text).toEqual('Found 0 documents in the collection "people":'); }); it("can run aggragation on an existing collection", async () => { @@ -79,11 +79,21 @@ describeWithMongoDB("aggregate tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(3); - expect(elements[0].text).toEqual('Found 2 documents in the collection "people":'); - /* eslint-disable @typescript-eslint/no-unsafe-assignment */ - expect(JSON.parse(elements[1].text)).toEqual({ _id: expect.any(Object), name: "Søren", age: 15 }); - expect(JSON.parse(elements[2].text)).toEqual({ _id: expect.any(Object), name: "Laura", age: 10 }); - /* eslint-enable @typescript-eslint/no-unsafe-assignment */ + expect(elements[0]?.text).toEqual('Found 2 documents in the collection "people":'); + expect(asObject(JSON.parse(elements[1]?.text ?? "{}"))).toEqual( + expect.objectContaining({ + _id: expect.any(Object) as object, + name: "Søren", + age: 15, + }) + ); + expect(asObject(JSON.parse(elements[2]?.text ?? "{}"))).toEqual( + expect.objectContaining({ + _id: expect.any(Object) as object, + name: "Laura", + age: 10, + }) + ); }); validateAutoConnectBehavior(integration, "aggregate", () => { @@ -97,3 +107,8 @@ describeWithMongoDB("aggregate tool", (integration) => { }; }); }); + +function asObject(val: unknown): Record { + if (typeof val === "object" && val !== null) return val as Record; + throw new Error("Expected an object"); +} diff --git a/tests/integration/tools/mongodb/read/collectionIndexes.test.ts b/tests/integration/tools/mongodb/read/collectionIndexes.test.ts index 3c5b2eb1..5902cfb7 100644 --- a/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +++ b/tests/integration/tools/mongodb/read/collectionIndexes.test.ts @@ -28,7 +28,7 @@ describeWithMongoDB("collectionIndexes tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(1); - expect(elements[0].text).toEqual( + expect(elements[0]?.text).toEqual( 'The indexes for "non-existent.people" cannot be determined because the collection does not exist.' ); }); @@ -47,8 +47,8 @@ describeWithMongoDB("collectionIndexes tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(2); - expect(elements[0].text).toEqual('Found 1 indexes in the collection "people":'); - expect(elements[1].text).toEqual('Name "_id_", definition: {"_id":1}'); + expect(elements[0]?.text).toEqual('Found 1 indexes in the collection "people":'); + expect(elements[1]?.text).toEqual('Name "_id_", definition: {"_id":1}'); }); it("returns all indexes for a collection", async () => { @@ -74,8 +74,8 @@ describeWithMongoDB("collectionIndexes tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(indexTypes.length + 2); - expect(elements[0].text).toEqual(`Found ${indexTypes.length + 1} indexes in the collection "people":`); - expect(elements[1].text).toEqual('Name "_id_", definition: {"_id":1}'); + expect(elements[0]?.text).toEqual(`Found ${indexTypes.length + 1} indexes in the collection "people":`); + expect(elements[1]?.text).toEqual('Name "_id_", definition: {"_id":1}'); for (const indexType of indexTypes) { const index = elements.find((element) => element.text.includes(`prop_${indexType}`)); diff --git a/tests/integration/tools/mongodb/read/find.test.ts b/tests/integration/tools/mongodb/read/find.test.ts index 05fd0b75..209ed1a5 100644 --- a/tests/integration/tools/mongodb/read/find.test.ts +++ b/tests/integration/tools/mongodb/read/find.test.ts @@ -149,10 +149,10 @@ describeWithMongoDB("find tool", (integration) => { }); const elements = getResponseElements(response.content); expect(elements).toHaveLength(expected.length + 1); - expect(elements[0].text).toEqual(`Found ${expected.length} documents in the collection "foo":`); + expect(elements[0]?.text).toEqual(`Found ${expected.length} documents in the collection "foo":`); for (let i = 0; i < expected.length; i++) { - expect(JSON.parse(elements[i + 1].text)).toEqual(expected[i]); + expect(JSON.parse(elements[i + 1]?.text ?? "{}")).toEqual(expected[i]); } }); } @@ -165,11 +165,11 @@ describeWithMongoDB("find tool", (integration) => { }); const elements = getResponseElements(response.content); expect(elements).toHaveLength(11); - expect(elements[0].text).toEqual('Found 10 documents in the collection "foo":'); + expect(elements[0]?.text).toEqual('Found 10 documents in the collection "foo":'); for (let i = 0; i < 10; i++) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(JSON.parse(elements[i + 1].text).value).toEqual(i); + expect(JSON.parse(elements[i + 1]?.text ?? "{}").value).toEqual(i); } }); @@ -194,10 +194,10 @@ describeWithMongoDB("find tool", (integration) => { const elements = getResponseElements(response.content); expect(elements).toHaveLength(2); - expect(elements[0].text).toEqual('Found 1 documents in the collection "foo":'); + expect(elements[0]?.text).toEqual('Found 1 documents in the collection "foo":'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(JSON.parse(elements[1].text).value).toEqual(fooObject.value); + expect(JSON.parse(elements[1]?.text ?? "{}").value).toEqual(fooObject.value); }); }); diff --git a/tests/integration/tools/mongodb/update/renameCollection.test.ts b/tests/integration/tools/mongodb/update/renameCollection.test.ts index e3d00e54..6e7a4128 100644 --- a/tests/integration/tools/mongodb/update/renameCollection.test.ts +++ b/tests/integration/tools/mongodb/update/renameCollection.test.ts @@ -94,7 +94,7 @@ describeWithMongoDB("renameCollection tool", (integration) => { .find({}) .toArray(); expect(docsInAfter).toHaveLength(1); - expect(docsInAfter[0].value).toEqual(42); + expect(docsInAfter[0]?.value).toEqual(42); }); it("returns an error when renaming to an existing collection", async () => { @@ -123,7 +123,7 @@ describeWithMongoDB("renameCollection tool", (integration) => { .find({}) .toArray(); expect(docsInBefore).toHaveLength(1); - expect(docsInBefore[0].value).toEqual(42); + expect(docsInBefore[0]?.value).toEqual(42); const docsInAfter = await integration .mongoClient() @@ -132,7 +132,7 @@ describeWithMongoDB("renameCollection tool", (integration) => { .find({}) .toArray(); expect(docsInAfter).toHaveLength(1); - expect(docsInAfter[0].value).toEqual(84); + expect(docsInAfter[0]?.value).toEqual(84); }); it("renames to existing collection with dropTarget", async () => { @@ -174,7 +174,7 @@ describeWithMongoDB("renameCollection tool", (integration) => { .find({}) .toArray(); expect(docsInAfter).toHaveLength(1); - expect(docsInAfter[0].value).toEqual(42); + expect(docsInAfter[0]?.value).toEqual(42); }); }); diff --git a/tests/unit/EJsonTransport.test.ts b/tests/unit/EJsonTransport.test.ts index f0371cf4..6bbb7999 100644 --- a/tests/unit/EJsonTransport.test.ts +++ b/tests/unit/EJsonTransport.test.ts @@ -37,7 +37,7 @@ describe("EJsonTransport", () => { ); expect(messages.length).toBe(1); - const message = messages[0].message; + const message = messages[0]?.message; expect(message).toEqual({ jsonrpc: "2.0", diff --git a/tests/unit/session.test.ts b/tests/unit/session.test.ts index f60feca1..44126359 100644 --- a/tests/unit/session.test.ts +++ b/tests/unit/session.test.ts @@ -53,7 +53,7 @@ describe("Session", () => { typeof NodeDriverServiceProvider.connect >; expect(connectMock).toHaveBeenCalledOnce(); - const connectionString = connectMock.mock.calls[0][0]; + const connectionString = connectMock.mock.calls[0]?.[0]; if (testCase.expectAppName) { expect(connectionString).toContain("appName=MongoDB+MCP+Server"); } else { diff --git a/tsconfig.build.json b/tsconfig.build.json index 1fe57f10..57edf983 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -7,6 +7,7 @@ "outDir": "./dist", "strict": true, "strictNullChecks": true, + "noUncheckedIndexedAccess": true, "esModuleInterop": true, "types": ["node"], "sourceMap": true,