Skip to content

Commit 7c61f73

Browse files
committed
Consolidate comment status into save-comment tool
Remove standalone approve-comment and spam-comment tools. Status changes are now handled via the status param on save-comment, which accepts: approve, approved, hold, unapproved, spam, or trash. Add normalize_comment_status() to map user-facing aliases to the values expected by wp_insert_comment and wp_set_comment_status. Update save_comment_tool to apply status via wp_set_comment_status when provided. Tool count updated from 55 to 53 in unit and integration tests.
1 parent 3674962 commit 7c61f73

3 files changed

Lines changed: 93 additions & 38 deletions

File tree

src/Tools/CommentManagementTools.php

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ private function add_comment_abilities() {
4848
'content' => $this->string_prop( 'Comment content.' ),
4949
'author_name' => $this->string_prop( 'Author name.' ),
5050
'author_email' => $this->string_prop( 'Author email.' ),
51-
'status' => $this->string_prop( 'Comment status.', 'hold' ),
51+
'status' => $this->string_prop( 'Comment status: approve, approved, hold, unapproved, spam, or trash.', 'hold' ),
5252
)
5353
), function ( $params ) {
5454
return $this->save_comment_tool( $params );
@@ -57,14 +57,6 @@ private function add_comment_abilities() {
5757
$this->add_ability( self::INTERNAL_PREFIX . 'delete-comment', 'Delete Comment', 'Delete a WordPress comment by ID', $id_schema, function ( $params ) {
5858
return $this->delete_comment_tool( (int) $params['id'] );
5959
}, false, 'moderate_comments' );
60-
61-
$this->add_ability( self::INTERNAL_PREFIX . 'approve-comment', 'Approve Comment', 'Approve a WordPress comment by ID', $id_schema, function ( $params ) {
62-
return $this->set_comment_status_tool( (int) $params['id'], 'approve' );
63-
}, false, 'moderate_comments' );
64-
65-
$this->add_ability( self::INTERNAL_PREFIX . 'spam-comment', 'Spam Comment', 'Mark a WordPress comment as spam by ID', $id_schema, function ( $params ) {
66-
return $this->set_comment_status_tool( (int) $params['id'], 'spam' );
67-
}, false, 'moderate_comments' );
6860
}
6961

7062
/**
@@ -138,12 +130,17 @@ private function add_comment_tool( $params ) {
138130
return Response::error( 'This ability requires a WordPress runtime.', 500 );
139131
}
140132

133+
$status = isset( $params['status'] ) ? $this->normalize_comment_status( $params['status'], 'insert' ) : '0';
134+
if ( is_array( $status ) ) {
135+
return $status;
136+
}
137+
141138
return Response::unwrap_wp_error( wp_insert_comment( array(
142139
'comment_post_ID' => (int) $params['post_id'],
143140
'comment_content' => $params['content'],
144141
'comment_author' => isset( $params['author_name'] ) ? $params['author_name'] : '',
145142
'comment_author_email' => isset( $params['author_email'] ) ? $params['author_email'] : '',
146-
'comment_approved' => isset( $params['status'] ) ? $params['status'] : 'hold',
143+
'comment_approved' => $status,
147144
) ) );
148145
}
149146

@@ -162,11 +159,29 @@ private function update_comment_tool( $params ) {
162159
if ( isset( $params['content'] ) ) {
163160
$data['comment_content'] = $params['content'];
164161
}
162+
163+
$result = true;
164+
if ( count( $data ) > 1 ) {
165+
$result = Response::unwrap_wp_error( wp_update_comment( $data, true ) );
166+
if ( is_array( $result ) && isset( $result['status'] ) && 'error' === $result['status'] ) {
167+
return $result;
168+
}
169+
}
170+
165171
if ( isset( $params['status'] ) ) {
166-
$data['comment_approved'] = $params['status'];
172+
if ( ! function_exists( 'wp_set_comment_status' ) ) {
173+
return Response::error( 'This ability requires a WordPress runtime.', 500 );
174+
}
175+
176+
$status = $this->normalize_comment_status( $params['status'], 'update' );
177+
if ( is_array( $status ) ) {
178+
return $status;
179+
}
180+
181+
return Response::unwrap_wp_error( wp_set_comment_status( (int) $params['id'], $status, true ) );
167182
}
168183

169-
return Response::unwrap_wp_error( wp_update_comment( $data, true ) );
184+
return $result;
170185
}
171186

172187
/**
@@ -186,21 +201,6 @@ private function delete_comment_tool( $id ) {
186201
);
187202
}
188203

189-
/**
190-
* Set comment status.
191-
*
192-
* @param int $id Comment ID.
193-
* @param string $status Status.
194-
* @return mixed
195-
*/
196-
private function set_comment_status_tool( $id, $status ) {
197-
if ( ! function_exists( 'wp_set_comment_status' ) ) {
198-
return Response::error( 'This ability requires a WordPress runtime.', 500 );
199-
}
200-
201-
return Response::unwrap_wp_error( wp_set_comment_status( $id, $status, true ) );
202-
}
203-
204204
/**
205205
* Format comment.
206206
*
@@ -218,4 +218,59 @@ private function format_comment( $comment ) {
218218
'date' => $comment->comment_date,
219219
);
220220
}
221+
222+
/**
223+
* Normalize user-facing comment statuses for WordPress comment APIs.
224+
*
225+
* @param mixed $status Status value.
226+
* @param string $context API context: insert or update.
227+
* @return string|array<string,mixed>
228+
*/
229+
private function normalize_comment_status( $status, $context ) {
230+
$status = strtolower( trim( (string) $status ) );
231+
$map = array(
232+
'1' => array(
233+
'insert' => '1',
234+
'update' => 'approve',
235+
),
236+
'approve' => array(
237+
'insert' => '1',
238+
'update' => 'approve',
239+
),
240+
'approved' => array(
241+
'insert' => '1',
242+
'update' => 'approve',
243+
),
244+
'0' => array(
245+
'insert' => '0',
246+
'update' => 'hold',
247+
),
248+
'hold' => array(
249+
'insert' => '0',
250+
'update' => 'hold',
251+
),
252+
'pending' => array(
253+
'insert' => '0',
254+
'update' => 'hold',
255+
),
256+
'unapproved' => array(
257+
'insert' => '0',
258+
'update' => 'hold',
259+
),
260+
'spam' => array(
261+
'insert' => 'spam',
262+
'update' => 'spam',
263+
),
264+
'trash' => array(
265+
'insert' => 'trash',
266+
'update' => 'trash',
267+
),
268+
);
269+
270+
if ( ! isset( $map[ $status ][ $context ] ) ) {
271+
return Response::error( 'Unsupported comment status: ' . $status, 400 );
272+
}
273+
274+
return $map[ $status ][ $context ];
275+
}
221276
}

tests/integration/mcp-endpoint.mjs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,6 @@ const expectedTools = [
214214
'wp-forge-get-comment',
215215
'wp-forge-save-comment',
216216
'wp-forge-delete-comment',
217-
'wp-forge-approve-comment',
218-
'wp-forge-spam-comment',
219217
'wp-forge-get-site-health-info',
220218
'wp-forge-list-site-health-tests',
221219
'wp-forge-read-error-log',
@@ -397,8 +395,12 @@ await expectSuccess('wp-forge-list-comments', { post_id: postId, per_page: 5 });
397395
const comment = await expectSuccess('wp-forge-get-comment', { id: commentId });
398396
assert(comment.id === commentId, 'wp-forge-get-comment returned the wrong comment');
399397
await expectSuccess('wp-forge-save-comment', { id: commentId, content: `MCP comment updated ${suffix}` });
400-
await expectSuccess('wp-forge-approve-comment', { id: commentId });
401-
await expectSuccess('wp-forge-spam-comment', { id: commentId });
398+
await expectSuccess('wp-forge-save-comment', { id: commentId, status: 'approved' });
399+
const approvedComment = await expectSuccess('wp-forge-get-comment', { id: commentId });
400+
assert(approvedComment.status === 'approved', 'wp-forge-save-comment did not approve the comment');
401+
await expectSuccess('wp-forge-save-comment', { id: commentId, status: 'spam' });
402+
const spamComment = await expectSuccess('wp-forge-get-comment', { id: commentId });
403+
assert(spamComment.status === 'spam', 'wp-forge-save-comment did not mark the comment as spam');
402404
await expectSuccess('wp-forge-delete-comment', { id: commentId });
403405

404406
const siteHealthInfo = await expectSuccess('wp-forge-get-site-health-info');

tests/run.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ function assert_same( $expected, $actual, $message ) {
171171
$all = $abilities->list_abilities();
172172
$names = array_column( $all, 'name' );
173173

174-
assert_same( 55, count( $all ), 'Expected the WordPress ability catalog.' );
174+
assert_same( 53, count( $all ), 'Expected the WordPress ability catalog.' );
175175
assert_true( in_array( 'wp-forge-search-content', $names, true ), 'Expected content search ability.' );
176176
assert_true( in_array( 'wp-forge-get-site-info', $names, true ), 'Expected site info ability.' );
177177
assert_true( in_array( 'wp-forge-run-api-function', $names, true ), 'Expected REST runner ability.' );
@@ -219,8 +219,6 @@ function assert_same( $expected, $actual, $message ) {
219219
'wp-forge-get-comment',
220220
'wp-forge-save-comment',
221221
'wp-forge-delete-comment',
222-
'wp-forge-approve-comment',
223-
'wp-forge-spam-comment',
224222
'wp-forge-get-site-health-info',
225223
'wp-forge-list-site-health-tests',
226224
'wp-forge-read-error-log',
@@ -253,7 +251,7 @@ function assert_same( $expected, $actual, $message ) {
253251

254252
$direct_tools = $abilities->list_tools();
255253
$direct_tool_names = array_column( $direct_tools, 'name' );
256-
assert_same( 55, count( $direct_tools ), 'Expected all abilities to be exposed as direct MCP tools.' );
254+
assert_same( 53, count( $direct_tools ), 'Expected all abilities to be exposed as direct MCP tools.' );
257255
assert_true( in_array( 'wp-forge-search-content', $direct_tool_names, true ), 'Direct tool list should include content search.' );
258256
assert_true( in_array( 'wp-forge-get-active-theme', $direct_tool_names, true ), 'Direct tool list should include active theme.' );
259257
assert_true( ! in_array( 'wp-forge-list-abilities', $direct_tool_names, true ), 'Gateway list tool should not be exposed.' );
@@ -295,11 +293,11 @@ function assert_same( $expected, $actual, $message ) {
295293
assert_same( array( 'page' ), array_column( $public_hierarchical_post_types['message']['post_types'], 'slug' ), 'Post type list should pass filters through to get_post_types().' );
296294

297295
$wp_ability_names = $abilities->get_wordpress_ability_names();
298-
assert_same( 55, count( $wp_ability_names ), 'Expected all abilities to be available for the MCP adapter.' );
296+
assert_same( 53, count( $wp_ability_names ), 'Expected all abilities to be available for the MCP adapter.' );
299297
assert_true( in_array( 'wp-forge/search-content', $wp_ability_names, true ), 'Adapter ability list should use WordPress ability names.' );
300298

301299
$abilities->register_wordpress_abilities();
302-
assert_same( 55, count( $registered_abilities ), 'Expected every ability to be registered with the WordPress Abilities API.' );
300+
assert_same( 53, count( $registered_abilities ), 'Expected every ability to be registered with the WordPress Abilities API.' );
303301
assert_true( isset( $registered_abilities['wp-forge/search-content'] ), 'Content search should be registered with the WordPress Abilities API.' );
304302
assert_same( 'Search and filter content for any registered post type', $registered_abilities['wp-forge/search-content']['description'], 'Registered ability should preserve descriptions.' );
305303
assert_same( true, $registered_abilities['wp-forge/search-content']['meta']['show_in_rest'], 'Registered abilities should be exposed through the Abilities REST API.' );
@@ -330,7 +328,7 @@ public function create_server() {
330328
assert_same( 'mcp', $adapter->args[1], 'Adapter server should keep the existing REST namespace.' );
331329
assert_same( 'wp-forge', $adapter->args[2], 'Adapter server should keep the existing REST route.' );
332330
assert_same( 'WordPress MCP', $adapter->args[3], 'Adapter server should preserve the server name.' );
333-
assert_same( 55, count( $adapter->args[9] ), 'Adapter server should expose every registered ability.' );
331+
assert_same( 53, count( $adapter->args[9] ), 'Adapter server should expose every registered ability.' );
334332
assert_true( in_array( 'wp-forge/search-content', $adapter->args[9], true ), 'Adapter server should expose content search.' );
335333

336334
echo 'Tests passed: ' . $tests_run . PHP_EOL;

0 commit comments

Comments
 (0)