Skip to content

传输模式 ​

插件通过 capabilities.mcp.transport 声明它如何与客户端通信。三种模式的运行位置、隔离性和启动开销差别很大,选错会带来不必要的复杂度。


三种模式对比 ​

维度in-processstdiosse
运行位置客户端主进程内独立子进程远程服务
通信方式InMemoryTransportstdin/stdoutHTTP SSE
代码来源本地 main 指向的模块本地入口或 npx/uvx 外部包远程端点
启动开销极低(无进程创建)中等(创建进程 + 运行时)低(网络连接)
崩溃隔离❌ 异常可能影响主进程✅ 子进程崩溃不影响客户端✅ 完全隔离
访问 Host 能力✅ 直接访问 Host❌ 仅能通过环境变量与网络❌
依赖管理无需 node_modules需自行安装依赖(npx 自动拉取)由服务端负责
典型用途自研工具、需桌面能力、带 UI 扩展标准 MCP Server、复用社区包团队自建远程 MCP

in-process ​

插件代码被客户端主进程直接 import,通过内存通道通信。这是自研插件最常用的模式。

manifest 片段

json
{
  "type": "tool",
  "main": "index.js",
  "capabilities": {
    "mcp": {
      "transport": "in-process",
      "autoConnect": true
    }
  }
}

in-process 模式不需要 command / args / url,客户端直接加载 main 指向的模块。

入口约定

模块必须导出一个工厂函数,客户端按 default → createMcpServer → create 的顺序识别,返回 { server, clientTransport }:

javascript
const { McpServer, InMemoryTransport, z } = globalThis.__AWEECLAW_HOST__

export function createMcpServer() {
  const server = new McpServer(
    { name: 'my-plugin', version: '1.0.0' },
    { capabilities: { tools: { listChanged: false } } },
  )

  server.tool('ping', 'Health check', {}, async () => ({
    content: [{ type: 'text', text: 'pong' }],
  }))

  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
  server.connect(serverTransport).catch(() => {})
  return { server, clientTransport }
}

若模块同时导出多个工厂,可用 inProcessExport 显式指定:

json
{
  "capabilities": {
    "mcp": {
      "transport": "in-process",
      "inProcessExport": "createMcpServer"
    }
  }
}

McpServer / InMemoryTransport / z(zod)必须从 globalThis.__AWEECLAW_HOST__ 获取,不要 import 外部 SDK —— 客户端已内置一套,重复引入会导致协议实例不匹配。

适用场景

  • 自研工具插件(本项目 plugins/util-*、plugins/design-* 等)
  • 需要访问桌面能力(截图、输入模拟、OCR、PPT 生成…)
  • 需要向客户端注入 UI(contributes)
  • 需要注册生命周期钩子或定时任务

stdio ​

客户端以子进程方式启动插件,通过 stdin/stdout 走 MCP 协议。分两种子形态。

A. 本地入口(自带代码) ​

json
{
  "type": "mcp",
  "main": "index.js",
  "capabilities": {
    "mcp": {
      "transport": "stdio",
      "command": "node",
      "args": ["{{pluginDir}}/index.js"],
      "autoConnect": true
    }
  }
}

入口使用标准 @modelcontextprotocol/sdk:

javascript
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'

const API_KEY = process.env.API_KEY
if (!API_KEY) {
  console.error('缺少 API_KEY 环境变量')
  process.exit(1)
}

const server = new Server(
  { name: 'my-mcp', version: '1.0.0' },
  { capabilities: { tools: {} } },
)

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'search',
      description: '搜索',
      inputSchema: {
        type: 'object',
        properties: { keyword: { type: 'string' } },
        required: ['keyword'],
      },
    },
  ],
}))

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params
  if (name !== 'search') {
    return { content: [{ type: 'text', text: `未知工具: ${name}` }], isError: true }
  }
  try {
    const resp = await fetch(`https://api.example.com/search?q=${encodeURIComponent(args.keyword)}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    })
    const data = await resp.json()
    return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
  } catch (err) {
    return { content: [{ type: 'text', text: `请求失败: ${err.message}` }], isError: true }
  }
})

await server.connect(new StdioServerTransport())

stdio 插件的 package.json 需要声明 SDK 依赖:

json
{
  "type": "module",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  }
}

打包时 node_modules 会被排除。若插件依赖第三方库,安装后由用户在插件目录执行 npm install,或改用 in-process 模式自行打包依赖。

B. 外部包(ConfigOnly) ​

不写任何代码,只声明如何调用社区 MCP 包。main 留空字符串,发布时后端直接存 manifest,无包文件下载。

json
{
  "id": "mcp-brave-search",
  "name": "Brave Search",
  "nameZh": "Brave 搜索",
  "type": "mcp",
  "version": "1.0.0",
  "main": "",
  "icon": "Search",
  "category": "search",
  "capabilities": {
    "mcp": {
      "transport": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-brave-search"],
      "autoConnect": true
    }
  },
  "configSchema": {
    "fields": [
      {
        "key": "BRAVE_API_KEY",
        "label": "Brave API Key",
        "labelZh": "Brave API 密钥",
        "type": "password",
        "required": true,
        "secret": true
      }
    ]
  },
  "permissions": ["network"]
}

Python 生态用 uvx,args 写 PyPI 包名。uvx 需用户本机安装(brew install uv 或 pip install uv)。

该模式要求目标机器能访问 npm / PyPI。企业内网或离线环境建议改用 in-process / Tarball 模式。


sse ​

连接远程托管的 MCP 服务:

json
{
  "type": "mcp",
  "main": "",
  "capabilities": {
    "mcp": {
      "transport": "sse",
      "url": "https://mcp.example.com/sse",
      "autoConnect": true
    }
  },
  "permissions": ["network"]
}

适用于团队自建、需要集中管控鉴权与配额的 MCP 服务。


变量替换 ​

args 与 env 中支持三个变量,由客户端在启动前替换:

变量含义
pluginDir插件安装目录(如 .../plugins/mcp-brave-search/1.0.0)
dataDir插件独立数据目录(用于落盘缓存、配置)
config.KEY用户在配置表单中填写的值,KEY 为 configSchema.fields[].key

示例:

json
{
  "capabilities": {
    "mcp": {
      "transport": "stdio",
      "command": "node",
      "args": ["--db-path", "{{config.DB_PATH}}", "{{dataDir}}/cache.json"]
    }
  }
}

用户填写 DB_PATH 为 /data/my.db 后,实际启动命令为:

bash
node --db-path /data/my.db "/path/to/plugins/my-plugin/1.0.0/cache.json"

连接生命周期 ​

项行为
autoConnect: true应用启动时自动连接;false 时需用户在「设置 → MCP 服务」手动连接
连接状态与日志客户端「设置 → MCP 服务」查看
工具调用超时capabilities.mcp.timeout(毫秒),默认 30000
配置变更修改配置后重新连接即生效(环境变量/参数重新注入)

长耗时工具(视频生成、批量转码)务必显式放大 timeout,否则会在默认 30 秒被中断:

json
{
  "capabilities": {
    "mcp": { "transport": "in-process", "timeout": 660000 }
  }
}

选型建议 ​

需要访问客户端原生能力(桌面/文件/OCR/UI)?
├── 是 → in-process
└── 否
    ├── 已有标准 MCP Server 实现,或依赖独立进程隔离?
    │   ├── 是 → stdio(本地入口)
    │   └── 否
    ├── 想复用社区 MCP 包(@modelcontextprotocol/*、PyPI)?
    │   └── 是 → stdio + ConfigOnly(main 留空)
    └── 服务已部署在远端,需要集中管控?
        └── 是 → sse

下一步 ​

AweeClaw AI 应用构建平台