Every MCP tool, what it does, and whether it writes.
Every MCP tool, grouped by primitive. Read-only tools never change state and set the MCP readOnlyHint annotation, so clients can auto-approve them. db_exec, db_branch, fs_write and memory_forget also set destructiveHint.
Tools act only on the group the API key or OAuth grant is scoped to (locally, the group in the /mcp/<group> path), and its tool allowlist controls which of them the agent sees. primitive_list is always available, whatever the allowlist. See Auth & scoping. Your client can fetch the full JSON Schema of each tool's input with tools/list.
A group can hold several primitives of each type, each with an id and a name. Agents discover, then use: call primitive_list first, then pass one of its results as primitive (the id or the name) to every db_, fs_, queue_, memory_, vault_, kv_ and storage_ tool, and to primitive_snapshot and primitive_fork. When the group has exactly one primitive of the tool's type, primitive can be left out. An unknown, ambiguous or wrong-type primitive returns an error telling the agent to call primitive_list to see the available primitives.
| Tool | Description | Read-only |
|---|
primitive_list | List this group's primitives (id, name, type). Call it first: other tools take one as primitive. | yes |
primitive_create | Create a named database, filesystem, queue, memory, vault, key-value store or object storage in this group. | no |
primitive_snapshot | Create a named restore point for one primitive. | no |
primitive_fork | Copy one primitive into a new primitive in this group. | no |
primitive_list: type? to filter. Returns { data: [{ id, name, type, paused }] } for the caller's group.
primitive_create: type (db, fs, queue, memory, vault, kv or storage), name. Returns { id, name, type }.
primitive_snapshot: primitive? (id or name), label? (the snapshot's name).
primitive_fork: primitive? (the source, id or name), new_name? (default <name>-fork-<6 chars>). The copy lands in the caller's own group.
| Tool | Description | Read-only |
|---|
db_query | Run a read-only SQL query and return rows. | yes |
db_exec | Execute a write statement or migration. | no |
db_branch | Create, promote or discard a database branch. | no |
db_query: primitive?, sql, params? (array for ?, object for named parameters), branch?. Statements that write are rejected, even if the key also allows db_exec.
db_exec: primitive?, then sql and params?, or statements: [{ sql, params? }] to run several in one transaction; branch?.
db_branch: primitive?, action (create, promote, discard or list), branch, force?.
| Tool | Description | Read-only |
|---|
fs_read | Read a file or list a directory. | yes |
fs_write | Write, move or delete files. | no |
fs_search | Glob and full-text search across the workspace. | yes |
fs_fork | Fork a directory for a sub-agent; large files are shared, not copied. | no |
fs_read: primitive?, path, list?, recursive?. A file comes back as { path, encoding, content, size, truncated }, with at most 256 KiB of content; binary files are base64.
fs_write: primitive?, action (write by default, move, delete or mkdir), path, content?, encoding? (utf8 or base64), to? for move, recursive? for delete.
fs_search: primitive?, glob, or query with an optional path; limit? up to 1,000.
fs_fork: primitive?, from, to?. Forks a directory inside the filesystem; to copy a whole filesystem, use primitive_fork.
| Tool | Description | Read-only |
|---|
queue_send | Enqueue a message with optional delay and dedupe key. | no |
queue_receive | Lease the next message from a queue. | no |
queue_ack | Acknowledge, retry or dead-letter a leased message. | no |
queue_send: primitive? (the queue primitive), queue (a queue inside it), body, delay?, dedupe_key?, priority? (0–9).
queue_receive: primitive?, queue, lease? (default 30s), wait? (up to 20s), max? (1–10). Returns { data: [jobs] }, each with a lease_token. It isn't read-only because it takes a lease.
queue_ack: primitive?, queue, id, lease_token, action (ack by default, retry, fail, dead or heartbeat), in? for retry, error?.
| Tool | Description | Read-only |
|---|
memory_remember | Store a typed memory (episodic, semantic, procedural). | no |
memory_recall | Hybrid search over memories, filtered by kind and tags. | yes |
memory_forget | Delete or supersede a memory. | no |
memory_remember: primitive?, kind, text, tags?, metadata?.
memory_recall: primitive?, query, kinds?, tags? (all must match), limit? (up to 50).
memory_forget: primitive?, action (delete by default, supersede or consolidate), id (not needed for consolidate), text and tags? for supersede.
| Tool | Description | Read-only |
|---|
vault_list | List secret names granted in a vault. Never values. | yes |
vault_use | Make an HTTP request with a secret injected by reference. | no |
vault_list: primitive?. Returns names, versions and grants.
vault_use: primitive?, url, auth, method?, headers?, body?. auth is one of { "bearer": NAME }, { "header": { "name", "secret" } }, { "basic": { "username", "secret" } } or { "query": { "param", "secret" } }.
| Tool | Description | Read-only |
|---|
kv_get | Read one key or many (value, version, expiry). | yes |
kv_list | List keys by prefix, optionally with values; paginated by cursor. | yes |
kv_set | Set a key (JSON or base64 bytes) with optional TTL and compare-and-swap, set many at once, or atomically increment. | no |
kv_delete | Delete one or more keys, optionally only at a given version. | no |
kv_get: primitive?, then key, or keys (up to 100) for many. A missing or expired key is null.
kv_list: primitive?, prefix?, cursor?, limit? (up to 1,000), values? (default false).
kv_set: primitive?, then key and value, with encoding? (json by default, or bytes with value in base64), ttl? and if_version? (0 means the key must not exist); or items: [{ key, value, encoding?, ttl?, if_version? }] to set up to 100 atomically; or key and incr (an integer, may be negative) with ttl? to increment. A set without ttl clears any expiry. A version mismatch fails with precondition_failed.
kv_delete: primitive?, then key and if_version?, or keys.
| Tool | Description | Read-only |
|---|
storage_list | List objects by prefix; with a delimiter, folders roll up into prefixes. | yes |
storage_read | Read an object's bytes (text or base64, capped) or just its metadata. | yes |
storage_write | Put, copy or delete objects, with content type, metadata and if-match/if-none-match conditions. | no |
storage_sign | Create a time-limited URL to download or upload one object without a token. | no |
storage_list: primitive?, prefix?, delimiter?, cursor?, limit?. Returns { data, prefixes, next_cursor }.
storage_read: primitive?, key, head? (info only), range? (such as 0-1023 or bytes=-4096). Returns { key, content_type, etag, metadata, size, range, encoding, content, truncated }, with text as UTF-8 and binary as base64. At most 256 KiB comes back per call; without range it reads the first 256 KiB and sets truncated if there's more.
storage_write: primitive?, action (put by default, copy or delete), key; for put, content, encoding? (utf8 or base64), content_type?, metadata?; to for copy; if_match? and if_none_match? ("*" to create only).
storage_sign: primitive?, key, method? (GET by default, or PUT), expires_in? (default 15m, up to 7d), content_type?, max_size? and if_none_match? ("*" for create-only) for uploads. Returns { url, method, headers, expires_at }. Anyone holding the URL can use it until it expires, so treat this tool like write access. See Signed URLs.
primitive_list
{ "type": "db" }
{ "data": [
{ "id": "01jbz3k8m5q2w7x9v4n6t0r1ce", "name": "orders", "type": "db", "paused": false },
{ "id": "01jbz3m2a7c4e6g8j0k2m4p6rs", "name": "cache", "type": "db", "paused": false }
] }
primitive_create
{ "type": "db", "name": "orders" }
db_query
{ "primitive": "orders", "sql": "select id, title from tasks where status = ?", "params": ["open"] }
db_exec
{ "statements": [
{ "sql": "create table tasks (id integer primary key, title text, status text)" },
{ "sql": "insert into tasks (title, status) values (?, ?)", "params": ["Write docs", "open"] }
] }
fs_write
{ "path": "/repo/plan.md", "content": "- [ ] ship it" }
queue_ack
{ "queue": "reviews", "id": "01k7c3…", "lease_token": "…", "action": "retry", "in": "1h" }
primitive_fork
{ "primitive": "workspace", "new_name": "workspace-try" }
memory_recall
{ "query": "package manager?", "kinds": ["semantic"], "tags": ["preferences"], "limit": 5 }
vault_use
{
"url": "https://api.github.com/user",
"method": "GET",
"auth": { "bearer": "GITHUB_TOKEN" }
}
kv_set
{ "primitive": "state", "key": "plan", "value": { "step": 4 }, "if_version": 12 }
storage_sign
{ "primitive": "artifacts", "key": "reports/q3.pdf", "expires_in": "1h" }
To give an agent read access only, create an API key in the dashboard scoped to its group and just the read-only tools, or pick only these on the OAuth consent page (primitive_list is always allowed):
db_query, fs_read, fs_search, memory_recall, vault_list, kv_get, kv_list, storage_list, storage_read
Locally, pass the same list in the URL:
claude mcp add --transport http primitives \
"http://localhost:7777/mcp/agent-42?tools=db_query,fs_read,fs_search,memory_recall,vault_list,kv_get,kv_list,storage_list,storage_read"