For AI agents
How a coding agent should install, wire, check and audit PermDock, and how PermDock's errors and denials are written for models.
PermDock's second audience is the coding agent working in a TypeScript repository. Every page, skill, error message and CLI report is written so that an agent can wire PermDock end-to-end, verify its work and explain a denial without reading library source. This page is the entry point for that agent. The permdock skill and the permdock-* skills ship in the permdock package. Point an MCP client at https://permdock.com/mcp for search and page-fetch; agent docs standards has install links for Cursor and VS Code and the Claude Code command.
Start here
npx skills add ScaleDockHQ/PermDockThis installs eight Agent Skills into the folders your agent reads (.agents/skills/, .claude/skills/, .cursor/skills/); --skill <name> installs one:
permdock: the mental model and invariants, reading aDecision,explainfor an unexpected denial, the docs MCP, and which skill to load next. Start here.permdock-wire: detect the framework and validator, createsrc/permissions.ts,src/policy.tsand the factory file, add the first check and the first UI guard, run the CLI checks, add CI steps.permdock-audit: review an existing installation or a pull request for ungranted or unused permissions, validation and identity gaps, untested grants, and the OWASP Agentic Top 10 mapping; it runs each topic skill's Verify list.permdock-agents,permdock-approvals,permdock-tenancy,permdock-data,permdock-credentials: one area each, from agent delegation and human approval to named scopes, queries and RLS, and tokens, API keys and share links.
The same skills ship inside the permdock npm package and can be installed with permdock skills install, so the skill version always matches the library version. In Claude Code, /plugin marketplace add ScaleDockHQ/permdock adds them as the permdock plugin from .claude-plugin/marketplace.json. See skills.
What the repository gives you
| Artifact | Where | Use it for |
|---|---|---|
AGENTS.md (CLAUDE.md imports it) | repository root | Maintainer guide: layout, commands, the invariants and an index of the topic rules |
.agents/rules/*.mdc | repository root, linked into .cursor/rules and .claude/rules | Maintainer rules attached by path: invariants, naming, docs, "when you change X also update Y", testing, prose |
llms.txt, llms-full.txt | docs site root, and under /docs | Index and full text of the docs for context loading |
.md per page | append .md to any docs URL | Read one page as Markdown without HTML |
| Docs MCP server | POST /mcp (search, list_pages, get_page) | Search and read docs from inside an MCP-capable agent |
| Docs WebMCP tools | every docs page (search_docs, read_page, open_page) | Search and read docs from the browser's own agent; experimental, Chrome 149+ behind a flag |
| Cloud MCP server | https://mcp.permdock.com | Read-only evidence, catalog and pending approvals; never resolves an approval |
permissions.catalog.json and its JSON Schema | app repository, permdock catalog --format schema | Know which permissions exist, their arity, scopes and metadata |
permdock doctor --json | app repository | Machine-readable findings with stable codes and fixes |
| Example apps | apps/examples/<adapter> | Working code for every adapter, each checked by permdock doctor |
Standards for these artifacts are on the Agent docs standards page.
The shortest wiring recipe
Three files and one check. Import paths and identifiers follow the naming convention; do not invent framework-prefixed names.
// src/permissions.ts — importable everywhere; no rules, no secrets
import { definePermissions, defineRoles, resource, crud } from "permdock";
import { Post } from "./schemas"; // any Standard Schema validator
export const permissions = definePermissions({
post: resource(
Post,
crud({
id: "id",
actions: { publish: { title: "Publish post" } },
}),
),
});
export const roles = defineRoles({ member: {} });// src/policy.ts — server-only
import { definePolicy, role, allow, principal } from "permdock";
import { permissions, roles } from "./permissions";
const member = role(roles.member, [
allow(permissions.post.read),
allow(permissions.post.list),
allow(permissions.post.create),
allow(permissions.post.update, { where: { authorId: principal.id } }),
allow(permissions.post.delete, {
where: { authorId: principal.id },
approval: "human",
}),
allow(permissions.post.publish, { approval: "human" }),
]);
export const policy = definePolicy(
{ permissions, roles },
{
roles: [member],
principal: (user: User | null) =>
user && { id: user.id, roles: user.roles },
},
);// src/permdock/server.ts — the explicit factory; same pattern for permdock/hono, permdock/mcp, permdock/ai-sdk
import { createPermDock } from "permdock/next";
import { policy } from "../policy";
export const { getPermDock, getPermission, PermDockProvider, permdockHandler } =
createPermDock(policy, {
subject: async () => getUser(await cookies()),
});Then, in a Server Component or Server Action:
const permdock = await getPermDock();
permdock.assert(permissions.post.update, post);And in a client component:
import { usePermission } from "permdock/react";
const { allowed, status } = usePermission(permissions.post.update, post);Finish with pnpm exec permdock doctor and add permdock collect --check and permdock usage --strict to CI. Full versions: quick start, Next.js adapter, MCP adapter, AI SDK adapter.
The shortest OpenAPI recipe
If the project already generates an OpenAPI description, do not write security by hand and do not add a PermDock wrapper around the generator. Emit PermDock's Overlay and let the existing pipeline apply it:
permdock openapi emit --doc public/openapi.json --format overlay --out permdock.overlay.jsonFor Next.js and the other frameworks next-openapi-gen scans, add overlay: { apply: ['./permdock.overlay.json'] } to openapi-gen.config.ts; for Redocly pipelines, redocly join openapi.json --overlay permdock.overlay.json -o dist/openapi.json. Hey API, Orval, Scalar and any OpenAPI-to-MCP bridge then read the applied description with no PermDock code. Rules: every operation needs an operationId; do not also declare security through the producer (@auth, authPresets) on operations PermDock covers; add permdock openapi emit --format overlay --check to CI. Details: OpenAPI adapter, adapters.
Invariants an agent must keep
Use permission references, never strings; keep the policy and every createPermDock call out of client files (permdock doctor reports it as PD001); take the subject only from the verified session or token, never from a model argument; prefer portable where conditions to closures; and put approval: 'human' on destructive actions an agent can reach. Everything else fails closed: a Decision is only ever granted, denied or approval-required. The full list, with the reason for each rule, is the threat model.
How errors and denials are written for models
PermDock assumes the reader of a denial may be a language model deciding what to do next, so denials carry structure rather than prose:
decide()returns{ outcome: 'denied', denials: [{ role, reason }], alternatives: [...] }.reasonis the policy author's string;alternativeslists permissions on the same resource this subject does hold, so the model can pick a permitted action instead of retrying the same one.approval-requiredcarries the grant, a reason and atoken. Adapters turn it into AI SDKuser-approval,WorkflowAgentneedsApproval, MCPinput_requiredor an HTTP 403 with the.../approval-requiredproblem type; the token must be echoed back when the approval resumes.- MCP refusals return
isError: truewith theDecisioninstructuredContent, and missing delegation produces ascopeChallengenaming the exact scope (post:delete) to request. - HTTP adapters emit RFC 9457
application/problem+jsonwithtype,title,permission,denialsandalternatives; see Errors and Problem Details. PermDockValidationErrornames the resource and the schema issues;PermDockDeniedErrorandPermDockApprovalRequiredErrorcarry theDecision.- CLI reports use stable codes (
PD001...) with a one-line fix each, and--jsonoutput carries a$schema. simulate([[permission, data], ...])lets an agent pre-flight a whole plan and read every decision before acting; it is the in-process form of AuthZENevaluations.simulate({ arazzo, openapi })does the same from an Arazzo workflow; aprovisional: truestep is not a final grant.
Reading the docs
- Concepts first: Permissions, Policies, Decisions, Subject, Authentication.
- One adapter page per framework, each with the same sections (purpose, API, lifecycle, validation, denials, example app, standards): Adapters.
- Design rationale lives in the Why sections of the owning pages; cite them instead of relitigating a constraint: permissions, Next.js plugin, decisions.
- A
Status: plannedorStatus: trackingline under a page's frontmatter means the thing does not exist yet; a page without one describes code in the package. Undecided design questions are listed on the roadmap.
Security framing for agent code
PermDock's agent features map onto the OWASP Top 10 for Agentic Applications: per-tool permissions with approval: 'human' gates and boundary validation address ASI02 Tool Misuse; the two-principal subject and delegation intersection address ASI03 Identity and Privilege Abuse. When writing tool handlers, wire the permission on the tool (permission in registerTool or the tools map) rather than checking inside the handler, so list_tools and capability middleware can hide what the caller may not do.
Last updated on
Changelog
Previous Page
Authorization landscape
Prior art for PermDock. In-process TypeScript libraries (CASL, permix, Kilpi, @zap-studio/permit, accesscontrol), external policy engines (Cerbos, OpenFGA, SpiceDB, Cedar, OPA, casbin), authorization bundled with auth providers and data layers, and the commercial vendor landscape, each with what PermDock adopts, adapts and avoids.