MCP App Store
Productivity
Hjarni Notes icon

Hjarni Notes

by Evert Van den Bruel

Overview

Hjarni is a knowledge base your AI can actually use. Write notes in Markdown. Organize them in folders and tags. ChatGPT reads them directly through a built-in MCP server. No plugins. No pasting the same context into every conversation. ChatGPT starts every conversation from zero. Connect Hjarni once and it stops asking. Ask questions across all your notes. Have ChatGPT write new notes, update old ones, and tag them the way you want. Set instructions per folder to control how ChatGPT writes, formats, and tags. Share a folder with your team so everyone's AI works from the same knowledge base. Search across everything in one call. Built on the same open MCP standard as Claude, so your knowledge base is not locked to one assistant. Use it as your second brain, your project memory, your research library, or your team's shared context. Notes, folders, tags. That is the product. Simple on purpose. Free to start. No credit card required.

Tools

containers-create

ChatGPT
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) for nesting inside another container, 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.

containers-create

ChatGPT
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) for nesting inside another container, 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.

containers-get

ChatGPT
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).

containers-get

ChatGPT
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).

containers-list

ChatGPT
List containers (folders) for organizing notes. Each container includes notes_count and children_count. 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.

containers-list

ChatGPT
List containers (folders) for organizing notes. Each container includes notes_count and children_count. 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.

containers-permissions

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

containers-permissions

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

containers-update

ChatGPT
Update an existing container — rename, change description or negative_space (what does NOT belong here), move to a different parent, or set display position. Works for both personal and team containers the current user can edit (team containers require the editor role). 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).

containers-update

ChatGPT
Update an existing container — rename, change description or negative_space (what does NOT belong here), move to a different parent, or set display position. Works for both personal and team containers the current user can edit (team containers require the editor role). 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).

dashboard-get

ChatGPT
Get an overview of the Second Brain: 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. No parameters required.

dashboard-get

ChatGPT
Get an overview of the Second Brain: 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. No parameters required.

feedback-submit

ChatGPT
Submit feedback about Hjarni itself — confusing tool descriptions, missing capabilities, unexpected errors, friction, or praise. Use this when something about the MCP server, a tool, or the product behavior is worth flagging to the maintainers. 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 workflow that broke).

feedback-submit

ChatGPT
Submit feedback about Hjarni itself — confusing tool descriptions, missing capabilities, unexpected errors, friction, or praise. Use this when something about the MCP server, a tool, or the product behavior is worth flagging to the maintainers. 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 workflow that broke).

files-attach

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

files-attach

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

files-attach_from_url

ChatGPT
Fetch a file from a public URL and attach it to one of your personal notes (personal notes only; for team or shared notes use files-create_upload_url). Follows one redirect. Required: note_id (integer), url (string). Optional: filename (default: derived from URL), content_type (default: from HTTP response), description.

files-attach_from_url

ChatGPT
Fetch a file from a public URL and attach it to one of your personal notes (personal notes only; for team or shared notes use files-create_upload_url). Follows one redirect. Required: note_id (integer), url (string). Optional: filename (default: derived from URL), content_type (default: from HTTP response), description.

files-check_upload

ChatGPT
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).

files-check_upload

ChatGPT
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).

files-create_upload_url

ChatGPT
Generate a one-time upload URL for attaching a file to a note. Share this URL with the user so they can upload directly in their browser — saves tokens by avoiding base64 encoding. The link expires after 30 minutes. Use files-check_upload to verify completion. Required: note_id (integer). Optional: description.

files-create_upload_url

ChatGPT
Generate a one-time upload URL for attaching a file to a note. Share this URL with the user so they can upload directly in their browser — saves tokens by avoiding base64 encoding. The link expires after 30 minutes. Use files-check_upload to verify completion. Required: note_id (integer). Optional: description.

files-get_download_url

ChatGPT
Get a temporary download URL for a file attached to a note. Share the URL with the user to download in their browser. URL expires after a few minutes. Required: note_id (integer), file_id (integer, from notes-get response).

files-get_download_url

ChatGPT
Get a temporary download URL for a file attached to a note. Share the URL with the user to download in their browser. URL expires after a few minutes. Required: note_id (integer), file_id (integer, from notes-get response).

files-remove

ChatGPT
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).

files-remove

ChatGPT
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).

instructions-get

ChatGPT
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 returns the full inheritance chain (personal root -> ancestors -> container).

instructions-get

ChatGPT
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 returns the full inheritance chain (personal root -> ancestors -> container).

instructions-update

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

instructions-update

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

me

ChatGPT
Get the connected user's profile, plan, 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 Pro features to free users. Returns onboarding.completed (boolean) and onboarding.missing_steps (array of 'connect_mcp' | 'first_note'), which together tell you what, if any, setup is left. Exposes the user's email address and plan — same data the user sees in account settings, but never billing or token metadata. No parameters required.

me

ChatGPT
Get the connected user's profile, plan, 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 Pro features to free users. Returns onboarding.completed (boolean) and onboarding.missing_steps (array of 'connect_mcp' | 'first_note'), which together tell you what, if any, setup is left. Exposes the user's email address and plan — same data the user sees in account settings, but never billing or token metadata. No parameters required.

notes-create

ChatGPT
Create a new note. 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'). Example: {title: 'Meeting Notes', body: '## Agenda\n...', container_id: 5, tag_list: 'meetings, q4'}.

notes-create

ChatGPT
Create a new note. 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'). Example: {title: 'Meeting Notes', body: '## Agenda\n...', container_id: 5, tag_list: 'meetings, q4'}.

notes-delete

ChatGPT
Permanently delete a note. This action is irreversible — the note and all its file attachments are destroyed. 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).

notes-delete

ChatGPT
Permanently delete a note. This action is irreversible — the note and all its file attachments are destroyed. 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).

notes-get

ChatGPT
Get a single note by ID, including its full Markdown body, tags, container path, linked notes (outgoing), backlinks (incoming links from other notes), file attachments, and inherited LLM instructions. Required: id (integer).

notes-get

ChatGPT
Get a single note by ID, including its full Markdown body, tags, container path, linked notes (outgoing), backlinks (incoming links from other notes), file attachments, and inherited LLM instructions. Required: id (integer).

notes-list

ChatGPT
List notes with optional filtering, sorting, and pagination. Returns paginated results. Optional: team_id (integer) to list team notes, scope ('active'|'archived'|'inbox'|'favorited'), 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). 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']}.

notes-list

ChatGPT
List notes with optional filtering, sorting, and pagination. Returns paginated results. Optional: team_id (integer) to list team notes, scope ('active'|'archived'|'inbox'|'favorited'), 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). 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']}.

notes-update

ChatGPT
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, personal 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. 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}.

notes-update

ChatGPT
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, personal 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. 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}.

tags-create

ChatGPT
Create a new tag. Check tags-list first to avoid duplicates. Required: name (string). Tag names are automatically lowercased.

tags-create

ChatGPT
Create a new tag. Check tags-list first to avoid duplicates. Required: name (string). Tag names are automatically lowercased.

tags-list

ChatGPT
List all tags with their notes_count. Paginated. Optional: page (integer), per_page (integer).

tags-list

ChatGPT
List all tags with their notes_count. Paginated. Optional: page (integer), per_page (integer).

tags-manage

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

tags-manage

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

teams-get

ChatGPT
Get team details including the 10 most recent notes. Required: id (integer).

teams-get

ChatGPT
Get team details including the 10 most recent notes. Required: id (integer).

teams-list

ChatGPT
List all teams the user is a member of, including members_count, notes_count, and containers_count for each team. No parameters required.

teams-list

ChatGPT
List all teams the user is a member of, including members_count, notes_count, and containers_count for each team. No parameters required.

Capabilities

Writes

App Stats

56

Tools

ChatGPT

Platforms

Category

Memory Notes

Works with

ChatGPT

Data refreshed daily