Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions scripts/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand All @@ -84,7 +84,7 @@ async function main() {
operationId: operation.operationId || "",
requiredParams,
hasResponseBody,
tag: operation.tags[0],
tag: operation.tags && operation.tags.length > 0 && operation.tags[0] ? operation.tags[0] : "",
});
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/common/atlas/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster {
};
});

const instanceSize = (regionConfigs.length <= 0 ? undefined : regionConfigs[0].instanceSize) || "UNKNOWN";
const instanceSize =
regionConfigs.length > 0 && regionConfigs[0]?.instanceSize ? regionConfigs[0].instanceSize : "UNKNOWN";

const clusterInstanceType = instanceSize == "M0" ? "FREE" : "DEDICATED";

Expand Down
2 changes: 2 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 7 additions & 1 deletion src/tools/atlas/create/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 4 additions & 2 deletions src/tools/atlas/read/listProjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export class ListProjectsTool extends AtlasToolBase {

const orgs: Record<string, string> = 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({
Expand All @@ -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 = project.orgId && orgs[project.orgId] ? 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
Expand Down
6 changes: 6 additions & 0 deletions src/tools/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/tools/atlas/accessLists.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});

Expand All @@ -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);
}
});
});
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/tools/atlas/alerts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions tests/integration/tools/atlas/atlasHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ export function parseTable(text: string): Record<string, string>[] {
.map((cells) => {
const row: Record<string, string> = {};
cells.forEach((cell, index) => {
row[headers[index]] = cell;
if (headers) {
row[headers[index] ?? ""] = cell;
}
Comment on lines +78 to +80
Copy link

Copilot AI Jun 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The headers array is always defined here; you can remove the runtime if check and use row[headers[index]!] = cell; to simplify the code.

Suggested change
if (headers) {
row[headers[index] ?? ""] = cell;
}
row[headers[index]!] = cell;

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'headers' is possibly 'undefined'.ts(18048)

});
return row;
});
Expand All @@ -87,14 +89,14 @@ async function createProject(apiClient: ApiClient): Promise<Group> {
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,
});

Expand Down
8 changes: 4 additions & 4 deletions tests/integration/tools/atlas/clusters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});

Expand All @@ -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} | `);
});
});

Expand All @@ -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} | `);
});
});

Expand Down Expand Up @@ -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}"`);
});
});
});
Expand Down
14 changes: 7 additions & 7 deletions tests/integration/tools/atlas/dbUsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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);
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/tools/atlas/orgs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
});
6 changes: 3 additions & 3 deletions tests/integration/tools/atlas/projects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});

Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/tools/mongodb/create/createIndex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
Expand Down
8 changes: 4 additions & 4 deletions tests/integration/tools/mongodb/metadata/dbStats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
20 changes: 10 additions & 10 deletions tests/integration/tools/mongodb/metadata/explain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
}
});
Expand Down Expand Up @@ -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");
}
});
}
Expand Down
Loading
Loading