AI-Native Development
MCP Tool Exposure
MyronCMS exposes many MyronAction instances as MCP tools. The MCP server is a protocol adapter over the same action registry and dispatcher used by the direct /api/v1/... API.
The core rule is simple: scopes are the gate. exposeToMcp:false is a narrow policy carve-out, not the default for writes.
Human Path
When authoring an action, decide whether the action should be visible to MCP:
return new MyronActionMetadata(
uri: '/api/v1/x/my_vendor/status',
method: 'GET',
domain: 'x.my_vendor.status',
resource: 'status',
verb: 'get',
inputSchema: ['type' => 'object', 'properties' => []],
outputSchema: ['type' => 'object', 'properties' => ['ready' => ['type' => 'boolean']]],
requiredScope: MyronApiScopes::CONTENT_READ,
summary: 'Return this extension status.',
exposeToMcp: true,
);
Use exposeToMcp:true when an AI integration that holds the required scope should be able to call the action. Use exposeToMcp:false only when the action fits a documented hidden category.
AI Path
An installed MCP client discovers available tools only after the MCP discovery boundary is complete:
- send
initializewithAuthorization: Bearer <token>, - read the
Mcp-Session-Idresponse header, - send
initialized/notifications/initializedwith the same bearer token and session header, - call
tools/list.
The server then:
- resolves the caller from the request headers,
- validates the MCP session state,
- iterates over registered actions,
- skips actions with
exposeToMcp:false, - skips policy-hidden identity/trust-gate actions,
- skips scoped actions the caller cannot invoke,
- emits each remaining tool with
name,description, andinputSchema.
An MCP client should treat absence from tools/list as a discovery result, not as proof the underlying API action does not exist. Check scope and exposure before deciding the action is unavailable.
Tool Names
The default tool name is built from action metadata:
{domain}_{resource}_{verb}
The MCP server normalizes non-alphanumeric separators to underscores, collapses repeated underscores, lowercases, and trims edges. For example:
| Metadata | Tool name |
|---|---|
domain x.my_vendor.status, resource status, verb get | x_my_vendor_status_status_get |
domain design, resource definition, verb apply | design_definition_apply |
Legacy dotted names may still be accepted by tools/call as transitional aliases, but tools/list should expose portable snake_case names.
tools/call
tools/call looks up the action by tool name, substitutes URI placeholders from arguments, then delegates to ActionDispatcher::dispatch() with the action method, concrete URI, headers, and arguments.
This means direct API and MCP calls share:
- authentication,
- scope checks,
- input schema validation,
- action execution,
- audit binding,
- rate limiting for AI bearer callers,
- canonical action errors.
Hidden Tool Classes
Use exposeToMcp:false with a reason. Current documentation should classify hidden status with one of these categories:
| Category | Treatment |
|---|---|
| Recursive control vector | Never document as MCP-callable. AI cannot mint, rotate, revoke, or re-scope its own trust gate. |
| Tier-3 identity/admin surface | Document as policy-hidden unless a later privileged-admin primitive changes the rule. Current server policy also hides registered administration user actions even when their metadata is otherwise exposed. |
| Visitor-facing action | Document runtime behavior without presenting it as operator MCP control. |
| Internal dispatch | Document only as internals, not public SDK surface. |
| Unresolved over-hide | Mark as parity gap candidate and route through Evaluate before changing. |
Do not use exposeToMcp:false because an action mutates state. Mutations can be safe MCP tools when the operator has granted the needed scope and the action declares side effects and preconditions.
API Manifest Versus MCP Tools
The direct manifest surface can show the complete registered action catalog, including actions hidden from MCP. MCP tools/list is filtered to tools an MCP caller can discover and normally invoke.
Use the direct manifest for catalog and audit visibility. Use MCP tools/list and the MCP mcp://myroncms/system/manifest resource for the AI client's current callable tool set. The MCP manifest resource is scope-filtered and policy-filtered.
Scopes
If an action has requiredScope: "content.write", a caller without content.write should not see that tool in tools/list. If the caller attempts tools/call anyway, dispatcher scope enforcement still protects the action.
Artifacts
Discovery and call surfaces:
- MCP
initialize - MCP
tools/list - MCP
tools/call - direct
/api/v1/manifest
Source contracts:
app/system/mcp/Server.phpapp/system/api/Action.phpapp/system/api/ActionDispatcher.php
Safety Boundary
An MCP client must not grant itself access. Trust gates are operator-controlled through scopes, integration tokens, and extension grants. Docs must not tell an AI to solve authorization failures by creating, rotating, revoking, or expanding its own integration.
Verification
Use two callers:
- A caller that holds the action scope.
- A caller that does not hold the action scope.
For an exposed action:
- scoped caller sees the tool in
tools/list, - under-scoped caller does not see it,
- scoped caller can
tools/call, - under-scoped direct API call returns
scope_insufficient.
For a hidden action:
- action may appear in the full manifest,
- action does not appear in MCP
tools/list, - docs state the hidden reason.
Failure Modes
| Symptom | Likely cause | Safe response |
|---|---|---|
Tool missing from tools/list | Not loaded, exposeToMcp:false, or caller lacks scope. | Check extension load state, metadata, and caller scope. |
| JSON-RPC session error | Client skipped initialize, did not preserve Mcp-Session-Id, or skipped initialized. | Restart the MCP lifecycle with the same bearer token and session header. |
tools/call method not found | Tool name does not match a registered exposed or legacy alias. | Re-read tools/list and use the listed name. |
| JSON-RPC auth error | Missing or invalid bearer token. | Use an operator-provisioned token. |
| JSON-RPC authz error | Caller lacks the action scope. | Use an appropriately scoped caller. |
| JSON-RPC rate-limit error | AI integration exceeded its request budget. | Back off and retry after the reported window. |
isError:true tool result | Dispatcher reached the action and it returned a normal action failure. | Read the text and resolve input or precondition issues. |