zudoku 0.83.1 → 0.84.0

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.
Files changed (43) hide show
  1. package/dist/cli/cli.js +72 -19
  2. package/dist/cli/worker.js +1 -0
  3. package/dist/declarations/app/notFoundRoute.d.ts +2 -0
  4. package/dist/declarations/config/validators/ZudokuConfig.d.ts +8 -0
  5. package/dist/declarations/lib/components/context/RenderContext.d.ts +3 -0
  6. package/dist/declarations/lib/plugins/api-catalog/index.d.ts +1 -0
  7. package/dist/declarations/lib/plugins/openapi/MCPEndpoint.d.ts +2 -1
  8. package/dist/declarations/lib/plugins/openapi/McpCatalog.d.ts +5 -0
  9. package/dist/declarations/lib/plugins/openapi/components/SelectOnClick.d.ts +2 -1
  10. package/dist/declarations/lib/plugins/openapi/graphql/gql.d.ts +1 -0
  11. package/dist/declarations/lib/plugins/openapi/graphql/graphql.d.ts +26 -0
  12. package/dist/declarations/lib/plugins/openapi/index.d.ts +1 -1
  13. package/dist/declarations/lib/plugins/openapi/interfaces.d.ts +5 -0
  14. package/dist/declarations/lib/plugins/openapi/mcp-configs.d.ts +14 -0
  15. package/dist/declarations/lib/plugins/openapi/util/documentType.d.ts +8 -0
  16. package/dist/declarations/lib/plugins/openapi/util/getRoutes.d.ts +1 -1
  17. package/dist/declarations/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.d.ts +1 -0
  18. package/dist/flat-config.d.ts +1 -0
  19. package/docs/configuration/api-reference.md +7 -0
  20. package/docs/guides/mcp-servers.md +33 -0
  21. package/docs/openapi-extensions/x-mcp-server.md +9 -0
  22. package/docs/openapi-extensions/x-zudoku-type.md +80 -0
  23. package/package.json +1 -1
  24. package/src/app/main.tsx +2 -1
  25. package/src/app/notFoundRoute.tsx +21 -0
  26. package/src/config/validators/ZudokuConfig.ts +1 -0
  27. package/src/lib/components/context/RenderContext.ts +10 -0
  28. package/src/lib/core/RouteGuard.tsx +6 -3
  29. package/src/lib/plugins/api-catalog/Catalog.tsx +1 -1
  30. package/src/lib/plugins/api-catalog/index.tsx +3 -0
  31. package/src/lib/plugins/openapi/MCPEndpoint.tsx +6 -4
  32. package/src/lib/plugins/openapi/McpCatalog.tsx +467 -0
  33. package/src/lib/plugins/openapi/OperationListItem.tsx +5 -1
  34. package/src/lib/plugins/openapi/components/SelectOnClick.tsx +16 -0
  35. package/src/lib/plugins/openapi/graphql/gql.ts +9 -0
  36. package/src/lib/plugins/openapi/graphql/graphql.ts +51 -0
  37. package/src/lib/plugins/openapi/index.tsx +6 -1
  38. package/src/lib/plugins/openapi/interfaces.ts +14 -0
  39. package/src/lib/plugins/openapi/mcp-configs.ts +73 -0
  40. package/src/lib/plugins/openapi/util/documentType.ts +59 -0
  41. package/src/lib/plugins/openapi/util/getRoutes.tsx +29 -1
  42. package/src/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.tsx +18 -6
  43. package/src/vite/plugin-api.ts +100 -23
package/dist/cli/cli.js CHANGED
@@ -3152,6 +3152,7 @@ var ApiOptionsSchema = z7.object({
3152
3152
  expandAllTags: z7.boolean(),
3153
3153
  showInfoPage: z7.boolean(),
3154
3154
  disableSecurity: z7.boolean(),
3155
+ disableMcpAuthInstructions: z7.boolean(),
3155
3156
  schemaDownload: z7.object({
3156
3157
  enabled: z7.boolean(),
3157
3158
  fileName: z7.string().regex(/^[A-Za-z0-9_-]+$/).optional()
@@ -4178,6 +4179,7 @@ import fs2 from "node:fs/promises";
4178
4179
  import path12 from "node:path";
4179
4180
  import { deepEqual as deepEqual2 } from "fast-equals";
4180
4181
  import { runnerImport as runnerImport3 } from "vite";
4182
+ import { parse as parseYaml } from "yaml";
4181
4183
 
4182
4184
  // src/config/validators/BuildSchema.ts
4183
4185
  import path7 from "node:path";
@@ -5365,6 +5367,34 @@ builder.queryType({
5365
5367
  var schema = builder.toSchema();
5366
5368
  var createGraphQLServer = (options) => createYoga({ schema, batching: true, ...options });
5367
5369
 
5370
+ // src/lib/plugins/openapi/interfaces.ts
5371
+ var DOCUMENT_TYPE_EXTENSION = "x-zudoku-type";
5372
+ var MCP_CATALOG = "mcp-catalog";
5373
+
5374
+ // src/lib/plugins/openapi/util/documentType.ts
5375
+ var MCP_SERVER_EXTENSION = "x-mcp-server";
5376
+ var HTTP_METHODS = [
5377
+ "get",
5378
+ "post",
5379
+ "put",
5380
+ "patch",
5381
+ "delete",
5382
+ "options",
5383
+ "head",
5384
+ "trace"
5385
+ ];
5386
+ var httpMethods = new Set(HTTP_METHODS);
5387
+ var KNOWN_DOCUMENT_TYPES = [MCP_CATALOG];
5388
+ var readDocumentType = (schema2) => schema2[DOCUMENT_TYPE_EXTENSION];
5389
+ var isKnownDocumentType = (value) => typeof value === "string" && KNOWN_DOCUMENT_TYPES.includes(value);
5390
+ var operationsOf = (schema2) => Object.values(schema2.paths ?? {}).flatMap(
5391
+ (pathItem) => pathItem && typeof pathItem === "object" ? Object.entries(pathItem).filter(([method]) => httpMethods.has(method.toLowerCase())).map(([, operation]) => operation) : []
5392
+ );
5393
+ var countOperations = (schema2) => operationsOf(schema2).length;
5394
+ var countMcpServers = (schema2) => operationsOf(schema2).filter(
5395
+ (operation) => !!operation && typeof operation === "object" && MCP_SERVER_EXTENSION in operation
5396
+ ).length;
5397
+
5368
5398
  // src/lib/util/ensureArray.ts
5369
5399
  var ensureArray = (value) => Array.isArray(value) ? value : [value];
5370
5400
 
@@ -6067,6 +6097,25 @@ var viteConfigReloadPlugin = () => ({
6067
6097
 
6068
6098
  // src/vite/plugin-api.ts
6069
6099
  var PROCESSED_STORE_SUBPATH = "node_modules/.zudoku/processed";
6100
+ var warn = (message) => {
6101
+ console.warn(`[zudoku] ${message}`);
6102
+ };
6103
+ var resolveDocumentType = (schema2, apiPath = "<unknown>") => {
6104
+ const value = readDocumentType(schema2);
6105
+ if (value === void 0) return void 0;
6106
+ if (isKnownDocumentType(value)) return value;
6107
+ warn(
6108
+ `Unknown "x-zudoku-type" value ${JSON.stringify(value)} in "${apiPath}". Rendering the default API view.`
6109
+ );
6110
+ return void 0;
6111
+ };
6112
+ var resolveRawDocumentType = (input, apiPath = "<unknown>") => {
6113
+ try {
6114
+ return resolveDocumentType(parseYaml(input), apiPath);
6115
+ } catch {
6116
+ return void 0;
6117
+ }
6118
+ };
6070
6119
  var viteApiPlugin = async () => {
6071
6120
  const virtualModuleId4 = "virtual:zudoku-api-plugins";
6072
6121
  const resolvedVirtualModuleId4 = `\0${virtualModuleId4}`;
@@ -6167,28 +6216,12 @@ var viteApiPlugin = async () => {
6167
6216
  );
6168
6217
  const apis = ensureArray(config.apis);
6169
6218
  const apiMetadata = [];
6170
- const httpMethods = /* @__PURE__ */ new Set([
6171
- "get",
6172
- "post",
6173
- "put",
6174
- "patch",
6175
- "delete",
6176
- "options",
6177
- "head",
6178
- "trace"
6179
- ]);
6180
6219
  for (const apiConfig of apis) {
6181
6220
  if (apiConfig.type === "file" && apiConfig.path) {
6182
6221
  const latestSchema = schemaManager.getLatestSchema(apiConfig.path);
6183
6222
  if (!latestSchema?.schema.info) continue;
6184
- const operationCount = Object.values(
6185
- latestSchema.schema.paths ?? {}
6186
- ).reduce((sum, pathItem) => {
6187
- if (!pathItem || typeof pathItem !== "object") return sum;
6188
- return sum + Object.keys(pathItem).filter(
6189
- (m) => httpMethods.has(m.toLowerCase())
6190
- ).length;
6191
- }, 0);
6223
+ const isCatalog = resolveDocumentType(latestSchema.schema, apiConfig.path) === MCP_CATALOG;
6224
+ const operationCount = isCatalog ? countMcpServers(latestSchema.schema) : countOperations(latestSchema.schema);
6192
6225
  const rawVersion = latestSchema.schema.info.version;
6193
6226
  const version = rawVersion ? rawVersion.startsWith("v") || rawVersion.startsWith("V") ? rawVersion : `v${rawVersion}` : void 0;
6194
6227
  apiMetadata.push({
@@ -6197,7 +6230,8 @@ var viteApiPlugin = async () => {
6197
6230
  description: latestSchema.schema.info.description ?? "",
6198
6231
  categories: apiConfig.categories ?? [],
6199
6232
  version,
6200
- operationCount
6233
+ operationCount,
6234
+ countLabel: isCatalog ? operationCount === 1 ? "server" : "servers" : void 0
6201
6235
  });
6202
6236
  }
6203
6237
  }
@@ -6228,18 +6262,34 @@ var viteApiPlugin = async () => {
6228
6262
  });
6229
6263
  const tags = Array.from(allSlugs);
6230
6264
  const schemaImports = schemaManager.getSchemaImports();
6265
+ const latest = schemas.at(0);
6266
+ const documentType = latest ? resolveDocumentType(latest.schema, apiConfig.path) : void 0;
6267
+ if (documentType === MCP_CATALOG) {
6268
+ if (schemas.length > 1) {
6269
+ warn(
6270
+ `"${apiConfig.path}" is an MCP catalog with ${schemas.length} versions. Only the latest is rendered; catalog documents do not support version switching.`
6271
+ );
6272
+ }
6273
+ if (latest && countMcpServers(latest.schema) === 0) {
6274
+ warn(
6275
+ `"${apiConfig.path}" is marked as an MCP catalog but has no operations with "x-mcp-server". The catalog will render empty.`
6276
+ );
6277
+ }
6278
+ }
6231
6279
  code.push(
6232
6280
  "configuredApiPlugins.push(openApiPlugin({",
6233
6281
  ` type: "file",`,
6234
6282
  ` input: ${JSON.stringify(versionedInput)},`,
6235
6283
  ` path: ${JSON.stringify(apiConfig.path)},`,
6236
6284
  ` tagPages: ${JSON.stringify(tags)},`,
6285
+ ...documentType ? [` documentType: ${JSON.stringify(documentType)},`] : [],
6237
6286
  ` options: {`,
6238
6287
  ` examplesLanguage: config.defaults?.apis?.examplesLanguage ?? config.defaults?.examplesLanguage,`,
6239
6288
  ` supportedLanguages: config.defaults?.apis?.supportedLanguages,`,
6240
6289
  ` disablePlayground: config.defaults?.apis?.disablePlayground,`,
6241
6290
  ` disableSidecar: config.defaults?.apis?.disableSidecar,`,
6242
6291
  ` disableSecurity: config.defaults?.apis?.disableSecurity ?? true,`,
6292
+ ` disableMcpAuthInstructions: config.defaults?.apis?.disableMcpAuthInstructions,`,
6243
6293
  ` showVersionSelect: config.defaults?.apis?.showVersionSelect ?? "if-available",`,
6244
6294
  ` expandAllTags: config.defaults?.apis?.expandAllTags ?? true,`,
6245
6295
  ` showInfoPage: config.defaults?.apis?.showInfoPage,`,
@@ -6256,15 +6306,18 @@ var viteApiPlugin = async () => {
6256
6306
  "}));"
6257
6307
  );
6258
6308
  } else {
6309
+ const documentType = apiConfig.type === "raw" ? resolveRawDocumentType(apiConfig.input, apiConfig.path) : void 0;
6259
6310
  code.push(
6260
6311
  "configuredApiPlugins.push(openApiPlugin({",
6261
6312
  ` ...${JSON.stringify(apiConfig)},`,
6313
+ ...documentType ? [` documentType: ${JSON.stringify(documentType)},`] : [],
6262
6314
  " options: {",
6263
6315
  ` examplesLanguage: config.defaults?.apis?.examplesLanguage ?? config.defaults?.examplesLanguage,`,
6264
6316
  ` supportedLanguages: config.defaults?.apis?.supportedLanguages,`,
6265
6317
  ` disablePlayground: config.defaults?.apis?.disablePlayground,`,
6266
6318
  ` disableSidecar: config.defaults?.apis?.disableSidecar,`,
6267
6319
  ` disableSecurity: config.defaults?.apis?.disableSecurity ?? true,`,
6320
+ ` disableMcpAuthInstructions: config.defaults?.apis?.disableMcpAuthInstructions,`,
6268
6321
  ` showVersionSelect: config.defaults?.apis?.showVersionSelect ?? "if-available",`,
6269
6322
  ` expandAllTags: config.defaults?.apis?.expandAllTags ?? false,`,
6270
6323
  ` showInfoPage: config.defaults?.apis?.showInfoPage,`,
@@ -2386,6 +2386,7 @@ var ApiOptionsSchema = z4.object({
2386
2386
  expandAllTags: z4.boolean(),
2387
2387
  showInfoPage: z4.boolean(),
2388
2388
  disableSecurity: z4.boolean(),
2389
+ disableMcpAuthInstructions: z4.boolean(),
2389
2390
  schemaDownload: z4.object({
2390
2391
  enabled: z4.boolean(),
2391
2392
  fileName: z4.string().regex(/^[A-Za-z0-9_-]+$/).optional()
@@ -0,0 +1,2 @@
1
+ import type { RouteObject } from "react-router";
2
+ export declare const notFoundRoute: RouteObject;
@@ -57,6 +57,7 @@ declare const ApiOptionsSchema: z.ZodObject<{
57
57
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
58
58
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
59
59
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
60
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
60
61
  schemaDownload: z.ZodOptional<z.ZodObject<{
61
62
  enabled: z.ZodOptional<z.ZodBoolean>;
62
63
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -7297,6 +7298,7 @@ export declare const ZudokuConfig: z.ZodObject<{
7297
7298
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
7298
7299
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
7299
7300
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
7301
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
7300
7302
  schemaDownload: z.ZodOptional<z.ZodObject<{
7301
7303
  enabled: z.ZodOptional<z.ZodBoolean>;
7302
7304
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -7333,6 +7335,7 @@ export declare const ZudokuConfig: z.ZodObject<{
7333
7335
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
7334
7336
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
7335
7337
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
7338
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
7336
7339
  schemaDownload: z.ZodOptional<z.ZodObject<{
7337
7340
  enabled: z.ZodOptional<z.ZodBoolean>;
7338
7341
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -7369,6 +7372,7 @@ export declare const ZudokuConfig: z.ZodObject<{
7369
7372
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
7370
7373
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
7371
7374
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
7375
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
7372
7376
  schemaDownload: z.ZodOptional<z.ZodObject<{
7373
7377
  enabled: z.ZodOptional<z.ZodBoolean>;
7374
7378
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -7401,6 +7405,7 @@ export declare const ZudokuConfig: z.ZodObject<{
7401
7405
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
7402
7406
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
7403
7407
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
7408
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
7404
7409
  schemaDownload: z.ZodOptional<z.ZodObject<{
7405
7410
  enabled: z.ZodOptional<z.ZodBoolean>;
7406
7411
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -7437,6 +7442,7 @@ export declare const ZudokuConfig: z.ZodObject<{
7437
7442
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
7438
7443
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
7439
7444
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
7445
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
7440
7446
  schemaDownload: z.ZodOptional<z.ZodObject<{
7441
7447
  enabled: z.ZodOptional<z.ZodBoolean>;
7442
7448
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -7473,6 +7479,7 @@ export declare const ZudokuConfig: z.ZodObject<{
7473
7479
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
7474
7480
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
7475
7481
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
7482
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
7476
7483
  schemaDownload: z.ZodOptional<z.ZodObject<{
7477
7484
  enabled: z.ZodOptional<z.ZodBoolean>;
7478
7485
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -7574,6 +7581,7 @@ export declare const ZudokuConfig: z.ZodObject<{
7574
7581
  expandAllTags: z.ZodOptional<z.ZodBoolean>;
7575
7582
  showInfoPage: z.ZodOptional<z.ZodBoolean>;
7576
7583
  disableSecurity: z.ZodOptional<z.ZodBoolean>;
7584
+ disableMcpAuthInstructions: z.ZodOptional<z.ZodBoolean>;
7577
7585
  schemaDownload: z.ZodOptional<z.ZodObject<{
7578
7586
  enabled: z.ZodOptional<z.ZodBoolean>;
7579
7587
  fileName: z.ZodOptional<z.ZodOptional<z.ZodString>>;
@@ -9,3 +9,6 @@ export type RenderContextValue = {
9
9
  ssrAuth?: SSRAuthState;
10
10
  };
11
11
  export declare const RenderContext: import("react").Context<RenderContextValue>;
12
+ export declare const setSsrStatus: (renderContext: RenderContextValue, status: number, { onlyIfUnset }?: {
13
+ onlyIfUnset?: boolean;
14
+ }) => void;
@@ -7,6 +7,7 @@ export type ApiCatalogItem = {
7
7
  categories: CatalogCategory[];
8
8
  version?: string;
9
9
  operationCount?: number;
10
+ countLabel?: string;
10
11
  };
11
12
  export type CatalogCategory = {
12
13
  label: string;
@@ -1,7 +1,8 @@
1
1
  import { type McpServerData } from "./mcp-configs.js";
2
- export declare const MCPEndpoint: ({ serverUrl, operationPath, summary, data, }: {
2
+ export declare const MCPEndpoint: ({ serverUrl, operationPath, summary, data, disableAuthInstructions, }: {
3
3
  serverUrl?: string;
4
4
  operationPath?: string;
5
5
  data?: McpServerData;
6
6
  summary?: string;
7
+ disableAuthInstructions?: boolean;
7
8
  }) => import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ export declare const GetMcpCatalogQuery: import("./graphql/graphql.js").TypedDocumentString<import("./graphql/graphql.js").GetMcpCatalogQuery, {
2
+ input: any;
3
+ type: import("./graphql/graphql.js").SchemaType;
4
+ }>;
5
+ export declare const McpCatalog: () => import("react").JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { type SlotProps } from "@radix-ui/react-slot";
2
- export declare const SelectOnClick: ({ asChild, onClick, enabled, ...props }: {
2
+ export declare const SelectOnClick: ({ asChild, onClick, copyValue, enabled, ...props }: {
3
3
  asChild?: boolean;
4
4
  enabled?: boolean;
5
+ copyValue?: string;
5
6
  } & SlotProps) => import("react").JSX.Element;
@@ -1,4 +1,5 @@
1
1
  export declare function graphql(source: "\n query ServersQuery($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n url\n servers {\n url\n }\n }\n }\n"): typeof import("./graphql.js").ServersQueryDocument;
2
+ export declare function graphql(source: "\n query GetMcpCatalog($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n title\n description\n tags {\n name\n slug\n operations {\n slug\n summary\n description\n operationId\n path\n extensions\n servers {\n url\n }\n }\n }\n }\n }\n"): typeof import("./graphql.js").GetMcpCatalogDocument;
2
3
  export declare function graphql(source: "\n fragment OperationsFragment on OperationItem {\n slug\n summary\n method\n description\n operationId\n contentTypes\n path\n deprecated\n extensions\n servers {\n url\n description\n }\n parameters {\n name\n in\n description\n required\n schema\n style\n explode\n allowReserved\n examples {\n name\n description\n externalValue\n value\n summary\n }\n }\n security {\n schemes {\n scopes\n scheme {\n name\n type\n description\n in\n paramName\n scheme\n bearerFormat\n openIdConnectUrl\n flows {\n implicit {\n authorizationUrl\n scopes {\n name\n description\n }\n }\n password {\n tokenUrl\n scopes {\n name\n description\n }\n }\n clientCredentials {\n tokenUrl\n scopes {\n name\n description\n }\n }\n authorizationCode {\n authorizationUrl\n tokenUrl\n scopes {\n name\n description\n }\n }\n }\n }\n }\n }\n requestBody {\n content {\n mediaType\n encoding {\n name\n }\n examples {\n name\n description\n externalValue\n value\n summary\n }\n schema\n }\n description\n required\n }\n responses {\n statusCode\n links\n description\n content {\n examples {\n name\n description\n externalValue\n value\n summary\n }\n mediaType\n encoding {\n name\n }\n schema\n }\n }\n }\n"): typeof import("./graphql.js").OperationsFragmentFragmentDoc;
3
4
  export declare function graphql(source: "\n query OperationsForTag(\n $input: JSON!\n $type: SchemaType!\n $tag: String\n $untagged: Boolean\n ) {\n schema(input: $input, type: $type) {\n servers {\n url\n }\n description\n summary\n title\n url\n version\n tag(slug: $tag, untagged: $untagged) {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n extensions\n next {\n name\n slug\n extensions\n }\n prev {\n name\n slug\n extensions\n }\n }\n }\n }\n"): typeof import("./graphql.js").OperationsForTagDocument;
4
5
  export declare function graphql(source: "\n query SchemaInfo($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n servers {\n url\n description\n }\n license {\n name\n url\n identifier\n }\n termsOfService\n externalDocs {\n description\n url\n }\n contact {\n name\n url\n email\n }\n description\n summary\n title\n url\n version\n tags {\n name\n description\n extensions\n }\n components {\n securitySchemes {\n name\n type\n description\n in\n paramName\n scheme\n bearerFormat\n openIdConnectUrl\n flows {\n implicit {\n authorizationUrl\n scopes {\n name\n description\n }\n }\n password {\n tokenUrl\n scopes {\n name\n description\n }\n }\n clientCredentials {\n tokenUrl\n scopes {\n name\n description\n }\n }\n authorizationCode {\n authorizationUrl\n tokenUrl\n scopes {\n name\n description\n }\n }\n }\n }\n }\n webhooks {\n name\n method\n summary\n description\n }\n }\n }\n"): typeof import("./graphql.js").SchemaInfoDocument;
@@ -23,6 +23,31 @@ export type ServersQueryQuery = {
23
23
  }>;
24
24
  };
25
25
  };
26
+ export type GetMcpCatalogQueryVariables = Exact<{
27
+ input: any;
28
+ type: SchemaType;
29
+ }>;
30
+ export type GetMcpCatalogQuery = {
31
+ schema: {
32
+ title: string;
33
+ description: string | null;
34
+ tags: Array<{
35
+ name: string | null;
36
+ slug: string | null;
37
+ operations: Array<{
38
+ slug: string;
39
+ summary: string | null;
40
+ description: string | null;
41
+ operationId: string | null;
42
+ path: string;
43
+ extensions: any;
44
+ servers: Array<{
45
+ url: string;
46
+ }>;
47
+ }>;
48
+ }>;
49
+ };
50
+ };
26
51
  export type OperationsFragmentFragment = {
27
52
  slug: string;
28
53
  summary: string | null;
@@ -342,6 +367,7 @@ export declare class TypedDocumentString<TResult, TVariables> extends String imp
342
367
  }
343
368
  export declare const OperationsFragmentFragmentDoc: TypedDocumentString<OperationsFragmentFragment, unknown>;
344
369
  export declare const ServersQueryDocument: TypedDocumentString<ServersQueryQuery, ServersQueryQueryVariables>;
370
+ export declare const GetMcpCatalogDocument: TypedDocumentString<GetMcpCatalogQuery, GetMcpCatalogQueryVariables>;
345
371
  export declare const OperationsForTagDocument: TypedDocumentString<OperationsForTagQuery, OperationsForTagQueryVariables>;
346
372
  export declare const SchemaInfoDocument: TypedDocumentString<SchemaInfoQuery, SchemaInfoQueryVariables>;
347
373
  export declare const GetSchemasDocument: TypedDocumentString<GetSchemasQuery, GetSchemasQueryVariables>;
@@ -1,6 +1,6 @@
1
1
  import type { ZudokuPlugin } from "../../core/plugins.js";
2
2
  import type { GetNavigationOperationsQuery as GetNavigationOperationsQueryResult } from "./graphql/graphql.js";
3
- import type { OasPluginConfig } from "./interfaces.js";
3
+ import { type OasPluginConfig } from "./interfaces.js";
4
4
  export declare const GetNavigationOperationsQuery: import("./graphql/graphql.js").TypedDocumentString<GetNavigationOperationsQueryResult, {
5
5
  input: any;
6
6
  type: import("./graphql/graphql.js").SchemaType;
@@ -58,12 +58,16 @@ export type GenerateCodeSnippetFn = (options: {
58
58
  example?: any | null;
59
59
  resolvedAuth?: ResolvedAuth;
60
60
  }) => string | false;
61
+ export declare const DOCUMENT_TYPE_EXTENSION = "x-zudoku-type";
62
+ export declare const MCP_CATALOG = "mcp-catalog";
63
+ export type OasDocumentType = typeof MCP_CATALOG;
61
64
  type BaseOasConfig = {
62
65
  server?: string;
63
66
  path?: string;
64
67
  skipPreload?: boolean;
65
68
  tagPages?: Array<string>;
66
69
  schemaImports?: SchemaImports;
70
+ documentType?: OasDocumentType;
67
71
  options?: {
68
72
  examplesLanguage?: string;
69
73
  supportedLanguages?: {
@@ -73,6 +77,7 @@ type BaseOasConfig = {
73
77
  disablePlayground?: boolean;
74
78
  disableSidecar?: boolean;
75
79
  disableSecurity?: boolean;
80
+ disableMcpAuthInstructions?: boolean;
76
81
  showVersionSelect?: "always" | "if-available" | "hide";
77
82
  expandAllTags?: boolean;
78
83
  showInfoPage?: boolean;
@@ -6,6 +6,12 @@ export interface AuthHeader {
6
6
  export type AuthType = "none" | "apiKey" | "oauth";
7
7
  export declare const getAuthType: (data?: McpServerData) => AuthType;
8
8
  export declare const getAuthHeader: (data?: McpServerData) => AuthHeader | undefined;
9
+ export declare const resolveMcpAuth: (data?: McpServerData, options?: {
10
+ disableAuthInstructions?: boolean;
11
+ }) => {
12
+ authType: AuthType;
13
+ auth?: AuthHeader;
14
+ };
9
15
  export interface McpSubApp {
10
16
  id: string;
11
17
  label: string;
@@ -19,6 +25,14 @@ export interface McpApp {
19
25
  export declare const MCP_APPS: McpApp[];
20
26
  export declare const getVisibleApps: (authType: AuthType) => McpApp[];
21
27
  export declare const getMcpServerName: (data?: McpServerData, summary?: string) => string;
28
+ export type McpTool = {
29
+ name: string;
30
+ description?: string;
31
+ };
32
+ export declare const isMcpServerObject: (data?: McpServerData) => data is Record<string, unknown>;
33
+ export declare const isMcpServerData: (value: unknown) => value is McpServerData;
34
+ export declare const getMcpServerTitle: (data?: McpServerData, summary?: string | null, operationId?: string | null) => string;
35
+ export declare const getMcpTools: (data?: McpServerData) => McpTool[];
22
36
  export declare const getMcpUrl: (serverUrl?: string, operationPath?: string, data?: McpServerData) => string;
23
37
  export declare const getClaudeCodeCommand: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
24
38
  export declare const getCodexCliCommand: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
@@ -0,0 +1,8 @@
1
+ import type { OpenAPIDocument } from "../../../oas/parser/index.js";
2
+ import { type OasDocumentType } from "../interfaces.js";
3
+ export declare const MCP_SERVER_EXTENSION = "x-mcp-server";
4
+ export declare const HTTP_METHODS: readonly ["get", "post", "put", "patch", "delete", "options", "head", "trace"];
5
+ export declare const readDocumentType: (schema: OpenAPIDocument) => unknown;
6
+ export declare const isKnownDocumentType: (value: unknown) => value is OasDocumentType;
7
+ export declare const countOperations: (schema: OpenAPIDocument) => number;
8
+ export declare const countMcpServers: (schema: OpenAPIDocument) => number;
@@ -1,7 +1,7 @@
1
1
  import { type RouteObject } from "react-router";
2
2
  import type { GraphQLClient } from "../client/GraphQLClient.js";
3
3
  import { type OpenApiPluginOptions } from "../index.js";
4
- import type { OasPluginConfig, VersionEntry } from "../interfaces.js";
4
+ import { type OasPluginConfig, type VersionEntry } from "../interfaces.js";
5
5
  export declare const getVersionMetadata: (config: OasPluginConfig) => {
6
6
  versions: string[];
7
7
  versionMap: Record<string, {
@@ -1 +1,2 @@
1
+ export declare function stripMarkdown(description: string, maxLength?: number): string;
1
2
  export declare function sanitizeMarkdownForMetatag(description: string, maxLength?: number): string;
@@ -521,6 +521,7 @@ export interface _Schema20 {
521
521
  expandAllTags?: boolean
522
522
  showInfoPage?: boolean
523
523
  disableSecurity?: boolean
524
+ disableMcpAuthInstructions?: boolean
524
525
  schemaDownload?: {
525
526
  enabled?: boolean
526
527
  fileName?: string
@@ -207,6 +207,7 @@ const config = {
207
207
  disablePlayground: false, // Disable the interactive API playground
208
208
  disableSidecar: false, // Disable the sidecar completely
209
209
  disableSecurity: true, // Disable security scheme display and playground auth (default)
210
+ disableMcpAuthInstructions: false, // Hide auth steps in the MCP server card
210
211
  showVersionSelect: "if-available", // Control version selector visibility
211
212
  expandAllTags: true, // Control initial expanded state of tag categories
212
213
  showInfoPage: true, // Always show the info page (unset = show only if a description is set)
@@ -229,6 +230,11 @@ Available options:
229
230
  - `disableSecurity`: Disable OpenAPI security scheme display (auth badges on operations, security
230
231
  schemes section on the info page, and the Authorize dialog in the playground). Disabled by default
231
232
  (`true`). Set to `false` to enable security scheme support
233
+ - `disableMcpAuthInstructions`: Hide the authentication instructions on
234
+ [MCP server](../guides/mcp-servers.md) endpoints. The MCP card normally derives a credential
235
+ header from the operation's security scheme and shows it in every install snippet. Set to `true`
236
+ to render the server as unauthenticated instead — no header snippets and no "replace
237
+ `YOUR_API_KEY`" steps
232
238
  - `showVersionSelect`: Control version selector visibility
233
239
  - `"if-available"`: Show version selector only when multiple versions exist (default)
234
240
  - `"always"`: Always show version selector (disabled if only one version)
@@ -260,6 +266,7 @@ const config = {
260
266
  disablePlayground: false, // Disable the interactive API playground
261
267
  disableSidecar: false, // Disable the sidecar completely
262
268
  disableSecurity: true, // Disable security scheme display and playground auth (default)
269
+ disableMcpAuthInstructions: false, // Hide auth steps in the MCP server card
263
270
  showVersionSelect: "if-available", // Control version selector visibility
264
271
  expandAllTags: false, // Control initial expanded state of tag categories
265
272
  showInfoPage: true, // Always show the info page (unset = show only if a description is set)
@@ -107,6 +107,39 @@ server dropdown. A value without a scheme (such as `/v2/mcp`) is treated as a pa
107
107
  instead. See the
108
108
  [`x-mcp-server` reference](/docs/openapi-extensions/x-mcp-server#overriding-the-url) for details.
109
109
 
110
+ ## Authentication instructions
111
+
112
+ When the `x-mcp-server` extension carries `security` and `securitySchemes` (Zuplo adds these
113
+ automatically for authenticated routes), the card derives the credential header from the first
114
+ scheme and threads it through every install snippet — for example an
115
+ `Authorization: Bearer YOUR_API_KEY` header in the `mcp.json` samples, plus a step telling users to
116
+ replace the placeholder with their key. API key auth also hides the clients that cannot send custom
117
+ headers (Claude Desktop and ChatGPT), and the one-click install buttons for Cursor and VS Code,
118
+ since those links cannot carry a secret.
119
+
120
+ If your users get their credentials some other way — the key is injected by a proxy, handled by your
121
+ own login flow, or simply documented elsewhere — turn the authentication instructions off in your
122
+ Zudoku config:
123
+
124
+ ```tsx title="zudoku.config.tsx"
125
+ const config: ZudokuConfig = {
126
+ apis: [
127
+ {
128
+ type: "file",
129
+ input: "./mcp-api.json",
130
+ path: "mcp",
131
+ options: {
132
+ disableMcpAuthInstructions: true,
133
+ },
134
+ },
135
+ ],
136
+ };
137
+ ```
138
+
139
+ The card then renders the server as if it were unauthenticated, whatever its security says: no
140
+ header in the snippets, no placeholder step, and every client available again. Set it under
141
+ `defaults.apis` instead to apply it to all APIs.
142
+
110
143
  ## Complete example
111
144
 
112
145
  This is a minimal but complete OpenAPI spec that produces an MCP endpoint page:
@@ -130,5 +130,14 @@ When detected, the operation page shows:
130
130
  The standard method badge, request body, parameters, and sidecar panels are hidden for MCP
131
131
  endpoints.
132
132
 
133
+ When the extension carries `security` and `securitySchemes`, the card also documents the credential
134
+ header and adds it to every install snippet. Set the
135
+ [`disableMcpAuthInstructions`](/docs/configuration/api-reference#options) API option to render the
136
+ server as unauthenticated instead.
137
+
133
138
  For a full walkthrough including Zudoku configuration, see the
134
139
  [Documenting MCP Servers guide](/docs/guides/mcp-servers).
140
+
141
+ If a document describes several MCP servers, mark it with
142
+ [`x-zudoku-type: mcp-catalog`](./x-zudoku-type) to render them as a searchable catalog instead of
143
+ individual operation pages.
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: x-zudoku-type
3
+ sidebar_icon: layout-grid
4
+ ---
5
+
6
+ Use `x-zudoku-type` to change how Zudoku renders an entire OpenAPI document, instead of the default
7
+ REST API view.
8
+
9
+ ## Location
10
+
11
+ The extension is added at the **Root Object** level — the outermost level of the OpenAPI
12
+ description.
13
+
14
+ | Option | Type | Description |
15
+ | --------------- | -------- | -------------------------------------- |
16
+ | `x-zudoku-type` | `string` | The renderer to use for this document. |
17
+
18
+ | Value | Renders as |
19
+ | --------------- | --------------------------------------------------------------- |
20
+ | `"mcp-catalog"` | A searchable, filterable catalog of the document's MCP servers. |
21
+
22
+ Values Zudoku does not recognise are ignored with a build warning, and the document falls back to
23
+ the default API view. This keeps a description written for a newer version of Zudoku building
24
+ against an older one.
25
+
26
+ ## `mcp-catalog`
27
+
28
+ A catalog document renders as a **single page** listing every operation marked with
29
+ [`x-mcp-server`](./x-mcp-server) as a card, in one grid that can be searched and filtered by tag.
30
+ Selecting a card opens the server's install instructions and its tool list.
31
+
32
+ Because the whole document becomes a catalog, a few things change:
33
+
34
+ - **Only MCP servers are rendered.** Operations without `x-mcp-server` are not shown, and the
35
+ document contributes no sidebar entries, tag pages, or schema page. If you also want to document
36
+ plain REST endpoints, put them in a separate OpenAPI document with its own `apis` entry.
37
+ - **Tags become filters** rather than pages or headings. Each tag appears as a filter chip and as a
38
+ badge on its servers' cards; the grid itself stays flat. Servers without a tag are filed under
39
+ "Other". The active filter is kept in the URL as `?tag=`, so a filtered view can be linked.
40
+ - **Only the latest version is rendered.** Catalog documents do not support version switching; a
41
+ versioned API marked as a catalog warns at build time.
42
+
43
+ The extension is read while your schema is processed, so it applies to `type: "file"` and
44
+ `type: "raw"` APIs. `type: "url"` schemas are fetched in the browser after routing is decided, so
45
+ the flag has no effect on them.
46
+
47
+ ## Example
48
+
49
+ ```yaml
50
+ openapi: 3.1.0
51
+ x-zudoku-type: mcp-catalog
52
+ info:
53
+ title: Employee MCP Servers
54
+ version: 1.0.0
55
+ servers:
56
+ - url: https://mcp.example.com
57
+ tags:
58
+ - name: CRM & Customer Operations
59
+ paths:
60
+ /v1/salesforce/sales-cloud/mcp:
61
+ post:
62
+ summary: Salesforce Sales Cloud
63
+ description: Read-only access to accounts, opportunities and contacts.
64
+ tags:
65
+ - CRM & Customer Operations
66
+ x-mcp-server:
67
+ name: salesforce-sales-cloud
68
+ version: 1.0.0
69
+ tools:
70
+ - name: searchAccounts
71
+ description: Find accounts by name, domain or owner.
72
+ ```
73
+
74
+ This renders one catalog page with a single `Salesforce Sales Cloud` card, reachable via a
75
+ `CRM & Customer Operations` filter chip. The card opens install snippets for Claude, ChatGPT,
76
+ Cursor, VS Code and Codex, alongside the server's tools.
77
+
78
+ ## Related
79
+
80
+ - [`x-mcp-server`](./x-mcp-server) — mark an individual operation as an MCP server
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.83.1",
3
+ "version": "0.84.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.22.0"
package/src/app/main.tsx CHANGED
@@ -29,6 +29,7 @@ import { RouteGuard } from "../lib/core/RouteGuard.js";
29
29
  import type { ZudokuContextOptions } from "../lib/core/ZudokuContext.js";
30
30
  import { RouterError } from "../lib/errors/RouterError.js";
31
31
  import { ZuploEnv } from "./env.js";
32
+ import { notFoundRoute } from "./notFoundRoute.js";
32
33
  import { processRoutes } from "./processRoutes.js";
33
34
  import { createRedirectRoutes } from "./utils/createRedirectRoutes.js";
34
35
  import {
@@ -109,7 +110,7 @@ export const getRoutesByOptions = (
109
110
  }))
110
111
  : [],
111
112
  )
112
- .concat([{ path: "*", element: <StatusPage statusCode={404} /> }])
113
+ .concat([notFoundRoute])
113
114
  .map((route) => ({
114
115
  ...route,
115
116
  errorElement: <RouterError className="w-full m-0" />,