传输模式
插件通过 capabilities.mcp.transport 声明它如何与客户端通信。三种模式的运行位置、隔离性和启动开销差别很大,选错会带来不必要的复杂度。
三种模式对比
| 维度 | in-process | stdio | sse |
|---|---|---|---|
| 运行位置 | 客户端主进程内 | 独立子进程 | 远程服务 |
| 通信方式 | InMemoryTransport | stdin/stdout | HTTP SSE |
| 代码来源 | 本地 main 指向的模块 | 本地入口或 npx/uvx 外部包 | 远程端点 |
| 启动开销 | 极低(无进程创建) | 中等(创建进程 + 运行时) | 低(网络连接) |
| 崩溃隔离 | ❌ 异常可能影响主进程 | ✅ 子进程崩溃不影响客户端 | ✅ 完全隔离 |
| 访问 Host 能力 | ✅ 直接访问 Host | ❌ 仅能通过环境变量与网络 | ❌ |
| 依赖管理 | 无需 node_modules | 需自行安装依赖(npx 自动拉取) | 由服务端负责 |
| 典型用途 | 自研工具、需桌面能力、带 UI 扩展 | 标准 MCP Server、复用社区包 | 团队自建远程 MCP |
in-process
插件代码被客户端主进程直接 import,通过内存通道通信。这是自研插件最常用的模式。
manifest 片段
{
"type": "tool",
"main": "index.js",
"capabilities": {
"mcp": {
"transport": "in-process",
"autoConnect": true
}
}
}in-process 模式不需要 command / args / url,客户端直接加载 main 指向的模块。
入口约定
模块必须导出一个工厂函数,客户端按 default → createMcpServer → create 的顺序识别,返回 { server, clientTransport }:
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 显式指定:
{
"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. 本地入口(自带代码)
{
"type": "mcp",
"main": "index.js",
"capabilities": {
"mcp": {
"transport": "stdio",
"command": "node",
"args": ["{{pluginDir}}/index.js"],
"autoConnect": true
}
}
}入口使用标准 @modelcontextprotocol/sdk:
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 依赖:
{
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
}
}打包时
node_modules会被排除。若插件依赖第三方库,安装后由用户在插件目录执行npm install,或改用 in-process 模式自行打包依赖。
B. 外部包(ConfigOnly)
不写任何代码,只声明如何调用社区 MCP 包。main 留空字符串,发布时后端直接存 manifest,无包文件下载。
{
"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 服务:
{
"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 |
示例:
{
"capabilities": {
"mcp": {
"transport": "stdio",
"command": "node",
"args": ["--db-path", "{{config.DB_PATH}}", "{{dataDir}}/cache.json"]
}
}
}用户填写 DB_PATH 为 /data/my.db 后,实际启动命令为:
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 秒被中断:
{
"capabilities": {
"mcp": { "transport": "in-process", "timeout": 660000 }
}
}选型建议
需要访问客户端原生能力(桌面/文件/OCR/UI)?
├── 是 → in-process
└── 否
├── 已有标准 MCP Server 实现,或依赖独立进程隔离?
│ ├── 是 → stdio(本地入口)
│ └── 否
├── 想复用社区 MCP 包(@modelcontextprotocol/*、PyPI)?
│ └── 是 → stdio + ConfigOnly(main 留空)
└── 服务已部署在远端,需要集中管控?
└── 是 → sse
