handoff-app 2.0.0-5 → 2.0.0-6
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/README.md +45 -0
- package/dist/app-builder/build.js +9 -2
- package/dist/app-builder/client-config.js +1 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +4 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +2 -1
- package/dist/types/config.d.ts +7 -0
- package/package.json +4 -2
- package/src/app/components/CopyToClipboard.tsx +27 -26
- package/src/app/components/Layout/Header.tsx +4 -12
- package/src/app/components/Layout/RuntimeModeBadge.tsx +18 -0
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +349 -0
- package/src/app/components/McpIntegration/McpIcon.tsx +10 -0
- package/src/app/components/Navigation/MobileNav.tsx +3 -1
- package/src/app/components/ui/dialog.tsx +73 -0
- package/src/app/lib/docs-api/backend.ts +5 -1
- package/src/app/lib/docs-api/page-discovery.ts +1 -6
- package/src/app/lib/docs-api/page-rendering.ts +21 -1
- package/src/app/lib/docs-api/records.ts +25 -10
- package/src/app/lib/docs-api/registry.ts +4 -2
- package/src/app/lib/docs-api/runtime-config.ts +5 -0
- package/src/app/lib/docs-api/search.ts +7 -2
- package/src/app/lib/mcp/auth.ts +64 -0
- package/src/app/lib/mcp/index.ts +2 -0
- package/src/app/lib/mcp/server.ts +343 -0
- package/src/app/lib/mcp/shape.ts +595 -0
- package/src/app/next.config.mjs +3 -0
- package/src/app/pages/[...slug]/index.tsx +16 -30
- package/src/app/pages/api/mcp.ts +56 -0
- package/src/app/pages/index.tsx +10 -9
- package/src/app-builder/build.ts +9 -2
- package/src/app-builder/client-config.ts +2 -1
- package/src/config/defaults.ts +3 -0
- package/src/config/index.ts +1 -1
- package/src/types/config.ts +7 -0
- package/templates/default/README.md.tpl +10 -0
- package/templates/default/handoff.config.js.tpl +6 -3
- package/templates/default/handoff.config.ts.tpl +6 -3
- package/templates/default/handoff.config.with-examples.js.tpl +6 -3
- package/templates/default/handoff.config.with-examples.ts.tpl +6 -3
package/README.md
CHANGED
|
@@ -385,6 +385,51 @@ runtime: {
|
|
|
385
385
|
},
|
|
386
386
|
```
|
|
387
387
|
|
|
388
|
+
## MCP
|
|
389
|
+
|
|
390
|
+
The documentation app serves a Model Context Protocol endpoint at `/api/mcp/`, so
|
|
391
|
+
coding agents can look up the pages, components and tokens that already exist instead
|
|
392
|
+
of inventing markup and values. It is stateless Streamable HTTP over `POST`;
|
|
393
|
+
there is nothing to start and no extra port. Keep the trailing slash: the app
|
|
394
|
+
sets `trailingSlash: true`, so `/api/mcp` answers with a 308 redirect.
|
|
395
|
+
|
|
396
|
+
| Target | Endpoint | Credential |
|
|
397
|
+
| --- | --- | --- |
|
|
398
|
+
| Workspace (`dev` / `start`) | `http://localhost:3000/api/mcp/` | none, as with `/api/docs/*` |
|
|
399
|
+
| Registry (standalone or Vercel) | `<registry url>/api/mcp/` | an access token, read-only is enough |
|
|
400
|
+
| Static export | not available, a static site has no API routes | n/a |
|
|
401
|
+
|
|
402
|
+
Registry tokens come from `handoff-app login` or Account settings in the
|
|
403
|
+
registry itself, and are the same credentials the rest of the registry API takes.
|
|
404
|
+
|
|
405
|
+
The endpoint is served unless a project turns it off:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
runtime: {
|
|
409
|
+
mcp: false,
|
|
410
|
+
},
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Like `runtime.mode`, this is config-only and baked at build time, so changing it
|
|
414
|
+
in a deployed registry means a rebuild. A disabled build answers 404 on the route,
|
|
415
|
+
drops the MCP SDK from the packaged bundle, and shows no connect affordance at all
|
|
416
|
+
rather than one that fails.
|
|
417
|
+
|
|
418
|
+
The documentation app carries its own endpoint and a ready-made client config
|
|
419
|
+
for Claude Code, Cursor and VS Code behind the plug icon in the header. By hand:
|
|
420
|
+
|
|
421
|
+
```json
|
|
422
|
+
{
|
|
423
|
+
"mcpServers": {
|
|
424
|
+
"handoff": {
|
|
425
|
+
"type": "http",
|
|
426
|
+
"url": "https://registry.example.com/api/mcp/",
|
|
427
|
+
"headers": { "Authorization": "Bearer hnd_..." }
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
388
433
|
## Configuration
|
|
389
434
|
|
|
390
435
|
Configuration is read from `handoff.config.ts`, `.js`, `.cjs`, or `.json`, in
|
|
@@ -40,6 +40,7 @@ const p = __importStar(require("@clack/prompts"));
|
|
|
40
40
|
const cross_spawn_1 = __importDefault(require("cross-spawn"));
|
|
41
41
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
42
42
|
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const config_1 = require("../config");
|
|
43
44
|
const components_1 = require("../pipeline/components");
|
|
44
45
|
const patterns_1 = require("../pipeline/patterns");
|
|
45
46
|
const resolve_1 = require("../registry/asset-storage/resolve");
|
|
@@ -216,6 +217,7 @@ const initializeProjectApp = (handoff_1, ...args_1) => __awaiter(void 0, [handof
|
|
|
216
217
|
// Resolved runtime mode + registry connection inputs (names only) baked into the Next bundle so the
|
|
217
218
|
// deployed registry app resolves its mode/DB env-var name without any build-machine filesystem.
|
|
218
219
|
const escapedRuntimeMode = escapeForSingleQuotedJsString(runtimeMode);
|
|
220
|
+
const escapedMcpEnabled = escapeForSingleQuotedJsString(String((0, config_1.isMcpEnabled)(handoff.config)));
|
|
219
221
|
const escapedRegistryDriver = escapeForSingleQuotedJsString((0, driver_1.resolveRegistryDriver)(handoff.config));
|
|
220
222
|
const escapedDatabaseUrlEnv = escapeForSingleQuotedJsString((0, driver_1.resolveDatabaseUrlEnv)(handoff.config));
|
|
221
223
|
// Asset storage selection baked (provider + module + env-var names + non-secret options JSON).
|
|
@@ -233,6 +235,7 @@ const initializeProjectApp = (handoff_1, ...args_1) => __awaiter(void 0, [handof
|
|
|
233
235
|
'%HANDOFF_EXPORT_PATH%': escapedExportPath,
|
|
234
236
|
'%HANDOFF_WEBSOCKET_PORT%': escapedWebsocketPort,
|
|
235
237
|
'%HANDOFF_RUNTIME_MODE%': escapedRuntimeMode,
|
|
238
|
+
'%HANDOFF_MCP_ENABLED%': escapedMcpEnabled,
|
|
236
239
|
'%HANDOFF_REGISTRY_DRIVER%': escapedRegistryDriver,
|
|
237
240
|
'%HANDOFF_REGISTRY_DATABASE_URL_ENV%': escapedDatabaseUrlEnv,
|
|
238
241
|
'%HANDOFF_ASSET_STORAGE_ADAPTER%': escapedAssetStorageAdapter,
|
|
@@ -426,12 +429,16 @@ containers, custom Node servers, and other non-Vercel hosts.
|
|
|
426
429
|
/**
|
|
427
430
|
* Resolve the set of npm packages the packaged registry app must be able to `require`/`import` at
|
|
428
431
|
* runtime, given the configured database driver. `next`/`react`/`react-dom` run the server and React
|
|
429
|
-
* runtime; `drizzle-orm` backs both the request-time DB client and the migration runner; the
|
|
430
|
-
*
|
|
432
|
+
* runtime; `drizzle-orm` backs both the request-time DB client and the migration runner; the MCP SDK
|
|
433
|
+
* (and its `zod` peer) is only needed when `/api/mcp/` is enabled; the driver package is
|
|
434
|
+
* driver-specific (the Neon serverless driver also needs `ws` for its Node WebSocket transport).
|
|
431
435
|
*/
|
|
432
436
|
const getRequiredRegistryRuntimeModules = (handoff) => {
|
|
433
437
|
var _a;
|
|
434
438
|
const base = ['next', 'next-auth', 'react', 'react-dom', 'drizzle-orm'];
|
|
439
|
+
if ((0, config_1.isMcpEnabled)(handoff.config)) {
|
|
440
|
+
base.push('@modelcontextprotocol/sdk', 'zod');
|
|
441
|
+
}
|
|
435
442
|
const driver = (0, driver_1.resolveRegistryDriver)(handoff.config);
|
|
436
443
|
const driverModules = driver === 'neon' ? ['@neondatabase/serverless', 'ws'] : ['pg'];
|
|
437
444
|
// Asset-storage SDKs the deployed registry must be able to load at request time. The pre-packaged
|
|
@@ -65,6 +65,7 @@ const buildServerRuntimeConfig = (config, modeOverride) => {
|
|
|
65
65
|
const assetStorage = (0, resolve_1.resolveAssetStorageFromConfig)(config);
|
|
66
66
|
return {
|
|
67
67
|
mode: (_b = modeOverride !== null && modeOverride !== void 0 ? modeOverride : (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.mode) !== null && _b !== void 0 ? _b : 'workspace',
|
|
68
|
+
mcp: (0, config_1.isMcpEnabled)(config),
|
|
68
69
|
registry: {
|
|
69
70
|
driver: (0, driver_1.resolveRegistryDriver)(config),
|
|
70
71
|
databaseUrlEnv: (0, driver_1.resolveDatabaseUrlEnv)(config),
|
|
@@ -6,6 +6,8 @@ export interface ImageStyle {
|
|
|
6
6
|
width: number;
|
|
7
7
|
description: string;
|
|
8
8
|
}
|
|
9
|
+
/** Whether the MCP endpoint is served. On unless `runtime.mcp` is explicitly `false`. */
|
|
10
|
+
export declare const isMcpEnabled: (config: Config | null | undefined) => boolean;
|
|
9
11
|
export declare const defaultConfig: () => Config;
|
|
10
12
|
/**
|
|
11
13
|
* Retrieves the client configuration from the provided handoff configuration.
|
package/dist/config/defaults.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getClientConfig = exports.defaultConfig = void 0;
|
|
3
|
+
exports.getClientConfig = exports.defaultConfig = exports.isMcpEnabled = void 0;
|
|
4
4
|
const connection_1 = require("../registry/connection");
|
|
5
|
+
/** Whether the MCP endpoint is served. On unless `runtime.mcp` is explicitly `false`. */
|
|
6
|
+
const isMcpEnabled = (config) => { var _a; return ((_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.mcp) !== false; };
|
|
7
|
+
exports.isMcpEnabled = isMcpEnabled;
|
|
5
8
|
const defaultConfig = () => {
|
|
6
9
|
var _a, _b, _c, _d;
|
|
7
10
|
return ({
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defaultConfig, getClientConfig } from './defaults';
|
|
1
|
+
export { defaultConfig, getClientConfig, isMcpEnabled } from './defaults';
|
|
2
2
|
export type { ImageStyle } from './defaults';
|
|
3
3
|
export { HandoffConfigError, initConfig, initConfigWithMetadata } from './loader';
|
|
4
4
|
export type { ConfigLoadContext } from './loader';
|
package/dist/config/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.writeEntries = exports.isEntryCovered = exports.validateConfig = exports.initRuntimeConfig = exports.defineConfig = exports.initConfigWithMetadata = exports.initConfig = exports.HandoffConfigError = exports.getClientConfig = exports.defaultConfig = void 0;
|
|
3
|
+
exports.writeEntries = exports.isEntryCovered = exports.validateConfig = exports.initRuntimeConfig = exports.defineConfig = exports.initConfigWithMetadata = exports.initConfig = exports.HandoffConfigError = exports.isMcpEnabled = exports.getClientConfig = exports.defaultConfig = void 0;
|
|
4
4
|
// Config defaults and client config
|
|
5
5
|
var defaults_1 = require("./defaults");
|
|
6
6
|
Object.defineProperty(exports, "defaultConfig", { enumerable: true, get: function () { return defaults_1.defaultConfig; } });
|
|
7
7
|
Object.defineProperty(exports, "getClientConfig", { enumerable: true, get: function () { return defaults_1.getClientConfig; } });
|
|
8
|
+
Object.defineProperty(exports, "isMcpEnabled", { enumerable: true, get: function () { return defaults_1.isMcpEnabled; } });
|
|
8
9
|
// Config file loading
|
|
9
10
|
var loader_1 = require("./loader");
|
|
10
11
|
Object.defineProperty(exports, "HandoffConfigError", { enumerable: true, get: function () { return loader_1.HandoffConfigError; } });
|
package/dist/types/config.d.ts
CHANGED
|
@@ -127,6 +127,13 @@ export interface HandoffRuntimeConfig {
|
|
|
127
127
|
* Mode is never inferred from environment variables, database settings, or token presence.
|
|
128
128
|
*/
|
|
129
129
|
mode?: RuntimeMode;
|
|
130
|
+
/**
|
|
131
|
+
* Whether the app serves the `/api/mcp/` Model Context Protocol endpoint. Set to `false` to leave
|
|
132
|
+
* the endpoint and the header affordance out of the build; the route then answers 404. Like
|
|
133
|
+
* `mode`, this is config-only and baked at build time, so changing it needs a rebuild.
|
|
134
|
+
* @default true
|
|
135
|
+
*/
|
|
136
|
+
mcp?: boolean;
|
|
130
137
|
/** Workspace-mode settings. */
|
|
131
138
|
workspace?: {
|
|
132
139
|
entries?: ConfigEntries;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handoff-app",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-6",
|
|
4
4
|
"description": "Automated documentation toolchain for building client side documentation from figma",
|
|
5
5
|
"author ": {
|
|
6
6
|
"name": "Convertiv",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@clack/prompts": "^0.7.0",
|
|
56
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
56
57
|
"@neondatabase/serverless": "^1.1.0",
|
|
57
58
|
"@radix-ui/react-accordion": "^1.2.8",
|
|
58
59
|
"@radix-ui/react-collapsible": "^1.1.2",
|
|
@@ -121,7 +122,8 @@
|
|
|
121
122
|
"vaul": "^1.1.2",
|
|
122
123
|
"vite": "^6.3.5",
|
|
123
124
|
"ws": "^8.18.3",
|
|
124
|
-
"yargs": "^17.7.2"
|
|
125
|
+
"yargs": "^17.7.2",
|
|
126
|
+
"zod": "^4.4.3"
|
|
125
127
|
},
|
|
126
128
|
"peerDependencies": {
|
|
127
129
|
"typescript": ">=5.6 <7"
|
|
@@ -7,11 +7,14 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/t
|
|
|
7
7
|
|
|
8
8
|
type CopyToClipboardProps = {
|
|
9
9
|
value: string;
|
|
10
|
+
/** Accessible name for the control, and the hover tooltip unless `showTooltip` is off. */
|
|
10
11
|
tooltip: string;
|
|
12
|
+
/** Hide the tooltip where the surrounding UI already says what the button copies. */
|
|
13
|
+
showTooltip?: boolean;
|
|
11
14
|
className?: string;
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
export const CopyToClipboard: React.FC<CopyToClipboardProps> = ({ value, tooltip, className }) => {
|
|
17
|
+
export const CopyToClipboard: React.FC<CopyToClipboardProps> = ({ value, tooltip, showTooltip = true, className }) => {
|
|
15
18
|
const [copied, setCopied] = React.useState<boolean>(false);
|
|
16
19
|
|
|
17
20
|
const handleCopy = async () => {
|
|
@@ -24,33 +27,32 @@ export const CopyToClipboard: React.FC<CopyToClipboardProps> = ({ value, tooltip
|
|
|
24
27
|
}
|
|
25
28
|
};
|
|
26
29
|
|
|
30
|
+
const button = (
|
|
31
|
+
<Button
|
|
32
|
+
variant="outline"
|
|
33
|
+
size="icon"
|
|
34
|
+
className={cn('disabled:opacity-100', className)}
|
|
35
|
+
onClick={handleCopy}
|
|
36
|
+
aria-label={copied ? 'Copied' : tooltip}
|
|
37
|
+
disabled={copied}
|
|
38
|
+
>
|
|
39
|
+
<div className={cn('transition-all', copied ? 'scale-100 opacity-100' : 'scale-0 opacity-0')}>
|
|
40
|
+
<CheckIcon className="stroke-gray-600" size={16} aria-hidden="true" />
|
|
41
|
+
</div>
|
|
42
|
+
<div className={cn('absolute transition-all', copied ? 'scale-0 opacity-0' : 'scale-100 opacity-100')}>
|
|
43
|
+
<CopyIcon aria-hidden="true" />
|
|
44
|
+
</div>
|
|
45
|
+
</Button>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (!showTooltip) {
|
|
49
|
+
return button;
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
return (
|
|
28
53
|
<TooltipProvider delayDuration={0}>
|
|
29
54
|
<Tooltip>
|
|
30
|
-
<TooltipTrigger asChild>
|
|
31
|
-
<Button
|
|
32
|
-
variant="outline"
|
|
33
|
-
size="icon"
|
|
34
|
-
className={cn('disabled:opacity-100', className)}
|
|
35
|
-
onClick={handleCopy}
|
|
36
|
-
aria-label={copied ? 'Copied' : 'Copy to clipboard'}
|
|
37
|
-
disabled={copied}
|
|
38
|
-
>
|
|
39
|
-
<div
|
|
40
|
-
className={cn('transition-all', copied ? 'scale-100 opacity-100' : 'scale-0 opacity-0')}
|
|
41
|
-
>
|
|
42
|
-
<CheckIcon className="stroke-gray-600" size={16} aria-hidden="true" />
|
|
43
|
-
</div>
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
'absolute transition-all',
|
|
47
|
-
copied ? 'scale-0 opacity-0' : 'scale-100 opacity-100'
|
|
48
|
-
)}
|
|
49
|
-
>
|
|
50
|
-
<CopyIcon aria-hidden="true" />
|
|
51
|
-
</div>
|
|
52
|
-
</Button>
|
|
53
|
-
</TooltipTrigger>
|
|
55
|
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
54
56
|
<TooltipContent className="px-2 py-1 text-xs">{tooltip}</TooltipContent>
|
|
55
57
|
</Tooltip>
|
|
56
58
|
</TooltipProvider>
|
|
@@ -58,4 +60,3 @@ export const CopyToClipboard: React.FC<CopyToClipboardProps> = ({ value, tooltip
|
|
|
58
60
|
};
|
|
59
61
|
|
|
60
62
|
export default CopyToClipboard;
|
|
61
|
-
|
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import Link from 'next/link';
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
+
import { McpConfigDialog } from '../../components/McpIntegration/McpConfigDialog';
|
|
3
4
|
import { ModeToggle } from '../../components/ModeSwitcher';
|
|
4
5
|
import { MainNav } from '../../components/Navigation/MainNav';
|
|
5
6
|
import { MobileNav } from '../../components/Navigation/MobileNav';
|
|
6
7
|
import { cn } from '../../lib/utils';
|
|
7
|
-
import { Badge } from '../ui/badge';
|
|
8
8
|
import { useConfigContext } from '../context/ConfigContext';
|
|
9
9
|
import { AuthControls } from '../Auth/AuthControls';
|
|
10
|
-
|
|
11
|
-
const RUNTIME_MODE_LABEL: Record<'workspace' | 'registry', string> = {
|
|
12
|
-
workspace: 'Workspace',
|
|
13
|
-
registry: 'Registry',
|
|
14
|
-
};
|
|
10
|
+
import { RuntimeModeBadge } from './RuntimeModeBadge';
|
|
15
11
|
|
|
16
12
|
export function Header() {
|
|
17
13
|
const context = useConfigContext();
|
|
18
14
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
19
15
|
const runtimeMode = context.config?.runtime?.mode ?? 'workspace';
|
|
20
|
-
const runtimeModeLabel = RUNTIME_MODE_LABEL[runtimeMode];
|
|
21
16
|
// The static export is a self-contained snapshot of the workspace, not a live runtime, so the
|
|
22
17
|
// runtime-mode badge carries no meaning there and is omitted.
|
|
23
18
|
const isStaticSnapshot = process.env.HANDOFF_BUILD_TARGET === 'static';
|
|
@@ -44,14 +39,11 @@ export function Header() {
|
|
|
44
39
|
<Link href="/">
|
|
45
40
|
<img className="max-h-5" src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/logo.svg`} alt={context.config?.app?.title} />
|
|
46
41
|
</Link>
|
|
47
|
-
{!isStaticSnapshot &&
|
|
48
|
-
<Badge variant={runtimeMode === 'registry' ? 'info' : 'default'} aria-label={`Runtime mode: ${runtimeModeLabel}`}>
|
|
49
|
-
{runtimeModeLabel}
|
|
50
|
-
</Badge>
|
|
51
|
-
)}
|
|
42
|
+
{!isStaticSnapshot && <RuntimeModeBadge mode={runtimeMode} />}
|
|
52
43
|
</div>
|
|
53
44
|
<div className="hidden items-center gap-4 @2xl:flex">
|
|
54
45
|
<MainNav />
|
|
46
|
+
<McpConfigDialog />
|
|
55
47
|
{runtimeMode === 'registry' ? <AuthControls /> : null}
|
|
56
48
|
<ModeToggle />
|
|
57
49
|
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RuntimeMode } from '@handoff/types/config';
|
|
2
|
+
import { Badge } from '../ui/badge';
|
|
3
|
+
|
|
4
|
+
const LABEL: Record<RuntimeMode, string> = {
|
|
5
|
+
workspace: 'Workspace',
|
|
6
|
+
registry: 'Registry',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The Workspace/Registry marker, shared by the header and the MCP connect dialog. `mode` is a prop
|
|
11
|
+
* because the header reads the projected client config while the dialog reads the mode baked into
|
|
12
|
+
* the bundle.
|
|
13
|
+
*/
|
|
14
|
+
export const RuntimeModeBadge: React.FC<{ mode: RuntimeMode; className?: string }> = ({ mode, className }) => (
|
|
15
|
+
<Badge variant={mode === 'registry' ? 'info' : 'default'} className={className} aria-label={`Runtime mode: ${LABEL[mode]}`}>
|
|
16
|
+
{LABEL[mode]}
|
|
17
|
+
</Badge>
|
|
18
|
+
);
|