Skip to content

Commit c862f5c

Browse files
committed
Merge branch 'main' into fmenezes/atlas_db_users
2 parents eb57608 + 27bc07b commit c862f5c

File tree

7 files changed

+263
-1
lines changed

7 files changed

+263
-1
lines changed

scripts/filter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ function filterOpenapi(openapi: OpenAPIV3_1.Document): OpenAPIV3_1.Document {
2626
"listClustersForAllProjects",
2727
"createDatabaseUser",
2828
"listDatabaseUsers",
29+
"listProjectIpAccessLists",
30+
"createProjectIpAccessList",
2931
];
3032

3133
const filteredPaths = {};

src/common/atlas/apiClient.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
PaginatedAtlasGroupView,
77
ClusterDescription20240805,
88
PaginatedClusterDescription20240805,
9+
PaginatedNetworkAccessView,
10+
NetworkPermissionEntry,
911
CloudDatabaseUser,
1012
PaginatedApiAtlasDatabaseUserView,
1113
} from "./openapi.js";
@@ -67,7 +69,7 @@ export class ApiClient {
6769
credentials: !this.token?.access_token ? undefined : "include",
6870
headers: {
6971
"Content-Type": "application/json",
70-
Accept: "application/vnd.atlas.2025-04-07+json",
72+
Accept: `application/vnd.atlas.${config.atlasApiVersion}+json`,
7173
"User-Agent": config.userAgent,
7274
...authHeaders,
7375
},
@@ -271,6 +273,20 @@ export class ApiClient {
271273
return await this.do<PaginatedAtlasGroupView>("/groups");
272274
}
273275

276+
async listProjectIpAccessLists(groupId: string): Promise<PaginatedNetworkAccessView> {
277+
return await this.do<PaginatedNetworkAccessView>(`/groups/${groupId}/accessList`);
278+
}
279+
280+
async createProjectIpAccessList(
281+
groupId: string,
282+
entries: NetworkPermissionEntry[]
283+
): Promise<PaginatedNetworkAccessView> {
284+
return await this.do<PaginatedNetworkAccessView>(`/groups/${groupId}/accessList`, {
285+
method: "POST",
286+
body: JSON.stringify(entries),
287+
});
288+
}
289+
274290
async getProject(groupId: string): Promise<Group> {
275291
return await this.do<Group>(`/groups/${groupId}`);
276292
}

src/common/atlas/openapi.d.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ export interface paths {
6868
patch?: never;
6969
trace?: never;
7070
};
71+
"/api/atlas/v2/groups/{groupId}/accessList": {
72+
parameters: {
73+
query?: never;
74+
header?: never;
75+
path?: never;
76+
cookie?: never;
77+
};
78+
/**
79+
* Return Project IP Access List
80+
* @description Returns all access list entries from the specified project's IP access list. Each entry in the project's IP access list contains either one IP address or one CIDR-notated block of IP addresses. MongoDB Cloud only allows client connections to the cluster from entries in the project's IP access list. To use this resource, the requesting Service Account or API Key must have the Project Read Only or Project Charts Admin roles. This resource replaces the whitelist resource. MongoDB Cloud removed whitelists in July 2021. Update your applications to use this new resource. The `/groups/{GROUP-ID}/accessList` endpoint manages the database IP access list. This endpoint is distinct from the `orgs/{ORG-ID}/apiKeys/{API-KEY-ID}/accesslist` endpoint, which manages the access list for MongoDB Cloud organizations.
81+
*/
82+
get: operations["listProjectIpAccessLists"];
83+
put?: never;
84+
/**
85+
* Add Entries to Project IP Access List
86+
* @description Adds one or more access list entries to the specified project. MongoDB Cloud only allows client connections to the cluster from entries in the project's IP access list. Write each entry as either one IP address or one CIDR-notated block of IP addresses. To use this resource, the requesting Service Account or API Key must have the Project Owner or Project Charts Admin roles. This resource replaces the whitelist resource. MongoDB Cloud removed whitelists in July 2021. Update your applications to use this new resource. The `/groups/{GROUP-ID}/accessList` endpoint manages the database IP access list. This endpoint is distinct from the `orgs/{ORG-ID}/apiKeys/{API-KEY-ID}/accesslist` endpoint, which manages the access list for MongoDB Cloud organizations. This endpoint doesn't support concurrent `POST` requests. You must submit multiple `POST` requests synchronously.
87+
*/
88+
post: operations["createProjectIpAccessList"];
89+
delete?: never;
90+
options?: never;
91+
head?: never;
92+
patch?: never;
93+
trace?: never;
94+
};
7195
"/api/atlas/v2/groups/{groupId}/clusters": {
7296
parameters: {
7397
query?: never;
@@ -4832,6 +4856,28 @@ export interface components {
48324856
*/
48334857
type: "MONTHLY";
48344858
};
4859+
NetworkPermissionEntry: {
4860+
/** @description Unique string of the Amazon Web Services (AWS) security group that you want to add to the project's IP access list. Your IP access list entry can be one **awsSecurityGroup**, one **cidrBlock**, or one **ipAddress**. You must configure Virtual Private Connection (VPC) peering for your project before you can add an AWS security group to an IP access list. You cannot set AWS security groups as temporary access list entries. Don't set this parameter if you set **cidrBlock** or **ipAddress**. */
4861+
awsSecurityGroup?: string;
4862+
/** @description Range of IP addresses in Classless Inter-Domain Routing (CIDR) notation that you want to add to the project's IP access list. Your IP access list entry can be one **awsSecurityGroup**, one **cidrBlock**, or one **ipAddress**. Don't set this parameter if you set **awsSecurityGroup** or **ipAddress**. */
4863+
cidrBlock?: string;
4864+
/** @description Remark that explains the purpose or scope of this IP access list entry. */
4865+
comment?: string;
4866+
/**
4867+
* Format: date-time
4868+
* @description Date and time after which MongoDB Cloud deletes the temporary access list entry. This parameter expresses its value in the ISO 8601 timestamp format in UTC and can include the time zone designation. The date must be later than the current date but no later than one week after you submit this request. The resource returns this parameter if you specified an expiration date when creating this IP access list entry.
4869+
*/
4870+
deleteAfterDate?: string;
4871+
/**
4872+
* @description Unique 24-hexadecimal digit string that identifies the project that contains the IP access list to which you want to add one or more entries.
4873+
* @example 32b6e34b3d91647abb20e7b8
4874+
*/
4875+
readonly groupId?: string;
4876+
/** @description IP address that you want to add to the project's IP access list. Your IP access list entry can be one **awsSecurityGroup**, one **cidrBlock**, or one **ipAddress**. Don't set this parameter if you set **awsSecurityGroup** or **cidrBlock**. */
4877+
ipAddress?: string;
4878+
/** @description List of one or more Uniform Resource Locators (URLs) that point to API sub-resources, related API resources, or both. RFC 5988 outlines these relationships. */
4879+
readonly links?: components["schemas"]["Link"][];
4880+
};
48354881
/**
48364882
* On-Demand Cloud Provider Snapshot Source
48374883
* @description On-Demand Cloud Provider Snapshots as Source for a Data Lake Pipeline.
@@ -5031,6 +5077,17 @@ export interface components {
50315077
*/
50325078
readonly totalCount?: number;
50335079
};
5080+
PaginatedNetworkAccessView: {
5081+
/** @description List of one or more Uniform Resource Locators (URLs) that point to API sub-resources, related API resources, or both. RFC 5988 outlines these relationships. */
5082+
readonly links?: components["schemas"]["Link"][];
5083+
/** @description List of returned documents that MongoDB Cloud provides when completing this request. */
5084+
readonly results?: components["schemas"]["NetworkPermissionEntry"][];
5085+
/**
5086+
* Format: int32
5087+
* @description Total number of documents available. MongoDB Cloud omits this value if `includeCount` is set to `false`. The total number is an estimate and may not be exact.
5088+
*/
5089+
readonly totalCount?: number;
5090+
};
50345091
PaginatedOrgGroupView: {
50355092
/** @description List of one or more Uniform Resource Locators (URLs) that point to API sub-resources, related API resources, or both. RFC 5988 outlines these relationships. */
50365093
readonly links?: components["schemas"]["Link"][];
@@ -6999,6 +7056,7 @@ export type IngestionSource = components["schemas"]["IngestionSource"];
69997056
export type InvoiceLineItem = components["schemas"]["InvoiceLineItem"];
70007057
export type Link = components["schemas"]["Link"];
70017058
export type MonthlyScheduleView = components["schemas"]["MonthlyScheduleView"];
7059+
export type NetworkPermissionEntry = components["schemas"]["NetworkPermissionEntry"];
70027060
export type OnDemandCpsSnapshotSource = components["schemas"]["OnDemandCpsSnapshotSource"];
70037061
export type OnlineArchiveSchedule = components["schemas"]["OnlineArchiveSchedule"];
70047062
export type OrgActiveUserResponse = components["schemas"]["OrgActiveUserResponse"];
@@ -7009,6 +7067,7 @@ export type OrgUserRolesResponse = components["schemas"]["OrgUserRolesResponse"]
70097067
export type PaginatedApiAtlasDatabaseUserView = components["schemas"]["PaginatedApiAtlasDatabaseUserView"];
70107068
export type PaginatedAtlasGroupView = components["schemas"]["PaginatedAtlasGroupView"];
70117069
export type PaginatedClusterDescription20240805 = components["schemas"]["PaginatedClusterDescription20240805"];
7070+
export type PaginatedNetworkAccessView = components["schemas"]["PaginatedNetworkAccessView"];
70127071
export type PaginatedOrgGroupView = components["schemas"]["PaginatedOrgGroupView"];
70137072
export type PeriodicCpsSnapshotSource = components["schemas"]["PeriodicCpsSnapshotSource"];
70147073
export type ReplicationSpec20240805 = components["schemas"]["ReplicationSpec20240805"];
@@ -7244,6 +7303,89 @@ export interface operations {
72447303
500: components["responses"]["internalServerError"];
72457304
};
72467305
};
7306+
listProjectIpAccessLists: {
7307+
parameters: {
7308+
query?: {
7309+
/** @description Flag that indicates whether Application wraps the response in an `envelope` JSON object. Some API clients cannot access the HTTP response headers or status code. To remediate this, set envelope=true in the query. Endpoints that return a list of results use the results object as an envelope. Application adds the status parameter to the response body. */
7310+
envelope?: components["parameters"]["envelope"];
7311+
/** @description Flag that indicates whether the response returns the total number of items (**totalCount**) in the response. */
7312+
includeCount?: components["parameters"]["includeCount"];
7313+
/** @description Number of items that the response returns per page. */
7314+
itemsPerPage?: components["parameters"]["itemsPerPage"];
7315+
/** @description Number of the page that displays the current set of the total objects that the response returns. */
7316+
pageNum?: components["parameters"]["pageNum"];
7317+
/** @description Flag that indicates whether the response body should be in the prettyprint format. */
7318+
pretty?: components["parameters"]["pretty"];
7319+
};
7320+
header?: never;
7321+
path: {
7322+
/** @description Unique 24-hexadecimal digit string that identifies your project. Use the [/groups](#tag/Projects/operation/listProjects) endpoint to retrieve all projects to which the authenticated user has access.
7323+
*
7324+
* **NOTE**: Groups and projects are synonymous terms. Your group id is the same as your project id. For existing groups, your group/project id remains the same. The resource and corresponding endpoints use the term groups. */
7325+
groupId: components["parameters"]["groupId"];
7326+
};
7327+
cookie?: never;
7328+
};
7329+
requestBody?: never;
7330+
responses: {
7331+
/** @description OK */
7332+
200: {
7333+
headers: {
7334+
[name: string]: unknown;
7335+
};
7336+
content: {
7337+
"application/vnd.atlas.2023-01-01+json": components["schemas"]["PaginatedNetworkAccessView"];
7338+
};
7339+
};
7340+
401: components["responses"]["unauthorized"];
7341+
500: components["responses"]["internalServerError"];
7342+
};
7343+
};
7344+
createProjectIpAccessList: {
7345+
parameters: {
7346+
query?: {
7347+
/** @description Flag that indicates whether Application wraps the response in an `envelope` JSON object. Some API clients cannot access the HTTP response headers or status code. To remediate this, set envelope=true in the query. Endpoints that return a list of results use the results object as an envelope. Application adds the status parameter to the response body. */
7348+
envelope?: components["parameters"]["envelope"];
7349+
/** @description Flag that indicates whether the response returns the total number of items (**totalCount**) in the response. */
7350+
includeCount?: components["parameters"]["includeCount"];
7351+
/** @description Number of items that the response returns per page. */
7352+
itemsPerPage?: components["parameters"]["itemsPerPage"];
7353+
/** @description Number of the page that displays the current set of the total objects that the response returns. */
7354+
pageNum?: components["parameters"]["pageNum"];
7355+
/** @description Flag that indicates whether the response body should be in the prettyprint format. */
7356+
pretty?: components["parameters"]["pretty"];
7357+
};
7358+
header?: never;
7359+
path: {
7360+
/** @description Unique 24-hexadecimal digit string that identifies your project. Use the [/groups](#tag/Projects/operation/listProjects) endpoint to retrieve all projects to which the authenticated user has access.
7361+
*
7362+
* **NOTE**: Groups and projects are synonymous terms. Your group id is the same as your project id. For existing groups, your group/project id remains the same. The resource and corresponding endpoints use the term groups. */
7363+
groupId: components["parameters"]["groupId"];
7364+
};
7365+
cookie?: never;
7366+
};
7367+
/** @description One or more access list entries to add to the specified project. */
7368+
requestBody: {
7369+
content: {
7370+
"application/vnd.atlas.2023-01-01+json": components["schemas"]["NetworkPermissionEntry"][];
7371+
};
7372+
};
7373+
responses: {
7374+
/** @description OK */
7375+
200: {
7376+
headers: {
7377+
[name: string]: unknown;
7378+
};
7379+
content: {
7380+
"application/vnd.atlas.2023-01-01+json": components["schemas"]["PaginatedNetworkAccessView"];
7381+
};
7382+
};
7383+
400: components["responses"]["badRequest"];
7384+
401: components["responses"]["unauthorized"];
7385+
403: components["responses"]["forbidden"];
7386+
500: components["responses"]["internalServerError"];
7387+
};
7388+
};
72477389
listClusters: {
72487390
parameters: {
72497391
query?: {

src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const packageMetadata = fs.readFileSync(path.join(__dirname, "..", "package.json
99
const packageJson = JSON.parse(packageMetadata);
1010

1111
export const config = {
12+
atlasApiVersion: `2025-03-12`,
1213
version: packageJson.version,
1314
apiBaseURL: process.env.API_BASE_URL || "https://cloud.mongodb.com/",
1415
clientID: process.env.CLIENT_ID || "0oabtxactgS3gHIR0297",

src/tools/atlas/createAccessList.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { AtlasToolBase } from "./atlasTool.js";
4+
import { ToolArgs } from "../tool.js";
5+
6+
const DEFAULT_COMMENT = "Added by Atlas MCP";
7+
8+
export class CreateAccessListTool extends AtlasToolBase {
9+
protected name = "atlas-create-access-list";
10+
protected description = "Allow Ip/CIDR ranges to access your MongoDB Atlas clusters.";
11+
protected argsShape = {
12+
projectId: z.string().describe("Atlas project ID"),
13+
ipAddresses: z
14+
.array(z.string().ip({ version: "v4" }))
15+
.describe("IP addresses to allow access from")
16+
.optional(),
17+
cidrBlocks: z.array(z.string().cidr()).describe("CIDR blocks to allow access from").optional(),
18+
comment: z.string().describe("Comment for the access list entries").default(DEFAULT_COMMENT).optional(),
19+
};
20+
21+
protected async execute({
22+
projectId,
23+
ipAddresses,
24+
cidrBlocks,
25+
comment,
26+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
27+
await this.ensureAuthenticated();
28+
29+
if (!ipAddresses?.length && !cidrBlocks?.length) {
30+
throw new Error("Either ipAddresses or cidrBlocks must be provided.");
31+
}
32+
33+
const ipInputs = (ipAddresses || []).map((ipAddress) => ({
34+
groupId: projectId,
35+
ipAddress,
36+
comment: comment || DEFAULT_COMMENT,
37+
}));
38+
39+
const cidrInputs = (cidrBlocks || []).map((cidrBlock) => ({
40+
groupId: projectId,
41+
cidrBlock,
42+
comment: comment || DEFAULT_COMMENT,
43+
}));
44+
45+
const inputs = [...ipInputs, ...cidrInputs];
46+
47+
await this.apiClient.createProjectIpAccessList(projectId, inputs);
48+
49+
return {
50+
content: [
51+
{
52+
type: "text",
53+
text: `IP/CIDR ranges added to access list for project ${projectId}.`,
54+
},
55+
],
56+
};
57+
}
58+
}

src/tools/atlas/inspectAccessList.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { AtlasToolBase } from "./atlasTool.js";
4+
import { ToolArgs } from "../tool.js";
5+
6+
export class InspectAccessListTool extends AtlasToolBase {
7+
protected name = "atlas-inspect-access-list";
8+
protected description = "Inspect Ip/CIDR ranges with access to your MongoDB Atlas clusters.";
9+
protected argsShape = {
10+
projectId: z.string().describe("Atlas project ID"),
11+
};
12+
13+
protected async execute({ projectId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
14+
await this.ensureAuthenticated();
15+
16+
const accessList = await this.apiClient.listProjectIpAccessLists(projectId);
17+
18+
if (!accessList.results?.length) {
19+
throw new Error("No access list entries found.");
20+
}
21+
22+
return {
23+
content: [
24+
{
25+
type: "text",
26+
text:
27+
`IP ADDRESS | CIDR | COMMENT
28+
------|------|------
29+
` +
30+
(accessList.results || [])
31+
.map((entry) => {
32+
return `${entry.ipAddress} | ${entry.cidrBlock} | ${entry.comment}`;
33+
})
34+
.join("\n"),
35+
},
36+
],
37+
};
38+
}
39+
}

src/tools/atlas/tools.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { ListClustersTool } from "./listClusters.js";
77
import { ListProjectsTool } from "./listProjects.js";
88
import { InspectClusterTool } from "./inspectCluster.js";
99
import { CreateFreeClusterTool } from "./createFreeCluster.js";
10+
import { CreateAccessListTool } from "./createAccessList.js";
11+
import { InspectAccessListTool } from "./inspectAccessList.js";
1012
import { ListDBUsersTool } from "./listDBUsers.js";
1113
import { CreateDBUserTool } from "./createDBUser.js";
1214

@@ -17,6 +19,8 @@ export function registerAtlasTools(server: McpServer, state: State, apiClient: A
1719
new ListProjectsTool(state, apiClient),
1820
new InspectClusterTool(state, apiClient),
1921
new CreateFreeClusterTool(state, apiClient),
22+
new CreateAccessListTool(state, apiClient),
23+
new InspectAccessListTool(state, apiClient),
2024
new ListDBUsersTool(state, apiClient),
2125
new CreateDBUserTool(state, apiClient),
2226
];

0 commit comments

Comments
 (0)