Most products that add an AI integration add a surface. A handful of endpoints get wrapped, the demo works, then the product moves on and the integration falls a year behind. The tell is the same: you can do a thing in the app that you cannot do through the API, and nobody can tell you which things those are.
The rule here runs the other way around. Every capability the panel has is a tool an agent can call. A feature ships on both surfaces or it does not ship, and a test fails when it does not.
The contract, in one table
| Endpoint | POST /mcp |
| Any other method | 405, JSON-RPC error -32000 |
| Transport | Streamable HTTP, stateless, JSON responses |
| Authentication | Authorization: Bearer pat_…, a personal access token |
| Unauthorized | 401, WWW-Authenticate header, JSON-RPC -32001 |
| Authorized but refused | 200, with isError set on the result |
| Tools registered | 132 |
The last two rows are the ones that matter to anybody writing a client. A missing or bad
credential is a transport-level failure and looks like one. Everything else, a ticket that does not
exist, a role that is not allowed, a value that does not validate, comes back as a successful tool
call carrying an error, so an agent can tell “I cannot reach this server” apart from “the server
understood me and said no”. A known error keeps its own message; an unexpected one is replaced with
Internal server error and the real type goes to the server log rather than to the client.
The rule is a test, not a promise
Parity is easy to claim and hard to keep, so it is checked. Every administrative capability the server knows about is required to carry both an HTTP route and an MCP tool. A capability missing either one fails the suite.
There is exactly one way out, and it is expensive on purpose: the capability has to be listed as a named exception with a reason attached, and the test asserts the reason is longer than 40 characters, because a label is not a decision. The suite also fails on a stale exception, meaning one written for a capability that is now fully served on both surfaces.
Three exceptions exist. Two of them are the same idea:
- Creating a personal access token. A token is the credential an MCP client authenticates with. Minting one from inside an authenticated agent session would let a leaked token extend itself indefinitely.
- Revoking one. The twin of the above: revocation stays on the surface that can also mint.
- Accepting an invite. It is performed by the invited person against a token, not by an administrator acting on someone else’s behalf, so there is no agent-facing tool to expose.
A second test runs beside it and classifies every mutating tool as administrative or deliberately exempt, so a tool that changes something without writing an audit event cannot be added quietly.
One authorization module, two doors
The interesting part is not the MCP server. It is that the MCP tools and the panel’s HTTP routes call the same authorization module, which around 60 files in the server import. There is no second, looser path into your data.
That module also decides what a refusal reveals. No membership answers 404 rather than 403, because 403 confirms the project exists; an insufficient role answers 403. An agent gets the same two answers a browser does, for the same reasons.
So an agent holding your token is bound by exactly the roles you were granted. If someone removes you from a project, the agent loses that project at the same instant you do. And every change an agent makes lands on the ticket’s activity trail under your name, because it is not a separate integration acting on your behalf. It is you, with a different client.
What an agent gets that an API would not
An HTTP API assumes the reader already knows its shape. An agent does not, and guessing is where most tool use goes wrong.
So the server describes itself. The initialize handshake returns orientation instructions, and
the get_documentation tool returns the whole manual, currently around 85 KiB of Markdown, as a
single block of text. The same content is published as a resource. An agent can read the entire
system without leaving the protocol, which is a better deal than hoping it inferred your
pagination convention correctly.
The tool count is held to the same standard. It is asserted against a real tools/list response
over an in-memory transport, so the number cannot drift from what the server registers, and a
separate test pins every place the figure appears in prose. The manual, this site’s home page and
the server itself have to agree, and they had already drifted to 19 and 122 against 132 before that
test existed.
What it costs
Parity is a tax on every feature. Adding a settings toggle means adding the tool that sets it, and the tool has to enforce the same permission checks and produce the same audit event. A feature is not done when the screen works.
That tax is the point. The alternative, an API that covers the parts somebody remembered, is what makes agents useless for real work: the moment the model hits a gap, it has to hand the task back to you, and you are back to doing it by hand with extra steps.
Connecting
Create a personal access token in Personal settings → API & MCP, then:
claude mcp add -t http notarus https://<your-domain>/mcp \
-H "Authorization: Bearer pat_…"
Ask it whoami on notarus to confirm the connection, then try something real: “list my tickets
in Received and take the newest one.” That is list_tickets followed by assign_ticket("me"),
and the ticket’s history will show that you did it. The full surface, with the transport and the
error shapes a client has to handle, is on the developer reference.