get_all_registered() as $name => $ability ) { $id = $ability->get_name() ? $ability->get_name() : (string) $name; if ( self::is_allowed( $id ) ) { $allowed[] = $id; } } sort( $allowed ); /** * Filter the abilities an external AI client may reach. * * Use this to LOCK DOWN a site — return a narrower list to withhold * abilities from connected agents. The namespace bound is re-applied * afterwards, so a filter cannot widen the surface past Era's own * abilities. Treat it as a membership question: it may be handed the * full list (here) or a single-ability list ({@see self::is_exposed()}), * so filter the ids you are given rather than inspecting the set. * * @param string[] $allowed Ability ids. */ $filtered = apply_filters( 'zip_ai_external_allowed_abilities', $allowed ); $filtered = array_filter( (array) $filtered, 'is_string' ); return array_values( array_filter( $filtered, array( self::class, 'is_allowed' ) ) ); } /** * Whether one ability id is reachable externally. * * @param string $id Ability id. * @return bool */ public static function is_allowed( string $id ) { $namespace = strtok( $id, '/' ); return in_array( $namespace, self::ALLOWED_NAMESPACES, true ); } /** * Whether one ability may be flagged `meta.mcp.public` — the check that * governs EXECUTION, not just the displayed list. The namespace bound and * the site's `zip_ai_external_allowed_abilities` lockdown filter both * apply; without the filter here, a site narrowing its surface would trim * the Connection screen while every ability stayed dispatchable. * * @param string $id Ability id. * @return bool */ public static function is_exposed( string $id ) { if ( ! self::is_allowed( $id ) ) { return false; } /** This filter is documented on {@see self::allowed()}. */ $filtered = array_filter( (array) apply_filters( 'zip_ai_external_allowed_abilities', array( $id ) ), 'is_string' ); return in_array( $id, $filtered, true ); } }