Recipe: MongoDB cluster to xDBML diagram
This recipe turns a live MongoDB deployment into a rendered entity-relationship diagram in a single AI-assistant conversation. The assistant reads inferred collection schemas through MongoDB's official MCP server, maps them to xDBML, then validates and renders through the xDBML MCP server.
MongoDB is a particularly good fit for xDBML because inferred document schemas contain exactly the structures that flat notations lose: fields holding nested documents, arrays of documents, and fields whose type varies across documents because the collection evolved over several application versions. xDBML expresses all of these losslessly with nested objects, array [...], union, and oneOf.
Prerequisites
Connect two MCP servers to your assistant:
The xDBML MCP server at https://xdbml-mcp.xdbml.workers.dev/mcp, remote and public, no key. Setup per client is covered in Use from AI assistants.
The MongoDB MCP server, connected to your deployment with a connection string. It works with Atlas, Community Edition, and Enterprise Advanced. For this recipe only read access is needed, so run it in read-only mode, which restricts it to read, connect, and metadata tools. See MongoDB's getting started guide.
How it works
The pipeline has five steps, and the assistant performs all of them.
1. Enumerate. List the databases and collections in scope through the MongoDB server's metadata tools.
2. Infer. Call collection-schema for each collection. The tool samples documents (50 by default) and returns a SimplifiedSchema: for every field, a frequency-ordered list of the types observed in the sample. Entries are either a scalar BSON type, an Array with its own element types, or a Document with nested fields.
3. Map. Convert each result to xDBML using the mapping rules below.
4. Validate. Call validate_xdbml and fix anything it flags. This is much cheaper than rendering and reports precise line and column positions.
5. Render. Call render_xdbml for the diagram and the playground link.
Mapping rules
Scalar BSON types map to xDBML's BSON-native types:
| bsonType | xDBML | bsonType | xDBML |
|---|---|---|---|
String | string | Double | double |
ObjectId | objectId | Decimal128 | Decimal128 |
Int32 | int32 | Boolean | boolean |
Int64 / Long | int64 | Date | Date |
Binary | BinData | Timestamp | timestamp |
The structural rules do the real work:
Nullability, not a type. Null and Undefined entries in a field's type list are signals that the field is nullable or missing in some documents. Strip them; a field whose remaining type list is a single type and that had no nullish entries gets not null.
Scalar variance folds to a union. A field observed as Decimal128 in recent documents and Double in older ones becomes total union [Decimal128, double], preserving the sample's frequency order so the first member is the dominant type.
Documents nest. A Document type becomes an inline object { ... }, recursively.
Arrays. One scalar element type gives tags array [string]. An array of documents gives line_items array [ line_item object { ... } ] with a singularized element name. Several scalar element types give array [int, varchar], which xDBML folds to a union at parse time.
Mixed document and scalar is oneOf. A field stored as a bare string by application v1 and as a subdocument by v2 becomes a oneOf with one variant per shape. This is the pattern unions cannot express, since union members are always simple types.
Keys and references. _id gets [pk]. MongoDB has no foreign keys, so references between collections exist only in application code and $lookup stages; the assistant derives them from aggregation analysis or ObjectId naming conventions and emits block-form Refs with precise cardinality. Use database-qualified paths (shop.orders.customer_id), because bare names fail to resolve when two databases contain same-named collections.
Provenance. Each Collection records how it was obtained:
Collection orders [x_inferred_from: 'mongodb-mcp collection-schema', x_sample_size: 50, x_inferred_on: '2026-07-11'] {
Note: 'Schema inferred from a document sample; may not represent the full collection.'
...
}A worked example
Given this excerpt of a collection-schema result for shop.orders, where total drifted from Double to Decimal128 and payment was a string before it became a subdocument:
{
"total": { "types": [ { "bsonType": "Decimal128" }, { "bsonType": "Double" } ] },
"payment": { "types": [
{ "bsonType": "Document", "fields": {
"method": { "types": [ { "bsonType": "String" } ] },
"last4": { "types": [ { "bsonType": "String" } ] },
"captured": { "types": [ { "bsonType": "Boolean" } ] }
} },
{ "bsonType": "String" }
] }
}the mapping produces:
total union [Decimal128, double]
payment oneOf {
as_document object {
method string [not null]
last4 string [not null]
captured boolean [not null]
}
as_string string
}The rendered diagram shows the union member list on the field row, the oneOf variants expanded beneath payment, and, when collections are grouped in a Database shop { ... } block, a dashed container boundary around them.
Try it
In a chat with both servers connected, ask:
Using the MongoDB tools, list the collections in database
<name>and call collection-schema on each. Map the results to xDBML: BSON-native types; strip Null/Undefined and mark single-typed fields not null; fold scalar type variance to union in frequency order; nest Document fields as object; render arrays of documents as array with a singularized element object; use oneOf when a field mixes document and scalar shapes; _id is pk. Detect cross-collection ObjectId references and emit database-qualified Refs with cardinality. Group collections in a Database block and add x_inferred_from, x_sample_size, and x_inferred_on to each Collection. First call xdbml_reference so you author idiomatic xDBML, then validate, fix any diagnostics, render, and give me the playground link.
Reference implementation
The repository ships a deterministic implementation of the mapping in mongodb-demo/: simplified-schema-to-xdbml.mjs is a dependency-free ES module that consumes collection-schema results and emits xDBML, and demo.mjs runs it on sample payloads shaped exactly like the tool's output. Run it with node demo.mjs from that folder. It is useful when you want the mapping to be reproducible rather than performed inline by the assistant, for example in a scheduled job that regenerates a model and diffs it against the committed one to detect schema drift.
Caveats
The schema is inferred from a sample, so rare fields and rare type variants can be missed; raise the tool's sampleSize parameter for wider coverage, and treat the result as a snapshot of observed shape, not a contract. Relationship detection is heuristic: naming conventions and aggregation analysis find most references, but only application knowledge confirms them, which is why the Refs carry explicit cardinality you can correct. And the provenance attributes exist precisely so a later reader knows which parts of the model were inferred, from how many documents, and when.