An ability with no input parameters sounds like the simplest possible case, and it’s also the one that has produced two separate, real, closed bugs in the MCP Adapter, for two entirely different reasons. This lesson covers both, what the official Abilities API documentation actually says an empty schema should look like, and what real MCP clients did with it before both bugs were fixed.
An ability with no required input parameters, and a way to call it from at least two different MCP clients or MCP Inspector (Lesson 6), since both bugs in this lesson are easiest to spot as an inconsistency between clients rather than a single clear error.
Step 1: What core actually recommends
The Abilities API’s own PHP reference documentation is direct about this: input_schema
is an optional argument, and it defaults to null when omitted. For an ability that
genuinely takes no input, the simplest, most correct option is to leave the key out of
$args entirely, rather than reaching for an empty array or an explicit empty-object
schema:
// File: my-plugin.php, simplest correct option for a no-input ability
add_action( 'wp_abilities_api_init', function () {
wp_register_ability( 'my-plugin/get-site-status', array(
'label' => __( 'Get site status', 'my-plugin' ),
'description' => __( 'Returns whether the site is in maintenance mode.', 'my-plugin' ),
'category' => 'site',
// no input_schema key at all
'execute_callback' => function () {
return array( 'maintenance_mode' => (bool) get_option( 'wp_maintenance_mode' ) );
},
'permission_callback' => '__return_true',
) );
} );
If you’d rather be explicit that the ability accepts an empty object and not
accidentally imply it accepts anything, an object schema with an empty properties is
the defensible alternative, and it’s what Course 2’s own examples use:
// File: my-plugin.php, explicit alternative
'input_schema' => array(
'type' => 'object',
'properties' => array(),
),
Both are valid starting points. The two real bugs below are about what happened when
developers wrote the bare empty version instead, 'input_schema' => array(), with no
type key at all, and about a separate, deeper issue in how the adapter validated
actual empty arguments sent by a client.
Step 2: Issue #35, an empty schema that broke registration in Cursor
Issue #35 reported that abilities
registered with a bare input_schema of array() behaved inconsistently across MCP
clients. In VS Code, the tool registered and worked fine. In Cursor, the same ability
failed with “Expected object, received array” and never appeared in the tool list at
all. The cause: PHP’s array() is ambiguous when JSON-encoded, an empty PHP array
serializes to [], not {}, and Cursor’s stricter client-side validation expected the
schema’s properties field to be a JSON object, not an array, even when empty.
The fix, PR #36, “Add type property
to empty input schema,” addressed it by ensuring the schema explicitly declares
'type' => 'object' rather than leaving the schema as a bare, untyped empty array. With
an explicit type, the adapter’s own JSON encoding treats the accompanying structure as
an object correctly, and the ambiguity that Cursor was rejecting goes away.
If your input_schema for a no-parameter ability is literally 'input_schema' => array() with no type key, that’s the exact shape issue #35 was filed against. Either
omit input_schema entirely (Step 1), or declare it explicitly as
array( 'type' => 'object', 'properties' => array() ). Don’t leave it as a bare,
untyped empty array.
Step 3: Issue #116, a deeper bug in validating empty call arguments
Issue #116 is a separate, later
bug, and it can bite you even with a correctly-typed input_schema. When an MCP client
calls a tool and sends an empty arguments object, {}, PHP’s JSON decoding turns that
into an empty PHP array, [], the same fundamental ambiguity as issue #35, but this
time on the incoming call arguments, not the schema declaration. The adapter’s input
validator at the time expected the decoded params to be a PHP associative array
representing an object and rejected the empty array with “input is not of type
object,” even though {} is exactly what the schema asked for.
The root cause and fix are documented directly in the issue thread: the project added a
new AbilityArgumentNormalizer utility that detects this specific case, an
object-typed schema paired with empty decoded arguments, and normalizes it back into
the object shape the validator expects, applied consistently across tool calls,
prompts, and the built-in execute-ability path. This landed in MCP Adapter v0.5.0.
If you’re still on v0.4.1 or earlier and seeing this exact “input is not of type
object” error against an ability that otherwise looks correctly schema’d, upgrading is
the actual fix, not a workaround in your own code.
# File: terminal, check your installed adapter version
wp eval 'echo defined( "MCP_ADAPTER_VERSION" ) ? MCP_ADAPTER_VERSION : "check composer.lock or the plugin readme instead";'
Issue #35 is about how you declare an empty schema and was fixed in the schema encoding itself (PR #36). Issue #116 is about how the adapter validates incoming call arguments against a correct schema and was fixed in v0.5.0’s argument normalization. A site could have the first problem fixed and still hit the second on an older version, or vice versa. Check which symptom you actually have before assuming one fix covers both.
Step 4: Diagnosing which one you’re looking at
Test it: call the ability with genuinely empty arguments
Use MCP Inspector (full setup in the next lesson) or a direct WP-CLI call to confirm your no-parameter ability accepts an actual empty object, not just no arguments at all:
# File: terminal
wp eval '
$ability = wp_get_ability( "my-plugin/get-site-status" );
var_dump( $ability->execute( array() ) );
'
If this returns your expected result rather than a validation error, your schema and your adapter version are both handling the empty-object case correctly.
Recap
input_schema is optional and defaults to null, the simplest choice for a
no-parameter ability is to omit it. If you declare one explicitly, always include
'type' => 'object', a bare array() is the exact shape that broke tool registration
in Cursor (issue #35, fixed in PR #36). Separately, older adapter versions had a real
bug validating genuinely empty call arguments sent as {}, fixed in v0.5.0 via the
AbilityArgumentNormalizer utility (issue #116). Check your adapter version before
assuming a schema-declaration fix covers a call-argument validation problem, they’re
different bugs with different fixes.
Resources & further reading
- Issue #35, Abilities with empty input_schema behave weirdly on different applications, GitHub
- PR #36, Add type property to empty input schema, GitHub
- Issue #116, Empty object parameters fails InputValidator with “input is not of type object”, GitHub
- Abilities API PHP reference, GitHub
- Abilities API reference, developer.wordpress.org