Skip to content

Add harper-mcp skill: comprehensive guide to Harper's MCP interface#69

Open
kylebernhardy wants to merge 7 commits into
mainfrom
feat/harper-mcp-skill
Open

Add harper-mcp skill: comprehensive guide to Harper's MCP interface#69
kylebernhardy wants to merge 7 commits into
mainfrom
feat/harper-mcp-skill

Conversation

@kylebernhardy

Copy link
Copy Markdown
Member

Fills the gap: no skill covered Harper's MCP interface. Adds harper-mcp/ in the established format (SKILL.md + rules.manifest.yaml + 10 synthesized rules + compiled AGENTS.md), following harper-best-practices' structure, category/priority taxonomy, and frontmatter conventions. npm run validate passes.

Coverage

Category Rules
1. Setup & Connection enabling-mcp (profiles, config surface), connecting-clients (handshake, session/protocol headers, debugging cheat sheet)
2. Tools & Prompts automatic-verb-tools (RBAC-filtered CRUD tools), custom-mcp-tools (incl. the anonymous-exposure security model + gating pattern), custom-mcp-prompts
3. Resources resources-surface (harper://, harper+rest://, subscriptions, list_changed), custom-mcp-resources (templates, {name} vs {+name} + encoded-separator guard, completions, reserved schemes)
4. Operations & Security rate-limiting (session + per-client buckets, identityHeader trust model), durable-quotas (fail-closed hook, race-safe counter guidance), security-posture (hardening checklist)

Content is authored from the current 5.1/5.2 implementation with runtime-verified behavior (the MCP feature work in harper#1613/#1633/#1694), with version availability noted inline (custom resources 5.1.18+, per-client limits and quotas 5.2.0+).

Open item for review

scripts/build.mjs (the npm dist bundle) is hardcoded to harper-best-practices; this PR deliberately does not change the published package shape. If harper-mcp should ship in the npm artifact too, that's a packaging decision (multi-skill exports vs per-skill packages) worth its own PR.

Generated by an LLM (Claude Fable 5).

Ten synthesized rules across four categories: setup & connection
(profiles/config, wire handshake incl. session and protocol-version
header semantics), tools & prompts (automatic RBAC-filtered verb tools,
custom mcpTools with the anonymous-exposure model, mcpPrompts),
resources (harper:// metadata, harper+rest:// descriptors,
subscriptions, custom mcpResources with template semantics and the
encoded-separator guard), and operations & security (session/per-client
rate limiting incl. identityHeader trust model, durable quota hook with
race-safe counter guidance, hardening checklist). Content authored from
the Harper 5.1/5.2 implementation and runtime-verified behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy kylebernhardy requested a review from a team as a code owner July 8, 2026 22:43
@kylebernhardy kylebernhardy requested a review from kriszyp July 8, 2026 22:43

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds comprehensive documentation and rules for Harper's Model Context Protocol (MCP) interface, covering setup, tools, prompts, resources, rate limiting, durable quotas, and security. The review feedback identifies several issues in the provided code examples that should be addressed to prevent potential security vulnerabilities and runtime errors: a path traversal risk in the custom resources example, a potential null pointer dereference in the custom prompts example, and a potential NaN evaluation in the durable quotas example.

Comment thread harper-mcp/rules/custom-mcp-resources.md
Comment thread harper-mcp/AGENTS.md
Comment thread harper-mcp/rules/custom-mcp-prompts.md Outdated
Comment thread harper-mcp/AGENTS.md Outdated
Comment thread harper-mcp/rules/durable-quotas.md
Comment thread harper-mcp/AGENTS.md
…s for flippable rules

Grounding pass against harper main + merged docs found four fidelity
gaps, all corrected: verb-tool names preserve Resource-path case
(get_Widget, with deterministic collision prefixing); allow/deny globs
are an operations-profile knob (replacing a read-only default), not an
application one — application trimming is exportTypes.mcp + RBAC;
maxTools is the tools/list page size (default 200), not a generation
cap; mcpPrompts entries carry a render(args) function returning the
messages shape, not a method name.

Manifest now declares sources + must_cover for the five rules that map
1:1 to canonical documentation pages (enabling-mcp, resources-surface,
custom-mcp-resources, rate-limiting, durable-quotas), readying the flip
to mode: generate; the remaining five are agent-oriented synthesis with
no single docs source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy

Copy link
Copy Markdown
Member Author

Two follow-ups pushed: (1) a grounding pass against the current implementation caught and fixed four fidelity gaps — verb-tool name casing (get_Widget), allow/deny being operations-profile-only, maxTools being a page size not a cap, and mcpPrompts using a render(args) function rather than a method name; (2) the manifest now declares sources + must_cover for the five rules that map 1:1 to canonical docs pages, so flipping them to mode: generate on the docs-driven pipeline is a one-line manifest edit once a generation run is reviewed. The other five rules are agent-oriented synthesis (debugging cheat sheet, security checklist, gating patterns) with no single docs source and should stay synthesized. Comment generated by an LLM (Claude Fable 5).

Cross-model review follow-ups (Codex + Gemini):
- Register harper-mcp in the SKILLS registry so validate-generated's
  manifest lint and AGENTS.md / SKILL.md round-trip checks actively
  cover it; regenerate both artifacts through the repo renderer so the
  round-trips pass by construction.
- The AGENTS.md lead paragraph moves from a hardcoded constant into the
  per-skill registry (agentsLead) — the single-skill assumption broke
  with a second skill.
- Category maps gain the harper-mcp categories; the ops key collision is
  avoided by naming the category `security`.
- Manifest: explicit mode: synthesized on every entry (house style);
  planned sources/must_cover mappings preserved as comments (the schema
  only permits them on generate rules) with a note that mcpPrompts is
  currently undocumented in reference/mcp/; cross_links added.
- package.json files + .npmignore include harper-mcp so the markdown
  ships to package consumers (dist/index.js export shape deliberately
  unchanged — flagged in the PR as a separate packaging decision).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy

Copy link
Copy Markdown
Member Author

Cross-model review round (Codex + Gemini) adjudicated in 0d8a451:

Codex (both findings taken):

  • harper-mcp is now in package.json files + .npmignore, so the markdown ships to package consumers. The dist/index.js export shape is deliberately unchanged — still flagged in the description as its own packaging decision.
  • The skill is registered in the generation pipeline's SKILLS registry, so validate-generated now actively lints its manifest and round-trips its AGENTS.md/SKILL.md index (both regenerated through the repo renderer so they pass by construction). This surfaced two latent single-skill assumptions, fixed here: the AGENTS.md lead paragraph is now per-skill (agentsLead in the registry), and the category maps gained the harper-mcp categories (the ops key collision avoided by naming the new category security).

Gemini (2 taken, 1 declined, 1 deferred-by-schema):

  • Explicit mode: synthesized added to every manifest entry (house style); cross_links added.
  • Sources mapped for automatic-verb-tools and custom-mcp-tools — but the manifest linter only permits live sources/must_cover on mode: generate rules, so all planned mappings are preserved as structured comments to uncomment with the flip.
  • Declined the Prefix-column suggestion: harper-best-practices' rule files don't carry prefixes either; the column is notational house convention.
  • Noted along the way: mcpPrompts has no documentation in reference/mcp/ (shipped 5.1.10) — recorded in the manifest as the reason that rule can't be docs-sourced yet; worth a docs issue.

Comment generated by an LLM (Claude Fable 5).

A clean-room agent evaluation (agent restricted to the skill as its
only Harper-MCP knowledge, building the protected docs-server scenario
against a live instance) surfaced four gaps, all fixed:

- Version-verification procedure: unsupported config keys — including
  the rateLimit.perClient*/quota.* security controls — are accepted
  and SILENTLY IGNORED by older Harper versions; the skill now says to
  check serverInfo.version and prove each protection denies once.
- The durable-quota example exposed its own counter table (exporting
  the hook class surfaces update_/delete_ verb tools and REST, letting
  clients reset their own counters); the example now carries
  exportTypes: { mcp: false } and a permissions note, and the
  security-posture checklist gained both items.
- Exported plain-Resource classes surface partial verb-tool families
  (a lone create_*) — now documented.
- "Tools stay in sync without restarts" gained its 5.1.18+ gate: the
  agent independently reproduced the pre-5.1.18 empty-registry-after-
  restart bug that harper#1613's lazy rebuild fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy

Copy link
Copy Markdown
Member Author

Ran a blind agent tire-kick: a clean-room agent, restricted to this skill as its only source of Harper-MCP knowledge, built the skill's flagship scenario (protected public docs server: custom resources + cost-bearing tool + per-client limit + durable quota) against a live instance and graded the experience. Wire-level accuracy came back excellent (handshake, headers, the full 400/400/406 debugging cheat sheet, tool result shapes — all verified exactly), and the agent independently reproduced the pre-5.1.18 restart-registration bug that harper#1613 fixed. Four real gaps surfaced and are fixed in the latest commit:

  1. Version verification is now a procedure, not a footnote — older versions accept and silently ignore the rateLimit.perClient*/quota.* security keys, so the skill now says: check serverInfo.version, then prove each protection denies once. (The evaluation ran on a pre-feature build, which made this failure mode vivid: config accepted, quota never invoked, no error anywhere.)
  2. The durable-quota example no longer exposes its own counter table — exporting the hook class surfaces update_/delete_ tools + REST for the quota table, letting clients reset their own counters. Example now carries exportTypes: { mcp: false }; the hardening checklist gained both this and the version-proof item.
  3. Documented that exported plain-Resource classes surface a partial verb family (a lone create_*).
  4. "Tools stay in sync without restarts" now carries its 5.1.18+ gate, with the pre-5.1.18 restart failure mode described.

Note for the docs repo: finding 2 applies to the merged quota example in reference/mcp/configuration.md as well — follow-up there.

Comment generated by an LLM (Claude Fable 5); evaluation by a sandboxed subagent.

@kriszyp

kriszyp commented Jul 9, 2026

Copy link
Copy Markdown
Member

Content checks out — cross-referenced every specific MCP claim (rate-limit config, quota hook contract, resource URIs, transport headers/status codes) against components/mcp/*.ts on main and it's accurate, including some genuinely subtle details.

Two things:

  1. The compiled harper-mcp/AGENTS.md ships with the wrong H1 (# Harper Best Practices, copy-pasted) — root cause is assembleAgentsMd in render.mjs hardcoding the title string instead of parameterizing it alongside agentsLead, which this PR does correctly thread through. Small fix: give each SKILLS manifest entry an agentsTitle and use it in place of the hardcoded string.
  2. Going forward we want to prefer generating skill content from documentation rather than hand-synthesizing — all 10 rules here are mode: synthesized even though rules.manifest.yaml already has commented-out sources:/must_cover: blocks mapping most of them 1:1 to existing pages under dev/documentation/reference/mcp/. That's real, actively-duplicated content today (913 lines covering much of the same ground). This mirrors how harper-best-practices itself started — fully synthesized, then flipped ~15 of 19 rules to mode: generate in follow-ups — so the staged approach isn't unreasonable, but given the stated preference, I'd rather see this PR (or an immediate follow-up) do the generate flip before/soon after merge rather than let two independently-maintained descriptions of the same surface drift apart.

— KrAIs

@Ethan-Arrowood Ethan-Arrowood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really strong skill — the security emphasis (anonymous exposure model, silently-ignored config keys, "prove each protection denies once") is exactly what this content needs, and Kris already verified the wire-level claims against the implementation.

Requesting changes to get the outstanding feedback resolved before merge:

  1. Kris's H1 fix — the compiled harper-mcp/AGENTS.md ships titled # Harper Best Practices (hardcoded in assembleAgentsMd). Since this now ships in the npm package, that needs the agentsTitle fix before merging.
  2. Gemini's example-code findings — please adjudicate each thread: the {+path} traversal guard in custom-mcp-resources seems worth taking (the rule's own prose explains why {+name} is multi-segment-unsafe, so the example shouldn't model the unguarded pattern), the null guard in custom-mcp-prompts is a trivial accept, and the NaN one is fine to decline with a short reply.

Happy to re-review once those are in — the content itself is in great shape. Thanks!

sent with Claude Fable 5

kylebernhardy and others added 2 commits July 9, 2026 18:44
…d 2)

- assembleAgentsMd hardcoded '# Harper Best Practices' as the H1 —
  parameterized as agentsTitle in the SKILLS registry alongside
  agentsLead (kriszyp), threaded through generate/validate call sites;
  harper-mcp/AGENTS.md now carries its own title.
- custom-mcp-resources example: the {+path} read now models the safe
  keyed-lookup pattern with an explicit warning against unvalidated
  filesystem path construction (gemini bot, seconded by Ethan — the
  rule's own prose says {+name} spans segments, so the example must
  not model the unguarded pattern).
- custom-mcp-prompts example: null-ticket guard (gemini bot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The prior commit threaded it through generate-rules but the validator
call site edit silently no-op'd (unasserted string replace against an
already-reformatted line) — the round-trip was comparing against the
default title.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kylebernhardy

Copy link
Copy Markdown
Member Author

Round 2 addressed (dec41cf + 64d2766):

@kriszyp #1 / @Ethan-Arrowood #1 — H1 fix: assembleAgentsMd now takes a title, sourced from a new agentsTitle field on each SKILLS registry entry (alongside agentsLead), threaded through both the generator and the validator's round-trip. harper-mcp/AGENTS.md now opens with # Harper MCP, and the validator genuinely enforces it (my first attempt threaded the generator but silently missed the validator call site — caught because the round-trip failed against the corrected artifact).

@Ethan-Arrowood #2 — Gemini threads adjudicated per your split: the {+path} example now models the safe keyed-lookup pattern with an explicit warning against unvalidated filesystem path construction (taken — the rule's prose says {+name} spans segments, so the example mustn't model the unguarded shape); the prompts example gained the null-ticket guard (taken); the ?? 0 NaN suggestion declined with rationale on the thread.

@kriszyp #2 — generate flip: agreed, and the manifest already carries the full sources/must_cover mapping (as comments — the schema only allows them live on mode: generate). Plan: immediate follow-up PR after this merges that flips the 7 docs-mapped rules and reviews the generation output, mirroring harper-best-practices' phased path. custom-mcp-prompts stays synthesized until documentation#575 fills the mcpPrompts docs gap (filed during this PR's tire-kick).

Comment generated by an LLM (Claude Fable 5).

…idance

A static exportTypes field on an exported class is never read (kriszyp
on documentation#576) — the wired mechanism is
server.resources.set(name, Class, { mcp: false, rest: false }).
Runtime-verified on main: hook class absent from tools/list, REST 404,
quota hook still resolves and denies. Also documents that mcp: false
excludes the class's own custom mcpTools, so cost-bearing tools belong
on a separate class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kriszyp

kriszyp commented Jul 10, 2026

Copy link
Copy Markdown
Member

Plan: immediate follow-up PR

Sorry, why?

@kylebernhardy

Copy link
Copy Markdown
Member Author

Fair question — "follow-up" was the wrong framing on my part. The real reason isn't process, it's that the docs sources aren't merge-ready to generate from yet:

  1. reference/mcp/configuration.md still has a wrong quota example. The mcp.<profile>.quota.* section models gating the hook table with a static exportTypes = { mcp: false } field — which Harper never reads off the class (you caught the same line here). I'm fixing it in documentation#576 (→ server.resources.set('McpQuota', McpQuota, { mcp: false, rest: false }), runtime-verified), but it hasn't merged. Flipping durable-quotas/rate-limiting to generate before that lands would bake the broken mechanism straight into the skill.
  2. custom-mcp-prompts has no docs page at allmcpPrompts is undocumented (documentation#575); it has to stay synthesized until that gap is filled.
  3. The value of this PR's content is that it was verified against the implementation (grounding pass + a blind-agent tire-kick), not against the docs — which themselves had at least one wrong example. Generating is an LLM rewrite of those docs, so the output needs the same verification pass, not a rubber stamp.

So the sequencing that actually serves the anti-drift goal is: land the docs fixes (#576, and #575 for prompts), then flip. I'm fine doing that flip in this PR — hold it open, rebase once #576 merges, generate + verify the 6 flippable rules, keep custom-mcp-prompts synthesized pending #575 — rather than a separate follow-up, if you'd prefer it all in one. The manifest already carries the sources/must_cover mappings as comments, so it's a mechanical uncomment-and-generate once the sources are right.

Your call on hold-and-flip-here vs merge-now-flip-next; either way the flip is gated on #576 landing, not on merging this.

Comment generated by an LLM (Claude Fable 5).

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition — this is a genuinely useful, thorough skill doc for the MCP interface, and the earlier agentsTitle fix (so harper-mcp/AGENTS.md now opens with the right H1 instead of the generic one) looks solid.

Two API-accuracy issues worth a quick fix, though neither should block merging — it sounds like this material is somewhat temporary, so flagging for a fast follow-up rather than requesting changes:

  1. automatic-verb-tools.md:47 (duplicated in the compiled AGENTS.md:151) shows gating MCP export via server.http(InternalThing, { exportTypes: { mcp: false } }). Checking server/http.ts on origin/main of dev/harper, httpServer() never reads options.exportTypes — this silently misregisters the resource instead of gating anything. The real mechanism is resources.set(path, Resource, { mcp: false }), which this same PR already gets right in durable-quotas.md/security-posture.md (commit 9ad155d1 fixed those two but missed this one).

  2. custom-mcp-prompts.md:28 documents render(args) with no second parameter, but the real dispatch in components/mcp/transport.ts calls render(args, { user, profile, sessionId }). Since the example serves a support-ticket body through prompts/get (exposed to all sessions, including anonymous ones, per the rule's own prose), an author copying this as-is has no way to know they can gate on context.user — worth adding before someone ships the unguarded version.

Both are small, mechanical fixes (swap the registration call / add the context param plus a one-line gate example) — not blocking, just worth doing before or shortly after merge.

— Claude (Sonnet 5), reviewed via review-queue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants