FieldwerkFieldwerk

MCP tools reference

The 119 tools the Fieldwerk MCP server exposes to connected agents. Each tool's full description is what the agent itself sees. Tools marked sign-in required need an OAuth-authenticated session — that includes every tool that creates, changes, or deletes anything. The rest are read tools that also work against a memo's shared link.

connect

Check whether the user is connected to a Fieldwerk account, and get the link to connect if they are not. Call this whenever a tool reports the user is not signed in, or when the user asks to connect or sign in. Reading shared memos works without sign-in; creating or editing anything requires a connected account. When connected is false, give the user connect_url, ask them to sign in and approve access, then retry the original tool. Many MCP clients also open the authorisation page automatically on the first call that needs it.

getting_started

sign-in required

Fetch the user's Fieldwerk onboarding state and the guidance for their current phase. Call this at the start of any 'help me get started', 'set up my first memo', or 'what can I do here' flow, and again after any meaningful step (you created, opened, or edited a memo for this user). Returns state plus guidance; it does not create anything. The phase is derived fresh from the account's real state on every call, never stored, so it is always safe to re-ask. Requires OAuth.

phase stringworkspace stringrefresh boolean

create_memo

sign-in required

Create a new Fieldwerk memo on the user's account. Requires the user's Fieldwerk account to be connected; an unauthenticated call returns an authentication challenge, and the connect tool provides the sign-in link. Returns the memo's single share URL in url. TIP for nicer link previews: the memo's markdown may begin with a YAML front-matter block (---\ntitle: My Doc\ndescription: One-liner\n---). If present, title (case-insensitive) becomes the page <title> and the Slack/social link-unfurl headline; description becomes the unfurl blurb. Without front-matter, previews fall back to a generic 'A Fieldwerk memo' card (privacy: we never auto-extract H1 text into metadata). Recommend adding front-matter when a memo is intended for sharing. TABLE OF CONTENTS URL HINT: any returned memo URL accepts an optional toc=shown or toc=hidden query param (e.g. https://my.fieldwerk.ai/m/abc123?toc=shown). When present it forces the per-memo TOC sidebar open or closed for that visitor; absent, it falls back to their saved preference. Append &toc=shown when handing a URL to the user for a long memo with many headings, or when they explicitly ask for a deeplink with the TOC open.

markdown stringtitle stringcollections arrayworkspace string

get_memo

Fetch a memo by URL or id. Returns markdown and metadata, including archived_at (null = live). An archived memo still reads fine for workspace members but rejects every write with memo_archived until someone unarchives it, so check archived_at before planning edits. The response includes a sections outline: one entry per heading with its canonical heading_path (plus level, duplicate_occurrence, ambiguous, and anchor). When you want to target a section with read_section, replace_section, or append_to_section, COPY THAT heading_path VERBATIM instead of reconstructing it from the raw markdown. The stored markdown can contain escape characters the matcher does not expect (e.g. ### 13\. Confirmation renders as 13. Confirmation), so the canonical path is the rendered text. If ambiguous is true, two sections share the same path and the matcher will reject it; disambiguate with the user or edit one heading first. Each entry's anchor is that heading's in-document anchor id: append it to the memo URL as #<anchor> to hand someone a link that opens the memo scrolled to that section, and use the same value when writing an in-body link like [jump](#<anchor>). Anchors are derived from the heading text, so rewording a heading changes its anchor and breaks links already written to it. When relaying a memo URL back to the user, you may append &toc=shown (or &toc=hidden) to force the table-of-contents sidebar open or closed for the visitor — useful for long memos with many headings. Image URLs in the markdown may carry layout flags (?size=, ?align=, ?wide=, ?px=); the image tools' descriptions explain them, and you may rewrite them in the body to resize or place an image. Images are NOT inlined here: to see what one shows, call read_memo_image with its URL. TWO FIELD LAYERS: fields is the memo's OWN front matter only. A memo filed in a collection can also carry that collection's own values for it (what its board shows, what a card move writes), returned here as collection_fields: one entry per collection you can see, each with the collection's name and its fields map for this memo. A memo with fields: {} is NOT unclassified while collection_fields has values, so read both before concluding anything about a memo's properties. collections and notebooks list every container you can see the memo in, whether or not you own the memo.

url stringid string

update_memo

sign-in required

Replace the full markdown of a memo. Requires an edit URL. PREFER a surgical tool over update_memo whenever the change fits one. Whole-memo replacement is expensive in tokens, races harder against concurrent editing, and is the most likely path to orphan comment threads. Reach for it only when the user actually asked for a rewrite of the whole thing: - Editing a single section → replace_section / append_to_section - Known string change → patch_memo (unified diff) - Adding to the end → append_to_memo - Toggling a checkbox → toggle_task - Adding a list item → add_list_item - Adding a table row → add_table_row - Setting metadata → set_field / rename_field / delete_field IMPORTANT: if you call get_memo before writing (or have called it recently), pass its returned version as base_version. The server will reject the write with a conflict error if another writer (browser or MCP) has modified the memo since. The rejection includes severity (minor if only 1–2 versions behind and same author, major if 3+ behind OR another author wrote since): on minor, TREAT current_markdown as ground truth, discard your draft, re-apply your intent, retry with the new base_version. On major, also consider surfacing the change to the user before overwriting — the world moved a real amount. If you truly mean to overwrite a major conflict, pass confirm_overwrite_changes: <current_version> (must equal the current_version from the prior rejection; if more concurrent writes land between attempts the flag stops working and you must re-confirm against the new number). CONCURRENCY-SAFE MERGE (recommended): whenever you pass base_version, ALSO pass merge_base_markdown = the exact markdown you got back from that same get_memo. If another writer changed a DIFFERENT part of the memo since your read, the server 3-way merges your rewrite onto their current version (using your read as the common base): their edit is preserved AND your change lands, with no conflict. A genuine same-lines overlap still returns a conflict for you to resolve. This is the difference between your whole-doc rewrite co-existing with a live editor and bouncing off (or clobbering) them — always send it. Omit base_version only if you explicitly mean to overwrite whatever is there. Front-matter convention: a leading YAML block (---\ntitle: …\ndescription: …\n---) drives the page title and Slack/social link-unfurl previews. title and description are case-insensitive. Add or update them when a memo is intended for sharing — they're the only fields that surface to unfurlers. COMMENT THREADS: the response may include comments_orphaned (an array of thread IDs whose anchored passage you rewrote so that the in-document highlight could not be reattached) and comments_reattached (threads whose anchor text was preserved and re-marked automatically). Whenever a write touches any anchored thread, BOTH fields are returned (even empty), so comments_orphaned: [] is an explicit guarantee you can assert, not a missing field you have to infer from. When comments_orphaned is non-empty, surface that to the user — those threads still exist and are readable, but no longer point at a specific passage. The structure-aware tools (replace_section, patch_memo, etc.) report the same fields. CONCURRENT ACTIVITY: the response may include concurrent_activity: { last_other_writer, seconds_ago } when SOMEONE ELSE edited the memo in the last ~30s — another agent, or a person typing in the browser. Someone merely opening or viewing the memo (without editing) is NOT concurrent activity and never appears here. Purely advisory — no behaviour change — but when it fires, treat it as a hint to pause or confirm with the user before the next push, since you may be racing a live editor. ORPHAN-COMMENT BUDGET: writes that would orphan more than 3 comment threads are rejected with error: would_orphan_comments and a threads_at_risk[] payload that includes per-thread anchor context + comment body. Two ways forward: 1. PREFERRED — comment_anchors: [{ thread_id, new_anchor_text }]: find where each at-risk passage now lives in your rewritten markdown and re-pin it. Strict improvement; never adds orphans. 2. ESCAPE HATCH — confirm_orphan_count: <count>: acknowledge the loss. Use only after surfacing the orphan list to the user. The count must match the latest rejection; if new comments land in between, the count grows and your flag stops working. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringmarkdown string*base_version integerconfirm_overwrite_changes integermerge_base_markdown stringcomment_anchors arrayconfirm_orphan_count integer

append_to_memo

sign-in required

Append markdown to the end of a memo. Requires an edit URL. PREFER omitting base_version for pure appends — they don't logically conflict with concurrent edits to other parts of the doc, and on a memo someone is actively typing in the version increments faster than you can read-then-write, leading to spurious conflict loops. Pass base_version only when you need the append to land atomically on top of a specific snapshot you just read — see update_memo for the conflict-and-retry pattern (and the confirm_overwrite_changes escape hatch) when that matters. Append is the lowest-risk write for comment threads — the existing body is left in place — but the orphan-budget gate still applies if the appended content somehow disturbs an existing anchor. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringmarkdown string*base_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

patch_memo

sign-in required

Apply a unified diff against the memo's current markdown. Useful for large memos where sending the full replacement would burn tokens. The diff must apply cleanly — fails loudly on any context/deletion mismatch (returns patch_conflict). Agent should then re-read with get_memo and regenerate the diff. Pass base_version to catch mid-air collisions; on major conflict see update_memo for the confirm_overwrite_changes escape hatch. The orphan-budget rejection applies here too — same comment_anchors[] / confirm_orphan_count surface. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringdiff string*base_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

read_section

Read the markdown body of a specific section, selected by heading_path (a >-delimited path through nested headings, e.g. 'Meeting notes > Action items'). Returns just the section's content, not the whole memo. The response echoes the section's canonical heading_path (built from the rendered heading text, the exact form the section tools match on). COPY THAT VALUE VERBATIM when you follow up with replace_section / append_to_section, rather than reconstructing the path from the raw markdown, which may contain escape characters (e.g. ### 13\. Confirmation) that the matcher does not expect.

url stringid stringheading_path string*

replace_section

sign-in required

Replace the body of a specific section, keeping its heading. markdown is the BODY ONLY — do NOT repeat the section's heading line in it (the same shape read_section returns, which is body-only for exactly this reason). The heading is preserved for you; passing it again is the one common way to corrupt a memo here, so a leading heading identical to the target is now dropped rather than duplicated. Use rename_heading to change the title line itself. Pass heading_path verbatim from a get_memo sections entry or a read_section response (TRA-395) rather than reconstructing it from the raw markdown. Optimistically concurrency-checked via base_version (pass the version from get_memo). On conflict, see update_memo for the severity/confirm_overwrite_changes escape hatch — the same flag works here. Also supports comment_anchors[] / confirm_orphan_count for the orphan-budget gate. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringheading_path string*markdown string*base_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

append_to_section

sign-in required

Append markdown at the end of a section (before the next sibling or parent heading). Ideal for 'add a bullet to Action Items' without replacing the whole section. markdown is content only — the section keeps its existing heading, so repeating it here just adds a second one mid-section. Pass heading_path verbatim from a get_memo sections entry or a read_section response (TRA-395) rather than reconstructing it from the raw markdown. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringheading_path string*markdown string*base_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

rename_heading

sign-in required

Rename a heading in place, keeping its level and the body underneath it untouched. This is the surgical tool for changing heading TEXT — replace_section deliberately keeps the heading and rewrites the body, so reach for rename_heading when only the title line is wrong (a typo, a renamed person/section). Pass heading_path verbatim from a get_memo sections entry or a read_section response rather than reconstructing it. new_heading is the replacement text only (no leading # — the existing level is preserved); inline markdown like **bold** is honored. Optimistically concurrency-checked via base_version; on conflict see update_memo for the severity/confirm_overwrite_changes escape hatch. Also supports comment_anchors[] / confirm_orphan_count for the orphan-budget gate, though renaming a heading rarely disturbs anchored passages in the body. NOTE: a heading's in-document anchor (its anchor in get_memo's sections) is derived from its text, so renaming one changes its anchor and breaks any #anchor link or shared URL already pointing at it. If the memo links to this section from elsewhere in its body, update those links in the same pass. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringheading_path string*new_heading string*base_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

add_list_item

sign-in required

Append an item to a list in the memo. Use heading_path to target the first list in a specific section, or omit it and use list_index (0-based) to pick a top-level list. Set task: true (and optionally checked) to add a task-list item. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringheading_path stringlist_index integertext string*task booleanchecked booleanbase_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

toggle_task

sign-in required

Toggle a task-list item's [ ] / [x] checkbox, or set it explicitly via checked. Locates the item by item_match (substring, case-insensitive; set fuzzy: true to allow looser matching). Errors with candidates if the match is ambiguous. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringheading_path stringitem_match string*fuzzy booleanchecked booleanbase_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

remove_list_item

sign-in required

Remove a list item matched by item_match. Same matching rules as toggle_task. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringheading_path stringitem_match string*fuzzy booleanbase_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

add_table_row

sign-in required

Append a row to a markdown table. Use heading_path to target the first table in a specific section, or omit it and use table_index (0-based) to pick a top-level table. Provide column values as either values (ordered array) or row (object keyed by header names, case-insensitive). Missing columns default to empty; extra columns are rejected with the expected headers. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringheading_path stringtable_index integervalues arrayrow objectbase_version integerconfirm_overwrite_changes integercomment_anchors arrayconfirm_orphan_count integer

set_field

sign-in required

Set a front-matter field on ONE memo. Pass field + value for a single field, or fields (a {name: value} map) to set several at once in a single write, single version bump, and single metered write. Prefer that over one call per property. To write the same field across MANY memos, use set_fields_bulk instead of looping this tool. value accepts any JSON type (string, number, boolean, null, array, object) and values keep their JSON types in storage: send 726 to store a number, true to store a boolean. A STRING value that exactly spells a scalar token is lifted to that type at ingest: "true"/"false" become booleans, "null" becomes null, and a plain number string like "726" becomes a number, unless lifting would lose information (leading zeros like "007", or more digits than a number holds, stay strings so IDs survive). Pass value_is_literal_string: true to store such a string verbatim instead. Non-string values are always stored exactly as sent. Fields are stored in a separate DB column (not embedded in the markdown), so they survive round-trips cleanly and are queryable via the where clauses on list_my_memos, search_memos, list_collection_memos, and list_shared_with_me. Note: a field write bumps the memo's version just like a body write. Without collection you are writing the memo's own front matter, its own words. Pass collection (an id, slug, or name) to instead write that collection's OWN value for this field on this memo (the memo/collection edge): that is what a board shows and what a card move changes, and it wins over the memo's front matter wherever it's set. A collection-scoped write needs edit access to the COLLECTION rather than the memo, has no base_version (the edge has no version counter), and fails with not_found if the memo isn't actually filed in that collection. Setting a value does NOT register it as an OPTION on the property. A board grouped by that property builds its columns from the registered options plus whatever values the loaded rows carry, so a value you set here shows as a column only while some card still holds it. A board whose property has no options and no matching cards renders nothing at all, while every write here still answers ok. Use set_field_definition to register the options, and list_field_definitions to see what is registered. A PERSON property (owner, assignee, reviewer, lead, dri and the other reserved person names) is different, and the two scopes behave differently. WITH collection (the board's own value) it has no options and must name someone who actually has access: read that property's people roster from list_field_definitions (or list_workspace_members) and send a display string, an email, or a user id for one of them. Anything else is REFUSED with the roster in the error, so do not invent labels the way you would for a select. WITHOUT collection you are writing the memo's own front matter, which is plain text the author owns: any string is accepted there, and it is stored exactly as written rather than resolved to a person. Send null to unassign in either scope. Check the board's group_by_scope in list_views first: a "collection" board cannot see a value written to the memo's front matter, and a "memo" board cannot see one written to the edge. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringfield stringvalue anyvalue_is_literal_string booleancollection stringfields objectbase_version integerconfirm_overwrite_changes integer

set_fields_bulk

sign-in required

Set fields on MANY memos in one call: the batch form of set_field. Send items, a list of {memo, fields, collection?} entries: memo is a memo id or URL, fields is a {name: value} map, and collection (an id, slug, or name) writes that collection's OWN values for this memo (what a board shows) instead of the memo's front matter, exactly as it does on set_field. Entries can mix the two scopes and can name different collections. Use this whenever you have already worked out a mapping. 'Review = To review on these eighteen memos' is ONE call, not eighteen. PARTIAL SUCCESS by design: each entry is applied independently and the response carries a per-entry result, so one refusal (a memo you can't edit, a person value nobody matches, a memo not filed in the named collection) never costs you the rest of the batch. Read ok and failed in the summary, then retry only the entries that report an error. Entries are applied in the order sent. Metering is honest: each entry counts as one write against the automated-write quota, exactly as the equivalent single calls would. This saves round trips, not allowance. At most 100 entries per call; a longer list is refused rather than quietly truncated. Values follow the same typing rules as set_field. No base_version here: a batch is a bulk edit, so if you need optimistic-concurrency protection on a particular memo, write that one with set_field. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

items array*value_is_literal_string boolean

batch_update_memos

sign-in required

Apply several changes to several memos in ONE call: per-memo field writes (front matter, or a collection's own board values) AND notebook add/remove, together — the shape a real reconciliation actually takes ('classify this whole set, then apply it') instead of one call per field per memo. Pass collection (an id, slug, or name) once to scope every entry's fields to that collection's OWN values (what a board shows), exactly like set_fields_bulk's collection argument; omit it and fields land on each memo's own front matter instead. With collection set, notebook_changes.add/remove may also name a notebook by its plain NAME (not just its id), resolved within that collection — without collection, notebook refs must be ids. An entry may create a new memo instead of naming an existing one (memo): pass create: {workspace?, title?, markdown?}, and its fields/notebook_changes apply to the memo just created, filed into collection first when one is given, so a board card is fully formed in one call instead of create-then-many-writes. WHAT 'ATOMIC' MEANS HERE: this database has no cross-statement transactions available to MCP writes, so there is no whole-batch rollback — same as set_fields_bulk and the membership tools. What IS guaranteed is PER-ENTRY: every value in an entry is validated (access, field type/options, notebook existence) before that entry writes anything, so one bad value in an entry never lands half of that entry. Notebook adds run before fields (an add can file the memo into collection, which a collection-scoped field write requires) and removes run last. Across DIFFERENT entries this is the same partial-success model as every other batch tool here: each entry reports its own ok/error, and one entry's refusal never costs the rest of the batch. Pass dry_run: true to validate every entry (access, field type/options, notebook resolution) with nothing written — the response is shaped exactly like a live run so you can read it the same way, under results[].would_apply (or would_create for a new memo) instead of the applied values. Pass idempotency_key (any string unique to this logical change) to make a retried call safe: a second call with the same key returns the FIRST call's recorded result (idempotent_replay: true) instead of re-applying the writes. Keys are scoped to you and expire after a couple of days; a dry run neither consumes nor is served from a key. The whole call emits ONE analytics event summarising it (entry count, applied/failed, collection) instead of one per field or membership change — the grouped signal a batch like this should read as, one coherent change rather than a flood of individual writes. Metering is honest, same as set_fields_bulk: each field write, notebook add/remove, and memo creation still counts against the automated-write quota exactly as the equivalent single calls would — this tool saves round trips, not allowance. At most 100 entries per call; a longer list is refused rather than truncated. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

collection stringupdates array*idempotency_key stringdry_run booleanvalue_is_literal_string boolean

get_field

Read a single front-matter field by key. The response carries exists alongside value, so an absent field (exists: false, value: null) is distinguishable from a field explicitly set to null (exists: true, value: null). Pass collection (an id, slug, or name) to read the RESOLVED value for that collection instead: the collection's own value for this field name wins over the memo's front matter, matching what a board shows and what a card move changes; the response then also carries source ("collection", "memo", or "none") and collision (true when both layers set this field name). Omit collection to read the memo's own front matter, unaffected by any collection.

url stringid stringfield string*collection string

list_fields

Return the memo's front-matter fields as a flat key/value map. Fields are structured metadata stored alongside the markdown body (not inside it). Use these tools to attach queryable metadata (status, tags, dates, etc.) to memos — list_my_memos, search_memos, list_collection_memos, and list_shared_with_me can all filter on them via the where clause. To discover fields across MANY memos at once, pass include_fields: true to those listing tools instead of calling this per memo. Pass collection (an id, slug, or name) to see this memo's RESOLVED fields in that collection instead: wherever the collection has its own value for a field name, it wins over the memo's front matter, matching what a board shows. Omit collection to see the memo's own front matter, unaffected by any collection.

url stringid stringcollection string

rename_field

sign-in required

Rename a front-matter field key in place; the value is preserved and insertion order is maintained. Returns conflict if to already exists (decide whether to delete that key first). Useful when a memo started with one schema and the user wants to evolve it (e.g. statestatus). Note: title and description are reserved conventional keys that drive link-unfurl previews; renaming them away strips that signal from social/Slack previews. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringfrom string*to string*base_version integerconfirm_overwrite_changes integer

delete_field

sign-in required

Remove a front-matter field. Bumps the memo's version like any other field or body write. Without collection this clears the memo's own front matter. Pass collection (an id, slug, or name) to instead clear that collection's OWN value for this field on this memo (the memo/collection edge), leaving the memo's own front matter untouched so it becomes visible again wherever it was shadowed. A collection-scoped clear needs edit access to the COLLECTION rather than the memo, has no base_version, and fails with not_found if the memo isn't actually filed in that collection. Concurrency: see the server instructions (CONCURRENCY MODEL) for how version/base_version work.

url stringid stringfield string*collection stringbase_version integerconfirm_overwrite_changes integer

search_memos

sign-in required

Full-text search the memos you can read — your own AND ones shared to you (a direct/group grant, or a memo in a collection you can access). It never returns a memo you couldn't open. Returns up to limit (default 10, max 20) hits ranked by relevance, with a server-generated snippet around the match. Title hits rank higher than body hits. The LAST token of q is prefix-matched, so a type-ahead query like "pric" matches "pricing"; other tokens are AND-joined. Soft-deleted (trashed) memos are excluded, and archived memos are excluded unless include_archived: true, which also brings back memos reachable only through an ARCHIVED collection (they stay openable while archived, they are just unlisted). Pass collection (an id, slug, or name of a collection you can access) to scope the search to memos filed in it. Pass notebook (an id, or a name when combined with collection) to scope to a notebook you own, a sub-group of memos inside one collection. Filter the hits by fields with where (shared operator grammar, see the where parameter: equals, not_equals, in, not_in, exists, contains; a clause with only field checks presence). The filter runs in the database BEFORE the ranked page window, so limit/offset page through the matching hits. Either scope (collection, or notebook, which names one) makes where and the echoed fields read that collection's RESOLVED values (its own value for a field on this memo, the memo's front matter underneath), so a scoped search agrees with the collection's board and with list_collection_memos. Unscoped, a memo's own front matter is the whole truth. Pass include_fields: true to get every hit's fields map without filtering, to discover the field vocabulary in use before constructing a where, or to check values without per-memo get_memo calls (hits always carry fields when where is passed). Paginate through the ranked hits with limit (default 10, max 20) and offset (default 0): pass offset 10 for the next page. Requires OAuth.

q string*limit numberoffset numbercollection stringcollection_tags arraynotebook stringworkspace stringinclude_archived booleanwhere arrayinclude_fields booleaninclude_field_sources boolean

list_my_memos

sign-in required

List memos owned by the authed user. Optionally filter by fields via where (shared operator grammar, see the where parameter: equals, not_equals, in, not_in, exists, contains; a clause with only field checks presence). The filter runs in the database BEFORE pagination, so limit/offset page through the matching memos. Pass collection (an id, slug, or name of a collection you own) to scope to memos filed in it, or notebook (an id, or a name when combined with collection) to scope to a notebook, a sub-group of memos inside one collection. Either scope makes where and the returned fields read that collection's RESOLVED values: its own value for a field on this memo, the memo's front matter underneath, so a scoped list agrees with the collection's board and with list_collection_memos. Unscoped, a memo's own front matter is the whole truth. Spans ALL your workspaces by default (each result is tagged with its own workspace); pass workspace (name, slug, or id from list_workspaces) to scope to one. Does NOT follow the web app's current workspace. Archived memos are excluded unless include_archived: true (included rows carry archived_at). Rows ALWAYS include their fields map here; include_fields is accepted for parity with the other listing tools but adds nothing. Paginate with limit (default 50, max 1000) and offset (default 0): pass offset 50 for the second page of 50, and so on. Requires OAuth.

limit numberoffset numberworkspace stringcollection stringcollection_tags arraynotebook stringinclude_archived booleanwhere arrayinclude_fields booleaninclude_field_sources boolean

list_shared_with_me

sign-in required

List memos genuinely shared WITH the authed user, most recently opened first. Requires OAuth. These are memos shared with them directly (a memo grant, personal or via a group) or through a collection shared with them, including content they were invited to as a guest — it mirrors the web 'Shared with me' view exactly. A directly shared memo appears here even if the user has NEVER opened it: those rows carry unopened: true and sort first (their last_opened_at falls back to the time it was shared). Memos the user merely opened via a public or edit link are NOT here (use search_memos / list_my_memos for those). direct_grant marks a personal/group grant vs collection-channel access. Useful when the user asks 'what did so-and-so share with me'. Filter by front-matter fields with where (shared operator grammar, see the where parameter: equals, not_equals, in, not_in, exists, contains; a clause with only field checks presence). The filter runs in the database BEFORE pagination, so limit/offset page through the matching memos and has_more describes the filtered stream. Pass include_fields: true to get every row's front-matter fields map without filtering, to discover the field vocabulary in use before constructing a where, or to check values without per-memo get_memo calls (rows always carry fields when where is passed). Paginate with limit (default 20, max 50) and offset (default 0); the has_more flag tells you when another page is available. Archived memos are excluded unless include_archived: true, which also brings back memos reachable only through an ARCHIVED collection (archiving a collection unlists its memos here; it does not revoke your access to them).

limit numberoffset numberinclude_archived booleanwhere arrayinclude_fields boolean

list_collection_memos

sign-in required

List every memo in a collection you own or that is shared with you, most recently active first. The access-aware twin of the web collection drill-in: identify the collection by collection (an id, slug, or name), and you get back the memos you're allowed to see in it (the owner sees all; a shared viewer sees the non-restricted memos plus any they were invited to). Unlike search_memos, this needs no query and is not owner-only, so it's the right tool to enumerate a collection a teammate shared with you. Returns each memo's id, title, a URL to open it, and its updated/last-activity times. This is also how you READ a collection's standing instructions: when its author has written any, the response carries them as instructions — how this collection is meant to be used. Read them first and follow them for the work you do in here. Filter by fields with where (shared operator grammar, see the where parameter: equals, not_equals, in, not_in, exists, contains; a clause with only field checks presence). The filter runs in the database BEFORE pagination, so limit/offset page through the matching memos. Both the filter and the returned fields read this collection's RESOLVED values: its own value for a field on a memo wins over that memo's front matter, and person values come back as display names, the same values its board shows. Pass include_fields: true to get every row's fields map without filtering, to discover the field vocabulary in use before constructing a where, or to check values without per-memo get_memo calls (rows always carry fields when where is passed). Archived memos are excluded unless include_archived: true (included rows carry archived_at). Paginate with limit (default 50, max 1000) and offset (default 0): pass offset 50 with limit 50 for the second page. Requires OAuth.

collection string*limit numberoffset numberinclude_archived booleanwhere arrayinclude_fields booleaninclude_field_sources boolean

list_memo_versions

List recorded versions of a memo, most recent first. Returns metadata only — version number, source ('web' / 'mcp'), op ('replace' / 'append'), author, comment-anchor audit (counts plus the thread IDs whose anchors couldn't be reattached on that write), and timestamps. Use get_memo_version to retrieve the markdown body for a specific version. Writes within a 30-second window from the same author + same source are coalesced into a single row, so quick agent loops show as one entry not twenty — version numbers in the list can skip as a result. A coalesced row never spans more than 5 minutes even if writes keep landing inside that window, so a long editing session still leaves periodic checkpoints. To walk deep history without downloading everything, pass from_version / to_version (inclusive version-number bounds, either alone is fine). The response's has_more tells you whether older rows exist below the returned window; continue by calling again with to_version set to one below the oldest version you received. Any role on the memo can read history.

url stringid stringlimit numberfrom_version numberto_version number

get_memo_version

Fetch the full markdown body of a specific version, plus its front-matter fields and the audit metadata. Useful when you want to see what the memo looked like before a particular edit, or to confirm what a previous agent run wrote. Address the version by version_id (UUID from list_memo_versions) or by version (the version number) — note coalescing means not every number has a recorded row.

url stringid stringversion_id stringversion number

list_comments

List comments on a memo. Returns all threads in one call — page-level comments live under thread_id == 'page'; range-anchored comments use a generated thread id. For range threads the response includes an anchor snippet ({ before, range, after }) so the model can tell *which* passage a comment refers to (the range field is the highlighted text). Pass thread_id to scope to a single thread, e.g. when iterating replies. Resolved comments are returned by default; pass include_resolved: false to skip them. Soft-deleted comments stay hidden; include_deleted: true is honored only when you are the memo owner. Assignment is thread-level (TRA-402): the fields are populated on the thread's first comment only — assigned_to_email (the invited user the thread is assigned to, or null), plus assigned_by_user_id and assigned_at. Use assign_comment / unassign_comment (which target the whole thread) to change them. If any connected app is actively working a thread right now, a top-level working array lists them ({ thread_id, display, expires_at }) — check it before jumping into a thread so you don't stomp another agent (set your own with set_working).

url stringid stringthread_id stringinclude_resolved booleaninclude_deleted booleanwith_anchor_text boolean

post_comment

sign-in required

Post a comment on a memo. Defaults to the page-level thread; pass thread_id of an existing range thread (obtained from list_comments) to reply on a specific passage. Pass parent_id to thread a reply under an existing comment. To start a NEW range-anchored thread (highlight a passage and comment on it, like selecting text in the editor), pass anchor_text: the exact text to highlight, copied from get_memo / read_section. If that text appears more than once, also pass context_before and/or context_after (the surrounding text) to pick the right occurrence — otherwise the call is rejected as ambiguous. The highlight is placed before the comment is saved, so a failed match never creates an empty thread. anchor_text is mutually exclusive with thread_id/parent_id. Requires Clerk auth (OAuth) and at minimum a comment-role URL.

url stringid stringbody string*thread_id stringparent_id stringanchor_text stringcontext_before stringcontext_after string

edit_comment

sign-in required

Edit the body of an existing comment. Only the comment's author or the memo owner may edit; other callers get forbidden. Sets edited_at. Use list_comments first to discover the comment_id.

url stringid stringcomment_id string*body string*

resolve_comment

sign-in required

Mark a comment resolved (or unresolve it with resolved: false). Anyone with edit access to the memo can resolve/unresolve a thread (not just the comment author or the memo owner). Resolved comments stay visible in list_comments unless the caller passes include_resolved: false.

url stringid stringcomment_id string*resolved boolean

delete_comment

sign-in required

Soft-delete a comment. The row stays in the database (so the comment can be recovered server-side) but disappears from list_comments unless the caller passes include_deleted: true. Same permission model as editing a comment: author or memo owner only.

url stringid stringcomment_id string*

assign_comment

sign-in required

Assign a comment THREAD to a user with Invited access to the memo (an email that has been invited via the share flow / share_memo), OR to one of YOUR OWN connected apps by passing assignee_client_id instead (so a human sees "Assigned to <app>" and that app gets a comment_assignment in its inbox). Pass exactly one of assignee_email / assignee_client_id. Assignment is thread-level, like Google Docs: pass any comment_id in the thread and the assignment lands on the whole thread (carried on its first comment), not that individual reply. Assigning to someone who hasn't been invited — or an app you haven't connected — is rejected. Pass a different assignee to reassign. A new human assignee is emailed a confirmation. Use unassign_comment to clear, and list_comments to read the current assigned_to_email / assigned_to_client_id. Requires OAuth and at minimum a comment-role URL; only the comment's author or the memo owner may assign.

url stringid stringcomment_id string*assignee_email stringassignee_client_id string

unassign_comment

sign-in required

Clear a comment THREAD's assignment (sets assigned_to_email back to null). Thread-level, like assign_comment: pass any comment_id in the thread. No email is sent. Same permission model as assign_comment — author or memo owner. Use list_comments to discover the comment_id.

url stringid stringcomment_id string*

list_invited_users

sign-in required

List the people you can @mention and assign comments to on a memo — the memo owner plus collaborators invited at comment/edit role (read-only invitees are excluded). Returns [{ email, display }]. Use this to discover valid mention names and to pick an assignee_email for assign_comment. Requires OAuth and that you are the owner or a participant invitee. If you reached the memo by public share link rather than being invited to it, the list is limited to the owner, the people who have commented, and you.

url stringid string

add_reaction

sign-in required

Add an emoji reaction to a comment. Idempotent: adding the same reaction twice is a no-op (returns added: false). Useful as a lightweight temporal flag when reviewing or actioning comments: 👀 for 'reading', 🤔 for 'considering', ✅ for 'actioned', 🚫 for 'declining', ❤️ for 'acknowledged'. Avoids the noise of posting a reply just to say 'noted'. Requires OAuth and at minimum a comment-role URL on the memo. Use remove_reaction to undo. Use list_comments to discover comment_ids.

url stringid stringcomment_id string*emoji string*

remove_reaction

sign-in required

Remove a previously-added emoji reaction from a comment. Idempotent: if the reaction isn't present, the call is a no-op (returns removed: false). Only removes YOUR reaction — other users' reactions are unaffected. Use after add_reaction to clear a temporal flag once the work is done (e.g. swap 👀 for ✅). Requires OAuth and at minimum a comment-role URL.

url stringid stringcomment_id string*emoji string*

list_inbox

sign-in required

Your agent inbox (TRA-614): comment threads that @mention THIS connected app, PLUS new activity on threads you're already working (any thread you've posted on or been mentioned in resurfaces when someone else comments — never your own messages). Across all your memos or scoped to one memo/notebook/collection/workspace. Each item gives the type (comment_mention | thread_activity), the memo (id + url), the thread and its status (open/resolved), and the latest message with who wrote it, whether it was an agent, and whether YOU wrote it (by_me) — so you can decide whether to act WITHOUT polling every memo, and skip threads where you already had the last word. Poll incrementally: pass the previous response's cursor back as since to get only what's new. Returns unacked items only unless include_acked=true; ack items with ack_inbox once handled. Requires a connected MCP app (OAuth).

memo_id stringcollection_id stringnotebook_id stringworkspace_id stringsince stringinclude_acked booleanlimit number

ack_inbox

sign-in required

Mark inbox items handled so they drop out of your default list_inbox feed (TRA-614). Ack a single item by notification_id, or every item for a thread by thread_id (e.g. once you've replied in that thread). Idempotent. Requires a connected MCP app (OAuth).

notification_id stringthread_id string

set_working

sign-in required

Signal that YOU (this connected app) are actively working a comment thread, so the humans watching it see "<your app> is working on this…" instead of silence while you read and compose (TRA-614). Call it as soon as you pick a thread up. It auto-expires after ttl_seconds (default 600 = 10 min) — call again to keep it alive (a heartbeat) on long work, and clear_working when you're done. If you crash or forget, it evaporates on its own, so it can never get stuck. Requires a connected MCP app (OAuth). Needs comment access to the memo.

memo_id stringurl stringthread_id stringttl_seconds number

clear_working

sign-in required

Clear your "working on it" signal on a thread once you've finished (or handed off), so the badge drops immediately rather than waiting for it to expire (TRA-614). Idempotent. Requires a connected MCP app (OAuth).

memo_id stringurl stringthread_id string

share_memo

sign-in required

Email someone a link to a memo and grant them access. The recipient gets the memo's single keyless link plus the access level you choose (view, comment, or edit) on the memo, so they keep that access when signed in. Requires OAuth and edit access: a member/collaborator editor of a claimed memo, or any edit-link holder of an unclaimed memo. Use when the user asks for something like 'share this with ben@pujji.co.nz as a commenter and ask him to look at it before 5'. Notes should be short, friendly, in the user's voice — the recipient sees the note, the sender's name, and the sender's email. Optional toc arg: pass "shown" to append ?toc=shown to the emailed link so the recipient lands with the table-of-contents sidebar open (useful for long memos with many headings); omit otherwise. Caps: 10 targets per call, 30/memo/hour, 50/sender/day, 100/memo lifetime. Example call for that user request: `` share_memo({ id: "abc123", role: "comment", targets: ["ben@pujji.co.nz"], note: "Could you have a look at this before 5?" }) ` Response shape: { ok, sent, rejected: [{email, reason}], rate_limit_remaining }. On 429, response includes rate_limited: { scope, retry_after_s } — relay the wait time to the user. Per-target failures (invalid_email, delivery_failed, self, duplicate) come back in rejected[]` with the call still 200; surface them concisely (e.g. 'sent to 2, couldn't reach foo@example.com — invalid email').

url stringid stringrole string*targets array*note stringtoc string

set_memo_grant

sign-in required

Grant (or update) a specific person or group access to a memo, silently — no email is sent (use share_memo to invite by email with a note). Owner only. Identify the subject with user_email (must be an existing Fieldwerk account) OR group (id or name within the memo's workspace). level: view / comment / edit. Re-granting updates the level. Requires OAuth.

url stringid stringuser_email stringgroup stringlevel string*

get_memo_sharing

sign-in required

Read a memo's full sharing posture (owner only): the public link level + password, the restricted flag, every direct grant (people and groups with their level), the collections it's shared through, and any pending email invites. Use this BEFORE changing sharing so you can report the current state. A non-owner gets an access error. Requires OAuth.

url stringid string

set_memo_restricted

sign-in required

Turn a memo's invited-only (restricted) mode on or off (owner only). This is the same control as the web share dialog's "Share via collections" toggle. When restricted: true, only people with a direct grant (and the owner) reach the memo — the collection channel is turned OFF, which also REMOVES the memo from every collection it's in (it then can't be added to one until you lift the restriction). The public link and direct grants are left exactly as they are — to also turn the link off, call set_memo_link with access: "none". restricted: false clears it so the memo can be filed into collections again. Requires OAuth.

url stringid stringrestricted boolean*

revoke_memo_access

sign-in required

Remove a person's or group's direct grant on a memo (owner only). Identify with user_email or group (as in set_memo_grant). Idempotent — removing a subject that has no grant is a no-op. Does NOT affect the public link or collection-derived access. Requires OAuth.

url stringid stringuser_email stringgroup string

list_workspaces

sign-in required

List the workspaces you belong to. Use a workspace's name, slug, or id as the workspace argument on create_memo, create_collection, duplicate_memo, move_memo, list_my_memos, search_memos, and list_collections to act in a specific one. active only marks which workspace the person currently has open in the web app; no tool defaults to it. When you belong to several workspaces, pass workspace explicitly or you'll get a workspace_required ask (then remember the user's choice for the session). If this connection is pinned to a workspace via its URL, pinned_to names it and every tool acts there. A workspace can carry STANDING INSTRUCTIONS of its own: the preamble that applies before you know which collection you are in (how collections are named, the notebook vocabulary, what not to restructure). has_instructions flags which do; pass include_instructions: true for the text, and read it BEFORE working anywhere in that workspace. Every collection and notebook read also echoes it as workspace_instructions. instructions_updated_at / instructions_updated_by say who last wrote it. Managers edit it with update_workspace. Requires OAuth.

include_instructions booleaninclude_instructions_history boolean

list_collections

sign-in required

List collections the authed user can access — the ones they own PLUS the ones shared to them (a workspace share, or a direct/group grant), matching what the web app shows. Each row carries owned (true = yours) and can_manage (may rename/archive/share). Collections group memos into folder-like buckets; a memo can belong to more than one. By default archived collections are excluded; include_archived: true brings them back, ones shared with you as well as ones you own, each row carrying archived_at. Reach for it before concluding a collection does not exist: an archived collection is still openable and still holds its memos. A row with has_instructions: true carries STANDING INSTRUCTIONS from its author — how that collection is meant to be used. Pass include_instructions: true to get the text on each row (or read one collection with get_collection), and follow them BEFORE you write anything into it. instructions_updated_at / instructions_updated_by say who last wrote them. Spans ALL your workspaces by default (each result is tagged with its own workspace); pass workspace (name, slug, or id from list_workspaces) to scope to one. Does NOT follow the web app's current workspace. Requires OAuth.

include_archived booleaninclude_instructions booleanworkspace stringtags array

get_collection

sign-in required

Read ONE collection in full: name, description, standing instructions (with instructions_updated_at / instructions_updated_by), its tags (the workspace's flat collection-tag vocabulary), its notebooks (each with has_instructions and its text), its property schema as field_definitions (name, type, options, description), its saved views (with each view's description note), memo count, your role, and the collection's url. The workspace's own preamble rides along as workspace_instructions when one is written, so reading a collection also hands you the layer above it. This is the tool to call to orient yourself before working in a collection; it replaces five listing calls. Memos are NOT included: page them with list_collection_memos. Pass include_instructions_history: true for the last ten writes to the instructions (who, when, the full text as it stood), so a rule that was dropped can be recovered. Identify the collection by collection (an id, slug, or name). Requires OAuth.

collection string*include_instructions_history boolean

create_collection

sign-in required

Create a new collection owned by the authed user. NOT idempotent — if an active collection with the same (case-insensitive) name already exists, the call fails with a conflict error so agents can't fork. Pass instructions to give the collection STANDING INSTRUCTIONS at creation: author-written guidance on how it is meant to be used, handed to whoever (human or agent) works in it later. Set them later, or edit them, with update_collection. Lifecycle is just create / archive / unarchive (no delete from MCP); archive lives on the dashboard only. The collection lands in a workspace: if you belong to multiple workspaces and don't pass workspace, the call returns a workspace_required disambiguation listing them (it will NOT silently use whatever workspace you last opened in the web app). Ask the user which one, retry with an explicit workspace, and remember their choice for the rest of this session so later writes pass it automatically. Requires OAuth.

name string*description stringinstructions stringprivate booleanworkspace string

update_collection

sign-in required

Edit a collection's name, description, and/or STANDING INSTRUCTIONS (the author-written brief for anyone working in it: how it is meant to be used, what belongs in it, the conventions to follow). Allowed for the owner OR a manager (workspace owner/admin, a workspace-wide Manage share, or an admin grant), the same set that may share it. The memos and their memberships are untouched. Pass the collection id (from list_collections, where can_manage tells you if you may) plus a new name, description and/or ONE instructions edit: instructions replaces the text wholesale (read the current text with get_collection first; empty string clears description or instructions), append_instructions adds to it without resending the rest, or patch_instructions applies a unified diff for a one-line change. Every instructions write is recorded with its author and time (see instructions_updated_by on reads, and get_collection with include_instructions_history). NOT idempotent against name clashes: if another active collection with the owner's (case-insensitive) name already exists in the workspace, the call fails with a conflict. Requires OAuth.

collection string*name stringdescription stringinstructions stringappend_instructions stringpatch_instructions string

add_memo_to_collection

sign-in required

File a memo into a collection. Filing does NOT change the memo's workspace, it stays where it is. To MOVE a memo to a different workspace, use move_memo instead. Idempotent: re-adding a memo already in the collection is a no-op. Pass memos (a list of ids) instead of memo to file several in one call rather than one call each. Archived collections are rejected (unarchive first). A memo MAY belong to several collections, including more than one PUBLISHED collection — membership is not exclusive and nothing is moved or removed. When the memo is filed into a published collection while it also lives elsewhere, the result includes a note that it is now reachable through this collection's link too. Requires OAuth: EDIT access to the memo AND EDIT access to the collection. Filing is an edit action, so ownership is NOT required — manage-level access is only for sharing, invites, and publish.

memo stringmemos arraycollection string*

remove_memo_from_collection

sign-in required

Remove a memo from one of its collections. The memo and the collection themselves are untouched; only the membership is dropped. Idempotent: removing a memo not in the collection is a no-op. Pass memos (a list of ids) instead of memo to unfile several in one call. Requires OAuth.

memo stringmemos arraycollection string*

list_collection_tags

sign-in required

List a workspace's COLLECTION TAGS: the flat vocabulary used to group collections without a hierarchy, each with the number of collections carrying it. Call it before set_collection_tags so you apply words the workspace already uses instead of coining near-duplicates — digital and Digital are the same tag, but digital and digital-team are two, and a vocabulary that forks stops being useful. Also tells you whether you may create new tags here (can_create), which is a workspace setting separate from your access to any collection. Requires OAuth.

workspace string

set_collection_tags

sign-in required

Change which tags a collection carries. Three ways to say it, and you must pass exactly one kind: tags REPLACES the set (a tag currently on the collection and absent from tags is removed; tags: [] clears them all), while add_tags and remove_tags change it in place and leave everything else alone — those two may be combined. Prefer add/remove: replacing means reading the current set first and sending it back with your change, and a stale read silently drops somebody's tag. Pass collections instead of collection to apply the same change across several at once (max 50) — every ref is resolved before anything is written, so a bad one fails the whole call rather than half-applying. A name the workspace already holds (case-insensitively) reuses that tag; a new name coins one, which requires the workspace's tag-edit permission — check can_create from list_collection_tags first if you plan to invent a word. Applying an EXISTING tag only needs edit access to the collection. Tags never change who can see anything; they are classification only. Requires OAuth.

collection string*collections arraytags arrayadd_tags arrayremove_tags array

update_collection_tag

sign-in required

Rename or MERGE a collection tag across the whole workspace. Merging is merge_into, and ONLY merge_into: a rename onto a name the workspace already holds is REFUSED and tells you what it clashed with, rather than quietly folding the two together. Fixing marketing beside Marketing is a merge and usually the right one, but it deletes a tag off every collection carrying it and cannot be undone, so it is worth a sentence to the user before you ask for it by name. Every change lands on every collection carrying the tag at once (list_collection_tags gives you the counts). Needs the workspace's tag-edit permission. Requires OAuth.

workspace stringtag string*name stringmerge_into string

delete_collection_tag

sign-in required

Delete a collection tag from the workspace, untagging every collection carrying it. The collections themselves are untouched — only the label goes. Irreversible, and it affects everyone in the workspace, so check the count with list_collection_tags and confirm with the user first. To fold a tag into another instead of losing it, use update_collection_tag with merge_into. Needs the workspace's tag-edit permission. Requires OAuth.

workspace stringtag string*

list_notebooks

sign-in required

List the notebooks inside a collection you can access — the ones you own AND ones shared to you, including view-only shares (listing is a read, so it doesn't require edit access). A notebook is a named sub-group of memos within one collection (a divider tab inside it). Every row carries has_instructions: true means the notebook's author wrote STANDING INSTRUCTIONS for it (how that notebook is meant to be used). Pass include_instructions: true to get the text itself back on each row, and follow it for work you do in that notebook. Pass collection as an id, slug, or name. By default archived notebooks are excluded; pass include_archived: true to see them too. Requires OAuth.

collection string*include_archived booleaninclude_instructions boolean

get_notebook

sign-in required

Read ONE notebook in full: name, description, standing instructions (with instructions_updated_at / instructions_updated_by), memo count, its saved views (the tabs scoped to this notebook), and the two layers of instructions above it: the parent collection's as collection_instructions and the workspace's as workspace_instructions, each only when written. Call this to orient yourself before working in a notebook. Memos are NOT included: filter list_collection_memos or search_memos by notebook. Pass include_instructions_history: true for the last ten writes to the notebook's instructions. Pass notebook as a notebook id (from list_notebooks, get_collection, or get_memo). Requires OAuth.

notebook string*include_instructions_history boolean

create_notebook

sign-in required

Create a notebook inside a collection you own or can edit. NOT idempotent: if an active notebook with the same (case-insensitive) name already exists in that collection, the call fails with a conflict so agents can't fork. Pass collection (id, slug, or name) plus a name. Pass instructions to give the notebook STANDING INSTRUCTIONS at creation: author-written guidance on how it is meant to be used, handed to whoever (human or agent) works in it later. Set or edit them afterwards with update_notebook, and read them back with get_notebook or list_notebooks (include_instructions: true). Lifecycle is create / archive / unarchive (no delete from MCP). Requires OAuth.

collection string*name string*description stringinstructions string

update_notebook

sign-in required

Edit a notebook's name, description, and/or STANDING INSTRUCTIONS (the author-written brief handed to anyone working in this notebook). The memos and their memberships are untouched. Pass the notebook id (from list_notebooks or get_memo) plus a new name, description, and/or ONE instructions edit: instructions replaces the text wholesale (read it first with get_notebook; empty string clears description or instructions), append_instructions adds to it, or patch_instructions applies a unified diff. Every instructions write is recorded with its author and time. NOT idempotent against name clashes within the collection. Requires OAuth and edit access to the parent collection.

notebook string*name stringdescription stringinstructions stringappend_instructions stringpatch_instructions string

update_workspace

sign-in required

Write a workspace's STANDING INSTRUCTIONS: the preamble every agent working anywhere in the workspace should read first (how collections are named, what the standard notebooks mean, where to look for more, what not to restructure). This is the top of the hierarchy; update_collection and update_notebook write the two layers below it, and every collection or notebook read echoes this text as workspace_instructions. Three ways to edit, exactly one per call: instructions replaces the whole text (read it first), append_instructions adds a rule without resending the rest, and patch_instructions applies a unified diff for a one-line change. Every write is recorded with its author and time (list_workspaces shows instructions_updated_by; include_instructions_history returns earlier versions). Workspace owner or admin only. Pass workspace as a name, slug, or id from list_workspaces. Requires OAuth.

workspace string*instructions stringappend_instructions stringpatch_instructions string

add_memo_to_notebook

sign-in required

Add a memo to a notebook. A notebook member must live in the notebook's parent collection, so this AUTO-ADDS the memo to that collection if it isn't already in it (the response says so). Idempotent. Pass memo and notebook ids, and optional pinned; pass memos (a list of ids) instead of memo to add several in one call. Requires OAuth, edit access to the memo, and edit access to the parent collection.

memo stringmemos arraynotebook string*pinned boolean

remove_memo_from_notebook

sign-in required

Remove a memo from one notebook. The memo stays in the parent collection (use remove_memo_from_collection to remove it from the collection, which also removes it from every notebook of that collection). Idempotent. Pass memo and notebook ids, or memos (a list of ids) instead of memo to remove several in one call. Requires OAuth, edit access to the memo, and edit access to the parent collection.

memo stringmemos arraynotebook string*

set_notebook_pin

sign-in required

Pin or unpin a memo within a notebook. Pinned members sort first in the notebook's list views. The memo must already be a member of the notebook (pinning a non-member is a no-op). Idempotent. Pass memo and notebook ids plus pinned (true to pin, false to unpin). Requires OAuth, edit access to the memo, and edit access to the parent collection.

memo string*notebook string*pinned boolean*

set_collection_pin

sign-in required

Pin or unpin a memo within a collection. A pinned memo leads that collection's list views, ahead of whatever the view's own sort says, which is what pinning means. The collection-level twin of set_notebook_pin. The memo must already be filed in the collection (pinning a non-member is a no-op, reported as changed: false). Idempotent. Pass memo and collection (an id, slug, or name) plus pinned (true to pin, false to unpin), or memos (a list of ids) instead of memo to pin several in one call. Requires OAuth and edit access to the collection: a pin arranges the collection, so it is gated like filing a memo into it rather than on the memo itself.

memo stringmemos arraycollection string*pinned boolean*

list_views

sign-in required

List the saved views on a collection: the shared tabs (smart lists, boards, canvases) that everyone who can see the collection also sees. Identify the collection by collection (an id, slug, or name). Pass notebook (a notebook id) to list only that notebook's tabs; otherwise you get every view on the collection, and each row names its notebook (null means a collection-level view spanning every memo in the collection, including memos filed in notebooks). Each view carries display (list, board, or canvas), its where filter in the same grammar the listing tools accept, its sort, a board's group_by field, its description (a note on what the view silently depends on, e.g. "only items with roadmap visibility = Yes appear here"; read it before building a similar view), its group_by_scope ("collection" reads the memo's values on ITS EDGE into this collection, "memo" reads the memo's own front matter, null resolves collection-first then memo) and columns layout, card_properties, and protected: every collection and notebook is guaranteed exactly one protected: true, display: "list" row (its List tab), ordinary to edit (rename, filter, sort, layout, reorder) but refused by delete_view. To read a view's memos, run list_collection_memos with the view's where (for a board, group the rows by its group_by field yourself). Every row also carries url, the link that OPENS that tab, and the response carries collection_url for the collection itself. A view is addressed as ?view=<view id> on its collection's page (a notebook view on the notebook's page), so you can hand a person a link straight to the board you built. The two reserved values list and canvas also work in that parameter and name the built-in tabs. Requires OAuth.

collection string*notebook string

save_view

sign-in required

Create or update a saved view on a collection. A view is a shared tab that everyone who can see the collection also sees (a smart list, a board, or a canvas), so save one when the team should keep it; for a one-off query use list_collection_memos with where instead. Omit view_id to create; pass it to update, which replaces the whole stored definition (omitted optional parts are cleared, so send the full view each time). display is "list", "board", or "canvas". where stores a front-matter filter in the shared grammar the listing tools accept; the view's membership is live, and a malformed filter is rejected with the same instructive error the listing tools give rather than saved broken. sort ({field, direction: "asc" | "desc"}) orders the view. A board additionally requires group_by (the field whose values become its columns), may carry group_by_scope to say which layer that field reads (see its own description), and may carry columns (the column order array plus a hidden array); all three are board-only. card_properties names the properties shown on each card or row: front-matter field names, or computed properties, which use a computed: prefix so they can never collide with a front-matter field of the same name: computed:notebook (the memo's notebook name in this collection), computed:created_at / computed:updated_at (the memo's Created at / Updated at timestamps), and computed:created_by / computed:updated_by (the memo's Created by / Last updated by people). A bare notebook here now names a memo's own front-matter field of that name; a view saved before this change still reads a stored bare notebook as the computed property, so always pass computed:notebook for the computed property going forward. Pass notebook (a notebook id in the collection) to put the tab on that notebook; otherwise the view is collection-level and spans every memo in the collection, including memos filed in notebooks. description is an optional note on the view (what it depends on, why a card might be missing), shown to people on the tab and to agents in list_views; it filters nothing. Any editor of the collection can save views. Updating the protected List view (see list_views) is ordinary EXCEPT display must stay "list" and notebook must stay whatever it already is, since either change would leave that collection or notebook without the List view it's guaranteed, so both are refused. The response carries url, the link that opens the saved view, so you can tell the person where it is. Requires OAuth.

collection string*notebook stringview_id stringname string*description stringdisplay string*where arraysort objectgroup_by stringgroup_by_scope stringcolumns objectcard_properties array

delete_view

sign-in required

Delete a saved view by view_id (get ids from list_views). The shared tab disappears for everyone who sees the collection; the memos in the view are not touched (a view is a saved way of looking, not a container). Any editor of the collection can delete its views, except the protected List view every collection and notebook is guaranteed one of, which this refuses to delete. Requires OAuth.

view_id string*

list_field_definitions

sign-in required

List a collection's PROPERTIES and, for each, the option set a board groups by. This is the schema, not values: list_fields tells you what one memo holds, this tells you what the collection has DEFINED. Each row carries field_name, field_type (select, person, date, number, text...), options (label + colour, for a select, each optionally with its own description), hidden, and description: what the property MEANS and when to set it, written by whoever defined it (e.g. "roadmap visibility = Yes is what puts an item on the roadmap board"). Read descriptions before setting values; write them with set_field_definition. options: null means the property exists but no option set was ever registered; [] means one was registered and is deliberately empty. A person property has NO options — its vocabulary is the workspace roster, returned on that row as people ({display, email, role}). Write the display string EXACTLY as listed (an email for the same person is accepted and stored as the display name); set_field refuses a person value that names nobody, because a name the app cannot resolve assigns work to no one while still looking assigned. Guests invited to a specific memo are accepted there too, though only members are listed here. Read this BEFORE building a board: a board's columns come from the registered options plus whatever values the loaded rows actually carry, so a value you set that is not a registered option only becomes a column while a card carries it. A column you name in a view's columns.order that is neither is dropped without warning. Identify the collection by collection (an id, slug, or name). Requires OAuth.

collection string*

set_field_definition

sign-in required

Define, describe, rename, or delete a collection PROPERTY — mutually exclusive modes on one tool, picked by which of options, rename_to, or delete you pass (at most one), plus description, which rides alongside options or stands alone. DESCRIBE (pass description, alone or with options): records what the property MEANS and when to set it, shown to people as a tooltip on the property and returned to agents by list_field_definitions. Alone, it changes nothing else (type and options stay as stored). Each option may carry its own description too. Empty string clears. Max 500 chars. DEFINE (pass options): sets the property's option set and type. This is how a board gets columns. Setting a value with set_field records the value and does NOT register it as an option, so a board grouped by that property shows a column only while some card carries the value. register the options here and the columns exist whether or not anything is in them yet. options REPLACES the whole set (send every option you want to keep, in the order you want them); an empty list clears them deliberately, which is different from never having set any. Each option is {label, color}, and color is genuinely optional: omit it and the option takes a stable colour derived from its label, so you only name one when you actually care. When you do name one it must be a palette token, and the schema's enum lists them all. REMOVING an option (leaving it out of the set) CLEARS that value from every card holding it, and the response reports how many in values_cleared — read the current options with list_field_definitions first so you don't drop one by accident. Renaming an OPTION LABEL here does NOT move memos already on the old label; use rename_field_option for that. RENAME (pass rename_to): renames the PROPERTY itself from field to rename_to, carrying its type, its options, and every memo's stored value across to the new name — a board keeps its columns and its cards. Refused with invalid_input if rename_to is already another property on this collection (rename or delete that one first). DELETE (pass delete: true): removes the property outright — its definition AND every memo's stored value under it in this collection are gone, reported as memos_cleared. NOT reversible; a memo's own front matter under the same name (if it happens to share it) is untouched. Requires edit access to the collection, and OAuth.

collection string*field string*description stringoptions arrayfield_type stringrename_to stringdelete boolean

rename_field_option

sign-in required

Rename ONE option on a collection property, carrying every memo already on the old label across to the new one. This is the difference between this tool and set_field_definition: renaming a label there edits the vocabulary only and leaves the cards behind on a value that is no longer an option, while renaming here moves the vocabulary and the values together, so a board's column keeps its cards. Identify the option by its exact current label (list_field_definitions shows them); not_found means no option on that property spells it that way. new_label is validated exactly as a saved option set is, so renaming onto a label another option on the SAME property already uses is refused rather than silently merging the two. Answers with the property's full option list plus memos_renamed, the number of cards whose value actually moved (0 is a perfectly good success: the label existed but nothing was on it). A renamed option carries its colour over untouched. Renaming reaches memos that are LOCKED or ARCHIVED, deliberately: this is the collection's own vocabulary staying consistent with the collection's own values, not an editorial change to anybody's memo, and skipping the frozen ones would leave them quietly disagreeing with everything else. Requires edit access to the collection, and OAuth.

collection string*field string*label string*new_label string*

duplicate_memo

sign-in required

COPY a memo into a brand new memo (new id, new URLs); the original is left in place. To RELOCATE an existing memo to another workspace without copying it (keeping the same id, history, and comments), use move_memo instead. The copy includes content, fields, images, embedded artifacts, and optionally comments. Any role (viewer, commenter, editor) can duplicate. Returns the new memo's role URLs. The copy lands in a workspace: pass workspace (name, slug, or id from list_workspaces) to choose; with keep_collections it adopts the source collections' workspace instead. If you belong to multiple workspaces and pass neither, the call returns a workspace_required disambiguation that lists them (it will NOT silently use whatever workspace you last opened in the web app). Ask the user which one, then retry with an explicit workspace, and remember their choice for the rest of this session so later writes pass it without asking again.

url stringid stringinclude_comments booleankeep_collections booleanworkspace string

get_memo_image_upload_url

sign-in required

Mint a one-shot signed URL the agent can PUT a RASTER image to (PNG, JPEG, WebP, or GIF). Returns { upload_url, image_url, markdown, expires_at }. NETWORK REQUIREMENT — READ BEFORE INVOKING: The PUT in step 2 below requires outbound HTTPS from your sandbox/runtime to *.fieldwerk.ai. Many MCP clients (notably Claude.ai web) sandbox the agent behind an egress proxy with an allowlist that does NOT include *.fieldwerk.ai by default. If your PUT returns HTTP 403 with a response header like x-deny-reason: host_not_allowed, the block is in your runtime's egress proxy — NOT in Fieldwerk — and retrying will fail the same way. When that happens: DO NOT retry. If your runtime can place the image's bytes into a tool call itself (a user attachment it already holds), use upload_memo_image_inline instead: the bytes travel inside the MCP call, so no outbound request is needed (images up to 1 MB). Otherwise tell the user verbatim: "Image upload requires outbound network access to *.fieldwerk.ai. On Claude.ai, this is in Settings → Privacy → Network (or similar in other MCP clients). Please enable egress to *.fieldwerk.ai, then ask me to retry." Wait for the user to confirm before retrying. HOW TO USE THIS TOOL: 1. Call this tool with mime_type matching the image you'll upload. If the image is generated at higher resolution than its intended display size (e.g. an AI-generated 1024×1024 image meant to display at ~512px, or a screenshot from a retina display), pass pixel_density: 2 (or 3). Fieldwerk will render it at half (or third) its natural width so it stays sharp without overflowing the column. 2. From your sandbox / Bash, PUT the image bytes to upload_url with Content-Type: <mime_type>. Example: curl -X PUT --data-binary @file.png -H 'Content-Type: image/png' '<upload_url>'. The bytes go directly to our worker — they never traverse this MCP channel, never go through your output tokens, never get truncated by tool-call size caps. This is the preferred way to upload an image from an agent context; upload_memo_image_inline is the fallback for runtimes that cannot make the PUT. 3. On success the worker returns 200. The image is then live at image_url. Splice the returned markdown into the memo via append_to_memo, replace_section, etc. IMPORTANT — SVG is NOT supported (security policy: SVG can carry executable script). The URL is signed to the specific mime_type you request; PUTting bytes of a different format will be rejected. If you do not already have a raster image to upload, DO NOT try to fabricate one by generating SVG/XML markup — the PUT will be rejected. Instead, ask the user to upload the image themselves (drag-and-drop in the memo editor) or to give you a URL to an existing raster image (use upload_memo_image_from_url for that case). Diagrams or other visuals the user wants are usually better expressed as text/markdown content (lists, headings, tables) than as constructed images. The URL expires in 10 minutes and is single-use in spirit (re-PUT overwrites the same R2 object). Max body size 5 MB. Requires Clerk OAuth and edit role on the memo. IMAGE LAYOUT: an image's size and placement live in its URL's query string, so ![alt](url) stays plain markdown. ?size=N (integer 10 to 100) makes the image N% of the width it has available (the column, or the wider breakout when also ?wide=1), stretching a small image if asked. ?align=left|center|right places it (absent: fit images sit left, wide images centred). ?wide=1 lets a big image break out of the column. Absent flags mean natural size. You can set these at upload with the size, align and wide inputs (the returned markdown carries them), or change them on an existing image by rewriting its URL in the memo body with any write tool. ?px=N is the display width a retina upload was made for and is stamped for you from pixel_density; leave it alone.

url stringid stringmime_type string*alt stringpixel_density integersize integeralign stringwide boolean

upload_memo_image_from_url

sign-in required

Upload a RASTER image (PNG, JPEG, WebP, or GIF) to a memo by giving the server a URL to fetch from. Use this when an image source URL is available (e.g. an image-generation tool returned a hosted URL) — it sidesteps the MCP message-size cap on the inline-base64 path. The server fetches the URL with strict guardrails (https only, our own zones / private IPs denied, manual redirects with per-hop revalidation, 5 MB cap, 10-second timeout) and stores the bytes as a memo image. Returns the hosted image URL plus pre-built markdown ready to splice into a follow-up write tool. IMPORTANT — source_url must point to a RASTER image. SVG is NOT supported (security policy) and will be rejected by the post-fetch MIME check. If the user is asking for a diagram or visual and you don't have a raster image URL, DO NOT try to fabricate one by generating SVG/XML — instead, ask the user to upload an image themselves or provide a URL to an existing raster image. Diagrams the user wants in their memo are usually better expressed as text/markdown content (lists, headings, tables) than as constructed images. Requires Clerk OAuth and edit role on the memo. Rate-limited to 60 fetches per hour per user. IMAGE LAYOUT: an image's size and placement live in its URL's query string, so ![alt](url) stays plain markdown. ?size=N (integer 10 to 100) makes the image N% of the width it has available (the column, or the wider breakout when also ?wide=1), stretching a small image if asked. ?align=left|center|right places it (absent: fit images sit left, wide images centred). ?wide=1 lets a big image break out of the column. Absent flags mean natural size. You can set these at upload with the size, align and wide inputs (the returned markdown carries them), or change them on an existing image by rewriting its URL in the memo body with any write tool. ?px=N is the display width a retina upload was made for and is stamped for you from pixel_density; leave it alone.

url stringid stringsource_url string*alt stringmax_bytes numbersize integeralign stringwide boolean

upload_memo_image_inline

sign-in required

Upload a RASTER image (PNG, JPEG, WebP, or GIF) to a memo by sending its bytes as base64 INSIDE this tool call. The server decodes, checks and stores them; the client makes no network request other than the MCP call itself. Returns { image_url, markdown, bytes, mime_type }; splice markdown into the memo with append_to_memo, replace_section, etc. WHEN TO USE IT, in order of preference: 1. The image is already hosted somewhere: upload_memo_image_from_url. 2. Your sandbox can make an outbound PUT to *.fieldwerk.ai: get_memo_image_upload_url. Preferred for anything large; the bytes never enter the MCP channel. 3. Your runtime holds the image (a user attachment, say) but its egress rules block that PUT: THIS tool. Pass the attachment's bytes as base64 in data (a data:image/png;base64, prefix is fine). Only do this when the runtime supplies the base64; never type an image out token by token. LIMITS: at most 1000000 decoded bytes (about 1 MB; base64 adds a third on top, and MCP clients cap tool-call arguments). Over that the call fails with inline_image_too_large and you should use path 1 or 2. mime_type must match the bytes: they are sniffed, and SVG or any other non-raster format is refused. If the image was made at higher resolution than its display size (a retina screenshot, a 1024px generated image meant for ~512px), pass pixel_density: 2 (or 3). Requires Clerk OAuth and edit role on the memo. Rate-limited to 60 uploads per hour per user. IMAGE LAYOUT: an image's size and placement live in its URL's query string, so ![alt](url) stays plain markdown. ?size=N (integer 10 to 100) makes the image N% of the width it has available (the column, or the wider breakout when also ?wide=1), stretching a small image if asked. ?align=left|center|right places it (absent: fit images sit left, wide images centred). ?wide=1 lets a big image break out of the column. Absent flags mean natural size. You can set these at upload with the size, align and wide inputs (the returned markdown carries them), or change them on an existing image by rewriting its URL in the memo body with any write tool. ?px=N is the display width a retina upload was made for and is stamped for you from pixel_density; leave it alone.

url stringid stringdata string*mime_type string*alt stringpixel_density integersize integeralign stringwide boolean

get_memo_image_url

Get a short-lived (5-minute) signed URL that lets you DOWNLOAD a raster image embedded in a memo, for handing the file to something else. To LOOK at an image yourself, call read_memo_image instead: it returns the pixels inline, which works in every client, whereas this URL is only useful if your runtime can fetch URLs. The memo's images are visible in its markdown as ![alt](https://img.fieldwerk.ai/...), but those public URLs are behind hotlink protection and cannot be fetched from non-browser contexts. This tool mints a time-limited URL that bypasses that restriction. Pass the image_url exactly as it appears in the memo's markdown, layout flags and all (?size=, ?align=, ?wide=, ?px= are ignored when locating the object). The URL must be on img.fieldwerk.ai and must belong to the memo you're reading. The returned read_url can be fetched with a plain GET (no auth headers needed). Requires any access role (read, comment, or edit) on the memo. IMAGE LAYOUT: an image's size and placement live in its URL's query string, so ![alt](url) stays plain markdown. ?size=N (integer 10 to 100) makes the image N% of the width it has available (the column, or the wider breakout when also ?wide=1), stretching a small image if asked. ?align=left|center|right places it (absent: fit images sit left, wide images centred). ?wide=1 lets a big image break out of the column. Absent flags mean natural size. You can set these at upload with the size, align and wide inputs (the returned markdown carries them), or change them on an existing image by rewriting its URL in the memo body with any write tool. ?px=N is the display width a retina upload was made for and is stamped for you from pixel_density; leave it alone.

url stringid stringimage_url string*

read_memo_image

Read a raster image embedded in a memo and return it INLINE as MCP image content, so you can look at it directly: screenshots, diagrams, photos. This is the tool to use when you need to see what an image shows; it works in every client because nothing has to fetch a URL. (get_memo_image_url is for downloading the file instead.) The result has two blocks: a text block with metadata (mime, width, height, the stored source_width/source_height, and resized), then the image itself. Large images are downscaled to at most 1568px on the long edge before being returned, which keeps text in screenshots readable while bounding the payload. PNG, JPEG, WebP and GIF are supported (a GIF is returned as stored; only its first frame is meaningful to a vision model). Only call this for an image you actually need to inspect: each call costs vision tokens and up to 1.5MB of transfer. get_memo never inlines images itself. Pass the image_url exactly as it appears in the memo's markdown, layout flags and all (?size=, ?align=, ?wide=, ?px= are ignored when locating the object). The URL must be on img.fieldwerk.ai and must belong to the memo you're reading. Requires any access role (read, comment, or edit) on the memo.

url stringid stringimage_url string*

get_memo_artifact_upload_url

sign-in required

Mint a one-shot signed URL the agent can PUT an HTML artifact to (self-contained HTML+CSS+JS, ≤1 MB). Returns { upload_url, artifact_url, markdown, expires_at }. WHAT IS AN ARTIFACT — interactive content the user wants to embed in their memo: a chart, a mini-app, a calculator, a visualisation, a tic-tac-toe board. The HTML you provide will render in a sandboxed iframe on a separate origin (fieldwerkartifacts.com) so it can run scripts safely without touching the user's Fieldwerk session. Same shape as Claude.ai's chat-side Artifacts. NETWORK REQUIREMENT — READ BEFORE INVOKING: the PUT requires outbound HTTPS from your sandbox to *.fieldwerk.ai. If the PUT returns HTTP 403 with x-deny-reason: host_not_allowed, your runtime is blocking it — tell the user to enable outbound to *.fieldwerk.ai in their MCP client's network settings (Claude.ai → Settings → Privacy → Network, or similar). Wait for them to confirm; don't retry until then. HOW TO USE THIS TOOL: 1. Call this tool. Returns upload_url (signed, 10-min TTL) and artifact_url (the eventual https URL the iframe will load from). 2. From your sandbox / Bash, PUT the HTML bytes to upload_url with Content-Type: text/html. Example: curl -X PUT --data-binary @artifact.html -H 'Content-Type: text/html' '<upload_url>'. Bytes go directly to our worker — never through your output tokens, never truncated by tool-call size caps. 3. Splice the returned markdown (a fenced ``artifact block) into the memo via append_to_memo / replace_section / etc. CONTENT REQUIREMENTS: • A self-contained HTML document. Inline <script> and <style> are fine. NO external scripts, stylesheets, or fonts — the artifact origin's CSP blocks them (script-src 'self' 'unsafe-inline', no CDNs). • NO network calls — connect-src 'none'. The artifact cannot fetch(), XHR, WebSocket, or WebRTC anywhere. • NO forms, popups, top-navigation, microphone, camera, geolocation, payment. Sandbox + Permissions-Policy lock these off. • Images must be inline (data: / blob:` URIs) or omitted. • Size cap: 1 MB. Keep artifacts focused — they're not full apps. If the user asks for something the artifact CSP can't allow (loading external data, making API calls), tell them so explicitly and suggest an alternative — don't silently strip the offending bits from the HTML and upload anyway. Requires Clerk OAuth and edit role on the memo.

url stringid stringheight integertitle string

get_asset_upload_url

sign-in required

Mint a one-shot signed URL to upload a FILE and attach it as an asset to a memo, collection, or notebook (provide exactly one target). Returns { asset_id, upload_url, expires_at }. HOW TO USE: 1) call this with the file's content_type (and ideally filename); 2) from your sandbox/Bash, PUT the bytes to upload_url with a matching Content-Type header, e.g. curl -X PUT --data-binary @file.pdf -H 'Content-Type: application/pdf' '<upload_url>'. Bytes go straight to our worker — they never traverse this MCP channel. On success (HTTP 200) the asset is live and listed by list_assets / get_memo. The PUT needs outbound HTTPS to *.fieldwerk.ai. Limits: 25 MB; allowed types include pdf, office docs, csv/tsv, txt/md, json, zip, common audio/video, and images. html/svg/executables are rejected. URL expires in 10 minutes. Requires OAuth + edit access to the target.

content_type string*filename stringsize integertitle stringdescription stringmemo stringcollection stringnotebook string

add_asset_from_url

sign-in required

Attach a FILE to a memo, collection, or notebook (exactly one target) by giving the server a URL to fetch — use this when you already have a hosted file URL (it sidesteps the MCP message-size cap and you don't need to PUT bytes yourself). The server fetches the URL with strict guardrails (https only, our own zones / private IPs denied, manual redirects, 25 MB cap, 10-second timeout) and stores it as an asset. You MUST pass content_type (must be an allowed type: pdf, office docs, csv/tsv, txt/md, json, zip, audio/video, images). Returns the created asset. Requires OAuth + edit access to the target.

source_url string*content_type string*filename stringtitle stringdescription stringmemo stringcollection stringnotebook string

list_assets

sign-in required

List the assets (files + links) attached to a memo, collection, or notebook (exactly one target). Requires read access to that target.

memo stringcollection stringnotebook string

get_asset

sign-in required

Get one asset's metadata by id. For file assets the response includes a short-lived download_url. Readable if you can read any parent it's attached to.

asset string*

attach_asset

sign-in required

Attach an EXISTING asset to another memo, collection, or notebook (exactly one target). Gated by edit access to that target.

asset string*memo stringcollection stringnotebook string

remove_asset

sign-in required

Detach an asset from a memo, collection, or notebook (exactly one target). The asset itself is not deleted — use delete_asset for that. Gated by edit access to the target.

asset string*memo stringcollection stringnotebook string

delete_asset

sign-in required

Permanently delete an asset (owner only). Removes it from every parent and frees its storage. Use remove_asset to only detach from one place.

asset string*

query_asset

sign-in required

Query a CSV/TSV FILE asset by id: optional column projection, simple AND filters (eq / neq / contains), and a row limit. Returns matching rows as JSON plus matched (total before limit). Readable if you can read the asset. Server-side parse, capped at 500 rows — for large datasets pull the columns/filters you need rather than the whole file.

asset string*columns arraywhere arraylimit integer

lock_memo

sign-in required

Lock a memo. While locked, no body, field, comment, or collection-membership writes succeed from any surface (REST, MCP, web). Reads continue to work normally and existing comments stay fully visible. Available to the owner or a member/collaborator with edit access, via Clerk OAuth. Idempotent: locking an already-locked memo returns the existing locked_at. To unlock, call unlock_memo and follow the returned URL — unlock cannot be done from MCP because the unlock action requires a human confirmation in the browser.

url stringid string

unlock_memo

sign-in required

Initiate unlocking a memo. Does NOT unlock from this tool — returns a one-shot URL the user must open in their browser, where the unlock confirmation dialog pops automatically and they have to click Unlock to confirm. This friction is deliberate: a locked memo is a protective intent the owner set, and an agent shouldn't be able to undo it for them. Available to the owner or a member/collaborator with edit access, via Clerk OAuth. Use when the user asks you to unlock a memo. RELAY THE RETURNED unlock_url VERBATIM TO THE USER and tell them to open it; do not retry, do not call any write tool against this memo until they confirm they've unlocked it.

url stringid string

archive_memo

sign-in required

Archive a memo. The memo stays in place with access and memberships intact, but becomes read-only: no body, field, comment, or collection-membership writes succeed from any surface until it is unarchived. It is hidden from default lists and searches (pass include_archived: true to list tools to see it) and never appears in published collections. NOT the same as locking: archive is an organisational state, the lock is a protective one, and either freezes writes on its own. Available to the owner or a member/collaborator with edit access, via Clerk OAuth. Idempotent: archiving an already-archived memo returns the existing archived_at. Reversible with unarchive_memo.

url stringid string

unarchive_memo

sign-in required

Unarchive a memo: restore it to the normal editable state and back into default lists. The counterpart to archive_memo. Unlike unlock_memo this DOES perform the change: archive is an organisational state, not a protective one, so no browser confirmation is required. Available to the owner or a member/collaborator with edit access, via Clerk OAuth. Idempotent: unarchiving a memo that isn't archived is a no-op.

url stringid string

delete_memo

sign-in required

Delete a memo. CLAIMED memos go to the user's trash (recoverable from /trash) for a window that depends on the workspace's plan. Calling again on the same id while it's already in trash removes it from the user's trash view; the content is NOT destroyed early and is still permanently deleted on its existing schedule. UNCLAIMED memos are hard-deleted immediately. If the memo is unclaimed, edit access via its link is enough. If the memo is claimed, you must be signed in via OAuth as the owner. Edit access alone is not sufficient. LOCKED memos cannot be deleted (TRA-345): the lock contract holds for destructive ops, so the owner must unlock_memo first. Soft-delete (to trash) and the second-call removal from trash both apply this guard.

id stringurl string

claim_memo

sign-in required

Attach an orphan memo to your account. Requires OAuth. Pass the memo's edit URL (or id).

url stringid string

transfer_memo_ownership

sign-in required

Transfer a memo to a new owner (CURRENT owner only). This changes only the owner, the memo stays in its current workspace. To MOVE a memo to a DIFFERENT workspace (optionally handing it to someone there at the same time), use move_memo instead. The new owner must be an existing Fieldwerk account (new_owner_email). You (the previous owner) are dropped to an Editor grant — you keep edit access but lose ownership and management. This is a deliberate, hard-to-undo action; confirm intent before calling. Requires OAuth.

url stringid stringnew_owner_email string*

move_memo

sign-in required

Move a memo to another workspace (CURRENT owner only). This is a MOVE, not a copy: the memo keeps its same id and its full edit history, versions, comments, reactions, images, and embedded artifacts all come with it. After the move the memo is removed from the source workspace entirely, and its link sharing and direct grants are reset to the destination's defaults. Identify the memo with url (or id). Pass workspace for the destination (a name, slug, or id from list_workspaces); it must differ from the memo's current workspace, and is required (a move places the memo into a specific workspace, never the one you last opened in the web app). When the user belongs to several workspaces, ask which destination they mean before calling, and remember that choice for the rest of the session. By default you become the new owner; pass new_owner_email to hand it to another member (they must be an active, non-guest member of the destination, as must you). Optionally file it into destination collections: collection_ids for existing ones, new_collections to create fresh ones (each a name, or { name, description? }), or recreate_source_collections: true to recreate the memo's current collections (name AND description) in the destination. New collections are owned by the new owner by default; pass new_collection_owner_email to assign them to another member (find people with list_workspace_members). Existing collections are left untouched. A workspace admin can turn moving content out of a workspace off (it is off by default): when it is, this tool changes nothing and answers ok: false with error: "workspace_setting_disabled", which is a settled answer, not a transient failure. Do not retry it or reach for another tool to work around it; tell the person their workspace admin has to allow the move. This is a deliberate, hard-to-undo action; confirm intent before calling. Requires OAuth.

url stringid stringworkspace string*new_owner_email stringcollection_ids arraynew_collections arraynew_collection_owner_email stringrecreate_source_collections boolean

restore_memo

sign-in required

Restore one of YOUR memos from the trash (undo a delete). Owner-only; the memo must currently be trashed. Idempotent — restoring a memo that isn't trashed is a no-op. An ARCHIVED memo unarchives on restore by default so it comes back visible and editable; pass keep_archived: true to restore it still archived (read-only, hidden from default lists). The counterpart to delete_memo. Requires OAuth.

url stringid stringkeep_archived boolean

empty_trash

sign-in required

Clear YOUR trash in ONE workspace: every trashed memo you own there is hidden from your trash view. Nothing is destroyed early. Each memo is still permanently deleted on the plan's schedule, counted from when it entered the trash, and on paid workspaces workspace administrators can recover cleared memos until that window ends. Returns removed_count and removed_ids (names kept for API stability; they are the memo ids CLEARED from view, not destroyed). Confirm intent with the user before calling. Scope: only memos YOU own are ever touched, never a teammate's trashed memos in the same workspace. Pass workspace (a name, slug, or id from list_workspaces) to say WHICH workspace's trash to clear. If you belong to several and pass none, the call returns a workspace_required disambiguation listing them rather than sweeping all of them: ask the user which they mean and retry with an explicit workspace. There is no way to list the trash over MCP first, so this deliberately will not guess. Requires OAuth.

workspace string

clone_collection

sign-in required

Clone a PUBLISHED collection into one of your workspaces. Deep-copies the collection and its memos — content, images, and artifacts — but NOT comments, and records where it came from. You must be signed in and not already a member of the source collection's workspace, and the owner must have enabled cloning on it. This is the server-side, lossless equivalent of recreating it by hand (images/artifacts can't be copied manually). Pass collection as the published collection's URL or id. Optionally: workspace (name/slug/id) to choose where it lands (defaults to your active workspace), name to rename the copy, memo_ids to copy a subset (defaults to all), and password if the collection link is password-protected. Requires OAuth.

collection string*workspace stringname stringmemo_ids arraypassword string

archive_collection

sign-in required

Archive a collection owned by the authed user. Archiving is the app's way of removing a collection: it stops appearing in the chip-row picker on memos and drops out of list_collections (unless you pass include_archived: true). It is NOT a delete and is reversible. The collection's memos and their memberships are preserved, so nothing is unlinked or deleted, and you can restore it later with unarchive_collection. Pass the collection id (from list_collections). Allowed for the owner OR a manager (see can_manage). Requires OAuth.

collection string*

unarchive_collection

sign-in required

Restore a previously archived collection so it appears in list_collections and the memo chip-row picker again. Pass the collection id (use list_collections with include_archived: true to find archived ids). NOT idempotent against name clashes: if another active collection of yours took this collection's (case-insensitive) name while it was archived, the call fails with a conflict error, so rename one of them first. Allowed for the owner OR a manager (see can_manage). Requires OAuth.

collection string*

restrict_collection

sign-in required

Restrict a collection: set its workspace share to none (only people with a direct grant, and the owner, reach it) and, by default, also turn its public link off (mute_link: false keeps the link). Manager only. To un-restrict, use set_collection_workspace_share with a level. Requires OAuth.

collection string*mute_link boolean

revoke_collection_access

sign-in required

Remove a person's or group's direct grant on a collection (manager only). Identify with user_email or group. Idempotent. Does NOT change the workspace-wide share or the published link. Requires OAuth.

collection string*user_email stringgroup string

get_collection_sharing

sign-in required

Read a collection's sharing posture (manager only): whether it's published (read-only link) plus its slug and password, the workspace-wide share level, and every direct grant (people/groups with their level). Use this before changing a collection's sharing. Requires OAuth.

collection string*

set_collection_grant

sign-in required

Grant (or update) a person or group access to a collection (manager only). Identify with user_email (existing account) or group (id or name in the collection's workspace). level: view / comment / edit / admin. Granting admin is OWNER-only (admins can't promote other admins). Requires OAuth.

collection string*user_email stringgroup stringlevel string*

set_collection_workspace_share

sign-in required

Set how the whole workspace can access a collection (manager only). level: none (not shared with the workspace) / view / comment / edit. manage: true additionally lets every non-guest member MANAGE the collection (only valid with a non-none level). Requires OAuth.

collection string*level string*manage boolean

publish_collection

sign-in required

Publish or unpublish a collection as a READ-ONLY public link (manager only). access: "view" publishes (anyone with the link reads its memos read-only), "none" unpublishes. Optionally set slug (the /c/<slug> alias; empty string clears it) and password (empty string clears it). A collection can only be published read-only — commenting and editing live on each memo's own link. Returns url, the public link to hand to the user (null when unpublished). Requires OAuth.

collection string*access string*slug stringpassword string

transfer_collection_ownership

sign-in required

Transfer a collection to a new owner (CURRENT owner only). The new owner must be an existing Fieldwerk account in the collection's workspace (new_owner_email). You (the previous owner) drop to an Editor grant. This is hard to undo; confirm intent before calling. Requires OAuth.

collection string*new_owner_email string*

archive_notebook

sign-in required

Archive a notebook. Archiving drops it from list_notebooks (unless you pass include_archived: true) but is NOT a delete and is reversible with unarchive_notebook. The notebook's memos and their memberships are preserved. Pass the notebook id. Requires OAuth and edit access to the parent collection.

notebook string*

unarchive_notebook

sign-in required

Restore a previously archived notebook so it appears in list_notebooks again. NOT idempotent against name clashes: if another active notebook in the collection took its (case-insensitive) name while it was archived, the call fails with a conflict. Pass the notebook id. Requires OAuth and edit access to the parent collection.

notebook string*

list_workspace_members

sign-in required

List the people in a workspace you can hand things to — active, non-guest members only (owners, admins, editors; guests are excluded). Returns [{ email, display, role }]. Use it to pick a new_owner_email or new_collection_owner_email for move_memo, or for any other 'assign to a member' choice. With one workspace it uses that; if you belong to several it won't guess (it does not follow the web app's current workspace), so pass workspace (a name, slug, or id from list_workspaces). Requires OAuth.

workspace string

describe_workspace_member

sign-in required

A workspace MANAGER's report on one member: who they are (email, display, role, status, joined, last seen), what they OWN in this workspace (collections with memo counts, live and archived memo counts, notebooks), what they were GRANTED (direct collection grants, group memberships), everywhere a person value still NAMES them (references.collection_fields: board values per collection and field, with memo ids; references.front_matter: memo front matter per field; references.views: saved views whose filter mentions them), and their ACTIVITY (memos created, edits, comments over 30 and 90 days; last edit and last comment). possible_same_person lists other members with the same display name, the two-logins-one-human case. Use it before changing someone's role, removing them, or moving a person from one identity to another: the references section is the list of what set_fields_bulk and save_view would have to rewrite. Read-only; matching on id, email and display name so legacy name values are found too. Pass member as an email, display name, or user id (list_workspace_members has them); an ambiguous name is refused with the candidates. With one workspace it uses that; if you belong to several, pass workspace (name, slug, or id). Workspace owner or admin only. Requires OAuth.

member string*workspace string

set_collection_favorite

sign-in required

Favorite or unfavorite one of your collections (owner only). Favorited collections pin to the top of your sidebar. favorite: true favorites, false unfavorites. Identify the collection by id, slug, or name. Requires OAuth.

collection string*favorite boolean*

set_default_collection

sign-in required

Make one of your collections the default that NEW memos are filed into (owner only; the default is per workspace). The collection can't be archived. Identify it by id, slug, or name. Requires OAuth.

collection string*

rename_collection

sign-in required

Alias of update_collection, kept for older clients; prefer that name. Edit a collection's name, description, and/or STANDING INSTRUCTIONS (the author-written brief for anyone working in it: how it is meant to be used, what belongs in it, the conventions to follow). Allowed for the owner OR a manager (workspace owner/admin, a workspace-wide Manage share, or an admin grant), the same set that may share it. The memos and their memberships are untouched. Pass the collection id (from list_collections, where can_manage tells you if you may) plus a new name, description and/or ONE instructions edit: instructions replaces the text wholesale (read the current text with get_collection first; empty string clears description or instructions), append_instructions adds to it without resending the rest, or patch_instructions applies a unified diff for a one-line change. Every instructions write is recorded with its author and time (see instructions_updated_by on reads, and get_collection with include_instructions_history). NOT idempotent against name clashes: if another active collection with the owner's (case-insensitive) name already exists in the workspace, the call fails with a conflict. Requires OAuth.

collection string*name stringdescription stringinstructions stringappend_instructions stringpatch_instructions string

rename_notebook

sign-in required

Alias of update_notebook, kept for older clients; prefer that name. Edit a notebook's name, description, and/or STANDING INSTRUCTIONS (the author-written brief handed to anyone working in this notebook). The memos and their memberships are untouched. Pass the notebook id (from list_notebooks or get_memo) plus a new name, description, and/or ONE instructions edit: instructions replaces the text wholesale (read it first with get_notebook; empty string clears description or instructions), append_instructions adds to it, or patch_instructions applies a unified diff. Every instructions write is recorded with its author and time. NOT idempotent against name clashes within the collection. Requires OAuth and edit access to the parent collection.

notebook string*name stringdescription stringinstructions stringappend_instructions stringpatch_instructions string