From 1365ba4380f5f10ed735e432504e84ba9ea84675 Mon Sep 17 00:00:00 2001 From: Ako Date: Mon, 9 Nov 2026 21:44:14 +0000 Subject: [PATCH 1/3] style: gofmt splice_handler_test.go Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011oK82qLAJChCDrvVEDoScz --- mdl/backend/mfmutator/splice_handler_test.go | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mdl/backend/mfmutator/splice_handler_test.go b/mdl/backend/mfmutator/splice_handler_test.go index 7e4eb00991..4a6ff434ab 100644 --- a/mdl/backend/mfmutator/splice_handler_test.go +++ b/mdl/backend/mfmutator/splice_handler_test.go @@ -45,18 +45,18 @@ func handlerFlow() ([]bson.D, []bson.D) { a := withEH(obj("a", "Microflows$ActionActivity", 250, 200), microflows.ErrorHandlingTypeCustomWithoutRollback) dSet(a, "Size", "160;60") // resized in Studio Pro: kept return []bson.D{ - obj("start", "Microflows$StartEvent", 100, 200), - a, - obj("h", "Microflows$ActionActivity", 250, 330), - obj("m", "Microflows$ExclusiveMerge", 400, 200), - obj("end", "Microflows$EndEvent", 550, 200), - }, []bson.D{ - flow("f1", "start", "a", 1, 3, false), - flow("f2", "a", "m", 1, 3, false), - flow("err", "a", "h", 2, 0, true), - flow("fh", "h", "m", 1, 2, false), - flow("f3", "m", "end", 1, 3, false), - } + obj("start", "Microflows$StartEvent", 100, 200), + a, + obj("h", "Microflows$ActionActivity", 250, 330), + obj("m", "Microflows$ExclusiveMerge", 400, 200), + obj("end", "Microflows$EndEvent", 550, 200), + }, []bson.D{ + flow("f1", "start", "a", 1, 3, false), + flow("f2", "a", "m", 1, 3, false), + flow("err", "a", "h", 2, 0, true), + flow("fh", "h", "m", 1, 2, false), + flow("f3", "m", "end", 1, 3, false), + } } func TestSplice_ReplaceKeepsTheErrorHandler(t *testing.T) { From f0f5272f51e79c0bb0c85f014ff60559a5889889 Mon Sep 17 00:00:00 2001 From: Ako Date: Mon, 9 Nov 2026 21:44:14 +0000 Subject: [PATCH 2/3] docs(changelog): release v0.31.0 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011oK82qLAJChCDrvVEDoScz --- CHANGELOG.md | 30 ++++++++++++ docs-site/src/SUMMARY.md | 1 + docs-site/src/blog/index.md | 1 + docs-site/src/blog/v0.31.0-release.md | 70 +++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 docs-site/src/blog/v0.31.0-release.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4272a5bc47..aa0b674800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [0.31.0] - 2026-11-09 + +Headline: **Publish your app's microflows as an MCP server, and keep secrets off the command line.** `create published mcp service` writes the MCP Server document Agent Editor starts at runtime, and agents gain tool approval, display text and streaming. The secret `run` flags and `--constant` take `@env:VAR` and `@file:path`, and the `brain` can be started before the project and edited in place. Alongside them come a long run of fixes that make `check --references` catch more of what the build would refuse, and `mxcli test --local` runs that start clean every time. + ### Added - **`create published mcp service` publishes the app's microflows as an MCP server** (ako/mxcli#2020) — the model configuration of MCP Server 5.x: an Agent Editor 2.6 `publishedMCPService` document that AgentEditorCommons starts at startup through `MCPServer.CreateMCPServer` and `AddTool`. `create [or modify]`, `alter … set / add tool / drop tool`, `describe`, `list published mcp services` and `drop`, with `tool name ( Microflow: …, Description: …, Title: …, Schema: … )` blocks and the OAuth settings (`AuthenticationEnabled`, `AuthenticationMicroflow`, `AuthorizationServerUrl`, `Scopes`). The server is served at `//mcp`; `ServerName`, which Studio Pro stores but its editor does not show, defaults to the document name and is kept when omitted on `create or modify`. A rewrite keeps the stored layout, each tool's `uuid` and every key mxcli does not model; `describe` → `exec` of the Studio Pro 11.16 sample writes nothing. `check` reports what `CreateMCPServer` would refuse at startup (MDL-MCPSERVER01): a reserved or sub-path `EndpointPath`, `Scopes` without an authorization server, an authorization server without an authentication microflow, a tool without a microflow. @@ -20,6 +24,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **`--constant Module.Name=@env:VAR` and `=@file:path` keep a secret out of `ps` and shell history** (mendixlabs/mxcli#1765) — `mxcli run` and `mxcli test --local` read the value from an environment variable or from a file, so a token no longer has to be typed into the command line, which shows it in `ps` for as long as the app runs. An unset or empty variable and an empty file are errors, not an empty value; the owner-only check on a file is made on Unix and not on Windows; a relative `@file:` path is stored as an absolute one, so `run restart` works from another directory; a file readable by its group or by others is refused (`chmod 600`); one trailing newline is dropped. A value that really starts with `@` is written `@@…`. The run state keeps the `@env:`/`@file:` form, never the value. - **The secret `run` flags take `@env:VAR` and `@file:path`, so a password stays off the command line and `run restart` still works** (supersedes mendixlabs/mxcli#1761) — `--db-password`, `--db-admin-password`, `--screenshot-password`, `--hub-secret` and `--debug-pass` resolve a reference the way `--constant` does: an unset or empty variable, an empty file and a file its group or others can read are refused, naming the flag and the source, never the value. The reference, not the secret, is what `ps` shows and what `.mxcli/run-state.json` stores, so a run started with one can be `--detach`ed and `run restart`ed; each start resolves it again in its own environment, and a relative `@file:` path is stored as an absolute one. mendixlabs/mxcli#1761 proposed reading the password from stdin, which cannot be read again and so ruled out `--detach` and `run restart`; its fix for a flag value that looks like `-p` being dropped from the detached command is included. +- **`brain` can start before the project, and entries can be edited, moved, sourced and listed** (ako/mxcli#1949, ako/mxcli#1950, ako/mxcli#1951, ako/mxcli#1952) — the brain's store now lives as long as the project does, from before it exists, and changes in place: + - `-C/--dir ` names the brain's directory on every `brain` command, so you can fill a brain before its `.mpr` exists, and `mxcli new` adopts a target directory that holds only a brain. `brain init` no longer takes an argument it used to ignore. + - `brain edit [] [-a @Anchor]…` rewrites a staged or committed entry in place and keeps its id; `brain move --slice ` moves a requirement to another slice. + - `capture --source [#]` and `edit --source` record which document an entry came from, and where in it. The path is stored relative to the project, so it works on other machines. + - `brain list [--slice] [--kind] [--open] [--json]` returns the committed store as data instead of rendered markdown, and reports malformed blocks rather than dropping them. +- **`alter page set` reaches a Data grid 2 column's attribute and a text template's parameters** (ako/mxcli#1644, ako/mxcli#1966) — `set (Attribute: …) on column(…)` binds the column as `create` does (bare name, `Module.Entity.Attr` or an `Assoc/…/Attr` path). `set (CaptionParams: …)` on a button and `set (ContentParams: …)` on a dynamic text replace the template's parameters. Before this, you had to replace the whole widget. +- **A queued call's retry policy in MDL** (ako/mxcli#1931) — `in queue … retry fixed|exponential (…)` writes `Queues$QueueFixedRetry` / `QueueExponentialRetry`; `describe` prints a stored retry so it round-trips, and `check` refuses `Retries: 0` (CE7044). A flow with a queued retry could not be rewritten before; a rewrite that would drop the stored retry is now refused. + ### Changed - **A secret `run` flag value starting with `@` is refused unless it is `@env:`, `@file:` or `@@`** — `--db-password`, `--db-admin-password`, `--screenshot-password`, `--hub-secret` and `--debug-pass` follow the `--constant` rule; write `@@…` for a password that really starts with `@`. @@ -80,6 +92,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - **`mxcli test --watch` no longer shows a green run for a rebuild that failed** — when the rebuild after an edit failed (or the changed tests could not be parsed or injected), the loop printed the error and then ran the suite again at once, against the model from before the edit, so a broken microflow could pass. The round is now reported as an error row naming the step and the reason, in the terminal and in the JUnit file, the suite does not run, and the loop waits for the next change. The change baseline is taken when the change is detected, so a save made while the rebuild is failing starts a new round, and the "run #N" counter only advances for a round that ran. Ctrl-C after a failed change ends the watch with the red result, so the command exits non-zero. (mendixlabs/mxcli#1766) - **A copied app folder no longer inherits the original's run state, and run state no longer stores secrets** (mendixlabs/mxcli#1764) — `.mxcli/run-state.json`, `run-local.json` and `test-endpoint.json` record the project they were written for, but nothing compared it, so in a copy of a running app `mxcli run` refused to start and `mxcli run stop` signalled the original's process, and `mxcli test --attach` talked to the original's app. All three are now ignored when they name another project (symlinks resolved, case-folded on macOS and Windows); `run status` and `run stop` say that they ignored one. `--constant` values, the password flags and the `DatabasePassword` and `MicroflowConstants` runtime settings are no longer stored in `run-state.json`, and the dev-loop handshake no longer holds the resolved constants. The handshake file `run-local.json` still holds the database password (re-applying constants needs it); it is written owner-only (0600). **Behaviour change:** a run started with `--db-password` (or another inline secret) cannot be `run restart`ed; `run restart` says so and names the alternatives (the `PGPASSWORD` environment variable, or re-running `mxcli run` with the password) instead of starting the app with a placeholder. A run recorded from a devcontainer or WSL path is treated as another folder by the host side, so its state is ignored there. - **`mxcli test` failure line shows the actual value of an enumeration compare** (mendixlabs/mxcli#1763) — `@expect $Paid/Status = Procurement.InvoiceStatus.PAID` failed with only `expected …`, because the actual value was reported only when the operand type was known and an attribute path next to an enumeration value never was. An enumeration value on one side now pins the other, so the line reads `expected $Paid/Status = Procurement.InvoiceStatus.PAID, actual: APPROVED` (the enumeration key, via `toString()`). +- **`mxcli test --local` no longer carries state or noise between runs** — a run of the suite now reports only what the suite did: + - Each run starts on an empty test database, on hsqldb and on PostgreSQL (where only the scratch `_test` database is cleared, never the dev loop's) (ako/mxcli#1853). + - When `mxbuild --serve` stops answering, the boot restarts it and builds once more instead of failing with `context deadline exceeded`; a test that does not build has its errors repeated above the summary rather than lost among the "not run" rows (ako/mxcli#1834). + - No more `Module 'MxTest' already exists` on a clean project (ako/mxcli#1856), and no MDL-V1-SLASH / MDL-DEPR001 per test on a headerless suite (ako/mxcli#1855). +- **`check --references` catches more of what `exec` or the build would refuse**: + - An image reference is matched case-sensitively, as Mendix does (a case difference is CE1613), and the hint names the stored spelling. + - `drop`, `rename` and `move` of a document that does not exist, and `alter enumeration` / `alter microflow` / `alter nanoflow` on one, are reported with the module's near names (ako/mxcli#1821). + - A `show page` passing a parameter that an earlier `alter page … add parameters` declared is accepted (ako/mxcli#1940). + - `alter page … set (DateFormat: Custom, CustomDateFormat: …)` is accepted in either order (ako/mxcli#1965). +- **Page widgets that `mx check` refused** — a text template bound to an unset optional data source is stored as null, as Studio Pro does, and no longer CE0463 (ako/mxcli#1967). A `snippetcall` passing a data view's object names the widget instead of a page parameter the page lacks (CE0115) (ako/mxcli#1968). On a 9.x project, editing a pluggable widget keeps its Database source type instead of turning it into XPath (ako/mxcli#1208). +- **Association paths are checked from the context entity** (ako/mxcli#834) — `database from $ctx/A1/A2` resolves multi-hop paths the way an association source does, and a hop that does not start at the context entity is refused instead of written as a step that cannot be followed. +- **An import mapping rooted at a JSON array returns one object** (ako/mxcli#1938) — the import activity was written as returning a list, which mx check reported as CE0243. +- **External entities keep their OData capabilities when `$metadata` aliases a vocabulary** (ako/mxcli#1977) — aliased Insert/Update/DeleteRestrictions and Core terms were skipped, so `create external entities` imported entities as not creatable or deletable (CE6630). +- **Workflows on Mendix 9** — the user-task claim MDL-WORKFLOW10 suggests uses `System.WorkflowUserTask_Assignee` on Mendix 9 and `_Assignees` from 10.0 (ako/mxcli#1810). An annotation is written as `Workflows$Annotation`; the old storage name made Studio Pro throw `TypeCacheUnknownTypeException` (ako/mxcli#1784). +- **`describe` output that did not re-execute** — a text holding `$$` (such as a JSON sample) is written as a string literal (ako/mxcli#1003). A message definition member is printed by the attribute it references after a rename, and `check` refuses a member naming an attribute the entity lacks (ako/mxcli#1935). +- **`marketplace diff` and `update` no longer report every Java action of a protected module as changed** — the reference project now holds the package's Java and JavaScript sources, as `mx module-import` writes them. +- **Lint MPR008 names the loop when a loop's box causes the overlap** (ako/mxcli#1839) — it gives the gap needed instead of asking for `@position` annotations that are already there. +- **`mxcli syntax` documents every built-in expression function and every `drop` statement** (ako/mxcli#1866, ako/mxcli#1911) — the function list is rendered from the type checker's own signature table, so the reference and `check` agree. ## [0.30.0] - 2026-11-02 diff --git a/docs-site/src/SUMMARY.md b/docs-site/src/SUMMARY.md index 43f78e7c9c..1390b86932 100644 --- a/docs-site/src/SUMMARY.md +++ b/docs-site/src/SUMMARY.md @@ -377,6 +377,7 @@ # Blog - [Blog](blog/index.md) + - [Release: mxcli v0.31.0](blog/v0.31.0-release.md) - [Release: mxcli v0.30.0](blog/v0.30.0-release.md) - [Release: mxcli v0.29.0](blog/v0.29.0-release.md) - [Release: mxcli v0.28.0](blog/v0.28.0-release.md) diff --git a/docs-site/src/blog/index.md b/docs-site/src/blog/index.md index 5fa10e1bc8..bff4bd6f91 100644 --- a/docs-site/src/blog/index.md +++ b/docs-site/src/blog/index.md @@ -6,6 +6,7 @@ News, release announcements, and demos from the mxcli team. | Date | Post | |------|------| +| November 9, 2026 | [Release: mxcli v0.31.0](v0.31.0-release.md) | | November 2, 2026 | [Release: mxcli v0.30.0](v0.30.0-release.md) | | October 30, 2026 | [Release: mxcli v0.29.0](v0.29.0-release.md) | | October 23, 2026 | [Release: mxcli v0.28.0](v0.28.0-release.md) | diff --git a/docs-site/src/blog/v0.31.0-release.md b/docs-site/src/blog/v0.31.0-release.md new file mode 100644 index 0000000000..08599baa37 --- /dev/null +++ b/docs-site/src/blog/v0.31.0-release.md @@ -0,0 +1,70 @@ +# Release: mxcli v0.31.0 + +*November 9, 2026* + +v0.31.0 is about **publishing your app's microflows as an MCP server, and keeping secrets off the command line**. + +## What's new + +**Publish microflows as an MCP server.** `create published mcp service` writes the MCP Server 5.x document that Agent Editor starts when the app starts. Each `tool` block exposes one microflow to external agents: + +```sql +create published mcp service AgentTest.CustomerMCP ( + EndpointPath: 'customers', + Version: '1.0.0', + ProtocolVersion: v2025_03_26 +) { + tool count_customers ( + Microflow: AgentTest.MCPTool_CountCustomers, + Description: 'Count the customers whose name starts with NamePrefix.', + Title: 'Count customers' + ) +}; +``` + +The server is served at `/customers/mcp`. `alter … add tool / drop tool`, `describe`, `list published mcp services` and `drop` complete the set, and the OAuth settings are there too. `check` reports what the server would refuse at startup: a reserved endpoint path, scopes without an authorization server, or a tool without a microflow. For MCP Server 3.x–4.x, the new `mcp-server` skill builds the same server from startup microflows. + +**Agents: tool approval and streaming.** Agent Editor's human-in-the-loop settings now have MDL. A tool can be hidden, shown, or require the user's confirmation (`UserAccessApproval`), with its own display title and description. Backend and UI streaming are switches on the agent. `check` also warns about a model or knowledge base `Provider` that Agent Editor would fail to import at startup. + +**Secrets stay off the command line.** The `run` password flags (`--db-password`, `--db-admin-password`, `--screenshot-password`, `--hub-secret`, `--debug-pass`) and `--constant` accept `@env:VAR` and `@file:path`: + +```bash +mxcli run --local -p app.mpr \ + --db-password @env:PGPASSWORD \ + --constant MyModule.ApiKey=@file:$HOME/.secrets/api-key +``` + +`ps` shows the reference, and the run state stores the reference, not the value. A detached run can still be restarted with `run restart`, because each start reads the secret again. A file that its group or others can read is refused, so `chmod 600` it first. + +**A brain from day one.** A `brain` can now be started before its project exists (`-C `), and `mxcli new` adopts it. Entries can be edited and moved without changing their id, can record the document and lines they came from (`--source`), and `brain list --json` returns the committed store as data for tools to read. + +**More of the model from MDL.** This release adds: + +- `send external object` and `delete external object`, so a flow that holds them survives a describe-and-recreate. +- *Associations to include* on a `call external action` argument. +- A module's version and module type. +- A queued call's retry policy. +- `alter page set` for a Data grid 2 column's attribute and for a text template's parameters. + +**`check` catches more before the build.** A case difference in an image reference, a `drop` or `alter` of a document that doesn't exist, and many smaller cases that `exec` or mxbuild would refuse are now reported by `check --references`. `mxcli test --local` starts every run on an empty test database, recovers when `mxbuild --serve` stops answering, and repeats a failed build's errors above the summary line. + +## Upgrading + +Download the `v0.31.0` binary for your platform from the [release page](https://github.com/mendixlabs/mxcli/releases/tag/v0.31.0): + +```bash +curl -fsSL -o mxcli \ + https://github.com/mendixlabs/mxcli/releases/download/v0.31.0/mxcli-linux-amd64 +chmod +x mxcli && sudo mv mxcli /usr/local/bin/ +``` + +Then refresh a project's skills and commands with `mxcli init --sync-skills`. + +Behaviour changes worth knowing: + +- A `--constant` value or a secret `run` flag that starts with `@` is refused unless it is `@env:`, `@file:` or `@@`. Write `@@…` for a value that really starts with `@`. +- Run state (`.mxcli/run-state.json` and friends) no longer stores secrets, and an app folder you copy no longer inherits the original's run state. +- `check --references` matches image names case-sensitively, as Mendix does. A script that relied on the old case-insensitive match now gets an error that names the stored spelling. +- The catalog schema moves to version 34, so a cached catalog is rebuilt once. + +Full changelog: [CHANGELOG.md](https://github.com/mendixlabs/mxcli/blob/main/CHANGELOG.md) From ca76e2433a71aebaf2e6f570f9e95ab642c719c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 06:26:20 +0000 Subject: [PATCH 3/3] style: gofmt splice_handler_test.go with the pinned toolchain's gofmt Upstream's 1365ba438 reformatted it with a gofmt other than go1.27.3's, the toolchain go.mod pins and make fmt-check runs, so the merged tree failed fmt-check (mendixlabs/mxcli#1793). This restores the fork's toolchain-formatted version; make fmt-check passes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AeAyg3qx2my26Q8cvGyW5z --- mdl/backend/mfmutator/splice_handler_test.go | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mdl/backend/mfmutator/splice_handler_test.go b/mdl/backend/mfmutator/splice_handler_test.go index 4a6ff434ab..7e4eb00991 100644 --- a/mdl/backend/mfmutator/splice_handler_test.go +++ b/mdl/backend/mfmutator/splice_handler_test.go @@ -45,18 +45,18 @@ func handlerFlow() ([]bson.D, []bson.D) { a := withEH(obj("a", "Microflows$ActionActivity", 250, 200), microflows.ErrorHandlingTypeCustomWithoutRollback) dSet(a, "Size", "160;60") // resized in Studio Pro: kept return []bson.D{ - obj("start", "Microflows$StartEvent", 100, 200), - a, - obj("h", "Microflows$ActionActivity", 250, 330), - obj("m", "Microflows$ExclusiveMerge", 400, 200), - obj("end", "Microflows$EndEvent", 550, 200), - }, []bson.D{ - flow("f1", "start", "a", 1, 3, false), - flow("f2", "a", "m", 1, 3, false), - flow("err", "a", "h", 2, 0, true), - flow("fh", "h", "m", 1, 2, false), - flow("f3", "m", "end", 1, 3, false), - } + obj("start", "Microflows$StartEvent", 100, 200), + a, + obj("h", "Microflows$ActionActivity", 250, 330), + obj("m", "Microflows$ExclusiveMerge", 400, 200), + obj("end", "Microflows$EndEvent", 550, 200), + }, []bson.D{ + flow("f1", "start", "a", 1, 3, false), + flow("f2", "a", "m", 1, 3, false), + flow("err", "a", "h", 2, 0, true), + flow("fh", "h", "m", 1, 2, false), + flow("f3", "m", "end", 1, 3, false), + } } func TestSplice_ReplaceKeepsTheErrorHandler(t *testing.T) {