Skip to content

Commit 557c511

Browse files
committed
Initial pass
1 parent 7e32ef1 commit 557c511

File tree

5 files changed

+161
-8
lines changed

5 files changed

+161
-8
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
"vitest": "^3.2.4"
116116
},
117117
"dependencies": {
118+
"@mcp-ui/server": "^5.13.1",
118119
"@modelcontextprotocol/sdk": "^1.24.2",
119120
"@mongodb-js/device-id": "^0.3.1",
120121
"@mongodb-js/devtools-proxy-support": "^0.5.3",

pnpm-lock.yaml

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/mongodb/metadata/listDatabases.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { MongoDBToolBase } from "../mongodbTool.js";
33
import type * as bson from "bson";
44
import type { OperationType } from "../../tool.js";
55
import { formatUntrustedData } from "../../tool.js";
6+
import { createUIResource } from "@mcp-ui/server";
7+
import { html as htmlContent } from "../../../ui/sample-bundle.js";
68

79
export class ListDatabasesTool extends MongoDBToolBase {
810
public name = "list-databases";
@@ -14,11 +16,41 @@ export class ListDatabasesTool extends MongoDBToolBase {
1416
const provider = await this.ensureConnected();
1517
const dbs = (await provider.listDatabases("")).databases as { name: string; sizeOnDisk: bson.Long }[];
1618

17-
return {
19+
// Transform data to match UI schema expectations
20+
const uiDatabases = dbs.map((db) => ({
21+
name: db.name,
22+
size: Number(db.sizeOnDisk),
23+
}));
24+
25+
const uiResource = createUIResource({
26+
uri: `ui://list-databases/${Date.now()}`,
27+
content: {
28+
type: "rawHtml",
29+
htmlString: htmlContent,
30+
},
31+
encoding: "text",
32+
// ✅ Embed the render data in uiMetadata
33+
// The MCP client will automatically extract this and pass it to the iframe via postMessage
34+
uiMetadata: {
35+
"initial-render-data": {
36+
databases: uiDatabases,
37+
totalCount: uiDatabases.length,
38+
},
39+
},
40+
});
41+
42+
const toolResult = {
1843
content: formatUntrustedData(
1944
`Found ${dbs.length} databases`,
2045
...dbs.map((db) => `Name: ${db.name}, Size: ${db.sizeOnDisk.toString()} bytes`)
2146
),
2247
};
48+
49+
const augmentedResult = {
50+
...toolResult,
51+
content: [...(toolResult.content || []), uiResource],
52+
};
53+
54+
return augmentedResult;
2355
}
2456
}

src/ui/augementWithUi.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
// import { createUIResource } from "@mcp-ui/server";
3+
// import { z } from "zod";
4+
// import { readFileSync } from "fs";
5+
// import { join, dirname } from "path";
6+
// import { fileURLToPath } from "url";
7+
8+
// const __filename = fileURLToPath(import.meta.url);
9+
// const __dirname = dirname(__filename);
10+
11+
// // Tool name to HTML file mappings
12+
// const TOOL_TO_HTML_MAPPINGS: Record<string, string> = {
13+
// "list-databases": "ListDatabases.html",
14+
// };
15+
16+
// /**
17+
// * Options for augmenting a tool result with UI
18+
// */
19+
// export interface AugmentOptions {
20+
// toolName: string;
21+
// renderData: Record<string, unknown>;
22+
// }
23+
24+
// /**
25+
// * Augment a tool result with UI using rawHtml content type
26+
// * This reads built HTML files from the UI build output and embeds them as rawHtml
27+
// *
28+
// * The render data is embedded in the UI resource using uiMetadata['initial-render-data'].
29+
// * The MCP client will automatically extract this data and pass it to the iframe via postMessage.
30+
// */
31+
// export function augmentWithUI(toolResult: CallToolResult, options: AugmentOptions): CallToolResult {
32+
// const { toolName, renderData } = options;
33+
34+
// console.log(`[augmentWithUI] Tool: ${toolName}, Result:`, JSON.stringify(toolResult, null, 2));
35+
36+
// // Check if we have a mapping for this tool
37+
// const htmlFileName = TOOL_TO_HTML_MAPPINGS[toolName];
38+
39+
// if (!htmlFileName) {
40+
// // No mapping registered for this tool name
41+
// return toolResult;
42+
// }
43+
44+
// // // Validate renderData against the schema for this tool (if schema exists)
45+
// // const schema = TOOL_SCHEMAS[toolName];
46+
// // if (schema) {
47+
// // try {
48+
// // schema.parse(renderData);
49+
// // console.log(`[augmentWithUI] Validation passed for tool: ${toolName}`);
50+
// // } catch (error) {
51+
// // const errorMessage =
52+
// // error instanceof z.ZodError
53+
// // ? `Schema validation failed for tool "${toolName}": ${error.issues
54+
// // .map((e) => e.message)
55+
// // .join(", ")}`
56+
// // : `Schema validation failed for tool "${toolName}"`;
57+
// // console.warn(`[augmentWithUI] ${errorMessage}`, error);
58+
// // // Return the tool result unmodified - validation failure prevents UI augmentation
59+
// // return toolResult;
60+
// // }
61+
// // }
62+
63+
// // Read the built HTML file from the UI dist directory
64+
// // Path: from src/tools/ go up to ui/, then to dist/embeddable-uis/
65+
// const uiDistPath = join(__dirname, "..", "..", "dist", "embeddable-uis", htmlFileName);
66+
// console.log(`[augmentWithUI] Looking for HTML file at: ${uiDistPath}`);
67+
68+
// let htmlContent: string;
69+
70+
// try {
71+
// htmlContent = readFileSync(uiDistPath, "utf-8");
72+
// console.log(`[augmentWithUI] Successfully read HTML file: ${htmlFileName} (${htmlContent.length} bytes)`);
73+
// } catch (error) {
74+
// console.error(`[augmentWithUI] Failed to read HTML file ${htmlFileName} from ${uiDistPath}:`, error);
75+
// // Return the tool result unmodified if HTML file cannot be read
76+
// return toolResult;
77+
// }
78+
79+
// // Create UI resource using @mcp-ui/server with rawHtml content
80+
// const uiResource = createUIResource({
81+
// uri: `ui://${toolName}/${Date.now()}`,
82+
// content: {
83+
// type: "rawHtml",
84+
// htmlString: htmlContent,
85+
// },
86+
// encoding: "text",
87+
// // ✅ Embed the render data in uiMetadata
88+
// // The MCP client will automatically extract this and pass it to the iframe via postMessage
89+
// uiMetadata: {
90+
// "initial-render-data": renderData,
91+
// },
92+
// });
93+
94+
// console.log(`[augmentWithUI] Created UI resource:`, JSON.stringify(uiResource, null, 2));
95+
96+
// // Append the UI resource to the tool result content (keep existing content like text data)
97+
// const augmentedResult = {
98+
// ...toolResult,
99+
// content: [...(toolResult.content || []), uiResource],
100+
// };
101+
102+
// console.log(`[augmentWithUI] Returning augmented result with ${augmentedResult.content.length} content items`);
103+
// return augmentedResult;
104+
// }

src/ui/sample-bundle.ts

Lines changed: 2 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)