PHPackages                             agtp/agtp-drupal - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. agtp/agtp-drupal

ActiveDrupal-module[Utility &amp; Helpers](/categories/utility)

agtp/agtp-drupal
================

Drupal module that wires AGTP handlers into the Drupal service container. Sites register tagged handler services; the module routes AGTP traffic through them via the gateway protocol.

00PHPCI failing

Since May 18Pushed 2mo agoCompare

[ Source](https://github.com/nomoticai/agtp-drupal)[ Packagist](https://packagist.org/packages/agtp/agtp-drupal)[ RSS](/packages/agtp-agtp-drupal/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (1)Used By (0)

AGTP for Drupal
===============

[](#agtp-for-drupal)

A Drupal module that exposes your site to the Agent Transfer Protocol (AGTP). Site builders write handler classes the same way they'd write Drupal services; AGTP traffic routes through them via the gateway protocol.

This module pairs with two other packages:

- **[agtp-php](https://github.com/nomoticai/agtp-php)** — the language library that defines `EndpointContext`, `EndpointResponse`, `EndpointError`, and the `#[AgtpEndpoint]` attribute. Handler classes use these directly.
- **[mod\_php](https://github.com/nomoticai/agtp-php)** — the runtime that connects to `agtpd` over a gateway socket. The drush command in this module wraps it. Lives in the `agtp-php` repository alongside the SDK.

You do not run a separate `agtpd` daemon as part of Drupal — `agtpd`is the AGTP server. You install it once on the host, and it listens on TCP/4480 the same way Apache listens on 80. This module is the Drupal-side worker that connects to it.

Why AGTP instead of JSON:API or REST?
-------------------------------------

[](#why-agtp-instead-of-jsonapi-or-rest)

You can already expose Drupal content over HTTP via JSON:API. So what does AGTP buy you?

**A warm Drupal process per request.** AGTP handlers run inside a long-lived `drush agtp:serve` worker. Drupal's container is built **once** at worker startup; every subsequent request reuses it. Cold boot for a typical Drupal 10 site is 200-500 ms. Warm PHP-FPM behind nginx is 50-150 ms per request. The AGTP worker is sub-millisecond per request for the dispatch layer plus whatever the handler does — the bootstrap tax is paid once at process start, not on every call. For agent traffic — which is bursty and often hits the same endpoints repeatedly — this is a measurable performance difference.

**Identity and scope at the protocol level.** `$ctx->agentId` is a cryptographically verified agent identifier by the time it reaches your handler. `$ctx->authorityScope` is the scope claim the daemon already checked against the endpoint's declared `requiredScopes`. You don't have to rebuild this with JWTs and middleware.

**Attribution at the protocol level.** Every method invocation produces a daemon-signed Attribution-Record. Audit logging is the transport's job, not yours.

**HTTP keeps working.** AGTP runs on its own port via `agtpd`. Drupal answers HTTP on 80/443 as before. The two protocols coexist on the same host without interfering.

Requirements
------------

[](#requirements)

- Drupal 10.2+ or Drupal 11
- PHP 8.1+
- `agtpd` running locally or on the same host
- Drush 12+

Deployment compatibility
------------------------

[](#deployment-compatibility)

EnvironmentLong-lived workers?StatusSelf-hosted (VPS, bare metal, Kubernetes, Docker Compose)Yes — systemd, Supervisor, k8s `Deployment`**Supported**Platform.shYes — native worker containersRecipe pending; should workDDEV / Lando (local dev)Yes — custom service overlaysRecipe pending; should workAcquia CloudNo native long-running workersNot supported. Run `agtpd` + worker on a sibling instance.PantheonQuicksilver is event-triggered onlyNot supported. Same answer as Acquia.AGTP for Drupal is **self-hosted-first**. Sites on PaaS platforms without long-running worker support need to run `agtpd` and the `drush agtp:serve` worker on a sibling host they control, then point the gateway socket at it via TCP loopback (`127.0.0.1:4481`) or over the network.

Install
-------

[](#install)

```
composer require agtp/agtp-drupal
drush en agtp_drupal
```

Writing a handler
-----------------

[](#writing-a-handler)

Three files: a handler class, a service registration tagging it `agtp.endpoint`, and (for a fresh module) an info file. Drupal's DI container collects everything tagged `agtp.endpoint` and feeds it to the worker at boot.

### 1. The handler class

[](#1-the-handler-class)

```
// web/modules/custom/example_agtp/src/Agtp/RoomHandlers.php
namespace Drupal\example_agtp\Agtp;

use Agtp\AgtpEndpoint;
use Agtp\EndpointContext;
use Agtp\EndpointError;
use Agtp\EndpointResponse;
use Drupal\Core\Entity\EntityTypeManagerInterface;

final class RoomHandlers
{
    public function __construct(
        private readonly EntityTypeManagerInterface $entityTypeManager,
    ) {}

    #[AgtpEndpoint(
        method: 'BOOK',
        path: '/room',
        errors: ['room_unavailable'],
        requiredScopes: ['booking:write'],
    )]
    public function book(EndpointContext $ctx): EndpointResponse|EndpointError
    {
        $nodes = $this->entityTypeManager
            ->getStorage('node')
            ->loadByProperties([
                'type' => 'room',
                'field_room_type' => $ctx->input['room_type'] ?? 'double',
            ]);

        if ($nodes === []) {
            return new EndpointError(
                code: 'room_unavailable',
                message: 'No rooms of that type are bookable.',
                details: ['room_type' => $ctx->input['room_type'] ?? null],
            );
        }

        $node = reset($nodes);
        return new EndpointResponse(body: [
            'reservation_id' => 'res-' . $node->id() . '-' . $ctx->agentId,
            'room_id' => $node->id(),
        ]);
    }
}
```

### 2. The service registration

[](#2-the-service-registration)

Tag the handler service with `agtp.endpoint`. The collector picks it up at boot.

```
# web/modules/custom/example_agtp/example_agtp.services.yml
services:
  example_agtp.room_handlers:
    class: Drupal\example_agtp\Agtp\RoomHandlers
    arguments:
      - '@entity_type.manager'
    tags:
      - { name: agtp.endpoint }
```

### 3. The module info file

[](#3-the-module-info-file)

```
# web/modules/custom/example_agtp/example_agtp.info.yml
name: Example AGTP handlers
type: module
package: AGTP
core_version_requirement: ^10.2 || ^11
dependencies:
  - agtp:agtp_drupal
```

Enable: `drush en example_agtp`.

Generate the daemon manifest
----------------------------

[](#generate-the-daemon-manifest)

After authoring handlers, project the `#[AgtpEndpoint]` attributes into daemon-side endpoint TOML files. This closes the silent-drift gap between the handler attribute and what `agtpd` is configured to serve.

```
# Write one TOML per handler into the agtpd endpoints directory
drush agtp:export-manifest --output=/etc/agtpd/endpoints

# Or preview to stdout
drush agtp:export-manifest --dry-run
```

The attribute is the source of truth. Re-run the command after every handler change. A typical deploy script runs `drush agtp:export-manifest`right after `drush updb` and before `systemctl reload agtp-drupal`.

Running the worker
------------------

[](#running-the-worker)

```
drush agtp:serve --gateway-socket=/var/run/agtpd/gateway.sock
```

What happens:

1. Drush bootstraps Drupal so the service container is built and your handler service is available.
2. `AgtpHandlerCollector` walks every service tagged `agtp.endpoint`and calls `HandlerRegistry::registerInstance()` on each, picking up every method decorated with `#[AgtpEndpoint]`.
3. A `GatewayClient` connects to the daemon, performs the handshake, receives the daemon's endpoint registration, and dispatches requests by looking up the registered handler.
4. The process serves until the daemon sends `goodbye` or the socket closes.

### Production deployment

[](#production-deployment)

Run the worker under a process supervisor:

```
# /etc/systemd/system/agtp-drupal.service
[Unit]
Description=AGTP for Drupal worker
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/example.com
ExecStart=/usr/bin/drush --root=/var/www/example.com/web agtp:serve --gateway-socket=/var/run/agtpd/gateway.sock
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
```

For higher request concurrency, run multiple worker units — `agtpd`accepts multiple module connections and routes among them.

Admin settings
--------------

[](#admin-settings)

After enabling, the settings page lives at `/admin/config/services/agtp`. It shows the configured gateway socket, the module identifier reported to `agtpd`, and a read-only listing of every endpoint the service container has collected — useful as a sanity check after deploy.

The page does not author handlers; handlers are PHP code in your custom modules. The page reflects what's in code.

Testing handlers
----------------

[](#testing-handlers)

Use [`Agtp\Testing`](https://github.com/nomoticai/agtp-php#testing-handlers)to exercise handler methods directly. Build a synthetic `EndpointContext`, call the method, assert on the result. No daemon, no gateway socket, no AGTP traffic.

```
public function testBookSuccess(): void
{
    $entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
    // ... stub entityTypeManager as needed ...
    $handler = new RoomHandlers($entityTypeManager);

    $ctx = Testing::makeContext(input: ['room_type' => 'double']);
    $response = Testing::assertOk($handler->book($ctx));
    $this->assertArrayHasKey('reservation_id', $response->body);
}
```

What this module does not do
----------------------------

[](#what-this-module-does-not-do)

- **Does not serve AGTP traffic over Drupal's HTTP request pipeline.**AGTP runs on its own port (4480) via `agtpd`. Drupal answers HTTP on its usual port. The two protocols coexist on the same host.
- **Does not expose handler endpoints to anonymous traffic.**Authentication happens at the `agtpd` layer (Agent-ID resolution and, when Agent-Cert lands, mTLS). Inside the handler, `$ctx->agentId` is the verified agent identity; trust it.
- **Does not provide a UI to author handlers.** Handlers are PHP code in your modules. The admin page surfaces what code declared.

Related
-------

[](#related)

- [`agtp-php`](https://github.com/nomoticai/agtp-php) — the SDK and the `mod_php` runtime
- [`agtp-symfony`](https://github.com/nomoticai/agtp-symfony) — the Symfony equivalent of this module

###  Health Score

18

—

LowBetter than 7% of packages

Maintenance57

Moderate activity, may be stable

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/488953?v=4)[chrishood](/maintainers/chrishood)[@chrishood](https://github.com/chrishood)

### Embed Badge

![Health badge](/badges/agtp-agtp-drupal/health.svg)

```
[![Health](https://phpackages.com/badges/agtp-agtp-drupal/health.svg)](https://phpackages.com/packages/agtp-agtp-drupal)
```

###  Alternatives

[w8tcha/ckeditor-wordcount-plugin

WordCount Plugin for CKEditor Editor

77482.0k](/packages/w8tcha-ckeditor-wordcount-plugin)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
