Skip to content

Commit fa2fdd6

Browse files
authored
refactor: move configuration to be passed (#101)
1 parent 5d378cc commit fa2fdd6

File tree

14 files changed

+127
-100
lines changed

14 files changed

+127
-100
lines changed

src/common/atlas/apiClient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import config from "../../config.js";
21
import createClient, { Client, FetchOptions, Middleware } from "openapi-fetch";
32
import { AccessToken, ClientCredentials } from "simple-oauth2";
43
import { ApiClientError } from "./apiClientError.js";
54
import { paths, operations } from "./openapi.js";
5+
import { packageInfo } from "../../packageInfo.js";
66

77
const ATLAS_API_VERSION = "2025-03-12";
88

@@ -67,7 +67,7 @@ export class ApiClient {
6767
baseUrl: options?.baseUrl || "https://cloud.mongodb.com/",
6868
userAgent:
6969
options?.userAgent ||
70-
`AtlasMCP/${config.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
70+
`AtlasMCP/${packageInfo.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
7171
};
7272

7373
this.client = createClient<paths>({

src/config.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ import path from "path";
22
import os from "os";
33
import argv from "yargs-parser";
44

5-
import packageJson from "../package.json" with { type: "json" };
65
import { ReadConcernLevel, ReadPreferenceMode, W } from "mongodb";
76

87
// If we decide to support non-string config options, we'll need to extend the mechanism for parsing
98
// env variables.
10-
interface UserConfig {
9+
export interface UserConfig {
1110
apiBaseUrl?: string;
1211
apiClientId?: string;
1312
apiClientSecret?: string;
@@ -33,19 +32,12 @@ const defaults: UserConfig = {
3332
disabledTools: [],
3433
};
3534

36-
const mergedUserConfig = {
35+
export const config = {
3736
...defaults,
3837
...getEnvConfig(),
3938
...getCliConfig(),
4039
};
4140

42-
const config = {
43-
...mergedUserConfig,
44-
version: packageJson.version,
45-
};
46-
47-
export default config;
48-
4941
function getLogPath(): string {
5042
const localDataPath =
5143
process.platform === "win32"

src/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,25 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
44
import logger from "./logger.js";
55
import { mongoLogId } from "mongodb-log-writer";
66
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7-
import config from "./config.js";
7+
import { config } from "./config.js";
88
import { Session } from "./session.js";
99
import { Server } from "./server.js";
10+
import { packageInfo } from "./packageInfo.js";
1011

1112
try {
12-
const session = new Session();
13+
const session = new Session({
14+
apiBaseUrl: config.apiBaseUrl,
15+
apiClientId: config.apiClientId,
16+
apiClientSecret: config.apiClientSecret,
17+
});
1318
const mcpServer = new McpServer({
14-
name: "MongoDB Atlas",
15-
version: config.version,
19+
name: packageInfo.mcpServerName,
20+
version: packageInfo.version,
1621
});
17-
1822
const server = new Server({
1923
mcpServer,
2024
session,
25+
userConfig: config,
2126
});
2227

2328
const transport = new StdioServerTransport();

src/logger.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import fs from "fs/promises";
22
import { MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
3-
import config from "./config.js";
43
import redact from "mongodb-redact";
54
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
65
import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
@@ -98,11 +97,11 @@ class ProxyingLogger extends LoggerBase {
9897
const logger = new ProxyingLogger();
9998
export default logger;
10099

101-
export async function initializeLogger(server: McpServer): Promise<void> {
102-
await fs.mkdir(config.logPath, { recursive: true });
100+
export async function initializeLogger(server: McpServer, logPath: string): Promise<void> {
101+
await fs.mkdir(logPath, { recursive: true });
103102

104103
const manager = new MongoLogManager({
105-
directory: config.logPath,
104+
directory: logPath,
106105
retentionDays: 30,
107106
onwarn: console.warn,
108107
onerror: console.error,

src/packageInfo.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import packageJson from "../package.json" with { type: "json" };
2+
3+
export const packageInfo = {
4+
version: packageJson.version,
5+
mcpServerName: "MongoDB MCP Server",
6+
};

src/server.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ import { AtlasTools } from "./tools/atlas/tools.js";
55
import { MongoDbTools } from "./tools/mongodb/tools.js";
66
import logger, { initializeLogger } from "./logger.js";
77
import { mongoLogId } from "mongodb-log-writer";
8-
import config from "./config.js";
8+
import { UserConfig } from "./config.js";
9+
10+
export interface ServerOptions {
11+
session: Session;
12+
userConfig: UserConfig;
13+
mcpServer: McpServer;
14+
}
915

1016
export class Server {
1117
public readonly session: Session;
1218
private readonly mcpServer: McpServer;
19+
private readonly userConfig: UserConfig;
1320

14-
constructor({ mcpServer, session }: { mcpServer: McpServer; session: Session }) {
15-
this.mcpServer = mcpServer;
21+
constructor({ session, mcpServer, userConfig }: ServerOptions) {
1622
this.session = session;
23+
this.mcpServer = mcpServer;
24+
this.userConfig = userConfig;
1725
}
1826

1927
async connect(transport: Transport) {
@@ -22,7 +30,7 @@ export class Server {
2230
this.registerTools();
2331
this.registerResources();
2432

25-
await initializeLogger(this.mcpServer);
33+
await initializeLogger(this.mcpServer, this.userConfig.logPath);
2634

2735
await this.mcpServer.connect(transport);
2836

@@ -36,12 +44,12 @@ export class Server {
3644

3745
private registerTools() {
3846
for (const tool of [...AtlasTools, ...MongoDbTools]) {
39-
new tool(this.session).register(this.mcpServer);
47+
new tool(this.session, this.userConfig).register(this.mcpServer);
4048
}
4149
}
4250

4351
private registerResources() {
44-
if (config.connectionString) {
52+
if (this.userConfig.connectionString) {
4553
this.mcpServer.resource(
4654
"connection-string",
4755
"config://connection-string",
@@ -52,7 +60,7 @@ export class Server {
5260
return {
5361
contents: [
5462
{
55-
text: `Preconfigured connection string: ${config.connectionString}`,
63+
text: `Preconfigured connection string: ${this.userConfig.connectionString}`,
5664
uri: uri.href,
5765
},
5866
],

src/session.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
22
import { ApiClient, ApiClientCredentials } from "./common/atlas/apiClient.js";
3-
import config from "./config.js";
3+
4+
export interface SessionOptions {
5+
apiBaseUrl?: string;
6+
apiClientId?: string;
7+
apiClientSecret?: string;
8+
}
49

510
export class Session {
611
serviceProvider?: NodeDriverServiceProvider;
712
apiClient: ApiClient;
813

9-
constructor() {
14+
constructor({ apiBaseUrl, apiClientId, apiClientSecret }: SessionOptions = {}) {
1015
const credentials: ApiClientCredentials | undefined =
11-
config.apiClientId && config.apiClientSecret
16+
apiClientId && apiClientSecret
1217
? {
13-
clientId: config.apiClientId,
14-
clientSecret: config.apiClientSecret,
18+
clientId: apiClientId,
19+
clientSecret: apiClientSecret,
1520
}
1621
: undefined;
1722

1823
this.apiClient = new ApiClient({
19-
baseUrl: config.apiBaseUrl,
24+
baseUrl: apiBaseUrl,
2025
credentials,
2126
});
2227
}

src/tools/atlas/atlasTool.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
import { ToolBase, ToolCategory } from "../tool.js";
2-
import { Session } from "../../session.js";
3-
import config from "../../config.js";
42

53
export abstract class AtlasToolBase extends ToolBase {
6-
constructor(protected readonly session: Session) {
7-
super(session);
8-
}
9-
104
protected category: ToolCategory = "atlas";
115

126
protected verifyAllowed(): boolean {
13-
if (!config.apiClientId || !config.apiClientSecret) {
7+
if (!this.config.apiClientId || !this.config.apiClientSecret) {
148
return false;
159
}
1610
return super.verifyAllowed();

src/tools/mongodb/metadata/connect.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { MongoDBToolBase } from "../mongodbTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
5-
import config from "../../../config.js";
65
import { MongoError as DriverError } from "mongodb";
76

87
export class ConnectTool extends MongoDBToolBase {
@@ -35,7 +34,7 @@ export class ConnectTool extends MongoDBToolBase {
3534
protected async execute({ options: optionsArr }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
3635
const options = optionsArr?.[0];
3736
let connectionString: string;
38-
if (!options && !config.connectionString) {
37+
if (!options && !this.config.connectionString) {
3938
return {
4039
content: [
4140
{ type: "text", text: "No connection details provided." },
@@ -46,7 +45,7 @@ export class ConnectTool extends MongoDBToolBase {
4645

4746
if (!options) {
4847
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
49-
connectionString = config.connectionString!;
48+
connectionString = this.config.connectionString!;
5049
} else if ("connectionString" in options) {
5150
connectionString = options.connectionString;
5251
} else {
@@ -72,17 +71,17 @@ export class ConnectTool extends MongoDBToolBase {
7271
// Sometimes the model will supply an incorrect connection string. If the user has configured
7372
// a different one as environment variable or a cli argument, suggest using that one instead.
7473
if (
75-
config.connectionString &&
74+
this.config.connectionString &&
7675
error instanceof DriverError &&
77-
config.connectionString !== connectionString
76+
this.config.connectionString !== connectionString
7877
) {
7978
return {
8079
content: [
8180
{
8281
type: "text",
8382
text:
8483
`Failed to connect to MongoDB at '${connectionString}' due to error: '${error.message}.` +
85-
`Your config lists a different connection string: '${config.connectionString}' - do you want to try connecting to it instead?`,
84+
`Your config lists a different connection string: '${this.config.connectionString}' - do you want to try connecting to it instead?`,
8685
},
8786
],
8887
};

src/tools/mongodb/mongodbTool.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
import { z } from "zod";
22
import { ToolArgs, ToolBase, ToolCategory } from "../tool.js";
3-
import { Session } from "../../session.js";
43
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
54
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
65
import { ErrorCodes, MongoDBError } from "../../errors.js";
7-
import config from "../../config.js";
86

97
export const DbOperationArgs = {
108
database: z.string().describe("Database name"),
119
collection: z.string().describe("Collection name"),
1210
};
1311

1412
export abstract class MongoDBToolBase extends ToolBase {
15-
constructor(session: Session) {
16-
super(session);
17-
}
18-
1913
protected category: ToolCategory = "mongodb";
2014

2115
protected async ensureConnected(): Promise<NodeDriverServiceProvider> {
22-
if (!this.session.serviceProvider && config.connectionString) {
23-
await this.connectToMongoDB(config.connectionString);
16+
if (!this.session.serviceProvider && this.config.connectionString) {
17+
await this.connectToMongoDB(this.config.connectionString);
2418
}
2519

2620
if (!this.session.serviceProvider) {
@@ -58,13 +52,13 @@ export abstract class MongoDBToolBase extends ToolBase {
5852
productDocsLink: "https://docs.mongodb.com/todo-mcp",
5953
productName: "MongoDB MCP",
6054
readConcern: {
61-
level: config.connectOptions.readConcern,
55+
level: this.config.connectOptions.readConcern,
6256
},
63-
readPreference: config.connectOptions.readPreference,
57+
readPreference: this.config.connectOptions.readPreference,
6458
writeConcern: {
65-
w: config.connectOptions.writeConcern,
59+
w: this.config.connectOptions.writeConcern,
6660
},
67-
timeoutMS: config.connectOptions.timeoutMS,
61+
timeoutMS: this.config.connectOptions.timeoutMS,
6862
});
6963

7064
this.session.serviceProvider = provider;

src/tools/tool.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import { Session } from "../session.js";
55
import logger from "../logger.js";
66
import { mongoLogId } from "mongodb-log-writer";
7-
import config from "../config.js";
7+
import { UserConfig } from "../config.js";
88

99
export type ToolArgs<Args extends ZodRawShape> = z.objectOutputType<Args, ZodNever>;
1010

@@ -24,7 +24,10 @@ export abstract class ToolBase {
2424

2525
protected abstract execute(...args: Parameters<ToolCallback<typeof this.argsShape>>): Promise<CallToolResult>;
2626

27-
protected constructor(protected session: Session) {}
27+
constructor(
28+
protected readonly session: Session,
29+
protected readonly config: UserConfig
30+
) {}
2831

2932
public register(server: McpServer): void {
3033
if (!this.verifyAllowed()) {
@@ -54,11 +57,11 @@ export abstract class ToolBase {
5457
// Checks if a tool is allowed to run based on the config
5558
protected verifyAllowed(): boolean {
5659
let errorClarification: string | undefined;
57-
if (config.disabledTools.includes(this.category)) {
60+
if (this.config.disabledTools.includes(this.category)) {
5861
errorClarification = `its category, \`${this.category}\`,`;
59-
} else if (config.disabledTools.includes(this.operationType)) {
62+
} else if (this.config.disabledTools.includes(this.operationType)) {
6063
errorClarification = `its operation type, \`${this.operationType}\`,`;
61-
} else if (config.disabledTools.includes(this.name)) {
64+
} else if (this.config.disabledTools.includes(this.name)) {
6265
errorClarification = `it`;
6366
}
6467

0 commit comments

Comments
 (0)