Noon Docs · Getting Started · Concepts · Guides · Reference

Noon has no browser admin screen. Every administrative action, from writing a page to rotating a credential, happens through an MCP tool call made by an agent you instruct. This page explains the shape of that interface so you know what to expect when you tell an agent to do something.

Reads go through one tool, writes through many

Every read runs through the query tool: {collection, select, where?, orderBy?, limit?, cursor?}. One tool covers pages, posts, templates, and every other readable collection, so an agent does not need a different reading tool per content type.

Writes are separate, typed tools: create_page, update_page, create_post, publish, and others, each with its own argument shape for the thing it changes. Splitting writes out this way is what lets each one validate strictly against what it is meant to do, rather than accepting a generic payload for anything.

The server tells the agent what is valid

Call describe_schema to get the live shape of every content type and every queryable collection. An agent asks the server what is valid instead of guessing at field names or component types, and the answer is always current because it is generated from the same validators the write path enforces, not a separate document that can drift out of date.

Nothing is live until you publish

A write to create_page, update_page, create_post, or update_post lands as an unpublished draft. The site keeps serving whatever was published before. Calling publish re-validates the draft, increments its version, clears the draft, and makes it live. Nothing an agent writes reaches a visitor until that separate call happens.

Concurrent writes are guarded, not silently overwritten

Every write that changes an existing row takes an expectedRevision. If another write landed first, the call fails with revision_conflict and the current revision comes back in the same error:

{
  "error": {
    "code": "revision_conflict",
    "message": "expectedRevision no longer matches the current row",
    "currentRevision": 4,
    "currentVersion": 2
  }
}

The agent re-reads with query, gets the fresh state, and retries with the currentRevision from the error. Nothing is lost silently; a losing write fails loudly and tells the agent exactly what to do next.

Invalid input names the problem and how to fix it

An agent that sends bad content does not get "invalid layout." It gets a validation_failed error with a violations list, and each violation names the exact path that is wrong and, where the fix is unambiguous, a suggestion the agent can apply directly.

Worked example. The agent tries to create a page with a HeroSection block that has no headline, which is a required prop:

{"tool": "create_page", "arguments": {
  "slug": "about",
  "title": "About",
  "layout": {"blocks": [{"type": "HeroSection", "props": {}}]}
}}

Noon rejects it:

{
  "error": {
    "code": "validation_failed",
    "message": "content failed schema validation",
    "violations": [
      {
        "path": "layout.blocks[0].props.headline",
        "code": "invalid_type",
        "message": "missing required string at \"layout.blocks[0].props.headline\"",
        "suggestion": { "op": "add", "path": "layout.blocks[0].props.headline", "value": "" }
      }
    ]
  }
}

The agent applies the suggested add at layout.blocks[0].props.headline, fills in a real headline in place of the empty placeholder, and retries the same call. This time it succeeds and returns a draft id and revision.

Every write is attributed and logged

Every write is attributed to the token that made it and recorded as a row in the audit_log collection: name (the tool or route that ran), tokenId (the acting token), entityType and entityId (what was acted on), action (for example create, publish, rollback, revoke_token), diff (a {before, after} pair, capped in size), and createdAt.

audit_log is readable only with a root-scoped token, through the same query tool as everything else:

{"tool": "query", "arguments": {"document": {
  "collection": "audit_log",
  "select": ["name", "tokenId", "entityType", "entityId", "action", "createdAt"],
  "where": {"entityType": "page"},
  "orderBy": [{"field": "createdAt", "dir": "desc"}],
  "limit": 20
}}}

See also