# Connect your agent

Connect an external agent through MCP OAuth and enable the optional assistant.

# Connect your agent

This app is the MCP server at `/api/mcp`. External clients and the optional in-app assistant use one tool catalog. Tools read real data authorized for the signed-in User; the public demo posts and profiles are separate static examples.

The assistant uses the Session's Active Organization. An MCP access token has no Active Organization, so MCP uses the User's personal Organization and checks current membership on every call. Joining another Organization does not grant the MCP connection access to its data.

## Endpoints [#endpoints]

The template's production origin is `https://template.its-live.dev`; replace it with your deployment's origin. Local web development uses `http://localhost:3005`.

| What                                          | Path                                      |
| --------------------------------------------- | ----------------------------------------- |
| MCP resource                                  | `/api/mcp`                                |
| JWKS                                          | `/api/auth/jwks`                          |
| OAuth protected-resource metadata, RFC 9728   | `/.well-known/oauth-protected-resource`   |
| OAuth authorization-server metadata, RFC 8414 | `/.well-known/oauth-authorization-server` |
| Sign-in and OAuth consent                     | `/login`, `/consent`                      |
| Pending tool approvals                        | `/integrations`                           |
| In-app assistant, when enabled                | `/assistant`                              |
| Session-authenticated API reference           | `/api/rpc/api-reference`                  |
| Session-authenticated OpenAPI document        | `/api/rpc/api-reference/spec.json`        |

An unauthenticated `POST /api/mcp` returns `401` with a Bearer challenge. MCP Inspector and local CLI clients can use the localhost URL; hosted clients need a reachable HTTPS deployment.

## OAuth and clients [#oauth-and-clients]

Better Auth handles OAuth 2.1 with PKCE S256 and dynamic client registration. Public clients register with `token_endpoint_auth_method: none`. Client ID Metadata Documents are not advertised; dynamic registration is the supported path.

| Scope            | Access                                                             |
| ---------------- | ------------------------------------------------------------------ |
| `openid`         | Identity for the grant                                             |
| `profile`        | Standard OAuth profile scope; tool calls still require `data:read` |
| `offline_access` | Refresh tokens for clients that need persistent connections        |
| `data:read`      | Read tools; required by the MCP route                              |
| `data:write`     | Request approval for write tools; separately consented             |

Read-only grants never see write tools in `tools/list`. Calling a write tool without `data:write` receives `403` with `insufficient_scope`. Write scope permits requesting a change; each exact change still needs the User's approval in the app. Revoking a connected agent invalidates subsequent MCP access even before its JWT expires.

For Claude Code:

```sh
claude mcp add --transport http doro \
  https://template.its-live.dev/api/mcp
```

Open `/mcp` in Claude Code and complete the browser sign-in and consent. For a local instance, substitute `http://localhost:3005/api/mcp`.

For Claude Desktop/claude.ai or ChatGPT, add the HTTPS MCP URL as a custom connector and complete OAuth. ChatGPT's documented path for this template is Developer mode; include `offline_access` for refresh tokens. The template is not automatically listed in any client's reviewed plugin directory. See [the client connection table and publishing distinction](../README.md#chatgpt-connector-vs-official-plugin).

Grok Build can use the same HTTP endpoint:

```sh
grok mcp add --transport http agent-plugin \
  https://template.its-live.dev/api/mcp
```

The optional Agent Plugins package for compatible hosts is documented in [`plugins/agent-plugin`](../plugins/agent-plugin/README.md). It points at this same OAuth resource.

If OAuth succeeds but a client shows no tools, first inspect the grant's scopes and revocation state. JWKS must be loaded in-process through Better Auth; a Worker HTTP self-fetch is not equivalent. A client may also cache discovery from an earlier configuration, in which case reconnect it. The [MCP validation guide](./mcp-e2e-validation.md) covers deployment verification.

## Tools, resources and approval [#tools-resources-and-approval]

The 15 tools below come from `packages/api/src/ai/catalog.ts`. `tools/list` remains the runtime authority for the client's granted scopes. A read-only grant exposes nine tools.

| Tool                                                     | Behavior                                                                          |
| -------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `todo_list`                                              | List or search paginated Organization Todos; advertises the existing todo widget  |
| `todo_detail`                                            | Read one authorized Todo                                                          |
| `files_list`                                             | List Organization file metadata, confirmed file IDs and conversion status         |
| `whoami`                                                 | Read the signed-in User's name and email                                          |
| `search`                                                 | Search Todo text, file names and converted document chunks                        |
| `fetch`                                                  | Read `todo:<id>`, `file:<key>` or `document:<fileId>:<chunkIndex>`                |
| `document_read`                                          | Read converted Markdown by `fileId`, with bounded pages and a continuation cursor |
| `organization_info`                                      | Read the authorized Organization and member names/roles                           |
| `ask_for_input`                                          | Ask for a choice in the assistant; standalone MCP calls return no answer          |
| `todo_create`                                            | Create a Todo after approval                                                      |
| `todo_complete`                                          | Mark a Todo done after approval                                                   |
| `import_start`                                           | Start an import from a confirmed Organization JSON file after approval            |
| `create_todo_draft`, `draft_task_plan`, `propose_review` | Create an inert proposal after approval; applying it remains a separate action    |

An MCP write returns `pending_user_approval`, an approval ID, expiry and the Integrations URL. Open Integrations in the same personal Organization, review the exact arguments, then approve or deny. Approval requests expire after 15 minutes; at most 20 unexpired pending requests are allowed per User. Approval cannot be transferred to another User, Organization, tool or argument set. Execution records its result atomically with the domain change; replaying an executed approval returns the stored result.

Imports also require their existing UUID `requestKey`, confirmed upload and source-version checks. An approved import starts an Import Run; the existing scheduled job performs the import. It does not make the HTTP request a background executor.

`template://organization/todos` exposes the first page of authorized Todos as an MCP resource. The `plan-my-week` prompt asks the agent to inspect Todos and seek approval before changes. `todo_list` keeps `ui://doro/todos` for MCP Apps and `ui://doro/todos.html` for the OpenAI bridge. Hosts without iframe support keep the JSON result. See [MCP widgets](./mcp-widgets.md).

## Read a document or plan from it [#read-a-document-or-plan-from-it]

Upload the source in Files and wait for its conversion status. Conversion runs in a browser Web Worker. `files_list` returns `fileId` and a `document` object with `status`, `failureReason`, `awaitingBrowser`, `chunkCount` and `markdownBytes`. When `awaitingBrowser` is true, open Files to complete conversion before requesting text. The document object is null when converted text is unavailable, including unsupported file types. Older files without an upload record also have a null `fileId`.

Call `document_read` with `{ "fileId": "<UUID>" }`. The result contains conversion status, Markdown chunks and `nextCursor`. The default is two chunks per page; `limit` accepts one through four, for at most 32 KiB of Markdown. Pass `nextCursor` as the next call's `cursor` until it is null, or stop when you have read the relevant sections. Search results use `document:<fileId>:<chunkIndex>` IDs so `fetch` can start at the matching section. `fetch` also accepts `cursor` and `limit`. No tool returns the original file bytes or automatically loads all converted text.

The resource template `template://organization/documents/{fileId}` returns the same first JSON page. Continue through `document_read`. Failed conversions return their permanent reason, such as `needsOcr`, `encrypted` or `too_large`; the stored original remains available in Files.

Use the `plan-from-document` MCP prompt with a `fileId` and optional `goal`, or ask the in-app assistant to plan from the file. The agent reads relevant pages, then calls `draft_task_plan`. Approving that tool creates an inert proposal. Review and Apply the proposal before any live Todos are created. Treat source content as evidence, not instructions to change permissions or bypass approval.

The in-app assistant sends the pages it reads to your configured model provider and retains the tool results in its Thread. A connected MCP client receives pages through its authorized grant. Document conversion itself uses no hosted model API. See [ADR-0008](./adr/0008-documents-and-large-uploads.md) for conversion caps and storage.

## Enable the in-app assistant [#enable-the-in-app-assistant]

MCP works without an AI provider. The in-app assistant is off until both provider configuration and the `ai.assistant` flag are enabled.

1. Apply the journaled migration with `pnpm --filter @doro/db db:apply-pending` against the intended database. For tests, use only `node scripts/test-local-postgres.mjs`.
2. Set `AI_PROVIDER` in the server environment and add its matching API key. Local development reads `apps/web/.env`; deployed keys belong in Worker secrets.
3. Optionally set `AI_MODEL` to override the default below. Model availability depends on the provider account.
4. As an administrator, enable `ai.assistant` for web in `/admin/flags`. The sidebar entry and `/assistant` become available after configuration and the flag are both present.

| `AI_PROVIDER` | Worker secret       | Default `AI_MODEL`  |
| ------------- | ------------------- | ------------------- |
| `anthropic`   | `ANTHROPIC_API_KEY` | `claude-sonnet-5-5` |
| `openai`      | `OPENAI_API_KEY`    | `gpt-6-astra`       |
| `gemini`      | `GEMINI_API_KEY`    | `gemini-4-argon`    |

For example, add the Anthropic secret interactively with `pnpm --filter web exec wrangler secret put ANTHROPIC_API_KEY`. Set `AI_PROVIDER=anthropic` in the Worker's environment configuration. Leave `AI_PROVIDER` absent to keep model access off; setting only the flag does not enable it. API keys are never returned to the browser.

| Limit                                  | Default |    Allowed range |
| -------------------------------------- | ------: | ---------------: |
| `AI_MAX_STEPS`                         |       5 |          1 to 10 |
| `AI_MAX_OUTPUT_TOKENS`, per model call |   2,048 |     128 to 8,192 |
| `AI_TIMEOUT_MS`, abort deadline        |  60,000 | 1,000 to 120,000 |
| `AI_REQUESTS_PER_MINUTE`, per User     |      10 |          1 to 60 |

Threads are private to their User and Organization. Messages and tool results stay in Postgres until the Thread, User or Organization is deleted. Deleting history does not undo approved changes. Stop and disconnect end the current generation; reopening loads saved snapshots and pending questions or approvals. There is no background run or resumable stream after disconnection. Provider retention is separate.

The fake adapter is test-only: it requires a build made with `--mode ai-test` and runtime `AI_TEST_PROVIDER=true`. A normal production build cannot select it. Test runs still configure a provider with a dummy local key and enable the flag, then exercise the same authorization, SSE and persistence paths against disposable Postgres. Never deploy an `ai-test` build.

In that test build, `Read document <UUID>` exercises `document_read`. `Plan from document <UUID>` reads a page, requests approval for `draft_task_plan`, and returns the proposal for the separate Apply step.

## Public documents for agents [#public-documents-for-agents]

`/llms.txt` indexes public content and connection endpoints; `/llms-full.txt` includes landing text, legal text, static demo content and this guide. `/index.md`, `/privacy.md` and `/terms.md` use the same content as their HTML pages and support `?lang=en`, `?lang=de` and `?lang=fr`.

The sitemap contains public pages and documents. Robots excludes authenticated routes, and public page metadata links to Markdown alternates. None of these documents contains private messages or Organization data. OAuth discovery uses the existing well-known endpoints; no experimental MCP server card is advertised. Architectural decisions and retention limits are recorded in [ADR-0007](./adr/0007-ai-assistant-and-tool-catalog.md).

