@cyanheads/interpro-mcp-server 0.1.3 → 0.2.0

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 (71) hide show
  1. package/AGENTS.md +38 -13
  2. package/CLAUDE.md +38 -13
  3. package/Dockerfile +9 -1
  4. package/README.md +82 -123
  5. package/changelog/0.2.x/0.2.0.md +31 -0
  6. package/dist/config/server-config.d.ts +13 -0
  7. package/dist/config/server-config.d.ts.map +1 -0
  8. package/dist/config/server-config.js +19 -0
  9. package/dist/config/server-config.js.map +1 -0
  10. package/dist/index.js +11 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.d.ts +45 -0
  13. package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.d.ts.map +1 -1
  14. package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.js +37 -14
  15. package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.js.map +1 -1
  16. package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.d.ts +1 -0
  17. package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.js +1 -1
  19. package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.d.ts +133 -0
  21. package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.d.ts.map +1 -0
  22. package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.js +128 -0
  23. package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.js.map +1 -0
  24. package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.d.ts +171 -0
  25. package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.d.ts.map +1 -0
  26. package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.js +290 -0
  27. package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.js.map +1 -0
  28. package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.js +1 -1
  29. package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.js.map +1 -1
  30. package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.d.ts +168 -0
  31. package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.d.ts.map +1 -0
  32. package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.js +187 -0
  33. package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.js.map +1 -0
  34. package/dist/services/interpro/http-adapter.d.ts +11 -2
  35. package/dist/services/interpro/http-adapter.d.ts.map +1 -1
  36. package/dist/services/interpro/http-adapter.js +67 -9
  37. package/dist/services/interpro/http-adapter.js.map +1 -1
  38. package/dist/services/interpro/interpro-service.d.ts +1 -1
  39. package/dist/services/interpro/interpro-service.d.ts.map +1 -1
  40. package/dist/services/interpro/interpro-service.js +6 -24
  41. package/dist/services/interpro/interpro-service.js.map +1 -1
  42. package/dist/services/interpro/reference.d.ts.map +1 -1
  43. package/dist/services/interpro/reference.js +23 -2
  44. package/dist/services/interpro/reference.js.map +1 -1
  45. package/dist/services/interpro/schemas.d.ts +1 -0
  46. package/dist/services/interpro/schemas.d.ts.map +1 -1
  47. package/dist/services/interpro/schemas.js +5 -0
  48. package/dist/services/interpro/schemas.js.map +1 -1
  49. package/dist/services/interpro/structure-schemas.d.ts +116 -0
  50. package/dist/services/interpro/structure-schemas.d.ts.map +1 -0
  51. package/dist/services/interpro/structure-schemas.js +74 -0
  52. package/dist/services/interpro/structure-schemas.js.map +1 -0
  53. package/dist/services/sequence-scan/scan-output.d.ts +94 -0
  54. package/dist/services/sequence-scan/scan-output.d.ts.map +1 -0
  55. package/dist/services/sequence-scan/scan-output.js +174 -0
  56. package/dist/services/sequence-scan/scan-output.js.map +1 -0
  57. package/dist/services/sequence-scan/scan-schemas.d.ts +112 -0
  58. package/dist/services/sequence-scan/scan-schemas.d.ts.map +1 -0
  59. package/dist/services/sequence-scan/scan-schemas.js +97 -0
  60. package/dist/services/sequence-scan/scan-schemas.js.map +1 -0
  61. package/dist/services/sequence-scan/sequence-scan-service.d.ts +119 -0
  62. package/dist/services/sequence-scan/sequence-scan-service.d.ts.map +1 -0
  63. package/dist/services/sequence-scan/sequence-scan-service.js +453 -0
  64. package/dist/services/sequence-scan/sequence-scan-service.js.map +1 -0
  65. package/dist/services/sequence-scan/sequence.d.ts +32 -0
  66. package/dist/services/sequence-scan/sequence.d.ts.map +1 -0
  67. package/dist/services/sequence-scan/sequence.js +63 -0
  68. package/dist/services/sequence-scan/sequence.js.map +1 -0
  69. package/package.json +10 -7
  70. package/server.json +18 -4
  71. package/skills/interpro-guide/SKILL.md +72 -0
package/AGENTS.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** @cyanheads/interpro-mcp-server
4
- **Version:** 0.1.3
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.16`
4
+ **Version:** 0.2.0
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.4.0
8
8
  **Zod:** ^4.6.7
9
9
 
10
10
  > **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.
@@ -48,7 +48,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
48
48
 
49
49
  ### Tool
50
50
 
51
- Abridged from `src/mcp-server/tools/definitions/interpro-get-entry.tool.ts`. All six tools share this shape: a service call through `service.begin(ctx)`, `ctx.enrich` for notices, a per-tool `errors` contract, and the shared `formatInterpro` formatter.
51
+ Abridged from `src/mcp-server/tools/definitions/interpro-get-entry.tool.ts`. All nine tools share this shape: a service call through `service.begin(ctx)`, `ctx.enrich` for notices, a per-tool `errors` contract, and the shared `formatInterpro` formatter.
52
52
 
53
53
  ```ts
54
54
  import { tool, z } from '@cyanheads/mcp-ts-core';
@@ -96,7 +96,7 @@ export const interproGetEntry = tool('interpro_get_entry', {
96
96
  });
97
97
  ```
98
98
 
99
- There are no resources or prompts and no server-specific environment variables: the InterPro API is keyless. Search/member pages clamp positive integer limits to 100, discovery to 50, with default 20 and composed notices. GO filters require the InterPro source and accept either `goTerm` or `goCategory`, never both; member recovery follows `member_databases` or `integratedInto`. Entry `sections` selects named metadata while retaining core and unknown fields; explicit `overlapLimit` enables whole-record paging. Retained descriptions gain readable `descriptionText` through `htmlparser2`, alongside unchanged raw descriptions and annotation flags. Protein maps append requested residue rules after matches and extra features, preserving complete records and old cursor scopes when the new flag is absent/false. Discovery requests native fragment status on every page; optional `architectureId` adds native IDA grouping and explicit entry/taxon detail availability without inventing missing arrays. Bind only explicit selections to cursor scope and preserve unknown upstream values.
99
+ There are no resources or prompts. The InterPro API is keyless; the one server-specific environment variable, optional `INTERPRO_SCAN_EMAIL` (parsed in `src/config/server-config.ts`), is the contact address the EBI Job Dispatcher requires for InterProScan submissions. Unset, `interpro_scan_sequence` still serves precalculated matches and fails other sequences as `scan_not_configured`; the hosted instance leaves it unset. The scan tools (`interpro_scan_sequence`, `interpro_get_scan_result`) run through `src/services/sequence-scan/`, outside the InterPro pacer, and `interpro_scan_sequence` is the only tool that is not read-only. `interpro_get_structure_domains` lifts each match's nested chain records into top-level `structures[]` joined by `entry` to stay inside the depth-8 parity walker, and its numbering contract (chain `start`/`end` beside author `auth_start`/`auth_end`, UniProt positions) is retained verbatim. Optional `proteomeId` on discovery joins the route after `taxonId` and binds to the cursor scope only when present. Search/member pages clamp positive integer limits to 100, discovery to 50, with default 20 and composed notices. GO filters require the InterPro source and accept either `goTerm` or `goCategory`, never both; member recovery follows `member_databases` or `integratedInto`. Entry `sections` selects named metadata while retaining core and unknown fields; explicit `overlapLimit` enables whole-record paging. Retained descriptions gain readable `descriptionText` through `htmlparser2`, alongside unchanged raw descriptions and annotation flags. Protein maps append requested residue rules after matches and extra features, preserving complete records and old cursor scopes when the new flag is absent/false. Discovery requests native fragment status on every page; optional `architectureId` adds native IDA grouping and explicit entry/taxon detail availability without inventing missing arrays. Bind only explicit selections to cursor scope and preserve unknown upstream values.
100
100
 
101
101
  ### Server identity and instructions
102
102
 
@@ -106,9 +106,13 @@ There are no resources or prompts and no server-specific environment variables:
106
106
  await createApp({
107
107
  name: 'interpro-mcp-server',
108
108
  title: 'interpro-mcp-server',
109
- tools: [/* six definitions */],
109
+ tools: [/* nine definitions */],
110
110
  sessionMode: 'stateless',
111
- setup() { initInterproService(); },
111
+ skills: 'skills',
112
+ setup() {
113
+ initInterproService();
114
+ initSequenceScanService(getServerConfig().scanEmail);
115
+ },
112
116
  teardown() { disposeInterproService(); },
113
117
  instructions: '…',
114
118
  });
@@ -182,12 +186,15 @@ See framework CLAUDE.md and the `api-errors` skill for the full auto-classificat
182
186
  ```text
183
187
  src/
184
188
  index.ts # createApp() entry point
189
+ config/
190
+ server-config.ts # INTERPRO_SCAN_EMAIL parsing (lazy, validated at startup)
185
191
  services/
186
192
  interpro/
187
193
  interpro-service.ts # Paced, cancellable request boundary (init/accessor pattern)
188
- http-adapter.ts # node:https HTTP/1.1 transport with 204 handling
194
+ http-adapter.ts # node:https HTTP/1.1 transport with 204 handling; also carries the scan tools' Matches API and Job Dispatcher calls
189
195
  routing.ts # Accession → database routing, fixed-origin URL building
190
196
  schemas.ts # Wire and output Zod schemas
197
+ structure-schemas.ts # PDB ID and chain validation, structure and chain-record schemas
191
198
  cursors.ts # Opaque cursor envelopes
192
199
  budget.ts # 100,000-byte response budget and whole-item fitting
193
200
  relationships.ts # Clan relationship-graph slicing
@@ -195,9 +202,14 @@ src/
195
202
  format.ts # Shared content[] formatter
196
203
  description-text.ts # Readable HTML fragment projection for entry detail
197
204
  upstream-errors.ts # Shared upstream error contract rows
205
+ sequence-scan/
206
+ sequence.ts # Sequence parsing and the UniParc MD5 digest
207
+ scan-schemas.ts # Matches API, Job Dispatcher, and match-record schemas
208
+ sequence-scan-service.ts # Precalculated lookup, job submission and polling, in-flight cap
209
+ scan-output.ts # Shared scan output schema and match segmentation
198
210
  mcp-server/
199
211
  tools/definitions/
200
- interpro-[name].tool.ts # Six tool definitions
212
+ interpro-[name].tool.ts # Nine tool definitions
201
213
  tests/
202
214
  services/ # Service, adapter, and budget tests
203
215
  tools/ # Tool contract tests
@@ -218,9 +230,18 @@ tests/
218
230
 
219
231
  ## Skills
220
232
 
221
- 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/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
233
+ Two skill trees live at the project root, written for different readers:
234
+
235
+ | Tree | For | Written by |
236
+ |:-----|:----|:-----------|
237
+ | `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |
238
+ | `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. Absent by default; usually none is needed |
239
+
240
+ 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/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand them to every agent that installs it.
241
+
242
+ A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree. This server publishes one, `skills/interpro-guide/SKILL.md` (identifier chains, gates, and chain/author/UniProt numbering), served with `skills: 'skills'`.
222
243
 
223
- **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.
244
+ **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.
224
245
 
225
246
  Available skills:
226
247
 
@@ -232,6 +253,7 @@ Available skills:
232
253
  | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
233
254
  | `add-resource` | Scaffold a new resource definition |
234
255
  | `add-prompt` | Scaffold a new prompt definition |
256
+ | `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 |
235
257
  | `add-service` | Scaffold a new service integration |
236
258
  | `add-test` | Scaffold test file for a tool, resource, or service |
237
259
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
@@ -279,7 +301,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
279
301
  | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
280
302
  | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
281
303
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
282
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
304
+ | `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) |
283
305
  | `bun run list-skills` | Print the skill registry |
284
306
  | `bun run tree` | Generate directory structure doc |
285
307
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -303,7 +325,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
303
325
 
304
326
  ## Bundling
305
327
 
306
- `npm 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. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
328
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, `CLAUDE.md`, `AGENTS.md`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
307
329
 
308
330
  **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": ""`.
309
331
 
@@ -370,7 +392,10 @@ import { getMyService } from '@/services/my-domain/my-service.js';
370
392
  - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
371
393
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
372
394
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
395
+ - [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0 — read by Codex, Cursor, GitHub Copilot, VS Code, Kiro) populated — `plugin.json` `name` = the unscoped repo name (lowercase `a-z`, `0-9`, `-`, `.` only); `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords` from `package.json`; no other top-level field, `extensions` included. `mcp.json` holds one `"type": "stdio"` entry keyed by the unscoped name, `npx -y <full package name>`, and the same fixed `env` as `.codex-plugin/mcp.json`. Neither file carries a user-supplied variable (no `""`, no `${…}` besides `${PLUGIN_ROOT}`/`${PLUGIN_DATA}`): keys reach Codex through `.codex-plugin/mcp.json` `env_vars`, which works only while `.codex-plugin/plugin.json` keeps `"mcpServers": "./.codex-plugin/mcp.json"`
373
396
  - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
374
397
  - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
375
398
  - [ ] `.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 write `"KEY": ""` into `env`
399
+ - [ ] Any new root dev path has a root-anchored line in `.mcpbignore`; `bun run bundle` and `lint:packaging` pass
400
+ - [ ] If the server publishes a skill in root `skills/`: `lint:packaging` passes, and `field-test` verified every gate and identifier claim in it against live tool behavior
376
401
  - [ ] `npm run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** @cyanheads/interpro-mcp-server
4
- **Version:** 0.1.3
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.16`
4
+ **Version:** 0.2.0
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.4.0
8
8
  **Zod:** ^4.6.7
9
9
 
10
10
  > **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.
@@ -48,7 +48,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
48
48
 
49
49
  ### Tool
50
50
 
51
- Abridged from `src/mcp-server/tools/definitions/interpro-get-entry.tool.ts`. All six tools share this shape: a service call through `service.begin(ctx)`, `ctx.enrich` for notices, a per-tool `errors` contract, and the shared `formatInterpro` formatter.
51
+ Abridged from `src/mcp-server/tools/definitions/interpro-get-entry.tool.ts`. All nine tools share this shape: a service call through `service.begin(ctx)`, `ctx.enrich` for notices, a per-tool `errors` contract, and the shared `formatInterpro` formatter.
52
52
 
53
53
  ```ts
54
54
  import { tool, z } from '@cyanheads/mcp-ts-core';
@@ -96,7 +96,7 @@ export const interproGetEntry = tool('interpro_get_entry', {
96
96
  });
97
97
  ```
98
98
 
99
- There are no resources or prompts and no server-specific environment variables: the InterPro API is keyless. Search/member pages clamp positive integer limits to 100, discovery to 50, with default 20 and composed notices. GO filters require the InterPro source and accept either `goTerm` or `goCategory`, never both; member recovery follows `member_databases` or `integratedInto`. Entry `sections` selects named metadata while retaining core and unknown fields; explicit `overlapLimit` enables whole-record paging. Retained descriptions gain readable `descriptionText` through `htmlparser2`, alongside unchanged raw descriptions and annotation flags. Protein maps append requested residue rules after matches and extra features, preserving complete records and old cursor scopes when the new flag is absent/false. Discovery requests native fragment status on every page; optional `architectureId` adds native IDA grouping and explicit entry/taxon detail availability without inventing missing arrays. Bind only explicit selections to cursor scope and preserve unknown upstream values.
99
+ There are no resources or prompts. The InterPro API is keyless; the one server-specific environment variable, optional `INTERPRO_SCAN_EMAIL` (parsed in `src/config/server-config.ts`), is the contact address the EBI Job Dispatcher requires for InterProScan submissions. Unset, `interpro_scan_sequence` still serves precalculated matches and fails other sequences as `scan_not_configured`; the hosted instance leaves it unset. The scan tools (`interpro_scan_sequence`, `interpro_get_scan_result`) run through `src/services/sequence-scan/`, outside the InterPro pacer, and `interpro_scan_sequence` is the only tool that is not read-only. `interpro_get_structure_domains` lifts each match's nested chain records into top-level `structures[]` joined by `entry` to stay inside the depth-8 parity walker, and its numbering contract (chain `start`/`end` beside author `auth_start`/`auth_end`, UniProt positions) is retained verbatim. Optional `proteomeId` on discovery joins the route after `taxonId` and binds to the cursor scope only when present. Search/member pages clamp positive integer limits to 100, discovery to 50, with default 20 and composed notices. GO filters require the InterPro source and accept either `goTerm` or `goCategory`, never both; member recovery follows `member_databases` or `integratedInto`. Entry `sections` selects named metadata while retaining core and unknown fields; explicit `overlapLimit` enables whole-record paging. Retained descriptions gain readable `descriptionText` through `htmlparser2`, alongside unchanged raw descriptions and annotation flags. Protein maps append requested residue rules after matches and extra features, preserving complete records and old cursor scopes when the new flag is absent/false. Discovery requests native fragment status on every page; optional `architectureId` adds native IDA grouping and explicit entry/taxon detail availability without inventing missing arrays. Bind only explicit selections to cursor scope and preserve unknown upstream values.
100
100
 
101
101
  ### Server identity and instructions
102
102
 
@@ -106,9 +106,13 @@ There are no resources or prompts and no server-specific environment variables:
106
106
  await createApp({
107
107
  name: 'interpro-mcp-server',
108
108
  title: 'interpro-mcp-server',
109
- tools: [/* six definitions */],
109
+ tools: [/* nine definitions */],
110
110
  sessionMode: 'stateless',
111
- setup() { initInterproService(); },
111
+ skills: 'skills',
112
+ setup() {
113
+ initInterproService();
114
+ initSequenceScanService(getServerConfig().scanEmail);
115
+ },
112
116
  teardown() { disposeInterproService(); },
113
117
  instructions: '…',
114
118
  });
@@ -182,12 +186,15 @@ See framework CLAUDE.md and the `api-errors` skill for the full auto-classificat
182
186
  ```text
183
187
  src/
184
188
  index.ts # createApp() entry point
189
+ config/
190
+ server-config.ts # INTERPRO_SCAN_EMAIL parsing (lazy, validated at startup)
185
191
  services/
186
192
  interpro/
187
193
  interpro-service.ts # Paced, cancellable request boundary (init/accessor pattern)
188
- http-adapter.ts # node:https HTTP/1.1 transport with 204 handling
194
+ http-adapter.ts # node:https HTTP/1.1 transport with 204 handling; also carries the scan tools' Matches API and Job Dispatcher calls
189
195
  routing.ts # Accession → database routing, fixed-origin URL building
190
196
  schemas.ts # Wire and output Zod schemas
197
+ structure-schemas.ts # PDB ID and chain validation, structure and chain-record schemas
191
198
  cursors.ts # Opaque cursor envelopes
192
199
  budget.ts # 100,000-byte response budget and whole-item fitting
193
200
  relationships.ts # Clan relationship-graph slicing
@@ -195,9 +202,14 @@ src/
195
202
  format.ts # Shared content[] formatter
196
203
  description-text.ts # Readable HTML fragment projection for entry detail
197
204
  upstream-errors.ts # Shared upstream error contract rows
205
+ sequence-scan/
206
+ sequence.ts # Sequence parsing and the UniParc MD5 digest
207
+ scan-schemas.ts # Matches API, Job Dispatcher, and match-record schemas
208
+ sequence-scan-service.ts # Precalculated lookup, job submission and polling, in-flight cap
209
+ scan-output.ts # Shared scan output schema and match segmentation
198
210
  mcp-server/
199
211
  tools/definitions/
200
- interpro-[name].tool.ts # Six tool definitions
212
+ interpro-[name].tool.ts # Nine tool definitions
201
213
  tests/
202
214
  services/ # Service, adapter, and budget tests
203
215
  tools/ # Tool contract tests
@@ -218,9 +230,18 @@ tests/
218
230
 
219
231
  ## Skills
220
232
 
221
- 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/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
233
+ Two skill trees live at the project root, written for different readers:
234
+
235
+ | Tree | For | Written by |
236
+ |:-----|:----|:-----------|
237
+ | `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |
238
+ | `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. Absent by default; usually none is needed |
239
+
240
+ 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/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand them to every agent that installs it.
241
+
242
+ A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree. This server publishes one, `skills/interpro-guide/SKILL.md` (identifier chains, gates, and chain/author/UniProt numbering), served with `skills: 'skills'`.
222
243
 
223
- **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.
244
+ **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.
224
245
 
225
246
  Available skills:
226
247
 
@@ -232,6 +253,7 @@ Available skills:
232
253
  | `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
233
254
  | `add-resource` | Scaffold a new resource definition |
234
255
  | `add-prompt` | Scaffold a new prompt definition |
256
+ | `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 |
235
257
  | `add-service` | Scaffold a new service integration |
236
258
  | `add-test` | Scaffold test file for a tool, resource, or service |
237
259
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
@@ -279,7 +301,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
279
301
  | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
280
302
  | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
281
303
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
282
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
304
+ | `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) |
283
305
  | `bun run list-skills` | Print the skill registry |
284
306
  | `bun run tree` | Generate directory structure doc |
285
307
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -303,7 +325,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
303
325
 
304
326
  ## Bundling
305
327
 
306
- `npm 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. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
328
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, `CLAUDE.md`, `AGENTS.md`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
307
329
 
308
330
  **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": ""`.
309
331
 
@@ -370,7 +392,10 @@ import { getMyService } from '@/services/my-domain/my-service.js';
370
392
  - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
371
393
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
372
394
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
395
+ - [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0 — read by Codex, Cursor, GitHub Copilot, VS Code, Kiro) populated — `plugin.json` `name` = the unscoped repo name (lowercase `a-z`, `0-9`, `-`, `.` only); `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords` from `package.json`; no other top-level field, `extensions` included. `mcp.json` holds one `"type": "stdio"` entry keyed by the unscoped name, `npx -y <full package name>`, and the same fixed `env` as `.codex-plugin/mcp.json`. Neither file carries a user-supplied variable (no `""`, no `${…}` besides `${PLUGIN_ROOT}`/`${PLUGIN_DATA}`): keys reach Codex through `.codex-plugin/mcp.json` `env_vars`, which works only while `.codex-plugin/plugin.json` keeps `"mcpServers": "./.codex-plugin/mcp.json"`
373
396
  - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
374
397
  - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
375
398
  - [ ] `.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 write `"KEY": ""` into `env`
399
+ - [ ] Any new root dev path has a root-anchored line in `.mcpbignore`; `bun run bundle` and `lint:packaging` pass
400
+ - [ ] If the server publishes a skill in root `skills/`: `lint:packaging` passes, and `field-test` verified every gate and identifier claim in it against live tool behavior
376
401
  - [ ] `npm run devcheck` passes
package/Dockerfile CHANGED
@@ -29,6 +29,11 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
29
29
  # Copy the rest of the source code
30
30
  COPY . .
31
31
 
32
+ # The skills a server publishes (`createApp({ skills: 'skills' })`) are read at
33
+ # runtime from the application root. Created when the project has none, so the
34
+ # production stage's copy below holds with or without one.
35
+ RUN mkdir -p skills
36
+
32
37
  # Build the application
33
38
  RUN bun run build
34
39
 
@@ -124,7 +129,7 @@ ENV NODE_ENV=production
124
129
  # OCI image metadata (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
125
130
  ARG APP_VERSION
126
131
  LABEL org.opencontainers.image.title="interpro-mcp-server"
127
- LABEL org.opencontainers.image.description="Search InterPro entries, map protein domains and functional sites, and find proteins by domain via MCP. STDIO or Streamable HTTP."
132
+ LABEL org.opencontainers.image.description="Search InterPro entries, map domains and functional sites onto UniProt proteins and PDB structures, find proteins by domain, and scan raw protein sequences via MCP. STDIO or Streamable HTTP."
128
133
  LABEL org.opencontainers.image.licenses="Apache-2.0"
129
134
  LABEL org.opencontainers.image.version="${APP_VERSION}"
130
135
  LABEL org.opencontainers.image.source="https://github.com/cyanheads/interpro-mcp-server"
@@ -136,6 +141,7 @@ COPY --from=deps /usr/src/app/node_modules ./node_modules
136
141
 
137
142
  # Copy the compiled application code from the build stage
138
143
  COPY --from=build /usr/src/app/dist ./dist
144
+ COPY --from=build /usr/src/app/skills ./skills
139
145
 
140
146
  # Mirror CLI (MirrorService adopters only — Tier 3, opt-in):
141
147
  # Copy your mirror lifecycle scripts and emit a runtime tsconfig so Bun resolves
@@ -175,6 +181,8 @@ ENV MCP_TRANSPORT_TYPE="http"
175
181
  ENV MCP_SESSION_MODE="stateless"
176
182
  ENV MCP_LOG_LEVEL="info"
177
183
  ENV LOGS_DIR="/var/log/interpro-mcp-server"
184
+ # Optional, no default: set INTERPRO_SCAN_EMAIL at run time (docker run -e) to let
185
+ # interpro_scan_sequence submit InterProScan jobs; unset, it serves precalculated matches only.
178
186
 
179
187
  # Expose the port the server listens on
180
188
  EXPOSE ${MCP_HTTP_PORT}