Schemas & Permissions

Controlling MCP Visibility with meta.mcp.public

⏱ 13 min

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.”

What you'll learn in this lesson
What meta.mcp.public actually gates
Discoverability by the default MCP server, distinct from execution permission.
Why an ability can be safe to call but unsafe to expose
The difference between "who can run this" and "who should even know this exists."
meta.mcp.type
The related flag controlling whether a public ability appears as a tool, resource, or prompt.
A practical decision process
Questions to ask before flipping any ability to public.
Prerequisites

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:

permission_callback versus meta.mcp.public
QuestionAnswered 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

Before setting meta.mcp.public to true, ask
Does this ability have a permission_callback appropriate for an untrusted, external caller?
Not just "does it have one," but one you would trust against a caller you cannot vouch for personally.
Have you tested it as a low-privileged user, not just as an administrator?
The permission check needs to hold up before exposure amplifies who can reach it.
Is the description written for a model, not a developer?
A public ability with a vague description invites a model to call it in the wrong situation.
Would you be comfortable this ability appeared in a list a client showed a user?
If the honest answer is "I would rather this stayed quiet," it should stay mcp.public: false.

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.

Assuming meta.mcp.public: true is the finish line

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.

Resources & further reading

← Writing Permission Callbacks: Securing Who Can Call What Serving Abilities Over HTTP Transport →