@cyanheads/brapi-mcp-server 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +391 -0
- package/Dockerfile +99 -0
- package/LICENSE +201 -0
- package/README.md +590 -0
- package/changelog/0.1.x/0.1.0.md +19 -0
- package/changelog/0.1.x/0.1.1.md +27 -0
- package/changelog/0.1.x/0.1.2.md +22 -0
- package/changelog/0.2.x/0.2.0.md +35 -0
- package/changelog/0.2.x/0.2.1.md +36 -0
- package/changelog/0.3.x/0.3.0.md +38 -0
- package/changelog/0.3.x/0.3.1.md +40 -0
- package/changelog/0.3.x/0.3.2.md +29 -0
- package/changelog/0.3.x/0.3.3.md +19 -0
- package/changelog/0.3.x/0.3.4.md +33 -0
- package/changelog/0.3.x/0.3.5.md +24 -0
- package/changelog/template.md +51 -0
- package/dist/config/alias-credentials.d.ts +82 -0
- package/dist/config/alias-credentials.d.ts.map +1 -0
- package/dist/config/alias-credentials.js +159 -0
- package/dist/config/alias-credentials.js.map +1 -0
- package/dist/config/server-config.d.ts +39 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +128 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/brapi-eda-study.prompt.d.ts +14 -0
- package/dist/mcp-server/prompts/definitions/brapi-eda-study.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/brapi-eda-study.prompt.js +75 -0
- package/dist/mcp-server/prompts/definitions/brapi-eda-study.prompt.js.map +1 -0
- package/dist/mcp-server/prompts/definitions/brapi-meta-analysis.prompt.d.ts +16 -0
- package/dist/mcp-server/prompts/definitions/brapi-meta-analysis.prompt.d.ts.map +1 -0
- package/dist/mcp-server/prompts/definitions/brapi-meta-analysis.prompt.js +109 -0
- package/dist/mcp-server/prompts/definitions/brapi-meta-analysis.prompt.js.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-calls.resource.d.ts +11 -0
- package/dist/mcp-server/resources/definitions/brapi-calls.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-calls.resource.js +46 -0
- package/dist/mcp-server/resources/definitions/brapi-calls.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-dataset.resource.d.ts +13 -0
- package/dist/mcp-server/resources/definitions/brapi-dataset.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-dataset.resource.js +26 -0
- package/dist/mcp-server/resources/definitions/brapi-dataset.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-filters.resource.d.ts +19 -0
- package/dist/mcp-server/resources/definitions/brapi-filters.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-filters.resource.js +45 -0
- package/dist/mcp-server/resources/definitions/brapi-filters.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.d.ts +18 -0
- package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.js +34 -0
- package/dist/mcp-server/resources/definitions/brapi-germplasm.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-server-info.resource.d.ts +11 -0
- package/dist/mcp-server/resources/definitions/brapi-server-info.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-server-info.resource.js +28 -0
- package/dist/mcp-server/resources/definitions/brapi-server-info.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-study.resource.d.ts +18 -0
- package/dist/mcp-server/resources/definitions/brapi-study.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/brapi-study.resource.js +34 -0
- package/dist/mcp-server/resources/definitions/brapi-study.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.d.ts +82 -0
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.js +106 -0
- package/dist/mcp-server/tools/definitions/brapi-connect.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-describe-filters.tool.d.ts +41 -0
- package/dist/mcp-server/tools/definitions/brapi-describe-filters.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-describe-filters.tool.js +88 -0
- package/dist/mcp-server/tools/definitions/brapi-describe-filters.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.d.ts +74 -0
- package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.js +386 -0
- package/dist/mcp-server/tools/definitions/brapi-find-genotype-calls.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.d.ts +72 -0
- package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.js +290 -0
- package/dist/mcp-server/tools/definitions/brapi-find-germplasm.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-images.tool.d.ts +65 -0
- package/dist/mcp-server/tools/definitions/brapi-find-images.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-images.tool.js +243 -0
- package/dist/mcp-server/tools/definitions/brapi-find-images.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.d.ts +63 -0
- package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.js +278 -0
- package/dist/mcp-server/tools/definitions/brapi-find-locations.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.d.ts +74 -0
- package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.js +288 -0
- package/dist/mcp-server/tools/definitions/brapi-find-observations.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.d.ts +69 -0
- package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.js +243 -0
- package/dist/mcp-server/tools/definitions/brapi-find-studies.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.d.ts +86 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.js +337 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variables.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.js +248 -0
- package/dist/mcp-server/tools/definitions/brapi-find-variants.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.js +306 -0
- package/dist/mcp-server/tools/definitions/brapi-get-germplasm.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-get-image.tool.d.ts +57 -0
- package/dist/mcp-server/tools/definitions/brapi-get-image.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-get-image.tool.js +291 -0
- package/dist/mcp-server/tools/definitions/brapi-get-image.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-get-study.tool.d.ts +75 -0
- package/dist/mcp-server/tools/definitions/brapi-get-study.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-get-study.tool.js +323 -0
- package/dist/mcp-server/tools/definitions/brapi-get-study.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-manage-dataset.tool.d.ts +87 -0
- package/dist/mcp-server/tools/definitions/brapi-manage-dataset.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-manage-dataset.tool.js +296 -0
- package/dist/mcp-server/tools/definitions/brapi-manage-dataset.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.d.ts +35 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.js +148 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-get.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.d.ts +33 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.js +126 -0
- package/dist/mcp-server/tools/definitions/brapi-raw-search.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.d.ts +51 -0
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.js +41 -0
- package/dist/mcp-server/tools/definitions/brapi-server-info.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.d.ts +117 -0
- package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.js +574 -0
- package/dist/mcp-server/tools/definitions/brapi-submit-observations.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.d.ts +52 -0
- package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.js +420 -0
- package/dist/mcp-server/tools/definitions/brapi-walk-pedigree.tool.js.map +1 -0
- package/dist/mcp-server/tools/shared/connect-auth-schema.d.ts +29 -0
- package/dist/mcp-server/tools/shared/connect-auth-schema.d.ts.map +1 -0
- package/dist/mcp-server/tools/shared/connect-auth-schema.js +55 -0
- package/dist/mcp-server/tools/shared/connect-auth-schema.js.map +1 -0
- package/dist/mcp-server/tools/shared/find-helpers.d.ts +143 -0
- package/dist/mcp-server/tools/shared/find-helpers.d.ts.map +1 -0
- package/dist/mcp-server/tools/shared/find-helpers.js +319 -0
- package/dist/mcp-server/tools/shared/find-helpers.js.map +1 -0
- package/dist/mcp-server/tools/shared/orientation-envelope.d.ts +97 -0
- package/dist/mcp-server/tools/shared/orientation-envelope.d.ts.map +1 -0
- package/dist/mcp-server/tools/shared/orientation-envelope.js +254 -0
- package/dist/mcp-server/tools/shared/orientation-envelope.js.map +1 -0
- package/dist/mcp-server/tools/shared/raw-routing-hints.d.ts +10 -0
- package/dist/mcp-server/tools/shared/raw-routing-hints.d.ts.map +1 -0
- package/dist/mcp-server/tools/shared/raw-routing-hints.js +46 -0
- package/dist/mcp-server/tools/shared/raw-routing-hints.js.map +1 -0
- package/dist/services/brapi-client/brapi-client.d.ts +76 -0
- package/dist/services/brapi-client/brapi-client.d.ts.map +1 -0
- package/dist/services/brapi-client/brapi-client.js +320 -0
- package/dist/services/brapi-client/brapi-client.js.map +1 -0
- package/dist/services/brapi-client/index.d.ts +9 -0
- package/dist/services/brapi-client/index.d.ts.map +1 -0
- package/dist/services/brapi-client/index.js +7 -0
- package/dist/services/brapi-client/index.js.map +1 -0
- package/dist/services/brapi-client/types.d.ts +82 -0
- package/dist/services/brapi-client/types.d.ts.map +1 -0
- package/dist/services/brapi-client/types.js +8 -0
- package/dist/services/brapi-client/types.js.map +1 -0
- package/dist/services/brapi-filters/catalog.d.ts +14 -0
- package/dist/services/brapi-filters/catalog.d.ts.map +1 -0
- package/dist/services/brapi-filters/catalog.js +490 -0
- package/dist/services/brapi-filters/catalog.js.map +1 -0
- package/dist/services/brapi-filters/index.d.ts +8 -0
- package/dist/services/brapi-filters/index.d.ts.map +1 -0
- package/dist/services/brapi-filters/index.js +7 -0
- package/dist/services/brapi-filters/index.js.map +1 -0
- package/dist/services/brapi-filters/types.d.ts +23 -0
- package/dist/services/brapi-filters/types.d.ts.map +1 -0
- package/dist/services/brapi-filters/types.js +9 -0
- package/dist/services/brapi-filters/types.js.map +1 -0
- package/dist/services/capability-registry/capability-registry.d.ts +51 -0
- package/dist/services/capability-registry/capability-registry.d.ts.map +1 -0
- package/dist/services/capability-registry/capability-registry.js +234 -0
- package/dist/services/capability-registry/capability-registry.js.map +1 -0
- package/dist/services/capability-registry/index.d.ts +9 -0
- package/dist/services/capability-registry/index.d.ts.map +1 -0
- package/dist/services/capability-registry/index.js +7 -0
- package/dist/services/capability-registry/index.js.map +1 -0
- package/dist/services/capability-registry/types.d.ts +67 -0
- package/dist/services/capability-registry/types.d.ts.map +1 -0
- package/dist/services/capability-registry/types.js +9 -0
- package/dist/services/capability-registry/types.js.map +1 -0
- package/dist/services/dataset-store/dataset-store.d.ts +35 -0
- package/dist/services/dataset-store/dataset-store.d.ts.map +1 -0
- package/dist/services/dataset-store/dataset-store.js +190 -0
- package/dist/services/dataset-store/dataset-store.js.map +1 -0
- package/dist/services/dataset-store/index.d.ts +8 -0
- package/dist/services/dataset-store/index.d.ts.map +1 -0
- package/dist/services/dataset-store/index.js +7 -0
- package/dist/services/dataset-store/index.js.map +1 -0
- package/dist/services/dataset-store/types.d.ts +65 -0
- package/dist/services/dataset-store/types.d.ts.map +1 -0
- package/dist/services/dataset-store/types.js +8 -0
- package/dist/services/dataset-store/types.js.map +1 -0
- package/dist/services/ontology-resolver/index.d.ts +8 -0
- package/dist/services/ontology-resolver/index.d.ts.map +1 -0
- package/dist/services/ontology-resolver/index.js +7 -0
- package/dist/services/ontology-resolver/index.js.map +1 -0
- package/dist/services/ontology-resolver/ontology-resolver.d.ts +49 -0
- package/dist/services/ontology-resolver/ontology-resolver.d.ts.map +1 -0
- package/dist/services/ontology-resolver/ontology-resolver.js +99 -0
- package/dist/services/ontology-resolver/ontology-resolver.js.map +1 -0
- package/dist/services/ontology-resolver/types.d.ts +38 -0
- package/dist/services/ontology-resolver/types.d.ts.map +1 -0
- package/dist/services/ontology-resolver/types.js +8 -0
- package/dist/services/ontology-resolver/types.js.map +1 -0
- package/dist/services/reference-data-cache/index.d.ts +9 -0
- package/dist/services/reference-data-cache/index.d.ts.map +1 -0
- package/dist/services/reference-data-cache/index.js +7 -0
- package/dist/services/reference-data-cache/index.js.map +1 -0
- package/dist/services/reference-data-cache/reference-data-cache.d.ts +31 -0
- package/dist/services/reference-data-cache/reference-data-cache.d.ts.map +1 -0
- package/dist/services/reference-data-cache/reference-data-cache.js +131 -0
- package/dist/services/reference-data-cache/reference-data-cache.js.map +1 -0
- package/dist/services/reference-data-cache/types.d.ts +42 -0
- package/dist/services/reference-data-cache/types.d.ts.map +1 -0
- package/dist/services/reference-data-cache/types.js +9 -0
- package/dist/services/reference-data-cache/types.js.map +1 -0
- package/dist/services/server-registry/index.d.ts +9 -0
- package/dist/services/server-registry/index.d.ts.map +1 -0
- package/dist/services/server-registry/index.js +7 -0
- package/dist/services/server-registry/index.js.map +1 -0
- package/dist/services/server-registry/server-registry.d.ts +57 -0
- package/dist/services/server-registry/server-registry.d.ts.map +1 -0
- package/dist/services/server-registry/server-registry.js +210 -0
- package/dist/services/server-registry/server-registry.js.map +1 -0
- package/dist/services/server-registry/types.d.ts +43 -0
- package/dist/services/server-registry/types.d.ts.map +1 -0
- package/dist/services/server-registry/types.js +10 -0
- package/dist/services/server-registry/types.js.map +1 -0
- package/package.json +86 -0
- package/server.json +99 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Agent Protocol
|
|
2
|
+
|
|
3
|
+
**Server:** brapi-mcp-server
|
|
4
|
+
**Version:** 0.3.5
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
|
|
6
|
+
|
|
7
|
+
> **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.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What's Next?
|
|
12
|
+
|
|
13
|
+
When the user asks what to do next, what's left, or needs direction, suggest relevant options based on the current project state:
|
|
14
|
+
|
|
15
|
+
1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date with the current codebase
|
|
16
|
+
2. **Run the `design-mcp-server` skill** — if the tool/resource surface hasn't been mapped yet, work through domain design
|
|
17
|
+
3. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt` skills
|
|
18
|
+
4. **Add services** — scaffold domain service integrations using the `add-service` skill
|
|
19
|
+
5. **Add tests** — scaffold tests for existing definitions using the `add-test` skill
|
|
20
|
+
6. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill, get a report of issues and pain points
|
|
21
|
+
7. **Run `devcheck`** — lint, format, typecheck, and security audit
|
|
22
|
+
8. **Run the `security-pass` skill** — audit handlers for MCP-specific security gaps: output injection, scope blast radius, input sinks, tenant isolation
|
|
23
|
+
9. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
|
|
24
|
+
10. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
|
|
25
|
+
|
|
26
|
+
Tailor suggestions to what's actually missing or stale — don't recite the full list every time.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Rules
|
|
31
|
+
|
|
32
|
+
- **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.
|
|
33
|
+
- **Use `ctx.log`** for request-scoped logging. No `console` calls.
|
|
34
|
+
- **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
|
|
35
|
+
- **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
|
|
36
|
+
- **Secrets in env vars only** — never hardcoded.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Patterns
|
|
41
|
+
|
|
42
|
+
### Tool — connection bootstrap
|
|
43
|
+
|
|
44
|
+
`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`.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// src/mcp-server/tools/definitions/brapi-connect.tool.ts (abbreviated)
|
|
48
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
49
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
50
|
+
import { resolveConnectInput } from '@/config/alias-credentials.js';
|
|
51
|
+
import { ConnectAuthSchema } from '../shared/connect-auth-schema.js';
|
|
52
|
+
|
|
53
|
+
export const brapiConnect = tool('brapi_connect', {
|
|
54
|
+
description: 'Connect to a BrAPI v2 server… baseUrl + auth fall back to BRAPI_<ALIAS>_* / BRAPI_DEFAULT_* env vars when omitted.',
|
|
55
|
+
annotations: { openWorldHint: true, readOnlyHint: false, idempotentHint: true },
|
|
56
|
+
errors: [
|
|
57
|
+
{ reason: 'auth_token_exchange_failed', code: JsonRpcErrorCode.Forbidden,
|
|
58
|
+
when: 'SGN or OAuth token exchange against /token failed',
|
|
59
|
+
recovery: 'Verify the credentials and that the server exposes /token before retrying.' },
|
|
60
|
+
{ reason: 'auth_no_access_token', code: JsonRpcErrorCode.Forbidden,
|
|
61
|
+
when: 'Token endpoint responded but did not return an access_token',
|
|
62
|
+
recovery: 'Confirm the credentials are valid and the IdP issues access tokens for this grant.' },
|
|
63
|
+
] as const,
|
|
64
|
+
input: z.object({
|
|
65
|
+
baseUrl: z.string().url().optional().describe('Falls back to BRAPI_<ALIAS>_BASE_URL → BRAPI_DEFAULT_BASE_URL.'),
|
|
66
|
+
auth: ConnectAuthSchema.optional().describe('Falls back to env-derived credentials.'),
|
|
67
|
+
alias: z.string().regex(/^[a-zA-Z0-9_-]+$/).default('default'),
|
|
68
|
+
}),
|
|
69
|
+
output: OrientationEnvelopeSchema,
|
|
70
|
+
async handler(input, ctx) {
|
|
71
|
+
const resolved = resolveConnectInput(input.alias, { baseUrl: input.baseUrl, auth: input.auth });
|
|
72
|
+
const connection = await getServerRegistry().register(ctx, {
|
|
73
|
+
alias: input.alias, baseUrl: resolved.baseUrl, auth: resolved.auth,
|
|
74
|
+
});
|
|
75
|
+
await getCapabilityRegistry().invalidate(connection.baseUrl, ctx);
|
|
76
|
+
return buildOrientationEnvelope(ctx, connection, { registry: getCapabilityRegistry(), client: getBrapiClient() });
|
|
77
|
+
},
|
|
78
|
+
format: (result) => [{ type: 'text', text: formatOrientationEnvelope(result) }],
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Tool — find with dataset spillover
|
|
83
|
+
|
|
84
|
+
`find_*` tools share a pattern: pull one page capped at `loadLimit`, compute distributions across the returned rows, and if the upstream total exceeds `loadLimit` spill the full union into `DatasetStore` and return a handle.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// src/mcp-server/tools/definitions/brapi-find-germplasm.tool.ts (abbreviated)
|
|
88
|
+
export const brapiFindGermplasm = tool('brapi_find_germplasm', {
|
|
89
|
+
description:
|
|
90
|
+
'Find germplasm by name, synonym, accession, PUI, crop, or free-text. Returns a dataset handle when the upstream total exceeds loadLimit.',
|
|
91
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
92
|
+
input: z.object({
|
|
93
|
+
alias: AliasInput,
|
|
94
|
+
names: z.array(z.string()).optional(),
|
|
95
|
+
crops: z.array(z.string()).optional(),
|
|
96
|
+
text: z.string().optional(),
|
|
97
|
+
loadLimit: LoadLimitInput,
|
|
98
|
+
extraFilters: ExtraFiltersInput,
|
|
99
|
+
}),
|
|
100
|
+
output: OutputSchema,
|
|
101
|
+
async handler(input, ctx) {
|
|
102
|
+
const connection = await getServerRegistry().get(ctx, input.alias ?? DEFAULT_ALIAS);
|
|
103
|
+
await getCapabilityRegistry().ensure(connection.baseUrl, { service: 'germplasm', method: 'GET' }, ctx);
|
|
104
|
+
|
|
105
|
+
const filters = mergeFilters(/* named + extraFilters */, warnings);
|
|
106
|
+
const firstPage = await loadInitialPage(client, connection, '/germplasm', filters, loadLimit, ctx);
|
|
107
|
+
|
|
108
|
+
if (firstPage.hasMore && firstPage.totalCount > loadLimit) {
|
|
109
|
+
const spill = await spillToDataset({ /* persists union into DatasetStore */ });
|
|
110
|
+
// ... attach dataset handle to result
|
|
111
|
+
}
|
|
112
|
+
return { /* results + distributions + refinementHint + dataset? */ };
|
|
113
|
+
},
|
|
114
|
+
format: (result) => [{ type: 'text', text: renderFindResult(result) }],
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Server config
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// src/config/server-config.ts — lazy-parsed, separate from framework config
|
|
122
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
123
|
+
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
124
|
+
|
|
125
|
+
const ServerConfigSchema = z.object({
|
|
126
|
+
defaultBaseUrl: z.string().url().optional(),
|
|
127
|
+
loadLimit: z.coerce.number().int().positive().default(200),
|
|
128
|
+
maxConcurrentRequests: z.coerce.number().int().positive().default(4),
|
|
129
|
+
retryMaxAttempts: z.coerce.number().int().min(0).default(3),
|
|
130
|
+
datasetTtlSeconds: z.coerce.number().int().positive().default(86_400),
|
|
131
|
+
referenceCacheTtlSeconds: z.coerce.number().int().positive().default(3_600),
|
|
132
|
+
// …see src/config/server-config.ts for the full schema
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
let _config: z.infer<typeof ServerConfigSchema> | undefined;
|
|
136
|
+
export function getServerConfig() {
|
|
137
|
+
_config ??= parseEnvConfig(ServerConfigSchema, {
|
|
138
|
+
defaultBaseUrl: 'BRAPI_DEFAULT_BASE_URL',
|
|
139
|
+
loadLimit: 'BRAPI_LOAD_LIMIT',
|
|
140
|
+
maxConcurrentRequests: 'BRAPI_MAX_CONCURRENT_REQUESTS',
|
|
141
|
+
retryMaxAttempts: 'BRAPI_RETRY_MAX_ATTEMPTS',
|
|
142
|
+
datasetTtlSeconds: 'BRAPI_DATASET_TTL_SECONDS',
|
|
143
|
+
referenceCacheTtlSeconds: 'BRAPI_REFERENCE_CACHE_TTL_SECONDS',
|
|
144
|
+
});
|
|
145
|
+
return _config;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`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.
|
|
150
|
+
|
|
151
|
+
**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.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Context
|
|
156
|
+
|
|
157
|
+
Handlers receive a unified `ctx` object. Currently used surface:
|
|
158
|
+
|
|
159
|
+
| Property | Description |
|
|
160
|
+
|:---------|:------------|
|
|
161
|
+
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
|
|
162
|
+
| `ctx.state` | Tenant-scoped KV — used by `ServerRegistry` (connection aliases), `DatasetStore` (spilled `find_*` results), and `CapabilityRegistry` (cached profiles). |
|
|
163
|
+
| `ctx.signal` | `AbortSignal` — threaded into every BrAPI HTTP call so client-side cancellation aborts the upstream request. |
|
|
164
|
+
| `ctx.requestId` | Unique request ID — auto-attached to every `ctx.log` entry. |
|
|
165
|
+
| `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio — scopes all `ctx.state` reads/writes. |
|
|
166
|
+
|
|
167
|
+
`ctx.elicit` is used by `brapi_submit_observations` to gate apply-mode writes behind user confirmation (with explicit `force: true` as the bypass). `ctx.sample` and `ctx.progress` are not used yet — they'll show up when long-running workflows (pedigree traversal, genotype-call pulls) need progress reporting or LLM sampling. `ctx.fail(reason, …)` is the typed thrower keyed off declared `errors[]` contracts — used by 8 tools and 3 resources today. `ctx.recoveryFor(reason)` resolves the matching contract entry's recovery hint into `data.recovery.hint` so it surfaces on the wire.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Errors
|
|
172
|
+
|
|
173
|
+
Handlers throw — the framework catches, classifies, and formats.
|
|
174
|
+
|
|
175
|
+
**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_connect`, `brapi_describe_filters`, `brapi_find_genotype_calls`, `brapi_get_germplasm`, `brapi_get_image`, `brapi_get_study`, `brapi_manage_dataset`, `brapi_raw_get`, `brapi_submit_observations`, plus the `brapi://study/{studyDbId}`, `brapi://germplasm/{germplasmDbId}`, and `brapi://filters/{endpoint}` resources.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
errors: [
|
|
179
|
+
{ reason: 'unknown_alias', code: JsonRpcErrorCode.NotFound,
|
|
180
|
+
when: 'No connection registered for this alias',
|
|
181
|
+
recovery: 'Call brapi_connect with this alias before retrying.' },
|
|
182
|
+
],
|
|
183
|
+
async handler(input, ctx) {
|
|
184
|
+
const conn = registry.peek(input.alias);
|
|
185
|
+
if (!conn) throw ctx.fail('unknown_alias', `No connection for ${input.alias}`,
|
|
186
|
+
{ ...ctx.recoveryFor('unknown_alias') });
|
|
187
|
+
// ...
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Fallback (no contract entry fits, services, prototype tools):** throw via factories or plain `Error`.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// Plain Error — framework auto-classifies from message patterns
|
|
195
|
+
throw new Error('Item not found'); // → NotFound
|
|
196
|
+
throw new Error('Invalid query format'); // → ValidationError
|
|
197
|
+
|
|
198
|
+
// Error factories — explicit code, concise
|
|
199
|
+
import { notFound, validationError, internalError, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
200
|
+
throw notFound('Item not found', { itemId });
|
|
201
|
+
throw serviceUnavailable('API unavailable', { url }, { cause: err });
|
|
202
|
+
|
|
203
|
+
// McpError — full control over code and data
|
|
204
|
+
import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
205
|
+
throw new McpError(JsonRpcErrorCode.DatabaseError, 'Connection failed', { pool: 'primary' });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Structure
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
src/
|
|
216
|
+
index.ts # createApp() entry point — registers 19 tools, 6 resources, 2 prompts; inits 7 services
|
|
217
|
+
config/
|
|
218
|
+
server-config.ts # BRAPI_* env vars (Zod schema, lazy-parsed)
|
|
219
|
+
alias-credentials.ts # Per-alias env-var resolution (BRAPI_<ALIAS>_*) for brapi_connect
|
|
220
|
+
services/
|
|
221
|
+
brapi-client/ # HTTP client — retry, concurrency cap, async-search poll, private-IP guard, binary fetch, POST/PUT
|
|
222
|
+
brapi-filters/ # Static v2.1 filter catalog
|
|
223
|
+
capability-registry/ # Per-connection /serverinfo cache + call guard
|
|
224
|
+
dataset-store/ # Tenant-scoped handles for spilled find_* results
|
|
225
|
+
ontology-resolver/ # Free-text → ontology-term matcher for variables
|
|
226
|
+
reference-data-cache/ # Programs / trials / locations / crops lookup cache
|
|
227
|
+
server-registry/ # Alias → live connection map with auth resolution
|
|
228
|
+
mcp-server/
|
|
229
|
+
tools/
|
|
230
|
+
definitions/
|
|
231
|
+
brapi-connect.tool.ts # Session bootstrap — auth, capability load, orientation envelope
|
|
232
|
+
brapi-server-info.tool.ts # Orientation envelope on demand
|
|
233
|
+
brapi-describe-filters.tool.ts # Static BrAPI v2.1 filter catalog lookup
|
|
234
|
+
brapi-find-studies.tool.ts # find_* — studies, distributions + spillover
|
|
235
|
+
brapi-get-study.tool.ts # get_* — study + FK resolution + companion counts
|
|
236
|
+
brapi-find-germplasm.tool.ts # find_* — germplasm
|
|
237
|
+
brapi-get-germplasm.tool.ts # get_* — germplasm + attributes + parents + companion counts
|
|
238
|
+
brapi-walk-pedigree.tool.ts # BFS DAG walk (ancestors / descendants / both) with cycle detection
|
|
239
|
+
brapi-find-variables.tool.ts # find_* — observation variables, free-text ranking via OntologyResolver
|
|
240
|
+
brapi-find-observations.tool.ts # find_* — observation records
|
|
241
|
+
brapi-find-images.tool.ts # find_* — image metadata
|
|
242
|
+
brapi-get-image.tool.ts # Fetch image bytes inline (imagecontent → imageURL fallback)
|
|
243
|
+
brapi-find-locations.tool.ts # find_* — locations, optional client-side bbox filter
|
|
244
|
+
brapi-find-variants.tool.ts # find_* — variants, 1-based inclusive/exclusive genomic region
|
|
245
|
+
brapi-find-genotype-calls.tool.ts # Async-search genotype calls with maxCalls cap + spillover
|
|
246
|
+
brapi-manage-dataset.tool.ts # Dataset lifecycle — list / summary / load / delete
|
|
247
|
+
brapi-submit-observations.tool.ts # Two-phase observation write — preview / apply (POST + PUT) with elicit gate
|
|
248
|
+
brapi-raw-get.tool.ts # Last-resort GET passthrough with routing nudge
|
|
249
|
+
brapi-raw-search.tool.ts # Last-resort POST /search passthrough with async polling
|
|
250
|
+
shared/
|
|
251
|
+
connect-auth-schema.ts # Tagged-union auth input
|
|
252
|
+
orientation-envelope.ts # Shared envelope builder + formatter
|
|
253
|
+
find-helpers.ts # Alias / loadLimit / extraFilters fragments, mergeFilters, maybeSpill, DatasetHandleSchema
|
|
254
|
+
raw-routing-hints.ts # Routing nudges emitted by raw_get / raw_search when a curated tool exists
|
|
255
|
+
resources/
|
|
256
|
+
definitions/
|
|
257
|
+
brapi-server-info.resource.ts # brapi://server/info — orientation envelope (default connection)
|
|
258
|
+
brapi-calls.resource.ts # brapi://calls — raw capability profile
|
|
259
|
+
brapi-study.resource.ts # brapi://study/{studyDbId} — single study with FKs
|
|
260
|
+
brapi-germplasm.resource.ts # brapi://germplasm/{germplasmDbId} — single germplasm with attributes + parents
|
|
261
|
+
brapi-dataset.resource.ts # brapi://dataset/{datasetId} — dataset metadata + provenance
|
|
262
|
+
brapi-filters.resource.ts # brapi://filters/{endpoint} — filter catalog
|
|
263
|
+
prompts/
|
|
264
|
+
definitions/
|
|
265
|
+
brapi-eda-study.prompt.ts # EDA playbook for one study (orient → variables → coverage → outliers → report)
|
|
266
|
+
brapi-meta-analysis.prompt.ts # Cross-study meta-analysis (resolve trait → discover studies → harmonize → summarize)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Naming
|
|
272
|
+
|
|
273
|
+
| What | Convention | Example |
|
|
274
|
+
|:-----|:-----------|:--------|
|
|
275
|
+
| Files | kebab-case with suffix | `search-docs.tool.ts` |
|
|
276
|
+
| Tool/resource/prompt names | snake_case | `search_docs` |
|
|
277
|
+
| Directories | kebab-case | `src/services/doc-search/` |
|
|
278
|
+
| Descriptions | Single string or template literal, no `+` concatenation | `'Search items by query and filter.'` |
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Skills
|
|
283
|
+
|
|
284
|
+
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.
|
|
285
|
+
|
|
286
|
+
**Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). This makes skills available as context without needing to reference `skills/` paths manually. After framework updates, run the `maintenance` skill — it re-syncs the agent directory automatically (Phase B).
|
|
287
|
+
|
|
288
|
+
Available skills:
|
|
289
|
+
|
|
290
|
+
| Skill | Purpose |
|
|
291
|
+
|:------|:--------|
|
|
292
|
+
| `setup` | Post-init project orientation |
|
|
293
|
+
| `design-mcp-server` | Design tool surface, resources, and services for a new server |
|
|
294
|
+
| `add-tool` | Scaffold a new tool definition |
|
|
295
|
+
| `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
|
|
296
|
+
| `add-resource` | Scaffold a new resource definition |
|
|
297
|
+
| `add-prompt` | Scaffold a new prompt definition |
|
|
298
|
+
| `add-service` | Scaffold a new service integration |
|
|
299
|
+
| `add-test` | Scaffold test file for a tool, resource, or service |
|
|
300
|
+
| `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
|
|
301
|
+
| `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
|
|
302
|
+
| `devcheck` | Lint, format, typecheck, audit |
|
|
303
|
+
| `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
|
|
304
|
+
| `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
|
|
305
|
+
| `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
|
|
306
|
+
| `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
|
|
307
|
+
| `api-auth` | Auth modes, scopes, JWT/OAuth |
|
|
308
|
+
| `api-config` | AppConfig, parseConfig, env vars |
|
|
309
|
+
| `api-context` | Context interface, logger, state, progress |
|
|
310
|
+
| `api-errors` | McpError, JsonRpcErrorCode, error patterns |
|
|
311
|
+
| `api-services` | LLM, Speech, Graph services |
|
|
312
|
+
| `api-testing` | createMockContext, test patterns |
|
|
313
|
+
| `api-utils` | Formatting, parsing, security, pagination, scheduling |
|
|
314
|
+
| `api-workers` | Cloudflare Workers runtime |
|
|
315
|
+
|
|
316
|
+
When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Commands
|
|
321
|
+
|
|
322
|
+
**Runtime:** Scripts use `tsx` — both `npm run <cmd>` and `bun run <cmd>` work. Prefer `bun` (declared in `packageManager`).
|
|
323
|
+
|
|
324
|
+
| Command | Purpose |
|
|
325
|
+
|:--------|:--------|
|
|
326
|
+
| `bun run build` | Compile TypeScript |
|
|
327
|
+
| `bun run rebuild` | Clean + build |
|
|
328
|
+
| `bun run clean` | Remove build artifacts |
|
|
329
|
+
| `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
|
|
330
|
+
| `bun run tree` | Generate `docs/tree.md` |
|
|
331
|
+
| `bun run format` | Auto-fix formatting via Biome |
|
|
332
|
+
| `bun run lint:mcp` | Validate MCP tool / resource / prompt definitions against the spec |
|
|
333
|
+
| `bun run test` | Vitest suite |
|
|
334
|
+
| `bun run start` | Production mode — defers transport selection to `MCP_TRANSPORT_TYPE` (stdio default) |
|
|
335
|
+
| `bun run start:stdio` | Production mode (stdio) — requires prior `bun run build` |
|
|
336
|
+
| `bun run start:http` | Production mode (HTTP) — requires prior `bun run build` |
|
|
337
|
+
| `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/<minor>.x/` |
|
|
338
|
+
| `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Changelog
|
|
343
|
+
|
|
344
|
+
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`.
|
|
345
|
+
|
|
346
|
+
Each per-version file opens with YAML frontmatter:
|
|
347
|
+
|
|
348
|
+
```markdown
|
|
349
|
+
---
|
|
350
|
+
summary: One-line headline, ≤250 chars # required — powers the rollup index
|
|
351
|
+
breaking: false # optional — true flags breaking changes
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
# 0.1.0 — YYYY-MM-DD
|
|
355
|
+
...
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
`breaking: true` renders a `· ⚠️ Breaking` badge in the rollup — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames).
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Imports
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
// Framework — z is re-exported, no separate zod import needed
|
|
366
|
+
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
367
|
+
import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
368
|
+
|
|
369
|
+
// Server's own code — via path alias
|
|
370
|
+
import { getMyService } from '@/services/my-domain/my-service.js';
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Checklist
|
|
376
|
+
|
|
377
|
+
- [ ] 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()`)
|
|
378
|
+
- [ ] 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`.
|
|
379
|
+
- [ ] JSDoc `@fileoverview` + `@module` on every file
|
|
380
|
+
- [ ] `ctx.log` for logging, `ctx.state` for storage — no `console`, no direct persistence access
|
|
381
|
+
- [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
|
|
382
|
+
- [ ] `format()` renders all data the LLM needs — different clients forward different surfaces (Claude Code → `structuredContent`, Claude Desktop → `content[]`); both must carry the same data
|
|
383
|
+
- [ ] BrAPI tool: resolves connection via `ServerRegistry.get(ctx, alias ?? DEFAULT_ALIAS)` before touching the client
|
|
384
|
+
- [ ] BrAPI tool: gates the call with `CapabilityRegistry.ensure(...)` — never fires against an endpoint the server didn't advertise
|
|
385
|
+
- [ ] BrAPI tool: raw / domain / output schemas reviewed against real upstream sparsity (most `/germplasm` and `/studies` fields are optional in the wild)
|
|
386
|
+
- [ ] BrAPI tool: normalization and `format()` preserve uncertainty — never fabricate missing IDs, names, or counts
|
|
387
|
+
- [ ] BrAPI tool with dataset spillover: rows beyond `loadLimit` persist via `DatasetStore`, handle surfaces in `result.dataset`, `hasMore` set correctly
|
|
388
|
+
- [ ] Tests include at least one sparse upstream payload (fields omitted) alongside the happy path
|
|
389
|
+
- [ ] Registered in the `tools` array of `createApp()` in `src/index.ts`
|
|
390
|
+
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
391
|
+
- [ ] `bun run devcheck` passes
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Build Stage
|
|
3
|
+
#
|
|
4
|
+
# This stage installs all dependencies (including dev), builds the TypeScript
|
|
5
|
+
# source code into JavaScript, and prepares the production assets.
|
|
6
|
+
# ==============================================================================
|
|
7
|
+
FROM oven/bun:1 AS build
|
|
8
|
+
|
|
9
|
+
WORKDIR /usr/src/app
|
|
10
|
+
|
|
11
|
+
# Copy dependency manifests for optimized layer caching
|
|
12
|
+
COPY package.json bun.lock ./
|
|
13
|
+
|
|
14
|
+
# Install all dependencies (including dev dependencies for building)
|
|
15
|
+
RUN bun install --frozen-lockfile
|
|
16
|
+
|
|
17
|
+
# Copy the rest of the source code
|
|
18
|
+
COPY . .
|
|
19
|
+
|
|
20
|
+
# Build the application
|
|
21
|
+
RUN bun run build
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ==============================================================================
|
|
25
|
+
# Production Stage
|
|
26
|
+
#
|
|
27
|
+
# This stage creates a minimal, optimized, and secure image for running the
|
|
28
|
+
# application. It uses a slim base image and only includes production
|
|
29
|
+
# dependencies and build artifacts.
|
|
30
|
+
# ==============================================================================
|
|
31
|
+
FROM oven/bun:1-slim AS production
|
|
32
|
+
|
|
33
|
+
WORKDIR /usr/src/app
|
|
34
|
+
|
|
35
|
+
# Set the environment to production for performance and to ensure only
|
|
36
|
+
# production dependencies are installed.
|
|
37
|
+
ENV NODE_ENV=production
|
|
38
|
+
|
|
39
|
+
# OCI image metadata (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
|
|
40
|
+
LABEL org.opencontainers.image.title="@cyanheads/brapi-mcp-server"
|
|
41
|
+
LABEL org.opencontainers.image.description="BrAPI v2.1 MCP server — studies, germplasm, observations, genotypes, images, and pedigrees across Breedbase, T3, Sweetpotatobase, and any BrAPI-compliant server."
|
|
42
|
+
LABEL org.opencontainers.image.source="https://github.com/cyanheads/brapi-mcp-server"
|
|
43
|
+
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
|
44
|
+
|
|
45
|
+
# Copy dependency manifests
|
|
46
|
+
COPY package.json bun.lock ./
|
|
47
|
+
|
|
48
|
+
# Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
|
|
49
|
+
# that are not needed in the final production image.
|
|
50
|
+
RUN bun install --production --frozen-lockfile --ignore-scripts
|
|
51
|
+
|
|
52
|
+
# Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
|
|
53
|
+
# These are not bundled by default to keep the base image lean. Enable at build time
|
|
54
|
+
# with: docker build --build-arg OTEL_ENABLED=true
|
|
55
|
+
ARG OTEL_ENABLED=true
|
|
56
|
+
RUN if [ "$OTEL_ENABLED" = "true" ]; then \
|
|
57
|
+
bun add @hono/otel \
|
|
58
|
+
@opentelemetry/instrumentation-http \
|
|
59
|
+
@opentelemetry/exporter-metrics-otlp-http \
|
|
60
|
+
@opentelemetry/exporter-trace-otlp-http \
|
|
61
|
+
@opentelemetry/instrumentation-pino \
|
|
62
|
+
@opentelemetry/resources \
|
|
63
|
+
@opentelemetry/sdk-metrics \
|
|
64
|
+
@opentelemetry/sdk-node \
|
|
65
|
+
@opentelemetry/sdk-trace-node \
|
|
66
|
+
@opentelemetry/semantic-conventions; \
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Copy the compiled application code from the build stage
|
|
70
|
+
COPY --from=build /usr/src/app/dist ./dist
|
|
71
|
+
|
|
72
|
+
# The 'oven/bun' image already provides a non-root user named 'bun'.
|
|
73
|
+
# We will use this existing user for enhanced security.
|
|
74
|
+
|
|
75
|
+
# Create and set permissions for the log directory, assigning ownership to the 'bun' user.
|
|
76
|
+
RUN mkdir -p /var/log/brapi-mcp-server && chown -R bun:bun /var/log/brapi-mcp-server
|
|
77
|
+
|
|
78
|
+
# Switch to the non-root user
|
|
79
|
+
USER bun
|
|
80
|
+
|
|
81
|
+
# Define an argument for the port, allowing it to be overridden at build time.
|
|
82
|
+
# The `PORT` variable is often injected by cloud environments at runtime.
|
|
83
|
+
ARG PORT
|
|
84
|
+
|
|
85
|
+
# Set runtime environment variables
|
|
86
|
+
# Note: PORT is an automatic variable in many cloud environments (e.g., Cloud Run)
|
|
87
|
+
ENV MCP_HTTP_PORT=${PORT:-3010}
|
|
88
|
+
ENV MCP_HTTP_HOST="0.0.0.0"
|
|
89
|
+
ENV MCP_TRANSPORT_TYPE="http"
|
|
90
|
+
ENV MCP_SESSION_MODE="stateless"
|
|
91
|
+
ENV MCP_LOG_LEVEL="info"
|
|
92
|
+
ENV LOGS_DIR="/var/log/brapi-mcp-server"
|
|
93
|
+
ENV MCP_FORCE_CONSOLE_LOGGING="true"
|
|
94
|
+
|
|
95
|
+
# Expose the port the server listens on
|
|
96
|
+
EXPOSE ${MCP_HTTP_PORT}
|
|
97
|
+
|
|
98
|
+
# The command to start the server
|
|
99
|
+
CMD ["bun", "run", "dist/index.js"]
|