Skip to content

Commit 74a2f0f

Browse files
authored
Add config option for connection string (#56)
1 parent f278e4f commit 74a2f0f

22 files changed

+96
-42
lines changed

src/config.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,34 @@ import argv from "yargs-parser";
44

55
import packageJson from "../package.json" with { type: "json" };
66
import fs from "fs";
7+
import { ReadConcernLevel, ReadPreferenceMode, W } from "mongodb";
78
const { localDataPath, configPath } = getLocalDataPath();
89

910
// If we decide to support non-string config options, we'll need to extend the mechanism for parsing
1011
// env variables.
11-
interface UserConfig extends Record<string, string | undefined> {
12+
interface UserConfig {
1213
apiBaseUrl: string;
1314
clientId: string;
1415
stateFile: string;
1516
connectionString?: string;
17+
connectOptions: {
18+
readConcern: ReadConcernLevel;
19+
readPreference: ReadPreferenceMode;
20+
writeConcern: W;
21+
timeoutMS: number;
22+
};
1623
}
1724

1825
const defaults: UserConfig = {
1926
apiBaseUrl: "https://cloud.mongodb.com/",
2027
clientId: "0oabtxactgS3gHIR0297",
2128
stateFile: path.join(localDataPath, "state.json"),
29+
connectOptions: {
30+
readConcern: "local",
31+
readPreference: "secondaryPreferred",
32+
writeConcern: "majority",
33+
timeoutMS: 30_000,
34+
},
2235
};
2336

2437
const mergedUserConfig = {
@@ -66,21 +79,52 @@ function getLocalDataPath(): { localDataPath: string; configPath: string } {
6679
// are prefixed with `MDB_MCP_` and the keys match the UserConfig keys, but are converted
6780
// to SNAKE_UPPER_CASE.
6881
function getEnvConfig(): Partial<UserConfig> {
69-
const camelCaseToSNAKE_UPPER_CASE = (str: string): string => {
70-
return str.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
71-
};
82+
function setValue(obj: Record<string, unknown>, path: string[], value: string): void {
83+
const currentField = path.shift()!;
84+
if (path.length === 0) {
85+
const numberValue = Number(value);
86+
if (!isNaN(numberValue)) {
87+
obj[currentField] = numberValue;
88+
return;
89+
}
90+
91+
const booleanValue = value.toLocaleLowerCase();
92+
if (booleanValue === "true" || booleanValue === "false") {
93+
obj[currentField] = booleanValue === "true";
94+
return;
95+
}
96+
97+
obj[currentField] = value;
98+
return;
99+
}
72100

73-
const result: Partial<UserConfig> = {};
74-
for (const key of Object.keys(defaults)) {
75-
const envVarName = `MDB_MCP_${camelCaseToSNAKE_UPPER_CASE(key)}`;
76-
if (process.env[envVarName]) {
77-
result[key] = process.env[envVarName];
101+
if (!obj[currentField]) {
102+
obj[currentField] = {};
78103
}
104+
105+
setValue(obj[currentField] as Record<string, unknown>, path, value);
106+
}
107+
108+
const result: Record<string, unknown> = {};
109+
const mcpVariables = Object.entries(process.env).filter(
110+
([key, value]) => value !== undefined && key.startsWith("MDB_MCP_")
111+
) as [string, string][];
112+
for (const [key, value] of mcpVariables) {
113+
const fieldPath = key
114+
.replace("MDB_MCP_", "")
115+
.split(".")
116+
.map((part) => SNAKE_CASE_toCamelCase(part));
117+
118+
setValue(result, fieldPath, value);
79119
}
80120

81121
return result;
82122
}
83123

124+
function SNAKE_CASE_toCamelCase(str: string): string {
125+
return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace("_", ""));
126+
}
127+
84128
// Gets the config supplied by the user as a JSON file. The file is expected to be located in the local data path
85129
// and named `config.json`.
86130
function getFileConfig(): Partial<UserConfig> {

src/tools/mongodb/collectionIndexes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class CollectionIndexesTool extends MongoDBToolBase {
99
protected operationType: DbOperationType = "read";
1010

1111
protected async execute({ database, collection }: ToolArgs<typeof DbOperationArgs>): Promise<CallToolResult> {
12-
const provider = this.ensureConnected();
12+
const provider = await this.ensureConnected();
1313
const indexes = await provider.getIndexes(database, collection);
1414

1515
return {

src/tools/mongodb/connect.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3-
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
43
import { DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
54
import { ToolArgs } from "../tool.js";
65
import { ErrorCodes, MongoDBError } from "../../errors.js";
@@ -58,21 +57,10 @@ export class ConnectTool extends MongoDBToolBase {
5857
throw new MongoDBError(ErrorCodes.InvalidParams, "Invalid connection options");
5958
}
6059

61-
await this.connect(connectionString);
60+
await this.connectToMongoDB(connectionString, this.state);
6261

6362
return {
6463
content: [{ type: "text", text: `Successfully connected to ${connectionString}.` }],
6564
};
6665
}
67-
68-
private async connect(connectionString: string): Promise<void> {
69-
const provider = await NodeDriverServiceProvider.connect(connectionString, {
70-
productDocsLink: "https://docs.mongodb.com/todo-mcp",
71-
productName: "MongoDB MCP",
72-
});
73-
74-
this.state.serviceProvider = provider;
75-
this.state.credentials.connectionString = connectionString;
76-
await this.state.persistCredentials();
77-
}
7866
}

src/tools/mongodb/create/insertMany.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class InsertManyTool extends MongoDBToolBase {
2121
collection,
2222
documents,
2323
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
24-
const provider = this.ensureConnected();
24+
const provider = await this.ensureConnected();
2525
const result = await provider.insertMany(database, collection, documents);
2626

2727
return {

src/tools/mongodb/create/insertOne.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class InsertOneTool extends MongoDBToolBase {
2323
collection,
2424
document,
2525
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
26-
const provider = this.ensureConnected();
26+
const provider = await this.ensureConnected();
2727
const result = await provider.insertOne(database, collection, document);
2828

2929
return {

src/tools/mongodb/createIndex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class CreateIndexTool extends MongoDBToolBase {
1515
protected operationType: DbOperationType = "create";
1616

1717
protected async execute({ database, collection, keys }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
18-
const provider = this.ensureConnected();
18+
const provider = await this.ensureConnected();
1919
const indexes = await provider.createIndexes(database, collection, [
2020
{
2121
key: keys,

src/tools/mongodb/delete/deleteMany.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class DeleteManyTool extends MongoDBToolBase {
2323
collection,
2424
filter,
2525
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
26-
const provider = this.ensureConnected();
26+
const provider = await this.ensureConnected();
2727
const result = await provider.deleteMany(database, collection, filter);
2828

2929
return {

src/tools/mongodb/delete/deleteOne.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class DeleteOneTool extends MongoDBToolBase {
2323
collection,
2424
filter,
2525
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
26-
const provider = this.ensureConnected();
26+
const provider = await this.ensureConnected();
2727
const result = await provider.deleteOne(database, collection, filter);
2828

2929
return {

src/tools/mongodb/delete/dropCollection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class DropCollectionTool extends MongoDBToolBase {
1212
protected operationType: DbOperationType = "delete";
1313

1414
protected async execute({ database, collection }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
15-
const provider = this.ensureConnected();
15+
const provider = await this.ensureConnected();
1616
const result = await provider.dropCollection(database, collection);
1717

1818
return {

src/tools/mongodb/delete/dropDatabase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class DropDatabaseTool extends MongoDBToolBase {
1111
protected operationType: DbOperationType = "delete";
1212

1313
protected async execute({ database }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
14-
const provider = this.ensureConnected();
14+
const provider = await this.ensureConnected();
1515
const result = await provider.dropDatabase(database);
1616

1717
return {

src/tools/mongodb/metadata/collectionSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class CollectionSchemaTool extends MongoDBToolBase {
1111
protected operationType: DbOperationType = "metadata";
1212

1313
protected async execute({ database, collection }: ToolArgs<typeof DbOperationArgs>): Promise<CallToolResult> {
14-
const provider = this.ensureConnected();
14+
const provider = await this.ensureConnected();
1515
const documents = await provider.find(database, collection, {}, { limit: 5 }).toArray();
1616
const schema = await parseSchema(documents);
1717

src/tools/mongodb/metadata/collectionStorageSize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class CollectionStorageSizeTool extends MongoDBToolBase {
1010
protected operationType: DbOperationType = "metadata";
1111

1212
protected async execute({ database, collection }: ToolArgs<typeof DbOperationArgs>): Promise<CallToolResult> {
13-
const provider = this.ensureConnected();
13+
const provider = await this.ensureConnected();
1414
const [{ value }] = await provider
1515
.aggregate(database, collection, [
1616
{ $collStats: { storageStats: {} } },

src/tools/mongodb/metadata/dbStats.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class DbStatsTool extends MongoDBToolBase {
1212
protected operationType: DbOperationType = "metadata";
1313

1414
protected async execute({ database }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
15-
const provider = this.ensureConnected();
15+
const provider = await this.ensureConnected();
1616
const result = await provider.runCommandWithCheck(database, {
1717
dbStats: 1,
1818
scale: 1,

src/tools/mongodb/metadata/listCollections.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class ListCollectionsTool extends MongoDBToolBase {
1212
protected operationType: DbOperationType = "metadata";
1313

1414
protected async execute({ database }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
15-
const provider = this.ensureConnected();
15+
const provider = await this.ensureConnected();
1616
const collections = await provider.listCollections(database);
1717

1818
return {

src/tools/mongodb/metadata/listDatabases.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class ListDatabasesTool extends MongoDBToolBase {
1010
protected operationType: DbOperationType = "metadata";
1111

1212
protected async execute(): Promise<CallToolResult> {
13-
const provider = this.ensureConnected();
13+
const provider = await this.ensureConnected();
1414
const dbs = (await provider.listDatabases("")).databases as { name: string; sizeOnDisk: bson.Long }[];
1515

1616
return {

src/tools/mongodb/mongodbTool.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { State } from "../../state.js";
44
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
55
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
66
import { ErrorCodes, MongoDBError } from "../../errors.js";
7+
import config from "../../config.js";
78

89
export const DbOperationArgs = {
910
database: z.string().describe("Database name"),
@@ -19,8 +20,12 @@ export abstract class MongoDBToolBase extends ToolBase {
1920

2021
protected abstract operationType: DbOperationType;
2122

22-
protected ensureConnected(): NodeDriverServiceProvider {
23+
protected async ensureConnected(): Promise<NodeDriverServiceProvider> {
2324
const provider = this.state.serviceProvider;
25+
if (!provider && config.connectionString) {
26+
await this.connectToMongoDB(config.connectionString, this.state);
27+
}
28+
2429
if (!provider) {
2530
throw new MongoDBError(ErrorCodes.NotConnectedToMongoDB, "Not connected to MongoDB");
2631
}
@@ -46,4 +51,21 @@ export abstract class MongoDBToolBase extends ToolBase {
4651

4752
return undefined;
4853
}
54+
55+
protected async connectToMongoDB(connectionString: string, state: State): Promise<void> {
56+
const provider = await NodeDriverServiceProvider.connect(connectionString, {
57+
productDocsLink: "https://docs.mongodb.com/todo-mcp",
58+
productName: "MongoDB MCP",
59+
readConcern: config.connectOptions.readConcern,
60+
readPreference: config.connectOptions.readPreference,
61+
writeConcern: {
62+
w: config.connectOptions.writeConcern,
63+
},
64+
timeoutMS: config.connectOptions.timeoutMS,
65+
});
66+
67+
state.serviceProvider = provider;
68+
state.credentials.connectionString = connectionString;
69+
await state.persistCredentials();
70+
}
4971
}

src/tools/mongodb/read/aggregate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class AggregateTool extends MongoDBToolBase {
1919
collection,
2020
pipeline,
2121
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
22-
const provider = this.ensureConnected();
22+
const provider = await this.ensureConnected();
2323
const documents = await provider.aggregate(database, collection, pipeline).toArray();
2424

2525
const content: Array<{ text: string; type: "text" }> = [

src/tools/mongodb/read/count.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class CountTool extends MongoDBToolBase {
2020
protected operationType: DbOperationType = "metadata";
2121

2222
protected async execute({ database, collection, query }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
23-
const provider = this.ensureConnected();
23+
const provider = await this.ensureConnected();
2424
const count = await provider.count(database, collection, query);
2525

2626
return {

src/tools/mongodb/read/find.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class FindTool extends MongoDBToolBase {
3838
limit,
3939
sort,
4040
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
41-
const provider = this.ensureConnected();
41+
const provider = await this.ensureConnected();
4242
const documents = await provider.find(database, collection, filter, { projection, limit, sort }).toArray();
4343

4444
const content: Array<{ text: string; type: "text" }> = [

src/tools/mongodb/update/renameCollection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class RenameCollectionTool extends MongoDBToolBase {
2020
newName,
2121
dropTarget,
2222
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
23-
const provider = this.ensureConnected();
23+
const provider = await this.ensureConnected();
2424
const result = await provider.renameCollection(database, collection, newName, {
2525
dropTarget,
2626
});

src/tools/mongodb/update/updateMany.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class UpdateManyTool extends MongoDBToolBase {
3535
update,
3636
upsert,
3737
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
38-
const provider = this.ensureConnected();
38+
const provider = await this.ensureConnected();
3939
const result = await provider.updateMany(database, collection, filter, update, {
4040
upsert,
4141
});

src/tools/mongodb/update/updateOne.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class UpdateOneTool extends MongoDBToolBase {
3535
update,
3636
upsert,
3737
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
38-
const provider = this.ensureConnected();
38+
const provider = await this.ensureConnected();
3939
const result = await provider.updateOne(database, collection, filter, update, {
4040
upsert,
4141
});

0 commit comments

Comments
 (0)