# AI Agent Readiness

Every compiled Quickback Worker advertises itself to AI agents and MCP clients — no user wiring required. One config flag controls the whole discovery surface.

## What the compiler generates

When `agents` is enabled (the default), the Worker serves:

| Route | What it is |
|-------|-----------|
| `GET /robots.txt` | AI-bot policy with the [Cloudflare Content Signals](https://developers.cloudflare.com/bots/concepts/content-signals/) preamble and explicit `Allow:` rules for GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot, Applebot-Extended, Bytespider, MistralAI, Cohere, DuckAssist. |
| `GET /llms.txt` | Agent-facing summary of the API — project name, authentication, resource list, endpoint conventions — derived from your own config. |
| `GET /.well-known/oauth-protected-resource` | [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) metadata declaring this Worker as a protected resource. |
| `GET /.well-known/oauth-authorization-server` | [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) metadata pointing at the sign-in and token endpoints. |
| `GET /.well-known/api-catalog` | [RFC 9727](https://datatracker.ietf.org/doc/html/rfc9727) catalog linking at `/openapi.json`. |
| `GET /.well-known/mcp.json` | MCP Server Card — discoverable metadata for the MCP server below. |
| `POST /mcp` | Auto-generated MCP-over-HTTP server. One tool per OpenAPI operation, JWT auth forwarded through the same middleware as the REST API. |

## Default behavior

```ts title="quickback.config.ts"
import { defineConfig } from '@quickback/compiler';

export default defineConfig({
  name: 'my-api',
  // …
  // agents: true is the default — no need to write it.
});
```

Run `quickback compile` and every endpoint above ships in the Worker.

## Opting out

```ts title="quickback.config.ts"
export default defineConfig({
  // …
  agents: false,  // disables everything
});
```

## Fine-grained control

Pass an object to toggle individual surfaces:

```ts title="quickback.config.ts"
export default defineConfig({
  // …
  agents: {
    robotsTxt: true,        // default
    llmsTxt: true,          // default
    mcp: true,              // default
    oauthDiscovery: true,   // default
    apiCatalog: true,       // default
    allowAiCrawlers: true,  // default — flip to false to Disallow in robots.txt
  },
});
```

## The MCP server

### Tools

Every operation in your generated OpenAPI spec becomes one MCP tool. The tool name is the operation's `operationId` (e.g. `listCustomers`, `createInvoice`, `sendInvoice`). Each tool's `inputSchema` is a flat JSON Schema combining path params, query params, and the request body.

`tools/call` re-enters the same Hono app via `app.request(...)` — so **every existing middleware runs exactly as it would for a direct REST call**: authentication, firewall, access control, guards, masking. No bypass.

### Access-filtered `tools/list`

`tools/list` only returns tools the calling identity could actually invoke. Each generated tool entry carries the operation's access metadata (`roles`, `userRole`, `or` / `and` combinators) and `tools/list` filters against `ctx` before responding:

- **Unauthenticated callers** see only tools whose access declares the `PUBLIC` pseudo-role, or operations that have no security requirement at all (e.g. sign-in / sign-up).
- **Authenticated callers** see tools whose role gate intersects their `ctx.roles` or `ctx.userRole`. Admin-only endpoints don't appear for non-admins.
- **Function-form access** (escape hatch) is opaque at list time — surfaced to any authenticated caller, then evaluated for real on `tools/call`.
- **Record predicates** are dropped from the list-time check (no record yet) and applied normally on the actual call.

This is recon-mitigation, not a security boundary. Hiding a tool from the list never weakens the underlying gate — `tools/call` still runs the full middleware chain and rejects unauthorized invocations the same way a direct REST call would.

The unauthenticated `GET /mcp` discovery payload returns server info only; it does **not** enumerate tool names. Inventory is exclusively available via the access-filtered `tools/list`.

### Authentication

MCP clients pass their credentials through the standard `Authorization: Bearer <jwt>` header. The generated MCP handler forwards the header verbatim into the internal request, and the same JWT middleware that guards `/api/v1/*` validates it.

Mint a JWT from an authenticated session:

```bash
curl -X POST https://my-api.example.com/api/v1/token \
  -H "Cookie: better-auth.session_token=..." \
  -H "Content-Type: application/json"
```

### Connecting clients

**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "my-api": {
      "url": "https://my-api.example.com/mcp",
      "headers": {
        "Authorization": "Bearer <jwt>"
      }
    }
  }
}
```

**Cursor** — use the same config shape under `.cursor/mcp.json`.

**Quick sanity check with curl:**

```bash
# List available tools
curl -X POST https://my-api.example.com/mcp \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Call a tool
curl -X POST https://my-api.example.com/mcp \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"listCustomers","arguments":{"limit":10}}}'
```

### Transport

The server speaks the **Streamable HTTP** MCP transport — JSON-RPC 2.0 over `POST`. A plain `GET /mcp` returns the server info document for sanity checks.

The MCP route does **not** emit its own CORS headers. The Worker's existing `cors()` middleware applies uniformly, so browser-origin access is governed by the same allowlist as the rest of the API (`auth.trustedOrigins` + the deploy origin). Server-to-server clients (Claude Desktop, Cursor, MCP Inspector) connect with a Bearer token and don't need browser CORS at all. If you want a browser-based MCP client (ChatGPT, Claude.ai) to connect cross-origin, add its origin to `auth.trustedOrigins` like any other web client.

## Verification

After you deploy, scan both the REST and MCP surfaces:

```bash
# Agent-readiness scan (external)
open https://isitagentready.com/?url=https://my-api.example.com

# MCP Inspector (official tool)
npx @modelcontextprotocol/inspector https://my-api.example.com/mcp
```
