Docs Reference

Configuration

Every option of definePoppyseed, and what it returns.

Everything Poppyseed does is set in one call: definePoppyseed(config) from poppyseed. It checks the config when it runs and throws a poppyseed: … error for anything inconsistent (an unknown scope on a tool, sign-in without accounts, no store in production), so mistakes show up at startup.

Required

Option Type Meaning
organization { name, domain } Your company's name, and the domain agents reach you at. domain must match the host serving poppy.json.
secret string At least 32 random characters. Seals Session Tokens, browser cookies and consent forms. Changing it signs every Session out.

Site

Option Default Meaning
baseUrl https://{domain} Your public origin, with no path (https://poppy.travel). DPoP proofs are checked against it, so set it to exactly what agents use (behind a proxy, the public URL, not the internal one).
store memory, outside production Where Sessions, tokens, operations and conversations live. Required in production: see Deploying.
apiDescription "{name} tools" One line describing your MCP and OpenAPI APIs to agents.
waitUntil none Keeps work alive after a response (slow operations, Company Agent replies). Pass Next.js after, wrapped in try/catch.
dev.allowInsecureClients false Accept agents whose metadata is on http:// or loopback addresses. Local development and tests only.
fetch Node: safeFetch, which refuses private addresses where it connects; elsewhere fetch How agent metadata is fetched. Override it in tests, or wrap the exported safeFetch to keep the address check.

Tools and scopes

Option Meaning
tools Record<name, tool(...)>. Names use letters, digits, - and _. Each tool has description, scope (a scope name, or null for anyone), input (a zod schema) and either run(input, ctx) or operation: { propose, perform, cancel?, withoutExtension? }.
scopes Your own scopes with the description people see on the consent page, e.g. { "travel:loyalty": "See your Poppy Miles balance" }. Names starting with poppy: are reserved.

run and perform get ctx: { sessionId, clientId, userId, account: { id, scopes } | null }. For a tool with a scope, account is always there, and its type says so. An operation's perform gets the terms its propose returned, with their types. It never contains tokens.

Operations

Field Meaning
propose(input, ctx) Returns { summary, terms, key?, expiresIn?, userApprovalRequired?, url? }. Must not change anything. key makes repeat proposals of the same action return the same operation, as a new revision when the terms differ. expiresIn is in seconds (default 900).
perform({ id, terms, summary }, ctx) Runs once after confirmation. Returns { summary, data? }, the account of what changed. Throw OperationFailed(summary) when it didn't complete; any other error leaves the operation in_progress until you call poppyseed.operations.settle.
cancel(operation, ctx) Optional. Tries to stop an operation in progress; return { summary } if it stopped, null if not.
withoutExtension "refuse" (default) answers extension_required to agents without the operations extension; "perform" acts on their call right away.
Option Default Meaning
operations.confirmWait 3000 Milliseconds a confirm waits for perform before answering in_progress with Retry-After.

Sign-in

Option Meaning
accounts.current(request) Who is signed in to your site in this browser: { id, label? } or null. Read your own session cookie. Required for any sign-in type.
accounts.signInUrl(returnTo) Your login page. It must send the person back to returnTo, and only to your own site.
signIn.direct { scopes }: Direct Sign-In (the person approves in their browser).
signIn.device { scopes, interval?, expiresIn? }: Device Sign-In (a code entered on any device). interval is the minimum seconds between polls (default 5); expiresIn how long a code lasts (default 600).
signIn.mediated { scopes, fields, verify, sendCode, codeRequired?, notify? }: Mediated Sign-In (the agent sends credentials). verify(credentials, attempt) returns { id } or null; return the same null for an unknown account and a wrong password.
Option Default Meaning
sessions.tokenTtl 3600 Session Token lifetime, seconds. Keep it to hours.
sessions.sessionTtl 43200 (12 h) How long a Session lasts.
sessions.accountTokenTtl 7776000 (90 days) Account Token lifetime. Using it doesn't extend it.
sessions.endOnSignOut false End Sessions when their Account Token is revoked, instead of continuing them signed out.

Agents

Option Meaning
clients.allow Only these agents may start Sessions: a list of client_id URLs, or (clientId) => boolean that checks your own registry. Omit to accept any agent.
clients.block client_id URLs that are refused.

Web browsing

Option Default Meaning
web off {} turns on /poppy/browser-session, so agents' browsers can join their Session.
web.cookieName poppyseed_session Name of the Session cookie.
web.cookieDomain this host Cookie Domain, to cover subdomains that agents browse.

Conversations

Option Default Meaning
conversations.agent required Your Company Agent: claudeAgent({ instructions, model?, apiKey? }) or any { reply(turn) }.
conversations.handoff.available(info) always no Whether a person can take a handoff now. When it says no, the agent is told why in a message.
conversations.maxWait 25 Longest wait a read is held, in seconds. Keep it under your platform's request timeout.
conversations.retainEvents 1000 Events kept per conversation; older cursors get cursor_expired.

Limits

Option Default Meaning
rateLimits.session 600 Requests per window for one Session.
rateLimits.user 1200 Requests per window for one person through one agent.
rateLimits.client 20000 Requests per window for one agent across everyone.
rateLimits.windowSeconds 60 Window length.

Pass rateLimits: false to turn limits off, for example behind a gateway that already enforces them. Mediated Sign-In has its own fixed attempt limits.

What definePoppyseed returns

Member Use
handle(request, { pathname? }) Serves a protocol request; undefined for other paths. toNextHandlers calls it.
urls The public URL of each part of the protocol, such as urls.mcp. Framework adapters rewrite ROUTES from poppyseed/paths.
tools Your tools as agents see them: name, description, scope, input schema.
protect(handler, { scope? }) Wraps your own route handler so it accepts only DPoP Session Tokens and gets ctx.
connections.list(accountId), .disconnect(accountId, clientId) Your account settings page.
browserSession(request) The agent Session a browser carries on your site, or null.
operations.get(id), .confirm(id, session, revision), .settle(id, state, result) Show, confirm on your website, or resolve operations.
conversations.queue(), .get(id), .join(id), .reply(id, text), .callTool(id, name, input), .release(id), .decline(id, reason), .close(id, reason) Your staff inbox.
endSession(sessionId) Ends a Session; its tokens stop working on the next request.