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 +5 -2
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +1 -0
- package/dist/declarations/lib/core/ZudokuContext.d.ts +1 -0
- package/dist/declarations/lib/plugins/openapi/MCPEndpoint.d.ts +2 -1
- package/dist/flat-config.d.ts +4 -0
- package/docs/configuration/navigation.mdx +294 -9
- package/docs/guides/mcp-servers.md +189 -0
- package/package.json +2 -2
- package/src/config/validators/ZudokuConfig.ts +5 -0
- package/src/lib/components/Meta.tsx +1 -0
- package/src/lib/core/ZudokuContext.ts +1 -0
- package/src/lib/plugins/openapi/MCPEndpoint.tsx +43 -7
- package/src/lib/plugins/openapi/OperationListItem.tsx +1 -0
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.
|
|
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">;
|
|
@@ -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;
|
package/dist/flat-config.d.ts
CHANGED
|
@@ -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
|
|
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`,
|
|
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
|
|
66
|
-
- `custom-
|
|
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
|
|
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<
|
|
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.
|
|
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.
|
|
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
|
</>
|
|
@@ -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
|
-
"
|
|
33
|
-
"
|
|
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-
|
|
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>
|