zudoku 0.72.0 → 0.73.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/cli.js CHANGED
@@ -3276,7 +3276,10 @@ var init_ZudokuConfig = __esm({
3276
3276
  keywords: z7.array(z7.string()),
3277
3277
  authors: z7.array(z7.string()),
3278
3278
  creator: z7.string(),
3279
- publisher: z7.string()
3279
+ publisher: z7.string(),
3280
+ robots: z7.string().describe(
3281
+ 'Sets the contents of the `meta[name="robots"]` tag. For example: `noindex, nofollow`.'
3282
+ )
3280
3283
  }).partial();
3281
3284
  FontConfigSchema = z7.union([
3282
3285
  z7.enum(GOOGLE_FONTS),
@@ -3810,7 +3813,7 @@ import {
3810
3813
  // package.json
3811
3814
  var package_default = {
3812
3815
  name: "zudoku",
3813
- version: "0.71.10",
3816
+ version: "0.73.0",
3814
3817
  type: "module",
3815
3818
  sideEffects: [
3816
3819
  "**/*.css",
@@ -6652,6 +6652,7 @@ export declare const ZudokuConfig: z.ZodObject<{
6652
6652
  authors: z.ZodOptional<z.ZodArray<z.ZodString>>;
6653
6653
  creator: z.ZodOptional<z.ZodString>;
6654
6654
  publisher: z.ZodOptional<z.ZodString>;
6655
+ robots: z.ZodOptional<z.ZodString>;
6655
6656
  }, z.core.$strip>>;
6656
6657
  authentication: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
6657
6658
  type: z.ZodLiteral<"clerk">;
@@ -45,6 +45,7 @@ type Metadata = Partial<{
45
45
  authors: string[];
46
46
  creator: string;
47
47
  publisher: string;
48
+ robots: string;
48
49
  }>;
49
50
  type Site = Partial<{
50
51
  dir?: "ltr" | "rtl";
@@ -1,5 +1,6 @@
1
- export declare const MCPEndpoint: ({ serverUrl, summary, data, }: {
1
+ export declare const MCPEndpoint: ({ serverUrl, operationPath, summary, data, }: {
2
2
  serverUrl?: string;
3
+ operationPath?: string;
3
4
  data?: boolean | Record<string, unknown>;
4
5
  summary?: string;
5
6
  }) => import("react/jsx-runtime").JSX.Element;
@@ -236,6 +236,10 @@ export interface FlatZudokuConfig {
236
236
  authors?: string[]
237
237
  creator?: string
238
238
  publisher?: string
239
+ /**
240
+ * Sets the contents of the `meta[name="robots"]` tag. For example: `noindex, nofollow`.
241
+ */
242
+ robots?: string
239
243
  }
240
244
  authentication?: ({
241
245
  type: "clerk"
@@ -11,7 +11,8 @@ import { Book, Code, FileText } from "zudoku/icons";
11
11
 
12
12
  Zudoku uses a single `navigation` array to control both the top navigation tabs and the sidebar.
13
13
  Items at the root of this array appear as tabs, and nested items build the sidebar tree. Navigation
14
- entries can be links, document references, categories or custom pages.
14
+ entries can be links, document references, categories, custom pages, separators, sections, or
15
+ filters.
15
16
 
16
17
  ## Basic configuration
17
18
 
@@ -45,7 +46,6 @@ one of several types. At the simplest level you may only have links and categori
45
46
  "to": "/api",
46
47
  "label": "API Reference",
47
48
  "icon": "code",
48
- "description": "Complete API documentation",
49
49
  "badge": {
50
50
  "label": "v2.0",
51
51
  "color": "blue"
@@ -58,13 +58,18 @@ one of several types. At the simplest level you may only have links and categori
58
58
 
59
59
  ## Navigation Items
60
60
 
61
- Navigation items can be of these types: `category`, `doc`, `link`, or `custom-page`.
61
+ Navigation items can be of these types: `category`, `doc`, `link`, `custom-page`, `separator`,
62
+ `section`, or `filter`.
62
63
 
63
64
  - `link`: A direct link to a page or external URL.
64
65
  - `category`: A group of links that can be expanded or collapsed.
65
- - `doc`: A reference to a document by it's file path: `file`.
66
- - `custom-pages`: A custom page that is made of a React component, see
66
+ - `doc`: A reference to a document by its file path: `file`.
67
+ - `custom-page`: A custom page that is made of a React component, see
67
68
  [Custom Pages](../guides/custom-pages.md)
69
+ - `separator`: A horizontal line to visually divide sidebar items.
70
+ - `section`: A non-interactive heading label to group sidebar items.
71
+ - `filter`: An inline search input that filters navigation items. Multiple filter inputs share the
72
+ same search query.
68
73
 
69
74
  ### `type: link`
70
75
 
@@ -87,10 +92,10 @@ type NavigationLink = {
87
92
  to: string;
88
93
  label: string;
89
94
  icon?: string; // Lucide icon name
90
- description?: string;
91
95
  badge?: {
92
96
  label: string;
93
97
  color: "green" | "blue" | "yellow" | "red" | "purple" | "indigo" | "gray" | "outline";
98
+ invert?: boolean;
94
99
  };
95
100
  display?:
96
101
  | "auth"
@@ -106,7 +111,8 @@ type NavigationLink = {
106
111
  ### `type: category`
107
112
 
108
113
  The `category` type groups related items under a collapsible section. The `label` is the displayed
109
- text, and the `items` array contains `id`s of documents, links, or other categories.
114
+ text, and the `items` array can contain any navigation item type (documents, links, categories,
115
+ custom pages, separators, sections, and filters).
110
116
 
111
117
  ```json
112
118
  {
@@ -131,11 +137,11 @@ text, and the `items` array contains `id`s of documents, links, or other categor
131
137
  type NavigationCategory = {
132
138
  type: "category";
133
139
  icon?: string; // Lucide icon name
134
- items: Array<NavigationDoc | NavigationLink | NavigationCategory | NavigationCustomPage>;
140
+ items: Array<NavigationItem>; // any navigation item type, including string shorthands for docs
135
141
  label: string;
136
142
  collapsible?: boolean;
137
143
  collapsed?: boolean;
138
- link?: string | { type: "doc"; file: string; label?: string };
144
+ link?: string | { type: "doc"; file: string; label?: string; path?: string };
139
145
  display?:
140
146
  | "auth"
141
147
  | "anon"
@@ -147,6 +153,50 @@ type NavigationCategory = {
147
153
 
148
154
  </details>
149
155
 
156
+ #### Category links
157
+
158
+ A category can have a `link` property that makes the category label itself clickable, navigating to
159
+ a document. This is useful when you want a category that acts as both a group and a landing page.
160
+
161
+ The `link` can be a simple string pointing to a file path, or an object for more control:
162
+
163
+ ```tsx title="String shorthand"
164
+ {
165
+ type: "category",
166
+ label: "Configuration",
167
+ link: "docs/configuration/overview",
168
+ items: [
169
+ "docs/configuration/navigation",
170
+ "docs/configuration/site",
171
+ ],
172
+ }
173
+ ```
174
+
175
+ ```tsx title="Object form with custom path"
176
+ {
177
+ type: "category",
178
+ label: "Documentation",
179
+ link: {
180
+ type: "doc",
181
+ file: "home.md",
182
+ path: "/",
183
+ },
184
+ items: [
185
+ "guides/getting-started",
186
+ "guides/advanced",
187
+ ],
188
+ }
189
+ ```
190
+
191
+ The object form supports these properties:
192
+
193
+ | Property | Type | Description |
194
+ | -------- | -------- | -------------------------------------------------------- |
195
+ | `type` | `"doc"` | Must be `"doc"` |
196
+ | `file` | `string` | Path to the markdown file |
197
+ | `label` | `string` | Override the label (defaults to the document title) |
198
+ | `path` | `string` | Custom URL path (overrides the default file-based route) |
199
+
150
200
  ### `type: doc`
151
201
 
152
202
  Doc is used to reference markdown files. The `label` is the text that will be displayed, and the
@@ -173,6 +223,7 @@ type NavigationDoc = {
173
223
  badge?: {
174
224
  label: string;
175
225
  color: "green" | "blue" | "yellow" | "red" | "purple" | "indigo" | "gray" | "outline";
226
+ invert?: boolean;
176
227
  };
177
228
  display?:
178
229
  | "auth"
@@ -223,6 +274,37 @@ Learn more in the [Markdown documentation](/docs/markdown/overview)
223
274
  The `path` property allows you to customize the URL path for a document. By default, Zudoku uses the
224
275
  file path to generate the URL, but you can override this behavior by specifying a custom path.
225
276
 
277
+ ```tsx title="Serving a doc at the root URL"
278
+ {
279
+ type: "doc",
280
+ file: "home.md",
281
+ path: "/",
282
+ label: "Home",
283
+ }
284
+ ```
285
+
286
+ ```tsx title="Custom slug"
287
+ {
288
+ type: "doc",
289
+ file: "guides/getting-started.md",
290
+ path: "/start-here",
291
+ label: "Start Here",
292
+ }
293
+ ```
294
+
295
+ When a file has a custom path, it will only be accessible at that custom path, not at its original
296
+ file-based path. See [Documentation - Custom Paths](/docs/configuration/docs#custom-paths) for more
297
+ details.
298
+
299
+ :::note
300
+
301
+ Avoid naming files `index.md` or `index.mdx` and relying on their default path. Some hosting
302
+ providers (e.g. Vercel) automatically strip `/index` from URLs with a redirect, which can cause
303
+ routing issues. Instead, give files descriptive names and use the `path` property to serve them at
304
+ the desired URL.
305
+
306
+ :::
307
+
226
308
  ### `type: custom-page`
227
309
 
228
310
  Custom pages allow you to create standalone pages that are not tied to a Markdown document. This is
@@ -247,6 +329,7 @@ type NavigationCustomPage = {
247
329
  label?: string;
248
330
  element: any;
249
331
  icon?: string; // Lucide icon name
332
+ layout?: "default" | "none";
250
333
  badge?: {
251
334
  label: string;
252
335
  color: "green" | "blue" | "yellow" | "red" | "purple" | "indigo" | "gray" | "outline";
@@ -263,6 +346,120 @@ type NavigationCustomPage = {
263
346
 
264
347
  </details>
265
348
 
349
+ Set `layout: "none"` to render the page without the default Zudoku layout (header, sidebar, footer).
350
+ This is useful for fully custom landing pages.
351
+
352
+ ### `type: separator`
353
+
354
+ A visual divider line in the sidebar. Use separators to create visual breaks between groups of
355
+ items.
356
+
357
+ ```tsx
358
+ {
359
+ type: "category",
360
+ label: "Documentation",
361
+ items: [
362
+ "guides/getting-started",
363
+ "guides/installation",
364
+ { type: "separator" },
365
+ "guides/advanced",
366
+ "guides/troubleshooting",
367
+ ],
368
+ }
369
+ ```
370
+
371
+ <details>
372
+ <summary>**TypeScript type declaration**</summary>
373
+
374
+ ```ts
375
+ type NavigationSeparator = {
376
+ type: "separator";
377
+ display?:
378
+ | "auth"
379
+ | "anon"
380
+ | "always"
381
+ | "hide"
382
+ | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean);
383
+ };
384
+ ```
385
+
386
+ </details>
387
+
388
+ ### `type: section`
389
+
390
+ A non-interactive heading label in the sidebar. Sections are rendered as small uppercase text and
391
+ are useful for labeling groups of items without adding a collapsible wrapper.
392
+
393
+ ```tsx
394
+ {
395
+ type: "category",
396
+ label: "Documentation",
397
+ items: [
398
+ { type: "section", label: "Getting Started" },
399
+ "guides/quickstart",
400
+ "guides/installation",
401
+ { type: "section", label: "Advanced" },
402
+ "guides/plugins",
403
+ "guides/deployment",
404
+ ],
405
+ }
406
+ ```
407
+
408
+ <details>
409
+ <summary>**TypeScript type declaration**</summary>
410
+
411
+ ```ts
412
+ type NavigationSection = {
413
+ type: "section";
414
+ label: string;
415
+ display?:
416
+ | "auth"
417
+ | "anon"
418
+ | "always"
419
+ | "hide"
420
+ | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean);
421
+ };
422
+ ```
423
+
424
+ </details>
425
+
426
+ ### `type: filter`
427
+
428
+ An inline search input that updates the shared navigation filter. When the user types, the query is
429
+ applied across the sidebar so that only matching items remain visible.
430
+
431
+ ```tsx
432
+ {
433
+ type: "category",
434
+ label: "Documentation",
435
+ items: [
436
+ { type: "filter", placeholder: "Filter documentation" },
437
+ "guides/getting-started",
438
+ "guides/installation",
439
+ "guides/authentication",
440
+ "guides/deployment",
441
+ ],
442
+ }
443
+ ```
444
+
445
+ <details>
446
+ <summary>**TypeScript type declaration**</summary>
447
+
448
+ ```ts
449
+ type NavigationFilter = {
450
+ type: "filter";
451
+ placeholder?: string;
452
+ display?:
453
+ | "auth"
454
+ | "anon"
455
+ | "always"
456
+ | "hide"
457
+ | ((params: { context: ZudokuContext; auth: UseAuthReturn }) => boolean);
458
+ };
459
+ ```
460
+
461
+ </details>
462
+
266
463
  ## Display Control
267
464
 
268
465
  All navigation items support a `display` property that controls when the item should be visible:
@@ -363,6 +560,94 @@ sidebar_label: Short Title
363
560
  In this example, the document's title remains "My Long Title," but the sidebar displays "Short
364
561
  Title."
365
562
 
563
+ For the complete list of supported frontmatter properties, see
564
+ [Frontmatter](/docs/markdown/frontmatter).
565
+
566
+ ## Common Patterns
567
+
568
+ ### Serving a document at the root URL
569
+
570
+ To make a markdown document accessible at `/`, use the `path` property to override the default
571
+ file-based route:
572
+
573
+ ```tsx title="Standalone root doc"
574
+ navigation: [
575
+ {
576
+ type: "doc",
577
+ file: "home.md",
578
+ path: "/",
579
+ label: "Home",
580
+ },
581
+ ],
582
+ ```
583
+
584
+ ```tsx title="Category with root landing page"
585
+ navigation: [
586
+ {
587
+ type: "category",
588
+ label: "Documentation",
589
+ link: {
590
+ type: "doc",
591
+ file: "home.md",
592
+ path: "/",
593
+ },
594
+ items: [
595
+ "guides/getting-started",
596
+ "guides/installation",
597
+ ],
598
+ },
599
+ ],
600
+ ```
601
+
602
+ ### Landing page with hidden tab
603
+
604
+ Use a `custom-page` with `display: "hide"` and `layout: "none"` to create a full-page landing
605
+ experience that doesn't appear in the navigation tabs:
606
+
607
+ ```tsx
608
+ navigation: [
609
+ {
610
+ type: "custom-page",
611
+ path: "/",
612
+ display: "hide",
613
+ layout: "none",
614
+ element: <LandingPage />,
615
+ },
616
+ {
617
+ type: "category",
618
+ label: "Documentation",
619
+ items: ["docs/quickstart", "docs/installation"],
620
+ },
621
+ ],
622
+ ```
623
+
624
+ ### Organized sidebar with sections and separators
625
+
626
+ Combine `section`, `separator`, and `filter` items to create a well-structured sidebar:
627
+
628
+ ```tsx
629
+ navigation: [
630
+ {
631
+ type: "category",
632
+ label: "Documentation",
633
+ items: [
634
+ { type: "filter", placeholder: "Filter documentation" },
635
+ { type: "section", label: "Getting Started" },
636
+ "guides/quickstart",
637
+ "guides/installation",
638
+ { type: "separator" },
639
+ { type: "section", label: "Advanced" },
640
+ {
641
+ type: "category",
642
+ label: "Plugins",
643
+ icon: "blocks",
644
+ items: ["plugins/overview", "plugins/custom"],
645
+ },
646
+ ],
647
+ },
648
+ ],
649
+ ```
650
+
366
651
  ## Navigation Rules
367
652
 
368
653
  Plugins generate sidebar navigation automatically (e.g. from OpenAPI tags). Navigation rules let you
@@ -0,0 +1,189 @@
1
+ ---
2
+ title: Documenting MCP Servers
3
+ sidebar_icon: bot
4
+ zuplo: false
5
+ ---
6
+
7
+ Zudoku can render a dedicated [MCP](https://modelcontextprotocol.io/) endpoint UI for any OpenAPI
8
+ operation that has the `x-mcp-server` extension. When detected, the operation page replaces the
9
+ standard request/response view with an MCP card showing the endpoint URL, a copy button, and tabbed
10
+ installation instructions for Claude, ChatGPT, Cursor, VS Code, and a generic config.
11
+
12
+ ## Adding the extension
13
+
14
+ Add the `x-mcp-server` extension to an operation in your OpenAPI spec. While MCP servers typically
15
+ use `POST`, the extension works on any HTTP method:
16
+
17
+ ```json title="openapi.json (paths section)"
18
+ {
19
+ "paths": {
20
+ "/mcp": {
21
+ "post": {
22
+ "summary": "My MCP Server",
23
+ "description": "MCP endpoint for querying documentation.",
24
+ "operationId": "mcpEndpoint",
25
+ "x-mcp-server": {
26
+ "name": "my-mcp-server",
27
+ "version": "1.0.0",
28
+ "tools": [
29
+ {
30
+ "name": "search_docs",
31
+ "description": "Search the documentation"
32
+ },
33
+ {
34
+ "name": "get_page",
35
+ "description": "Retrieve a specific documentation page"
36
+ }
37
+ ]
38
+ },
39
+ "responses": {
40
+ "200": {
41
+ "description": "MCP response"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ The UI will display beneath the operation heading, showing the full MCP URL derived from the server
51
+ URL and the operation path.
52
+
53
+ You can also use the shorthand `"x-mcp-server": true` to enable the MCP UI without specifying any
54
+ metadata. In this case, the operation's `summary` is used as the server name.
55
+
56
+ ## Extension properties
57
+
58
+ | Property | Type | Required | Description |
59
+ | --------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
60
+ | `name` | `string` | No | Display name used in the generated client configuration snippets. Falls back to the operation `summary`, then `"mcp-server"` |
61
+ | `version` | `string` | No | Version metadata (included for completeness; not currently rendered in UI) |
62
+ | `tools` | `array` | No | Tools metadata (used by Zuplo enrichment; not currently rendered in UI) |
63
+
64
+ Each tool in the `tools` array has:
65
+
66
+ | Property | Type | Required | Description |
67
+ | ------------- | -------- | -------- | ------------------------------- |
68
+ | `name` | `string` | Yes | Tool name |
69
+ | `description` | `string` | No | Human-readable tool description |
70
+
71
+ ## MCP URL resolution
72
+
73
+ The displayed MCP URL is constructed from the **server URL** of the API and the **path** of the
74
+ operation. The server URL comes from the OpenAPI `servers` array (or the operation-level `servers`
75
+ override if present).
76
+
77
+ For example, with this configuration:
78
+
79
+ ```json
80
+ {
81
+ "servers": [{ "url": "https://api.example.com" }],
82
+ "paths": {
83
+ "/mcp/docs": {
84
+ "post": {
85
+ "x-mcp-server": { "name": "docs-mcp" },
86
+ "responses": { "200": { "description": "OK" } }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ The displayed MCP URL will be `https://api.example.com/mcp/docs`.
94
+
95
+ ## Complete example
96
+
97
+ This is a minimal but complete OpenAPI spec that produces an MCP endpoint page:
98
+
99
+ ```json title="mcp-api.json"
100
+ {
101
+ "openapi": "3.0.3",
102
+ "info": {
103
+ "title": "Documentation MCP Server",
104
+ "version": "1.0.0"
105
+ },
106
+ "servers": [
107
+ {
108
+ "url": "https://api.example.com",
109
+ "description": "Production"
110
+ }
111
+ ],
112
+ "paths": {
113
+ "/mcp": {
114
+ "post": {
115
+ "tags": ["MCP"],
116
+ "summary": "Documentation MCP Server",
117
+ "description": "MCP endpoint powered by Inkeep for searching and querying documentation.",
118
+ "operationId": "mcpEndpoint",
119
+ "x-mcp-server": {
120
+ "name": "example-docs",
121
+ "version": "1.0.0",
122
+ "tools": [
123
+ {
124
+ "name": "search_docs",
125
+ "description": "Search the documentation"
126
+ }
127
+ ]
128
+ },
129
+ "responses": {
130
+ "200": {
131
+ "description": "MCP response"
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
140
+ Then reference this spec in your Zudoku config (see
141
+ [API Reference](/docs/configuration/api-reference) for full `apis` configuration):
142
+
143
+ ```tsx title="zudoku.config.tsx"
144
+ import type { ZudokuConfig } from "zudoku";
145
+
146
+ const config: ZudokuConfig = {
147
+ apis: [
148
+ {
149
+ type: "file",
150
+ input: "./mcp-api.json",
151
+ path: "mcp",
152
+ },
153
+ ],
154
+ navigation: [
155
+ {
156
+ type: "link",
157
+ label: "MCP Server",
158
+ to: "/mcp",
159
+ icon: "bot",
160
+ },
161
+ ],
162
+ };
163
+
164
+ export default config;
165
+ ```
166
+
167
+ You can see a live example of this in the
168
+ [Cosmo Cargo demo](https://www.cosmocargo.dev/catalog/api-ai-cargo/ai-operations#universal-mcp-endpoint).
169
+
170
+ ## Generated UI
171
+
172
+ When Zudoku detects the `x-mcp-server` extension on an operation, the page shows:
173
+
174
+ - **MCP Endpoint card** with the full URL and a copy button
175
+ - **AI Tool Configuration** tabs with setup instructions for:
176
+ - **Claude** — `claude_desktop_config.json` using native HTTP transport
177
+ - **ChatGPT** — connector setup via Settings
178
+ - **Cursor** — `mcp.json` configuration (global or project-level)
179
+ - **VS Code** — `.vscode/mcp.json` for GitHub Copilot
180
+ - **Generic** — standard `mcp.json` format compatible with most MCP clients
181
+
182
+ The standard method badge, request body, parameters, and sidecar panels are hidden for MCP endpoints
183
+ since they use a different interaction model.
184
+
185
+ ## Using with Zuplo
186
+
187
+ If you are using [Zuplo](https://zuplo.com) to host your API, the `x-mcp-server` extension is
188
+ automatically added to POST operations that use the `mcpServerHandler`. No manual schema changes are
189
+ needed. See the [Zuplo MCP documentation](https://zuplo.com/docs/handlers/mcp-handler) for details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.72.0",
3
+ "version": "0.73.1",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -283,7 +283,7 @@
283
283
  "@types/unist": "3.0.3",
284
284
  "@types/yargs": "17.0.35",
285
285
  "@vitest/coverage-v8": "4.0.18",
286
- "happy-dom": "20.8.3",
286
+ "happy-dom": "20.8.9",
287
287
  "oxc-parser": "^0.119.0",
288
288
  "react": "19.2.4",
289
289
  "react-dom": "19.2.4",
@@ -506,6 +506,11 @@ const MetadataSchema = z
506
506
  authors: z.array(z.string()),
507
507
  creator: z.string(),
508
508
  publisher: z.string(),
509
+ robots: z
510
+ .string()
511
+ .describe(
512
+ 'Sets the contents of the `meta[name="robots"]` tag. For example: `noindex, nofollow`.',
513
+ ),
509
514
  })
510
515
  .partial();
511
516
 
@@ -39,6 +39,7 @@ export const Meta = ({ children }: PropsWithChildren) => {
39
39
  ))}
40
40
  {meta?.creator && <meta name="creator" content={meta.creator} />}
41
41
  {meta?.publisher && <meta name="publisher" content={meta.publisher} />}
42
+ {meta?.robots && <meta name="robots" content={meta.robots} />}
42
43
  </Helmet>
43
44
  {children}
44
45
  </>
@@ -65,6 +65,7 @@ type Metadata = Partial<{
65
65
  authors: string[];
66
66
  creator: string;
67
67
  publisher: string;
68
+ robots: string;
68
69
  }>;
69
70
 
70
71
  type Site = Partial<{
@@ -11,15 +11,17 @@ import { cn } from "../../util/cn.js";
11
11
 
12
12
  export const MCPEndpoint = ({
13
13
  serverUrl,
14
+ operationPath,
14
15
  summary,
15
16
  data,
16
17
  }: {
17
18
  serverUrl?: string;
19
+ operationPath?: string;
18
20
  data?: boolean | Record<string, unknown>;
19
21
  summary?: string;
20
22
  }) => {
21
23
  const [isCopied, setIsCopied] = useState(false);
22
- const mcpUrl = `${(serverUrl ?? "").replace(/\/+$/, "")}/mcp`;
24
+ const mcpUrl = `${(serverUrl ?? "").replace(/\/+$/, "")}${operationPath ?? "/mcp"}`;
23
25
 
24
26
  const name =
25
27
  typeof data === "boolean"
@@ -29,11 +31,8 @@ export const MCPEndpoint = ({
29
31
  const claudeConfig = `{
30
32
  "mcpServers": {
31
33
  "${name}": {
32
- "command": "npx",
33
- "args": [
34
- "mcp-remote",
35
- "${mcpUrl}"
36
- ]
34
+ "type": "http",
35
+ "url": "${mcpUrl}"
37
36
  }
38
37
  }
39
38
  }`;
@@ -48,6 +47,14 @@ export const MCPEndpoint = ({
48
47
 
49
48
  const chatgptConfig = mcpUrl;
50
49
 
50
+ const genericConfig = `{
51
+ "mcpServers": {
52
+ "${name}": {
53
+ "url": "${mcpUrl}"
54
+ }
55
+ }
56
+ }`;
57
+
51
58
  const vscodeConfig = `{
52
59
  "servers": {
53
60
  "${name}": {
@@ -117,11 +124,12 @@ export const MCPEndpoint = ({
117
124
  <hr className="my-4" />
118
125
 
119
126
  <Tabs defaultValue="claude" className="w-full">
120
- <TabsList className="grid w-full grid-cols-4">
127
+ <TabsList className="grid w-full grid-cols-5">
121
128
  <TabsTrigger value="claude">Claude</TabsTrigger>
122
129
  <TabsTrigger value="chatgpt">ChatGPT</TabsTrigger>
123
130
  <TabsTrigger value="cursor">Cursor</TabsTrigger>
124
131
  <TabsTrigger value="vscode">VS Code</TabsTrigger>
132
+ <TabsTrigger value="generic">Generic</TabsTrigger>
125
133
  </TabsList>
126
134
 
127
135
  <Typography className="text-sm max-w-full">
@@ -264,6 +272,34 @@ export const MCPEndpoint = ({
264
272
  <ExternalLinkIcon className="h-3 w-3" />
265
273
  </a>
266
274
  </TabsContent>
275
+
276
+ <TabsContent value="generic" className="space-y-3">
277
+ <p>
278
+ Generic <InlineCode>.mcp.json</InlineCode> configuration
279
+ format that works with most MCP-compatible AI tools.
280
+ </p>
281
+ <SyntaxHighlight
282
+ showLanguageIndicator
283
+ title=".mcp.json"
284
+ language="json"
285
+ code={genericConfig}
286
+ className="mt-2"
287
+ />
288
+ <p className="text-sm text-muted-foreground">
289
+ Place this file in your project root or the appropriate
290
+ configuration directory for your AI tool. The exact location
291
+ depends on your specific tool.
292
+ </p>
293
+ <a
294
+ href="https://modelcontextprotocol.io/"
295
+ target="_blank"
296
+ rel="noopener noreferrer"
297
+ className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
298
+ >
299
+ Learn more about MCP
300
+ <ExternalLinkIcon className="h-3 w-3" />
301
+ </a>
302
+ </TabsContent>
267
303
  </Typography>
268
304
  </Tabs>
269
305
  </div>
@@ -88,6 +88,7 @@ export const OperationListItem = ({
88
88
  <div className="col-span-full">
89
89
  <MCPEndpoint
90
90
  serverUrl={displayServerUrl}
91
+ operationPath={operation.path}
91
92
  summary={operation.summary ?? undefined}
92
93
  data={operation.extensions?.["x-mcp-server"]}
93
94
  />