Permissions and Tools

Each MCP tool checks 4 things before it touches data:

  1. The bearer token is a valid, unexpired agent key.
  2. The key holds the permission of the tool.
  3. The current organization role of the key owner still allows that permission.
  4. The plan of the organization includes the tool.

tools/list shows a key only the tools that pass these checks, and a call to a hidden tool fails with tool 'NAME' not found. whoami and list_workspaces need no permission, so every key sees them. prompts/list works the same way: a key sees a prompt only when it can use every tool in the steps of the prompt.

Workspace scope comes after the permission checks. A workspace in another organization, or outside the member scope of the owner, returns not_found: no such workspace. A call never returns data from another tenant.

Workspace arguments

Most tools are workspace-scoped.

Drafts

When Published pages only is on, a key reads DRAFT and IN REVIEW articles only with article.create, article.update, article.propose, article.publish, or article.place. The setting is in Admin > Settings > API, and new organizations have it on. whoami shows readsDrafts.

A key without one of these permissions gets published articles only from search_docs, semantic_search, list_articles, and the sidebar resource, also with published_only set to false. get_article and the article resource answer not_found for a draft and for a slug that redirects to one, and the suggestions name published articles only. The analytics tools don't follow the setting. See Agent Keys.

Errors

A failed call returns a tool result with isError set to true. The text starts with an error code and a colon, then the message. The REST API returns the same code for the same failure. When the error has details, a second line holds them as JSON:

stale_revision: the article changed after revision 8f3a2c1b9d0e. Read it again and redo the change
details: {"baseRevision":"8f3a2c1b9d0e","currentRevision":"1a2b3c4d5e6f"}

The details name the facts for the next step: the field and the allowed values of a bad argument, close slugs for an unknown article, or the anchors of an article. A permission error names the permission in the message and in details.permission, for example permission_denied: this key lacks the article.publish permission. A plan error names the plan feature in details.feature. The REST API reference lists the error codes.

Field reference conventions

Fields go in the params.arguments object of a tools/call. The tables below use these conventions.

Permission groups

Group Permission Enables
None none whoami, list_workspaces, workspaces.json resource
Content search search_docs, semantic_search, report_content_gap
Content article.read list_articles, get_article, report_content_gap, article and sidebar resources
Content article.create create_article
Content article.update update_article for the text, title, section label, and visibility
Content article.publish status changes through update_article
Content article.propose propose_article_edit, list_proposals
Content upload.image upload_image
Landing landing.read get_landing, landing.json resource
Landing landing.propose propose_landing_edit, list_proposals
Landing landing.update set_landing, autofill_landing
Navigation section.create create_section
Navigation article.place place_article
Changelog changelog.read list_changelog, get_changelog, changelog.json resource
Changelog changelog.create create_changelog
Changelog changelog.update update_changelog, and drafts in list_changelog and get_changelog
Changelog changelog.publish publish or unpublish changelog entries
Changelog changelog.delete delete_changelog
Workspaces workspace.create create_workspace
Workspaces workspace.update update_workspace
Workspaces workspace.delete delete_workspace
Workspaces workspace.read No tool needs it. list_workspaces works with every key
OpenAPI openapi.read get_openapi_status, list_openapi_pages, openapi resource
OpenAPI openapi.attach attach_openapi_spec
OpenAPI openapi.sync sync_openapi
OpenAPI openapi.detach detach_openapi_spec, detach_openapi_page
OpenAPI openapi.layout create_openapi_section, place_openapi_page
Analytics analytics.read agent_insights, get_analytics_report, get_content_insights

Plans

The workspace tools and the analytics tools agent_insights, get_analytics_report, and get_content_insights need a paid plan, the Open source plan, or an active free trial. Every other tool works on every plan, and that includes the OpenAPI tools. When the plan doesn't include a tool, tools/list hides it.

A paid feature inside an allowed tool fails with upgrade_required, for example internal visibility in update_article, or one more workspace than the plan allows in create_workspace. Over REST, the same refusal is HTTP 402 upgrade_required, and details.feature names the feature.

Review of agent changes

An organization can review the changes that agent keys make to content that readers see. An admin turns it on in Admin > Settings > API > Review agent changes, and every new organization starts with it on. While it is on, these writes file a proposal for an editor and change nothing for readers:

Tool Writes that wait for review
update_article A change to a published article, and a status that publishes a draft
place_article A move of a published article
set_landing Every change of the landing page
create_changelog An entry with status published
update_changelog A change to a published entry, and a status that publishes an entry
delete_changelog A delete of a published entry
update_workspace Every change of the workspace settings. Only an admin can approve it
attach_openapi_spec, sync_openapi A spec that changes the stored spec or the API reference pages
detach_openapi_spec A detach while a spec or a generated page exists
place_openapi_page A move of a published API reference page

delete_workspace has no proposal form, so it fails with review_required. create_workspace fails with review_required when it passes access_mode public or listed true. Create the workspace as internal, then call update_workspace, which files a proposal. Drafts, new draft articles, new sections, and image uploads still save at once, and so do detach_openapi_page and create_openapi_section.

A write that waits for review returns pendingReview instead of its usual result:

{
  "pendingReview": true,
  "message": "An editor must approve this change before readers see it. Give the user the reviewUrl.",
  "proposal": {
    "id": 42, "objectType": "article", "status": "pending", "target": "article:17", "slug": "getting-started",
    "summary": "Publish getting-started: DRAFT to STABLE",
    "reviewUrl": "https://contextowl.co/admin/proposals?ws=platform&id=42",
    "baseRevision": "8f3a2c1b9d0e", "outcome": "created"
  }
}

Give the reviewUrl to the user, and don't send the write again.

Account tools

Tool Permission Result
whoami none The key, its organization and plan, the role of its owner, the workspaces it reaches, its permissions, the blocked permissions with the reason, and readsDrafts (JSON)

whoami takes no arguments and returns no email address or user id. Call it when an error names a workspace or a permission.

Content tools

Tool Permission Result
search_docs search { semantic, results, suggestions } with article and changelog hits (JSON)
semantic_search search The search_docs result, ranked by meaning (JSON)
list_articles article.read { articles, sections } (JSON)
get_article article.read Front matter and Markdown, plus the article object as structured content
create_article article.create { slug, status, nav, url, revision } of the new draft
update_article article.update { slug, status, nav, updatedAt, url, revision, previousRevision, changed, placed }, or pendingReview
propose_article_edit article.propose { id, status, slug, reviewUrl, baseRevision } of the pending proposal
list_proposals article.propose, landing.propose, or a permission whose writes can wait for review { proposals } as compact rows (JSON)
create_section section.create { key, label, created }
place_article article.place { slug, section, visibility, visibilityChanged }, or pendingReview
upload_image upload.image { url, absoluteUrl }

Fields, in addition to the shared workspace field.

Tool Field Type Required Notes
search_docs query string yes Search text in the words that the docs use. The search uses the first 256 characters.
search_docs question string no The question of the user in the user's own words, without names, email addresses, or secrets. The docs team sees it. The search uses query, not question.
search_docs limit integer no 1 to 50. Defaults to 10.
search_docs published_only boolean no Leave out DRAFT and IN REVIEW articles. When whoami shows readsDrafts false, the result never holds them.
semantic_search query string yes Natural-language search text.
semantic_search question string no The question of the user, as for search_docs.
semantic_search limit integer no 1 to 50. Defaults to 10.
semantic_search published_only boolean no Leave out DRAFT and IN REVIEW articles. When whoami shows readsDrafts false, the result never holds them.
list_articles status string no Comma-separated statuses, such as DRAFT,IN REVIEW.
list_articles section_key string no Only the articles in this sidebar section. none lists the unplaced articles.
list_articles updated_since string no An RFC 3339 time, a date such as 2026-07-01, or a duration such as 7d or 24h. The rows then sort newest first.
list_articles published_only boolean no Leave out DRAFT and IN REVIEW articles. When whoami shows readsDrafts false, the result never holds them.
get_article slug string yes Slug from search_docs or list_articles. When whoami shows readsDrafts false, a DRAFT or IN REVIEW article is not found.
get_article section string no Anchor of one heading, from the anchors line. Returns that heading and its text, subheadings included.
get_article max_chars integer no 1,000 to 200,000. Defaults to 50,000.
get_article offset integer no Character offset to start from. Pass next_offset of a truncated read.
create_article title string yes Article title.
create_article slug string no URL slug. A slug in use fails with slug_taken. Defaults to a free slug made from the title.
create_article section_key string no Key of the sidebar section for the draft. Defaults to unplaced.
create_article section string no Section label shown on the article. Defaults to Guides. It doesn't place the article.
create_article markdown string no Body in Markdown. Defaults to empty.
update_article slug string yes Slug of the article to change.
update_article title string no New title.
update_article section string no New section label. It doesn't move the article. Use place_article for that.
update_article markdown string no New full body. Don't send it together with edits.
update_article edits object[] no 1 to 20 replacements, each { old, new }. Don't send them together with markdown.
update_article base_revision string no Revision from get_article. A changed article fails with stale_revision.
update_article allow_shrink boolean no Accept a body that removes most of the text. Defaults to false.
update_article status string no DRAFT, IN REVIEW, BETA, STABLE, or DEPRECATED. Needs article.publish.
update_article visibility string no public, internal, or private. Internal and private need a paid plan.
update_article note string no Note for the reviewer when the organization reviews agent changes. A direct write ignores it.
propose_article_edit slug string no Article to change. Leave it out to propose a new article, which then needs title.
propose_article_edit title string no Proposed title. A change keeps the current title when you leave it out.
propose_article_edit markdown string no Proposed full body. Send markdown or edits.
propose_article_edit edits object[] no 1 to 20 replacements, each { old, new }, for an existing article.
propose_article_edit base_revision string no Revision from get_article. A changed article fails with stale_revision.
propose_article_edit allow_shrink boolean no Accept a body that removes most of the text. Defaults to false.
propose_article_edit section_key string no New article only: the sidebar section that it goes into when a reviewer approves it.
propose_article_edit note string no Note for the reviewer: what the change fixes and where the facts come from.
list_proposals status string no pending, approved, rejected, or all. Defaults to pending, or to all with id.
list_proposals id integer no Read one proposal with its base revision, the current revision, and its proposed Markdown when it changes the text.
create_section label string yes Section display name.
place_article slug string yes Slug of the article to move.
place_article section string yes Target section key, or none to unlist.
place_article position integer no Zero-based place in the section. Defaults to the end.
place_article note string no Note for the reviewer when the organization reviews agent changes. A direct write ignores it.
upload_image data string yes PNG, JPEG, WebP, or GIF bytes as standard base64. Max 10 MiB.

Notes:

Landing tools

Tool Permission Result
get_landing landing.read The landing document (JSON)
autofill_landing landing.update A landing draft built from workspace data (JSON, not saved)
set_landing landing.update The saved landing document (JSON), or pendingReview
propose_landing_edit landing.propose { id, status } of the pending proposal

Fields, in addition to the shared workspace field.

Tool Field Type Required Notes
set_landing landing string yes The whole landing document as JSON text.
set_landing note string no Note for the reviewer when the organization reviews agent changes. A direct write ignores it.
propose_landing_edit landing string yes The whole proposed landing document as JSON text.
propose_landing_edit note string no Note for the reviewer.

The landing document has these fields:

Notes:

Changelog tools

Tool Permission Result
list_changelog changelog.read { entries }, one page, newest first (JSON)
get_changelog changelog.read One entry (JSON)
create_changelog changelog.create The new entry (JSON), or pendingReview
update_changelog changelog.update The changed entry (JSON), or pendingReview
delete_changelog changelog.delete { deleted: id }, or pendingReview

Each entry has id, title, markdown, tags, status, publishedAt, author, updatedAt, createdAt, and url. The url opens the entry on the changelog page of the workspace.

Fields, in addition to the shared workspace field.

Tool Field Type Required Notes
list_changelog limit integer no 1 to 100. Defaults to 50.
list_changelog offset integer no Entries to skip. Defaults to 0.
list_changelog since string no Entries published at or after this time: an RFC 3339 time, a date, or a duration such as 30d.
list_changelog drafts boolean no Add the drafts and the scheduled entries. Needs changelog.update.
get_changelog id integer yes Entry id, from list_changelog or from a changelog hit of search_docs.
create_changelog title string yes Entry title. Max 200 characters.
create_changelog markdown string no Entry body in Markdown. Max 20,000 characters.
create_changelog tags string[] no Tags or aliases, as the notes below list them.
create_changelog status string no draft or published. Defaults to draft. published needs changelog.publish.
create_changelog published_at string no RFC 3339 time. A future time schedules the entry.
create_changelog note string no Note for the reviewer when the organization reviews agent changes. A direct write ignores it.
update_changelog id integer yes Id of the entry to change.
update_changelog title string no New title. Max 200 characters. It can't be empty.
update_changelog markdown string no New Markdown body. Max 20,000 characters.
update_changelog tags string[] no Replaces all tags of the entry.
update_changelog status string no draft or published. Changing it needs changelog.publish.
update_changelog published_at string no RFC 3339 time. A future time schedules the entry.
update_changelog note string no Note for the reviewer when the organization reviews agent changes. A direct write ignores it.
delete_changelog id integer yes Id of the entry to delete.
delete_changelog note string no Note for the reviewer when the organization reviews agent changes. A direct write ignores it.

Notes:

Workspace tools

Tool Permission Result
list_workspaces none { workspaces } that the key reaches (JSON)
create_workspace workspace.create The created workspace (JSON)
update_workspace workspace.update The updated workspace (JSON), or pendingReview
delete_workspace workspace.delete { deleted: id }

Fields. list_workspaces takes no arguments. delete_workspace takes only the shared workspace field. create_workspace doesn't take workspace, because it creates a new workspace in the organization of the key.

Tool Field Type Required Notes
create_workspace name string yes Workspace name.
create_workspace color string no Accent color as #rrggbb. Defaults to #6b7280.
create_workspace access_mode string no public, internal, or private. Defaults to internal.
create_workspace listed boolean no Show this public workspace in the workspace switcher. Defaults to false.
create_workspace llms_txt boolean no Include public pages in llms.txt. Defaults to true.
update_workspace name string no New name. It can't be empty.
update_workspace color string no Accent color as #rrggbb.
update_workspace access_mode string no public opens the docs to everyone, internal asks visitors to sign in, and private hides the workspace.
update_workspace listed boolean no Show this public workspace in the workspace switcher.
update_workspace llms_txt boolean no Include public pages in llms.txt.
update_workspace note string no Note for the reviewer when the organization reviews agent changes. A direct write ignores it.

Notes:

OpenAPI tools

Tool Permission Result
get_openapi_status openapi.read Status of the attached spec (JSON)
attach_openapi_spec openapi.attach Sync status after generation (JSON), or pendingReview
sync_openapi openapi.sync Sync status after the pages are generated again (JSON), or pendingReview
detach_openapi_spec openapi.detach { detached: true, removed }, or pendingReview
detach_openapi_page openapi.detach The detached article (JSON)
list_openapi_pages openapi.read { sections, pages } (JSON)
create_openapi_section openapi.layout { key, label, created }
place_openapi_page openapi.layout The moved article (JSON), or pendingReview

Fields, in addition to the shared workspace field. get_openapi_status and list_openapi_pages take no other fields.

Tool Field Type Required Notes
attach_openapi_spec spec string one of spec or url Raw OpenAPI 3.0 or 3.1 JSON or YAML. Max 2 MiB.
attach_openapi_spec url string one of spec or url HTTPS URL of an OpenAPI JSON or YAML document.
attach_openapi_spec, sync_openapi, detach_openapi_spec, place_openapi_page note string no Note for the reviewer when the org reviews agent changes. A direct write ignores it.
detach_openapi_page slug string yes Slug of the generated page, from list_openapi_pages.
create_openapi_section label string yes Section display name.
place_openapi_page slug string yes Slug of the generated page to move.
place_openapi_page section string yes Target section key, or none to unlist.
place_openapi_page position number no Zero-based position in the section. Defaults to the end.

Notes:

Analytics tools

Read how people and agents use a workspace, and what to write, update, or fix next. Any agent that can search can also tell the docs team about a question that the docs didn't answer.

Tool Permission Result
agent_insights analytics.read What AI agents asked over MCP, the API, and the docs site: totals, top questions, unanswered questions, articles agents read beside human reads, agents, and keys (JSON)
get_analytics_report analytics.read The report of Admin > Analytics: reads by people and AI agents per day, channels, top articles, searches, referrers, AI assistants, agents and crawlers, not-found paths, helpful votes, the agent section, and growth, the pages and searches that rose since the period before (JSON)
get_content_insights analytics.read The next steps: unanswered questions grouped by topic with the closest article, pages to update, missing article paths with a suggested redirect target, and rising, the topics that rose since the period before (JSON)
report_content_gap search or article.read Records a question that the docs didn't answer, for the docs team
Tool Field Type Required Notes
agent_insights days number no 1 to 90 days up to and including today. The default is 30.
agent_insights principal string no key, anonymous, or reader: only the agents with a key, the agents without a key, or the agents that signed-in readers connected. Leave it out for every agent.
get_analytics_report days number no 1 to 731 days up to and including today. The default is 30.
get_content_insights days number no 1 to 90 days up to and including today. The default is 30.
report_content_gap question string yes The question as the user asked it. The first 256 characters are kept.
report_content_gap slug string no Slug of the article that came closest.

Notes:

Resources

A client can load the shape of a workspace in one read with MCP resources, instead of many tool calls. Each resource checks the same permission and workspace scope as the matching tool.

URI Permission Contents
contextowl://workspaces.json none The workspaces that the key reaches. It appears in resources/list
contextowl://{workspace}/sidebar.json article.read Nav groups and article placement (JSON)
contextowl://{workspace}/changelog.json changelog.read The 50 newest published changelog entries, each with its url (JSON)
contextowl://{workspace}/landing.json landing.read The landing document (JSON)
contextowl://{workspace}/{slug}.md article.read One article in the get_article format, without a length limit (text/markdown)
contextowl://{workspace}/openapi openapi.read The attached OpenAPI spec, as application/json or application/yaml

A workspace-bound key can use - as the workspace in the article and openapi resources, for example contextowl://-/mcp.md.

Use resources when a client wants to attach a page or the workspace structure as context instead of calling a tool. Resource subscriptions and list-changed notifications are not offered: the deployed transport is stateless, so clients re-read on demand.

Prompts

The server publishes workflow templates that a client finds with prompts/list and expands with prompts/get. Each prompt is text that names the tools to call, so a client can chain a task without guessing the API. A prompt does nothing on its own.

Prompt Arguments Purpose
onboard_openapi workspace, spec_url Attach an OpenAPI spec and arrange the generated pages in the sidebar.
draft_changelog workspace, summary Check the recent entries with list_changelog, then draft an entry for an editor to review.
audit_stale_docs workspace, since Find the articles that recent changes made stale, and propose fixes with propose_article_edit. since defaults to 90d.
import_docs workspace, source Import local Markdown files as proposals, one for each file, with the file path in each note.

Leave out workspace when the key is bound to one workspace.