Skip to content

Commit 6fa4300

Browse files
authored
Merge pull request #693 from CodingCatDev/dev
Release: Complete IsAgentReady & WebMCP AI Agent Protocol Enhancements
2 parents 1ebe3cc + 0ad384a commit 6fa4300

27 files changed

Lines changed: 683 additions & 63 deletions

‎apps/site/baseline/robots.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
User-Agent: *
22
Content-Signal: ai-train=no, search=yes, ai-input=yes
3+
Content-Usage: / train-ai=n, search=y
34
Allow: /
45
Allow: /api/search
56
Allow: /api/mcp
67
Allow: /.well-known/
78
Allow: /llms.txt
89
Allow: /llms-full.txt
910
Allow: /auth.md
11+
Allow: /AGENTS.md
12+
Allow: /pricing.md
1013
Disallow: /api/
1114
Disallow: /dashboard/
1215

‎apps/site/src/components/ContentCard.astro‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,18 @@ const people = [
2727
{person ? (
2828
<CoverImage
2929
image={item.coverImage}
30+
fallbackAlt={item.title || "CodingCat.dev author photo"}
3031
width={256}
3132
height={256}
3233
class="w-full h-auto aspect-square rounded-md object-cover"
3334
priority={priority}
3435
/>
3536
) : (
36-
<CoverImage image={item.coverImage} priority={priority} />
37+
<CoverImage
38+
image={item.coverImage}
39+
fallbackAlt={item.title || "CodingCat.dev tutorial cover image"}
40+
priority={priority}
41+
/>
3742
)}
3843
</a>
3944
)

‎apps/site/src/components/CoverImage.astro‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { SanityImageSource } from "@sanity/image-url";
44
55
interface Props {
66
image?: (SanityImageSource & { alt?: string }) | null;
7+
fallbackAlt?: string;
78
width?: number;
89
height?: number;
910
quality?: number;
@@ -14,6 +15,7 @@ interface Props {
1415
1516
const {
1617
image,
18+
fallbackAlt = "CodingCat.dev tutorial cover image",
1719
width = 1920,
1820
height = 1080,
1921
quality = 80,
@@ -30,7 +32,8 @@ const src = Astro.locals.sanity
3032
3133
// stegaClean: alt lands in an attribute, where stega's zero-width characters
3234
// would be read out verbatim by screen readers.
33-
const alt = stegaClean((image as { alt?: string } | null)?.alt) ?? "";
35+
const rawAlt = stegaClean((image as { alt?: string } | null)?.alt);
36+
const alt = rawAlt && rawAlt.trim().length > 0 ? rawAlt : fallbackAlt;
3437
---
3538

3639
<div class="transition-shadow duration-200 shadow-md group-hover:shadow-lg sm:mx-0">

‎apps/site/src/components/Footer.astro‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import NavLink from "./NavLink.astro";
44
const year = new Date().getUTCFullYear();
55
66
const legalLinks = [
7-
{ href: "/ftc-disclosure", label: "FTC Disclosure" },
7+
{ href: "/about", label: "About" },
8+
{ href: "/contact", label: "Contact" },
9+
{ href: "/developers", label: "Developers" },
10+
{ href: "/privacy", label: "Privacy Policy" },
811
{ href: "/terms-of-use", label: "Terms of Use" },
9-
{ href: "/privacy-policy", label: "Privacy Policy" },
12+
{ href: "/ftc-disclosure", label: "FTC Disclosure" },
1013
{ href: "/sponsors", label: "Sponsors" },
1114
{ href: "/sponsorships", label: "Sponsorships" },
1215
];
@@ -84,14 +87,18 @@ const socialLinks = [
8487
action="https://buttondown.email/api/emails/embed-subscribe/codingcatdev"
8588
method="post"
8689
target="popupwindow"
90+
toolname="subscribe_newsletter"
91+
tooldescription="Subscribe to the CodingCat.dev web development newsletter"
8792
>
8893
<label class="sr-only" for="footer-email">Email</label>
8994
<input
9095
id="footer-email"
9196
name="email"
9297
type="email"
98+
autocomplete="email"
9399
required
94100
placeholder="Enter your email"
101+
toolparamdescription="Subscriber email address"
95102
class="flex-1 h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
96103
/>
97104
<input type="hidden" value="1" name="embed" />

‎apps/site/src/components/HomeSection.astro‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const { title, description, items, muted = false } = Astro.props;
4242
{item.coverImage && (
4343
<CoverImage
4444
image={item.coverImage}
45+
fallbackAlt={item.title || "CodingCat.dev tutorial cover image"}
4546
width={320}
4647
height={180}
4748
class="aspect-video w-full object-cover object-center"

‎apps/site/src/layouts/BaseLayout.astro‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ const graph = buildGraph([
115115
<link rel="openid-configuration" href={`${origin}/.well-known/openid-configuration`} />
116116
<link rel="oauth-authorization-server" href={`${origin}/.well-known/oauth-authorization-server`} />
117117
<link rel="oauth-protected-resource" href={`${origin}/.well-known/oauth-protected-resource`} />
118+
<link rel="agent-guide" type="text/markdown" href={`${origin}/AGENTS.md`} />
119+
<link rel="pricing" type="text/markdown" href={`${origin}/pricing.md`} />
120+
<link rel="agents" href={`${origin}/.well-known/agents.json`} />
118121
<link rel="author" type="text/markdown" href={`${origin}/auth.md`} />
119122
<JsonLd data={graph} />
120123
{/* WebMCP API: Expose client-side tools to browser AI agents */}
@@ -127,6 +130,10 @@ const graph = buildGraph([
127130
mc.registerTool({
128131
name: "search_content",
129132
description: "Search CodingCat.dev tutorials, articles, podcasts, guests, and sponsors",
133+
annotations: {
134+
readOnlyHint: true,
135+
untrustedContentHint: true,
136+
},
130137
inputSchema: {
131138
type: "object",
132139
properties: {

‎apps/site/src/middleware.ts‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
3333

3434
const origin = context.locals.siteUrl.origin;
3535
const pathname = context.url.pathname;
36-
const isDedicatedMarkdownEndpoint = pathname === "/auth.md";
36+
const isDedicatedMarkdownEndpoint =
37+
pathname === "/auth.md" ||
38+
pathname === "/pricing.md" ||
39+
pathname.toLowerCase() === "/agents.md";
3740
const isMarkdownUrl =
3841
!isDedicatedMarkdownEndpoint &&
3942
(pathname.endsWith(".md") || pathname.endsWith("/index.md"));
@@ -43,7 +46,9 @@ export const onRequest = defineMiddleware(async (context, next) => {
4346
const wantsMarkdown = isMarkdownUrl || Boolean(acceptMarkdown);
4447

4548
let response: Response;
46-
if (isMarkdownUrl) {
49+
if (pathname === "/agents.md") {
50+
response = await context.rewrite("/AGENTS.md");
51+
} else if (isMarkdownUrl) {
4752
const cleanPath =
4853
pathname.replace(/\/index\.md$/, "").replace(/\.md$/, "") || "/";
4954
response = await context.rewrite(cleanPath);
@@ -58,10 +63,23 @@ export const onRequest = defineMiddleware(async (context, next) => {
5863
response.headers.set("X-Robots-Tag", "noindex, nofollow");
5964
}
6065

66+
// Security & Trust Headers
67+
response.headers.set(
68+
"Strict-Transport-Security",
69+
"max-age=31536000; includeSubDomains; preload",
70+
);
71+
response.headers.set("X-Content-Type-Options", "nosniff");
72+
response.headers.set("X-Frame-Options", "SAMEORIGIN");
73+
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
74+
response.headers.set("Content-Security-Policy", "frame-ancestors 'self'");
75+
76+
// AIPREF / AI Preference Headers
77+
response.headers.set("Content-Usage", "train-ai=n, search=y");
78+
6179
// Always emit Link headers (RFC 8288) for AI agent discoverability
6280
response.headers.set(
6381
"Link",
64-
`<${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"`,
82+
`<${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"`,
6583
);
6684
response.headers.append("Vary", "Accept");
6785

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { APIRoute } from "astro";
2+
3+
export const prerender = false;
4+
5+
/**
6+
* Serves A2A Agent Card for agent-to-agent discovery
7+
*/
8+
export const GET: APIRoute = async ({ locals }) => {
9+
const origin = locals.siteUrl.origin;
10+
11+
const agentCard = {
12+
name: "CodingCat.dev Agent",
13+
description:
14+
"CodingCat.dev AI agent for fullstack web development tutorials, podcasts, and semantic search.",
15+
url: origin,
16+
version: "1.0.0",
17+
protocol: "a2a",
18+
endpoints: {
19+
mcp: `${origin}/api/mcp`,
20+
search: `${origin}/api/search`,
21+
auth: `${origin}/agent/auth`,
22+
claim: `${origin}/agent/claim`,
23+
},
24+
capabilities: {
25+
search: true,
26+
mcp: true,
27+
read_content: true,
28+
},
29+
documentation: `${origin}/AGENTS.md`,
30+
};
31+
32+
return new Response(JSON.stringify(agentCard, null, 2), {
33+
headers: {
34+
"content-type": "application/json; charset=utf-8",
35+
"cache-control": "public, max-age=3600, s-maxage=86400",
36+
"access-control-allow-origin": "*",
37+
},
38+
});
39+
};

‎apps/site/src/pages/.well-known/agent-skills/index.json.ts‎

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,22 @@ export const GET: APIRoute = async ({ locals }) => {
66
const origin = locals.siteUrl.origin;
77

88
const skillsIndex = {
9-
$schema: "https://agentskills.io/schema/v1/index.json",
10-
version: "1.0",
9+
$schema:
10+
"https://raw.githubusercontent.com/cloudflare/agent-skills-discovery-rfc/main/schema/index.schema.json",
11+
version: "0.2.0",
1112
provider: {
1213
name: "CodingCat.dev",
1314
url: origin,
1415
},
1516
skills: [
1617
{
17-
id: "search-content",
18-
name: "Search Content",
18+
name: "search-content",
19+
type: "skill-md",
1920
description:
20-
"Search full articles, podcast transcripts, and coding tutorials on CodingCat.dev across fullstack web development topics.",
21-
endpoint: `${origin}/api/search`,
22-
method: "GET",
23-
parameters: [
24-
{
25-
name: "q",
26-
type: "string",
27-
required: true,
28-
description: "The programming term or question to search",
29-
},
30-
],
31-
},
32-
{
33-
id: "mcp-tools",
34-
name: "Model Context Protocol Tools",
35-
description:
36-
"Streamable HTTP MCP tool execution for autonomous AI agent pair programming and search.",
37-
endpoint: `${origin}/api/mcp`,
38-
method: "POST",
39-
documentation: `${origin}/.well-known/mcp/server-card.json`,
21+
"Search fullstack web development tutorials, podcasts, and blog posts on CodingCat.dev",
22+
url: `${origin}/.well-known/agent-skills/search-content/SKILL.md`,
23+
digest:
24+
"sha256:16567e9f7d6d3ed78c4be72cdb450eb46b384f5f91fc355ae866f364859164db",
4025
},
4126
],
4227
};
@@ -45,6 +30,7 @@ export const GET: APIRoute = async ({ locals }) => {
4530
headers: {
4631
"content-type": "application/json; charset=utf-8",
4732
"cache-control": "public, max-age=3600, s-maxage=86400",
33+
"access-control-allow-origin": "*",
4834
Vary: "Accept",
4935
Link: `<${origin}/llms.txt>; rel="llms-txt", <${origin}/.well-known/api-catalog>; rel="api-catalog"`,
5036
},
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { APIRoute } from "astro";
2+
3+
export const prerender = false;
4+
5+
export const SKILL_CONTENT = `---
6+
name: search-content
7+
description: Search fullstack web development tutorials, podcasts, and blog posts on CodingCat.dev
8+
---
9+
10+
# Search CodingCat.dev Content
11+
12+
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).
13+
14+
## Actions
15+
- Search API: GET https://codingcat.dev/api/search?q={query}
16+
- Model Context Protocol (MCP): POST https://codingcat.dev/api/mcp
17+
`;
18+
19+
export const GET: APIRoute = async () => {
20+
return new Response(SKILL_CONTENT, {
21+
headers: {
22+
"content-type": "text/markdown; charset=utf-8",
23+
"cache-control": "public, max-age=3600, s-maxage=86400",
24+
},
25+
});
26+
};

0 commit comments

Comments
 (0)