@poppykit/server 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +21 -0
- package/AUTH.md +60 -0
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +50 -0
- package/DELIVERY.md +113 -0
- package/INTEGRATIONS.md +84 -0
- package/LICENSE +21 -0
- package/README.md +116 -2
- package/RELEASING.md +193 -0
- package/SECURITY.md +13 -0
- package/STORAGE.md +119 -0
- package/dist/adapter-contract.d.ts +12 -0
- package/dist/adapter-contract.js +140 -0
- package/dist/auth/authenticator.d.ts +6 -0
- package/dist/auth/authenticator.js +51 -0
- package/dist/auth/authority.d.ts +55 -0
- package/dist/auth/authority.js +16 -0
- package/dist/auth/dpop.d.ts +15 -0
- package/dist/auth/dpop.js +49 -0
- package/dist/browser.d.ts +44 -0
- package/dist/browser.js +110 -0
- package/dist/conversation/service.d.ts +126 -0
- package/dist/conversation/service.js +754 -0
- package/dist/conversation/store.d.ts +26 -0
- package/dist/conversation/store.js +14 -0
- package/dist/feedback.d.ts +13 -0
- package/dist/feedback.js +44 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +14 -0
- package/dist/network/discovery.d.ts +101 -0
- package/dist/network/discovery.js +276 -0
- package/dist/network/transport.d.ts +31 -0
- package/dist/network/transport.js +305 -0
- package/dist/protocol/sse.d.ts +15 -0
- package/dist/protocol/sse.js +90 -0
- package/dist/protocol/wire.d.ts +128 -0
- package/dist/protocol/wire.js +278 -0
- package/dist/reference-auth/index.d.ts +209 -0
- package/dist/reference-auth/index.js +999 -0
- package/dist/reference-auth/store.d.ts +53 -0
- package/dist/reference-auth/store.js +2 -0
- package/dist/sqlite-init.d.ts +2 -0
- package/dist/sqlite-init.js +17 -0
- package/dist/sqlite.d.ts +27 -0
- package/dist/sqlite.js +369 -0
- package/dist/store.d.ts +1 -0
- package/dist/store.js +1 -0
- package/examples/quickstart.mjs +29 -0
- package/package.json +67 -4
package/API.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
All public entry points ship TypeScript declarations in `dist`. Use editor
|
|
4
|
+
completion for complete options and results; this map identifies the integration seams.
|
|
5
|
+
|
|
6
|
+
| Entry point | Main exports | Guide |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| `@poppykit/server` | `createRequestAuthenticator`, `AuthorityError`, authority types | [authentication](AUTH.md) |
|
|
9
|
+
| `@poppykit/server` | `createConversationService`, `createConversationNotifications` | [delivery](DELIVERY.md) |
|
|
10
|
+
| `@poppykit/server` | `createBrowserSessions`, `createFeedbackHandler`, MCP metadata/error helpers | [integrations](INTEGRATIONS.md) |
|
|
11
|
+
| `@poppykit/server/reference-auth` | `createReferenceIssuer`, `createMetadataResolver`, `createNodeTransport` | [authentication](AUTH.md) |
|
|
12
|
+
| `@poppykit/server/sqlite` | `SqliteCompanyDatabase` | [storage](STORAGE.md) |
|
|
13
|
+
| `@poppykit/server/adapter-contract` | adapter verification helpers | [storage](STORAGE.md) |
|
|
14
|
+
|
|
15
|
+
Conversation workers claim accepted work, enforce account/scopes, publish replies
|
|
16
|
+
and finish work through service operations. External effects require application
|
|
17
|
+
idempotency and reconciliation. Existing issuers implement `ExternalAuthorityAdapter`
|
|
18
|
+
and do not need the optional reference issuer. Your framework owns HTTPS routing,
|
|
19
|
+
trusted public URLs, ingress limits, request cancellation and private-error handling.
|
|
20
|
+
|
|
21
|
+
Run the [quickstart](examples/quickstart.mjs) without a listener or external service.
|
package/AUTH.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Company authentication
|
|
2
|
+
|
|
3
|
+
Integrating an existing issuer uses the core `ExternalAuthorityAdapter` and
|
|
4
|
+
`createRequestAuthenticator`. It does not import, run, or persist the optional
|
|
5
|
+
reference issuer. The host must describe its issuer's revocation visibility and
|
|
6
|
+
order authorization checks with protected work. The reference issuer is a
|
|
7
|
+
separate `/reference-auth` import and uses the shared company database coordinator
|
|
8
|
+
for immediate revocation visibility.
|
|
9
|
+
|
|
10
|
+
Reference Direct sign-in uses `beginAuthorization` / `completeAuthorization`.
|
|
11
|
+
The host authenticates the human and protects consent with its own browser
|
|
12
|
+
session/CSRF controls; pass a high-entropy browser binding. Registered callback
|
|
13
|
+
URLs must be HTTPS on the client metadata domain and exactly match registration.
|
|
14
|
+
|
|
15
|
+
Enable reference Device sign-in with `device: { endpoint, verificationUri,
|
|
16
|
+
scopes }`. Route the configured device endpoint to `deviceAuthorization`.
|
|
17
|
+
On the human page call `beginDeviceAuthorization(userCode, browserBinding)` and
|
|
18
|
+
show the client identity and requested scopes. After an authenticated explicit
|
|
19
|
+
approve/decline action call `completeDeviceAuthorization`. Opening a link must
|
|
20
|
+
never approve it. The token endpoint handles durable polling intervals, slowdown,
|
|
21
|
+
denial, expiry, partial consent, one-use codes and the Session's sticky account.
|
|
22
|
+
|
|
23
|
+
Enable Mediated sign-in with `mediated: { endpoint, scopes, fields,
|
|
24
|
+
authenticate, verifyCode }` and route that endpoint and its sign-in-ID children
|
|
25
|
+
to `mediatedSignIn`. The SDK authenticates the Session Token and DPoP proof,
|
|
26
|
+
validates declared fields, counts attempts before calling the host backend,
|
|
27
|
+
and binds challenges to the initiating client, UserID and Session. The host
|
|
28
|
+
verifies credentials in `authenticate` and may return an account plus a narrower
|
|
29
|
+
scope list. It may instead return a challenge handle and safe delivery hint,
|
|
30
|
+
then verify codes using `verifyCode`. Host hooks must not log or retain credentials
|
|
31
|
+
and must enforce their own backend security and OTP delivery/verification.
|
|
32
|
+
|
|
33
|
+
Credentials and codes exist only during the trusted handler call; the SDK clears
|
|
34
|
+
input objects on exit, stores no passwords/codes and never includes them in
|
|
35
|
+
responses. Backend errors return a safe unavailable code. Pending remote hooks
|
|
36
|
+
are not automatically repeated after interruption. An operation left verifying
|
|
37
|
+
expires; the user can explicitly start another sign-in subject to attempt limits.
|
|
38
|
+
|
|
39
|
+
Defaults are five credential attempts per Session per minute, 100 per client per
|
|
40
|
+
minute, five code attempts per challenge, and ten-minute sign-in expiry. Configure
|
|
41
|
+
limits for your service and apply network-level limits before authentication.
|
|
42
|
+
SQLite persists attempt windows and bounded tombstones; `cleanup` removes sign-in
|
|
43
|
+
records only after their original expiry plus 30 seconds. A failed/expired code
|
|
44
|
+
continues returning the same terminal status through that retained window.
|
|
45
|
+
|
|
46
|
+
Each pending request captures a client revision. Account disconnect increments
|
|
47
|
+
the revision and records that account's fence; later credential/consent results
|
|
48
|
+
for that account cannot undo disconnect. Requests for other accounts sharing the
|
|
49
|
+
same personal-agent client continue. A fresh explicit sign-in captures the new
|
|
50
|
+
revision. Revision and per-account fence records remain until explicit issuer
|
|
51
|
+
reset and never count as pending requests. Terminal method tombstones also do
|
|
52
|
+
not consume pending quota.
|
|
53
|
+
|
|
54
|
+
Issuer state is version 3. Older development state is refused without mutation;
|
|
55
|
+
there is no automatic migration. Follow the explicit stop/backup/reset procedure
|
|
56
|
+
in [STORAGE.md](STORAGE.md) only for state you choose to discard.
|
|
57
|
+
|
|
58
|
+
Advertise only configured methods in your `poppy.json`; `authorizationMetadata()`
|
|
59
|
+
adds the device endpoint only when enabled. There is no login/consent UI, identity
|
|
60
|
+
provider, CLI or application workflow in this package.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — initial development release candidate (unpublished)
|
|
4
|
+
|
|
5
|
+
Request authentication, existing-issuer integration, optional Direct/Device/Mediated
|
|
6
|
+
reference issuer, atomic targeted SQLite adapters, JSON/SSE conversation lifecycle,
|
|
7
|
+
API/browser/feedback hooks and independent-peer/crash regressions.
|
|
8
|
+
|
|
9
|
+
- Select MIT licensing and ship the complete LICENSE.
|
|
10
|
+
- Prepare public package metadata and the initial 0.1.0 version.
|
|
11
|
+
- Preserve J8 strict TypeScript tests, real subprocess/security regressions and
|
|
12
|
+
immutable installed-package verification on Linux, macOS and Windows.
|
|
13
|
+
|
|
14
|
+
No npm publication has occurred. Version 0.x APIs/storage may still change; record
|
|
15
|
+
compatibility and migration/reset implications with future changes. This release
|
|
16
|
+
does not promise stable 1.0 compatibility or certify external host integrations.
|
|
17
|
+
|
|
18
|
+
## 0.0.0 — historical private development candidates
|
|
19
|
+
|
|
20
|
+
J1–J8 established protocol capabilities, targeted storage, durable recovery,
|
|
21
|
+
documentation, strict tests and fail-closed release verification. Their prior
|
|
22
|
+
commits/artifact results remain historical evidence for those exact candidates.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
This package targets Personal Agent Protocol draft 0.2. Check the current
|
|
4
|
+
[specification](https://personalagentprotocol.org/docs/0.2/spec) and
|
|
5
|
+
[guides](https://personalagentprotocol.org/docs/0.2/guides); retained snapshots and
|
|
6
|
+
tests are supporting evidence. Distinguish MUST requirements, SHOULD recommendations
|
|
7
|
+
and product choices. Individual extensions and demo/business workflows are excluded.
|
|
8
|
+
|
|
9
|
+
Use Node 24.12.0, TypeScript 5.9.3 and @types/node 24.10.0.
|
|
10
|
+
Install with the pinned pnpm version in package.json and `pnpm install --frozen-lockfile`.
|
|
11
|
+
Run `npm run typecheck`, `npm run build`, `npm test` and `npm run check:docs`.
|
|
12
|
+
CI partitions tests without omitting any test file; full `npm test` remains useful
|
|
13
|
+
locally. Read [RELEASING.md](RELEASING.md) before changing workflow/release behavior.
|
|
14
|
+
|
|
15
|
+
Keep changes small and complete: include callers, declarations, regression checks
|
|
16
|
+
and documentation. Do not log credentials, weaken destination validation or silently
|
|
17
|
+
reset persisted data. Pull requests must explain checks actually run and limitations.
|
|
18
|
+
Use the repository's Issues for non-sensitive defects and feature discussions.
|
|
19
|
+
Code is licensed under [MIT](LICENSE). No separate contribution agreement is
|
|
20
|
+
configured; confirm permission for third-party contributions and obtain owner
|
|
21
|
+
authorization before publishing.
|
|
22
|
+
|
|
23
|
+
## TypeScript test harness and coverage
|
|
24
|
+
|
|
25
|
+
First-party tests, executable fixtures, process workers and packaged verification
|
|
26
|
+
scripts are canonical `.mts` files. Run `npm run typecheck:tests` for the complete
|
|
27
|
+
strict program and `npm run build:tests` before individual
|
|
28
|
+
`node scripts/run-checks.mjs GROUP` commands (unit, contracts, integration or
|
|
29
|
+
validation). `npm test` performs that checked harness build and runs all groups.
|
|
30
|
+
Build production first with `npm run build`; tests import compiled `dist/*.js`,
|
|
31
|
+
not TypeScript source. No loader or native type stripping replaces the compiler.
|
|
32
|
+
|
|
33
|
+
The existing TypeScript compiler emits ignored adjacent `.mjs` files only after
|
|
34
|
+
all strict diagnostics pass. Failed builds remove stale canonical outputs.
|
|
35
|
+
`scripts/build-tests.mjs` is compiler/bootstrap tooling, not an unchecked test
|
|
36
|
+
runner. Canonical test sources use LF, enforced by `.gitattributes`, so Linux and
|
|
37
|
+
Windows validate the same exact source bytes. Test output stays outside the npm
|
|
38
|
+
package allowlist. Retained JavaScript release/build tools and packaged examples
|
|
39
|
+
are genuine production tooling/examples; retained protocol captures are references.
|
|
40
|
+
|
|
41
|
+
Run `npm run coverage` for a production build, checked harness and native Node
|
|
42
|
+
24.12.0 line/branch/function report. `coverage/summary.txt`, `coverage/lcov.info`,
|
|
43
|
+
`coverage/report.json` and `coverage/raw/` are generated local reports. Metrics
|
|
44
|
+
refer to first-party compiled `dist` JavaScript; production emits no source maps
|
|
45
|
+
and no TypeScript remapping is claimed. Third-party `node_modules` is excluded;
|
|
46
|
+
unloaded modules are listed explicitly. Raw child-process reports are retained,
|
|
47
|
+
but arbitrary subprocess execution is not guaranteed to be merged into the native
|
|
48
|
+
runner summary, and killed/crashed workers may not flush counters. Actual installed
|
|
49
|
+
package/OS checks remain separate evidence. No percentage threshold substitutes
|
|
50
|
+
for protocol, negative/security, process and platform assertions.
|
package/DELIVERY.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Conversation delivery and transport
|
|
2
|
+
|
|
3
|
+
The SDK targets Personal Agent Protocol draft 0.2. Company content, messages,
|
|
4
|
+
unknown events and partial text remain untrusted data for the host application.
|
|
5
|
+
|
|
6
|
+
## Client
|
|
7
|
+
|
|
8
|
+
Bind `createConversationClient` to the existing auth lifecycle:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const target = await auth.target(connectionId);
|
|
12
|
+
const conversations = createConversationClient({
|
|
13
|
+
...target, connectionId, store, clock: { now: Date.now },
|
|
14
|
+
authorizedRequest: request => auth.authorizedFetch(connectionId, request, target),
|
|
15
|
+
authorization: (id, scopes) => auth.authorization(id, scopes),
|
|
16
|
+
});
|
|
17
|
+
for await (const item of conversations.watch(conversationId, { consumer: 'ui', signal })) {
|
|
18
|
+
await processEvent(item.event);
|
|
19
|
+
await item.ack();
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`authorization(eventOrError)` returns the single auth lifecycle's verified
|
|
24
|
+
required/granted/missing scopes and eligible methods. The UI requests consent and
|
|
25
|
+
chooses an advertised method; Device or Mediated does not require Direct support.
|
|
26
|
+
|
|
27
|
+
`read` returns one page; `drain` follows `has_more` immediately. Named consumers
|
|
28
|
+
advance only on explicit acknowledgement. `watch` accepts SSE or JSON fallback,
|
|
29
|
+
skips unknown event types, deduplicates up to 10,000 event IDs per invocation, and
|
|
30
|
+
never rewinds its cursor on a duplicate. JSON pagination has its separate
|
|
31
|
+
`maxPages` bound (default 1000), without consuming reconnect attempts. Reconnects
|
|
32
|
+
are bounded (default 4); exhaustion is explicit `reconnect_exhausted`.
|
|
33
|
+
|
|
34
|
+
`lastEventId` takes precedence over the starting cursor. On reconnect the watcher
|
|
35
|
+
sends `Last-Event-ID`. `onPartial` is display-only: a null value clears unfinished
|
|
36
|
+
text on completion/disconnect; never execute or reason from an incomplete delta.
|
|
37
|
+
Messages are actionable only after a complete `message` event. SSE frames and
|
|
38
|
+
JSON fallback are limited to 1 MiB, streams to 16 MiB, and partial text per stream
|
|
39
|
+
to 1 MiB. UTF-8 fragmentation and CR/LF/CRLF are supported.
|
|
40
|
+
|
|
41
|
+
`cursor_expired` is an explicit history gap. Inform the user, then reset a named
|
|
42
|
+
consumer and reread retained history, or supply `onHistoryGap` returning true.
|
|
43
|
+
Automatic recovery is limited to one reset per watch; it does not recover missing
|
|
44
|
+
events. Checkpoints and pending sends survive process restart with durable stores.
|
|
45
|
+
Send retries keep their original connection, issuer, endpoint, ID, body and read
|
|
46
|
+
parameters. Abort signals are invocation-local and are never persisted. Canceling
|
|
47
|
+
delivery does not cancel an accepted company action or prove a lost send failed.
|
|
48
|
+
|
|
49
|
+
## Node transport
|
|
50
|
+
|
|
51
|
+
`createNodeTransport` uses Node HTTPS agents, with separate discovery, JSON and
|
|
52
|
+
stream pools. Defaults: 8 pools total, 2 connections per pool (including pending
|
|
53
|
+
TLS), 32 active operations, 15-second JSON timeout, and 5-second discovery timeout.
|
|
54
|
+
There is no hidden request queue: excess pool capacity fails `overloaded`, so a
|
|
55
|
+
later attempt performs fresh DNS and policy checks. Hosts decide scheduling.
|
|
56
|
+
Streams use `transport.stream` with a 5-minute/16-MiB lifetime budget. Call
|
|
57
|
+
`transport.close()` to cancel outstanding reads, DNS waiters, connects and streams.
|
|
58
|
+
|
|
59
|
+
Every request rechecks DNS and current address policy before pool assignment.
|
|
60
|
+
Changed approved addresses retire incompatible pools; actual TLS peers and reused
|
|
61
|
+
sockets are checked before HTTP bytes. TLS certificates remain verified. Only
|
|
62
|
+
explicit hostname/address exceptions permit private peers. Discovery never carries
|
|
63
|
+
credentials and follows only HTTPS redirects; credential-bearing requests never
|
|
64
|
+
follow redirects. Pool retirement may interrupt an active stream; reconnect uses
|
|
65
|
+
the durable cursor and fresh authorization.
|
|
66
|
+
|
|
67
|
+
Metadata caching remains bounded and honors freshness, Age, validators, no-cache
|
|
68
|
+
and no-store, with a 300-second upper bound. Expired entries revalidate; 304s retain
|
|
69
|
+
prior caching directives. `resolver.invalidate(target?)` fences old pending cache
|
|
70
|
+
writes without bypassing fetch concurrency/rate limits. J2 rejects changed saved
|
|
71
|
+
issuer/endpoint/resource bindings rather than rerouting persisted work.
|
|
72
|
+
|
|
73
|
+
## Company service and retention
|
|
74
|
+
|
|
75
|
+
The server accepts `supportedScopes`, the union offered by its actual sign-in
|
|
76
|
+
methods. `directScopes` remains a compatibility alias. One coordinator must still
|
|
77
|
+
cover authority checks and durable conversation reads/mutations.
|
|
78
|
+
|
|
79
|
+
Default JSON/request/frame budget is 1 MiB; the maximum encoded event is
|
|
80
|
+
1 MiB minus 1025 bytes. Page sizing reserves 1024 bytes for worst-case IDs, cursors
|
|
81
|
+
and envelopes and counts UTF-8 bytes. Company events and client messages that
|
|
82
|
+
cannot fit are rejected before commit; no receipt/work survives a rejected write.
|
|
83
|
+
Do not reduce configured budgets below those used for retained accepted events.
|
|
84
|
+
|
|
85
|
+
Notifications are post-commit hints. Subscribe before reading; reconcile durable
|
|
86
|
+
state on wake or every 1000 ms by default. `createConversationNotifications` is
|
|
87
|
+
local acceleration only. Multiple processes work without a broker through targeted
|
|
88
|
+
SQLite reconciliation; deployments can implement the same notification contract
|
|
89
|
+
with their own distributed adapter. Lost/duplicate hints cannot erase a commit.
|
|
90
|
+
SQLite event windows and append operations do not reload complete event histories.
|
|
91
|
+
|
|
92
|
+
Server streams default to 30 seconds and apply pull backpressure, rechecking
|
|
93
|
+
current authority before further pages. This bounded-connection policy omits
|
|
94
|
+
periodic idle comments; proxies may close an idle stream sooner, and the client
|
|
95
|
+
reconnects. Longer-lived deployments should supply heartbeat handling at their
|
|
96
|
+
HTTP adapter. Hosts must propagate disconnect/AbortSignal and body cancellation.
|
|
97
|
+
|
|
98
|
+
| Record | Lifetime / deletion rule |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| Conversation and message receipts | Entire conversation lifetime, including after closure and payload pruning; no automatic deletion |
|
|
101
|
+
| Event payloads | Retained indefinitely by default; host may call SQLite `pruneEvents(id, before, limit)` in bounded batches; recommend at least 30 days |
|
|
102
|
+
| Event cursor identities | Conversation lifetime; distinguish expired payloads (410) from foreign cursors (400) |
|
|
103
|
+
| Active work | Never pruned while held/pending/claimed; event pruning protects these conversations |
|
|
104
|
+
| Completed work | Bounded inspection retention; stale fences cannot publish |
|
|
105
|
+
| Latest user message, context and account binding | Operational conversation record, retained for its lifetime, including handoff after payload pruning |
|
|
106
|
+
| Client outbound/checkpoints | Retained until explicit local store deletion/reset; preserve pending/uncertain sends until reconciled |
|
|
107
|
+
| Pending auth and replay records | Separate auth validity/owner rules in AUTH.md and STORAGE.md; not the event TTL |
|
|
108
|
+
| CLI transcripts/retries | CLI ownership; local deletion never deletes company history or revokes authorization |
|
|
109
|
+
|
|
110
|
+
Pruning is explicit operator policy, never automatic data loss. It preserves old
|
|
111
|
+
message receipts and cursor identity. Concurrent reads see a coherent snapshot or
|
|
112
|
+
`cursor_expired`. Invalid/expired waited-POST cursors are rejected before accepting
|
|
113
|
+
the new write; an existing message ID still returns its original receipt.
|
package/INTEGRATIONS.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# API, browser and feedback integration
|
|
2
|
+
|
|
3
|
+
Use the same authority policy and coordinator as protected conversation work.
|
|
4
|
+
Supply the trusted public request URL; never derive it from arbitrary forwarding
|
|
5
|
+
headers. Custom issuers and transports remain responsible for their documented
|
|
6
|
+
revocation, address-validation and storage guarantees.
|
|
7
|
+
|
|
8
|
+
## MCP authentication
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
const metadata = mcpResourceMetadata({ resource: mcpUrl, issuer, dpop: true });
|
|
12
|
+
const authenticator = createRequestAuthenticator({ authority, now: Date.now,
|
|
13
|
+
endpoints: [{ origin: 'https://company.example', pathPrefix: '/mcp', resource: mcpUrl,
|
|
14
|
+
mcp: { dpop: true, metadataUrl: 'https://company.example/.well-known/oauth-protected-resource/mcp' } }],
|
|
15
|
+
});
|
|
16
|
+
// Serve metadata publicly without credentials. In the existing MCP handler:
|
|
17
|
+
try {
|
|
18
|
+
const principal = await authenticator.authenticate(request);
|
|
19
|
+
return await authority.policy.withCurrent(principal, ['tools:read'], () => handleMcp(request));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof AuthorityError) return mcpAuthorizationError(error, metadataUrl, true);
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
MCP metadata identifies the exact resource URL, issuer and ES256 support when
|
|
27
|
+
enabled. Challenges include Bearer and, if supported, DPoP with `resource_metadata`.
|
|
28
|
+
The exact endpoint accepts no child paths or added query. All other APIs retain
|
|
29
|
+
DPoP-only authentication. Configure the reference issuer `mcpResources` with the
|
|
30
|
+
same `{url,dpop}` list: every grant chooses Bearer for a non-DPoP MCP server even
|
|
31
|
+
when a proof was provided. DPoP MCP requires a valid proof. Bearer tokens are
|
|
32
|
+
resource-bound; DPoP-bound tokens cannot be presented as Bearer.
|
|
33
|
+
|
|
34
|
+
An existing issuer can accept ordinary MCP OAuth clients through its own flow.
|
|
35
|
+
Its introspector returns `dpopJkt: null` only for truly unbound Bearer credentials;
|
|
36
|
+
ordinary MCP principals have empty `userId` and an account-bound session owned by
|
|
37
|
+
the MCP client. Such sessions end on sign-in revocation/expiry and must not
|
|
38
|
+
continue signed out. The external issuer must enforce this on every introspection
|
|
39
|
+
and policy check and document its revocation visibility. The reference issuer
|
|
40
|
+
implements personal-agent grants; it does not add a general OAuth login product.
|
|
41
|
+
|
|
42
|
+
## Browser sessions
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const browser = createBrowserSessions({ endpoint: browserEndpoint, domain: companyDomain,
|
|
46
|
+
metadata, authority: referenceIssuer.browserSessions,
|
|
47
|
+
policy: referenceIssuer.authority.policy, cookies: cookieStore, now: Date.now,
|
|
48
|
+
});
|
|
49
|
+
// Mount browser.join on browserEndpoint; no request-body logging at this route.
|
|
50
|
+
const result = await browser.withSession(request, ['records:read'], principal => readRecords(principal));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`BrowserCookieStore.put/get` stores SHA256 cookie-value hashes, session bindings and
|
|
54
|
+
expiry. Supply durable storage with targeted operations and expire old rows. Never
|
|
55
|
+
store plaintext cookie values. `BrowserSessionAuthority.join` must atomically
|
|
56
|
+
consume assertion jti and validate an active same-client/same-user session; `current`
|
|
57
|
+
must read current session permissions. The reference issuer implements both on its
|
|
58
|
+
existing durable issuer store/coordinator; SQLite uses targeted browser selectors.
|
|
59
|
+
|
|
60
|
+
The browser handler verifies JWKS signature, asymmetric algorithm, typ, exact aud,
|
|
61
|
+
times/lifetime, replay, client/user/session and HTTPS company return URL. Invalid
|
|
62
|
+
requests return 400 with no cookie; successful requests set a host-only cookie and
|
|
63
|
+
return 303 to the validated URL. Defaults are `__Host-poppy-session`, Path=/, Secure,
|
|
64
|
+
HttpOnly, SameSite=Lax and max 3600 seconds capped by the current session lifetime.
|
|
65
|
+
SameSite=None is supported; Strict is refused. `authenticate` resolves current
|
|
66
|
+
authority on every request. Use `withSession` for scoped mutations so permission
|
|
67
|
+
checks and work are ordered by the same policy/coordinator. Sign-in, scope changes
|
|
68
|
+
and revocation are reflected immediately; no permissions are cached in cookies.
|
|
69
|
+
Host adapters must isolate the agent browser from the user's consent browser.
|
|
70
|
+
|
|
71
|
+
## Feedback
|
|
72
|
+
|
|
73
|
+
`createFeedbackHandler({endpoint, authenticator, policy, accept, allow?})` validates
|
|
74
|
+
a Session Token, including a signed-out token with no account scope. It checks the
|
|
75
|
+
JSON/UTF8 byte budget and nonempty task/description limits (200/2000 Unicode
|
|
76
|
+
codepoints), accepts unknown kinds and passes only a frozen inert-data report to
|
|
77
|
+
`accept`. Do not interpret its text as instructions, including in later model use.
|
|
78
|
+
The optional host `allow` hook supplies rate limiting; false returns 429. Malformed
|
|
79
|
+
reports return 400, auth errors use the shared challenge, success is empty 202.
|
|
80
|
+
There is no protocol deduplication identifier and no exactly-once promise. Hosts
|
|
81
|
+
own report retention, privacy filtering and downstream side-effect handling.
|
|
82
|
+
|
|
83
|
+
`tests/integrations.test.mts` exercises browser replay/current authority in memory
|
|
84
|
+
and SQLite, MCP schemes/resources/ordinary-client integration and feedback.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PoppyKit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PoppyKit server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for companies exposing services through [Personal Agent Protocol
|
|
4
|
+
draft 0.2](https://personalagentprotocol.org/docs/0.2/spec). Verify requests, serve
|
|
5
|
+
JSON/SSE conversations, enforce current permissions, support browser-session joining
|
|
6
|
+
and accept feedback. Your company owns accounts, consent UI, agent and business tools.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Requires Node **24.12.0 or later** and ESM. CI certifies Node 24.12.0; later majors
|
|
11
|
+
have no separate certification. Optional SQLite uses experimental `node:sqlite`
|
|
12
|
+
and requires local filesystem locking.
|
|
13
|
+
|
|
14
|
+
Version **0.1.0** is an initial development release candidate under the [MIT license](LICENSE).
|
|
15
|
+
It has not been published. Install a verified tarball:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install ./poppykit-server-0.1.0.tgz
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The only production dependency is `jose@6.2.12`. Neither client SDK nor CLI is required.
|
|
22
|
+
First-publication preparation is in [RELEASING.md](RELEASING.md).
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
The complete [quickstart](examples/quickstart.mjs) creates an optional reference
|
|
27
|
+
issuer, verified request authenticator and conversation service, then checks that
|
|
28
|
+
an unauthenticated request is rejected. It starts no network listener:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
node examples/quickstart.mjs
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Its essential service configuration is:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { createRequestAuthenticator, createConversationService } from '@poppykit/server';
|
|
38
|
+
const conversations = createConversationService({
|
|
39
|
+
endpoint, store: database.conversations(issuer), policy: auth.authority.policy,
|
|
40
|
+
authenticator: createRequestAuthenticator({ authority: auth.authority,
|
|
41
|
+
endpoints: [{ origin: 'https://company.example', pathPrefix: '/conversations', resource: endpoint }],
|
|
42
|
+
now: Date.now }), clock: { now: Date.now },
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`database`, `auth`, `issuer` and `endpoint` are created in the runnable example.
|
|
47
|
+
Use a private database file for persistence and your HTTPS framework to route
|
|
48
|
+
trusted public Requests to `conversations.handle(request)`. Configure company
|
|
49
|
+
account/consent hooks before advertising sign-in methods.
|
|
50
|
+
|
|
51
|
+
## Configuration and authentication
|
|
52
|
+
|
|
53
|
+
Existing issuers integrate through `ExternalAuthorityAdapter`: provide token
|
|
54
|
+
introspection, an explicit revocation-visibility guarantee and `AuthorityPolicy`
|
|
55
|
+
that orders checks with protected writes. `createRequestAuthenticator` verifies
|
|
56
|
+
the actual public method/URL and DPoP/resource binding. Resolve public URLs at
|
|
57
|
+
your trusted proxy boundary; do not trust arbitrary forwarded headers. Core
|
|
58
|
+
imports do not load or run the optional reference issuer.
|
|
59
|
+
|
|
60
|
+
The separate `/reference-auth` implements Direct, Device and Mediated flows.
|
|
61
|
+
Configure endpoints/trusted hooks, publish discovery/authorization metadata and
|
|
62
|
+
route token/revocation and method handlers. Your company authenticates humans,
|
|
63
|
+
protects consent with browser sessions/CSRF, delivers/verifies codes and chooses
|
|
64
|
+
scopes. Opening a consent link never approves it. See [AUTH.md](AUTH.md).
|
|
65
|
+
|
|
66
|
+
## Examples and common operations
|
|
67
|
+
|
|
68
|
+
Route protocol requests to `conversations.handle(request)`. Workers use `claim`,
|
|
69
|
+
`reply`, `finish` and lifecycle methods rather than writing rows directly. Use
|
|
70
|
+
`requireAccount(work, scopes)` or protected-operation hooks before accessing
|
|
71
|
+
account data. Model text cannot grant permission. Your application owns tool
|
|
72
|
+
execution, validation and effect idempotency; receipt/publication deduplication
|
|
73
|
+
does not make external writes exactly-once.
|
|
74
|
+
|
|
75
|
+
`createBrowserSessions` integrates your cookie store with current session authority.
|
|
76
|
+
`createFeedbackHandler` accepts validated reports, including signed-out sessions.
|
|
77
|
+
`mcpResourceMetadata` and `mcpAuthorizationError` integrate an existing MCP server
|
|
78
|
+
and issuer. See [INTEGRATIONS.md](INTEGRATIONS.md), [DELIVERY.md](DELIVERY.md) and
|
|
79
|
+
the independent generic request fixtures in `tests/independent-peer.test.mjs`.
|
|
80
|
+
|
|
81
|
+
## Errors, cancellation, retries and limits
|
|
82
|
+
|
|
83
|
+
`AuthorityError` contains a safe code, HTTP status and authentication headers.
|
|
84
|
+
Conversation handlers return protocol responses for known errors; your framework
|
|
85
|
+
must handle unexpected host failures without exposing private diagnostics. Bound
|
|
86
|
+
ingress bodies/rates before authentication. Propagate cancellation; a disconnected
|
|
87
|
+
client cannot undo accepted application work.
|
|
88
|
+
|
|
89
|
+
JSON pages and SSE events obey UTF-8 budgets. Notifications are hints; durable
|
|
90
|
+
targeted reads reconcile lost notifications. [DELIVERY.md](DELIVERY.md) documents
|
|
91
|
+
cursor/history gaps, waiting and record-specific retention. Choose
|
|
92
|
+
`stop-account-work` or `allow-accepted-work-to-finish` deliberately; new requests
|
|
93
|
+
always need current access. Immediate reference-issuer revocation visibility is
|
|
94
|
+
an implementation policy, not a universal protocol requirement.
|
|
95
|
+
|
|
96
|
+
## Storage and production responsibilities
|
|
97
|
+
|
|
98
|
+
Use one `SqliteCompanyDatabase` for reference issuer, conversations and coordinator
|
|
99
|
+
so authorization, revocation and protected writes share a transaction. SQLite
|
|
100
|
+
serializes writers and requires local filesystem locking. Supply your own
|
|
101
|
+
`ConversationStore`, `IssuerStore` and `AuthorityCoordinator` for other databases;
|
|
102
|
+
adapters enforce cross-process atomicity. [STORAGE.md](STORAGE.md) specifies
|
|
103
|
+
contracts, retention and explicit reset.
|
|
104
|
+
|
|
105
|
+
Company credentials and signing infrastructure remain your responsibility. The
|
|
106
|
+
personal-agent identity provider is a distinct trust role in the client package.
|
|
107
|
+
No model, database service, deployment vendor or demo is required.
|
|
108
|
+
|
|
109
|
+
## Reference and support
|
|
110
|
+
|
|
111
|
+
[API.md](API.md) maps public entry points and declarations to deeper guides.
|
|
112
|
+
This independent draft implementation has generic integration and packaged tests;
|
|
113
|
+
deployed issuer, proxy, database and browser policies need operator validation.
|
|
114
|
+
Individual extensions and business workflows are excluded. See
|
|
115
|
+
[CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md),
|
|
116
|
+
[CHANGELOG.md](CHANGELOG.md) and [RELEASING.md](RELEASING.md). No public license
|
|
117
|
+
grant has been selected; licensing remains an owner decision before publication.
|