diff --git a/e2e/docs-ui-static.spec.ts b/e2e/docs-ui-static.spec.ts
index 553d0a7..f5ae9f2 100644
--- a/e2e/docs-ui-static.spec.ts
+++ b/e2e/docs-ui-static.spec.ts
@@ -24,20 +24,41 @@ test.describe('Cloudflare Static Assets export', () => {
test('supports client navigation between generated documentation pages', async ({ page }) => {
await page.goto('/docs/quickstart');
- await expect(page.getByText('Getting Started').first()).toBeVisible();
- await page
- .getByRole('link', { name: /Configuration/ })
- .last()
- .click();
- await expect(page).toHaveURL(/\/docs\/configuration/);
- await expect(page.getByRole('heading', { name: 'Configuration' }).first()).toBeVisible();
+ await expect(page).toHaveTitle('Petstore Docs');
+ await expect(page.getByRole('heading', { name: 'Quickstart' })).toBeVisible();
+ await expect(page.getByRole('link', { name: 'Configuration' })).toHaveCount(0);
});
test('serves generated MCP and SDK deep links', async ({ page }) => {
await page.goto('/mcp/docs_quickstart');
await expect(page.getByText('docs_quickstart').first()).toBeVisible();
+ await page.goto('/mcp/sdk_typescript_petstore_typescript_client_sdk');
+ await expect(
+ page.getByText('sdk_typescript_petstore_typescript_client_sdk').first(),
+ ).toBeVisible();
+
await page.goto('/sdks/typescript');
await expect(page.getByText('TypeScript').first()).toBeVisible();
});
+
+ test('matches the local demo documentation and MCP SDK tools', async ({ request }) => {
+ const docsResponse = await request.get('/api/docs');
+ const docs = await docsResponse.json();
+ expect(docs.sections).toEqual([
+ expect.objectContaining({
+ section: 'Get started',
+ documents: [expect.objectContaining({ title: 'Quickstart', slug: 'quickstart' })],
+ }),
+ ]);
+
+ const mcpResponse = await request.get('/api/mcp');
+ const mcp = await mcpResponse.json();
+ const toolNames = mcp.tools.map((tool: { name: string }) => tool.name);
+ expect(toolNames.filter((name: string) => name.startsWith('docs_'))).toEqual([
+ 'docs_quickstart',
+ ]);
+ expect(toolNames.filter((name: string) => name.startsWith('sdk_'))).toHaveLength(11);
+ expect(toolNames).toContain('sdk_typescript_petstore_typescript_client_sdk');
+ });
});
diff --git a/packages/docs-ui/scripts/cloudflare.mjs b/packages/docs-ui/scripts/cloudflare.mjs
index e355495..252ab20 100644
--- a/packages/docs-ui/scripts/cloudflare.mjs
+++ b/packages/docs-ui/scripts/cloudflare.mjs
@@ -22,6 +22,8 @@ if (!['demo', 'docs'].includes(target)) {
const require = createRequire(import.meta.url);
const scriptDir = dirname(fileURLToPath(import.meta.url));
const docsUiDir = resolve(scriptDir, '..');
+const workspaceRoot = resolve(docsUiDir, '..', '..');
+const cliMain = join(workspaceRoot, 'packages', 'cli', 'dist', 'main.js');
const outputDir = join(docsUiDir, '.next-cloudflare');
const nextCli = require.resolve('next/dist/bin/next');
const wranglerPackagePath = require.resolve('wrangler/package.json');
@@ -61,10 +63,10 @@ const env = {
: {}),
};
-function run(executable, args) {
+function run(executable, args, cwd = docsUiDir) {
return new Promise((resolveCommand, rejectCommand) => {
const child = spawn(executable, args, {
- cwd: docsUiDir,
+ cwd,
env,
stdio: 'inherit',
});
@@ -102,6 +104,13 @@ function validateStaticOutput() {
try {
rmSync(outputDir, { recursive: true, force: true });
+ if (target === 'demo') {
+ if (!existsSync(cliMain)) {
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
+ await run(npm, ['run', 'build:cli'], workspaceRoot);
+ }
+ await run(process.execPath, [cliMain, 'generate'], prepared.demoDir);
+ }
await run(process.execPath, [nextCli, 'build', '--webpack']);
validateStaticOutput();
diff --git a/packages/docs-ui/scripts/prepare-demo.mjs b/packages/docs-ui/scripts/prepare-demo.mjs
index ba5dacc..8fbb2ec 100644
--- a/packages/docs-ui/scripts/prepare-demo.mjs
+++ b/packages/docs-ui/scripts/prepare-demo.mjs
@@ -12,6 +12,51 @@ const demoDir = join(docsUiDir, '.cortex-demo');
const fixturesDir = join(workspaceRoot, 'packages', 'core', '__fixtures__');
const docsSiteDir = join(workspaceRoot, 'packages', 'docs-site');
+const quickstart = `# Quickstart
+
+Welcome to your API documentation! This guide will help you get started.
+
+## API Reference
+
+Browse the full API reference to see all available endpoints, request/response schemas, and authentication details.
+
+## SDKs
+
+Cortex generates type-safe SDKs for your API in multiple languages. Install the SDK for your language of choice and start making API calls in minutes.
+
+## MCP Server
+
+An MCP (Model Context Protocol) server is generated alongside your SDKs, enabling AI assistants to interact with your API using structured tool calls.
+
+## Next Steps
+
+- Explore the **API Reference** tab for endpoint details
+- Visit the **SDKs** tab to download generated clients
+- Check the **MCP** tab for AI integration setup
+`;
+
+const apiReferenceIcon = ``;
+
+function buildLogo(textColor) {
+ const name = 'Petstore';
+ const totalWidth = Math.ceil(22 + 4 + name.length * 8.5);
+ const fillOpacity = textColor === '#ffffff' ? '0.1' : '0.08';
+ return ``;
+}
+
function copyFixture(sourceName, targetName, transform = (content) => content) {
const content = readFileSync(join(fixturesDir, sourceName), 'utf8');
writeFileSync(join(demoDir, 'specs', targetName), transform(content), 'utf8');
@@ -61,7 +106,26 @@ export function prepareDemo(apiUrl = process.env.CORTEX_DEMO_API_URL || 'http://
});
cpSync(join(docsSiteDir, 'assets'), join(demoDir, 'assets'), { recursive: true });
- cpSync(join(docsSiteDir, 'docs'), join(demoDir, 'docs'), { recursive: true });
+ writeFileSync(join(demoDir, 'docs', 'quickstart.md'), quickstart, 'utf8');
+ writeFileSync(
+ join(demoDir, 'docs', 'REST_INTRO.md'),
+ `Welcome to the Petstore API. This API provides endpoints for managing resources.
+
+## Base URL
+
+\`\`\`
+${apiUrl}
+\`\`\`
+
+## Rate Limiting
+
+API requests are rate-limited to **1000 requests per minute** per API key. When you exceed the limit, requests return a \`429 Too Many Requests\` response. The \`Retry-After\` header indicates how long to wait before retrying.
+`,
+ 'utf8',
+ );
+ writeFileSync(join(demoDir, 'assets', 'logo_dark.svg'), buildLogo('#ffffff'), 'utf8');
+ writeFileSync(join(demoDir, 'assets', 'logo_light.svg'), buildLogo('#0a0a0a'), 'utf8');
+ writeFileSync(join(demoDir, 'assets', 'api-reference-icon.svg'), apiReferenceIcon, 'utf8');
writeFileSync(
join(demoDir, 'assets', 'custom.css'),
':root { --cortex-custom-head-loaded: yes; }\n',
@@ -70,8 +134,8 @@ export function prepareDemo(apiUrl = process.env.CORTEX_DEMO_API_URL || 'http://
const languages = sourceLanguages();
const config = {
- project: 'cortex-demo',
- title: 'Cortex Docs Demo',
+ project: 'Petstore',
+ title: 'Petstore Docs',
logo_dark: './assets/logo_dark.svg',
logo_light: './assets/logo_light.svg',
logoHeight: 24,
@@ -85,29 +149,29 @@ export function prepareDemo(apiUrl = process.env.CORTEX_DEMO_API_URL || 'http://
theme: 'system',
primaryColor: '#ffffff',
home: {
- title: 'Cortex Docs Demo',
+ title: 'Petstore Docs',
description:
- 'Explore API documentation, generated SDKs, and MCP tools for the Petstore example.',
- cta: { label: 'Open API Reference', href: '/api-reference' },
+ 'Explore the full API surface, grab a client SDK, or wire up AI coding agents via our MCP for faster integration.',
+ cta: { label: 'Getting Started', href: '/docs' },
sections: [
{
title: 'API Reference',
- description: 'Send requests to the Worker-native Petstore API.',
- badge: 'Live demo',
- href: '/api-reference',
- icon: 'assets/docs-icon.svg',
+ description: 'Try endpoints, visualize schema, and check out code samples.',
+ badge: 'Reference',
+ href: '/reference',
+ icon: 'assets/api-reference-icon.svg',
},
{
title: 'SDKs',
- description: 'Review generated clients for all supported languages.',
+ description: 'Typed client libraries for every major language.',
badge: 'Libraries',
href: '/sdks',
icon: 'assets/sdks-icon.svg',
},
{
title: 'MCP',
- description: 'Review the generated MCP server and tool definitions.',
- badge: 'AI agents',
+ description: 'Hook up AI coding agents via our MCP in seconds.',
+ badge: 'AI Agents',
href: '/mcp',
icon: 'assets/mcp-icon.svg',
},
@@ -115,19 +179,8 @@ export function prepareDemo(apiUrl = process.env.CORTEX_DEMO_API_URL || 'http://
},
docs: [
{
- section: 'Getting Started',
- sources: [
- { title: 'Quickstart', document: 'docs/quickstart.md' },
- { title: 'Configuration', document: 'docs/configuration.md' },
- ],
- },
- {
- section: 'Features',
- sources: [
- { title: 'SDK Generation', document: 'docs/sdk-generation.md' },
- { title: 'MCP Servers', document: 'docs/mcp-servers.md' },
- { title: 'Publishing', document: 'docs/publishing.md' },
- ],
+ section: 'Get started',
+ sources: [{ title: 'Quickstart', document: 'docs/quickstart.md' }],
},
],
sources: [
@@ -135,6 +188,7 @@ export function prepareDemo(apiUrl = process.env.CORTEX_DEMO_API_URL || 'http://
title: 'REST API V1',
type: 'openapi-spec',
spec: './specs/petstore.yaml',
+ intro: './docs/REST_INTRO.md',
languages,
},
{
diff --git a/scripts/check-demo.mjs b/scripts/check-demo.mjs
index 16fbe31..c2197d3 100644
--- a/scripts/check-demo.mjs
+++ b/scripts/check-demo.mjs
@@ -34,6 +34,15 @@ async function check(path, round, cacheBust = false) {
await response.arrayBuffer();
}
+async function readJson(path) {
+ const response = await fetch(`${baseUrl}${path}?check=${Date.now()}`, {
+ headers: { 'user-agent': 'cortex-demo-health-check/1.0' },
+ signal: AbortSignal.timeout(30_000),
+ });
+ if (!response.ok) throw new Error(`${path} returned ${response.status}.`);
+ return response.json();
+}
+
let propagationFailures = [];
for (let attempt = 1; attempt <= maximumPropagationAttempts; attempt += 1) {
const results = await Promise.allSettled(
@@ -53,6 +62,28 @@ if (propagationFailures.length > 0) {
throw propagationFailures[0].reason;
}
+const [config, docs, mcp] = await Promise.all([
+ readJson('/api/config'),
+ readJson('/api/docs'),
+ readJson('/api/mcp'),
+]);
+if (config.project !== 'Petstore' || config.title !== 'Petstore Docs') {
+ throw new Error('The deployed demo does not use the local Petstore project configuration.');
+}
+const documents = docs.sections?.flatMap((section) => section.documents ?? []) ?? [];
+if (documents.length !== 1 || documents[0]?.title !== 'Quickstart') {
+ throw new Error('The deployed demo must contain only the Quickstart documentation page.');
+}
+const toolNames = mcp.tools?.map((tool) => tool.name) ?? [];
+const sdkTools = toolNames.filter((name) => name.startsWith('sdk_'));
+const docsTools = toolNames.filter((name) => name.startsWith('docs_'));
+if (sdkTools.length !== 11 || !sdkTools.includes('sdk_typescript_petstore_typescript_client_sdk')) {
+ throw new Error(`The deployed demo exposed ${sdkTools.length} sdk_* MCP tools instead of 11.`);
+}
+if (docsTools.length !== 1 || docsTools[0] !== 'docs_quickstart') {
+ throw new Error(`The deployed demo exposed unexpected documentation MCP tools: ${docsTools}.`);
+}
+
for (let round = 1; round <= rounds; round += 1) {
await Promise.all(paths.map((path) => check(path, round)));
}
diff --git a/scripts/smoke-cli-package.mjs b/scripts/smoke-cli-package.mjs
index 5f47625..98b9d5a 100644
--- a/scripts/smoke-cli-package.mjs
+++ b/scripts/smoke-cli-package.mjs
@@ -188,6 +188,14 @@ async function verifyGeneratedMcp() {
if (result.tools.length === 0) {
throw new Error('The MCP server generated by the packaged CLI exposed no tools.');
}
+ const sdkTools = result.tools.filter((tool) => tool.name.startsWith('sdk_'));
+ if (
+ !sdkTools.some((tool) => tool.name === 'sdk_typescript_registry_smoke_typescript_client_sdk')
+ ) {
+ throw new Error(
+ 'The MCP server generated by the packaged CLI exposed no TypeScript SDK tool.',
+ );
+ }
} finally {
await client.close();
}