Branch: refs/heads/experimental Home: https://github.com/xwiki-contrib/ai-llm Commit: a425ff04a10079d61a08bc7bbb4ae0dea303d56a https://github.com/xwiki-contrib/ai-llm/commit/a425ff04a10079d61a08bc7bbb4ae... Author: Paul Pantiru <[email protected]> Date: 2026-07-11 (Sat, 11 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPDocumentAccess.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/resources/META-INF/components.txt M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPDocumentAccessTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentToolTest.java Log Message: ----------- LLMAI-154: Add the delete_document MCP tool * New write tool (off by default) deleting a document to the recycle bin: base_version is REQUIRED as proof-of-recent-read, a bin-less wiki is refused outright (the tool never deletes permanently), and rights are checked twice - Right.DELETE through the access door, then api.Document.delete()'s own re-check * No recursive delete: a space home page with children is refused with the platform delete UI's own child count (its query shape, escaped-literal binds, hidden/translations included); the lookup deliberately bypasses the row-query door so unviewable children still block the delete, and fails closed on query errors. The WebPreferences-only-child case proceeds and reports the remainder (we never cascade, diverging from the UI's delete-both default) * Sensitive-reference denylist (security audit): WebPreferences, XWikiPreferences, main-wiki XWikiServer* descriptors and the MCP config document are refused with a delete-it-manually message and URL - a per-tool capability limit where the UI's human confirmation has no MCP equivalent; deleting the MCP config doc would reset the endpoint to enabled defaults * MCPWriteSupport gains a right-parameterized resolveFor (edit tools unchanged) and the access door's denial message now says "delete" for DELETE denials; TOCTOU between the guards and the delete is documented as accepted risk (platform's own delete action carries the same race with no version check at all) * 20 behavioral tests for the tool + the two access-door denial tests now verify the space filter is never consulted after a rights deny Commit: f95c704f65bb0412a7b42451b19b6a76b25db9f0 https://github.com/xwiki-contrib/ai-llm/commit/f95c704f65bb0412a7b42451b19b6... Author: Paul Pantiru <[email protected]> Date: 2026-07-11 (Sat, 11 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPRowQuery.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/access/MCPRowQuery.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetSchemaTool.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaText.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/resources/META-INF/components.txt M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPRowQueryTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetSchemaToolTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaTextTest.java Log Message: ----------- LLMAI-155: Add the get_schema MCP tool * First tool of the structured-data family (new man category): no-arg lists the wiki's instantiated classes with instance counts, with a class reference it renders the field definitions in a precise field-per-line grammar (name: Type(detail) "Pretty Name" modifiers [validation: regexp]) designed to double as write_schema's future input; the man page documents the grammar for agent parsing * Catalog counts ride an HQL group-by through the row-query door (translation guard, no hidden clause - class docs are conventionally hidden) and each row's CLASS DOCUMENT is authorized (space filter + view) before listing; counts stay deliberate upper bounds with an honesty note, plus a fetch-ceiling incompleteness note that survives the output budget cut * The PropertyClass-family fan-out lives in a package-private MCPSchemaText renderer (tool 17/20, renderer 10/20, no suppressions): type dispatch on getClassType() strings with instanceof-guarded casts; DBList/ComputedField script bodies are never read, Password fields carry a masked-in-results note, and every wiki-authored fragment is line-break-stripped and clamped (per-fragment cap, StaticList values joined at value boundaries, whole response under the module's output budget with a truncation notice) * MCPRowQuery gains a Map-bind overload (single-bind delegates via singletonMap to preserve null-value behavior); both overloads now document the scalar row shape of single-column selects * 24 tool + 10 renderer tests (exact-text grammar pins over real oldcore property classes, forgery line-count pin, budget/clamp pins, load-failure error hygiene, view-denied catalog drop); module at 609 Commit: 08b4aff45fcc5151ad17a4ff95137641f67b83ae https://github.com/xwiki-contrib/ai-llm/commit/08b4aff45fcc5151ad17a4ff95137... Author: Paul Pantiru <[email protected]> Date: 2026-07-17 (Fri, 17 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPRowQuery.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/access/MCPRowQuery.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectQuerySupport.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryObjectsTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaText.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPTextGuards.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/resources/META-INF/components.txt M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPRowQueryTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectQuerySupportTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryObjectsToolTest.java Log Message: ----------- LLMAI-156: Add the query_objects MCP tool * Find XObjects by class + declarative field filters ("field op value", ops = != > < contains, AND-ed, capped at 10) compiled into bound HQL: every agent value is a bind (contains rides the platform's escaped QueryParameter chain via a new MCPRowQuery.Contains record); the only composed text is Hibernate entity names and op tokens from closed maps plus generated aliases - field names, class name and document scope are all bind values * Filter values are validated AND coerced by the class's own PropertyClass.fromString (17.4 returns null on garbage - treated as a teaching refusal naming the expected format; booleans get explicit vocabulary checks since fromString never fails them; bare ISO dates accepted as UTC midnight); refused as filter/sort targets: unknown fields (listing valid ones), Password (value-probing oracle), computed fields (no stored rows), list-typed fields, large-text sort (SELECT DISTINCT over CLOB breaks on some DBMS) * Totals are AUTHORIZED-ONLY, deliberately diverging from the search tools' upper bounds (security audit High): a DB-side count under attacker-chosen value predicates is a value-extraction oracle over rights-denied documents (differential counting binary-searches exact field values), so the tool scans up to the 2000-row door ceiling, authorizes every row, pages client-side and reports the exact viewable total ("N+" at the ceiling); a dedicated test proves denied matches leave no countable trace * Results render (object NUMBER, vVERSION) headers - the exact addressing write_object will need - with values read only via safeget/getValue (never the display pipeline), Password masked unread, hidden documents included by design (documented, get_schema rationale), every wiki-authored fragment stripped and clamped via the new shared MCPTextGuards, whole response under the output budget * 28 tool + 29 support tests over the real door and real oldcore property classes (statement/bind pins, escaping chain end-to-end, coercion matrix, refusal matrix, paging/ceiling/budget); module 665 Commit: ebf80fa6ba279883f3dc68654934990a43243106 https://github.com/xwiki-contrib/ai-llm/commit/ebf80fa6ba279883f3dc686549349... Author: Paul Pantiru <[email protected]> Date: 2026-07-17 (Fri, 17 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-api/src/main/java/org/xwiki/contrib/llm/mcp/MCPToolSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-api/src/test/java/org/xwiki/contrib/llm/mcp/MCPToolSupportTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentTool.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectQuerySupport.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectWriteSupport.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/resources/META-INF/components.txt A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteObjectToolTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectWriteSupportTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectToolTest.java Log Message: ----------- LLMAI-157, LLMAI-158: Add the write_object and delete_object MCP tools * write_object creates or updates one XObject's fields, delete_object removes one - both off by default, addressed by class + object NUMBER (the query_objects header addressing), sharing an MCPObjectWriteSupport seam that owns the whole BaseObject/BaseClass/ PropertyClass family so the tools stay under fan-out * Field values are validated and coerced by the class's own PropertyClass.fromString wired exactly as BaseClass.fromMap does (fromString -> setObject -> safeput, storing the very instance validation inspected); the read side's vocabulary is reused, not reimplemented - unknown field lists the valid ones, Password and computed fields refused, fromString-null is a teaching refusal naming the expected format, booleans get an explicit vocabulary check; both tools mutate xdoc.clone() and save through api.Document with [AI] comments so the acting user is recorded content author * Sensitive-class denylist (security audit High+Medium): objects of XWikiRights/XWikiGlobalRights/XWikiServerClass/MCPServerConfigClass/ XWikiGroups/XWikiUsers are refused for write AND delete - the document denylist cannot protect these because a page's rights live as XWikiRights objects ON the page and group membership is an XWikiGroups object ON the group document (adding yourself to the admin group = indirect admin grant); the sensitive-DOCUMENT denylist is now shared from MCPWriteSupport (delete_document delegates, behavior byte-identical). Password fields refused as write targets * write_object mirrors write_document's base_version discipline (required on existing, tolerant on create); delete_object requires base_version always (destructive); object-only saves version normally and the compare URL renders the object diff, so every change is one click from human review * mcp-api MCPToolSupport gains a string-map param type (fields) plus requiredStringMap/requiredInteger builders and requireStringMap/ requireInteger accessors so required params advertise honestly * 28 write + 14 delete + 3 support + 8 api tests (real-store create/ update/delete incl. object-number holes, the full refusal matrix, sensitive-class/document refusals saving nothing, date parse both ways, clone-before-mutate on save failure); mcp-server 714, mcp-api 49 Commit: 6fb0aaed7aa05dbec27deb2c7a71065883ade2b7 https://github.com/xwiki-contrib/ai-llm/commit/6fb0aaed7aa05dbec27deb2c7a710... Author: Paul Pantiru <[email protected]> Date: 2026-07-17 (Fri, 17 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectQuerySupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectQuerySupportTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectWriteSupportTest.java Log Message: ----------- LLMAI-156, LLMAI-157, LLMAI-158: Fix two live-only object-tool bugs on platform 17.10 Both surfaced in the first live smoke on a 17.10.8 instance (the extension builds against 17.4.0-8); both were invisible to the unit suite - one a fake-store gap, one build-vs-run version skew. * delete_object removal now persists. It cloned the loaded document, removeXObject'd the clone, then saved via a fresh api.Document - whose own lazy getDoc() clone drops the xObjectsToRemove list that cloneInternal never copies, so the store issued no row DELETE and the object survived while the version still bumped (creates/updates persist because they live in the object vector, which IS deep-copied). Now removes THROUGH the api wrapper (getObject(EntityReference,int) + removeObject on the wrapper's own internal clone, then save), the platform's own REST idiom - the mutated instance is the saved instance. MCPObjectWriteSupport.removeObject becomes read-only requireObjectExists; the tool guards presence, then removes via the already-authorized classRef (not a re-resolved name, which would hit the absent 'explicit' resolver and NPE). MockitoOldcore cannot model the clone-drops-toRemove behavior (verified: the old code passes the unit test), so this fix's sole gate was the live re-smoke - confirmed: delete then re-read shows the row gone. * Bad Number/Date values no longer leak/mislead. PropertyClass. fromString returns null on garbage on 17.4 (our validation signal) but THROWS XWikiException on 17.10 (XWIKI-20910); query_objects leaked the raw exception to the wire (-32603) and write_object reported a misleading save-failure. New shared parseOrNull wraps fromString in catch(Exception) - XWikiException can't be caught narrowly (not declared thrown on the 17.4 build target, so it won't compile), and Exception covers both the 17.4 null and the 17.10 throw - routed from coerce/dateBind/validateAndCoerce so both tools give the same teaching refusal on either platform. Throwing-stub tests pin the catch (the 17.4 test JVM can't reproduce the real throw). mcp-server 716 tests, 0 checkstyle; live-validated on 17.10.8 incl. the new ComputedField/DBList field types (schema render, filter refusals, computed output masked, no script/SQL echo). Commit: faeaae81d56328eaed2ad9c1e60d20d81ec940bb https://github.com/xwiki-contrib/ai-llm/commit/faeaae81d56328eaed2ad9c1e60d2... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaText.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaWriteSupport.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSchemaTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/resources/META-INF/components.txt A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaWriteSupportTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSchemaToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-ui/src/main/resources/AI/MCP/Code/MCPServerConfigSheet.xml Log Message: ----------- LLMAI-159: Add the write_schema MCP tool * write_schema creates or modifies ONE field of an XClass - operation add_field / modify_field / remove_field, off by default. The target document IS the class; the first add_field on a fieldless/new document creates the class (a zero-field class serialises to empty class-XML and is invisible, so there is no separate "create class" verb). Types are added through BaseClass.add*Field (ComputedField constructed by hand); per-type attributes (size, numberType, values, sql, script, multiSelect, contentType...) are set generically as PropertyClass meta-properties via setIntValue/setStringValue/setLargeStringValue behind a per-type allowed table, so the whole BaseClass/PropertyClass fan-out lives in a new package-private MCPSchemaWriteSupport seam and the tool stays under cap * modify_field changes pretty-name + same-type attributes only; a TYPE change is remove_field then add_field (adders no-op on an existing name, and routing a swap through remove fires the orphan disclosure). base_version mirrors the siblings: required-on-existing / absent-on-create for add and modify, required ALWAYS for remove (destructive) * Removing a field orphans data REVERSIBLY - class defs live in XML, values in separate name-keyed tables, so values become orphaned rows that still load, re-save and reappear if the field is re-added with a compatible type. So no confirm handshake: base_version is the gate and the remove result discloses an AUTHORIZED-ONLY orphan count (scan to the door ceiling, authorise each doc, N+ at 2000; class + field names are HQL binds), for cardinality-over-denied-docs consistency with query_objects * Sensitive-class-DEFINITION guard (security audit): the object-write SENSITIVE_CLASSES set is reused via a new MCPObjectWriteSupport.isSensitiveClass to refuse EDITING THE SCHEMA of XWikiRights/GlobalRights/Groups/Users/ ServerClass/MCPServerConfigClass - a hole the object denylist (guards object writes) and the document denylist did not cover; redefining XWiki.XWikiUsers would corrupt the farm's user model. Sensitive-document denylist still applies * Script/SQL-bearing field types (ComputedField, DBList/DBTreeList/Page, Velocity TextArea) are ALLOWED - rights are king. Execution is capped at the class doc author's rights, and api.Document.save routes a non-programming author through saveAsAuthor, overwriting the author with the acting agent, so an authored field can never run above the agent's own rights (auditor traced end to end: ComputedField runs via AuthorExecutor at getAuthorReference = the agent). The real footgun - the author downgrade can disable an app's own scripts on an edited class doc - is named in the man page's WARNING * Reviewer clean on blockers/must-fix; security audit found no Critical/High/ Medium (HQL binding, door routing, denylist evasion, the author downgrade and the orphan-count oracle all verified). Fixes folded in: the tool's fragments use MCPTextGuards.fragment (clamp consistency), the stored version comment is neutralised, +6 refusal-branch tests. mcp-server 756 tests, mcp-ui 0 checkstyle Commit: 4822533873d601bb1af14d3d8c32da8f06cfff75 https://github.com/xwiki-contrib/ai-llm/commit/4822533873d601bb1af14d3d8c32d... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/internal/xwikistore/XWikiDocumentStoreEventListener.java A application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/xwikistore/XWikiDocumentStoreEventListenerTest.java Log Message: ----------- LLMAI-161: Deleting a document orphaned its RAG index chunks * The delete cleanup in XWikiDocumentStoreEventListener serialized the blank delete-event source with the default serializer, which drops the locale reference parameter - the produced ID (wiki:Space.Page) matched neither the trailing-; form of default documents nor the ;fr form of translations written by XWikiDocumentDocument#getID, so every deletion (any locale) left its chunks orphaned in Solr * Fix: serialize getOriginalDocument().getDocumentReferenceWithLocale() with the withparameters serializer - on deletion the event source is a blank locale-free document and the actually deleted row, including its locale, is the original document; deleteAllDocuments fires one event per translation, so each row cleans exactly its own chunks * New test class pins both ID forms (;fr and trailing ;) with the real serializers, the event source constructed exactly as the platform builds it, plus update-branch task queueing and the collection space-list branch (set-compare: reordering is not a change) * Existing indexes likely contain orphaned chunks from past deletions; reindexing the affected collections clears them Commit: 5abfa2ffd8365076e85a0df3e8443f28453c21da https://github.com/xwiki-contrib/ai-llm/commit/5abfa2ffd8365076e85a0df3e8443... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/pom.xml M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentToolTest.java Log Message: ----------- LLMAI-160: Make delete_document and rendered reads translation-aware * delete_document now deletes a page's translations with it via XWiki#deleteAllDocuments (platform-UI behavior: every row to the recycle bin under one shared batch ID); the tool's rights-checked- twice property is preserved with an explicit Document#hasAccessLevel re-check, since deleteAllDocuments itself checks nothing - the delete right attaches to the locale-free reference, so one check covers all translations (no per-translation right exists) * Success result names the deleted translation count and locales; the failure message admits possible partial deletion (the per-row delete is not transactional) and that retrying is safe - the default document, the base_version holder, is deleted last; man page states that base_version covers the default-locale document only * get_document rendered mode now renders the addressed document deterministically via XWikiDocument#displayDocument (identical render path with retrieveTranslation=false) - getRenderedContent silently swapped in a translation selected by the request language preference while title, version and base_version described the default document * Tests: fr+de translation rows on the real mock store all gone after one delete, api-level re-check denial after the door passed, honest partial-failure message, displayDocument method-choice pin (never getRenderedContent); needs a registered BatchOperationExecutor and an explicit xwiki-platform-refactoring-api test dependency Commit: 487a8fbdf2042dfc96893387b9d1104d2867e3c6 https://github.com/xwiki-contrib/ai-llm/commit/487a8fbdf2042dfc96893387b9d11... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/SolrConnector.java M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/internal/mcp/MCPSearchCollectionsTool.java A application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/SolrConnectorTest.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/DefaultCollectionManagerTest.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/mcp/MCPSearchCollectionsToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/pom.xml M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryDocumentsTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryDocumentsToolTest.java M application-ai-llm-models/application-ai-llm-models-api/src/main/java/org/xwiki/contrib/llm/openai/Context.java Log Message: ----------- LLMAI-160: Add read-side translation support to the MCP tools * get_document gains an optional locale parameter: exact-match, no language fallback - a missing translation is a teaching refusal listing the translations that exist and the default language; a locale equal to the default language is served as a default read (the default row stores no language). Both the source-mode and rendered-mode loads carry the locale, and rendered reads set and restore the context locale so translation-macro/sheet output matches the emitted language= view URL (URL-encoded value) * Headers gain Language: (omitted for an undeclared ROOT default locale) and Translations: fr, de (default: en) discovery lines; authorization stays locale-free by design - rights attach to the document and locale-carrying references never reach authorization APIs; man page states writes/edits touch the default language version only while delete removes all translations, and that a translation's Version is not a valid base_version * query_documents emits Language: per hit ((translation) suffix when the row is a translation - N translations of a page are no longer indistinguishable) and gains a locale filter: exact match on the indexed locale field (doclocale is indexed but empty on default rows, so only locale expresses content-in-language-X) * search_collections emits a locale-free <reference> plus <language> per hit so search results map directly onto get_document calls; <reference> is double-guarded - withparameters round-trip (shape) AND the chunk's collection must use the XWiki document store (provenance), since internal-store IDs are client-chosen and a reference-shaped upload ID must not attribute uploaded content to a real page; language flows through a new Context.language record component populated in SolrConnector (was fetched and dropped) * A validated Locale is NOT a safe token: commons-lang3 toLocale accepts arbitrary variant segments including newlines, so every locale echo is line-break-stripped (header lines, refusals, the query tool's Solr-derived language values) and the Solr filter value is escaped via toCompleteFilterQueryString * Live-validated on 17.10 multilingual (en/fr/ro): exact locale reads, refusals, rendered coherence, locale filter, wiki-store reference/language emission incl. the trailing-; default-row ID form, internal-store reference suppression, and LLMAI-161 chunk cleanup on delete observed working end to end * Tests: mcp-server 776 (variant-smuggled locale refusal single-line, ROOT-locale header omission, context-locale set/restore order, URL-encoding pin, filter fq pin, active-filters echo), index-api 107 (spoofed reference-shaped internal ID emits no reference, sanitized language, lookup-failure caching), models-api green; explicit localization-api dependency (module convention) Commit: a29d49d1c70e62f05ea91e656110e5b3180ff730 https://github.com/xwiki-contrib/ai-llm/commit/a29d49d1c70e62f05ea91e656110e... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-api/pom.xml M application-ai-llm-mcp/application-ai-llm-mcp-api/src/main/java/org/xwiki/contrib/llm/mcp/MCPToolSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-api/src/test/java/org/xwiki/contrib/llm/mcp/MCPToolSupportTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/pom.xml M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPEditDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryDocumentsTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSchemaTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPEditDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSchemaToolTest.java Log Message: ----------- LLMAI-160: Add write-side translation support to the MCP tools * write_document and edit_document gain an optional locale parameter: exact-match like the read side, base_version is ROW-scoped (a translation has its own history - omitted when creating the translation row, required by write_document / optionally checked by edit_document against that row's version when updating); success, conflict and no-change messages name the translation, and view/ compare URLs carry a URL-encoded language= parameter * Creating a NEW translation is refused when the default document is missing (the store would happily persist an orphan row) and on non-multilingual wikis (the platform UI silently mis-routes that save into the default document); editing an existing translation row is allowed regardless, matching the platform. Translation rows are loaded exactly (never getTranslatedDocument - parent-locale fallback would edit fr when fr_FR was asked), never built by cloning the default document (objects are keyed by fullName across rows), and get the page's syntax copied on creation (deliberate divergence: the UI leaves the wiki default syntax) * Every MCP document creation now stamps the wiki default locale (XWiki#getDefaultLocale - deterministic, unlike the UI's persisted request-dependent locale preference) through one seam in MCPWriteSupport#inTargetWiki, covering write_document, edit_document, write_object and write_schema; translation rows are never stamped * The default-language predicate is shared between read and write and falls back to the WIKI default when a document's defaultLocale is ROOT - programmatically created pages (extension initializers, REST, old imports) never get the UI's stamp, and without the fallback a locale=wiki-default write would mint a bogus translation row (the read side refused); gate order puts the short-circuit before the missing-default refusal so locale=wiki-default on a missing page is a plain create. Same fallback fixes a live-found header bug: the (default: X) suffix claimed the loaded ROW's own language on translation reads of such pages; the Language: line still names only DECLARED content languages * parseLocale is deduplicated into MCPToolSupport using commons-lang3 directly (already a mcp-api dependency for StringUtils since LLMAI-153; the platform localization wrapper would add a dep for unused conveniences) and refuses locales longer than 5 characters (the legacy XWD_LANGUAGE column length - longer values fail the save or silently truncate; security-audit Low); xwiki-platform-localization-api stays in mcp-server as an explicit test-scope dependency (test-only imports) * Live-validated on 17.10 multilingual (en/fr/ro): ro translation create/update/edit with independent row versions, full refusal matrix incl. the storage cap, ROOT-fallback default-row write, default-locale stamp on a fresh page, honest (default: en) header * Tests: mcp-api 55 (parseLocale forms, cap, stripped echo), mcp-server 802 (bogus-en-row pin, wiki-default-on-missing create, realistic ROOT-defaultLocale translation rows, stamp survival through object/schema clones, byte-identical locale-less messages); MCPEditDocumentTool fan-out suppression (measured 21/20, Locale is the 21st type) Commit: ece4b93163cae4f1f8cee2c9e0a13942a2f63736 https://github.com/xwiki-contrib/ai-llm/commit/ece4b93163cae4f1f8cee2c9e0a13... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/CollectionManager.java M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/SolrConnector.java M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/internal/DefaultCollectionManager.java M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/internal/mcp/MCPSearchCollectionsTool.java M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/internal/rest/DefaultSearchResource.java M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/internal/xwikistore/XWikiDocumentDocument.java M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/rest/SearchResource.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/SolrConnectorTest.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/DefaultCollectionManagerTest.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/mcp/MCPSearchCollectionsToolTest.java A application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/rest/DefaultSearchResourceTest.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/xwikistore/XWikiDocumentDocumentTest.java Log Message: ----------- LLMAI-160: Pin translation language in RAG citations and add a search locale filter * Translation chunks' source URLs now carry ?language=xx (URL-encoded; the URL factory appends query strings verbatim) so a French citation opens the French page; default-row chunks keep bare URLs (a stamped default would go stale if the wiki default changes). URLs are baked into chunks at index time: existing collections need a reindex for pinned citations, which is embedding-free (unchanged content means the embeddings are copied) * Collection search gains a locale filter: fq on the AI core's language string field (indexed by default like the collection/wiki filter fields - no schema change, works on already-indexed chunks; on Solr 9.4.1 the fq is a KNN PRE-filter, so topK is computed among matching chunks). Our chunks store the concrete default locale even on default rows, so locale=fr means content-in-French including French-default pages. Exposed as a locale parameter on search_collections (shared parseLocale) and on the search REST resource (lang3 validation + the same 5-char storage cap, static 400, no input echo); threaded through a new locale overload of CollectionManager#hybridSearch (old signature delegates; the interface default documents that it ignores the filter) * search_collections now sanitizes the <url> element like <language> (uploader-typed internal-store URLs could forge </url><reference> elements, undercutting the store-provenance guard) * New DefaultSearchResourceTest - the REST resource had no tests at all; covers the locale outcomes without pinning the limit argument order (LLMAI-163 fixes the pre-existing semantic/keyword swap found during this review) * Review round also surfaced LLMAI-164 (keywordSearch missing the wiki filter - pre-existing, separate fix); chat-side locale filtering deferred (no per-request channel; would need a RAGChatRequestFilterClass field + UI) * Tests: index-api 123 green incl. KNN-prefilter fq placement pins (filter queries only, never the knn query string), URL-encoding pin on a variant-carrying locale, forged-url single-line pin, and a parameterized hybridSearch locale-forwarding matrix Commit: 0e12284aeffce0bdbef9db51833e40555c0974e4 https://github.com/xwiki-contrib/ai-llm/commit/0e12284aeffce0bdbef9db51833e4... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/internal/rest/DefaultSearchResource.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/internal/rest/DefaultSearchResourceTest.java Log Message: ----------- LLMAI-163: REST search swapped the semantic and keyword result limits * DefaultSearchResource passed limitKeywordResults into hybridSearch's limitSemanticSimilarity position and vice versa, swapped since the hybrid search was introduced (LLMAI-130); with asymmetric limits the caller's keyword budget silently drove the KNN topK. The MCP tool and the chat filter pass theirs correctly * New test pins the interface argument order (the test class itself is new as of LLMAI-160 Stage 3; its other tests deliberately avoid pinning the limit order so this fix lands cleanly) Commit: 6a4466a2dea444f09abcf5a98cb20b5170dddc3b https://github.com/xwiki-contrib/ai-llm/commit/6a4466a2dea444f09abcf5a98cb20... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-index/application-ai-llm-index-api/src/main/java/org/xwiki/contrib/llm/SolrConnector.java M application-ai-llm-index/application-ai-llm-index-api/src/test/java/org/xwiki/contrib/llm/SolrConnectorTest.java Log Message: ----------- LLMAI-164: keywordSearch was missing the wiki filter * The keyword path built its query with the collections filter but never applied buildWikiQuery, unlike the semantic path. Collection names in the filter are not wiki-qualified, so a same-named collection on another wiki leaked its chunks into keyword results - past the collection-level group gate, which was evaluated against the current wiki's collection only (the per-document canView post-filter still applied; security-audit Medium) * Fix: the same buildWikiQuery filter query as prepareQuery, with a test pinning the wiki fq on the keyword path Commit: 5043bcdfda5e11f22f2f37d0e79a660e82845325 https://github.com/xwiki-contrib/ai-llm/commit/5043bcdfda5e11f22f2f37d0e79a6... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/script/MCPFarmScriptService.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/script/MCPFarmScriptServiceTest.java M application-ai-llm-mcp/application-ai-llm-mcp-ui/src/main/resources/AI/MCP/Code/MCPServerConfigSheet.xml M application-ai-llm-mcp/application-ai-llm-mcp-ui/src/main/resources/AI/MCP/Code/Translations.xml Log Message: ----------- LLMAI-162: Add the MCP Server URL with copy-to-clipboard to the config pages * New MCPFarmScriptService#getServerUrl(wikiId): composes the external endpoint URL (<base>/<webapp>rest/wikis/<id>/aiLLM/mcp, the literal path mirroring MCPResource's @Path) from the server URL the platform advertises for the wiki - descriptor alias, or xwiki.home for the main wiki - falling back to the current request's server URL when a wiki has no advertised base; same composition as the platform's REST getBaseURI incl. the root-webapp special case. Read-only and ungated like isEnabled (a script-right caller already gets the full wiki descriptor ungated from the platform); the wiki id path segment is URL-encoded defensively (ids outside the alphanumeric convention) * Config sheet: read-only URL row (input-group + copy button) at the top of the settings fieldset - the input carries no name so the save form never posts it, and Enter in it is swallowed (submitting from a subwiki would persist the display-seeded enable state); the farm dashboard gains a per-row copy button using each ROW wiki's URL * Clipboard JS in the sheet's existing vanilla-JS JSX: navigator.clipboard first (secure contexts), select+execCommand fallback (config row selects the adjacent input, dashboard rows a transient textarea), Notification feedback on success AND on fallback failure; notification texts are HTML-escaped in JS because the 17.4-baseline Notification widget renders innerHTML (the plain text default only exists since 17.10.9; security-audit Low) * All URL/label emissions escaped; the endpoint works from any farm host (the wiki is selected by the path segment, stated in the hint) * Tests: mcp-server 30 script-service tests incl. exact URL-shape pin, trailing-slash and root-webapp cases, descriptor-preferred pin (request factory never consulted when the wiki advertises a base), null-base fallback, MalformedURLException-to-null, encoded odd id Commit: e34c9efbced9570009540f8d8b79433fbf03135f https://github.com/xwiki-contrib/ai-llm/commit/e34c9efbced9570009540f8d8b794... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPEditDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPManTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPEditDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteDocumentToolTest.java Log Message: ----------- [Misc] Agent-UX polish from the first real-usage friction log * man xwiki-syntax ESCAPING now warns about the two traps that shipped real bugs when an agent wrote docs ABOUT syntax: link/formatting markup is still parsed inside ##monospace## (escape ~[~[...~]~]), and word:// in prose is auto-detected as a URI scheme * man edit_document blesses the append idiom for growing large pages (anchor old_string on the exact current tail; empty old_string is refused on existing pages precisely so appends stay read-anchored) * The plain rendered banner steers verification to format="html" (plain text flattens links/fences/tables; a missing link target is marked wikicreatelink there - the resolved-link class is stripped by default, so only the negative oracle is advertised) * write_document/edit_document success results note when the saved body contains {{velocity}}/{{groovy}} macros - what users see is generated at view time, so the agent is pointed at rendered=true (the read-side provenance note never covered the write path) * mcp-server 809 tests green Commit: a7e29edbf87de95adf3adb4e3f6750e8d4997997 https://github.com/xwiki-contrib/ai-llm/commit/a7e29edbf87de95adf3adb4e3f675... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetSchemaTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaText.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaTextTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSchemaToolTest.java Log Message: ----------- [Misc] get_schema display honesty + write-path refusal polish (usage-test friction) * get_schema flags a StaticList default that is not among the values (a live app shipped default=How-To against tutorial|howto|...) and resolves TextArea content types honestly: the class editor stores a literal --- placeholder that the old code echoed verbatim; now known types render as plain/wiki/velocityCode/velocityWiki, the --- placeholder derives from the editor via the platform's own compatibility rule, and only editor=PureText with unset contenttype stays a bare TextArea * The display renames required write-side normalization to keep the read-to-write round-trip whole (review catch): write_schema's contentType/editor attributes were stored RAW, and 'plain' is not platform vocabulary - getByValue misses it and the field silently wiki-renders while get_schema echoes 'plain' back, hiding the mismatch. Both attributes now accept the display AND platform vocabularies case-insensitively, store the canonical enum values, and refuse unknown tokens with a teaching list * ComputedField and Password write refusals gain the house tail: state that nothing was saved and name the corrective action * mcp-server 828 tests green Commit: 7e45a27c474f698a7483a6805ebb0368eda8ac8a https://github.com/xwiki-contrib/ai-llm/commit/7e45a27c474f698a7483a6805ebb0... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectQuerySupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryObjectsTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryObjectsToolTest.java Log Message: ----------- LLMAI-156: query_objects lists a document's objects without a class * class becomes optional when document is given: a document-only call inventories every object on that page grouped by class - class reference, object number, document version in the header, and deliberately NO field values (strictly less than any per-class call on the same viewable document already shows, so the authorized-only totals and value-oracle decisions are untouched) * Same door as the per-class document restriction (VIEW via resolveAndAuthorize before any query), same ceiling, same per-row authorization and paging; the inventory statement is the per-class HQL minus the class bind with className as a third select column, built from constants with the document name as a bind * select/filters/sort are class-scoped and refused with a teaching message when class is omitted; a nonexistent document reports No such document (not a false empty inventory), echoes neutralized * Born from the DocApp real-usage test: 'what objects does this page carry?' needed the class known in advance * Tests incl. exact grouped-output and statement pins, mid-class paging header re-emission, hostile-echo neutralization, door denial with the store never queried Commit: 4a95506f1ed5a091538b9d6fb820354c7e3e51bc https://github.com/xwiki-contrib/ai-llm/commit/4a95506f1ed5a091538b9d6fb8203... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectToolTest.java Log Message: ----------- LLMAI-157: write_object accepts an optional title * Applied on create and update with write_document's exact semantics and echoes (Title set. / Title updated. / silence when omitted or unchanged), compared against the pre-save clone, one save * Kills the extra version the object-first creation flow needed just to name its page (DocApp real-usage friction: a landing page IS a pure object + title) * Tests: create-with-title single 1.1 version, update retitle, unchanged title not echoed, omitted title untouched Commit: a50d16c1da8bee7a5af8e31579e01bca8f4622e2 https://github.com/xwiki-contrib/ai-llm/commit/a50d16c1da8bee7a5af8e31579e01... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-api/src/main/java/org/xwiki/contrib/llm/mcp/MCPSourceText.java M application-ai-llm-mcp/application-ai-llm-mcp-api/src/test/java/org/xwiki/contrib/llm/mcp/MCPSourceTextTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPDocumentAccess.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPWikiReach.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPEditDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetSchemaTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPObjectQuerySupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryDocumentsTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryObjectsTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPSchemaWriteSupport.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteDocumentTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSchemaTool.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/main/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSupport.java Log Message: ----------- LLMAI-153: Budget-cutter seam, echo neutralization everywhere, small hardenings * MCPSourceText gains the shared output-budget cutter: public OUTPUT_TRUNCATION_NOTE + budgeted() (cut at the last complete line within MAX_OUTPUT_CHARS, hard cut when no newline, note appended; callers append honesty footers AFTER the cut). Kills the byte-identical private copies get_schema and query_objects carried - the third and fourth output-budget dialect collapse into one home, next to the constants they read. 3 new MCPSourceTextTest pins * Every echo of an agent-supplied argument (reference, class, wiki id, query text, filter echo) in an agent-facing message is neutralized: MCPTextGuards.fragment inside internal.tool, stripLineBreaks in internal.access (package visibility). Agent-supplied values launder wiki-authored text - agents copy references they just read from page content - so a raw echo is a line-forgery vector into the tool-result grammar the agent trusts. ~30 sites across 13 files converged; supersedes get_document's old "echoed verbatim" @param convention. Sanctioned exception: the aggregated describeActiveFilters echo keeps bare stripLineBreaks (joined parts legitimately exceed the 200 cap) * delete_document's child-count read now shape-dispatches on Number / Object[] like its siblings instead of a raw (Long) cast that would escape as an uncaught CCE on an unexpected store row shape * Dead @SuppressWarnings("checkstyle:IllegalCatch") dropped from MCPObjectQuerySupport.parseOrNull - the ruleset ships that rule commented out, so the suppression suppressed nothing (the XWIKI-20910 version-skew rationale lives on in the Javadoc) * MCPSchemaWriteSupport.setAttr's silent default -> {} now throws IllegalStateException: a future AttrKind without a setter fails loud in tests instead of silently dropping the attribute Commit: 62803e9fc97d06a3cfc1aaca649004cd1aad6d16 https://github.com/xwiki-contrib/ai-llm/commit/62803e9fc97d06a3cfc1aaca64900... Author: Paul Pantiru <[email protected]> Date: 2026-07-19 (Sun, 19 Jul 2026) Changed paths: M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/access/DefaultMCPDocumentAccessTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/access/MCPWikiReachTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/AbstractMCPToolTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/AbstractMCPWriteToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPDeleteObjectToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPEditDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetSchemaToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPGetTreeToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPListWikisToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPManToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryDocumentsToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPQueryObjectsToolTest.java A application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPToolTestUtils.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteDocumentToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteObjectToolTest.java M application-ai-llm-mcp/application-ai-llm-mcp-server/src/test/java/org/xwiki/contrib/llm/mcp/internal/tool/MCPWriteSchemaToolTest.java Log Message: ----------- LLMAI-153: Extract the shared MCP tool test harness * The scan's own math came due: 10 new test classes since the harness was spec'd, textOf copied 9x, the oldcore write prologue duplicated near line-for-line including its comments across six fixtures * MCPToolTestUtils (textOf/request), AbstractMCPToolTest (getTool seam over the MCPTool interface: call/callText/referenceDescription + the 3x-duplicated parseRef/localName fixture helpers), and AbstractMCPWriteToolTest (the four universal @MockComponent doors, USER_REFERENCE, a base @BeforeEach with only the invariant oldcore core, opt-in helpers for the divergent parts: allowSimpleSavePath / registerCurrentResolver / registerLocalizationContext + the shared load/store/translation/version/nothing-saved plumbing) * Platform-grounded on AbstractJobTest/AbstractAttachmentsResourceTest: annotations and @InjectMockComponents stay on the concrete classes; both Mockito extensions walk superclasses via ReflectionUtils.getAllFields, which dedupes by field NAME subclass-first - migration deletes per-class copies, never shadows * 13 classes migrated, 358 lines of duplicated plumbing removed; deliberately NOT unified: buildQueryMock/blogClass (three genuinely different fixtures each), per-class LogCaptureExtension (two write tests deliberately have none), pinned message literals (the house value-oracle stance), and the index module's two tool tests (would need an mcp-api test-jar - flagged, not done) * Hostile-echo pin tests for the neutralization sweep (13 sites, one per swept production file) + a shape pin for delete_document's Object[]/Number child-count row * Pure relocation proved by the gate: mcp-server 842 tests green (841 baseline + the shape pin), checkstyle 0, jacoco met Compare: https://github.com/xwiki-contrib/ai-llm/compare/a425ff04a100%5E...62803e9fc9... To unsubscribe from these emails, change your notification settings at https://github.com/xwiki-contrib/ai-llm/settings/notifications