Skip to content

chembl mcp server changelog

io.github.cyanheads/chembl-mcp-server

10 changes recorded. MCP Harbor re-reads this listing from the official registry every six hours and asks the live server what it exposes, and keeps what changed between one look and the next.

  • Tools +1
    • + chembl_get_documents

  • server.json 1 field changed
    version
    0.5.0 0.6.0

  • AGENTS.md +45 −17

    https://raw.githubusercontent.com/cyanheads/chembl-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.4.0− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`− **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1− | `chembl_search_molecules` | Discovery entry point — find compounds by name / ChEMBL ID / InChIKey, or structure search (exact / similarity / substructure) from a SMILES |− | `chembl_get_bioactivities` | Flagship compound↔target bridge — bioactivity measurements for a molecule, a target, or both (which narrows to that compound–target pair); `potency_view` picks the measurements with a derivable `pchembl_value` (default) or exactly those without one; large sets spill to a per-view DataCanvas table SQL'd via `chembl_dataframe_query`; whenever a call stages nothing (canvas off, or an inline fit with no `canvas_id`), `cursor` / `nextCursor` page through the view, and a− | `chembl_get_drug_info` | Drug pharmacology — mechanism(s) of action, target(s), first-approval year, clinical indications |− **Service** (`src/services/chembl/chembl-service.ts`): the single upstream client. Builds Django-style filtered `.json` URLs, walks a bioactivity view by offset on a total order (`-pchembl_value` then `activity_id`, or `activity_id` alone for the null-potency view — never `page_meta.next`), coerces string numerics → `number | null` at the boundary (absent → `null`, never `0` — the scientific-fidelity rule), flattens nested upstream structures into the flat domain types in `types.ts`. Every fetch− **Canvas:** `chembl_get_bioactivities` spills large rowsets to a DataCanvas table when `CANVAS_PROVIDER_TYPE=duckdb` — one per `potency_view` (`bioactivities` / `bioactivities_null_potency`), so both can coexist on one canvas and `UNION ALL` back into the honest full set; otherwise it inlines a page that `nextCursor` continues, and the `chembl_dataframe_*` tools return a `canvas_disabled` error. A canvas is minted only when a view spills: without a `canvas_id`, `spillover()` gets a stand-in that−     assay_chembl_id: z.string().min(1).describe("ChEMBL assay ID, e.g. \"CHEMBL674637\"."),−     return await getChemblService().getAssay(input.assay_chembl_id.trim(), ctx);−     chemblId: z.string().regex(/^CHEMBL\d+$/, 'Must be a ChEMBL ID like CHEMBL25.').describe('ChEMBL molecule ID.'),− `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Here it carries the canonical cross-server chains (UniProt → `chembl_search_targets` → `chembl_get_bioactivities`), the ranking trap (`pchembl_value` is comparable only within one `standard_type`), and the ChEMBL CC BY-SA attribution — instead of repeating that context across tool descriptions.− tests/                                  # Vitest — services/, tools/, resources/ (no prompts)− Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, and this server ships `.claude-plugin/` and `.codex-plugin/`, so that name would hand these development skills to every agent that installs it. Keep `skills/` free for − **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.− | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |− `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 + **Version:** 0.5.0+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.4.0+ | `chembl_search_molecules` | Discovery entry point — find compounds by name / ChEMBL ID / InChIKey, or structure search (exact / similarity / substructure) from a SMILES; every row names its family `parent_chembl_id`; a total at ChEMBL's name (10,000) or substructure (30,000) search ceiling comes back `totalCountCapped`; an unparseable similarity or substructure SMILES fails fast as `invalid_structure` |+ | `chembl_get_bioactivities` | Flagship compound↔target bridge — bioactivity measurements for a molecule, a target, or both (which narrows to that compound–target pair); `include_alternate_forms` widens the molecule to its family by parent; `variant` keeps wild-type measurements or one recorded mutant; `potency_view` picks the measurements with a derivable `pchembl_value` (default) or exactly those without one; large sets spill to a per-view DataCanvas table SQL'd via `chembl_dataframe_query`; w+ | `chembl_get_drug_info` | Drug pharmacology — mechanism(s) of action, target(s), first-approval year, clinical indications, read across the molecule's family (the parent its record resolves to, and that parent's salts and alternate forms) |+ **Published skill** (`skills/chembl-guide/`: `SKILL.md` + `references/sql-recipes.md`): gates and workflows for the `chembl_*` tools: pinning a protein to its single-protein target, ranking leads without mixing mutants, salts, or repeat measurements, reading selectivity with the inactive results, and checking assay provenance and approval status. `createApp({ skills: 'skills' })` serves it over MCP as `skill://chembl-guide/SKILL.md` plus the `skill://{+path}` template; the same tree reaches plug+ **Service** (`src/services/chembl/chembl-service.ts`): the single upstream client. Builds Django-style filtered `.json` URLs, walks a bioactivity view by offset on a total order (`-pchembl_value` then `activity_id`, or `activity_id` alone for the null-potency view — never `page_meta.next`), asks every activity page for only the fields `normalizeActivity` reads (`only=` from `ACTIVITY_FIELDS`, derived from a field set that `satisfies` ties to `RawActivity` both ways, with a test that the list equ+ **Canvas:** `chembl_get_bioactivities` spills large rowsets to a DataCanvas table under `CANVAS_PROVIDER_TYPE=duckdb`, the default `src/index.ts` sets (§ Server identity and instructions) — one per `potency_view` (`bioactivities` / `bioactivities_null_potency`), so both can coexist on one canvas and `UNION ALL` back into the honest full set; under `none` it inlines a page that `nextCursor` continues, and the `chembl_dataframe_*` tools return a `canvas_disabled` error. A canvas is minted only whe+ import { chemblIdInput } from '@/mcp-server/input-normalization.js';+     // Parses any case and surrounding whitespace to the canonical ID; the advertised+     // pattern spells those variants out, since JSON Schema `pattern` takes no flags.+     assay_chembl_id: chemblIdInput('Must be a ChEMBL assay ID like CHEMBL674637 — …')+       .describe("ChEMBL assay ID, e.g. \"CHEMBL674637\"."),+     return await getChemblService().getAssay(input.assay_chembl_id, ctx);+ import { chemblIdInput } from '@/mcp-server/input-normalization.js';+     chemblId: chemblIdInput('Must be a ChEMBL ID like CHEMBL25 — …').describe('ChEMBL molecule ID.'),+ parseConfig();                                             // loads .env first, so a .env value wins+ process.env.CANVAS_PROVIDER_TYPE ||= 'duckdb';             // DataCanvas on unless set to none+ resetConfig();                                             // the framework logger parsed config at import; drop that cache+   skills: 'skills',                                        // serves the root skills/ tree (chembl-guide) over MCP+ `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Here it carries the canonical cross-server chains (UniProt → `chembl_search_targets` → `chembl_get_bioactivities`), the ranking trap (`pchembl_value` is comparable only within one `standard_type`), the coverage trap (measurements without a `pchembl_value` sit in the `null_potency` view), the salt trap (a drug's measurements split across its alternate forms), and the ChEMBL CC BY-SA attributi+     input-normalization.ts              # Shared input-edge parsing: ChEMBL IDs, molecule refs, assay_type, codes, limit clamp+     markdown-text.ts                    # The format() escape: upstream strings render inert in content[] (mdInline / mdLineStart)+ skills/+   chembl-guide/                         # Published agent skill: SKILL.md + references/sql-recipes.md+ framework-skills/                       # Development skills for the agent working on this repo+ plugin.json                             # Agent Plugins 1.0.0 manifest (Codex, Cursor, Copilot, VS Code, Kiro)+ mcp.json                                # Its stdio server entry+ tests/                                  # Vitest — services/, tools/, resources/, mcp-server/ (no prompts)+ Two skill trees live at the project root, written for different readers:+ | Tree | For | Written by |+ |:-----|:----|:-----------|+ | `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | the `maintenance` sync (framework skills); you (your own) |+ | `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates. Holds one skill, `chembl-guide` | You, deliberately, with the `add-skill` skill |+ Development skills are modular instructions. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. Their directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, and this server ships `.claude-plugin/` and `.codex-plugin/`, so that name would hand them to every agent that installs it.+ `skills/chembl-guide/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package (`files`) and the Docker image, and reaches MCP clients because `src/index.ts` opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree.+ **Agent skill directory:** Copy `framework-skills/` into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.+ | `add-skill` | Author a skill the server publishes in root `skills/` for the agents that use it — when one earns its place, structure, validation, delivery |+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, `.mcpbignore` coverage, plugin manifests, the published `skills/` tree (run by devcheck) |

  • Resources +1
    • + chembl-guide

  • server.json 1 field changed
    version
    0.4.1 0.5.0

  • server.json 1 field changed
    version
    0.4.0 0.4.1

  • AGENTS.md +38 −26

    https://raw.githubusercontent.com/cyanheads/chembl-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.3.3− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.14`− **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0− **Zod:** ^4.6.5− | `chembl_get_bioactivities` | Flagship compound↔target bridge — bioactivity measurements for a molecule, a target, or both (which narrows to that compound–target pair); `potency_view` picks the measurements with a derivable `pchembl_value` (default) or exactly those without one; large sets spill to a per-view DataCanvas table SQL'd via `chembl_dataframe_query` |− | `chembl_dataframe_query` | Read-only SQL SELECT over the spilled bioactivity rows (canvas) |− **Service** (`src/services/chembl/chembl-service.ts`): the single upstream client. Builds Django-style filtered `.json` URLs, paginates `page_meta`, coerces string numerics → `number | null` at the boundary (absent → `null`, never `0` — the scientific-fidelity rule), flattens nested upstream structures into the flat domain types in `types.ts`. Every fetch routes through `fetchJson`, which wraps the framework HTTP utility in `withRetry`.− **Canvas:** `chembl_get_bioactivities` spills large rowsets to a DataCanvas table when `CANVAS_PROVIDER_TYPE=duckdb` — one per `potency_view` (`bioactivities` / `bioactivities_null_potency`), so both can coexist on one canvas and `UNION ALL` back into the honest full set; otherwise it inlines a preview and the `chembl_dataframe_*` tools return a `canvas_disabled` error. The spill passes `caps: { maxRows: config.maxSpillRows }`, which bounds the upstream page walk behind the lazy stream — a cappe− - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.−     .describe('Base URL for the ChEMBL REST data API. Override for a private mirror.'),−   _config ??= parseEnvConfig(ServerConfigSchema, {−     apiBaseUrl: 'CHEMBL_API_BASE_URL',−     requestTimeoutMs: 'CHEMBL_REQUEST_TIMEOUT_MS',−     maxPageSize: 'CHEMBL_MAX_PAGE_SIZE',−     defaultLimit: 'CHEMBL_DEFAULT_LIMIT',−     maxSpillRows: 'CHEMBL_MAX_SPILL_ROWS',−     dataframeDropEnabled: 'CHEMBL_DATAFRAME_DROP_ENABLED',−   });− `parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`CHEMBL_REQUEST_TIMEOUT_MS`) not the path (`requestTimeoutMs`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.− For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.−     initChemblService(config);− **No `teardown` hook, deliberately.** `teardown(core)` is the `setup()` counterpart for releasing what setup allocated — a watcher, a socket, a non-`unref()`'d timer. `setup()` here constructs the stateless `ChemblService` (config values only, no handles) and stores a reference to `core.canvas`, which the framework owns and shuts down itself. Nothing is left to release, and an empty hook is ceremony, not adoption.− `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 − **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, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`.− - [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch− - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never wri+ **Version:** 0.4.0+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1+ **Zod:** ^4.6.7+ | `chembl_get_bioactivities` | Flagship compound↔target bridge — bioactivity measurements for a molecule, a target, or both (which narrows to that compound–target pair); `potency_view` picks the measurements with a derivable `pchembl_value` (default) or exactly those without one; large sets spill to a per-view DataCanvas table SQL'd via `chembl_dataframe_query`; whenever a call stages nothing (canvas off, or an inline fit with no `canvas_id`), `cursor` / `nextCursor` page through the view, and a+ | `chembl_dataframe_query` | Read-only SQL SELECT over the staged bioactivity rows (canvas) |+ **Service** (`src/services/chembl/chembl-service.ts`): the single upstream client. Builds Django-style filtered `.json` URLs, walks a bioactivity view by offset on a total order (`-pchembl_value` then `activity_id`, or `activity_id` alone for the null-potency view — never `page_meta.next`), coerces string numerics → `number | null` at the boundary (absent → `null`, never `0` — the scientific-fidelity rule), flattens nested upstream structures into the flat domain types in `types.ts`. Every fetch+ **Canvas:** `chembl_get_bioactivities` spills large rowsets to a DataCanvas table when `CANVAS_PROVIDER_TYPE=duckdb` — one per `potency_view` (`bioactivities` / `bioactivities_null_potency`), so both can coexist on one canvas and `UNION ALL` back into the honest full set; otherwise it inlines a page that `nextCursor` continues, and the `chembl_dataframe_*` tools return a `canvas_disabled` error. A canvas is minted only when a view spills: without a `canvas_id`, `spillover()` gets a stand-in that+ - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters. The exceptions are canvas plumbing: re-raising a framework canvas failure whose throw-site recovery hint the calling tool cannot follow (`chembl_dataframe_drop`, and `chembl_get_bioactivities` at its canvas mint, which also release+     .describe('Base URL for the ChEMBL REST data API. Override for a private mirror or pinned host.'),+   maxConcurrentPages: z.coerce.number().int().min(1).max(6).default(4),+   spillDeadlineMs: z.coerce.number().int().min(10_000).max(600_000).default(45_000),+ const ENV_VARS = {+   apiBaseUrl: 'CHEMBL_API_BASE_URL',+   requestTimeoutMs: 'CHEMBL_REQUEST_TIMEOUT_MS',+   maxPageSize: 'CHEMBL_MAX_PAGE_SIZE',+   defaultLimit: 'CHEMBL_DEFAULT_LIMIT',+   maxSpillRows: 'CHEMBL_MAX_SPILL_ROWS',+   maxConcurrentPages: 'CHEMBL_MAX_CONCURRENT_PAGES',+   spillDeadlineMs: 'CHEMBL_SPILL_DEADLINE_MS',+   dataframeDropEnabled: 'CHEMBL_DATAFRAME_DROP_ENABLED',+ } as const;+   _config ??= parseEnvConfig(ServerConfigSchema, ENV_VARS);+ // The drop flag alone, never throwing — for the registration gate at module load.+ export function isDataframeDropEnabled(): boolean {+   const flag = ServerConfigSchema.shape.dataframeDropEnabled.safeParse(+     process.env[ENV_VARS.dataframeDropEnabled],+   );+   return flag.success && flag.data;+ }+ `parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`CHEMBL_REQUEST_TIMEOUT_MS`) not the path (`requestTimeoutMs`). It throws `ConfigurationError`, and `src/index.ts` calls `getServerConfig()` inside `setup()`, where the framework prints it as a clean startup banner and exits 1. Never call it at module load: an invalid value there escapes as a raw stack before `createApp()` runs. The drop tool's registration form is chosen at module load, so `index.ts` reads that + For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off/y/n/enabled/disabled` (case-insensitive) and rejects anything else, so `=false` actually disables.+     initChemblService(getServerConfig());                  // parse here, so a bad value reaches the banner+ **No `teardown` hook, deliberately.** `teardown(core)` is the `setup()` counterpart for releasing what setup allocated — a watcher, a socket, a non-`unref()`'d timer. `setup()` here constructs the `ChemblService` (config values and a page pacer that sets only `maxConcurrent`, so it arms no timer — no handles) and stores a reference to `core.canvas`, which the framework owns and shuts down itself. Nothing is left to release, and an empty hook is ceremony, not adoption.+ `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 + **Adding a user-settable env var touches four files:** `server.json` (registry discovery, `environmentVariables[]` — the source of each option's description), `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`, unless the option needs the canvas), `.claude-plugin/plugin.json` (`userConfig` + `"KEY": "${user_config.KEY}"` in `env`), and `.codex-plugin/mcp.json` (`env_vars`). Each option keys on its env var name; `manifest.json` and `.claude-plugin/plugin.json` give it the same `+ - [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch beyond the canvas exceptions in Core Rules+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — the same options as `manifest.json`'s `user_config`, plus th

  • server.json 2 fields changed
    env_vars
    CANVAS_PROVIDER_TYPE, CHEMBL_API_BASE_URL, CHEMBL_REQUEST_TIMEOUT_MS, CHEMBL_MAX_PAGE_SIZE, CHEMBL_DEFAULT_LIMIT, CHEMBL_MAX_SPILL_ROWS, CHEMBL_DATAFRAME_DROP_ENABLED, MCP_LOG_LEVEL CANVAS_PROVIDER_TYPE, CHEMBL_API_BASE_URL, CHEMBL_REQUEST_TIMEOUT_MS, CHEMBL_MAX_PAGE_SIZE, CHEMBL_DEFAULT_LIMIT, CHEMBL_MAX_SPILL_ROWS, CHEMBL_MAX_CONCURRENT_PAGES, CHEMBL_SPILL_DEADLINE_MS, CHEMBL_DATAFRAME_DROP_ENABLED, MCP_LOG_LEVEL
    version
    0.3.3 0.4.0

  • AGENTS.md +12 −12

    https://raw.githubusercontent.com/cyanheads/chembl-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.3.2− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.9`− **MCP SDK:** `@modelcontextprotocol/server` ^2.1.0− | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts JSON-serializable values; reads return their JSON form (a `Date` becomes an ISO string). |− | `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. This server declares no such surface. |− | `ctx.inputs` | Reader over a retried request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. Empty on the first round. |− | `ctx.requestId` | Unique request ID. |− **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reaso− Tool error `content[]` closes with `(reason … · not retryable)` whenever `data.reason` is a non-empty string or `data.retryable` a boolean, so a test pinning that text asserts containment, never a byte-exact match. An argument rejection — an unknown root key, a wrong type, a missing required field — is `InvalidParams` (-32602) with `data.reason: "invalid_arguments"`, never `ValidationError`.−   if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));− throw new McpError(JsonRpcErrorCode.DatabaseError, 'Connection failed', { pool: 'primary' });− **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of w+ **Version:** 0.3.3+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.14`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0+ | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any JSON-serializable value; reads return its JSON form (a `Date` comes back as an ISO string). |+ | `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |+ | `ctx.inputs` | The request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped` — limited to what the client declared (`elicitation` and its form/url modes, `sampling`, `roots`). Client-supplied: a consent gate trusts only a `ctx.state` record it stored when it asked, bound to the operation, caller, and target (see the `api-context` skill). |+ | `ctx.clientCapabilities` | What the client declared for this request, `undefined` when no view exists. Decides whether to ask for optional context (e.g. roots); never a reason to skip a consent prompt. |+ | `ctx.requestId` | Request ID — the one every log record of the call carries and its error envelope returns as `data.requestId`. |+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. The framework puts it on the+   if (!item) throw ctx.fail('no_match', `No item ${input.id}`);+ throw new McpError(JsonRpcErrorCode.InitializationFailed, 'Connection failed', { pool: 'primary' });+ **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the release digest: theme line, `## Changes`, `## Gates`, changelog link last); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewri

  • server.json 1 field changed
    version
    0.3.2 0.3.3