# Permissions and Tools

Each MCP tool checks three things before touching data:

1. The bearer token is a valid, unexpired agent key.
2. The key includes the tool's required permission.
3. The key owner's current org role still allows the matching action.

Workspace scope is enforced after permission checks. Cross-organization and out-of-scope workspaces return ordinary tool errors such as no such workspace, never cross-tenant data.

## Workspace arguments

Most tools are workspace-scoped.

- Workspace-bound key: omit workspace, or pass the same workspace id.
- Organization-wide key: pass workspace for every workspace-scoped tool.
- Restricted member owner: the key can only reach the owner's assigned workspaces.

## Permission groups

| Group | Permission | Enables |
| --- | --- | --- |
| Content | search | search_docs |
| Content | article.read | list_articles, get_article, article resources |
| Content | article.create | create_article |
| Content | article.update | update_article content and metadata changes |
| Content | article.publish | status changes through update_article |
| Navigation | section.create | create_section |
| Navigation | article.place | place_article |
| Changelog | changelog.read | list_changelog |
| Changelog | changelog.create | create_changelog |
| Changelog | changelog.update | update_changelog fields other than publishing |
| Changelog | changelog.publish | publish or unpublish changelog entries |
| Changelog | changelog.delete | delete_changelog |
| Workspaces | workspace.read | list_workspaces |
| Workspaces | workspace.create | create_workspace |
| Workspaces | workspace.update | update_workspace |
| Workspaces | workspace.delete | delete_workspace |
| OpenAPI | openapi.read | get_openapi_status, list_openapi_pages |
| 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 |

## Content tools

| Tool | Permission | Arguments | Result |
| --- | --- | --- | --- |
| search_docs | search | workspace, query | Matching article search results |
| list_articles | article.read | workspace | Article metadata for the workspace sidebar |
| get_article | article.read | workspace, slug | Article Markdown text |
| create_article | article.create | workspace, title, section, markdown | Created article slug |
| update_article | article.update | workspace, slug, title, section, markdown, status, version | Updated article slug |
| create_section | section.create | workspace, label | New sidebar section key |
| place_article | article.place | workspace, slug, section | Article slug and target section |

Notes:

- create_article always creates a draft.
- update_article only changes fields you pass.
- Changing status requires article.publish in addition to article.update.
- OpenAPI-generated pages cannot be edited with update_article until they are detached.
- Encrypted article content is not exposed over MCP. The tool returns an explanatory message.

## Changelog tools

| Tool | Permission | Arguments | Result |
| --- | --- | --- | --- |
| list_changelog | changelog.read | workspace, drafts | Changelog entries |
| create_changelog | changelog.create | workspace, title, markdown, tags, status, published_at | Created entry |
| update_changelog | changelog.update | workspace, id, title, markdown, tags, status, published_at | Updated entry |
| delete_changelog | changelog.delete | workspace, id | Deleted entry id |

Notes:

- drafts=true on list_changelog is honored only when the key can update changelog entries.
- Publishing at create or update time requires changelog.publish.
- tags must be one of new, improved, fixed, deprecated, security.
- published_at must be an RFC3339 timestamp.

## Workspace tools

| Tool | Permission | Arguments | Result |
| --- | --- | --- | --- |
| list_workspaces | workspace.read | none | Workspaces visible to the key |
| create_workspace | workspace.create | name, color, public_docs, llms_txt | Created workspace |
| update_workspace | workspace.update | workspace, name, color, public_docs, llms_txt | Updated workspace |
| delete_workspace | workspace.delete | workspace | Deleted workspace id |

Notes:

- color must be #rrggbb.
- Single-workspace keys cannot create or delete workspaces.
- The organization must keep at least one workspace.

## OpenAPI tools

| Tool | Permission | Arguments | Result |
| --- | --- | --- | --- |
| get_openapi_status | openapi.read | workspace | Attached spec status |
| attach_openapi_spec | openapi.attach | workspace, spec or url | Sync status after generation |
| sync_openapi | openapi.sync | workspace | Sync status after regeneration |
| detach_openapi_spec | openapi.detach | workspace | Detach confirmation |
| detach_openapi_page | openapi.detach | workspace, slug | Detached article |
| list_openapi_pages | openapi.read | workspace | API sections and generated pages |
| create_openapi_section | openapi.layout | workspace, label | New API section key |
| place_openapi_page | openapi.layout | workspace, slug, section, position | Moved generated article |

Notes:

- attach_openapi_spec accepts either raw spec or an HTTPS url, not both.
- Raw specs are limited to 2 MiB.
- URL-backed specs are refetched by sync_openapi.
- detach_openapi_spec leaves generated pages as editable articles.
- detach_openapi_page detaches one generated page so it can be edited manually.
- place_openapi_page accepts section=none to unlist a generated page.

## Resources

Article Markdown is also available as an MCP resource when the key has article.read:

```text
contextowl://{workspace}/{slug}.md
```

Use resources when a client wants to attach a specific page as context instead of calling get_article.
