{
  "markdown": "# TellDone MCP Server\n\nConnect your [TellDone](https://telldone.app) voice notes, tasks, events, and reports to **Claude** (Desktop, claude.ai, and Code), Cursor, Windsurf, Codex, and any MCP-compatible client — with **one-click OAuth** (browser sign-in, no token to copy) or a bearer token.\n\nTellDone is a voice-first planning app. Dictate your thoughts, and AI automatically creates structured notes, tasks, events, and daily productivity reports.\n\nVoice recording is available on **iOS** and **Apple Watch**. Android coming soon. You can also send text through MCP using `process_note` for the same AI analysis pipeline.\n\n> Use promo code **`MCPBETA26`** after signup to get free MCP access (read & write for 30 days, then read-only for a year).\n\n## Quick Start\n\nConnect in one of two ways: **one-click OAuth** (recommended — browser sign-in, nothing to copy) or a **bearer token** (works with every MCP client).\n\n### Option A — One-click OAuth · Claude Desktop, claude.ai, Claude Code\n\nAdd TellDone as a connector, sign in to your account in the browser, and approve. Access tokens are short-lived, scope-limited, and revocable any time in Settings — there's nothing to copy or store.\n\n**Claude Desktop & claude.ai** — in **Settings**, open **Connectors**, choose **Add custom connector**, and paste:\n\n```\nhttps://api.telldone.app/mcp/user\n```\n\nClick **Connect**, sign in to TellDone, and approve the access. No manual app registration — TellDone supports Client ID Metadata Documents, so Claude registers itself automatically.\n\n**Claude Code**\n```bash\nclaude mcp add --transport http telldone https://api.telldone.app/mcp/user\n```\nThen run `/mcp` and choose **telldone → Authenticate** to open the TellDone consent page in your browser — sign in and approve once. (CLI v2.1.186+: `claude mcp login telldone` does the same from the shell.)\n\n> Any MCP client that supports OAuth 2.1 discovery (RFC 9728 + RFC 8414) connects the same way.\n\n### Option B — Bearer token · Cursor, Windsurf, Codex, or any client\n\nFirst get a token: sign up at [app.telldone.app](https://app.telldone.app), open **Settings → AI Agents (MCP)**, and click **Enable**. Then add the server with your token:\n\n**Claude Code**\n```bash\nclaude mcp add telldone --transport http \\\n  https://api.telldone.app/mcp/user/mcp \\\n  --header \"Authorization: Bearer YOUR_TOKEN\"\n```\n\n**Cursor** `.cursor/mcp.json`\n```json\n{\n  \"mcpServers\": {\n    \"telldone\": {\n      \"url\": \"https://api.telldone.app/mcp/user/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer YOUR_TOKEN\" }\n    }\n  }\n}\n```\n\n**Windsurf** `.codeium/windsurf/mcp_config.json`\n```json\n{\n  \"mcpServers\": {\n    \"telldone\": {\n      \"serverUrl\": \"https://api.telldone.app/mcp/user/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer YOUR_TOKEN\" }\n    }\n  }\n}\n```\n\n**Codex** `codex.json`\n```json\n{\n  \"mcpServers\": {\n    \"telldone\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.telldone.app/mcp/user/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer YOUR_TOKEN\" }\n    }\n  }\n}\n```\n\n**OpenClaw**\nSettings > MCP Servers > Add > Name: `TellDone`, URL: `https://api.telldone.app/mcp/user/mcp`, Auth: `Bearer YOUR_TOKEN`\n\n### Start Using\n\nAsk your AI tool things like:\n\n- *\"What did I work on today?\"*\n- *\"Create a task: review quarterly report, high priority, deadline Friday\"*\n- *\"Find all notes about the marketing strategy\"*\n- *\"Mark the Figma task as done\"*\n- *\"Create an event: team standup tomorrow at 10am, remind me 15 min before\"*\n- *\"Process this meeting summary and extract tasks\"*\n- *\"What events do I have next week?\"*\n- *\"Show me my daily report from yesterday\"*\n\n## Data Formats — Read This Before Parsing Output\n\nEvery tool returns JSON. The MCP wire response wraps payloads in `result.content[0].text` as a **JSON-encoded string** — parse it with `json.loads()` (or equivalent) to get the actual data.\n\n**All datetimes, dates, and UUIDs in the decoded JSON are STRINGS, not native language types.** Do not call `.toordinal()`, `.weekday()`, or any datetime method directly on them — you will get `TypeError: 'str' has no attribute 'toordinal'`. Parse them first.\n\n### Scalar output types\n\n| Field shape | Wire format | Example | Parse with |\n|-------------|-------------|---------|------------|\n| UUID | string (lowercase hex with dashes) | `\"b3f3c8a0-9a4d-4e12-9f4a-1a1b2c3d4e5f\"` | use as-is |\n| Datetime (timestamp) | ISO 8601 string with timezone offset | `\"2026-04-18T11:30:00+00:00\"` | `datetime.fromisoformat(s)` in Python; `new Date(s)` in JS |\n| Date (calendar day, no time) | `YYYY-MM-DD` string | `\"2026-04-18\"` | `date.fromisoformat(s)` in Python |\n| Boolean | `true` / `false` | `true` | native |\n| Integer | JSON number | `42` | native |\n| Nullable field | JSON `null` | `null` | `None` / `null` |\n\n### Array / object output types\n\n| Field | Wire format |\n|-------|-------------|\n| `tags` (on notes/tasks/events) | array of strings, OR `null` if never set, OR `[]` if cleared |\n| `reminder_minutes` (events, writable field) | array of ints on input/output |\n| `attendees` (events, writable field) | array of strings (names/emails) |\n| `metadata` (notes) | JSON object or `null` |\n| `tasks` / `events` arrays inside `get_note` / `get_notes_full` | always present, possibly empty `[]` |\n\n### Enum values\n\n- `priority` — `\"low\"`, `\"medium\"`, `\"high\"`, or `null`\n- `note.type` — `\"task\"`, `\"idea\"`, `\"info\"`, `\"status\"`, `\"meeting\"`, `\"event\"`, `\"reflection\"`\n- `note.status` — `\"active\"`, `\"archived\"` (deleted records are excluded from every read tool)\n- `task.status` — `\"todo\"`, `\"done\"` (query param `status=\"all\"` means \"all not-deleted\")\n- `event.status` — `\"confirmed\"`, `\"tentative\"`, `\"cancelled\"`\n- `report.type` — `\"daily\"`, `\"weekly\"`, `\"monthly\"`, `\"yearly\"`\n- `source` (tasks), `completed_by` (tasks) — free-form strings: `\"mcp\"`, `\"app\"`, `\"sync\"`, `\"audio\"`, `\"todoist\"`, `\"notion\"`, etc.\n\n### Per-tool output fields\n\n| Tool | Returned fields (all at top level of each array item unless noted) |\n|------|------|\n| `get_profile` | `id` UUID, `email` str, `display_name` str\\|null, `locale` str, `transcription_locale` str\\|null, `timezone` str (IANA), `subscription` str, `mcp_mode` str, `created_at` ISO 8601 datetime str, `stats` {notes:int, tasks:int, events:int} |\n| `get_notes` | `id` UUID, `title` str, `summary` str\\|null, `type` enum, `tags` str[]\\|null, `priority` enum\\|null, `status` enum, `recorded_at` ISO 8601 datetime str\\|null, `created_at` ISO 8601 datetime str |\n| `get_note` | note fields (`id`, `title`, `summary`, `transcript` str\\|null, `type`, `tags`, `priority`, `status`, `metadata` obj\\|null, `created_at`) + `tasks[]` + `events[]` arrays with subset fields |\n| `get_notes_full` | array of notes with `tasks[]` and `events[]` embedded (same subset as `get_note`, minus `metadata`) |\n| `get_tasks` | `id` UUID, `title` str, `description` str\\|null, `status` enum, `priority` enum\\|null, `tags` str[]\\|null, `deadline` YYYY-MM-DD str\\|null, `reminder_at` ISO 8601 datetime str\\|null, `completed_at` ISO 8601 datetime str\\|null, `completed_by` str\\|null, `source` str\\|null, `created_at` ISO 8601 datetime str |\n| `get_events` | `id` UUID, `title` str, `description` str\\|null, `status` enum, `start_at` ISO 8601 datetime str (non-null), `end_at` ISO 8601 datetime str (non-null), `location` str\\|null, `is_all_day` bool\\|null, `tags` str[]\\|null, `created_at` ISO 8601 datetime str. **Note:** `attendees`, `reminder_minutes`, `recurrence_rule` are writable via `create_event`/`update_event` but are NOT returned by `get_events`. |\n| `get_reports` | `id` UUID, `type` enum, `period_start` YYYY-MM-DD str, `period_end` YYYY-MM-DD str, `content_md` str\\|null, `created_at` ISO 8601 datetime str |\n| `get_tags` | `tag` str, `usage_count` int, `is_pinned` bool, `is_manual` bool |\n| `search` | `{notes: [...], tasks: [...], events: [...]}` — each item is `{id UUID, type \"note\"/\"task\"/\"event\", title str, detail str\\|null, created_at ISO 8601 datetime str}`. All three arrays always present, possibly empty. |\n| Write tools | minimal: `{id UUID, title str, status enum}` (plus `type` on notes). Do **not** expect full records — call the matching `get_*` tool if you need more fields. |\n| Delete tools | `{id UUID, deleted: true}` |\n| `process_note` | `{audio_id UUID, status \"processing\", mode \"audio+stt\"/\"text-only\", message str}` — async; final result arrives via WebSocket `note_ready` or a later `get_notes` call. |\n| Any tool on error | `{error: \"message\"}` — always null-check for the `error` key before treating the response as a record. |\n\n### Input parameter formats\n\n- `note_id`, `task_id`, `event_id`, `parent_*_id` — UUID strings\n- `date_from`, `date_to`, `deadline` — `YYYY-MM-DD` strings (empty string means \"no filter\")\n- `start_at`, `end_at`, `reminder_at` — ISO 8601 datetime strings, e.g. `\"2026-04-15T09:00:00Z\"` or `\"2026-04-15T09:00:00+00:00\"`\n- `tags` — comma-separated string on input (e.g. `\"work,urgent\"`); stored/returned as `string[]`\n- `reminder_minutes`, `attendees` — comma-separated strings on input; stored/returned as arrays\n- `is_all_day` — boolean on `create_event`; string `\"true\"`/`\"false\"` on `update_event`\n- `recurrence_rule` — RRULE string, e.g. `\"FREQ=WEEKLY;BYDAY=MO,WE,FR\"`\n\n### Parsing example (Python)\n\n```python\nimport json\nfrom datetime import datetime, date\n\n# tools/call response → text → decode\npayload = json.loads(response[\"result\"][\"content\"][0][\"text\"])\n\n# Check for error first\nif \"error\" in payload:\n    raise RuntimeError(payload[\"error\"])\n\n# Events: start_at is a STRING like \"2026-04-18T11:30:00+00:00\"\nfor e in payload:   # payload is list from get_events\n    start = datetime.fromisoformat(e[\"start_at\"])   # -> tz-aware datetime\n    if e[\"end_at\"]:\n        end = datetime.fromisoformat(e[\"end_at\"])\n\n# Tasks: deadline is a STRING like \"2026-04-18\" (date only)\nfor t in tasks_payload:\n    if t[\"deadline\"]:\n        d = date.fromisoformat(t[\"deadline\"])\n        days_left = (d - date.today()).days\n```\n\n## Note Fields: title, summary, transcript\n\nEvery note has three text fields with **different roles and different limits**. Choosing the right field matters — LLM clients (Claude Desktop, Cursor, etc.) should split content appropriately when using `create_note` or `update_note`.\n\n| Field | Role | Limit | Included in report LLM prompts? |\n|-------|------|-------|----------------------------------|\n| `title` | One-line subject shown in lists, previews, push, email subjects. | 200 chars | ✅ (as list header) |\n| `summary` | 1–3 sentence **teaser**. | **Hard cap 1000 chars** — product decision. | ✅ **Verbatim.** Keep it concise. |\n| `transcript` | Full note **body**. Shown in detail view. | **Plan-based** (see below) | ❌ Never. Safe to be long. |\n\n**Plan-based transcript limits** (`subscription_plans.max_text_length`):\n\n| Plan | Max transcript chars |\n|------|----------------------|\n| Free | 2 000 |\n| Basic | 8 000 |\n| **Pro** | **20 000** |\n| **Ultra** | **50 000** |\n| Custom | 100 000 |\n\n**Rule of thumb for LLM clients:**\n\n- **Short output (notes, reminders, todos):** just `title` + `summary`. Leave `transcript` empty.\n- **Long output (meeting notes, drafts, brainstorms, research dumps):** put a 1–3 sentence `summary` and the **full body in `transcript`**. Do not pack everything into summary — you'll hit the 1000-char error.\n\n**Overflow error messages:**\n\n- `summary too long (max 1000 chars, got N). For long-form content use the 'transcript' parameter (plan-based limit).`\n- `transcript too long (max 20000 chars for pro plan, got N)`\n\n**Correct usage example** (Claude Desktop captures a meeting):\n```jsonc\ncreate_note({\n  title: \"Weekly engineering sync — API versioning\",\n  summary: \"Team agreed on semver deprecation policy with 6-month sunset window. Owner: Alex. Next sync: Thu.\",\n  transcript: \"Full meeting transcript: Alice raised the question of how to deprecate v1...\\n\\n[... 5 KB of detail ...]\",\n  tags: \"engineering,versioning,meeting\"\n})\n```\n\n**Why two different caps?**\n\n- `summary` is included **verbatim** in daily/weekly/monthly report LLM prompts. If every summary could be 20 KB, report prompts would blow up in cost and latency (and risk context-window overflow for heavy users). 1000 chars was set in April 2026 after measuring prod data (median 247, max 554).\n- `transcript` is **never** in report prompts — it only shows up in the UI detail view. Large transcripts cost only storage, not LLM tokens. Capping by plan prevents abuse but otherwise lets you be generous.\n\n**Backward compatibility:** `transcript` is an optional parameter (default `\"\"`). Old clients calling `create_note(title, summary, tags, type)` continue to work unchanged — the database stores `transcript = NULL`. No migration needed.\n\n---\n\n## Tools (20)\n\nAll tools include [MCP annotations](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#tool-annotations) — `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` — so MCP clients can surface the right confirmation UI. Every tool runs against the Telldone database only (`openWorldHint: false`) — the server never reaches out to external APIs.\n\n### Read Tools (9)\n\n| Tool | Description |\n|------|-------------|\n| `get_profile` | Returns the authenticated user's profile including display name, email, locale, timezone, subscription plan, and usage statistics (total notes, tasks, events). Use this to check account status or quota. |\n| `get_notes` | Lists voice notes with optional filters: `date_from`/`date_to` (ISO 8601), `tag` (string), `type` (task/idea/info/status/meeting/event/reflection), `search` (text query), `limit` (default 20, max 100), `offset`. Returns note metadata without full body — use `get_note` for details. |\n| `get_note` | Returns a single note by UUID `note_id`, including full transcription, AI summary, and all linked child tasks and events. Use when you need complete note content after finding it via `get_notes` or `search`. |\n| `get_notes_full` | Bulk retrieval of notes with embedded children (tasks + events). Same filters as `get_notes`. Use instead of calling `get_note` in a loop. Returns larger payloads — set `limit` appropriately. |\n| `get_tasks` | Lists tasks with filters: `status` (todo/done/all, default: todo), `tag`, `priority` (low/medium/high), `date_from`/`date_to` for deadline range, `limit`, `offset`. Returns title, priority, deadline, reminder_at, tags, completion status. |\n| `get_events` | Lists calendar events with `date_from`/`date_to` range filters, `status` (confirmed/tentative/cancelled), `limit`, `offset`. Returns event title, start/end times, location, attendees, and reminders. |\n| `get_reports` | Returns AI-generated productivity reports. Filter by `period` (daily/weekly/monthly/yearly) and `date_from`/`date_to`. Reports summarize completed tasks, patterns, and productivity insights. |\n| `get_tags` | Returns all user-defined tags sorted by usage frequency (most used first). No parameters. Use to discover available tags before filtering notes or tasks. |\n| `search` | Hybrid text + semantic search across notes, tasks, and events. Parameter: `query` (string). Combines keyword matching with vector similarity for relevant results even with different wording. Returns mixed result types with relevance scores. |\n\n### Write Tools (11)\n\n| Tool | Description |\n|------|-------------|\n| `process_note` | Runs the full AI analysis pipeline on text or audio input — identical to recording a voice note in the mobile app. Accepts `text` (string) or `audio_base64` + `audio_format` (m4a/wav/mp3). AI extracts structured note + tasks + events + tags. Returns immediately with `audio_id`; results arrive asynchronously. Poll with `get_notes()` to retrieve processed output. |\n| `create_note` | Creates a plain text note without AI analysis. Parameters: `title` (required, max 200), `summary` (max 1000 chars, concise teaser — included in report LLM prompts), `transcript` (plan-based limit, long-form body — NOT in report prompts), `type` (task/idea/info/status/meeting/event/reflection), `tags` (comma-separated, max 20). See **Note Fields** section for where to put long text. |\n| `create_task` | Creates a task with `title` (required), `description`, `priority` (low/medium/high, default: medium), `deadline` (ISO 8601 date), `reminder_at` (ISO 8601 datetime), `tags` (comma-separated). Task is created with status \"todo\". Syncs to mobile app in real-time. |\n| `create_event` | Creates a calendar event with `title` (required), `start_at`/`end_at` (ISO 8601 datetime), `location`, `attendees` (comma-separated), `reminder_minutes` (integer), `recurrence` (rrule string), `status` (confirmed/tentative). |\n| `update_note` | Updates note fields by `note_id` (UUID, required). Optional: `title`, `summary` (max 1000), `transcript` (plan-based limit, long-form body), `type`, `tags`, `priority`, `status`. Only provided fields are changed; omitted fields remain unchanged. Pass `\" \"` (single space) for `summary` or `transcript` to clear the field. |\n| `update_task` | Updates task fields by `task_id` (UUID, required). Optional: `title`, `description`, `priority`, `deadline`, `reminder_at`, `tags`, `status` (todo/done). Use `complete_task` as a shortcut for marking done. |\n| `update_event` | Updates event fields by `event_id` (UUID, required). Optional: `title`, `start_at`, `end_at`, `location`, `attendees`, `status` (confirmed/tentative/cancelled), `reminder_minutes`. |\n| `complete_task` | Marks a task as done by `task_id` (UUID). Shortcut for `update_task` with `status: \"done\"`. Records completion timestamp and source (\"mcp\"). |\n| `delete_note` | Soft-deletes a note by `note_id` (UUID). Cascades to all linked child tasks and events — they are also soft-deleted. Reversible from the web app. |\n| `delete_task` | Soft-deletes a task by `task_id` (UUID). Does not affect the parent note. Reversible from the web app. |\n| `delete_event` | Soft-deletes an event by `event_id` (UUID). Does not affect the parent note. Reversible from the web app. |\n\nAll write tools sync in real-time to connected mobile and web clients via WebSocket.\n\n## Full Pipeline: `process_note`\n\nThe `process_note` tool runs the same pipeline as recording in the mobile app:\n\n```\nText or Audio --> STT (if audio) --> LLM Analysis --> Note + Tasks + Events + Tags\n```\n\n**Text mode** (skip STT):\n```json\n{\"name\": \"process_note\", \"arguments\": {\"text\": \"Need to buy groceries. Meeting with Katie at 3pm.\"}}\n```\n\n**Audio mode** (base64-encoded):\n```json\n{\"name\": \"process_note\", \"arguments\": {\"audio_base64\": \"...\", \"audio_format\": \"m4a\"}}\n```\n\nReturns immediately with `audio_id`. Results arrive via WebSocket or poll with `get_notes()`.\n\n## Examples\n\n### `examples/test-connection.sh`\n\n```bash\n#!/bin/bash\n# Test your TellDone MCP connection\nTOKEN=\"${1:?Usage: ./test-connection.sh YOUR_TOKEN}\"\nURL=\"https://api.telldone.app/mcp/user/mcp\"\n\necho \"=== Testing connection ===\"\ncurl -s -X POST \"$URL\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"get_profile\"}}' \\\n  | python3 -m json.tool\n\necho \"\"\necho \"=== Listing tools ===\"\ncurl -s -X POST \"$URL\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\"}' \\\n  | python3 -c \"import sys,json; tools=json.load(sys.stdin).get('result',{}).get('tools',[]); print(f'{len(tools)} tools available'); [print(f'  {t[\\\"name\\\"]}') for t in tools]\"\n```\n\n### `examples/daily-summary.sh`\n\n```bash\n#!/bin/bash\n# Get today's tasks and notes summary\nTOKEN=\"${1:?Usage: ./daily-summary.sh YOUR_TOKEN}\"\nURL=\"https://api.telldone.app/mcp/user/mcp\"\nTODAY=$(date +%Y-%m-%d)\n\ncall() {\n  curl -s -X POST \"$URL\" \\\n    -H \"Authorization: Bearer $TOKEN\" \\\n    -H \"Content-Type: application/json\" \\\n    -H \"Accept: application/json\" \\\n    -d \"$1\"\n}\n\necho \"=== Today's Notes ($TODAY) ===\"\ncall \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"tools/call\\\",\\\"params\\\":{\\\"name\\\":\\\"get_notes\\\",\\\"arguments\\\":{\\\"date_from\\\":\\\"$TODAY\\\",\\\"limit\\\":20}}}\" \\\n  | python3 -c \"\nimport sys, json\nr = json.loads(json.load(sys.stdin)['result']['content'][0]['text'])\nfor n in r: print(f'  [{n[\\\"type\\\"]}] {n[\\\"title\\\"]}')\" 2>/dev/null\n\necho \"\"\necho \"=== Active Tasks ===\"\ncall '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"get_tasks\",\"arguments\":{\"status\":\"todo\",\"limit\":10}}}' \\\n  | python3 -c \"\nimport sys, json\nr = json.loads(json.load(sys.stdin)['result']['content'][0]['text'])\nfor t in r: print(f'  [{t[\\\"priority\\\"]}] {t[\\\"title\\\"]}')\" 2>/dev/null\n\necho \"\"\necho \"=== Upcoming Events ===\"\ncall \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":3,\\\"method\\\":\\\"tools/call\\\",\\\"params\\\":{\\\"name\\\":\\\"get_events\\\",\\\"arguments\\\":{\\\"date_from\\\":\\\"$TODAY\\\",\\\"limit\\\":5}}}\" \\\n  | python3 -c \"\nimport sys, json\nr = json.loads(json.load(sys.stdin)['result']['content'][0]['text'])\n# NOTE: start_at is a STRING like '2026-04-18T11:30:00+00:00' — parse before date math\nfor e in r: print(f'  {e[\\\"start_at\\\"][:16]} {e[\\\"title\\\"]}')\" 2>/dev/null\n```\n\n### `examples/create-task.sh`\n\n```bash\n#!/bin/bash\n# Create a task via MCP\nTOKEN=\"${1:?Usage: ./create-task.sh YOUR_TOKEN 'Task title'}\"\nTITLE=\"${2:?Usage: ./create-task.sh YOUR_TOKEN 'Task title'}\"\nPRIORITY=\"${3:-medium}\"\n\ncurl -s -X POST \"https://api.telldone.app/mcp/user/mcp\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json\" \\\n  -d \"{\\\"jsonrpc\\\":\\\"2.0\\\",\\\"id\\\":1,\\\"method\\\":\\\"tools/call\\\",\\\"params\\\":{\\\"name\\\":\\\"create_task\\\",\\\"arguments\\\":{\\\"title\\\":\\\"$TITLE\\\",\\\"priority\\\":\\\"$PRIORITY\\\"}}}\" \\\n  | python3 -m json.tool\n```\n\n## Plans and Access\n\n| Plan | MCP Access | Read | Write | Price |\n|------|-----------|------|-------|-------|\n| Free | -- | -- | -- | $0 |\n| Basic | -- | -- | -- | $4.99/mo |\n| **Pro** | **Read & Write** | **9 tools** | **11 tools** | **$11.99/mo** |\n| **Ultra** | **Read & Write** | **9 tools** | **11 tools** | **$24.99/mo** |\n\nPro and Ultra have the same MCP tools. Ultra has higher quotas (unlimited notes, 1500 STT min/mo, 300 uploads/day).\n\n## Authentication\n\nTellDone supports two ways to connect — pick one.\n\n### OAuth 2.1 (recommended)\n\nOne-click browser sign-in, nothing to copy or store. Standards-compliant: OAuth 2.1 with PKCE (S256), authorization-server + protected-resource discovery (RFC 8414 / RFC 9728), audience-bound access tokens (RFC 8707), rotating refresh tokens, and Client ID Metadata Documents (so Claude Desktop / claude.ai connect with no manual registration). Access is **scoped** — you approve exactly what the app may do on the consent screen, and read-only vs read & write follows your plan. Revoke any time in **Settings → AI Agents** or by disabling MCP.\n\n- **Connector URL**: `https://api.telldone.app/mcp/user`\n- **Discovery**: `https://api.telldone.app/.well-known/oauth-protected-resource`\n- **Scopes**: `notes:read` · `notes:write` · `tasks:read` · `tasks:write` · `events:read` · `events:write` · `reports:read` · `tags:read` · `tags:write` · `profile:read` · `offline_access`\n\n### Bearer token\n\nFor clients without OAuth. Generate a long-lived token in the web app — **Settings → AI Agents (MCP) → Enable** — and send it as `Authorization: Bearer <token>`.\n\n- **Endpoint URL**: `https://api.telldone.app/mcp/user/mcp`\n- **Regenerate**: Settings → AI Agents → Regenerate (old token revoked instantly)\n- **Disable**: Settings → AI Agents → Disable (token deleted)\n\n**Rate limit (both):** 5 requests/second.\n\n## Transport\n\nMCP Streamable HTTP (stateless). Each request is independent.\n\n```\nPOST https://api.telldone.app/mcp/user/mcp\nAuthorization: Bearer <token>\nContent-Type: application/json\nAccept: application/json\n```\n\n## Links\n\n- **App**: [app.telldone.app](https://app.telldone.app)\n- **Website**: [telldone.app](https://telldone.app)\n- **Docs**: [docs.telldone.app](https://docs.telldone.app)\n- **iOS App**: [App Store](https://apps.apple.com/app/telldone/id6742044622)\n",
  "bytes": 23993,
  "sha": "cf5674661df028c63e0a78683b8f9180170b6c36213089d734212e760b77d6ac",
  "repo_slug": "exp78/telldone-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_exp78_telldone_e6241680/readme"
}