Repository navigation
MCP integration: laravel/mcp, shared entry title rule and AI assistant MCP tests - #668
Draft
uldisrudzitis wants to merge 17 commits into
Draft
uldisrudzitis wants to merge 17 commits into
uldisrudzitis wants to merge 17 commits into
Conversation
laravel/mcp moves from a dev-only dependency to require, so production installs can serve the AI assistant plugin's MCP endpoint. Adds tests for the MCP endpoint auth (site JWT in X-Authorization), the list-sections tool, SiteContentReader and the chat prompt's sections text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
SectionEntryRenderService::showsEntryTitles() holds the rule "entry titles are shown on the default template and in portfolio sections", used by the renderer and the AI assistant plugin. Tests cover the chat's entries tool output, SiteContentReader entries (title rule, unknown and ../ section names) and the list-entries tool. New linkBertaEngine() and writeTemplate() test helpers; the shop test root now uses linkBertaEngine() too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins the chat's GetEntryGalleryTool output, covers SiteContentReader::entryGallery() (untyped files, entries without a gallery, unknown or ../ sections and entries) and the get-entry-gallery tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins the GetEntryContentTool output on the default template, checks that titles are left out on templates without entry titles, and that entryContent() matches the entry list and rejects unknown or ../ sections and entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…text Covers SiteContentReader::siteSettings() (titles, unknown keys, list items, internal group left out, siteTexts kept) and the get-site-settings tool. A snapshot pins the AI chat's current site settings prompt text ahead of moving it onto the shared reader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
phpunit.xml and Pest pick up app/Plugins/*/tests, so the AI assistant plugin's tests move to its repository. Core gets its own test for the entry title rule, which only the plugin's tests covered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
givePlanFeatures() puts a test website on a hosting plan with the given features and limits, as UserModel reads them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor's settings and design endpoints saved any value they were sent. They now check it against the setting's definition with the editor's own rules (allowed values, colors, CSS units, blank values, validation kinds) and the plan feature a setting needs, and answer 422 with error_message otherwise. Entries of list settings are left as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The specs used pointerId 1, the real mouse's id in Chrome, so moving the mouse over a visible test page could join a synthetic drag and, rarely, stop the auto-scroll spec from scrolling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every editor API response carries the website's latest change (ChangeMarker, in Laravel's cache). A save from an editor window that missed a change made elsewhere - in another window or by an AI agent - is refused with 409, and the editor asks to reload, also when the tab comes back into view. Login, logout and an empty marker never ask. The session token is renewed at most hourly while the editor is used, keeping its claims, for up to 30 days after login and only while the account hasn't changed since (password change, website transfer). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks done
# Conflicts: # _api_app/tests/Pest.php
Removes script elements, event handler attributes, iframe documents and javascript:/vbscript: links, as the editor's rich text field does, and keeps everything else (forms, embeds, styling). HTML with nothing to remove is returned unchanged. Used for HTML that AI agents write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…renamed or deleted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the berta.me MCP integration. AI agents (Claude, ChatGPT, Cursor…) connect to the hosting app's MCP gateway (berta-cms/berta-hosting#42), which forwards site requests to each site's own MCP server. That server lives in the AI assistant plugin (companion PR in
berta-cms/ai-assistant-plugin, branchmcp-integration).Draft: the
mcp-integrationbranch keeps growing until the integration is finished.Changes
laravel/mcp^1.0 moves torequire(v1.1.1). It was only a dev dependency, so production installs couldn't serve the plugin's MCP endpoint.SectionEntryRenderService::showsEntryTitles($templateName, $sectionType). Titles show on thedefaulttemplate and inportfoliosections. The renderer now uses it (rendering is unchanged), and so does the plugin'slist-entriestool.SectionEntryRenderService::acceptsEntries($sectionType): every section type exceptexternal_linkandshopping_cart. The editor's "Move entry to" list now uses it (the list is unchanged), and so do the plugin'screate-entryandmove-entrytools.phpunit.xmlhas aPluginstest suite forapp/Plugins/*/tests, andtests/Pest.phpbindsTestCasethere too. So each plugin ships its tests, and an install without plugins simply has none (checked: the core suites run on their own without warnings).SiteContentReader, chat tools and prompt, quota) moved to its repository, together with the chat's settings prompt snapshot.givePlanFeatures()(intests/Pest.php) puts a test website on a hosting plan with given features and limits, asUserModelreads them. The plugin's multisite and AI chat tests use it.linkBertaEngine()andwriteTemplate()stay intests/Pest.php: they set up a temporary Berta root, and plugin tests use them.setUpShopTestRoot()now useslinkBertaEngine().mcp-integrationbranch makes itssetUpShopTestRoot()use this branch'slinkBertaEngine(), so it must be merged together with this PR.SiteSettingsController::update(),SiteTemplateSettingsController::update()) saved any value they were sent, so a crafted request could store a value outside a select's list, or a setting that needs a plan feature the website doesn't have (e.g. thecustom_javascriptHTML fields).App\Configuration\SettingValueValidatorchecks a value against its definition with the editor's own rules (validation.service.ts,setting.component.ts): allowed values of select and font settings, colors (hex,rgb(), and thergba()the color picker saves), blank values unlessallow_blank, CSS units and thevalidationkinds, plusrequires_feature. Formats the editor doesn't validate (url, longtext, richtext, files, editor actions) take any value.error_message, the editor's existing error shape: it logs the error and keeps the field as it was. Entries of list settings and paths without a definition are saved as before.App\Shared\ChangeMarkerkeeps the website's latest change (a token, the editor window that made it, and its source) in Laravel's cache, not in the site'sstorage/folder that backups and transfers copy. It never expires. A cache that doesn't work (e.g. the first request after updating an install from beforeCACHE_STORE) turns the checks off instead of breaking the editor.EditorSyncmiddleware on the editor's route groups: every response carries the latest change (X-Berta-Change,-Writer,-Source). A save from a window (X-Berta-Editor, hex id) that hasn't seen a change someone else made since is refused with 409. A window's own quick saves pass. Successful saves mark a change; responses witherror_messagedon't. Password change and theme preview opt out, since they change no site data. When someone else marks a change while a save runs, the window is told about it, so it reloads instead of missing it.GET v1/change-markerfor the editor's check when a tab comes back into view.shared/editor-sync.ts): an interceptor sends the window id and the last seen change to the editor API only (same origin), and reads the answers. A change by someone else shows a modal warn popup, "This website was changed by an AI agent / in another editor window. Reload to see the changes.", that comes back if another popup replaces it. Login and logout start over (the login loads all data fresh), and an empty marker (e.g. a cleared cache) never asks. The 409 skips the generic error toast.App\User\EditorTokenissues the token (used byAuthController).RenewEditorTokenmiddleware renews a valid token older than an hour, at most hourly, and returns it inX-Berta-Token(withCache-Control: no-store) and in thetokencookie. The editor stores it (UserTokenRenewedAction).uid, and are signed with the same key, so hosting-issued and renewed tokens stay interchangeable.auth_time), and none for sessions that started before the account last changed (engine/config/inc.conf.phpis rewritten by a password change, and by hosting on a website transfer or email change). So a previous owner's or a stolen session still ends within 24 hours, as before.App\Shared\HtmlCleanerremoves what can run scripts: script elements (also in SVG),on…event attributes, iframesrcdocandjavascript:/vbscript:links (also disguised with tabs or entities). It keeps everything else, as the editor's rich text field (TinyMCE) does: forms and PayPal buttons, iframe embeds, styling. HTML with nothing to remove is returned unchanged. Built on PHP 8.4's own HTML5 parser, no new dependency. The AI plugin cleans the HTML agents write with it; the editor's own endpoints still trust the editor's client-side cleaning and could adopt it later.<name><id>; before, the names grew with every entry (works1,works12,works123…). A name that exists or another gallery refers to is skipped, so two entries never end up sharing a folder (renaming onto an empty folder used to replace it). The background folder keeps its old name when it can't be renamed, instead of the section pointing at a folder that doesn't exist.works-1with all its folders.Storage::mediaFoldersReferencedOutside()), so folders that older sites already share never lose files.copyFolder()reports failures. An emptymediafolderno longer makes a move or a section copy duplicate the whole media folder. A failed section copy returns the editor's usualerror_messageinstead of a server error.tests/Unit/SectionEntryRenderServiceTest.phpcoversshowsEntryTitles(), which only the plugin's tests covered before, andacceptsEntries().Merge order
Merge this first, then the plugin PR, because the plugin needs
laravel/mcpinstalled and its tests need thePluginstest suite. Merge the shop plugin'smcp-integrationbranch (Bitbucketberta-shop-laravel-plugin) at the same time, since its tests call this branch'slinkBertaEngine().Notes
Test plan
php artisan test --compactpasses with the plugins installed: 613 tests, none skippedng test), production build succeedstests/Feature/EditorSyncTest.php(checks, own saves, agent and other-window changes, no marker, failing cache, invalid window ids, refused saves, opted-out routes, setup wizard, changes during a save),tests/Feature/RenewEditorTokenTest.php(renewal, claims kept, 30-day limit, account change, no-store),shared/editor-sync.spec.tstests/Feature/MediaFoldersTest.php(copying a section with a folder not named after it, moving, a failed move keeping the entry, renaming, taken names, a title case change, the background, and deletes keeping shared folders); 8 of its 10 tests fail on the code before the fixtests/Unit/HtmlCleanerTest.php(what's removed, and real entry HTML with embeds and shop forms kept unchanged)tests/Unit/SettingValueValidatorTest.php(rules per format) andtests/Feature/SettingsValidationTest.php(both endpoints save valid values, refuse invalid and missing-feature values with 422 without writing, and still save list entries)http://berta.test→list-sections,list-entries,get-entry-gallery,get-settingsandget-design-settingsreturn the website's content. On themessytemplate, entries come back withouttitleand withentry_titles: false.🤖 Generated with Claude Code