Skip to content

Commit 5bcac97

Browse files
chore: ask for export description in the tool itself
1 parent c1d0117 commit 5bcac97

File tree

5 files changed

+56
-37
lines changed

5 files changed

+56
-37
lines changed

src/common/exportsManager.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type JSONExportFormat = z.infer<typeof jsonExportFormat>;
1717

1818
interface CommonExportData {
1919
exportName: string;
20+
exportTitle: string;
2021
exportURI: string;
2122
exportPath: string;
2223
}
@@ -53,7 +54,7 @@ type StoredExport = ReadyExport | InProgressExport;
5354
*
5455
* Ref Cursor: https://forum.cursor.com/t/cursor-mcp-resource-feature-support/50987
5556
* JIRA: https://jira.mongodb.org/browse/MCP-104 */
56-
type AvailableExport = Pick<StoredExport, "exportName" | "exportURI" | "exportPath">;
57+
type AvailableExport = Pick<StoredExport, "exportName" | "exportTitle" | "exportURI" | "exportPath">;
5758

5859
export type ExportsManagerConfig = Pick<UserConfig, "exportsPath" | "exportTimeoutMs" | "exportCleanupIntervalMs">;
5960

@@ -86,8 +87,9 @@ export class ExportsManager extends EventEmitter<ExportsManagerEvents> {
8687
!isExportExpired(storedExport.exportCreatedAt, this.config.exportTimeoutMs)
8788
);
8889
})
89-
.map(({ exportName, exportURI, exportPath }) => ({
90+
.map(({ exportName, exportTitle, exportURI, exportPath }) => ({
9091
exportName,
92+
exportTitle,
9193
exportURI,
9294
exportPath,
9395
}));
@@ -158,10 +160,12 @@ export class ExportsManager extends EventEmitter<ExportsManagerEvents> {
158160
public createJSONExport({
159161
input,
160162
exportName,
163+
exportTitle,
161164
jsonExportFormat,
162165
}: {
163166
input: FindCursor;
164167
exportName: string;
168+
exportTitle: string;
165169
jsonExportFormat: JSONExportFormat;
166170
}): AvailableExport {
167171
try {
@@ -174,6 +178,7 @@ export class ExportsManager extends EventEmitter<ExportsManagerEvents> {
174178
const exportFilePath = path.join(this.exportsDirectoryPath, exportNameWithExtension);
175179
const inProgressExport: InProgressExport = (this.storedExports[exportNameWithExtension] = {
176180
exportName: exportNameWithExtension,
181+
exportTitle,
177182
exportPath: exportFilePath,
178183
exportURI: exportURI,
179184
exportStatus: "in-progress",

src/resources/common/exportedData.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ export class ExportedData {
4949
private listResourcesCallback: ListResourcesCallback = () => {
5050
try {
5151
return {
52-
resources: this.session.exportsManager.availableExports.map(({ exportName, exportURI }) => ({
53-
name: exportName,
54-
description: this.exportNameToDescription(exportName),
55-
uri: exportURI,
56-
mimeType: "application/json",
57-
})),
52+
resources: this.session.exportsManager.availableExports.map(
53+
({ exportName, exportTitle, exportURI }) => ({
54+
name: exportName,
55+
description: exportTitle,
56+
uri: exportURI,
57+
mimeType: "application/json",
58+
})
59+
),
5860
};
5961
} catch (error) {
6062
this.session.logger.error({
@@ -112,20 +114,4 @@ export class ExportedData {
112114
};
113115
}
114116
};
115-
116-
private exportNameToDescription(exportName: string): string {
117-
const match = exportName.match(/^(.+)\.(\d+)\.(.+)\.json$/);
118-
if (!match) return "Exported data for an unknown namespace.";
119-
120-
const [, namespace, timestamp] = match;
121-
if (!namespace) {
122-
return "Exported data for an unknown namespace.";
123-
}
124-
125-
if (!timestamp) {
126-
return `Export from ${namespace}.`;
127-
}
128-
129-
return `Export from ${namespace} done on ${new Date(parseInt(timestamp)).toLocaleString()}`;
130-
}
131117
}

src/tools/mongodb/read/export.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export class ExportTool extends MongoDBToolBase {
1010
public name = "export";
1111
protected description = "Export a collection data or query results in the specified EJSON format.";
1212
protected argsShape = {
13+
exportTitle: z.string().describe("A short description to uniquely identify the export."),
1314
...DbOperationArgs,
1415
...FindArgs,
1516
limit: z.number().optional().describe("The maximum number of documents to return"),
@@ -33,6 +34,7 @@ export class ExportTool extends MongoDBToolBase {
3334
projection,
3435
sort,
3536
limit,
37+
exportTitle,
3638
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
3739
const provider = await this.ensureConnected();
3840
const findCursor = provider.find(database, collection, filter ?? {}, {
@@ -42,15 +44,14 @@ export class ExportTool extends MongoDBToolBase {
4244
promoteValues: false,
4345
bsonRegExp: true,
4446
});
45-
// The format is namespace.date.objectid.json
46-
// - namespace to identify which namespace the export belongs to
47-
// - date to identify when the export was generated
48-
// - objectid for uniqueness of the names
49-
const exportName = `${database}.${collection}.${Date.now()}.${new ObjectId().toString()}.json`;
47+
const exportName = `${database}.${collection}.${new ObjectId().toString()}.json`;
5048

5149
const { exportURI, exportPath } = this.session.exportsManager.createJSONExport({
5250
input: findCursor,
5351
exportName,
52+
exportTitle:
53+
exportTitle ||
54+
`Export for namespace ${database}.${collection} requested on ${new Date().toLocaleString()}`,
5455
jsonExportFormat,
5556
});
5657
const toolCallContent: CallToolResult["content"] = [

tests/integration/resources/exportedData.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describeWithMongoDB(
6565
await integration.connectMcpClient();
6666
const exportResponse = await integration.mcpClient().callTool({
6767
name: "export",
68-
arguments: { database: "db", collection: "coll" },
68+
arguments: { database: "db", collection: "coll", exportTitle: "Export for db.coll" },
6969
});
7070

7171
const exportedResourceURI = (exportResponse as CallToolResult).content.find(
@@ -89,7 +89,7 @@ describeWithMongoDB(
8989
await integration.connectMcpClient();
9090
const exportResponse = await integration.mcpClient().callTool({
9191
name: "export",
92-
arguments: { database: "db", collection: "coll" },
92+
arguments: { database: "db", collection: "coll", exportTitle: "Export for db.coll" },
9393
});
9494
const content = exportResponse.content as CallToolResult["content"];
9595
const exportURI = contentWithResourceURILink(content)?.uri as string;
@@ -112,7 +112,7 @@ describeWithMongoDB(
112112
await integration.connectMcpClient();
113113
const exportResponse = await integration.mcpClient().callTool({
114114
name: "export",
115-
arguments: { database: "big", collection: "coll" },
115+
arguments: { database: "big", collection: "coll", exportTitle: "Export for big.coll" },
116116
});
117117
const content = exportResponse.content as CallToolResult["content"];
118118
const exportURI = contentWithResourceURILink(content)?.uri as string;

tests/integration/tools/mongodb/read/export.test.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ describeWithMongoDB(
5656
"Export a collection data or query results in the specified EJSON format.",
5757
[
5858
...databaseCollectionParameters,
59-
59+
{
60+
name: "exportTitle",
61+
description: "A short description to uniquely identify the export.",
62+
type: "string",
63+
required: true,
64+
},
6065
{
6166
name: "filter",
6267
description: "The query filter, matching the syntax of the query argument of db.collection.find()",
@@ -117,7 +122,11 @@ describeWithMongoDB(
117122
it("when provided with incorrect namespace, export should have empty data", async function () {
118123
const response = await integration.mcpClient().callTool({
119124
name: "export",
120-
arguments: { database: "non-existent", collection: "foos" },
125+
arguments: {
126+
database: "non-existent",
127+
collection: "foos",
128+
exportTitle: "Export for non-existent.foos",
129+
},
121130
});
122131
const content = response.content as CallToolResult["content"];
123132
const exportURI = contentWithResourceURILink(content)?.uri as string;
@@ -152,7 +161,11 @@ describeWithMongoDB(
152161
await integration.connectMcpClient();
153162
const response = await integration.mcpClient().callTool({
154163
name: "export",
155-
arguments: { database: integration.randomDbName(), collection: "foo" },
164+
arguments: {
165+
database: integration.randomDbName(),
166+
collection: "foo",
167+
exportTitle: `Export for ${integration.randomDbName()}.foo`,
168+
},
156169
});
157170
const content = response.content as CallToolResult["content"];
158171
const exportURI = contentWithResourceURILink(content)?.uri as string;
@@ -176,7 +189,12 @@ describeWithMongoDB(
176189
await integration.connectMcpClient();
177190
const response = await integration.mcpClient().callTool({
178191
name: "export",
179-
arguments: { database: integration.randomDbName(), collection: "foo", filter: { name: "foo" } },
192+
arguments: {
193+
database: integration.randomDbName(),
194+
collection: "foo",
195+
filter: { name: "foo" },
196+
exportTitle: `Export for ${integration.randomDbName()}.foo`,
197+
},
180198
});
181199
const content = response.content as CallToolResult["content"];
182200
const exportURI = contentWithResourceURILink(content)?.uri as string;
@@ -199,7 +217,12 @@ describeWithMongoDB(
199217
await integration.connectMcpClient();
200218
const response = await integration.mcpClient().callTool({
201219
name: "export",
202-
arguments: { database: integration.randomDbName(), collection: "foo", limit: 1 },
220+
arguments: {
221+
database: integration.randomDbName(),
222+
collection: "foo",
223+
limit: 1,
224+
exportTitle: `Export for ${integration.randomDbName()}.foo`,
225+
},
203226
});
204227
const content = response.content as CallToolResult["content"];
205228
const exportURI = contentWithResourceURILink(content)?.uri as string;
@@ -227,6 +250,7 @@ describeWithMongoDB(
227250
collection: "foo",
228251
limit: 1,
229252
sort: { longNumber: 1 },
253+
exportTitle: `Export for ${integration.randomDbName()}.foo`,
230254
},
231255
});
232256
const content = response.content as CallToolResult["content"];
@@ -255,6 +279,7 @@ describeWithMongoDB(
255279
collection: "foo",
256280
limit: 1,
257281
projection: { _id: 0, name: 1 },
282+
exportTitle: `Export for ${integration.randomDbName()}.foo`,
258283
},
259284
});
260285
const content = response.content as CallToolResult["content"];
@@ -287,6 +312,7 @@ describeWithMongoDB(
287312
limit: 1,
288313
projection: { _id: 0 },
289314
jsonExportFormat: "relaxed",
315+
exportTitle: `Export for ${integration.randomDbName()}.foo`,
290316
},
291317
});
292318
const content = response.content as CallToolResult["content"];
@@ -320,6 +346,7 @@ describeWithMongoDB(
320346
limit: 1,
321347
projection: { _id: 0 },
322348
jsonExportFormat: "canonical",
349+
exportTitle: `Export for ${integration.randomDbName()}.foo`,
323350
},
324351
});
325352
const content = response.content as CallToolResult["content"];

0 commit comments

Comments
 (0)