Quickback Docs

AI Agent Readiness

Every Quickback Worker ships agent-discovery files and an auto-generated MCP server. Let Claude, ChatGPT, Cursor, and Perplexity find and call your API out of the box.

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:

RouteWhat it is
GET /robots.txtAI-bot policy with the Cloudflare Content Signals preamble and explicit Allow: rules for GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot, Applebot-Extended, Bytespider, MistralAI, Cohere, DuckAssist.
GET /llms.txtAgent-facing summary of the API — project name, authentication, resource list, endpoint conventions — derived from your own config.
GET /.well-known/oauth-protected-resourceRFC 9728 metadata declaring this Worker as a protected resource.
GET /.well-known/oauth-authorization-serverRFC 8414 authorization-server metadata. When the oauthProvider plugin is enabled it delegates to Better Auth's real OAuth 2.1 surface (/oauth2/authorize, /oauth2/token, /oauth2/register dynamic client registration, PKCE); otherwise it advertises only the truthful token-mint subset.
GET /.well-known/api-catalogRFC 9727 catalog linking at /openapi.json.
GET /.well-known/mcp.jsonMCP Server Card — discoverable metadata for the MCP server below.
POST /mcpAuto-generated MCP-over-HTTP server. One tool per OpenAPI operation, JWT auth forwarded through the same middleware as the REST API.

Default behavior

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

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

Fine-grained control

Pass an object to toggle individual surfaces:

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
  },
});

mcp also accepts an object for an authenticated-only MCP surface:

quickback.config.ts
export default defineConfig({
  // …
  agents: {
    mcp: { requireAuth: true },  // /mcp challenges anonymous callers → interactive OAuth
  },
});

See Interactive OAuth below.

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. Platform-operator endpoints don't appear for callers outside the required user roles.
  • 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:

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

Interactive OAuth (requireAuth)

Statically pasting a Bearer token works, but it isn't how an MCP connector wants to authenticate — it expects to launch a sign-in flow. By default /mcp serves an anonymous session (the access-filtered public tool list), and a connector that never sees an auth challenge never offers to sign in. Set agents.mcp.requireAuth: true to turn /mcp into an authenticated-only endpoint:

quickback.config.ts
export default defineConfig({
  // …
  agents: { mcp: { requireAuth: true } },
});

This does three things:

  1. Force-enables the oauthProvider + bearer Better Auth plugins — so /oauth2/authorize, /oauth2/token, and /oauth2/register (RFC 7591 dynamic client registration) exist for a connector to discover, with PKCE.
  2. Challenges unauthenticated /mcp requests with 401 and a WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource" header (RFC 9728 §5.1). This is the signal MCP connectors use to start OAuth.
  3. Makes the root /.well-known/oauth-authorization-server doc delegate to Better Auth's real metadata, so discovery resolves to working /oauth2/* endpoints instead of a hand-rolled stub.

The full handshake a connector runs:

POST /mcp  (no token)
  → 401 + WWW-Authenticate: resource_metadata="…/.well-known/oauth-protected-resource"
GET  /.well-known/oauth-protected-resource   → authorization_servers: [<origin>]
GET  /.well-known/oauth-authorization-server → authorize/token/register + PKCE
POST /oauth2/register                        → dynamic client registration
GET  /oauth2/authorize?code_challenge=…      → redirect to your Account login → consent
POST /oauth2/token  (code + code_verifier)   → access token
POST /mcp  (Bearer access token)             → authenticated tool list

The login and consent screens are served by your Account SPA (ACCOUNT_URL/login, ACCOUNT_URL/consent), so users sign in with whatever methods you already enabled (Google, GitHub, email, passkey). No connector-specific configuration is required — adding the server URL is enough.

requireAuth removes anonymous public-tool access over /mcp. Use it when the MCP surface is meant for authenticated users; leave it off (the default) for servers that expose genuinely public tools.

Connecting clients

Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.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:

# 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:

# 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

On this page