zudoku 0.74.3 → 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 +87 -15
- 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/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/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",
|
|
@@ -7775,6 +7775,7 @@ async function writeOutput(dir, {
|
|
|
7775
7775
|
// src/vite/prerender/prerender.ts
|
|
7776
7776
|
init_logger();
|
|
7777
7777
|
init_file_exists();
|
|
7778
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
7778
7779
|
import { readFile as readFile2, rm } from "node:fs/promises";
|
|
7779
7780
|
import os from "node:os";
|
|
7780
7781
|
import path21 from "node:path";
|
|
@@ -7945,7 +7946,9 @@ var prerender = async ({
|
|
|
7945
7946
|
paths.push(joinUrl(r.from));
|
|
7946
7947
|
}
|
|
7947
7948
|
}
|
|
7948
|
-
const maxThreads
|
|
7949
|
+
const { maxThreads, maxOldGenerationSizeMb } = getWorkerScaling(
|
|
7950
|
+
buildConfig?.prerender?.workers
|
|
7951
|
+
);
|
|
7949
7952
|
const start = performance.now();
|
|
7950
7953
|
const LOG_INTERVAL_MS = 3e4;
|
|
7951
7954
|
let lastLogTime = start;
|
|
@@ -7974,7 +7977,11 @@ var prerender = async ({
|
|
|
7974
7977
|
const pool = new Piscina({
|
|
7975
7978
|
filename: new URL("./worker.js", import.meta.url).href,
|
|
7976
7979
|
idleTimeout: 5e3,
|
|
7980
|
+
minThreads: 1,
|
|
7977
7981
|
maxThreads,
|
|
7982
|
+
resourceLimits: {
|
|
7983
|
+
maxOldGenerationSizeMb
|
|
7984
|
+
},
|
|
7978
7985
|
workerData: {
|
|
7979
7986
|
template: html,
|
|
7980
7987
|
distDir,
|
|
@@ -7986,12 +7993,6 @@ var prerender = async ({
|
|
|
7986
7993
|
const workerResults = await Promise.all(
|
|
7987
7994
|
paths.map(async (urlPath) => {
|
|
7988
7995
|
const result = await pool.run({ urlPath });
|
|
7989
|
-
if (result.statusCode < 400) {
|
|
7990
|
-
await pagefindIndex?.addHTMLFile({
|
|
7991
|
-
url: urlPath,
|
|
7992
|
-
content: result.html
|
|
7993
|
-
});
|
|
7994
|
-
}
|
|
7995
7996
|
completedCount++;
|
|
7996
7997
|
if (isTTY()) {
|
|
7997
7998
|
writeProgress(completedCount, paths.length, urlPath);
|
|
@@ -8009,9 +8010,6 @@ var prerender = async ({
|
|
|
8009
8010
|
return result;
|
|
8010
8011
|
})
|
|
8011
8012
|
);
|
|
8012
|
-
const pagefindWriteResult = await pagefindIndex?.writeFiles({
|
|
8013
|
-
outputPath: path21.join(distDir, "pagefind")
|
|
8014
|
-
});
|
|
8015
8013
|
const seconds = ((performance.now() - start) / 1e3).toFixed(1);
|
|
8016
8014
|
const message = `\u2713 finished prerendering ${paths.length} routes in ${seconds} seconds using ${maxThreads} workers`;
|
|
8017
8015
|
if (isTTY()) {
|
|
@@ -8020,10 +8018,38 @@ var prerender = async ({
|
|
|
8020
8018
|
} else {
|
|
8021
8019
|
logger.info(colors7.blue(message));
|
|
8022
8020
|
}
|
|
8023
|
-
if (
|
|
8024
|
-
|
|
8025
|
-
|
|
8021
|
+
if (pagefindIndex) {
|
|
8022
|
+
const pagesToIndex = workerResults.flatMap(
|
|
8023
|
+
({ statusCode, html: html2 }, i) => statusCode < 400 ? { url: paths[i], html: html2 } : []
|
|
8026
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
|
+
}
|
|
8027
8053
|
}
|
|
8028
8054
|
const redirectUrls = getRedirectUrls(workerResults, config2.basePath);
|
|
8029
8055
|
await generateSitemap({
|
|
@@ -8074,6 +8100,52 @@ var prerender = async ({
|
|
|
8074
8100
|
}
|
|
8075
8101
|
return { workerResults, rewrites };
|
|
8076
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
|
+
};
|
|
8077
8149
|
|
|
8078
8150
|
// src/vite/build.ts
|
|
8079
8151
|
var DIST_DIR = "dist";
|
|
@@ -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).
|