---
name: polish-docs-meta
description: >
Finalize documentation and project metadata for a ship-ready MCP server. Use after implementation is complete, tests pass, and devcheck is clean. Safe to run at any stage — each step checks current state and only acts on what still needs work.
metadata:
author: cyanheads
version: "2.22"
audience: external
type: workflow
---
## When to Use
- Server implementation is functionally complete (tools, resources, prompts, services all working)
- `bun run devcheck` passes, tests pass
- You're preparing for first commit, first release, or making the repo public
- User says "polish", "polish docs", "finalize", "make it ship-ready", "clean up docs", or similar
- Re-running after adding/removing tools, resources, or other surface area changes
Prefer running after implementation is complete, but safe to re-run at any point — steps are idempotent.
**Companion:** pair with `security-pass` for a full pre-ship review — this skill polishes docs and metadata; `security-pass` audits handlers for MCP-specific security gaps.
## Prerequisites
- [ ] All tools/resources/prompts implemented and registered
- [ ] `bun run devcheck` passes
- [ ] Tests pass (`bun run test`)
If these aren't met, address them first.
## Steps
### 1. Audit the Surface Area
Read all tool, resource, and prompt definitions. Build a mental model of what the server actually does — names, descriptions, input/output shapes, auth scopes. This inventory drives every document below.
Read:
- `src/index.ts` (what's registered in `createApp()`)
- All files in `src/mcp-server/tools/definitions/`
- All files in `src/mcp-server/resources/definitions/`
- All files in `src/mcp-server/prompts/definitions/`
- All files in `src/services/` (if any)
- `src/config/server-config.ts` (if any)
Capture: tool count, resource count, prompt count, service count, required env vars.
### 2. README.md
**Read the gold standard first: the `pubmed-mcp-server` README** — https://github.com/cyanheads/pubmed-mcp-server/blob/main/README.md (or `../pubmed-mcp-server/README.md` when that repo is checked out beside this one). Mirror its structure, section order, heading forms, and density. Reuse its non-server-specific content as-is — the framework line under Features, and the Getting started / Configuration / Running the server / Development guide / Contributing boilerplate — and tune only what is server-specific: the header, the Overview description, the primitives and their Capability reference entries, the domain-specific and agent-friendly bullets, env vars, project structure. Then read `references/readme.md` for the conventions spelled out; where it and the pubmed README disagree, the README wins — note the discrepancy in your report rather than editing the reference.
If `README.md` doesn't exist, create it from scratch. If it exists, diff the current content against the audit — update tool/resource/prompt tables, env var lists, and descriptions to match the actual surface area. Don't restructure sections that are already accurate and already in the gold-standard shape.
**Every run is also a concision pass.** READMEs accrete: each release adds a bullet, and nobody removes one. Accurate is not the same as done — on every run, tighten what is already there, especially the Capability reference. Read `references/readme.md` § *Concision* for what to keep and what to cut. The bar is natural prose a human reads once, that still carries every fact a caller needs before the first call, and where every retained claim has been checked against the definition it describes.
The bold header tagline (the `` text inside the first `
`) must match the `package.json` `description`. The surface count is a nested `
` inside the same `
`, separated by `•`.
### 3. Agent Protocol (CLAUDE.md / AGENTS.md)
Update the project's agent protocol file to reflect the actual server. Scope is the project-root `CLAUDE.md` / `AGENTS.md` only — **do not edit `framework-skills/*/SKILL.md` or their `references/` files**. Those are external skill files synced from `@cyanheads/mcp-ts-core` and get overwritten on the next `maintenance` refresh.
Read `references/agent-protocol.md` for the full update checklist, then review the current file and address what's stale or missing:
- If a "First Session" onboarding block is still present and onboarding is complete, it can go
- If example patterns still use generic/template names (e.g., `searchItems`, `itemData`), replace with real definitions from this server
- If server-specific skills were added, update the skills table
- Verify the structure diagram matches the actual directory layout
- If custom scripts were added to `package.json`, update the commands table
### 4. `.env.example`
Compare `.env.example` against the server config Zod schema. Add any missing server-specific vars with a comment and default (if any). Remove vars for features that no longer exist. Group by category. Preserve existing framework vars that are still relevant.
### 5. `package.json` Metadata
Check for empty or placeholder metadata fields. Read `references/package-meta.md` for which fields matter and why. Fill in anything still missing — skip fields that are already correct.
Key fields: `name`, `description`, `repository`, `author`, `homepage`, `bugs`, `keywords`.
**`name` must communicate the server's domain at a glance.** See `references/package-meta.md` for the naming convention — ambiguous abbreviations and acronym-only names fail the scannability test for humans and agents alike.
**`name` and `title` in `createApp()` / `createWorkerHandler()` must match the unscoped `package.json` `name`** — display identity is the machine name on every surface; `lint:packaging` (run by `devcheck`) enforces the match and warns when the pair is partial. `description` is never duplicated into the entrypoint — `package.json` is the canonical source (the framework derives the served description from it). Adopting the pair also seeds `OTEL_SERVICE_NAME` when unset, so telemetry's `service.name` switches to the machine name on first boot — expect a one-time series split in backends keyed on the old scoped label.
**`description` is the canonical source.** Every other surface (README header, `server.json`, Dockerfile OCI label, GitHub repo description) derives from it. Write it here first, then propagate.
### 6. `server.json`
Read `references/server-json.md` for the official MCP server manifest schema. If `server.json` doesn't exist, create it from the surface area audit. If it exists, diff against current state and update stale fields.
Key sync points:
- `$schema` set to `https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json`
- `name` matches `mcpName` from `package.json` (reverse-domain: `io.github.{owner}/{repo}`)
- `version` matches `package.json` version (in all three places: top-level + each package entry)
- `description` matches `package.json` description
- `environmentVariables` reflect the server config Zod schema — server-specific required vars in both entries, transport vars only in HTTP entry
- Two package entries: one for stdio, one for HTTP (if both transports supported)
- npm entries carry `runtimeHint: "npx"` and no `packageArguments` — a registry client runs the package's bin, which ignores `run start:*` arguments; the HTTP entry fixes `MCP_TRANSPORT_TYPE` as `"value": "http"`, never as a `default`. `lint:packaging` fails the arguments and a missing or wrong transport value
### 7. GitHub Repository Metadata
Sync the GitHub repo with `package.json` using the `gh` CLI. Skip if the repo isn't hosted on GitHub or `gh` isn't available.
**Description:**
```bash
gh repo edit / --description ""
```
**Topics ↔ Keywords:**
Compare GitHub topics (`gh repo view --json repositoryTopics`) against `package.json` `keywords`. They should be the union — add any that exist in one but not the other:
- Missing from GitHub → `gh repo edit --add-topic `
- Missing from `package.json` → add to `keywords` array
Common keywords shared across MCP servers (e.g., `mcp`, `mcp-server`, `model-context-protocol`, `typescript`) should appear in both. Domain-specific keywords should also be present in both.
### 8. `bunfig.toml`
Verify a `bunfig.toml` exists at the project root. If not, create one:
```toml
[install]
auto = "fallback"
frozenLockfile = false
[run]
bun = true
```
### 9. Changelog
Two patterns are supported — pick one and stay consistent.
| Pattern | Best for |
|:---|:---|
| **Directory-based** (template default) | Published libraries, or servers whose consumers run the `maintenance` skill against them — per-version files ship inside `node_modules//changelog/.x/.md` for direct agent inspection. |
| **Monolithic `CHANGELOG.md`** | Runtime-only consumer servers where nobody imports types and nobody runs `maintenance` against the package — skips the build step and devcheck drift gate. |
Both are acceptable. The template scaffolds the directory-based structure by default; collapse to monolithic only if the rollup tooling is pure ceremony for this project.
**Directory-based** — per-version files live at `changelog/.x/.md` (e.g. `changelog/0.1.x/0.1.0.md`), and `CHANGELOG.md` is a rollup regenerated by `bun run changelog:build`. Devcheck's `Changelog Sync` step enforces drift protection. `changelog/template.md` is a **pristine format reference** — never edited, never moved, never renamed. Read it to remember the frontmatter + section layout when scaffolding a new per-version file.
If the structure doesn't exist yet:
1. Make the `changelog/` directory
2. Create `changelog/template.md` once from the template (frontmatter stub + H1 `# — YYYY-MM-DD` placeholder + empty Added/Changed/Fixed sections) — this file is a format reference only and stays as-is after creation
3. If the server already has a shipped version (e.g. 0.1.0), create the series directory and initial entry: `changelog/0.1.x/0.1.0.md` with H1 `# 0.1.0 — YYYY-MM-DD`, concrete version and date — do **not** rename or move `template.md` to create the version file; author the per-version file directly
4. Run `bun run changelog:build` to generate `CHANGELOG.md`
Per-version file format:
```markdown
---
summary: One-line headline for the rollup index — ≤350 chars, no markdown
breaking: false
---
# 0.1.0 — YYYY-MM-DD
Optional narrative intro (1-3 sentences).
## Added
- [list tools, resources, prompts, key capabilities]
```
**Frontmatter:** `summary` is required (powers the CHANGELOG.md index), `breaking` is optional and defaults to `false` (set `true` for releases requiring consumer code changes).
Never hand-edit `CHANGELOG.md` when using this pattern — it's a build artifact. Never edit `changelog/template.md` — it's the format reference. Never use `[Unreleased]` as a version header in a released file.
**Monolithic** — maintain `CHANGELOG.md` directly in [Keep a Changelog](https://keepachangelog.com/) format. To collapse from the template default: delete the `changelog/` directory, remove `changelog:build` and `changelog:check` from `package.json` scripts (and from `devcheck.config.json` if referenced), and drop `"changelog/"` from the `files` array. The `release` skill's directory-specific steps then don't apply — just edit `CHANGELOG.md` and bump version at release time.
### 10. Plugin Metadata (Codex / Claude Code)
`lint:packaging` (run by `devcheck`) now enforces the high-value subset automatically when these manifests are present: non-empty descriptions, `version` equal to `package.json`'s, identity/install correctness — display fields (`name`, server key, `interface.displayName`) must be the **unscoped** machine name, while the `npx -y` install arg must be the full `package.json` `name` (scoped if scoped) — and the env contract below (no `""` values; every `${user_config.*}` reference declared). Opt out per project with `"packaging": { "pluginManifests": false }` in `devcheck.config.json`. The checks below cover the fields the gate doesn't (repository / license sync, category, the wording of each option).
**How user-supplied values reach the server.** Neither client passes the user's shell environment through untouched, so an env entry of `"KEY": ""` is not a hint — it is the value the server receives, and the framework reads an empty string as unset. Claude Code prompts for values declared under `userConfig` at enable time and substitutes `${user_config.