zudoku 0.74.2 → 0.75.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.
- package/dist/cli/cli.js +88 -15
- package/dist/declarations/lib/plugins/markdown/MdxPage.d.ts +2 -1
- package/dist/declarations/lib/plugins/openapi/MCPEndpoint.d.ts +2 -1
- package/dist/declarations/lib/plugins/openapi/mcp-configs.d.ts +28 -0
- package/docs/configuration/authentication-auth0.md +6 -3
- package/docs/configuration/authentication-openid.md +110 -0
- package/docs/configuration/authentication.md +5 -2
- package/docs/openapi-extensions/x-code-samples.md +57 -0
- package/docs/openapi-extensions/x-display-name.md +31 -0
- package/docs/openapi-extensions/x-mcp-server.md +104 -0
- package/docs/openapi-extensions/x-mcp.md +171 -0
- package/docs/openapi-extensions/x-tag-groups.md +65 -0
- package/docs/openapi-extensions/x-zudoku-collapsed.md +35 -0
- package/docs/openapi-extensions/x-zudoku-collapsible.md +35 -0
- package/docs/openapi-extensions/x-zudoku-playground-enabled.md +43 -0
- package/package.json +2 -2
- package/src/lib/components/Markdown.tsx +34 -7
- package/src/lib/plugins/markdown/MdxPage.tsx +5 -0
- package/src/lib/plugins/markdown/index.tsx +1 -0
- package/src/lib/plugins/openapi/Endpoint.tsx +17 -26
- package/src/lib/plugins/openapi/MCPEndpoint.tsx +292 -209
- package/src/lib/plugins/openapi/mcp-configs.ts +265 -0
- package/src/vite/plugin-docs.ts +1 -0
- package/src/vite/prerender/prerender.ts +136 -16
- package/src/zuplo/enrich-with-zuplo-mcp.ts +111 -10
package/dist/cli/cli.js
CHANGED
|
@@ -3813,7 +3813,7 @@ import {
|
|
|
3813
3813
|
// package.json
|
|
3814
3814
|
var package_default = {
|
|
3815
3815
|
name: "zudoku",
|
|
3816
|
-
version: "0.74.
|
|
3816
|
+
version: "0.74.3",
|
|
3817
3817
|
type: "module",
|
|
3818
3818
|
sideEffects: [
|
|
3819
3819
|
"**/*.css",
|
|
@@ -3951,7 +3951,7 @@ var package_default = {
|
|
|
3951
3951
|
"fast-equals": "6.0.0",
|
|
3952
3952
|
glob: "13.0.6",
|
|
3953
3953
|
"glob-parent": "6.0.2",
|
|
3954
|
-
graphql: "16.13.
|
|
3954
|
+
graphql: "16.13.2",
|
|
3955
3955
|
"graphql-type-json": "0.3.2",
|
|
3956
3956
|
"graphql-yoga": "5.18.0",
|
|
3957
3957
|
"gray-matter": "4.0.3",
|
|
@@ -6730,6 +6730,7 @@ var viteDocsPlugin = () => {
|
|
|
6730
6730
|
` basePath: "${config2.basePath ?? ""}",`,
|
|
6731
6731
|
` fileImports,`,
|
|
6732
6732
|
` defaultOptions: ${JSON.stringify(docsConfig.defaultOptions)},`,
|
|
6733
|
+
` publishMarkdown: ${JSON.stringify(docsConfig.publishMarkdown)},`,
|
|
6733
6734
|
`});`
|
|
6734
6735
|
);
|
|
6735
6736
|
await writePluginDebugCode(config2.__meta.rootDir, "docs-plugin", code);
|
|
@@ -7774,6 +7775,7 @@ async function writeOutput(dir, {
|
|
|
7774
7775
|
// src/vite/prerender/prerender.ts
|
|
7775
7776
|
init_logger();
|
|
7776
7777
|
init_file_exists();
|
|
7778
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
7777
7779
|
import { readFile as readFile2, rm } from "node:fs/promises";
|
|
7778
7780
|
import os from "node:os";
|
|
7779
7781
|
import path21 from "node:path";
|
|
@@ -7944,7 +7946,9 @@ var prerender = async ({
|
|
|
7944
7946
|
paths.push(joinUrl(r.from));
|
|
7945
7947
|
}
|
|
7946
7948
|
}
|
|
7947
|
-
const maxThreads
|
|
7949
|
+
const { maxThreads, maxOldGenerationSizeMb } = getWorkerScaling(
|
|
7950
|
+
buildConfig?.prerender?.workers
|
|
7951
|
+
);
|
|
7948
7952
|
const start = performance.now();
|
|
7949
7953
|
const LOG_INTERVAL_MS = 3e4;
|
|
7950
7954
|
let lastLogTime = start;
|
|
@@ -7973,7 +7977,11 @@ var prerender = async ({
|
|
|
7973
7977
|
const pool = new Piscina({
|
|
7974
7978
|
filename: new URL("./worker.js", import.meta.url).href,
|
|
7975
7979
|
idleTimeout: 5e3,
|
|
7980
|
+
minThreads: 1,
|
|
7976
7981
|
maxThreads,
|
|
7982
|
+
resourceLimits: {
|
|
7983
|
+
maxOldGenerationSizeMb
|
|
7984
|
+
},
|
|
7977
7985
|
workerData: {
|
|
7978
7986
|
template: html,
|
|
7979
7987
|
distDir,
|
|
@@ -7985,12 +7993,6 @@ var prerender = async ({
|
|
|
7985
7993
|
const workerResults = await Promise.all(
|
|
7986
7994
|
paths.map(async (urlPath) => {
|
|
7987
7995
|
const result = await pool.run({ urlPath });
|
|
7988
|
-
if (result.statusCode < 400) {
|
|
7989
|
-
await pagefindIndex?.addHTMLFile({
|
|
7990
|
-
url: urlPath,
|
|
7991
|
-
content: result.html
|
|
7992
|
-
});
|
|
7993
|
-
}
|
|
7994
7996
|
completedCount++;
|
|
7995
7997
|
if (isTTY()) {
|
|
7996
7998
|
writeProgress(completedCount, paths.length, urlPath);
|
|
@@ -8008,9 +8010,6 @@ var prerender = async ({
|
|
|
8008
8010
|
return result;
|
|
8009
8011
|
})
|
|
8010
8012
|
);
|
|
8011
|
-
const pagefindWriteResult = await pagefindIndex?.writeFiles({
|
|
8012
|
-
outputPath: path21.join(distDir, "pagefind")
|
|
8013
|
-
});
|
|
8014
8013
|
const seconds = ((performance.now() - start) / 1e3).toFixed(1);
|
|
8015
8014
|
const message = `\u2713 finished prerendering ${paths.length} routes in ${seconds} seconds using ${maxThreads} workers`;
|
|
8016
8015
|
if (isTTY()) {
|
|
@@ -8019,10 +8018,38 @@ var prerender = async ({
|
|
|
8019
8018
|
} else {
|
|
8020
8019
|
logger.info(colors7.blue(message));
|
|
8021
8020
|
}
|
|
8022
|
-
if (
|
|
8023
|
-
|
|
8024
|
-
|
|
8021
|
+
if (pagefindIndex) {
|
|
8022
|
+
const pagesToIndex = workerResults.flatMap(
|
|
8023
|
+
({ statusCode, html: html2 }, i) => statusCode < 400 ? { url: paths[i], html: html2 } : []
|
|
8025
8024
|
);
|
|
8025
|
+
const BATCH_SIZE = 40;
|
|
8026
|
+
const pagefindStart = performance.now();
|
|
8027
|
+
for (let offset = 0; offset < pagesToIndex.length; offset += BATCH_SIZE) {
|
|
8028
|
+
const batch = pagesToIndex.slice(offset, offset + BATCH_SIZE);
|
|
8029
|
+
await Promise.all(
|
|
8030
|
+
batch.map(
|
|
8031
|
+
({ url, html: html2 }) => pagefindIndex.addHTMLFile({ url, content: html2 })
|
|
8032
|
+
)
|
|
8033
|
+
);
|
|
8034
|
+
if (isTTY()) {
|
|
8035
|
+
const done = offset + batch.length;
|
|
8036
|
+
writeLine(
|
|
8037
|
+
`pagefind indexing (${done}/${pagesToIndex.length}) ${colors7.dim(batch.at(-1)?.url ?? "")}`
|
|
8038
|
+
);
|
|
8039
|
+
}
|
|
8040
|
+
}
|
|
8041
|
+
if (isTTY()) writeLine("");
|
|
8042
|
+
const { outputPath } = await pagefindIndex.writeFiles({
|
|
8043
|
+
outputPath: path21.join(distDir, "pagefind")
|
|
8044
|
+
});
|
|
8045
|
+
if (outputPath) {
|
|
8046
|
+
const duration = (performance.now() - pagefindStart) / 1e3;
|
|
8047
|
+
logger.info(
|
|
8048
|
+
colors7.blue(
|
|
8049
|
+
`\u2713 pagefind index built in ${duration.toFixed(1)} seconds: ${outputPath}`
|
|
8050
|
+
)
|
|
8051
|
+
);
|
|
8052
|
+
}
|
|
8026
8053
|
}
|
|
8027
8054
|
const redirectUrls = getRedirectUrls(workerResults, config2.basePath);
|
|
8028
8055
|
await generateSitemap({
|
|
@@ -8073,6 +8100,52 @@ var prerender = async ({
|
|
|
8073
8100
|
}
|
|
8074
8101
|
return { workerResults, rewrites };
|
|
8075
8102
|
};
|
|
8103
|
+
var getWorkerScaling = (workersOverride) => {
|
|
8104
|
+
const PER_WORKER_HEAP_LIMIT_MB = 4096;
|
|
8105
|
+
const MAX_WORKERS = 8;
|
|
8106
|
+
const osTotalMb = Math.floor(os.totalmem() / (1024 * 1024));
|
|
8107
|
+
const cgroupMemMb = getContainerMemoryLimitMb();
|
|
8108
|
+
const totalMemMb = cgroupMemMb !== void 0 ? Math.min(cgroupMemMb, osTotalMb) : osTotalMb;
|
|
8109
|
+
const reservedMb = Math.max(2048, Math.floor(totalMemMb * 0.25));
|
|
8110
|
+
const availableForWorkersMb = totalMemMb - reservedMb;
|
|
8111
|
+
const memBasedWorkers = Math.max(
|
|
8112
|
+
1,
|
|
8113
|
+
Math.floor(availableForWorkersMb / PER_WORKER_HEAP_LIMIT_MB)
|
|
8114
|
+
);
|
|
8115
|
+
const cpuBasedWorkers = Math.max(1, Math.floor(os.cpus().length * 0.5));
|
|
8116
|
+
const defaultWorkers = Math.min(
|
|
8117
|
+
memBasedWorkers,
|
|
8118
|
+
cpuBasedWorkers,
|
|
8119
|
+
MAX_WORKERS
|
|
8120
|
+
);
|
|
8121
|
+
const validOverride = workersOverride && workersOverride > 0 ? workersOverride : void 0;
|
|
8122
|
+
const maxThreads = validOverride ?? defaultWorkers;
|
|
8123
|
+
const maxOldGenerationSizeMb = Math.min(
|
|
8124
|
+
PER_WORKER_HEAP_LIMIT_MB,
|
|
8125
|
+
Math.max(512, Math.floor(availableForWorkersMb / maxThreads))
|
|
8126
|
+
);
|
|
8127
|
+
return { maxThreads, maxOldGenerationSizeMb };
|
|
8128
|
+
};
|
|
8129
|
+
var getContainerMemoryLimitMb = () => {
|
|
8130
|
+
const CGROUP_PATHS = [
|
|
8131
|
+
"/sys/fs/cgroup/memory.max",
|
|
8132
|
+
// cgroup v2
|
|
8133
|
+
"/sys/fs/cgroup/memory/memory.limit_in_bytes"
|
|
8134
|
+
// cgroup v1
|
|
8135
|
+
];
|
|
8136
|
+
for (const filePath of CGROUP_PATHS) {
|
|
8137
|
+
try {
|
|
8138
|
+
const raw = readFileSync2(filePath, "utf8").trim();
|
|
8139
|
+
if (raw === "max") return void 0;
|
|
8140
|
+
const bytes = Number(raw);
|
|
8141
|
+
if (!Number.isFinite(bytes) || bytes > 2 ** 50) return void 0;
|
|
8142
|
+
return Math.floor(bytes / (1024 * 1024));
|
|
8143
|
+
} catch (err) {
|
|
8144
|
+
if (err.code !== "ENOENT") throw err;
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
return void 0;
|
|
8148
|
+
};
|
|
8076
8149
|
|
|
8077
8150
|
// src/vite/build.ts
|
|
8078
8151
|
var DIST_DIR = "dist";
|
|
@@ -7,8 +7,9 @@ declare global {
|
|
|
7
7
|
}) => string[] | undefined;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
export declare const MdxPage: ({ mdxComponent: MdxComponent, basePath, frontmatter, defaultOptions, __filepath, tableOfContents, excerpt, }: PropsWithChildren<Omit<MDXImport, "default"> & {
|
|
10
|
+
export declare const MdxPage: ({ mdxComponent: MdxComponent, basePath, frontmatter, defaultOptions, publishMarkdown, __filepath, tableOfContents, excerpt, }: PropsWithChildren<Omit<MDXImport, "default"> & {
|
|
11
11
|
basePath: string;
|
|
12
12
|
mdxComponent: MDXImport["default"];
|
|
13
13
|
defaultOptions?: MarkdownPluginDefaultOptions;
|
|
14
|
+
publishMarkdown?: boolean;
|
|
14
15
|
}>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type McpServerData } from "./mcp-configs.js";
|
|
1
2
|
export declare const MCPEndpoint: ({ serverUrl, operationPath, summary, data, }: {
|
|
2
3
|
serverUrl?: string;
|
|
3
4
|
operationPath?: string;
|
|
4
|
-
data?:
|
|
5
|
+
data?: McpServerData;
|
|
5
6
|
summary?: string;
|
|
6
7
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type McpServerData = boolean | Record<string, unknown>;
|
|
2
|
+
export interface AuthHeader {
|
|
3
|
+
headerName: string;
|
|
4
|
+
placeholder: string;
|
|
5
|
+
}
|
|
6
|
+
export type AuthType = "none" | "apiKey" | "oauth";
|
|
7
|
+
export declare const getAuthType: (data?: McpServerData) => AuthType;
|
|
8
|
+
export declare const getAuthHeader: (data?: McpServerData) => AuthHeader | undefined;
|
|
9
|
+
export interface McpSubApp {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
supportedAuth: AuthType[];
|
|
13
|
+
}
|
|
14
|
+
export interface McpApp {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
subApps: McpSubApp[];
|
|
18
|
+
}
|
|
19
|
+
export declare const MCP_APPS: McpApp[];
|
|
20
|
+
export declare const getVisibleApps: (authType: AuthType) => McpApp[];
|
|
21
|
+
export declare const getMcpServerName: (data?: McpServerData, summary?: string) => string;
|
|
22
|
+
export declare const getMcpUrl: (serverUrl?: string, operationPath?: string) => string;
|
|
23
|
+
export declare const getClaudeCodeCommand: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
|
|
24
|
+
export declare const getCodexCliCommand: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
|
|
25
|
+
export declare const getCursorConfig: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
|
|
26
|
+
export declare const getVscodeConfig: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
|
|
27
|
+
export declare const getCodexConfig: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
|
|
28
|
+
export declare const getGenericConfig: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
|
|
@@ -34,7 +34,10 @@ If you don't have an Auth0 account, you can sign up for a
|
|
|
34
34
|
- Production: `https://your-site.com/oauth/callback`
|
|
35
35
|
- Preview (wildcard): `https://*.your-domain.com/oauth/callback`
|
|
36
36
|
- Local Development: `http://localhost:3000/oauth/callback`
|
|
37
|
-
- **Allowed Logout URLs**:
|
|
37
|
+
- **Allowed Logout URLs**:
|
|
38
|
+
- Production: `https://your-site.com/oauth/logout-callback`
|
|
39
|
+
- Preview (wildcard): `https://*.your-domain.com/oauth/logout-callback`
|
|
40
|
+
- Local Development: `http://localhost:3000/oauth/logout-callback`
|
|
38
41
|
|
|
39
42
|
- **Allowed Web Origins**:
|
|
40
43
|
- Production: `https://your-site.com`
|
|
@@ -112,8 +115,8 @@ To enable logout for your Auth0 application:
|
|
|
112
115
|
|
|
113
116
|
1. Ensure your **Allowed Logout URLs** are configured in Auth0 (see
|
|
114
117
|
[Configure Auth0 Application](#setup-steps) above)
|
|
115
|
-
2. The logout URL
|
|
116
|
-
production)
|
|
118
|
+
2. The logout URL must use the `/oauth/logout-callback` path (e.g.,
|
|
119
|
+
`https://your-site.com/oauth/logout-callback` for production)
|
|
117
120
|
|
|
118
121
|
For older tenants, you may need to enable **RP-Initiated Logout** in your tenant settings. See the
|
|
119
122
|
[Auth0 logout documentation](https://auth0.com/docs/authenticate/login/logout/log-users-out-of-auth0)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OpenID Connect (OIDC)
|
|
3
|
+
sidebar_label: OpenID Connect
|
|
4
|
+
description:
|
|
5
|
+
Configure any OpenID Connect compliant identity provider (Okta, Keycloak, Authentik, etc.) as the
|
|
6
|
+
authentication provider for Zudoku.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Zudoku supports any identity provider that implements the
|
|
10
|
+
[OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) protocol via the generic
|
|
11
|
+
`openid` provider type. This includes Okta, Keycloak, Authentik, Ory, ZITADEL, AWS Cognito, Google
|
|
12
|
+
Identity, and most enterprise IdPs.
|
|
13
|
+
|
|
14
|
+
## Configuration
|
|
15
|
+
|
|
16
|
+
Add the `authentication` property to your [Zudoku configuration](./overview.md):
|
|
17
|
+
|
|
18
|
+
```typescript title="zudoku.config.ts"
|
|
19
|
+
{
|
|
20
|
+
// ...
|
|
21
|
+
authentication: {
|
|
22
|
+
type: "openid",
|
|
23
|
+
clientId: "<your-client-id>",
|
|
24
|
+
issuer: "<the-issuer-url>",
|
|
25
|
+
scopes: ["openid", "profile", "email"], // Optional
|
|
26
|
+
},
|
|
27
|
+
// ...
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Option | Required | Description |
|
|
32
|
+
| ---------- | -------- | -------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `clientId` | Yes | The OAuth client ID issued by your provider. |
|
|
34
|
+
| `issuer` | Yes | The issuer URL. Zudoku discovers endpoints from `<issuer>/.well-known/openid-configuration`. |
|
|
35
|
+
| `scopes` | No | Scopes to request. Defaults to `["openid", "profile", "email"]`. |
|
|
36
|
+
|
|
37
|
+
## Provider Setup
|
|
38
|
+
|
|
39
|
+
Register Zudoku as a public SPA / single page application client in your identity provider and set:
|
|
40
|
+
|
|
41
|
+
- Callback / Redirect URI to `https://your-site.com/oauth/callback`
|
|
42
|
+
- For local development, add `http://localhost:3000/oauth/callback`
|
|
43
|
+
- If your provider supports wildcards, add `https://*.your-domain.com/oauth/callback` for preview
|
|
44
|
+
environments
|
|
45
|
+
- Add your site origin to the list of allowed CORS origins
|
|
46
|
+
- Enable the `Authorization Code` grant with PKCE and the `Refresh Token` grant
|
|
47
|
+
|
|
48
|
+
### Okta
|
|
49
|
+
|
|
50
|
+
1. In the Okta admin console go to **Applications** → **Applications** → **Create App Integration**.
|
|
51
|
+
2. Select **OIDC - OpenID Connect** and **Single Page Application**.
|
|
52
|
+
3. Set **Sign-in redirect URIs** to `https://your-site.com/oauth/callback` (add
|
|
53
|
+
`http://localhost:3000/oauth/callback` for local development).
|
|
54
|
+
4. Under **Assignments**, assign the users or groups that should have access.
|
|
55
|
+
5. After creating the app, copy the **Client ID**. Your issuer is your Okta domain, for example
|
|
56
|
+
`https://your-tenant.okta.com` or a custom authorization server like
|
|
57
|
+
`https://your-tenant.okta.com/oauth2/default`.
|
|
58
|
+
6. Under **Security** → **API** → **Trusted Origins**, add your site origin for both CORS and
|
|
59
|
+
Redirect.
|
|
60
|
+
|
|
61
|
+
```typescript title="zudoku.config.ts"
|
|
62
|
+
{
|
|
63
|
+
authentication: {
|
|
64
|
+
type: "openid",
|
|
65
|
+
clientId: "<your-okta-client-id>",
|
|
66
|
+
issuer: "https://your-tenant.okta.com/oauth2/default",
|
|
67
|
+
scopes: ["openid", "profile", "email"],
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Keycloak
|
|
73
|
+
|
|
74
|
+
Use the realm issuer URL:
|
|
75
|
+
|
|
76
|
+
```typescript title="zudoku.config.ts"
|
|
77
|
+
{
|
|
78
|
+
authentication: {
|
|
79
|
+
type: "openid",
|
|
80
|
+
clientId: "zudoku",
|
|
81
|
+
issuer: "https://keycloak.example.com/realms/<your-realm>",
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
In the realm, create a client with **Client type** `OpenID Connect`, **Access type** `public`, and
|
|
87
|
+
enable **Standard Flow** (Authorization Code).
|
|
88
|
+
|
|
89
|
+
## Verifying the Issuer
|
|
90
|
+
|
|
91
|
+
You can confirm your issuer URL is correct by opening `<issuer>/.well-known/openid-configuration` in
|
|
92
|
+
a browser. It should return a JSON document listing `authorization_endpoint`, `token_endpoint`,
|
|
93
|
+
`userinfo_endpoint`, and `jwks_uri`.
|
|
94
|
+
|
|
95
|
+
## User Profile
|
|
96
|
+
|
|
97
|
+
After sign-in Zudoku calls the provider's
|
|
98
|
+
[UserInfo endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) and reads
|
|
99
|
+
`name`, `email`, `picture`, and `email_verified` from the response. Map these claims in your
|
|
100
|
+
provider if they are not emitted by default.
|
|
101
|
+
|
|
102
|
+
## Troubleshooting
|
|
103
|
+
|
|
104
|
+
- **Discovery fails**: verify `<issuer>/.well-known/openid-configuration` resolves and matches the
|
|
105
|
+
`issuer` value in the document.
|
|
106
|
+
- **CORS errors on token / userinfo**: add your site origin to the provider's allowed origins.
|
|
107
|
+
- **Redirect URI mismatch**: the URI registered with the provider must match the Zudoku origin
|
|
108
|
+
exactly, including protocol and port.
|
|
109
|
+
- **Missing profile fields**: ensure `profile` and `email` scopes are granted and that the provider
|
|
110
|
+
includes `name`, `email`, and `picture` claims in the UserInfo response.
|
|
@@ -15,8 +15,8 @@ authentication provider you use.
|
|
|
15
15
|
|
|
16
16
|
## Authentication Providers
|
|
17
17
|
|
|
18
|
-
Zudoku supports Clerk, Auth0, Supabase, Firebase, Azure B2C, and any OpenID provider
|
|
19
|
-
|
|
18
|
+
Zudoku supports Clerk, Auth0, Supabase, Firebase, Azure B2C, and any OpenID Connect provider
|
|
19
|
+
(including Okta, Keycloak, Authentik, and PingFederate).
|
|
20
20
|
|
|
21
21
|
Not seeing your authentication provider? [Let us know](https://github.com/zuplo/zudoku/issues)
|
|
22
22
|
|
|
@@ -96,6 +96,9 @@ When configuring your OpenID provider, you will need to set the following:
|
|
|
96
96
|
By default, the scopes "openid", "profile", and "email" are requested. You can customize these by
|
|
97
97
|
providing your own array of scopes.
|
|
98
98
|
|
|
99
|
+
For provider-specific guides (Okta, Keycloak, etc.), see the
|
|
100
|
+
[OpenID Connect setup page](./authentication-openid.md).
|
|
101
|
+
|
|
99
102
|
### Firebase
|
|
100
103
|
|
|
101
104
|
For Firebase authentication, you will need your Firebase project configuration. You can find this in
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: x-code-samples
|
|
3
|
+
sidebar_icon: code
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use `x-code-samples` (or `x-codeSamples`) to provide custom code snippets for an API operation. When
|
|
7
|
+
present, these samples appear in the sidecar panel alongside the auto-generated request examples.
|
|
8
|
+
|
|
9
|
+
## Location
|
|
10
|
+
|
|
11
|
+
The extension is added at the **Operation Object** level.
|
|
12
|
+
|
|
13
|
+
| Option | Type | Description |
|
|
14
|
+
| ---------------- | ---------------------- | ----------------------------- |
|
|
15
|
+
| `x-code-samples` | `[Code Sample Object]` | Array of custom code samples. |
|
|
16
|
+
| `x-codeSamples` | `[Code Sample Object]` | Alias for `x-code-samples`. |
|
|
17
|
+
|
|
18
|
+
## Code Sample Object
|
|
19
|
+
|
|
20
|
+
| Property | Type | Required | Description |
|
|
21
|
+
| -------- | -------- | -------- | ---------------------------------------------------- |
|
|
22
|
+
| `lang` | `string` | Yes | Language identifier used for syntax highlighting. |
|
|
23
|
+
| `label` | `string` | No | Display label for the tab. Defaults to `lang` value. |
|
|
24
|
+
| `source` | `string` | Yes | The code snippet content. |
|
|
25
|
+
|
|
26
|
+
## Example
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
paths:
|
|
30
|
+
/users:
|
|
31
|
+
get:
|
|
32
|
+
summary: List users
|
|
33
|
+
x-code-samples:
|
|
34
|
+
- lang: curl
|
|
35
|
+
label: cURL
|
|
36
|
+
source: |
|
|
37
|
+
curl -X GET https://api.example.com/users \
|
|
38
|
+
-H "Authorization: Bearer $TOKEN"
|
|
39
|
+
- lang: python
|
|
40
|
+
label: Python
|
|
41
|
+
source: |
|
|
42
|
+
import requests
|
|
43
|
+
|
|
44
|
+
response = requests.get(
|
|
45
|
+
"https://api.example.com/users",
|
|
46
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
47
|
+
)
|
|
48
|
+
- lang: javascript
|
|
49
|
+
label: JavaScript
|
|
50
|
+
source: |
|
|
51
|
+
const response = await fetch("https://api.example.com/users", {
|
|
52
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
53
|
+
});
|
|
54
|
+
responses:
|
|
55
|
+
"200":
|
|
56
|
+
description: Successful response
|
|
57
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: x-displayName
|
|
3
|
+
sidebar_icon: tag
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use `x-displayName` to override the display label for a tag in the API navigation and documentation.
|
|
7
|
+
By default, Zudoku uses the tag's `name` field. This extension lets you set a different
|
|
8
|
+
human-friendly label without changing the tag name used for grouping operations.
|
|
9
|
+
|
|
10
|
+
## Location
|
|
11
|
+
|
|
12
|
+
The extension is added at the **Tag Object** level.
|
|
13
|
+
|
|
14
|
+
| Option | Type | Description |
|
|
15
|
+
| --------------- | -------- | ------------------------------------------------------ |
|
|
16
|
+
| `x-displayName` | `string` | Custom display name shown in the sidebar and headings. |
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
tags:
|
|
22
|
+
- name: ai-ops
|
|
23
|
+
description: AI-powered operations
|
|
24
|
+
x-displayName: AI Operations
|
|
25
|
+
- name: user-mgmt
|
|
26
|
+
description: User management endpoints
|
|
27
|
+
x-displayName: User Management
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Without `x-displayName`, the sidebar would show `ai-ops` and `user-mgmt`. With it, the sidebar
|
|
31
|
+
displays `AI Operations` and `User Management` instead.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: x-mcp-server
|
|
3
|
+
sidebar_icon: bot
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use `x-mcp-server` to mark an individual OpenAPI operation as an
|
|
7
|
+
[MCP](https://modelcontextprotocol.io/) (Model Context Protocol) endpoint. When Zudoku detects this
|
|
8
|
+
extension, it replaces the standard request/response view with a dedicated MCP card showing the
|
|
9
|
+
endpoint URL, a copy button, and tabbed installation instructions for popular AI clients.
|
|
10
|
+
|
|
11
|
+
:::note
|
|
12
|
+
|
|
13
|
+
The `x-mcp-server` extension is applied at the **operation level** to mark specific endpoints. If
|
|
14
|
+
you want to describe an entire MCP server at the root level of your OpenAPI document, see the
|
|
15
|
+
[`x-mcp` extension](./x-mcp).
|
|
16
|
+
|
|
17
|
+
:::
|
|
18
|
+
|
|
19
|
+
## Location
|
|
20
|
+
|
|
21
|
+
The `x-mcp-server` extension is added at the **Operation Object** level.
|
|
22
|
+
|
|
23
|
+
| Option | Type | Description |
|
|
24
|
+
| -------------- | -------------------------------- | ---------------------------------------------- |
|
|
25
|
+
| `x-mcp-server` | `boolean` or `MCP Server Object` | Marks the operation as an MCP server endpoint. |
|
|
26
|
+
|
|
27
|
+
## MCP Server Object
|
|
28
|
+
|
|
29
|
+
When using the object form, the following properties are available:
|
|
30
|
+
|
|
31
|
+
| Property | Type | Required | Description |
|
|
32
|
+
| --------- | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `name` | `string` | No | Display name used in the generated client configuration snippets. Falls back to the operation `summary`, then `"mcp-server"` |
|
|
34
|
+
| `version` | `string` | No | Version metadata |
|
|
35
|
+
| `tools` | `[Tool Object]` | No | Array of tools provided by the MCP server |
|
|
36
|
+
|
|
37
|
+
Each item in the `tools` array:
|
|
38
|
+
|
|
39
|
+
| Property | Type | Required | Description |
|
|
40
|
+
| ------------- | -------- | -------- | ------------------------------- |
|
|
41
|
+
| `name` | `string` | Yes | Tool name |
|
|
42
|
+
| `description` | `string` | No | Human-readable tool description |
|
|
43
|
+
|
|
44
|
+
## MCP URL resolution
|
|
45
|
+
|
|
46
|
+
The displayed MCP URL is constructed from the **server URL** of the API and the **path** of the
|
|
47
|
+
operation. The server URL comes from the OpenAPI `servers` array (or the operation-level `servers`
|
|
48
|
+
override if present).
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
### Boolean shorthand
|
|
53
|
+
|
|
54
|
+
Use `true` to enable MCP UI without specifying metadata. The operation's `summary` is used as the
|
|
55
|
+
server name.
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
paths:
|
|
59
|
+
/mcp:
|
|
60
|
+
post:
|
|
61
|
+
summary: My MCP Server
|
|
62
|
+
x-mcp-server: true
|
|
63
|
+
responses:
|
|
64
|
+
"200":
|
|
65
|
+
description: MCP response
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Object form
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
paths:
|
|
72
|
+
/mcp:
|
|
73
|
+
post:
|
|
74
|
+
summary: My MCP Server
|
|
75
|
+
x-mcp-server:
|
|
76
|
+
name: my-mcp-server
|
|
77
|
+
version: 1.0.0
|
|
78
|
+
tools:
|
|
79
|
+
- name: search_docs
|
|
80
|
+
description: Search the documentation
|
|
81
|
+
- name: get_page
|
|
82
|
+
description: Retrieve a specific documentation page
|
|
83
|
+
responses:
|
|
84
|
+
"200":
|
|
85
|
+
description: MCP response
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Generated UI
|
|
89
|
+
|
|
90
|
+
When detected, the operation page shows:
|
|
91
|
+
|
|
92
|
+
- **MCP Endpoint card** with the full URL and a copy button
|
|
93
|
+
- **AI Tool Configuration** tabs with setup instructions for:
|
|
94
|
+
- **Claude** — add via Connectors UI or `claude mcp add` CLI command
|
|
95
|
+
- **ChatGPT** — app setup via Settings → Apps → Advanced Settings
|
|
96
|
+
- **Cursor** — `mcp.json` configuration (global or project-level)
|
|
97
|
+
- **VS Code** — `.vscode/mcp.json` with native HTTP transport for GitHub Copilot
|
|
98
|
+
- **Generic** — standard `mcp.json` format compatible with most MCP clients
|
|
99
|
+
|
|
100
|
+
The standard method badge, request body, parameters, and sidecar panels are hidden for MCP
|
|
101
|
+
endpoints.
|
|
102
|
+
|
|
103
|
+
For a full walkthrough including Zudoku configuration, see the
|
|
104
|
+
[Documenting MCP Servers guide](/docs/guides/mcp-servers).
|