New scan
59%
Grade C
AI Readiness for

vercel.com

13 of 22 applicable checks passed.

llms.txt robots.txt (agent-aware)View raw report

Agent Ready Summary

3 fully passing

These standards are live on your site. Here's what AI agents can do with them today.

llms.txt

Docs

Improves how AI agents understand and interact with your site.

robots.txt directives

Docs

Improves how AI agents understand and interact with your site.

Content quality

Improves how AI agents understand and interact with your site.

Agent Ready

3 capabilities fully passing — agents can rely on these.

llms.txt

Learn →
2 / 2 checks passed
llms.txt found
Markdown structure

robots.txt directives

Learn →
3 / 3 checks passed
Agents allowed
No AI agents blocked(optional)
Google-Extended not blocked(optional)

Content quality

1 / 1 checks passed
Canonical URL
Schema.org types(optional)

(Optional) Declare at least two Schema.org `@type` values across your homepage JSON-LD. `Organization` alone is just entity resolution; agents benefit when you also describe *what the page is* — e.g. `SoftwareApplication` or `WebApplication` for a SaaS, `LocalBusiness` + `Service` for a local site, `Product` for a product page, `FAQPage` for a homepage FAQ. Richer types help agents understand your content structure. See https://agentgrade.com/kb/organization-jsonld.

<!-- Declare multiple Schema.org types so agents understand what kind of page this is. -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://vercel.com/#org",
      "name": "vercel.com",
      "url": "https://vercel.com/"
    },
    {
      "@type": "SoftwareApplication",
      "name": "vercel.com",
      "applicationCategory": "BusinessApplication",
      "operatingSystem": "Web",
      "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
    }
  ]
}
</script>
RSS/Atom feed(optional)

(Optional) Add `<link rel="alternate" type="application/rss+xml" href="/feed">` or serve a feed at /feed, /rss, /atom.xml, or /feed.json. Lets agents subscribe to content updates.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>vercel.com</title>
    <link>https://vercel.com/</link>
    <description>Updates from vercel.com</description>
    <item>
      <title>First post</title>
      <link>https://vercel.com/posts/first</link>
      <pubDate>Mon, 12 May 2026 12:00:00 GMT</pubDate>
      <description>One-line summary of the post.</description>
    </item>
  </channel>
</rss>

Needs Work

9 areas with missing or partial checks.

Tailored Recommendations

4 actions

Prioritized by impact on agent performance. Tackle Critical and High items first.

  1. 1

    llms-full.txt

    Medium impact0/1 passingHow to fix

    Why it matters: Missing this signal makes it harder for agents to understand your site.

    Fix these 1 item
    • llms-full.txt found
      Serve `/llms-full.txt` — the companion to llms.txt containing the full concatenated text of your site so non-browsing LLMs (RAG pipelines, embedded agents) can ingest your content in one fetch. See [the spec](https://llmstxt.org/).
  2. 2

    Homepage & Meta

    Medium impact4/6 passing

    Why it matters: Missing this signal makes it harder for agents to understand your site.

    Fix these 2 items
    • llms.txt linked from HTML
      Add `<link rel="alternate" type="text/plain" href="/llms.txt">` to your HTML `<head>` so search engines (especially Google for Gemini) index the relationship.
    • Organization JSON-LD
      Add an `Organization` JSON-LD block to your homepage with `name`, `url`, `logo`, and `description`. This is the canonical entity declaration AI agents and search engines use to identify your company. Use your real URL and a reachable logo URL — fake URLs make agents distrust the block. See https://agentgrade.com/kb/organization-jsonld.
  3. 3

    Content negotiation

    Medium impact0/5 passingHow to fix

    Why it matters: Missing this signal makes it harder for agents to understand your site.

    Fix these 5 items
    • Agent UA gets non-HTML
      Agent fetch tools commonly send `Accept: text/markdown, text/html, */*` — markdown listed first, which per [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept) signals it as the preferred type. Serve markdown or plain text in that case, not HTML. Three common bugs produce the wrong outcome: (1) substring-matching the Accept header (`if accept.includes('text/html')`) — ignores preference order entirely; (2) framework defaults that pick HTML whenever `*/*` is in Accept, overriding explicit non-HTML preferences (Rails 8: `Accept: text/markdown, */*` returns HTML even though markdown is listed first); (3) preferring server-declared format order over client preference and ignoring q-values — even `Accept: text/plain;q=0.9, text/html;q=0.5` returns HTML when the server's internal preference list favors HTML. Fix recipes by stack: Express — `req.accepts(['text/html','text/markdown','text/plain'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — set `request.format` explicitly in a `before_action` based on the leading Accept type. All proper Accept negotiators handle preference order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which of the three bug patterns is in play. Pass requires Content-Type `text/markdown`, `text/plain`, or `application/json` (anything but HTML) with body ≥20 bytes. See https://agentgrade.com/kb/content-negotiation.
    • Accept: JSON returns JSON
      Return JSON with status 200 when `Accept: application/json` is sent to the root URL. Note: the response must not have a top-level `"error"` field — structured error responses like `{"error": "..."}` will fail this check.
    • Accept: text returns text
      Return `Content-Type: text/plain` or `text/markdown` with status 200 when `Accept: text/plain` is sent to the root URL. The body must be at least 20 bytes.
    • Accept: markdown returns markdown
      Return `Content-Type: text/markdown` when the client sends `Accept: text/markdown`. Agents that prefer markdown can request it directly without scraping HTML.
    • + 1 more — see full breakdown below.
  4. 4

    Infrastructure

    Medium impact3/4 passingHow to fix

    Why it matters: Missing this signal makes it harder for agents to understand your site.

    Fix these 1 item
    • Structured errors (JSON 404)
      Return JSON error responses instead of HTML error pages. Agents can parse `{"error": "not found"}` but not an HTML 404 page wrapped in nav/footer chrome. Use content negotiation: when `Accept: application/json`, return JSON; for browsers, return HTML. SPA gotcha: if your catch-all serves `index.html` with status 200 for unknown paths, the check fails — return status 404 from the server, then let the SPA route on the client. See https://agentgrade.com/kb/infrastructure.
0%

llms-full.txt

Learn →
0 / 1 checks passed
llms-full.txt found

Serve `/llms-full.txt` — the companion to llms.txt containing the full concatenated text of your site so non-browsing LLMs (RAG pipelines, embedded agents) can ingest your content in one fetch. See [the spec](https://llmstxt.org/).

llms-full.txt linked from HTML(optional)

Add `<link rel="alternate" type="text/plain" href="/llms-full.txt">` to your HTML `<head>` so agents and search engines discover the full-content companion file.

<link rel="alternate" type="text/plain" title="llms-full.txt" href="/llms-full.txt">
67%

Homepage & Meta

4 / 6 checks passed
llms.txt linked from HTML

Add `<link rel="alternate" type="text/plain" href="/llms.txt">` to your HTML `<head>` so search engines (especially Google for Gemini) index the relationship.

<link rel="alternate" type="text/plain" title="llms.txt" href="/llms.txt">
JSON-LD structured data
Organization JSON-LD

Add an `Organization` JSON-LD block to your homepage with `name`, `url`, `logo`, and `description`. This is the canonical entity declaration AI agents and search engines use to identify your company. Use your real URL and a reachable logo URL — fake URLs make agents distrust the block. See https://agentgrade.com/kb/organization-jsonld.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "vercel.com",
  "url": "https://vercel.com/",
  "logo": "https://vercel.com/logo.png",
  "description": "What your company does, in one sentence."
}
</script>
sameAs entity links(optional)

(Optional) Add a `sameAs` array to your Organization JSON-LD listing your canonical surfaces (GitHub, npm package, X profile, LinkedIn, registry listings). List only profiles you actually maintain — agents follow these links and dead/wrong entries undermine the trust signal. See https://agentgrade.com/kb/organization-jsonld.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "vercel.com",
  "url": "https://vercel.com/",
  "sameAs": [
    "https://github.com/your-org",
    "https://x.com/your-handle",
    "https://www.linkedin.com/company/your-org",
    "https://www.npmjs.com/package/your-package"
  ]
}
</script>
og:image declared
og:image reachable
twitter:card set(optional)
Favicon SVG(optional)

Add `<link rel="icon" type="image/svg+xml" href="/favicon.svg">` for crisp icons at any resolution.

Favicon PNG(optional)

Add `<link rel="icon" type="image/png" href="/favicon.png">` (at least 180x180) or serve `/favicon.png` for broad browser compatibility.

0%

agents.txt

Learn →
0 / 0 checks passed
agents.txt found(optional)

Serve `/agents.txt` with at least 10 characters of content. There is no required format — the scanner only checks for existence. Best practice: define agent access policies, rate limits, and allowed actions.

0%

Content negotiation

Learn →
0 / 5 checks passed
Agent UA gets non-HTML

Agent fetch tools commonly send `Accept: text/markdown, text/html, */*` — markdown listed first, which per [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept) signals it as the preferred type. Serve markdown or plain text in that case, not HTML. Three common bugs produce the wrong outcome: (1) substring-matching the Accept header (`if accept.includes('text/html')`) — ignores preference order entirely; (2) framework defaults that pick HTML whenever `*/*` is in Accept, overriding explicit non-HTML preferences (Rails 8: `Accept: text/markdown, */*` returns HTML even though markdown is listed first); (3) preferring server-declared format order over client preference and ignoring q-values — even `Accept: text/plain;q=0.9, text/html;q=0.5` returns HTML when the server's internal preference list favors HTML. Fix recipes by stack: Express — `req.accepts(['text/html','text/markdown','text/plain'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — set `request.format` explicitly in a `before_action` based on the leading Accept type. All proper Accept negotiators handle preference order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which of the three bug patterns is in play. Pass requires Content-Type `text/markdown`, `text/plain`, or `application/json` (anything but HTML) with body ≥20 bytes. See https://agentgrade.com/kb/content-negotiation.

Accept: JSON returns JSON

Return JSON with status 200 when `Accept: application/json` is sent to the root URL. Note: the response must not have a top-level `"error"` field — structured error responses like `{"error": "..."}` will fail this check.

Accept: text returns text

Return `Content-Type: text/plain` or `text/markdown` with status 200 when `Accept: text/plain` is sent to the root URL. The body must be at least 20 bytes.

Accept: markdown returns markdown

Return `Content-Type: text/markdown` when the client sends `Accept: text/markdown`. Agents that prefer markdown can request it directly without scraping HTML.

Returns preferred Content-Type

Stricter than "Agent UA gets non-HTML": the response Content-Type must match the type the client actually preferred, accounting for client order AND q-values. The scanner probes four variations: (1) `Accept: text/markdown, text/html, */*` should return `text/markdown` (Claude Code / Cursor pattern); (2) `Accept: text/html, text/markdown, */*` should return `text/html` (reverse-order — catches sites that ignore client order and use server-side preference instead); (3) `Accept: text/markdown;q=0.5, text/html;q=1.0` should return `text/html` (catches sites that ignore q-values entirely — see [RFC 9110 §12.5.1](https://www.rfc-editor.org/rfc/rfc9110#name-accept)); (4) `Accept: application/json, text/html, */*` should return `application/json` (programmatic discovery). All four must pass. Common bug patterns: substring matching the Accept header (`if accept.includes('text/html')`); framework defaults that fall back to HTML when `*/*` is present (Rails 8); hand-rolled selection logic using server-side priority order; q-value parsing missing entirely. Fix recipe by stack: Express — `req.accepts(['text/html','text/markdown','text/plain','application/json'])`; Node bare — `negotiator` npm package; Python — `werkzeug.wrappers.AcceptMixin`; Go — `github.com/markusthoemmes/goautoneg`; Rails — `before_action` setting `request.format` based on the leading non-wildcard Accept type. All proper Accept negotiators handle order, q-values, and wildcards per RFC 9110 — the fix recipe is the same regardless of which bug pattern is in play. See https://agentgrade.com/kb/content-negotiation.

Inline content negotiation(optional)

Your homepage redirects agent requests (302/301) to a different URL — likely `/llms.txt` or similar. Industry best practice is **inline content negotiation**: same URL returns different bodies based on the `Accept` header. Cited by [RFC 9110 §12.2](https://www.rfc-editor.org/rfc/rfc9110#name-reactive-negotiation) (reactive/redirect-based negotiation "suffers from... needing a second request"), and used by every major content-negotiation-aware site we tested — GitHub API, Stripe docs, Cloudflare docs, Vercel, Mintlify, Sanity. The fix: detect Accept on the same URL, serve the right body inline, set `Vary: Accept`. No 302. Concrete benefits: one fetch instead of two (half the latency), the URL the agent reports to the user is the URL they asked about (not a redirect target), and caches handle it cleanly. See https://agentgrade.com/kb/content-negotiation.

Vary: Accept set on negotiated response(optional)

When the same URL returns different bodies based on the `Accept` header, include `Vary: Accept` in the response so shared caches (CDNs, proxies) key entries by `Accept`. Without it, a CDN can serve JSON to a browser or HTML to an agent depending on which request hit first. `Vary: *` or `Cache-Control: private` / `no-store` also satisfy this. Only applies to sites that actually negotiate — API-only origins serving JSON to every request are exempt.

75%

Infrastructure

Learn →
3 / 4 checks passed
HTTPS redirect
Sitemap.xml
Cache headers
Structured errors (JSON 404)

Return JSON error responses instead of HTML error pages. Agents can parse `{"error": "not found"}` but not an HTML 404 page wrapped in nav/footer chrome. Use content negotiation: when `Accept: application/json`, return JSON; for browsers, return HTML. SPA gotcha: if your catch-all serves `index.html` with status 200 for unknown paths, the check fails — return status 404 from the server, then let the SPA route on the client. See https://agentgrade.com/kb/infrastructure.

// Content negotiation: JSON for agents, HTML for browsers
app.use((req, res) => {
  res.status(404);
  if (req.accepts('json') && !req.accepts('html')) {
    return res.json({ error: 'not_found', path: req.path });
  }
  res.format({
    'application/json': () => res.json({ error: 'not_found', path: req.path }),
    'text/html': () => res.send('<h1>404 Not Found</h1>'),
    default: () => res.type('text/plain').send('Not found'),
  });
});
Rate limit headers(optional)

(Optional) Return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, or `Retry-After` headers so agents can self-throttle. See https://agentgrade.com/kb/infrastructure.

// Surface rate-limit state so agents can self-throttle
import rateLimit from 'express-rate-limit';

app.use('/api', rateLimit({
  windowMs: 60_000,
  max: 60,
  standardHeaders: true,   // adds RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset
  legacyHeaders: false,
}));
0%

Bot authentication

Learn →
0 / 0 checks passed
Signatures directory published(optional)

Serve `/.well-known/http-message-signatures-directory` with a JSON object listing your agent identity and public keys (RFC 9421).

Members declared(optional)

Add a `members` array to your signatures directory with at least one entry containing `name` and `publicKeyUrl`.

Public keys available(optional)

Each member in your signatures directory should have a `publicKeyUrl` pointing to a fetchable public key for signature verification.

0%

A2A (Agent-to-Agent)

Learn →
0 / 0 checks passed
Agent Card published(optional)

(Optional) Publish a Google A2A Agent Card at `/.well-known/agent.json` with `name`, `url`, `description`, and `capabilities` to let other agents discover and interact with your agent. Use your real URL and only capabilities you actually expose. See https://agentgrade.com/kb/a2a.

{
  "name": "vercel.com",
  "url": "https://vercel.com/",
  "description": "What this agent does, in one sentence.",
  "version": "1.0.0",
  "capabilities": {
    "streaming": false,
    "pushNotifications": false
  },
  "skills": [
    {
      "id": "search",
      "name": "Search catalog",
      "description": "Search the product catalog for matching items"
    }
  ]
}
Agent Card verified(optional)

(Optional) Ensure your Agent Card contains at least `name` and `url` fields so agents can identify and connect to your service. See https://agentgrade.com/kb/a2a.

0%

WebMCP

Learn →
0 / 0 checks passed
WebMCP manifest(optional)

(Optional) Publish a WebMCP manifest at `/.well-known/webmcp.json` listing tools your site exposes to browser-based AI agents. See https://agentgrade.com/kb/webmcp.

{
  "name": "vercel.com",
  "version": "1.0.0",
  "description": "Browser-accessible MCP tools for vercel.com",
  "tools": [
    {
      "name": "search",
      "description": "Search the catalog",
      "inputSchema": {
        "type": "object",
        "properties": { "query": { "type": "string" } },
        "required": ["query"]
      }
    }
  ]
}
Form tool annotations(optional)

(Optional) Add `tool-name` and `tool-description` attributes to `<form>` elements so browser AI agents can invoke them as structured tools. See https://agentgrade.com/kb/webmcp.

<form action="/api/v1/search" method="post"
      tool-name="search"
      tool-description="Search the catalog">
  <input name="query" type="text" placeholder="Search..." required>
  <button type="submit">Search</button>
</form>
0%

Identity

Learn →
0 / 0 checks passed
WebFinger(optional)
DID Document(optional)
Nostr NIP-05(optional)
AT Protocol DID(optional)
Apple App Links(optional)
Android Asset Links(optional)

Not Applicable

Skipped for this type of site.

Machine Payments

Learn →
Not applicable to this site
Live 402 response

Return HTTP 402 with a Payment-Required header on at least one paid endpoint.

x402

Add x402 middleware that returns a base64-encoded Payment-Required header with amount, asset, payTo, and facilitator.

MPP

Add MPP middleware: `npm install mppx`, configure with MPP_SECRET_KEY and MPP_RECIPIENT_ADDRESS.

SPT (Stripe)

Add Stripe SPT support via mppx: `npm install mppx`, configure with Stripe secret key. Returns 402 with `WWW-Authenticate: Payment method="stripe"`.

L402

Add L402 support: return 402 with `WWW-Authenticate: L402 macaroon="...", invoice="..."`.

Payment header decodable

Ensure the Payment-Required header is valid base64-encoded JSON with x402Version, accepts[], and resource fields.

Recipient specified

Include `payTo` (wallet address) in the payment challenge or in /.well-known/x402.json.

Payment terms specified

Include `amount` in the payment challenge.

Discovery published

Publish `/.well-known/x402.json` so agents can discover your paid endpoints. Required fields: `x402Version`, `name`, `network`, `facilitator`, `payTo`, `services[]`. See https://agentgrade.com/kb/bazaar for schema and example.

Bazaar discovery

Learn →
Not applicable to this site
Bazaar in live 402 header

Add `"extensions": { "bazaar": { "discoverable": true } }` inside the base64-encoded Payment-Required header JSON. See https://agentgrade.com/kb/bazaar for a full example.

/.well-known/x402.json exists

Serve `/.well-known/x402.json` so agents can discover your paid endpoints. Required fields: `x402Version`, `name`, `network`, `facilitator`, `payTo`, `services[]`. See https://agentgrade.com/kb/bazaar for the full spec.

{
  "x402Version": 2,
  "name": "vercel.com",
  "network": "base",
  "facilitator": "coinbase",
  "payTo": "0xYourWalletAddress",
  "services": [
    {
      "method": "POST",
      "path": "/your-paid-endpoint",
      "description": "What this endpoint does",
      "amount": "0.50"
    }
  ]
}
Discoverable service declared

Declare at least one service with `method` and `path` fields (e.g. `"method": "POST", "path": "/your-endpoint"`). Optionally add `extensions.bazaar.discoverable: true` for Bazaar registry listing.

Provider (name) specified

Add `"name": "vercel.com"` to `/.well-known/x402.json`.

Facilitator specified

Add `"facilitator": "coinbase"` (or your facilitator URL) to `/.well-known/x402.json`.

payTo specified

Add `"payTo": "0xYourWallet"` to `/.well-known/x402.json` — this is the wallet address that receives payments.

MCP endpoint

Learn →
Not applicable to this site
MCP endpoint responds

Serve a JSON-RPC 2.0 endpoint (checked at /mcp, /api/mcp, /.well-known/mcp.json, /mcp/docs). The scanner accepts 200, 405, or 406 as indicators that an MCP endpoint exists. See https://agentgrade.com/kb/mcp.

// Minimal Express JSON-RPC 2.0 handler at /mcp
app.post('/mcp', express.json(), (req, res) => {
  const { id, method, params } = req.body || {};
  if (method === 'initialize') {
    return res.json({ jsonrpc: '2.0', id, result: {
      protocolVersion: '2024-11-05',
      capabilities: { tools: {} },
      serverInfo: { name: 'vercel.com', version: '1.0.0' },
    }});
  }
  if (method === 'tools/list') {
    return res.json({ jsonrpc: '2.0', id, result: { tools: [
      { name: 'echo', description: 'Echoes its input',
        inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } },
    ]}});
  }
  if (method === 'tools/call') {
    const { name, arguments: args } = params || {};
    if (name === 'echo') {
      return res.json({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: args.text }] }});
    }
    return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Unknown tool: ' + name }});
  }
  res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' }});
});
Initialize handshake

Handle the `initialize` JSON-RPC method and return `{ "protocolVersion": "2024-11-05", "capabilities": {}, "serverInfo": { "name": "your-service" } }` in the result. See https://agentgrade.com/kb/mcp.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "vercel.com", "version": "1.0.0" }
  }
}
Tools listed

Handle `tools/list` and return `{ "tools": [{ "name": "...", "description": "...", "inputSchema": {...} }] }`. See https://agentgrade.com/kb/mcp.

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "search",
        "description": "Search the catalog for matching items",
        "inputSchema": {
          "type": "object",
          "properties": { "query": { "type": "string" } },
          "required": ["query"]
        }
      }
    ]
  }
}
tools/call responds

Handle `tools/call` with `{ "name": "tool_name", "arguments": {...} }` — even a structured JSON-RPC error response proves the pipeline works. See https://agentgrade.com/kb/mcp.

// Handle tools/call in the same /mcp handler
if (method === 'tools/call') {
  const { name, arguments: args } = params || {};
  if (name === 'search') {
    const results = await db.search(args.query);
    return res.json({ jsonrpc: '2.0', id, result: {
      content: [{ type: 'text', text: JSON.stringify(results) }]
    }});
  }
  return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Unknown tool: ' + name }});
}
Server name present

Return `name` in your MCP manifest or `serverInfo.name` in the initialize response. See https://agentgrade.com/kb/mcp.

SSE transport(optional)

(Optional) Serve SSE by returning `Content-Type: text/event-stream` on GET requests to the MCP endpoint. Streamable HTTP (the newer MCP transport) is detected separately via 406 status. See https://agentgrade.com/kb/mcp.

CORS enabled

Add `Access-Control-Allow-Origin: *`, `Access-Control-Allow-Methods: GET, POST, OPTIONS`, and `Access-Control-Allow-Headers: Content-Type, Accept, Authorization` to your MCP endpoint, and respond to OPTIONS preflight with 204. Without these, browser-based agents (Claude.ai web, ChatGPT browser, extensions) cannot reach your MCP endpoint. Use a specific origin instead of `*` if your endpoint is paid or auth-gated. See https://agentgrade.com/kb/mcp.

// Browser-based agents (Claude.ai web, ChatGPT browser) need CORS on /mcp
app.options('/mcp', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization');
  res.status(204).end();
});

app.use('/mcp', (req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  next();
});

OpenAPI spec

Learn →
Not applicable to this site
OpenAPI spec found

Serve an OpenAPI spec (checked at /openapi.json, /openapi.yaml, /swagger.json, /api-docs, /v1/openapi.json). Must contain an `openapi` or `swagger` key and be valid JSON. See https://agentgrade.com/kb/openapi.

{
  "openapi": "3.1.0",
  "info": {
    "title": "vercel.com API",
    "version": "1.0.0",
    "description": "Public API for vercel.com"
  },
  "servers": [{ "url": "https://vercel.com" }],
  "paths": {
    "/api/v1/things": {
      "post": {
        "summary": "Create a thing",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "properties": { "name": { "type": "string" }, "qty": { "type": "integer" } },
            "required": ["name"]
          }}}
        },
        "responses": { "201": { "description": "Created" } }
      }
    }
  }
}
Paths defined

Add `paths` to your OpenAPI spec with at least one endpoint definition.

Paths reachable

Ensure the paths declared in your OpenAPI spec resolve to real routes (any non-404 response counts, including 401 and 402).

Response matches spec

Ensure at least one declared endpoint returns a JSON response with status 200-499 (excluding 404) and a Content-Type matching your spec.

Version identified

Include `"openapi": "3.0.0"` or `"swagger": "2.0"` at the root of your spec.

Title present

Add `"info": { "title": "Your API" }` to your OpenAPI spec.

x-payment-info declared(optional)

Add `x-payment-info` to paid OpenAPI operations: `"x-payment-info": { "protocols": [...], "price": { "amount": "0.50", "currency": "USD" } }`. Helps agents discover pricing before triggering a 402.

Skill file

Learn →
Not applicable to this site
Skill file found

Serve `/skill.md` per the [agentskills.io](https://agentskills.io/specification) spec — markdown with YAML frontmatter (`name`, `description`) and a body of agent instructions.

Frontmatter valid

Add YAML frontmatter to your skill.md: `---\nname: my-skill\ndescription: One-line summary\n---`. `name` is 1-64 chars, lowercase alphanumeric with single hyphens. `description` is 1-1024 chars.

Made changes? Re-scan