From acbe280db8f68e8e7fc1858d121ca1ec12f82df0 Mon Sep 17 00:00:00 2001 From: Nick Chisiu <8492343+nickchisiu@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:38:27 +0300 Subject: [PATCH 1/2] fix(demo): match local docs and generate SDK MCP tools --- e2e/docs-ui-static.spec.ts | 35 +++++-- packages/docs-ui/scripts/cloudflare.mjs | 13 ++- packages/docs-ui/scripts/prepare-demo.mjs | 106 ++++++++++++++++------ scripts/check-demo.mjs | 31 +++++++ scripts/smoke-cli-package.mjs | 6 ++ 5 files changed, 156 insertions(+), 35 deletions(-) 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 ` + + + + + + ${name} +`; +} + 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..186bda3 100644 --- a/scripts/smoke-cli-package.mjs +++ b/scripts/smoke-cli-package.mjs @@ -188,6 +188,12 @@ 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_petstore_typescript_client_sdk')) { + throw new Error( + 'The MCP server generated by the packaged CLI exposed no TypeScript SDK tool.', + ); + } } finally { await client.close(); } From b516df57006723a019c6d7ec5e30e46c5ecd8af4 Mon Sep 17 00:00:00 2001 From: Nick Chisiu <8492343+nickchisiu@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:05:50 +0300 Subject: [PATCH 2/2] test(cli): assert the registry smoke SDK tool --- scripts/smoke-cli-package.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/smoke-cli-package.mjs b/scripts/smoke-cli-package.mjs index 186bda3..98b9d5a 100644 --- a/scripts/smoke-cli-package.mjs +++ b/scripts/smoke-cli-package.mjs @@ -189,7 +189,9 @@ async function verifyGeneratedMcp() { 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_petstore_typescript_client_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.', );