@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.
- package/AGENTS.md +38 -13
- package/CLAUDE.md +38 -13
- package/Dockerfile +9 -1
- package/README.md +82 -123
- package/changelog/0.2.x/0.2.0.md +31 -0
- package/dist/config/server-config.d.ts +13 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +19 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.d.ts +45 -0
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.js +37 -14
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.d.ts +133 -0
- package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.js +128 -0
- package/dist/mcp-server/tools/definitions/interpro-get-scan-result.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.d.ts +171 -0
- package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.js +290 -0
- package/dist/mcp-server/tools/definitions/interpro-get-structure-domains.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.d.ts +168 -0
- package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.js +187 -0
- package/dist/mcp-server/tools/definitions/interpro-scan-sequence.tool.js.map +1 -0
- package/dist/services/interpro/http-adapter.d.ts +11 -2
- package/dist/services/interpro/http-adapter.d.ts.map +1 -1
- package/dist/services/interpro/http-adapter.js +67 -9
- package/dist/services/interpro/http-adapter.js.map +1 -1
- package/dist/services/interpro/interpro-service.d.ts +1 -1
- package/dist/services/interpro/interpro-service.d.ts.map +1 -1
- package/dist/services/interpro/interpro-service.js +6 -24
- package/dist/services/interpro/interpro-service.js.map +1 -1
- package/dist/services/interpro/reference.d.ts.map +1 -1
- package/dist/services/interpro/reference.js +23 -2
- package/dist/services/interpro/reference.js.map +1 -1
- package/dist/services/interpro/schemas.d.ts +1 -0
- package/dist/services/interpro/schemas.d.ts.map +1 -1
- package/dist/services/interpro/schemas.js +5 -0
- package/dist/services/interpro/schemas.js.map +1 -1
- package/dist/services/interpro/structure-schemas.d.ts +116 -0
- package/dist/services/interpro/structure-schemas.d.ts.map +1 -0
- package/dist/services/interpro/structure-schemas.js +74 -0
- package/dist/services/interpro/structure-schemas.js.map +1 -0
- package/dist/services/sequence-scan/scan-output.d.ts +94 -0
- package/dist/services/sequence-scan/scan-output.d.ts.map +1 -0
- package/dist/services/sequence-scan/scan-output.js +174 -0
- package/dist/services/sequence-scan/scan-output.js.map +1 -0
- package/dist/services/sequence-scan/scan-schemas.d.ts +112 -0
- package/dist/services/sequence-scan/scan-schemas.d.ts.map +1 -0
- package/dist/services/sequence-scan/scan-schemas.js +97 -0
- package/dist/services/sequence-scan/scan-schemas.js.map +1 -0
- package/dist/services/sequence-scan/sequence-scan-service.d.ts +119 -0
- package/dist/services/sequence-scan/sequence-scan-service.d.ts.map +1 -0
- package/dist/services/sequence-scan/sequence-scan-service.js +453 -0
- package/dist/services/sequence-scan/sequence-scan-service.js.map +1 -0
- package/dist/services/sequence-scan/sequence.d.ts +32 -0
- package/dist/services/sequence-scan/sequence.d.ts.map +1 -0
- package/dist/services/sequence-scan/sequence.js +63 -0
- package/dist/services/sequence-scan/sequence.js.map +1 -0
- package/package.json +10 -7
- package/server.json +18 -4
- package/skills/interpro-guide/SKILL.md +72 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { configurationError, McpError, notFound, rateLimited, serviceUnavailable, timeout, validationError, } from '@cyanheads/mcp-ts-core/errors';
|
|
2
|
+
import { withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
3
|
+
import { createInterproHttpFetch, MAX_RESPONSE_BYTES, upstreamStatusError, } from '../interpro/http-adapter.js';
|
|
4
|
+
import { JOB_ID_PATTERN, JobStatusSchema, MAX_JOB_ID_CHARS, MatchesLookupSchema, ScanResultSchema, } from './scan-schemas.js';
|
|
5
|
+
/** One budget per tool call, below the 60-second default MCP client request timeout. */
|
|
6
|
+
export const CALL_BUDGET_MS = 50_000;
|
|
7
|
+
/**
|
|
8
|
+
* Most one attempt of each request may take, further bounded by what remains of the call budget.
|
|
9
|
+
* Bodies get the longest bound: EBI throughput varies, and a 0.7 MB lookup or a 1.5 MB result
|
|
10
|
+
* has taken 13-15 seconds, where a retry would only restart the same download.
|
|
11
|
+
*/
|
|
12
|
+
const ATTEMPT_MS = { status: 15_000, run: 20_000, body: 40_000 };
|
|
13
|
+
/** Minimum spacing between `status` polls of one job within a call. */
|
|
14
|
+
export const POLL_INTERVAL_MS = 5_000;
|
|
15
|
+
/** Call budget kept back from polling so a FINISHED job's result can still be read. */
|
|
16
|
+
const RESULT_RESERVE_MS = 15_000;
|
|
17
|
+
/** Longest wait window a call can use: the budget less the result reserve. */
|
|
18
|
+
export const MAX_WAIT_SECONDS = (CALL_BUDGET_MS - RESULT_RESERVE_MS) / 1000;
|
|
19
|
+
/** Job Dispatcher fair use asks for at most 30 jobs in flight per user; one address is one user. */
|
|
20
|
+
export const MAX_IN_FLIGHT = 25;
|
|
21
|
+
/** A tracked job nobody observed finishing frees its slot after this long (runs take ~10 min). */
|
|
22
|
+
export const IN_FLIGHT_TTL_MS = 20 * 60_000;
|
|
23
|
+
export const MATCHES_URL = 'https://www.ebi.ac.uk/interpro/matches/api/matches';
|
|
24
|
+
export const JOB_DISPATCHER_BASE = 'https://www.ebi.ac.uk/Tools/services/rest/iprscan6/';
|
|
25
|
+
const now = () => performance.now();
|
|
26
|
+
const deadlineError = () => timeout('InterProScan call exhausted its 50-second budget.', {
|
|
27
|
+
reason: 'retry_deadline_exceeded',
|
|
28
|
+
retryable: true,
|
|
29
|
+
deadlineMs: CALL_BUDGET_MS,
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Resolve with `promise`, or reject with the caller's abort reason or the call's deadline error,
|
|
33
|
+
* whichever comes first. Uses the global timer so fake timers drive it in tests.
|
|
34
|
+
*/
|
|
35
|
+
function bounded(promise, signal, deadline) {
|
|
36
|
+
signal.throwIfAborted();
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const done = () => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
signal.removeEventListener('abort', abort);
|
|
41
|
+
};
|
|
42
|
+
const abort = () => {
|
|
43
|
+
done();
|
|
44
|
+
reject(signal.reason);
|
|
45
|
+
};
|
|
46
|
+
const timer = setTimeout(() => {
|
|
47
|
+
done();
|
|
48
|
+
reject(deadlineError());
|
|
49
|
+
}, Math.max(0, deadline - now()));
|
|
50
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
51
|
+
promise.then((value) => {
|
|
52
|
+
done();
|
|
53
|
+
resolve(value);
|
|
54
|
+
}, (error) => {
|
|
55
|
+
done();
|
|
56
|
+
reject(error);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Wait `ms`, rejecting at once with the caller's reason when the signal aborts. */
|
|
61
|
+
function delay(ms, signal) {
|
|
62
|
+
signal.throwIfAborted();
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const abort = () => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
reject(signal.reason);
|
|
67
|
+
};
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
signal.removeEventListener('abort', abort);
|
|
70
|
+
resolve();
|
|
71
|
+
}, Math.max(0, ms));
|
|
72
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async function readText(response, signal) {
|
|
76
|
+
if (!response.body)
|
|
77
|
+
return '';
|
|
78
|
+
const reader = response.body.getReader();
|
|
79
|
+
const chunks = [];
|
|
80
|
+
let bytes = 0;
|
|
81
|
+
try {
|
|
82
|
+
while (true) {
|
|
83
|
+
signal.throwIfAborted();
|
|
84
|
+
const { done, value } = await reader.read();
|
|
85
|
+
if (done)
|
|
86
|
+
break;
|
|
87
|
+
bytes += value.byteLength;
|
|
88
|
+
if (bytes > MAX_RESPONSE_BYTES) {
|
|
89
|
+
await reader.cancel();
|
|
90
|
+
throw serviceUnavailable('InterProScan response exceeds 4 MiB.', {
|
|
91
|
+
reason: 'upstream_response_too_large',
|
|
92
|
+
retryable: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
chunks.push(value);
|
|
96
|
+
}
|
|
97
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
reader.releaseLock();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function parseJson(body) {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(body);
|
|
106
|
+
}
|
|
107
|
+
catch (cause) {
|
|
108
|
+
throw serviceUnavailable('InterProScan service returned malformed JSON.', { reason: 'upstream_shape_invalid', retryable: false }, { cause });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const shapeInvalid = (what) => serviceUnavailable(`${what} does not match the required response structure.`, {
|
|
112
|
+
reason: 'upstream_shape_invalid',
|
|
113
|
+
retryable: false,
|
|
114
|
+
});
|
|
115
|
+
/** The native transport, its backstop no tighter than the per-operation attempt bounds. */
|
|
116
|
+
const scanHttpFetch = createInterproHttpFetch(CALL_BUDGET_MS);
|
|
117
|
+
/** Lookup, submission, polling, and result reads for one configured submission address. */
|
|
118
|
+
export class SequenceScanService {
|
|
119
|
+
email;
|
|
120
|
+
fetch;
|
|
121
|
+
inFlight = new Map();
|
|
122
|
+
constructor(email, fetch = scanHttpFetch) {
|
|
123
|
+
this.email = email;
|
|
124
|
+
this.fetch = fetch;
|
|
125
|
+
}
|
|
126
|
+
/** Start the call's single deadline. */
|
|
127
|
+
begin(ctx) {
|
|
128
|
+
const started = now();
|
|
129
|
+
return { ctx, started, deadline: started + CALL_BUDGET_MS };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Look the MD5 up in the Matches API; on a miss, submit (or join the submission already in
|
|
133
|
+
* flight for this MD5) and wait up to `waitMs`. `lookupOnly` resumes a precalculated walk and
|
|
134
|
+
* never submits.
|
|
135
|
+
*/
|
|
136
|
+
async scan(call, sequence, waitMs, lookupOnly = false) {
|
|
137
|
+
const hit = await this.lookup(call, sequence.md5);
|
|
138
|
+
if (hit)
|
|
139
|
+
return { source: 'precalculated', matches: hit, sourceUrl: MATCHES_URL };
|
|
140
|
+
if (lookupOnly)
|
|
141
|
+
throw validationError('The precalculated result this cursor continues is no longer available.', { reason: 'invalid_cursor', retryable: false });
|
|
142
|
+
const { jobId, submittedAt } = await this.submit(call, sequence);
|
|
143
|
+
const state = await this.withJob(jobId, () => this.awaitJob(call, jobId, submittedAt === undefined ? now() : submittedAt + POLL_INTERVAL_MS, waitMs));
|
|
144
|
+
if (state.status === 'FINISHED' && state.result.md5 !== sequence.md5)
|
|
145
|
+
throw shapeInvalid('InterProScan result for a different sequence');
|
|
146
|
+
return { source: 'interproscan', jobId, sourceUrl: this.jobUrl(jobId, state.status), ...state };
|
|
147
|
+
}
|
|
148
|
+
/** Read a job's state, waiting up to `waitMs` for it to finish; polls at least once. */
|
|
149
|
+
async result(call, jobId, waitMs) {
|
|
150
|
+
const state = await this.awaitJob(call, jobId, call.started, waitMs);
|
|
151
|
+
return { ...state, sourceUrl: this.jobUrl(jobId, state.status) };
|
|
152
|
+
}
|
|
153
|
+
jobUrl(jobId, status) {
|
|
154
|
+
return status === 'FINISHED'
|
|
155
|
+
? `${JOB_DISPATCHER_BASE}result/${jobId}/json`
|
|
156
|
+
: `${JOB_DISPATCHER_BASE}status/${jobId}`;
|
|
157
|
+
}
|
|
158
|
+
/** Name the in-flight job in any failure after submission, so the caller keeps its handle. */
|
|
159
|
+
async withJob(jobId, work) {
|
|
160
|
+
try {
|
|
161
|
+
return await work();
|
|
162
|
+
}
|
|
163
|
+
catch (cause) {
|
|
164
|
+
if (!(cause instanceof McpError) || cause.data?.jobId)
|
|
165
|
+
throw cause;
|
|
166
|
+
throw new McpError(cause.code, `${cause.message} InterProScan job ${jobId} was submitted; read it with interpro_get_scan_result.`, { ...cause.data, jobId }, { cause });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Precalculated matches, or undefined when UniParc holds no result for the MD5. */
|
|
170
|
+
async lookup(call, md5) {
|
|
171
|
+
const parsed = await this.send(call, call.ctx.signal, 'interpro.matches.lookup', MATCHES_URL, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
body: JSON.stringify({ md5: [md5] }),
|
|
174
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
175
|
+
}, 'body', async (response, signal) => {
|
|
176
|
+
if (!response.ok)
|
|
177
|
+
throw await upstreamStatusError(response, 'InterPro Matches API');
|
|
178
|
+
const result = MatchesLookupSchema.safeParse(parseJson(await readText(response, signal)));
|
|
179
|
+
if (!result.success)
|
|
180
|
+
throw shapeInvalid('InterPro Matches API JSON');
|
|
181
|
+
return result.data;
|
|
182
|
+
});
|
|
183
|
+
const [entry] = parsed.results;
|
|
184
|
+
if (entry?.md5.toUpperCase() !== md5)
|
|
185
|
+
throw shapeInvalid('InterPro Matches API result');
|
|
186
|
+
return entry.found ? entry.matches : undefined;
|
|
187
|
+
}
|
|
188
|
+
/** Expire tracked jobs nobody saw finish, so the map stays bounded without a timer. */
|
|
189
|
+
sweep() {
|
|
190
|
+
const cutoff = now() - IN_FLIGHT_TTL_MS;
|
|
191
|
+
for (const [md5, slot] of this.inFlight)
|
|
192
|
+
if (slot.kind === 'job' && slot.since < cutoff)
|
|
193
|
+
this.inFlight.delete(md5);
|
|
194
|
+
}
|
|
195
|
+
/** Free the slot of a job observed in a terminal state. */
|
|
196
|
+
release(jobId) {
|
|
197
|
+
for (const [md5, slot] of this.inFlight)
|
|
198
|
+
if (slot.kind === 'job' && slot.jobId === jobId)
|
|
199
|
+
this.inFlight.delete(md5);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The job for this MD5: one already tracked, one being submitted by another call, or a new
|
|
203
|
+
* submission. `submittedAt` is set only when this call's own `run` (or one it joined) made it.
|
|
204
|
+
*/
|
|
205
|
+
async submit(call, sequence) {
|
|
206
|
+
this.sweep();
|
|
207
|
+
const existing = this.inFlight.get(sequence.md5);
|
|
208
|
+
if (existing?.kind === 'job')
|
|
209
|
+
return { jobId: existing.jobId };
|
|
210
|
+
if (existing)
|
|
211
|
+
return { jobId: await this.join(existing, call), submittedAt: existing.since };
|
|
212
|
+
const { email } = this;
|
|
213
|
+
if (!email)
|
|
214
|
+
throw configurationError('This sequence has no precalculated matches, and this server is not configured to submit InterProScan jobs.', { reason: 'scan_not_configured', retryable: false });
|
|
215
|
+
if (this.inFlight.size >= MAX_IN_FLIGHT) {
|
|
216
|
+
const frees = Math.min(...[...this.inFlight.values()].map((slot) => slot.kind === 'job' ? slot.since + IN_FLIGHT_TTL_MS : slot.since + ATTEMPT_MS.run));
|
|
217
|
+
throw rateLimited(`This server already has ${MAX_IN_FLIGHT} InterProScan jobs in flight, the most it keeps under the Job Dispatcher fair-use limit.`, {
|
|
218
|
+
reason: 'rate_limited',
|
|
219
|
+
retryable: true,
|
|
220
|
+
retryAfter: Math.max(1, Math.ceil((frees - now()) / 1000)),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const slot = {
|
|
224
|
+
kind: 'submitting',
|
|
225
|
+
md5: sequence.md5,
|
|
226
|
+
since: now(),
|
|
227
|
+
controller: new AbortController(),
|
|
228
|
+
outcome: Promise.resolve({ ok: false, error: undefined }),
|
|
229
|
+
waiters: 0,
|
|
230
|
+
settled: false,
|
|
231
|
+
};
|
|
232
|
+
this.inFlight.set(sequence.md5, slot);
|
|
233
|
+
slot.outcome = (async () => {
|
|
234
|
+
try {
|
|
235
|
+
const jobId = await this.run(call, slot.controller.signal, email, sequence);
|
|
236
|
+
const current = this.inFlight.get(sequence.md5);
|
|
237
|
+
if (current === slot || current === undefined)
|
|
238
|
+
this.inFlight.set(sequence.md5, { kind: 'job', since: now(), jobId });
|
|
239
|
+
return { ok: true, value: jobId };
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (this.inFlight.get(sequence.md5) === slot)
|
|
243
|
+
this.inFlight.delete(sequence.md5);
|
|
244
|
+
return { ok: false, error };
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
slot.settled = true;
|
|
248
|
+
}
|
|
249
|
+
})();
|
|
250
|
+
return { jobId: await this.join(slot, call), submittedAt: slot.since };
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Await a shared submission. A caller leaving early detaches only itself; the request is
|
|
254
|
+
* aborted when the last waiting caller leaves before it settles.
|
|
255
|
+
*/
|
|
256
|
+
async join(slot, call) {
|
|
257
|
+
slot.waiters++;
|
|
258
|
+
try {
|
|
259
|
+
const outcome = await bounded(slot.outcome, call.ctx.signal, call.deadline);
|
|
260
|
+
if (!outcome.ok)
|
|
261
|
+
throw outcome.error;
|
|
262
|
+
return outcome.value;
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
slot.waiters--;
|
|
266
|
+
if (!slot.waiters && !slot.settled) {
|
|
267
|
+
// Abandoned: a later call for this MD5 must start afresh, not inherit this abort.
|
|
268
|
+
if (this.inFlight.get(slot.md5) === slot)
|
|
269
|
+
this.inFlight.delete(slot.md5);
|
|
270
|
+
slot.controller.abort(call.ctx.signal.aborted ? call.ctx.signal.reason : deadlineError());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** One `run` request, never retried: a lost response may still have created a job. */
|
|
275
|
+
async run(call, signal, email, sequence) {
|
|
276
|
+
const body = new URLSearchParams({
|
|
277
|
+
email,
|
|
278
|
+
sequence: `>query\n${sequence.residues}`,
|
|
279
|
+
stype: 'p',
|
|
280
|
+
goterms: 'false',
|
|
281
|
+
pathways: 'false',
|
|
282
|
+
}).toString();
|
|
283
|
+
const jobId = await this.send(call, signal, 'interpro.scan.run', `${JOB_DISPATCHER_BASE}run`, {
|
|
284
|
+
method: 'POST',
|
|
285
|
+
body,
|
|
286
|
+
headers: { Accept: 'text/plain', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
287
|
+
}, 'run', async (response, attemptSignal) => {
|
|
288
|
+
const text = await readText(response, attemptSignal);
|
|
289
|
+
if (response.status === 400) {
|
|
290
|
+
const description = this.describeRejection(text);
|
|
291
|
+
throw serviceUnavailable(`Job Dispatcher rejected the InterProScan submission${description ? `: ${description}` : '.'}`, { reason: 'scan_rejected', retryable: false, status: 400 });
|
|
292
|
+
}
|
|
293
|
+
if (!response.ok)
|
|
294
|
+
throw await upstreamStatusError(response, 'Job Dispatcher');
|
|
295
|
+
const id = text.trim();
|
|
296
|
+
if (id.length > MAX_JOB_ID_CHARS || !JOB_ID_PATTERN.test(id))
|
|
297
|
+
throw shapeInvalid('Job Dispatcher job ID');
|
|
298
|
+
return id;
|
|
299
|
+
});
|
|
300
|
+
call.ctx.log.info('InterProScan job submitted', { md5: sequence.md5, jobId });
|
|
301
|
+
return jobId;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The `<description>` of a Job Dispatcher XML error, single-line, bounded, and with the
|
|
305
|
+
* configured address replaced, since rejections can echo submitted fields.
|
|
306
|
+
*/
|
|
307
|
+
describeRejection(xml) {
|
|
308
|
+
const match = /<description>([^<]{1,500})<\/description>/.exec(xml.slice(0, 4096));
|
|
309
|
+
if (!match?.[1])
|
|
310
|
+
return undefined;
|
|
311
|
+
let text = match[1].replace(/[\p{Cc}\s]+/gu, ' ').trim();
|
|
312
|
+
if (this.email)
|
|
313
|
+
text = text.replace(new RegExp(this.email.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'), '[address]');
|
|
314
|
+
return text.slice(0, 300) || undefined;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Poll `status` from `firstPollAt`, at most every five seconds, until a terminal status or
|
|
318
|
+
* the window closes, then read the result of a FINISHED job. The window is `waitMs` from the
|
|
319
|
+
* call's start, capped so the result read keeps its reserve of the deadline.
|
|
320
|
+
*/
|
|
321
|
+
async awaitJob(call, jobId, firstPollAt, waitMs) {
|
|
322
|
+
const windowEnd = Math.min(call.started + waitMs, call.deadline - RESULT_RESERVE_MS);
|
|
323
|
+
let status;
|
|
324
|
+
for (let next = firstPollAt; next <= windowEnd; next = now() + POLL_INTERVAL_MS) {
|
|
325
|
+
await delay(next - now(), call.ctx.signal);
|
|
326
|
+
status = await this.status(call, jobId);
|
|
327
|
+
if (status !== 'QUEUED' && status !== 'RUNNING')
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
switch (status) {
|
|
331
|
+
case undefined:
|
|
332
|
+
return { status: 'submitted' };
|
|
333
|
+
case 'QUEUED':
|
|
334
|
+
case 'RUNNING':
|
|
335
|
+
return { status };
|
|
336
|
+
case 'FINISHED': {
|
|
337
|
+
// Released only once read, so a retried scan after a failed read rejoins this job.
|
|
338
|
+
const result = await this.read(call, jobId);
|
|
339
|
+
this.release(jobId);
|
|
340
|
+
return { status, result };
|
|
341
|
+
}
|
|
342
|
+
case 'NOT_FOUND':
|
|
343
|
+
this.release(jobId);
|
|
344
|
+
throw notFound(`Job Dispatcher has no InterProScan job ${jobId}; it never existed or expired after seven days.`, { reason: 'scan_not_found', retryable: false, jobId });
|
|
345
|
+
default:
|
|
346
|
+
this.release(jobId);
|
|
347
|
+
throw serviceUnavailable(`InterProScan job ${jobId} ended with status ${status}.`, {
|
|
348
|
+
reason: 'scan_failed',
|
|
349
|
+
retryable: false,
|
|
350
|
+
jobId,
|
|
351
|
+
status,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async status(call, jobId) {
|
|
356
|
+
return await this.send(call, call.ctx.signal, 'interpro.scan.status', `${JOB_DISPATCHER_BASE}status/${jobId}`, { headers: { Accept: 'text/plain' } }, 'status', async (response, signal) => {
|
|
357
|
+
if (!response.ok)
|
|
358
|
+
throw await upstreamStatusError(response, 'Job Dispatcher');
|
|
359
|
+
const parsed = JobStatusSchema.safeParse((await readText(response, signal)).trim());
|
|
360
|
+
if (!parsed.success)
|
|
361
|
+
throw shapeInvalid('Job Dispatcher status');
|
|
362
|
+
return parsed.data;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
async read(call, jobId) {
|
|
366
|
+
return await this.send(call, call.ctx.signal, 'interpro.scan.result', `${JOB_DISPATCHER_BASE}result/${jobId}/json`, { headers: { Accept: 'application/json' } }, 'body', async (response, signal) => {
|
|
367
|
+
if (response.status === 400 || response.status === 404) {
|
|
368
|
+
this.release(jobId);
|
|
369
|
+
throw notFound(`Job Dispatcher has no result for InterProScan job ${jobId}.`, {
|
|
370
|
+
reason: 'scan_not_found',
|
|
371
|
+
retryable: false,
|
|
372
|
+
jobId,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
if (!response.ok)
|
|
376
|
+
throw await upstreamStatusError(response, 'Job Dispatcher');
|
|
377
|
+
const parsed = ScanResultSchema.safeParse(parseJson(await readText(response, signal)));
|
|
378
|
+
if (!parsed.success)
|
|
379
|
+
throw shapeInvalid('InterProScan result JSON');
|
|
380
|
+
const [result] = parsed.data.results;
|
|
381
|
+
return {
|
|
382
|
+
md5: result.md5.toUpperCase(),
|
|
383
|
+
length: result.sequence.length,
|
|
384
|
+
interproVersion: parsed.data['interpro-version'],
|
|
385
|
+
interproscanVersion: parsed.data['interproscan-version'],
|
|
386
|
+
matches: result.matches,
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* One fixed-origin request under the call deadline. Each attempt is bounded by the smaller of
|
|
392
|
+
* the operation's attempt bound and what remains. Reads get one more attempt on a transient
|
|
393
|
+
* failure; a `run` never does.
|
|
394
|
+
*/
|
|
395
|
+
async send(call, signal, operation, url, init, kind, read) {
|
|
396
|
+
signal.throwIfAborted();
|
|
397
|
+
const remaining = call.deadline - now();
|
|
398
|
+
if (remaining <= 0)
|
|
399
|
+
throw deadlineError();
|
|
400
|
+
return await withRetry(async ({ signal: retrySignal }) => {
|
|
401
|
+
const controller = new AbortController();
|
|
402
|
+
const timer = setTimeout(() => controller.abort(timeout('InterProScan attempt deadline elapsed.', {
|
|
403
|
+
reason: 'upstream_timeout',
|
|
404
|
+
retryable: true,
|
|
405
|
+
})), Math.max(0, Math.min(ATTEMPT_MS[kind], call.deadline - now())));
|
|
406
|
+
const attemptSignal = AbortSignal.any([retrySignal, controller.signal, signal]);
|
|
407
|
+
try {
|
|
408
|
+
const response = await this.fetch(url, {
|
|
409
|
+
...init,
|
|
410
|
+
signal: attemptSignal,
|
|
411
|
+
redirect: 'manual',
|
|
412
|
+
});
|
|
413
|
+
const value = await read(response, attemptSignal);
|
|
414
|
+
attemptSignal.throwIfAborted();
|
|
415
|
+
return value;
|
|
416
|
+
}
|
|
417
|
+
catch (cause) {
|
|
418
|
+
if (signal.aborted)
|
|
419
|
+
throw signal.reason;
|
|
420
|
+
if (retrySignal.aborted)
|
|
421
|
+
throw retrySignal.reason;
|
|
422
|
+
if (controller.signal.aborted)
|
|
423
|
+
throw controller.signal.reason;
|
|
424
|
+
if (cause instanceof McpError)
|
|
425
|
+
throw cause;
|
|
426
|
+
throw serviceUnavailable('InterProScan network request failed.', { reason: 'upstream_unavailable', retryable: true }, { cause });
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
clearTimeout(timer);
|
|
430
|
+
}
|
|
431
|
+
}, {
|
|
432
|
+
signal,
|
|
433
|
+
context: call.ctx,
|
|
434
|
+
operation,
|
|
435
|
+
deadlineMs: remaining,
|
|
436
|
+
maxRetries: kind === 'run' ? 0 : 1,
|
|
437
|
+
baseDelayMs: 1000,
|
|
438
|
+
maxDelayMs: 3000,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
let service;
|
|
443
|
+
/** Initialize the process service in createApp.setup with the configured submission address. */
|
|
444
|
+
export function initSequenceScanService(email, fetch = scanHttpFetch) {
|
|
445
|
+
service = new SequenceScanService(email, fetch);
|
|
446
|
+
}
|
|
447
|
+
/** Obtain the initialized process service. */
|
|
448
|
+
export function getSequenceScanService() {
|
|
449
|
+
if (!service)
|
|
450
|
+
throw new Error('Sequence scan service is not initialized.');
|
|
451
|
+
return service;
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=sequence-scan-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequence-scan-service.js","sourceRoot":"","sources":["../../../src/services/sequence-scan/sequence-scan-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Most characters one sequence argument may hold, FASTA header and line breaks included. */
|
|
2
|
+
export declare const MAX_SEQUENCE_CHARS = 100000;
|
|
3
|
+
/** A parsed sequence, or the reason it cannot be scanned. */
|
|
4
|
+
export type ParsedSequence = {
|
|
5
|
+
readonly ok: true;
|
|
6
|
+
readonly residues: string;
|
|
7
|
+
readonly md5: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly ok: false;
|
|
10
|
+
readonly reason: 'empty_sequence';
|
|
11
|
+
} | {
|
|
12
|
+
readonly ok: false;
|
|
13
|
+
readonly reason: 'multiple_sequences';
|
|
14
|
+
readonly position: number;
|
|
15
|
+
} | {
|
|
16
|
+
readonly ok: false;
|
|
17
|
+
readonly reason: 'invalid_residue';
|
|
18
|
+
readonly position: number;
|
|
19
|
+
readonly codePoint: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Read one protein sequence: an optional byte-order mark as the very first character, optional
|
|
23
|
+
* leading whitespace, an optional single FASTA header line, then IUPAC residue letters (all 26,
|
|
24
|
+
* case-insensitive) separated by any spaces, tabs, or line breaks. A `>` past the header starts a
|
|
25
|
+
* second record; a byte-order mark anywhere else is an invalid residue. Each character is visited
|
|
26
|
+
* once, so the cost is linear in the input. Positions are 1-based character offsets into the
|
|
27
|
+
* argument, the skipped mark included.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseSequence(text: string): ParsedSequence;
|
|
30
|
+
/** A code point as `U+XXXX`, so an invalid character is named without echoing it. */
|
|
31
|
+
export declare const codePointLabel: (codePoint: number) => string;
|
|
32
|
+
//# sourceMappingURL=sequence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../../../src/services/sequence-scan/sequence.ts"],"names":[],"mappings":"AAMA,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,SAAU,CAAC;AAE1C,6DAA6D;AAC7D,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACxF;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAWN;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CA8B1D;AAED,qFAAqF;AACrF,eAAO,MAAM,cAAc,cAAe,MAAM,WACc,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview One-pass protein sequence parsing and the UniParc-compatible MD5 digest.
|
|
3
|
+
* @module services/sequence-scan/sequence
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
/** Most characters one sequence argument may hold, FASTA header and line breaks included. */
|
|
7
|
+
export const MAX_SEQUENCE_CHARS = 100_000;
|
|
8
|
+
const CR = 0x0d;
|
|
9
|
+
const LF = 0x0a;
|
|
10
|
+
const GT = 0x3e;
|
|
11
|
+
/** U+FEFF, the byte-order mark a text file saved as UTF-8 with BOM opens with. */
|
|
12
|
+
const BOM = 0xfeff;
|
|
13
|
+
const isWhitespace = (code) => code === 0x20 || code === 0x09 || code === CR || code === LF;
|
|
14
|
+
const isUpper = (code) => code >= 0x41 && code <= 0x5a;
|
|
15
|
+
const isLower = (code) => code >= 0x61 && code <= 0x7a;
|
|
16
|
+
/**
|
|
17
|
+
* Read one protein sequence: an optional byte-order mark as the very first character, optional
|
|
18
|
+
* leading whitespace, an optional single FASTA header line, then IUPAC residue letters (all 26,
|
|
19
|
+
* case-insensitive) separated by any spaces, tabs, or line breaks. A `>` past the header starts a
|
|
20
|
+
* second record; a byte-order mark anywhere else is an invalid residue. Each character is visited
|
|
21
|
+
* once, so the cost is linear in the input. Positions are 1-based character offsets into the
|
|
22
|
+
* argument, the skipped mark included.
|
|
23
|
+
*/
|
|
24
|
+
export function parseSequence(text) {
|
|
25
|
+
const residues = new Uint8Array(text.length);
|
|
26
|
+
let count = 0;
|
|
27
|
+
let index = text.charCodeAt(0) === BOM ? 1 : 0;
|
|
28
|
+
while (index < text.length && isWhitespace(text.charCodeAt(index)))
|
|
29
|
+
index++;
|
|
30
|
+
if (text.charCodeAt(index) === GT) {
|
|
31
|
+
while (index < text.length && text.charCodeAt(index) !== LF && text.charCodeAt(index) !== CR)
|
|
32
|
+
index++;
|
|
33
|
+
}
|
|
34
|
+
for (; index < text.length; index++) {
|
|
35
|
+
const code = text.charCodeAt(index);
|
|
36
|
+
if (isUpper(code))
|
|
37
|
+
residues[count++] = code;
|
|
38
|
+
else if (isLower(code))
|
|
39
|
+
residues[count++] = code - 0x20;
|
|
40
|
+
else if (isWhitespace(code))
|
|
41
|
+
continue;
|
|
42
|
+
else if (code === GT)
|
|
43
|
+
return { ok: false, reason: 'multiple_sequences', position: index + 1 };
|
|
44
|
+
else
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
reason: 'invalid_residue',
|
|
48
|
+
position: index + 1,
|
|
49
|
+
codePoint: text.codePointAt(index) ?? code,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!count)
|
|
53
|
+
return { ok: false, reason: 'empty_sequence' };
|
|
54
|
+
const bytes = residues.subarray(0, count);
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
residues: Buffer.from(bytes).toString('latin1'),
|
|
58
|
+
md5: createHash('md5').update(bytes).digest('hex').toUpperCase(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** A code point as `U+XXXX`, so an invalid character is named without echoing it. */
|
|
62
|
+
export const codePointLabel = (codePoint) => `U+${codePoint.toString(16).toUpperCase().padStart(4, '0')}`;
|
|
63
|
+
//# sourceMappingURL=sequence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequence.js","sourceRoot":"","sources":["../../../src/services/sequence-scan/sequence.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/interpro-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"mcpName": "io.github.cyanheads/interpro-mcp-server",
|
|
5
|
-
"description": "Search InterPro entries, map
|
|
5
|
+
"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.",
|
|
6
6
|
"author": "Casey Hand <casey@caseyjhand.com> (https://caseyjhand.com)",
|
|
7
7
|
"homepage": "https://github.com/cyanheads/interpro-mcp-server#readme",
|
|
8
8
|
"bugs": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"CLAUDE.md",
|
|
34
34
|
"AGENTS.md",
|
|
35
35
|
"Dockerfile",
|
|
36
|
-
"server.json"
|
|
36
|
+
"server.json",
|
|
37
|
+
"skills/"
|
|
37
38
|
],
|
|
38
39
|
"scripts": {
|
|
39
40
|
"build": "bun run scripts/build.ts",
|
|
@@ -70,6 +71,8 @@
|
|
|
70
71
|
"interpro",
|
|
71
72
|
"pfam",
|
|
72
73
|
"protein-domains",
|
|
74
|
+
"pdb",
|
|
75
|
+
"interproscan",
|
|
73
76
|
"bioinformatics"
|
|
74
77
|
],
|
|
75
78
|
"repository": {
|
|
@@ -86,20 +89,20 @@
|
|
|
86
89
|
"access": "public"
|
|
87
90
|
},
|
|
88
91
|
"dependencies": {
|
|
89
|
-
"@cyanheads/mcp-ts-core": "^0.13.
|
|
92
|
+
"@cyanheads/mcp-ts-core": "^0.13.18",
|
|
90
93
|
"htmlparser2": "^12.0.0",
|
|
91
94
|
"pino-pretty": "^13.2.0",
|
|
92
95
|
"zod": "^4.6.7"
|
|
93
96
|
},
|
|
94
97
|
"devDependencies": {
|
|
95
|
-
"@biomejs/biome": "2.6.
|
|
98
|
+
"@biomejs/biome": "2.6.3",
|
|
96
99
|
"@socketsecurity/bun-security-scanner": "^1.1.3",
|
|
97
100
|
"@types/node": "26.6.5",
|
|
98
101
|
"@vitest/coverage-istanbul": "5.0.5",
|
|
99
102
|
"depcheck": "^1.4.7",
|
|
100
103
|
"fast-check": "^4.10.2",
|
|
101
|
-
"ignore": "^7.0.
|
|
102
|
-
"tsc-alias": "^1.9.
|
|
104
|
+
"ignore": "^7.0.14",
|
|
105
|
+
"tsc-alias": "^1.9.9",
|
|
103
106
|
"typescript": "^7.0.2",
|
|
104
107
|
"vitest": "^5.0.5"
|
|
105
108
|
}
|