Skip to content

Commit ee7ce69

Browse files
committed
fix state-saving
1 parent 45e10a8 commit ee7ce69

File tree

5 files changed

+42
-42
lines changed

5 files changed

+42
-42
lines changed

src/config.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import os from "os";
55

66
import argv from "yargs-parser";
77

8+
const localDataPath = getLocalDataPath();
9+
810
// If we decide to support non-string config options, we'll need to extend the mechanism for parsing
911
// env variables.
1012
interface UserConfig extends Record<string, string> {
@@ -19,7 +21,7 @@ const cliConfig = argv(process.argv.slice(2)) as unknown as Partial<UserConfig>;
1921
const defaults: UserConfig = {
2022
apiBaseUrl: "https://cloud.mongodb.com/",
2123
clientId: "0oabtxactgS3gHIR0297",
22-
stateFile: path.join(getLocalDataPath(), "state.json"),
24+
stateFile: path.join(localDataPath, "state.json"),
2325
projectId: "",
2426
};
2527

@@ -36,21 +38,27 @@ const config = {
3638
atlasApiVersion: `2025-03-12`,
3739
version: packageJson.version,
3840
userAgent: `AtlasMCP/${packageJson.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
39-
localDataPath: getLocalDataPath(),
41+
localDataPath,
4042
};
4143

4244
export default config;
4345

4446
function getLocalDataPath(): string {
47+
let result: string | undefined;
48+
4549
if (process.platform === "win32") {
4650
const appData = process.env.APPDATA;
4751
const localAppData = process.env.LOCALAPPDATA ?? process.env.APPDATA;
4852
if (localAppData && appData) {
49-
return path.join(localAppData, "mongodb", "mongodb-mcp");
53+
result = path.join(localAppData, "mongodb", "mongodb-mcp");
5054
}
5155
}
5256

53-
return path.join(os.homedir(), ".mongodb", "mongodb-mcp");
57+
result ??= path.join(os.homedir(), ".mongodb", "mongodb-mcp");
58+
59+
fs.mkdirSync(result, { recursive: true });
60+
61+
return result;
5462
}
5563

5664
// Gets the config supplied by the user as environment variables. The variable names
@@ -75,7 +83,7 @@ function getEnvConfig(): Partial<UserConfig> {
7583
// Gets the config supplied by the user as a JSON file. The file is expected to be located in the local data path
7684
// and named `config.json`.
7785
function getFileConfig(): Partial<UserConfig> {
78-
const configPath = path.join(getLocalDataPath(), "config.json");
86+
const configPath = path.join(localDataPath, "config.json");
7987

8088
try {
8189
const config = fs.readFileSync(configPath, "utf8");

src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ export class Server {
2121

2222
this.apiClient = new ApiClient({
2323
token: this.state?.auth.token,
24-
saveToken: (token) => {
24+
saveToken: async (token) => {
2525
if (!this.state) {
2626
throw new Error("State is not initialized");
2727
}
2828
this.state.auth.code = undefined;
2929
this.state.auth.token = token;
3030
this.state.auth.status = "issued";
31-
saveState(this.state);
31+
await saveState(this.state);
3232
},
3333
});
3434

src/state.ts

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs from "fs";
1+
import fs from "fs/promises";
22
import config from "./config.js";
33
import { OauthDeviceCode, OAuthToken } from "./common/atlas/apiClient.js";
44

@@ -8,37 +8,26 @@ export interface State {
88
code?: OauthDeviceCode;
99
token?: OAuthToken;
1010
};
11+
connectionString?: string;
1112
}
1213

1314
export async function saveState(state: State): Promise<void> {
14-
return new Promise((resolve, reject) => {
15-
fs.writeFile(config.stateFile, JSON.stringify(state), function (err) {
16-
if (err) {
17-
return reject(err);
18-
}
19-
20-
return resolve();
21-
});
22-
});
15+
await fs.writeFile(config.stateFile, JSON.stringify(state), { encoding: "utf-8" });
2316
}
2417

25-
export async function loadState() {
26-
return new Promise<State>((resolve, reject) => {
27-
fs.readFile(config.stateFile, "utf-8", (err, data) => {
28-
if (err) {
29-
if (err.code === "ENOENT") {
30-
// File does not exist, return default state
31-
const defaultState: State = {
32-
auth: {
33-
status: "not_auth",
34-
},
35-
};
36-
return resolve(defaultState);
37-
} else {
38-
return reject(err);
39-
}
40-
}
41-
return resolve(JSON.parse(data) as State);
42-
});
43-
});
18+
export async function loadState(): Promise<State> {
19+
try {
20+
const data = await fs.readFile(config.stateFile, "utf-8");
21+
return JSON.parse(data) as State;
22+
} catch (err: unknown) {
23+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
24+
return {
25+
auth: {
26+
status: "not_auth",
27+
},
28+
};
29+
}
30+
31+
throw err;
32+
}
4433
}

src/tools/atlas/auth.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class AuthTool extends AtlasToolBase {
1111
protected argsShape = {};
1212

1313
private async isAuthenticated(): Promise<boolean> {
14-
return isAuthenticated(this.state!, this.apiClient);
14+
return isAuthenticated(this.state, this.apiClient);
1515
}
1616

1717
async execute(): Promise<CallToolResult> {
@@ -25,11 +25,11 @@ export class AuthTool extends AtlasToolBase {
2525
try {
2626
const code = await this.apiClient.authenticate();
2727

28-
this.state!.auth.status = "requested";
29-
this.state!.auth.code = code;
30-
this.state!.auth.token = undefined;
28+
this.state.auth.status = "requested";
29+
this.state.auth.code = code;
30+
this.state.auth.token = undefined;
3131

32-
await saveState(this.state!);
32+
await saveState(this.state);
3333

3434
return {
3535
content: [

src/tools/mongodb/connect.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver
44
import { DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
55
import { ToolArgs } from "../tool";
66
import { ErrorCodes, MongoDBError } from "../../errors.js";
7+
import { saveState } from "../../state.js";
78

89
export class ConnectTool extends MongoDBToolBase {
910
protected name = "connect";
@@ -20,8 +21,8 @@ export class ConnectTool extends MongoDBToolBase {
2021
protected async execute({
2122
connectionStringOrClusterName,
2223
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
24+
connectionStringOrClusterName ??= this.state.connectionString;
2325
if (!connectionStringOrClusterName) {
24-
// TODO: try reconnecting to the default connection
2526
return {
2627
content: [
2728
{ type: "text", text: "No connection details provided." },
@@ -71,5 +72,7 @@ export class ConnectTool extends MongoDBToolBase {
7172
});
7273

7374
this.mongodbState.serviceProvider = provider;
75+
this.state.connectionString = connectionString;
76+
await saveState(this.state);
7477
}
7578
}

0 commit comments

Comments
 (0)