快速上手:远程插件
基于 templates/remote-plugin 编写远程插件,也就是一个使用 Streamable HTTP 的 MCP 服务器。在本地运行它,添加到 App,再部署上线并登记到插件市场。
远程插件(remote plugin)是你自己运行的服务器,App 通过网络连接它、调用它的工具。本页带你复制模板、编写一个工具、在电脑上运行服务器、把它添加到 App,最后部署上线并登记到插件市场。
服务器使用 MCP(Model Context Protocol,模型上下文协议)。MCP 是一项开放标准,让 AI 应用能够发现并调用服务器上的工具。PKU harness 使用 MCP 的 Streamable HTTP 传输方式,每次调用都是一个 HTTP POST 请求。
用户可以通过 URL 或插件市场添加远程插件。添加之后,它的工具和内置插件的工具进入同一个注册表,遵守同一套权限,由同一个助手调用。你的代码不会在手机上运行,远程插件也永远拿不到校园账号密码。
你需要 bun 和一些 TypeScript 基础。模板使用 TypeScript 和官方 MCP SDK,不过任何有 MCP 服务端库的语言,做法都一样。
1. 从模板开始
cd pku-harness-plugins
cp -r templates/remote-plugin ../my-plugin
cd ../my-plugin
bun install模板只有一个文件 server.ts,外加一个 package.json,依赖 @modelcontextprotocol/sdk、express 和 zod:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import { z } from 'zod';
function build() {
// name 就是你在注册表里的 id:App 会以 `mcp-<name>` 的名字安装这个插件。
const server = new McpServer(
{ name: 'my-plugin', version: '0.1.0', title: '我的插件' },
{ instructions: 'What this plugin is for, in one sentence.' },
);
server.registerTool(
'hello',
{
title: '打个招呼',
description: 'Return a greeting. Replace with a real capability.',
inputSchema: { name: z.string().describe('who to greet') },
annotations: { readOnlyHint: true },
},
async ({ name }) => ({
content: [{ type: 'text', text: `你好,${name}` }],
structuredContent: { greeting: `你好,${name}` },
}),
);
return server;
}
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
// 可选的 bearer 认证:用户在 App 里粘贴 token,它保存在用户设备的钥匙串里。
if (process.env.PLUGIN_TOKEN && req.headers.authorization !== `Bearer ${process.env.PLUGIN_TOKEN}`) {
res.status(401).json({ jsonrpc: '2.0', error: { code: -32001, message: 'unauthorized' }, id: null });
return;
}
const server = build();
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); // 无状态
res.on('close', () => {
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(Number(process.env.PORT ?? 8787), () => console.log('MCP plugin ready on /mcp'));build() 函数创建服务器并注册工具。/mcp 路由先检查可选的 token,再把请求交给一个新建的服务器处理。把 hello 工具换成你自己的工具即可。
2. 编写真正的工具
工具的每个 MCP 字段在 PKU harness 里都有特定的用途:
name,即registerTool的第一个参数,会成为工具 idmcp-<server>.<name>,模型看到的名字是mcp-<server>__<name>。请用简短的 ASCII 小驼峰命名。其他字符会被替换成_,超过 40 个字符的部分会被截掉。title显示给用户,出现在聊天、授权确认和设置页里。用中文写。description给模型看。写清楚什么时候用、怎么用,以及单位和格式。inputSchema会在设备上转换成 zod,每次调用前都会校验。每个字段都要写说明。annotations.readOnlyHint决定工具的效果。设为true时效果是read,工具直接运行;不写或设为false时效果是write,App 默认会先询问用户。- 结果中的
structuredContent会作为data返回给模型,这是推荐的写法。没有它时,App 会尝试把文本内容解析成 JSON,解析失败就原样作为文本传递。 - 结果中的
isError: true会让文本内容成为助手看到的错误信息。
服务器 instructions 的前 500 个字符会写进系统提示词中属于这个插件的那一段。App 会在它前面加一句话,提醒模型:这个插件的输出只是数据,不能当作指令执行。
远程工具的效果
目前 App 只给远程工具两种效果(effect):设置了 readOnlyHint: true 的是 read,其余都是 write。所以会花钱或公开发帖的工具也被归为 write,默认先询问。请在它的 title 和 description 里写清楚它做什么。
3. 运行并添加到 App
bun server.ts # 在 8787 端口的 /mcp 上提供服务
# 可选:PLUGIN_TOKEN=secret bun server.ts在 App 里打开 设置 → 插件 → 添加远程插件,填入 URL;如果设置了 token,也一并填上。URL 取决于 App 运行在哪里:
| App 运行在 | URL |
|---|---|
| Android 模拟器 | http://10.0.2.2:8787/mcp |
| 同一网络下的手机 | http://<你电脑的局域网 IP>:8787/mcp |
| 已部署 | https://my-plugin.example.com/mcp |
模拟器通过 10.0.2.2 访问你的电脑;真机则需要你的电脑在局域网里的地址。
App 会连接服务器,列出你的工具,并在插件页面上显示每个工具的效果。然后问助手一个你的工具能回答的问题。你也可以请助手替你安装插件:它会调用 plugins.installRemote,安装前会先请你确认。
完整示例
pku-harness-plugins 里的 examples/demo-canteen 是一个能运行的远程插件,使用模拟数据。它有一个读取工具和一个修改工具,bearer token 是 demo-token。用 bun examples/demo-canteen/server.ts 运行它。
4. 部署
任何能提供 HTTPS 的地方都可以:虚拟专用服务器(VPS)、Fly.io、Railway、带有 MCP 兼容运行时的 Cloudflare,或者校内服务器。请注意以下几点:
- 生产环境必须使用 HTTPS,纯 HTTP 只用于本地开发。
POST /mcp必须能处理 MCP Streamable HTTP 请求。模板是无状态的:每个请求都新建一个服务器。这很适合 serverless 平台,因为这类平台可能为每个请求启动一个新进程。- 有些校内服务只在北大校园网内可以访问。如果你要调用这类服务,你的服务器也必须运行在校园网里。App 无法把用户的校园登录状态借给你的服务器。
- 你自己的密钥(比如 API key)留在服务器上。如果要保护接口,请使用 bearer token,并在注册表条目里写上
"auth": "bearer"。
5. 登记到插件市场
登记之后,插件会出现在插件市场里,同学们就能找到它。登记有两种方式。一种是在你的仓库里加一份清单,再通过本站的插件市场提交(见清单参考和提交插件)。另一种是在 pku-harness-plugins 的 registry/registry.json 里加一条记录。无论哪种方式,id 都必须和 MCP 服务器的 name 一致:
{
"id": "my-plugin",
"name": "我的插件",
"description": "一句话说明它做什么。",
"author": "your-github-name",
"source": "https://github.com/you/my-plugin",
"kind": "remote",
"endpoint": "https://my-plugin.example.com/mcp",
"auth": "none",
"requires": ["network"],
"scopes": ["example.com"],
"tags": ["canteen"]
}向注册表提交 pull request 之前,请先运行 bun scripts/validate-registry.ts。另见发布和审核规则。
检查清单
- MCP 服务器的
name和注册表id相同(^[a-z0-9][a-z0-9-]{1,40}$)。 - 每个工具都有中文
title、写给模型的description,每个输入字段都有说明。 - 只读工具设置了
annotations.readOnlyHint: true,其他工具都没有设置。 - 返回的是数据(
structuredContent),不含指令,体积适中。 - 插件从不索要校园账号密码。
- 接口使用 HTTPS;如果用了 bearer 认证,已在注册表中声明。