Agent workflow
Connect Claude Code, read and write records through the MCP verbs, then watch a read-only key remove the write tools and refuse a call.
Standards is an MCP server at https://api.standards.new/v1/mcp. Every object your key can read becomes a tool surface, under the same permissions as the REST API. This cookbook walks one session on a contacts object, then narrows the key and shows both refusal shapes on the wire.
What you will do
- Connect Claude Code with OAuth, or with an API key.
- Read the
instructionsthe server sends on connect. reada schema →query→writeto create →writeto update.- Switch to a read-only key: the write tools vanish from
tools/list, and a visible tool can still be refused inside a call.
Prerequisites
- A workspace with a
contactsobject (any object with anameattribute works; substitute its name). - A member account for the OAuth flow, or an API key whose role grants read, create and update on
contacts. - Claude Code, or any MCP client that speaks Streamable HTTP.
Connect Claude Code
With OAuth, the client needs only the URL. The hosted platform discovers the client through its Client ID Metadata Document; dynamic client registration is disabled. Login opens a browser tab, then a consent screen where you pick the workspace and the scopes.
claude mcp add --transport http standards https://api.standards.new/v1/mcpBoth paths land on the same server. What differs is how the tool catalogue is narrowed: OAuth scopes (mcp:read, mcp:write, mcp:delete, mcp:people:read) for a delegated session, the key's roles for an API key.
Read the instructions
The server sends an instructions string with the handshake. Claude Code shows it under /mcp; it lists the objects this key can read, then the fixed usage rules quoted on What the client receives.
The index is bounded by object count. Attribute detail belongs to read of schema:<object>, not to the handshake.
Read, then write
Ask Claude Code: "Add Ada Lovelace to contacts, then set her role to Analyst." Following the rules above, it makes a few calls. Here is what travels for each.
read opens a schema by reference, schema:<object>, and answers the object with its attributes and views; each list in a view comes with its columns, filters, sorts and grouping. schema:* lists every readable object:
{ "name": "read", "arguments": { "refs": ["schema:contacts"] } }query looks for an existing record first. It lists the records of one known object; an exact criterion, such as a status, a date or a postal code, belongs in filters, which is the only read that is complete. (search is for free text when the object is not known: it ranks a name or a phrase by token relevance, so it is not the way to count or list by value.) Each row carries its reference as ref:
{
"name": "query",
"arguments": {
"object": "contacts",
"limit": 5,
"filters": {
"combinator": "and",
"rules": [{ "attribute": "name", "operator": "is", "value": "Ada Lovelace" }]
}
}
}{
"rows": [],
"total": 0,
"hasMore": false,
"objectLabel": "Contact",
"objectName": "contacts"
}No match, so write with action: "create" writes one. data is validated against the object's attributes before anything runs, like every tool; an unknown key is rejected:
{
"name": "write",
"arguments": {
"action": "create",
"object": "contacts",
"items": [{ "data": { "name": "Ada Lovelace", "email": "[email protected]" } }]
}
}{
"recordId": "3f0b6d1e-…",
"ref": "record:3f0b6d1e-…",
"kind": "record",
"title": "Ada Lovelace",
"label": "Ada Lovelace",
"objectLabel": "Contact",
"message": "Created record: Ada Lovelace"
}write with action: "update" takes the record's reference, so no object name is needed:
{
"name": "write",
"arguments": {
"action": "update",
"items": [{ "ref": "record:3f0b6d1e-…", "data": { "role": "Analyst" } }]
}
}Every call is re-authorised per object. A record id resolves to its object first, and the actor's permission on that object decides.
Switch to a read-only key
Now connect with a key whose role grants read on contacts and nothing else. Create it in the app from a read-only role, or standards keys create --name mcp-ro --role <read-only role> --yes.
claude mcp remove standards
claude mcp add --transport http standards \
https://api.standards.new/v1/mcp \
--header "Authorization: Bearer stndrds_readonly_key"tools/list now answers the read tools only. The write tools were not merely disabled: they are not registered for this actor, so the client never sees them.
["propose_memory", "query", "read", "search"]Calling a withheld tool by name is a -32602 JSON-RPC error, Tool write not found, answered before any tool code runs; the shape is on Tools.
A visible tool, refused inside the call
A key can hold create on one object and not on another. Say the role grants create on tasks and read on contacts. write is then in tools/list, because some object grants create, but a call on contacts is refused by the tool itself. The result is a normal tools/call answer with isError set:
{
"content": [
{
"type": "text",
"text": "{\"error\":\"Access denied: not authorized to create \\\"contacts\\\"\"}"
}
],
"isError": true
}The text decodes to the tool's own { error } object: Access denied: not authorized to create "contacts". The message is deterministic, so the agent can read the action and the object and report back instead of retrying.
Two shapes, then: -32602 when the tool is absent from the catalogue, "isError": true when the tool exists but this object is out of reach.
The workspace directory
The user kind of search and read is the one thing that does not follow object permissions. It answers to the people:read system permission, the same one GET /users requires. A key holding read on every object still gets no member list without it. Over OAuth, the mcp:people:read scope exposes search and read restricted to members, without granting or requiring record read.
Deletes
delete appears only when a role grants delete, and takes one reference per call. The server's instructions ask the agent to confirm with you before calling either; the delete itself is reversible.
Next steps
- MCP tools: the full catalogue, what each tool reads or writes, and every refusal.
- OAuth: scopes, the CIMD rules, consent and token lifetimes.
- Workspaces and API keys: the roles and permissions behind the narrowing.
- Status page: the workspace this agent could report incidents into.
- Book a demo