boot(); } return self::$instance; } /** * Private constructor — use instance(). */ private function __construct() { $this->container = new Container(); } /** * Expose the container for advanced extension use. */ public function container(): Container { return $this->container; } /** * Register services and attach WordPress hooks. */ private function boot(): void { if ( $this->booted ) { return; } $this->booted = true; $this->register_services(); $this->register_hooks(); /** * Fires after the plugin has registered its services and hooks. * * Premium modules and third-party extensions should hook here to * register their own services, tools, auth providers, or RPC methods. * * @param Plugin $plugin The plugin instance. * @param Container $container The service container. */ do_action( 'gripmcp/booted', $this, $this->container ); } /** * Bind all first-party services into the container. */ private function register_services(): void { $c = $this->container; $c->singleton( Settings::class, static fn(): Settings => new Settings() ); $c->singleton( Logger::class, static fn( Container $c ): Logger => new Logger( $c->get( Settings::class ) ) ); $c->singleton( Token_Repository::class, static fn(): Token_Repository => new Token_Repository() ); $c->singleton( Authenticator::class, static function ( Container $c ): Authenticator { $auth = new Authenticator(); // First-party auth providers. Application Passwords and cookie // auth are handled natively by WordPress's REST pipeline; the // Bearer provider adds token-based auth for MCP clients. $providers = array( new Bearer_Token_Provider( $c->get( Token_Repository::class ) ), ); /** * Filter the list of MCP authentication providers. * * @param \GripMCP\Contracts\Auth_Provider_Interface[] $providers Providers. */ $providers = apply_filters( 'gripmcp/auth_providers', $providers ); foreach ( $providers as $provider ) { $auth->add_provider( $provider ); } return $auth; } ); $c->singleton( Schema_Validator::class, static fn(): Schema_Validator => new Schema_Validator() ); $c->singleton( Package_Service::class, static fn(): Package_Service => new Package_Service() ); $c->singleton( Tool_Registry::class, static function ( Container $c ): Tool_Registry { $registry = new Tool_Registry( $c->get( Settings::class ), $c->get( Package_Service::class ) ); $registry->boot(); return $registry; } ); $c->singleton( Server::class, static fn( Container $c ): Server => new Server( $c->get( Tool_Registry::class ), $c->get( Schema_Validator::class ), $c->get( Logger::class ) ) ); $c->singleton( Rest_Controller::class, static fn( Container $c ): Rest_Controller => new Rest_Controller( $c->get( Server::class ), $c->get( Settings::class ) ) ); $c->singleton( Short_Endpoint::class, static fn(): Short_Endpoint => new Short_Endpoint() ); $c->singleton( Upload_Endpoint::class, static fn( Container $c ): Upload_Endpoint => new Upload_Endpoint( $c->get( Settings::class ) ) ); $c->singleton( Approval_Endpoint::class, static fn( Container $c ): Approval_Endpoint => new Approval_Endpoint( $c->get( Settings::class ) ) ); $c->singleton( OAuth_Server::class, static fn( Container $c ): OAuth_Server => new OAuth_Server( $c->get( Settings::class ) ) ); $c->singleton( Admin::class, static fn( Container $c ): Admin => new Admin( $c->get( Settings::class ), $c->get( Token_Repository::class ), $c->get( Logger::class ), $c->get( OAuth_Server::class ) ) ); // The Creator, Operator and Fleet tools, their services and the full // audit log. Only the paid version has this class: Freemius leaves its // folder out of the WordPress.org plugin, which then simply has none // of it. if ( class_exists( Premium::class ) ) { Premium::boot( $c ); } } /** * Attach subsystems to WordPress hooks. */ private function register_hooks(): void { $c = $this->container; // Translations come from translate.wordpress.org and are loaded by // WordPress itself (just-in-time, for the 'gripmcp' text domain). // OAuth authorization server. Registered *before* the Authenticator is // resolved so its resource-server provider is added via the // gripmcp/auth_providers filter in time. $c->get( OAuth_Server::class )->register(); // The daily clean-up also drops activity log rows older than the // plan keeps, so a quiet site does not hold them until its next calls. add_action( 'gripmcp/oauth_gc', static function () use ( $c ) { $c->get( Logger::class )->prune_expired(); } ); // Authentication providers register their own WP hooks (e.g. determine_current_user). $c->get( Authenticator::class )->register(); // A deleted account's access tokens go with it. add_action( 'deleted_user', static function ( $user_id ) use ( $c ) { $c->get( Token_Repository::class )->revoke_user( (int) $user_id ); } ); // REST transport. add_action( 'rest_api_init', static function () use ( $c ) { $c->get( Rest_Controller::class )->register_routes(); } ); // Short URL (site.com/mcp) in front of the REST route. Registered // unconditionally: it only rewrites the path, and the REST route's own // permission callback still decides whether the request is served. $c->get( Short_Endpoint::class )->register(); // Answers the GripMCP screen's test of whether the server passes the // Authorization header on. Read-only; it never signs anyone in. ( new Header_Check() )->register(); // Browser-facing upload page + staging POST (admin-post.php). Registered // unconditionally; each handler re-checks the feature switch and the // session token before doing anything. $c->get( Upload_Endpoint::class )->register(); // Browser-facing approval page (admin-post.php). Registered // unconditionally; the handlers re-check the feature switch, the // visitor's session and their capability before anything is decided. $c->get( Approval_Endpoint::class )->register(); // wp_page_html keeps a page for reading its later parts. An edit made // anywhere while one is kept (wp-admin, the Elementor editor, another // plugin) makes it fetch the page again. Page_Html_Tool::watch_changes(); // Admin UI (only load in wp-admin). if ( is_admin() ) { $c->get( Admin::class )->register(); // GripMCP's header and look on Freemius's Account and Contact Us // pages. Freemius_Screens::register(); // "Reconnect to see the new tools" after a plugin such as // Elementor adds a tool group. Tool_List_Watch::register(); // Two copies of GripMCP active (the free and the paid version, or // two paid copies): which one runs, and which to deactivate. Plugin_Copies::register(); } } }