Status: Draft Date: 2026-04-13
Mendix 11.9 introduces Agents as a first-class concept for building agentic AI applications. Agents are defined in Studio Pro through the Agent Editor extension and stored as CustomBlobDocuments$CustomBlobDocument with CustomDocumentType = "agenteditor.agent". The ecosystem includes five marketplace modules:
| Module | Role |
|---|---|
| GenAICommons | Core AI types: Request/Response, DeployedModel, Tool, ToolCall, Trace, KnowledgeBase |
| MxGenAIConnector | Mendix Cloud AI backend (Bedrock), model config, embeddings, knowledge base collections |
| AgentCommons | Agent management: versioned agents, tools, knowledge bases, MCP, test cases |
| AgentEditorCommons | Bridge between Studio Pro Agent Editor extension and AgentCommons runtime entities |
| MCPClient | Model Context Protocol client: server connections, tool/prompt discovery, execution |
| ConversationalUI | Chat widgets, message rendering, tool approval UI, trace monitoring, token & observability dashboards |
Currently, mxcli has no visibility into agent documents. SHOW STRUCTURE reports the Agents module as empty because it only contains CustomBlobDocument units, which are not parsed. An AI coding agent cannot discover, inspect, or create agents via MDL.
All four agent-editor document types (Agent, Model, Knowledge Base, Consumed MCP Service) share the same outer wrapper — a generic CustomBlobDocument with a JSON payload in Contents. They're distinguished by the CustomDocumentType field.
CustomBlobDocuments$CustomBlobDocument:
$ID: bytes
$Type: "CustomBlobDocuments$CustomBlobDocument"
Name: string
Contents: string (JSON payload — schema depends on CustomDocumentType)
DocumentType: "agenteditor.agent"
| "agenteditor.model"
| "agenteditor.knowledgebase"
| "agenteditor.consumedMCPService"
Documentation: string
Excluded: bool
ExportLevel: "Hidden"
Metadata:
$ID: bytes
$Type: "CustomBlobDocuments$CustomBlobDocumentMetadata"
CreatedByExtension: "extension/agent-editor"
ReadableTypeName: "Agent" | "Model" | "Knowledge base" | "Consumed MCP service"
Key observations about the wrapper:
CustomDocumentTypeis the discriminator for the inner JSON schemaContentsis a JSON string (not nested BSON) — the agent editor extension owns the inner schemaMetadata.ReadableTypeNameis a human-friendly label (also used as the UI badge in Studio Pro)Excludedisfalseby default; can be changed totrueby the user. Then the document cannot be used by other app logic and no errors will be shown for this document.
Observed in Agents.MyFirstModel:
{
"type": "Text generation",
"name": "",
"displayName": "",
"provider": "MxCloudGenAI",
"providerFields": {
"environment": "",
"deepLinkURL": "",
"keyId": "",
"keyName": "",
"resourceName": "",
"key": {
"documentId": "51b85be5-f040-4562-bf4c-086347d387a9",
"qualifiedName": "Agents.LLMKey"
}
}
}provideris a top-level discriminator —"MxCloudGenAI"is the only observed value.providerFieldsshape depends onprovider.providerFields.keyreferences a String constant (holding the Mendix Cloud GenAI Portal key) by{documentId, qualifiedName}.type,name,displayName,environment,deepLinkURL,keyId,keyName,resourceNameare all empty in the sample — they values are decoded from the key the user selects as a string constant. After decoding the key, a call to the backend might update the reference to the exact model if that was changed in the Mendix Cloud GenAI portal. This check can also be triggered by the user when clicking Test Key in Studio Pro.
Observed in Agents.Knowledge_base:
{
"name": "",
"provider": "MxCloudGenAI",
"providerFields": {
"environment": "",
"deepLinkURL": "",
"keyId": "",
"keyName": "",
"modelDisplayName": "",
"modelName": "",
"key": {
"documentId": "51b85be5-f040-4562-bf4c-086347d38712",
"qualifiedName": "Agents.KnowledbaseKey"
}
}
}Same shape as Model, but providerFields includes embedding-model info (modelDisplayName, modelName) instead of resourceName. The key reference points to a String constant with a knowledge base key generated in the Mendix Cloud GenAI portal.
Observed in Agents.Consumed_MCP_service:
{
"protocolVersion": "v2025_03_26",
"documentation": " fqwef qwec qwefc",
"version": "0.0.1",
"connectionTimeoutSeconds": 30,
"endpoint" : {
"documentId": "51b85be5-f040-4562-bf4c-086347d38734",
"qualifiedName": "Agents.MCPEndpoint"
},
"authenticationMicroflow" : {
"documentId": "51b85be5-f040-4562-bf4c-086347d387ab",
"qualifiedName": "Agents.AuthenticationMicroflow"
}
}Endpoint is a reference to a string constant with the MCP endpoint. If the server requried authentication those can be created in an authentication microflow which the user can optionally select. An authentication microflow cannot have input parameters and needs to return a list of System.HttpHeader.
For MCP tool discovery inside Studio Pro, user can add headers in the UI. Those will not be persisted, nor transferred into the authentication microflow to be used at runtime.
Enum values for protocolVersion: "v2024_11_05" or "v2025_03_26". Use v2025_03_26 for newer MCP servers that support streamable http transport, v2024_11_05 for servers that only support SSE transport.
Simple agent (observed in AgentEditorCommons.TranslationAgent):
{
"description": "",
"systemPrompt": "Translate the given text into {{Description}}.",
"userPrompt": "...",
"usageType": "Task",
"variables": [
{ "key": "Description", "isAttributeInEntity": true }
],
"tools": [],
"knowledgebaseTools": [],
"entity": {
"documentId": "83d81a7b-4a84-416e-a64f-0ffa981c8408",
"qualifiedName": "System.Language"
},
"model": {
"documentId": "3addaaa1-8bd3-4654-8cc9-2c886d0a01e9",
"qualifiedName": "Agents.MyFirstModel"
}
}Fully-populated agent (observed in Agents.Agent007):
{
"description": "doing your stuff for you",
"systemPrompt": "Do you intereesting and useful stuff that makes me money",
"userPrompt": "Just do it",
"usageType": "Task",
"variables": [],
"tools": [
{
"id": "044bc8c2-8ca6-4166-b8f0-9d2245aba8c7",
"name": "",
"description": "",
"enabled": true,
"toolType": "MCP",
"document": {
"qualifiedName": "Agents.Consumed_MCP_service",
"documentId": "47c9987a-e922-44eb-a389-e641f325ce15"
}
},
{
"id": "044bc8c2-8ca6-4166-b8f0-9d2245aba8c7",
"name": "GetBankHolidays",
"description": "Gets bank holidays",
"enabled": true,
"toolType": "Microflow",
"document": {
"qualifiedName": "Agents.GetBankHolidays",
"documentId": "47c9987a-e922-44eb-a389-e641f325ce18"
}
}
],
"knowledgebaseTools": [
{
"id": "20980c3d-399b-409e-8292-49df7d0ab533",
"name": "My_mem",
"description": "My memory of useful stuff",
"enabled": true,
"toolType": "",
"document": {
"qualifiedName": "Agents.Knowledge_base",
"documentId": "cccc0b5b-7600-47a9-8f6e-5761ce2fc620"
},
"collectionIdentifier": "agent1-collection",
"maxResults": 3,
"minSimilarity": 0.5
}
],
"model": {
"documentId": "3addaaa1-8bd3-4654-8cc9-2c886d0a01e9",
"qualifiedName": "Agents.MyFirstModel"
},
"maxTokens": 16384,
"temperature": 0.5,
"topP": 1.0,
"toolChoice": "Tool",
"toolChoiceToolName": "GetBankHolidays"
}Agent schema observations:
model: Reference to a Model document by{documentId, qualifiedName}.entitythe prompts might contain some variables (words in the user prompt surrounded by double curly brackets, i.e. {{Language}}). Variables will be replaced at runtime by attribute values on an initialized object of type entity. Therefore it is neccessary that the variables key is the same as an attribute on the entity.usageType: Defaults to"Task"agents which have aan optional system prompt and a required user prompt."Chat"agents (introduced in v1.1.0 of the agent editor) do not have a user prompt on the agent definition since that will be provided by the user at runtime.tools[]: Optional. Array of tool references. Each entry has a UUIDid, uniquename,description,enabledboolean, and atoolTypediscriminator. ObservedtoolTypevalues:"MCP"(a whole MCP service attached as tools) or a microflow-tool"Microflow"wheredocumentreferencces a microflow document. Tool microflows need to return a String and can only have primitive types and GenAICommons.Request or GenAICommons.Tool objects as input parameters.knowledgebaseTools[]: Optional. Array of KB references. Same base fields pluscollectionIdentifier,maxResultsandminSimilarity. ToolType is irrelevant for knowledgebaseTools. MaxResults needs to be a positive integer, minSimilarity is a decimal between 0 and 1.variables[]: Should be left empty this will be automatically populated by the extension based on the detected variables in the user or system prompt.entity: Optional. Present on older agents withisAttributeInEntity: truevariables; absent onAgent007.maxTokens: Optional. Can be set by the user to restrict the number of tokens to consume in one agent call.toolChoice: Optional. Agent-level inference parameters. Enum values fortoolChoiceobserved:"Auto"(capitalized, not the lowercaseautoused byGenAICommons.ENUM_ToolChoiceat runtime). Other values:"None","Any","Tool". If tool choice is set toTool, then also thetoolChoiceToolNameneeds to be set by the unique name referencing one tool intools[]. Only tools wheretoolType= "Microflow" can become tool choice.temperature,topP: Optional. Can be set by the user to influence the randomness of the response.- No
UserAccessApproval/Accessfield on tools. That's a runtime-only concern (set onAgentCommons.Toolentity, not the document). This is a correction to earlier versions of this proposal.
| Document | Type | Notes |
|---|---|---|
AgentEditorCommons.InformationExtractorAgent |
Agent | Older format, Excluded: true, no model reference |
AgentEditorCommons.SummarizationAgent |
Agent | Older format |
AgentEditorCommons.TranslationAgent |
Agent | Older format, bound to System.Language |
AgentEditorCommons.ProductDescription |
Agent | Older format, bound to AgentCommons.ProductDescriptionGenerator_EXAMPLE |
Agents.Agent007 |
Agent | New format with model reference, MCP tool, KB tool |
Agents.MyFirstModel |
Model | Provider MxCloudGenAI, key → Agents.LLMKey |
Agents.Knowledge_base |
Knowledge Base | Provider MxCloudGenAI, key → Agents.LLMKey |
Agents.Consumed_MCP_service |
Consumed MCP Service | Protocol v2025_03_26, timeout 30s |
SHOW AGENTS [IN Module]Output:
| Qualified Name | Module | Name | UsageType | Entity | Variables |
|---|---|---|---|---|---|
| AgentEditorCommons.InformationExtractorAgent | AgentEditorCommons | InformationExtractorAgent | Task | AgentCommons.InformationExtractor_EXAMPLE | Information |
| AgentEditorCommons.SummarizationAgent | AgentEditorCommons | SummarizationAgent | Task | ||
| AgentEditorCommons.TranslationAgent | AgentEditorCommons | TranslationAgent | Task | System.Language | Description |
| AgentEditorCommons.ProductDescription | AgentEditorCommons | ProductDescription | Task | AgentCommons.ProductDescriptionGenerator_EXAMPLE | ProductName, Keywords |
DESCRIBE AGENT AgentEditorCommons.TranslationAgentOutput (round-trippable MDL):
CREATE AGENT AgentEditorCommons."TranslationAgent" (
UsageType: Task,
Entity: System.Language,
Variables: ("Description": EntityAttribute),
SystemPrompt: 'Translate the given text into {{Description}}.',
UserPrompt: 'What is a multi-agent AI system?...'
);
/The syntax follows the same shape as CREATE REST CLIENT: top-level configuration in (...) followed by a {...} body containing one block per attached resource (TOOL, KNOWLEDGE BASE, CONSUMED MCP SERVICE). Simple agents with no resources omit the body entirely.
Simple task agent (no body needed):
CREATE AGENT MyModule."SentimentAnalyzer" (
UsageType: Task,
Entity: MyModule.FeedbackItem,
Variables: ("FeedbackText": EntityAttribute),
Model: MyModule.GPT4Model,
SystemPrompt: 'Analyze the sentiment of {{FeedbackText}}. Classify as positive, negative, or neutral.',
UserPrompt: '{{FeedbackText}}'
);Agent with tools, knowledge bases, and MCP services (matches Agents.Agent007):
CREATE AGENT Agents."Agent007" (
UsageType: Task,
Model: Agents.MyFirstModel,
MaxTokens: 16384,
ToolChoice: Auto,
Description: 'doing your stuff for you',
SystemPrompt: 'Do you intereesting and useful stuff that makes me money',
UserPrompt: 'Just do it'
)
{
CONSUMED MCP SERVICE Agents.Consumed_MCP_service {
Enabled: true
}
KNOWLEDGE BASE My_mem {
Source: Agents.Knowledge_base,
Collection: 'agent1-collection',
MaxResults: 3,
Description: 'My memory of useful stuff',
Enabled: true
}
};Block-level property reference:
Each block maps to one entry in the agent's Contents JSON (tools[] for TOOL/CONSUMED MCP SERVICE, knowledgebaseTools[] for KNOWLEDGE BASE). Block IDs (the id UUID field in JSON) are auto-generated by the writer.
| Block | Referenced by | Properties | Maps to JSON field |
|---|---|---|---|
CONSUMED MCP SERVICE <QualifiedName> { ... } |
ConsumedMCPService document | Enabled, Description |
tools[] entry with toolType: "MCP", document: {...} |
TOOL <Name> { Microflow: <QualifiedName>, Description, Enabled } |
microflow name | Microflow, Enabled, Description |
tools[] entry with toolType: "Microflow", document: { qualifiedName: <microflow>, documentId: <uuid> }. Microflow tools must be microflow references (qualified name) and the target microflow must return a String; input parameters are limited to primitives and GenAICommons.Request/GenAICommons.Tool types. |
KNOWLEDGE BASE <Name> { Source: ... } |
KB document via Source: |
Source (required), Collection, MaxResults, Description, Enabled |
knowledgebaseTools[] entry |
DROP AGENT MyModule."SentimentAnalyzer"Everything inside CREATE AGENT — including the properties in the top-level (...) — is design-time configuration that is stored in the agent document. None of it is an invocation parameter. Runtime inputs are supplied at the CALL AGENT site.
The layering follows the same pattern as CREATE REST CLIENT:
| Layer | REST CLIENT | AGENT |
|---|---|---|
| Document-level static config (stored in document) | BaseUrl, Authentication |
UsageType, Description, Entity, Model, MaxTokens, ToolChoice, SystemPrompt, UserPrompt |
| Input contract (what the caller must bring at call time) | Parameters: ($id: String) on each operation |
Variables: ("Topic": String, ...) on the agent |
| Attached resources (body blocks) | OPERATION blocks |
TOOL / KNOWLEDGE BASE / CONSUMED MCP SERVICE blocks |
| Runtime invocation (values supplied at call site) | SEND REST REQUEST Mod.Api.GetItems (id = $x) |
CALL AGENT WITH HISTORY $agent REQUEST $req CONTEXT $obj |
In other words:
UsageType,Entity,SystemPrompt,UserPromptare the same kind of property asBaseUrlon a REST client — baked into the document, changed by editing the document.Variables: (...)is the same kind of property asParameters: (...)on a REST operation — it declares the schema of what the caller must supply, not the values. Actual values arrive at runtime: forEntityAttributevariables, they're read from matching attributes on theCONTEXTobject; for free-form variables (future extension), they'd be passed directly.TOOL,KNOWLEDGE BASE,CONSUMED MCP SERVICEblocks describe capabilities the agent carries with it — the LLM can invoke them autonomously at runtime, but they aren't something the caller passes in.
Example — all of this is stored in the agent document:
CREATE AGENT Reviews."SentimentAnalyzer" (
UsageType: Task, -- design-time mode
Entity: Reviews.ProductReview, -- context entity contract
Variables: ("ProductName": EntityAttribute, -- input contract; which attributes to read from the context object at runtime
"ReviewText": EntityAttribute),
SystemPrompt: 'Analyze the review for {{ProductName}}.', -- prompt template
UserPrompt: '{{ReviewText}}' -- prompt template
);And this is the runtime call — the only place values flow in:
CALL AGENT WITHOUT HISTORY $Agent CONTEXT $Review INTO $Response;
-- $Review is a Reviews.ProductReview instance;
-- its ProductName and ReviewText attributes satisfy the Variables contract.| Decision | Rationale |
|---|---|
AGENT as document type keyword |
Matches Metadata.ReadableTypeName = "Agent" and Mendix UI terminology |
Top-level (Key: Value) config + {...} body with singular blocks |
Mirrors CREATE REST CLIENT ... (...) { OPERATION Name {...} } exactly — same shape, same mental model |
Model: <QualifiedName> in top-level config |
Agent documents can reference a Model document directly via the model JSON field (confirmed in Agent007). |
UsageType: Task determines if user prompt is a template or not |
Task agents have a fixed user prompt, potentially containing variables, while Chat agents do not have a predefined userprompt, because it is determined by the user at runtime. |
ToolChoice: Auto PascalCase enum literal |
Matches the real JSON value ("Auto"), which differs from the lowercase auto used by GenAICommons.ENUM_ToolChoice at runtime. Values: Auto, None, Any, Tool. When ToolChoice is set to Tool, the toolChoiceToolName property must be set to the agent-local tool name (the name field in tools[]). The writer/validator must ensure the named tool exists on the agent, is unique, has toolType: "Microflow", and is Enabled: true; otherwise emit a validation error. toolChoiceToolName selects the microflow tool the agent will prefer when ToolChoice is fixed to Tool. |
MaxTokens: <int> on the agent |
Matches the JSON maxTokens field; agent-level inference parameter |
TOOL, KNOWLEDGE BASE, CONSUMED MCP SERVICE as singular block types |
Matches the OPERATION singular used in REST CLIENT; each block defines one resource |
CONSUMED MCP SERVICE <QualifiedName> { Enabled, Description } |
The name is the qualified name of a ConsumedMCPService document (the whole service is attached as a bundle of tools) |
KNOWLEDGE BASE <Name> { Source: <doc>, Collection, MaxResults, ... } |
<Name> is the per-agent identifier stored in JSON name; Source: references the KB document. Matches Agent007's My_mem KB entry |
TOOL <Name> { Microflow: <QualifiedName>, Description, Enabled } |
Microflow tool references a microflow by qualified name; the writer encodes this in tools[] with toolType: "Microflow" and document: { qualifiedName: <microflow>, documentId: <uuid> }. Target microflows must return a String; input parameters are restricted to primitives and GenAICommons.Request/GenAICommons.Tool types. |
Variables: (...) is the input-schema analog of REST CLIENT's Parameters: (...) |
Declares what the caller must supply; values flow in via the CONTEXT object at the CALL AGENT site. Inline form matches REST CLIENT's Parameters: ($id: String) |
No Access: on tool blocks |
UserAccessApproval is NOT stored in the agent document JSON — it's a runtime-only concern on the AgentCommons.Tool entity. (Earlier drafts of this proposal incorrectly placed it on the block.) |
| Body omitted when there are no tools/KB/MCP | Same concession REST CLIENT makes implicitly — empty bodies are awkward; drop them |
| Prompts as string literals | Consistent with other MDL string properties; {{var}} placeholders are just text |
Auto-generated id UUIDs on block entries |
Each tool/KB entry has a UUID id in the JSON (Studio Pro-generated). The MDL writer will generate these; they round-trip stably through DESCRIBE |
| Layer | Status | Location |
|---|---|---|
| Go type | No | — |
| BSON parser | No (CustomBlobDocuments not parsed) | — |
| Reader/Catalog | No | — |
| Grammar | No | — |
| AST | No | — |
| Visitor | No | — |
| Executor | No | — |
| Generated metamodel | Partial | generated/metamodel/types.go has CustomBlobDocuments$CustomBlobDocument |
In a new file sdk/agents/types.go (or extend an existing domain):
package agents
import "github.com/nicholasgasior/modelsdk-go/model"
type Agent struct {
ContainerID model.ID
ID model.ID
Name string
Documentation string
// Parsed from Contents JSON
Description string
SystemPrompt string
UserPrompt string
UsageType string // "Task", "Chat"
Variables []Variable
Tools []ToolRef // tools[] array
KBTools []KBToolRef // knowledgebaseTools[] array
Model *DocRef // optional, points to a Model document
Entity *EntityRef // optional, points to a domain entity
MaxTokens *int // optional
ToolChoice string // optional: "Auto", "None", "Any", "Tool"
ToolChoiceToolName *string // optional: when ToolChoice == "Tool", the agent-local tool `name` to prefer
Temperature *float64 // optional
TopP *float64 // optional
}
type Variable struct {
Key string
IsAttributeInEntity bool // true when an attribute with name == Key is found on the referenced entity
}
type EntityRef struct {
DocumentID string // UUID of the entity's domain model
QualifiedName string // Module.EntityName
}
type DocRef struct {
DocumentID string // UUID of the referenced CustomBlobDocument or a microflow
QualifiedName string // Module.DocumentName
}
// Entry in the agent's tools[] array
type ToolRef struct {
ID string // per-tool UUID (generated by writer)
Name string // only relevant for microflow tools; unique tool name (used by `toolChoiceToolName`); Tool name must start with a letter or underscore and contain only letters, numbers, and underscores.
Description string // only relevant for microflow tools
Enabled bool // diabled tools will be ignored at runtime
ToolType string // "MCP" | "Microflow"
Document *DocRef // references ConsumedMCPService for ToolType=="MCP", references a microflow for ToolType=="Microflow"
}
// Entry in the agent's knowledgebaseTools[] array
type KBToolRef struct {
ID string
Name string
Description string
Enabled bool
ToolType string // unused for knowledge base tools
Document *DocRef // references KnowledgeBase document
CollectionIdentifier string
MaxResults int
MinSimilarity float64 // decimal between 0.0 and 1.0
}
// Peer document types (same wrapper, different Contents JSON)
type Model struct {
ContainerID model.ID
ID model.ID
Name string
Documentation string
Type string // Portal-populated, usually empty
DisplayName string // Portal-populated
Provider string // "MxCloudGenAI"
Fields map[string]interface{} // providerFields — shape depends on provider
KeyConstant *ConstantRef // providerFields.key → String constant
}
type KnowledgeBase struct {
ContainerID model.ID
ID model.ID
Name string
Documentation string
Provider string // "MxCloudGenAI"
Fields map[string]interface{} // providerFields (includes modelDisplayName, modelName)
KeyConstant *ConstantRef // providerFields.key → String constant
}
type ConsumedMCPService struct {
ContainerID model.ID
ID model.ID
Name string
Documentation string
ProtocolVersion string // "v2024_11_05" | "v2025_03_26"
Version string // app-specified version
InnerDocumentation string // Contents.documentation (free text)
ConnectionTimeoutSeconds int
Endpoint *DocRef // reference to a String constant document containing the MCP endpoint
AuthenticationMicroflow *DocRef // optional microflow used to produce auth headers; must have no input params and return List<System.HttpHeader>
}
type ConstantRef struct {
DocumentID string
QualifiedName string // e.g. "Agents.LLMKey"
}In sdk/mpr/parser_customblob.go (generic — handles all four document types):
- Parse
CustomBlobDocuments$CustomBlobDocumentdocuments - Dispatch by
CustomDocumentType:"agenteditor.agent"→ decode Contents JSON asAgent"agenteditor.model"→ decode asModel"agenteditor.knowledgebase"→ decode asKnowledgeBase"agenteditor.consumedMCPService"→ decode asConsumedMCPService- unknown → store raw Contents, warn
- Store in per-type maps on the reader
The parser should be tolerant: unknown JSON fields in Contents are ignored (the agent editor extension may add fields in future versions).
func (r *Reader) Agents() []*agenteditor.Agent
func (r *Reader) AgentByQualifiedName(name string) *agenteditor.Agent
func (r *Reader) Models() []*agenteditor.Model
func (r *Reader) ModelByQualifiedName(name string) *agenteditor.Model
func (r *Reader) KnowledgeBases() []*agenteditor.KnowledgeBase
func (r *Reader) KnowledgeBaseByQualifiedName(name string) *agenteditor.KnowledgeBase
func (r *Reader) ConsumedMCPServices() []*agenteditor.ConsumedMCPService
func (r *Reader) ConsumedMCPServiceByQualifiedName(name string) *agenteditor.ConsumedMCPServiceCATALOG.AGENTS(module, name, qualified_name, usage_type, entity, model, variables, tool_count, kb_count)CATALOG.MODELS(module, name, qualified_name, provider, key_constant)CATALOG.KNOWLEDGE_BASES(module, name, qualified_name, provider, key_constant)CATALOG.CONSUMED_MCP_SERVICES(module, name, qualified_name, endpoint_constant, protocol_version, authentication_microflow, timeout_seconds)
- Grammar:
SHOW {AGENTS | MODELS | KNOWLEDGE BASES | CONSUMED MCP SERVICES} [IN module] - Grammar:
DESCRIBE {AGENT | MODEL | KNOWLEDGE BASE | CONSUMED MCP SERVICE} qualifiedName - AST:
ShowCustomBlobStmt,DescribeCustomBlobStmt(discriminated by type enum) - Executor: format output using standard table/MDL patterns
Recommended implementation order (matches user preference to start with MODEL):
- Generic wrapper parser +
Modeltype +SHOW MODELS+DESCRIBE MODEL(smallest Contents JSON) ConsumedMCPService(also small)KnowledgeBase(similar shape to Model)Agent(largest, depends on the other three for resolvingmodel/documentreferences in its body)
In sdk/mpr/writer_customblob.go (generic wrapper for all four types):
- Serialize any of
Agent/Model/KnowledgeBase/ConsumedMCPServicestructs to aCustomBlobDocuments$CustomBlobDocumentBSON - Set
CustomDocumentTypeper type (agenteditor.agent,agenteditor.model, etc.) - Set
Metadata.CreatedByExtension = "extension/agent-editor" - Set
Metadata.ReadableTypeNameper type ("Agent","Model","Knowledge base","Consumed MCP service") - Serialize
Contentsas a JSON string (per-type encoder) - Set
Excluded = false,ExportLevel = "Hidden"(matches the new Agent Editor defaults) - Generate stable UUIDs for
$IDandMetadata.$ID - For
Agent: generate UUIDs foridfield on eachtools[]andknowledgebaseTools[]entry - For
Model/KnowledgeBase/ConsumedMCPService: resolve theKeyorEndpointconstant reference to{documentId, qualifiedName}by looking up the String constant in the reader
- Grammar:
CREATE AGENT qualifiedName properties variablesClause? - AST:
CreateAgentStmt,DropAgentStmt - Executor: validate, write BSON, register in module
- Entity reference must exist (if specified)
- Variables marked
EntityAttributemust correspond to attributes on the referenced entity UsageTypemust be a known value (TaskorChat)- Variable names used in
{{...}}in prompts should match declared variables (warning, not error)
- Include agents in
SHOW STRUCTUREoutput - Add
CATALOG.AGENTStable for SQL queries - Include agent references in
SHOW REFERENCES/SHOW IMPACT - Wire into
REFRESH CATALOG(both fast and full modes)
- Agent documents (
CustomBlobDocuments$CustomBlobDocument) require Mendix 11.x - Add to
sdk/versions/mendix-11.yaml:agents: agent_document: min_version: "11.9.0" mdl: "CREATE AGENT Module.Name (...) { TOOL ... { ... } ... }" notes: "Requires AgentEditorCommons marketplace module"
- Executor pre-check:
checkFeature("agent_document")before CREATE
- Hover on agent names shows system prompt summary
- Go-to-definition navigates to agent document
- Completion for
DESCRIBE AGENTwith agent names
Full agent support requires MDL coverage of related CustomBlobDocument types and the new "Call Agent" microflow activity. These are split into sub-phases but all are needed for the examples in this proposal to work end-to-end.
Models are peer CustomBlobDocuments that reference a Mendix Cloud GenAI Portal key stored in a String constant. The minimum input from the user is the provider and the constant reference — Portal metadata (displayName, keyId, keyName, environment, resourceName, etc.) is filled by Studio Pro when a constant with a valid key value is selected.
Matches the observed BSON for Agents.MyFirstModel:
CREATE MODEL Agents."MyFirstModel" (
Provider: MxCloudGenAI,
Key: Agents.LLMKey
);DESCRIBE MODEL may show Portal-populated fields when present (round-trip preserves them, but they're not user-editable in MDL):
-- What DESCRIBE produces for a model that has been activated against the Portal
CREATE MODEL Agents."MyFirstModel" (
Provider: MxCloudGenAI,
Key: Agents.LLMKey,
DisplayName: 'GPT-4 Turbo', -- Portal-populated, read-only in MDL
KeyName: 'prod-gpt4', -- Portal-populated, read-only in MDL
Environment: 'production' -- Portal-populated, read-only in MDL
);At runtime, AgentEditorCommons.ASU_AgentEditor reads the constant and creates the corresponding GenAICommons.DeployedModel.
JSON output shape:
{
"type": "",
"name": "",
"displayName": "<portal-populated or empty>",
"provider": "MxCloudGenAI",
"providerFields": {
"environment": "", "deepLinkURL": "", "keyId": "", "keyName": "", "resourceName": "",
"key": { "documentId": "<uuid>", "qualifiedName": "Agents.LLMKey" }
}
}Same shape as Model, but providerFields carries besides information about the knowledgebase also a refrence to an embedding-model. User-settable fields are just Provider and Key:
CREATE KNOWLEDGE BASE Agents."Knowledge_base" (
Provider: MxCloudGenAI,
Key: Agents.KBKey
);DESCRIBE can round-trip Portal-populated fields:
CREATE KNOWLEDGE BASE Agents."Knowledge_base" (
Provider: MxCloudGenAI,
Key: Agents.KBKey,
ModelDisplayName: 'text-embedding-3-large', -- Portal-populated
ModelName: 'text-embedding-3-large' -- Portal-populated
);Referenced from agents via KNOWLEDGE BASE <Name> { Source: <QualifiedName>, ... } blocks inside the agent body.
At runtime, AgentEditorCommons.ASU_AgentEditor reads the constant and creates the corresponding GenAICommons.ConsumedKnowledgeBase.
JSON output shape:
{
"name": "",
"provider": "MxCloudGenAI",
"providerFields": {
"environment": "", "deepLinkURL": "", "keyId": "", "keyName": "",
"modelDisplayName": "", "modelName": "",
"key": { "documentId": "<uuid>", "qualifiedName": "Agents.KBKey" }
}
}Matches the observed BSON for Agents.Consumed_MCP_service. The document carries protocol version, app-level version, timeout, documentation, and an endpoint constant reference. It can also carry an optional authentication microflow reference.
CREATE CONSUMED MCP SERVICE Agents."Consumed_MCP_service" (
ProtocolVersion: v2025_03_26,
Version: '0.0.1',
ConnectionTimeoutSeconds: 30,
Endpoint: Agents.MCPEndpoint,
AuthenticationMicroflow: Agents.AuthenticationMicroflow,
Documentation: 'Description of what this MCP service provides'
);Referenced from agents via CONSUMED MCP SERVICE <QualifiedName> { ... } blocks inside the agent body.
At runtime, AgentEditorCommons.ASU_AgentEditor reads the constant and creates the corresponding MCPClient.ConsumedMCPService.
JSON output shape:
{
"protocolVersion": "v2025_03_26",
"documentation": "Description of what this MCP service provides",
"version": "0.0.1",
"connectionTimeoutSeconds": 30,
"endpoint": {
"documentId": "<uuid>",
"qualifiedName": "Agents.MCPEndpoint"
},
"authenticationMicroflow": {
"documentId": "<uuid>",
"qualifiedName": "Agents.AuthenticationMicroflow"
}
}Note:
Documentationas a top-level property maps to the JSONdocumentationfield (insideContents), not to the outer BSONDocumentationfield on the CustomBlobDocument wrapper. Two different fields with the same name — the MDL writer sets both consistently.
New MDL microflow statements mapping to the Agents Kit toolbox actions (see the "New MDL Statement" section under "Building Smart Apps" for syntax):
| MDL Statement | Java Action | Purpose |
|---|---|---|
CALL AGENT WITH HISTORY $agent REQUEST $req [CONTEXT $obj] INTO $Response |
AgentCommons.Agent_Call_WithHistory |
Call a conversational agent with chat history |
CALL AGENT WITHOUT HISTORY $agent [CONTEXT $obj] [REQUEST $req] [FILES $fc] INTO $Response |
AgentCommons.Agent_Call_WithoutHistory |
Call a single-call (Task) agent |
NEW CHAT FOR AGENT $agent ACTION MICROFLOW <mf> [CONTEXT $obj] [MODEL $dm] INTO $ChatContext |
AgentCommons.ChatContext_Create_ForAgent |
Create a ChatContext wired to an agent |
These need a new BSON activity type (or mapping to the generic Java action call — see Open Question 4).
Follows the same shape as ALTER PAGE — in-place modifications to top-level properties and body blocks:
ALTER AGENT MyModule."SentimentAnalyzer" {
SET SystemPrompt = 'New prompt with {{Variable}}.';
SET Variables = ("FeedbackText": EntityAttribute, "NewVar": String);
SET Model = MyModule.OtherModel;
SET ToolChoice = None;
INSERT CONSUMED MCP SERVICE MyModule.NewMCPService {
Enabled: true
};
INSERT KNOWLEDGE BASE NewKB {
Source: MyModule.OtherKB,
Collection: 'other-collection',
MaxResults: 5
};
DROP CONSUMED MCP SERVICE MyModule.OldMCPService;
};This section demonstrates how MDL agent support, combined with the existing agentic marketplace modules (GenAICommons, AgentCommons, MCPClient, MCPServer, ConversationalUI, and one of the connectors such as MxGenAIConnector), enables building complete AI-powered applications entirely from MDL scripts.
Sources: This section follows the official Mendix documentation for the Agent Editor, Agent Commons, Conversational UI, Mendix Cloud GenAI Connector, MCP Client, and MCP Server.
A "smart app" in Mendix typically has these layers, all expressible in MDL:
┌─────────────────────────────────────────────────────────────────┐
│ Conversational UI │
│ Chat widget, tool approval, trace monitoring │
├─────────────────────────────────────────────────────────────────┤
│ Agent Layer │
│ Agent documents (CREATE AGENT) — prompts, variables, │
│ tools, knowledge bases, MCP servers │
├──────────────┬────────────────────┬─────────────────────────────┤
│ Tools │ Knowledge Bases │ MCP Services │
│ Microflows │ RAG retrieval │ External tool servers │
├──────────────┴────────────────────┴─────────────────────────────┤
│ Model Documents + MxGenAIConnector │
│ Model key constant → DeployedModel at runtime │
├─────────────────────────────────────────────────────────────────┤
│ Domain Model │
│ Entities, associations, enumerations │
└─────────────────────────────────────────────────────────────────┘
Unlike the initial draft of this proposal, agents in Mendix are not wired up by building the request manually in an action microflow. The correct flow is:
- Studio Pro design time — the developer creates agent documents (and model documents) in Studio Pro. Tools, knowledge bases, and MCP servers are attached to the agent in the agent document itself (not added at runtime).
- Model key — a Mendix Cloud GenAI Portal key is stored in a String constant on the model document. At runtime,
ASU_AgentEditor(registered as after-startup microflow) reads the key and auto-creates the correspondingGenAICommons.DeployedModel, theGenAICommons.ConsumedKnowledgeBase,MCPlient.ConsumedMCPServiceand links all up inAgentCommons.Agentobjects. - Call Agent activity — in a microflow, a single "Call Agent With History" or "Call Agent Without History" toolbox action does everything: resolve the agent's in-use version, select its deployed model, replace variable placeholders from the context object, wire in tools/knowledge bases/MCP servers declared on the agent, and call the LLM.
- Conversational UI — to use the agent in a chat, call "New Chat for Agent" which creates a
ChatContextpre-configured with the agent's deployed model, system prompt, and action microflow. The action microflow for chat just calls "Call Agent With History" with the request built byDefault Preprocessing.
Before any agent can be used, the following one-time setup is required (see the Agent Editor prerequisites):
-- 1. Encryption key must be 32 characters (App > Settings > Configuration).
-- The Encryption module is a prerequisite; its setup is outside MDL.
-- 2. Register ASU_AgentEditor as an after-startup microflow
ALTER SETTINGS MODEL AfterStartupMicroflow = AgentEditorCommons.ASU_AgentEditor;
-- 3. A model key constant must hold a Mendix Cloud GenAI Portal key.
-- Use one constant per model. The agent editor references this constant
-- in the model document.
CREATE CONSTANT "MyApp"."DefaultModelKey" (
Type: String,
DefaultValue: '' -- set via environment config or configuration UI
);
-- 4. Ensure the required module roles are assigned.
-- MxGenAIConnector.Administrator is needed to configure the connector.
-- AgentCommons.AgentAdmin is needed to manage agents in the runtime UI.
-- 5. Exclude the auto-created /agenteditor folder from version control.
-- (This is handled in .gitignore, outside MDL.)Because "Call Agent" is a first-class Mendix toolbox activity (distinct from a generic Java action call), this proposal also introduces a corresponding MDL microflow statement:
CALL AGENT WITH HISTORY <agent> REQUEST <request> [CONTEXT <obj>] INTO $Response
CALL AGENT WITHOUT HISTORY <agent> [CONTEXT <obj>] [REQUEST <req>] [FILES <fc>] INTO $Response
NEW CHAT FOR AGENT <agent> ACTION MICROFLOW <microflow> [CONTEXT <obj>] [MODEL <dm>] INTO $ChatContext
These map directly to the AgentCommons.Agent_Call_WithHistory, AgentCommons.Agent_Call_WithoutHistory, and AgentCommons.ChatContext_Create_ForAgent Java actions (all exposed in the "Agents Kit" toolbox category). The MDL form exists so these show up as the actual "Call Agent" activity in Studio Pro rather than as opaque Java action calls.
A conversational agent that helps customer support reps by looking up orders, checking shipment status, and drafting responses. Tools (microflows) are attached to the agent in the agent document — at runtime the "Call Agent" activity handles tool invocation automatically.
-- Domain model for the support system
@Position(100, 100)
CREATE PERSISTENT ENTITY Support."Customer" (
"Name": String(200) NOT NULL ERROR 'Name is required',
"Email": String(200),
"Phone": String(50),
"AccountTier": Enumeration(Support.AccountTier)
);
@Position(350, 100)
CREATE PERSISTENT ENTITY Support."Order" (
"OrderNumber": String(50) NOT NULL ERROR 'Order number is required',
"OrderDate": DateTime,
"TotalAmount": Decimal,
"Status": Enumeration(Support.OrderStatus)
);
@Position(600, 100)
CREATE PERSISTENT ENTITY Support."SupportTicket" (
"Subject": String(200),
"Description": String(unlimited),
"Priority": Enumeration(Support.TicketPriority),
"Resolution": String(unlimited),
"IsResolved": Boolean DEFAULT false
);
CREATE ASSOCIATION Support."Order_Customer"
FROM Support."Order" TO Support."Customer";
CREATE ASSOCIATION Support."SupportTicket_Customer"
FROM Support."SupportTicket" TO Support."Customer";
CREATE ASSOCIATION Support."SupportTicket_Order"
FROM Support."SupportTicket" TO Support."Order";
CREATE ENUMERATION Support."OrderStatus" (
Pending = 'Pending',
Shipped = 'Shipped',
Delivered = 'Delivered',
Returned = 'Returned'
);
CREATE ENUMERATION Support."TicketPriority" (
Low = 'Low',
Medium = 'Medium',
High = 'High',
Urgent = 'Urgent'
);
CREATE ENUMERATION Support."AccountTier" (
Standard = 'Standard',
Premium = 'Premium',
Enterprise = 'Enterprise'
);Tool microflows take the Mendix data types that the LLM should fill in as input parameters and must return a String (which becomes the tool result shown to the model). The Agent Editor infers the tool's JSON schema from the microflow signature — so parameter names and types are what the LLM sees.
/**
* Tool microflow: Look up a customer by email address.
* The Agent Editor will expose this as a tool with input parameter "Email".
*/
CREATE MICROFLOW Support."Tool_LookupCustomer" (
$Email: String
)
RETURNS String
BEGIN
RETRIEVE $Customer FROM DATABASE Support.Customer
WHERE Email = $Email LIMIT 1;
IF $Customer != empty THEN
RETURN 'Customer: ' + $Customer/Name
+ ', Tier: ' + getKey($Customer/AccountTier)
+ ', Phone: ' + $Customer/Phone;
ELSE
RETURN 'No customer found with email: ' + $Email;
END IF;
END;
/
/**
* Tool microflow: Look up recent orders for a customer by name.
*/
CREATE MICROFLOW Support."Tool_GetOrders" (
$CustomerName: String
)
RETURNS String
BEGIN
RETRIEVE $Customer FROM DATABASE Support.Customer
WHERE Name = $CustomerName LIMIT 1;
IF $Customer = empty THEN
RETURN 'Customer not found: ' + $CustomerName;
END IF;
RETRIEVE $OrderList FROM DATABASE Support.Order
WHERE Support.Order_Customer = $Customer;
DECLARE $Result String = '';
LOOP $Order IN $OrderList
BEGIN
SET $Result = $Result + 'Order ' + $Order/OrderNumber
+ ' (' + formatDateTime($Order/OrderDate, 'yyyy-MM-dd') + ')'
+ ' - ' + formatDecimal($Order/TotalAmount, 2) + ' EUR'
+ ' - Status: ' + getKey($Order/Status) + '\n';
END LOOP;
RETURN if $Result = '' then 'No orders found' else $Result;
END;
/
/**
* Tool microflow: Create a support ticket.
* Multiple primitive parameters become structured input for the tool.
*/
CREATE MICROFLOW Support."Tool_CreateTicket" (
$Subject: String,
$Description: String,
$Priority: ENUM Support.TicketPriority
)
RETURNS String
BEGIN
$Ticket = CREATE Support.SupportTicket (
Subject = $Subject,
Description = $Description,
Priority = $Priority,
IsResolved = false
);
COMMIT $Ticket;
RETURN 'Ticket created (ID: ' + toString($Ticket/System.id) + ').';
END;
/Tools, knowledge bases, and MCP servers are declared in the agent document itself — not attached at runtime in the action microflow. This is the key fix vs. the earlier draft.
-- The agent definition — stored as a CustomBlobDocument in the project
CREATE AGENT Support."CustomerSupportAgent" (
UsageType: Chat,
Description: 'Customer support agent with lookup and ticketing tools',
SystemPrompt: 'You are a helpful customer support agent for an e-commerce company.
Your capabilities:
- Look up customer information by email
- Check order history and shipment status
- Create support tickets for unresolved issues
Guidelines:
- Always verify the customer identity before sharing order details
- For Premium and Enterprise customers, prioritize their requests
- If you cannot resolve an issue, create a support ticket
- Be empathetic and professional in your responses'
)
{
TOOL LookupCustomer {
Microflow: Support.Tool_LookupCustomer,
Description: 'Look up a customer by their email address',
Access: VisibleForUser
}
TOOL GetOrders {
Microflow: Support.Tool_GetOrders,
Description: 'Get recent orders for a customer by name',
Access: VisibleForUser
}
TOOL CreateTicket {
Microflow: Support.Tool_CreateTicket,
Description: 'Create a new support ticket with the given subject, description, and priority',
Access: UserConfirmationRequired
}
};The Access property maps to GenAICommons.ENUM_UserAccessApproval:
HiddenForUser— tool executes silentlyVisibleForUser— tool call is shown in the chat UI but executes automaticallyUserConfirmationRequired— tool call is shown and user must approve before execution
With tools declared on the agent, the action microflow becomes a simple two-step: preprocess, then call the agent. The "Call Agent With History" activity handles tool invocation, knowledge base retrieval, and the LLM round-trips internally.
/**
* Action microflow for the Customer Support chat.
* Wired up by "New Chat for Agent" when the chat is created.
*
* @param $ChatContext The conversation context from the chat widget
* @returns Boolean indicating success
*/
CREATE MICROFLOW Support."Chat_CustomerSupport" (
$ChatContext: ConversationalUI.ChatContext
)
RETURNS Boolean
BEGIN
-- 1. Default Preprocessing: extract user message, build Request with history
$Request = CALL MICROFLOW ConversationalUI.ChatContext_Preprocessing(
ChatContext = $ChatContext
) ON ERROR ROLLBACK;
IF $Request = empty THEN
RETURN false;
END IF;
-- 2. Retrieve the agent (created automatically from the agent document
-- when ASU_AgentEditor runs at startup)
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'Support.CustomerSupportAgent' LIMIT 1;
-- 3. Call Agent With History — single activity that:
-- - Selects the in-use version + its deployed model
-- - Wires in the agent's tools, knowledge bases, MCP servers
-- - Calls Chat Completions
-- - Handles tool-call round-trips
CALL AGENT WITH HISTORY $Agent REQUEST $Request INTO $Response
ON ERROR ROLLBACK;
-- 4. Update the chat UI with the response (same as any ConversationalUI flow)
IF $Response != empty AND $Response/GenAICommons.Response_Message != empty THEN
$Message = CALL MICROFLOW ConversationalUI.ChatContext_UpdateAssistantResponse(
ChatContext = $ChatContext,
MessageStatus = ConversationalUI.ENUM_MessageStatus.Success,
Response = $Response
) ON ERROR ROLLBACK;
RETURN true;
ELSE
RETURN false;
END IF;
END;
/The chat page uses New Chat for Agent to create a ChatContext pre-configured with the agent's model, system prompt, and action microflow. This replaces the manual ProviderConfig wiring from the earlier draft.
/**
* Microflow that opens a support chat. Called from a "Start Chat" button.
* Uses "New Chat for Agent" to create a ChatContext configured with this agent.
*/
CREATE MICROFLOW Support."ACT_StartSupportChat" ()
BEGIN
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'Support.CustomerSupportAgent' LIMIT 1;
NEW CHAT FOR AGENT $Agent
ACTION MICROFLOW Support.Chat_CustomerSupport
INTO $ChatContext
ON ERROR ROLLBACK;
SHOW PAGE Support.SupportChat($ChatContext = $ChatContext);
END;
/
/**
* Customer support chat page.
* Data source is the ChatContext passed from ACT_StartSupportChat.
*/
CREATE PAGE Support."SupportChat" (
Title: 'Customer Support',
Layout: Atlas_Core.Atlas_Default
) {
HEADER h1 {
DYNAMICTEXT title (Caption: 'AI Customer Support')
}
DATAVIEW chatView (DataSource: CONTEXT ConversationalUI.ChatContext) {
-- The ConversationalUI chat snippet renders the conversation,
-- send box, tool call approvals, and message history
SNIPPETCALL chatWidget (Snippet: ConversationalUI.Snippet_Output_WithHistory)
}
};
/-- Module roles
CREATE MODULE ROLE Support."User";
CREATE MODULE ROLE Support."Admin";
-- Entity access
GRANT Support.User ON Support.Customer (READ *);
GRANT Support.User ON Support.Order (READ *);
GRANT Support.User ON Support.SupportTicket (CREATE, READ *, WRITE *);
GRANT Support.Admin ON Support.Customer (CREATE, DELETE, READ *, WRITE *);
GRANT Support.Admin ON Support.Order (CREATE, DELETE, READ *, WRITE *);
GRANT Support.Admin ON Support.SupportTicket (CREATE, DELETE, READ *, WRITE *);
-- Microflow access
GRANT EXECUTE ON MICROFLOW Support.ACT_StartSupportChat TO Support.User;
GRANT EXECUTE ON MICROFLOW Support.Chat_CustomerSupport TO Support.User;
-- Tool microflows must be callable because the agent invokes them
GRANT EXECUTE ON MICROFLOW Support.Tool_LookupCustomer TO Support.User;
GRANT EXECUTE ON MICROFLOW Support.Tool_GetOrders TO Support.User;
GRANT EXECUTE ON MICROFLOW Support.Tool_CreateTicket TO Support.User;
-- Page access
GRANT VIEW ON PAGE Support.SupportChat TO Support.User;An agent that connects to external MCP servers to access tools like web search, file reading, and database queries. The key insight: the consumed MCP service is a document (created in Studio Pro alongside the agent), and it's attached to the agent document directly — no runtime wiring needed.
CREATE PERSISTENT ENTITY Research."ResearchProject" (
"Title": String(200),
"Objective": String(unlimited),
"Status": Enumeration(Research.ProjectStatus),
"Summary": String(unlimited)
);
CREATE ENUMERATION Research."ProjectStatus" (
InProgress = 'In Progress',
Completed = 'Completed',
OnHold = 'On Hold'
);Before defining the consumed MCP service, create a microflow that returns the HTTP headers needed to authenticate to the server. The microflow must take no parameters and return List<System.HttpHeader>.
/**
* Returns HTTP headers used to authenticate to the research MCP server.
* Referenced from the ConsumedMCPService document.
*/
CREATE MICROFLOW Research."MCP_GetCredentials" ()
RETURNS List of System.HttpHeader
BEGIN
DECLARE $Headers List of System.HttpHeader = empty;
$AuthHeader = CREATE System.HttpHeader (
Key = 'Authorization',
Value = 'Bearer ' + @Research.ResearchMCPToken
);
SET $Headers = $Headers + $AuthHeader;
RETURN $Headers;
END;
/The ConsumedMCPService is a model document (like the agent itself), not a runtime-created entity. In this proposal's Phase 4 future extensions, we would also add MDL for it:
-- Proposed (future extension): declare a consumed MCP service as a document
CREATE CONSUMED MCP SERVICE Research."ResearchTools" (
Endpoint: 'https://mcp.example.com/research',
ProtocolVersion: v2025_03_26,
GetCredentialsMicroflow: Research.MCP_GetCredentials,
ConnectionTimeOutInSeconds: 30
);At runtime, ASU_AgentEditor syncs this document into a MCPClient.ConsumedMCPService entity and discovers its tools.
The agent references the consumed MCP service by qualified name. At runtime, "Call Agent" automatically adds all (enabled) tools from the attached MCP server to the request.
CREATE AGENT Research."ResearchAssistant" (
UsageType: Chat,
Description: 'Research assistant with web search and document analysis via MCP',
Entity: Research.ResearchProject,
Variables: ("Title": EntityAttribute, "Objective": EntityAttribute),
SystemPrompt: 'You are a research assistant helping with project: {{Title}}.
Objective: {{Objective}}
Use the available tools to:
1. Search the web for relevant information
2. Read and analyze documents
3. Summarize findings
Always cite your sources. Present findings in a structured format.'
)
{
CONSUMED MCP SERVICE Research.ResearchTools {
Access: VisibleForUser
}
};Because tools and MCP services are declared on the agent document, the action microflow stays minimal. "Call Agent With History" takes care of wiring MCP tools into the request automatically.
/**
* Action microflow for the research chat.
* Because MCP services are attached to the agent document, no MCP-specific
* code is needed here — "Call Agent" handles it.
*/
CREATE MICROFLOW Research."Chat_Research" (
$ChatContext: ConversationalUI.ChatContext
)
RETURNS Boolean
BEGIN
$Request = CALL MICROFLOW ConversationalUI.ChatContext_Preprocessing(
ChatContext = $ChatContext
) ON ERROR ROLLBACK;
IF $Request = empty THEN
RETURN false;
END IF;
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'Research.ResearchAssistant' LIMIT 1;
-- Retrieve the context object passed from the page (ResearchProject).
-- Variables "Title" and "Objective" are replaced from this object's
-- attributes by Call Agent automatically.
RETRIEVE $ProjectList FROM $ChatContext/ConversationalUI.ChatContext_Owner
/System.User; -- simplified; real apps pass project via extension entity
DECLARE $Project Research.ResearchProject;
CALL AGENT WITH HISTORY $Agent REQUEST $Request CONTEXT $Project INTO $Response
ON ERROR ROLLBACK;
IF $Response != empty AND $Response/GenAICommons.Response_Message != empty THEN
CALL MICROFLOW ConversationalUI.ChatContext_UpdateAssistantResponse(
ChatContext = $ChatContext,
MessageStatus = ConversationalUI.ENUM_MessageStatus.Success,
Response = $Response
) ON ERROR ROLLBACK;
RETURN true;
ELSE
RETURN false;
END IF;
END;
/Not all agents need a chat interface. A Task (single-call) agent processes one request and returns a result — useful for batch operations, background processing, and microflow-embedded AI. The "Call Agent Without History" activity handles everything in one step.
@Position(100, 100)
CREATE PERSISTENT ENTITY Reviews."ProductReview" (
"ProductName": String(200),
"ReviewText": String(unlimited),
"Sentiment": String(50),
"KeyThemes": String(unlimited),
"IsProcessed": Boolean DEFAULT false
);CREATE AGENT Reviews."SentimentAnalyzer" (
UsageType: Task,
Description: 'Single-call agent that extracts sentiment and themes from a product review',
Entity: Reviews.ProductReview,
Variables: ("ProductName": EntityAttribute, "ReviewText": EntityAttribute),
SystemPrompt: 'Analyze the following product review for {{ProductName}}.
Extract:
1. Overall sentiment (Positive, Negative, Neutral, Mixed)
2. Key themes mentioned (comma-separated)
Respond in this exact format:
Sentiment: <sentiment>
Themes: <theme1>, <theme2>, <theme3>',
UserPrompt: '{{ReviewText}}'
);The context object ($Review) carries the attribute values that replace {{ProductName}} and {{ReviewText}} in the prompts. "Call Agent Without History" resolves everything and returns the Response in a single activity.
/**
* Process a single product review using the SentimentAnalyzer agent.
* Called from a batch microflow, a button action, or a scheduled event.
*
* @param $Review The review to analyze — its attributes replace prompt variables
*/
CREATE MICROFLOW Reviews."ProcessReview" (
$Review: Reviews.ProductReview
)
BEGIN
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'Reviews.SentimentAnalyzer' LIMIT 1;
-- One activity: resolve version, deployed model, prompts, and call the LLM
CALL AGENT WITHOUT HISTORY $Agent CONTEXT $Review INTO $Response
ON ERROR ROLLBACK;
IF $Response = empty OR $Response/GenAICommons.Response_Message = empty THEN
LOG WARNING NODE 'Reviews' 'Sentiment analysis failed for review: '
+ toString($Review/System.id);
RETURN;
END IF;
DECLARE $ResponseText String = CALL MICROFLOW
GenAICommons.Response_GetModelResponseString(Response = $Response);
CHANGE $Review (
Sentiment = $ResponseText,
IsProcessed = true
);
COMMIT $Review;
END;
/
/**
* Batch process all unprocessed reviews.
*/
CREATE MICROFLOW Reviews."ProcessAllReviews" (
$ReviewList: List of Reviews.ProductReview
)
BEGIN
LOOP $Review IN $ReviewList
BEGIN
IF $Review/IsProcessed = false THEN
CALL MICROFLOW Reviews.ProcessReview(Review = $Review) ON ERROR CONTINUE;
END IF;
END LOOP;
END;
/A finance agent where sensitive tool calls (approving expenses) require user confirmation. The user-confirmation flow is configured declaratively on the agent's tools, not built in the action microflow — ConversationalUI renders the approval dialog automatically.
/**
* Read-only tool: Look up pending expense reports.
* Safe for auto-execution (VisibleForUser).
*/
CREATE MICROFLOW Finance."Tool_LookupExpenses" (
$Department: String
)
RETURNS String
BEGIN
RETRIEVE $ExpenseList FROM DATABASE Finance.ExpenseReport
WHERE Department = $Department AND Status = Finance.ExpenseStatus.Pending;
DECLARE $Result String = '';
LOOP $Expense IN $ExpenseList
BEGIN
SET $Result = $Result + 'Expense #' + $Expense/ReportNumber
+ ' by ' + $Expense/SubmittedBy
+ ' - ' + formatDecimal($Expense/Amount, 2) + ' EUR: '
+ $Expense/Description + '\n';
END LOOP;
RETURN if $Result = '' then 'No pending expenses' else $Result;
END;
/
/**
* Write tool: Approve an expense report.
* Requires user confirmation before execution.
*/
CREATE MICROFLOW Finance."Tool_ApproveExpense" (
$ReportNumber: String,
$ApprovalNote: String
)
RETURNS String
BEGIN
RETRIEVE $Expense FROM DATABASE Finance.ExpenseReport
WHERE ReportNumber = $ReportNumber LIMIT 1;
IF $Expense = empty THEN
RETURN 'Expense report not found: ' + $ReportNumber;
END IF;
CHANGE $Expense (
Status = Finance.ExpenseStatus.Approved,
ApprovalNote = $ApprovalNote,
ApprovedDate = [%CurrentDateTime%]
);
COMMIT $Expense;
RETURN 'Expense ' + $ReportNumber + ' approved.';
END;
/The ACCESS modifier per tool controls what ConversationalUI does at runtime:
VisibleForUser— tool call shown in chat, executes automaticallyUserConfirmationRequired— chat shows an approval dialog, user clicks Approve/DeclineHiddenForUser— executes silently (use for internal/lookup tools)
CREATE AGENT Finance."ExpenseApprovalAgent" (
UsageType: Chat,
Description: 'Review and approve expense reports with user confirmation for writes',
SystemPrompt: 'You are a financial assistant that helps managers review and approve expense reports.
You have access to tools that can:
- Look up expense report details (auto-executes)
- Approve expense reports (requires user confirmation)
IMPORTANT: Always show the expense details before recommending approval.
Never approve expenses that exceed typical department limits without explicit user instruction.'
)
{
TOOL LookupExpenses {
Microflow: Finance.Tool_LookupExpenses,
Description: 'List pending expense reports for a department',
Access: VisibleForUser
}
TOOL ApproveExpense {
Microflow: Finance.Tool_ApproveExpense,
Description: 'Approve a specific expense report by report number',
Access: UserConfirmationRequired
}
};Because tool approval is declared on the agent, the action microflow is identical to Example 1 — Call Agent With History handles tool-call round-trips and cooperates with ConversationalUI's approval widget automatically.
CREATE MICROFLOW Finance."Chat_ExpenseApproval" (
$ChatContext: ConversationalUI.ChatContext
)
RETURNS Boolean
BEGIN
$Request = CALL MICROFLOW ConversationalUI.ChatContext_Preprocessing(
ChatContext = $ChatContext
) ON ERROR ROLLBACK;
IF $Request = empty THEN RETURN false; END IF;
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'Finance.ExpenseApprovalAgent' LIMIT 1;
CALL AGENT WITH HISTORY $Agent REQUEST $Request INTO $Response
ON ERROR ROLLBACK;
IF $Response != empty AND $Response/GenAICommons.Response_Message != empty THEN
CALL MICROFLOW ConversationalUI.ChatContext_UpdateAssistantResponse(
ChatContext = $ChatContext,
MessageStatus = ConversationalUI.ENUM_MessageStatus.Success,
Response = $Response
) ON ERROR ROLLBACK;
RETURN true;
ELSE
RETURN false;
END IF;
END;
/An agent that uses a knowledge base (vector store) for Retrieval-Augmented Generation. As with tools and MCP services, the knowledge base is attached to the agent document — "Call Agent" performs retrieval automatically before invoking the LLM, and source references flow through to the chat UI.
A knowledge base is a separate model document that references a Mendix Cloud GenAI Knowledge Base resource via its key. The underlying GenAICommons.ConsumedKnowledgeBase is auto-created by ASU_AgentEditor at startup from the document.
-- Proposed (future extension): declare a knowledge base as a document
CREATE KNOWLEDGE BASE HelpDesk."ProductDocsKB" (
DisplayName: 'Product Documentation',
Architecture: 'MxCloud',
KeyConstant: HelpDesk.ProductDocsKBKey -- String constant with the KB resource key
);CREATE AGENT HelpDesk."ProductExpert" (
UsageType: Chat,
Description: 'Answers product questions from the documentation knowledge base',
SystemPrompt: 'You are a product expert for our software platform.
Answer questions using ONLY the information from the provided knowledge base context.
If the knowledge base does not contain relevant information, say so clearly.
Always include the source document reference in your answer.
Do not make up information that is not in the context.'
)
{
KNOWLEDGE BASE HelpDesk.ProductDocsKB {
Collection: 'product-documentation',
MaxResults: 5,
MinSimilarity: 0.7
}
};Because the knowledge base is attached to the agent, RAG retrieval happens inside "Call Agent With History". Source references are automatically added to the Response/Message, and ChatContext_UpdateAssistantResponse already handles rendering them (it calls Source_Create internally).
CREATE MICROFLOW HelpDesk."Chat_ProductExpert" (
$ChatContext: ConversationalUI.ChatContext
)
RETURNS Boolean
BEGIN
$Request = CALL MICROFLOW ConversationalUI.ChatContext_Preprocessing(
ChatContext = $ChatContext
) ON ERROR ROLLBACK;
IF $Request = empty THEN RETURN false; END IF;
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'HelpDesk.ProductExpert' LIMIT 1;
-- RAG retrieval happens inside "Call Agent With History" automatically
CALL AGENT WITH HISTORY $Agent REQUEST $Request INTO $Response
ON ERROR ROLLBACK;
IF $Response != empty AND $Response/GenAICommons.Response_Message != empty THEN
CALL MICROFLOW ConversationalUI.ChatContext_UpdateAssistantResponse(
ChatContext = $ChatContext,
MessageStatus = ConversationalUI.ENUM_MessageStatus.Success,
Response = $Response
) ON ERROR ROLLBACK;
RETURN true;
ELSE
RETURN false;
END IF;
END;
/Notice that Examples 1, 2, 4, and 5 all have the same shape for the action microflow — only the agent reference changes. This is the power of declarative agent documents: the capabilities (tools, KB, MCP) are metadata the "Call Agent" activity consumes, not code the developer writes.
Mendix apps can also act as MCP servers, exposing their microflows as tools that external AI systems (Claude, ChatGPT, or another Mendix app) can call. This is done via the MCPServer marketplace module — not a custom Published REST Service. The module provides Create MCP Server and Add Tool toolbox actions that handle the MCP protocol.
Each tool microflow must accept primitives or an MCPServer.Tool object as input and return either String or TextContent. The Agent Editor / MCP Server infers the JSON schema from the signature.
-- Tool microflow: The MCP server will expose this as a tool named "lookup_customer"
CREATE MICROFLOW Support."MCP_LookupCustomer" (
$Email: String
)
RETURNS String
BEGIN
RETRIEVE $Customer FROM DATABASE Support.Customer
WHERE Email = $Email LIMIT 1;
IF $Customer = empty THEN
RETURN 'No customer found';
END IF;
RETURN 'Customer: ' + $Customer/Name + ', Tier: ' + getKey($Customer/AccountTier);
END;
/
CREATE MICROFLOW Support."MCP_GetOrderStatus" (
$OrderNumber: String
)
RETURNS String
BEGIN
RETRIEVE $Order FROM DATABASE Support.Order
WHERE OrderNumber = $OrderNumber LIMIT 1;
IF $Order = empty THEN
RETURN 'Order not found: ' + $OrderNumber;
END IF;
RETURN 'Order ' + $Order/OrderNumber + ' status: ' + getKey($Order/Status);
END;
/
/**
* Set up the MCP server at startup and register the tools.
* Register this as (part of) the after-startup microflow.
*/
CREATE MICROFLOW Support."ASU_SetupMCPServer" ()
BEGIN
-- 1. Create the MCP server instance (Mendix runtime listens for MCP requests)
$Server = CALL JAVA ACTION MCPServer.CreateMCPServer(
Name = 'CustomerSupportMCP',
Version = '1.0',
ProtocolVersion = MCPServer.ENUM_ProtocolVersion.v2025_03_26
) ON ERROR ROLLBACK;
-- 2. Expose each tool microflow. The MCP Server module builds the JSON
-- schema from each microflow's signature.
CALL JAVA ACTION MCPServer.AddTool(
Server = $Server,
Name = 'lookup_customer',
Description = 'Look up customer information by email address',
Microflow = 'Support.MCP_LookupCustomer'
) ON ERROR ROLLBACK;
CALL JAVA ACTION MCPServer.AddTool(
Server = $Server,
Name = 'get_order_status',
Description = 'Get the status of an order by order number',
Microflow = 'Support.MCP_GetOrderStatus'
) ON ERROR ROLLBACK;
LOG INFO NODE 'MCP' 'MCP server started with 2 tools';
END;
/External agents (a Claude Desktop client, another Mendix app using MCPClient, or any MCP-compliant system) can now connect to this server, discover the tools, and call them. The MCPServer module handles protocol framing, authentication, and dispatch to the appropriate microflow.
This script creates a complete AI-powered IT help desk application in a single MDL file, showing all the correct pieces end-to-end.
-- =============================================================
-- IT Help Desk Smart App
-- A complete AI-powered help desk built with MDL
-- Prerequisites: Encryption module configured, model key in constant
-- =============================================================
-- 1. Module
CREATE MODULE ITHelp;
-- 2. Domain Model
@Position(100, 100)
CREATE PERSISTENT ENTITY ITHelp."Ticket" (
"Subject": String(200) NOT NULL ERROR 'Subject is required',
"Description": String(unlimited),
"Category": Enumeration(ITHelp.Category),
"Status": Enumeration(ITHelp.TicketStatus),
"AssignedTo": String(200),
"Resolution": String(unlimited)
);
@Position(100, 300)
CREATE PERSISTENT ENTITY ITHelp."KBArticle" (
"Title": String(200),
"Content": String(unlimited),
"Category": Enumeration(ITHelp.Category),
"ViewCount": Integer DEFAULT 0
);
CREATE ENUMERATION ITHelp."Category" (
Network = 'Network',
Hardware = 'Hardware',
Software = 'Software',
Access = 'Access & Permissions',
Other = 'Other'
);
CREATE ENUMERATION ITHelp."TicketStatus" (
New = 'New',
InProgress = 'In Progress',
WaitingOnUser = 'Waiting on User',
Resolved = 'Resolved',
Closed = 'Closed'
);
-- 3. Model Key Constant (set via environment or Configuration_Overview page)
CREATE CONSTANT ITHelp."ModelKey" (
Type: String,
DefaultValue: ''
);
-- 4. Tool microflows — signatures become the tool JSON schemas
CREATE MICROFLOW ITHelp."Tool_SearchKB" ($Query: String)
RETURNS String
BEGIN
RETRIEVE $Articles FROM DATABASE ITHelp.KBArticle
WHERE contains(Title, $Query) OR contains(Content, $Query);
DECLARE $Result String = '';
LOOP $Article IN $Articles
BEGIN
SET $Result = $Result + '## ' + $Article/Title + '\n'
+ $Article/Content + '\n\n';
END LOOP;
RETURN if $Result = '' then 'No articles found for: ' + $Query else $Result;
END;
/
CREATE MICROFLOW ITHelp."Tool_CreateTicket" (
$Subject: String,
$Description: String,
$Category: ENUM ITHelp.Category
)
RETURNS String
BEGIN
$Ticket = CREATE ITHelp.Ticket (
Subject = $Subject,
Description = $Description,
Category = $Category,
Status = ITHelp.TicketStatus.New
);
COMMIT $Ticket;
RETURN 'Ticket ' + toString($Ticket/System.id) + ' created.';
END;
/
CREATE MICROFLOW ITHelp."Tool_GetTicketStatus" ($TicketId: String)
RETURNS String
BEGIN
RETRIEVE $Ticket FROM DATABASE ITHelp.Ticket WHERE System.id = $TicketId LIMIT 1;
IF $Ticket = empty THEN RETURN 'Ticket not found'; END IF;
RETURN 'Status: ' + getKey($Ticket/Status)
+ ', Assigned to: ' + $Ticket/AssignedTo;
END;
/
-- 5. Agent document — tools declared here, not at runtime
CREATE AGENT ITHelp."ITSupportAgent" (
UsageType: Chat,
Description: 'AI-powered first-line IT support',
SystemPrompt: 'You are an IT support agent for a corporate help desk.
Capabilities (use these tools):
1. Search the knowledge base for solutions to common problems
2. Create support tickets when issues need escalation
3. Check the status of existing tickets
Always try the knowledge base first before creating a ticket.
Be patient and ask clarifying questions when the issue is unclear.
For password resets and access requests, always create a ticket.'
)
{
TOOL SearchKB {
Microflow: ITHelp.Tool_SearchKB,
Description: 'Search the knowledge base for articles matching a query',
Access: VisibleForUser
}
TOOL CreateTicket {
Microflow: ITHelp.Tool_CreateTicket,
Description: 'Create a new support ticket with subject, description, and category',
Access: UserConfirmationRequired
}
TOOL GetTicketStatus {
Microflow: ITHelp.Tool_GetTicketStatus,
Description: 'Get current status of an existing support ticket by ID',
Access: VisibleForUser
}
};
-- 6. Chat action microflow — uniform pattern with Call Agent
CREATE MICROFLOW ITHelp."Chat_ITSupport" (
$ChatContext: ConversationalUI.ChatContext
)
RETURNS Boolean
BEGIN
$Request = CALL MICROFLOW ConversationalUI.ChatContext_Preprocessing(
ChatContext = $ChatContext
) ON ERROR ROLLBACK;
IF $Request = empty THEN RETURN false; END IF;
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'ITHelp.ITSupportAgent' LIMIT 1;
CALL AGENT WITH HISTORY $Agent REQUEST $Request INTO $Response
ON ERROR ROLLBACK;
IF $Response != empty AND $Response/GenAICommons.Response_Message != empty THEN
CALL MICROFLOW ConversationalUI.ChatContext_UpdateAssistantResponse(
ChatContext = $ChatContext,
MessageStatus = ConversationalUI.ENUM_MessageStatus.Success,
Response = $Response
) ON ERROR ROLLBACK;
RETURN true;
ELSE
RETURN false;
END IF;
END;
/
-- 7. Entry-point microflow uses "New Chat for Agent"
CREATE MICROFLOW ITHelp."ACT_StartHelpChat" ()
BEGIN
RETRIEVE $Agent FROM DATABASE AgentCommons.Agent
WHERE _QualifiedName = 'ITHelp.ITSupportAgent' LIMIT 1;
NEW CHAT FOR AGENT $Agent
ACTION MICROFLOW ITHelp.Chat_ITSupport
INTO $ChatContext
ON ERROR ROLLBACK;
SHOW PAGE ITHelp.HelpDesk($ChatContext = $ChatContext);
END;
/
-- 8. Pages
CREATE PAGE ITHelp."Home" (
Title: 'IT Help Desk',
Layout: Atlas_Core.Atlas_Default
) {
HEADER h1 { DYNAMICTEXT t (Caption: 'IT Help Desk') }
CONTAINER c {
ACTIONBUTTON startChat (
Caption: 'Start Chat with IT Support',
Action: MICROFLOW ITHelp.ACT_StartHelpChat()
)
}
};
/
CREATE PAGE ITHelp."HelpDesk" (
Title: 'IT Support Chat',
Layout: Atlas_Core.Atlas_Default
) {
HEADER h1 { DYNAMICTEXT t (Caption: 'IT Support') }
DATAVIEW chatDv (DataSource: CONTEXT ConversationalUI.ChatContext) {
SNIPPETCALL chat (Snippet: ConversationalUI.Snippet_Output_WithHistory)
}
};
/
CREATE PAGE ITHelp."TicketOverview" (
Title: 'Support Tickets',
Layout: Atlas_Core.Atlas_Default
) {
HEADER h1 { DYNAMICTEXT t (Caption: 'Support Tickets') }
DATAGRID ticketGrid (DataSource: DATABASE ITHelp.Ticket) {
COLUMN col1 (Attribute: Subject, Caption: 'Subject')
COLUMN col2 (Attribute: Category, Caption: 'Category')
COLUMN col3 (Attribute: Status, Caption: 'Status')
COLUMN col4 (Attribute: AssignedTo, Caption: 'Assigned To')
}
};
/
-- 9. Security
CREATE MODULE ROLE ITHelp."User";
CREATE MODULE ROLE ITHelp."Admin";
GRANT ITHelp.User ON ITHelp.Ticket (CREATE, READ *, WRITE (ITHelp.Ticket.Description));
GRANT ITHelp.User ON ITHelp.KBArticle (READ *);
GRANT ITHelp.Admin ON ITHelp.Ticket (CREATE, DELETE, READ *, WRITE *);
GRANT ITHelp.Admin ON ITHelp.KBArticle (CREATE, DELETE, READ *, WRITE *);
GRANT EXECUTE ON MICROFLOW ITHelp.ACT_StartHelpChat TO ITHelp.User;
GRANT EXECUTE ON MICROFLOW ITHelp.Chat_ITSupport TO ITHelp.User;
GRANT EXECUTE ON MICROFLOW ITHelp.Tool_SearchKB TO ITHelp.User;
GRANT EXECUTE ON MICROFLOW ITHelp.Tool_CreateTicket TO ITHelp.User;
GRANT EXECUTE ON MICROFLOW ITHelp.Tool_GetTicketStatus TO ITHelp.User;
GRANT VIEW ON PAGE ITHelp.Home TO ITHelp.User;
GRANT VIEW ON PAGE ITHelp.HelpDesk TO ITHelp.User;
GRANT VIEW ON PAGE ITHelp.TicketOverview TO ITHelp.User, ITHelp.Admin;
-- 10. After-startup microflow registration
ALTER SETTINGS MODEL AfterStartupMicroflow = AgentEditorCommons.ASU_AgentEditor;
-- (Add custom setup to a composite microflow if needed.)
-- 11. Navigation
CREATE OR REPLACE NAVIGATION Responsive_web
HOME PAGE ITHelp.Home FOR ITHelp.User
MENU (
ITEM 'Help Desk' PAGE ITHelp.Home,
ITEM 'Tickets' PAGE ITHelp.TicketOverview,
ITEM 'Agent Admin' PAGE AgentCommons.Agent_Overview
);| Capability | Without MDL Agent Support | With MDL Agent Support |
|---|---|---|
| Discover agents | Open Studio Pro, navigate to Agent Editor | SHOW AGENTS in CLI or script |
| Inspect agent prompts | Click through Agent Editor UI | DESCRIBE AGENT Module.Name |
| Create agents | Only via Studio Pro Agent Editor | CREATE AGENT in MDL scripts |
| Version control | Binary CustomBlobDocument diffs | Human-readable MDL diffs |
| AI-assisted development | AI cannot see or create agents | AI generates complete smart apps |
| Batch operations | Manual, one agent at a time | Script creates multiple agents |
| Code review | Cannot review agent changes in PR | MDL changes are reviewable text |
| Migration | Manual recreation in new project | Copy/paste MDL scripts |
| Documentation | Screenshots of Agent Editor | DESCRIBE AGENT produces docs |
| Testing | Manual testing in Studio Pro | Scriptable test cases with mxcli |
The combination of CREATE AGENT (document definition), tool microflows (business logic), MCP connections (external tools), knowledge bases (RAG), and ConversationalUI (chat interface) means an AI coding agent can scaffold an entire smart app from a natural-language description — creating all layers from domain model to navigation in a single MDL session.
-
CustomBlobDocument extensibility (answered): Mendix uses
CustomBlobDocumentas a general extension pattern. FourCustomDocumentTypevalues observed so far:agenteditor.agent,agenteditor.model,agenteditor.knowledgebase,agenteditor.consumedMCPService. The parser dispatches byCustomDocumentTyperather than hardcoding agent-specific logic. Future types (other extensions, other agent-editor documents) plug in naturally. -
Contents JSON schema for tools/KB/MCP (answered): The microflow-tool shape is confirmed:
toolType: "Microflow"with adocument: { qualifiedName, documentId }reference pointing to the microflow (sameDocRefshape as MCP tools). Tool microflows must return aStringand accept only primitive types andGenAICommons.Request/GenAICommons.Toolas input parameters. -
Separate document types for Model, Knowledge Base, and MCP Service (answered): Confirmed. Phase 4 of the implementation plan covers
CREATE MODEL,CREATE KNOWLEDGE BASE,CREATE CONSUMED MCP SERVICEwith schemas matching the observed BSON. -
CALL AGENTactivity BSON format: The proposedCALL AGENT WITH HISTORY/CALL AGENT WITHOUT HISTORY/NEW CHAT FOR AGENTMDL statements need to map to a Studio Pro microflow activity. Is this a dedicated activity type in BSON, or does Studio Pro render a genericJavaActionCallAction(pointing atAgentCommons.Agent_Call_WithHistory) as "Call Agent"? If it's the latter, MDL can emit a standard Java action call; if the former, we need to identify the new activity BSON$Type. Action: inspect a Studio Pro microflow that uses "Call Agent" to resolve. -
ASU_AgentEditor behavior:
AgentEditorCommons.ASU_AgentEditoris the after-startup microflow that syncs agent documents to runtimeAgentCommons.Agententities. DoesCREATE AGENTvia MDL need to trigger this sync, or does it happen automatically on next app startup? What happens if MDL creates an agent and the app is already running? -
Module placement (partially answered): The new documents in test3 live in a user-created
Agentsmodule (not inAgentEditorCommons), confirming that users can place agent-editor documents in their own modules. The older 4 agents inAgentEditorCommonsappear to be samples shipped with the marketplace module. -
Cross-document UUID stability: Agent documents reference model/KB/MCP documents by both
qualifiedNameANDdocumentId(UUID). When MDL creates a document, the generated UUID must be stable so that subsequentCREATE AGENTstatements can correctly fill thedocumentIdfield. If anALTERor re-create changes the UUID, all referring agents break. The writer must either (a) preserve existing UUIDs on update or (b) allow the agent'sdocumentIdfield to be left empty and resolved at app-startup time by qualified name. -
Portal-populated fields on Model/KB: Fields like
displayName,keyId,keyName,environment,resourceName,modelName,modelDisplayNameare populated by Studio Pro after the user clicks "Test Key". Should MDLCREATE MODELwrite them as empty strings (letting Studio Pro fill them on next open), preserve them if provided byDESCRIBEround-trip, or outright reject user-supplied values? Current proposal: accept-and-round-trip but document them as read-only. -
Non-Mendix-Cloud model providers (e.g., OpenRouter): The Agent Editor docs state that model documents require "a String constant that contains the key for a Text Generation resource... obtained in the Mendix Cloud GenAI Portal" — so the model document format is currently locked to Mendix Cloud GenAI. Meanwhile,
GenAICommons.DeployedModelis provider-agnostic (it's justDisplayName+Architecture+ aMicroflowpointer), and marketplace connectors exist for OpenAI, Amazon Bedrock, Google Gemini, and Mistral. This creates a split:- Users who want OpenAI-compatible endpoints like OpenRouter (including its free models:
google/gemini-flash-1.5-8b:free,mistralai/mistral-7b-instruct:free, etc.) cannot use the Agent Editor's model documents today. Workarounds: (a) reconfigure the OpenAI Connector's base URL to OpenRouter; (b) build a custom microflow-basedDeployedModel; (c) skip the Agent Editor and createAgentCommons.Agent/Versionentities at runtime instead. - Option (c) means losing the design-time benefits of agent documents (MDL support, version control in the project, LLM-friendly static configuration).
CREATE AGENTin MDL therefore won't help these users until Mendix opens the model document format to other providers. - Implications for this proposal: The proposed
CREATE MODELdocument (Phase 4) should not hard-codeArchitecture: 'MxCloud'. If/when Mendix supports third-party architectures in model documents, theCREATE MODELsyntax must acceptArchitecture: 'OpenAI' | 'OpenRouter' | 'Bedrock' | ...and a connector-specific configuration block. TheCREATE AGENTbody is already model-provider-agnostic (it references a model document by name, not by architecture), so no changes needed there. - Track this externally: Monitor Mendix release notes for the Agent Editor opening to additional providers. If that happens, the MDL grammar already has room for it — we'd just add more valid
Architecturevalues toCREATE MODEL.
- Users who want OpenAI-compatible endpoints like OpenRouter (including its free models:
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
Microflow-tool JSON shape in Contents.tools[] |
Low | Resolved | Confirmed: toolType: "Microflow", document: { qualifiedName, documentId } pointing to the microflow. Implementation can proceed. |
CALL AGENT activity is a new BSON $Type |
Medium | Medium | Inspect a Studio Pro microflow that uses "Call Agent" before implementing |
| Cross-document UUIDs become stale when documents are re-created | High | High | Preserve UUIDs on update; validate referring agents on CREATE/DROP of a referenced document |
| Contents JSON schema changes in future Mendix versions | Medium | Medium | Parse tolerantly (ignore unknown fields), version-gate new fields |
| CustomBlobDocument format changes | Low | High | Monitor Mendix release notes, BSON schema comparison |
| Studio Pro fails to open MDL-created documents | Medium | High | Test with mx check and Studio Pro after creation; compare BSON byte-for-byte with editor-created documents (Agents.MyFirstModel etc.) |
| Portal-populated fields overwritten by MDL round-trip | Medium | Medium | On CREATE MODEL / CREATE KNOWLEDGE BASE, preserve any existing Portal fields if the document already exists; write empty strings only on fresh creates |
| Prerequisites (Encryption, ASU_AgentEditor) not set up before CREATE AGENT | Medium | Medium | MDL CREATE AGENT should warn/pre-check that prerequisites are configured |
| Agent document + matching Model/KB/MCP documents out of sync | Medium | Medium | mxcli check should validate cross-document references when --references is passed |
| Users want third-party LLM providers (OpenRouter, custom OpenAI-compatible) but Agent Editor model documents are Mendix-Cloud-only | High | Low (out of scope) | Document the workarounds (reconfigure OpenAI connector, custom microflow DeployedModel, skip agent documents); keep CREATE MODEL syntax open to future Provider values |
- Test project:
mx-test-projects/test3-app/test3.mpr(Mendix 11.9.0) - Older agent documents (AgentEditorCommons module):
InformationExtractorAgent,ProductDescription,SummarizationAgent,TranslationAgent—Excluded: true, no model reference, no tools/KB - New Agent Editor sample documents (Agents module):
Agents.Agent007— fully populated agent with model, MCP tool, KB tool (mprcontents/e0/72/e072318a-...mxunit)Agents.MyFirstModel— Model document, providerMxCloudGenAI(mprcontents/3a/dd/3addaaa1-...mxunit)Agents.Knowledge_base— Knowledge Base document (mprcontents/cc/cc/cccc0b5b-...mxunit)Agents.Consumed_MCP_service— Consumed MCP Service document (mprcontents/47/c9/47c9987a-...mxunit)
- Agent Editor extension manifest:
.mendix-cache/modules/agenteditor.mxmodule/extensions/agent-editor/manifest.json - AgentCommons module: Marketplace v3.1.0 (31 entities, 226 microflows)
- AgentEditorCommons module: Marketplace v1.0.0 (9 entities, 32 microflows, including
ASU_AgentEditor) - MCPClient module: Marketplace v3.0.1 (20 entities, 35 microflows)
- GenAICommons module: Marketplace v6.1.0 (34 entities, 112 microflows)
- ConversationalUI module: Marketplace v6.1.0 (17 entities, 152 microflows)
- Mendix Agent Editor documentation