Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion admin/build/index.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '410d31640c83864208ef');
<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '3a8e65d912ca9ca5e467');
8 changes: 4 additions & 4 deletions admin/build/index.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions admin/src/components/McpDiagnostics.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,13 @@ export default function McpDiagnostics() {
<p className="saddle-mcp-diag__health">
{ health.registered === undefined
? __(
'No app has connected yet, so there’s nothing to report.',
'No app has asked for the tool list yet. Requests are recorded below either way.',
'saddle'
)
: sprintf(
/* translators: 1: number of tools available, 2: number expected. */
/* translators: 1: number of tools that loaded, 2: number installed. */
__(
'%1$d of %2$d tools are available to connected apps.',
'%1$d of the %2$d tools installed here loaded correctly. How many an app is offered depends on its access level — the requests below show that number.',
'saddle'
),
health.registered,
Expand Down
25 changes: 25 additions & 0 deletions includes/class-saddle-mcp.php
Original file line number Diff line number Diff line change
Expand Up @@ -757,8 +757,18 @@ private static function server_instructions() {
*/
private static function list_tools() {
$tools = array();
$names = array();
$built = array();

foreach ( self::saddle_abilities() as $name => $ability ) {
// Counted before the filter below, and deliberately so. These feed
// the health record, which answers "did the tools load?" — a
// property of the site. How many of them a given credential is
// offered is a different question, answered per request by the
// trace's own tool count.
$names[] = $name;
$built[] = self::mcp_tool_name( $name );

// Same rule as the adapter path (filter_adapter_tools_list): only
// advertise what this credential could call. On a WordPress.org
// install this transport is the only one there is, so this is the
Expand Down Expand Up @@ -786,6 +796,21 @@ private static function list_tools() {
$tools[] = $tool;
}

// The health record's only other writer is register_adapter_server(),
// so on every zip Saddle actually ships — none of which carry the
// vendored library — nothing wrote it at all, and the Client traffic
// card told the owner "no app has connected yet" no matter what had
// happened (#115). It is the first line of the one screen a support
// conversation points at.
//
// Recorded here rather than at route registration because this is the
// first moment the number is real: abilities register lazily, and
// rest_api_init fires on requests that have nothing to do with MCP.
// Nothing can fail to convert on this path — every resolved ability
// becomes a tool — so expected and registered are the same list, and
// assess() flags the case that does happen: an empty one.
Saddle_MCP_Diagnostics::record_health( Saddle_MCP_Diagnostics::assess( $names, $built ) );

return $tools;
}

Expand Down
18 changes: 9 additions & 9 deletions languages/saddle.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
# This file is distributed under the GPL-2.0-or-later.
msgid ""
msgstr ""
"Project-Id-Version: Saddle – Control Your Site with AI (MCP Server) 1.0.0-rc4\n"
"Project-Id-Version: Saddle – Control Your Site with AI (MCP Server) 1.0.0-rc5\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/saddle\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2026-08-16T19:02:59+00:00\n"
"POT-Creation-Date: 2026-08-17T19:37:27+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.12.0\n"
"X-Domain: saddle\n"
Expand Down Expand Up @@ -2519,19 +2519,19 @@ msgstr ""
msgid "Saddle exposes tiered, approval-gated access to this site's posts, pages, and media. Call saddle/get-instructions for the current scope and safety rules before acting."
msgstr ""

#: includes/class-saddle-mcp.php:918
#: includes/class-saddle-mcp.php:943
msgid "Invalid params: a Saddle tool name is required."
msgstr ""

#. translators: %s: tool name.
#. translators: %s: tool name
#: includes/class-saddle-mcp.php:928
#: includes/class-saddle-mcp.php:953
#: includes/lib/wp-mcp/includes/Infrastructure/ErrorHandling/McpErrorFactory.php:246
#, php-format
msgid "Tool not found: %s"
msgstr ""

#: includes/class-saddle-mcp.php:958
#: includes/class-saddle-mcp.php:983
msgid "None of Saddle’s site-wide gates (pause, access level, per-tool toggles) blocked this — the connected WordPress account likely lacks a capability this tool requires, or the specific item is protected. Do not retry the same call."
msgstr ""

Expand Down Expand Up @@ -4255,7 +4255,7 @@ msgid "This persisted attribute does nothing — rewrite it on a path the block
msgstr ""

#. translators: %s: minimum WordPress version.
#: saddle.php:332
#: saddle.php:345
#, php-format
msgid "Saddle requires WordPress %s or later (the core Abilities API). The MCP server is disabled until you upgrade."
msgstr ""
Expand Down Expand Up @@ -5263,13 +5263,13 @@ msgid "If a connected app says it can’t see any tools, record its next attempt
msgstr ""

#: admin/src/components/McpDiagnostics.jsx:94
msgid "No app has connected yet, so there’s nothing to report."
msgid "No app has asked for the tool list yet. Requests are recorded below either way."
msgstr ""

#. translators: 1: number of tools available, 2: number expected.
#. translators: 1: number of tools that loaded, 2: number installed.
#: admin/src/components/McpDiagnostics.jsx:100
#, js-format
msgid "%1$d of %2$d tools are available to connected apps."
msgid "%1$d of the %2$d tools installed here loaded correctly. How many an app is offered depends on its access level — the requests below show that number."
msgstr ""

#. translators: %s: comma-separated tool names.
Expand Down
41 changes: 41 additions & 0 deletions tests/mcp-transport-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -678,4 +678,45 @@ public function test_disabled_tool_denial_names_the_toggle() {

wp_set_current_user( 0 );
}

/**
* The health record had one writer, on the adapter path — so on every zip
* Saddle ships, none of which carry the vendored library, it was never
* written and the Client traffic card opened with "No app has connected
* yet" whatever had actually happened (#115). That sentence is the first
* thing a customer reads on the one screen support points them at.
*/
public function test_a_tools_list_on_this_transport_writes_the_health_record() {
delete_option( Saddle_MCP_Diagnostics::HEALTH_OPTION );

$tools = $this->list_tools();
$this->assertNotEmpty( $tools, 'Precondition: this transport must offer tools.' );

$health = Saddle_MCP_Diagnostics::health();

$this->assertNotEmpty( $health, 'A tools/list must leave a health record on the built-in transport.' );
$this->assertSame( $health['expected'], $health['registered'], 'Nothing can fail to convert on this path.' );
$this->assertFalse( $health['degraded'], 'A server that listed tools is not degraded.' );
}

/**
* The count is a property of the site, not of the caller. Reading it off
* the tier-filtered payload would report a read-tier site as having lost
* the tools it is merely not offering — turning the working guardrail into
* a fault report on the support screen.
*/
public function test_the_health_count_is_not_narrowed_by_the_caller_tier() {
delete_option( Saddle_MCP_Diagnostics::HEALTH_OPTION );
$this->list_tools();
$installed = Saddle_MCP_Diagnostics::health()['registered'];

Saddle_Capabilities::set_tier( 'read' );
delete_option( Saddle_MCP_Diagnostics::HEALTH_OPTION );
$offered = count( $this->list_tools() );
$health = Saddle_MCP_Diagnostics::health();

$this->assertLessThan( $installed, $offered, 'Precondition: the read tier must withhold something.' );
$this->assertSame( $installed, $health['registered'], 'The health count must not follow the tier filter.' );
$this->assertFalse( $health['degraded'], 'Withholding tools by tier is not degradation.' );
}
}
Loading