Skip to content

Commit d1df244

Browse files
authored
Merge branch 'main' into api-error-check
2 parents b79e533 + d061543 commit d1df244

File tree

9 files changed

+552
-96
lines changed

9 files changed

+552
-96
lines changed

README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ You can pass your connection string via args, make sure to use a valid username
6767

6868
#### Option 2: Atlas API credentials args
6969

70-
Use your Atlas API Service Accounts credentials. More details in the [Atlas API Access](#atlas-api-access) section.
70+
Use your Atlas API Service Accounts credentials. Must follow all the steps in [Atlas API Access](#atlas-api-access) section.
7171

7272
```json
7373
{
@@ -87,9 +87,24 @@ Use your Atlas API Service Accounts credentials. More details in the [Atlas API
8787
}
8888
```
8989

90-
#### Other options
90+
### Option 3: Standalone Service using command arguments
9191

92-
Alternatively you can use environment variables in the config file or set them and run the server via npx.
92+
Start Server using npx command:
93+
94+
```shell
95+
npx -y mongodb-mcp-server --apiClientId="your-atlas-service-accounts-client-id" --apiClientSecret="your-atlas-service-accounts-client-secret"
96+
```
97+
98+
- For a complete list of arguments see [Configuration Options](#configuration-options)
99+
- To configure your Atlas Service Accounts credentials please refer to [Atlas API Access](#atlas-api-access)
100+
101+
#### Option 4: Standalone Service using environment variables
102+
103+
```shell
104+
npx -y mongodb-mcp-server
105+
```
106+
107+
You can use environment variables in the config file or set them and run the server via npx.
93108

94109
- Connection String via environment variables in the MCP file [example](#connection-string-with-environment-variables)
95110
- Atlas API credentials via environment variables in the MCP file [example](#atlas-api-credentials-with-environment-variables)
@@ -229,7 +244,7 @@ To learn more about Service Accounts, check the [MongoDB Atlas documentation](ht
229244
- After creation, you'll be shown the Client ID and Client Secret
230245
- **Important:** Copy and save the Client Secret immediately as it won't be displayed again
231246

232-
3. **Add Access List Entry (Optional but recommended):**
247+
3. **Add Access List Entry:**
233248

234249
- Add your IP address to the API access list
235250

scripts/filter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ function filterOpenapi(openapi: OpenAPIV3_1.Document): OpenAPIV3_1.Document {
2525
"createProject",
2626
"deleteProject",
2727
"listClusters",
28+
"listFlexClusters",
2829
"getCluster",
30+
"getFlexCluster",
2931
"createCluster",
32+
"createFlexCluster",
3033
"deleteCluster",
34+
"deleteFlexCluster",
3135
"listClustersForAllProjects",
3236
"createDatabaseUser",
3337
"deleteDatabaseUser",

src/common/atlas/apiClient.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,46 @@ export class ApiClient {
275275
}
276276
}
277277

278+
async listFlexClusters(options: FetchOptions<operations["listFlexClusters"]>) {
279+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/flexClusters", options);
280+
if (error) {
281+
throw ApiClientError.fromError(response, error);
282+
}
283+
return data;
284+
}
285+
286+
async createFlexCluster(options: FetchOptions<operations["createFlexCluster"]>) {
287+
const { data, error, response } = await this.client.POST(
288+
"/api/atlas/v2/groups/{groupId}/flexClusters",
289+
options
290+
);
291+
if (error) {
292+
throw ApiClientError.fromError(response, error);
293+
}
294+
return data;
295+
}
296+
297+
async deleteFlexCluster(options: FetchOptions<operations["deleteFlexCluster"]>) {
298+
const { error, response } = await this.client.DELETE(
299+
"/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
300+
options
301+
);
302+
if (error) {
303+
throw ApiClientError.fromError(response, error);
304+
}
305+
}
306+
307+
async getFlexCluster(options: FetchOptions<operations["getFlexCluster"]>) {
308+
const { data, error, response } = await this.client.GET(
309+
"/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
310+
options
311+
);
312+
if (error) {
313+
throw ApiClientError.fromError(response, error);
314+
}
315+
return data;
316+
}
317+
278318
async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
279319
const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
280320
if (error) {

src/common/atlas/cluster.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { ClusterDescription20240805, FlexClusterDescription20241113 } from "./openapi.js";
2+
import { ApiClient } from "./apiClient.js";
3+
import logger, { LogId } from "../../logger.js";
4+
5+
export interface Cluster {
6+
name?: string;
7+
instanceType: "FREE" | "DEDICATED" | "FLEX";
8+
instanceSize?: string;
9+
state?: "IDLE" | "CREATING" | "UPDATING" | "DELETING" | "REPAIRING";
10+
mongoDBVersion?: string;
11+
connectionString?: string;
12+
}
13+
14+
export function formatFlexCluster(cluster: FlexClusterDescription20241113): Cluster {
15+
return {
16+
name: cluster.name,
17+
instanceType: "FLEX",
18+
instanceSize: undefined,
19+
state: cluster.stateName,
20+
mongoDBVersion: cluster.mongoDBVersion,
21+
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
22+
};
23+
}
24+
25+
export function formatCluster(cluster: ClusterDescription20240805): Cluster {
26+
const regionConfigs = (cluster.replicationSpecs || [])
27+
.map(
28+
(replicationSpec) =>
29+
(replicationSpec.regionConfigs || []) as {
30+
providerName: string;
31+
electableSpecs?: {
32+
instanceSize: string;
33+
};
34+
readOnlySpecs?: {
35+
instanceSize: string;
36+
};
37+
analyticsSpecs?: {
38+
instanceSize: string;
39+
};
40+
}[]
41+
)
42+
.flat()
43+
.map((regionConfig) => {
44+
return {
45+
providerName: regionConfig.providerName,
46+
instanceSize:
47+
regionConfig.electableSpecs?.instanceSize ||
48+
regionConfig.readOnlySpecs?.instanceSize ||
49+
regionConfig.analyticsSpecs?.instanceSize,
50+
};
51+
});
52+
53+
const instanceSize = (regionConfigs.length <= 0 ? undefined : regionConfigs[0].instanceSize) || "UNKNOWN";
54+
55+
const clusterInstanceType = instanceSize == "M0" ? "FREE" : "DEDICATED";
56+
57+
return {
58+
name: cluster.name,
59+
instanceType: clusterInstanceType,
60+
instanceSize: clusterInstanceType == "DEDICATED" ? instanceSize : undefined,
61+
state: cluster.stateName,
62+
mongoDBVersion: cluster.mongoDBVersion,
63+
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
64+
};
65+
}
66+
67+
export async function inspectCluster(apiClient: ApiClient, projectId: string, clusterName: string): Promise<Cluster> {
68+
try {
69+
const cluster = await apiClient.getCluster({
70+
params: {
71+
path: {
72+
groupId: projectId,
73+
clusterName,
74+
},
75+
},
76+
});
77+
return formatCluster(cluster);
78+
} catch (error) {
79+
try {
80+
const cluster = await apiClient.getFlexCluster({
81+
params: {
82+
path: {
83+
groupId: projectId,
84+
name: clusterName,
85+
},
86+
},
87+
});
88+
return formatFlexCluster(cluster);
89+
} catch (flexError) {
90+
const err = flexError instanceof Error ? flexError : new Error(String(flexError));
91+
logger.error(LogId.atlasInspectFailure, "inspect-cluster", `error inspecting cluster: ${err.message}`);
92+
throw error;
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)