PKU harness
Marketplace

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.

campus-tools/canteen/PLUGIN.md
---
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.

  1. If the root of the repository has a pku-harness.json file, 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.

  2. Otherwise, every PLUGIN.md in 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, tests and __tests__;
    • fixtures, __fixtures__ and testdata;
    • 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.md is not listed. A folder named in pku-harness.json is 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.

FieldRequiredNotes
specyes1. The app refuses versions it doesn't know.
idyes^[a-z0-9][a-z0-9-]{1,40}$. Unique across the marketplace, because tools are registered as <id>.<tool>.
nameyesDisplay name, in Chinese first.
descriptionyesOne sentence, 4–200 characters.
versionyesSemver. Quote it if YAML would read it as a number ("1.0" is not semver anyway).
authoryesGitHub handle or name.
icon, homepageAn icon name from the app's icon set; a URL.
minAppThe lowest app version the plugin needs (x.y.z).
campusCampus services the app calls with the user's session (list below).
networkPublic hosts the plugin calls (dining.pku.edu.cn, *.example.com). HTTPS only.
abilitiesHost abilities the plugin uses (list below).
sharedA YAML file of constants and functions that every tool sees (shared.yaml).
toolsMap of tool name to a file (tools/menu.yaml) or an inline tool. See Tools.
dataSourcesRead tools that widgets and scheduled checks poll: { tool, args?, ttl }.
widgets, screensMap of name to a JSON file (a generative-UI spec with data bindings).
settings, commands, defaultModesSettings rows, slash commands, and suggested permission modes per effect.
requiresOptional, because the marketplace derives it from the other fields.
cloudUse of PKU harness Cloud, with a plain-language disclosure (below).
serverServer functions. Off on the official server for now (see below).
i18n.enEnglish 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.

tools/talks.yaml
title: 学术日历
summary: 近期的学术讲座
description: >-
  Academic talks between two dates, sorted by start time. Default: today to 30 days ahead.
effect: read
untrusted: true

To 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:

DeclarationImplies
any campus servicecampus-session, network
campus: [cwfw]also campus-network (cwfw only answers on the campus network)
network hosts, or remotenetwork
abilities: [scheduler]background
cloud.uses not empty, or servercloud

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.

PLUGIN.md
---
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.

pku-harness.plugin.json
{
  "$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

FieldTypeRequiredNotes
idstringyes^[a-z0-9][a-z0-9-]{1,40}$. Unique across the marketplace. Use it as your MCP server's name (see below).
namestringyesDisplay name, in Chinese first (≤ 40 chars).
descriptionstringyesOne sentence, 4–200 chars, saying what it does for students.
authorstringDefaults to the GitHub owner of the repo.
kind"remote" | "builtin"Defaults to "remote". "builtin" is reserved for plugins bundled in the app.
endpointhttps URL | nullPublic 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.
iconstringAn icon name from the app's set (wallet, food, library, bus, …) or an https:// URL to a square image.
requirescapability[]What the plugin needs. See Capabilities.
scopesstring[]Host names the plugin talks to ("dining.pku.edu.cn", "*.example.com"). Shown to users.
tagsstring[]Lowercase search tags.
cloudobjectUse of PKU harness Cloud, copied from PluginDef.cloud. See below.
versionstringShown on the plugin page.
licensestringSPDX id, e.g. "MIT".
homepagehttps URLDocs or project page.
i18n.en{ name?, description? }English strings.
$schemastringIgnored 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

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.

ValueMeaning shown to users
networkTalks to servers on the internet.
campus-networkSome hosts only answer on the PKU campus network.
campus-sessionActs with the user's logged-in campus session.
notificationsSends local notifications.
backgroundRuns scheduled checks while the app is closed.
shell, filesystemPhones have neither. The app hides plugins that need them, and the scanner warns.
cloudUses PKU harness Cloud. Requires a cloud block.
ongoingShows 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:

  • uses has no duplicates;
  • if uses is not empty, there is at least one disclosure.zh line that says in plain words what leaves the device;
  • no line is empty;
  • if disclosure.en is present, it translates every zh line;
  • an external backend.url uses https.

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.

CheckLevel
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 useserror
A named file that is absolute, contains .., or doesn't exist; total over 512 KBerror
A tool file without title, description or a valid effect, or with a summary over 80 characterserror
dataSources pointing at an undeclared tool, or settings at an undeclared screenerror
network entries that aren't host nameserror
Endpoint (remote.mcp, endpoint) not https, a private or local host or IP, credentials or a query stringerror
The cloud disclosure rules above; server without cloud.disclosure.zherror
Unknown abilitieserror
Unknown top-level fields in PLUGIN.md (the app parses strictly)error
kind: "builtin" outside pkuinfo/pku-harness-plugins (JSON)error
Unknown campus serviceswarning
server (off on the official server for now); abilities: [cloud] without a cloud or server blockwarning
campus on a remote plugin (campus-session in a JSON manifest)warning
shell or filesystem in requireswarning
requires: cloud without a cloud blockwarning
Empty or very long Markdown body; no tools, commands or widgetswarning
tools on a remote plugin (they are ignored)warning
Unknown fields in a JSON manifestwarning
No i18n.en; in JSON, no endpoint (self-hosted) or no licenseinfo

The scanner also has size limits:

  • 100 plugins and 300 package.json files 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.

On this page