` components that have matching titles.
+
+```mdx
+
+
+```javascript example.js
+const greeting = "Hello, world!";
+console.log(greeting);
+```
+
+```python example.py
+greeting = "Hello, world!"
+print(greeting)
+```
+
+
+```
+
+For dropdown style instead of tabs:
+
+```mdx
+
+ ...code blocks...
+
+```
+
+## Expandables
+
+Show/hide nested properties. Primarily used in API documentation.
+
+```mdx
+
+ Unique identifier.
+ Display name.
+
+```
+
+Props:
+- `title` (string): Toggle label.
+- `defaultOpen` (boolean, default: false): Initially expanded.
+
+## Fields
+
+Document API parameters and response structures.
+
+### ParamField
+
+```mdx
+
+ Maximum number of results to return.
+
+
+
+ User email address.
+
+
+
+ Bearer token for authentication.
+
+```
+
+Props:
+- First parameter format: `query.name`, `path.name`, `body.name`, or `header.name`.
+- `type` (string): `number`, `string`, `boolean`, `object`. Append `[]` for arrays.
+- `required` (boolean): Mark as required.
+- `deprecated` (boolean): Mark as deprecated.
+- `default` (any): Default value.
+- `placeholder` (string): Playground input placeholder.
+
+### ResponseField
+
+```mdx
+
+ Unique user identifier.
+
+
+
+
+ Record ID.
+ Current status.
+
+
+```
+
+Props:
+- `name` (string, required): Field name.
+- `type` (string, required): Field type.
+- `required` (boolean): Required indicator.
+- `deprecated` (boolean): Deprecation flag.
+- `default` (string): Default value.
+- `pre` (string[]): Labels rendered before the field name.
+- `post` (string[]): Labels rendered after the field name.
+
+## Request and response examples
+
+Display code examples in the right sidebar on API pages.
+
+```mdx
+
+
+```bash cURL
+curl --request POST \
+ --url https://api.example.com/users \
+ --header 'Authorization: Bearer TOKEN'
+```
+
+```python Python
+import requests
+response = requests.post(
+ "https://api.example.com/users",
+ headers={"Authorization": "Bearer TOKEN"}
+)
+```
+
+
+
+
+
+```json 200
+{
+ "id": "usr_123",
+ "status": "active"
+}
+```
+
+
+```
+
+## Frames
+
+Styled container for images with optional captions.
+
+```mdx
+
+
+
+```
+
+Props:
+- `caption` (string): Text below image. Supports Markdown.
+- `hint` (string): Text above image.
+
+## Icons
+
+Display icons inline.
+
+```mdx
+
+
+Text with inline icon.
+```
+
+Props:
+- `icon` (string, required): Icon name, URL, or file path.
+- `iconType` (string): Font Awesome style.
+- `size` (number): Pixel size.
+- `color` (string): Hex color.
+
+## Tooltips
+
+Hover-triggered contextual help.
+
+```mdx
+
+ API
+ requests are sent over HTTPS.
+```
+
+Props:
+- `tip` (string, required): Tooltip text.
+- `headline` (string): Text above tip.
+- `cta` (string): Call-to-action link text.
+- `href` (string): Link URL (required if using `cta`).
+
+## Badge
+
+Inline labels and status indicators.
+
+```mdx
+
+ Active
+
+```
+
+Props:
+- `color` (string, default: "gray"): `gray`, `blue`, `green`, `yellow`, `orange`, `red`, `purple`, `white`, `surface`.
+- `size` (string, default: "md"): `xs`, `sm`, `md`, `lg`.
+- `shape` (string, default: "rounded"): `rounded`, `pill`.
+- `icon` (string): Icon name.
+- `stroke` (boolean): Outline style instead of filled.
+- `disabled` (boolean): Reduced opacity.
+
+## Tree
+
+Display hierarchical file/folder structures.
+
+```mdx
+
+
+
+
+
+
+
+
+
+
+```
+
+Tree.Folder props:
+- `name` (string, required): Folder name.
+- `defaultOpen` (boolean, default: false): Expanded by default.
+- `openable` (boolean, default: true): Can expand/collapse.
+
+Tree.File props:
+- `name` (string, required): File name.
+
+## Mermaid diagrams
+
+Use mermaid code blocks for flowcharts, sequence diagrams, and more.
+
+````mdx
+```mermaid
+flowchart LR
+ A[Start] --> B{Decision}
+ B -->|Yes| C[Action]
+ B -->|No| D[Other action]
+```
+````
+
+## Panel
+
+Customize right sidebar content, replacing the table of contents.
+
+```mdx
+
+ Custom sidebar content goes here.
+
+```
+
+## Prompt
+
+Display copyable AI prompts.
+
+```mdx
+
+You are a technical writer. Generate a README for a Node.js project
+that includes installation, usage, and contributing sections.
+
+```
+
+Props:
+- `description` (string, required): Card header. Supports Markdown.
+- `actions` (array, default: ["copy"]): `"copy"`, `"cursor"`.
+- `icon` (string): Icon name.
+
+## Color
+
+Display color palettes with click-to-copy.
+
+```mdx
+
+
+
+
+
+```
+
+Table variant with rows:
+
+```mdx
+
+
+
+
+
+
+```
+
+## Tiles
+
+Visual preview cards, typically used in grid layouts.
+
+```mdx
+
+
+
+
+
+```
+
+Props:
+- `href` (string, required): Link destination.
+- `title` (string): Tile title.
+- `description` (string): Short description.
+
+## Update
+
+Display changelog entries and release notes.
+
+```mdx
+
+ ## What's new
+
+ - Added dark mode support
+ - Improved search performance
+
+```
+
+Props:
+- `label` (string, required): Date or version identifier.
+- `description` (string): Version or release name.
+- `tags` (string[]): Filterable tags.
+- `rss` (object): Custom RSS entry with `title` and `description`.
+
+## Visibility
+
+Show different content to humans (web UI) versus AI agents (Markdown output). Content marked `for="humans"` renders on the site but is excluded from `.md` URLs; content marked `for="agents"` is hidden on the site but included in Markdown output.
+
+```mdx
+
+ Click the **Get started** button in the top-right corner.
+
+
+
+ To create an account, call `POST /v1/accounts` with a valid email.
+
+```
+
+Props:
+- `for` (string, required): `"humans"` or `"agents"`.
+
+## View
+
+Language/framework-specific content sections that switch with a multi-view dropdown.
+
+```mdx
+
+ ```javascript
+ console.log("Hello from JavaScript!");
+ ```
+
+
+
+ ```python
+ print("Hello from Python!")
+ ```
+
+```
+
+Props:
+- `title` (string, required): View selector label.
+- `icon` (string): Icon name.
diff --git a/agent-context/context/skills/mintlify/reference/configuration.md b/agent-context/context/skills/mintlify/reference/configuration.md
new file mode 100644
index 0000000000..82706316e1
--- /dev/null
+++ b/agent-context/context/skills/mintlify/reference/configuration.md
@@ -0,0 +1,581 @@
+# Configuration reference
+
+Full docs.json settings, snippets, hidden pages, and custom CSS/JS.
+
+## docs.json
+
+The `docs.json` file controls the entire site. Required fields: `theme`, `name`, `colors.primary`, and `navigation`.
+
+### Splitting configuration with `$ref`
+
+Use `$ref` at any level of `docs.json` to load configuration from another JSON file. Useful for splitting large configs or sharing navigation across deployments.
+
+```json
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Your Docs",
+ "colors": { "primary": "#3B82F6" },
+ "navigation": {
+ "$ref": "./navigation.json"
+ }
+}
+```
+
+Rules:
+- `$ref` must be a relative path to a `.json` file.
+- When `$ref` resolves to an object, sibling keys in the same block take precedence over matching keys in the referenced file.
+- When `$ref` resolves to a non-object (e.g., an array), sibling keys are ignored.
+- Referenced files can contain their own `$ref` entries, resolved relative to that file.
+- Paths must stay within the project root. Circular references cause a build error.
+
+```json
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Your Docs",
+ "colors": {
+ "primary": "#3B82F6"
+ },
+ "navigation": {
+ "groups": [
+ {
+ "group": "Getting started",
+ "pages": ["index", "quickstart"]
+ }
+ ]
+ }
+}
+```
+
+## Complete frontmatter fields
+
+The SKILL.md file lists common frontmatter fields. Here is the complete set:
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `title` | string | Yes | Page title in navigation and browser tabs. |
+| `description` | string | No | Brief description for SEO. Displays under the title. |
+| `sidebarTitle` | string | No | Short title for sidebar navigation. |
+| `icon` | string | No | Lucide, Font Awesome, or Tabler icon name. Also accepts a URL or file path. |
+| `iconType` | string | No | Font Awesome icon style: `regular`, `solid`, `light`, `thin`, `sharp-solid`, `duotone`, `brands`. |
+| `tag` | string | No | Label next to page title in sidebar (e.g., "NEW"). |
+| `hidden` | boolean | No | Remove from sidebar. Page still accessible by URL. |
+| `noindex` | boolean | No | Prevent search engine indexing. |
+| `mode` | string | No | Page layout: `default`, `wide`, `custom`, `frame`, `center`. |
+| `keywords` | array | No | Search terms for internal search and SEO. |
+| `api` | string | No | API endpoint for interactive playground (e.g., `"POST /users"`). |
+| `openapi` | string | No | OpenAPI endpoint reference (e.g., `"GET /endpoint"`). |
+| `url` | string | No | External URL. Makes the nav entry link externally. |
+| `timestamp` | boolean | No | Override global timestamp setting for this page. |
+
+## Page modes
+
+Control page layout with the `mode` frontmatter field.
+
+```yaml
+# Default: standard layout with sidebar and table of contents
+---
+title: "Page title"
+---
+
+# Wide: hides table of contents for extra horizontal space
+---
+title: "Page title"
+mode: "wide"
+---
+
+# Custom: blank canvas, only top navbar visible
+---
+title: "Page title"
+mode: "custom"
+---
+
+# Frame: like custom but keeps sidebar (Aspen, Almond, and Luma themes only)
+---
+title: "Page title"
+mode: "frame"
+---
+
+# Center: removes sidebar and TOC, centers content (Mint and Linden themes only)
+---
+title: "Page title"
+mode: "center"
+---
+```
+
+## Theme
+
+One of: `mint`, `maple`, `palm`, `willow`, `linden`, `almond`, `aspen`, `sequoia`, `luma`.
+
+| Theme | Character |
+|-------|-----------|
+| `mint` | Classic, time-tested |
+| `maple` | Modern, clean, good for AI/SaaS |
+| `palm` | Sophisticated, fintech-focused |
+| `willow` | Stripped-back, minimal |
+| `linden` | Retro terminal, monospace |
+| `almond` | Card-based, minimalist |
+| `aspen` | Modern, supports complex navigation |
+| `sequoia` | Minimal, elegant, large-scale content |
+| `luma` | Clean, minimal design for polished documentation |
+
+## Colors
+
+```json
+"colors": {
+ "primary": "#3B82F6",
+ "light": "#F8FAFC",
+ "dark": "#0F172A"
+}
+```
+
+- `primary` (required): Main color, generally for emphasis in light mode.
+- `light`: Color for emphasis in dark mode.
+- `dark`: Color for buttons and hover states.
+
+All values must be hex codes starting with `#`.
+
+## Logo
+
+```json
+"logo": {
+ "light": "/logo/light.svg",
+ "dark": "/logo/dark.svg",
+ "href": "https://example.com"
+}
+```
+
+## Favicon
+
+Single file or light/dark variants:
+
+```json
+"favicon": "/favicon.ico"
+```
+
+```json
+"favicon": {
+ "light": "/favicon.png",
+ "dark": "/favicon-dark.png"
+}
+```
+
+## Icons
+
+```json
+"icons": {
+ "library": "lucide"
+}
+```
+
+Options: `"fontawesome"` (default), `"lucide"`, or `"tabler"`. You can only use one library per project. Individual icons can still use URLs or file paths regardless of this setting.
+
+## Fonts
+
+```json
+"fonts": {
+ "family": "Inter"
+}
+```
+
+Google Fonts load automatically by family name. For custom fonts:
+
+```json
+"fonts": {
+ "family": "CustomFont",
+ "source": "/fonts/CustomFont.woff2",
+ "format": "woff2",
+ "weight": 400,
+ "heading": {
+ "family": "HeadingFont",
+ "weight": 700
+ },
+ "body": {
+ "family": "BodyFont",
+ "weight": 400
+ }
+}
+```
+
+## Appearance
+
+```json
+"appearance": {
+ "default": "system",
+ "strict": false
+}
+```
+
+- `default`: `"system"`, `"light"`, or `"dark"`.
+- `strict`: Set `true` to hide the light/dark mode toggle.
+
+## Background
+
+```json
+"background": {
+ "image": {
+ "light": "/bg-light.svg",
+ "dark": "/bg-dark.svg"
+ },
+ "decoration": "gradient",
+ "color": {
+ "light": "#FFFFFF",
+ "dark": "#000000"
+ }
+}
+```
+
+- `decoration`: `"gradient"`, `"grid"`, or `"windows"`.
+
+## Styling
+
+```json
+"styling": {
+ "eyebrows": "breadcrumbs",
+ "latex": true,
+ "codeblocks": {
+ "theme": {
+ "light": "github-light",
+ "dark": "github-dark"
+ }
+ }
+}
+```
+
+- `eyebrows`: `"section"` (default) or `"breadcrumbs"`.
+- `latex`: Override automatic LaTeX detection.
+- `codeblocks`: `"system"` (default), `"dark"`, a Shiki theme name, or an object with `light`/`dark` themes.
+
+## Navbar
+
+```json
+"navbar": {
+ "links": [
+ {
+ "label": "Community",
+ "href": "https://example.com/community"
+ },
+ {
+ "type": "github",
+ "href": "https://github.com/example/repo"
+ }
+ ],
+ "primary": {
+ "type": "button",
+ "label": "Get Started",
+ "href": "https://example.com/start"
+ }
+}
+```
+
+Link types: omit `type` for standard text link, `"github"` for repo with star count, `"discord"` for server with online count.
+
+Primary button types: `"button"`, `"github"`, `"discord"`.
+
+## Footer
+
+```json
+"footer": {
+ "socials": {
+ "x": "https://x.com/example",
+ "github": "https://github.com/example",
+ "linkedin": "https://linkedin.com/company/example"
+ },
+ "links": [
+ {
+ "header": "Resources",
+ "items": [
+ { "label": "Blog", "href": "https://example.com/blog" }
+ ]
+ }
+ ]
+}
+```
+
+Valid social keys: `x`, `website`, `facebook`, `youtube`, `discord`, `slack`, `github`, `linkedin`, `instagram`, `hacker-news`, `medium`, `telegram`, `bluesky`, `threads`, `reddit`, `podcast`.
+
+## Banner
+
+```json
+"banner": {
+ "content": "Version 2.0 is live! [Learn more](/changelog)",
+ "dismissible": true,
+ "type": "info",
+ "color": {
+ "light": "#7C3AED",
+ "dark": "#5B21B6"
+ }
+}
+```
+
+- `content` (required): Supports basic Markdown (links, bold, italic). Custom components are not supported.
+- `dismissible`: Show a close button. Stays hidden for a user until content changes. Default: `false`.
+- `type`: Background style. `"info"` (primary color, default), `"warning"` (amber), `"critical"` (red).
+- `color`: Custom background hex. Overrides `type`. Object with `light` and `dark` keys, or a single hex string. Banner text is white — choose a dark enough background.
+
+Language-specific banners can be set inside the `navigation.languages` entries.
+
+## Variables
+
+Global content variables substituted at build time using `{{variableName}}` syntax in MDX files.
+
+```json
+"variables": {
+ "apiVersion": "v2",
+ "baseUrl": "https://api.example.com"
+}
+```
+
+Keys must be alphanumeric with hyphens only. Values are plain strings. Use in any `.mdx` file:
+
+```mdx
+The current API version is {{apiVersion}}.
+```
+
+## Redirects
+
+```json
+"redirects": [
+ {
+ "source": "/old-page",
+ "destination": "/new-page",
+ "permanent": true
+ }
+]
+```
+
+## Metadata
+
+```json
+"metadata": {
+ "timestamp": true
+}
+```
+
+Shows "Last modified on [date]" on all pages. Override per-page with `timestamp` frontmatter.
+
+## Interaction
+
+```json
+"interaction": {
+ "drilldown": false
+}
+```
+
+Controls whether clicking a navigation group navigates to its first page (`true`) or only expands/collapses (`false`).
+
+## SEO
+
+```json
+"seo": {
+ "metatags": {
+ "canonical": "https://docs.example.com",
+ "og:locale": "en_US"
+ },
+ "indexing": "navigable"
+}
+```
+
+- `indexing`: `"navigable"` (only nav pages) or `"all"` (every page including hidden).
+
+## Search
+
+```json
+"search": {
+ "prompt": "Search documentation..."
+}
+```
+
+## Contextual menu
+
+```json
+"contextual": {
+ "options": ["copy", "chatgpt", "claude", "cursor", "vscode"],
+ "display": "header"
+}
+```
+
+- `options` (required): First item is the default action. Built-in values: `"assistant"`, `"copy"`, `"view"`, `"chatgpt"`, `"claude"`, `"perplexity"`, `"grok"`, `"aistudio"`, `"devin"`, `"windsurf"`, `"mcp"`, `"add-mcp"`, `"cursor"`, `"vscode"`, `"devin-mcp"`. Custom objects accepted with `title`, `description`, `icon`, and `href` fields.
+- `display`: Where to show the menu. `"header"` (default) or `"toc"`.
+
+## Thumbnails
+
+```json
+"thumbnails": {
+ "appearance": "light",
+ "background": "/images/thumbnail-bg.svg",
+ "fonts": {
+ "family": "Inter"
+ }
+}
+```
+
+## Error handling
+
+```json
+"errors": {
+ "404": {
+ "redirect": true,
+ "title": "Page not found",
+ "description": "This page doesn't exist."
+ }
+}
+```
+
+## API configuration
+
+```json
+"api": {
+ "openapi": "openapi.json",
+ "playground": {
+ "display": "interactive",
+ "proxy": true
+ },
+ "examples": {
+ "languages": ["bash", "javascript", "python"],
+ "defaults": "all",
+ "prefill": false,
+ "autogenerate": true
+ },
+ "mdx": {
+ "server": "https://api.example.com",
+ "auth": {
+ "method": "bearer"
+ }
+ }
+}
+```
+
+- `openapi`: Single file, array, or object with `source` and `directory`.
+- `asyncapi`: Same format as `openapi` for AsyncAPI specs.
+- `playground.display`: `"interactive"`, `"simple"`, `"none"`, or `"auth"`.
+- `playground.proxy`: Route requests through Mintlify's proxy. Default: `true`.
+- `playground.credentials`: Include cookies and auth headers for cross-origin requests when proxy is `false`. Default: `false`.
+- `params.expanded`: Expand all parameters by default. `"all"` or `"closed"` (default).
+- `params.post`: OpenAPI schema field keys to surface as pills next to parameter names.
+- `url`: Set to `"full"` to always show the full base URL (default: only shown when multiple base URLs exist).
+- `examples.languages`: `bash`, `go`, `java`, `javascript`, `node`, `php`, `powershell`, `python`, `ruby`, `swift`.
+- `examples.defaults`: `"required"` or `"all"` (include optional params).
+- `examples.prefill`: Pre-fill playground fields with spec example values. Default: `false`.
+- `examples.autogenerate`: Generate code samples from API specs. Default: `true`.
+- `mdx.auth.method`: `"bearer"`, `"basic"`, `"key"`, `"cobo"`.
+
+## Integrations
+
+```json
+"integrations": {
+ "ga4": { "measurementId": "G-XXXXXXXXXX" },
+ "gtm": { "tagId": "GTM-XXXXX" },
+ "posthog": { "apiKey": "phc_xxx", "apiHost": "https://app.posthog.com" },
+ "amplitude": { "apiKey": "xxx" },
+ "mixpanel": { "projectToken": "xxx" },
+ "segment": { "key": "xxx" },
+ "clarity": { "projectId": "xxx" },
+ "fathom": { "siteId": "xxx" },
+ "hotjar": { "hjid": "xxx", "hjsv": "xxx" },
+ "logrocket": { "appId": "xxx" },
+ "heap": { "appId": "xxx" },
+ "pirsch": { "id": "xxx" },
+ "plausible": { "domain": "xxx", "server": "optional" },
+ "hightouch": { "writeKey": "xxx", "apiHost": "optional" },
+ "clearbit": { "publicApiKey": "xxx" },
+ "intercom": { "appId": "xxx" },
+ "frontchat": { "snippetId": "xxx" },
+ "telemetry": { "enabled": true },
+ "cookies": { "key": "consent_key", "value": "accepted" }
+}
+```
+
+## Reusable snippets
+
+Store reusable content in the `/snippets/` directory.
+
+### MDX snippets
+
+```mdx
+
+Before you begin, make sure you have:
+- Node.js 18+
+- A Mintlify account
+```
+
+Import in any page:
+
+```mdx
+import Prerequisites from "/snippets/prerequisites.mdx";
+
+
+```
+
+### JSX components
+
+```jsx
+// snippets/counter.jsx
+export const Counter = () => {
+ const [count, setCount] = useState(0);
+ return (
+
+
+ {count}
+
+
+ );
+};
+```
+
+Import in any page using the root-relative path to the file:
+
+```mdx
+import { Counter } from "/snippets/counter.jsx";
+
+
+```
+
+JSX components can live in any directory, not just `/snippets/`. Nested imports between snippet files are not supported.
+
+## Hidden pages
+
+Set `hidden: true` in frontmatter to remove from sidebar. Page remains accessible by URL.
+
+```yaml
+---
+title: "Internal reference"
+hidden: true
+---
+```
+
+Or omit the page from `docs.json` navigation entirely.
+
+## .mintignore
+
+Exclude files completely from the published docs. Place `.mintignore` in the docs root. Uses `.gitignore` syntax.
+
+```
+drafts/
+*.draft.mdx
+private-notes.md
+**/internal/**
+!important.mdx
+```
+
+Files in `.mintignore` are not published, not indexed, and not accessible by URL.
+
+## Custom CSS and JavaScript
+
+### CSS
+
+Add `.css` files to your repository. Class names become available in all MDX files.
+
+```css
+/* styles.css */
+#navbar {
+ background: #fffff2;
+}
+```
+
+Built-in Tailwind CSS v3 classes are available. Arbitrary values (e.g., `w-[350px]`) are not supported — use inline `style` instead.
+
+### JavaScript
+
+Any `.js` file in the content directory is included globally on all pages.
diff --git a/agent-context/context/skills/mintlify/reference/navigation.md b/agent-context/context/skills/mintlify/reference/navigation.md
new file mode 100644
index 0000000000..ff44a1a236
--- /dev/null
+++ b/agent-context/context/skills/mintlify/reference/navigation.md
@@ -0,0 +1,352 @@
+# Navigation reference
+
+All navigation patterns for the `navigation` property in `docs.json`.
+
+## Pages
+
+Flat list of pages with no grouping.
+
+```json
+{
+ "navigation": {
+ "pages": ["index", "quickstart", "guides/example"]
+ }
+}
+```
+
+## Groups
+
+```json
+{
+ "navigation": {
+ "groups": [
+ {
+ "group": "Getting started",
+ "icon": "rocket",
+ "pages": ["index", "quickstart"]
+ },
+ {
+ "group": "Guides",
+ "icon": "book-open",
+ "tag": "NEW",
+ "pages": [
+ "guides/overview",
+ {
+ "group": "Advanced",
+ "expanded": false,
+ "pages": ["guides/advanced/config", "guides/advanced/deploy"]
+ }
+ ]
+ }
+ ]
+ }
+}
+```
+
+Group properties:
+- `group` (required): Section title.
+- `pages` (required): Array of page paths or nested groups.
+- `icon`: Icon name.
+- `tag`: Label displayed next to group name.
+- `root`: Page that opens when clicking the group title.
+- `expanded`: Default open state for nested groups (`true`/`false`). Top-level groups are always expanded.
+- `directory`: When the group has a `root` page, render a listing of child pages below the root page content. Values: `"none"` (default), `"accordion"` (collapsible list), `"card"` (horizontal cards). Inherits recursively; descendants can override.
+- `boost`: Numeric multiplier for in-product search ranking of every page in the group. Use values `> 1` to prioritize, `0–1` to de-prioritize.
+
+## Tabs
+
+```json
+{
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Documentation",
+ "icon": "book-open",
+ "groups": [
+ {
+ "group": "Getting started",
+ "pages": ["index", "quickstart"]
+ }
+ ]
+ },
+ {
+ "tab": "API reference",
+ "icon": "square-terminal",
+ "pages": ["api/overview", "api/endpoints"]
+ },
+ {
+ "tab": "Blog",
+ "icon": "newspaper",
+ "href": "https://example.com/blog"
+ }
+ ]
+ }
+}
+```
+
+### Menus (within tabs)
+
+```json
+{
+ "tab": "Developer tools",
+ "menu": [
+ {
+ "item": "API reference",
+ "icon": "rocket",
+ "groups": [
+ {
+ "group": "Endpoints",
+ "pages": ["api/get", "api/post"]
+ }
+ ]
+ },
+ {
+ "item": "SDKs",
+ "icon": "code",
+ "description": "Client libraries",
+ "pages": ["sdk/javascript", "sdk/python"]
+ }
+ ]
+}
+```
+
+## Anchors
+
+```json
+{
+ "navigation": {
+ "anchors": [
+ {
+ "anchor": "Documentation",
+ "icon": "book-open",
+ "groups": [
+ {
+ "group": "Getting started",
+ "pages": ["quickstart", "tutorial"]
+ }
+ ]
+ },
+ {
+ "anchor": "Blog",
+ "href": "https://example.com/blog"
+ }
+ ]
+ }
+}
+```
+
+### Global anchors
+
+Appear on all pages regardless of active section:
+
+```json
+{
+ "navigation": {
+ "global": {
+ "anchors": [
+ {
+ "anchor": "Changelog",
+ "icon": "list",
+ "href": "/changelog"
+ }
+ ]
+ },
+ "tabs": [...]
+ }
+}
+```
+
+## Global navigation
+
+`navigation.global` supports tabs, anchors, dropdowns, languages, versions, and products that appear on all pages regardless of active section. Useful for persistent switchers and cross-cutting links.
+
+```json
+{
+ "navigation": {
+ "global": {
+ "tabs": [
+ { "tab": "API", "href": "/api-reference", "icon": "square-terminal" }
+ ],
+ "anchors": [
+ { "anchor": "Changelog", "icon": "list", "href": "/changelog" }
+ ],
+ "languages": [
+ { "language": "en", "default": true },
+ { "language": "es" }
+ ],
+ "versions": [
+ { "version": "v2", "default": true },
+ { "version": "v1" }
+ ],
+ "products": [
+ { "product": "Core API", "icon": "server" },
+ { "product": "Mobile SDK", "icon": "smartphone" }
+ ]
+ }
+ }
+}
+```
+
+Global element properties:
+- `global.tabs`: Each entry requires `tab` (string) and `href`. Optional: `icon`, `iconType`, `hidden`.
+- `global.anchors`: Each entry requires `anchor` (string) and `href`. Optional: `icon`, `iconType`, `color.light`, `color.dark`, `hidden`.
+- `global.dropdowns`: Each entry requires `dropdown` (string) and `href`. Optional: `icon`, `iconType`, `hidden`.
+- `global.languages`: Each entry requires `language` (code string). Optional: `default`, `hidden`, `href`.
+- `global.versions`: Each entry requires `version` (string). Optional: `default`, `hidden`, `href`.
+- `global.products`: Each entry requires `product` (string). Optional: `description`, `icon`, `iconType`.
+
+## Dropdowns
+
+```json
+{
+ "navigation": {
+ "dropdowns": [
+ {
+ "dropdown": "Documentation",
+ "icon": "book-open",
+ "groups": [
+ {
+ "group": "Getting started",
+ "pages": ["index", "quickstart"]
+ }
+ ]
+ },
+ {
+ "dropdown": "API reference",
+ "icon": "square-terminal",
+ "pages": ["api/overview"]
+ }
+ ]
+ }
+}
+```
+
+## Products
+
+```json
+{
+ "navigation": {
+ "products": [
+ {
+ "product": "Core API",
+ "description": "Core API documentation",
+ "icon": "server",
+ "tabs": [
+ {
+ "tab": "Documentation",
+ "groups": [
+ { "group": "Getting started", "pages": ["core/quickstart"] }
+ ]
+ }
+ ]
+ },
+ {
+ "product": "Mobile SDK",
+ "icon": "smartphone",
+ "pages": ["mobile/overview"]
+ }
+ ]
+ }
+}
+```
+
+## Versions
+
+```json
+{
+ "navigation": {
+ "versions": [
+ {
+ "version": "2.0.0",
+ "default": true,
+ "tag": "Latest",
+ "groups": [
+ { "group": "Getting started", "pages": ["v2/overview", "v2/quickstart"] }
+ ]
+ },
+ {
+ "version": "1.0.0",
+ "tag": "Deprecated",
+ "groups": [
+ { "group": "Getting started", "pages": ["v1/overview", "v1/quickstart"] }
+ ]
+ }
+ ]
+ }
+}
+```
+
+Version properties:
+- `version` (required): Version label shown in the selector.
+- `default`: Set `true` to make this the default version (otherwise the first entry is the default).
+- `tag`: Badge label displayed in the version selector dropdown (e.g., `"Latest"`, `"Recommended"`, `"Beta"`).
+
+## Languages
+
+```json
+{
+ "navigation": {
+ "languages": [
+ {
+ "language": "en",
+ "groups": [
+ { "group": "Getting started", "pages": ["en/overview", "en/quickstart"] }
+ ]
+ },
+ {
+ "language": "es",
+ "groups": [
+ { "group": "Comenzando", "pages": ["es/overview", "es/quickstart"] }
+ ]
+ }
+ ]
+ }
+}
+```
+
+Each language entry can include its own `banner`, `footer`, and `navbar` configuration overrides.
+
+## OpenAPI in navigation
+
+```json
+{
+ "navigation": {
+ "groups": [
+ {
+ "group": "API reference",
+ "openapi": "/path/to/openapi.json",
+ "pages": [
+ "overview",
+ "GET /users",
+ "POST /users",
+ {
+ "group": "Products",
+ "openapi": "/path/to/openapi-v2.json",
+ "pages": ["GET /products", "POST /products"]
+ }
+ ]
+ }
+ ]
+ }
+}
+```
+
+When you add `openapi` to a navigation element without specifying pages, Mintlify auto-generates pages for all endpoints.
+
+## Choosing a navigation pattern
+
+| Pattern | When to use |
+|---------|-------------|
+| Groups | Default. Single audience, straightforward hierarchy. |
+| Tabs | Distinct sections with different audiences or content types. |
+| Anchors | Persistent section links at sidebar top. |
+| Dropdowns | Multiple sections users switch between. |
+| Products | Multi-product company with separate docs per product. |
+| Versions | Multiple API/product versions. |
+| Languages | Localized content. |
+
+Navigation elements can nest within each other. Common combinations:
+- Tabs containing groups
+- Products containing tabs
+- Versions containing tabs
+- Anchors containing groups
diff --git a/agent-context/context/skills/mintlify/reference/site-discovery.md b/agent-context/context/skills/mintlify/reference/site-discovery.md
new file mode 100644
index 0000000000..bac809a625
--- /dev/null
+++ b/agent-context/context/skills/mintlify/reference/site-discovery.md
@@ -0,0 +1,29 @@
+# Product discovery for new documentation sites
+
+Use this workflow when creating a new site or replacing substantial placeholder content. Skip it for targeted edits to an established site.
+
+## Build a product brief
+
+Inspect the repository, supplied URLs, existing pages, and attachments first. Determine what they already establish about:
+
+- What the product helps people accomplish
+- Who the primary documentation reader is and what brings them to the docs
+- The first 1–3 tasks that reader must complete
+- Why the product was built or chosen over the current approach
+
+Ask one round of up to four questions for important gaps that only the user can answer. Make each question specific to the source material. When the interface supports choices, suggest 2–4 plausible answers derived from the sources and allow a custom answer.
+
+Do not ask about facts you can verify yourself. Infer presentation choices such as theme, page grouping, and component usage unless choosing incorrectly would waste substantial work.
+
+If the user does not answer, state or record reasonable assumptions and continue. Do not repeat the questions later in generation.
+
+## Use the brief
+
+Synthesize answers and verified facts into a concise internal brief:
+
+- `description`: the outcome the product creates
+- `primaryAudience`: the main reader and their context
+- `jobsToBeDone`: the critical tasks the docs must enable
+- `motivation`: the problem, differentiation, or reason the product exists
+
+Use the brief to prioritize the homepage, introduction, quickstart, navigation, and examples. Preserve the user's terminology. Do not copy the brief mechanically onto every page.
diff --git a/agent-context/package.json b/agent-context/package.json
new file mode 100644
index 0000000000..eda1e3bb5a
--- /dev/null
+++ b/agent-context/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@mintlify/agent-context",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "engines": {
+ "node": ">=22"
+ },
+ "scripts": {
+ "build": "node scripts/build.mjs",
+ "check": "node scripts/check.mjs",
+ "status": "node scripts/status.mjs",
+ "test": "node --test"
+ }
+}
diff --git a/agent-context/scripts/build.mjs b/agent-context/scripts/build.mjs
new file mode 100644
index 0000000000..6f1656c273
--- /dev/null
+++ b/agent-context/scripts/build.mjs
@@ -0,0 +1,12 @@
+import path from 'node:path';
+import { buildAll, repositoryRoot } from './lib.mjs';
+
+const selectedIds = process.argv.slice(2);
+const results = await buildAll({
+ outputRoot: path.join(repositoryRoot, 'dist'),
+ selectedIds,
+});
+
+for (const { provenance } of results) {
+ console.log(`${provenance.target}: ${provenance.artifactDigest}`);
+}
diff --git a/agent-context/scripts/check.mjs b/agent-context/scripts/check.mjs
new file mode 100644
index 0000000000..d6fa77974b
--- /dev/null
+++ b/agent-context/scripts/check.mjs
@@ -0,0 +1,36 @@
+import assert from 'node:assert/strict';
+import { mkdtemp, readFile, rm } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import { buildAll } from './lib.mjs';
+
+const outputRoot = await mkdtemp(path.join(tmpdir(), 'mintlify-agent-context-'));
+
+try {
+ const results = await buildAll({ outputRoot });
+ assert.equal(results.length, 3);
+
+ const sharedFiles = [
+ 'api-docs.md',
+ 'cli.md',
+ 'components.md',
+ 'configuration.md',
+ 'navigation.md',
+ 'site-discovery.md',
+ ];
+ for (const file of sharedFiles) {
+ const contents = await Promise.all(
+ results.map(({ provenance }) =>
+ readFile(
+ path.join(outputRoot, provenance.target, 'skills', 'mintlify', 'reference', file),
+ 'utf8',
+ ),
+ ),
+ );
+ assert.ok(contents.every((content) => content === contents[0]), `${file} drifted`);
+ }
+
+ console.log('All generated targets are valid and shared references are identical.');
+} finally {
+ await rm(outputRoot, { recursive: true, force: true });
+}
diff --git a/agent-context/scripts/lib.mjs b/agent-context/scripts/lib.mjs
new file mode 100644
index 0000000000..15c0fb41cd
--- /dev/null
+++ b/agent-context/scripts/lib.mjs
@@ -0,0 +1,187 @@
+import { createHash } from 'node:crypto';
+import { execFileSync } from 'node:child_process';
+import {
+ cp,
+ mkdir,
+ readFile,
+ readdir,
+ rm,
+ stat,
+ writeFile,
+} from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const scriptsDirectory = path.dirname(fileURLToPath(import.meta.url));
+export const repositoryRoot = path.resolve(scriptsDirectory, '..');
+
+const contextDirectory = path.join(repositoryRoot, 'context', 'skills', 'mintlify');
+const targetsDirectory = path.join(repositoryRoot, 'targets');
+
+export async function loadTargets(selectedIds = []) {
+ const entries = (await readdir(targetsDirectory))
+ .filter((entry) => entry.endsWith('.json'))
+ .sort();
+ const targets = await Promise.all(
+ entries.map(async (entry) =>
+ JSON.parse(await readFile(path.join(targetsDirectory, entry), 'utf8')),
+ ),
+ );
+
+ const ids = new Set(targets.map((target) => target.id));
+ for (const id of selectedIds) {
+ if (!ids.has(id)) {
+ throw new Error(`Unknown target: ${id}`);
+ }
+ }
+
+ return selectedIds.length === 0
+ ? targets
+ : targets.filter((target) => selectedIds.includes(target.id));
+}
+
+function render(template, values, fileName) {
+ const rendered = template.replace(/\{\{([A-Za-z][A-Za-z0-9]*)\}\}/g, (_, key) => {
+ if (!(key in values) || typeof values[key] !== 'string') {
+ throw new Error(`${fileName} requires a string value for ${key}`);
+ }
+ return values[key];
+ });
+
+ const unresolved = rendered.match(/\{\{[^}]+\}\}/g);
+ if (unresolved) {
+ throw new Error(`${fileName} contains unresolved variables: ${unresolved.join(', ')}`);
+ }
+
+ return rendered;
+}
+
+function markGenerated(skill) {
+ const frontmatterEnd = skill.indexOf('\n---\n', 4);
+ if (frontmatterEnd === -1) {
+ throw new Error('SKILL.md frontmatter is not closed');
+ }
+
+ const insertionPoint = frontmatterEnd + '\n---\n'.length;
+ return `${skill.slice(0, insertionPoint)}\n\n${skill.slice(insertionPoint)}`;
+}
+
+async function walkFiles(directory, prefix = '') {
+ const entries = await readdir(directory);
+ const files = [];
+
+ for (const entry of entries.sort()) {
+ const absolutePath = path.join(directory, entry);
+ const relativePath = path.join(prefix, entry);
+ const entryStat = await stat(absolutePath);
+ if (entryStat.isDirectory()) {
+ files.push(...(await walkFiles(absolutePath, relativePath)));
+ } else if (entryStat.isFile()) {
+ files.push(relativePath);
+ }
+ }
+
+ return files;
+}
+
+async function artifactDigest(directory) {
+ const hash = createHash('sha256');
+ for (const file of await walkFiles(directory)) {
+ hash.update(file.split(path.sep).join('/'));
+ hash.update('\0');
+ hash.update(await readFile(path.join(directory, file)));
+ hash.update('\0');
+ }
+ return `sha256:${hash.digest('hex')}`;
+}
+
+function sourceCommit() {
+ if (process.env.GITHUB_SHA) {
+ return process.env.GITHUB_SHA;
+ }
+
+ try {
+ const changes = execFileSync('git', ['status', '--porcelain', '--', '.'], {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'ignore'],
+ }).trim();
+ if (changes) {
+ return 'working-tree';
+ }
+
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'ignore'],
+ }).trim();
+ } catch {
+ return 'working-tree';
+ }
+}
+
+function validateSkill(skill, target) {
+ if (!skill.startsWith('---\n')) {
+ throw new Error(`${target.id}: SKILL.md must start with YAML frontmatter`);
+ }
+ if (!/^name: mintlify$/m.test(skill) || !/^description: .+$/m.test(skill)) {
+ throw new Error(`${target.id}: SKILL.md requires name and description fields`);
+ }
+ if (/mint analytics|mint workflow/.test(skill)) {
+ throw new Error(`${target.id}: SKILL.md contains retired CLI commands`);
+ }
+}
+
+export async function buildTarget(target, outputRoot) {
+ const targetRoot = path.join(outputRoot, target.id);
+ const skillOutput = path.join(targetRoot, 'skills', 'mintlify');
+ await rm(targetRoot, { recursive: true, force: true });
+ await mkdir(skillOutput, { recursive: true });
+
+ const skillTemplate = await readFile(path.join(contextDirectory, 'SKILL.md'), 'utf8');
+ const skill = markGenerated(render(skillTemplate, target, 'SKILL.md'));
+ validateSkill(skill, target);
+ await writeFile(path.join(skillOutput, 'SKILL.md'), skill);
+ await cp(path.join(contextDirectory, 'reference'), path.join(skillOutput, 'reference'), {
+ recursive: true,
+ });
+
+ const provenance = {
+ schemaVersion: 1,
+ sourceRepository: 'mintlify/docs',
+ sourcePath: 'agent-context',
+ sourceCommit: sourceCommit(),
+ target: target.id,
+ artifactDigest: await artifactDigest(path.join(targetRoot, 'skills')),
+ };
+ await writeFile(
+ path.join(targetRoot, '.mintlify-agent-context.json'),
+ `${JSON.stringify(provenance, null, 2)}\n`,
+ );
+
+ return { targetRoot, provenance };
+}
+
+export async function buildAll({ outputRoot, selectedIds = [] } = {}) {
+ const resolvedOutput = outputRoot ?? path.join(repositoryRoot, 'dist');
+ await mkdir(resolvedOutput, { recursive: true });
+ const targets = await loadTargets(selectedIds);
+ return Promise.all(targets.map((target) => buildTarget(target, resolvedOutput)));
+}
+
+export async function copyTargetToRepository(targetId, destination, outputRoot) {
+ const sourceRoot = path.join(outputRoot, targetId);
+ const sourceSkill = path.join(sourceRoot, 'skills', 'mintlify');
+ const destinationSkill = path.join(destination, 'skills', 'mintlify');
+
+ await stat(sourceSkill);
+ await rm(destinationSkill, { recursive: true, force: true });
+ await mkdir(path.dirname(destinationSkill), { recursive: true });
+ await cp(sourceSkill, destinationSkill, { recursive: true });
+ await cp(
+ path.join(sourceRoot, '.mintlify-agent-context.json'),
+ path.join(destination, '.mintlify-agent-context.json'),
+ );
+}
+
+export { artifactDigest, walkFiles };
diff --git a/agent-context/scripts/status.mjs b/agent-context/scripts/status.mjs
new file mode 100644
index 0000000000..158981c8ba
--- /dev/null
+++ b/agent-context/scripts/status.mjs
@@ -0,0 +1,51 @@
+import { readFile } from 'node:fs/promises';
+import { mkdtemp, rm } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import { artifactDigest, buildAll, loadTargets, repositoryRoot } from './lib.mjs';
+
+const workspaceRoot = path.resolve(process.argv[2] ?? path.join(repositoryRoot, '..', '..'));
+const outputRoot = await mkdtemp(path.join(tmpdir(), 'mintlify-agent-context-status-'));
+const targets = await loadTargets();
+const rows = [];
+
+try {
+ const builds = await buildAll({ outputRoot });
+ const expectedByTarget = new Map(
+ builds.map(({ provenance }) => [provenance.target, provenance.artifactDigest]),
+ );
+
+ for (const target of targets) {
+ const repositoryName = target.repository.split('/').at(-1);
+ const pluginRoot = path.join(workspaceRoot, repositoryName);
+ const expectedDigest = expectedByTarget.get(target.id);
+
+ try {
+ const actualDigest = await artifactDigest(path.join(pluginRoot, 'skills'));
+ const lock = JSON.parse(
+ await readFile(path.join(pluginRoot, '.mintlify-agent-context.json'), 'utf8'),
+ );
+ const current = actualDigest === expectedDigest && lock.artifactDigest === expectedDigest;
+ rows.push({
+ target: target.id,
+ repository: target.repository,
+ state: current ? 'current' : 'behind',
+ source: lock.sourceCommit,
+ });
+ } catch {
+ rows.push({
+ target: target.id,
+ repository: target.repository,
+ state: 'unavailable',
+ source: '-',
+ });
+ }
+ }
+} finally {
+ await rm(outputRoot, { recursive: true, force: true });
+}
+
+console.table(rows);
+if (rows.some((row) => row.state !== 'current')) {
+ process.exitCode = 1;
+}
diff --git a/agent-context/scripts/sync-target.mjs b/agent-context/scripts/sync-target.mjs
new file mode 100644
index 0000000000..a79ce505a0
--- /dev/null
+++ b/agent-context/scripts/sync-target.mjs
@@ -0,0 +1,13 @@
+import path from 'node:path';
+import { buildAll, copyTargetToRepository, repositoryRoot } from './lib.mjs';
+
+const [targetId, destinationArgument] = process.argv.slice(2);
+if (!targetId || !destinationArgument) {
+ throw new Error('Usage: node scripts/sync-target.mjs ');
+}
+
+const destination = path.resolve(process.cwd(), destinationArgument);
+const outputRoot = path.join(repositoryRoot, 'dist');
+await buildAll({ outputRoot, selectedIds: [targetId] });
+await copyTargetToRepository(targetId, destination, outputRoot);
+console.log(`Synced ${targetId} context to ${destination}`);
diff --git a/agent-context/targets/claude.json b/agent-context/targets/claude.json
new file mode 100644
index 0000000000..f0508ee7fa
--- /dev/null
+++ b/agent-context/targets/claude.json
@@ -0,0 +1,7 @@
+{
+ "id": "claude",
+ "repository": "mintlify/mintlify-claude-plugin",
+ "searchMcpName": "Mintlify (docs MCP)",
+ "adminMcpName": "Mintlify MCP (dashboard MCP)",
+ "authenticationBehavior": "Claude Code will open a browser window to authenticate."
+}
diff --git a/agent-context/targets/codex.json b/agent-context/targets/codex.json
new file mode 100644
index 0000000000..b742530005
--- /dev/null
+++ b/agent-context/targets/codex.json
@@ -0,0 +1,7 @@
+{
+ "id": "codex",
+ "repository": "mintlify/codex-plugin",
+ "searchMcpName": "Search MCP",
+ "adminMcpName": "Admin MCP",
+ "authenticationBehavior": "Codex will open a browser window to authenticate."
+}
diff --git a/agent-context/targets/cursor.json b/agent-context/targets/cursor.json
new file mode 100644
index 0000000000..c787cbe053
--- /dev/null
+++ b/agent-context/targets/cursor.json
@@ -0,0 +1,7 @@
+{
+ "id": "cursor",
+ "repository": "mintlify/cursor-plugin",
+ "searchMcpName": "Mintlify (docs MCP)",
+ "adminMcpName": "Mintlify MCP (dashboard MCP)",
+ "authenticationBehavior": "your editor will prompt you to authenticate via browser."
+}
diff --git a/agent-context/test/build.test.mjs b/agent-context/test/build.test.mjs
new file mode 100644
index 0000000000..cb253ea10b
--- /dev/null
+++ b/agent-context/test/build.test.mjs
@@ -0,0 +1,61 @@
+import assert from 'node:assert/strict';
+import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import test from 'node:test';
+import { buildAll, copyTargetToRepository } from '../scripts/lib.mjs';
+
+test('builds all client variants from one canonical skill', async () => {
+ const outputRoot = await mkdtemp(path.join(tmpdir(), 'mintlify-agent-context-test-'));
+
+ try {
+ await buildAll({ outputRoot });
+ const codex = await readFile(
+ path.join(outputRoot, 'codex', 'skills', 'mintlify', 'SKILL.md'),
+ 'utf8',
+ );
+ const cursor = await readFile(
+ path.join(outputRoot, 'cursor', 'skills', 'mintlify', 'SKILL.md'),
+ 'utf8',
+ );
+ const claude = await readFile(
+ path.join(outputRoot, 'claude', 'skills', 'mintlify', 'SKILL.md'),
+ 'utf8',
+ );
+
+ assert.match(codex, /### Search MCP/);
+ assert.match(cursor, /### Mintlify \(docs MCP\)/);
+ assert.match(claude, /Claude Code will open a browser window/);
+ for (const skill of [codex, cursor, claude]) {
+ assert.match(skill, /Generated from mintlify\/docs\/agent-context/);
+ assert.match(skill, /mint automations/);
+ assert.doesNotMatch(skill, /mint analytics|mint workflow|\{\{/);
+ }
+ } finally {
+ await rm(outputRoot, { recursive: true, force: true });
+ }
+});
+
+test('sync replaces only generated context paths', async () => {
+ const root = await mkdtemp(path.join(tmpdir(), 'mintlify-agent-context-sync-test-'));
+ const outputRoot = path.join(root, 'dist');
+ const destination = path.join(root, 'plugin');
+
+ try {
+ await mkdir(path.join(destination, 'skills', 'mintlify'), { recursive: true });
+ await writeFile(path.join(destination, 'README.md'), 'target-owned\n');
+ await writeFile(path.join(destination, 'skills', 'mintlify', 'stale.md'), 'remove me\n');
+
+ await buildAll({ outputRoot, selectedIds: ['codex'] });
+ await copyTargetToRepository('codex', destination, outputRoot);
+
+ assert.equal(await readFile(path.join(destination, 'README.md'), 'utf8'), 'target-owned\n');
+ await assert.rejects(readFile(path.join(destination, 'skills', 'mintlify', 'stale.md')));
+ assert.match(
+ await readFile(path.join(destination, 'skills', 'mintlify', 'SKILL.md'), 'utf8'),
+ /### Search MCP/,
+ );
+ } finally {
+ await rm(root, { recursive: true, force: true });
+ }
+});
From 797f835a79759f0b98ef6cd69f208220352bdb65 Mon Sep 17 00:00:00 2001
From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com>
Date: Fri, 24 Jul 2026 15:51:33 -0700
Subject: [PATCH 2/6] remove plugin-specific MCP info
---
.github/workflows/sync-agent-context.yml | 5 +-
agent-context/README.md | 11 ++--
agent-context/context/mcp-servers.json | 10 ++++
.../context/skills/mintlify/SKILL.md | 6 +-
agent-context/scripts/lib.mjs | 58 ++++++++++++-------
agent-context/scripts/status.mjs | 9 ++-
agent-context/targets/claude.json | 5 +-
agent-context/targets/codex.json | 5 +-
agent-context/targets/cursor.json | 5 +-
agent-context/test/build.test.mjs | 31 ++++++++--
10 files changed, 99 insertions(+), 46 deletions(-)
create mode 100644 agent-context/context/mcp-servers.json
diff --git a/.github/workflows/sync-agent-context.yml b/.github/workflows/sync-agent-context.yml
index f517db330b..df168213af 100644
--- a/.github/workflows/sync-agent-context.yml
+++ b/.github/workflows/sync-agent-context.yml
@@ -43,12 +43,15 @@ jobs:
- target: codex
repository: mintlify/codex-plugin
repository_name: codex-plugin
+ mcp_file: .mcp.json
- target: cursor
repository: mintlify/cursor-plugin
repository_name: cursor-plugin
+ mcp_file: mcp.json
- target: claude
repository: mintlify/mintlify-claude-plugin
repository_name: mintlify-claude-plugin
+ mcp_file: .mcp.json
steps:
- name: Check out context source
@@ -102,7 +105,7 @@ jobs:
git config user.name "${APP_SLUG}[bot]"
git config user.email "${APP_SLUG}[bot]@users.noreply.github.com"
git checkout -B "$BRANCH"
- git add skills/mintlify .mintlify-agent-context.json
+ git add skills/mintlify .mintlify-agent-context.json "${{ matrix.mcp_file }}"
git commit -m "Sync Mintlify agent context"
git fetch origin "$BRANCH:refs/remotes/origin/$BRANCH" || true
git push --force-with-lease origin "HEAD:$BRANCH"
diff --git a/agent-context/README.md b/agent-context/README.md
index 48e163f18c..7f83f05fb2 100644
--- a/agent-context/README.md
+++ b/agent-context/README.md
@@ -5,12 +5,13 @@ Single source of truth, maintained in the Mintlify documentation repository, for
## Repository structure
- `context/skills/mintlify/` contains canonical, client-neutral context.
-- `targets/*.json` contains the small set of intentional client differences.
+- `context/mcp-servers.json` contains canonical MCP names, URLs, and transport settings.
+- `targets/*.json` contains only client packaging differences such as MCP config file and schema names.
- `scripts/build.mjs` renders self-contained plugin artifacts into `dist/`.
- `scripts/sync-target.mjs` replaces only `skills/mintlify/` in a target repository.
- `../.github/workflows/sync-agent-context.yml` opens generated sync pull requests in all three plugin repositories.
-Plugin manifests, MCP configuration files, assets, READMEs, and Cursor rules remain owned by their target repositories. This project generates only the context shared by the plugins.
+Plugin manifests, assets, READMEs, and Cursor rules remain owned by their target repositories. This project generates the shared skill and each client's MCP configuration file.
## Local development
@@ -36,7 +37,7 @@ node scripts/sync-target.mjs codex ../../codex-plugin
git -C ../../codex-plugin diff
```
-The sync command replaces `skills/mintlify/` and writes `.mintlify-agent-context.json` with the source commit and artifact digest. It does not change any other plugin files.
+The sync command replaces `skills/mintlify/`, writes the client-specific MCP configuration file, and writes `.mintlify-agent-context.json` with the source commit and artifact digest. It does not change any other plugin files.
`npm run status` compares locally checked-out sibling plugin repositories with fresh builds and reports whether each one is current. Pass a workspace root as the final argument if the repositories do not share this repository's parent directory.
@@ -54,6 +55,6 @@ Every qualifying push to `main` validates the source and opens or updates the `a
## Editing rules
-Edit shared knowledge in `context/`, not in generated plugin copies. Put a value in `targets/` only when a plugin genuinely requires different wording or behavior.
+Edit shared knowledge and MCP definitions in `context/`, not in generated plugin copies. Put a value in `targets/` only when a client requires a different packaging format.
-The build rejects retired CLI commands and unresolved template variables. The consistency check verifies that every detailed reference file is identical across targets.
+The build rejects retired CLI commands. Tests verify that the skill, detailed references, and MCP definitions remain semantically identical across targets.
diff --git a/agent-context/context/mcp-servers.json b/agent-context/context/mcp-servers.json
new file mode 100644
index 0000000000..4ade053aa7
--- /dev/null
+++ b/agent-context/context/mcp-servers.json
@@ -0,0 +1,10 @@
+{
+ "Mintlify Search": {
+ "type": "http",
+ "url": "https://mintlify.com/docs/mcp"
+ },
+ "Mintlify Admin": {
+ "type": "http",
+ "url": "https://mcp.mintlify.com"
+ }
+}
diff --git a/agent-context/context/skills/mintlify/SKILL.md b/agent-context/context/skills/mintlify/SKILL.md
index a5ed9de55a..26bbc09068 100644
--- a/agent-context/context/skills/mintlify/SKILL.md
+++ b/agent-context/context/skills/mintlify/SKILL.md
@@ -24,7 +24,7 @@ Read these files **only when your task requires them**. They are in the `referen
Two Mintlify MCP servers are available. Use them alongside the reference files in this skill.
-### {{searchMcpName}}
+### Mintlify Search
Read-only access to Mintlify's published documentation. Use it when the reference files don't cover a specific detail, when you need an up-to-date component signature, or to verify an unfamiliar config option.
@@ -32,9 +32,9 @@ Tools:
- `search_mintlify` — Search the Mintlify knowledge base by query. Good for finding guides, examples, and API references.
- `query_docs_filesystem_mintlify` — Browse the docs file tree (`ls`, `cat`, `grep`, `find`, etc.). Good for reading a specific docs page.
-### {{adminMcpName}}
+### Mintlify Admin
-Write access to a Mintlify project. Requires OAuth login on first use — {{authenticationBehavior}}
+Write access to a Mintlify project. Requires OAuth on first use. Complete authentication in the browser when prompted.
Use this server when the user wants to edit their Mintlify content, restructure navigation, or open a pull request. All changes happen on a branch and must be reviewed before merging.
diff --git a/agent-context/scripts/lib.mjs b/agent-context/scripts/lib.mjs
index 15c0fb41cd..b29f5c88b2 100644
--- a/agent-context/scripts/lib.mjs
+++ b/agent-context/scripts/lib.mjs
@@ -16,6 +16,7 @@ const scriptsDirectory = path.dirname(fileURLToPath(import.meta.url));
export const repositoryRoot = path.resolve(scriptsDirectory, '..');
const contextDirectory = path.join(repositoryRoot, 'context', 'skills', 'mintlify');
+const mcpServersPath = path.join(repositoryRoot, 'context', 'mcp-servers.json');
const targetsDirectory = path.join(repositoryRoot, 'targets');
export async function loadTargets(selectedIds = []) {
@@ -28,6 +29,17 @@ export async function loadTargets(selectedIds = []) {
),
);
+ for (const target of targets) {
+ if (
+ typeof target.id !== 'string' ||
+ typeof target.repository !== 'string' ||
+ !['.mcp.json', 'mcp.json'].includes(target.mcpConfigFile) ||
+ !['mcp_servers', 'mcpServers'].includes(target.mcpConfigKey)
+ ) {
+ throw new Error(`Invalid target configuration: ${JSON.stringify(target)}`);
+ }
+ }
+
const ids = new Set(targets.map((target) => target.id));
for (const id of selectedIds) {
if (!ids.has(id)) {
@@ -40,22 +52,6 @@ export async function loadTargets(selectedIds = []) {
: targets.filter((target) => selectedIds.includes(target.id));
}
-function render(template, values, fileName) {
- const rendered = template.replace(/\{\{([A-Za-z][A-Za-z0-9]*)\}\}/g, (_, key) => {
- if (!(key in values) || typeof values[key] !== 'string') {
- throw new Error(`${fileName} requires a string value for ${key}`);
- }
- return values[key];
- });
-
- const unresolved = rendered.match(/\{\{[^}]+\}\}/g);
- if (unresolved) {
- throw new Error(`${fileName} contains unresolved variables: ${unresolved.join(', ')}`);
- }
-
- return rendered;
-}
-
function markGenerated(skill) {
const frontmatterEnd = skill.indexOf('\n---\n', 4);
if (frontmatterEnd === -1) {
@@ -84,12 +80,18 @@ async function walkFiles(directory, prefix = '') {
return files;
}
-async function artifactDigest(directory) {
+export async function generatedArtifactDigest(root, target) {
+ const skillRoot = path.join(root, 'skills', 'mintlify');
+ const files = (await walkFiles(skillRoot)).map((file) =>
+ path.join('skills', 'mintlify', file),
+ );
+ files.push(target.mcpConfigFile);
+
const hash = createHash('sha256');
- for (const file of await walkFiles(directory)) {
+ for (const file of files.sort()) {
hash.update(file.split(path.sep).join('/'));
hash.update('\0');
- hash.update(await readFile(path.join(directory, file)));
+ hash.update(await readFile(path.join(root, file)));
hash.update('\0');
}
return `sha256:${hash.digest('hex')}`;
@@ -139,20 +141,27 @@ export async function buildTarget(target, outputRoot) {
await mkdir(skillOutput, { recursive: true });
const skillTemplate = await readFile(path.join(contextDirectory, 'SKILL.md'), 'utf8');
- const skill = markGenerated(render(skillTemplate, target, 'SKILL.md'));
+ const skill = markGenerated(skillTemplate);
validateSkill(skill, target);
await writeFile(path.join(skillOutput, 'SKILL.md'), skill);
await cp(path.join(contextDirectory, 'reference'), path.join(skillOutput, 'reference'), {
recursive: true,
});
+ const mcpServers = JSON.parse(await readFile(mcpServersPath, 'utf8'));
+ const mcpConfig = { [target.mcpConfigKey]: mcpServers };
+ await writeFile(
+ path.join(targetRoot, target.mcpConfigFile),
+ `${JSON.stringify(mcpConfig, null, 2)}\n`,
+ );
+
const provenance = {
schemaVersion: 1,
sourceRepository: 'mintlify/docs',
sourcePath: 'agent-context',
sourceCommit: sourceCommit(),
target: target.id,
- artifactDigest: await artifactDigest(path.join(targetRoot, 'skills')),
+ artifactDigest: await generatedArtifactDigest(targetRoot, target),
};
await writeFile(
path.join(targetRoot, '.mintlify-agent-context.json'),
@@ -170,6 +179,7 @@ export async function buildAll({ outputRoot, selectedIds = [] } = {}) {
}
export async function copyTargetToRepository(targetId, destination, outputRoot) {
+ const [target] = await loadTargets([targetId]);
const sourceRoot = path.join(outputRoot, targetId);
const sourceSkill = path.join(sourceRoot, 'skills', 'mintlify');
const destinationSkill = path.join(destination, 'skills', 'mintlify');
@@ -178,10 +188,14 @@ export async function copyTargetToRepository(targetId, destination, outputRoot)
await rm(destinationSkill, { recursive: true, force: true });
await mkdir(path.dirname(destinationSkill), { recursive: true });
await cp(sourceSkill, destinationSkill, { recursive: true });
+ await cp(
+ path.join(sourceRoot, target.mcpConfigFile),
+ path.join(destination, target.mcpConfigFile),
+ );
await cp(
path.join(sourceRoot, '.mintlify-agent-context.json'),
path.join(destination, '.mintlify-agent-context.json'),
);
}
-export { artifactDigest, walkFiles };
+export { walkFiles };
diff --git a/agent-context/scripts/status.mjs b/agent-context/scripts/status.mjs
index 158981c8ba..b5438cca8f 100644
--- a/agent-context/scripts/status.mjs
+++ b/agent-context/scripts/status.mjs
@@ -2,7 +2,12 @@ import { readFile } from 'node:fs/promises';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
-import { artifactDigest, buildAll, loadTargets, repositoryRoot } from './lib.mjs';
+import {
+ buildAll,
+ generatedArtifactDigest,
+ loadTargets,
+ repositoryRoot,
+} from './lib.mjs';
const workspaceRoot = path.resolve(process.argv[2] ?? path.join(repositoryRoot, '..', '..'));
const outputRoot = await mkdtemp(path.join(tmpdir(), 'mintlify-agent-context-status-'));
@@ -21,7 +26,7 @@ try {
const expectedDigest = expectedByTarget.get(target.id);
try {
- const actualDigest = await artifactDigest(path.join(pluginRoot, 'skills'));
+ const actualDigest = await generatedArtifactDigest(pluginRoot, target);
const lock = JSON.parse(
await readFile(path.join(pluginRoot, '.mintlify-agent-context.json'), 'utf8'),
);
diff --git a/agent-context/targets/claude.json b/agent-context/targets/claude.json
index f0508ee7fa..852b09b3ca 100644
--- a/agent-context/targets/claude.json
+++ b/agent-context/targets/claude.json
@@ -1,7 +1,6 @@
{
"id": "claude",
"repository": "mintlify/mintlify-claude-plugin",
- "searchMcpName": "Mintlify (docs MCP)",
- "adminMcpName": "Mintlify MCP (dashboard MCP)",
- "authenticationBehavior": "Claude Code will open a browser window to authenticate."
+ "mcpConfigFile": ".mcp.json",
+ "mcpConfigKey": "mcpServers"
}
diff --git a/agent-context/targets/codex.json b/agent-context/targets/codex.json
index b742530005..422c70a0c3 100644
--- a/agent-context/targets/codex.json
+++ b/agent-context/targets/codex.json
@@ -1,7 +1,6 @@
{
"id": "codex",
"repository": "mintlify/codex-plugin",
- "searchMcpName": "Search MCP",
- "adminMcpName": "Admin MCP",
- "authenticationBehavior": "Codex will open a browser window to authenticate."
+ "mcpConfigFile": ".mcp.json",
+ "mcpConfigKey": "mcp_servers"
}
diff --git a/agent-context/targets/cursor.json b/agent-context/targets/cursor.json
index c787cbe053..ca7fca11ee 100644
--- a/agent-context/targets/cursor.json
+++ b/agent-context/targets/cursor.json
@@ -1,7 +1,6 @@
{
"id": "cursor",
"repository": "mintlify/cursor-plugin",
- "searchMcpName": "Mintlify (docs MCP)",
- "adminMcpName": "Mintlify MCP (dashboard MCP)",
- "authenticationBehavior": "your editor will prompt you to authenticate via browser."
+ "mcpConfigFile": "mcp.json",
+ "mcpConfigKey": "mcpServers"
}
diff --git a/agent-context/test/build.test.mjs b/agent-context/test/build.test.mjs
index cb253ea10b..af2ed4710d 100644
--- a/agent-context/test/build.test.mjs
+++ b/agent-context/test/build.test.mjs
@@ -22,15 +22,32 @@ test('builds all client variants from one canonical skill', async () => {
path.join(outputRoot, 'claude', 'skills', 'mintlify', 'SKILL.md'),
'utf8',
);
+ const codexMcp = JSON.parse(
+ await readFile(path.join(outputRoot, 'codex', '.mcp.json'), 'utf8'),
+ );
+ const cursorMcp = JSON.parse(
+ await readFile(path.join(outputRoot, 'cursor', 'mcp.json'), 'utf8'),
+ );
+ const claudeMcp = JSON.parse(
+ await readFile(path.join(outputRoot, 'claude', '.mcp.json'), 'utf8'),
+ );
- assert.match(codex, /### Search MCP/);
- assert.match(cursor, /### Mintlify \(docs MCP\)/);
- assert.match(claude, /Claude Code will open a browser window/);
+ assert.equal(cursor, codex);
+ assert.equal(claude, codex);
for (const skill of [codex, cursor, claude]) {
assert.match(skill, /Generated from mintlify\/docs\/agent-context/);
+ assert.match(skill, /### Mintlify Search/);
+ assert.match(skill, /### Mintlify Admin/);
+ assert.match(skill, /Complete authentication in the browser when prompted/);
assert.match(skill, /mint automations/);
assert.doesNotMatch(skill, /mint analytics|mint workflow|\{\{/);
}
+ assert.deepEqual(codexMcp.mcp_servers, cursorMcp.mcpServers);
+ assert.deepEqual(claudeMcp.mcpServers, cursorMcp.mcpServers);
+ assert.deepEqual(Object.keys(cursorMcp.mcpServers), [
+ 'Mintlify Search',
+ 'Mintlify Admin',
+ ]);
} finally {
await rm(outputRoot, { recursive: true, force: true });
}
@@ -45,6 +62,7 @@ test('sync replaces only generated context paths', async () => {
await mkdir(path.join(destination, 'skills', 'mintlify'), { recursive: true });
await writeFile(path.join(destination, 'README.md'), 'target-owned\n');
await writeFile(path.join(destination, 'skills', 'mintlify', 'stale.md'), 'remove me\n');
+ await writeFile(path.join(destination, '.mcp.json'), '{"stale":true}\n');
await buildAll({ outputRoot, selectedIds: ['codex'] });
await copyTargetToRepository('codex', destination, outputRoot);
@@ -53,8 +71,13 @@ test('sync replaces only generated context paths', async () => {
await assert.rejects(readFile(path.join(destination, 'skills', 'mintlify', 'stale.md')));
assert.match(
await readFile(path.join(destination, 'skills', 'mintlify', 'SKILL.md'), 'utf8'),
- /### Search MCP/,
+ /### Mintlify Search/,
);
+ const mcpConfig = JSON.parse(await readFile(path.join(destination, '.mcp.json'), 'utf8'));
+ assert.deepEqual(Object.keys(mcpConfig.mcp_servers), [
+ 'Mintlify Search',
+ 'Mintlify Admin',
+ ]);
} finally {
await rm(root, { recursive: true, force: true });
}
From 929006021b09155c1424faabb8a21fd103be562e Mon Sep 17 00:00:00 2001
From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com>
Date: Fri, 24 Jul 2026 16:28:17 -0700
Subject: [PATCH 3/6] remove artifact digest
---
agent-context/README.md | 2 +-
agent-context/scripts/build.mjs | 2 +-
agent-context/scripts/lib.mjs | 50 +------------------------
agent-context/scripts/status.mjs | 63 +++++++++++---------------------
4 files changed, 26 insertions(+), 91 deletions(-)
diff --git a/agent-context/README.md b/agent-context/README.md
index 7f83f05fb2..62c3453630 100644
--- a/agent-context/README.md
+++ b/agent-context/README.md
@@ -37,7 +37,7 @@ node scripts/sync-target.mjs codex ../../codex-plugin
git -C ../../codex-plugin diff
```
-The sync command replaces `skills/mintlify/`, writes the client-specific MCP configuration file, and writes `.mintlify-agent-context.json` with the source commit and artifact digest. It does not change any other plugin files.
+The sync command replaces `skills/mintlify/`, writes the client-specific MCP configuration file, and writes `.mintlify-agent-context.json` with the source commit. It does not change any other plugin files.
`npm run status` compares locally checked-out sibling plugin repositories with fresh builds and reports whether each one is current. Pass a workspace root as the final argument if the repositories do not share this repository's parent directory.
diff --git a/agent-context/scripts/build.mjs b/agent-context/scripts/build.mjs
index 6f1656c273..a0fb9ce03b 100644
--- a/agent-context/scripts/build.mjs
+++ b/agent-context/scripts/build.mjs
@@ -8,5 +8,5 @@ const results = await buildAll({
});
for (const { provenance } of results) {
- console.log(`${provenance.target}: ${provenance.artifactDigest}`);
+ console.log(`${provenance.target}: ${provenance.sourceCommit}`);
}
diff --git a/agent-context/scripts/lib.mjs b/agent-context/scripts/lib.mjs
index b29f5c88b2..aed20925e6 100644
--- a/agent-context/scripts/lib.mjs
+++ b/agent-context/scripts/lib.mjs
@@ -1,14 +1,6 @@
-import { createHash } from 'node:crypto';
import { execFileSync } from 'node:child_process';
-import {
- cp,
- mkdir,
- readFile,
- readdir,
- rm,
- stat,
writeFile,
-} from 'node:fs/promises';
+import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -62,42 +54,7 @@ function markGenerated(skill) {
return `${skill.slice(0, insertionPoint)}\n\n${skill.slice(insertionPoint)}`;
}
-async function walkFiles(directory, prefix = '') {
- const entries = await readdir(directory);
- const files = [];
-
- for (const entry of entries.sort()) {
- const absolutePath = path.join(directory, entry);
- const relativePath = path.join(prefix, entry);
- const entryStat = await stat(absolutePath);
- if (entryStat.isDirectory()) {
- files.push(...(await walkFiles(absolutePath, relativePath)));
- } else if (entryStat.isFile()) {
- files.push(relativePath);
- }
- }
-
- return files;
-}
-
-export async function generatedArtifactDigest(root, target) {
- const skillRoot = path.join(root, 'skills', 'mintlify');
- const files = (await walkFiles(skillRoot)).map((file) =>
- path.join('skills', 'mintlify', file),
- );
- files.push(target.mcpConfigFile);
-
- const hash = createHash('sha256');
- for (const file of files.sort()) {
- hash.update(file.split(path.sep).join('/'));
- hash.update('\0');
- hash.update(await readFile(path.join(root, file)));
- hash.update('\0');
- }
- return `sha256:${hash.digest('hex')}`;
-}
-
-function sourceCommit() {
+export function sourceCommit() {
if (process.env.GITHUB_SHA) {
return process.env.GITHUB_SHA;
}
@@ -161,7 +118,6 @@ export async function buildTarget(target, outputRoot) {
sourcePath: 'agent-context',
sourceCommit: sourceCommit(),
target: target.id,
- artifactDigest: await generatedArtifactDigest(targetRoot, target),
};
await writeFile(
path.join(targetRoot, '.mintlify-agent-context.json'),
@@ -197,5 +153,3 @@ export async function copyTargetToRepository(targetId, destination, outputRoot)
path.join(destination, '.mintlify-agent-context.json'),
);
}
-
-export { walkFiles };
diff --git a/agent-context/scripts/status.mjs b/agent-context/scripts/status.mjs
index b5438cca8f..1f71a8bda6 100644
--- a/agent-context/scripts/status.mjs
+++ b/agent-context/scripts/status.mjs
@@ -1,53 +1,34 @@
import { readFile } from 'node:fs/promises';
-import { mkdtemp, rm } from 'node:fs/promises';
-import { tmpdir } from 'node:os';
import path from 'node:path';
-import {
- buildAll,
- generatedArtifactDigest,
- loadTargets,
- repositoryRoot,
-} from './lib.mjs';
+import { loadTargets, repositoryRoot, sourceCommit } from './lib.mjs';
const workspaceRoot = path.resolve(process.argv[2] ?? path.join(repositoryRoot, '..', '..'));
-const outputRoot = await mkdtemp(path.join(tmpdir(), 'mintlify-agent-context-status-'));
const targets = await loadTargets();
+const currentSourceCommit = sourceCommit();
const rows = [];
-try {
- const builds = await buildAll({ outputRoot });
- const expectedByTarget = new Map(
- builds.map(({ provenance }) => [provenance.target, provenance.artifactDigest]),
- );
+for (const target of targets) {
+ const repositoryName = target.repository.split('/').at(-1);
+ const pluginRoot = path.join(workspaceRoot, repositoryName);
- for (const target of targets) {
- const repositoryName = target.repository.split('/').at(-1);
- const pluginRoot = path.join(workspaceRoot, repositoryName);
- const expectedDigest = expectedByTarget.get(target.id);
-
- try {
- const actualDigest = await generatedArtifactDigest(pluginRoot, target);
- const lock = JSON.parse(
- await readFile(path.join(pluginRoot, '.mintlify-agent-context.json'), 'utf8'),
- );
- const current = actualDigest === expectedDigest && lock.artifactDigest === expectedDigest;
- rows.push({
- target: target.id,
- repository: target.repository,
- state: current ? 'current' : 'behind',
- source: lock.sourceCommit,
- });
- } catch {
- rows.push({
- target: target.id,
- repository: target.repository,
- state: 'unavailable',
- source: '-',
- });
- }
+ try {
+ const lock = JSON.parse(
+ await readFile(path.join(pluginRoot, '.mintlify-agent-context.json'), 'utf8'),
+ );
+ rows.push({
+ target: target.id,
+ repository: target.repository,
+ state: lock.sourceCommit === currentSourceCommit ? 'current' : 'behind',
+ source: lock.sourceCommit,
+ });
+ } catch {
+ rows.push({
+ target: target.id,
+ repository: target.repository,
+ state: 'unavailable',
+ source: '-',
+ });
}
-} finally {
- await rm(outputRoot, { recursive: true, force: true });
}
console.table(rows);
From e0566c1eaba9691028f86b512cfe896ed0975c4c Mon Sep 17 00:00:00 2001
From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com>
Date: Fri, 24 Jul 2026 16:29:38 -0700
Subject: [PATCH 4/6] fix stray writeFile import line left over from digest
removal
---
agent-context/scripts/lib.mjs | 1 -
1 file changed, 1 deletion(-)
diff --git a/agent-context/scripts/lib.mjs b/agent-context/scripts/lib.mjs
index aed20925e6..437c9e5ff1 100644
--- a/agent-context/scripts/lib.mjs
+++ b/agent-context/scripts/lib.mjs
@@ -1,5 +1,4 @@
import { execFileSync } from 'node:child_process';
- writeFile,
import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
From 96fe68fdf1f827f94a7f15135d8628598dd0fbbb Mon Sep 17 00:00:00 2001
From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com>
Date: Fri, 24 Jul 2026 16:51:21 -0700
Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=92=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../context/skills/mintlify/SKILL.md | 35 ++++++++------
.../context/skills/mintlify/reference/cli.md | 2 +
.../skills/mintlify/reference/components.md | 4 ++
.../mintlify/reference/configuration.md | 46 +++++++++++--------
agent-context/scripts/lib.mjs | 2 +-
5 files changed, 55 insertions(+), 34 deletions(-)
diff --git a/agent-context/context/skills/mintlify/SKILL.md b/agent-context/context/skills/mintlify/SKILL.md
index 26bbc09068..10c0265c50 100644
--- a/agent-context/context/skills/mintlify/SKILL.md
+++ b/agent-context/context/skills/mintlify/SKILL.md
@@ -31,6 +31,7 @@ Read-only access to Mintlify's published documentation. Use it when the referenc
Tools:
- `search_mintlify` — Search the Mintlify knowledge base by query. Good for finding guides, examples, and API references.
- `query_docs_filesystem_mintlify` — Browse the docs file tree (`ls`, `cat`, `grep`, `find`, etc.). Good for reading a specific docs page.
+- `submit_feedback` — Report a docs page that is incorrect, outdated, confusing, or incomplete.
### Mintlify Admin
@@ -43,11 +44,13 @@ Workflow: call `checkout` first (always), then use `read`/`search`/`edit_page`/`
Key tools:
- **`checkout`** — Start a session on a branch (required first call). Returns an `editorUrl` to preview changes live.
- **`list_branches`** — List existing branches; call before `checkout` to attach to one.
+- **`list_deployments`** — Discover which deployment(s) this connection can access.
- **`read`** / **`search`** — Fetch a page's MDX or search across pages.
- **`edit_page`** / **`write_page`** — Apply targeted edits or overwrite a page.
- **`list_nodes`** / **`create_node`** / **`update_node`** / **`move_node`** / **`delete_node`** — Manage the navigation tree.
- **`update_config`** — Modify `docs.json` (theme, nav roots, integrations, SEO).
- **`diff`** — See all changes relative to `main`.
+- **`get_session_state`** — Check the current session's status.
- **`save`** — Open a PR (`mode: "pr"`) or push to the branch (`mode: "commit"`).
- **`discard_session`** — Drop all in-session changes.
@@ -102,7 +105,7 @@ Store images in an `images/` directory. Reference with root-relative paths. All
## Page frontmatter
-Every page requires `title` in its frontmatter. Include `description` and `keywords` for SEO.
+Include `title`, `description`, and `keywords` in frontmatter. `title` is technically optional (Mintlify generates one from the file path if omitted), but set it explicitly for clarity and SEO.
```yaml
---
@@ -114,22 +117,24 @@ keywords: ["relevant", "search", "terms"]
### Common frontmatter fields
-| Field | Type | Required | Description |
-|-------|------|----------|-------------|
-| `title` | string | Yes | Page title in navigation and browser tabs. |
-| `description` | string | No | Brief description for SEO. Displays under the title. |
-| `sidebarTitle` | string | No | Short title for sidebar navigation. |
-| `icon` | string | No | Lucide, Font Awesome, or Tabler icon name. Also accepts a URL or file path. |
-| `tag` | string | No | Label next to page title in sidebar (e.g., "NEW"). |
-| `hidden` | boolean | No | Remove from sidebar. Page still accessible by URL. |
-| `mode` | string | No | Page layout: `default`, `wide`, `custom`, `frame`, `center`. |
-| `keywords` | array | No | Search terms for internal search and SEO. |
-| `api` | string | No | API endpoint for interactive playground (e.g., `"POST /users"`). |
-| `openapi` | string | No | OpenAPI endpoint reference (e.g., `"GET /endpoint"`). |
+| Field | Type | Description |
+|-------|------|-------------|
+| `title` | string | Page title in navigation and browser tabs. Auto-generated from the path if omitted. |
+| `description` | string | Brief description for SEO. Displays under the title. |
+| `sidebarTitle` | string | Short title for sidebar navigation. |
+| `icon` | string | Lucide, Font Awesome, or Tabler icon name. Also accepts a URL or file path. |
+| `tag` | string | Label next to page title in sidebar (e.g., "NEW"). |
+| `hidden` | boolean | Remove from sidebar. Page still accessible by URL. |
+| `mode` | string | Page layout: `default`, `wide`, `custom`, `frame`, `center`. |
+| `keywords` | array | Search terms for internal search and SEO. |
+| `api` | string | API endpoint for interactive playground (e.g., `"POST /users"`). |
+| `openapi` | string | OpenAPI endpoint reference (e.g., `"GET /endpoint"`). |
+
+For the complete list including `searchable`, `boost`, `deprecated`, `related`, `groups`, and more, read `reference/configuration.md`.
## Quick component reference
-Below are the most commonly used components. For full props and all 24 components, read `reference/components.md`.
+Below are the most commonly used components. For full props and all 25 components, read `reference/components.md`.
### Callouts
@@ -212,7 +217,7 @@ Use `` to arrange cards (or other content) in a grid. `cols` accepts 1-
## CLI commands
-Install with `npm i -g mint`. Key commands: `mint dev` (local preview), `mint validate`, `mint broken-links`, `mint a11y`, `mint score`, `mint automations`, `mint new`. Read `reference/cli.md` for full flags and subcommands.
+Install with `npm i -g mint`. Key commands: `mint dev` (local preview), `mint validate`, `mint broken-links`, `mint a11y`, `mint score`, `mint automations`, `mint deslop`, `mint new`, `mint signup`. Read `reference/cli.md` for full flags and subcommands.
## Writing standards
diff --git a/agent-context/context/skills/mintlify/reference/cli.md b/agent-context/context/skills/mintlify/reference/cli.md
index 68cc9635c3..8c90f3534c 100644
--- a/agent-context/context/skills/mintlify/reference/cli.md
+++ b/agent-context/context/skills/mintlify/reference/cli.md
@@ -25,12 +25,14 @@ Available on all commands.
- `mint broken-links` — Check for broken internal links. `--check-anchors` validates `#` anchors. `--check-external` checks external URLs. `--check-redirects` checks that redirect destinations in `docs.json` resolve. `--check-snippets` checks links inside `` components.
- `mint a11y` — Accessibility checks (alt text, color contrast). `--skip-contrast` or `--skip-alt-text` to narrow scope.
- `mint score [url]` — Score a docs site's AI/agent readiness. Checks llms.txt, MCP discoverability, robots.txt, sitemap, structured data, response latency, and more. Requires `mint login`. Defaults to your configured subdomain. `--format` accepts `table` (default), `plain`, or `json`.
+- `mint deslop [files...]` — Check pages for AI-sounding prose and get rewrite suggestions. Requires `mint login`. Without `files`, checks the `.md`/`.mdx` pages changed in your working tree (Git diff plus untracked files). Flags: `--format` (`table`/`plain`/`json`), `--subdomain`, `--threshold` (0-1, default 0.5), `--fix-whitespace` (normalizes trailing spaces, blank-line runs, and invisible Unicode outside code blocks/frontmatter).
## Authentication
- `mint login` — Authenticate your Mintlify account.
- `mint logout` — Log out of your account.
- `mint status` — Show current authentication status (CLI version, email, org, subdomain).
+- `mint signup [flags]` — Create a new Mintlify account from the terminal. Flags: `--firstName`, `--lastName`, `--company`, `--email`; omit any to enter it interactively. Waits until you click the emailed verification link before it logs you in — run as a background process in scripts.
## Configuration
diff --git a/agent-context/context/skills/mintlify/reference/components.md b/agent-context/context/skills/mintlify/reference/components.md
index afbc293de9..7633f300e4 100644
--- a/agent-context/context/skills/mintlify/reference/components.md
+++ b/agent-context/context/skills/mintlify/reference/components.md
@@ -23,6 +23,10 @@ Custom callout with icon and color:
```
+## Banner
+
+Not an MDX component. A site-wide announcement banner configured via the `banner` field in `docs.json`. See `reference/configuration.md`.
+
## Accordions
Expandable/collapsible content sections.
diff --git a/agent-context/context/skills/mintlify/reference/configuration.md b/agent-context/context/skills/mintlify/reference/configuration.md
index 82706316e1..b062ca1be3 100644
--- a/agent-context/context/skills/mintlify/reference/configuration.md
+++ b/agent-context/context/skills/mintlify/reference/configuration.md
@@ -50,24 +50,34 @@ Rules:
## Complete frontmatter fields
-The SKILL.md file lists common frontmatter fields. Here is the complete set:
-
-| Field | Type | Required | Description |
-|-------|------|----------|-------------|
-| `title` | string | Yes | Page title in navigation and browser tabs. |
-| `description` | string | No | Brief description for SEO. Displays under the title. |
-| `sidebarTitle` | string | No | Short title for sidebar navigation. |
-| `icon` | string | No | Lucide, Font Awesome, or Tabler icon name. Also accepts a URL or file path. |
-| `iconType` | string | No | Font Awesome icon style: `regular`, `solid`, `light`, `thin`, `sharp-solid`, `duotone`, `brands`. |
-| `tag` | string | No | Label next to page title in sidebar (e.g., "NEW"). |
-| `hidden` | boolean | No | Remove from sidebar. Page still accessible by URL. |
-| `noindex` | boolean | No | Prevent search engine indexing. |
-| `mode` | string | No | Page layout: `default`, `wide`, `custom`, `frame`, `center`. |
-| `keywords` | array | No | Search terms for internal search and SEO. |
-| `api` | string | No | API endpoint for interactive playground (e.g., `"POST /users"`). |
-| `openapi` | string | No | OpenAPI endpoint reference (e.g., `"GET /endpoint"`). |
-| `url` | string | No | External URL. Makes the nav entry link externally. |
-| `timestamp` | boolean | No | Override global timestamp setting for this page. |
+The SKILL.md file lists common frontmatter fields. Here is the complete set. All fields are optional; if `title` is omitted, Mintlify generates one from the file path (dashes and underscores become spaces, first letter capitalized).
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `title` | string | Page title in navigation and browser tabs. Auto-generated from the path if omitted. |
+| `description` | string | Brief description for SEO. Displays under the title. |
+| `sidebarTitle` | string | Short title for sidebar navigation. |
+| `icon` | string | Lucide, Font Awesome, or Tabler icon name. Also accepts a URL or file path. |
+| `iconType` | string | Font Awesome icon style: `regular`, `solid`, `light`, `thin`, `sharp-solid`, `duotone`, `brands`. |
+| `tag` | string | Label next to page title in sidebar (e.g., "NEW"). |
+| `hidden` | boolean | Remove from sidebar. Page still accessible by URL. Do not set to `false`; remove the field entirely to make a page visible again. |
+| `noindex` | boolean | Exclude from site search, sitemaps, search engine indexing, and AI assistant context. Still visible in navigation. |
+| `searchable` | boolean | Defaults to `true`. Set `false` to exclude the page from site search and AI assistant context while keeping it indexable externally and visible in navigation. |
+| `boost` | number | Multiply the page's in-product search ranking. Values above 1 prioritize, between 0 and 1 de-prioritize. No effect when `searchable: false`. |
+| `deprecated` | boolean | Show a "deprecated" label next to the page title. |
+| `hideFooterPagination` | boolean | Hide the previous/next navigation links at the bottom of the page. |
+| `related` | array or boolean | Related pages shown in the **Related topics** section, or `false` to hide it. Requires the Related pages add-on. |
+| `hideApiMarker` | boolean | Hide the HTTP method badge next to the page title in the sidebar. |
+| `contextual` | object | Override the site-wide contextual menu (`options`, `display`) for this page. `options: []` disables it. |
+| `groups` | string[] | Restrict the page to users in specific groups. Requires authentication configured first. |
+| `mode` | string | Page layout: `default`, `wide`, `custom`, `frame`, `center`. |
+| `keywords` | array | Search terms for internal search and SEO. |
+| `api` | string | API endpoint for interactive playground (e.g., `"POST /users"`). |
+| `openapi` | string | OpenAPI endpoint reference (e.g., `"GET /endpoint"`). |
+| `url` | string | External URL. Makes the nav entry link externally. |
+| `timestamp` | boolean | Override global timestamp setting for this page. |
+
+Any other key is accepted as custom frontmatter (e.g. `product: "API"`).
## Page modes
diff --git a/agent-context/scripts/lib.mjs b/agent-context/scripts/lib.mjs
index 437c9e5ff1..8bcd2a8e6f 100644
--- a/agent-context/scripts/lib.mjs
+++ b/agent-context/scripts/lib.mjs
@@ -85,7 +85,7 @@ function validateSkill(skill, target) {
if (!/^name: mintlify$/m.test(skill) || !/^description: .+$/m.test(skill)) {
throw new Error(`${target.id}: SKILL.md requires name and description fields`);
}
- if (/mint analytics|mint workflow/.test(skill)) {
+ if (/mint analytics/.test(skill)) {
throw new Error(`${target.id}: SKILL.md contains retired CLI commands`);
}
}
From 0a938132fecf5275a6ed6703cc7ae3aab33a0a5d Mon Sep 17 00:00:00 2001
From: Ethan Palm <56270045+ethanpalm@users.noreply.github.com>
Date: Fri, 24 Jul 2026 16:52:25 -0700
Subject: [PATCH 6/6] bug bot
---
.github/workflows/sync-agent-context.yml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/sync-agent-context.yml b/.github/workflows/sync-agent-context.yml
index df168213af..790e258002 100644
--- a/.github/workflows/sync-agent-context.yml
+++ b/.github/workflows/sync-agent-context.yml
@@ -119,10 +119,14 @@ jobs:
BRANCH: automation/sync-agent-context
SOURCE_SHA: ${{ github.sha }}
run: |
- if [[ -z "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')" ]]; then
+ BODY="Generated from mintlify/docs at ${SOURCE_SHA} using agent-context. Do not edit generated skill files in this repository."
+ EXISTING_PR="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')"
+ if [[ -z "$EXISTING_PR" ]]; then
gh pr create \
--head "$BRANCH" \
--base main \
--title "Sync Mintlify agent context" \
- --body "Generated from mintlify/docs at ${SOURCE_SHA} using agent-context. Do not edit generated skill files in this repository."
+ --body "$BODY"
+ else
+ gh pr edit "$EXISTING_PR" --body "$BODY"
fi