Skip to content

Commit 1793a21

Browse files
committed
[agent-setup] Drive Skills and MCP lists from middlecache manifests
- Add cloudflare-skills-manifest and cloudflare-mcp-server-card content collections backed by middlecacheLoader with custom parsers - Add Zod schemas for both collections - Rewrite SkillsList.astro to use getCollection instead of hardcoded array - Rewrite McpServerList.astro to use getEntry and derive server names from remote URLs; descriptions remain locally maintained - Remove Tools for agents section and CloudflareToolsBanner from index - Link Cloudflare Skills and MCP servers in the lede to their GitHub repos
1 parent 4ab81fe commit 1793a21

8 files changed

Lines changed: 158 additions & 167 deletions

File tree

src/components/agent-setup/McpServerList.astro

Lines changed: 75 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
11
---
2-
// Simple list of Cloudflare MCP servers.
3-
// Renders each server as an orange name (+ optional "bundled" tag) followed
4-
// by a short description.
5-
//
6-
// Sources:
7-
// • Code Mode API server — https://github.com/cloudflare/mcp (README + /.mcp.json)
8-
// • Domain-specific servers — https://github.com/cloudflare/mcp-server-cloudflare
9-
// • Plugin bundle — https://github.com/cloudflare/skills (/.mcp.json)
10-
//
11-
// When new servers are added upstream, add them here.
12-
13-
interface McpServer {
14-
/** Server name as it appears in JSON config (e.g. `"cloudflare-api"`). */
15-
name: string;
16-
/** One-line purpose, sourced from the upstream README. */
17-
description: string;
18-
/** True if this server is pre-registered by the cloudflare/skills plugin. */
19-
bundled?: boolean;
20-
}
2+
import { getEntry } from "astro:content";
213
224
interface Props {
235
/** Whether to show the "bundled" chip. Only relevant for plugin-based agents (Claude Code, Cursor). */
@@ -26,94 +8,83 @@ interface Props {
268
279
const { showBundled = false } = Astro.props;
2810
29-
const SERVERS: McpServer[] = [
30-
{
31-
name: "cloudflare-api",
32-
description:
33-
"Entire Cloudflare API (2,500+ endpoints) in ~1,000 tokens via Code Mode.",
34-
bundled: true,
35-
},
36-
{
37-
name: "cloudflare-docs",
38-
description: "Up-to-date Cloudflare documentation and reference.",
39-
bundled: true,
40-
},
41-
{
42-
name: "cloudflare-bindings",
43-
description:
44-
"Build Workers applications with storage, AI, and compute primitives.",
45-
bundled: true,
46-
},
47-
{
48-
name: "cloudflare-builds",
49-
description: "Manage and get insights into Workers builds.",
50-
bundled: true,
51-
},
52-
{
53-
name: "cloudflare-observability",
54-
description: "Debug and analyze application logs and analytics.",
55-
bundled: true,
56-
},
57-
{
58-
name: "cloudflare-radar",
59-
description:
60-
"Global Internet traffic insights, trends, URL scans, and utilities.",
61-
},
62-
{
63-
name: "cloudflare-containers",
64-
description: "Spin up a sandbox development environment.",
65-
},
66-
{
67-
name: "cloudflare-browser",
68-
description:
69-
"Fetch web pages, convert them to markdown, and take screenshots.",
70-
},
71-
{
72-
name: "cloudflare-logpush",
73-
description: "Quick summaries for Logpush job health.",
74-
},
75-
{
76-
name: "cloudflare-ai-gateway",
77-
description:
78-
"Search AI Gateway logs and inspect prompts, responses, and token usage.",
79-
},
80-
{
81-
name: "cloudflare-autorag",
82-
description: "List and search documents on your AutoRAGs.",
83-
},
84-
{
85-
name: "cloudflare-auditlogs",
86-
description: "Query audit logs and generate reports for review.",
87-
},
88-
{
89-
name: "cloudflare-dns-analytics",
90-
description:
91-
"Optimize DNS performance and debug issues based on current setup.",
92-
},
93-
{
94-
name: "cloudflare-dex",
95-
description:
96-
"Digital Experience Monitoring — insights on critical applications.",
97-
},
98-
{
99-
name: "cloudflare-casb",
100-
description:
101-
"Cloudflare One CASB — identify SaaS security misconfigurations.",
102-
},
103-
{
104-
name: "cloudflare-graphql",
105-
description: "Query analytics data through Cloudflare's GraphQL API.",
106-
},
107-
];
11+
const card = await getEntry("cloudflare-mcp-server-card", "cloudflare-mcp");
12+
13+
/**
14+
* Descriptions are not available in the server-card spec, so they are
15+
* maintained here and keyed by the server name derived from the remote URL.
16+
*
17+
* Name derivation: the subdomain of *.mcp.cloudflare.com becomes the suffix
18+
* after "cloudflare-", except for the root mcp.cloudflare.com which maps to
19+
* "cloudflare-api" (the Code Mode server).
20+
*/
21+
const DESCRIPTIONS: Record<string, string> = {
22+
"cloudflare-api":
23+
"Entire Cloudflare API (2,500+ endpoints) in ~1,000 tokens via Code Mode.",
24+
"cloudflare-docs": "Up-to-date Cloudflare documentation and reference.",
25+
"cloudflare-bindings":
26+
"Build Workers applications with storage, AI, and compute primitives.",
27+
"cloudflare-builds": "Manage and get insights into Workers builds.",
28+
"cloudflare-observability":
29+
"Debug and analyze application logs and analytics.",
30+
"cloudflare-radar":
31+
"Global Internet traffic insights, trends, URL scans, and utilities.",
32+
"cloudflare-containers": "Spin up a sandbox development environment.",
33+
"cloudflare-browser":
34+
"Fetch web pages, convert them to markdown, and take screenshots.",
35+
"cloudflare-logs": "Quick summaries for Logpush job health.",
36+
"cloudflare-ai-gateway":
37+
"Search AI Gateway logs and inspect prompts, responses, and token usage.",
38+
"cloudflare-auditlogs":
39+
"Query audit logs and generate reports for review.",
40+
"cloudflare-dns-analytics":
41+
"Optimize DNS performance and debug issues based on current setup.",
42+
"cloudflare-dex":
43+
"Digital Experience Monitoring — insights on critical applications.",
44+
"cloudflare-casb":
45+
"Cloudflare One CASB — identify SaaS security misconfigurations.",
46+
"cloudflare-graphql":
47+
"Query analytics data through Cloudflare's GraphQL API.",
48+
};
49+
50+
/**
51+
* Servers included in the cloudflare/skills plugin bundle (Claude Code, Cursor).
52+
*/
53+
const BUNDLED = new Set([
54+
"cloudflare-api",
55+
"cloudflare-docs",
56+
"cloudflare-bindings",
57+
"cloudflare-builds",
58+
"cloudflare-observability",
59+
]);
60+
61+
/**
62+
* Derive a stable server name from a remote URL.
63+
*
64+
* https://mcp.cloudflare.com/mcp → cloudflare-api
65+
* https://docs.mcp.cloudflare.com/mcp → cloudflare-docs
66+
* https://ai-gateway.mcp.cloudflare.com → cloudflare-ai-gateway
67+
*/
68+
function nameFromUrl(url: string): string {
69+
const { hostname } = new URL(url);
70+
// hostname: "mcp.cloudflare.com" or "{sub}.mcp.cloudflare.com"
71+
const sub = hostname.replace(/\.mcp\.cloudflare\.com$/, "");
72+
return sub === "mcp" ? "cloudflare-api" : `cloudflare-${sub}`;
73+
}
74+
75+
// Use only the streamable-http remotes to get one entry per logical server.
76+
const servers = (card?.data.remotes ?? [])
77+
.filter((r) => r.type === "streamable-http")
78+
.map((r) => nameFromUrl(r.url));
10879
---
10980

11081
<ul class="agent-simple-list">
11182
{
112-
SERVERS.map((s) => (
83+
servers.map((name) => (
11384
<li class="agent-simple-item">
11485
<span class="agent-simple-name">
115-
{s.name}
116-
{showBundled && s.bundled && (
86+
{name}
87+
{showBundled && BUNDLED.has(name) && (
11788
<span
11889
class="agent-simple-tag"
11990
title="Included in the cloudflare/skills plugin"
@@ -122,7 +93,9 @@ const SERVERS: McpServer[] = [
12293
</span>
12394
)}
12495
</span>
125-
<span class="agent-simple-desc">{s.description}</span>
96+
<span class="agent-simple-desc">
97+
{DESCRIPTIONS[name] ?? ""}
98+
</span>
12699
</li>
127100
))
128101
}

src/components/agent-setup/SkillsList.astro

Lines changed: 7 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,18 @@
11
---
2-
// Simple list of Cloudflare Skills shipped by the cloudflare/skills plugin.
3-
// Renders each Skill as an orange name followed by a short description.
4-
//
5-
// Descriptions are copied verbatim from the upstream README at
6-
// https://github.com/cloudflare/skills. Keep this list in sync with that file
7-
// when new Skills are added.
2+
import { getCollection } from "astro:content";
83
9-
interface Skill {
10-
name: string;
11-
description: string;
12-
}
4+
const skills = await getCollection("cloudflare-skills-manifest");
135
14-
const SKILLS: Skill[] = [
15-
{
16-
name: "cloudflare",
17-
description:
18-
"Comprehensive platform skill covering Workers, Pages, storage (KV, D1, R2), AI (Workers AI, Vectorize, Agents SDK), networking (Tunnel, Spectrum), security (WAF, DDoS), and IaC (Terraform, Pulumi).",
19-
},
20-
{
21-
name: "agents-sdk",
22-
description:
23-
"Building stateful AI agents with state, scheduling, RPC, MCP servers, email, and streaming chat.",
24-
},
25-
{
26-
name: "durable-objects",
27-
description:
28-
"Stateful coordination (chat rooms, games, booking), RPC, SQLite, alarms, WebSockets.",
29-
},
30-
{
31-
name: "sandbox-sdk",
32-
description:
33-
"Secure code execution for AI code execution, code interpreters, CI/CD systems, and interactive dev environments.",
34-
},
35-
{
36-
name: "wrangler",
37-
description:
38-
"Deploying and managing Workers, KV, R2, D1, Vectorize, Queues, Workflows.",
39-
},
40-
{
41-
name: "web-perf",
42-
description:
43-
"Auditing Core Web Vitals (FCP, LCP, TBT, CLS), render-blocking resources, network chains.",
44-
},
45-
{
46-
name: "building-mcp-server-on-cloudflare",
47-
description:
48-
"Building remote MCP servers with tools, OAuth, and deployment.",
49-
},
50-
{
51-
name: "building-ai-agent-on-cloudflare",
52-
description:
53-
"Building AI agents with state, WebSockets, and tool integration.",
54-
},
55-
];
6+
// Sort alphabetically so the order is stable and deterministic
7+
skills.sort((a, b) => a.id.localeCompare(b.id));
568
---
579

5810
<ul class="agent-simple-list">
5911
{
60-
SKILLS.map((s) => (
12+
skills.map((s) => (
6113
<li class="agent-simple-item">
62-
<span class="agent-simple-name">{s.name}</span>
63-
<span class="agent-simple-desc">{s.description}</span>
14+
<span class="agent-simple-name">{s.data.name}</span>
15+
<span class="agent-simple-desc">{s.data.description}</span>
6416
</li>
6517
))
6618
}

src/content.config.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { skillsLoader } from "astro-skills";
99
import { productAvailabilityCollectionConfig } from "./content/collections/product-availability";
1010
import { granularControlApplicationsCollectionConfig } from "./content/collections/granular-control-applications";
1111

12+
import { middlecacheLoader } from "./util/custom-loaders";
13+
1214
import {
1315
appsSchema,
1416
catalogModelsSchema,
@@ -27,6 +29,8 @@ import {
2729
fieldsSchema,
2830
partialsSchema,
2931
streamSchema,
32+
cloudflareSkillSchema,
33+
mcpServerCardSchema,
3034
} from "~/schemas";
3135

3236
function contentLoader(name: string) {
@@ -132,4 +136,23 @@ export const collections = {
132136
skills: defineCollection({
133137
loader: skillsLoader({ base: "./skills" }),
134138
}),
139+
"cloudflare-skills-manifest": defineCollection({
140+
loader: middlecacheLoader("v1/cloudflare-skills/skills-manifest.json", {
141+
parser: (fileContent: string) => {
142+
const data = JSON.parse(fileContent) as {
143+
skills: Array<{ name: string; description: string; files: string[] }>;
144+
};
145+
return Object.fromEntries(data.skills.map((s) => [s.name, s]));
146+
},
147+
}),
148+
schema: cloudflareSkillSchema,
149+
}),
150+
"cloudflare-mcp-server-card": defineCollection({
151+
loader: middlecacheLoader("v1/cloudflare-mcps/server-card.json", {
152+
parser: (fileContent: string) => {
153+
return { "cloudflare-mcp": JSON.parse(fileContent) };
154+
},
155+
}),
156+
schema: mcpServerCardSchema,
157+
}),
135158
};

src/pages/agent-setup/index.astro

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { StarlightPageProps } from "@astrojs/starlight/props";
44
import CatalogWithFilter from "~/components/agent-setup/CatalogWithFilter.astro";
55
import AgentComparison from "~/components/agent-setup/AgentComparison.astro";
66
import AgentPrimer from "~/components/agent-setup/AgentPrimer.astro";
7-
import CloudflareToolsBanner from "~/components/agent-setup/CloudflareToolsBanner.astro";
87
import { AGENTS } from "~/components/agent-setup/agents";
98
109
import "~/styles/agent-setup.css";
@@ -24,18 +23,10 @@ const props = {
2423

2524
<StarlightPage {...props}>
2625
<p class="agent-setup-lede">
27-
Install an agent of your choice, connect Skills and MCP servers, and start
26+
Install an agent of your choice, connect Cloudflare <a href="https://github.com/cloudflare/skills" target="_blank" rel="noopener noreferrer">Skills &nearr;</a> and <a href="https://github.com/cloudflare/mcp" target="_blank" rel="noopener noreferrer">MCP servers &nearr;</a>, and start
2827
deploying to Cloudflare — all from your editor or terminal.
2928
</p>
3029

31-
<div class="agent-setup-section" style="margin-top: 3rem;">
32-
<div class="agent-setup-section-header">
33-
<h2>Tools for agents</h2>
34-
<p>Cloudflare is built for agents. Give yours the tools to deploy, manage, and scale on the world's fastest network.</p>
35-
</div>
36-
<CloudflareToolsBanner />
37-
</div>
38-
3930
<div class="agent-setup-section-header">
4031
<h2>Pick your agent</h2>
4132
<p>Select an agent to get step-by-step setup instructions.</p>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { z } from "astro/zod";
2+
3+
const mcpRemoteSchema = z.object({
4+
url: z.url(),
5+
type: z.enum(["streamable-http", "sse"]),
6+
headers: z
7+
.array(
8+
z.object({
9+
name: z.string(),
10+
description: z.string().optional(),
11+
// server-card.json uses both camelCase and snake_case variants
12+
isRequired: z.boolean().optional(),
13+
isSecret: z.boolean().optional(),
14+
is_required: z.boolean().optional(),
15+
is_secret: z.boolean().optional(),
16+
}),
17+
)
18+
.optional(),
19+
});
20+
21+
export const mcpServerCardSchema = z.object({
22+
name: z.string(),
23+
version: z.string(),
24+
title: z.string(),
25+
description: z.string(),
26+
websiteUrl: z.url().optional(),
27+
repository: z
28+
.object({
29+
url: z.url(),
30+
source: z.string(),
31+
})
32+
.optional(),
33+
remotes: z.array(mcpRemoteSchema),
34+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { z } from "astro/zod";
2+
3+
export const cloudflareSkillSchema = z.object({
4+
name: z.string(),
5+
description: z.string(),
6+
files: z.array(z.string()),
7+
});

src/schemas/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export * from "./apps";
22
export * from "./base";
3+
export * from "./cloudflare-skills-manifest";
4+
export * from "./cloudflare-mcp-server-card";
35
export * from "./catalog-models";
46
export * from "./changelog";
57
export * from "./release-notes";

0 commit comments

Comments
 (0)