<!--
Architecture:
This Markdown template is the canonical agent setup guide. Runtime code only
injects deployment-specific URLs into placeholder tokens.
-->
# Connect an AI Agent to AppsPonds
AppsPonds lets a user publish web apps into their pond. An AI agent can connect
through browser-approved integration access, inspect visible ponds and apps, and
publish an AppsPonds package when the user has publishing access.
## Tell the User
Use this prompt when a user asks you to connect to AppsPonds:
> I can connect to AppsPonds using browser approval. I will ask AppsPonds for
> the minimum scopes I need, show or open one approval link for you, and keep
> polling that same request while you approve it. I will show the request name,
> user code, and approval link so you can confirm the browser page matches the
> request I am polling. The approval page will not show me a token; AppsPonds
> returns the token only once to my active poll.
> For publishing I need `apps:create` or `apps:publish`; read-only browsing
> only needs `ponds:read` and `apps:read`.
If the user wants to publish, also tell them:
> AppsPonds may require early publishing access or owner/App Manager approval.
> If approval is required, I can stage the candidate version and report that it
> is pending approval instead of claiming it is live.
## Discovery
- Human page: https://admin.appsponds.com/agents
- Markdown: https://admin.appsponds.com/agents.md
- llms.txt: https://admin.appsponds.com/llms.txt
- Full agent context: https://admin.appsponds.com/llms-full.txt
- API catalog: https://admin.appsponds.com/.well-known/api-catalog
- OpenAPI: https://admin.appsponds.com/openapi.json
- Package manifest schema: https://admin.appsponds.com/schemas/appsponds-package.schema.json
- Agent manifest: https://admin.appsponds.com/.well-known/appsponds-agent.json
## Browser-Approved Connection Flow
1. If you already have an AppsPonds token in a secure agent credential store,
test it first with `GET /api/agent/ponds`. Reuse it if AppsPonds returns
`ok: true`. If AppsPonds returns `401 authentication_error`, discard that
token locally and continue with a new browser approval flow.
2. Start authorization with `POST /api/integrations/authorize/start`.
3. Request only the scopes needed for the user's task.
4. Keep the returned `requestId` and `deviceCode` together in the active
agent process. They are required for the only token exchange.
5. Start polling `POST /api/integrations/authorize/poll` immediately with
that exact `requestId` and `deviceCode`, before or at the same time as
showing the approval link. Keep polling the same request until it returns a
token, expires, or the user cancels.
6. Open or show `approvalUrl` in a browser where the user is signed in. If you
are connecting through a tunnel or alternate origin, use the same-origin
approval URL returned by the local companion when available. If the user
cannot see the page, show the same request name, `userCode`, and link
again; do not create another request unless this one expires or the user
explicitly cancels it.
7. When a poll response includes `token`, capture it synchronously before
replying to the user. Store it only in the agent session or configured
secure credential store. Do not print it or commit it to a project.
8. If polling returns `status: "consumed"` with `token: null`, that request
has already performed its one-time exchange and this agent missed the raw
token. Stop polling that request, explain that it is unrecoverable, and ask
before starting a replacement request.
Authorization requests expire quickly. Issued integration tokens are scoped,
revocable, audited, and expire by default.
### Polling Contract
- The raw bearer token is returned only once, in the poll response that observes
browser approval and consumes the request.
- The token is never shown on the approval page, returned by the approval
endpoint, placed in a redirect URL, recoverable from the admin UI, or returned
by later polls.
- Treat `consumed` without a token as terminal. It is not a signal to keep
polling or to look for the token elsewhere.
- Do not wait for the user to reply "approved" before starting the poll loop.
Poll while the user approves.
- Do not create multiple authorization requests for one connection attempt.
Track one request/code/link at a time so the user approves the same request
the agent is polling.
- Always tell the user exactly which request to approve using the server
response fields, for example:
`Approve "Codex static mockup" publish with user code "0923567F" at <approvalUrl>`.
- If your host can interrupt long-running tool calls or conversation turns,
run the poll loop in a single local script or tool call that captures the
token before returning status to the conversation.
- New tokens automatically supersede older active tokens for the same user,
integration name, and scopes. Prefer reusing a valid stored token instead of
starting a new request.
## Exact MCP Publish Sequence
When the AppsPonds MCP/local companion is available, use this sequence. Do not
invent alternate argument names.
1. Optional existing token check: call `list_apps` with `appsPondsUrl` and the
stored `token`. If it succeeds, skip login.
2. If no valid token exists, call `start_login` with:
`{ "appsPondsUrl": "https://admin.appsponds.com", "name": "AppsPonds MCP Agent", "scopes": ["ponds:read", "apps:read", "apps:publish"] }`
for publishing an existing app, or add `"apps:create"` only when creating a
new app.
3. Immediately keep calling `poll_login` with the returned `appsPondsUrl`,
`requestId`, and `deviceCode` while the user approves the returned link.
4. Call `list_apps` with `appsPondsUrl` and `token`. Pick the existing app by
AppsPonds app slug; a pond name is not a local folder.
5. To publish from a folder, call `publish_existing_app` with:
`{ "appsPondsUrl": "https://admin.appsponds.com", "token": "<token>", "appSlug": "<slug>", "folderPath": "<absolute-project-folder>", "appKind": "server", "sourceType": "docker-zip", "dockerfilePath": "Dockerfile", "healthCheckPath": "/" }`
for a server app. For a static app, use `"appKind": "static"` and
`"sourceType": "static"`.
6. To publish a prebuilt zip, call `publish_existing_app` with `artifactPath`
instead of `folderPath`. Do not pass both.
MCP argument names are exact:
- `appKind`: `"static"` or `"server"`. This is an MCP packaging argument.
- `sourceType`: `"static"` or `"docker-zip"` for package/zip publishing.
- `folderPath`: absolute local project folder to inspect and zip.
- `artifactPath`: absolute path to an already built zip.
- `appSlug`: AppsPonds app slug from `list_apps`, not a pond name.
- Do not pass `type`, `kind`, `mode`, or `publishMode` to MCP tools unless
the tool schema explicitly lists that argument.
## Exact HTTP Publish Fields
Hosted agents without MCP should use the OpenAPI document, but these field names
are the common path:
- `GET /api/agent/ponds`: validate a stored token and list visible ponds.
- `GET /api/agent/apps`: list apps and choose `slug` for existing-app
publishing.
- `POST /api/agent/apps/{slug}/publish`: multipart form for an existing app.
Include `artifact` as the zip file, `sourceType` as `static` or
`docker-zip`, optional `packageManifest` JSON, optional `dockerfilePath`,
optional `healthCheckPath`, optional `releaseCommand`, and optional
`environmentVariables` JSON patch entries. Do not send `publishMode` to
this endpoint.
- `POST /api/agent/apps`: multipart form for creating a new app. Include
`displayName`, `slug`, `kind` as `static` or `server`, `sourceType`,
optional `pondId`, and `artifact`. Include `publishMode=publish` only
when the user asked to create and publish immediately; omit it for draft.
- Invalid, expired, or wrong-scope bearer tokens return `401` with
`authentication_error`. Do not retry the same bad token in a loop.
## Published App User Context
When a user opens a published app, AppsPonds authenticates at the Traefik
forward-auth layer before the request reaches the app container. App code should
read the AppsPonds user from request headers, not by implementing its own
AppsPonds login flow.
For a signed-in user who has an app access grant, AppsPonds forwards these
headers to the app:
- `X-Auth-User-Id`: stable AppsPonds user id.
- `X-Auth-Email`: signed-in user email address.
- `X-Auth-App`: current AppsPonds app slug.
- `X-Auth-Role`: app access role name, for example `Viewer` or a custom role.
- `X-Auth-Role-Guid`: stable role id when the grant is tied to an app role.
- `X-Auth-Entitlement`: JSON string with the current entitlement, currently
`{ "role": { "name": "...", "guid": "..." } }`.
- `X-Auth-Context-Jwt`: short-lived AppsPonds-signed context token with
`sub`, `email`, `app`, `role`, `iat`, and `exp` claims.
Roles here are app-user roles only. They identify the user's entitlement inside
the published app; they are not the same as AppsPonds control-plane
collaborator capabilities such as Apps Manager, Marketing Manager, or Owner.
If the app needs authorization, map `X-Auth-Role` or
`X-Auth-Entitlement.role.guid` to app-specific permissions.
If public/direct app access is enabled and the request is anonymous, or the
signed-in user has no grant, AppsPonds may allow the request but sends no
`X-Auth-*` identity headers. Treat missing `X-Auth-Email` or
`X-Auth-User-Id` as an anonymous user.
AppsPonds strips browser cookies before proxying to app containers. Apps should
trust `X-Auth-*` headers only when deployed behind the AppsPonds proxy; do not
trust client-supplied `X-Auth-*` headers in local development or any bypass
path. For client-side apps that need the same context, call
`GET /auth/context?appHost=<app-hostname>` from the app origin with browser
credentials. It returns the same signed context only for the matching app
origin and a granted signed-in user.
### Testing roles locally
When implementing or debugging role-aware app behavior in this repository, open
the app through the AppsPonds development proxy at `/apps/<app-slug>/`.
Access Managers can use the injected AppsPonds dev auth switcher to choose among
the app's real granted users and roles. The proxy then sends the same
`X-Auth-*` headers to server-side requests and serves
`/apps/<app-slug>/auth/context` with the same signed context shape for
browser-only code.
Do not invent app-local role mocks, fake headers, or separate test identities
unless the user explicitly asks for that. Prefer the AppsPonds proxy switcher so
server routes, page navigations, and browser auth-context calls all exercise the
published app permission model.
## Exact Zip Package Rules
- Zip packages use `appsponds-package.json` at the archive root.
- For static zip packages, set
`"app": { "kind": "static", "sourceType": "static" }`.
- For server zip packages, set
`"app": { "kind": "server", "sourceType": "docker-zip" }`.
- Never use `"docker-git"` inside a zip package manifest. Git deployment is a
browser form/API mode, not a zip package source type.
- Write `appsponds-package.json` as strict UTF-8 JSON without a byte order mark
(BOM), and validate it with `JSON.parse` before zipping.
- Exclude `node_modules`, `.git`, `_apdeploy`, `.env`, `.env.*`,
private keys, certificates, and npm credentials.
## Scopes
- `ponds:read`: list ponds the user can see.
- `apps:read`: list apps visible to the user.
- `apps:create`: create a new app from a package. Ask only when creating.
- `apps:publish`: publish a package to an existing app. Ask only when publishing.
Default authorization is read-only: `ponds:read` and `apps:read`.
## Agent API
All agent API requests use `Authorization: Bearer <token>`.
- `GET /api/agent/ponds`: list visible ponds.
- `GET /api/agent/apps`: list visible apps.
- `POST /api/agent/apps`: create an app from a zip artifact.
- `POST /api/agent/apps/{slug}/publish`: publish a zip artifact over an owned app.
Use the OpenAPI document at `/openapi.json` for request and response shapes.
## Publishing Rules
- A user may need early access before publishing. If a publish endpoint rejects
the request because publishing access is unavailable, tell the user to open
AppsPonds and request access from the publish workflow.
- Creating a new app requires a target pond. If the user started from a specific
pond, use that pond and do not ask again.
- Publishing an existing app is owner-only in this v1 API.
- If AppsPonds says the publish is pending approval, tell the user the candidate
version is staged and waiting for an Owner or Apps Manager to approve it.
- Do not claim a version is live until AppsPonds reports it as deployed/current.
- For package uploads, prefer a generated `appsponds-package.json` manifest so
AppsPonds can validate the app kind, source type, health check, resources, and
static path before deployment.
## Package Format
Packages are zip archives. A package can include `appsponds-package.json` at
the archive root:
```json
{
"schemaVersion": 1,
"app": { "kind": "server", "sourceType": "docker-zip" },
"deployment": {
"dockerfilePath": "Dockerfile",
"healthCheckPath": "/health"
}
}
```
Static apps use `"kind": "static"`, `"sourceType": "static"`, and may set
`deployment.staticPath` when `index.html` is inside a build directory.
Server apps must listen on `0.0.0.0:3000` or `process.env.PORT`. Multi-
container apps, background-only workers, arbitrary public ports, and dedicated
per-app Postgres are not part of v1.
## Optional Resources
AppsPonds never provisions resources unless the package requests them.
- External Postgres: `resources.postgres.mode = "external"`; the app must use
the configured env var, normally `DATABASE_URL`.
- AppsPonds shared Postgres: `resources.postgres.mode = "appsponds-shared"`.
- SQLite: request `resources.sqlite`; AppsPonds provides a durable
`sqlite:////data/app.sqlite` URL by default.
- Mounted storage: request `resources.storage.mountPath`, normally `/data`.
Agents should inspect dependencies, env examples, migrations, ORM config, and
database files. If no database is required, do not add database resources.
## Local Companion and MCP
When working from this repository, the local companion can expose deterministic
packaging and MCP tools:
```powershell
npm run agent -- serve
npm run agent -- mcp
```
The MCP server exposes tools to inspect a folder, package a folder, start and
poll browser approval, list visible apps, create an app, and publish an existing
app. Hosted agents that do not have the local companion can call the HTTP API
directly using the OpenAPI document.
## Safety Requirements for Agents
- Request the narrowest scope needed.
- Do not ask for write scopes until the user wants to create or publish.
- Do not persist returned log output, tokens, app env values, or generated
packages unless the user asks for a local artifact.
- Do not upload secret-like files such as `.env`, private keys, certificates,
or npm credentials.
- Verify package contents and health checks before reporting success.
- Treat redaction as best-effort; never intentionally print secrets into app
logs or package summaries.