diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml new file mode 100644 index 00000000..3a170cdc --- /dev/null +++ b/.github/workflows/docs-preview.yml @@ -0,0 +1,65 @@ +name: Docs Preview + +on: + pull_request: + types: [opened, synchronize, reopened, closed] + paths: + - "docs/**" + - "src/**" + - "README.md" + - "typedoc.config.mjs" + - "scripts/typedoc-*.mjs" + - ".github/workflows/docs-preview.yml" + +permissions: + contents: read + pull-requests: write + +concurrency: + group: docs-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + build-and-deploy: + # Refuse to run for fork PRs — forks do not have access to the Cloudflare + # secrets, so deploys would fail anyway. + if: >- + github.event.action != 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: "22" + cache: npm + + - run: npm ci + + - run: npm run build + + - run: npm run docs + + - name: Deploy preview + uses: modelcontextprotocol/actions/cloudflare-pages-preview/deploy@main + with: + directory: docs + project-name: mcp-ext-apps-docs-preview + api-token: ${{ secrets.CF_PAGES_PREVIEW_API_TOKEN }} + account-id: ${{ secrets.CF_PAGES_PREVIEW_ACCOUNT_ID }} + comment-title: "📖 Docs Preview Deployed" + comment-marker: "" + + cleanup: + if: >- + github.event.action == 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: modelcontextprotocol/actions/cloudflare-pages-preview/cleanup@main + with: + project-name: mcp-ext-apps-docs-preview + api-token: ${{ secrets.CF_PAGES_PREVIEW_API_TOKEN }} + account-id: ${{ secrets.CF_PAGES_PREVIEW_ACCOUNT_ID }} + comment-marker: "" diff --git a/README.md b/README.md index 73125c41..7348e154 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@
- MCP Apps - MCP Apps + + + + MCP Apps +

MCP Apps

Build interactive UIs for MCP tools — charts, forms, dashboards — that render inline in Claude, ChatGPT and any other compliant chat client. diff --git a/docs/mcp-theme.css b/docs/mcp-theme.css index 8dd20410..d786e55f 100644 --- a/docs/mcp-theme.css +++ b/docs/mcp-theme.css @@ -2,21 +2,23 @@ * MCP Apps custom theme — aligns TypeDoc styling with modelcontextprotocol.io */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"); /* ------------------------------------------------------------------ */ /* Fonts */ /* ------------------------------------------------------------------ */ :root { - --font-family-text: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-family-code: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --font-family-text: + "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-family-code: + "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; } body { - font-family: var(--font-family-text); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + font-family: var(--font-family-text); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } /* ------------------------------------------------------------------ */ @@ -24,12 +26,12 @@ body { /* ------------------------------------------------------------------ */ :root { - --light-color-background: #ffffff; - --light-color-background-secondary: #f9fafb; - --light-color-background-navbar: #ffffff; - --light-color-accent: #e5e7eb; - --light-color-text: #111827; - --light-color-text-aside: #6b7280; + --light-color-background: #ffffff; + --light-color-background-secondary: #f9fafb; + --light-color-background-navbar: #ffffff; + --light-color-accent: #e5e7eb; + --light-color-text: #111827; + --light-color-text-aside: #6b7280; } /* ------------------------------------------------------------------ */ @@ -37,12 +39,12 @@ body { /* ------------------------------------------------------------------ */ :root { - --dark-color-background: #0f1117; - --dark-color-background-secondary: #161b22; - --dark-color-background-navbar: #0f1117; - --dark-color-accent: #30363d; - --dark-color-text: #f0f6fc; - --dark-color-text-aside: #8b949e; + --dark-color-background: #0f1117; + --dark-color-background-secondary: #161b22; + --dark-color-background-navbar: #0f1117; + --dark-color-accent: #30363d; + --dark-color-text: #f0f6fc; + --dark-color-text-aside: #8b949e; } /* ------------------------------------------------------------------ */ @@ -50,32 +52,55 @@ body { /* ------------------------------------------------------------------ */ :root { - --dim-toolbar-contents-height: 3.5rem; + --dim-toolbar-contents-height: 3.5rem; } .tsd-page-toolbar { - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); } @media (prefers-color-scheme: light) { - .tsd-page-toolbar { - background-color: rgba(255, 255, 255, 0.85); - } + .tsd-page-toolbar { + background-color: rgba(255, 255, 255, 0.85); + } } @media (prefers-color-scheme: dark) { - .tsd-page-toolbar { - background-color: rgba(15, 17, 23, 0.75); - } + .tsd-page-toolbar { + background-color: rgba(15, 17, 23, 0.75); + } } -:root[data-theme='light'] .tsd-page-toolbar { - background-color: rgba(255, 255, 255, 0.85); +:root[data-theme="light"] .tsd-page-toolbar { + background-color: rgba(255, 255, 255, 0.85); } -:root[data-theme='dark'] .tsd-page-toolbar { - background-color: rgba(15, 17, 23, 0.75); +:root[data-theme="dark"] .tsd-page-toolbar { + background-color: rgba(15, 17, 23, 0.75); +} + +/* ------------------------------------------------------------------ */ +/* Theme-aware logo (rewritten from README by mcpstyle plugin) */ +/* ------------------------------------------------------------------ */ + +/* Explicit theme selection via TypeDoc switcher — takes precedence */ +:root[data-theme="light"] .mcp-logo-dark, +:root[data-theme="dark"] .mcp-logo-light { + display: none; +} + +/* "OS" mode (data-theme='os') follows prefers-color-scheme */ +@media (prefers-color-scheme: light) { + :root[data-theme="os"] .mcp-logo-dark { + display: none; + } +} + +@media (prefers-color-scheme: dark) { + :root[data-theme="os"] .mcp-logo-light { + display: none; + } } /* ------------------------------------------------------------------ */ @@ -83,66 +108,68 @@ body { /* ------------------------------------------------------------------ */ .tsd-navigation a { - padding: 0.375rem 0.75rem; - font-size: 0.875rem; - line-height: 1.5; - border-radius: 0.75rem; - color: var(--color-text-aside); - transition: background-color 0.15s ease, color 0.15s ease; + padding: 0.375rem 0.75rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.75rem; + color: var(--color-text-aside); + transition: + background-color 0.15s ease, + color 0.15s ease; } .tsd-navigation a:hover:not(.current) { - background-color: rgba(107, 114, 128, 0.05); - color: var(--color-text); + background-color: rgba(107, 114, 128, 0.05); + color: var(--color-text); } .tsd-navigation a.current { - background-color: rgba(107, 114, 128, 0.15); - color: var(--color-text); - font-weight: 700; + background-color: rgba(107, 114, 128, 0.15); + color: var(--color-text); + font-weight: 700; } /* Group headers (Documents, Security, API Documentation) */ .tsd-accordion-summary > h3 { - font-weight: 600; - font-size: 0.875rem; - letter-spacing: 0.01em; + font-weight: 600; + font-size: 0.875rem; + letter-spacing: 0.01em; } .site-menu { - padding: 1.25rem 0; + padding: 1.25rem 0; } @media (prefers-color-scheme: dark) { - .tsd-navigation a { - color: var(--color-text-aside); - } + .tsd-navigation a { + color: var(--color-text-aside); + } - .tsd-navigation a:hover:not(.current) { - background-color: rgba(229, 231, 235, 0.05); - color: var(--color-text); - } + .tsd-navigation a:hover:not(.current) { + background-color: rgba(229, 231, 235, 0.05); + color: var(--color-text); + } - .tsd-navigation a.current { - background-color: rgba(229, 231, 235, 0.15); - color: var(--color-text); - font-weight: 700; - } + .tsd-navigation a.current { + background-color: rgba(229, 231, 235, 0.15); + color: var(--color-text); + font-weight: 700; + } } -:root[data-theme='dark'] .tsd-navigation a { - color: var(--color-text-aside); +:root[data-theme="dark"] .tsd-navigation a { + color: var(--color-text-aside); } -:root[data-theme='dark'] .tsd-navigation a:hover:not(.current) { - background-color: rgba(229, 231, 235, 0.05); - color: var(--color-text); +:root[data-theme="dark"] .tsd-navigation a:hover:not(.current) { + background-color: rgba(229, 231, 235, 0.05); + color: var(--color-text); } -:root[data-theme='dark'] .tsd-navigation a.current { - background-color: rgba(229, 231, 235, 0.15); - color: var(--color-text); - font-weight: 700; +:root[data-theme="dark"] .tsd-navigation a.current { + background-color: rgba(229, 231, 235, 0.15); + color: var(--color-text); + font-weight: 700; } /* ------------------------------------------------------------------ */ @@ -150,21 +177,21 @@ body { /* ------------------------------------------------------------------ */ .tsd-typography { - line-height: 1.6; + line-height: 1.6; } .tsd-panel.tsd-typography h1 { - margin-top: 0; - padding-bottom: 0.4em; + margin-top: 0; + padding-bottom: 0.4em; } .tsd-panel.tsd-typography h2 { - margin-top: 2rem; - padding-bottom: 0.3em; + margin-top: 2rem; + padding-bottom: 0.3em; } .tsd-panel.tsd-typography h3 { - margin-top: 1.75rem; + margin-top: 1.75rem; } /* ------------------------------------------------------------------ */ @@ -173,17 +200,17 @@ body { code, pre { - font-family: var(--font-family-code); - font-size: 1em; + font-family: var(--font-family-code); + font-size: 1em; } .tsd-typography pre { - padding: 1rem 1.25rem; - border: 1px solid var(--color-accent); + padding: 1rem 1.25rem; + border: 1px solid var(--color-accent); } /* Inline code */ .tsd-typography code:not(pre code) { - padding: 0.15em 0.35em; - font-size: 1em; + padding: 0.15em 0.35em; + font-size: 1em; } diff --git a/scripts/typedoc-plugin-mcpstyle.mjs b/scripts/typedoc-plugin-mcpstyle.mjs index bcd40f75..aea65bc5 100644 --- a/scripts/typedoc-plugin-mcpstyle.mjs +++ b/scripts/typedoc-plugin-mcpstyle.mjs @@ -4,6 +4,9 @@ * - Moves custom.css to load last so overrides win the cascade * - Replaces breadcrumbs with the document group name (e.g. "Security") * - Marks the current sidebar nav link for CSS highlighting + * - Rewrites the README logo to a pair of theme-tagged s so + * the logo follows the TypeDoc theme switcher (data-theme), not just the + * OS prefers-color-scheme media query */ import { Renderer } from "typedoc"; @@ -38,6 +41,17 @@ export function load(app) { ); } + // The README logo uses with prefers-color-scheme, which follows + // the OS setting and ignores the TypeDoc theme switcher (data-theme attr). + // Rewrite it as two tags — CSS in mcp-theme.css picks the correct + // one based on both data-theme and prefers-color-scheme (for "OS" mode). + page.contents = page.contents.replace( + /\s*\s*\s*]*)>\s*<\/picture>/, + (_, darkSrc, lightSrc, imgAttrs) => + `` + + ``, + ); + // Inject script to mark the current sidebar nav link with a "current" class. // TypeDoc does not natively add this class for document pages. // The sidebar is populated asynchronously from compressed navigation data,