diff --git a/wordpress.org/public_html/wp-content/plugins/login-application-passwords/clients/mcp/class-mcp-authorization.php b/wordpress.org/public_html/wp-content/plugins/login-application-passwords/clients/mcp/class-mcp-authorization.php
new file mode 100644
index 0000000000..f16eee550a
--- /dev/null
+++ b/wordpress.org/public_html/wp-content/plugins/login-application-passwords/clients/mcp/class-mcp-authorization.php
@@ -0,0 +1,141 @@
+ 'WordPress.org MCP',
+ 'hosts' => array(),
+ );
+
+ return $apps;
+ }
+
+ /**
+ * Renders the MCP client configuration after application password creation.
+ *
+ * @param string $new_password The newly created application password.
+ * @param array $request The request data.
+ * @param WP_User $user The user who authorized the application.
+ */
+ public static function render_config( string $new_password, array $request, WP_User $user ): void {
+ if ( ( $request['app_id'] ?? '' ) !== self::APP_ID ) {
+ return;
+ }
+
+ $mcp_config = array(
+ 'mcpServers' => array(
+ 'wporg-mcp-server' => array(
+ 'command' => 'npx',
+ 'args' => array( '-y', '@automattic/mcp-wordpress-remote@latest' ),
+ 'env' => array(
+ 'WP_API_URL' => rest_url( 'mcp/wporg' ),
+ 'WP_API_USERNAME' => $user->user_login,
+ 'WP_API_PASSWORD' => WP_Application_Passwords::chunk_password( $new_password ),
+ ),
+ ),
+ ),
+ );
+
+ $json = wp_json_encode( $mcp_config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
+ $json = str_replace( ' ', ' ', $json );
+
+ ?>
+
+
+
+
+
+
+ - Claude Desktop —
+ - Claude Code —
+ .mcp.json' );
+ ?>
+
+ - Cursor —
+ - VS Code —
+ .vscode/mcp.json',
+ '
servers',
+ 'mcpServers'
+ );
+ ?>
+
+
+
+ Map of app_id UUID => app config.
+ */
+function wporg_get_allowed_apps(): array {
+ /**
+ * Filters the registered applications allowed to use the application password authorization flow.
+ *
+ * Each entry must be keyed by a valid UUID (the app_id) and contain an array
+ * with 'name' (non-empty string) and optionally 'hosts' (array of allowed
+ * callback domains). If 'hosts' is omitted, it defaults to an empty array,
+ * meaning the app does not support callback URLs.
+ *
+ * Example:
+ *
+ * add_filter( 'wporg_login_application_passwords_allowed_apps', function ( $apps ) {
+ * $apps['c4c73a54-96d7-47b9-9bdc-1a66b9b04505'] = array(
+ * 'name' => 'My Application',
+ * 'hosts' => array( 'example.com' ),
+ * );
+ * return $apps;
+ * } );
+ *
+ * @param array $apps Map of app_id UUID => app config.
+ */
+ $apps = apply_filters( 'wporg_login_application_passwords_allowed_apps', array() );
+
+ $validated = array();
+
+ foreach ( $apps as $app_id => $config ) {
+ $config = wp_parse_args( $config, array( 'hosts' => array() ) );
+
+ if ( ! wp_is_uuid( $app_id ) ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf( 'App ID must be a valid UUID. Got: %s', esc_html( $app_id ) ),
+ '1.0.0'
+ );
+ continue;
+ }
+
+ if ( ! is_array( $config ) || empty( $config['name'] ) || ! is_string( $config['name'] ) ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf( 'App "%s" must have a non-empty "name" string.', esc_html( $app_id ) ),
+ '1.0.0'
+ );
+ continue;
+ }
+
+ if ( ! is_array( $config['hosts'] ) ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf( 'App "%s" "hosts" must be an array.', esc_html( $app_id ) ),
+ '1.0.0'
+ );
+ continue;
+ }
+
+ $validated[ $app_id ] = $config;
+ }
+
+ return $validated;
+}
+
+/**
+ * Handles the authorize_application action on wp-login.php.
+ */
+function wporg_handle_authorize_application_login(): void {
+ if ( ! is_user_logged_in() ) {
+ $authorize_url = add_query_arg(
+ array(
+ 'action' => 'authorize_application',
+ 'app_id' => sanitize_text_field( wp_unslash( $_REQUEST['app_id'] ?? '' ) ),
+ 'success_url' => sanitize_url( wp_unslash( $_REQUEST['success_url'] ?? '' ) ),
+ 'reject_url' => sanitize_url( wp_unslash( $_REQUEST['reject_url'] ?? '' ) ),
+ ),
+ site_url( 'wp-login.php', 'login' )
+ );
+
+ wp_safe_redirect( wp_login_url( $authorize_url ) );
+ exit;
+ }
+
+ $error = null;
+ $new_password = '';
+
+ // Handle form submission.
+ if ( isset( $_SERVER['REQUEST_METHOD'] )
+ && 'POST' === $_SERVER['REQUEST_METHOD']
+ && isset( $_POST['wp_action'] ) && 'authorize_application_password' === $_POST['wp_action']
+ ) {
+ check_admin_referer( 'authorize_application_password' );
+
+ $app_id = sanitize_text_field( wp_unslash( $_POST['app_id'] ?? '' ) );
+ $success_url = sanitize_url( wp_unslash( $_POST['success_url'] ?? '' ) );
+ $reject_url = sanitize_url( wp_unslash( $_POST['reject_url'] ?? '' ) );
+ $redirect = '';
+
+ // Re-validate POST values (don't trust hidden form fields).
+ $user = wp_get_current_user();
+ $allowed_apps = wporg_get_allowed_apps();
+ $app_name = $allowed_apps[ $app_id ]['name'] ?? '';
+ $request = compact( 'app_name', 'app_id', 'success_url', 'reject_url' );
+
+ $is_valid = wporg_validate_authorize_app_request( $request, $user );
+ if ( is_wp_error( $is_valid ) ) {
+ wp_die(
+ wp_kses_post( implode( ' ', $is_valid->get_error_messages() ) ),
+ esc_html__( 'Cannot Authorize Application' )
+ );
+ }
+
+ if ( isset( $_POST['reject'] ) ) {
+ $redirect = $reject_url ?: admin_url(); // phpcs:ignore Universal.Operators.DisallowShortTernary
+ } elseif ( isset( $_POST['approve'] ) ) {
+ // Revoke any existing password for this app_id.
+ if ( $app_id ) {
+ $existing = WP_Application_Passwords::get_user_application_passwords( get_current_user_id() );
+ foreach ( $existing as $item ) {
+ if ( isset( $item['app_id'] ) && $item['app_id'] === $app_id ) {
+ $deleted = WP_Application_Passwords::delete_application_password( get_current_user_id(), $item['uuid'] );
+ if ( is_wp_error( $deleted ) ) {
+ $error = $deleted;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( ! is_wp_error( $error ) ) {
+ $created = WP_Application_Passwords::create_new_application_password(
+ get_current_user_id(),
+ array(
+ 'name' => $app_name,
+ 'app_id' => $app_id,
+ )
+ );
+
+ if ( is_wp_error( $created ) ) {
+ $error = $created;
+ } else {
+ list( $new_password ) = $created;
+
+ if ( $success_url ) {
+ $redirect = add_query_arg(
+ array(
+ 'site_url' => rawurlencode( 'https://wordpress.org' ),
+ 'user_login' => rawurlencode( wp_get_current_user()->user_login ),
+ 'password' => rawurlencode( $new_password ),
+ ),
+ $success_url
+ );
+ }
+ }
+ }
+ }
+
+ if ( $redirect ) {
+ $allowed_hosts = $allowed_apps[ $app_id ]['hosts'] ?? array();
+ add_filter(
+ 'allowed_redirect_hosts',
+ function ( $hosts ) use ( $allowed_hosts ) {
+ return array_merge( $hosts, $allowed_hosts );
+ }
+ );
+
+ wp_safe_redirect( $redirect );
+ exit;
+ }
+ }
+
+ // Extract and validate request parameters.
+ $app_id = sanitize_text_field( wp_unslash( $_REQUEST['app_id'] ?? '' ) );
+ $success_url = sanitize_url( wp_unslash( $_REQUEST['success_url'] ?? '' ) ) ?: null; // phpcs:ignore Universal.Operators.DisallowShortTernary
+
+ if ( ! empty( $_REQUEST['reject_url'] ) ) {
+ $reject_url = sanitize_url( wp_unslash( $_REQUEST['reject_url'] ) );
+ } elseif ( $success_url ) {
+ $reject_url = add_query_arg( 'success', 'false', $success_url );
+ } else {
+ $reject_url = null;
+ }
+
+ $user = wp_get_current_user();
+ $allowed_apps = wporg_get_allowed_apps();
+ $app_name = $allowed_apps[ $app_id ]['name'] ?? '';
+ $request = compact( 'app_name', 'app_id', 'success_url', 'reject_url' );
+
+ $is_valid = wporg_validate_authorize_app_request( $request, $user );
+ if ( is_wp_error( $is_valid ) ) {
+ wp_die(
+ wp_kses_post( implode( ' ', $is_valid->get_error_messages() ) ),
+ esc_html__( 'Cannot Authorize Application' )
+ );
+ }
+
+ if ( ! wp_is_application_passwords_available_for_user( $user ) ) {
+ if ( wp_is_application_passwords_available() ) {
+ $message = __( 'Application passwords are not available for your account. Please contact the site administrator for assistance.' );
+ } else {
+ $message = __( 'Application passwords are not available.' );
+ }
+
+ wp_die(
+ esc_html( $message ),
+ esc_html__( 'Cannot Authorize Application' ),
+ array(
+ 'response' => 501,
+ 'link_text' => esc_html__( 'Go Back' ),
+ 'link_url' => esc_url( $reject_url ? add_query_arg( 'error', 'disabled', $reject_url ) : admin_url() ),
+ )
+ );
+ }
+
+ // Render the authorization form.
+ $title = __( 'Authorize Application' );
+ $wp_error = new WP_Error();
+
+ if ( is_wp_error( $error ) ) {
+ $wp_error->add( 'authorize_error', $error->get_error_message() );
+ }
+
+ add_filter( 'login_site_html_link', '__return_empty_string' );
+
+ login_header( $title, '', $wp_error );
+
+ ?>
+
+ ' . esc_html( $app_name ) . ''
+ );
+ } else {
+ $message = sprintf(
+ /* translators: %s: Website name. */
+ esc_html__( 'Please log in to %s to proceed with authorization.' ),
+ get_bloginfo( 'name', 'display' )
+ );
+ }
+
+ $errors->add( 'authorize_application', $message, 'message' );
+
+ return $errors;
+}
+add_filter( 'wp_login_errors', 'wporg_authorize_application_login_message', 10, 2 );
+
+/**
+ * Enqueues the authorize_application stylesheet on the login page.
+ */
+function wporg_authorize_application_login_styles(): void {
+ global $action;
+
+ if ( 'authorize_application' !== $action ) {
+ return;
+ }
+
+ wp_enqueue_style(
+ 'login-authorize-application',
+ plugins_url( 'css/authorize-application.css', __FILE__ ),
+ array(),
+ filemtime( __DIR__ . '/css/authorize-application.css' )
+ );
+}
+add_action( 'login_enqueue_scripts', 'wporg_authorize_application_login_styles' );
+
+/**
+ * Validates the authorize application request.
+ *
+ * Inlined from wp_is_authorize_application_password_request_valid() in
+ * wp-admin/includes/user.php, which is not loaded in the login context.
+ *
+ * @param array $request The request data.
+ * @param WP_User $user The user authorizing the application.
+ *
+ * @return true|WP_Error True if valid, WP_Error otherwise.
+ */
+function wporg_validate_authorize_app_request( array $request, WP_User $user ): bool|WP_Error {
+ $error = new WP_Error();
+
+ // Validate redirect URLs.
+ $is_local = 'local' === wp_get_environment_type();
+
+ foreach ( array( 'success_url', 'reject_url' ) as $key ) {
+ if ( empty( $request[ $key ] ) ) {
+ continue;
+ }
+
+ if ( ! wp_http_validate_url( $request[ $key ] ) ) {
+ $error->add( 'invalid_redirect_url', __( 'The provided URL is not valid.' ) );
+ } elseif ( 'https' !== wp_parse_url( $request[ $key ], PHP_URL_SCHEME ) && ! $is_local ) {
+ $error->add( 'invalid_redirect_scheme', __( 'The URL must be served over a secure connection.' ) );
+ }
+ }
+
+ // Validate app_id format.
+ if ( empty( $request['app_id'] ) ) {
+ $error->add( 'missing_app_id', __( 'An application ID is required.' ) );
+ } elseif ( ! wp_is_uuid( $request['app_id'] ) ) {
+ $error->add( 'invalid_app_id', __( 'The application ID must be a UUID.' ) );
+ }
+
+ // Validate app_id against the allowlist and check callback domains.
+ $allowed_apps = wporg_get_allowed_apps();
+
+ if ( ! empty( $request['app_id'] ) && wp_is_uuid( $request['app_id'] ) ) {
+ if ( ! isset( $allowed_apps[ $request['app_id'] ] ) ) {
+ $error->add( 'unauthorized_app', __( 'This application is not authorized.' ) );
+ } else {
+ $allowed_hosts = $allowed_apps[ $request['app_id'] ]['hosts'];
+
+ if ( empty( $allowed_hosts ) ) {
+ if ( ! empty( $request['success_url'] ) || ! empty( $request['reject_url'] ) ) {
+ $error->add( 'redirect_not_allowed', __( 'This application does not support callback URLs.' ) );
+ }
+ } else {
+ foreach ( array( 'success_url', 'reject_url' ) as $key ) {
+ if ( empty( $request[ $key ] ) ) {
+ continue;
+ }
+
+ $host = strtolower( wp_parse_url( $request[ $key ], PHP_URL_HOST ) );
+ if ( ! $host || ! in_array( $host, array_map( 'strtolower', $allowed_hosts ), true ) ) {
+ $error->add( 'unauthorized_redirect', __( 'The callback URL does not point to an allowed domain.' ) );
+ }
+ }
+ }
+ }
+ }
+
+ /** This action is documented in wp-admin/includes/user.php */
+ do_action( 'wp_authorize_application_password_request_errors', $error, $request, $user );
+
+ if ( $error->has_errors() ) {
+ return $error;
+ }
+
+ return true;
+}
+