# Connect an AI agent (MCP)

> Connect Claude Code, Claude Desktop, claude.ai, Cursor or any MCP client to By Dawn, so your agent can read and manage your team's tickets as you.

This page shows you how to connect your own AI agent to By Dawn, so it can read and manage your team's tickets for you.
By the end your agent will answer "What's open for me this cycle?" from your board, and you'll know what it may do,
how it signs in, and how to cut it off.

By Dawn runs an **MCP server**. MCP (the Model Context Protocol) is the standard way an AI client such as Claude Code or
Cursor is given tools. Once you connect, your agent sees By Dawn's tools, such as **search tickets** or **move a ticket**,
and calls them while it works. It acts as you: it sees what you see in By Dawn and can change only what you could change,
within the scopes you approve.

The server's address is:

```text
https://workspace.dev.sawrun.com/mcp
```

It's a remote server, so there's nothing to install. You sign in through your browser the first time, and your agent
keeps itself signed in after that.

## Before you start

- **Your organization has to allow it.** AI agents are off until an owner or admin turns them on, and they choose
  which scopes members may hand out. [AI agents (MCP)](https://docs.bydawn.com/ai-agents/#turn-it-on-for-your-organization) shows them how.
  Until then, the consent screen shows your organization greyed out with "Your admin hasn't turned on AI agents".
- **You need a By Dawn account in that organization.** The agent never gets more than you have.
- **Organizations that require SSO or MFA can't use AI agents yet.** An agent's token isn't tied to a sign-in, so
  an organization that requires single sign-on or multi-factor sign-in refuses it, as it refuses the Slack, Discord
  and Teams integrations. The consent screen shows such an organization greyed out with "Your org requires SSO; AI
  agents can't connect yet", so you can't approve there.

## Connect your client

Add the server to your client, then sign in when it asks. Each client below opens By Dawn in your browser for the
sign-in, then shows the consent screen described in [Approve the connection](#approve-the-connection).

**Claude Code**

Add the server from a terminal:

```sh
claude mcp add --transport http saw https://workspace.dev.sawrun.com/mcp
```

That adds it for the project you're in. Add `--scope user` to have it in every project.

Then, in Claude Code, run `/mcp`, pick **saw** and choose **Authenticate**. Claude Code opens your browser. Once you
approve, `/mcp` shows **saw** as connected, and its tools are available in the session.

**Claude Desktop and claude.ai**

Claude Desktop and claude.ai share your connectors, so adding By Dawn in one adds it in the other.

1. Open **Settings › Connectors** and choose **Add custom connector**.

2. Name it `SAW` and paste `https://workspace.dev.sawrun.com/mcp` as its URL. Leave the advanced OAuth fields
   empty: By Dawn registers the client itself.

3. Choose **Add**, then **Connect**. Your browser opens By Dawn's consent screen.

In a chat, turn the By Dawn connector on from the tools menu to use it.

**Cursor**

Add the server to `~/.cursor/mcp.json` to have it in every project, or `.cursor/mcp.json` in one repo:

```json
{ "mcpServers": { "saw": { "url": "https://workspace.dev.sawrun.com/mcp" } } }
```

Cursor lists **saw** in its MCP settings and asks you to sign in to it. Choose it, and your browser opens By Dawn's
consent screen.

**Any MCP client**

Any client that speaks MCP's **Streamable HTTP** transport and its **OAuth** authorization can connect. Give it the
URL, and it finds everything else on its own: By Dawn publishes standard metadata that points the client at By Dawn's sign-in,
and the client registers itself, so there's no client ID or secret to set up. [How discovery
works](#how-discovery-works) has the details, if your client asks for them or you're writing one.

## Approve the connection

The first time, your browser opens By Dawn's consent screen (sign in first if it asks). It names the client, such as
"Claude Code", and where By Dawn will send you back afterwards, so you can check it's the client you set up a moment ago.

1. **Pick the organization.** The agent works in one organization. If you're in several, connect once for each. An
   organization that hasn't turned AI agents on can't be picked.

2. **Choose the scopes.** You see the ones your client asked for, within what your organization allows (see
   [Scopes](#scopes)). Reading tickets is always included. Untick anything you don't want the agent to do.

3. **Choose Allow.** By Dawn sends you back to the client, and the page says you can close the tab.

Choose **Deny** instead and the client gets nothing. Connecting the same client again later in the same organization
updates its scopes, rather than adding a second connection.

## Scopes

A scope is one kind of thing the agent may do. It never widens what you can do: if you can't move a ticket in By Dawn,
your agent can't either.

| Scope | Lets the agent | Offered by default |
|---|---|---|
| `tickets:read` | read projects, tickets, comments, members, columns, labels, cycles and epics | always included |
| `tickets:write` | create and update tickets, move them, comment, link tickets and triage | yes |
| `workers:start` | start and stop an agent worker on a ticket | no, an admin has to allow it |
| `mission:answer` | answer a [mission checkpoint](https://docs.bydawn.com/mission-checkpoints/) (approve or send back a plan, pick an option) or a worker's question | yes |

`workers:start` is off unless an admin allows it, because a worker an agent starts runs on your organization's seats.
There's no scope for merging, deleting or archiving, or changing settings, so an AI agent can't do any of those.

A client that doesn't ask for particular scopes asks for `tickets:read` and `tickets:write`. To be offered
`workers:start` or `mission:answer`, your client has to ask for them too.

## Tools

The agent sees only the tools its scopes allow. Every tool takes the names you'd use yourself: a ticket as `SAW-123`,
a project by its key or name, a column by its name, a person as `me`, `none`, a name or an email, and a cycle as
`current` or its name. If a name matches more than one thing, the tool says so and lists the matches, so the agent
can ask you which one.

| Tool | Scope | What it does |
|---|---|---|
| `list_projects` | `tickets:read` | Lists the projects you can see. |
| `list_members` | `tickets:read` | Lists the people in the organization, or in one project. |
| `list_statuses` | `tickets:read` | Lists a project's columns. |
| `list_labels` | `tickets:read` | Lists the organization's labels. |
| `list_cycles` | `tickets:read` | Lists a project's cycles. |
| `list_epics` | `tickets:read` | Lists a project's epics. |
| `search_tickets` | `tickets:read` | Finds tickets by project, column, assignee, cycle, epic, label or text. Open tickets only, unless asked otherwise. Up to 50 at a time, 25 by default, with a cursor for more. |
| `get_ticket` | `tickets:read` | Reads one ticket: its details, plan, sub-tickets, links and the last 10 comments. |
| `team_overview` | `tickets:read` | Shows a project's open tickets by person and column, the unassigned ones, and the stale ones (not updated in 7 days), for the current cycle or another. |
| `create_ticket` | `tickets:write` | Creates a ticket, with its description, priority, assignee, column, cycle, epic, labels or parent if given. |
| `update_ticket` | `tickets:write` | Changes a ticket's title, description, priority, assignee, cycle, epic, labels or parent. |
| `move_ticket` | `tickets:write` | Moves a ticket to another column. |
| `comment_on_ticket` | `tickets:write` | Comments on a ticket. |
| `relate_tickets` | `tickets:write` | Links two tickets, for example as blocking or related. |
| `triage_ticket` | `tickets:write` | Accepts, snoozes or declines a ticket waiting in triage. |
| `start_worker` | `workers:start` | Starts an agent worker on a ticket, to plan or to build, with an optional message. |
| `stop_worker` | `workers:start` | Stops the worker on a ticket. |
| `answer_checkpoint` | `mission:answer` | Answers a ticket's mission checkpoint or its worker's question. |

Each tool answers with a short summary the agent reads, plus the same data as JSON (`structuredContent`) for clients
that use it. When By Dawn refuses something, the tool says why in words, such as "You don't have permission to …", and the
agent can tell you.

## Things to ask

Ask in your own words. The agent picks the tools. For example:

- "What's open for me this cycle?"
- "Give me an overview of the WEB project: who has what, and what's gone stale?"
- "Assign SAW-123 to Dana and move it to Review."
- "File a bug in WEB: the export button does nothing on Safari. Put it in the current cycle."
- "Summarise the comments on SAW-88 and reply that we'll ship the fix on Friday."
- "Which tickets in triage look like duplicates? Decline the ones that are."

With `workers:start` and `mission:answer`, it can also run work: "Start a worker on SAW-140 to plan it", then later
"Approve the plan on SAW-140".

## How it keeps you safe

- **It acts as you, and only as far as you let it.** What the agent can do is what your role and project access allow,
  narrowed to the scopes you approved. Restricted projects you can't see stay hidden from it too.
- **Everything it does is yours, and labelled.** A ticket it creates or a comment it posts shows you as the author,
  "via MCP (Claude Code)" or whichever client it was, on the ticket's timeline.
- **Its credential works in one place.** The client holds an access token that By Dawn accepts only at the MCP server's
  address. Sent to By Dawn's API or any other By Dawn service, it's refused. Inside By Dawn, each request is carried out with a
  separate short-lived token that never leaves By Dawn.
- **Tokens are short-lived.** An access token lasts 15 minutes. The client renews it with a refresh token, which
  changes every time it's used and expires after 30 days without use. If an old refresh token is ever used again, By Dawn
  assumes it was stolen and ends that connection's tokens.
- **A client can tie its tokens to a key it holds.** A client that supports DPoP gets tokens that work only alongside a
  proof signed with its own private key, so a copied token is useless on its own. See [Tokens tied to a
  key](#tokens-tied-to-a-key-dpop).
- **You can cut it off at any time.** The **Connected agents** card in **Settings › You › Connections** lists your
  connected clients, with the organization, scopes, when each was connected and last used; choose **Disconnect**. The
  client stops working within seconds.
- **You see who published an agent's details.** For a client that identifies itself with a Client ID Metadata
  Document, the consent screen and **Connected agents** in **Settings › You › Connections** show the site that
  published it next to its name, because the name is whatever the client chose.
- **Your admins stay in control.** Owners and admins can turn AI agents off for the organization, which pauses every
  connection (turning it back on restores them), limit the scopes members may approve, and see and revoke anyone's
  connection. Leaving the organization, or having your role lowered, disconnects your agents.

## How discovery works

You don't need this to connect a ready-made client. It's for writing a client, or working out why one doesn't
connect. By Dawn follows the [MCP authorization
specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), so a client finds
everything from the server's URL in four steps.

1. **The server says where its metadata is.** A request to `/mcp` without a token, or with one that has expired, gets
   `401` with a pointer to the server's own metadata ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728)):

   ```http
   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer resource_metadata="https://workspace.dev.sawrun.com/.well-known/oauth-protected-resource/mcp"
   WWW-Authenticate: DPoP resource_metadata="https://workspace.dev.sawrun.com/.well-known/oauth-protected-resource/mcp", algs="ES256"
   ```

   The second challenge offers [DPoP](#tokens-tied-to-a-key-dpop). A client that doesn't use it can ignore it.

   That document names By Dawn's authorization server, which is the accounts host:

   ```json
   {
     "resource": "https://workspace.dev.sawrun.com/mcp",
     "authorization_servers": ["https://accounts.dev.sawrun.com"],
     "scopes_supported": ["tickets:read", "tickets:write", "workers:start", "mission:answer"],
     "bearer_methods_supported": ["header"],
     "resource_name": "SAW",
     "dpop_signing_alg_values_supported": ["ES256"],
     "dpop_bound_access_tokens_required": false
   }
   ```

2. **The authorization server lists its endpoints.** `GET
   https://accounts.dev.sawrun.com/.well-known/oauth-authorization-server` ([RFC
   8414](https://www.rfc-editor.org/rfc/rfc8414)) gives the `authorization_endpoint`, `token_endpoint`,
   `registration_endpoint` and `revocation_endpoint`, all under `https://accounts.dev.sawrun.com/oauth/`. It supports
   the authorization code grant with PKCE (`S256` only) and the refresh token grant, for public clients
   (`token_endpoint_auth_method` `none`), and says `"client_id_metadata_document_supported": true`. Its
   `dpop_signing_alg_values_supported` (`["ES256"]`) says the token endpoint accepts DPoP proofs.

3. **The client identifies itself, in one of two ways.** Either way there's no client secret. Redirect URIs must be
   `https`, `http` on a loopback address (`127.0.0.1`, `[::1]` or `localhost`, any port), or an app's own scheme such
   as `cursor://`. The client name is what you see on the consent screen and under **Connected agents** in
   **Settings › You › Connections**.

   - **A Client ID Metadata Document.** This is what the [2025-11-25 MCP authorization
     specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) recommends. The
     client publishes its metadata as JSON at an `https` URL it controls and uses that URL as its `client_id`, with no
     registration call. By Dawn fetches the document the first time the client sends someone to `/oauth/authorize`:

     ```json
     {
       "client_id": "https://agent.example.com/oauth/client-metadata.json",
       "client_name": "Example Agent",
       "redirect_uris": ["http://127.0.0.1/callback"],
       "token_endpoint_auth_method": "none"
     }
     ```

     The document's `client_id` must be exactly the URL it's served from. The URL itself must be `https` on the
     default port, use a domain name (not an IP address or `localhost`), have a path, and carry no query, fragment or
     `.`/`..` segments. By Dawn reads at most 5 KB and waits at most 5 seconds. It accepts only a `200` response served as
     `application/json`, and it doesn't follow redirects. It refuses any host that resolves to a private, loopback or
     link-local address. The document must not include a `client_secret`.

     By Dawn keeps the document for as long as its `Cache-Control: max-age` says, between 5 minutes and 24 hours (an hour
     if it doesn't say), then fetches it again at the next sign-in. If the document is unreachable or no longer
     valid at that point, sign-in fails until it's fixed. A document that fails is rejected with `invalid_client` and
     the reason, and isn't fetched again for a minute. Because anyone can publish a document with any name, the
     consent screen shows the site that published it next to the name.

   - **Dynamic registration.** `POST /oauth/register` ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)) with a
     `client_name` and its `redirect_uris` returns a `client_id`. This is how Claude Code, Claude Desktop, claude.ai and
     Cursor connect today.

4. **You sign in and approve, and the client gets tokens.** The client opens `/oauth/authorize` in your browser with
   its PKCE `code_challenge`, the `scope` it wants, and `resource` set to the server's URL ([RFC
   8707](https://www.rfc-editor.org/rfc/rfc8707)). By Dawn shows its consent screen at
   `https://app.dev.sawrun.com/oauth/consent`, then sends the browser back to the client's redirect URI with a code.
   The client swaps the code and its `code_verifier` at `POST /oauth/token` for an access token and a refresh token.

The token response is standard OAuth (not By Dawn's API envelope):

```json
{
  "access_token": "eyJ…",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "…",
  "scope": "tickets:read tickets:write"
}
```

The client sends the access token with every request to `/mcp` as `Authorization: Bearer …`. Before it expires, the
client posts `grant_type=refresh_token` to the same token endpoint and keeps the new refresh token it gets back,
because the old one stops working. A client can narrow its scopes on a refresh, but never widen them.
`POST /oauth/revoke` ([RFC 7009](https://www.rfc-editor.org/rfc/rfc7009)) with the refresh token disconnects it.

### Tokens tied to a key (DPoP)

A plain access token works for whoever holds it until it expires. With DPoP ([RFC
9449](https://www.rfc-editor.org/rfc/rfc9449), and MCP's optional DPoP profile, SEP-1932), the client ties its tokens to a
P-256 key pair it keeps, so a token copied out of a log or a proxy is useless without the private key. By Dawn offers it
and doesn't require it: a client that doesn't send proofs keeps getting `Bearer` tokens. In the TypeScript MCP SDK
(2.1.0 and later) a client turns it on with `OAuthClientProvider.dpop()`.

A DPoP proof is a short JWT the client signs for each request: `typ` `dpop+jwt`, `alg` `ES256`, its public key as
`jwk` in the header, and the request's method (`htm`) and URL (`htu`), the time (`iat`) and a unique `jti` in the
body. By Dawn accepts each proof once, within a minute of its `iat`.

- **At the token endpoint.** Send a proof in a `DPoP` header with the code exchange. The response says
  `"token_type": "DPoP"`, the access token carries the key's thumbprint (`cnf.jkt`), and the refresh token is tied to
  the same key: every refresh needs a proof signed with it. A refresh without one is refused with
  `invalid_dpop_proof`, and one signed with another key with `invalid_grant`. Neither uses up the refresh token,
  so a stolen copy can't rotate it or end your connection.
- **At `/mcp`.** Send the token as `Authorization: DPoP …` (not `Bearer`), with a proof whose `htu` is the server's URL
  and whose `ath` is the base64url SHA-256 of the access token. The server also wants a `nonce` it issued. The first
  request without one gets `401` with `WWW-Authenticate: DPoP error="use_dpop_nonce"` and a `DPoP-Nonce` header, so
  the client signs a new proof with that nonce and sends the request again. Every successful answer carries the
  current `DPoP-Nonce`, so keep the latest one and use it in the next proof.

```http
POST /mcp HTTP/1.1
Host: workspace.dev.sawrun.com
Authorization: DPoP eyJ…
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIs…
Content-Type: application/json
```

A bad proof gets `401` with `WWW-Authenticate: DPoP error="invalid_dpop_proof"`: a missing or second `DPoP` header,
another key, another URL, an old `iat`, or a proof used before. A bound token sent as `Bearer` gets `error="invalid_token"`,
because whoever sends it that way may not hold the key.

> **The transport:** `/mcp` is stateless Streamable HTTP: each `POST` carries one JSON-RPC message and gets one JSON answer, with no event
> stream and no session. `GET` and `DELETE` answer `405`, and batches are refused. By Dawn speaks MCP protocol versions
> `2025-11-25`, `2025-06-18` and `2025-03-26`, and answers with `2025-06-18` to a client that asks for another.

## Troubleshooting

**The consent screen says "Your admin hasn't turned on AI agents".** Your organization hasn't allowed them yet. Ask an
owner or admin to turn them on (see [AI agents (MCP)](https://docs.bydawn.com/ai-agents/)).

**The scope I want isn't on the consent screen.** Either your organization doesn't allow it, in which case ask an
admin, or your client didn't ask for it. A client that asks for no scopes is offered `tickets:read` and
`tickets:write` only.

**The agent's requests keep getting `401` with `use_dpop_nonce` or `invalid_dpop_proof`.** The client is using DPoP
and its proofs aren't accepted. It must sign a new proof for every request, with the latest `DPoP-Nonce` By Dawn sent and
a `jti` it hasn't used before, so check that it isn't reusing one. A clock more than a minute off also makes its
proofs look old.

**The agent says By Dawn answered `401`, or asks you to sign in again.** Its tokens no longer work: it was revoked, it
went 30 days unused, your organization turned AI agents off, or you left the organization or your role changed.
Reconnect it: in Claude Code run `/mcp` and choose **Authenticate** again, and in other clients disconnect and
reconnect By Dawn.

**The consent screen greys out your organization with "Your org requires SSO" (or multi-factor sign-in).** Agents
can't connect to an organization that requires SSO or MFA yet. Pick another organization, or choose **Deny**.

**An agent that used to work now fails to refresh, and asks you to authorize again.** If your organization started
requiring SSO or MFA after you connected it, By Dawn stops issuing it tokens. Authorizing again shows the organization
greyed out. The connection is still listed under **Connected agents** in **Settings › You › Connections**, where you
can disconnect it.

**The agent can't find a project or ticket you know exists.** It sees only what you see. A restricted project you
aren't a member of stays hidden from it, and so do its tickets. Check that you can open it in By Dawn.

**A tool says "You don't have permission to …".** Your role in By Dawn doesn't allow that change, or the connection's
scopes don't include it. Starting a worker, for example, needs `workers:start` on the connection and permission to
run agents in that project.

**The agent's changes stop with a rate-limit error.** Each connection has its own request budget, so a runaway agent
can't use up yours. Wait a moment, then ask again.

## Where to go next

- [AI agents (MCP)](https://docs.bydawn.com/ai-agents/): turning AI agents on for your organization, and managing connections.
- [Authentication](https://docs.bydawn.com/developers/authentication/): API keys, for a script or service that calls By Dawn's API without a
  person.
- [Start a ticket](https://docs.bydawn.com/start-a-ticket/): what happens when a worker starts.

Source: https://docs.bydawn.com/developers/mcp/
