Skip to content
Docs

Command palette

Jump to a page or view, or find a todo by name.

Changelog

Connect your agent

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

Markdown

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

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.

WhatPath
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

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.

ScopeAccess
openidIdentity for the grant
profileStandard OAuth profile scope; tool calls still require data:read
offline_accessRefresh tokens for clients that need persistent connections
data:readRead tools; required by the MCP route
data:writeRequest 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:

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.

Grok Build can use the same HTTP endpoint:

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. 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 covers deployment verification.

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.

ToolBehavior
todo_listList or search paginated Organization Todos; advertises the existing todo widget
todo_detailRead one authorized Todo
files_listList Organization file metadata, confirmed file IDs and conversion status
whoamiRead the signed-in User's name and email
searchSearch Todo text, file names and converted document chunks
fetchRead todo:<id>, file:<key> or document:<fileId>:<chunkIndex>
document_readRead converted Markdown by fileId, with bounded pages and a continuation cursor
organization_infoRead the authorized Organization and member names/roles
ask_for_inputAsk for a choice in the assistant; standalone MCP calls return no answer
todo_createCreate a Todo after approval
todo_completeMark a Todo done after approval
import_startStart an import from a confirmed Organization JSON file after approval
create_todo_draft, draft_task_plan, propose_reviewCreate 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.

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 for conversion caps and storage.

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_PROVIDERWorker secretDefault AI_MODEL
anthropicANTHROPIC_API_KEYclaude-sonnet-5-5
openaiOPENAI_API_KEYgpt-6-astra
geminiGEMINI_API_KEYgemini-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.

LimitDefaultAllowed range
AI_MAX_STEPS51 to 10
AI_MAX_OUTPUT_TOKENS, per model call2,048128 to 8,192
AI_TIMEOUT_MS, abort deadline60,0001,000 to 120,000
AI_REQUESTS_PER_MINUTE, per User101 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

/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.

Edit on GitHubDocumentation is in English.