Download the PHP package koh/poppy without Composer
On this page you can find all versions of the php package koh/poppy. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Package poppy
Short Description Framework-agnostic server core for the Personal Agent Protocol (Poppy): poppy.json discovery, issuer binding, agent identity, DPoP, Sessions, Direct and Device Sign-In, Account Tokens.
License MIT
Homepage https://ole-hartwig.eu/open-source
Informations about the package poppy
koh/poppy
A framework-agnostic PHP server for the Personal Agent Protocol (Poppy), draft 0.2: the protocol through which a person's own AI agent works with a company on their behalf. The agent discovers what the company offers, starts a Session, browses its website and calls its APIs in that Session, and signs in to the person's account only when a task needs it — with the person approving on the company's own page.
This package is the company side. It has no framework dependency and no global state: it speaks PSR-7, PSR-17, PSR-18 and PSR-20, and leaves storage, pages and routing to the integration. Integrations exist for Sylius and TYPO3.
What it covers
| Section of the specification | Here |
|---|---|
| 3 Discovery | PoppyDocument (/.well-known/poppy.json) and AuthorizationServerMetadata (RFC 8414, with poppy_domains for the issuer binding) |
| 4.1 Agent identity | ClientMetadataResolver, ClientAuthenticator — client ID metadata documents, private_key_jwt, aud pinned to the issuer |
| 4.2 Sessions | TokenEndpoint, JWT bearer grant, session_id, renewal |
| 4.3 DPoP | DpopVerifier (RFC 9449), Bearer tokens only for MCP servers without DPoP |
| 4.5 Direct Sign-In | DirectSignIn — authorization code with PKCE S256 and RFC 9207 iss |
| 4.6 Device Sign-In | DeviceAuthorizationEndpoint, DeviceVerification (RFC 8628) |
| 4.8–4.9 Account Tokens, sign-out | refresh_token grant, RevocationEndpoint, AccountDisconnector |
| 5 Web browsing | BrowserSessions — browser assertions; the integration sets its own cookie |
| 6 APIs | ResourceGuard, SessionContext, Challenge (WWW-Authenticate) |
| 8 Feedback | FeedbackEndpoint |
Not covered: Mediated Sign-In (4.7), in which the agent sends the person's password to the company, and conversations with a Company Agent (section 7). A company that offers neither is fully conformant; the discovery document only declares what is wired up.
The checks that matter
- Issuer binding, both directions.
poppy.jsonnames the issuer, and the issuer's metadata lists the domain inpoppy_domains. The document builder refuses a domain the issuer does not list. The metadata also carries the same binding asprotected_resources, the form draft 0.3 uses. - Assertions addressed to the issuer. Client and Session assertions must
carry the issuer as a single-string
aud— never the token endpoint, never an array — live at most five minutes, and carry ajtithat is accepted once. Browser assertions have their owntypand audience, so neither kind passes for the other. - Every token request proves a key. Session Tokens are bound to the DPoP
key of the request that obtained them; proofs are checked for method, URL,
age,
ath, key binding and — at the token endpoint — replay. ES256 is always accepted. Nonces are optional (TimeSlotNonceSource). - Bearer only where it cannot be avoided. A Bearer Session Token exists only for an MCP server that does not verify DPoP, and is accepted at that server's URL and nowhere else.
- Sign-out takes effect on the next request. Whether a request is signed in is read from the Session each time, not from the token, so a revoked Account Token turns every token issued under it into a signed-out one at once.
- A Session keeps its account. Once signed in, a Session can be signed in
again only to the same account; anything else is
account_mismatchand no tokens. - Nothing is fetched that should not be. Client metadata and key sets are fetched over HTTPS on port 443 only, never from loopback, private, link-local or other non-public addresses, never following redirects, and only after every check that needs no network has passed — a blocked or malformed request makes the server send nothing.
- No token is stored. Tokens are random strings; storage sees only their SHA-256 hash.
Usage
The integration provides the pages a person sees — sign-in and consent for
Direct Sign-In, code entry for Device Sign-In, the account page where Personal
Agents are disconnected — and calls DirectSignIn, DeviceVerification and
AccountDisconnector from them.
Storage on Doctrine DBAL
Storage\Dbal\DbalStorage, DbalReplayGuard and DbalFeedbackSink implement
the storage ports on DBAL 3.10 or 4 — the DBAL both Sylius and TYPO3 run on.
PoppySchema::addTo($schema, $prefix) describes the tables, for a Doctrine
migration or as the source of a TYPO3 ext_tables.sql. The single-use
operations are atomic in the database itself: the replay guard on a primary
key, codes and device approvals on the affected-row count of the statement
that takes them. SymfonyRateLimiter adapts symfony/rate-limiter.
What the integration must get right
- Replay guard: atomic, shared.
claim()must be one set-if-absent operation across every worker. Two operations ("exists?", then "store") let two parallel requests both accept the same assertion. - Storage: two single-use operations.
consumeAuthorizationCode()andconsumeApprovedDeviceAuthorization()must hand a record out at most once under concurrency. - HTTP client: no redirects, address pinning.
PublicAddressGuardchecks the resolved addresses before the request; the HTTP client resolves again. Use a client that refuses private addresses at connect time (Symfony'sNoPrivateNetworkHttpClient, or cURL withCURLOPT_RESOLVE) to close that gap. - Public URLs. Pass the public URL of an endpoint to the verifiers rather than the URL the request arrived at, which behind a TLS-terminating proxy is the internal one.
- Rate limits. Implement
RateLimiter, and rate-limit the device code entry page per client address: user codes are short by design.
Requirements
PHP 8.3 or later with openssl and sodium, a PSR-18 client and PSR-17
factories.
Independence
This is an independent implementation, written from the Personal Agent Protocol specification. It is not affiliated with the authors of the specification.
Licence
MIT. See LICENSE.
All versions of poppy with dependencies
ext-json Version *
ext-mbstring Version *
ext-openssl Version *
ext-sodium Version *
psr/clock Version ^1.0
psr/http-client Version ^1.0.3
psr/http-factory Version ^1.1.0
psr/http-message Version ^1.1 || ^2.0