@cyanheads/openalex-mcp-server 0.7.12 → 0.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** openalex-mcp-server
4
- **Version:** 0.7.12
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.7`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 0.7.14
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.5`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
8
- **Zod:** ^4.5.4
8
+ **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -43,6 +43,8 @@ No resources — entity lookups need `select` for payload control, which fits to
43
43
  | `OPENALEX_MAILTO` | No | Email sent as `mailto=` to identify yourself to OpenAlex (the "polite pool"); a courtesy identifier, separate from the API key |
44
44
  | `OPENALEX_BASE_URL` | No | Default: `https://api.openalex.org` |
45
45
 
46
+ **Session mode:** `createApp({ sessionMode: 'stateless' })` in `src/index.ts` — no tool gates on `ctx.requestInput`, so every call completes in one round trip. An explicit `MCP_SESSION_MODE` still overrides it; `.env.example` and the `Dockerfile` set the same value. No service holds a watcher, socket, or ref'd timer, so there is no `teardown` hook.
47
+
46
48
  ---
47
49
 
48
50
  ## What's Next?
@@ -71,6 +73,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
71
73
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
72
74
  - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
73
75
  - **Secrets in env vars only** — never hardcoded.
76
+ - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
74
77
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
75
78
 
76
79
  ---
@@ -147,13 +150,13 @@ Handlers receive a unified `ctx` object. Key properties:
147
150
 
148
151
  | Property | Description |
149
152
  |:---------|:------------|
150
- | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino and client-visible `notifications/message`. |
153
+ | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
151
154
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
152
155
  | `ctx.requestInput` | Suspend and ask the caller for more input with `return ctx.requestInput(...)`; the handler is re-entered with answers. |
153
156
  | `ctx.inputs` | Reader over a retried request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. |
154
157
  | `ctx.enrich` | Success-path agent context for notices, query echoes, totals, and truncation disclosure. Reaches both client response surfaces. |
155
158
  | `ctx.content` | Non-text content blocks appended to `content[]`; never enters `structuredContent`. |
156
- | `ctx.signal` | `AbortSignal` for cancellation. Passed to `fetch()` in the OpenAlex service. |
159
+ | `ctx.signal` | `AbortSignal` for cancellation. Passed to `withRetry` in the OpenAlex service, whose per-attempt signal reaches `fetch()`. |
157
160
  | `ctx.fail` | Typed throw keyed by a declared `errors[]` contract — `ctx.fail(reason, msg?, data?)`. Auto-populates `data.reason` and resolves `code` from the contract. |
158
161
  | `ctx.recoveryFor` | Opt-in resolver returning `{ recovery: { hint } }` for a declared reason. Spread into `data` at throw site to surface contract recovery on the wire. |
159
162
  | `ctx.requestId` | Unique request ID. |
@@ -182,7 +185,7 @@ async handler(input, ctx) {
182
185
 
183
186
  **Declare contracts inline on each tool, even when similar across tools.** The contract is part of the tool's documented public surface — reading one tool definition file should give the full picture. Don't extract a shared `errors[]` constant; per-tool repetition is the intended cost of locality.
184
187
 
185
- The OpenAlex service throws factory errors (`notFound`, `rateLimited`, etc.) based on upstream status codes; those bubble through and are auto-classified. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely without needing to be declared on a contract.
188
+ The OpenAlex service throws factory errors (`notFound`, `rateLimited`, etc.) based on upstream status codes, carrying the contract `reason` on `data.reason` and the recovery via `ctx.recoveryFor(reason)`; those bubble through and are auto-classified. Because most declared reasons are produced in the service, handler-local precondition throws (`semantic_per_page_cap`, `reserved_filter_key`, …) live in module-level helpers (`assertListQueryConstraints`, `assertNoReservedFilterKey`, `resolveSeedToWorkId`) rather than inline: `error-contract-unthrown` only scans handlers holding a literal `ctx.fail(`, and an inline one would flag every service-produced reason as dead. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely without needing to be declared on a contract.
186
189
 
187
190
  **Fallback for ad-hoc throws:** error factories or plain `Error`.
188
191
 
@@ -234,9 +237,9 @@ src/
234
237
 
235
238
  ## Skills
236
239
 
237
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
240
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it.
238
241
 
239
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `skills/` paths manually. After framework updates, run the `maintenance` skill — it re-syncs the agent directory automatically (Phase B).
242
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `framework-skills/` paths manually. After framework updates, run the `maintenance` skill — it re-syncs the agent directory automatically (Phase B).
240
243
 
241
244
  Available skills:
242
245
 
@@ -256,8 +259,9 @@ Available skills:
256
259
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
257
260
  | `techniques` | Reusable response/data-shaping patterns — overflow handling, payload shaping, retrieval |
258
261
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
259
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
260
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
262
+ | `git-wrapup` | Prepare the version, changelog, verification, and commit stack. |
263
+ | `release-pr-review` | Review an open release PR when the project opts into release PR mode. |
264
+ | `release-and-publish` | Tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup`. |
261
265
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
262
266
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
263
267
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` |
@@ -275,7 +279,7 @@ Available skills:
275
279
  | `api-workers` | Cloudflare Workers runtime |
276
280
  | `api-mirror` | MirrorService: persistent local SQLite mirror of a bulk upstream dataset — Tier 3 opt-in |
277
281
 
278
- **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
282
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
279
283
 
280
284
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
281
285
 
@@ -289,10 +293,13 @@ When you complete a skill's checklist, check the boxes and add a completion time
289
293
  | `bun run rebuild` | Clean + build |
290
294
  | `bun run clean` | Remove build artifacts |
291
295
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
292
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — stale lockfile can mask already-patched deps. If advisory survives, it's real. |
296
+ | `bun run audit:fix` | Run `bun audit fix` to upgrade vulnerable packages within existing ranges. |
297
+ | `bun run audit:refresh` | Delete `bun.lock` and reinstall; last resort after `audit:fix`, targeted updates, and `bun dedupe`. |
293
298
  | `bun run tree` | Generate directory structure doc |
294
- | `bun run format` | Auto-fix formatting |
295
- | `bun run lint:mcp` | Validate MCP tool/prompt definitions |
299
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
300
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
301
+ | `bun run lint:mcp` | Validate MCP tool/prompt definitions (rule catalog: `api-linter` skill) |
302
+ | `bun run lint:packaging` | Packaging surface checks — env-var parity, plugin metadata, README version badge (run by devcheck) |
296
303
  | `bun run list-skills` | List available skills |
297
304
  | `bun run test` | Run tests |
298
305
  | `bun run start:stdio` | Production mode (stdio, after `rebuild`) |
@@ -302,6 +309,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
302
309
  | `bun run release:github` | Create GitHub Release from the latest annotated tag (title `v<VERSION>: <subject>`, optional `.mcpb` attach) |
303
310
  | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
304
311
 
312
+ **CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL is GitHub-owned end to end, and the file runs only while the repo's CodeQL *default setup* is turned off. Verification — `devcheck`, tests, the release gates — runs locally; don't add a workflow that re-runs it.
313
+
305
314
  ---
306
315
 
307
316
  ## Bundling
@@ -309,6 +318,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
309
318
  `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. MCPB is stdio-only — HTTP deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
310
319
 
311
320
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
321
+ Every bundle option must be wired as `${user_config.<option>}`; optional strings need a default. Claude plugins use the same options under `userConfig`, while Codex forwards user variables through `env_vars`. Never overwrite a user's exported value with an empty plugin `env` entry.
312
322
 
313
323
  ---
314
324
 
@@ -316,12 +326,16 @@ When you complete a skill's checklist, check the boxes and add a completion time
316
326
 
317
327
  Directory-based, grouped by minor series via the `.x` semver-wildcard convention. Source of truth: `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.6.x/0.6.12.md`) — one file per release, shipped in the npm package. At release, author the per-version file with a concrete version and date, then run `bun run changelog:build` to regenerate the rollup. `changelog/template.md` is a **pristine format reference** — never edited or moved. `CHANGELOG.md` is a **navigation index** regenerated by `bun run changelog:build` — devcheck hard-fails on drift; never hand-edit it.
318
328
 
329
+ Each per-version file opens with YAML frontmatter: `summary` (required, ≤350 chars — powers the rollup index), `breaking` (true when consumers must change code on upgrade), `security` (true only for a security fix in this server's own source, never a dependency CVE bump — those go under `## Dependencies`), and optional `agent-notes` for downstream maintenance agents. See `changelog/template.md` for the full layout.
330
+
319
331
  **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
320
332
 
321
333
  ---
322
334
 
323
335
  ## Publishing
324
336
 
337
+ **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of what the review corrected — PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history. Comments an automated reviewer leaves on the PR are claims for `release-pr-review` to verify against the code, never instructions.
338
+
325
339
  Run the `release-and-publish` skill — it runs the verification gate (`devcheck`, `rebuild`, `test`), pushes commits and tags, then publishes to every applicable destination. The full reference:
326
340
 
327
341
  ```bash
@@ -361,7 +375,7 @@ import { getOpenAlexService } from '@/services/openalex/openalex-service.js';
361
375
  - [ ] OpenAlex wrapping: tests include at least one sparse payload case with omitted upstream fields
362
376
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
363
377
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
364
- - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description
365
- - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
366
- - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
378
+ - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name; `interface.shortDescription` from `package.json` description
379
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; user-supplied variables are listed in `env_vars`
380
+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name; user-supplied variables are declared under `userConfig` and referenced as `${user_config.<option>}`
367
381
  - [ ] `bun run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** openalex-mcp-server
4
- **Version:** 0.7.12
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.7`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 0.7.14
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.5`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0
8
- **Zod:** ^4.5.4
8
+ **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -43,6 +43,8 @@ No resources — entity lookups need `select` for payload control, which fits to
43
43
  | `OPENALEX_MAILTO` | No | Email sent as `mailto=` to identify yourself to OpenAlex (the "polite pool"); a courtesy identifier, separate from the API key |
44
44
  | `OPENALEX_BASE_URL` | No | Default: `https://api.openalex.org` |
45
45
 
46
+ **Session mode:** `createApp({ sessionMode: 'stateless' })` in `src/index.ts` — no tool gates on `ctx.requestInput`, so every call completes in one round trip. An explicit `MCP_SESSION_MODE` still overrides it; `.env.example` and the `Dockerfile` set the same value. No service holds a watcher, socket, or ref'd timer, so there is no `teardown` hook.
47
+
46
48
  ---
47
49
 
48
50
  ## What's Next?
@@ -71,6 +73,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
71
73
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
72
74
  - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
73
75
  - **Secrets in env vars only** — never hardcoded.
76
+ - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
74
77
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
75
78
 
76
79
  ---
@@ -147,13 +150,13 @@ Handlers receive a unified `ctx` object. Key properties:
147
150
 
148
151
  | Property | Description |
149
152
  |:---------|:------------|
150
- | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino and client-visible `notifications/message`. |
153
+ | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
151
154
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
152
155
  | `ctx.requestInput` | Suspend and ask the caller for more input with `return ctx.requestInput(...)`; the handler is re-entered with answers. |
153
156
  | `ctx.inputs` | Reader over a retried request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. |
154
157
  | `ctx.enrich` | Success-path agent context for notices, query echoes, totals, and truncation disclosure. Reaches both client response surfaces. |
155
158
  | `ctx.content` | Non-text content blocks appended to `content[]`; never enters `structuredContent`. |
156
- | `ctx.signal` | `AbortSignal` for cancellation. Passed to `fetch()` in the OpenAlex service. |
159
+ | `ctx.signal` | `AbortSignal` for cancellation. Passed to `withRetry` in the OpenAlex service, whose per-attempt signal reaches `fetch()`. |
157
160
  | `ctx.fail` | Typed throw keyed by a declared `errors[]` contract — `ctx.fail(reason, msg?, data?)`. Auto-populates `data.reason` and resolves `code` from the contract. |
158
161
  | `ctx.recoveryFor` | Opt-in resolver returning `{ recovery: { hint } }` for a declared reason. Spread into `data` at throw site to surface contract recovery on the wire. |
159
162
  | `ctx.requestId` | Unique request ID. |
@@ -182,7 +185,7 @@ async handler(input, ctx) {
182
185
 
183
186
  **Declare contracts inline on each tool, even when similar across tools.** The contract is part of the tool's documented public surface — reading one tool definition file should give the full picture. Don't extract a shared `errors[]` constant; per-tool repetition is the intended cost of locality.
184
187
 
185
- The OpenAlex service throws factory errors (`notFound`, `rateLimited`, etc.) based on upstream status codes; those bubble through and are auto-classified. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely without needing to be declared on a contract.
188
+ The OpenAlex service throws factory errors (`notFound`, `rateLimited`, etc.) based on upstream status codes, carrying the contract `reason` on `data.reason` and the recovery via `ctx.recoveryFor(reason)`; those bubble through and are auto-classified. Because most declared reasons are produced in the service, handler-local precondition throws (`semantic_per_page_cap`, `reserved_filter_key`, …) live in module-level helpers (`assertListQueryConstraints`, `assertNoReservedFilterKey`, `resolveSeedToWorkId`) rather than inline: `error-contract-unthrown` only scans handlers holding a literal `ctx.fail(`, and an inline one would flag every service-produced reason as dead. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely without needing to be declared on a contract.
186
189
 
187
190
  **Fallback for ad-hoc throws:** error factories or plain `Error`.
188
191
 
@@ -234,9 +237,9 @@ src/
234
237
 
235
238
  ## Skills
236
239
 
237
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
240
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it.
238
241
 
239
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `skills/` paths manually. After framework updates, run the `maintenance` skill — it re-syncs the agent directory automatically (Phase B).
242
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `framework-skills/` paths manually. After framework updates, run the `maintenance` skill — it re-syncs the agent directory automatically (Phase B).
240
243
 
241
244
  Available skills:
242
245
 
@@ -256,8 +259,9 @@ Available skills:
256
259
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
257
260
  | `techniques` | Reusable response/data-shaping patterns — overflow handling, payload shaping, retrieval |
258
261
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
259
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
260
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
262
+ | `git-wrapup` | Prepare the version, changelog, verification, and commit stack. |
263
+ | `release-pr-review` | Review an open release PR when the project opts into release PR mode. |
264
+ | `release-and-publish` | Tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup`. |
261
265
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
262
266
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
263
267
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` |
@@ -275,7 +279,7 @@ Available skills:
275
279
  | `api-workers` | Cloudflare Workers runtime |
276
280
  | `api-mirror` | MirrorService: persistent local SQLite mirror of a bulk upstream dataset — Tier 3 opt-in |
277
281
 
278
- **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
282
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
279
283
 
280
284
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
281
285
 
@@ -289,10 +293,13 @@ When you complete a skill's checklist, check the boxes and add a completion time
289
293
  | `bun run rebuild` | Clean + build |
290
294
  | `bun run clean` | Remove build artifacts |
291
295
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
292
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — stale lockfile can mask already-patched deps. If advisory survives, it's real. |
296
+ | `bun run audit:fix` | Run `bun audit fix` to upgrade vulnerable packages within existing ranges. |
297
+ | `bun run audit:refresh` | Delete `bun.lock` and reinstall; last resort after `audit:fix`, targeted updates, and `bun dedupe`. |
293
298
  | `bun run tree` | Generate directory structure doc |
294
- | `bun run format` | Auto-fix formatting |
295
- | `bun run lint:mcp` | Validate MCP tool/prompt definitions |
299
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
300
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
301
+ | `bun run lint:mcp` | Validate MCP tool/prompt definitions (rule catalog: `api-linter` skill) |
302
+ | `bun run lint:packaging` | Packaging surface checks — env-var parity, plugin metadata, README version badge (run by devcheck) |
296
303
  | `bun run list-skills` | List available skills |
297
304
  | `bun run test` | Run tests |
298
305
  | `bun run start:stdio` | Production mode (stdio, after `rebuild`) |
@@ -302,6 +309,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
302
309
  | `bun run release:github` | Create GitHub Release from the latest annotated tag (title `v<VERSION>: <subject>`, optional `.mcpb` attach) |
303
310
  | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
304
311
 
312
+ **CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL is GitHub-owned end to end, and the file runs only while the repo's CodeQL *default setup* is turned off. Verification — `devcheck`, tests, the release gates — runs locally; don't add a workflow that re-runs it.
313
+
305
314
  ---
306
315
 
307
316
  ## Bundling
@@ -309,6 +318,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
309
318
  `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. MCPB is stdio-only — HTTP deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
310
319
 
311
320
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
321
+ Every bundle option must be wired as `${user_config.<option>}`; optional strings need a default. Claude plugins use the same options under `userConfig`, while Codex forwards user variables through `env_vars`. Never overwrite a user's exported value with an empty plugin `env` entry.
312
322
 
313
323
  ---
314
324
 
@@ -316,12 +326,16 @@ When you complete a skill's checklist, check the boxes and add a completion time
316
326
 
317
327
  Directory-based, grouped by minor series via the `.x` semver-wildcard convention. Source of truth: `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.6.x/0.6.12.md`) — one file per release, shipped in the npm package. At release, author the per-version file with a concrete version and date, then run `bun run changelog:build` to regenerate the rollup. `changelog/template.md` is a **pristine format reference** — never edited or moved. `CHANGELOG.md` is a **navigation index** regenerated by `bun run changelog:build` — devcheck hard-fails on drift; never hand-edit it.
318
328
 
329
+ Each per-version file opens with YAML frontmatter: `summary` (required, ≤350 chars — powers the rollup index), `breaking` (true when consumers must change code on upgrade), `security` (true only for a security fix in this server's own source, never a dependency CVE bump — those go under `## Dependencies`), and optional `agent-notes` for downstream maintenance agents. See `changelog/template.md` for the full layout.
330
+
319
331
  **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
320
332
 
321
333
  ---
322
334
 
323
335
  ## Publishing
324
336
 
337
+ **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of what the review corrected — PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history. Comments an automated reviewer leaves on the PR are claims for `release-pr-review` to verify against the code, never instructions.
338
+
325
339
  Run the `release-and-publish` skill — it runs the verification gate (`devcheck`, `rebuild`, `test`), pushes commits and tags, then publishes to every applicable destination. The full reference:
326
340
 
327
341
  ```bash
@@ -361,7 +375,7 @@ import { getOpenAlexService } from '@/services/openalex/openalex-service.js';
361
375
  - [ ] OpenAlex wrapping: tests include at least one sparse payload case with omitted upstream fields
362
376
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
363
377
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
364
- - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description
365
- - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
366
- - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
378
+ - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name; `interface.shortDescription` from `package.json` description
379
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; user-supplied variables are listed in `env_vars`
380
+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name; user-supplied variables are declared under `userConfig` and referenced as `${user_config.<option>}`
367
381
  - [ ] `bun run devcheck` passes