• Resolved allanantoni

    (@allanantoni)


    ('openapi',): Input should be '3.1.1' or '3.1.0'

    ('paths', '/wp-json/aicom/v1/tools/session.close', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/session.close', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/session.close', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/session.status', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/session.status', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/session.status', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/server.status', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/server.status', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/server.status', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/site.summary', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/site.summary', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/site.summary', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.site.info', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.site.info', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/wp.site.info', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.post_types.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.post_types.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/wp.post_types.list', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.taxonomies.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.taxonomies.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/wp.taxonomies.list', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.menus.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.menus.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/wp.menus.list', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.menus.locations.get', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.menus.locations.get', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/wp.menus.locations.get', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.roles.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/wp.roles.list', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/wp.roles.list', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/a11y.site_report', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/a11y.site_report', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/a11y.site_report', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/yoast.status', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/yoast.status', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/yoast.status', 'post', 'requestBody', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/yoast.site.get', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Reference', '$ref'): Field required

    ('paths', '/wp-json/aicom/v1/tools/yoast.site.get', 'post', 'requestBody', 'RequestBody', 'content', 'application/json', 'schema', 'Schema', 'properties'): Input should be a valid dictionary

    ('paths', '/wp-json/aicom/v1/tools/yoast.site.get', 'post', 'requestBody', 'Reference', '$ref'): Field required

    Am I doing something wrong?

    It looks like the Schema version isn’t compatible; do the other issues relate to plugins?

    I’m using Yoast SEO, Elementor, and JetEngine, all on their latest versions.

    I really like the idea behind the plugin and am very interested in using it.

Viewing 2 replies - 1 through 2 (of 2 total)
  • Thread Starter allanantoni

    (@allanantoni)

    <?php
    defined( 'ABSPATH' ) || exit;

    class AICOM_Schema_Generator {

    /**
    * Generate an OpenAPI 3.1.1 schema from all registered AICOM tools.
    *
    * @param array|null $allowed_scopes When non-null, only tools whose required_scopes
    * intersect this list are included (scope-filtered view).
    */
    public static function generate( ?array $allowed_scopes = null ): array {
    $tools = AICOM_Tool_Registry::get_all();
    $paths = [];

    $allowed_tools = [
    'session.open',
    'session.close',
    'site.summary',

    'wp.posts.list',
    'wp.posts.get',
    'wp.posts.create',
    'wp.posts.update',
    'wp.posts.preview_url',

    'wp.meta.get',
    'wp.meta.set',
    'wp.meta.set_many',

    'media.list',
    'media.get',
    'media.upload',
    'media.update_meta',
    'media.set_featured',

    'elementor.page.get_tree',
    'elementor.page.get_texts',
    'elementor.page.bulk_update_texts',
    'elementor.page.backup',
    'elementor.page.create_from_template',
    'elementor.page.regenerate_assets',

    'yoast.status',
    'yoast.post.get',
    'yoast.post.set',

    'aicom.recipes',
    ];

    foreach ( $tools as $name => $meta ) {

    if ( ! in_array( $name, $allowed_tools, true ) ) {
    continue;
    }


    // Skip internal meta-tools that aren't callable via REST (contain slashes)
    if ( strpos( $name, '/' ) !== false ) {
    continue;
    }

    if ( $allowed_scopes !== null ) {
    $required = $meta['required_scopes'] ?? [];
    if ( $required && ! array_intersect( $required, $allowed_scopes ) ) {
    continue;
    }
    }

    $operation_id = str_replace( [ '.', '-' ], '_', $name );
    $properties = [];
    $required_params = [];

    foreach ( $meta['input_schema'] ?? [] as $param => $schema ) {
    $properties[ $param ] = self::normalize_property_schema( (array) $schema );

    if ( ! empty( $schema['required'] ) ) {
    $required_params[] = $param;
    }
    }

    // IMPORTANT: empty PHP arrays are encoded as [] by wp_json_encode/json_encode.
    // OpenAPI requires properties to be an object, so empty properties must be {}.
    $request_schema = [
    'type' => 'object',
    'properties' => empty( $properties ) ? new stdClass() : $properties,
    ];

    if ( $required_params ) {
    $request_schema['required'] = $required_params;
    }

    $description = $meta['description'] ?? $name;
    if ( ! empty( $meta['supports_dry_run'] ) ) {
    $description .= ' Supports dry_run (pass dry_run: true to preview without changes).';
    }

    $paths[ '/wp-json/aicom/v1/tools/' . $name ] = [
    'post' => [
    'operationId' => $operation_id,
    'summary' => mb_substr( $meta['description'] ?? $name, 0, 290 ),
    'description' => mb_substr( $description, 0, 290 ),
    'tags' => [ $meta['module'] ?? 'core' ],
    'requestBody' => [
    'required' => ! empty( $required_params ),
    'content' => [
    'application/json' => [
    'schema' => $request_schema,
    ],
    ],
    ],
    'responses' => [
    '200' => [
    'description' => 'Tool result (errors also return HTTP 200; check body for error field)',
    ],
    ],
    ],
    ];
    }

    return [
    'openapi' => '3.1.1',
    'info' => [
    'title' => 'AICOM - AI Commander',
    'version' => AICOM_VERSION,
    'description' => 'WordPress MCP server. Manage content, media, users, WooCommerce, Elementor, Yoast SEO, accessibility, and more via AI agents. Import this schema into ChatGPT Custom GPT Actions, then set Authentication → API Key → Bearer with your AICOM key.',
    ],
    'servers' => [ [ 'url' => get_site_url() ] ],
    'security' => [ [ 'bearerAuth' => [] ] ],
    'components' => [
    'schemas' => new stdClass(),
    'securitySchemes' => [
    'bearerAuth' => [
    'type' => 'http',
    'scheme' => 'bearer',
    'description' => 'AICOM API key (format: aicom_XXXXXXXX_...)',
    ],
    ],
    ],
    'paths' => $paths,
    ];
    }

    /**
    * Convert AICOM's simple input_schema definitions into valid OpenAPI/JSON Schema.
    */
    private static function normalize_property_schema( array $schema ): array {
    $type = $schema['type'] ?? 'string';

    $prop = [ 'type' => $type ];

    if ( isset( $schema['description'] ) ) {
    $prop['description'] = $schema['description'];
    }
    if ( array_key_exists( 'default', $schema ) ) {
    $prop['default'] = $schema['default'];
    }
    if ( isset( $schema['enum'] ) ) {
    $prop['enum'] = $schema['enum'];
    }

    // OpenAPI/JSON Schema validators commonly require array schemas to define items.
    if ( $type === 'array' ) {
    if ( isset( $schema['items'] ) && is_array( $schema['items'] ) ) {
    $prop['items'] = self::normalize_property_schema( $schema['items'] );
    } else {
    // Accept any item type when the plugin did not define a more specific schema.
    $prop['items'] = new stdClass();
    }
    }

    // Keep object schemas flexible when no child properties are defined.
    if ( $type === 'object' ) {
    if ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) {
    $child_props = [];
    foreach ( $schema['properties'] as $child_name => $child_schema ) {
    $child_props[ $child_name ] = self::normalize_property_schema( (array) $child_schema );
    }
    $prop['properties'] = empty( $child_props ) ? new stdClass() : $child_props;
    } else {
    $prop['additionalProperties'] = true;
    }
    }

    return $prop;
    }
    }

    I made these adjustments, and they worked perfectly.

    Modified file:
    /public_html/wp-content/plugins/aicom/includes/class-schema-generator.php

    I hope this helps someone.

    Plugin Author dudaster

    (@dudaster)

    Hi @allanantoni. Thank you for the detailed report and for sharing your fix!You diagnosed it correctly: the schema generator was returning openapi: 3.0.0 (which ChatGPT rejects), and tools with no parameters were serializing their empty properties as a JSON array [] instead of an object {} : both valid PHP but invalid OpenAPI.This is now fixed in v3.9.0 (just released):- Upgraded to openapi: 3.1.1- Empty properties now correctly serialize as {} using stdClass- Added components.schemas: {} required by strict validators- normalize_property_schema() helper for array/object typesNo manual file edits needed going forward, just update the plugin. Thank you again for taking the time to investigate and share the solution!

    • This reply was modified 5 days, 1 hour ago by dudaster.
Viewing 2 replies - 1 through 2 (of 2 total)

You must be logged in to reply to this topic.