@cyanheads/brapi-mcp-server 0.7.11 → 0.7.13

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 (135) hide show
  1. package/AGENTS.md +453 -0
  2. package/CLAUDE.md +24 -16
  3. package/README.md +453 -136
  4. package/changelog/0.7.x/0.7.12.md +35 -0
  5. package/changelog/0.7.x/0.7.13.md +38 -0
  6. package/changelog/template.md +9 -26
  7. package/dist/config/alias-credentials.d.ts +1 -1
  8. package/dist/config/alias-credentials.d.ts.map +1 -1
  9. package/dist/config/alias-credentials.js +24 -8
  10. package/dist/config/alias-credentials.js.map +1 -1
  11. package/dist/config/server-config.d.ts +1 -1
  12. package/dist/config/server-config.d.ts.map +1 -1
  13. package/dist/config/server-config.js +3 -1
  14. package/dist/config/server-config.js.map +1 -1
  15. package/dist/index.js +7 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/mcp-server/resources/definitions/brapi-calls.resource.d.ts +1 -0
  18. package/dist/mcp-server/resources/definitions/brapi-calls.resource.d.ts.map +1 -1
  19. package/dist/mcp-server/resources/definitions/brapi-calls.resource.js +2 -1
  20. package/dist/mcp-server/resources/definitions/brapi-calls.resource.js.map +1 -1
  21. package/dist/mcp-server/resources/definitions/brapi-filters.resource.js +1 -1
  22. package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.d.ts +1 -0
  23. package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.d.ts.map +1 -1
  24. package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.js +2 -1
  25. package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.js.map +1 -1
  26. package/dist/mcp-server/resources/definitions/brapi-server-info.resource.d.ts +1 -0
  27. package/dist/mcp-server/resources/definitions/brapi-server-info.resource.d.ts.map +1 -1
  28. package/dist/mcp-server/resources/definitions/brapi-server-info.resource.js +2 -1
  29. package/dist/mcp-server/resources/definitions/brapi-server-info.resource.js.map +1 -1
  30. package/dist/mcp-server/resources/definitions/brapi-study.resource.d.ts +1 -0
  31. package/dist/mcp-server/resources/definitions/brapi-study.resource.d.ts.map +1 -1
  32. package/dist/mcp-server/resources/definitions/brapi-study.resource.js +2 -1
  33. package/dist/mcp-server/resources/definitions/brapi-study.resource.js.map +1 -1
  34. package/dist/mcp-server/resources/definitions/brapi-variable.resource.d.ts +1 -0
  35. package/dist/mcp-server/resources/definitions/brapi-variable.resource.d.ts.map +1 -1
  36. package/dist/mcp-server/resources/definitions/brapi-variable.resource.js +2 -1
  37. package/dist/mcp-server/resources/definitions/brapi-variable.resource.js.map +1 -1
  38. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.d.ts +2 -0
  39. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.d.ts.map +1 -1
  40. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.js +2 -0
  41. package/dist/mcp-server/tools/definitions/brapi-build-phenotype-matrix.tool.js.map +1 -1
  42. package/dist/mcp-server/tools/definitions/brapi-dataframe-export.tool.d.ts +1 -0
  43. package/dist/mcp-server/tools/definitions/brapi-dataframe-export.tool.d.ts.map +1 -1
  44. package/dist/mcp-server/tools/definitions/brapi-dataframe-export.tool.js +1 -0
  45. package/dist/mcp-server/tools/definitions/brapi-dataframe-export.tool.js.map +1 -1
  46. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.d.ts +1 -0
  47. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.d.ts.map +1 -1
  48. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.js +1 -0
  49. package/dist/mcp-server/tools/definitions/brapi-export-genotype-matrix.tool.js.map +1 -1
  50. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.d.ts +1 -0
  51. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.d.ts.map +1 -1
  52. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.js +1 -0
  53. package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.js.map +1 -1
  54. package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.d.ts +2 -0
  55. package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.d.ts.map +1 -1
  56. package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.js +2 -0
  57. package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.js.map +1 -1
  58. package/dist/mcp-server/tools/definitions/brapi-find-images.tool.d.ts +2 -0
  59. package/dist/mcp-server/tools/definitions/brapi-find-images.tool.d.ts.map +1 -1
  60. package/dist/mcp-server/tools/definitions/brapi-find-images.tool.js +2 -0
  61. package/dist/mcp-server/tools/definitions/brapi-find-images.tool.js.map +1 -1
  62. package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.d.ts +2 -0
  63. package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.d.ts.map +1 -1
  64. package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.js +2 -0
  65. package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.js.map +1 -1
  66. package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.d.ts +2 -0
  67. package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.d.ts.map +1 -1
  68. package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.js +2 -0
  69. package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.js.map +1 -1
  70. package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.d.ts +2 -0
  71. package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.d.ts.map +1 -1
  72. package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.js +2 -0
  73. package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.js.map +1 -1
  74. package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.d.ts +2 -0
  75. package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.d.ts.map +1 -1
  76. package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.js +2 -0
  77. package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.js.map +1 -1
  78. package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.d.ts +2 -0
  79. package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.d.ts.map +1 -1
  80. package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.js +2 -0
  81. package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.js.map +1 -1
  82. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.d.ts +1 -0
  83. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.d.ts.map +1 -1
  84. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.js +1 -0
  85. package/dist/mcp-server/tools/definitions/brapi-germplasm-performance.tool.js.map +1 -1
  86. package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.d.ts +1 -0
  87. package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.d.ts.map +1 -1
  88. package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.js +1 -0
  89. package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.js.map +1 -1
  90. package/dist/mcp-server/tools/definitions/brapi-get-image.tool.d.ts +1 -0
  91. package/dist/mcp-server/tools/definitions/brapi-get-image.tool.d.ts.map +1 -1
  92. package/dist/mcp-server/tools/definitions/brapi-get-image.tool.js +1 -0
  93. package/dist/mcp-server/tools/definitions/brapi-get-image.tool.js.map +1 -1
  94. package/dist/mcp-server/tools/definitions/brapi-get-study.tool.d.ts +1 -0
  95. package/dist/mcp-server/tools/definitions/brapi-get-study.tool.d.ts.map +1 -1
  96. package/dist/mcp-server/tools/definitions/brapi-get-study.tool.js +1 -0
  97. package/dist/mcp-server/tools/definitions/brapi-get-study.tool.js.map +1 -1
  98. package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.d.ts +1 -0
  99. package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.d.ts.map +1 -1
  100. package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.js +1 -0
  101. package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.js.map +1 -1
  102. package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.d.ts +1 -0
  103. package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.d.ts.map +1 -1
  104. package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.js +1 -0
  105. package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.js.map +1 -1
  106. package/dist/mcp-server/tools/definitions/brapi-server-info.tool.d.ts +1 -0
  107. package/dist/mcp-server/tools/definitions/brapi-server-info.tool.d.ts.map +1 -1
  108. package/dist/mcp-server/tools/definitions/brapi-server-info.tool.js +1 -0
  109. package/dist/mcp-server/tools/definitions/brapi-server-info.tool.js.map +1 -1
  110. package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.d.ts +1 -0
  111. package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.d.ts.map +1 -1
  112. package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.js +1 -0
  113. package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.js.map +1 -1
  114. package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.d.ts +1 -0
  115. package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.d.ts.map +1 -1
  116. package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.js +1 -0
  117. package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.js.map +1 -1
  118. package/dist/mcp-server/tools/definitions/index.d.ts +28 -0
  119. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  120. package/dist/services/brapi-client/brapi-client.d.ts +7 -0
  121. package/dist/services/brapi-client/brapi-client.d.ts.map +1 -1
  122. package/dist/services/brapi-client/brapi-client.js +21 -12
  123. package/dist/services/brapi-client/brapi-client.js.map +1 -1
  124. package/dist/services/brapi-client/types.d.ts +1 -1
  125. package/dist/services/brapi-dialect/detect.d.ts +1 -1
  126. package/dist/services/brapi-dialect/index.d.ts +2 -2
  127. package/dist/services/brapi-dialect/index.js +1 -1
  128. package/dist/services/capability-registry/capability-registry.d.ts +1 -1
  129. package/dist/services/capability-registry/capability-registry.js +1 -1
  130. package/dist/services/reference-data-cache/reference-data-cache.d.ts +2 -2
  131. package/dist/services/reference-data-cache/reference-data-cache.js +1 -1
  132. package/dist/services/server-registry/types.d.ts +1 -1
  133. package/manifest.json +8 -6
  134. package/package.json +22 -10
  135. package/server.json +9 -3
package/AGENTS.md ADDED
@@ -0,0 +1,453 @@
1
+ # Agent Protocol
2
+
3
+ **Server:** brapi-mcp-server
4
+ **Version:** 0.7.13
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
8
+
9
+ > **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.
10
+
11
+ ---
12
+
13
+ ## What's Next?
14
+
15
+ When the user asks what to do next, what's left, or needs direction, suggest relevant options based on the current project state:
16
+
17
+ 1. **Re-run the `setup` skill** — ensures AGENTS.md, skills, structure, and metadata are populated and up to date with the current codebase
18
+ 2. **Run the `design-mcp-server` skill** — if the tool/resource surface hasn't been mapped yet, work through domain design
19
+ 3. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt` skills
20
+ 4. **Add services** — scaffold domain service integrations using the `add-service` skill
21
+ 5. **Add tests** — scaffold tests for existing definitions using the `add-test` skill
22
+ 6. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill, get a report of issues and pain points
23
+ 7. **Run `devcheck`** — lint, format, typecheck, and security audit
24
+ 8. **Run the `security-pass` skill** — audit handlers for MCP-specific security gaps: output injection, scope blast radius, input sinks, tenant isolation
25
+ 9. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
26
+ 10. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
27
+
28
+ Tailor suggestions to what's actually missing or stale — don't recite the full list every time.
29
+
30
+ ---
31
+
32
+ ## Core Rules
33
+
34
+ - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. The framework catches, classifies, and formats. Default to typed contracts: declare `errors: [...]` and throw via `ctx.fail(reason, …)` so failures carry stable `data.reason` codes for agent-client routing. Fall back to error factories (`notFound()`, `validationError()`, etc.) only for services or when no contract entry fits.
35
+ - **Use `ctx.log`** for request-scoped logging. No `console` calls.
36
+ - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
37
+ - **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.
38
+ - **Secrets in env vars only** — never hardcoded.
39
+ - **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.
40
+
41
+ ---
42
+
43
+ ## Patterns
44
+
45
+ ### Tool — connection bootstrap
46
+
47
+ `brapi_connect` is the session handshake. It registers the BrAPI server under a named alias, forces a capability refresh, and inlines the full orientation envelope so one call orients the agent. `baseUrl` and `auth` are both `optional()` — when omitted, `resolveConnectInput` fills them from `BRAPI_<ALIAS>_*` then `BRAPI_DEFAULT_*` env vars, so credentials never enter the LLM context. Same envelope is available on-demand via `brapi_server_info`.
48
+
49
+ ```ts
50
+ // src/mcp-server/tools/definitions/brapi-connect.tool.ts (abbreviated)
51
+ import { tool, z } from '@cyanheads/mcp-ts-core';
52
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
53
+ import { resolveConnectInput } from '@/config/alias-credentials.js';
54
+ import { ConnectAuthSchema } from '../shared/connect-auth-schema.js';
55
+
56
+ export const brapiConnect = tool('brapi_connect', {
57
+ description: 'Connect to a BrAPI v2 server… baseUrl + auth fall back to BRAPI_<ALIAS>_* / BRAPI_DEFAULT_* env vars when omitted.',
58
+ annotations: { openWorldHint: true, readOnlyHint: false, idempotentHint: true },
59
+ errors: [
60
+ { reason: 'auth_token_exchange_failed', code: JsonRpcErrorCode.Forbidden,
61
+ when: 'SGN or OAuth token exchange against /token failed',
62
+ recovery: 'Verify the credentials and that the server exposes /token before retrying.' },
63
+ { reason: 'auth_no_access_token', code: JsonRpcErrorCode.Forbidden,
64
+ when: 'Token endpoint responded but did not return an access_token',
65
+ recovery: 'Confirm the credentials are valid and the IdP issues access tokens for this grant.' },
66
+ ] as const,
67
+ input: z.object({
68
+ baseUrl: z.string().url().optional().describe('Falls back to BRAPI_<ALIAS>_BASE_URL → BRAPI_DEFAULT_BASE_URL.'),
69
+ auth: ConnectAuthSchema.optional().describe('Falls back to env-derived credentials.'),
70
+ alias: z.string().regex(/^[a-zA-Z0-9_-]+$/).default('default'),
71
+ }),
72
+ output: OrientationEnvelopeSchema,
73
+ async handler(input, ctx) {
74
+ const resolved = resolveConnectInput(input.alias, { baseUrl: input.baseUrl, auth: input.auth });
75
+ const connection = await getServerRegistry().register(ctx, {
76
+ alias: input.alias, baseUrl: resolved.baseUrl, auth: resolved.auth,
77
+ });
78
+ await getCapabilityRegistry().invalidate(connection.baseUrl, ctx);
79
+ return buildOrientationEnvelope(ctx, connection, { registry: getCapabilityRegistry(), client: getBrapiClient() });
80
+ },
81
+ format: (result) => [{ type: 'text', text: formatOrientationEnvelope(result) }],
82
+ });
83
+ ```
84
+
85
+ ### Tool — find with dataframe spillover
86
+
87
+ `find_*` tools share a pattern: pull one page capped at `loadLimit`, compute distributions across the returned rows, and if the upstream total exceeds `loadLimit` materialize the full union as a canvas dataframe and return a handle. Spilled rows live in DuckDB only — there is no parallel JSON store. Canvas is mandatory: startup fails closed when `core.canvas` is undefined.
88
+
89
+ ```ts
90
+ // src/mcp-server/tools/definitions/brapi-find-germplasm.tool.ts (abbreviated)
91
+ export const brapiFindGermplasm = tool('brapi_find_germplasm', {
92
+ description:
93
+ 'Find germplasm by name, synonym, accession, PUI, crop, or free-text. Spills to a canvas dataframe when the upstream total exceeds loadLimit — query with brapi_dataframe_query (SQL).',
94
+ annotations: { readOnlyHint: true, openWorldHint: true },
95
+ input: z.object({
96
+ alias: AliasInput,
97
+ names: z.array(z.string()).optional(),
98
+ crops: z.array(z.string()).optional(),
99
+ text: z.string().optional(),
100
+ loadLimit: LoadLimitInput,
101
+ extraFilters: ExtraFiltersInput,
102
+ }),
103
+ output: OutputSchema,
104
+ async handler(input, ctx) {
105
+ const connection = await getServerRegistry().get(ctx, input.alias ?? DEFAULT_ALIAS);
106
+ await getCapabilityRegistry().ensure(connection.baseUrl, { service: 'germplasm', method: 'GET' }, ctx);
107
+ const bridge = getCanvasBridge();
108
+
109
+ const filters = mergeFilters(/* named + extraFilters */, warnings);
110
+ const firstPage = await loadInitialPage(client, connection, '/germplasm', filters, loadLimit, ctx);
111
+
112
+ const { fullRows, dataframe } = await maybeSpill({
113
+ firstPage, client, connection, bridge,
114
+ path: '/germplasm', filters, source: 'find_germplasm', loadLimit, ctx,
115
+ });
116
+ return { /* results + distributions + refinementHint + dataframe? */ };
117
+ },
118
+ format: (result) => [{ type: 'text', text: renderFindResult(result) }],
119
+ });
120
+ ```
121
+
122
+ ### Server config
123
+
124
+ ```ts
125
+ // src/config/server-config.ts — lazy-parsed, separate from framework config
126
+ import { z } from '@cyanheads/mcp-ts-core';
127
+ import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
128
+
129
+ const ServerConfigSchema = z.object({
130
+ defaultBaseUrl: z.string().url().optional(),
131
+ loadLimit: z.coerce.number().int().positive().default(1_000),
132
+ maxConcurrentRequests: z.coerce.number().int().positive().default(4),
133
+ retryMaxAttempts: z.coerce.number().int().min(0).default(3),
134
+ datasetTtlSeconds: z.coerce.number().int().positive().default(86_400),
135
+ referenceCacheTtlSeconds: z.coerce.number().int().positive().default(3_600),
136
+ sessionIsolation: z.enum(['true', 'false']).default('true').transform((v) => v === 'true'),
137
+ // …see src/config/server-config.ts for the full schema
138
+ });
139
+
140
+ let _config: z.infer<typeof ServerConfigSchema> | undefined;
141
+ export function getServerConfig() {
142
+ _config ??= parseEnvConfig(ServerConfigSchema, {
143
+ defaultBaseUrl: 'BRAPI_DEFAULT_BASE_URL',
144
+ loadLimit: 'BRAPI_LOAD_LIMIT',
145
+ maxConcurrentRequests: 'BRAPI_MAX_CONCURRENT_REQUESTS',
146
+ retryMaxAttempts: 'BRAPI_RETRY_MAX_ATTEMPTS',
147
+ datasetTtlSeconds: 'BRAPI_DATASET_TTL_SECONDS',
148
+ referenceCacheTtlSeconds: 'BRAPI_REFERENCE_CACHE_TTL_SECONDS',
149
+ });
150
+ return _config;
151
+ }
152
+ ```
153
+
154
+ `parseEnvConfig` maps Zod schema paths → env var names so validation errors name the actual variable (`BRAPI_LOAD_LIMIT`) rather than the internal path (`loadLimit`). It throws a `ConfigurationError` the framework catches and prints as a clean startup banner.
155
+
156
+ **Per-alias credentials** live in `src/config/alias-credentials.ts`. `readAliasCredentials(alias)` reads `BRAPI_<ALIAS>_*` (uppercased, hyphens → underscores), `deriveAuthFromCredentials(creds)` derives the auth mode from which fields are set (USERNAME+PASSWORD → `sgn`; BEARER_TOKEN → `bearer`; API_KEY → `api_key`; OAUTH_CLIENT_ID+SECRET → `oauth2`; mixing families raises `ValidationError`), and `resolveConnectInput(alias, agentInput)` layers agent input → alias env → default env → no-auth fallback.
157
+
158
+ ---
159
+
160
+ ## Context
161
+
162
+ Handlers receive a unified `ctx` object. Currently used surface:
163
+
164
+ | Property | Description |
165
+ |:---------|:------------|
166
+ | `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. |
167
+ | `ctx.state` | Tenant-scoped KV — used by `ServerRegistry` (connection aliases), `CanvasBridge` (default canvas pointer + per-table provenance), and `CapabilityRegistry` (cached profiles). Spilled `find_*` rows live on the canvas (DuckDB), not in `ctx.state`. |
168
+ | `ctx.sessionId` | Mcp-Session-Id (HTTP stateful/auto); `undefined` for stdio, stateless HTTP unless `exposeStatelessSessionId` is opted in, and every request on protocol revision 2026-07-28, which is session-less by design. Composed into `ServerRegistry.connKey` and `CanvasBridge.defaultCanvasKey` when `BRAPI_SESSION_ISOLATION=true` (default), so concurrent HTTP sessions in the same tenant don't share connection state or canvas. Discovery / scoping key on top of tenant-keyed state — not an authorization principal. |
169
+ | `ctx.signal` | `AbortSignal` — threaded into every BrAPI HTTP call so client-side cancellation aborts the upstream request. |
170
+ | `ctx.requestId` | Unique request ID — auto-attached to every `ctx.log` entry. |
171
+ | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio / HTTP+`auth=none` — outer scope on all `ctx.state` reads/writes. |
172
+ | `ctx.requestInput` / `ctx.inputs` | Multi-round-trip input. `brapi_submit_observations` gates apply-mode writes on a `confirm` elicitation: it reads `ctx.inputs.view('confirm')`, returns `ctx.requestInput({ inputRequests: … })` when the answer is missing, and treats a declined, cancelled, or unparseable answer as terminal (`user_declined`). `force: true` skips the round. |
173
+ | `ctx.enrich` | Success-path agent context. `brapi_dataframe_query` and `brapi_build_phenotype_matrix` disclose capped results with `ctx.enrich.truncated({ shown, cap, guidance })`. |
174
+
175
+ `ctx.fail(reason, …)` is the typed thrower keyed off declared `errors[]` contracts — used by 14 tools and 1 resource today. `ctx.recoveryFor(reason)` resolves the matching contract entry's recovery hint into `data.recovery.hint` so it surfaces on the wire. `ctx.content` is unused — no tool emits media blocks outside `brapi_get_image`, which returns image bytes through its own output schema.
176
+
177
+ ---
178
+
179
+ ## Errors
180
+
181
+ Handlers throw — the framework catches, classifies, and formats.
182
+
183
+ **Default for new tools: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata (≥ 5 words, lint-validated); to surface it on the wire, spread `...ctx.recoveryFor('reason')` into `data` or pass an explicit `{ recovery: { hint: '...' } }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring. Live across the BrAPI surface today: `brapi_build_phenotype_matrix`, `brapi_dataframe_describe`, `brapi_dataframe_export`, `brapi_dataframe_query`, `brapi_describe_filters`, `brapi_export_genotype_matrix`, `brapi_find_genotype_calls`, `brapi_germplasm_performance`, `brapi_get_germplasm`, `brapi_get_image`, `brapi_get_study`, `brapi_raw_get`, `brapi_raw_search`, `brapi_submit_observations`, plus the `brapi://variable/{observationVariableDbId}` resource.
184
+
185
+ ```ts
186
+ errors: [
187
+ { reason: 'unknown_alias', code: JsonRpcErrorCode.NotFound,
188
+ when: 'No connection registered for this alias',
189
+ recovery: 'Call brapi_connect with this alias before retrying.' },
190
+ ],
191
+ async handler(input, ctx) {
192
+ const conn = registry.peek(input.alias);
193
+ if (!conn) throw ctx.fail('unknown_alias', `No connection for ${input.alias}`,
194
+ { ...ctx.recoveryFor('unknown_alias') });
195
+ // ...
196
+ }
197
+ ```
198
+
199
+ **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 (input, output, errors, handler, format). Don't extract a shared `errors[]` constant or contract module to deduplicate near-identical entries; per-tool repetition is the intended cost of locality, and dynamic `recovery` hints often need tool-specific runtime context anyway.
200
+
201
+ **Fallback (no contract entry fits, services, prototype tools):** throw via factories or plain `Error`.
202
+
203
+ ```ts
204
+ // Plain Error — framework auto-classifies from message patterns
205
+ throw new Error('Item not found'); // → NotFound
206
+ throw new Error('Invalid query format'); // → ValidationError
207
+
208
+ // Error factories — explicit code, concise
209
+ import { notFound, validationError, internalError, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
210
+ throw notFound('Item not found', { itemId });
211
+ throw serviceUnavailable('API unavailable', { url }, { cause: err });
212
+
213
+ // McpError — full control over code and data
214
+ import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
215
+ throw new McpError(JsonRpcErrorCode.DatabaseError, 'Connection failed', { pool: 'primary' });
216
+ ```
217
+
218
+ Available factories include `notFound`, `validationError`, `forbidden`, `unauthorized`, `serviceUnavailable`, `rateLimited`, `timeout`, `conflict`, `internalError`, `serializationError`, `databaseError`, `configurationError`, `invalidParams`, `invalidRequest`. See framework CLAUDE.md for the full auto-classification table and the `api-errors` skill for contract patterns.
219
+
220
+ ---
221
+
222
+ ## Structure
223
+
224
+ ```text
225
+ src/
226
+ index.ts # createApp() entry point — registers 25 tools, 6 resources, 2 prompts; inits 7 services
227
+ config/
228
+ server-config.ts # BRAPI_* env vars (Zod schema, lazy-parsed)
229
+ alias-credentials.ts # Per-alias env-var resolution (BRAPI_<ALIAS>_*) for brapi_connect
230
+ services/
231
+ brapi-client/ # HTTP client — retry, concurrency cap, async-search poll, private-IP guard, binary fetch, POST/PUT
232
+ brapi-dialect/ # Per-server filter / payload adapters (spec, cassavabase) — translates plural→singular, drops searchText, declares known-dead POST /search routes; envelope surfaces id + source + disabled-search nouns
233
+ brapi-filters/ # Static v2.1 filter catalog
234
+ canvas-bridge/ # Default-canvas resolver (per-session when BRAPI_SESSION_ISOLATION=true; per-tenant otherwise), df_<uuid> table generator, provenance store
235
+ capability-registry/ # Per-connection /serverinfo cache + call guard
236
+ ontology-resolver/ # Free-text → ontology-term matcher for variables
237
+ reference-data-cache/ # Programs / trials / locations / crops lookup cache
238
+ server-registry/ # Alias → live connection map with auth resolution; session-scoped under BRAPI_SESSION_ISOLATION=true
239
+ mcp-server/
240
+ tools/
241
+ definitions/
242
+ brapi-connect.tool.ts # Session bootstrap — auth, capability load, orientation envelope
243
+ brapi-server-info.tool.ts # Orientation envelope on demand
244
+ brapi-describe-filters.tool.ts # Static BrAPI v2.1 filter catalog lookup
245
+ brapi-find-studies.tool.ts # find_* — studies, distributions + spillover
246
+ brapi-get-study.tool.ts # get_* — study + FK resolution + companion counts
247
+ brapi-find-germplasm.tool.ts # find_* — germplasm
248
+ brapi-get-germplasm.tool.ts # get_* — germplasm + attributes + parents + companion counts
249
+ brapi-walk-pedigree.tool.ts # BFS DAG walk (ancestors / descendants / both) with cycle detection
250
+ brapi-find-variables.tool.ts # find_* — observation variables, free-text ranking via OntologyResolver
251
+ brapi-find-observations.tool.ts # find_* — observation records
252
+ brapi-find-images.tool.ts # find_* — image metadata
253
+ brapi-get-image.tool.ts # Fetch image bytes inline (imagecontent → imageURL fallback)
254
+ brapi-find-locations.tool.ts # find_* — locations, optional client-side bbox filter
255
+ brapi-find-variants.tool.ts # find_* — variants, 1-based inclusive/exclusive genomic region
256
+ brapi-find-genotype-calls.tool.ts # Async-search genotype calls with maxCalls cap + dataframe spillover
257
+ brapi-dataframe-describe.tool.ts # List / describe canvas dataframes with columns, row counts, provenance
258
+ brapi-dataframe-query.tool.ts # Run SQL across canvas dataframes (SELECT only); typed columns response
259
+ brapi-dataframe-drop.tool.ts # Drop a dataframe by name (opt-in via BRAPI_CANVAS_DROP_ENABLED)
260
+ brapi-dataframe-export.tool.ts # Write CSV/Parquet/JSON to BRAPI_EXPORT_DIR (opt-in, stdio-only)
261
+ brapi-build-phenotype-matrix.tool.ts # Germplasm × trait matrix from studies; materialized as canvas dataframe
262
+ brapi-germplasm-performance.tool.ts # Per-variable aggregates (n, mean, median, sd) for a single germplasm
263
+ brapi-export-genotype-matrix.tool.ts # Genotype calls → germplasm × variant dataframe + VCF-lite / PLINK serialization
264
+ brapi-submit-observations.tool.ts # Two-phase observation write — preview / apply (POST + PUT) behind a confirmation round trip
265
+ brapi-raw-get.tool.ts # Last-resort GET passthrough with routing nudge
266
+ brapi-raw-search.tool.ts # Last-resort POST /search passthrough with async polling
267
+ shared/
268
+ connect-auth-schema.ts # Tagged-union auth input
269
+ orientation-envelope.ts # Shared envelope builder + formatter
270
+ find-helpers.ts # Alias / loadLimit / extraFilters fragments, mergeFilters, maybeSpill, DataframeHandleSchema
271
+ raw-routing-hints.ts # Routing nudges emitted by raw_get / raw_search when a curated tool exists
272
+ canvas-columns.ts # SQL-safe column-name sanitizer + variableLegend builder (shared by matrix tools)
273
+ observations.ts # Study-anchored observation pull shared by phenotype-matrix and germplasm-performance
274
+ genotype-calls.ts # Async genotype-call collector shared by find-genotype-calls and export-genotype-matrix
275
+ resources/
276
+ definitions/
277
+ brapi-server-info.resource.ts # brapi://server/info — orientation envelope (default connection)
278
+ brapi-calls.resource.ts # brapi://calls — raw capability profile
279
+ brapi-study.resource.ts # brapi://study/{studyDbId} — single study with FKs
280
+ brapi-germplasm.resource.ts # brapi://germplasm/{germplasmDbId} — single germplasm with attributes + parents
281
+ brapi-filters.resource.ts # brapi://filters/{endpoint} — filter catalog
282
+ brapi-variable.resource.ts # brapi://variable/{observationVariableDbId} — observation variable (trait, scale, method)
283
+ prompts/
284
+ definitions/
285
+ brapi-eda-study.prompt.ts # EDA playbook for one study (orient → variables → coverage → outliers → report)
286
+ brapi-meta-analysis.prompt.ts # Cross-study meta-analysis (resolve trait → discover studies → harmonize → summarize)
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Naming
292
+
293
+ | What | Convention | Example |
294
+ |:-----|:-----------|:--------|
295
+ | Files | kebab-case with suffix | `search-docs.tool.ts` |
296
+ | Tool/resource/prompt names | snake_case | `search_docs` |
297
+ | Directories | kebab-case | `src/services/doc-search/` |
298
+ | Descriptions | Single string or template literal, no `+` concatenation | `'Search items by query and filter.'` |
299
+
300
+ ---
301
+
302
+ ## Skills
303
+
304
+ 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. Keep development skills out of root `skills/`: plugin hosts load that directory for installing agents.
305
+
306
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, Codex: `.codex/skills/`, shared: `.agents/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).
307
+
308
+ Available skills:
309
+
310
+ | Skill | Purpose |
311
+ |:------|:--------|
312
+ | `setup` | Post-init project orientation |
313
+ | `design-mcp-server` | Design tool surface, resources, and services for a new server |
314
+ | `add-tool` | Scaffold a new tool definition |
315
+ | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
316
+ | `add-resource` | Scaffold a new resource definition |
317
+ | `add-prompt` | Scaffold a new prompt definition |
318
+ | `add-service` | Scaffold a new service integration |
319
+ | `add-test` | Scaffold test file for a tool, resource, or service |
320
+ | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
321
+ | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
322
+ | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
323
+ | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
324
+ | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
325
+ | `devcheck` | Lint, format, typecheck, audit |
326
+ | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
327
+ | `git-wrapup` | Land working-tree changes as a versioned commit stack; opens a release PR when the project declares release PR mode. |
328
+ | `release-pr-review` | Review an open release PR, land fixups, and keep its body current. Release PR mode only. |
329
+ | `release-and-publish` | Tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup`. |
330
+ | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
331
+ | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
332
+ | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
333
+ | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
334
+ | `api-auth` | Auth modes, scopes, JWT/OAuth |
335
+ | `api-linter` | Definition lint rule reference (`format-parity`, `schema-*`, `name-*`, `server-json-*`, …) |
336
+ | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
337
+ | `api-config` | AppConfig, parseConfig, env vars |
338
+ | `api-context` | Context interface, RequestContext, logger, state, multi-round-trip input |
339
+ | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
340
+ | `api-mirror` | MirrorService: persistent SQLite-backed local mirror of a bulk upstream dataset with FTS5 — Tier 3 opt-in |
341
+ | `api-services` | LLM, Speech, Graph services |
342
+ | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
343
+ | `api-testing` | createMockContext, test patterns |
344
+ | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
345
+ | `api-workers` | Cloudflare Workers runtime |
346
+
347
+ **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.
348
+
349
+ When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
350
+
351
+ ---
352
+
353
+ ## Commands
354
+
355
+ **Runtime:** Scripts and the production entry point use Bun directly — Bun executes TypeScript natively, no `tsx` shim. The `packageManager` field pins the version.
356
+
357
+ | Command | Purpose |
358
+ |:--------|:--------|
359
+ | `bun run build` | Compile TypeScript |
360
+ | `bun run rebuild` | Clean + build |
361
+ | `bun run clean` | Remove build artifacts |
362
+ | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
363
+ | `bun run audit:fix` | Upgrade vulnerable packages within existing ranges with `bun audit fix`; try before `bun update <name>` and `bun dedupe`. |
364
+ | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Last resort after in-place fixes; re-resolves ranged dependencies, including the framework. |
365
+ | `bun run list-skills` | Print the skill index for this project (name, version, description) |
366
+ | `bun run tree` | Generate `docs/tree.md` |
367
+ | `bun run format` | Auto-fix formatting via Biome |
368
+ | `bun run lint:mcp` | Validate MCP tool / resource / prompt definitions against the spec |
369
+ | `bun run lint:packaging` | Verify env var alignment between `manifest.json` and `server.json` |
370
+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
371
+ | `bun run test` | Vitest suite |
372
+ | `bun run start` | Production mode — defers transport selection to `MCP_TRANSPORT_TYPE` (stdio default) |
373
+ | `bun run start:stdio` | Production mode (stdio) — requires prior `bun run build` |
374
+ | `bun run start:http` | Production mode (HTTP) — requires prior `bun run build` |
375
+ | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/<minor>.x/` |
376
+ | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
377
+
378
+ ---
379
+
380
+ ## Bundling
381
+
382
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. The bundle therefore ships portable without the DuckDB native — `@duckdb/node-api` is loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP deployments are unaffected. To opt out, delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly when `manifest.json` is absent.
383
+
384
+ **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.
385
+
386
+ ---
387
+
388
+ ## Changelog
389
+
390
+ Directory-based, grouped by minor series using the `.x` semver-wildcard convention. Source of truth is `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.1.x/0.1.0.md`) — one file per released version, shipped in the npm package. At release time, author the per-version file with a concrete version and date, then run `npm run changelog:build` to regenerate the rollup. `changelog/template.md` is a **pristine format reference** — never edited, never renamed, never moved. Read it to remember the frontmatter + section layout when scaffolding a new per-version file. `CHANGELOG.md` is a **navigation index** (header + link + one-line summary per version), regenerated by `npm run changelog:build`. Devcheck hard-fails on drift. Never hand-edit `CHANGELOG.md`.
391
+
392
+ Each per-version file opens with YAML frontmatter:
393
+
394
+ ```markdown
395
+ ---
396
+ summary: One-line headline, ≤350 chars # required — powers the rollup index
397
+ breaking: false # optional — true flags breaking changes
398
+ security: false # optional — true ONLY for a source-code security fix, never a dependency CVE bump
399
+ ---
400
+
401
+ # 0.1.0 — YYYY-MM-DD
402
+ ...
403
+ ```
404
+
405
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
406
+
407
+ `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
408
+
409
+ **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security, then Dependencies. Include only sections with entries — don't ship empty headers.
410
+
411
+ **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. Subject omits the version number (GitHub prepends it). Follow `framework-skills/release-and-publish/SKILL.md` for the tag format.
412
+
413
+ ---
414
+
415
+ ## Publishing
416
+
417
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `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-and-publish` then 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. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **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.
418
+
419
+ ---
420
+
421
+ ## Imports
422
+
423
+ ```ts
424
+ // Framework — z is re-exported, no separate zod import needed
425
+ import { tool, z } from '@cyanheads/mcp-ts-core';
426
+ import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
427
+
428
+ // Server's own code — via path alias
429
+ import { getMyService } from '@/services/my-domain/my-service.js';
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Checklist
435
+
436
+ - [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()`)
437
+ - [ ] Optional nested objects: handler guards for empty inner values from form-based clients (`if (input.obj?.field && ...)`, not just `if (input.obj)`). When schema-level regex/length matters, use `z.union([z.literal(''), z.string().regex(...).describe(...)])` — literal variants are exempt from `describe-on-fields`.
438
+ - [ ] JSDoc `@fileoverview` + `@module` on every file
439
+ - [ ] `ctx.log` for logging, `ctx.state` for storage — no `console`, no direct persistence access
440
+ - [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
441
+ - [ ] `format()` renders all data the LLM needs — different clients forward different surfaces (Claude Code → `structuredContent`, Claude Desktop → `content[]`); both must carry the same data
442
+ - [ ] BrAPI tool: resolves connection via `ServerRegistry.get(ctx, alias ?? DEFAULT_ALIAS)` before touching the client
443
+ - [ ] BrAPI tool: gates the call with `CapabilityRegistry.ensure(...)` — never fires against an endpoint the server didn't advertise
444
+ - [ ] BrAPI tool: raw / domain / output schemas reviewed against real upstream sparsity (most `/germplasm` and `/studies` fields are optional in the wild)
445
+ - [ ] BrAPI tool: normalization and `format()` preserve uncertainty — never fabricate missing IDs, names, or counts
446
+ - [ ] BrAPI tool with dataframe spillover: rows beyond `loadLimit` materialize as a `df_<uuid>` canvas table via `CanvasBridge.registerDataframe`, handle surfaces in `result.dataframe`, `hasMore` set correctly
447
+ - [ ] Tests include at least one sparse upstream payload (fields omitted) alongside the happy path
448
+ - [ ] Registered in the `tools` array of `createApp()` in `src/index.ts`
449
+ - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
450
+ - [ ] `.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
451
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; user-supplied variables are listed in `env_vars`, never set to empty strings in `env`
452
+ - [ ] `.claude-plugin/plugin.json` populated — metadata from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name; user-supplied variables declared under `userConfig` and referenced as `${user_config.<option>}`
453
+ - [ ] `bun run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** brapi-mcp-server
4
- **Version:** 0.7.11
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 0.7.13
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
8
8
 
9
9
  > **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.
@@ -180,7 +180,7 @@ Handlers receive a unified `ctx` object. Currently used surface:
180
180
 
181
181
  Handlers throw — the framework catches, classifies, and formats.
182
182
 
183
- **Default for new tools: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata (≥ 5 words, lint-validated); to surface it on the wire, spread `...ctx.recoveryFor('reason')` into `data` or pass an explicit `{ recovery: { hint: '...' } }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring. Live across the BrAPI surface today: `brapi_build_phenotype_matrix`, `brapi_dataframe_describe`, `brapi_dataframe_export`, `brapi_dataframe_query`, `brapi_describe_filters`, `brapi_export_genotype_matrix`, `brapi_find_genotype_calls`, `brapi_germplasm_performance`, `brapi_get_germplasm`, `brapi_get_image`, `brapi_get_study`, `brapi_raw_get`, `brapi_raw_search`, `brapi_submit_observations`, plus the `brapi://variable/{observationVariableDbId}` resource.
183
+ **Default for new tools: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required descriptive metadata (≥ 5 words, lint-validated); to surface it on the wire, spread `...ctx.recoveryFor('reason')` into `data` or pass an explicit `{ recovery: { hint: '...' } }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring. Live across the BrAPI surface today: `brapi_build_phenotype_matrix`, `brapi_dataframe_describe`, `brapi_dataframe_export`, `brapi_dataframe_query`, `brapi_describe_filters`, `brapi_export_genotype_matrix`, `brapi_find_genotype_calls`, `brapi_germplasm_performance`, `brapi_get_germplasm`, `brapi_get_image`, `brapi_get_study`, `brapi_raw_get`, `brapi_raw_search`, `brapi_submit_observations`, plus the `brapi://variable/{observationVariableDbId}` resource.
184
184
 
185
185
  ```ts
186
186
  errors: [
@@ -301,9 +301,9 @@ src/
301
301
 
302
302
  ## Skills
303
303
 
304
- 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.
304
+ 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. Keep development skills out of root `skills/`: plugin hosts load that directory for installing agents.
305
305
 
306
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, Codex: `.codex/skills/`, shared: `.agents/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).
306
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, Codex: `.codex/skills/`, shared: `.agents/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).
307
307
 
308
308
  Available skills:
309
309
 
@@ -324,8 +324,9 @@ Available skills:
324
324
  | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
325
325
  | `devcheck` | Lint, format, typecheck, audit |
326
326
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
327
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
328
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
327
+ | `git-wrapup` | Land working-tree changes as a versioned commit stack; opens a release PR when the project declares release PR mode. |
328
+ | `release-pr-review` | Review an open release PR, land fixups, and keep its body current. Release PR mode only. |
329
+ | `release-and-publish` | Tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup`. |
329
330
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
330
331
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
331
332
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
@@ -343,7 +344,7 @@ Available skills:
343
344
  | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
344
345
  | `api-workers` | Cloudflare Workers runtime |
345
346
 
346
- **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.
347
+ **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.
347
348
 
348
349
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
349
350
 
@@ -359,7 +360,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
359
360
  | `bun run rebuild` | Clean + build |
360
361
  | `bun run clean` | Remove build artifacts |
361
362
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
362
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
363
+ | `bun run audit:fix` | Upgrade vulnerable packages within existing ranges with `bun audit fix`; try before `bun update <name>` and `bun dedupe`. |
364
+ | `bun run audit:refresh` | Delete `bun.lock`, reinstall, re-audit. Last resort after in-place fixes; re-resolves ranged dependencies, including the framework. |
363
365
  | `bun run list-skills` | Print the skill index for this project (name, version, description) |
364
366
  | `bun run tree` | Generate `docs/tree.md` |
365
367
  | `bun run format` | Auto-fix formatting via Biome |
@@ -377,7 +379,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
377
379
 
378
380
  ## Bundling
379
381
 
380
- `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. The bundle therefore ships portable without the DuckDB native — `@duckdb/node-api` is loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP deployments are unaffected. To opt out, delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly when `manifest.json` is absent.
382
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. The bundle therefore ships portable without the DuckDB native — `@duckdb/node-api` is loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP deployments are unaffected. To opt out, delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly when `manifest.json` is absent.
381
383
 
382
384
  **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.
383
385
 
@@ -404,9 +406,15 @@ security: false # optional — true ONLY for a source-c
404
406
 
405
407
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
406
408
 
407
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
409
+ **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security, then Dependencies. Include only sections with entries — don't ship empty headers.
408
410
 
409
- **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
411
+ **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. Subject omits the version number (GitHub prepends it). Follow `framework-skills/release-and-publish/SKILL.md` for the tag format.
412
+
413
+ ---
414
+
415
+ ## Publishing
416
+
417
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `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-and-publish` then 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. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **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.
410
418
 
411
419
  ---
412
420
 
@@ -439,7 +447,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
439
447
  - [ ] Tests include at least one sparse upstream payload (fields omitted) alongside the happy path
440
448
  - [ ] Registered in the `tools` array of `createApp()` in `src/index.ts`
441
449
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
442
- - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description
443
- - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
444
- - [ ] `.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
450
+ - [ ] `.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
451
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; user-supplied variables are listed in `env_vars`, never set to empty strings in `env`
452
+ - [ ] `.claude-plugin/plugin.json` populated — metadata from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name; user-supplied variables declared under `userConfig` and referenced as `${user_config.<option>}`
445
453
  - [ ] `bun run devcheck` passes