Skip to content

feat: Documents MCP tools (create-from-template, versions, share, public link, shortcuts, favorites) - EXO-88456 - #2001

Merged
bmestrallet merged 16 commits into
feature/ai-contributionfrom
feat/documents-mcp-tools-EXO-88354
Jul 22, 2026
Merged

feat: Documents MCP tools (create-from-template, versions, share, public link, shortcuts, favorites) - EXO-88456#2001
bmestrallet merged 16 commits into
feature/ai-contributionfrom
feat/documents-mcp-tools-EXO-88354

Conversation

@bmestrallet

Copy link
Copy Markdown
Contributor

What

Adds Documents MCP tools for the EVA AI agent (delegating to the ecms/documents services), acting as the current user (ACL-enforced):

  • create_document_from_template (via DocumentService)
  • document version tools, share + public-link tools, shortcut creation, and favorite documents
  • create/copy/duplicate/move/rename, filters, size, import — with integration fixes (conflict values, copy/duplicate return, favorites type, share no-downgrade, undo-delete via system session, template extension match)

Dependency / CI

documents-services consumes io.meeds.mcp-server:mcp-server-tools — if pinned to the *-ai-contribution-SNAPSHOT line, CI will be red until mcp-server #13 (UploadToolUtils, EXO-88348) merges and a consuming version is published.

Related

Companion office-side PR exoplatform/onlyoffice#342 (also EXO-88354).

Board: EXO-88354.

🤖 Generated with Claude Code

@bmestrallet
bmestrallet requested a review from ahamdi July 8, 2026 14:39
@bmestrallet bmestrallet changed the title feat: Documents MCP tools (create-from-template, versions, share, public link, shortcuts, favorites) - EXO-88354 feat: Documents MCP tools (create-from-template, versions, share, public link, shortcuts, favorites) - EXO-88456 Jul 8, 2026
@exo-swf
exo-swf force-pushed the feature/ai-contribution branch 3 times, most recently from f931146 to 91faea0 Compare July 11, 2026 01:39
bmestrallet and others added 14 commits July 19, 2026 12:14
Move the generic document MCP tools out of the enterprise distribution
into the open Documents add-on so they ship in all editions.

- Add DocumentMcpTool (@service @Profile("mcp-server")) exposing the 9
  document tools (get_root_folder_by_space, attach_document_to_content,
  update_document_description, get_documents_by_folder_id,
  get_root_folder_for_user, get_document_by_id, get_document_content_by_id,
  get_document_transcription_by_id, search_documents) as the current user.
- get_document_transcription_by_id is now a plain read of the stored
  transcription (DocumentFileService.getAudioTranscription) with no
  ai-agent-service coupling; on-demand AI transcription stays enterprise.
- Add documents-services ai-tool-definitions.json with the 9 tool entries.
- documents-services: add mcp-server-tools (provided), ecms-core-services
  (AttachmentService), notes-service (NoteService) and the -parameters
  javac flag required for MCP argument binding by name.

The AI-experience layer (UX bindings, curated prompts, auto-transcription
listener) intentionally remains in the enterprise edition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The relocated DocumentMcpTool imported DocumentModel / DocumentFileModel /
DocumentFolderModel from org.exoplatform.documents.model, but those three
records were left behind in the enterprise edition, so documents-services
failed to compile ("cannot find symbol").

Bring them into the add-on under org.exoplatform.documents.mcp.model
(co-located with the tool, mirroring the poll add-on's io.meeds.poll.mcp.model
layout) instead of splitting the org.exoplatform.documents.model package
across the api and services jars. Update the imports in DocumentMcpTool
accordingly.

Add DocumentMcpToolTest (13 cases, JUnit 4 like the rest of the module) so the
new tool methods keep the module above its 0.55 coverage gate.

No AI (io.meeds.ai / ai-agent-service) dependency is introduced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… management)

Extend DocumentMcpTool with 15 new MCP tools acting as the current user
(ACL enforced by DocumentFileService):

Reads: list_folder_children, get_folder_breadcrumb, get_folder_tree,
list_document_versions, get_documents_size.

Writes (require_approval): create_folder, rename_document, move_document,
copy_document, duplicate_document, delete_document (trash only, delay=0),
undo_delete_document, restore_document_version, update_version_summary,
set_document_visibility.

Adds thin model records (BreadcrumbItemModel, DocumentVersionModel,
DocumentTreeItemModel, DocumentsSizeModel), matching ai-tool-definitions.json
entries, and DocumentMcpToolTest coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The JCR storage createFolder/getBreadcrumb resolve the parent from the
node id (folderId) and treat 'folderPath' as a RELATIVE sub-path from
that node (node.getNode(folderPath) / getNodeByPath). DocumentMcpTool
was passing the parent node's ABSOLUTE JCR path, so creating a folder
under the user's ROOT folder failed with
ObjectNotFoundException: Folder with path : /Users/.../Private isn't found.

Pass null as folderPath in createFolder and getFolderBreadcrumb since
the folder id already identifies the JCR node uniquely. move/delete keep
passing absolute paths (the storage uses them as absolute JCR paths).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tils - EXO-88354

create_document turns chat-authored text/markdown/HTML into a real DMS file;
upload_document imports a binary file from base64 or an SSRF-guarded URL fetch.
Both stage the bytes via UploadToolUtils.materialize, wrap them in a one-entry
zip (importFiles unzips), import into the target folder as the current user, and
poll the folder to return the created document. Pins mcp-server-tools to the
ai-contribution build that ships UploadToolUtils.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rs mime_type - EXO-88354

upload_document now declares attachment_object_type/attachment_object_id so
EVA forwards a chat-attached file/image; the bytes are resolved server-side as
the current user (ACL enforced) via UploadToolUtils.resolveImage's attachment
branch (no image-only constraint), keeping the existing base64/url paths.

create_document forces the requested mime_type on the stored file via a new
DocumentFileService/Storage updateDocumentMimeType (JCR jcr:content/jcr:mimeType),
fixing .md files that importFiles left as application/octet-stream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s file extension - EXO-88354

The prior commit forced the requested mime_type onto the stored file via a
new DocumentFileService/Storage updateDocumentMimeType that opened a JCR
session and set jcr:content/jcr:mimeType. Revert that JCR plumbing entirely:
create_document's stored content type now follows the file extension
(resolved by the platform MimeTypeResolver), the pre-fix behavior. The
mime_type param is kept but is advisory (extension inference only). The
upload_document chat-attachment support is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…put - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h extension - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s MCP tools

Adds six new MCP tools to DocumentMcpTool, each acting as the current user
(ACLs enforced) with a matching ai-tool-definitions.json entry and tests:

- add_document_version: uploads updated content onto an existing file as a new
  version via DocumentFileService.createNewVersion (checkout -> set jcr:content
  -> checkin/VersionHistoryUtils.createVersion), from text/base64/url/chat
  attachment, with an optional version summary stamped on the new current
  version.
- create_public_link: PublicDocumentAccessService.createPublicDocumentAccess,
  returning the /portal/download-document/{nodeId} shareable link.
- create_document_shortcut: DocumentFileService.createShortcut into a resolved
  destination folder path.
- share_document: DocumentFileService.shareDocument with a user or a space.
- favorite_document / unfavorite_document: social FavoriteService with the
  "document"/"folder" object types.

All writes require approval and pre-check edit permission where applicable.
Skipped by design: list_trash (admin-only global trash, no per-user scope),
share permission levels (service exposes no clean granular API), and
create_document_from_template (still no ECMS service method; tracked on #88422).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, copy/duplicate return, favorites type, filters, size, share permissions, undo-delete, import conflict) - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ocumentService) - EXO-88354

Adds a Documents MCP tool that creates a new empty office document
(Word/Excel/PowerPoint, + ODF when the editor add-on provides it) from the
platform's blank template, by delegating to the ecms DocumentService rather
than re-implementing template/office logic.

- documents-storage-jcr: inject DocumentService, add
  createDocumentFromTemplate mirroring createFolder (session/parent/ACL),
  resolve the template by matching NewDocumentTemplate.getExtension against
  the requested type, then call ecms createDocumentFromTemplate.
- documents-api / documents-services: propagate the new storage/service
  signatures.
- DocumentMcpTool.createDocumentFromTemplate + ai-tool-definitions entry
  (require_approval, document_type enum). ecms does NOT append the extension,
  so the tool builds a title that ends with '.<type>'. Softened
  create_document's office-format rejection to point at the new tool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ade, undo-delete system session, favorite docs - EXO-88354

Fix 1: create_document_from_template never matched a template because
onlyoffice stores extensions with a leading dot (".docx") while the caller's
type is dot-stripped ("docx"). Normalize the dot on both sides of the compare
in JCRDocumentFileStorage. Trim supported types to docx/xlsx/pptx (the 3 the
editor add-on actually ships) in DocumentMcpTool and ai-tool-definitions.json.

Fix 2: share_document silently downgraded an existing editor to read because
it always appended (recipient,"read") after the preserved (recipient,"edit"),
and storage writes permissions in list order. Only add the read grant when the
recipient is not already a collaborator.

Fix 3: undo_delete_document resolved the trashed node via the user-session
overload, but trash lives under a system session, so it false-negatived with a
misleading "permanently deleted" error. Use the system-session getDocumentById
overload.

Fix 4: favorite/unfavorite descriptions and Javadoc advertised "file or
folder" though folders are rejected. Correct to files-only wording.

Adds regression tests: template dotted-extension match (storage), share
edit-preservation, and undo-delete via the system-session overload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bmestrallet
bmestrallet force-pushed the feat/documents-mcp-tools-EXO-88354 branch from 262d0f8 to 13a4780 Compare July 19, 2026 11:18
@boubaker

Copy link
Copy Markdown
Member

Code review

🟡 Medium — resolveAddedChildFile's "last new file" heuristic can return the wrong document under concurrent writes

DocumentMcpTool.java:1382-1395:

return children.stream()
               .filter(FileNode.class::isInstance)
               .map(FileNode.class::cast)
               .filter(file -> !beforeFileIds.contains(file.getId()))
               .reduce((first, second) -> second)   // assumes at most one file was added since the snapshot
               .map(this::toDocumentFileModel)
               .orElse(null);

This backs copyDocument and duplicateDocument (whose underlying storage calls return the destination/parent folder, not the new node — a real limitation the comments call out) and importContent/findImportedDocument behind create_document/upload_document. It snapshots the folder's file ids, calls the storage op, then diffs — but if any other file lands in that same folder between the snapshot and the re-read (a teammate uploading into a shared space folder, another concurrent copy/import, the polling loop's own retries), the diff contains more than one id and this silently returns whichever one the stream produced last — the wrong document, reported to the caller/AI-agent as "the copy you asked for."

Only the single-new-file case is tested (copyDocumentReturnsResolvedCopyNotDestinationFolder, duplicateDocumentReturnsResolvedDuplicateNotParentFolder in DocumentMcpToolTest); nothing exercises "two files added between snapshot and read."

Fix: narrow the diff further (e.g. match on name in addition to "not in before-set"), or — the more durable fix — have the storage layer return the actual created/copied/duplicated node so no snapshot-diff heuristic is needed at all.


🟢 Nit — attachDocumentToContent skips the id validation every sibling method applies

DocumentMcpTool.java:216-221:

public void attachDocumentToContent(String documentId, String contentType, long contentId) throws IllegalAccessException {
  // FIXME Why using this content type name ??!!
  contentId = fixNoteContentId(contentType, contentId);
  // FIXME Why using this content type name ??!!
  contentType = fixNoteContentType(contentType);
  attachmentService.linkAttachmentToEntity(getCurrentUserIdentityId(), contentId, contentType, documentId);
}

Every other document-touching method in this class opens with checkDocumentIdParameter/checkCanAccessDocument (or both); this one uses documentId straight from the AI agent with no blank/existence check. Impact looks low — it can only create a dangling attachment link, since any later read of the referenced document still goes through DocumentFileService's own ACL — but it's the one inconsistency in an otherwise very deliberately validated class, and the two carried-forward FIXME Why using this content type name ??!! comments suggest this corner was copied as-is rather than revisited.


This is a careful, well-tested PR: each of the "integration fixes" called out in the description has a dedicated regression test that fails without the fix and passes with it — createDocumentFromTemplateMatchesDottedExtension (onlyoffice's dotted extension), createNewVersionAddsVersionableMixinWhenMissing (silent no-op on non-versionable files), shareDocumentDoesNotDowngradeExistingEditor (no-downgrade on re-share), and the copy/duplicate resolution tests above. The undo_delete_document system-session rationale and the share_document no-downgrade logic are both explained in-line with the actual mechanism they work around, not just what changed — makes this easy to trust and to revisit later.

🤖 Generated with Claude Code

… of a blind folder-diff (boubaker review, MCP-only) - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bmestrallet

Copy link
Copy Markdown
Contributor Author

@boubaker — fixed MCP-only, no storage/JCR changes (commit 0303f3c):

  • 🟡 Medium — replaced the .reduce((first,second) -> second) "last new file" guess. resolveAddedChildFile now narrows the folder-diff to the file matching the expected name — I matched the actual JCR naming (source exo:title + the " (n)" clash counter, plus the duplicate prefix; FileNode.getName() returns the title), so a concurrent different-named upload can no longer be picked. And if more than one candidate still matches, it throws a clear error instead of silently guessing (0 matches for copy/duplicate → an explicit "could not locate" error). Applied to copyDocument, duplicateDocument, and the import/create path.
  • NitattachDocumentToContent now calls checkCanAccessDocument(documentId) up front (id + ACL), matching every sibling method.
  • Tests: copy/duplicate/import each with a concurrent differently-named file added around the op (assert the correct node), the ambiguity guard (two matches → clear throw), and blank-id attach. Each fails against the pre-fix heuristic; 85 green.

I deliberately kept the storage/JCR layer untouched — resolving deterministically-by-name at the MCP layer with an honest error for the rare true same-name collision, rather than changing the shared copyDocument/duplicateDocument return contract that the REST paste() path relies on.

mcp-server's develop refactored UploadToolUtils.resolveImage, replacing the
four loose imageUrl/imageBase64/attachmentObjectType/attachmentObjectId
parameters with a single ImageSource record. Now that feature/ai-contribution
tracks that version, the old eight-argument calls no longer compile. Wrap the
four values in ImageSource at both call sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bmestrallet
bmestrallet merged commit 1f92fbe into feature/ai-contribution Jul 22, 2026
1 check passed
@bmestrallet
bmestrallet deleted the feat/documents-mcp-tools-EXO-88354 branch July 22, 2026 19:03
bmestrallet added a commit that referenced this pull request Jul 28, 2026
…lic link, shortcuts, favorites) - EXO-88456 (#2001)

* feat: Relocate Document MCP tools into the Documents add-on

Move the generic document MCP tools out of the enterprise distribution
into the open Documents add-on so they ship in all editions.

- Add DocumentMcpTool (@service @Profile("mcp-server")) exposing the 9
  document tools (get_root_folder_by_space, attach_document_to_content,
  update_document_description, get_documents_by_folder_id,
  get_root_folder_for_user, get_document_by_id, get_document_content_by_id,
  get_document_transcription_by_id, search_documents) as the current user.
- get_document_transcription_by_id is now a plain read of the stored
  transcription (DocumentFileService.getAudioTranscription) with no
  ai-agent-service coupling; on-demand AI transcription stays enterprise.
- Add documents-services ai-tool-definitions.json with the 9 tool entries.
- documents-services: add mcp-server-tools (provided), ecms-core-services
  (AttachmentService), notes-service (NoteService) and the -parameters
  javac flag required for MCP argument binding by name.

The AI-experience layer (UX bindings, curated prompts, auto-transcription
listener) intentionally remains in the enterprise edition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: Move Document MCP model records into the Documents add-on

The relocated DocumentMcpTool imported DocumentModel / DocumentFileModel /
DocumentFolderModel from org.exoplatform.documents.model, but those three
records were left behind in the enterprise edition, so documents-services
failed to compile ("cannot find symbol").

Bring them into the add-on under org.exoplatform.documents.mcp.model
(co-located with the tool, mirroring the poll add-on's io.meeds.poll.mcp.model
layout) instead of splitting the org.exoplatform.documents.model package
across the api and services jars. Update the imports in DocumentMcpTool
accordingly.

Add DocumentMcpToolTest (13 cases, JUnit 4 like the rest of the module) so the
new tool methods keep the module above its 0.55 coverage gate.

No AI (io.meeds.ai / ai-agent-service) dependency is introduced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: Add v1 batch of Documents MCP tools (folders, versions, content management)

Extend DocumentMcpTool with 15 new MCP tools acting as the current user
(ACL enforced by DocumentFileService):

Reads: list_folder_children, get_folder_breadcrumb, get_folder_tree,
list_document_versions, get_documents_size.

Writes (require_approval): create_folder, rename_document, move_document,
copy_document, duplicate_document, delete_document (trash only, delay=0),
undo_delete_document, restore_document_version, update_version_summary,
set_document_visibility.

Adds thin model records (BreadcrumbItemModel, DocumentVersionModel,
DocumentTreeItemModel, DocumentsSizeModel), matching ai-tool-definitions.json
entries, and DocumentMcpToolTest coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: create_folder under root folder fails in Documents MCP tools

The JCR storage createFolder/getBreadcrumb resolve the parent from the
node id (folderId) and treat 'folderPath' as a RELATIVE sub-path from
that node (node.getNode(folderPath) / getNodeByPath). DocumentMcpTool
was passing the parent node's ABSOLUTE JCR path, so creating a folder
under the user's ROOT folder failed with
ObjectNotFoundException: Folder with path : /Users/.../Private isn't found.

Pass null as folderPath in createFolder and getFolderBreadcrumb since
the folder id already identifies the JCR node uniquely. move/delete keep
passing absolute paths (the storage uses them as absolute JCR paths).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: Add create_document/upload_document MCP tools using UploadToolUtils - EXO-88354

create_document turns chat-authored text/markdown/HTML into a real DMS file;
upload_document imports a binary file from base64 or an SSRF-guarded URL fetch.
Both stage the bytes via UploadToolUtils.materialize, wrap them in a one-entry
zip (importFiles unzips), import into the target folder as the current user, and
poll the folder to return the created document. Pins mcp-server-tools to the
ai-contribution build that ships UploadToolUtils.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: upload_document accepts chat attachments + create_document honors mime_type - EXO-88354

upload_document now declares attachment_object_type/attachment_object_id so
EVA forwards a chat-attached file/image; the bytes are resolved server-side as
the current user (ACL enforced) via UploadToolUtils.resolveImage's attachment
branch (no image-only constraint), keeping the existing base64/url paths.

create_document forces the requested mime_type on the stored file via a new
DocumentFileService/Storage updateDocumentMimeType (JCR jcr:content/jcr:mimeType),
fixing .md files that importFiles left as application/octet-stream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* revert: drop JCR mime-forcing in create_document; content-type follows file extension - EXO-88354

The prior commit forced the requested mime_type onto the stored file via a
new DocumentFileService/Storage updateDocumentMimeType that opened a JCR
session and set jcr:content/jcr:mimeType. Revert that JCR plumbing entirely:
create_document's stored content type now follows the file extension
(resolved by the platform MimeTypeResolver), the pre-fix behavior. The
mime_type param is kept but is advisory (extension inference only). The
upload_document chat-attachment support is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: use renamed UploadToolUtils.FetchedContent - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: clear error messages for create_document/upload_document bad input - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: drop mime_type from create_document; require a filename with extension - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: add version, share, public-link, shortcut and favorite Documents MCP tools

Adds six new MCP tools to DocumentMcpTool, each acting as the current user
(ACLs enforced) with a matching ai-tool-definitions.json entry and tests:

- add_document_version: uploads updated content onto an existing file as a new
  version via DocumentFileService.createNewVersion (checkout -> set jcr:content
  -> checkin/VersionHistoryUtils.createVersion), from text/base64/url/chat
  attachment, with an optional version summary stamped on the new current
  version.
- create_public_link: PublicDocumentAccessService.createPublicDocumentAccess,
  returning the /portal/download-document/{nodeId} shareable link.
- create_document_shortcut: DocumentFileService.createShortcut into a resolved
  destination folder path.
- share_document: DocumentFileService.shareDocument with a user or a space.
- favorite_document / unfavorite_document: social FavoriteService with the
  "document"/"folder" object types.

All writes require approval and pre-check edit permission where applicable.
Skipped by design: list_trash (admin-only global trash, no per-user scope),
share permission levels (service exposes no clean granular API), and
create_document_from_template (still no ECMS service method; tracked on #88422).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: correct integration bugs in Documents MCP tools (conflict values, copy/duplicate return, favorites type, filters, size, share permissions, undo-delete, import conflict) - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: add create_document_from_template MCP tool (delegates to ecms DocumentService) - EXO-88354

Adds a Documents MCP tool that creates a new empty office document
(Word/Excel/PowerPoint, + ODF when the editor add-on provides it) from the
platform's blank template, by delegating to the ecms DocumentService rather
than re-implementing template/office logic.

- documents-storage-jcr: inject DocumentService, add
  createDocumentFromTemplate mirroring createFolder (session/parent/ACL),
  resolve the template by matching NewDocumentTemplate.getExtension against
  the requested type, then call ecms createDocumentFromTemplate.
- documents-api / documents-services: propagate the new storage/service
  signatures.
- DocumentMcpTool.createDocumentFromTemplate + ai-tool-definitions entry
  (require_approval, document_type enum). ecms does NOT append the extension,
  so the tool builds a title that ends with '.<type>'. Softened
  create_document's office-format rejection to point at the new tool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: re-verify follow-ups — template extension match, share no-downgrade, undo-delete system session, favorite docs - EXO-88354

Fix 1: create_document_from_template never matched a template because
onlyoffice stores extensions with a leading dot (".docx") while the caller's
type is dot-stripped ("docx"). Normalize the dot on both sides of the compare
in JCRDocumentFileStorage. Trim supported types to docx/xlsx/pptx (the 3 the
editor add-on actually ships) in DocumentMcpTool and ai-tool-definitions.json.

Fix 2: share_document silently downgraded an existing editor to read because
it always appended (recipient,"read") after the preserved (recipient,"edit"),
and storage writes permissions in list order. Only add the read grant when the
recipient is not already a collaborator.

Fix 3: undo_delete_document resolved the trashed node via the user-session
overload, but trash lives under a system session, so it false-negatived with a
misleading "permanently deleted" error. Use the system-session getDocumentById
overload.

Fix 4: favorite/unfavorite descriptions and Javadoc advertised "file or
folder" though folders are rejected. Correct to files-only wording.

Adds regression tests: template dotted-extension match (storage), share
edit-preservation, and undo-delete via the system-session overload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: resolve created document by name with an ambiguity guard instead of a blind folder-diff (boubaker review, MCP-only) - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: adapt DocumentMcpTool to the new UploadToolUtils.ImageSource API

mcp-server's develop refactored UploadToolUtils.resolveImage, replacing the
four loose imageUrl/imageBase64/attachmentObjectType/attachmentObjectId
parameters with a single ImageSource record. Now that feature/ai-contribution
tracks that version, the old eight-argument calls no longer compile. Wrap the
four values in ImageSource at both call sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bmestrallet added a commit that referenced this pull request Aug 1, 2026
…lic link, shortcuts, favorites) - EXO-88456 (#2001)

* feat: Relocate Document MCP tools into the Documents add-on

Move the generic document MCP tools out of the enterprise distribution
into the open Documents add-on so they ship in all editions.

- Add DocumentMcpTool (@service @Profile("mcp-server")) exposing the 9
  document tools (get_root_folder_by_space, attach_document_to_content,
  update_document_description, get_documents_by_folder_id,
  get_root_folder_for_user, get_document_by_id, get_document_content_by_id,
  get_document_transcription_by_id, search_documents) as the current user.
- get_document_transcription_by_id is now a plain read of the stored
  transcription (DocumentFileService.getAudioTranscription) with no
  ai-agent-service coupling; on-demand AI transcription stays enterprise.
- Add documents-services ai-tool-definitions.json with the 9 tool entries.
- documents-services: add mcp-server-tools (provided), ecms-core-services
  (AttachmentService), notes-service (NoteService) and the -parameters
  javac flag required for MCP argument binding by name.

The AI-experience layer (UX bindings, curated prompts, auto-transcription
listener) intentionally remains in the enterprise edition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: Move Document MCP model records into the Documents add-on

The relocated DocumentMcpTool imported DocumentModel / DocumentFileModel /
DocumentFolderModel from org.exoplatform.documents.model, but those three
records were left behind in the enterprise edition, so documents-services
failed to compile ("cannot find symbol").

Bring them into the add-on under org.exoplatform.documents.mcp.model
(co-located with the tool, mirroring the poll add-on's io.meeds.poll.mcp.model
layout) instead of splitting the org.exoplatform.documents.model package
across the api and services jars. Update the imports in DocumentMcpTool
accordingly.

Add DocumentMcpToolTest (13 cases, JUnit 4 like the rest of the module) so the
new tool methods keep the module above its 0.55 coverage gate.

No AI (io.meeds.ai / ai-agent-service) dependency is introduced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: Add v1 batch of Documents MCP tools (folders, versions, content management)

Extend DocumentMcpTool with 15 new MCP tools acting as the current user
(ACL enforced by DocumentFileService):

Reads: list_folder_children, get_folder_breadcrumb, get_folder_tree,
list_document_versions, get_documents_size.

Writes (require_approval): create_folder, rename_document, move_document,
copy_document, duplicate_document, delete_document (trash only, delay=0),
undo_delete_document, restore_document_version, update_version_summary,
set_document_visibility.

Adds thin model records (BreadcrumbItemModel, DocumentVersionModel,
DocumentTreeItemModel, DocumentsSizeModel), matching ai-tool-definitions.json
entries, and DocumentMcpToolTest coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: create_folder under root folder fails in Documents MCP tools

The JCR storage createFolder/getBreadcrumb resolve the parent from the
node id (folderId) and treat 'folderPath' as a RELATIVE sub-path from
that node (node.getNode(folderPath) / getNodeByPath). DocumentMcpTool
was passing the parent node's ABSOLUTE JCR path, so creating a folder
under the user's ROOT folder failed with
ObjectNotFoundException: Folder with path : /Users/.../Private isn't found.

Pass null as folderPath in createFolder and getFolderBreadcrumb since
the folder id already identifies the JCR node uniquely. move/delete keep
passing absolute paths (the storage uses them as absolute JCR paths).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: Add create_document/upload_document MCP tools using UploadToolUtils - EXO-88354

create_document turns chat-authored text/markdown/HTML into a real DMS file;
upload_document imports a binary file from base64 or an SSRF-guarded URL fetch.
Both stage the bytes via UploadToolUtils.materialize, wrap them in a one-entry
zip (importFiles unzips), import into the target folder as the current user, and
poll the folder to return the created document. Pins mcp-server-tools to the
ai-contribution build that ships UploadToolUtils.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: upload_document accepts chat attachments + create_document honors mime_type - EXO-88354

upload_document now declares attachment_object_type/attachment_object_id so
EVA forwards a chat-attached file/image; the bytes are resolved server-side as
the current user (ACL enforced) via UploadToolUtils.resolveImage's attachment
branch (no image-only constraint), keeping the existing base64/url paths.

create_document forces the requested mime_type on the stored file via a new
DocumentFileService/Storage updateDocumentMimeType (JCR jcr:content/jcr:mimeType),
fixing .md files that importFiles left as application/octet-stream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* revert: drop JCR mime-forcing in create_document; content-type follows file extension - EXO-88354

The prior commit forced the requested mime_type onto the stored file via a
new DocumentFileService/Storage updateDocumentMimeType that opened a JCR
session and set jcr:content/jcr:mimeType. Revert that JCR plumbing entirely:
create_document's stored content type now follows the file extension
(resolved by the platform MimeTypeResolver), the pre-fix behavior. The
mime_type param is kept but is advisory (extension inference only). The
upload_document chat-attachment support is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: use renamed UploadToolUtils.FetchedContent - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: clear error messages for create_document/upload_document bad input - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: drop mime_type from create_document; require a filename with extension - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: add version, share, public-link, shortcut and favorite Documents MCP tools

Adds six new MCP tools to DocumentMcpTool, each acting as the current user
(ACLs enforced) with a matching ai-tool-definitions.json entry and tests:

- add_document_version: uploads updated content onto an existing file as a new
  version via DocumentFileService.createNewVersion (checkout -> set jcr:content
  -> checkin/VersionHistoryUtils.createVersion), from text/base64/url/chat
  attachment, with an optional version summary stamped on the new current
  version.
- create_public_link: PublicDocumentAccessService.createPublicDocumentAccess,
  returning the /portal/download-document/{nodeId} shareable link.
- create_document_shortcut: DocumentFileService.createShortcut into a resolved
  destination folder path.
- share_document: DocumentFileService.shareDocument with a user or a space.
- favorite_document / unfavorite_document: social FavoriteService with the
  "document"/"folder" object types.

All writes require approval and pre-check edit permission where applicable.
Skipped by design: list_trash (admin-only global trash, no per-user scope),
share permission levels (service exposes no clean granular API), and
create_document_from_template (still no ECMS service method; tracked on #88422).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: correct integration bugs in Documents MCP tools (conflict values, copy/duplicate return, favorites type, filters, size, share permissions, undo-delete, import conflict) - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: add create_document_from_template MCP tool (delegates to ecms DocumentService) - EXO-88354

Adds a Documents MCP tool that creates a new empty office document
(Word/Excel/PowerPoint, + ODF when the editor add-on provides it) from the
platform's blank template, by delegating to the ecms DocumentService rather
than re-implementing template/office logic.

- documents-storage-jcr: inject DocumentService, add
  createDocumentFromTemplate mirroring createFolder (session/parent/ACL),
  resolve the template by matching NewDocumentTemplate.getExtension against
  the requested type, then call ecms createDocumentFromTemplate.
- documents-api / documents-services: propagate the new storage/service
  signatures.
- DocumentMcpTool.createDocumentFromTemplate + ai-tool-definitions entry
  (require_approval, document_type enum). ecms does NOT append the extension,
  so the tool builds a title that ends with '.<type>'. Softened
  create_document's office-format rejection to point at the new tool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: re-verify follow-ups — template extension match, share no-downgrade, undo-delete system session, favorite docs - EXO-88354

Fix 1: create_document_from_template never matched a template because
onlyoffice stores extensions with a leading dot (".docx") while the caller's
type is dot-stripped ("docx"). Normalize the dot on both sides of the compare
in JCRDocumentFileStorage. Trim supported types to docx/xlsx/pptx (the 3 the
editor add-on actually ships) in DocumentMcpTool and ai-tool-definitions.json.

Fix 2: share_document silently downgraded an existing editor to read because
it always appended (recipient,"read") after the preserved (recipient,"edit"),
and storage writes permissions in list order. Only add the read grant when the
recipient is not already a collaborator.

Fix 3: undo_delete_document resolved the trashed node via the user-session
overload, but trash lives under a system session, so it false-negatived with a
misleading "permanently deleted" error. Use the system-session getDocumentById
overload.

Fix 4: favorite/unfavorite descriptions and Javadoc advertised "file or
folder" though folders are rejected. Correct to files-only wording.

Adds regression tests: template dotted-extension match (storage), share
edit-preservation, and undo-delete via the system-session overload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: resolve created document by name with an ambiguity guard instead of a blind folder-diff (boubaker review, MCP-only) - EXO-88354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: adapt DocumentMcpTool to the new UploadToolUtils.ImageSource API

mcp-server's develop refactored UploadToolUtils.resolveImage, replacing the
four loose imageUrl/imageBase64/attachmentObjectType/attachmentObjectId
parameters with a single ImageSource record. Now that feature/ai-contribution
tracks that version, the old eight-argument calls no longer compile. Wrap the
four values in ImageSource at both call sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants