Skip to content

Commit 2dd426c

Browse files
authored
Merge pull request #126 from a8cteam51/feat/mcp-atlantis-status
Expose wpcom:atlantis-status via MCP
2 parents e7ff322 + ad1bbc2 commit 2dd426c

3 files changed

Lines changed: 179 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ claude mcp add team51 -- team51 --mcp
9797

9898
### Available Tools
9999

100-
The MCP server currently exposes 66 tools across all services. The table below shows a subset of commonly used tools:
100+
The MCP server currently exposes 67 tools across all services. The table below shows a subset of commonly used tools:
101101

102102
| Service | Read Tools | Write Tools |
103103
|---------|-----------|-------------|

commands/WPCOM_Atlantis_Status.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,11 @@ private function resolve_boolean_flag( InputInterface $input, OutputInterface $o
370370
*/
371371
private function initialize_single_site( string $site_id_or_url, OutputInterface $output ): void {
372372
$lookup = $site_id_or_url;
373-
if ( \str_contains( $lookup, 'http' ) ) {
374-
$host = \parse_url( $lookup, PHP_URL_HOST );
375-
if ( ! \is_string( $host ) || '' === $host ) {
376-
throw new \InvalidArgumentException( "Invalid URL '$site_id_or_url'." );
377-
}
373+
$host = \parse_url( $lookup, PHP_URL_HOST );
374+
if ( ! \is_string( $host ) || '' === $host ) {
375+
$host = \parse_url( 'https://' . \ltrim( $lookup, '/' ), PHP_URL_HOST );
376+
}
377+
if ( \is_string( $host ) && '' !== $host ) {
378378
$lookup = $host;
379379
}
380380

mcp/Team51McpTools.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,179 @@ public function wpcom_get_site( string $site_id_or_url ): array {
296296
return (array) $site;
297297
}
298298

299+
/**
300+
* Report the status of the Atlantis plugin and its modules across
301+
* Jetpack-connected WordPress.com sites.
302+
*
303+
* When `$site_id_or_url` is provided, returns the status for that single
304+
* site only and `$exclude_staging` is ignored. Otherwise queries the full
305+
* Jetpack-connected fleet. Sites without Atlantis are reported with
306+
* `atlantis_installed: false`.
307+
*
308+
* @param string|null $site_id_or_url Optional. Single site URL or WPCOM numeric ID.
309+
* @param string|null $module Optional. Restrict the report to a single module column. One of: messages, colophon, tracking, autoupdates.
310+
* @param bool $exclude_staging Exclude sites whose URL contains "staging" (case-insensitive). Ignored in single-site mode.
311+
* @param bool $issues_only Only return sites where Atlantis is not installed or at least one module is not on.
312+
* @param bool $with_messages_only Only return sites that have at least one stored Atlantis custom message.
313+
*/
314+
#[McpTool( name: 'wpcom_get_atlantis_status' )]
315+
public function wpcom_get_atlantis_status(
316+
?string $site_id_or_url = null,
317+
?string $module = null,
318+
bool $exclude_staging = false,
319+
bool $issues_only = false,
320+
bool $with_messages_only = false
321+
): array {
322+
$identity_error = self::ensure_identity();
323+
if ( $identity_error ) {
324+
return $identity_error;
325+
}
326+
327+
// Keep this list in sync with WPCOM_Atlantis_Status::KNOWN_MODULE_KEYS.
328+
$known_modules = array( 'messages', 'colophon', 'tracking', 'autoupdates' );
329+
330+
$module_keys = $known_modules;
331+
if ( ! \is_null( $module ) ) {
332+
$module = \strtolower( $module );
333+
if ( ! \in_array( $module, $known_modules, true ) ) {
334+
return array(
335+
'error' => "Unknown module '$module'. Accepted values: " . \implode( ', ', $known_modules ) . '.',
336+
);
337+
}
338+
$module_keys = array( $module );
339+
}
340+
341+
$sites = get_wpcom_jetpack_sites();
342+
if ( null === $sites ) {
343+
return array( 'error' => 'Failed to fetch Jetpack-connected sites.' );
344+
}
345+
346+
if ( ! \is_null( $site_id_or_url ) ) {
347+
$lookup = $site_id_or_url;
348+
$host = \parse_url( $lookup, PHP_URL_HOST );
349+
if ( ! \is_string( $host ) || '' === $host ) {
350+
$host = \parse_url( 'https://' . \ltrim( $lookup, '/' ), PHP_URL_HOST );
351+
}
352+
if ( \is_string( $host ) && '' !== $host ) {
353+
$lookup = $host;
354+
}
355+
356+
$matched = null;
357+
if ( \ctype_digit( $lookup ) ) {
358+
$matched = $sites[ (int) $lookup ] ?? null;
359+
} else {
360+
$lookup_lc = \strtolower( $lookup );
361+
foreach ( $sites as $site ) {
362+
$candidate_host = \parse_url( (string) ( $site->siteurl ?? '' ), PHP_URL_HOST );
363+
if ( \is_string( $candidate_host ) && \strtolower( $candidate_host ) === $lookup_lc ) {
364+
$matched = $site;
365+
break;
366+
}
367+
}
368+
}
369+
370+
if ( null === $matched ) {
371+
return array( 'error' => "Site '$site_id_or_url' is not in the connected Jetpack sites list." );
372+
}
373+
374+
$site_id = (int) ( $matched->userblog_id ?? 0 );
375+
if ( 0 === $site_id ) {
376+
return array( 'error' => "Resolved site '$site_id_or_url' has no usable ID." );
377+
}
378+
379+
$sites = array( $site_id => $matched );
380+
} elseif ( $exclude_staging ) {
381+
$sites = \array_filter(
382+
$sites,
383+
static fn( $site ) => false === \stripos( (string) ( $site->siteurl ?? '' ), 'staging' )
384+
);
385+
}
386+
387+
$errors = array();
388+
$statuses = get_wpcom_sites_atlantis_status_batch( \array_column( $sites, 'userblog_id' ), $errors );
389+
if ( null === $statuses ) {
390+
return array( 'error' => 'Failed to fetch Atlantis status batch.' );
391+
}
392+
393+
$rows = array();
394+
$installed_count = 0;
395+
$issue_count = 0;
396+
$sites_with_messages_count = 0;
397+
$module_enabled_counts = \array_fill_keys( $module_keys, 0 );
398+
399+
foreach ( $sites as $site_id => $site ) {
400+
$status = $statuses[ $site_id ] ?? null;
401+
402+
$row = array(
403+
'site_id' => (int) $site->userblog_id,
404+
'site_url' => $site->siteurl ?? null,
405+
'atlantis_installed' => false,
406+
'atlantis_version' => null,
407+
'custom_messages_count' => null,
408+
'modules' => \array_fill_keys( $module_keys, null ),
409+
);
410+
411+
$has_issue = true;
412+
$messages_count = 0;
413+
414+
if ( \is_object( $status ) ) {
415+
++$installed_count;
416+
$has_issue = false;
417+
$row['atlantis_installed'] = true;
418+
$row['atlantis_version'] = $status->plugin->version ?? 'unknown';
419+
420+
$count_raw = $status->modules->messages->count ?? null;
421+
if ( \is_int( $count_raw ) || ( \is_string( $count_raw ) && \ctype_digit( $count_raw ) ) ) {
422+
$messages_count = (int) $count_raw;
423+
$row['custom_messages_count'] = $messages_count;
424+
if ( $messages_count > 0 ) {
425+
++$sites_with_messages_count;
426+
}
427+
}
428+
429+
foreach ( $module_keys as $module_key ) {
430+
$enabled = $status->modules->$module_key->enabled ?? null;
431+
if ( true === $enabled ) {
432+
$row['modules'][ $module_key ] = 'on';
433+
++$module_enabled_counts[ $module_key ];
434+
} elseif ( false === $enabled ) {
435+
$row['modules'][ $module_key ] = 'off';
436+
$has_issue = true;
437+
} else {
438+
$has_issue = true;
439+
}
440+
}
441+
}
442+
443+
if ( $has_issue ) {
444+
++$issue_count;
445+
}
446+
447+
if ( $issues_only && ! $has_issue ) {
448+
continue;
449+
}
450+
if ( $with_messages_only && $messages_count <= 0 ) {
451+
continue;
452+
}
453+
454+
$rows[] = $row;
455+
}
456+
457+
return array(
458+
'count' => count( $rows ),
459+
'sites' => $rows,
460+
'errors' => $errors,
461+
'summary' => array(
462+
'total_sites_queried' => count( $sites ),
463+
'sites_with_atlantis' => $installed_count,
464+
'sites_with_issues' => $issue_count,
465+
'sites_with_messages' => $sites_with_messages_count,
466+
'modules_on' => $module_enabled_counts,
467+
'errors_count' => count( $errors ),
468+
),
469+
);
470+
}
471+
299472
/**
300473
* List plugins installed on a specific WordPress.com or Jetpack-connected site.
301474
*

0 commit comments

Comments
 (0)