For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt, and this page is available as Markdown at /zh/guides/advanced-features/mcp-apps/custom-server.md.
  • 简体中文
  • 接入自建 MCP Server

    如果已经维护 MCP Server,可以让 Modern.js 只负责构建 React 卡片。业务工具、鉴权和 MCP 协议服务继续由已有服务管理,通过 UI manifest 关联工具与构建产物。

    配置纯 UI 构建

    在 modern.config.ts 中启用纯 UI 模式,无需安装或启用 BFF 插件。此模式关闭默认页面入口扫描,因此无需创建首页:

    import { appTools, defineConfig } from '@modern-js/app-tools';
    import { mcpAppsPlugin } from '@modern-js/plugin-mcp-apps';
    
    export default defineConfig({
      plugins: [appTools(), mcpAppsPlugin({ mode: 'ui-only' })],
      output: { assetPrefix: 'https://cdn.example.com/' },
    });

    使用 mcp_apps.ts 声明 UI,避免导入服务端 handler:

    import { defineMcpUi } from '@modern-js/mcp-apps/config';
    
    export default defineMcpUi({
      tools: [{ name: 'greet', view: { module: './src/components/Greeting.tsx' } }],
    });

    运行 modern build 后,产物包含 dist/mcp-apps/ui-manifest.json 和 dist/mcp-apps/ui/。将 JS、CSS 等静态产物发布到 assetPrefix 对应的地址,将 manifest 和 HTML 交给 MCP Server。开发模式的 manifest 位于 <internalDirectory>/mcp-apps/ui-manifest.json。

    构建产物与工具映射

    manifest 的 version 当前为 1,tools 按工具名索引,每项包含 resourceUri、mimeType、html、assetBase 和 ui 元数据。html 相对于 manifest 所在目录解析,显式指定的 HTTPS HTML 地址保持原样。manifest 仅描述本地视图,不包含业务 handler、源模块路径或 MF remote。MF 继续使用标准 MF manifest 和 expose 配置。

    {
      "version": 1,
      "tools": {
        "greet": {
          "resourceUri": "ui://app/greet",
          "mimeType": "text/html;profile=mcp-app",
          "html": "./ui/mcp-ui-231bf89d7268.html",
          "assetBase": "https://cdn.example.com/",
          "ui": {
            "csp": { "connectDomains": [], "resourceDomains": [] },
            "prefersBorder": true
          }
        }
      }
    }

    通过 tools.greet.html 查找文件即可,无需自行计算 hash 文件名。部署时保留 manifest 与 HTML 的相对目录关系。读取辅助函数会根据公开资源地址补充 CSP,下面示例展示具体接入方式。

    接入已有 Node MCP Server

    下面的函数为已有官方 MCP SDK Server 注册一个工具及 UI 资源。它使用与仓库一致的 setRequestHandler API。已有工具较多时,将这些分支合入现有处理器,避免覆盖原有处理器。

    import { readFile } from 'node:fs/promises';
    import path from 'node:path';
    import {
      createMcpViewResult,
      readUiManifestResource,
      type McpUiManifest,
    } from '@modern-js/mcp-apps/server';
    
    const directory = path.resolve('dist/mcp-apps');
    const manifest: McpUiManifest = JSON.parse(
      await readFile(path.join(directory, 'ui-manifest.json'), 'utf8'),
    );
    if (manifest.version !== 1) throw new Error('Unsupported UI manifest version');
    const view = manifest.tools.greet;
    
    export function registerGreeting(server) {
      server.setRequestHandler('tools/list', async () => ({
        tools: [
          {
            name: 'greet',
            description: 'Greet someone',
            inputSchema: {
              type: 'object',
              properties: { name: { type: 'string' } },
              required: ['name'],
              additionalProperties: false,
            },
            _meta: { ui: { resourceUri: view.resourceUri } },
          },
        ],
      }));
      server.setRequestHandler('resources/list', async () => ({
        resources: [
          {
            name: 'greet',
            uri: view.resourceUri,
            mimeType: view.mimeType,
            _meta: { ui: view.ui },
          },
        ],
      }));
      server.setRequestHandler('resources/read', async ({ params }) => {
        if (params.uri !== view.resourceUri) throw new Error('Unknown UI resource');
        return {
          contents: [
            await readUiManifestResource(manifest, 'greet', {
              directory,
              assetBase: 'https://cdn.example.com/',
            }),
          ],
        };
      });
      server.setRequestHandler('tools/call', async ({ params }) => {
        const name = params.arguments?.name;
        if (params.name !== 'greet' || typeof name !== 'string') {
          throw new Error('Invalid tool call');
        }
        const message = `Hello, ${name}!`;
        return createMcpViewResult(
          { name },
          {
            content: [{ type: 'text', text: message }],
            viewProps: { message },
          },
        );
      });
    }

    readUiManifestResource() 读取 HTML、注入静态资源的 base 地址,并将资源来源补充到 CSP。默认的 assetBase: 'request' 需要调用方显式传入公开地址,独立 Server 的 origin 不一定是 UI 的静态资源地址。

    createMcpViewResult(args, result) 保留业务结果,将工具参数写入 _meta['modernjs.dev/mcp-app'].args,将视图参数写入 _meta['modernjs.dev/mcp-app'].viewProps。createMcpView() 按工具输入、结果 args、viewProps 的顺序合并 props,并注入 mcpApp。

    非 Node Server 可以直接读取 JSON manifest,返回相同的 UI 资源和工具结果结构。若自行返回 HTML,也需要处理 base 地址和 CSP,确保脚本及样式可以加载。

    集成模式同样输出 manifest,生产 BFF 优先使用其中的 HTML 映射。开发首次编译前和缺少 manifest 的旧产物继续使用约定路径绑定。

    使用 ui-only 时也可以打开开发调试页,先模拟参数和 viewProps 预览本地卡片,或切换到 Live server 连接自建 MCP Server。详见MCP Apps Inspector。