WP_REST_Server::ALLMETHODS, 'callback' => array( __CLASS__, 'handle_request' ), 'permission_callback' => array( Agentmatica_Auth::class, 'verify_request_token' ), ) ); } /** * @param WP_REST_Request $request Incoming REST request. * @return WP_REST_Response|WP_Error */ public static function handle_request( WP_REST_Request $request ) { if ( 'GET' === $request->get_method() ) { return new WP_REST_Response( array( 'jsonrpc' => '2.0', 'error' => array( 'code' => -32000, 'message' => __( 'SSE transport is not supported. Use POST JSON-RPC.', 'agentmatica-mcp-connector' ), ), 'id' => null, ), 405 ); } $payload = $request->get_json_params(); if ( ! is_array( $payload ) ) { return self::json_rpc_error( null, -32700, __( 'Parse error', 'agentmatica-mcp-connector' ) ); } if ( self::is_batch_request( $payload ) ) { $responses = array(); foreach ( $payload as $message ) { if ( ! is_array( $message ) ) { $responses[] = self::build_json_rpc_error( null, -32600, __( 'Invalid Request', 'agentmatica-mcp-connector' ) ); continue; } $responses[] = self::dispatch_message( $message ); } return new WP_REST_Response( $responses, 200 ); } return self::dispatch_message( $payload ); } /** * @param array $message JSON-RPC message. * @return WP_REST_Response */ private static function dispatch_message( array $message ): WP_REST_Response { $method = isset( $message['method'] ) ? (string) $message['method'] : ''; $id = array_key_exists( 'id', $message ) ? $message['id'] : null; $params = isset( $message['params'] ) && is_array( $message['params'] ) ? $message['params'] : array(); if ( '' === $method ) { return self::json_rpc_error( $id, -32600, __( 'Invalid Request', 'agentmatica-mcp-connector' ) ); } if ( 0 === strpos( $method, 'notifications/' ) ) { return new WP_REST_Response( null, 202 ); } if ( null === $id ) { return self::json_rpc_error( null, -32600, __( 'Invalid Request', 'agentmatica-mcp-connector' ) ); } try { switch ( $method ) { case 'initialize': return self::json_rpc_result( $id, self::handle_initialize( $params ) ); case 'ping': return self::json_rpc_result( $id, new stdClass() ); case 'tools/list': return self::json_rpc_result( $id, self::handle_tools_list() ); case 'tools/call': return self::json_rpc_result( $id, self::handle_tools_call( $params ) ); default: return self::json_rpc_error( $id, -32601, __( 'Method not found', 'agentmatica-mcp-connector' ) ); } } catch ( Throwable $exception ) { return self::json_rpc_error( $id, -32603, __( 'Internal error', 'agentmatica-mcp-connector' ), array( 'detail' => $exception->getMessage(), ) ); } } /** * @param array $params Initialize params. * @return array */ private static function handle_initialize( array $params ): array { $protocol_version = isset( $params['protocolVersion'] ) ? sanitize_text_field( (string) $params['protocolVersion'] ) : self::DEFAULT_PROTOCOL_VERSION; return array( 'protocolVersion' => $protocol_version, 'capabilities' => array( 'tools' => new stdClass(), ), 'serverInfo' => array( 'name' => 'Agentmatica WordPress UCP MCP', 'version' => AGENTMATICA_VERSION, ), ); } /** * @return array */ private static function handle_tools_list(): array { $tools = array( self::build_tool_definition( 'search_catalog', __( 'Search the store product catalog (UCP). Matches product titles, categories, SKU, and description; handles common singular/plural forms (e.g. shirt/shirts).', 'agentmatica-mcp-connector' ), self::catalog_input_schema() ), self::build_tool_definition( 'lookup_catalog', __( 'Lookup products or variants by identifier (UCP).', 'agentmatica-mcp-connector' ), self::lookup_input_schema() ), self::build_tool_definition( 'get_product', __( 'Get full product details (UCP).', 'agentmatica-mcp-connector' ), self::get_product_input_schema() ), self::build_tool_definition( 'agentmatica_site_info', __( 'Site name, URL, tagline, and language.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => new stdClass(), ) ), self::build_tool_definition( 'agentmatica_pages_list', __( 'List published WordPress pages.', 'agentmatica-mcp-connector' ), self::content_list_schema() ), self::build_tool_definition( 'agentmatica_posts_list', __( 'List published WordPress posts.', 'agentmatica-mcp-connector' ), self::content_list_schema() ), self::build_tool_definition( 'agentmatica_content_get', __( 'Fetch a single published page or post.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'page', 'post' ), ), 'id' => array( 'type' => 'integer' ), 'slug' => array( 'type' => 'string' ), ), 'required' => array( 'type' ), ) ), ); if ( class_exists( 'WooCommerce' ) ) { $tools[] = self::build_tool_definition( 'export_catalog_snapshot', __( 'Internal Agentmatica catalog snapshot for inventory-map generation. Not for visitor chat.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'page' => array( 'type' => 'integer', 'minimum' => 1, ), 'page_size' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, ), ), ) ); $tools[] = self::build_tool_definition( 'create_cart', __( 'Create or update a guest cart session (UCP).', 'agentmatica-mcp-connector' ), self::cart_input_schema() ); $tools[] = self::build_tool_definition( 'update_cart', __( 'Update a guest cart session (UCP).', 'agentmatica-mcp-connector' ), self::cart_input_schema() ); $tools[] = self::build_tool_definition( 'get_cart', __( 'Get cart session state (UCP).', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'string' ), ), ) ); $tools[] = self::build_tool_definition( 'create_checkout', __( 'Create checkout from cart (UCP).', 'agentmatica-mcp-connector' ), self::checkout_input_schema() ); $tools = array_merge( $tools, self::woocommerce_support_tools() ); } return array( 'tools' => $tools ); } /** * @param array $params Tool call params. * @return array */ private static function handle_tools_call( array $params ): array { Agentmatica_Ability_Delegation::set_mcp_authenticated( true ); try { return self::handle_tools_call_inner( $params ); } finally { Agentmatica_Ability_Delegation::set_mcp_authenticated( false ); } } /** * @param array $params Tool call params. * @return array */ private static function handle_tools_call_inner( array $params ): array { $tool_name = isset( $params['name'] ) ? sanitize_text_field( (string) $params['name'] ) : ''; $arguments = isset( $params['arguments'] ) && is_array( $params['arguments'] ) ? $params['arguments'] : array(); if ( '' === $tool_name ) { throw new InvalidArgumentException( 'Tool name is required.' ); } switch ( $tool_name ) { case 'search_catalog': $structured = Agentmatica_Ucp_Catalog::search_catalog( $arguments ); break; case 'lookup_catalog': $structured = Agentmatica_Ucp_Catalog::lookup_catalog( $arguments ); break; case 'get_product': $structured = Agentmatica_Ucp_Catalog::get_product( $arguments ); break; case 'export_catalog_snapshot': $structured = Agentmatica_Ucp_Catalog::export_catalog_snapshot( $arguments ); break; case 'create_cart': $structured = Agentmatica_Ucp_Cart::create_cart( $arguments ); break; case 'update_cart': $structured = Agentmatica_Ucp_Cart::update_cart( $arguments ); break; case 'get_cart': $structured = Agentmatica_Ucp_Cart::get_cart( $arguments ); break; case 'create_checkout': $structured = Agentmatica_Ucp_Cart::create_checkout( $arguments ); break; case 'agentmatica_site_info': $structured = Agentmatica_Abilities::execute( Agentmatica_Content_Module::SITE_INFO, array() ); break; case 'agentmatica_pages_list': $structured = Agentmatica_Abilities::execute( Agentmatica_Content_Module::PAGES_LIST, self::content_tool_parameters( $arguments ) ); break; case 'agentmatica_posts_list': $structured = Agentmatica_Abilities::execute( Agentmatica_Content_Module::POSTS_LIST, self::content_tool_parameters( $arguments ) ); break; case 'agentmatica_content_get': $structured = Agentmatica_Abilities::execute( Agentmatica_Content_Module::CONTENT_GET, self::content_tool_parameters( $arguments ) ); break; case 'customer_verify': $structured = Agentmatica_WooCommerce_Orders::customer_verify( $arguments ); break; case 'orders_list': $structured = Agentmatica_WooCommerce_Orders::orders_list( $arguments ); break; case 'order_get': $structured = Agentmatica_WooCommerce_Orders::order_get( $arguments ); break; case 'verify_customer_token': $structured = Agentmatica_WooCommerce_Module::verify_customer_token( $arguments ); break; case 'cancel_woo_order': $structured = Agentmatica_WooCommerce_Order_Mutations::cancel_order( $arguments ); break; case 'update_woo_order_address': $structured = Agentmatica_WooCommerce_Order_Mutations::update_shipping_address( $arguments ); break; case 'request_woo_return': $structured = Agentmatica_WooCommerce_Order_Mutations::request_return( $arguments ); break; default: throw new InvalidArgumentException( esc_html( sprintf( /* translators: %s: tool name */ __( 'Unknown tool: %s', 'agentmatica-mcp-connector' ), sanitize_text_field( $tool_name ) ) ) ); } return self::wrap_tool_result( $structured ); } /** * @return list> */ private static function woocommerce_support_tools(): array { $session_schema = array( 'type' => 'object', 'properties' => array( 'session_key' => array( 'type' => 'string' ), ), 'required' => array( 'session_key' ), ); $tools = array( self::build_tool_definition( 'customer_verify', __( 'Verify order number + billing email and bind this chat session.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'session_key' => array( 'type' => 'string' ), 'order_number' => array( 'type' => 'string' ), 'email' => array( 'type' => 'string' ), ), 'required' => array( 'session_key', 'order_number', 'email' ), ) ), self::build_tool_definition( 'orders_list', __( 'List recent orders for a verified chat session.', 'agentmatica-mcp-connector' ), $session_schema ), self::build_tool_definition( 'order_get', __( 'Get order details for a verified chat session.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'session_key' => array( 'type' => 'string' ), 'order_number' => array( 'type' => 'string' ), ), 'required' => array( 'session_key', 'order_number' ), ) ), self::build_tool_definition( 'verify_customer_token', __( 'Validate a short-lived logged-in customer token.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'session_key' => array( 'type' => 'string' ), 'customer_token' => array( 'type' => 'string' ), ), 'required' => array( 'session_key', 'customer_token' ), ) ), ); if ( Agentmatica_Support_Settings::is_write_actions_enabled() ) { $tools[] = self::build_tool_definition( 'cancel_woo_order', __( 'Cancel an unfulfilled order for a logged-in verified customer.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'session_key' => array( 'type' => 'string' ), 'order_number' => array( 'type' => 'string' ), 'reason' => array( 'type' => 'string' ), 'confirmed' => array( 'type' => 'boolean' ), 'cancel_refund_mode' => array( 'type' => 'string', 'enum' => array( 'none', 'original' ), ), ), 'required' => array( 'session_key', 'order_number' ), ) ); $tools[] = self::build_tool_definition( 'update_woo_order_address', __( 'Update shipping address on an unfulfilled order.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'session_key' => array( 'type' => 'string' ), 'order_number' => array( 'type' => 'string' ), 'first_name' => array( 'type' => 'string' ), 'last_name' => array( 'type' => 'string' ), 'address1' => array( 'type' => 'string' ), 'address2' => array( 'type' => 'string' ), 'city' => array( 'type' => 'string' ), 'zip' => array( 'type' => 'string' ), 'province' => array( 'type' => 'string' ), 'state' => array( 'type' => 'string' ), 'country' => array( 'type' => 'string' ), 'country_code' => array( 'type' => 'string' ), 'confirmed' => array( 'type' => 'boolean' ), 'address_same_region_only' => array( 'type' => 'boolean' ), ), 'required' => array( 'session_key', 'order_number', 'address1', 'city', 'zip' ), ) ); $tools[] = self::build_tool_definition( 'request_woo_return', __( 'Submit a return request for merchant review.', 'agentmatica-mcp-connector' ), array( 'type' => 'object', 'properties' => array( 'session_key' => array( 'type' => 'string' ), 'order_number' => array( 'type' => 'string' ), 'reason' => array( 'type' => 'string' ), 'confirmed' => array( 'type' => 'boolean' ), ), 'required' => array( 'session_key', 'order_number' ), ) ); } return $tools; } /** * @param array $arguments Tool arguments. * @return array */ private static function content_tool_parameters( array $arguments ): array { unset( $arguments['meta'] ); return $arguments; } /** * @return array */ private static function catalog_input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'meta' => array( 'type' => 'object' ), 'catalog' => array( 'type' => 'object', 'properties' => array( 'query' => array( 'type' => 'string' ), 'pagination' => array( 'type' => 'object', 'properties' => array( 'limit' => array( 'type' => 'integer' ), ), ), ), ), ), ); } /** * @return array */ private static function lookup_input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'meta' => array( 'type' => 'object' ), 'catalog' => array( 'type' => 'object', 'properties' => array( 'ids' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), ), ), 'required' => array( 'ids' ), ), ), ); } /** * @return array */ private static function get_product_input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'meta' => array( 'type' => 'object' ), 'catalog' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'string' ), ), 'required' => array( 'id' ), ), ), ); } /** * @return array */ private static function cart_input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'meta' => array( 'type' => 'object' ), 'cart' => array( 'type' => 'object', 'properties' => array( 'context' => array( 'type' => 'object' ), 'line_items' => array( 'type' => 'array' ), ), ), ), ); } /** * @return array */ private static function checkout_input_schema(): array { return array( 'type' => 'object', 'properties' => array( 'meta' => array( 'type' => 'object' ), 'checkout' => array( 'type' => 'object', 'properties' => array( 'cart_id' => array( 'type' => 'string' ), ), ), ), ); } /** * @return array */ private static function content_list_schema(): array { return array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string' ), 'per_page' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ), ), ); } /** * @param array $structured Structured tool payload. * @return array */ private static function wrap_tool_result( array $structured ): array { return array( 'content' => array( array( 'type' => 'text', 'text' => wp_json_encode( $structured ), ), ), 'structuredContent' => $structured, ); } /** * @param string $name Tool name. * @param string $description Tool description. * @param array $input_schema JSON schema. * @return array */ private static function build_tool_definition( string $name, string $description, array $input_schema ): array { return array( 'name' => $name, 'description' => $description, 'inputSchema' => $input_schema, ); } /** * @param mixed $payload Request body. */ private static function is_batch_request( $payload ): bool { if ( ! is_array( $payload ) || array() === $payload ) { return false; } return array_keys( $payload ) === range( 0, count( $payload ) - 1 ); } /** * @param mixed $id Request id. * @param array $result JSON-RPC result. */ private static function json_rpc_result( $id, $result ): WP_REST_Response { return new WP_REST_Response( array( 'jsonrpc' => '2.0', 'id' => $id, 'result' => $result, ), 200 ); } /** * @param mixed $id Request id. * @param int $code Error code. * @param string $message Error message. * @param array $data Optional error data. */ private static function json_rpc_error( $id, int $code, string $message, array $data = array() ): WP_REST_Response { return new WP_REST_Response( self::build_json_rpc_error( $id, $code, $message, $data ), 200 ); } /** * @param mixed $id Request id. * @param int $code Error code. * @param string $message Error message. * @param array $data Optional error data. * @return array */ private static function build_json_rpc_error( $id, int $code, string $message, array $data = array() ): array { $error = array( 'code' => $code, 'message' => $message, ); if ( ! empty( $data ) ) { $error['data'] = $data; } return array( 'jsonrpc' => '2.0', 'id' => $id, 'error' => $error, ); } }