MCP server
Axowl speaks Model Context Protocol over Streamable HTTP at
https://testapi.axowl.com/mcp. Point your own agent runtime at it and the agent acts under its
badge — Axowl issues and enforces the authority, your runtime does the work.
This is the primary way to run an agent. The Axowl console harness is the fallback for teams with no runtime of their own.
Connect
Section titled “Connect”In the dashboard, open your app group’s Agents tab and press the plug icon on the agent you want to run. Axowl issues a credential bound to that agent’s badge — expiring when the agent does — and hands you the exact command:
claude mcp add --transport http axowl https://testapi.axowl.com/mcp --header "Authorization: Bearer ah_live_..."The credential is shown once and cannot be retrieved afterwards; issue a new one if you lose it.
To issue it yourself instead, POST /api/org/{slug}/settings/keys/api-keys with connectedIdId
set to the agent’s badge. Either way it must be bound to an agent badge — a key with no
connectedId, a human badge, or a badge that is suspended, revoked, or past its TTL is refused
with a sentence explaining which.
Sign in instead (OAuth)
Section titled “Sign in instead (OAuth)”If your client speaks OAuth — claude mcp add --transport http axowl https://testapi.axowl.com/mcp
with no header — it discovers the authorization server, registers itself, and opens a browser. You
sign in to Axowl, pick which agent it may act as, and approve. The token it receives names that
one agent and names you as the delegating person.
This is the better path when a person is present, because the delegating person is whoever actually signed in, rather than whoever happened to issue a key months ago.
Under the hood: RFC 9728 protected-resource metadata, RFC 8414 server metadata, RFC 7591 dynamic client registration, authorization code with PKCE (S256 required), and rotating refresh tokens. Access tokens last an hour.
An agent that is suspended, revoked, or past its TTL stops working immediately — the badge is re-checked on every call, not at token issuance. Revoking does not wait for a token to expire.
Authority: the AND rule
Section titled “Authority: the AND rule”An agent’s authority is what the agent holds ∩ what the person who delegated it holds.
The delegating person is whoever signed in (OAuth) or whoever issued the API key. This is not a
formality — a request with no delegating person is refused outright, because an agent cannot act on
nobody’s behalf. If you see every tool refusing, that is the reason, and axowl_whoami says so
directly.
The consequence worth internalising: widening the agent alone will not lift a refusal that names the delegating user. A person cannot hand a machine authority they do not have themselves.
What you get: materials, not screens
Section titled “What you get: materials, not screens”Axowl does not build your application. It hands the agent the materials already attached to the app group and enforces what it may do with them:
app group ─┬─ connected repository (code) ──> read it, propose a pull request └─ attached database (data) ──> read the schema, run SELECTsConnecting the repository and the database is a person’s job — a prerequisite to hiring, not something the agent does. The agent gets read access to both, and the only way it changes code is a pull request.
| Tool | What it does | Gate |
|---|---|---|
axowl_context | App group, connected repo, attached DB, and what this agent may do | none |
axowl_whoami | Badge, agent, app group, TTL, and who delegated it | none |
axowl_list_permissions | Every concrete scope on the badge | none |
axowl_check_permission | Ask about one scope before attempting it | none |
axowl_repo_list_files | Paths in the connected repository, on any branch | org.git_repository.read |
axowl_repo_read_file | Read one file, on any branch | org.git_repository.read |
axowl_repo_pull_requests | Your open pull requests, their branches, CI results and review comments | org.git_repository.read |
axowl_repo_ci_log | Why CI failed — the last lines of the failed job’s log | org.git_repository.read |
axowl_db_schema | Tables and columns of the attached database | org.data_source.read |
axowl_db_query | Run a SELECT and get rows back | org.data_source.read |
axowl_propose_code_change | Open or continue a pull request with whole-file changes and deletions | org.git_repository.propose_change |
axowl_approval_forms | The approval forms this org defines, what each grants, who would stamp | org.work_item.list |
axowl_request_permission | Refused a scope? Submit the approval that grants it | org.reporting_instance.create |
axowl_approval_submit | Submit any approval document (access or plain document approval) | org.reporting_instance.create |
axowl_approval_status | Your submitted documents and each stamp box | org.reporting_instance.read |
axowl_request_workflow | Ask for a deployment workflow to be installed — a person stamps the exact file | org.reporting_instance.create |
axowl_request_pages_project | Ask for a Cloudflare Pages project on the customer’s own account (you choose the name only) | org.reporting_instance.create |
axowl_request_migration | Ask for a schema change on staging or production — a person stamps the exact SQL, Axowl runs it | org.reporting_instance.create |
axowl_repo_secrets | Which repository secrets the deploy needs, and which are set (names only) | org.git_repository.read |
axowl_docs_list | Axowl’s own documentation — every page, by section | platform.doc_page.read |
axowl_docs_read | Read one documentation page | platform.doc_page.read |
axowl_pm_stories | The stories assigned to you across this app group’s projects | assignment |
axowl_pm_story | One assigned story in full — description, checklist, recent activity | assignment |
axowl_pm_move | Start a story (InProgress), send it for testing (InReview, with a test guide), or give it back (ToDo) | assignment |
axowl_pm_comment | Leave a note on one of your stories | assignment |
axowl_pm_task | Add a sub-task, or tick one off | assignment |
axowl_pm_create_story | File something you found mid-flight into the backlog, or (forPerson) ask a person for setup | assignment |
axowl_pm_link | Attach a pull request, commit or branch to one of your stories (not a preview — Axowl attaches that) | assignment |
axowl_pm_files | The documents in a project’s file box — specs, decisions, notes | project |
axowl_pm_file | Read one of those documents, as text | project |
axowl_pm_epics | The epics across your projects, with their state and story counts | project |
axowl_pm_propose_epic | Propose an epic — it lands in Backlog for a person to accept | project |
Start with axowl_context. It tells you what is connected and which of the above will actually
work, so you find out before you try rather than after.
Project work: assignment is the permission
Section titled “Project work: assignment is the permission”The axowl_pm_* tools carry no scope of their own. What opens them is being assigned the
story — a person puts your badge on it on the project board, and from then on you can read and
move that story and nothing else. Three conditions hold before any of them answer:
- the project belongs to your app group,
- the person who delegated this session can read that project,
- that app group is connected to Project Management AI (a paid add-on).
Fail any of them and the tool says so in a sentence. A story you are not assigned to answers the same way whether it exists or not — knowing an id must not tell you a project’s shape.
Two of the tools work at the project level rather than the story level. axowl_pm_epics and
axowl_pm_propose_epic need the three conditions above but no assignment, because proposing work
is how the assignment gets created in the first place. Planning happens here, on your tokens.
A person writes a brief in the project wizard; it arrives as an epic in Backlog. You read it, check
what already exists so you do not propose the same thing twice, and file epics of your own. Leave
the owner unset — you cannot assign work to yourself, and an epic stays in Backlog until a person
moves it. Axowl runs no model of its own for this.
One story at a time: write, test, deploy, done
Section titled “One story at a time: write, test, deploy, done”Stories move through the board one at a time per agent and project:
| Column | Who moves it there | What happens |
|---|---|---|
| To do | A person | Waiting its turn |
| In progress | A person presses Start, or Axowl opens the next one when the previous is done | You write it: branch, pull request, CI |
| Testing | You, with axowl_pm_move(state: "InReview", testGuide: …) | A person follows your test guide |
| Deploying | A person presses Pass | You are woken to call axowl_request_merge; a person stamps it |
| Done | Axowl, when that merge goes through | The next story opens |
You cannot start a second story while one of yours is in progress, testing or deploying, and you cannot move a story to Deploying or Done yourself.
Moving to Testing needs three things, and Axowl checks them rather than taking your word: the pull
request attached with axowl_pm_link, its CI green, and a test guide. Write the guide in
markdown with three headings. ## Setup is what the person prepares first (a test organization,
an account, some data), with where in the console to do it. You do not do the setup yourself, and
you never put a password, key or token in the guide. ## Check is numbered steps, each saying what
they should see. ## Not in this story is what they should not expect yet.
When the story reaches Testing, Axowl attaches the preview address for that branch to the card,
so the person opens the running result rather than a list of code changes. The address comes from
the GitHub Deployment your repository’s deploy workflow made (environment: preview), read by Axowl
— you cannot write it: axowl_pm_link refuses a preview kind, because an address you typed would
not have to be the thing that was deployed. A repository with no Axowl deploy workflow has no preview
and is tested from the pull request; ask for one with axowl_request_workflow.
Previews and deploys land on the customer’s own Cloudflare account, connected once by a person in
the console. Until it is connected there is nowhere to publish — Axowl has no account of its own to fall
back to — so a repository with no connection gets a refusal saying exactly that. If the Pages project
itself does not exist yet, ask for it with axowl_request_pages_project; you choose the name, a person
stamps it, and Axowl creates it on their account.
Redo, Back to backlog and a completed merge delete the agent branch, and the preview goes with it — it is a public address and does not outlive the story. Request revision does not: you keep working on the same branch and pull request.
From Testing only a person moves the card, with one of four buttons. Each wakes you with its own
reason, and axowl_pm_story shows the decision and its note at the top:
| Button | Card goes to | reason | What you do |
|---|---|---|---|
| Pass | Deploying | story_passed | Ask for the merge |
| Request revision | In progress | story_revise | Fix it on the same branch, send it back with a fresh guide |
| Redo | In progress | story_redo | Start a new branch, send it back with a fresh guide |
| Back to backlog | Backlog | story_hold | Stop; the person is changing the story itself |
axowl_request_merge refuses a pull request whose story has not been passed. If the merge is
refused, fails or its approval is rejected, the card goes back to Testing and the person is told.
When the project needs something only a person can make before you can go on, such as a connected
Cloudflare account, a secret or a test organization, call axowl_pm_create_story with
forPerson: true. It becomes a setup story assigned to the person who tests your work, and you are
woken with setup_done when they move it to Done. Builds, deploys and hosting run on the accounts
the customer connects, never on Axowl’s own credentials.
You are woken when work arrives: assignment and state changes fire agent.wake with reason
story_assigned / story_state_changed (plus the test reasons above, and story_start when a
story is opened for you), and axowl_context lists your queue at the top of every session. When
your last story closes, file a completion approval with axowl_approval_submit: finishing and
archiving a project is a person’s stamp, not yours.
Project documents live in the file box, not in the repository
Section titled “Project documents live in the file box, not in the repository”A repository holds code. Specs, decisions, pricing and drafts belong in the project’s file box, because a
deploy publishes a repository and those documents are not meant to be public. People upload them on the
project’s Documents tab; you read them with axowl_pm_files and axowl_pm_file, alongside the project
summary, your epic and your story.
You cannot upload, change or delete a document — a person manages what is in there. Reading is limited to text files (markdown, text, json, yaml, csv, sql, xml, html, css, js, ts) up to 512KB, trimmed if long; anything else is refused rather than dumped into your context. A document from a project you cannot see answers the same way whether it exists or not.
Schema changes: a person stamps the SQL
Section titled “Schema changes: a person stamps the SQL”The database is read-only to you, and a pull request containing a migration is refused whole — code can be
reverted, data cannot. axowl_request_migration(sql, reason) is the way through: it files an approval whose
facts block holds the SQL in full, plus a warning line counting any statements that destroy data. Once a
person stamps it, Axowl runs it against the database attached to your app group — whether that is one Axowl
manages or one the customer connected — in a single transaction. If any statement fails, nothing is applied
and the reason comes back on the approval.
Two things follow from that. Write SQL a person can read in one sitting; anything over 20,000 characters is refused for that reason alone. And propose the code that depends on the change in the same breath — a column nothing reads is as broken as a reader with no column.
If the connected database’s account has no permission to change structure, the database refuses and the approval says so; a person then reconnects with an account that can, or grants it.
Schema changes go to staging first when the app group has a staging database. Call
axowl_request_migration with target: "staging" (that is the default when staging exists), have a
person stamp it, then file the production one. Axowl checks that the exact same SQL already ran on
staging before it applies anything to production, and refuses when it has not — a stamp does not
override that check. A person marks which connection is staging in the console; you cannot.
Discarding your work (a person pressing Redo or Back to backlog) does not undo what already ran on staging — migrations only go forward. Axowl notes on the story what was left behind; reversing it is another approval, with the reversing SQL written out.
Deployment workflows: you choose a template, a person stamps the file
Section titled “Deployment workflows: you choose a template, a person stamps the file”.github/ stays off limits to you — a workflow you could edit is a workflow that could deploy your own code
without anyone looking. But a project still needs one, so the file arrives the same way a merge does:
you ask, a person stamps, Axowl’s server does it.
axowl_request_workflow() with no arguments lists the templates Axowl offers. Call it again with the
template, the Cloudflare Pages project name and the folder (or .csproj) to publish, and it files an
approval whose facts block contains the whole file, so the person stamping reads exactly what will be
written. On approval Axowl commits it to the default branch.
What the templates guarantee, and why:
- the build job has no secrets — that is where your code runs;
- the deploy job does not run your code, it only uploads the artifact the build produced;
- deploy secrets hang off GitHub environments, and
productiononly from the default branch; - every push of an agent branch gets its own preview URL, which Axowl reads back from the deployment.
The workflow runs on the customer’s GitHub Actions minutes and publishes with the customer’s own
Cloudflare account — Axowl’s credentials are not used for your project’s builds or deploys. The repository
secrets each template needs are named in the result. You cannot set them, and you must not ask anyone to
paste one to you: file a setup story instead with axowl_pm_create_story(forPerson: true), and a person
enters the value where it belongs.
Publishing the repository root is refused — name the folder. A repository often holds documents that are not meant to be public.
Read the documentation before you wire anything to Axowl
Section titled “Read the documentation before you wire anything to Axowl”axowl_docs_list / axowl_docs_read serve this documentation site through the tool surface,
bundled with the server so the pages always match the version you are talking to. Every agent
template carries the scope, so this works out of the box — it appears on the role because
everything an agent can do should be visible there, not because we expect anyone to withhold it.
They exist because an agent that cannot read the platform’s own documentation will infer how the platform behaves from the repository in front of it, and quietly get it wrong. A sign-in URL invented that way looks perfectly reasonable and is simply not a real address. So: when the task touches Axowl itself — sign-in, permissions, seals, billing, organizations — read the page first and say so if it isn’t there. Guessing is the failure mode this tool removes.
The database is read-only here
Section titled “The database is read-only here”axowl_db_query runs SELECT and nothing else — that is the definition of the call, not a convention
layered on top. Writes go through a separate path that passes an approval gate.
A failing query returns the database’s own error message, so your model can correct the statement and try again.
Code changes are pull requests, never pushes
Section titled “Code changes are pull requests, never pushes”axowl_propose_code_change takes whole files, not patches, and opens a pull request against the
repository connected to this agent’s client. There is no direct-push path: merging is the human step.
Three things are checked before anything is written — the badge’s authority, the paths (CI
configuration and secrets are off limits, because an agent that can edit the pipeline can deploy
itself), and the size (20 files, 512KB per pull request). Each rejection is reported individually.
Set "delete": true on an entry to remove that path. Deletions pass exactly the same path filter —
an agent that cannot edit .github/ cannot delete it either.
Building something bigger than one call
Section titled “Building something bigger than one call”Real work does not fit in 20 files, so a proposal is built up over several calls on one branch.
The first call returns a branch name; pass it back as branch on every following call and the
changes stack onto the same pull request instead of forking fresh copies of the default branch.
axowl_propose_code_change(prompt, files) → Opened a pull request with 20 file(s) branch: axowl/agent/20260826-101500axowl_repo_read_file(path, "axowl/agent/20260826-101500") → (what you just wrote)axowl_propose_code_change(prompt, files, "axowl/agent/…") → Added 18 change(s) to the existing PRaxowl_repo_pull_requests(number) → state, CI, review commentsOnly agent branches (axowl/agent/…) can be continued. Naming the default branch is refused —
that would be a direct-push path by another name, and merging stays the human step either way.
axowl_repo_pull_requests closes the loop: it says whether a proposal was merged (so its files are
already on the default branch), whether it still merges cleanly, what CI concluded, and what
reviewers wrote. Without it a second round is written blind.
Refused? Ask — through approval
Section titled “Refused? Ask — through approval”An agent in Axowl is an employee, and employees do not give up when a door is locked: they file a
request. axowl_request_permission(scope, reason) finds the Access Approval form that grants the
scope, submits it under the agent’s badge, and routes it to the people who stamp it. When they
approve, the scope lands on the badge — call axowl_check_permission again and carry on.
axowl_propose_code_change → code — denied (agent lacks org.git_repository.propose_change)axowl_request_permission → submitted acme-2026-000031 — 1 stamp needed: Jane Park (stamped in the dashboard with a passkey)axowl_check_permission → allowed — org.git_repository.propose_changeThree rules keep this honest:
- The agent only submits. People stamp. There is no stamping tool; approval is a passkey act in the dashboard. An agent’s badge credential is what proves the submission is the agent’s own.
- Machines never self-approve. A form with risk level 0 is auto-approved when a person submits
it (routine work). When an agent submits it, the line is raised so that at least one person in the
agent’s reporting line has to stamp — and if nobody there holds what the form grants, the document
stays
submittedand the tool says so. This is enforced where the approval line is derived, so it holds for any client, not only MCP. - Approval grants the badge, not the delegating person. If the refusal named the delegating user,
axowl_request_permissiontells you approval will not help and who has to obtain the scope first.
The agent’s manager is the person who hired it — hiring creates that reporting line, and the org chart can move it. The organization also has to define the form: if no Access Approval form grants the scope, the tool asks you to tell the delegating user which scope to set up under Forms & Policies.
Every kind of agent holds the scopes to list forms and submit documents (org.work_item.list,
org.reporting_instance.create, and their read counterparts). Even a test agent — submitting a
request is asking, not writing.
Everything lands where a human can see it
Section titled “Everything lands where a human can see it”Each write is recorded as a turn in a console thread named after the agent, so “what has this agent
done” stays one screen. Refusals are sealed as agent.action_denied audit events; a pull request is
sealed as agent.code_proposed; a submitted approval is a numbered, sealed document like any other —
it shows up in Requests & Approvals with the agent as drafter.
Retries are safe: an identical call is answered once
Section titled “Retries are safe: an identical call is answered once”Agent runtimes retry a tool call that timed out. Every write tool therefore treats an identical
call from the same badge within 24 hours as the same action and returns the first result again,
prefixed with (replayed — …), instead of doing it twice: no second pull request, no second
approval document, no second question email, no duplicate story, link or note, and a checklist
toggle does not flip back. If the first call is still running when the retry arrives, the retry is
told to wait and read the result with the matching read tool.
“Identical” means the same arguments. Each write tool also takes an optional idempotencyKey: pass
any string that names this one action and Axowl recognises the retry by that key even if you
reworded something; pass a new key to deliberately repeat an identical action. Refusals and errors
are never replayed — once you have the permission, or the database is back, the same call runs for
real. This applies to axowl_propose_code_change, the approval tools (axowl_approval_submit,
axowl_request_permission, axowl_request_merge, axowl_request_migration, axowl_request_workflow,
axowl_request_pages_project, axowl_request_deploy_target), axowl_ask, and the project tools
(axowl_pm_move, axowl_pm_link, axowl_pm_comment, axowl_pm_task, axowl_pm_create_story,
axowl_pm_propose_epic).
Refusals are answers, errors are errors
Section titled “Refusals are answers, errors are errors”The two are deliberately different:
- No credential at all is a protocol problem.
/mcpanswers 401 with aWWW-Authenticate: Bearer resource_metadata="…"header, which is what lets a client discover the authorization server and start signing in. - A credential that lacks authority is a modelling problem. The tool returns 200 and the reason
as its result —
boards — denied (delegating user lacks org.record_schema.read). Your model reads that, understands what it lacks, and can ask a human to fix it. This is the same contract the Axowl console harness uses: a denial goes back to the model, never silently swallowed.
What is not here yet
Section titled “What is not here yet”- No database writes. Reading is open; changing rows is not.
- No stamping. An agent can submit an approval but never approve one — by design, not omission.
- One repository per agent. The agent reads the repository connected to the client it owns. If a
client has none connected,
axowl_contextsays so. - No merging, and no replying on a pull request. An agent reads review comments and CI results; answering them means proposing another change on the same branch.
- No running the code. Build and test results reach the agent only through CI on the pull request — an agent cannot edit CI configuration, so a person sets that up once.
- No self-assignment, and no archiving. An agent cannot put itself on a story, take itself off one, or finish and archive a project. Assignment is what grants it work, so granting it to itself would be the same as having no gate; archiving writes a snapshot and cannot be undone, so it stays behind a human stamp.
See also Agents and Roles & permissions.