@harperfast/template-react-ts-studio 1.11.2 → 1.11.4

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.
Files changed (54) hide show
  1. package/.agents/skills/harper-best-practices/AGENTS.md +702 -490
  2. package/.agents/skills/harper-best-practices/SKILL.md +1 -0
  3. package/.agents/skills/harper-best-practices/rules/automatic-apis.md +46 -78
  4. package/.agents/skills/harper-best-practices/rules/defining-relationships.md +41 -63
  5. package/.agents/skills/harper-best-practices/rules/delegating-to-the-built-in-agent.md +141 -0
  6. package/.agents/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +59 -45
  7. package/.agents/skills/harper-best-practices/rules/load-env.md +24 -23
  8. package/.agents/skills/harper-best-practices/rules/programmatic-table-requests.md +62 -78
  9. package/.agents/skills/harper-best-practices/rules/querying-rest-apis.md +92 -65
  10. package/.agents/skills/harper-best-practices/rules/schema-design-tooling.md +53 -55
  11. package/.agents/skills/harper-best-practices/rules/v5-upgrade.md +98 -62
  12. package/.agents/skills/harper-best-practices/rules/vector-indexing.md +113 -42
  13. package/.agents/skills/harper-best-practices/rules.manifest.yaml +9 -0
  14. package/.agents/skills/harper-mcp/AGENTS.md +501 -0
  15. package/.agents/skills/harper-mcp/SKILL.md +91 -0
  16. package/.agents/skills/harper-mcp/rules/automatic-verb-tools.md +50 -0
  17. package/.agents/skills/harper-mcp/rules/connecting-clients.md +59 -0
  18. package/.agents/skills/harper-mcp/rules/custom-mcp-prompts.md +52 -0
  19. package/.agents/skills/harper-mcp/rules/custom-mcp-resources.md +64 -0
  20. package/.agents/skills/harper-mcp/rules/custom-mcp-tools.md +72 -0
  21. package/.agents/skills/harper-mcp/rules/durable-quotas.md +73 -0
  22. package/.agents/skills/harper-mcp/rules/enabling-mcp.md +63 -0
  23. package/.agents/skills/harper-mcp/rules/rate-limiting.md +62 -0
  24. package/.agents/skills/harper-mcp/rules/resources-surface.md +44 -0
  25. package/.agents/skills/harper-mcp/rules/security-posture.md +36 -0
  26. package/.agents/skills/harper-mcp/rules.manifest.yaml +203 -0
  27. package/agent/skills/harper-best-practices/AGENTS.md +702 -490
  28. package/agent/skills/harper-best-practices/SKILL.md +1 -0
  29. package/agent/skills/harper-best-practices/rules/automatic-apis.md +46 -78
  30. package/agent/skills/harper-best-practices/rules/defining-relationships.md +41 -63
  31. package/agent/skills/harper-best-practices/rules/delegating-to-the-built-in-agent.md +141 -0
  32. package/agent/skills/harper-best-practices/rules/deploying-to-harper-fabric.md +59 -45
  33. package/agent/skills/harper-best-practices/rules/load-env.md +24 -23
  34. package/agent/skills/harper-best-practices/rules/programmatic-table-requests.md +62 -78
  35. package/agent/skills/harper-best-practices/rules/querying-rest-apis.md +92 -65
  36. package/agent/skills/harper-best-practices/rules/schema-design-tooling.md +53 -55
  37. package/agent/skills/harper-best-practices/rules/v5-upgrade.md +98 -62
  38. package/agent/skills/harper-best-practices/rules/vector-indexing.md +113 -42
  39. package/agent/skills/harper-best-practices/rules.manifest.yaml +9 -0
  40. package/agent/skills/harper-mcp/AGENTS.md +501 -0
  41. package/agent/skills/harper-mcp/SKILL.md +83 -0
  42. package/agent/skills/harper-mcp/rules/automatic-verb-tools.md +50 -0
  43. package/agent/skills/harper-mcp/rules/connecting-clients.md +59 -0
  44. package/agent/skills/harper-mcp/rules/custom-mcp-prompts.md +52 -0
  45. package/agent/skills/harper-mcp/rules/custom-mcp-resources.md +64 -0
  46. package/agent/skills/harper-mcp/rules/custom-mcp-tools.md +72 -0
  47. package/agent/skills/harper-mcp/rules/durable-quotas.md +73 -0
  48. package/agent/skills/harper-mcp/rules/enabling-mcp.md +63 -0
  49. package/agent/skills/harper-mcp/rules/rate-limiting.md +62 -0
  50. package/agent/skills/harper-mcp/rules/resources-surface.md +44 -0
  51. package/agent/skills/harper-mcp/rules/security-posture.md +36 -0
  52. package/agent/skills/harper-mcp/rules.manifest.yaml +203 -0
  53. package/package.json +1 -1
  54. package/skills-lock.json +7 -1
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: connecting-clients
3
+ description: How MCP clients connect to Harper - the initialize handshake, session and protocol-version headers, and authentication.
4
+ metadata:
5
+ mode: synthesized
6
+ ---
7
+
8
+ # Connecting Clients
9
+
10
+ The wire-level contract an MCP client (or your own HTTP code) must follow against a Harper MCP endpoint.
11
+
12
+ ## When to Use
13
+
14
+ Use this skill when configuring an MCP client against Harper, writing integration tests that drive `/mcp` directly, or debugging 400/404 responses from the endpoint.
15
+
16
+ ## How It Works
17
+
18
+ Harper implements MCP **Streamable HTTP** (spec rev 2025-06-18; rev 2025-03-26 also accepted):
19
+
20
+ 1. **Initialize.** POST a JSON-RPC `initialize` to the mount path with `Accept: application/json, text/event-stream`. The response carries the negotiated `protocolVersion`, the server's capabilities, and — critically — an `Mcp-Session-Id` response header.
21
+ 2. **Session header.** Every subsequent request MUST send that `Mcp-Session-Id` back. Missing/unknown ids get 400/404 (a 404 means re-initialize — sessions idle out after `mcp.session.idleTimeoutSeconds`, default 30 minutes).
22
+ 3. **Protocol-version header.** Requests after initialize should send `MCP-Protocol-Version: <negotiated version>`. A header naming a _different_ supported version than the session negotiated is rejected (400). A **missing** header is accepted as the session's own negotiated version (5.2.0+, patched into 5.1.x; older 5.1 releases treated a missing header as `2025-03-26` and rejected it as a mismatch on `2025-06-18` sessions).
23
+ 4. **Server-push SSE.** A GET on the mount path (with `Accept: text/event-stream`) opens the server→client stream used for `notifications/*/list_changed`, resource-update notifications, progress, and server-initiated requests. Some flows require it: `resources/subscribe` is rejected until the session has an open SSE stream.
24
+ 5. **Authentication.** Standard Harper authentication applies (Basic auth, tokens, mTLS — whatever the instance is configured with). Anonymous sessions are accepted when the deployment allows them; see the [Security Posture](security-posture.md) skill for what anonymous callers can reach.
25
+
26
+ ## Examples
27
+
28
+ Full curl handshake:
29
+
30
+ ```bash
31
+ # 1. initialize — capture the session id from the response headers
32
+ curl -si http://localhost:9926/mcp \
33
+ -H 'content-type: application/json' \
34
+ -H 'accept: application/json, text/event-stream' \
35
+ -u admin:password \
36
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
37
+ "protocolVersion":"2025-06-18","capabilities":{},
38
+ "clientInfo":{"name":"my-client","version":"1.0"}}}'
39
+ # → 200, header: Mcp-Session-Id: <uuid>
40
+
41
+ # 2. subsequent calls carry the session + protocol headers
42
+ curl -s http://localhost:9926/mcp \
43
+ -H 'content-type: application/json' \
44
+ -H 'accept: application/json, text/event-stream' \
45
+ -H 'mcp-session-id: <uuid>' \
46
+ -H 'mcp-protocol-version: 2025-06-18' \
47
+ -u admin:password \
48
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
49
+ ```
50
+
51
+ Claude Desktop / MCP-client configuration is just the URL (plus auth): point a Streamable HTTP transport at `http://<host>:9926/mcp`.
52
+
53
+ Debugging cheat sheet:
54
+
55
+ - `400 missing Mcp-Session-Id header` — you skipped step 2.
56
+ - `404` on a previously working session — idle timeout; re-initialize.
57
+ - `400 MCP-Protocol-Version mismatch` — you sent a header naming a different version than the session negotiated.
58
+ - `406` — missing the required `Accept` media type (JSON for POST, `text/event-stream` for GET).
59
+ - `403 origin_not_allowed` — browser-origin request rejected by CORS-tied origin validation; see [Security Posture](security-posture.md).
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: custom-mcp-prompts
3
+ description: How to publish reusable prompt templates to MCP clients via static mcpPrompts.
4
+ metadata:
5
+ mode: synthesized
6
+ ---
7
+
8
+ # Custom MCP Prompts
9
+
10
+ Publish parameterized prompt templates that MCP clients surface to their users (slash-command style).
11
+
12
+ ## When to Use
13
+
14
+ Use this skill when your application wants to hand well-crafted, data-aware prompts to any connected AI client — "summarize this account", "draft a reply about order X" — instead of hoping each client writes a good one.
15
+
16
+ ## How It Works
17
+
18
+ 1. **Declare `static mcpPrompts`** on a Resource class. Each entry carries a `render` **function** (not a method name):
19
+
20
+ ```javascript
21
+ export class Support extends tables.Ticket {
22
+ static mcpPrompts = [
23
+ {
24
+ name: 'draft_reply',
25
+ title: 'Draft support reply',
26
+ description: 'Draft a support reply for a ticket',
27
+ arguments: [{ name: 'ticketId', description: 'Ticket to reply to', required: true }],
28
+ async render(args) {
29
+ const ticket = await Support.get(args.ticketId);
30
+ if (!ticket) throw new Error(`ticket not found: ${args.ticketId}`);
31
+ return {
32
+ messages: [
33
+ {
34
+ role: 'user',
35
+ content: { type: 'text', text: `Draft a courteous reply to: ${ticket.body}` },
36
+ },
37
+ ],
38
+ };
39
+ },
40
+ },
41
+ ];
42
+ }
43
+ ```
44
+
45
+ 2. **Entry shape.** `name` and `render` are required (invalid entries are skipped with a warning); `title`, `description`, and `arguments` (`{ name, description?, required? }`) are optional metadata surfaced to clients.
46
+ 3. **Render contract.** `render(args)` receives the client-supplied argument values (strings) and returns the MCP prompt result shape — a `messages` array of `{ role, content }` entries (`content.type`: `text`, `image`, `audio`, or `resource`). It runs server-side and can read tables, so prompts can embed live data.
47
+ 4. **Surface.** Prompts appear in `prompts/list` and render via `prompts/get`; declared arguments are served through `completion/complete`. Connected sessions get `notifications/prompts/list_changed` when the set changes (reload/deploy).
48
+ 5. **Exposure.** Like custom tools, prompts are listed to every session on the profile, including anonymous ones — keep secrets out of prompt text and gate inside `render` if needed.
49
+
50
+ ## Examples
51
+
52
+ A data-aware prompt beats a static template: fetch current state (ticket, order, account) inside the render method so the client's LLM starts from live facts rather than asking for them turn by turn.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: custom-mcp-resources
3
+ description: How to serve custom content (docs pages, reports, binaries) as MCP resources via static mcpResources with URI templates and completions.
4
+ metadata:
5
+ mode: synthesized
6
+ ---
7
+
8
+ # Custom MCP Resources
9
+
10
+ Serve author-defined content — documentation pages, rendered reports, binary assets — under your own URIs (5.1.18+).
11
+
12
+ ## When to Use
13
+
14
+ Use this skill to build a content surface for AI clients: a public docs server over MCP, per-entity report resources, or any read-oriented content the auto-generated descriptors can't express.
15
+
16
+ ## How It Works
17
+
18
+ 1. **Declare `static mcpResources`** on a Resource class. Each entry has exactly one of `uri` (fixed) or `uriTemplate`, plus a `method` naming the instance method that serves reads:
19
+
20
+ ```javascript
21
+ export class DocsPages extends Resource {
22
+ static mcpResources = [
23
+ {
24
+ uri: 'docs:///index',
25
+ name: 'docs index',
26
+ description: 'List of all documentation pages',
27
+ mimeType: 'text/markdown',
28
+ method: 'readIndex',
29
+ },
30
+ {
31
+ uriTemplate: 'docs:///{+path}',
32
+ name: 'docs page',
33
+ mimeType: 'text/markdown',
34
+ method: 'readPage',
35
+ completions: { path: ['guides/install.md', 'guides/deploy.md'] },
36
+ },
37
+ ];
38
+
39
+ async readIndex() {
40
+ return { text: '- docs:///guides/install.md\n…', mimeType: 'text/markdown' };
41
+ }
42
+
43
+ async readPage(params /* { path } */, context /* { user, profile, sessionId } */) {
44
+ // {+path} spans segments BY DESIGN (it can contain `/` and `..`), so never
45
+ // hand it to filesystem path construction unvalidated. A keyed lookup like
46
+ // this is inherently safe; if you must touch the filesystem, resolve and
47
+ // verify containment first.
48
+ const page = PAGES.get(params.path);
49
+ if (!page) throw new Error(`no such page: ${params.path}`);
50
+ return { text: page, mimeType: 'text/markdown' };
51
+ }
52
+ }
53
+ ```
54
+
55
+ 2. **Templates.** `{name}` matches exactly one path segment; `{+name}` matches across segments (RFC-6570-style reserved expansion — how MCP clients expand templates). The single-segment contract is enforced through percent-decoding: a URI smuggling an encoded separator (`%2F`/`%5C`) into a `{name}` slot simply fails to match, so `{name}` is safe to use in path construction. Duplicate parameter names are rejected at registration.
56
+ 3. **Schemes.** Pick a custom scheme (`docs:///…` above). The reserved schemes — `harper:`, `harper+rest:`, `http:`, `https:` — are rejected at registration (and a template cannot parameterize the scheme position), so custom entries can never shadow the built-in surfaces.
57
+ 4. **Content shapes.** The read method returns a string (text), `{ text, mimeType? }`, `{ blob, mimeType? }` (base64 binary), or any other object (serialized as JSON). Read errors surface to the client as a sanitized JSON-RPC error; the raw error goes to the server log only.
58
+ 5. **Listing and completion.** Fixed URIs appear in `resources/list`, templates in `resources/templates/list`; declared `completions` values serve `completion/complete` per template parameter. Custom URIs win over the discovered surfaces on `resources/read`.
59
+ 6. **Anonymous by design.** Custom resources are served to any session — including unauthenticated ones (the public-docs case this feature targets). The MCP layer performs no auth check for them; gate inside the read method (`context.user`) if content is restricted.
60
+ 7. **No tables required.** A tableless component's resources register reliably — the registry rebuilds lazily per request when component loading completes after boot.
61
+
62
+ ## Examples
63
+
64
+ Public docs server: the two-entry declaration above plus `mcp.application.mountPath` is the whole server — clients browse `resources/list`, complete paths, and read pages, all anonymous. Pair with [Rate Limiting](rate-limiting.md) and [Durable Quotas](durable-quotas.md) if any companion tools bear cost.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: custom-mcp-tools
3
+ description: >-
4
+ How to expose custom instance methods as MCP tools via static mcpTools,
5
+ including the anonymous-exposure security model.
6
+ metadata:
7
+ mode: generate
8
+ sources:
9
+ - reference/v5/mcp/tools-and-resources.md#Custom `mcpTools` opt-in
10
+ sourceCommit: d7d2ddb120ce5f2ad39dc425f628f5a4f220c151
11
+ inputHash: 84a2ffac9bb7b66a
12
+ ---
13
+
14
+ # Custom MCP Tools
15
+
16
+ Expose non-CRUD operations — an LLM-backed `answer`, a domain action, a report generator — as first-class MCP tools.
17
+
18
+ ## When to Use
19
+
20
+ Use this skill when the auto-generated verb tools aren't enough: the AI should invoke _behavior_, not just CRUD. Also read it before shipping any custom tool on a publicly reachable instance — the security model differs from verb tools.
21
+
22
+ ## How It Works
23
+
24
+ 1. **Declare `static mcpTools`** on a Resource class (typically in `resources.js`/`resources.ts`):
25
+
26
+ ```javascript
27
+ export class Orders extends tables.Orders {
28
+ static mcpTools = [
29
+ {
30
+ name: 'reconcile_unsettled',
31
+ description: 'Reconcile all unsettled orders and return a summary',
32
+ method: 'reconcileUnsettled',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: { since: { type: 'string', description: 'ISO 8601 timestamp' } },
36
+ },
37
+ },
38
+ ];
39
+
40
+ async reconcileUnsettled(args, context) {
41
+ // context: { user, profile, sessionId, signal, progress?, serverRequest? }
42
+ return { reconciled: 12 };
43
+ }
44
+ }
45
+ ```
46
+
47
+ 2. **Dispatch is live-class.** Calls construct an instance of the class currently in the Resource registry, so an exported subclass (and its access-control overrides) always wins after a reload/deploy.
48
+ 3. **Per-call context.** The second argument carries `user`, `profile`, `sessionId`, an `AbortSignal` (`signal`) wired to MCP cancellation, and — on streaming calls — `progress()` and `serverRequest()`. Guard optional members (`context.progress?.(…)`).
49
+ 4. **Results and errors.** Return values are wrapped into MCP tool results (objects become structured content). Thrown errors surface as `isError: true` tool results with the message only — stack traces stay in the server log.
50
+ 5. **Security: custom tools are exposed to ANY session, including anonymous ones.** Unlike verb tools (RBAC-filtered per user), a custom tool is listed to every session and its method executes even with no logged-in user (`context.user` may be empty). The method runs inside the normal `transactional()` envelope, so data access it performs still hits per-record `allow*` predicates — but the _tool itself_ has no gate. To restrict one:
51
+
52
+ ```javascript
53
+ async reconcileUnsettled(args, context) {
54
+ if (!context.user?.username) {
55
+ throw new Error('authentication required');
56
+ }
57
+ // ...
58
+ }
59
+ ```
60
+
61
+ 6. **Cost control for public tools.** A cost-bearing anonymous tool needs more than auth checks — see [Rate Limiting](rate-limiting.md) (per-client buckets survive session cycling) and [Durable Quotas](durable-quotas.md) (persisted per-identity limits).
62
+
63
+ ## Examples
64
+
65
+ Warn-worthy anti-pattern — a public instance with an expensive tool and no gating:
66
+
67
+ ```javascript
68
+ static mcpTools = [{ name: 'answer', method: 'llmAnswer', ... }];
69
+ async llmAnswer(args) { return await callExpensiveModel(args.q); } // anonymous callers burn your budget
70
+ ```
71
+
72
+ Fixed: check `context.user` (or accept anonymity deliberately) _and_ configure `rateLimit.perClientPerSecond` + a `quota` hook.
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: durable-quotas
3
+ description: >-
4
+ Operator-pluggable durable quotas for MCP tools/call via the
5
+ server.setMcpQuotaHandler registration hook, with a race-safe counter pattern.
6
+ metadata:
7
+ mode: generate
8
+ sources:
9
+ - reference/v5/mcp/configuration.md#Durable quota handler
10
+ sourceCommit: d7d2ddb120ce5f2ad39dc425f628f5a4f220c151
11
+ inputHash: d2aa76fdfd4e3e7f
12
+ ---
13
+
14
+ # Durable Quotas
15
+
16
+ Persisted, restart-surviving cost controls for `tools/call`, registered as a plain function in your component code (5.2.0+).
17
+
18
+ ## When to Use
19
+
20
+ Use this skill when in-memory [rate limiting](rate-limiting.md) is not enough as a cost control — typically a public, unauthenticated, cost-bearing tool (an LLM-backed `answer`) where you need "N calls per identity per day" semantics that survive restarts and span workers.
21
+
22
+ ## How It Works
23
+
24
+ 1. **Register a handler function** with `server.setMcpQuotaHandler` at component load — the policy is a **function**, never an exposed Resource. It is opt-in: no handler registered means calls are allowed. The latest registration wins, so a reloaded component replaces the previous handler; pass `undefined` to clear it.
25
+ 2. **The handler is called before each admitted `tools/call`** with `{ identity, tool, user, profile, sessionId }` (`identity` is the client identity from the rate-limit layer — socket IP or trusted-header value — and may be `undefined`). Return `true` (or any truthy non-object) to allow, or `{ allowed: false, message?, retryAfterSeconds? }` to deny; denials surface as `isError` tool results with `kind: 'quota_exceeded'` plus your `message`/`retryAfterSeconds`.
26
+ 3. **Gate per profile in code.** The single handler receives `profile`, so branch on it (e.g. `if (profile !== 'application') return true`) rather than configuring per-profile hooks.
27
+ 4. **Ordering.** The handler runs _after_ the in-memory buckets admit the call, so rate-limited clients cannot spam a table-backed handler.
28
+ 5. **Fail-closed.** A handler that throws **denies** the call — the raw error goes to the server log only; the client sees a sanitized message. Cost protection that silently disables itself on a bug is worse than a hard failure. Harper calls the handler once per attempted tool call; whether you count on check or on success is your business.
29
+ 6. **Race-safety is your handler's business.** It can run concurrently for the same identity — within a worker (interleavings across your own `await`s) and across workers. A naive `get` → `put used+1` counter undercounts under concurrency and admits calls past the limit; make the read-modify-write atomic (a transaction that serializes conflicting writers, a compare-and-set retry loop, or a store with native atomic increments).
30
+
31
+ ## Examples
32
+
33
+ Persisted per-identity counter backed by an **internal** table (schema + registration):
34
+
35
+ ```graphql
36
+ # schema.graphql — INTERNAL counter. No @export, so no client can read or reset it.
37
+ type QuotaCounter @table {
38
+ id: ID @primaryKey
39
+ used: Int
40
+ }
41
+ ```
42
+
43
+ ```javascript
44
+ // resources.js
45
+ const DAILY_LIMIT = 100;
46
+
47
+ // The cost-bearing tool clients call (exported — this is the public surface).
48
+ export class Answerer extends Resource {
49
+ static mcpTools = [{ name: 'answer', description: 'Answer a question', method: 'doAnswer' }];
50
+ async doAnswer(args) {
51
+ return { answered: args?.q ?? '' };
52
+ }
53
+ }
54
+
55
+ // Register the durable quota policy as a function, backed by the internal counter table.
56
+ server.setMcpQuotaHandler(async ({ identity, tool, user, profile, sessionId }) => {
57
+ if (profile !== 'application') return true; // gate per profile in code
58
+ const id = identity ?? 'unknown';
59
+ // NOTE: naive get-then-put shown for shape; production code must make this
60
+ // read-modify-write atomic (see Race-safety above).
61
+ const existing = await tables.QuotaCounter.get(id);
62
+ const used = (existing?.used ?? 0) + 1;
63
+ await tables.QuotaCounter.put({ id, used });
64
+ if (used > DAILY_LIMIT) {
65
+ return { allowed: false, message: 'daily quota reached', retryAfterSeconds: 3600 };
66
+ }
67
+ return true;
68
+ });
69
+ ```
70
+
71
+ Because the policy is a plain function and not a Resource, it exposes no `update_/delete_` MCP tools or REST surface — unlike an exported class, whose inherited CRUD would let a permitted client reset its own counter. Keep the storage table **unexported** (no `@export`) so it stays off every transport.
72
+
73
+ Also verify the handler actually runs (call the tool past the limit once): on Harper versions before 5.2.0 `server.setMcpQuotaHandler` is unavailable — see [Enabling MCP](enabling-mcp.md).
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: enabling-mcp
3
+ description: >-
4
+ How to enable and configure Harper's MCP server profiles (application and
5
+ operations).
6
+ metadata:
7
+ mode: generate
8
+ sources:
9
+ - reference/v5/mcp/overview.md
10
+ - reference/v5/mcp/configuration.md
11
+ sourceCommit: d7d2ddb120ce5f2ad39dc425f628f5a4f220c151
12
+ inputHash: 236b335c4fc56602
13
+ ---
14
+
15
+ # Enabling MCP
16
+
17
+ Instructions for exposing a Harper instance as a Model Context Protocol (MCP) server.
18
+
19
+ ## When to Use
20
+
21
+ Use this skill when an AI client (Claude, an agent framework, or any MCP-capable tool) should talk to Harper directly — calling tools backed by your tables and Resources, reading data as MCP resources, or driving Harper operations.
22
+
23
+ ## How It Works
24
+
25
+ Harper ships two independent MCP **profiles**, each a Streamable HTTP endpoint (MCP spec rev 2025-06-18):
26
+
27
+ 1. **`application` profile** — the profile most apps want. Exposes your application's surface: auto-generated CRUD verb tools for `@export`ed tables, plus anything components opt in via `static mcpTools` / `static mcpPrompts` / `static mcpResources`. Mounts on the application HTTP port (default `9926`).
28
+ 2. **`operations` profile** — exposes Harper's operations API as tools (user-filtered by operation permissions). Mounts on the operations port (default `9925`). Intended for administrative agents, not application clients.
29
+
30
+ Enable them in `harper-config.yaml` (or the equivalent `HARPER_SET_CONFIG` process env var):
31
+
32
+ ```yaml
33
+ mcp:
34
+ application:
35
+ mountPath: /mcp # path on the application HTTP port
36
+ operations:
37
+ mountPath: /mcp # path on the operations port
38
+ ```
39
+
40
+ A profile is enabled by the **presence** of its config sub-block — there is no separate `enabled` flag, so `mcp: { application: {} }` alone turns the application profile on with defaults. Each profile is independent; enable only what you need. Key per-profile options:
41
+
42
+ - `mountPath` — path the endpoint mounts on (default `/mcp`).
43
+ - `allow` / `deny` (operations profile only) — glob patterns or literal operation names selecting which operations become tools. The default is a deliberately read-only list; setting `allow` **replaces** it (no merge), so destructive operations like `set_configuration` must be opted in explicitly.
44
+ - `maxTools` — page size for `tools/list` responses (default 200); overflow pages via the MCP cursor.
45
+ - `rateLimit.*` — see the [Rate Limiting](rate-limiting.md) skill.
46
+ - durable quotas — registered in code with `server.setMcpQuotaHandler`, not a config key; see the [Durable Quotas](durable-quotas.md) skill.
47
+
48
+ Version notes: the transport and tool surface shipped across 5.1.x (complete protocol surface — prompts, resources, subscriptions, completions, cancellation, progress — in 5.1.10+). Custom content resources (`mcpResources`) are 5.1.18+. Per-client rate limiting and durable quotas are 5.2.0+.
49
+
50
+ **Verify the version before relying on gated features.** Unsupported config keys (such as the `rateLimit.perClient*` security controls) are **accepted and silently ignored** by older versions — nothing errors, the feature just doesn't run. Check `serverInfo.version` in the `initialize` response (or read `harper://about`) first, and after configuring a limit, prove it denies at least once before trusting it.
51
+
52
+ ## Examples
53
+
54
+ Minimal application-profile setup for a project with `@export`ed tables:
55
+
56
+ ```yaml
57
+ # harper-config.yaml
58
+ mcp:
59
+ application:
60
+ mountPath: /mcp
61
+ ```
62
+
63
+ An MCP client pointed at `http://<host>:9926/mcp` then sees `get_*` / `search_*` / `create_*` / `update_*` / `delete_*` tools for every exported table, RBAC-filtered per authenticated user. See the [Connecting Clients](connecting-clients.md) skill for the handshake.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: rate-limiting
3
+ description: >-
4
+ MCP tools/call rate limiting - per-tool, per-session, and per-client-identity
5
+ token buckets, and the identityHeader trust model.
6
+ metadata:
7
+ mode: generate
8
+ sources:
9
+ - reference/v5/mcp/configuration.md#`mcp.<profile>.rateLimit.*`
10
+ sourceCommit: d7d2ddb120ce5f2ad39dc425f628f5a4f220c151
11
+ inputHash: c07b006ea6def652
12
+ ---
13
+
14
+ # Rate Limiting
15
+
16
+ In-memory token buckets that bound `tools/call` throughput per profile.
17
+
18
+ ## When to Use
19
+
20
+ Use this skill when tuning MCP throughput, and **always** when a tool is exposed on a public or anonymous-accessible instance — the default limits alone do not stop session-cycling abuse.
21
+
22
+ ## How It Works
23
+
24
+ Four session-scoped limits ship with sensible defaults (per profile, configurable under `mcp.<profile>.rateLimit.*`):
25
+
26
+ - `perToolPerSecond` / `perToolBurst` — sustained rate and burst per (session, tool).
27
+ - `sessionConcurrency` — max in-flight calls per session.
28
+ - `sessionPerSecond` — sustained rate per session across all tools.
29
+
30
+ Limit hits return an `isError` tool result with `kind: 'rate_limited'` and a `scope` field (not a JSON-RPC error), so the calling LLM can see and back off.
31
+
32
+ **Session buckets are evadable by anonymous clients**: `initialize → call → drop session → repeat` gets fresh buckets every loop. Two additions close that (5.2.0+):
33
+
34
+ - `perClientPerSecond` / `perClientBurst` (default off) — a bucket keyed on **client identity** rather than session, surviving the cycling loop. Burst defaults to the sustained rate, floored at one whole token (a fractional rate like `0.1` = "6/minute" still admits its first call). Denials report `scope: 'per_client'`.
35
+ - `identityHeader` — identity defaults to the client socket IP; deployments behind a reverse proxy can name a trusted header (typically `x-forwarded-for`, first value wins). **Only set this when the proxy strips or replaces the header on untrusted traffic** — a client-controlled identity header lets callers mint fresh identities per request and bypass the limit; Harper logs a startup warning when it is configured.
36
+
37
+ All bucket state is in-memory per worker: it resets on restart and is not shared across workers. For durable, restart-surviving limits, see the [Durable Quotas](durable-quotas.md) skill.
38
+
39
+ On Harper versions before 5.2.0 the `perClient*`/`identityHeader` keys are **accepted and silently ignored** — verify `serverInfo.version` and prove a denial once before trusting the limit (see [Enabling MCP](enabling-mcp.md)).
40
+
41
+ ## Examples
42
+
43
+ Public docs server with a cost-bearing `answer` tool — bound instantaneous abuse:
44
+
45
+ ```yaml
46
+ mcp:
47
+ application:
48
+ mountPath: /mcp
49
+ rateLimit:
50
+ perClientPerSecond: 0.5 # 30/minute sustained per client
51
+ perClientBurst: 5
52
+ ```
53
+
54
+ Behind a proxy that manages `X-Forwarded-For` correctly:
55
+
56
+ ```yaml
57
+ mcp:
58
+ application:
59
+ rateLimit:
60
+ identityHeader: x-forwarded-for
61
+ perClientPerSecond: 0.5
62
+ ```
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: resources-surface
3
+ description: The MCP resources surface - harper:// metadata URIs, harper+rest:// table descriptors, templates, subscriptions, and list_changed notifications.
4
+ metadata:
5
+ mode: synthesized
6
+ ---
7
+
8
+ # Resources Surface
9
+
10
+ What `resources/list`, `resources/read`, `resources/templates/list`, and `resources/subscribe` expose from a Harper instance.
11
+
12
+ ## When to Use
13
+
14
+ Use this skill when an MCP client should _read_ from Harper (schemas, API descriptions, data descriptors) rather than call tools, or when wiring change notifications.
15
+
16
+ ## How It Works
17
+
18
+ Three URI families appear on the application profile:
19
+
20
+ 1. **`harper://` metadata resources** (no HTTP equivalent):
21
+ - `harper://about` — server version, profile, protocol versions, capabilities (both profiles).
22
+ - `harper://schema/{database}/{table}` — per-table attribute definitions, RBAC-filtered at read time (also offered as a URI template).
23
+ - `harper://openapi` — the OpenAPI 3.0.3 document for the REST surface.
24
+ - `harper://operations` — operations profile only; the caller's allowed operation names.
25
+ 2. **`harper+rest://<host>:<port>/<path>` descriptors** — one per exported Resource passing the `exportTypes.mcp` gate. Reading one returns a small JSON descriptor (path, database, table, and a hint to use the corresponding verb tools for records) — it is a pointer, not a data dump. The scheme is `harper+rest` (5.1.18+) because the MCP spec reserves `https://` for resources a client can fetch from the web; legacy `http(s)://` URIs from older listings still read and subscribe.
26
+ 3. **Author-defined custom resources** — arbitrary URIs/templates served by component code; see the [Custom MCP Resources](custom-mcp-resources.md) skill. Custom URIs take precedence over the discovered surfaces on `resources/read`.
27
+
28
+ Other behaviors:
29
+
30
+ - **Subscriptions.** Row-backed application resources support `resources/subscribe` (change notifications delivered on the SSE stream). The session must have its GET SSE stream open first — subscribe calls before that are rejected with an instructive error. Subscriptions are restored on session resume.
31
+ - **`notifications/resources/list_changed`.** Sessions are notified when their _visible_ resource set (including templates) actually changes — per-session diffing keeps no-op rebuilds silent.
32
+ - **Pagination.** All list endpoints use opaque cursors per the MCP spec; treat `nextCursor` as a black box.
33
+
34
+ ## Examples
35
+
36
+ Reading a table's schema as an AI-consumable resource:
37
+
38
+ ```json
39
+ → {"method":"resources/read","params":{"uri":"harper://schema/data/widget"}}
40
+ ← {"result":{"contents":[{"uri":"harper://schema/data/widget","mimeType":"application/json",
41
+ "text":"{\"attributes\":[{\"name\":\"id\",\"type\":\"ID\"},...]}"}]}}
42
+ ```
43
+
44
+ Point a client at `harper://openapi` when it needs the whole REST contract in one read.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: security-posture
3
+ description: The MCP security model - anonymous access, RBAC boundaries, origin validation, audit logging, and the hardening checklist for public instances.
4
+ metadata:
5
+ mode: synthesized
6
+ ---
7
+
8
+ # Security Posture
9
+
10
+ What is and is not protected on a Harper MCP endpoint, and the checklist for exposing one publicly.
11
+
12
+ ## When to Use
13
+
14
+ Read this skill before exposing any MCP profile beyond localhost, and when reasoning about what an anonymous or low-privilege caller can reach.
15
+
16
+ ## How It Works
17
+
18
+ 1. **Authentication is Harper's, not MCP's.** The MCP layer adds no login gate. Sessions bind to whatever user Harper's auth pipeline resolves — which can be nobody: anonymous sessions initialize, list, and call successfully where the deployment allows unauthenticated requests.
19
+ 2. **Two different tool trust models.** Auto-generated verb tools are RBAC-filtered per user and enforce table permissions (including per-record `allow*` predicates) on every call. Custom `mcpTools` / `mcpPrompts` / `mcpResources` are exposed to **every** session — access control is entirely the author's method's responsibility. Never assume a custom tool is login-gated.
20
+ 3. **Origin validation (DNS-rebinding defense).** Browser-origin requests are validated against Harper's CORS configuration; a disallowed `Origin` gets 403. The secure default for anything browser-reachable beyond loopback: enable CORS with an explicit allow-list (`http.cors` + `http.corsAccessList` for the application profile; `operationsApi.network.*` for operations). CORS off means origin checks are off — appropriate only for localhost/non-browser clients.
21
+ 4. **Error hygiene.** Tool and resource errors cross the wire as sanitized messages; stack traces and raw author errors stay in the server log.
22
+ 5. **Audit.** Every `tools/call` (including rate-limited, quota-denied, and protocol-error outcomes) emits an audit entry — profile, session, tool, user, duration, status, with sensitive-looking argument keys redacted.
23
+ 6. **Template safety.** Custom resource URI templates enforce their single-segment contract against encoded-separator smuggling, and custom URIs cannot claim reserved schemes — see [Custom MCP Resources](custom-mcp-resources.md).
24
+
25
+ ## Examples
26
+
27
+ Hardening checklist for a public application-profile endpoint:
28
+
29
+ - [ ] Every custom tool/prompt/resource either tolerates anonymous callers or checks `context.user` and throws.
30
+ - [ ] `rateLimit.perClientPerSecond` set (session limits alone are cycle-evadable); `identityHeader` only if the proxy strips it.
31
+ - [ ] A durable `quota` hook backs any cost-bearing tool ([Durable Quotas](durable-quotas.md)), with an atomic counter.
32
+ - [ ] CORS allow-list configured if browsers will reach the endpoint.
33
+ - [ ] The tool surface is trimmed to what the AI needs: `exportTypes: { mcp: false }` on internal Resources (application), a deliberate `allow` list (operations — remember it replaces the read-only default).
34
+ - [ ] Audit log shipping somewhere you actually read.
35
+ - [ ] Version verified (`serverInfo.version` ≥ the feature gates you rely on) and each protection **proven to deny once** — older versions accept and silently ignore `rateLimit.perClient*` / `quota.*` keys.
36
+ - [ ] The quota hook's table is not itself exposed — register it via `server.resources.set('McpQuota', McpQuota, { mcp: false, rest: false })` instead of module-exporting it (a `static exportTypes` field is not read); otherwise clients can reset their own counters.