zudoku 0.83.0 → 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 (53) hide show
  1. package/dist/cli/cli.js +77 -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/Search.d.ts +2 -0
  6. package/dist/declarations/lib/components/context/RenderContext.d.ts +3 -0
  7. package/dist/declarations/lib/core/plugins.d.ts +1 -0
  8. package/dist/declarations/lib/plugins/api-catalog/index.d.ts +1 -0
  9. package/dist/declarations/lib/plugins/openapi/MCPEndpoint.d.ts +2 -1
  10. package/dist/declarations/lib/plugins/openapi/McpCatalog.d.ts +5 -0
  11. package/dist/declarations/lib/plugins/openapi/components/SelectOnClick.d.ts +2 -1
  12. package/dist/declarations/lib/plugins/openapi/graphql/gql.d.ts +2 -1
  13. package/dist/declarations/lib/plugins/openapi/graphql/graphql.d.ts +27 -0
  14. package/dist/declarations/lib/plugins/openapi/index.d.ts +1 -1
  15. package/dist/declarations/lib/plugins/openapi/interfaces.d.ts +5 -0
  16. package/dist/declarations/lib/plugins/openapi/mcp-configs.d.ts +15 -1
  17. package/dist/declarations/lib/plugins/openapi/util/documentType.d.ts +8 -0
  18. package/dist/declarations/lib/plugins/openapi/util/getRoutes.d.ts +1 -1
  19. package/dist/declarations/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.d.ts +1 -0
  20. package/dist/flat-config.d.ts +1 -0
  21. package/docs/configuration/api-reference.md +7 -0
  22. package/docs/configuration/search.md +5 -0
  23. package/docs/guides/mcp-servers.md +48 -0
  24. package/docs/openapi-extensions/x-mcp-server.md +39 -0
  25. package/docs/openapi-extensions/x-zudoku-type.md +80 -0
  26. package/package.json +1 -1
  27. package/src/app/main.tsx +2 -1
  28. package/src/app/notFoundRoute.tsx +21 -0
  29. package/src/config/validators/ZudokuConfig.ts +1 -0
  30. package/src/lib/components/Header.tsx +93 -89
  31. package/src/lib/components/Search.tsx +53 -15
  32. package/src/lib/components/context/RenderContext.ts +10 -0
  33. package/src/lib/core/RouteGuard.tsx +6 -3
  34. package/src/lib/core/plugins.ts +1 -0
  35. package/src/lib/oas/graphql/index.ts +6 -0
  36. package/src/lib/plugins/api-catalog/Catalog.tsx +1 -1
  37. package/src/lib/plugins/api-catalog/index.tsx +3 -0
  38. package/src/lib/plugins/openapi/MCPEndpoint.tsx +7 -5
  39. package/src/lib/plugins/openapi/McpCatalog.tsx +467 -0
  40. package/src/lib/plugins/openapi/OperationListItem.tsx +5 -1
  41. package/src/lib/plugins/openapi/components/SelectOnClick.tsx +16 -0
  42. package/src/lib/plugins/openapi/graphql/gql.ts +12 -3
  43. package/src/lib/plugins/openapi/graphql/graphql.ts +53 -0
  44. package/src/lib/plugins/openapi/index.tsx +7 -1
  45. package/src/lib/plugins/openapi/interfaces.ts +14 -0
  46. package/src/lib/plugins/openapi/mcp-configs.ts +105 -2
  47. package/src/lib/plugins/openapi/util/createNavigationCategory.tsx +10 -6
  48. package/src/lib/plugins/openapi/util/documentType.ts +59 -0
  49. package/src/lib/plugins/openapi/util/getRoutes.tsx +29 -1
  50. package/src/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.tsx +18 -6
  51. package/src/lib/plugins/search-inkeep/index.tsx +51 -19
  52. package/src/lib/plugins/search-inkeep/inkeep.ts +5 -1
  53. 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";
@@ -5104,6 +5106,11 @@ var OperationItem = builder.objectRef("OperationItem").implement({
5104
5106
  operationId: t.exposeString("operationId", { nullable: true }),
5105
5107
  summary: t.exposeString("summary", { nullable: true }),
5106
5108
  description: t.exposeString("description", { nullable: true }),
5109
+ // Lean flag so consumers (e.g. the sidebar) can tell an MCP server
5110
+ // endpoint apart without pulling in the whole `x-mcp-server` extension.
5111
+ isMcpServer: t.boolean({
5112
+ resolve: (parent) => resolveExtensions(parent)["x-mcp-server"] !== void 0
5113
+ }),
5107
5114
  contentTypes: t.stringList({
5108
5115
  resolve: (parent) => Object.keys(parent.requestBody?.content ?? {})
5109
5116
  }),
@@ -5360,6 +5367,34 @@ builder.queryType({
5360
5367
  var schema = builder.toSchema();
5361
5368
  var createGraphQLServer = (options) => createYoga({ schema, batching: true, ...options });
5362
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
+
5363
5398
  // src/lib/util/ensureArray.ts
5364
5399
  var ensureArray = (value) => Array.isArray(value) ? value : [value];
5365
5400
 
@@ -6062,6 +6097,25 @@ var viteConfigReloadPlugin = () => ({
6062
6097
 
6063
6098
  // src/vite/plugin-api.ts
6064
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
+ };
6065
6119
  var viteApiPlugin = async () => {
6066
6120
  const virtualModuleId4 = "virtual:zudoku-api-plugins";
6067
6121
  const resolvedVirtualModuleId4 = `\0${virtualModuleId4}`;
@@ -6162,28 +6216,12 @@ var viteApiPlugin = async () => {
6162
6216
  );
6163
6217
  const apis = ensureArray(config.apis);
6164
6218
  const apiMetadata = [];
6165
- const httpMethods = /* @__PURE__ */ new Set([
6166
- "get",
6167
- "post",
6168
- "put",
6169
- "patch",
6170
- "delete",
6171
- "options",
6172
- "head",
6173
- "trace"
6174
- ]);
6175
6219
  for (const apiConfig of apis) {
6176
6220
  if (apiConfig.type === "file" && apiConfig.path) {
6177
6221
  const latestSchema = schemaManager.getLatestSchema(apiConfig.path);
6178
6222
  if (!latestSchema?.schema.info) continue;
6179
- const operationCount = Object.values(
6180
- latestSchema.schema.paths ?? {}
6181
- ).reduce((sum, pathItem) => {
6182
- if (!pathItem || typeof pathItem !== "object") return sum;
6183
- return sum + Object.keys(pathItem).filter(
6184
- (m) => httpMethods.has(m.toLowerCase())
6185
- ).length;
6186
- }, 0);
6223
+ const isCatalog = resolveDocumentType(latestSchema.schema, apiConfig.path) === MCP_CATALOG;
6224
+ const operationCount = isCatalog ? countMcpServers(latestSchema.schema) : countOperations(latestSchema.schema);
6187
6225
  const rawVersion = latestSchema.schema.info.version;
6188
6226
  const version = rawVersion ? rawVersion.startsWith("v") || rawVersion.startsWith("V") ? rawVersion : `v${rawVersion}` : void 0;
6189
6227
  apiMetadata.push({
@@ -6192,7 +6230,8 @@ var viteApiPlugin = async () => {
6192
6230
  description: latestSchema.schema.info.description ?? "",
6193
6231
  categories: apiConfig.categories ?? [],
6194
6232
  version,
6195
- operationCount
6233
+ operationCount,
6234
+ countLabel: isCatalog ? operationCount === 1 ? "server" : "servers" : void 0
6196
6235
  });
6197
6236
  }
6198
6237
  }
@@ -6223,18 +6262,34 @@ var viteApiPlugin = async () => {
6223
6262
  });
6224
6263
  const tags = Array.from(allSlugs);
6225
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
+ }
6226
6279
  code.push(
6227
6280
  "configuredApiPlugins.push(openApiPlugin({",
6228
6281
  ` type: "file",`,
6229
6282
  ` input: ${JSON.stringify(versionedInput)},`,
6230
6283
  ` path: ${JSON.stringify(apiConfig.path)},`,
6231
6284
  ` tagPages: ${JSON.stringify(tags)},`,
6285
+ ...documentType ? [` documentType: ${JSON.stringify(documentType)},`] : [],
6232
6286
  ` options: {`,
6233
6287
  ` examplesLanguage: config.defaults?.apis?.examplesLanguage ?? config.defaults?.examplesLanguage,`,
6234
6288
  ` supportedLanguages: config.defaults?.apis?.supportedLanguages,`,
6235
6289
  ` disablePlayground: config.defaults?.apis?.disablePlayground,`,
6236
6290
  ` disableSidecar: config.defaults?.apis?.disableSidecar,`,
6237
6291
  ` disableSecurity: config.defaults?.apis?.disableSecurity ?? true,`,
6292
+ ` disableMcpAuthInstructions: config.defaults?.apis?.disableMcpAuthInstructions,`,
6238
6293
  ` showVersionSelect: config.defaults?.apis?.showVersionSelect ?? "if-available",`,
6239
6294
  ` expandAllTags: config.defaults?.apis?.expandAllTags ?? true,`,
6240
6295
  ` showInfoPage: config.defaults?.apis?.showInfoPage,`,
@@ -6251,15 +6306,18 @@ var viteApiPlugin = async () => {
6251
6306
  "}));"
6252
6307
  );
6253
6308
  } else {
6309
+ const documentType = apiConfig.type === "raw" ? resolveRawDocumentType(apiConfig.input, apiConfig.path) : void 0;
6254
6310
  code.push(
6255
6311
  "configuredApiPlugins.push(openApiPlugin({",
6256
6312
  ` ...${JSON.stringify(apiConfig)},`,
6313
+ ...documentType ? [` documentType: ${JSON.stringify(documentType)},`] : [],
6257
6314
  " options: {",
6258
6315
  ` examplesLanguage: config.defaults?.apis?.examplesLanguage ?? config.defaults?.examplesLanguage,`,
6259
6316
  ` supportedLanguages: config.defaults?.apis?.supportedLanguages,`,
6260
6317
  ` disablePlayground: config.defaults?.apis?.disablePlayground,`,
6261
6318
  ` disableSidecar: config.defaults?.apis?.disableSidecar,`,
6262
6319
  ` disableSecurity: config.defaults?.apis?.disableSecurity ?? true,`,
6320
+ ` disableMcpAuthInstructions: config.defaults?.apis?.disableMcpAuthInstructions,`,
6263
6321
  ` showVersionSelect: config.defaults?.apis?.showVersionSelect ?? "if-available",`,
6264
6322
  ` expandAllTags: config.defaults?.apis?.expandAllTags ?? false,`,
6265
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>>;
@@ -1,3 +1,5 @@
1
+ import { type PropsWithChildren } from "react";
2
+ export declare const SearchProvider: ({ children }: PropsWithChildren) => import("react").JSX.Element;
1
3
  export declare const Search: ({ className }: {
2
4
  className?: string;
3
5
  }) => import("react").JSX.Element | null;
@@ -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;
@@ -23,6 +23,7 @@ export interface ApiIdentityPlugin {
23
23
  export interface SearchProviderPlugin {
24
24
  renderSearch: (o: {
25
25
  isOpen: boolean;
26
+ onOpen: () => void;
26
27
  onClose: () => void;
27
28
  }) => React.JSX.Element | null;
28
29
  }
@@ -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,8 +1,9 @@
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;
5
6
  export declare function graphql(source: "\n query GetSchemas($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n title\n description\n summary\n components {\n schemas {\n name\n schema\n extensions\n }\n }\n }\n }\n"): typeof import("./graphql.js").GetSchemasDocument;
6
7
  export declare function graphql(source: "\n query getServerQuery($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n url\n servers {\n url\n }\n }\n }\n"): typeof import("./graphql.js").GetServerQueryDocument;
7
- export declare function graphql(source: "\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n extensions\n description\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n"): typeof import("./graphql.js").GetNavigationOperationsDocument;
8
+ export declare function graphql(source: "\n query GetNavigationOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n extensions\n description\n tags {\n slug\n name\n extensions\n operations {\n summary\n slug\n method\n operationId\n path\n isMcpServer\n }\n }\n components {\n schemas {\n __typename\n }\n }\n }\n }\n"): typeof import("./graphql.js").GetNavigationOperationsDocument;
8
9
  export declare function graphql(source: "\n query SchemaWarmup($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n openapi\n }\n }\n"): typeof import("./graphql.js").SchemaWarmupDocument;
@@ -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;
@@ -314,6 +339,7 @@ export type GetNavigationOperationsQuery = {
314
339
  method: string;
315
340
  operationId: string | null;
316
341
  path: string;
342
+ isMcpServer: boolean;
317
343
  }>;
318
344
  }>;
319
345
  components: {
@@ -341,6 +367,7 @@ export declare class TypedDocumentString<TResult, TVariables> extends String imp
341
367
  }
342
368
  export declare const OperationsFragmentFragmentDoc: TypedDocumentString<OperationsFragmentFragment, unknown>;
343
369
  export declare const ServersQueryDocument: TypedDocumentString<ServersQueryQuery, ServersQueryQueryVariables>;
370
+ export declare const GetMcpCatalogDocument: TypedDocumentString<GetMcpCatalogQuery, GetMcpCatalogQueryVariables>;
344
371
  export declare const OperationsForTagDocument: TypedDocumentString<OperationsForTagQuery, OperationsForTagQueryVariables>;
345
372
  export declare const SchemaInfoDocument: TypedDocumentString<SchemaInfoQuery, SchemaInfoQueryVariables>;
346
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,7 +25,15 @@ 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;
22
- export declare const getMcpUrl: (serverUrl?: string, operationPath?: 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[];
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;
25
39
  export declare const getCursorConfig: (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)
@@ -182,6 +182,11 @@ You can also pass
182
182
  Zudoku's defaults and passed through to Inkeep as-is, so any option Inkeep supports can be used —
183
183
  including ones added after this Zudoku version was released.
184
184
 
185
+ Zudoku owns the search modal's open state, so `modalSettings.isOpen` is managed for you. The
186
+ <kbd>⌘</kbd>+<kbd>K</kbd> / <kbd>Ctrl</kbd>+<kbd>K</kbd> shortcut is handled by Zudoku's search
187
+ button as well, which is why Inkeep's own `shortcutKey` is disabled by default. Setting it adds an
188
+ additional shortcut rather than replacing it.
189
+
185
190
  For example, to categorize results into tabs based on their URL:
186
191
 
187
192
  ```typescript
@@ -59,6 +59,7 @@ metadata. In this case, the operation's `summary` is used as the server name.
59
59
  | --------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
60
60
  | `name` | `string` | No | Display name used in the generated client configuration snippets. Falls back to the operation `summary`, then `"mcp-server"` |
61
61
  | `version` | `string` | No | Version metadata (included for completeness; not currently rendered in UI) |
62
+ | `url` | `string` | No | Overrides the endpoint URL shown in the card and install snippets |
62
63
  | `tools` | `array` | No | Tools metadata (used by Zuplo enrichment; not currently rendered in UI) |
63
64
 
64
65
  Each tool in the `tools` array has:
@@ -92,6 +93,53 @@ For example, with this configuration:
92
93
 
93
94
  The displayed MCP URL will be `https://api.example.com/mcp/docs`.
94
95
 
96
+ If your MCP server lives on its own hostname, set `url` on the extension to override the derived
97
+ endpoint:
98
+
99
+ ```json
100
+ {
101
+ "x-mcp-server": { "name": "docs-mcp", "url": "https://mcp.example.com/mcp" }
102
+ }
103
+ ```
104
+
105
+ An absolute URL is used verbatim everywhere the endpoint appears, and takes precedence over the
106
+ server dropdown. A value without a scheme (such as `/v2/mcp`) is treated as a path on the server URL
107
+ instead. See the
108
+ [`x-mcp-server` reference](/docs/openapi-extensions/x-mcp-server#overriding-the-url) for details.
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
+
95
143
  ## Complete example
96
144
 
97
145
  This is a minimal but complete OpenAPI spec that produces an MCP endpoint page: