Safety and the audit
How tools are classified, which endpoints are skipped or disabled, and what the audit enforces.
Nothing is written to disk until every candidate tool has been through the safety layer. It does three jobs: classify what calling a tool does, decide its starting state, and lint the result in plain language.
Classification
Every tool is classified by what calling it does to the world:
| Label | Meaning | Examples |
|---|---|---|
read | Changes nothing | GET endpoints, HEAD, OPTIONS |
write | Changes things, reversibly | POST, PUT, PATCH |
destructive | Cannot easily be undone | DELETE, plus any route or name containing words like cancel, refund, revoke, purge |
Search-style POSTs are reads. If a POST's name contains a reading word as a whole
segment (search, query, list, find, filter, estimate, preview, validate,
check, lookup, autocomplete, suggest), it is classified as a read. POST /search
and POST /estimate are reads wearing a write verb. The audit shows a warning for these,
so a mislabeled one is visible.
Classification drives the WebMCP hints (readOnlyHint, destructiveHint,
idempotentHint), which the generated files carry as metadata.
Starting state: enabled or disabled
Classification also decides whether a tool works out of the box:
- Reads start enabled. The generated
execute()calls your API from the page, with the signed-in user's session. It works immediately. - Writes and destructive tools start disabled. The same working code is generated but commented out. The tool still registers, so agents can see it; calling it returns a clear "this tool is disabled" message that tells the agent to ask the human. Enabling one is deleting the return line and uncommenting the code.
User confirmation for mutations is generated, not remembered. The register wrapper for
write and destructive tools asks the user to confirm before every call, and that step lives
in the generated region of the file, so it cannot be edited away by accident. The default
is window.confirm; replace requestUserConfirmation() in runtime.webmcp.ts with your
app's own dialog when you outgrow it.
Endpoint roles
Some endpoints should not become agent tools just because they exist in the spec:
- Webhooks are skipped entirely. A webhook receives server callbacks; an agent has nothing to call. Skips are listed in the report with the reason.
- Auth endpoints (sign-in, logout, token, password, session) are generated disabled, with a warning. Agents should not drive authentication.
- Admin endpoints are generated disabled, with a warning. Exposing admin operations is a deliberate decision to make endpoint by endpoint.
Your own rules join via safety.exclude in the config: matched endpoints are skipped and
listed, never silently dropped.
The audit
After classification, every tool is linted. Findings come in two levels:
- Errors block generation. Generation writes nothing until they are fixed (or you pass
--force). Example: a safe verb (GET) carrying a destructive name. That is either a mislabeled spec or a genuinely dangerous GET, and both deserve a human look. - Warnings never block. Missing descriptions, descriptions that read like agent instructions (a prompt-injection smell), responses that may expose PII, mutating tools behind auth.
The audit is meant to run in CI like npm audit: exit codes, not vibes.
npx @webmcp-stack/codegen generate --dry-run # see the findings without writing anythingPII scanning
Response schemas are scanned for fields that look like personal data or secrets
(email, password, ssn, token, phone, and more; extend with safety.piiFields).
The security-relevant direction is data leaving the page and reaching the agent, so only
outputs are scanned. Flagged fields are called out in the report, on the dashboard, and
in a comment above the tool's execute().