{"id":52045836,"url":"https://github.com/dirnbauer/typo3-abilities","last_synced_at":"2026-08-02T06:01:45.241Z","repository":{"id":369918056,"uuid":"1291532473","full_name":"dirnbauer/typo3-abilities","owner":"dirnbauer","description":"Abilities registry for TYPO3 — one typed, permissioned registry of what the installation can do; MCP tools, CLI commands and REST routes become projections of it.","archived":false,"fork":false,"pushed_at":"2026-07-07T11:51:47.000Z","size":54,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-07T13:19:50.498Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dirnbauer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-06T22:15:35.000Z","updated_at":"2026-07-07T11:51:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dirnbauer/typo3-abilities","commit_stats":null,"previous_names":["dirnbauer/typo3-abilities"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/dirnbauer/typo3-abilities","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirnbauer%2Ftypo3-abilities","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirnbauer%2Ftypo3-abilities/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirnbauer%2Ftypo3-abilities/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirnbauer%2Ftypo3-abilities/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dirnbauer","download_url":"https://codeload.github.com/dirnbauer/typo3-abilities/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirnbauer%2Ftypo3-abilities/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36182791,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-08-02T02:00:06.915Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-08-02T06:01:44.597Z","updated_at":"2026-08-02T06:01:45.228Z","avatar_url":"https://github.com/dirnbauer.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TYPO3 Abilities Registry\n\nOne typed, permissioned registry of what a TYPO3 installation can do. MCP tools, CLI commands and REST routes become **projections** of this registry — never hand-rolled endpoints.\n\nWordPress proved the architecture with the **Abilities API** (core 6.9) and its official **MCP Adapter**: register capabilities once — with contracts, permissions and annotations — and project them onto whatever protocol the agentic web speaks this year. This extension is that architectural bet for TYPO3, wired into the governance vocabulary this ecosystem already uses ([typo3-capability-manifest](https://github.com/dirnbauer/typo3-capability-manifest) side-effect subsystems and policy semantics, [sg_apicore](https://github.com/dirnbauer/sg_apicore) `resource:operation` scopes, [typo3-mcp-server](https://github.com/dirnbauer/typo3-mcp-server) tool projection).\n\n## The registry schema\n\nEvery ability declares, via the `#[AsAbility]` attribute:\n\n| Field | Meaning |\n|---|---|\n| `name` | Unique `namespace/ability-name` (lowercase kebab-case) |\n| `title`, `description`, `category` | Presentation for humans and agents |\n| **contract** | `getInputSchema()` / `getOutputSchema()` — JSON Schema, may be computed at runtime |\n| `scopes` | Required token scopes, `resource:operation` convention (e.g. `news:write`) |\n| `riskTier` | `low` / `medium` / `high` / `critical` — same vocabulary and scores as capability manifests |\n| `sideEffects` | Capability-manifest subsystem vocabulary (`database:write`, `network:outbound`, `mail:send`, …); empty = read-only |\n| `idempotent`, `destructive` | Truthful execution hints, projected onto MCP tool annotations |\n| `expose` | Which surfaces may project this ability: `mcp`, `cli`, `rest` |\n\n## Registering an ability\n\n```php\nuse Webconsulting\\Abilities\\Attribute\\AsAbility;\nuse Webconsulting\\Abilities\\Domain\\ExecutionContext;\nuse Webconsulting\\Abilities\\Domain\\RiskTier;\nuse Webconsulting\\Abilities\\Registry\\AbstractAbility;\n\n#[AsAbility(\n    name: 'news/create-article',\n    title: 'Create news article',\n    description: 'Creates a news article as a workspace draft.',\n    category: 'content',\n    scopes: ['news:write'],\n    riskTier: RiskTier::Medium,\n    sideEffects: ['database:write'],\n)]\nfinal class CreateArticleAbility extends AbstractAbility\n{\n    public function getInputSchema(): array\n    {\n        return [\n            'type' =\u003e 'object',\n            'required' =\u003e ['title'],\n            'properties' =\u003e [\n                'title' =\u003e ['type' =\u003e 'string', 'minLength' =\u003e 3],\n                'bodytext' =\u003e ['type' =\u003e 'string', 'default' =\u003e ''],\n            ],\n        ];\n    }\n\n    public function getOutputSchema(): array\n    {\n        return ['type' =\u003e 'object', 'required' =\u003e ['uid'], 'properties' =\u003e ['uid' =\u003e ['type' =\u003e 'integer']]];\n    }\n\n    public function execute(array $input, ExecutionContext $context): mixed\n    {\n        // … create the record (workspace-aware) …\n        return ['uid' =\u003e $uid];\n    }\n}\n```\n\nThat's the whole registration: the class implements `AbilityInterface` (auto-tagged `abilities.ability`), the DI container collects it into the `AbilitiesRegistry`, and every projection picks it up from there.\n\n## Execution pipeline\n\nEvery surface goes through the same governed pipeline (`AbilityExecutor`):\n\n```\npolicy gate → input validation → scope check → permission check → execute → output validation\n```\n\n- Mirrors the WordPress Abilities API order (validate → permission → execute → validate), with a **policy gate** in front because governance outranks contracts.\n- Results are a stable envelope: `{ok, data}` or `{ok: false, errorCode, error}` with machine-readable codes (`policy_denied`, `invalid_input`, `permission_denied`, `execution_error`, `invalid_output`).\n- The schema validator is dependency-free (a documented JSON Schema subset) so the extension stays clean enough to propose upstream.\n\n## Discovery — “what can this site do?”\n\nBecause every ability is a typed record in one registry, an external system can **ask the site what it can do and get a structured answer** — a list of abilities, each with a JSON-Schema input/output contract, its risk tier and its required scopes. Nothing is hardcoded on either side.\n\n```http\nGET /api/abilities/v1/abilities            → { \"abilities\": [ … ], \"total\": 18 }\nGET /api/abilities/v1/abilities/news/create-article\n                                           → the full contract + input/output JSON Schemas\nPOST /api/abilities/v1/abilities/news/create-article/run\n                                           → runs it, returns the {ok,data} envelope\n```\n\nThat single fact — *the registry is self-describing* — is what lets automation reach TYPO3 without a bespoke integration per action:\n\n- **AI agents / MCP clients** discover abilities through `tools/list` (the MCP projection below); the JSON schemas tell the model exactly how to call each one.\n- **Automation platforms (n8n, Zapier, Make, …)** point a generic HTTP node (or an MCP node) at the discovery endpoint, read the returned schemas, and build the request dynamically — the same node drives *any* ability, present or future, instead of a hand-written connector per operation.\n- **The agent-readable web** — [`webconsulting/typo3-llms-txt`](https://github.com/dirnbauer/typo3-llms-txt) advertises the live registry in each site's `agents.md`, so a crawler learns the site's capabilities before it makes a single call.\n\nAdd a new `#[AsAbility]` class and it shows up in all of these at once — no endpoint, no client change, no redeploy of the integration.\n\n## Projections\n\nOne registry, many protocol projections — a projection never re-implements an ability, it just exposes the registry over one more surface.\n\n**MCP** — when [hn/typo3-mcp-server](https://github.com/dirnbauer/typo3-mcp-server) is installed, a compiler pass generates one `mcp.tool`-tagged service per ability exposed to `mcp`. `news/create-article` appears as the MCP tool `ability_news_create-article`, with `readOnlyHint` / `destructiveHint` / `idempotentHint` / `openWorldHint` annotations derived from the registry metadata. The MCP tool list is *generated from* the registry at container compile time.\n\n**REST** — the discovery + run endpoints shown above, served by the [sg_apicore](https://github.com/dirnbauer/sg_apicore) fork behind backend-user-bound opaque tokens (scope-checked per ability). This is the surface HTTP automation and the web client (`examples/abilities-studio.html`) talk to.\n\n**CLI** —\n\n```bash\nvendor/bin/typo3 abilities:list                # the registry, human-readable (or --json)\nvendor/bin/typo3 abilities:describe news/create-article   # full contract as JSON\nvendor/bin/typo3 abilities:run news/create-article --input '{\"title\": \"Hello\"}'\nvendor/bin/typo3 abilities:run risky/thing --approve-review   # HITL flag for review-gated abilities\n```\n\n**Backend module** — *System → Abilities* browses the registry and runs any ability as the logged-in backend user, through the same governed pipeline, with no token or extra login. Read-only listing plus a per-ability runner (and a review-approval checkbox for high-risk abilities).\n\n**Desktop / custom hosts** — any extension can host its own projection: the [desktop connector](https://github.com/kdirnbauer/typo3-desktop-connector) fronts its news/pages/content/workspace abilities over its own JWT API for the Electron editor. A projection just calls `AbilityExecutor::execute()` with an `ExecutionContext` for its surface.\n\n## Site-wide policy\n\nOptional `config/abilities-policy.yaml` in the project root (same location and semantics family as capability-manifest's `capability-policy.yaml`):\n\n```yaml\npolicy:\n  name: \"Production abilities policy\"\n  deny:\n    - \"side-effect:network:outbound\"   # no ability may call out\n    - \"experimental/*\"                 # namespace-wide block\n  review_required:\n    - \"risk:high\"                      # HITL approval required\n    - \"scope:workspace:publish\"\n  max_risk_tier: \"high\"                # critical abilities never run\n```\n\nRule grammar: exact name, `namespace/*`, `*`, `risk:\u003ctier\u003e`, `scope:\u003cscope\u003e` (prefix), `side-effect:\u003csubsystem\u003e` (prefix). `deny` always wins; `review_required` blocks unless the execution context carries an explicit human approval (`--approve-review` on CLI). No policy file means allow-all — scopes and the ability's own permission check still always run.\n\n## Trust model\n\n- **Trusted surfaces** run with `grantedScopes: null` — scope checks are skipped because the host already authenticated the actor; policy and `checkPermission()` still apply. This covers the CLI (`_cli_`), MCP (behind the MCP server's OAuth), the **backend module** (the logged-in backend user) and the **desktop connector** (behind its JWT).\n- **Scoped surfaces** pass explicit grants — REST carries the token's scopes, and every scope an ability declares must be present or the run is denied.\n- `checkPermission()` is the place for TYPO3-native checks (backend user, table permissions, workspace) — it runs on *every* surface, trusted or scoped.\n\n## Development\n\n```bash\ncomposer install\ncomposer test      # PHPUnit\ncomposer phpstan   # level max\n```\n\n## Status \u0026 roadmap\n\nAlpha, but broad — the registry core, execution pipeline, policy gate, execution traces (`tx_abilities_trace`) and **five projections** (CLI, MCP, REST, backend module, desktop) are implemented, tested and verified live. Next, in order:\n\n1. Consent/policy records in TCA instead of YAML-only (strategy item 15)\n2. Cross-check audit: an ability's `sideEffects` ⊆ its host extension's capability manifest\n3. Migrate the generic MCP-server tools into abilities so the registry is the single source of truth for them too (leaving the MCP server as pure transport)\n4. TER release + proposal to the TYPO3 AI initiative (item 23)\n\nPart of the [TYPO3 agentic strategy](https://github.com/dirnbauer) — item 19: *a capability registry, not hand-rolled endpoints*.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirnbauer%2Ftypo3-abilities","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdirnbauer%2Ftypo3-abilities","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirnbauer%2Ftypo3-abilities/lists"}