* Add workspace MCP settings and token access controls. Ship MCP settings UI, OAuth revoke/list helpers, Passport deploy wiring, and workspace.token:mcp gating so assistants can connect without pulling in welcome/onboarding from the parent epic. Co-authored-by: Cursor <cursoragent@cursor.com> * Type MCP client config shapes instead of string checks. Encode http/config-root on each advanced client and tighten primary client ids so snippet generation does not branch on magic strings. Co-authored-by: Cursor <cursoragent@cursor.com> * Polish MCP settings follow-ups from review. Translate Ukrainian MCP copy, deep-link ChatGPT into connector creation, drop an unused asset and revoke arg, and assert PATs are rejected on the MCP endpoint. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden MCP connected clients, revoke scope, and OAuth consent. List recoverable sessions with live refresh tokens, revoke only PATs, throttle registration alone, and block viewers from authorizing MCP. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify MCP OAuth route throttling to a single middleware group. Co-authored-by: Cursor <cursoragent@cursor.com> * Allow workspace viewers read-only MCP access with web policy writes. Mirror the web app: MCP connects on view + OAuth mcp:use, write tools enforce createPost/update/delete/manageAccounts/manageTeam, and demotion to Viewer keeps grants. Cover role denials, consent, and disconnect. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden MCP tool authz with shared workspace helpers. Route ApiKey tools through AuthorizesMcpTool, fail closed on null user or policy argument, and resolve the current workspace before mutating. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop redundant string casts on validated request data. Enum::from and validated() fields are already strings, so the casts add noise without changing behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * Show only the current user's MCP connections in settings. Match API keys privacy: list and disconnect your own OAuth clients, not teammates' across the account. Co-authored-by: Cursor <cursoragent@cursor.com> * Cover LoadWorkspaceFromToken gaps and harden AuthorizesMcpTool tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop redundant is_string guard before UpdatePostTool find. Co-authored-by: Cursor <cursoragent@cursor.com> * Refactor AppSidebar to always show MCP link and simplify route middleware definition in ai.php. The MCP link is now consistently displayed regardless of the current workspace state, and the route middleware syntax has been streamlined. * Refresh MCP connected clients with Inertia usePoll. Co-authored-by: Cursor <cursoragent@cursor.com> * Bump laravel/mcp to 0.9.1 and add the TryPost server icon. Requires laravel/boost 2.5 for the Icon attribute; expose images/trypost/icon.png on TryPostServer. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop no-op ReflectionClass import in TryPostServerTest. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
Detection Checklist
Every dimension here is a genuine fork: Laravel offers two or more valid approaches, the app's choice changes what the next agent writes, and no active project tool can pick for you. Left out on purpose: pure formatting (Pint owns it), any form an installed and enabled Rector rule rewrites to one canonical shape ($casts to casts(), $fillable to attributes, pipe-string rules to arrays, named to anonymous migrations, $signature to #[Signature]), and framework defaults any agent writes unprompted (ShouldQueue jobs, relation return types, HasFactory).
Each item gives the fork, then a hint (a grep or dir to spot which side the app takes). Hints are only a start. Read the matched files, never record on a raw count. Apply the ground rules to every verdict: a consistent choice that is a default or a tool's target form is not a pattern. Rows tagged (architecture) are the highest-signal, so record presence and deliberate absence.
A. Validation & HTTP input
- Validation entry point: inline
$request->validate()vs Form Request classes vsValidator::make().- Hint:
ls app/Http/Requests; grep->validate(/Validator::make(inapp/Http/Controllers.
- Hint:
- Custom rule location: invokable rule objects in
app/Rulesvs inline closures vsValidator::extend()in a provider. Rule objects are the defaultmake:rulepath, so record only if the app leans on closures orValidator::extendinstead. "No rule objects" alone is just no-signal.- Hint:
ls app/Rules; grepValidator::extendinapp/Providers.
- Hint:
- Typed input retrieval: typed getters (
$request->string(),->integer(),->enum(),->date()) vs raw$request->input()/ dynamic properties.- Hint: grep
->string(/->integer(/->enum(vs->input(inapp/Http.
- Hint: grep
- Custom messages/attributes:
lang/*/validation.phpvs Form Requestmessages()/attributes()methods.- Hint:
ls lang; grepfunction messages,function attributesinapp/Http/Requests.
- Hint:
B. Controllers & routing
- Controller shape: invokable single-action (
__invoke) vs resource controllers vs plain multi-method.- Hint: grep
__invokein controllers;Route::resource/apiResourcevs verb routes.
- Hint: grep
- Business-logic location (architecture): fat controllers vs delegated to Actions / Services / Jobs.
- Hint: read a few controller methods;
ls app/Actions app/Services.
- Hint: read a few controller methods;
- Route handler style: closures in
routes/*.phpvs controller classes.- Hint: count
function ()vs::classinroutes/web.php,routes/api.php.
- Hint: count
- Middleware assignment: route/group
->middleware()vs controllerHasMiddleware::middleware()vs#[Middleware]attribute.- Hint: grep
implements HasMiddleware,#[Middleware(in controllers vs->middleware(in routes.
- Hint: grep
- Route model binding: implicit (type-hinted models) vs explicit
Route::bindvs manualfindOrFail.- Hint: typed model params in signatures vs
findOrFail(in controllers; grepRoute::bind.
- Hint: typed model params in signatures vs
- Rate limiting: named
RateLimiter::for()+throttle:namevs inlinethrottle:60,1.- Hint: grep
RateLimiter::forin providers vsthrottle:in route files.
- Hint: grep
C. Authorization
- Authorization home: Gates (
Gate::define) vs Policy classes inapp/Policies.- Hint:
ls app/Policies; grepGate::defineinapp/Providers.
- Hint:
- Authorization call site:
$this->authorize()/Gate::authorize()vs$user->can()vscanmiddleware vs#[Authorize]vs@canin Blade.- Hint: grep
authorize(,->can(,middleware('can:,#[Authorize(,@can(.
- Hint: grep
D. Eloquent & models
- Mass assignment:
$fillableallow-list vs$guardedblock-list.- Hint: grep
protected $fillable/protected $guardedinapp/Models.
- Hint: grep
- Accessors/mutators: modern
Attributeclass vs legacygetXxxAttribute()/setXxxAttribute(). Record a legacy hold, it goes against the tool's grain.- Hint: grep
: Attribute/Attribute::makevsfunction get[A-Z].*Attributeinapp/Models.
- Hint: grep
- Primary keys: auto-increment vs
HasUuidsvsHasUlids.- Hint: grep
HasUuids/HasUlidsinapp/Models; migrationid()vsuuid('id').
- Hint: grep
- Custom casts: dedicated
CastsAttributesclasses (app/Casts) vs inlineAttributevs built-in cast strings.- Hint:
ls app/Casts; grepCast::class,AsStringable::classin models.
- Hint:
- Data/query layer (architecture): Eloquent directly in controllers vs repositories vs dedicated query objects (e.g. classes exposing
builder(): Builder).- Hint:
ls app/Repositories app/Queries; see where non-trivial queries are built.
- Hint:
- Query scopes: local
scope/#[Scope]methods vs dedicated builder classes.- Hint: grep
function scope/#[Scope]in models;ls app/*/Builders.
- Hint: grep
- Model events: observers (
app/Observers,#[ObservedBy]) vsbooted()closures vs event classes.- Hint:
ls app/Observers; grepbooted,::observe,#[ObservedBy].
- Hint:
- Eager-load posture: explicit per-query
->with()vs model-level$withdefaults. TreatpreventLazyLoading()separately as a development guard because it can complement either posture.- Hint: grep
protected $with,->with(, and separatelypreventLazyLoadinginapp/.
- Hint: grep
E. Architecture & organization
- Action/Service structure (architecture): Action classes (invoked via
handle/execute/__invoke) vs service objects vs neither. Cross-check the Step 0app/map: anyActions/Services/Pipelines/Jobs-as-actions folder is this pattern, so record how it is invoked.- Hint:
ls app/(the whole tree, not justActions/Services); grep the invocation method in the folder you find.
- Hint:
- DTOs (architecture): spatie/laravel-data vs plain readonly classes vs arrays everywhere.
- Hint:
ls app/Data; grepextends Data,readonly classinapp/.
- Hint:
- Dependency acquisition: constructor/method injection vs
app()/resolve()/App::make()service location.- Hint: grep
app(/resolve(/::make(inapp/vs promoted constructor deps.
- Hint: grep
- Decoupling: events + listeners vs direct service calls.
- Hint:
ls app/Events app/Listeners; grepevent(,::dispatch(.
- Hint:
- Helper vs facade idiom: global helpers (
config(),auth(),response()) vs facades (Config::,Auth::,Response::).- Hint: ratio of
config(vsConfig::(etc.) acrossapp/.
- Hint: ratio of
- Namespace layout (architecture): default
app/skeleton vs domain/module folders (app/Domain/**, modules).- Hint:
ls app/, look forDomain/,Modules/, bounded-context folders.
- Hint:
- Enums: backed vs pure; case naming; where they live.
- Hint:
ls app/Enums; grepenum .*: string,enum .*: int.
- Hint:
F. Frontend & views
This app ships a frontend stack, so the items below apply.
- Frontend stack: Blade+Livewire vs Inertia (Vue/React/Svelte) vs Blade-only / API + separate SPA.
- Hint:
composer.json+package.json;ls resources/js/pages,resources/views.
- Hint:
- Blade composition: class
<x-*>components vs anonymous components (@props) vs@includepartials.- Hint:
ls app/View/Components; grep<x-,@includeinresources/views.
- Hint:
- Localization: short keys (
lang/*/*.php+__('messages.welcome')) vs JSON string keys (lang/*.json+__('Full sentence')).- Hint:
ls lang; grep dotted__('vs sentence keys.
- Hint:
G. Database & migrations
- Foreign keys:
foreignId()->constrained()vsforeignIdFor(Model::class)vs manualforeign()->references()->on().- Hint: grep
foreignId(,foreignIdFor(,->foreign(indatabase/migrations.
- Hint: grep
down()methods: real reverse logic vs omitted / one-way migrations.- Hint: grep
function downvs the migration count.
- Hint: grep
- Enum storage: DB
enum()column vsstring()+ PHP-enum cast on the model.- Hint: grep
->enum(in migrations vs string columns cast to enums.
- Hint: grep
- Transactions:
DB::transaction(fn ...)closure vs manualbeginTransaction/commit/rollBack.- Hint: grep
DB::transaction,beginTransactioninapp/.
- Hint: grep
- Idempotent writes:
upsert/updateOrCreate/firstOrCreatevs find-then-save.- Hint: grep
upsert(,updateOrCreate(,firstOrCreate(inapp/.
- Hint: grep
H. Testing
- Framework: Pest (
it()/test()/expect()) vs PHPUnit classes.- Hint:
ls tests/Pest.php; grepit(/test(vsextends TestCase.
- Hint:
- DB reset:
RefreshDatabasevsDatabaseTruncationvsDatabaseMigrations.- Hint: grep those trait names in
tests/.
- Hint: grep those trait names in
- Fixtures: compare how equivalent test-owned records are created, such as factories vs manual inserts. Track seeders separately for shared reference data because
$this->seed()commonly and legitimately coexists with factories.- Hint: grep
::factory(and direct inserts intests/; separately inspect$this->seed(calls and what those seeders provide.
- Hint: grep
- Collaborator isolation: how the app doubles its own classes, Mockery
mock()/spy()vs real integration. Ignore facade fakes likeMail::fake()here, they isolate framework services by default and are not a fork against Mockery.- Hint: grep
->mock(,->spy(,Mockery::intests/.
- Hint: grep
- Endpoint assertions: array
assertJson([...])/assertJsonFragmentvs fluentAssertableJson.- Hint: grep
AssertableJson,assertJsonFragmentintests/.
- Hint: grep
I. Responses & API resources
- Response shape: API Resource classes vs
response()->json()vs returning models/arrays directly.- Hint:
ls app/Http/Resources; grepJsonResource,->json(in controllers.
- Hint:
- Resource relationship inclusion:
whenLoaded()guards vs unconditional relationship access. Do not count ordinary scalar attributes as rivals to conditional relationships, and evaluate generalwhen()fields separately.- Hint: compare relationship fields using
whenLoaded(with unconditional relationship property access inapp/Http/Resources.
- Hint: compare relationship fields using
- Pagination contracts: within comparable endpoint categories, length-aware
paginate()vssimplePaginate()vscursorPaginate(). These have different totals, navigation, ordering, and performance contracts, so record only a stable path-scoped API policy, never a project-wide majority.- Hint: grep those in
app/, then group matches by endpoint type and client contract before comparing them.
- Hint: grep those in
- Web redirects/URLs:
route('name')vsurl('/path')vsaction([...]).- Hint: grep
route(',url('/,action([inapp/Httpand views.
- Hint: grep
J. Strings, collections & dates
- Iteration idiom:
collect()->map()->filter()pipelines vsarray_map/foreach.- Hint: grep
collect(,->map(vsarray_map,foreachdensity inapp/.
- Hint: grep
- String API: fluent
Str::of()->...(Stringable) vs staticStr::vs native (trim,strtoupper).- Hint: grep
Str::of(vsStr::vs native string funcs.
- Hint: grep
- Dates: compare equivalent construction call styles (
now()/today()helpers vsCarbon::) separately from the application's mutable/immutable date policy.Date::use(CarbonImmutable::class)can make helpers return immutable dates, so those signals are complementary rather than conflicting.- Hint: grep
now(andCarbon::for call style; separately inspectCarbonImmutableandDate::usefor mutability policy.
- Hint: grep
Genuine forks only. Every row survived the "no tool can decide this, and it isn't the default" filter. Give each applicable dimension exactly one verdict: pattern, conflict, default, no-signal, tooling-owned, or already-recorded. The rows tagged (architecture) are where the highest-value rules come from.