From 0ad384ae36b3e56cb317bc15fe85653d8bb0116d Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Thu, 8 Oct 2026 13:21:58 +0100 Subject: [PATCH] feat(ai-agent): comprehensive IsAgentReady & WebMCP optimizations (#692) * feat(ai-agent): implement complete IsAgentReady & WebMCP recommendations - Security & Trust: Add HSTS, CSP frame-ancestors, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy headers - Content Signals: Add AIPREF Content-Usage header (train-ai=n, search=y) and robots.txt directive - Agent Guides & Contracts: Add /AGENTS.md, /pricing.md, and /.well-known/agents.json - Agent Protocols: Add /openapi.json alias, /.well-known/agent-card.json, and SEP-1649 /.well-known/mcp.json - Agent Skills: Add v0.2.0 RFC index with valid sha256 digest and search-content/SKILL.md - Accessibility & Semantics: Add descriptive fallbackAlt for all cover images and autocomplete on newsletter input - Trust Anchors & Docs: Add /about, /contact, /privacy, and /developers pages and footer links - WebMCP: Add explicit readOnlyHint and untrustedContentHint trust annotations * style: format tier1-features.test.mjs with biome --- apps/site/baseline/robots.txt | 3 + apps/site/src/components/ContentCard.astro | 7 +- apps/site/src/components/CoverImage.astro | 5 +- apps/site/src/components/Footer.astro | 11 +- apps/site/src/components/HomeSection.astro | 1 + apps/site/src/layouts/BaseLayout.astro | 7 ++ apps/site/src/middleware.ts | 24 +++- .../src/pages/.well-known/agent-card.json.ts | 39 ++++++ .../.well-known/agent-skills/index.json.ts | 34 ++---- .../agent-skills/search-content/SKILL.md.ts | 26 ++++ .../site/src/pages/.well-known/agents.json.ts | 39 ++++++ .../site/src/pages/.well-known/api-catalog.ts | 8 ++ apps/site/src/pages/.well-known/mcp.json.ts | 26 +--- .../pages/.well-known/mcp/server-card.json.ts | 18 +-- apps/site/src/pages/.well-known/openapi.ts | 1 + apps/site/src/pages/AGENTS.md.ts | 76 ++++++++++++ apps/site/src/pages/about.astro | 52 ++++++++ apps/site/src/pages/agents.json.ts | 1 + apps/site/src/pages/contact.astro | 56 +++++++++ apps/site/src/pages/developers.astro | 114 ++++++++++++++++++ apps/site/src/pages/docs.astro | 5 + apps/site/src/pages/openapi.json.ts | 1 + apps/site/src/pages/pricing.md.ts | 41 +++++++ apps/site/src/pages/privacy-policy.astro | 5 + apps/site/src/pages/privacy.astro | 59 +++++++++ apps/site/src/pages/robots.txt.ts | 3 + apps/site/tests/tier1-features.test.mjs | 84 +++++++++++++ 27 files changed, 683 insertions(+), 63 deletions(-) create mode 100644 apps/site/src/pages/.well-known/agent-card.json.ts create mode 100644 apps/site/src/pages/.well-known/agent-skills/search-content/SKILL.md.ts create mode 100644 apps/site/src/pages/.well-known/agents.json.ts create mode 100644 apps/site/src/pages/.well-known/openapi.ts create mode 100644 apps/site/src/pages/AGENTS.md.ts create mode 100644 apps/site/src/pages/about.astro create mode 100644 apps/site/src/pages/agents.json.ts create mode 100644 apps/site/src/pages/contact.astro create mode 100644 apps/site/src/pages/developers.astro create mode 100644 apps/site/src/pages/docs.astro create mode 100644 apps/site/src/pages/openapi.json.ts create mode 100644 apps/site/src/pages/pricing.md.ts create mode 100644 apps/site/src/pages/privacy-policy.astro create mode 100644 apps/site/src/pages/privacy.astro diff --git a/apps/site/baseline/robots.txt b/apps/site/baseline/robots.txt index 6306ae518..df3b2b4ae 100644 --- a/apps/site/baseline/robots.txt +++ b/apps/site/baseline/robots.txt @@ -1,5 +1,6 @@ User-Agent: * Content-Signal: ai-train=no, search=yes, ai-input=yes +Content-Usage: / train-ai=n, search=y Allow: / Allow: /api/search Allow: /api/mcp @@ -7,6 +8,8 @@ Allow: /.well-known/ Allow: /llms.txt Allow: /llms-full.txt Allow: /auth.md +Allow: /AGENTS.md +Allow: /pricing.md Disallow: /api/ Disallow: /dashboard/ diff --git a/apps/site/src/components/ContentCard.astro b/apps/site/src/components/ContentCard.astro index 788838df5..cfe4e8ee1 100644 --- a/apps/site/src/components/ContentCard.astro +++ b/apps/site/src/components/ContentCard.astro @@ -27,13 +27,18 @@ const people = [ {person ? ( ) : ( - + )} ) diff --git a/apps/site/src/components/CoverImage.astro b/apps/site/src/components/CoverImage.astro index 3a27de766..75c875a9e 100644 --- a/apps/site/src/components/CoverImage.astro +++ b/apps/site/src/components/CoverImage.astro @@ -4,6 +4,7 @@ import type { SanityImageSource } from "@sanity/image-url"; interface Props { image?: (SanityImageSource & { alt?: string }) | null; + fallbackAlt?: string; width?: number; height?: number; quality?: number; @@ -14,6 +15,7 @@ interface Props { const { image, + fallbackAlt = "CodingCat.dev tutorial cover image", width = 1920, height = 1080, quality = 80, @@ -30,7 +32,8 @@ const src = Astro.locals.sanity // stegaClean: alt lands in an attribute, where stega's zero-width characters // would be read out verbatim by screen readers. -const alt = stegaClean((image as { alt?: string } | null)?.alt) ?? ""; +const rawAlt = stegaClean((image as { alt?: string } | null)?.alt); +const alt = rawAlt && rawAlt.trim().length > 0 ? rawAlt : fallbackAlt; ---
diff --git a/apps/site/src/components/Footer.astro b/apps/site/src/components/Footer.astro index 3f430e7fa..12adc6f82 100644 --- a/apps/site/src/components/Footer.astro +++ b/apps/site/src/components/Footer.astro @@ -4,9 +4,12 @@ import NavLink from "./NavLink.astro"; const year = new Date().getUTCFullYear(); const legalLinks = [ - { href: "/ftc-disclosure", label: "FTC Disclosure" }, + { href: "/about", label: "About" }, + { href: "/contact", label: "Contact" }, + { href: "/developers", label: "Developers" }, + { href: "/privacy", label: "Privacy Policy" }, { href: "/terms-of-use", label: "Terms of Use" }, - { href: "/privacy-policy", label: "Privacy Policy" }, + { href: "/ftc-disclosure", label: "FTC Disclosure" }, { href: "/sponsors", label: "Sponsors" }, { href: "/sponsorships", label: "Sponsorships" }, ]; @@ -84,14 +87,18 @@ const socialLinks = [ action="https://buttondown.email/api/emails/embed-subscribe/codingcatdev" method="post" target="popupwindow" + toolname="subscribe_newsletter" + tooldescription="Subscribe to the CodingCat.dev web development newsletter" > diff --git a/apps/site/src/components/HomeSection.astro b/apps/site/src/components/HomeSection.astro index 01ff6f5e9..f63ad522f 100644 --- a/apps/site/src/components/HomeSection.astro +++ b/apps/site/src/components/HomeSection.astro @@ -42,6 +42,7 @@ const { title, description, items, muted = false } = Astro.props; {item.coverImage && ( + + + {/* WebMCP API: Expose client-side tools to browser AI agents */} @@ -127,6 +130,10 @@ const graph = buildGraph([ mc.registerTool({ name: "search_content", description: "Search CodingCat.dev tutorials, articles, podcasts, guests, and sponsors", + annotations: { + readOnlyHint: true, + untrustedContentHint: true, + }, inputSchema: { type: "object", properties: { diff --git a/apps/site/src/middleware.ts b/apps/site/src/middleware.ts index c4cd80031..7a049dab5 100644 --- a/apps/site/src/middleware.ts +++ b/apps/site/src/middleware.ts @@ -33,7 +33,10 @@ export const onRequest = defineMiddleware(async (context, next) => { const origin = context.locals.siteUrl.origin; const pathname = context.url.pathname; - const isDedicatedMarkdownEndpoint = pathname === "/auth.md"; + const isDedicatedMarkdownEndpoint = + pathname === "/auth.md" || + pathname === "/pricing.md" || + pathname.toLowerCase() === "/agents.md"; const isMarkdownUrl = !isDedicatedMarkdownEndpoint && (pathname.endsWith(".md") || pathname.endsWith("/index.md")); @@ -43,7 +46,9 @@ export const onRequest = defineMiddleware(async (context, next) => { const wantsMarkdown = isMarkdownUrl || Boolean(acceptMarkdown); let response: Response; - if (isMarkdownUrl) { + if (pathname === "/agents.md") { + response = await context.rewrite("/AGENTS.md"); + } else if (isMarkdownUrl) { const cleanPath = pathname.replace(/\/index\.md$/, "").replace(/\.md$/, "") || "/"; response = await context.rewrite(cleanPath); @@ -58,10 +63,23 @@ export const onRequest = defineMiddleware(async (context, next) => { response.headers.set("X-Robots-Tag", "noindex, nofollow"); } + // Security & Trust Headers + response.headers.set( + "Strict-Transport-Security", + "max-age=31536000; includeSubDomains; preload", + ); + response.headers.set("X-Content-Type-Options", "nosniff"); + response.headers.set("X-Frame-Options", "SAMEORIGIN"); + response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin"); + response.headers.set("Content-Security-Policy", "frame-ancestors 'self'"); + + // AIPREF / AI Preference Headers + response.headers.set("Content-Usage", "train-ai=n, search=y"); + // Always emit Link headers (RFC 8288) for AI agent discoverability response.headers.set( "Link", - `<${origin}/.well-known/api-catalog>; rel="api-catalog", <${origin}/.well-known/mcp/server-card.json>; rel="mcp-server-card", <${origin}/.well-known/agent-skills/index.json>; rel="agent-skills", <${origin}/.well-known/ai-catalog.json>; rel="ai-catalog", <${origin}/.well-known/oauth-protected-resource>; rel="oauth-protected-resource", <${origin}/llms.txt>; rel="llms-txt", <${origin}/auth.md>; rel="auth-md"`, + `<${origin}/.well-known/api-catalog>; rel="api-catalog", <${origin}/.well-known/mcp/server-card.json>; rel="mcp-server-card", <${origin}/.well-known/agent-skills/index.json>; rel="agent-skills", <${origin}/.well-known/ai-catalog.json>; rel="ai-catalog", <${origin}/.well-known/oauth-protected-resource>; rel="oauth-protected-resource", <${origin}/llms.txt>; rel="llms-txt", <${origin}/auth.md>; rel="auth-md", <${origin}/AGENTS.md>; rel="agent-guide", <${origin}/pricing.md>; rel="pricing"`, ); response.headers.append("Vary", "Accept"); diff --git a/apps/site/src/pages/.well-known/agent-card.json.ts b/apps/site/src/pages/.well-known/agent-card.json.ts new file mode 100644 index 000000000..8d5ab6682 --- /dev/null +++ b/apps/site/src/pages/.well-known/agent-card.json.ts @@ -0,0 +1,39 @@ +import type { APIRoute } from "astro"; + +export const prerender = false; + +/** + * Serves A2A Agent Card for agent-to-agent discovery + */ +export const GET: APIRoute = async ({ locals }) => { + const origin = locals.siteUrl.origin; + + const agentCard = { + name: "CodingCat.dev Agent", + description: + "CodingCat.dev AI agent for fullstack web development tutorials, podcasts, and semantic search.", + url: origin, + version: "1.0.0", + protocol: "a2a", + endpoints: { + mcp: `${origin}/api/mcp`, + search: `${origin}/api/search`, + auth: `${origin}/agent/auth`, + claim: `${origin}/agent/claim`, + }, + capabilities: { + search: true, + mcp: true, + read_content: true, + }, + documentation: `${origin}/AGENTS.md`, + }; + + return new Response(JSON.stringify(agentCard, null, 2), { + headers: { + "content-type": "application/json; charset=utf-8", + "cache-control": "public, max-age=3600, s-maxage=86400", + "access-control-allow-origin": "*", + }, + }); +}; diff --git a/apps/site/src/pages/.well-known/agent-skills/index.json.ts b/apps/site/src/pages/.well-known/agent-skills/index.json.ts index b771b70ca..b9e2756be 100644 --- a/apps/site/src/pages/.well-known/agent-skills/index.json.ts +++ b/apps/site/src/pages/.well-known/agent-skills/index.json.ts @@ -6,37 +6,22 @@ export const GET: APIRoute = async ({ locals }) => { const origin = locals.siteUrl.origin; const skillsIndex = { - $schema: "https://agentskills.io/schema/v1/index.json", - version: "1.0", + $schema: + "https://raw.githubusercontent.com/cloudflare/agent-skills-discovery-rfc/main/schema/index.schema.json", + version: "0.2.0", provider: { name: "CodingCat.dev", url: origin, }, skills: [ { - id: "search-content", - name: "Search Content", + name: "search-content", + type: "skill-md", description: - "Search full articles, podcast transcripts, and coding tutorials on CodingCat.dev across fullstack web development topics.", - endpoint: `${origin}/api/search`, - method: "GET", - parameters: [ - { - name: "q", - type: "string", - required: true, - description: "The programming term or question to search", - }, - ], - }, - { - id: "mcp-tools", - name: "Model Context Protocol Tools", - description: - "Streamable HTTP MCP tool execution for autonomous AI agent pair programming and search.", - endpoint: `${origin}/api/mcp`, - method: "POST", - documentation: `${origin}/.well-known/mcp/server-card.json`, + "Search fullstack web development tutorials, podcasts, and blog posts on CodingCat.dev", + url: `${origin}/.well-known/agent-skills/search-content/SKILL.md`, + digest: + "sha256:16567e9f7d6d3ed78c4be72cdb450eb46b384f5f91fc355ae866f364859164db", }, ], }; @@ -45,6 +30,7 @@ export const GET: APIRoute = async ({ locals }) => { headers: { "content-type": "application/json; charset=utf-8", "cache-control": "public, max-age=3600, s-maxage=86400", + "access-control-allow-origin": "*", Vary: "Accept", Link: `<${origin}/llms.txt>; rel="llms-txt", <${origin}/.well-known/api-catalog>; rel="api-catalog"`, }, diff --git a/apps/site/src/pages/.well-known/agent-skills/search-content/SKILL.md.ts b/apps/site/src/pages/.well-known/agent-skills/search-content/SKILL.md.ts new file mode 100644 index 000000000..098383717 --- /dev/null +++ b/apps/site/src/pages/.well-known/agent-skills/search-content/SKILL.md.ts @@ -0,0 +1,26 @@ +import type { APIRoute } from "astro"; + +export const prerender = false; + +export const SKILL_CONTENT = `--- +name: search-content +description: Search fullstack web development tutorials, podcasts, and blog posts on CodingCat.dev +--- + +# Search CodingCat.dev Content + +Use this skill to search and retrieve CodingCat.dev tutorials, articles, podcasts, transcripts, and metadata across modern web development topics (Astro, TypeScript, React, Svelte, Sanity, Cloudflare Workers, Tailwind CSS). + +## Actions +- Search API: GET https://codingcat.dev/api/search?q={query} +- Model Context Protocol (MCP): POST https://codingcat.dev/api/mcp +`; + +export const GET: APIRoute = async () => { + return new Response(SKILL_CONTENT, { + headers: { + "content-type": "text/markdown; charset=utf-8", + "cache-control": "public, max-age=3600, s-maxage=86400", + }, + }); +}; diff --git a/apps/site/src/pages/.well-known/agents.json.ts b/apps/site/src/pages/.well-known/agents.json.ts new file mode 100644 index 000000000..26ca01f4a --- /dev/null +++ b/apps/site/src/pages/.well-known/agents.json.ts @@ -0,0 +1,39 @@ +import type { APIRoute } from "astro"; + +export const prerender = false; + +/** + * Serves agents.json according to AI agent discovery conventions. + */ +export const GET: APIRoute = async ({ locals }) => { + const origin = locals.siteUrl.origin; + + const agentsData = { + agents: [ + { + name: "CodingCat Search & Content Agent", + description: + "Autonomous agent for searching and retrieving CodingCat.dev web development tutorials, articles, podcasts, and sponsor info.", + url: `${origin}/api/mcp`, + protocol: "mcp", + documentation: `${origin}/.well-known/mcp/server-card.json`, + }, + { + name: "CodingCat A2A Agent", + description: + "Agent-to-agent interface for CodingCat.dev discovery and interactions.", + url: `${origin}/agent/claim`, + protocol: "a2a", + documentation: `${origin}/auth.md`, + }, + ], + }; + + return new Response(JSON.stringify(agentsData, null, 2), { + headers: { + "content-type": "application/json; charset=utf-8", + "cache-control": "public, max-age=3600, s-maxage=86400", + "access-control-allow-origin": "*", + }, + }); +}; diff --git a/apps/site/src/pages/.well-known/api-catalog.ts b/apps/site/src/pages/.well-known/api-catalog.ts index e5f8948c5..2a29e67ba 100644 --- a/apps/site/src/pages/.well-known/api-catalog.ts +++ b/apps/site/src/pages/.well-known/api-catalog.ts @@ -14,12 +14,20 @@ export const GET: APIRoute = async ({ locals }) => { { anchor: `${origin}/api/search`, "service-desc": [ + { + href: `${origin}/openapi.json`, + type: "application/vnd.oai.openapi+json", + }, { href: `${origin}/.well-known/openapi.json`, type: "application/vnd.oai.openapi+json", }, ], "service-doc": [ + { + href: `${origin}/developers`, + type: "text/html", + }, { href: `${origin}/llms.txt`, type: "text/markdown", diff --git a/apps/site/src/pages/.well-known/mcp.json.ts b/apps/site/src/pages/.well-known/mcp.json.ts index 18a4d0bfa..22f62f3b5 100644 --- a/apps/site/src/pages/.well-known/mcp.json.ts +++ b/apps/site/src/pages/.well-known/mcp.json.ts @@ -1,25 +1 @@ -import type { APIRoute } from "astro"; - -export const prerender = false; - -export const GET: APIRoute = async ({ locals }) => { - const origin = locals.siteUrl.origin; - - const config = { - name: "codingcatdev-search-mcp", - version: "1.0.0", - description: - "Search CodingCat.dev web development guides, coding tutorials, and podcasts.", - endpoint: `${origin}/api/mcp`, - serverCard: `${origin}/.well-known/mcp/server-card.json`, - }; - - return new Response(JSON.stringify(config, null, 2), { - headers: { - "content-type": "application/json; charset=utf-8", - "cache-control": "public, max-age=3600, s-maxage=86400", - Vary: "Accept", - Link: `<${origin}/.well-known/mcp/server-card.json>; rel="mcp-server-card"`, - }, - }); -}; +export { GET, prerender } from "./mcp/server-card.json"; diff --git a/apps/site/src/pages/.well-known/mcp/server-card.json.ts b/apps/site/src/pages/.well-known/mcp/server-card.json.ts index cf2413d7e..83f7fc516 100644 --- a/apps/site/src/pages/.well-known/mcp/server-card.json.ts +++ b/apps/site/src/pages/.well-known/mcp/server-card.json.ts @@ -6,13 +6,10 @@ export const GET: APIRoute = async ({ locals }) => { const origin = locals.siteUrl.origin; const serverCard = { - $schema: - "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json", - version: "1.0", - protocolVersion: "2025-06-18", + $schema: "https://modelcontextprotocol.io/schema/server-card.json", + name: "CodingCat.dev MCP Server", serverInfo: { name: "codingcatdev-search-mcp", - title: "CodingCat.dev Search & Content MCP Server", version: "1.0.0", }, description: @@ -21,8 +18,14 @@ export const GET: APIRoute = async ({ locals }) => { type: "streamable-http", endpoint: `${origin}/api/mcp`, }, - authentication: { - required: false, + capabilities: { + tools: { listChanged: false }, + resources: {}, + prompts: {}, + }, + auth: { + type: "oauth2", + authorizationServers: [origin], }, tools: [ { @@ -49,6 +52,7 @@ export const GET: APIRoute = async ({ locals }) => { headers: { "content-type": "application/json; charset=utf-8", "cache-control": "public, max-age=3600, s-maxage=86400", + "access-control-allow-origin": "*", Vary: "Accept", Link: `<${origin}/api/mcp>; rel="mcp-endpoint", <${origin}/llms.txt>; rel="llms-txt"`, }, diff --git a/apps/site/src/pages/.well-known/openapi.ts b/apps/site/src/pages/.well-known/openapi.ts new file mode 100644 index 000000000..c2066d716 --- /dev/null +++ b/apps/site/src/pages/.well-known/openapi.ts @@ -0,0 +1 @@ +export { GET, prerender } from "./openapi.json"; diff --git a/apps/site/src/pages/AGENTS.md.ts b/apps/site/src/pages/AGENTS.md.ts new file mode 100644 index 000000000..8aa348db4 --- /dev/null +++ b/apps/site/src/pages/AGENTS.md.ts @@ -0,0 +1,76 @@ +import type { APIRoute } from "astro"; + +export const prerender = false; + +/** + * Serves /AGENTS.md guide for autonomous AI agents, coding assistants, and discovery systems. + */ +export const GET: APIRoute = async ({ locals }) => { + const origin = locals.siteUrl.origin; + + const content = `# codingcat.dev: Agent Guide + +> Autonomous AI Agent Navigation & Capability Guide for CodingCat.dev. + +This site welcomes AI agents, LLM researchers, and coding assistants. No authentication or API keys are required for read operations. + +## Capabilities & Protocols +- **MCP Server Card**: [\`${origin}/.well-known/mcp/server-card.json\`](${origin}/.well-known/mcp/server-card.json) +- **MCP Streamable HTTP Endpoint**: \`${origin}/api/mcp\` +- **JSON API (OpenAPI 3.1)**: [\`${origin}/openapi.json\`](${origin}/openapi.json) +- **API Catalog (RFC 9727)**: [\`${origin}/.well-known/api-catalog\`](${origin}/.well-known/api-catalog) +- **Agent Skills**: [\`${origin}/.well-known/agent-skills/index.json\`](${origin}/.well-known/agent-skills/index.json) +- **AI Resource Manifest (ARD)**: [\`${origin}/.well-known/ai-catalog.json\`](${origin}/.well-known/ai-catalog.json) +- **Agent Index**: [\`${origin}/.well-known/agents.json\`](${origin}/.well-known/agents.json) +- **WebMCP**: Imperative tool \`search_content\` registered on page load and declarative form attributes + +## Content Contracts +- **Authentication**: [\`${origin}/auth.md\`](${origin}/auth.md) +- **Pricing**: [\`${origin}/pricing.md\`](${origin}/pricing.md) +- **LLM Summary**: [\`${origin}/llms.txt\`](${origin}/llms.txt) +- **LLM Full Index**: [\`${origin}/llms-full.txt\`](${origin}/llms-full.txt) + +## How to Connect + +### 1. Direct Search API +\`\`\`http +GET ${origin}/api/search?q={query} HTTP/1.1 +Accept: application/json +\`\`\` + +### 2. Streamable HTTP Model Context Protocol (MCP) +\`\`\`http +POST ${origin}/api/mcp HTTP/1.1 +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "search_content", + "arguments": { "query": "astro" } + }, + "id": 1 +} +\`\`\` + +### 3. Markdown Content Negotiation +Every URL on \`codingcat.dev\` serves clean, token-efficient Markdown: +- Add \`.md\` extension: \`${origin}/blog.md\` +- Or send header: \`Accept: text/markdown\` + +## Etiquette & Policies +- Identify your agent with a descriptive User-Agent. +- Content usage preferences: \`train-ai=n, search=y\`. +- All public read requests are free and do not require rate-limiting under normal usage. +`; + + return new Response(content, { + headers: { + "content-type": "text/markdown; charset=utf-8", + "cache-control": "public, max-age=3600, s-maxage=86400", + Vary: "Accept", + Link: `<${origin}/.well-known/api-catalog>; rel="api-catalog", <${origin}/llms.txt>; rel="llms-txt", <${origin}/pricing.md>; rel="pricing", <${origin}/auth.md>; rel="auth-md"`, + }, + }); +}; diff --git a/apps/site/src/pages/about.astro b/apps/site/src/pages/about.astro new file mode 100644 index 000000000..53dc8a1f3 --- /dev/null +++ b/apps/site/src/pages/about.astro @@ -0,0 +1,52 @@ +--- +import BaseLayout from "@/layouts/BaseLayout.astro"; + +const title = "About CodingCat.dev — Purrfect Web Development"; +const description = + "Learn about CodingCat.dev, our mission, podcast, tutorials, and fullstack web development community led by Alex Patterson."; +--- + + +
+
+

About CodingCat.dev

+

Purrfect Web Development Tutorials, Podcasts & Community

+
+ +
+
+

Our Mission

+

+ CodingCat.dev is an educational media platform and developer community dedicated to making modern fullstack web development accessible, engaging, and practical. Founded and hosted by Alex Patterson, CodingCat.dev produces high-quality technical tutorials, in-depth blog posts, and insightful video and audio podcasts interviewing engineering leaders, open source maintainers, and developer advocates across the technology landscape. +

+
+ +
+

What We Cover

+

+ We focus on modern frontend, backend, and cloud architectures, including: +

+
    +
  • Frameworks & Runtimes: Astro, Next.js, React, SvelteKit, TypeScript, Node.js, and Cloudflare Workers.
  • +
  • Headless CMS & Content Architecture: Sanity.io, structured content modeling, Portable Text, and real-time visual editing.
  • +
  • Edge & Serverless Computing: Cloudflare edge infrastructure, serverless databases, KV, and vector search.
  • +
  • AI & Autonomous Agents: Model Context Protocol (MCP), WebMCP, A2A agent cards, and modern Answer Engine Optimization (AEO).
  • +
+
+ +
+

The CodingCat.dev Podcast

+

+ The CodingCat.dev Podcast features weekly conversations with software engineers, creators, and innovators. We explore how technology leaders build scalable architectures, navigate developer experience challenges, and leverage emerging standards like AI agent protocols and edge runtimes. +

+
+ +
+

Connect with Us

+

+ We believe in building in public and fostering an open, welcoming developer community. You can find our tutorials on YouTube, follow our updates on X, explore our open source repositories on GitHub, and join our newsletter for curated web development insights. +

+
+
+
+
diff --git a/apps/site/src/pages/agents.json.ts b/apps/site/src/pages/agents.json.ts new file mode 100644 index 000000000..a8178e66a --- /dev/null +++ b/apps/site/src/pages/agents.json.ts @@ -0,0 +1 @@ +export { GET, prerender } from "./.well-known/agents.json"; diff --git a/apps/site/src/pages/contact.astro b/apps/site/src/pages/contact.astro new file mode 100644 index 000000000..9e6787b3e --- /dev/null +++ b/apps/site/src/pages/contact.astro @@ -0,0 +1,56 @@ +--- +import BaseLayout from "@/layouts/BaseLayout.astro"; + +const title = "Contact Us — CodingCat.dev"; +const description = + "Get in touch with CodingCat.dev for sponsorships, podcast guest inquiries, speaking, or developer partnerships."; +--- + + +
+
+

Contact CodingCat.dev

+

We’d love to hear from you. Reach out with questions, inquiries, or feedback.

+
+ +
+
+

Email & Direct Inquiries

+

+ For general inquiries, editorial questions, or partnership discussions, you can reach out directly via email at: + alex@codingcat.dev. + We make every effort to respond to developer inquiries and community messages within 1–2 business days. +

+
+ +
+

Podcast Guest Appearances

+

+ Interested in being a guest on the CodingCat.dev Podcast? We love spotlighting developers building exciting tools, open source libraries, and novel developer experiences. You can connect with us directly via email or through our official PodMatch profile at + PodMatch. +

+
+ +
+

Sponsorships & Media Partnerships

+

+ If your organization is looking to reach thousands of passionate web developers, software engineers, and technical leaders, explore our media sponsorship packages. We offer podcast pre-roll and mid-roll spots, dedicated tutorial videos, sponsored newsletter features, and social shoutouts. + Visit our dedicated Sponsorships Page to review packages and submit an inquiry. +

+
+ +
+

Social Media & Community

+

+ Stay connected across our social channels: +

+ +
+
+
+
diff --git a/apps/site/src/pages/developers.astro b/apps/site/src/pages/developers.astro new file mode 100644 index 000000000..d064753d5 --- /dev/null +++ b/apps/site/src/pages/developers.astro @@ -0,0 +1,114 @@ +--- +import BaseLayout from "@/layouts/BaseLayout.astro"; + +const title = "Developer Documentation & APIs — CodingCat.dev"; +const description = + "Developer guides, APIs, Model Context Protocol (MCP) server endpoints, and agent integration documentation for CodingCat.dev."; +const origin = Astro.locals.siteUrl.origin; +--- + + +
+
+

Developer & Agent Documentation

+

APIs, Machine-Readable Manifests & Autonomous Agent Protocols

+
+ +
+
+

Overview

+

+ CodingCat.dev provides open, machine-readable interfaces designed for both human developers and autonomous AI coding agents. All public read APIs are free and do not require API keys or registration. +

+
+ +
+

Discovery Manifests

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StandardEndpointDescription
RFC 9727 API Catalog/.well-known/api-catalogIANA linkset discovery for all public APIs
OpenAPI 3.1/openapi.jsonComplete machine-readable OpenAPI schema
MCP Server Card/.well-known/mcp/server-card.jsonModel Context Protocol server capability card
ARD AI Catalog/.well-known/ai-catalog.jsonAgentic Resource Discovery manifest
Agent Skills/.well-known/agent-skills/index.jsonCloudflare Agent Skills Discovery index
Auth.md/auth.mdAgent registration & authentication contracts
Pricing.md/pricing.mdAgent pricing and usage constraints
Agent Guide/AGENTS.mdRoot-level agent capability guide
+
+
+ +
+

Search API

+

Perform keyword search across tutorials, blog posts, podcasts, authors, and sponsors:

+
GET {origin}/api/search?q=astro HTTP/1.1
+Accept: application/json
+
+ +
+

Streamable Model Context Protocol (MCP)

+

Agents can invoke MCP tools over HTTP POST:

+
POST {origin}/api/mcp HTTP/1.1
+Content-Type: application/json
+
+{
+  "jsonrpc": "2.0",
+  "method": "tools/call",
+  "params": {
+    "name": "search_content",
+    "arguments": { "query": "typescript" }
+  },
+  "id": 1
+}
+
+ +
+

Markdown Content Negotiation

+

+ Any page on CodingCat.dev can be retrieved in clean Markdown by appending .md to the URL (e.g., /blog.md) or sending the HTTP header Accept: text/markdown. +

+
+
+
+
diff --git a/apps/site/src/pages/docs.astro b/apps/site/src/pages/docs.astro new file mode 100644 index 000000000..7151f9159 --- /dev/null +++ b/apps/site/src/pages/docs.astro @@ -0,0 +1,5 @@ +--- +import DevelopersPage from "./developers.astro"; +--- + + diff --git a/apps/site/src/pages/openapi.json.ts b/apps/site/src/pages/openapi.json.ts new file mode 100644 index 000000000..a00e757a8 --- /dev/null +++ b/apps/site/src/pages/openapi.json.ts @@ -0,0 +1 @@ +export { GET, prerender } from "./.well-known/openapi.json"; diff --git a/apps/site/src/pages/pricing.md.ts b/apps/site/src/pages/pricing.md.ts new file mode 100644 index 000000000..27b627fdf --- /dev/null +++ b/apps/site/src/pages/pricing.md.ts @@ -0,0 +1,41 @@ +import type { APIRoute } from "astro"; + +export const prerender = false; + +/** + * Serves /pricing.md machine-readable pricing and usage contract for AI agents. + */ +export const GET: APIRoute = async ({ locals }) => { + const origin = locals.siteUrl.origin; + + const content = `# CodingCat.dev Pricing for Agents + +> Machine-readable pricing, usage tiers, and API constraints for AI agents and automated systems. + +## Public Read Operations +- **Cost**: $0.00 (Completely Free) +- **Authentication**: None required +- **Access**: Full access to all published articles, tutorials, podcast transcripts, author bios, guest listings, and search. +- **Fair Use**: Standard edge rate limiting applies (up to 60 requests per minute per origin). + +## Agent Actions & Tools +- **Tool \`search_content\`**: Free (unlimited standard usage). +- **Tool \`submit_sponsorship_inquiry\`**: Free to submit. +- **Model Context Protocol (MCP)**: Free public read tools via \`${origin}/api/mcp\`. + +## Commercial Partnerships & Sponsorships +For media sponsorships, newsletter inclusions, video features, or sponsored developer tutorials: +- **Packages & Inquiries**: [\`${origin}/sponsorships\`](${origin}/sponsorships) +- **Direct Contact**: alex@codingcat.dev +- **Agent Identity Registration**: [\`${origin}/auth.md\`](${origin}/auth.md) +`; + + return new Response(content, { + headers: { + "content-type": "text/markdown; charset=utf-8", + "cache-control": "public, max-age=3600, s-maxage=86400", + Vary: "Accept", + Link: `<${origin}/.well-known/api-catalog>; rel="api-catalog", <${origin}/llms.txt>; rel="llms-txt", <${origin}/auth.md>; rel="auth-md"`, + }, + }); +}; diff --git a/apps/site/src/pages/privacy-policy.astro b/apps/site/src/pages/privacy-policy.astro new file mode 100644 index 000000000..f9ff9266e --- /dev/null +++ b/apps/site/src/pages/privacy-policy.astro @@ -0,0 +1,5 @@ +--- +import PrivacyPage from "./privacy.astro"; +--- + + diff --git a/apps/site/src/pages/privacy.astro b/apps/site/src/pages/privacy.astro new file mode 100644 index 000000000..c143df968 --- /dev/null +++ b/apps/site/src/pages/privacy.astro @@ -0,0 +1,59 @@ +--- +import BaseLayout from "@/layouts/BaseLayout.astro"; + +const title = "Privacy Policy — CodingCat.dev"; +const description = + "Privacy Policy for CodingCat.dev detailing our data collection practices, newsletter handling, cookie usage, and user rights."; +--- + + +
+
+

Privacy Policy

+

Last updated: October 2026

+
+ +
+
+

1. Introduction

+

+ CodingCat.dev ("we", "our", or "us") respects your privacy and is committed to protecting the personal data you share with us. This Privacy Policy explains how we collect, use, and safeguard your information when you visit https://codingcat.dev, read our tutorials, listen to our podcasts, or subscribe to our newsletter. +

+
+ +
+

2. Information We Collect

+

+ We collect minimal information necessary to deliver educational web development content: +

+
    +
  • Newsletter Subscriptions: When you subscribe to our newsletter via our website form, we collect your email address. This is processed securely via Buttondown Email. You can unsubscribe at any time using the link in the footer of any email.
  • +
  • Anonymous Usage & Analytics: We may use privacy-preserving, cookieless edge analytics provided by Cloudflare to understand traffic trends, popular tutorials, and performance metrics without tracking individuals across third-party websites.
  • +
  • Contact Inquiries: If you email us or submit a sponsorship inquiry, we collect your name, email address, company name, and message content to respond to your request.
  • +
+
+ +
+

3. Cookies and Local Storage

+

+ CodingCat.dev does not use invasive advertising cookies or cross-site tracking scripts. We utilize standard local storage solely for user-selected website preferences (such as light/dark theme toggles). +

+
+ +
+

4. Third-Party Services

+

+ Our website may contain links to external websites, platforms, or services (such as YouTube, GitHub, PodMatch, X, and sponsor sites). We are not responsible for the privacy practices of external platforms. We encourage you to review the privacy policies of any third-party websites you visit. +

+
+ +
+

5. Contact Information

+

+ If you have questions or concerns regarding this Privacy Policy or your personal data, please contact us at: + alex@codingcat.dev. +

+
+
+
+
diff --git a/apps/site/src/pages/robots.txt.ts b/apps/site/src/pages/robots.txt.ts index e1b9f694d..b21a87e3c 100644 --- a/apps/site/src/pages/robots.txt.ts +++ b/apps/site/src/pages/robots.txt.ts @@ -21,6 +21,7 @@ export const GET: APIRoute = async ({ locals }) => { const body = isProduction ? `User-Agent: * Content-Signal: ai-train=no, search=yes, ai-input=yes +Content-Usage: / train-ai=n, search=y Allow: / Allow: /api/search Allow: /api/mcp @@ -28,6 +29,8 @@ Allow: /.well-known/ Allow: /llms.txt Allow: /llms-full.txt Allow: /auth.md +Allow: /AGENTS.md +Allow: /pricing.md Disallow: /api/ Disallow: /dashboard/ diff --git a/apps/site/tests/tier1-features.test.mjs b/apps/site/tests/tier1-features.test.mjs index 2a3f7ca07..2b8efb5c2 100644 --- a/apps/site/tests/tier1-features.test.mjs +++ b/apps/site/tests/tier1-features.test.mjs @@ -1114,5 +1114,89 @@ describe("Tier 1 - Feature Coverage", () => { assert.ok(body.includes("/agent/claim")); assert.ok(body.includes("/agent/auth")); }); + + it("7.7 AGENTS.md and pricing.md serve valid agent guides and content contracts", async () => { + const agentsMdModule = loadModule("src/pages/AGENTS.md.ts"); + const pricingMdModule = loadModule("src/pages/pricing.md.ts"); + + const agentsRes = await agentsMdModule.GET({ locals }); + assert.equal(agentsRes.status, 200); + assert.ok( + agentsRes.headers.get("content-type")?.includes("text/markdown"), + ); + const agentsBody = await agentsRes.text(); + assert.ok(agentsBody.includes("# codingcat.dev: Agent Guide")); + assert.ok(agentsBody.includes("MCP Server Card")); + + const pricingRes = await pricingMdModule.GET({ locals }); + assert.equal(pricingRes.status, 200); + assert.ok( + pricingRes.headers.get("content-type")?.includes("text/markdown"), + ); + const pricingBody = await pricingRes.text(); + assert.ok(pricingBody.includes("# CodingCat.dev Pricing for Agents")); + assert.ok(pricingBody.includes("Public Read Operations")); + }); + + it("7.8 agents.json and agent-card.json serve agent discovery specifications", async () => { + const agentsJsonModule = loadModule( + "src/pages/.well-known/agents.json.ts", + ); + const agentCardModule = loadModule( + "src/pages/.well-known/agent-card.json.ts", + ); + + const agentsRes = await agentsJsonModule.GET({ locals }); + assert.equal(agentsRes.status, 200); + const agentsData = await agentsRes.json(); + assert.ok(Array.isArray(agentsData.agents)); + assert.equal( + agentsData.agents[0].name, + "CodingCat Search & Content Agent", + ); + + const cardRes = await agentCardModule.GET({ locals }); + assert.equal(cardRes.status, 200); + const cardData = await cardRes.json(); + assert.equal(cardData.protocol, "a2a"); + assert.equal(cardData.name, "CodingCat.dev Agent"); + }); + + it("7.9 SEP-1649 MCP server card serves transport, capabilities, and auth metadata", async () => { + const mcpCardModule = loadModule( + "src/pages/.well-known/mcp/server-card.json.ts", + ); + const res = await mcpCardModule.GET({ locals }); + assert.equal(res.status, 200); + const data = await res.json(); + assert.equal(data.serverInfo.name, "codingcatdev-search-mcp"); + assert.equal(data.transport.type, "streamable-http"); + assert.ok(data.capabilities.tools); + assert.ok( + data.auth.authorizationServers.includes("https://codingcat.dev"), + ); + }); + + it("7.10 Agent Skills index conforms to v0.2.0 RFC with valid sha256 digest and SKILL.md", async () => { + const skillsIndexModule = loadModule( + "src/pages/.well-known/agent-skills/index.json.ts", + ); + const skillMdModule = loadModule( + "src/pages/.well-known/agent-skills/search-content/SKILL.md.ts", + ); + + const indexRes = await skillsIndexModule.GET({ locals }); + assert.equal(indexRes.status, 200); + const indexData = await indexRes.json(); + assert.equal(indexData.version, "0.2.0"); + assert.ok(Array.isArray(indexData.skills)); + assert.equal(indexData.skills[0].type, "skill-md"); + assert.match(indexData.skills[0].digest, /^sha256:[a-f0-9]{64}$/); + + const skillRes = await skillMdModule.GET(); + assert.equal(skillRes.status, 200); + const skillText = await skillRes.text(); + assert.ok(skillText.includes("name: search-content")); + }); }); });