Manifest reference
How the marketplace reads PLUGIN.md (plugin language v1), where the scanner looks and what it checks, and the pku-harness.plugin.json format for remote MCP servers.
This page lists the parts of a plugin that the marketplace reads and checks. Use it when you prepare a repository for listing, or when the scanner reports a problem you want to understand.
A plugin is a folder with a PLUGIN.md file. The YAML frontmatter at the top of the file is the
plugin's manifest: it says what the plugin is and what it may access. The Markdown body below it
holds the instructions the agent receives. The full plugin language, including tools, steps,
expressions and UI, is specified in
docs/PLUGIN-LANGUAGE.md.
For an introduction, read Declarative plugins first.
---
spec: 1
id: canteen-menu
name: 食堂菜单
description: 今天各食堂的菜单和营业时间。
version: 1.0.0
author: alice
icon: food
network: [dining.pku.edu.cn]
tools:
menu: tools/menu.yaml
widgets:
today: widgets/today.json
dataSources:
today: { tool: menu, args: { day: today }, ttl: 30m }
i18n:
en: { name: Canteen menu, description: "Today's menus and opening hours." }
---
Use `menu` when the user asks what to eat or which canteen is open. Prices are in yuan.Where the scanner looks
The scanner reads the default branch of the repository through the GitHub API. It finds plugins in one of two ways.
-
If the root of the repository has a
pku-harness.jsonfile, the scanner checks only the folders listed there (up to 200), in the order given. This is useful in large monorepos:pku-harness.json { "plugins": ["plugins/canteen", "plugins/library-seat"] }"."means the root of the repository. If a listed folder has no manifest, the scanner reports an error. -
Otherwise, every
PLUGIN.mdin the repository is a plugin, at any depth. The scanner skips folders that hold dependencies, tests or test data, so example manifests in them are never listed. The skipped folders are:node_modules, and any folder whose name starts with.;test,testsand__tests__;fixtures,__fixtures__andtestdata;e2e(end-to-end tests).
A folder with one of these names is skipped wherever it appears in the path. For example,
plugins/canteen/tests/PLUGIN.mdis not listed. A folder named inpku-harness.jsonis always checked, whatever its name.
A folder holds at most one plugin. If a folder contains more than one kind of manifest,
PLUGIN.md is used first, then pku-harness.plugin.json, then a package.json with a
"pkuHarness" object (see remote MCP plugins).
You can also submit the URL of a sub-folder, such as
https://github.com/owner/repo/tree/main/plugins. The scanner then looks only inside that
folder.
Frontmatter
The table lists every top-level field. Fields without "yes" are optional.
| Field | Required | Notes |
|---|---|---|
spec | yes | 1. The app refuses versions it doesn't know. |
id | yes | ^[a-z0-9][a-z0-9-]{1,40}$. Unique across the marketplace, because tools are registered as <id>.<tool>. |
name | yes | Display name, in Chinese first. |
description | yes | One sentence, 4–200 characters. |
version | yes | Semver. Quote it if YAML would read it as a number ("1.0" is not semver anyway). |
author | yes | GitHub handle or name. |
icon, homepage | An icon name from the app's icon set; a URL. | |
minApp | The lowest app version the plugin needs (x.y.z). | |
campus | Campus services the app calls with the user's session (list below). | |
network | Public hosts the plugin calls (dining.pku.edu.cn, *.example.com). HTTPS only. | |
abilities | Host abilities the plugin uses (list below). | |
shared | A YAML file of constants and functions that every tool sees (shared.yaml). | |
tools | Map of tool name to a file (tools/menu.yaml) or an inline tool. See Tools. | |
dataSources | Read tools that widgets and scheduled checks poll: { tool, args?, ttl }. | |
widgets, screens | Map of name to a JSON file (a generative-UI spec with data bindings). | |
settings, commands, defaultModes | Settings rows, slash commands, and suggested permission modes per effect. | |
requires | Optional, because the marketplace derives it from the other fields. | |
cloud | Use of PKU harness Cloud, with a plain-language disclosure (below). | |
server | Server functions. Off on the official server for now (see below). | |
i18n.en | English strings: name, description, and overlays such as tools.<name>.{title,summary}. | |
remote | { mcp: https URL, auth?: none | bearer } makes the folder a remote MCP plugin. |
campus accepts these service names: card, its, treehole, blackboard, elective,
cwfw, cwpay, bdkj, library, library-space, portal, and others the app knows. An unknown
name is a warning.
abilities accepts verify, otp, todos, timetable, scheduler, browser,
campusPage, sms, files, crypto and cloud. The
declarative plugins guide lists the
operations and effects of each one. Any other name is an error, because the app refuses it.
server declares server functions, which run on PKU harness Cloud
(CLOUD-FUNCTIONS.md).
A plugin with server needs cloud.disclosure, and it implies cloud.uses: [functions]. Server
functions are off on the official server for now, so users of the official instance can't run
them. Keep the work on the phone, or use your own backend.
The instructions
The Markdown body of PLUGIN.md becomes the plugin's instructions for the model. The app sends
it with every prompt, so keep it short. Say when to use which tool and what the numbers mean. The
marketplace shows the body on the plugin page, cut off at 20,000 characters, and warns when it is
longer than 8,000 characters.
Tools
Each entry in tools is a tool the agent can call. It is either the path of a YAML file, such as
tools/menu.yaml, or a tool written inline. Every tool needs a title, a description and an
honest effect: read, write, money or public.
A tool can also have a summary: one short line, at most 80 characters, that tells people what
the tool does. The description is written for the model and is often long. The summary is
written for the user. The plugin page shows it under the tool's title, and the marketplace search
includes it.
title: 学术日历
summary: 近期的学术讲座
description: >-
Academic talks between two dates, sorted by start time. Default: today to 30 days ahead.
effect: read
untrusted: trueTo show an English title and summary on the English marketplace, add them under
i18n.en.tools.<name> in PLUGIN.md. A tool without an English summary shows the Chinese one.
i18n:
en:
name: Campus events
description: Calendars of academic talks and campus activities.
tools: { talks: { title: Academic talks, summary: Upcoming academic talks } }Files
The app loads only the files that the frontmatter names in tools, shared, widgets and
screens. It never lists a folder to find more. For each named path, the scanner checks that it:
- is relative to
PLUGIN.md, with no.., no leading/and no URL scheme; - exists in the repository (a missing file is an error);
- together with
PLUGIN.md, stays within 512 KB in total (from the blob sizes in the GitHub tree).
The scanner also downloads each named tool file to read its title, summary and effect. The
plugin page shows these as part of the permission sheet. Files that the frontmatter doesn't name,
such as tests/, README.md and assets/, are ignored.
Derived capabilities
The plugin page and the marketplace filters show a requires list. It contains everything you
list in requires, plus everything your other declarations imply:
| Declaration | Implies |
|---|---|
any campus service | campus-session, network |
campus: [cwfw] | also campus-network (cwfw only answers on the campus network) |
network hosts, or remote | network |
abilities: [scheduler] | background |
cloud.uses not empty, or server | cloud |
Install links
The page of each PLUGIN.md plugin shows how to install it:
- the install link, which is the GitHub folder URL on the default branch, for example
https://github.com/alice/campus-tools/tree/main/canteen; - an Open in app button with the deep link
pkuharness://plugin?src=<url-encoded install link>; - a QR code of that deep link.
The handle shown on the page is owner/repo/path.
Remote MCP plugins
A remote plugin runs on your own server and speaks the Model Context Protocol (MCP) over
Streamable HTTP. The app connects to it as the MCP server mcp-<id>, so set your server's name
to the plugin's id.
Describe a remote plugin with a PLUGIN.md that has a remote field and no tools. The tools come
from the server, so any tools declared in the file are ignored, with a warning.
---
spec: 1
id: canteen-live
name: 食堂实时人数
description: 各食堂当前排队人数。
version: 1.0.0
author: alice
remote: { mcp: "https://canteen.example.com/mcp", auth: none }
---
Use the tools when the user asks how crowded a canteen is.A remote server never receives the user's campus session. For that reason, campus on a remote
plugin is a warning.
The older format: pku-harness.plugin.json
Until remote.mcp in PLUGIN.md replaces it, the marketplace still reads
pku-harness.plugin.json, and the "pkuHarness" object in package.json, for remote MCP
servers. If you point $schema at
/schema/plugin-manifest.v1.json, your editor checks the file
as you type.
{
"$schema": "https://<this site>/schema/plugin-manifest.v1.json",
"id": "canteen-live",
"name": "食堂实时人数",
"description": "各食堂当前排队人数。",
"endpoint": "https://canteen.example.com/mcp",
"requires": ["network"],
"scopes": ["dining.pku.edu.cn"]
}pku-harness.plugin.json fields
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | ^[a-z0-9][a-z0-9-]{1,40}$. Unique across the marketplace. Use it as your MCP server's name (see below). |
name | string | yes | Display name, in Chinese first (≤ 40 chars). |
description | string | yes | One sentence, 4–200 chars, saying what it does for students. |
author | string | Defaults to the GitHub owner of the repo. | |
kind | "remote" | "builtin" | Defaults to "remote". "builtin" is reserved for plugins bundled in the app. | |
endpoint | https URL | null | Public MCP endpoint (Streamable HTTP). Leave it out, or set null, for a self-hosted plugin. | |
auth | "none" | "bearer" | Defaults to "none". With "bearer", users paste a token in the app. | |
icon | string | An icon name from the app's set (wallet, food, library, bus, …) or an https:// URL to a square image. | |
requires | capability[] | What the plugin needs. See Capabilities. | |
scopes | string[] | Host names the plugin talks to ("dining.pku.edu.cn", "*.example.com"). Shown to users. | |
tags | string[] | Lowercase search tags. | |
cloud | object | Use of PKU harness Cloud, copied from PluginDef.cloud. See below. | |
version | string | Shown on the plugin page. | |
license | string | SPDX id, e.g. "MIT". | |
homepage | https URL | Docs or project page. | |
i18n.en | { name?, description? } | English strings. | |
$schema | string | Ignored by the scanner. |
The id matters for more than the marketplace. The app installs the plugin as the MCP server
mcp-<id> and uses that name to show that the plugin is installed. If your server's name is
different, the app can't match the two.
The endpoint must use https:// and a public host, and it must not contain credentials or a
query string.
Unknown fields are ignored with a warning, so a typo doesn't drop data without telling you.
In package.json
{
"name": "@campus-tools/library-seat",
"version": "0.3.0",
"description": "图书馆座位查询与预约提醒。",
"license": "MIT",
"pkuHarness": {
"id": "library-seat",
"name": "图书馆座位",
"endpoint": "https://seat.example.com/mcp",
"requires": ["network"],
"scopes": ["seat.lib.pku.edu.cn"]
}
}description, version, license and homepage default to the package's own fields. Anything
inside pkuHarness overrides them. A pkuHarness object without an id counts as SDK
metadata and isn't listed. The bundled plugins use this form, { "builtin": true }.
Capabilities (requires)
Each value below is shown to users on the plugin page, with the meaning in the right column.
| Value | Meaning shown to users |
|---|---|
network | Talks to servers on the internet. |
campus-network | Some hosts only answer on the PKU campus network. |
campus-session | Acts with the user's logged-in campus session. |
notifications | Sends local notifications. |
background | Runs scheduled checks while the app is closed. |
shell, filesystem | Phones have neither. The app hides plugins that need them, and the scanner warns. |
cloud | Uses PKU harness Cloud. Requires a cloud block. |
ongoing | Shows a live, ongoing activity. |
A PLUGIN.md plugin gets campus-session by declaring campus; the app then signs the requests
itself. A remote MCP server never gets a campus session, so the scanner warns when one asks for it.
A remote plugin runs on your server, so declare only what the phone has to provide.
Cloud disclosure
If a plugin uses PKU harness Cloud, its cloud block must say in plain words what data leaves the
device. The marketplace and the app both show these lines on the plugin's page.
"cloud": {
"uses": ["store"],
"e2e": true,
"disclosure": { "zh": ["你的收藏列表会加密存到云端,用于多设备同步。"], "en": ["Your favourites are stored encrypted in the cloud to sync devices."] }
}The scanner applies the same rules as cloudIssues() in the registry:
useshas no duplicates;- if
usesis not empty, there is at least onedisclosure.zhline that says in plain words what leaves the device; - no line is empty;
- if
disclosure.enis present, it translates everyzhline; - an external
backend.urluseshttps.
What the scanner checks
An error keeps a plugin out of the marketplace. A warning doesn't; it is shown on the plugin's page under Checks. An info note is a suggestion.
| Check | Level |
|---|---|
No frontmatter, invalid YAML or JSON, or a schema violation (spec, semver version, …) | error |
Duplicate id in the repo, an official id, or an id another listed repo already uses | error |
A named file that is absolute, contains .., or doesn't exist; total over 512 KB | error |
A tool file without title, description or a valid effect, or with a summary over 80 characters | error |
dataSources pointing at an undeclared tool, or settings at an undeclared screen | error |
network entries that aren't host names | error |
Endpoint (remote.mcp, endpoint) not https, a private or local host or IP, credentials or a query string | error |
The cloud disclosure rules above; server without cloud.disclosure.zh | error |
Unknown abilities | error |
Unknown top-level fields in PLUGIN.md (the app parses strictly) | error |
kind: "builtin" outside pkuinfo/pku-harness-plugins (JSON) | error |
Unknown campus services | warning |
server (off on the official server for now); abilities: [cloud] without a cloud or server block | warning |
campus on a remote plugin (campus-session in a JSON manifest) | warning |
shell or filesystem in requires | warning |
requires: cloud without a cloud block | warning |
| Empty or very long Markdown body; no tools, commands or widgets | warning |
tools on a remote plugin (they are ignored) | warning |
| Unknown fields in a JSON manifest | warning |
No i18n.en; in JSON, no endpoint (self-hosted) or no license | info |
The scanner also has size limits:
- 100 plugins and 300
package.jsonfiles per repository; - 40 tool files per plugin, and 400 per repository;
- 256 KB per downloaded file;
- 10 seconds per GitHub request.
If a repository is too big for one GitHub tree request, the scanner warns. Add a
pku-harness.json that lists the plugin folders to fix this.