Add optional LMS MCP write tools - #122
Conversation
Give AI clients a package-local stdio server to create and edit Course → Lesson → Step data from hosted YouTube/Vimeo URLs, with new courses private by default. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the MCP Unreleased entry and the released v4.7.7 dashboard changelog. Co-authored-by: Cursor <cursoragent@cursor.com>
Require isLmsAdmin for authenticated MCP writes, guard tenant context when tenancy is enabled, prefix auto external_ids that start with a digit, and assert on JSON-safe video fields in LmsMcpTest. Co-authored-by: Cursor <cursoragent@cursor.com>
Assert on the video id and provider name instead of exact JSON key spacing. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 51e1dc8. Configure here.
| $videoName = filled($input['video_name'] ?? null) ? (string) $input['video_name'] : $name; | ||
| $url = $this->resolveVideoUrl((string) $input['video_url']); | ||
|
|
||
| $this->assertUniqueStepSlug($slug); |
There was a problem hiding this comment.
Step slugs collide across courses
Medium Severity
Auto-generated step slugs are only {lesson-slug}-{name} and then checked for global uniqueness. A second course with similarly named lessons and steps fails that check, so create_video_course rolls back even though learner URLs are scoped by course slug.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 51e1dc8. Configure here.
| 'name' => $name, | ||
| 'slug' => filled($validated['slug'] ?? null) ? (string) $validated['slug'] : Str::slug($name), | ||
| 'order' => $validated['order'] ?? $this->nextLessonOrder($course), | ||
| ]); |
There was a problem hiding this comment.
Lesson order argument is ignored
Medium Severity
create_lesson accepts order and Lesson::create sets it, but Lesson uses Spatie’s SortableTrait with default sort_when_creating. That creating hook overwrites the column with the global max plus one, so the requested (and per-course) order never sticks.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 51e1dc8. Configure here.
|
|
||
| if (isset($validated['video_url']) || isset($validated['video_name'])) { | ||
| $video = $step->material instanceof Video ? $step->material : null; | ||
| $url = isset($validated['video_url']) ? $this->resolveVideoUrl((string) $validated['video_url']) : $video?->url; |
There was a problem hiding this comment.
Step update commits before video validation
Medium Severity
update_step writes name, slug, lesson, and text first, then validates video_url. An invalid URL returns an error after those fields are already saved, so the tool can leave a partial update with no transaction to roll back.
Reviewed by Cursor Bugbot for commit 51e1dc8. Configure here.
| 'material_id' => $video->id, | ||
| 'material_type' => 'video', | ||
| ]); | ||
| } |
There was a problem hiding this comment.
Video update orphans existing materials
Medium Severity
When update_step receives a video_url on a non-video step, it creates a Video and retargets material_id / material_type without removing the previous material. Tests, documents, and other morph records are left orphaned and the step type changes in place.
Reviewed by Cursor Bugbot for commit 51e1dc8. Configure here.


Summary
filament-lms) with write tools for Course → Lesson → video Step.create_video_coursebuilds a nested private course from YouTube/Vimeo URLs; granular list/get/update/delete tools cover later edits.composer require laravel/mcp. The package auto-registers stdio only; web + Sanctum stays in the host app.Test plan
vendor/bin/pest tests/Feature/LmsMcpTest.phpphp artisan mcp:start filament-lmsworks in a host withlaravel/mcpfilament-lms.mcp.enabled = falseskips local registrationMade with Cursor