Every ability you’ve registered so far has meta.mcp.public set to false, meaning it
exists, has a permission check, and can be called directly from PHP, but is invisible to
the MCP Adapter’s default server. This lesson is about that specific gate: what it
controls, why it’s separate from permission_callback, and how to decide which of your
abilities should ever cross into “an external AI client can see this exists.”
The my-plugin/create-draft-post ability with its permission callback from Lesson 4.
The MCP Adapter active (Lesson 1).
Step 1: What the flag controls, precisely
By default, a WordPress ability registered via wp_register_ability() is not
reachable through the MCP Adapter’s default server, regardless of how permissive its
permission_callback is. To make it discoverable, you set meta.mcp.public to true:
// File: my-mcp-abilities.php (meta fragment)
'meta' => array(
'annotations' => array( 'destructive' => false ),
'mcp' => array(
'public' => true,
'type' => 'tool',
),
),
With public set to true, the ability becomes visible to the default server’s
mcp-adapter-discover-abilities meta-tool, and callable through
mcp-adapter-execute-ability. With it left false (or omitted), the ability is
completely invisible over MCP, an AI client cannot discover it, name it, or call it,
even if it somehow guessed the ability’s name. Note that this default-server behavior is
separate from custom servers, covered in Lesson 8, where you list abilities explicitly
and this flag isn’t required.
Step 2: Why this is a second gate, not a redundant one
It’s tempting to think permission_callback already handles “who’s allowed,” so a
visibility flag is unnecessary ceremony. It isn’t, because permission and exposure
answer different questions:
| Question | Answered by |
|---|---|
| Can this specific caller execute this ability right now? | permission_callback |
| Does this ability exist in the set of things an external AI client can discover at all? | meta.mcp.public |
An ability can have an extremely strict permission_callback and still be a bad
candidate for public: true. Consider an internal ability that reads unpublished draft
content for an editorial workflow tool: its permission check might correctly require
edit_others_posts, but you may still not want it discoverable by any external AI
client, because the mere existence of “an ability that can enumerate all draft content”
is more attack surface than your threat model wants exposed, independent of whether the
permission check currently guards it correctly. Keeping it mcp.public: false means it
simply isn’t part of the conversation for anything connecting over MCP, while it remains
fully usable by your own plugin’s internal PHP.
Step 3: The companion flag, meta.mcp.type
Alongside public, meta.mcp.type controls what kind of MCP primitive a public ability
becomes: tool (the default, an executable action), resource (exposed data, requires
a uri in meta), or prompt (a structured template, requires arguments in meta).
Most abilities you write for actions, like creating or updating content, are tools.
Reach for resource when the ability’s purpose is exposing read-only data for an AI
client to pull into context rather than act on.
// File: my-mcp-abilities.php (explicit type, though 'tool' is the default)
'meta' => array(
'mcp' => array(
'public' => true,
'type' => 'tool',
),
),
Step 4: A decision process before flipping the flag
Test it
Toggle the create-draft-post ability’s flag to true, then confirm it’s actually
discoverable through the default server’s meta-tool over STDIO (STDIO setup itself is
covered fully in Lesson 7, this is just enough to verify the flag worked):
# File: terminal
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"mcp-adapter-discover-abilities","arguments":{}}}' \
| wp mcp-adapter serve --user=admin --server=mcp-adapter-default-server
Look for my-plugin/create-draft-post in the JSON response. Then flip the flag back to
false, run the same command again, and confirm it’s gone from the list.
Setting the flag makes an ability reachable, it does not make it safe by itself. The permission check still runs on every call, exactly as before, the flag only changes whether an external client can find and name the ability in the first place. Treat flipping this flag as the last step after the schema and permission work are done and tested, not the first step you add before either.
Recap
meta.mcp.public is a discoverability gate, separate from and layered on top of
permission_callback. An ability can be perfectly safe to call and still deliberately
kept off the MCP Adapter’s default server if you’d rather it stayed internal-only.
meta.mcp.type decides whether a public ability shows up as a tool, resource, or
prompt. With schemas, permissions, and visibility all covered, the next module moves to
actually serving abilities to real clients, starting with HTTP transport.