MCP integration

Nottario speaks MCP over streamable HTTP from the same binary that serves the web UI. One container, two interfaces.

Wire an agent in one command

From your project's Settings → Tokens tab in the web UI, issue an API token. The page shows a copy-paste line that looks like:

claude mcp add nottario --transport http \
  --header "Authorization: Bearer <your-token>" \
  -- https://your-host/mcp

That installs the MCP server into your local Claude Code config, scoped to one project (the project the token was issued under). An agent using that token can only read or write data in that project — admin tokens are not exempt.

Narrow the tool catalogue

Every tool definition a client loads stays in the agent's context for the whole session. Two optional parameters on the MCP URL trim the catalogue for one connection:

  • ?tools=tasks,docs keeps only those domains (arch, cycles, docs, projects, search, skill, tasks); whoami always stays.
  • ?shell=true is for agents that can run curl: it hides docs.read and docs.write, because such an agent moves documents through docs.download_url and docs.upload_url instead.

For example, a coding agent that never touches the architecture graph: https://your-host/mcp?tools=tasks,docs,projects,search,skill&shell=true. Without parameters the connection sees every tool.

Tools

The MCP server exposes one tool per natural verb. Highlights:

  • nottario.tasks.list — list tasks, with filters by state, type, assignee, role, parent, cycle.
  • nottario.tasks.claim_next — atomic "pick the next eligible task and assign it to me", in one locked statement.
  • nottario.tasks.claim — atomic claim by id.
  • nottario.tasks.create — files a task; claim: true creates it already assigned to the caller and in doing, for work the agent is about to start.
  • nottario.tasks.set_state — transitions with precondition checks. Moving a task out of todo assigns it to the caller when it has no assignee, so nothing sits in doing or reaches done unowned.
  • nottario.tasks.add_comment, nottario.tasks.link_commit — keep the audit trail honest.
  • nottario.tasks.add_steps, nottario.tasks.update_step, nottario.tasks.remove_step, nottario.tasks.move_step — keep a task's checklist of short steps. Closing a task with steps undone is allowed; the response lists them under pending_steps with a notice asking the agent to tell the human.
  • nottario.docs.read, nottario.docs.write — versioned markdown documents with optimistic concurrency via expected_version.
  • nottario.docs.move — rename a document, keeping its history and its architecture links.
  • nottario.docs.stat, nottario.docs.append — the cheap pair: fingerprint a document without its body to decide whether a write is needed, and add to the end of one without resending it.
  • nottario.docs.upload_url, nottario.docs.download_url — signed, five-minute URLs to upload a whole document from a file on disk (single use) and to download one to a file, so document bodies do not pass through the agent's context.
  • nottario.arch.upsert_node, nottario.arch.upsert_edge — maintain the architecture graph.
  • nottario.search — full-text across tasks, docs and arch nodes.
  • nottario.skill.install — returns a signed URL + install plan for the skill bundle zip (see below).

nottario.tasks.next exists as a read-only preview but does not claim — prefer claim_next to avoid races.

Response shape

The tools return slim payloads by default. A mutation like tasks.create, tasks.set_state or tasks.add_comment returns only the fields an agent needs to chain the next call — id, title, state, priority, updated_at, role/assignee — and does not echo back the description or comment body the agent just sent. tasks.list rows follow the same rule. docs.write and docs.append answer with {path, current_version, updated_at} — the markdown you just sent is not read back to you.

The same instinct applies to reads. docs.stat exists so an agent can check whether a document changed without paying for its body, and docs.read { head_only: true } returns frontmatter plus the first 400 characters when you only need to confirm you have the right file.

When the full object is genuinely needed (e.g. an agent reading a task it didn't create), pass verbose: true on the call to opt back into every field.

tasks.list defaults to open tasks only (state='todo' or 'doing'). Closed rows accumulate forever and would dominate every walk in a long-lived project; pass include_closed: true when you genuinely need them, or set an explicit state filter (e.g. state='done') to scope to a closed bucket.

tasks.get returns the base task in full (descriptions are the reason you called get in the first place) together with related: the parent feature, the subtasks, the tasks it depends on and the tasks it blocks, each as {id, title, state, type}, and steps, the open items of the task's checklist in order, each {id, text, done} (include_done_steps: true lists the done ones too). The step tools answer with the step they touched and the progress, not the whole list. List rows and slim shapes carry only the progress, steps: {done, total}. Commits and comments are omitted unless you ask for them:

nottario.tasks.get {
  project_id, task_id,
  include_commits: true,
  include_comments: true,
}

Each flag defaults to false. The motivation is plain: comment lists and per-row descriptions are the largest payloads in the API, and an agent that doesn't need them shouldn't pay tokens for them. A typical "claim → comment → done" loop now sits in ~600 B of MCP traffic instead of ~5 KB.

Skills: zip-and-install

Nottario ships a skill bundle aimed at agents (operating rules, task discipline, sqlc conventions). Installation is a single tool call:

nottario.skill.install returns a small JSON descriptor with a short-lived signed URL for the bundle zip, the install plan, and a bundle_version: the SHA-256 of the SHA256SUMS file inside the zip, which the agent compares against its installed copy before downloading anything. The bundle content does not flow through the response — the agent fetches the URL out of band and unzips into the local skill directory using whatever HTTP and unzip tools its host exposes. Prefer <repo>/.claude/skills/nottario/ (workspace-scoped, committed alongside the code); fall back to ~/.claude/skills/nottario/ when you work across unrelated checkouts.

Restart the host application (Claude Code or whichever agent client you use) after a sync — most clients load the bundle once at session start. See Agent skills for the response shape and copy-pasteable fetch/extract commands per platform.

Authentication

Every MCP request must carry a Bearer token. Tokens are issued per-project from the Settings tab and stored hashed. Revoking from the UI invalidates immediately.

The token is configured once, in the MCP client, by the person setting up the agent. Agents are instructed never to read it, search for it or reuse it against the HTTP API: everything they need is an MCP tool, and for whole files nottario.docs.upload_url and nottario.docs.download_url issue short-lived signed URLs instead of exposing a credential. A signed upload URL stops working after five minutes, after one successful upload, or as soon as the token that requested it is revoked; a download URL names one version of one document and stops working after five minutes or on revocation.