MyronCMS Developers Hub Docs sandbox review

Cookbook

Action Error Handling

Every action call returns either a direct API envelope or an MCP response derived from that envelope. Extension authors should return declared errors for expected failures and let the dispatcher handle authentication, scope, schema, rate-limit, and uncaught-exception failures.

Human Path

Return success with:

return MyronActionResult::ok(['noteId' => $noteId]);

Return a known failure with:

return MyronActionResult::error('precondition_failed', 'title and body must be non-empty.');

Use declared errors for validation or business conditions the caller can fix. Do not throw exceptions for routine precondition failures.

AI Path

An AI coding agent can author error handling in local PHP files and verify the failure modes. An installed MCP client receives mapped error responses and should remediate only within its granted scope.

Safe AI remediation means:

  • correct malformed input,
  • satisfy a documented precondition,
  • ask the operator for missing trust grants,
  • wait on rate limits,
  • stop when the error indicates policy or authorization denial.

Direct API Envelope

The dispatcher wraps action results in this shape:

{
  "ok": true,
  "result": {},
  "error": null,
  "actionId": "act_...",
  "auditEventId": "aud_..."
}

On declared errors, ok is false, result is an empty object, and error contains a stable code and human-readable message.

Direct API Errors

CodeHTTP statusSourceLikely causeSafe remediation
action_not_found404Dispatcher resolutionNo registered action matches method and URI.Check extension load state, URI, method, and namespace.
auth_required401AuthorizationNo session or bearer token for a private action.Use an authenticated caller.
auth_invalid401AuthorizationBearer token is malformed, revoked, production-disallowed, or unknown.Use an operator-provisioned active token.
rate_limited429Rate limiterAI integration exceeded request budget.Back off and retry after the window; do not parallel-retry.
scope_insufficient403Dispatcher scope checkCaller lacks requiredScope.Use a caller with the action scope or ask the operator to review grants.
input_invalid400Dispatcher schema validation or action-declared validationRequired field missing or field type mismatched.Fix request shape according to inputSchema.
precondition_failed422Action resultRequired domain state is missing.Satisfy the named precondition before retrying.
business_rule_failed422Action resultDomain rule rejected the operation.Fix the domain state or input.
uncaught_exception500Dispatcher catch-allAction threw unexpectedly.Fix the action to catch known failures and return declared errors.

input_invalid, precondition_failed, and business_rule_failed can be returned by action code. The dispatcher always uses 400 for schema validation failures and 422 for declared action errors.

MCP Mapping

tools/call delegates to the same dispatcher, then maps the result to MCP:

Dispatcher resultMCP response
401 or 403JSON-RPC error with data.code = MCP_AUTHZ_DENIED.
429JSON-RPC error with data.code = MCP_RATE_LIMITED and retry metadata.
Other action error envelopeCallToolResult with isError: true and textual error content.
Successful action envelopeCallToolResult with isError: false and JSON text content.
Unknown tool nameJSON-RPC method-not-found style error.

For MCP clients, isError:true usually means the tool call reached the action. Treat it as a normal action failure, not a protocol failure.

Cookbook Pattern

Use a small helper style inside actions:

public function execute(array $input, MyronActionCallerContext $caller): MyronActionResult
{
    $title = trim((string) ($input['title'] ?? ''));
    if ($title === '') {
        return MyronActionResult::error('precondition_failed', 'title is required.');
    }

    try {
        $noteId = $this->createNote($title);
    } catch (InvalidArgumentException $e) {
        return MyronActionResult::error('input_invalid', $e->getMessage());
    } catch (RuntimeException $e) {
        return MyronActionResult::error('business_rule_failed', $e->getMessage());
    }

    return MyronActionResult::ok(['noteId' => $noteId]);
}

Let unexpected Throwable cases bubble to the dispatcher only when they are genuinely unexpected. If callers can fix the problem, return a declared error.

Scopes

Error handling does not change scope requirements. A missing scope must stay a failed authorization result. Do not catch scope_insufficient in action code; the dispatcher checks scope before execute() runs.

Artifacts

Source contracts:

  • MyronActionResult
  • MyronActionDispatcher
  • MyronApiAuthorization
  • MyronRateLimiter
  • MyronMcpServer

Runtime surfaces:

  • direct API action envelope,
  • MCP tools/call result,
  • audit event metadata when audit logging is configured.

Safety Boundary

Do not include secrets in action errors. The audit layer redacts known secret keys, but action messages should still avoid exposing credentials, tokens, raw request payloads, or provider responses that may contain secrets.

Do not convert authorization failures into business errors. Auth, scope, and rate-limit decisions belong to the dispatcher and server layers.

Verification

For each new action, verify:

  1. valid input returns ok: true,
  2. missing required input returns input_invalid,
  3. unmet business precondition returns a declared 422 error,
  4. under-scoped caller returns scope_insufficient,
  5. MCP tools/call maps action-declared failures to isError:true,
  6. known failures do not appear as uncaught_exception.

Failure Modes

FailureWhat it meansSafe response
Every bad input becomes uncaught_exceptionThe action throws for expected caller-correctable failures.Catch known failures and return declared errors.
MCP client retries a write after isError:trueThe caller treated a business failure as a transient protocol failure.Read the error text and satisfy the precondition first.
Error message exposes a token or provider secretThe action included sensitive data in failure text.Replace with a non-secret diagnostic and rely on redacted audit context.
Under-scoped caller is told to broaden itselfDocumentation or agent remediation violates the trust boundary.Route grant changes through the operator/admin path.
Validation is duplicated inconsistentlySchema and action-level checks disagree.Keep shape/type checks in inputSchema and domain checks in action code.