{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "mcp-server-editor", "title": "MCP Server Editor", "description": "Controlled canonical MCP Server editor with streamable-http, SSE, and stdio transport-specific fields", "dependencies": ["@wener/ai@^0.1.21", "daisyui"], "registryDependencies": ["https://ui-components.wener.me/r/ai-config-editor.json"], "files": [ { "path": "registry/default/blocks/mcp-server-editor/mcp-server-editor.tsx", "content": "'use client';\n\nimport { type McpServerConfig, McpServerConfigSchema, type McpTransport } from '@wener/ai/mcp';\nimport type { ComponentPropsWithRef, ReactNode } from 'react';\nimport {\n\tAiConfigEditor,\n\ttype AiConfigEditorSlots,\n\tAiConfigFieldGrid,\n\tAiConfigFieldSlot,\n\tAiConfigFormSection,\n\tAiConfigKeyValueField,\n\tAiConfigNumberField,\n\ttype AiConfigSchema,\n\tAiConfigStringListField,\n\tAiConfigTextareaField,\n\tAiConfigTextField,\n\tAiConfigToggleField,\n\ttype AiConfigValidationMode,\n\ttype AiResourceEditorMessageOverrides,\n\ttype AiResourceEditorMessages,\n\taiConfigFieldError,\n\tJsonValueEditor,\n\tmergeResourceEditorMessages,\n\tuseAiConfigDraft,\n} from '../../ui/ai-config-editor';\n\nexport type McpServerEditorField =\n\t| 'id'\n\t| 'name'\n\t| 'title'\n\t| 'description'\n\t| 'transport'\n\t| 'url'\n\t| 'headers'\n\t| 'command'\n\t| 'args'\n\t| 'env'\n\t| 'cwd'\n\t| 'timeout'\n\t| 'timeoutMs'\n\t| 'options'\n\t| 'tags'\n\t| 'metadata'\n\t| 'extensions'\n\t| 'enabled';\nexport type McpServerEditorSection = 'identity' | 'transport' | 'runtime' | 'metadata';\nexport type McpServerEditorMessages = AiResourceEditorMessages;\nexport type McpServerEditorMessageOverrides = AiResourceEditorMessageOverrides<\n\tMcpServerEditorField,\n\tMcpServerEditorSection\n>;\nexport type McpServerEditorProps = Omit, 'onChange' | 'title'> & {\n\tvalue: McpServerConfig;\n\tonChange: (value: McpServerConfig) => void;\n\tonSubmit?: (value: McpServerConfig) => void;\n\tschema?: AiConfigSchema;\n\tvalidationMode?: AiConfigValidationMode;\n\tdisabled?: boolean;\n\treadOnly?: boolean;\n\tmessages?: McpServerEditorMessageOverrides;\n\tslots?: AiConfigEditorSlots;\n};\n\nexport const defaultMcpServer: McpServerConfig = McpServerConfigSchema.parse({\n\tid: 'mcp-new',\n\tname: 'mcp-new',\n\ttransport: 'streamable-http',\n\turl: 'https://example.com/mcp',\n\tenabled: true,\n});\nconst defaultMessages: McpServerEditorMessages = {\n\ttitle: 'MCP Server 配置',\n\tdescription: '在 HTTP、SSE 与本地 stdio transport 之间切换。',\n\tsections: { identity: '身份', transport: 'Transport', runtime: '运行参数', metadata: '标签与扩展' },\n\tfields: {\n\t\tid: 'ID',\n\t\tname: '名称',\n\t\ttitle: '显示名称',\n\t\tdescription: '描述',\n\t\ttransport: 'Transport',\n\t\turl: 'Server URL',\n\t\theaders: '请求头',\n\t\tcommand: '命令',\n\t\targs: '参数',\n\t\tenv: '环境变量',\n\t\tcwd: '工作目录',\n\t\ttimeout: '超时(秒)',\n\t\ttimeoutMs: '超时(毫秒)',\n\t\toptions: 'Options',\n\t\ttags: '标签',\n\t\tmetadata: 'Metadata',\n\t\textensions: 'Extensions',\n\t\tenabled: '启用 MCP Server',\n\t},\n};\n\nexport function McpServerEditor({\n\tvalue,\n\tonChange,\n\tonSubmit,\n\tschema = McpServerConfigSchema,\n\tvalidationMode,\n\tdisabled = false,\n\treadOnly = false,\n\tmessages: overrides,\n\tslots,\n\t...props\n}: McpServerEditorProps) {\n\tconst messages = mergeResourceEditorMessages(defaultMessages, overrides);\n\tconst controller = useAiConfigDraft({\n\t\tvalue,\n\t\tfallbackValue: defaultMcpServer,\n\t\tschema,\n\t\tonChange,\n\t\tonSubmit,\n\t\tvalidationMode,\n\t});\n\tconst draft = controller.draft;\n\tconst locked = disabled || readOnly;\n\tconst patch = (next: Partial) => controller.setDraft({ ...draft, ...next } as McpServerConfig);\n\tconst field = (name: McpServerEditorField, defaultField: ReactNode) => (\n\t\t\n\t);\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'id',\n\t\t\t\t\t\t patch({ id: optional(id) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'name',\n\t\t\t\t\t\t patch({ name: optional(name) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'title',\n\t\t\t\t\t\t patch({ title: optional(title) })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'description',\n\t\t\t\t\t patch({ description: optional(description) })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'transport',\n\t\t\t\t\t controller.setDraft(changeMcpTransport(draft, transport))}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t{draft.transport === 'stdio' ? (\n\t\t\t\t\t\n\t\t\t\t\t\t{field(\n\t\t\t\t\t\t\t'command',\n\t\t\t\t\t\t\t patch({ command: optional(command) })}\n\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{field(\n\t\t\t\t\t\t\t'cwd',\n\t\t\t\t\t\t\t patch({ cwd: optional(cwd) })}\n\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{field(\n\t\t\t\t\t\t\t'args',\n\t\t\t\t\t\t\t patch({ args })}\n\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{field(\n\t\t\t\t\t\t\t'env',\n\t\t\t\t\t\t\t patch({ env })}\n\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t
\n\t\t\t\t\t\t{field(\n\t\t\t\t\t\t\t'url',\n\t\t\t\t\t\t\t patch({ url: optional(url) })}\n\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{field(\n\t\t\t\t\t\t\t'headers',\n\t\t\t\t\t\t\t patch({ headers })}\n\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'timeout',\n\t\t\t\t\t\t patch({ timeout })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'timeoutMs',\n\t\t\t\t\t\t patch({ timeoutMs })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'options',\n\t\t\t\t\t patch({ options: options as McpServerConfig['options'] })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{field(\n\t\t\t\t\t'tags',\n\t\t\t\t\t patch({ tags })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'metadata',\n\t\t\t\t\t\t patch({ metadata: metadata as McpServerConfig['metadata'] })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t\t{field(\n\t\t\t\t\t\t'extensions',\n\t\t\t\t\t\t patch({ extensions: extensions as McpServerConfig['extensions'] })}\n\t\t\t\t\t\t/>,\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\t{field(\n\t\t\t\t\t'enabled',\n\t\t\t\t\t patch({ enabled })}\n\t\t\t\t\t/>,\n\t\t\t\t)}\n\t\t\t
\n\t\t\n\t);\n}\n\nexport function changeMcpTransport(value: McpServerConfig, transport: McpTransport): McpServerConfig {\n\tconst shared = {\n\t\tid: value.id,\n\t\tname: value.name,\n\t\ttitle: value.title,\n\t\tdescription: value.description,\n\t\ttransport,\n\t\ttimeout: value.timeout,\n\t\ttimeoutMs: value.timeoutMs,\n\t\toptions: value.options,\n\t\ttags: value.tags,\n\t\tmetadata: value.metadata,\n\t\textensions: value.extensions,\n\t\tenabled: value.enabled,\n\t};\n\tif (transport === 'stdio')\n\t\treturn McpServerConfigSchema.parse({\n\t\t\t...shared,\n\t\t\tcommand: value.transport === 'stdio' ? value.command : 'node',\n\t\t\targs: value.transport === 'stdio' ? value.args : [],\n\t\t\tenv: value.transport === 'stdio' ? value.env : {},\n\t\t\tcwd: value.transport === 'stdio' ? value.cwd : undefined,\n\t\t});\n\treturn McpServerConfigSchema.parse({\n\t\t...shared,\n\t\turl: value.transport === 'stdio' ? 'https://example.com/mcp' : value.url,\n\t\theaders: value.transport === 'stdio' ? {} : value.headers,\n\t});\n}\n\nfunction TransportSwitch({\n\tvalue,\n\tonChange,\n\tlabel,\n\tdisabled,\n}: {\n\tvalue: McpTransport;\n\tonChange: (value: McpTransport) => void;\n\tlabel: string;\n\tdisabled: boolean;\n}) {\n\treturn (\n\t\t
\n\t\t\t{label}\n\t\t\t
\n\t\t\t\t{(['streamable-http', 'sse', 'stdio'] as const).map((transport) => (\n\t\t\t\t\t onChange(transport)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{transport}\n\t\t\t\t\t\n\t\t\t\t))}\n\t\t\t
\n\t\t
\n\t);\n}\n\nfunction optional(value: string): string | undefined {\n\treturn value.trim() ? value : undefined;\n}\n", "type": "registry:component", "target": "@components/blocks/mcp-server-editor/mcp-server-editor.tsx" }, { "path": "registry/default/blocks/mcp-server-editor/index.ts", "content": "export * from './mcp-server-editor';\n", "type": "registry:component", "target": "@components/blocks/mcp-server-editor/index.ts" } ], "type": "registry:block" }