MCP server
Connect Claude and Codex to it. Each gets your access, and nothing more.
attest_tag is an MCP server. Give Claude, Claude Code, Codex, Cursor or VS Code one address, sign in and press Allow, and your assistant can rewrite the documents the bot answers from, keep its memories, set up its routines, and read what it did and what that cost. Every call acts as you, with exactly the permissions your role holds.
https://app.attesttag.com/mcpstreamable HTTPOAuth or an API key26 tools
The deploy runbook still says we ship from main. We ship from release branches now. Fix the bot's copy.
attest-tagread_documentrunbooks/deploys.mdread-only
attest-tagwrite_documentrunbooks/deploys.mdwrite
saved · indexing in the background
Updated runbooks/deploys.md: deploys go from release/* now. The bot answers from the new text once the re-index finishes.
And every weekday at nine, post the failed payouts in #finance.
attest-taglist_scopesread-only
attest-tagcreate_routine#finance · 0 9 * * 1-5write
Routine 7 is set. It runs as the bot in #finance, and anything it tries to change waits for a Confirm there.
Connect
One address. Sign in, press Allow, and it is connected.
On the hosted service the address is https://app.attesttag.com/mcp; on a deployment of your own it is /mcp on the console’s host. The console shows it under Developer → MCP, with the steps for each client.
The client registers itself and opens the console’s consent screen. If you are not signed in, you sign in first, and your organisation’s sign-in policy and two-factor rule apply as they always do. Press Allow and the client is sent back, connected as you, in the organisation you signed in to.
Scripts, CI and clients configured with a header use a developer key instead: make one under Developer → API keys and send it as a bearer token. The key is the person who made it, and revoking it cuts the client off.
claude mcp add with the address, then /mcp, pick attest-tag and choose Authenticate.codex mcp add with the address, then codex mcp login attest-tag, which opens a browser for you to sign in. The CLI and the IDE extension share the setting.Claude Code
claude mcp add --transport http attest-tag \ https://app.attesttag.com/mcp # then, in Claude Code: /mcp → attest-tag → Authenticate
Codex
codex mcp add attest-tag --url https://app.attesttag.com/mcp codex mcp login attest-tag # opens a browser to sign in
With an API key instead
# Claude Code claude mcp add --transport http attest-tag \ https://app.attesttag.com/mcp \ --header "Authorization: Bearer $ATTESTTAG_KEY" # Codex reads the key from the variable you name codex mcp add attest-tag --url https://app.attesttag.com/mcp \ --bearer-token-env-var ATTESTTAG_KEY
JSON config (.mcp.json, Cursor's mcp.json)
{
"mcpServers": {
"attest-tag": {
"type": "http",
"url": "https://app.attesttag.com/mcp",
"headers": {
"Authorization": "Bearer <your API key>"
}
}
}
}Keep the key out of anything you commit. A config file checked into a repository is a key handed to everyone who can read it.
Tools
26 tools, each one route of the API.
A tool runs its /v1 route in-process, as the caller: the same permission check, the same boundary around your organisation, the same validation and the same audit row. It cannot say or allow anything the route does not.
whoamilist_workspaceslist_scopesThe organisation, account and permissions the connection acts with; the Slack workspaces and Teams tenants connected; which channels the bot is in and how each is set up.
list_documentsread_documentwrite_documentdelete_documentWhat the bot searches, with the channel each document is limited to; one document's text; writing or removing one. A write re-indexes in the background, so the next answer comes from the new text.
list_memoriesadd_memoryupdate_memorydelete_memoryThe facts it keeps for a workspace or a channel: added, reworded, forgotten.
list_routineslist_routine_runsrun_routinecreate_routineupdate_routinedelete_routineScheduled prompts and every run's output, cost and error, quiet runs included. Run one now, make one, change or pause one, delete one.
list_artifactsget_artifactThe files it made in its answers (reports, tables, drafts) with their contents.
list_jobsget_jobWhat each job is changing, its status, branch and pull request, and what it cost.
list_access_requestsWho asked the bot for access to what, and how each request was closed.
list_activitylist_auditEvery turn with its model, tokens and cost; the audit log of sign-ins and where they came from, changes, approvals and exports. Both page forward from the last row seen, which is what a collector wants.
get_usageget_billingThis month's spend against the budget, the busiest channels, and the limit that paused the bot if one has; the plan, when it renews, and the credit left.
Amber tools change something, and the deletes among them say they are destructive: the hints an MCP client reads to decide what to ask you before it calls one. Reading workspaces, documents, memories, routines and spend needs only a connection. Changing documents, memories or routines needs the permission for each, which admin and editor both hold. Artifacts, fix jobs, access requests and activity each need a view permission every built-in role holds; the audit log needs audit.view, which among the built-in roles only admin holds. A client is offered only the tools its person’s role may use.
Two things are deliberately not tools. There is none that asks the bot a question: it answers in the channel, and what a client gets is what the bot knows, what it did and what that cost. And nothing reaches a stored credential. Connections are set up in the console, and their secrets leave through the proxy and nowhere else. Uploading a PDF is not a tool either; the API takes it.
Your access
It acts as you, and checks who you are on every call.
A connection acts as the person who approved it, and their role is read again on every request. Give them a smaller role and the next call is smaller; remove them and it stops. An admin sees all 26 tools; an editor sees all but the audit log.
Approving an app takes what making an API key takes: a role that may manage keys, which admin and editor both hold, and, where the deployment sends mail, a confirmed email address. The consent screen names the app, your account, the organisation and the address it will send you back to. Read the address: an app’s name is whatever it called itself.
An access token lasts an hour and opens /mcp and nothing else. The refresh token behind it lasts thirty days from its last use and changes every time it is used. If a spent one ever comes back, somebody else has a copy, so the connection ends there and you connect again.
Model Context Protocol
Connect Claude
Claude wants to use attest_tag as you.
- Account
- [email protected]
- Organisation
- Nexa Labs
- Returns to
- claude.ai
It can do what your role can do through the API: read documents, memories, activity and spend, and change documents and memories where your role may. It keeps that until you disconnect it under Developer → MCP, or leave the organisation.
Only allow this if you started it from Claude, and it is sending you back to claude.ai.
Routines
A routine made from Claude still waits for a Confirm.
create_routine puts a scheduled prompt in a channel the bot is in, named by its id or as #name, at most every fifteen minutes. It runs as the bot, with that channel’s connections and budget, never as you, so it reaches nobody’s own accounts.
Anything it tries to change waits for a Confirm in the channel, the way a write in a thread does, until someone who manages routines switches that off in the console. Rewriting what a routine does takes it off the person it ran as, as the console’s editor does, and from then on it runs as the bot. How a write is held.
Audit
Every change says it came over MCP, and which connection brought it.
A write through a tool is recorded as the same write through the API would be, with who made it and from which address, marked as having come over MCP along with the key or connection it came through. Approving an app, disconnecting one, and a connection ended because a spent refresh token came back each get an audit row of their own.
Developer → MCP lists every connected app with the person it acts as. You disconnect your own; anyone who may manage API keys can disconnect any. Removing someone from the organisation, or a password reset or change, ends their connections as it revokes their keys. Calls are limited to 240 a minute per connection, as keys are.
The developer API
Or skip MCP: the same routes, over HTTP with a key.
Every tool is a route of /v1, and a script can call the routes directly: keep the runbooks the bot answers from in step with the repository they live in, or feed the activity and audit logs to a collector.
A key carries the access of whoever made it and not a scrap more, read again on every request, so an integration cannot reach further than its maker and stops when they leave. It is shown once and stored only as a hash. It skips the sign-in policy and two-factor, which belong to somebody at a keyboard, and is bounded instead by its expiry, if it was given one, and the revoke button.
A record outside your organisation answers 404, never 403: a key cannot tell a record it may not see from one that does not exist. Keys and connections are each limited to 240 requests a minute.
The developer API
export ATTESTTAG_KEY=atk1.… # Developer → API keys
curl https://app.attesttag.com/v1/whoami \
-H "Authorization: Bearer $ATTESTTAG_KEY"
# Keep what the bot reads in step with the repository.
curl https://app.attesttag.com/v1/documents/runbooks/deploys.md \
-X PUT \
-H "Authorization: Bearer $ATTESTTAG_KEY" \
-H 'Content-Type: application/json' \
-d '{"content": "# Deploys\n\nShip from release/*.\n"}'For client authors
The deployment is its own authorization server.
It follows the MCP authorization spec, so a client that implements the spec needs nothing from us but the address. The same endpoints are on every deployment, hosted or your own.
/.well-known/oauth-protected-resource/mcp (RFC 9728), which a 401 from /mcp points at, and /.well-known/oauth-authorization-server (RFC 8414).POST /oauth/register, open dynamic client registration (RFC 7591). Clients are public by default, with PKCE standing behind them; one that registers with client_secret_basic or client_secret_post is given a secret and must use it.GET /oauth/authorize, the authorization code grant. PKCE with S256 is required, resource (RFC 8707) must name this server if it is sent, and every redirect back carries iss (RFC 9207). A code lasts ten minutes and works once; used a second time, it ends what the first use made.POST /oauth/token for authorization_code and refresh_token; POST /oauth/revoke (RFC 7009). Access tokens open /mcp and nothing else.Connect it, and see what it knows.
Sign up and the address is under Developer → MCP in the console. Run it yourself and it is /mcp on your own host. The tools are the same either way.