trypost/app/Models/AccessToken.php

261 lines
7.4 KiB
PHP
Raw Permalink Normal View History

<?php
declare(strict_types=1);
namespace App\Models;
Activation checklist + MCP OAuth authorize UX (#239) (#250) * Wire onboarding activation into Account, observers, and shared Inertia data Add onboarding casts/hasFinishedOnboarding, AccessToken ObservedBy, Platform::connectableOptions, Post/SocialAccount onboarding broadcast hooks, and lazy onboardingResidual share + SharedData types. * Register onboarding routes and post-checkout activation redirects. Wire billing processing and the sidebar checklist so owners land on activation after subscribe, with locale sidebar/uk onboarding strings. * Align MCP grant usability with onboarding activation checks Unbound MCP tokens fall back to the user's current workspace and require createPost so viewer/unscoped grants neither unlock the checklist nor broadcast onboarding status. * Require bound MCP workspace for onboarding activation. Drop current-workspace fallback from usable MCP grants so checklist detection and broadcasts match Passport token scoping; viewers still cannot unlock the MCP step. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding review findings and tighten locale strings. Fix Welcome/Persona/TrackPost suites broken by the activation route reuse and PostObserver analytics side effects, restore Echo poll fallbacks, reject unbound MCP grants in tests, and drop unused onboarding.mcp keys. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove unused sidebar and MCP authorization locale keys. Drop dead sidebar menu/theme strings (including the overwritten workspace label and api_keys nav entry) and unused MCP authorize app_title/approving copy across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix SetLocale crashing on Passport Symfony OAuth responses. OAuth errors return a raw Symfony Response without withCookie(); attach the default locale cookie via headers so authorize no longer 500s. Co-authored-by: Cursor <cursoragent@cursor.com> * Prompt OAuth guests to log in before rejecting unknown clients. MCP Inspector often reuses a stale client_id; validateAuthorizationRequest was returning invalid_client JSON before the login redirect. Guests now hit /login first, then client validation runs after authentication. Co-authored-by: Cursor <cursoragent@cursor.com> * Render Inertia OAuth authorize errors for browser logins. After login, Inertia follows the intended authorize URL; raw invalid_client JSON broke that visit. HTML/Inertia requests now get mcp/AuthorizeError while API JSON clients still receive the OAuth error payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Detect Inertia OAuth error pages via Request::inertia(). Use the framework helper so post-login authorize failures keep returning an Inertia page instead of raw OAuth JSON. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify OAuth authorize error page detection to expectsJson. Drop the X-Inertia header sniff; browser and Inertia visits already do not expectsJson, while API clients still receive the OAuth JSON payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Share MCP authorize layout and drop the error close button. Keep authorize and authorize-error on the same centered card shell instead of the auth split layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding activation for reviewability and safety. Use an exists-based MCP check, keep GETs read-only, move sync into syncAndNotify, clear MCP skips on connect, restrict complete to owners, and share Echo/poll via one composable. Co-authored-by: Cursor <cursoragent@cursor.com> * Move MCP OAuth authorize UX out of the onboarding PR. Keep the activation checklist focused; OAuth guest/error-page work now lives on fix/mcp-oauth-authorize-ux. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix corrupted French MCP locale after OAuth key cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore MCP OAuth authorize UX onto the onboarding branch. Keep authorize error page, guest login-before-client validation, and SetLocale Symfony cookie fix in #250. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth prompt=none redirects and harden onboarding tests. Keep login_required/consent_required as redirects instead of Inertia, add regression coverage for owner-only activation, require invite email confirmation, and align MCP connected apps with the sessions list UI. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding guards and dedupe viewed analytics. Introduce isOnboardingOpen / belongsToAccount helpers, collapse duplicated sync/dispatch paths, and capture onboarding.viewed once per account. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding event, observers, and status helpers. Tighten Account onboarding predicates, drop nullable broadcast/dispatch APIs, and collapse repeated observer/controller guards. Co-authored-by: Cursor <cursoragent@cursor.com> * Treat in-app users as always having an account. Add resolveAccount(), tighten belongsToAccount to string ids, and fold guest residual handling into ResolveOnboardingStatus. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename onboarding residual share test to progress. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding status and rename residual to progress. Use accountOrFail, extract MCP onboarding scope, auto-leave the ready screen, and send non-onboarding checkout back to accounts. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract HasAccount and prefer data_get in onboarding flows. Move account helpers off User, drop nullable sidebarProgress, and read OAuth/onboarding payloads with data_get. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding checks and extract HasOnboarding. Use Eloquent + policies for MCP/backfill paths, and move account onboarding helpers into a dedicated trait. Co-authored-by: Cursor <cursoragent@cursor.com> * Add trait tests and tidy onboarding imports. Cover HasAccount and HasOnboarding under Models/Traits, prefer filled() for checkout session ids, and import Throwable instead of FQCN. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify checkout session_id and OAuth error props. Read session_id via request->string(), and take OAuth error details from the League exception instead of decoding the response body. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify PostObserver onboarding notify path. Share one otherPosts check for first-create and last-delete instead of separate callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Use post author as onboarding sync actor. Drop Auth::user() preference in PostObserver; checklist sync attributes to $post->user. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify SocialAccountObserver and OAuth authorize flow. Share create/delete onboarding notify, drop Auth actor fallback to owner, and inline Passport Inertia error handling. Co-authored-by: Cursor <cursoragent@cursor.com> * Use lazy Inertia props for onboarding partial reloads. Drop partial-header branching; wrap page props in closures and always redirect completed/dismissed accounts to the calendar. Co-authored-by: Cursor <cursoragent@cursor.com> * Defer sidebar onboarding progress and stamp completion as owner-only. Skip the MCP checklist work on full Inertia visits via deferred shared props, early-exit token scans, and keep account completion stamps owner-gated. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify deferred onboarding progress share via canShowProgress. Co-authored-by: Cursor <cursoragent@cursor.com> * Add User firstName for shared auth and simplify onboarding page. Co-authored-by: Cursor <cursoragent@cursor.com> * Move User firstName coverage into UserTest. Co-authored-by: Cursor <cursoragent@cursor.com> * Use first_name directly without empty-name fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Resolve onboarding sample prompt on the frontend via i18n. Co-authored-by: Cursor <cursoragent@cursor.com> * Stamp onboarding completion via the account owner after teammate unlocks. Co-authored-by: Cursor <cursoragent@cursor.com> * Count only the account owner MCP grant toward onboarding activation. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth consent auth-token mismatch for mid-activation owners. Skip deferred onboardingProgress on Passport authorize so Inertia does not rotate the session authToken, cover happy and stale-token paths in tests, and polish MCP setup copy plus sidebar/onboarding layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep users on onboarding after activation completes. Stamp completion and re-render the finished checklist instead of redirecting to the calendar so owners can review the done state. Co-authored-by: Cursor <cursoragent@cursor.com> * Clarify Passport consent-view opt-out and guard app-route deferral. Rename the authorize-only route check and assert onboardingProgress still defers on calendar, onboarding, and MCP settings. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding completion and MCP consent workspace binding. Reject OAuth approve without a workspace, retry auto-complete until stamped, send dismissed complete straight to calendar, and cover the device consent defer opt-out. Co-authored-by: Cursor <cursoragent@cursor.com> * Enable activation checklist for self-hosted installs. Remove the self-hosted onboarding redirects, keep the SaaS-only dismiss backfill, and cover subscription-less owners plus skip/complete destinations. Co-authored-by: Cursor <cursoragent@cursor.com> * Add GitHub, Hacker News, and directories referral sources. Expand the welcome referral step with open-source and directory discovery channels. Co-authored-by: Cursor <cursoragent@cursor.com> * Refine welcome referral sources and labels. Split Instagram/Threads, add Founder, and shorten Google, GitHub, AI, and blog option labels. Co-authored-by: Cursor <cursoragent@cursor.com> * Sort accounts platforms alphabetically and drop connect hover plus. Reuse connectableOptions for the accounts index and remove the unused plus badge on disconnected cards. Co-authored-by: Cursor <cursoragent@cursor.com> * Centralize PostHog once-capture so disabled installs don't burn dedupe keys. Move isEnabled + Cache::add into PostHogService::captureOnce and route onboarding viewed/step events through it. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding backfill to complete every existing open account. Drop self-hosted and subscription filters; down clears completed_at again. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop PostHog captureOnce and use plain capture for onboarding. Remove cache-based event dedupe; callers rely on PostHogService::capture gating. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 23:34:43 +00:00
use App\Observers\AccessTokenObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
MCP: workspace settings, viewer read access, and token access (#241) * 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>
2026-08-06 12:54:51 +00:00
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Laravel\Passport\Token;
Activation checklist + MCP OAuth authorize UX (#239) (#250) * Wire onboarding activation into Account, observers, and shared Inertia data Add onboarding casts/hasFinishedOnboarding, AccessToken ObservedBy, Platform::connectableOptions, Post/SocialAccount onboarding broadcast hooks, and lazy onboardingResidual share + SharedData types. * Register onboarding routes and post-checkout activation redirects. Wire billing processing and the sidebar checklist so owners land on activation after subscribe, with locale sidebar/uk onboarding strings. * Align MCP grant usability with onboarding activation checks Unbound MCP tokens fall back to the user's current workspace and require createPost so viewer/unscoped grants neither unlock the checklist nor broadcast onboarding status. * Require bound MCP workspace for onboarding activation. Drop current-workspace fallback from usable MCP grants so checklist detection and broadcasts match Passport token scoping; viewers still cannot unlock the MCP step. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding review findings and tighten locale strings. Fix Welcome/Persona/TrackPost suites broken by the activation route reuse and PostObserver analytics side effects, restore Echo poll fallbacks, reject unbound MCP grants in tests, and drop unused onboarding.mcp keys. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove unused sidebar and MCP authorization locale keys. Drop dead sidebar menu/theme strings (including the overwritten workspace label and api_keys nav entry) and unused MCP authorize app_title/approving copy across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix SetLocale crashing on Passport Symfony OAuth responses. OAuth errors return a raw Symfony Response without withCookie(); attach the default locale cookie via headers so authorize no longer 500s. Co-authored-by: Cursor <cursoragent@cursor.com> * Prompt OAuth guests to log in before rejecting unknown clients. MCP Inspector often reuses a stale client_id; validateAuthorizationRequest was returning invalid_client JSON before the login redirect. Guests now hit /login first, then client validation runs after authentication. Co-authored-by: Cursor <cursoragent@cursor.com> * Render Inertia OAuth authorize errors for browser logins. After login, Inertia follows the intended authorize URL; raw invalid_client JSON broke that visit. HTML/Inertia requests now get mcp/AuthorizeError while API JSON clients still receive the OAuth error payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Detect Inertia OAuth error pages via Request::inertia(). Use the framework helper so post-login authorize failures keep returning an Inertia page instead of raw OAuth JSON. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify OAuth authorize error page detection to expectsJson. Drop the X-Inertia header sniff; browser and Inertia visits already do not expectsJson, while API clients still receive the OAuth JSON payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Share MCP authorize layout and drop the error close button. Keep authorize and authorize-error on the same centered card shell instead of the auth split layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding activation for reviewability and safety. Use an exists-based MCP check, keep GETs read-only, move sync into syncAndNotify, clear MCP skips on connect, restrict complete to owners, and share Echo/poll via one composable. Co-authored-by: Cursor <cursoragent@cursor.com> * Move MCP OAuth authorize UX out of the onboarding PR. Keep the activation checklist focused; OAuth guest/error-page work now lives on fix/mcp-oauth-authorize-ux. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix corrupted French MCP locale after OAuth key cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore MCP OAuth authorize UX onto the onboarding branch. Keep authorize error page, guest login-before-client validation, and SetLocale Symfony cookie fix in #250. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth prompt=none redirects and harden onboarding tests. Keep login_required/consent_required as redirects instead of Inertia, add regression coverage for owner-only activation, require invite email confirmation, and align MCP connected apps with the sessions list UI. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding guards and dedupe viewed analytics. Introduce isOnboardingOpen / belongsToAccount helpers, collapse duplicated sync/dispatch paths, and capture onboarding.viewed once per account. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding event, observers, and status helpers. Tighten Account onboarding predicates, drop nullable broadcast/dispatch APIs, and collapse repeated observer/controller guards. Co-authored-by: Cursor <cursoragent@cursor.com> * Treat in-app users as always having an account. Add resolveAccount(), tighten belongsToAccount to string ids, and fold guest residual handling into ResolveOnboardingStatus. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename onboarding residual share test to progress. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding status and rename residual to progress. Use accountOrFail, extract MCP onboarding scope, auto-leave the ready screen, and send non-onboarding checkout back to accounts. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract HasAccount and prefer data_get in onboarding flows. Move account helpers off User, drop nullable sidebarProgress, and read OAuth/onboarding payloads with data_get. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding checks and extract HasOnboarding. Use Eloquent + policies for MCP/backfill paths, and move account onboarding helpers into a dedicated trait. Co-authored-by: Cursor <cursoragent@cursor.com> * Add trait tests and tidy onboarding imports. Cover HasAccount and HasOnboarding under Models/Traits, prefer filled() for checkout session ids, and import Throwable instead of FQCN. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify checkout session_id and OAuth error props. Read session_id via request->string(), and take OAuth error details from the League exception instead of decoding the response body. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify PostObserver onboarding notify path. Share one otherPosts check for first-create and last-delete instead of separate callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Use post author as onboarding sync actor. Drop Auth::user() preference in PostObserver; checklist sync attributes to $post->user. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify SocialAccountObserver and OAuth authorize flow. Share create/delete onboarding notify, drop Auth actor fallback to owner, and inline Passport Inertia error handling. Co-authored-by: Cursor <cursoragent@cursor.com> * Use lazy Inertia props for onboarding partial reloads. Drop partial-header branching; wrap page props in closures and always redirect completed/dismissed accounts to the calendar. Co-authored-by: Cursor <cursoragent@cursor.com> * Defer sidebar onboarding progress and stamp completion as owner-only. Skip the MCP checklist work on full Inertia visits via deferred shared props, early-exit token scans, and keep account completion stamps owner-gated. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify deferred onboarding progress share via canShowProgress. Co-authored-by: Cursor <cursoragent@cursor.com> * Add User firstName for shared auth and simplify onboarding page. Co-authored-by: Cursor <cursoragent@cursor.com> * Move User firstName coverage into UserTest. Co-authored-by: Cursor <cursoragent@cursor.com> * Use first_name directly without empty-name fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Resolve onboarding sample prompt on the frontend via i18n. Co-authored-by: Cursor <cursoragent@cursor.com> * Stamp onboarding completion via the account owner after teammate unlocks. Co-authored-by: Cursor <cursoragent@cursor.com> * Count only the account owner MCP grant toward onboarding activation. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth consent auth-token mismatch for mid-activation owners. Skip deferred onboardingProgress on Passport authorize so Inertia does not rotate the session authToken, cover happy and stale-token paths in tests, and polish MCP setup copy plus sidebar/onboarding layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep users on onboarding after activation completes. Stamp completion and re-render the finished checklist instead of redirecting to the calendar so owners can review the done state. Co-authored-by: Cursor <cursoragent@cursor.com> * Clarify Passport consent-view opt-out and guard app-route deferral. Rename the authorize-only route check and assert onboardingProgress still defers on calendar, onboarding, and MCP settings. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding completion and MCP consent workspace binding. Reject OAuth approve without a workspace, retry auto-complete until stamped, send dismissed complete straight to calendar, and cover the device consent defer opt-out. Co-authored-by: Cursor <cursoragent@cursor.com> * Enable activation checklist for self-hosted installs. Remove the self-hosted onboarding redirects, keep the SaaS-only dismiss backfill, and cover subscription-less owners plus skip/complete destinations. Co-authored-by: Cursor <cursoragent@cursor.com> * Add GitHub, Hacker News, and directories referral sources. Expand the welcome referral step with open-source and directory discovery channels. Co-authored-by: Cursor <cursoragent@cursor.com> * Refine welcome referral sources and labels. Split Instagram/Threads, add Founder, and shorten Google, GitHub, AI, and blog option labels. Co-authored-by: Cursor <cursoragent@cursor.com> * Sort accounts platforms alphabetically and drop connect hover plus. Reuse connectableOptions for the accounts index and remove the unused plus badge on disconnected cards. Co-authored-by: Cursor <cursoragent@cursor.com> * Centralize PostHog once-capture so disabled installs don't burn dedupe keys. Move isEnabled + Cache::add into PostHogService::captureOnce and route onboarding viewed/step events through it. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding backfill to complete every existing open account. Drop self-hosted and subscription filters; down clears completed_at again. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop PostHog captureOnce and use plain capture for onboarding. Remove cache-based event dedupe; callers rely on PostHogService::capture gating. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 23:34:43 +00:00
#[ObservedBy(AccessTokenObserver::class)]
class AccessToken extends Token
{
/**
* @var list<string>
*/
protected $fillable = [
'id',
'user_id',
'client_id',
'workspace_id',
'name',
'scopes',
'revoked',
'expires_at',
'last_used_at',
];
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'scopes' => 'json',
'revoked' => 'bool',
'expires_at' => 'datetime',
'last_used_at' => 'datetime',
];
}
public function workspace(): BelongsTo
{
return $this->belongsTo(Workspace::class);
}
MCP: workspace settings, viewer read access, and token access (#241) * 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>
2026-08-06 12:54:51 +00:00
/**
* Passport resolves the user model via the OAuth client's provider, which
* breaks eager-loading `user` (the relation is built on an empty token with
* no client). Tokens in TryPost always belong to App\Models\User.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Active OAuth grants used by MCP clients (excludes personal access API keys).
*
* @param Builder<static> $query
* @return Builder<static>
*/
public function scopeActiveMcpOAuth(Builder $query): Builder
{
return $query
->mcpOAuth()
->where('revoked', false)
->where(function (Builder $expires): void {
$expires->whereNull('expires_at')
->orWhere('expires_at', '>', now());
});
}
/**
* MCP OAuth grants that still represent a live or recoverable session
* (unexpired access token, or expired access with a live refresh token).
*
* @param Builder<static> $query
* @return Builder<static>
*/
public function scopeConnectedMcpOAuth(Builder $query): Builder
{
return $query
->mcpOAuth()
->where('revoked', false)
->where(function (Builder $alive): void {
$alive
->where(function (Builder $expires): void {
$expires->whereNull('expires_at')
->orWhere('expires_at', '>', now());
})
->orWhereHas(
'refreshToken',
fn (Builder $refresh): Builder => $refresh
->where('revoked', false)
->where(function (Builder $refreshExpires): void {
$refreshExpires->whereNull('expires_at')
->orWhere('expires_at', '>', now());
}),
);
});
}
/**
* @param Builder<static> $query
* @return Builder<static>
*/
public function scopeMcpOAuth(Builder $query): Builder
{
return $query
->whereJsonContains('scopes', 'mcp:use')
->whereHas(
'client',
fn (Builder $client): Builder => $client
->where('revoked', false)
->whereJsonDoesntContain('grant_types', 'personal_access'),
);
}
/**
* Personal-access API keys (REST), excluding MCP OAuth clients.
*
* @param Builder<static> $query
* @return Builder<static>
*/
public function scopePersonalAccessApiKey(Builder $query): Builder
{
return $query->whereHas(
'client',
fn (Builder $client): Builder => $client
->where('revoked', false)
->whereJsonContains('grant_types', 'personal_access'),
);
}
/**
* Whether this token was issued by a live personal-access client (REST API keys).
*/
public function isPersonalAccessToken(): bool
{
$this->loadMissing('client');
return $this->client !== null
&& ! $this->client->revoked
&& $this->client->hasGrantType('personal_access');
}
/**
* Whether this is a non-revoked MCP OAuth grant with mcp:use (ignores expiry).
*/
public function isMcpOAuthGrant(): bool
{
$this->loadMissing('client');
if ($this->revoked) {
return false;
}
if (! in_array('mcp:use', $this->scopes ?? [], true)) {
return false;
}
return $this->client !== null
&& ! $this->client->revoked
&& ! $this->client->hasGrantType('personal_access');
}
/**
* Whether this is a non-revoked, unexpired MCP OAuth grant with mcp:use.
*/
public function isActiveMcpGrant(): bool
{
if (! $this->isMcpOAuthGrant()) {
return false;
}
return $this->expires_at === null || ! $this->expires_at->isPast();
}
Activation checklist + MCP OAuth authorize UX (#239) (#250) * Wire onboarding activation into Account, observers, and shared Inertia data Add onboarding casts/hasFinishedOnboarding, AccessToken ObservedBy, Platform::connectableOptions, Post/SocialAccount onboarding broadcast hooks, and lazy onboardingResidual share + SharedData types. * Register onboarding routes and post-checkout activation redirects. Wire billing processing and the sidebar checklist so owners land on activation after subscribe, with locale sidebar/uk onboarding strings. * Align MCP grant usability with onboarding activation checks Unbound MCP tokens fall back to the user's current workspace and require createPost so viewer/unscoped grants neither unlock the checklist nor broadcast onboarding status. * Require bound MCP workspace for onboarding activation. Drop current-workspace fallback from usable MCP grants so checklist detection and broadcasts match Passport token scoping; viewers still cannot unlock the MCP step. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding review findings and tighten locale strings. Fix Welcome/Persona/TrackPost suites broken by the activation route reuse and PostObserver analytics side effects, restore Echo poll fallbacks, reject unbound MCP grants in tests, and drop unused onboarding.mcp keys. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove unused sidebar and MCP authorization locale keys. Drop dead sidebar menu/theme strings (including the overwritten workspace label and api_keys nav entry) and unused MCP authorize app_title/approving copy across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix SetLocale crashing on Passport Symfony OAuth responses. OAuth errors return a raw Symfony Response without withCookie(); attach the default locale cookie via headers so authorize no longer 500s. Co-authored-by: Cursor <cursoragent@cursor.com> * Prompt OAuth guests to log in before rejecting unknown clients. MCP Inspector often reuses a stale client_id; validateAuthorizationRequest was returning invalid_client JSON before the login redirect. Guests now hit /login first, then client validation runs after authentication. Co-authored-by: Cursor <cursoragent@cursor.com> * Render Inertia OAuth authorize errors for browser logins. After login, Inertia follows the intended authorize URL; raw invalid_client JSON broke that visit. HTML/Inertia requests now get mcp/AuthorizeError while API JSON clients still receive the OAuth error payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Detect Inertia OAuth error pages via Request::inertia(). Use the framework helper so post-login authorize failures keep returning an Inertia page instead of raw OAuth JSON. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify OAuth authorize error page detection to expectsJson. Drop the X-Inertia header sniff; browser and Inertia visits already do not expectsJson, while API clients still receive the OAuth JSON payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Share MCP authorize layout and drop the error close button. Keep authorize and authorize-error on the same centered card shell instead of the auth split layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding activation for reviewability and safety. Use an exists-based MCP check, keep GETs read-only, move sync into syncAndNotify, clear MCP skips on connect, restrict complete to owners, and share Echo/poll via one composable. Co-authored-by: Cursor <cursoragent@cursor.com> * Move MCP OAuth authorize UX out of the onboarding PR. Keep the activation checklist focused; OAuth guest/error-page work now lives on fix/mcp-oauth-authorize-ux. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix corrupted French MCP locale after OAuth key cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore MCP OAuth authorize UX onto the onboarding branch. Keep authorize error page, guest login-before-client validation, and SetLocale Symfony cookie fix in #250. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth prompt=none redirects and harden onboarding tests. Keep login_required/consent_required as redirects instead of Inertia, add regression coverage for owner-only activation, require invite email confirmation, and align MCP connected apps with the sessions list UI. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding guards and dedupe viewed analytics. Introduce isOnboardingOpen / belongsToAccount helpers, collapse duplicated sync/dispatch paths, and capture onboarding.viewed once per account. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding event, observers, and status helpers. Tighten Account onboarding predicates, drop nullable broadcast/dispatch APIs, and collapse repeated observer/controller guards. Co-authored-by: Cursor <cursoragent@cursor.com> * Treat in-app users as always having an account. Add resolveAccount(), tighten belongsToAccount to string ids, and fold guest residual handling into ResolveOnboardingStatus. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename onboarding residual share test to progress. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding status and rename residual to progress. Use accountOrFail, extract MCP onboarding scope, auto-leave the ready screen, and send non-onboarding checkout back to accounts. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract HasAccount and prefer data_get in onboarding flows. Move account helpers off User, drop nullable sidebarProgress, and read OAuth/onboarding payloads with data_get. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding checks and extract HasOnboarding. Use Eloquent + policies for MCP/backfill paths, and move account onboarding helpers into a dedicated trait. Co-authored-by: Cursor <cursoragent@cursor.com> * Add trait tests and tidy onboarding imports. Cover HasAccount and HasOnboarding under Models/Traits, prefer filled() for checkout session ids, and import Throwable instead of FQCN. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify checkout session_id and OAuth error props. Read session_id via request->string(), and take OAuth error details from the League exception instead of decoding the response body. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify PostObserver onboarding notify path. Share one otherPosts check for first-create and last-delete instead of separate callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Use post author as onboarding sync actor. Drop Auth::user() preference in PostObserver; checklist sync attributes to $post->user. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify SocialAccountObserver and OAuth authorize flow. Share create/delete onboarding notify, drop Auth actor fallback to owner, and inline Passport Inertia error handling. Co-authored-by: Cursor <cursoragent@cursor.com> * Use lazy Inertia props for onboarding partial reloads. Drop partial-header branching; wrap page props in closures and always redirect completed/dismissed accounts to the calendar. Co-authored-by: Cursor <cursoragent@cursor.com> * Defer sidebar onboarding progress and stamp completion as owner-only. Skip the MCP checklist work on full Inertia visits via deferred shared props, early-exit token scans, and keep account completion stamps owner-gated. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify deferred onboarding progress share via canShowProgress. Co-authored-by: Cursor <cursoragent@cursor.com> * Add User firstName for shared auth and simplify onboarding page. Co-authored-by: Cursor <cursoragent@cursor.com> * Move User firstName coverage into UserTest. Co-authored-by: Cursor <cursoragent@cursor.com> * Use first_name directly without empty-name fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Resolve onboarding sample prompt on the frontend via i18n. Co-authored-by: Cursor <cursoragent@cursor.com> * Stamp onboarding completion via the account owner after teammate unlocks. Co-authored-by: Cursor <cursoragent@cursor.com> * Count only the account owner MCP grant toward onboarding activation. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth consent auth-token mismatch for mid-activation owners. Skip deferred onboardingProgress on Passport authorize so Inertia does not rotate the session authToken, cover happy and stale-token paths in tests, and polish MCP setup copy plus sidebar/onboarding layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep users on onboarding after activation completes. Stamp completion and re-render the finished checklist instead of redirecting to the calendar so owners can review the done state. Co-authored-by: Cursor <cursoragent@cursor.com> * Clarify Passport consent-view opt-out and guard app-route deferral. Rename the authorize-only route check and assert onboardingProgress still defers on calendar, onboarding, and MCP settings. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding completion and MCP consent workspace binding. Reject OAuth approve without a workspace, retry auto-complete until stamped, send dismissed complete straight to calendar, and cover the device consent defer opt-out. Co-authored-by: Cursor <cursoragent@cursor.com> * Enable activation checklist for self-hosted installs. Remove the self-hosted onboarding redirects, keep the SaaS-only dismiss backfill, and cover subscription-less owners plus skip/complete destinations. Co-authored-by: Cursor <cursoragent@cursor.com> * Add GitHub, Hacker News, and directories referral sources. Expand the welcome referral step with open-source and directory discovery channels. Co-authored-by: Cursor <cursoragent@cursor.com> * Refine welcome referral sources and labels. Split Instagram/Threads, add Founder, and shorten Google, GitHub, AI, and blog option labels. Co-authored-by: Cursor <cursoragent@cursor.com> * Sort accounts platforms alphabetically and drop connect hover plus. Reuse connectableOptions for the accounts index and remove the unused plus badge on disconnected cards. Co-authored-by: Cursor <cursoragent@cursor.com> * Centralize PostHog once-capture so disabled installs don't burn dedupe keys. Move isEnabled + Cache::add into PostHogService::captureOnce and route onboarding viewed/step events through it. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding backfill to complete every existing open account. Drop self-hosted and subscription filters; down clears completed_at again. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop PostHog captureOnce and use plain capture for onboarding. Remove cache-based event dedupe; callers rely on PostHogService::capture gating. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 23:34:43 +00:00
/**
* Bound MCP grant that unlocks the account onboarding checklist
* (account-owner grant + usable + createPost on the bound workspace).
*/
public function unlocksOnboardingChecklist(?User $user = null): bool
{
$user ??= $this->user;
$workspace = $this->workspace;
return $user instanceof User
&& $workspace instanceof Workspace
&& $user->isAccountOwner()
&& $this->isUsableMcpGrant($user, $workspace)
&& $user->can('createPost', $workspace);
}
MCP: workspace settings, viewer read access, and token access (#241) * 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>
2026-08-06 12:54:51 +00:00
/**
* Whether a refresh token can still mint a new access token for this grant.
*/
public function hasLiveRefreshToken(): bool
{
$this->loadMissing('refreshToken');
$refresh = $this->refreshToken;
if ($refresh === null || $refresh->revoked) {
return false;
}
return $refresh->expires_at === null || $refresh->expires_at->isFuture();
}
/**
* Whether this MCP grant can actually use the product (active token + a
* workspace the owner can view write tools enforce createPost themselves).
*/
public function isUsableMcpGrant(?User $user = null, ?Workspace $workspace = null): bool
{
if (! $this->isActiveMcpGrant()) {
return false;
}
return $this->ownerCanViewWorkspace($user, $workspace);
}
/**
* Whether this MCP grant should appear in the connected-clients list
* (usable now, or recoverable via refresh, for a user who can view a workspace).
*/
public function isListedMcpConnection(?User $user = null, ?Workspace $workspace = null): bool
{
if (! $this->isMcpOAuthGrant()) {
return false;
}
if (! $this->isActiveMcpGrant() && ! $this->hasLiveRefreshToken()) {
return false;
}
return $this->ownerCanViewWorkspace($user, $workspace);
}
private function ownerCanViewWorkspace(?User $user = null, ?Workspace $workspace = null): bool
{
$user ??= User::query()
->with('currentWorkspace')
->find($this->user_id);
if (! $user instanceof User) {
return false;
}
Scope MCP OAuth tokens to user + workspace (#222) (#245) * Scope MCP OAuth tokens to user + workspace Bind authorization-code grants to the authorizing workspace (via auth codes), inherit workspace on refresh, resolve MCP/API requests from the token instead of current_workspace_id, backfill existing grants, and revoke workspace tokens when a member is removed. Co-authored-by: Cursor <cursoragent@cursor.com> * Add multi-workspace MCP OAuth coverage Cover coexistence of the same client across workspaces, settings list/disconnect scoped to the current workspace, and API key controllers excluding workspace-bound MCP grants. Co-authored-by: Cursor <cursoragent@cursor.com> * Use constrained foreignUuid for oauth_auth_codes.workspace_id Match the project's UUID foreign-key convention instead of a separate foreign() call. Co-authored-by: Cursor <cursoragent@cursor.com> * Localize the MCP OAuth authorize consent screen Wire authorize.blade.php to mcp.* translation keys (including the workspace scope copy) and cover pt-BR rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix invalid Mockery import in bind workspace test CI treats the non-compound `use Mockery` as an ErrorException and aborts the whole parallel suite. Co-authored-by: Cursor <cursoragent@cursor.com> * Inline MCP OAuth workspace backfill into the migration Move the one-shot backfill out of a dedicated Action and wrap it in an explicit transaction so a failure rolls back partial binds/revokes. Co-authored-by: Cursor <cursoragent@cursor.com> * Nest MCP authorize i18n keys and test backfill rollback Group consent-screen copy under mcp.authorize.*, and assert the workspace backfill migration rolls back binds when it fails before commit. Co-authored-by: Cursor <cursoragent@cursor.com> * Hardcode TryPost in the MCP authorize page title Drop the config('app.name') interpolation from the consent screen title. Co-authored-by: Cursor <cursoragent@cursor.com> * Add workspace picker to MCP OAuth consent screen Let users choose which workspace to bind at authorize time instead of always using current_workspace_id; silent re-consent still falls back. Co-authored-by: Cursor <cursoragent@cursor.com> * Tighten MCP authorize workspace select spacing Match NativeSelect styling and give the label, control, and helper text room to breathe. Co-authored-by: Cursor <cursoragent@cursor.com> * Convert MCP OAuth consent screen to Inertia Vue Reuse AuthCardLayout, Button, and NativeSelect so the authorize page matches the app UI. Keep native form posts so Passport's external redirect still works for MCP client popups. Co-authored-by: Cursor <cursoragent@cursor.com> * Polish MCP authorize layout with logo and workspace combobox Drop the shield and AuthCardLayout double-logo, put TryPost branding at the top, and reuse the app Combobox pattern for workspace search. Co-authored-by: Cursor <cursoragent@cursor.com> * Align MCP OAuth workspace backfill with mcpOAuth scope Reuse AccessToken::mcpOAuth() so the migration only touches mcp:use grants on non-PAT clients, matching the rest of the codebase. Co-authored-by: Cursor <cursoragent@cursor.com> * Tighten MCP OAuth workspace backfill heuristics Only touch connected MCP sessions, bind a sole membership or a valid current workspace, and revoke ambiguous multi-workspace grants instead of guessing the oldest workspace. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop Passport connection override from auth code migration Always use the app default database connection from .env. Co-authored-by: Cursor <cursoragent@cursor.com> * Bind MCP OAuth workspace in AccessTokenRepository Replace the AccessTokenCreated listener with the same Passport repository override pattern used for auth codes, so workspace_id is set at persist. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify AccessTokenRepository workspace binding Drop redundant string casts and the oldest-workspace fallback; keep a small ownedWorkspace/payloadId helper surface instead. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract Passport MCP authorization view from AppServiceProvider Keep configurePassport thin by moving the Inertia consent props into an invokable App\Passport\AuthorizationView class. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify AuthorizationView and cover it with direct tests Use collection higher-order mapping for workspaces/scopes and add focused tests for current-workspace selection and empty-user props. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename BindWorkspaceToAccessTokenTest after listener removal The suite now covers AuthCodeRepository and AccessTokenRepository workspace binding, not an AccessTokenCreated listener. * Fail closed when auth code has no bindable workspace Authorization-code grants no longer fall back to the user's current workspace, so a token cannot be minted for a different tenant than consent. Co-authored-by: Cursor <cursoragent@cursor.com> * Retrigger CI after GitHub Actions infrastructure failures Co-authored-by: Cursor <cursoragent@cursor.com> * chore: retrigger CI Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden MCP OAuth workspace binding on refresh and backfill Co-authored-by: Cursor <cursoragent@cursor.com> * fix: always show MCP OAuth consent to pick a workspace Disable Passport silent re-consent and require an explicit workspace_id from the consent form, with Passport wiring moved to its own provider. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: sort MCP connected clients by last used Show most recently used OAuth connections first on the workspace MCP settings page. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 00:59:34 +00:00
// Bound grants resolve only from the token — never the switcher.
$workspace ??= $this->workspace;
MCP: workspace settings, viewer read access, and token access (#241) * 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>
2026-08-06 12:54:51 +00:00
return $workspace instanceof Workspace
&& $user->can('view', $workspace);
}
}