{"generated_at":"2026-10-20T00:32:25Z","source":"Mcp::ToolDefinitions::TOOLS in this running application version","endpoint":"https://hjarni.com/mcp","exposure":"Tool availability follows the connected account's features and access. Agent tokens expose only supported tools and reach the folders they were granted.","tools":[{"name":"me","description":"Get the connected user's profile, onboarding state, team memberships, and note quota in a single call. Call this once at the start of a conversation so you can greet the user by first name, run the onboarding script only when needed, route notes to the right team space, and avoid suggesting features their account does not include. Returns onboarding.completed (boolean) and onboarding.missing_steps (array of 'connect_mcp' | 'first_note'), which together tell you what, if any, setup is left. May include a `nudge` (key, message, url) — one frequency-capped suggestion; see the NUDGES section of the server instructions for how to handle it. Exposes the user's email address — same data the user sees in account settings, but never billing or token metadata. No parameters required.","annotations":{"title":"Get current user","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"No parameters required.","properties":{},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"dashboard-get","description":"Get an overview of the user's Hjarni account: counts of notes, containers, tags, and inbox items, plus recent_notes (the 5 most recently created personal notes) and recent_changes (the 5 most recently edited notes across ALL spaces — personal, teams, and shared containers — newest edit first). Use recent_changes to orient at the start of a conversation on what changed lately everywhere. If everything is empty because the user hasn't saved anything yet, do not just report that: call `me` and follow onboarding.next_action to run their 60-second setup. No parameters required.","annotations":{"title":"Get Dashboard","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"No parameters required.","properties":{},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"search","description":"Search across notes, containers, and tags in one call — the retrieval tool for ANYTHING the user has saved in their second brain: preferences, workflows, routines, projects, decisions, meeting notes, research, people, how-tos, code snippets, references, journal entries, and any other saved knowledge or memories. When a task needs something the user previously stored, this is the tool that finds it. Returns results grouped by type with pagination metadata (total_count, page, per_page, total_pages). Required: query (string). Optional: types (array, default all three), search_scope ('all'|'personal'|'team:<id>'), scope ('active'|'archived'), container_id (integer, ignored when search_scope is 'all'), tags (array, AND logic), tag_ids (array, AND logic), include_nested (boolean), include_body (boolean, default false — when true each note includes its full body), include_instructions (boolean, defaults to include_body — when true each note carries inherited_instructions, the same user-written instruction chain notes-get returns, so a full-body search hit arrives with the rules that govern it and needs no follow-up notes-get; pass false to omit the chain from a body-only page), created_after / created_before / updated_after / updated_before (ISO 8601 datetime filters on note timestamps), page (integer, default 1), per_page (integer, default 25, max 100). Note results include a snippet of the matching portion. If results are empty because the user hasn't saved anything yet, do not just report that: call `me` and follow onboarding.next_action to run their 60-second setup.","annotations":{"title":"Search","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Unified search across notes, containers, and tags.","properties":{"query":{"type":"string","description":"Search query string (required)"},"types":{"type":"array","items":{"type":"string","enum":["notes","containers","tags"]},"description":"Which types to search. Defaults to all three: ['notes', 'containers', 'tags']"},"search_scope":{"type":"string","description":"Search scope: 'all' (default, personal + all teams), 'personal' (personal notes only), or 'team:<id>' (specific team). Applies to note results."},"scope":{"type":"string","enum":["active","archived"],"description":"Search active or archived notes (default: active)"},"container_id":{"type":"integer","description":"Filter note results to this container (ignored when search_scope is 'all')"},"tags":{"type":"array","items":{"type":"string"},"description":"Filter note results to notes with ALL these tags (by name)"},"tag_ids":{"type":"array","items":{"type":"integer"},"description":"Filter note results to notes with ALL these tags (by ID)"},"include_nested":{"type":"boolean","description":"Include notes from sub-containers when container_id is set (default: false)"},"include_body":{"type":"boolean","description":"Include the full note body on each note result (default: false)"},"include_instructions":{"type":"boolean","description":"Include inherited_instructions (brain, space root, ancestor and container instruction layers, outermost first) on each note result — the same chain notes-get returns. Defaults to the value of include_body, so full-body results carry their governing instructions unless you pass false."},"created_after":{"type":"string","description":"Filter notes created on or after this ISO 8601 datetime (e.g. '2026-04-01T00:00:00Z')"},"created_before":{"type":"string","description":"Filter notes created before this ISO 8601 datetime"},"updated_after":{"type":"string","description":"Filter notes updated on or after this ISO 8601 datetime"},"updated_before":{"type":"string","description":"Filter notes updated before this ISO 8601 datetime"},"page":{"type":"integer","description":"Page number for note results (default: 1)"},"per_page":{"type":"integer","description":"Results per page for notes, max 100 (default: 25)"}},"required":["query"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"notes-list","description":"List and browse the user's saved notes — their stored knowledge and memories (preferences, workflows, projects, meeting notes, references, and the rest) — with optional filtering, sorting, and pagination. Use search instead when hunting for a topic or keyword; use this to enumerate a folder, tag, or scope. Returns paginated results as a JSON array, followed by a line stating how many notes matched in total and whether more pages remain — read it to know if you have the whole folder before acting on it. Optional: team_id (integer) to list team notes, scope ('active'|'archived'|'inbox'|'favorited'|'trashed'), container_id (integer) with include_nested (boolean), tags (array of strings, AND logic), tag_ids (array of integers, AND logic), summary_stale (boolean, filter to notes with outdated summaries), stale (boolean, filter to notes whose freshness is stale — past their review_after date or unverified for a while), sort ('recent'|'oldest'|'title'), page (integer, default 1), per_page (integer, max 100, default 25), include_body (boolean, default false — include each note's full body, so a scoped/paginated listing can retrieve complete contents without a notes-get call per note; withheld for trashed notes, same as notes-get), include_instructions (boolean, defaults to include_body — include each note's inherited_instructions, the same chain notes-get returns, so a full-body listing also carries the rules governing those notes; withheld for trashed notes). container_id can be combined with team_id to list a specific team container. Example: list ruby-tagged notes in a container: {container_id: 5, tags: ['ruby']}. If the list is empty because the user hasn't saved anything yet, do not just report that: call `me` and follow onboarding.next_action to run their 60-second setup.","annotations":{"title":"List Notes","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"List notes with optional filtering and sorting.","properties":{"team_id":{"type":["integer","null"],"description":"List notes in this team instead of personal notes. Omit it (or send null) for personal notes."},"scope":{"type":"string","enum":["active","archived","inbox","favorited","trashed"],"description":"Filter scope (default: active). 'inbox' and 'favorited' only for personal notes; 'trashed' lists the recoverable Trash — restore an entry with notes-restore."},"container_id":{"type":"integer","description":"Filter by container ID"},"tags":{"type":"array","items":{"type":"string"},"description":"Filter to notes with ALL these tags by name (AND logic). Example: ['ruby', 'rails']"},"tag_ids":{"type":"array","items":{"type":"integer"},"description":"Filter to notes with ALL these tags by ID (AND logic)"},"include_nested":{"type":"boolean","description":"Include notes from sub-containers when container_id is set (default: false)"},"summary_stale":{"type":"boolean","description":"Filter to notes with outdated summaries (default: not filtered)"},"stale":{"type":"boolean","description":"Filter to notes whose freshness is stale — past their review_after date or unverified beyond the freshness window (default: not filtered)"},"sort":{"type":"string","enum":["recent","oldest","title"],"description":"Sort order: 'recent' (updated_at desc, default), 'oldest' (updated_at asc), or 'title' (alphabetical)"},"page":{"type":"integer","description":"Page number (default: 1)"},"per_page":{"type":"integer","description":"Results per page, max 100 (default: 25)"},"include_body":{"type":"boolean","description":"Include each note's full body in the results (default: false)"},"include_instructions":{"type":"boolean","description":"Include inherited_instructions (brain, space root, ancestor and container instruction layers, outermost first) on each note — the same chain notes-get returns. Defaults to the value of include_body, so full-body listings carry their governing instructions unless you pass false."}},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"notes-get","description":"Get a single note by ID, including its full Markdown body, tags, container path, linked notes (every note it links to or is linked from), backlinks (the subset that links to it), file attachments, and inherited_instructions — every user-written LLM instruction layer that applies to the note, outermost first (an inbox note still carries brain plus its space root), so no separate instructions lookup is needed before editing. A missing brain entry means the user has no custom brain instructions; the defaults come from instructions-get with level 'brain'. A note in Trash returns a status: 'trashed' summary (id, title, purge deadline, how to restore) instead of its content — restore it with notes-restore to read or edit it. Required: id (integer).","annotations":{"title":"Get Note","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Get a single note by ID, including its full body content, tags, container, linked notes, backlinks, and file attachments.","properties":{"id":{"type":"integer","description":"Note ID (required)"}},"required":["id"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"notes-create","description":"Create a note in the connected user's personal Hjarni account, an authorized shared folder, or a team workspace they belong to. The server enforces write access. Approaching or reaching a note quota may email an account-limit notification to the account or team owner; sent emails cannot be recalled. Required: title (string). Always include a 2-3 sentence summary too (required by convention, even though the schema only enforces title) so the note is useful to future LLM sessions. Optional: body (Markdown with [[id:Note Title]] wiki-links), summary, source_url, container_id, tag_list (comma-separated), team_id (to create in a team), review_after (ISO 8601 datetime, only set this for time-boxed notes that should flag themselves stale after a date). A new note starts verified (status 'active'). Search for an existing note on the topic first and update that instead when there is one. Example: {title: 'Meeting Notes', body: '## Agenda\\n...', container_id: 5, tag_list: 'meetings, q4'}.","annotations":{"title":"Create Note","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Create a new note.","properties":{"title":{"type":"string","description":"Note title (required)"},"body":{"type":"string","description":"Note body content (Markdown with [[id:Note Title]] wiki-links)"},"summary":{"type":"string","description":"A 2-3 sentence summary of the note. Always provide one."},"source_url":{"type":"string","description":"Source URL reference"},"container_id":{"type":"integer","description":"Container ID to place the note in"},"tag_list":{"type":"string","description":"Comma-separated list of tags (e.g., 'ruby, rails, testing')"},"team_id":{"type":["integer","null"],"description":"Create note in this team instead of personal space. Omit it (or send null) for personal space."},"review_after":{"type":["string","null"],"description":"Optional ISO 8601 datetime after which the note should be treated as stale. Use for time-boxed notes (e.g. '2026-12-31')."}},"required":["title"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"notes-update","description":"Update a note: edit its content, move it to a different folder (set container_id, or null for the inbox), archive/favorite it, or change its tags. This is the tool for moving notes between folders; there is no separate move tool. Required: id (integer). Optional content (exactly one body-mutation mode at a time): title, body (full replace), append_body (appends to existing body), insert_after + insert_body (insert text immediately after a unique anchor snippet from the existing body), insert_before + insert_body (insert before a unique anchor), replace_find (+ optional replace_with) (replace a unique snippet; omit replace_with entirely to delete the snippet). Markdown-structure ops (heading/section/checklist aware — safer than eyeballing a unique snippet on long notes): replace_section + section_body (replace everything UNDER a heading, keeping the heading line); append_to_section + section_body (add content at the END of a section — the safe 'insert under heading' when you don't know its last line); rename_heading + new_heading (rename a heading in place, preserving its level unless new_heading carries its own '#'); check_item / uncheck_item (tick/untick a checklist item by its text, e.g. '- [ ] ship it'). Headings and checklist items must each match exactly one line. Anchor and find snippets must match exactly once; include enough surrounding context to disambiguate. Also optional: summary, source_url. Freshness: verified (boolean — pass true to mark the note re-confirmed as still true right now; only send this after the user has actually confirmed it), review_after (ISO 8601 datetime to time-box the note, or null to clear it). Organization: container_id (move note), archived (boolean — works on your own personal notes and on team notes where you have the editor role; notes in shared containers are owner-only), favorited (boolean). Tags: tag_list (full replace, comma-separated), add_tags, remove_tags. tag_list takes precedence over add_tags/remove_tags. Concurrent edit safety: pass expected_lock_version (the lock_version you saw when you last read the note via notes-get / notes-list / search) whenever you want a stale-write guard. If it doesn't match the current version, the update is rejected with the current state included so you can re-read and re-apply (append_body-only calls are exempt; see expected_lock_version). Surgical edits (append_body / insert_after / insert_before / replace_find) are anchor-based and so don't *need* expected_lock_version for their body change — but if you also change title / summary / container_id alongside, those fields can still silently overwrite a newer save unless you supply expected_lock_version. Examples: insert under a heading {id: 42, append_to_section: 'Open Questions', section_body: '- Should we ship Friday?'}; replace a section {id: 42, replace_section: '## Status', section_body: 'Shipped 🎉'}; rename a heading {id: 42, rename_heading: 'TODO', new_heading: 'Done'}; tick a checklist item {id: 42, check_item: 'ship it'}; fix a typo {id: 42, replace_find: 'recieved', replace_with: 'received'}; safe full rewrite {id: 42, body: '...', expected_lock_version: 5}. Every edit is recorded as a named, revertable revision attributed to you — use notes-history to see who changed what, and notes-revert to undo a change.","annotations":{"title":"Update Note","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Update a note. Supports partial updates — only provided fields are changed. The body-mutation modes (body, append_body, insert_body, replace_find, replace_section, append_to_section, rename_heading, check_item, uncheck_item) are mutually exclusive — supply at most one per call. Supplying two surgical modes together is an error, but `body` is not checked against them: if you send `body` alongside a surgical mode, `body` wins and the surgical edit is ignored rather than rejected, so never send both.","properties":{"id":{"type":"integer","description":"Note ID (required)"},"title":{"type":"string","description":"New title"},"body":{"type":"string","description":"New body content — full replacement. Mutually exclusive with the other body-mutation modes. Pair with expected_lock_version for concurrent-edit safety."},"append_body":{"type":"string","description":"Content to append to the existing body. Mutually exclusive with the other body-mutation modes."},"insert_after":{"type":"string","description":"Anchor snippet from the existing body — insert_body is inserted immediately after the unique occurrence. Anchor must match exactly once; include surrounding context to disambiguate."},"insert_before":{"type":"string","description":"Anchor snippet from the existing body — insert_body is inserted immediately before the unique occurrence. Anchor must match exactly once."},"insert_body":{"type":"string","description":"Text to insert. Must be paired with either insert_after or insert_before. Mutually exclusive with the other body-mutation modes."},"replace_find":{"type":"string","description":"Snippet to find in the existing body. Must match exactly once. Mutually exclusive with the other body-mutation modes."},"replace_with":{"type":"string","description":"Optional replacement for replace_find. Omit it (or pass an empty string) to delete the matched snippet."},"replace_section":{"type":"string","description":"Heading whose section content should be replaced (with or without leading '#', e.g. '## Status' or 'Status'). Replaces everything under the heading up to the next same-or-higher-level heading, keeping the heading line. Pair with section_body. Heading must match exactly one."},"append_to_section":{"type":"string","description":"Heading to append content to (with or without leading '#'). Adds section_body at the END of that section — the safe way to 'insert under a heading'. Pair with section_body. Heading must match exactly one."},"section_body":{"type":"string","description":"The content for replace_section / append_to_section. Required when either is given."},"rename_heading":{"type":"string","description":"Heading to rename (with or without leading '#'). Pair with new_heading. Heading must match exactly one."},"new_heading":{"type":"string","description":"New heading text for rename_heading. The original level is preserved unless this carries its own leading '#' markers."},"check_item":{"type":"string","description":"Text of a checklist item to tick (set to '[x]'). Matched case- and whitespace-insensitively; a leading bullet/checkbox in the text is ignored. Must match exactly one item."},"uncheck_item":{"type":"string","description":"Text of a checklist item to untick (set to '[ ]'). Same matching rules as check_item."},"summary":{"type":"string","description":"New summary"},"source_url":{"type":"string","description":"New source URL"},"verified":{"type":"boolean","description":"Pass true to mark the note re-confirmed as still true as of now (the 'mark verified' affordance). Refreshes verified_at and clears 'stale' status. Only send after the user has confirmed the note is still accurate."},"review_after":{"type":["string","null"],"description":"ISO 8601 datetime after which the note should be treated as stale (time-boxing), or null to clear it."},"container_id":{"type":["integer","null"],"description":"Folder (container) id to move the note into; pass null to move it to the inbox (remove it from its folder)"},"tag_list":{"type":"string","description":"Full replacement comma-separated tag list (takes precedence over add_tags/remove_tags)"},"add_tags":{"type":"string","description":"Comma-separated tags to add to existing tags (ignored if tag_list is provided)"},"remove_tags":{"type":"string","description":"Comma-separated tags to remove from existing tags (ignored if tag_list is provided)"},"archived":{"type":"boolean","description":"Archive (true) or unarchive (false) the note. Works on your own personal notes and on team notes where you have the editor role; notes in shared containers can only be archived by their owner."},"favorited":{"type":"boolean","description":"Favorite (true) or unfavorite (false) the note. Personal and team notes."},"expected_lock_version":{"type":"integer","description":"Optional concurrent-edit guard. Pass the lock_version you saw when you last read the note; if it doesn't match the current version, the update is rejected and you should re-read and re-apply. Checked whenever supplied — covers title, summary, container_id, body, anything else — with one exception: when append_body is the call's ONLY edit, a stale value does not reject (a bare append lands at the end of the current body and can't lose anyone's update). That exemption also means an append is not replay-protected: if you retry an identical append-only call whose response you never saw, the text is appended twice, so re-read with notes-get instead of blind-retrying. Surgical body edits without this param still work and remain anchor-safe; supply it any time you want a stale-write guard for the other fields too."}},"required":["id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"notes-delete","description":"Move a note to Trash within the connected user's personal Hjarni account or an authorized team workspace. This is RECOVERABLE — the note (with its body, attachments, and history) is restorable with notes-restore until its purge date (default 30 days); it is not an immediate permanent erase. Deleting the wrong note can be undone with notes-restore. Works on your own personal notes and on team notes where you have the editor role. You cannot delete notes in a shared container (only the owner can). Required: id (integer).","annotations":{"title":"Delete Note","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Move a note to Trash (recoverable with notes-restore until its purge date).","properties":{"id":{"type":"integer","description":"Note ID (required)"}},"required":["id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"notes-restore","description":"Restore a note that notes-delete moved to Trash within the connected user's personal Hjarni account or an authorized team workspace, bringing back its body, tags, attachments, and history. Only works before the note's purge date. Required: id (integer) — the same note id you deleted.","annotations":{"title":"Restore Note","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Restore a trashed note by its id.","properties":{"id":{"type":"integer","description":"Note ID (required)"}},"required":["id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"notes-history","description":"Read a note's revision history and provenance: who wrote each version (you, the user, or which AI client), when, and what changed. Use it to attribute facts to their source and to see recent edits before making your own. Read-only. Required: id (integer). Optional: limit (default 20, max 100), before_seq (paginate to older revisions), include_body (boolean — reconstruct each version's full text), seq (integer — return only that revision, with its full reconstructed body).","annotations":{"title":"Note History","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Read a note's revision history and provenance.","properties":{"id":{"type":"integer","description":"Note ID (required)"},"limit":{"type":"integer","description":"Max revisions, newest first (default 20, max 100)"},"before_seq":{"type":"integer","description":"Return revisions older than this seq (pagination)"},"include_body":{"type":"boolean","description":"Reconstruct and include each revision's full body (default false)"},"seq":{"type":"integer","description":"Return only this revision, including its reconstructed body"}},"required":["id"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"notes-revert","description":"Revert a note's body to an earlier revision (find the seq via notes-history). This does NOT erase history — it appends a NEW revision whose body equals the chosen earlier one, attributed to you, so the revert can itself be undone. Use only when the user explicitly asks to undo a change. Optional expected_lock_version (from notes-get) rejects the revert if the note changed since you read it, instead of clobbering that change. Required: id (integer), seq (integer).","annotations":{"title":"Revert Note","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Revert a note's body to an earlier revision (appends a new revision; history is preserved).","properties":{"id":{"type":"integer","description":"Note ID (required)"},"seq":{"type":"integer","description":"The revision seq to restore the body to (from notes-history)"},"expected_lock_version":{"type":"integer","description":"Optional concurrent-edit guard; pass the lock_version you last saw via notes-get"}},"required":["id","seq"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"notes-open","title":"Hjarni Notes","icons":[{"src":"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-linecap%3D%22round%22%3E%3Cg%20transform%3D%22translate%281.53%200.12%29%20scale%280.235%29%22%20stroke-width%3D%225.66%22%3E%3Cpath%20d%3D%22M36%208C20%208%208%2022%208%2036C8%2048%2014%2054%2012%2062C10%2070%2016%2076%2028%2076%22%2F%3E%3Cpath%20d%3D%22M36%208C52%208%2064%2022%2064%2036C64%2048%2058%2054%2060%2062C62%2070%2056%2076%2044%2076%22%2F%3E%3Cpath%20d%3D%22M18%2034C26%2030%2030%2038%2036%2036M54%2034C46%2030%2042%2038%2036%2036%22%20stroke-width%3D%224.25%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E","mimeType":"image/svg+xml","sizes":["any"]}],"description":"Open a note in the Hjarni note panel beside the conversation, where the user can read it, edit the title and body, and save. Use it when the user asks to open, show or work on a specific note; find the id with search first. Returns the same data as notes-get (including body and lock_version), plus space (personal, team or shared), can_edit and uri. Clients without panels get just that data. Without an id the panel opens on a note picker. The user may edit the note in the panel: before changing it with notes-update, pass expected_lock_version from your latest read. Optional: id (integer).","annotations":{"title":"Hjarni Notes","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"_meta":{"ui":{"resourceUri":"ui://hjarni/note-panel-v5"},"openai/ui":{"entrypoints":[{"type":"global"},{"type":"thread"}],"preferredModelDisplayMode":"fullscreen"}},"inputSchema":{"type":"object","description":"Open a note in the note panel. Omit id to open the panel on a note picker.","properties":{"id":{"type":["integer","null"],"description":"Note ID to open. Omit it to show a picker of recent notes."}},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"notes-panel","title":"Note panel actions","description":"Used only by the Hjarni note panel UI to reload, preview and save the note it shows. Assistants should not call this: read with notes-get and edit with notes-update. Actions: 'get' (id; known_lock_version answers status 'unchanged' when the note has not moved), 'search' (query: the panel's note picker, with a short preview per note; with id, only the notes that note can link to), 'browse' (place: one level of the folder tree, its folders and notes), 'preview' (id, body: renders Markdown without saving), 'save' (id, lock_version, title and/or body and/or add_tags/remove_tags; a lock_version that is no longer current saves nothing and answers status 'conflict' with the saved version and a line diff; resolve 'merge_hunks' with hunks {index: 'saved'|'draft'} applies a per-hunk choice), 'create_note' (place, title, body, add_tags), 'create_folder' (place, name), 'update_folder' (place folder:<id>; name, description, llm_instructions, negative_space), 'instructions' (place personal: brain, personal; place team:<id>: team), 'folder_options' (id: where that note can move), 'move' (id, place), 'trash' (id), 'restore' (id), 'favorite' (id, favorited), 'archive' (id, archived), 'history' (id; with seq, that version and its changes), 'revert' (id, seq, lock_version), 'verify' (id), 'keep_summary' (id, lock_version: clears summary_stale and leaves the summary as it is; a lock_version that is no longer current answers status 'stale'), 'file_description' (id, file_id, description), 'save_to_personal' (id of a team note), 'purge' (id of a trashed note: delete for good), 'folder_archive' (place folder:<id>, archived), 'folder_delete' (place), 'folder_restore' (id), 'folder_purge' (id), 'folder_parents' (place), 'folder_move' (place, to), 'folder_reorder' (place, direction up|down), 'tag' (place tag:<id>, op rename|merge|delete, name), 'share' (id, or place folder:<id>; op status|enable|disable|editing_on|editing_off), 'bulk' (ids, op move|archive|unarchive|trash, place for move), 'toggle' (id, lock_version, index, checked: ticks one task checkbox; a lock_version that is no longer current ticks nothing and answers status 'stale' with the saved version).","annotations":{"title":"Note panel actions","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"_meta":{"ui":{"visibility":["app"]}},"inputSchema":{"type":"object","description":"Load, preview or save the note shown in the note panel.","properties":{"action":{"type":"string","enum":["get","search","browse","preview","save","toggle","create_note","create_folder","update_folder","instructions","folder_options","move","trash","restore","favorite","archive","history","revert","verify","keep_summary","file_description","save_to_personal","purge","folder_archive","folder_delete","folder_restore","folder_purge","folder_move","folder_reorder","folder_parents","tag","share","bulk"],"description":"What to do (required)"},"name":{"type":"string","description":"create_folder, update_folder: folder name; tag: the new name, or the tag to merge into"},"description":{"type":"string","description":"update_folder: what the folder is for; file_description: the file's description"},"llm_instructions":{"type":"string","description":"update_folder: the folder's AI instructions"},"negative_space":{"type":"string","description":"update_folder: what does not belong in the folder"},"brain":{"type":"string","description":"instructions (place personal): instructions for everything"},"personal":{"type":"string","description":"instructions (place personal): instructions for Personal"},"team":{"type":"string","description":"instructions (place team:<id>): the team's instructions (owners only)"},"place":{"type":"string","description":"browse: spaces, personal, shared, team:<id> or folder:<id> (default spaces), also inbox, favorites, tags, tag:<id>, archived, trash; create_note, create_folder, move: where; update_folder: folder:<id>; instructions: personal or team:<id>"},"id":{"type":"integer","description":"Note ID (required for get, preview and save)"},"query":{"type":"string","description":"search: text to find; empty lists recently edited notes"},"known_lock_version":{"type":"integer","description":"get: the lock_version the panel already shows"},"lock_version":{"type":"integer","description":"save: the lock_version the edit started from (required for save); keep_summary: the version the person read"},"title":{"type":"string","description":"save: new title"},"body":{"type":"string","description":"save: new body (full replacement); preview: Markdown to render"},"add_tags":{"type":"array","items":{"type":"string"},"description":"save: tags to add (applied to the saved tags, never a conflict)"},"remove_tags":{"type":"array","items":{"type":"string"},"description":"save: tags to remove"},"index":{"type":"integer","description":"toggle: which task checkbox, counted from 0 in the note"},"favorited":{"type":"boolean","description":"favorite: the star's new state"},"seq":{"type":"integer","description":"history, revert: the version"},"summary":{"type":"string","description":"save: new summary"},"source_url":{"type":"string","description":"save: new source URL"},"file_id":{"type":"integer","description":"file_description: the file"},"to":{"type":"string","description":"folder_move: the new parent: personal, team:<id> or folder:<id>"},"direction":{"type":"string","enum":["up","down"],"description":"folder_reorder"},"op":{"type":"string","description":"tag: rename|merge|delete; share: status|enable|disable|editing_on|editing_off; bulk: move|archive|unarchive|trash"},"ids":{"type":"array","items":{"type":"integer"},"description":"bulk: the notes"},"sort":{"type":"string","enum":["recent","oldest","title"],"description":"browse: note order"},"space":{"type":"string","description":"search: only personal, shared or team:<id>"},"archived":{"type":"boolean","description":"archive: archive (true) or unarchive (false)"},"checked":{"type":"boolean","description":"toggle: the box's new state"},"resolve":{"type":"string","enum":["merge_hunks"],"description":"save: apply per-hunk choices from a conflict"},"hunks":{"type":"object","additionalProperties":{"type":"string","enum":["saved","draft"]},"description":"save with resolve: hunk index => 'saved' or 'draft'"}},"required":["action"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"notes-search_mentions","title":"Hjarni notes","description":"Used only by the ChatGPT composer's @-mention picker: returns the user's notes matching a typeahead query as selectable resources, each with its space and folder as a subtitle. Assistants should use search instead. Required: query (string, may be empty for recent notes).","annotations":{"title":"Hjarni notes","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"_meta":{"openai/extensions":{"mentions/search":{}},"ui":{"visibility":["app"]}},"inputSchema":{"type":"object","description":"Typeahead search for notes to mention.","properties":{"query":{"type":"string","description":"Search text; empty returns recently edited notes"}},"required":["query"]},"outputSchema":{"type":"object","description":"Selectable notes.","properties":{"items":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["resource"]},"resourceUri":{"type":"string"},"title":{"type":"string"},"subtitle":{"type":"string"}},"required":["type","resourceUri","title"]}}},"required":["items"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"containers-list","description":"List containers (folders) for organizing notes. Each container includes notes_count and children_count. Returns a JSON array, followed by a line stating the total for the folders it pages through and whether more pages remain; folders shared WITH you are appended in full on the first page and counted separately in that line. Optional: team_id (integer) for team containers, scope ('roots' default|'all'|'archived'), page, per_page. Shared containers are automatically included when listing root-level personal containers. The space's root (the personal root, or a team's root folder) is not a folder: it holds the notes filed outside any folder and cannot be renamed, moved, archived or deleted. It is left out of the default 'roots' scope and appears under 'all' flagged root: true.","annotations":{"title":"List Containers","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"List containers (folders) for organizing notes.","properties":{"team_id":{"type":["integer","null"],"description":"List containers in this team instead of personal containers. Omit it (or send null) for personal containers."},"scope":{"type":"string","enum":["roots","all","archived","trashed"],"description":"Filter scope (default: roots). 'archived' lists archived folders (personal, or the team's with team_id). 'trashed' lists deleted folders still restorable via containers-restore, one entry per delete with restores_with counts. On those entries notes_count and children_count are the folder's OWN notes and sub-folders in that delete, while restores_with totals the whole subtree that comes back with it."},"page":{"type":"integer","description":"Page number"},"per_page":{"type":"integer","description":"Results per page"}},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"containers-get","description":"Get a single container by ID, including notes_count, children_count, description, negative_space (what does NOT belong in the folder), and LLM instructions if set. Optional: include_tree (boolean) to also get ancestor chain and children. Required: id (integer). A row flagged root: true is the space's root (the notes filed outside any folder), which containers-update refuses; set that space's instructions with instructions-update instead.","annotations":{"title":"Get Container","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Get a single container by ID, including its LLM instructions if set.","properties":{"id":{"type":"integer","description":"Container ID (required)"},"include_tree":{"type":"boolean","description":"Include ancestors and children arrays (default: false)"}},"required":["id"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"containers-create","description":"Create a new container (folder) for organizing notes. Required: name (string). Optional: description (string), negative_space (string — what does NOT belong in this folder, so notes can be routed away from it), parent_id (integer or null) for nesting inside another container — null or omitted creates it at the top level (so does the space's root id: the root is not a folder, and the response then reports parent_id null), team_id (integer) to create the container in a team instead of personal space. When team_id is set, parent_id (if provided) must belong to the same team. After creating, consider setting up LLM instructions with instructions-update.","annotations":{"title":"Create Container","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Create a new container (folder) for organizing notes.","properties":{"name":{"type":"string","description":"Container name (required)"},"description":{"type":"string","description":"Container description"},"negative_space":{"type":["string","null"],"description":"What does NOT belong in this folder (e.g. 'No meeting notes, those go in Meetings'). Helps route notes away from the wrong place."},"parent_id":{"type":["integer","null"],"description":"Parent container ID for nesting, or null/omitted for a top-level container. Must belong to the same scope (personal or team) as the new container."},"team_id":{"type":["integer","null"],"description":"Create container in this team instead of personal space. Omit it (or send null) for personal space."}},"required":["name"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"containers-update","description":"Update a folder within the connected user's personal Hjarni account or an authorized team workspace — rename, change description or negative_space (what does NOT belong here), move to a different parent, set display position, or archive/unarchive it. Works for both personal and team containers the current user can edit (team containers require the editor role). Refuses the space's root (root: true in containers-get); set that space's instructions with instructions-update instead, and pass parent_id null, not the root's id, for a top-level folder. Required: id (integer). Optional: name, description, negative_space (pass null to clear it), parent_id (null for root, must be in the same scope), position (integer, lower = first). Archiving: archived (boolean) archives or unarchives the folder itself; add include_notes: true to also retire/reactivate every note filed in it IN BULK, and include_nested: true to sweep nested sub-folders (and, with include_notes, their notes) too. This is THE way to retire a whole folder — e.g. a finished project or a large import whose notes keep surfacing in dashboards — in one call; never loop notes-update over each note for that. The response then includes containers_changed and notes_changed counts (rows actually flipped; note bodies are not returned). On team containers an include_nested sweep requires the editor role on every affected sub-folder. Caveat on reversing: an unarchive sweep reactivates EVERYTHING it touches — include_nested resurrects sub-folders, and include_notes resurrects notes, that the user had archived individually before the sweep. When that matters, unarchive selectively: archived: false on specific folders, and individual notes via notes-update. Unarchiving a nested folder also reactivates its archived ancestor folders, so the restored folder stays reachable. Find archived folders again later with containers-list scope 'archived'.","annotations":{"title":"Update Container","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Update an existing container.","properties":{"id":{"type":"integer","description":"Container ID (required)"},"name":{"type":"string","description":"New name"},"description":{"type":"string","description":"New description"},"negative_space":{"type":["string","null"],"description":"What does NOT belong in this folder, or null to clear it."},"parent_id":{"type":["integer","null"],"description":"New parent container ID, or null to move the container to root level. Must belong to the same scope as the container."},"position":{"type":"integer","description":"Display order position (lower numbers appear first)"},"archived":{"type":"boolean","description":"Archive (true) or unarchive (false) the folder. On its own this only flags the folder; combine with include_notes/include_nested to retire its contents too."},"include_notes":{"type":"boolean","description":"With archived: also set the same archived state on every kept note filed in the folder (and in its sub-folders when include_nested is true), in one bulk update. Default: false."},"include_nested":{"type":"boolean","description":"With archived: apply the same archived state to all nested sub-folders as well. Default: false."}},"required":["id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"containers-permissions","description":"Change access only for existing members of the same Hjarni team; this does not invite anyone or publish a folder. Set or clear a team member's role on a team container. Roles cascade to descendants unless a child has its own role. Caller must have admin role on the target container. Required: container_id (integer, must be a team container), user_id (integer, must be a member of the same team), role ('viewer' | 'editor' | 'admin' | 'inherit'). Use 'inherit' to delete an explicit role and fall back to the role inherited from an ancestor (or from team membership). Team owners are always admin and cannot be downgraded. Returns the user's resulting effective_role on that container.","annotations":{"title":"Set Container Permissions","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Set a per-container role for a team member.","properties":{"container_id":{"type":"integer","description":"Team container ID (required)"},"user_id":{"type":"integer","description":"Team member's user ID (required)"},"role":{"type":"string","enum":["viewer","editor","admin","inherit"],"description":"New role, or 'inherit' to clear an explicit role (required)"}},"required":["container_id","user_id","role"]},"operation":{"type":"sensitive_write","effect":"Changes another team member's access to folder contents."}},{"name":"containers-delete","description":"Move a folder to Trash within the connected user's personal Hjarni account or an authorized team workspace, taking its sub-folders and their notes with it as ONE recoverable unit. Restorable with containers-restore for 30 days, then permanently removed. This is a real delete (the folder leaves every listing); to retire a folder while keeping it browsable in the Archive, use containers-update with archived: true instead. Team folders require the editor role on every folder the delete touches. Required: id (integer).","annotations":{"title":"Delete Container","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Move a folder and its contents to the Trash.","properties":{"id":{"type":"integer","description":"Container ID (required)"}},"required":["id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"containers-restore","description":"Restore a deleted folder from Trash within the connected user's personal Hjarni account or an authorized team workspace, bringing back everything its delete took -- sub-folders and notes return together, exactly as they were. Find restorable folders with containers-list scope 'trashed'. A note that was deleted TOGETHER with a folder can only come back this way (notes-restore refuses it and names the folder). Required: id (integer, the deleted folder's id).","annotations":{"title":"Restore Container","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Restore a deleted folder and its contents from the Trash.","properties":{"id":{"type":"integer","description":"Deleted container ID (required)"}},"required":["id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"tags-list","description":"List all tags with their notes_count. Returns a JSON array, followed by a line stating the total and whether more pages remain. Optional: page (integer), per_page (integer).","annotations":{"title":"List Tags","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"List all tags with note counts.","properties":{"page":{"type":"integer","description":"Page number"},"per_page":{"type":"integer","description":"Results per page"}},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"tags-create","description":"Create a new tag. Check tags-list first to avoid duplicates. Required: name (string). Tag names are automatically lowercased.","annotations":{"title":"Create Tag","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Create a new tag.","properties":{"name":{"type":"string","description":"Tag name (required)"}},"required":["name"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"tags-manage","description":"Rename, merge, or delete a tag — the cleanup tools for the tag list (this operates on the tag itself, unlike notes-update which only edits one note's tags). Required: action ('rename'|'merge'|'delete') and the tag to act on via name (string, case-insensitive) or tag_id (integer). For 'rename' also pass new_name. For 'merge' also pass the destination via target_name or target_id: every note on the source tag is moved onto the destination and the source tag is deleted — ideal for collapsing duplicates like 'machine learning' into 'machine-learning'. If target_name names a tag that doesn't exist yet, it's created, so you can merge straight into a clean canonical name without creating it first. 'delete' removes the tag from all its notes and deletes it; deleting a name that doesn't exist succeeds as a no-op (already_absent: true), so it's safe to retry. Merge and delete are destructive and cannot be undone; check tags-list first.","annotations":{"title":"Manage Tags","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Rename, merge, or delete a tag.","properties":{"action":{"type":"string","enum":["rename","merge","delete"],"description":"Action: 'rename', 'merge', or 'delete' (required)"},"name":{"type":"string","description":"Name of the tag to act on (case-insensitive). Provide this or tag_id."},"tag_id":{"type":"integer","description":"ID of the tag to act on. Provide this or name."},"new_name":{"type":"string","description":"New name for the tag (required for action 'rename')."},"target_name":{"type":"string","description":"Name of the destination tag to merge into (for action 'merge'). Provide this or target_id."},"target_id":{"type":"integer","description":"ID of the destination tag to merge into (for action 'merge'). Provide this or target_name."}},"required":["action"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"feedback-submit","description":"Send feedback to Hjarni's maintainers, storing a feedback record and emailing an admin notification that cannot be recalled. Use only when the user asks to send feedback, and include only the task-specific details they authorized. Submit feedback about Hjarni itself — confusing tool descriptions, missing capabilities, unexpected errors, friction, or praise. Do NOT use this for the user's own notes or knowledge — those belong in notes-create. Required: category ('bug'|'confusing'|'missing_feature'|'friction'|'praise'|'other'), message (string, what's wrong and ideally what you'd expect instead). Optional: severity ('low'|'medium'|'high', default 'medium'), tool_name (the MCP tool the feedback is about, e.g. 'notes-update'), context (JSON-encoded string with any extra structured data — error excerpts, the arguments you tried, the steps that broke).","annotations":{"title":"Submit Feedback","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"inputSchema":{"type":"object","description":"Submit feedback about Hjarni to the maintainers.","properties":{"category":{"type":"string","enum":["bug","confusing","missing_feature","friction","praise","other"],"description":"What kind of feedback this is (required)"},"message":{"type":"string","description":"The feedback itself. Be specific — describe what happened, what you expected, and (if relevant) what would have helped. (required, max 4000 chars)"},"severity":{"type":"string","enum":["low","medium","high"],"description":"How impactful this is for users (default: medium)"},"tool_name":{"type":"string","description":"The MCP tool this feedback is about, if any (e.g. 'notes-update', 'search')"},"context":{"type":"string","description":"Optional extra context as a JSON-encoded string (e.g. error messages, arguments tried, related note IDs). Non-JSON strings are stored as plain text."}},"required":["category","message"]},"operation":{"type":"sensitive_write","effect":"Sends an email to Hjarni support; that communication cannot be recalled."}},{"name":"teams-list","description":"List all teams the user is a member of, including members_count, notes_count, and containers_count for each team. No parameters required.","annotations":{"title":"List Teams","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"No parameters required.","properties":{},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"teams-get","description":"Get team details including the 10 most recent notes. Required: id (integer).","annotations":{"title":"Get Team","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Get team details including recent notes.","properties":{"id":{"type":"integer","description":"Team ID (required)"}},"required":["id"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"teams-create","description":"Create a new team owned by the user (they become the owner). Required: name (string). Optional: ref — set to \"assistant_suggestion\" ONLY when creating in response to the me → suggestions.create_team offer, so the creation is attributed to that surface; omit it when the user asked to create a team unprompted. A user can own at most 3 free teams: at that limit this returns an error naming the teams they own and asking them to delete one — relay it rather than retrying. Returns join_url: a shareable link that lets teammates join themselves, with join_link_expires_at and join_link_uses_remaining. Offer that link FIRST — most people cannot recall a colleague's address mid-conversation — and use teams-invite when the user names a specific address.","annotations":{"title":"Create Team","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Create a new team owned by the user.","properties":{"name":{"type":"string","description":"Team name (required)"},"ref":{"type":"string","enum":["assistant_suggestion"],"description":"Attribution surface. Set to 'assistant_suggestion' only when acting on me.suggestions.create_team; otherwise omit."}},"required":["name"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"teams-invite","description":"Get a teammate into a team (team owner only), two ways. Required: team_id (integer, from teams-list). WITH email: emails an invite to that address. WITHOUT email: returns join_url, a shareable link anyone can open to join themselves, plus join_link_expires_at and join_link_uses_remaining. Prefer the LINK unless the user names an address — few people can recall a colleague's address mid-conversation, and asking is where teams stall at one member. Give the link to the USER to share; never post it anywhere yourself. Creating a link REPLACES any existing one, so to repeat a link already shared, read join_url from teams-get instead of calling this. Optional: action ('create' (default) | 'revoke').","annotations":{"title":"Invite Teammate","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"inputSchema":{"type":"object","description":"Invite a teammate by email, or get a shareable join link.","properties":{"team_id":{"type":"integer","description":"Team ID (required; use teams-list to find it)"},"email":{"type":"string","description":"Address to email an invite to. Omit to get a shareable join link instead."},"action":{"type":"string","enum":["create","revoke"],"description":"Link mode only (no email): 'create' (default) mints a link and invalidates any previous one; 'revoke' removes it."}},"required":["team_id"]},"operation":{"type":"sensitive_write","effect":"Sends invitations or manages a bearer team join link, expands team access, and may add a billed seat."}},{"name":"email-addresses-list","description":"List the user's email capture addresses. Mail sent to one of these becomes a note in the folder it is bound to. Each entry has id, label, address, destination (folder name, or 'Inbox'), container_id, team_id, tags, notes_count and last_received_at. Returns an empty array when the account has none, and accepting_mail: false when mail sent to them is currently being dropped. Treat every address as a secret: it is a write credential for that folder, so never put one in a note, a summary, or anything shared.","annotations":{"title":"List Email Capture Addresses","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"List the user's email capture addresses.","properties":{},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"email-addresses-create","description":"Create an email capture address. Anything mailed to the returned address becomes a note in the chosen folder, with the subject as the title and attachments carried across. Optional: container_id (integer or null — null or omitted files into the Inbox), label (string — defaults to the folder's name and becomes the first part of the address), tags (string, comma-separated, added to every note alongside the automatic 'email' tag). Tell the user the address once and remind them it is a secret anyone can write to the folder with.","annotations":{"title":"Create Email Capture Address","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Create an email capture address bound to one folder.","properties":{"container_id":{"type":["integer","null"],"description":"Folder the mail is filed into, or null/omitted for the Inbox"},"label":{"type":"string","description":"How the address is named here, and the first part of the address itself. Defaults to the folder name."},"tags":{"type":"string","description":"Comma-separated tags added to every note from this address"}},"required":[]},"operation":{"type":"sensitive_write","effect":"Creates a bearer email address through which new notes can be added."}},{"name":"instructions-get","description":"Get LLM instructions at the specified level. Call with level 'brain' early in conversations to learn user preferences. Optional: level ('brain'|'personal_root'|'container'|'team'), defaults to 'brain' if omitted or blank; the response echoes resolved_level and defaulted_level (true when the level was defaulted). Optional: id (integer, required for 'container' and 'team' levels). 'container' level takes a personal (or shared) container id and returns the full inheritance chain, outermost first; each entry carries a `level` field ('brain'|'personal_root'|'team'|'container'). Team container ids are not addressable here — read a team note's chain from notes-get, or the team's own instructions with level 'team'. For an agent token, level 'brain' also returns agent_instructions, written by its person for that agent alone; brain_instructions_off: true means its person kept their brain instructions from it.","annotations":{"title":"Get Instructions","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Get LLM instructions at the specified level.","properties":{"level":{"type":"string","enum":["brain","personal_root","container","team"],"default":"brain","description":"Instruction level: 'brain' (global), 'personal_root', 'container', or 'team'. Defaults to 'brain' if omitted or blank."},"id":{"type":"integer","description":"Container ID or Team ID (required for 'container' and 'team' levels)"}},"required":[]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"instructions-update","description":"Update LLM instructions at the specified level. Required: level ('brain'|'personal_root'|'container'|'team'), instructions (string). Optional: id (integer, required for 'container' and 'team'), mode ('replace' default|'append'). The 'container' level updates personal containers only; to set instructions for a team, use level 'team' (team owners only). In 'replace' mode (default), the provided text overwrites existing instructions. In 'append' mode, the text is appended to existing instructions with a newline separator. Always read current instructions first before replacing to avoid losing existing content.","annotations":{"title":"Update Instructions","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Update LLM instructions at the specified level.","properties":{"level":{"type":"string","enum":["brain","personal_root","container","team"],"description":"Instruction level to update (required)"},"id":{"type":"integer","description":"Container ID or Team ID (required for 'container' and 'team' levels)"},"instructions":{"type":"string","description":"The instructions text. In 'replace' mode (default), this overwrites existing instructions. In 'append' mode, this is appended to existing instructions."},"mode":{"type":"string","enum":["replace","append"],"description":"Update mode: 'replace' (default) overwrites existing instructions, 'append' adds to them"}},"required":["level","instructions"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"nudges-dismiss","description":"Dismiss a nudge served in the `me` payload, on the user's behalf, so it stops appearing on every surface (including the web app). Call this ONLY when the user has actually declined the suggestion or asked not to be reminded — never on mere silence; ignored nudges are already frequency-capped server-side. Required: key (string, the `nudge.key` from `me`). Idempotent.","annotations":{"title":"Dismiss Nudge","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Dismiss a nudge for the current user.","properties":{"key":{"type":"string","description":"The nudge key from the `me` payload's nudge block (required)"}},"required":["key"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"links-manage","description":"Create or remove a bidirectional link between two notes. Required: action ('link'|'unlink'), source_note_id (integer), target_note_id (integer). Prefer wiki-link syntax [[id:Title]] in note bodies for inline linking — use this tool for programmatic links without modifying body text. Unlinking is destructive and cannot be undone.","annotations":{"title":"Manage Note Links","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Create or remove a bidirectional link between two notes.","properties":{"action":{"type":"string","enum":["link","unlink"],"description":"Action: 'link' to create a link, 'unlink' to remove it (required)"},"source_note_id":{"type":"integer","description":"First note ID (required)"},"target_note_id":{"type":"integer","description":"Second note ID (required)"}},"required":["action","source_note_id","target_note_id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"files-attach","description":"Attach a file to one of your personal notes via base64-encoded data (personal notes only; for team or shared notes use files-create_upload_url). Prefer files-create_upload_url for large files to save tokens. Required: note_id (integer), filename (string), data (base64 string). Optional: content_type (MIME type, default: application/octet-stream), description.","annotations":{"title":"Attach File to Note","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Attach a file to a note via base64-encoded data.","properties":{"note_id":{"type":"integer","description":"Note ID (required)"},"filename":{"type":"string","description":"Filename (e.g. report.pdf) (required)"},"data":{"type":"string","description":"Base64-encoded file contents (required)"},"content_type":{"type":"string","description":"MIME type (e.g. application/pdf). Defaults to application/octet-stream"},"description":{"type":"string","description":"Optional file description"}},"required":["note_id","filename","data"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"files-attach_from_url","description":"Fetch a file from a public URL or a native ChatGPT file reference and attach it to one of your personal notes (personal notes only; for team or shared notes use files-create_upload_url). Required: note_id and either url or file. In ChatGPT, prefer file for an available file reference, including generated or processed files when ChatGPT can provide one. ChatGPT supplies the temporary download URL and file ID; never invent them, pass a local file path, or encode the bytes as base64. If both sources are supplied, a non-empty file reference takes precedence; an all-blank file placeholder is ignored. Follows up to three redirects. Optional: filename (overrides reference metadata, otherwise derived from URL), content_type (overrides reference metadata, otherwise from HTTP response), description. When no reference or public URL is available, use files-create_upload_url.","annotations":{"title":"Attach File from URL or Reference","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"_meta":{"openai/fileParams":["file"]},"inputSchema":{"type":"object","description":"Attach a file from a public URL or a ChatGPT file reference. Supply url or file.","properties":{"note_id":{"type":"integer","description":"Personal note ID to attach the file to"},"url":{"type":"string","description":"Public URL to fetch; optional when file is supplied"},"file":{"type":"object","description":"Native file reference supplied by ChatGPT; optional when url is supplied","properties":{"download_url":{"type":"string","description":"Temporary HTTPS download URL supplied by ChatGPT"},"file_id":{"type":"string","description":"File ID supplied by ChatGPT"},"mime_type":{"type":"string","description":"File MIME type, when available"},"file_name":{"type":"string","description":"Original filename, when available"}},"required":["download_url","file_id"],"additionalProperties":false},"filename":{"type":"string","description":"Override filename; otherwise reference metadata or URL path"},"content_type":{"type":"string","description":"Override MIME type; otherwise reference metadata or HTTP response"},"description":{"type":"string","description":"Optional file description"}},"required":["note_id"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"files-remove","description":"Permanently remove a file attachment from one of your personal notes (personal notes only). This action is irreversible. Required: note_id (integer), file_id (integer, from notes-get response).","annotations":{"title":"Remove File from Note","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false},"inputSchema":{"type":"object","description":"Permanently remove a file attachment from a note.","properties":{"note_id":{"type":"integer","description":"Note ID (required)"},"file_id":{"type":"integer","description":"File ID from notes-get response (required)"}},"required":["note_id","file_id"]},"operation":{"type":"sensitive_write","effect":"Permanently removes an attachment."}},{"name":"files-create_upload_url","description":"Generate a one-time upload link for attaching files to a note. Share the upload_url with the user so they can upload directly in their browser, which saves tokens by avoiding base64 encoding. Some clients also render this result as an inline drop-zone the user can drop files into; you cannot detect that, so always share the link either way. The link expires after 30 minutes and accepts up to max_files_per_upload files per request, each up to max_file_size_bytes; when remaining_bytes/remaining_files are present the whole batch must also fit inside those. Use files-check_upload to verify completion. Required: note_id (integer). Optional: description.","annotations":{"title":"Create File Upload URL","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"_meta":{"ui":{"resourceUri":"ui://hjarni/file-upload-v3"}},"inputSchema":{"type":"object","description":"Generate a one-time upload URL for attaching a file to a note.","properties":{"note_id":{"type":"integer","description":"Note ID to attach the file to (required)"},"description":{"type":"string","description":"Optional file description"}},"required":["note_id"]},"outputSchema":{"type":"object","description":"The one-time upload link and the limits it will be judged against.","properties":{"upload_url":{"type":"string","description":"One-time upload URL to share with the user"},"token":{"type":"string","description":"Pass to files-check_upload to confirm completion"},"remaining_bytes":{"type":"integer","description":"Bytes left in the account's file allowance; absent when only the per-file cap applies"},"remaining_files":{"type":"integer","description":"Files left in the account's file allowance; absent when only the per-file cap applies"},"expires_at":{"type":"string","description":"ISO 8601 expiry instant"},"expires_in_seconds":{"type":"integer","description":"Seconds until the link expires, measured server-side"},"note_id":{"type":"integer","description":"Note the files will attach to"},"note_title":{"type":"string","description":"Title of that note"},"max_file_size_bytes":{"type":"integer","description":"Largest single file this upload will accept"},"max_files_per_upload":{"type":"integer","description":"Most files one upload request may carry"}},"required":["upload_url","token","expires_at","expires_in_seconds","note_id","note_title","max_file_size_bytes","max_files_per_upload"]},"operation":{"type":"write","effect":"Changes Hjarni data or supplies an interface capable of writing it."}},{"name":"files-check_upload","description":"Check the status of a file upload created by files-create_upload_url. Returns status: 'pending' (not uploaded yet), 'completed' (file attached, includes file metadata), or 'expired' (link timed out). Required: token (string, from files-create_upload_url response).","annotations":{"title":"Check File Upload","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Check the status of a file upload link.","properties":{"token":{"type":"string","description":"Upload token from files-create_upload_url response (required)"}},"required":["token"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}},{"name":"files-get_download_url","description":"Get a time-limited download URL for a file attached to a note. Share the URL with the user to download in their browser. The URL expires 15 minutes after it is issued (see download_url_expires_at in the response); request a fresh one rather than reusing an old URL. It requires no login while valid, so treat it as a credential. Required: note_id (integer), file_id (integer, from notes-get response).","annotations":{"title":"Get File Download URL","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"inputSchema":{"type":"object","description":"Get a time-limited download URL for a file attached to a note.","properties":{"note_id":{"type":"integer","description":"Note ID (required)"},"file_id":{"type":"integer","description":"File ID from notes-get response (required)"}},"required":["note_id","file_id"]},"operation":{"type":"read","effect":"Retrieves account data without a content write."}}]}