Capture, find, read and update Notes from external tools. Omit parentId to create a root Note, or pass another Note's identifier to add a Note beneath it.Endpoint: https://api.charmiq.ai/mcp/noteNote content uses one representation: complete authored RichtextMarkdown (RMD),
including metadata, inline styles, the authored stylesheet and Theme/import
references. Generated resolved CSS is not included. Create, read and save return
content and its version together. No format, view flag or read receipt is accepted.
VFS separately offers bounded reads and targeted editing.Create a Note
Call create_note with:| Argument | Required | Description |
|---|
content | Yes | Complete authored RichtextMarkdown |
parentId | No | Identifier of the parent Note; omit for a root |
source | No | Subject reference with a uri and optional label |
place | No | {url, label?, quote?} or {segments}, with an optional origin; the Platform encodes URL Places |
{
"name": "create_note",
"arguments": {
"content": "# Research decision\n\nKeep the existing approval flow.",
"source": {
"uri": "https://example.com/research",
"label": "Research reference"
}
}
}
The result is {noteId, content, version}. Content is canonical authored RMD at
Version 0. Edit that returned content and send it with the returned version to
update_note; no intervening read is needed. Use noteId as parentId when
adding another Note. Repeating a successful creation creates another Note.Every Note stores its own content. Adding a Note leaves its ancestors' content and revisions unchanged.Captured Place
For a web page, send its URL rather than constructing resource identifiers:{
"name": "create_note",
"arguments": {
"content": "Keep this quotation for the opening.",
"place": {
"url": "https://twitter.com/alice/status/123",
"label": "Opening quotation",
"origin": "browser"
}
}
}
The server builds Web → Site → URL from the page's host and full address, without the
credentials a link carries (signatures, tokens, user info), which are never stored.
Another host is another site. The address without its credentials remains the return
link. It is not fetched. A capture the browser extension made sends
origin: 'browser'. The segment form below also supports Platform resources.place records where the thought was captured. It is independent of the subject's
source reference and of Note parentage. For example, a browser extension capturing
while researching a Workspace can send:{
"name": "create_note",
"arguments": {
"content": "Keep the opening focused on the customer's first five minutes.",
"place": {
"origin": "browser",
"segments": [
{ "kind": "section", "id": "workspaces", "label": "Workspaces" },
{ "kind": "material", "id": "Document:<workspace-id>", "uri": "/workspace/<workspace-id>" }
]
}
}
}
A CapturedPlace has one to eight segments, ordered broad to specific in the order the
layers are reached. Segment kinds are section, material, site, url, overlay,
view and focus; each carries an id and optional label and return uri. A
material id is typed as {MaterialType}:{id} (for example Document:<workspace-id>
or folder:<folder-id>); a focus id is typed as {FocusKind}:{id} with kinds
button, agent, entity, node, tool, prompt and quote; every other kind
carries a bare id. list_notes looks up at most four segments: a Note captured deeper
is listed under its four-segment ancestor with its complete capture. IDs and labels are limited to 256 characters,
return URIs to 4,096 characters, and the encoded resource address to 1,500 UTF-8 bytes.
Return URIs accept local routes or HTTP(S) URLs without credentials. A material
segment names a Material by its identity alone: a label sent with it is not stored, and
each reader sees the title they may read. A Platform segment (section, material,
overlay, view, focus) returns to the Platform's own page for its identity, so a
uri sent with one is not stored; a quote under a page returns to its passage on that
page. A page and its site are identified by the page's link: a url segment's id and a
site segment's id and label derive from it, whatever was sent.Over MCP, origin is mcp (the default), or browser for a capture the browser
extension made; platform and action are the Platform's own channels and are refused.
Origin does not change resource identity. The server derives
Search prefixes; callers cannot submit those query fields.Place details are a frozen snapshot, readable by the Note's readers. They grant no
access to the referenced resource and are not verified evidence of the caller's
screen. A Place that names a Material is accepted only when you can read that Material;
otherwise nothing is created. Omit place for an unrelated thought. Notes without a place remain ordinary
Notes. The Notes Where filter and source controls use this association for recall
and return navigation.Find Notes at a Place
list_notes finds the active root Notes you can read at a Place and more specific
Places, whoever wrote them: your own and those shared with you. Children, archived
Notes and deleted Notes are excluded.| Argument | Description |
|---|
place | {url} for this page; {url, scope: 'site'} for its site; or {segments} for a resource prefix |
origin | Optional capture-channel filter: platform, browser, mcp, or action |
limit | Maximum Notes per page, 1–50; default 50 |
cursor | Return the previous response's cursor unchanged with the same Place and origin filter |
{
"name": "list_notes",
"arguments": {
"place": { "url": "https://x.com/alice/status/123" },
"limit": 1
}
}
The response contains notes: [{noteId, title, createdByYou, lastEditTimestamp, place}],
totalCount, totalCountCapped, hasMore and an optional cursor. The edit
timestamp is Unix milliseconds; results sort newest edit first, with descending
Note ID breaking ties. createdByYou is true for your own Note and false for one shared
with you. Place includes captured labels and return URLs.Badge counts are exact from 0–9, then show 9+. Every count query stops at ten
matches. At that boundary the response contains totalCount: 10 and
totalCountCapped: true; it is a lower bound, not an exact total. Below ten,
totalCountCapped is false. Use this count for the badge, not the number of Notes
in one page. Use hasMore and the cursor to browse every Note: the count cap does
not limit result pages.The cursor carries {noteId, seconds, nanoseconds} from the last returned
snapshot. It survives editing or deletion of that record; every request still
checks the caller and scope. Counts and pages are separate reads, and concurrent
edits can reorder the live list. Refresh to restart browsing.Passing a captured Place, including its nested origin, does not narrow the capture
channel: only the separate origin argument filters it. For example, one Browser
capture and one MCP capture of a post return total 2; adding origin: 'browser'
returns total 1. A site lookup includes captures of other posts too.Find Notes by Meaning
search_notes finds the Notes you can read whose content is closest in meaning to a
query, closest first: your own and those shared with you. It searches every Note you
can read that is not deleted, once the Note is indexed for search, so unlike
list_notes it is not limited to the active root Notes at a Place.| Argument | Description |
|---|
query | Required. What the Notes are about, in natural language: a topic, a page title, a passage |
maxResults | Maximum Notes to return, 1–25; default 10 |
include | Optional thumbnails for each Note: thumbnail (light), thumbnailDark (dark) |
{
"name": "search_notes",
"arguments": {
"query": "notes on intrinsic galaxy alignments",
"maxResults": 5
}
}
The response contains notes: [{noteId, title, createdByYou, lastEditTimestamp, place?, score}],
each as list_notes shows a Note, with place present only for a Note captured at a
Place. score is the relevance to the query: higher is closer. The text result says
whether each Note is yours or shared with you.Thumbnails are for programs that show results. Each one named in include arrives as
{data, mimeType, width, height} (base64 bytes) in the structured result, once the Note
has been rendered; the text result never carries them.Read a Note or Subtree
read_note takes noteId (a root or child) and returns that Note's content. include selects none (default), ids, or content for related Notes. Related Notes are direct children unless recursive: true is supplied.| Argument | Description |
|---|
maxDepth | With recursive reads, maximum levels below noteId (1–16); 1 means children. Omit for all descendant levels |
limit | Maximum related Notes per page; the response byte budget may shorten a page |
cursor | Continuation from the previous page; keep the starting Note and selection unchanged |
version | Omit for current content; supply a retained Richtext Version for historical content |
{
"name": "read_note",
"arguments": {
"noteId": "<note-id>",
"include": "content",
"recursive": true,
"maxDepth": 2
}
}
Children retain noteId, parentId, and rootNodeId. Parents precede descendants; siblings are newest first. Follow cursor while hasMore is true. Each page ends at a complete Note. Each Note requires its own current grants, including historical reads.The addressed Note and every included child return complete authored content
with its own version. Only the set of related Notes paginates. An individual
Note that exceeds the response budget fails explicitly rather than returning a
partial document; VFS provides bounded reads for those cases. Read metadata is
outside the content: submit only the content field when saving.The text response marks each generated footer as [version N; Note read metadata, not document content]. Saving that marker is refused; ordinary authored text such
as [version 3] is valid content.Full-fidelity administrative exports use export_note_bundle on the Defect MCP server. That export preserves stored content, metadata and relationships with Firestore sentinels.Update a Note
update_note accepts the complete edited content. The server computes granular
Richtext edits, including authored stylesheet changes, and saves through ordinary
collaboration. It leaves the Note's Place, source, capture origin, parent and
children unchanged.| Argument | Required | Description |
|---|
noteId | Yes | Existing root or child Note identifier |
content | Yes | Complete edited content; an empty string clears existing writing |
version | No | Authoring Version paired with the content from create, read or save; omitted means latest when editing starts |
{
"name": "update_note",
"arguments": {
"noteId": "<note-id>",
"version": 7,
"content": "# Research decision\n\nKeep approval before publishing."
}
}
The result is {noteId, content, version, merged}. Content is the complete
canonical authored RMD at that acknowledged version, including concurrent changes
incorporated by collaboration. merged reports whether such changes were
incorporated. Always use the returned content and version together as the next
editing baseline; retaining the submitted text with a newer returned version can
delete merged writing on the next save.Identical content creates no edit and returns the unchanged authoring content and
Version, even if later Versions exist. Converted submissions have the same 512 KiB
limit as creation. Retain metadata and styles while editing: the complete submitted
document is authoritative, so omissions can remove presentation. Unchanged authored
CSS retains its stored form. A stylesheet change that cannot be stored is refused,
not silently dropped.For example, the extension reads Version 7 and edits its opening while someone
adds a separate paragraph in Drive. Sending version: 7 authors only the extension's
changes against that state and rebases them over the Drive edit. Version is not an
unchanged-document precondition. Omitting it compares the full submission with the
latest state loaded by the server, so omitted current writing is a requested deletion.
A later append race still uses ordinary collaboration. A confirmed client overwrite
omits the version; it needs no preliminary read, receipt or separate overwrite flag,
including for large Notes.If typing continues during a save, retain those newer local edits and reconcile them
with the returned baseline. Do not replace the live editor blindly, or label its
unreconciled content with the returned version.Overlapping changes follow ordinary collaboration. The shared differ can group
separate block changes into a wider replacement; it does not promise a minimal or
semantic merge. A body-only edit preserves concurrent stylesheet changes; competing
stylesheet replacements conflict because a stylesheet is one stored attribute.Collaboration failures set MCP isError: true and return structured
{noteId, error: {code, message}}, without content, version or merged.
The output schema declares both success and failure fields. The text response begins
Error [<code>]:. Classify error.code, not the prose:| Code | Meaning |
|---|
conflict | No authored change landed; a competing stylesheet edit or removed target prevented the save |
partial-application | Some authored changes remain saved; others were dropped |
Read the current Note before forming a new update after either outcome. A partial
failure is not a rollback. Other failures retain the ordinary MCP error contract.Updates require RICHTEXT_CUD and current Editor access on the addressed Note,
including at the write. A child does not require an ancestor grant to edit.Upload an Inline Image
An inline image lives inside Note content, the way a pasted image lives inside a
document. It is not a Drive Asset and never appears in Drive; to put a file in Drive,
use the Asset tools.Call upload_inline_image with:| Argument | Required | Description |
|---|
mimeType | Yes | image/png, image/jpeg, image/gif or image/webp; the server reads the bytes and refuses a mismatch |
data | Yes | The image bytes, base64-encoded; up to 10 MiB of image bytes |
The response is {url, width, height}. Reference the image from content as
 in create_note or update_note; read_note returns the same
reference. Anyone with the URL can open the image.{
"name": "upload_inline_image",
"arguments": { "mimeType": "image/png", "data": "iVBORw0KGgoAAAANSUhEUgAA…" }
}
{
"name": "create_note",
"arguments": {
"content": "# Evidence\n\n\n\nSeen 2026-09-19 12:00 UTC.",
"place": { "url": "https://example.com/article", "origin": "browser" }
}
}
Uploads require RICHTEXT_CUD. Other arguments are refused rather than ignored: a
folder, a name or a description means the caller wanted an Asset, not an inline image.Content and Sources
RMD content is converted to ProseMirror before storage. Schema-based editors can use
parseRichtextMarkdown and toRichtextMarkdown from
@charmiq/service-common/richtextEditor/client; text-based clients edit RMD directly.
Never hand-unescape content or submit local editor JSON as RMD.A Note supports up to 512 KiB of serialized ProseMirror content, measured in UTF-8 bytes. The root is depth zero; the maximum depth is 16.A source is an optional subject reference. Its URI and label are trimmed; blank labels are omitted. Creating a Note does not fetch the referenced resource or grant access to it.Access
The connection uses the authenticated User and Organization context and requires access to create Notes. The creator owns a new root Note; adding a Note requires at least Commenter access to the addressed parent. The parent and every ancestor must be active.A new child copies its immediate parent's grants and gives its creator Owner. Later sharing changes affect only the addressed Note; permission propagation is separate. Organization membership, authorship, source/place references and optional Charm attribution do not bypass a Note's current grants.