@cyanheads/interpro-mcp-server 0.0.0-stage → 0.1.1
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 +375 -0
- package/CLAUDE.md +375 -0
- package/Dockerfile +186 -0
- package/LICENSE +201 -0
- package/README.md +263 -2
- package/changelog/0.1.x/0.1.1.md +20 -0
- package/changelog/template.md +151 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.d.ts +177 -0
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.js +192 -0
- package/dist/mcp-server/tools/definitions/interpro-find-proteins-by-domain.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-clan.tool.d.ts +160 -0
- package/dist/mcp-server/tools/definitions/interpro-get-clan.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-clan.tool.js +259 -0
- package/dist/mcp-server/tools/definitions/interpro-get-clan.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-entry.tool.d.ts +130 -0
- package/dist/mcp-server/tools/definitions/interpro-get-entry.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-entry.tool.js +100 -0
- package/dist/mcp-server/tools/definitions/interpro-get-entry.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.d.ts +172 -0
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.js +374 -0
- package/dist/mcp-server/tools/definitions/interpro-get-protein-domains.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.d.ts +89 -0
- package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.js +99 -0
- package/dist/mcp-server/tools/definitions/interpro-list-reference.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-search-entries.tool.d.ts +184 -0
- package/dist/mcp-server/tools/definitions/interpro-search-entries.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/interpro-search-entries.tool.js +155 -0
- package/dist/mcp-server/tools/definitions/interpro-search-entries.tool.js.map +1 -0
- package/dist/services/interpro/budget.d.ts +26 -0
- package/dist/services/interpro/budget.d.ts.map +1 -0
- package/dist/services/interpro/budget.js +41 -0
- package/dist/services/interpro/budget.js.map +1 -0
- package/dist/services/interpro/cursors.d.ts +25 -0
- package/dist/services/interpro/cursors.d.ts.map +1 -0
- package/dist/services/interpro/cursors.js +99 -0
- package/dist/services/interpro/cursors.js.map +1 -0
- package/dist/services/interpro/format.d.ts +17 -0
- package/dist/services/interpro/format.d.ts.map +1 -0
- package/dist/services/interpro/format.js +130 -0
- package/dist/services/interpro/format.js.map +1 -0
- package/dist/services/interpro/http-adapter.d.ts +11 -0
- package/dist/services/interpro/http-adapter.d.ts.map +1 -0
- package/dist/services/interpro/http-adapter.js +134 -0
- package/dist/services/interpro/http-adapter.js.map +1 -0
- package/dist/services/interpro/interpro-service.d.ts +57 -0
- package/dist/services/interpro/interpro-service.d.ts.map +1 -0
- package/dist/services/interpro/interpro-service.js +284 -0
- package/dist/services/interpro/interpro-service.js.map +1 -0
- package/dist/services/interpro/reference.d.ts +31 -0
- package/dist/services/interpro/reference.d.ts.map +1 -0
- package/dist/services/interpro/reference.js +122 -0
- package/dist/services/interpro/reference.js.map +1 -0
- package/dist/services/interpro/relationships.d.ts +38 -0
- package/dist/services/interpro/relationships.d.ts.map +1 -0
- package/dist/services/interpro/relationships.js +70 -0
- package/dist/services/interpro/relationships.js.map +1 -0
- package/dist/services/interpro/routing.d.ts +24 -0
- package/dist/services/interpro/routing.d.ts.map +1 -0
- package/dist/services/interpro/routing.js +106 -0
- package/dist/services/interpro/routing.js.map +1 -0
- package/dist/services/interpro/schemas.d.ts +182 -0
- package/dist/services/interpro/schemas.d.ts.map +1 -0
- package/dist/services/interpro/schemas.js +215 -0
- package/dist/services/interpro/schemas.js.map +1 -0
- package/dist/services/interpro/upstream-errors.d.ts +43 -0
- package/dist/services/interpro/upstream-errors.d.ts.map +1 -0
- package/dist/services/interpro/upstream-errors.js +49 -0
- package/dist/services/interpro/upstream-errors.js.map +1 -0
- package/package.json +103 -4
- package/server.json +85 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Query-bound, versioned pagination tokens.
|
|
3
|
+
* @module services/interpro/cursors
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
/** Deterministic canonical scope, independent of object key insertion order. */
|
|
7
|
+
export declare function cursorScope(value: unknown): string;
|
|
8
|
+
/** Code-unit string order, the tiebreak used wherever a page boundary must be deterministic. */
|
|
9
|
+
export declare const compareText: (a: string, b: string) => -1 | 0 | 1;
|
|
10
|
+
/** Encode only the continuation value, never an upstream request URL. */
|
|
11
|
+
export declare function encodeCursor(operation: string, scope: unknown, upstreamCursor: string): string;
|
|
12
|
+
/** Reject malformed tokens and tokens reused with a changed target or filter. */
|
|
13
|
+
export declare function decodeCursor(token: string | undefined, operation: string, scope: unknown): string | undefined;
|
|
14
|
+
/** Encode a tool-defined resumption point, for walks that can stop inside an upstream page. */
|
|
15
|
+
export declare function encodePositionCursor(operation: string, scope: unknown, position: unknown): string;
|
|
16
|
+
/** Decode a resumption point and validate it against the tool's own position schema. */
|
|
17
|
+
export declare function decodePositionCursor<T extends z.ZodType>(token: string | undefined, operation: string, scope: unknown, schema: T): z.output<T> | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Truncation guidance for a capped cursor page: the call that continues it, plus a larger
|
|
20
|
+
* `limit` only while the current one is below the tool's maximum.
|
|
21
|
+
*/
|
|
22
|
+
export declare function pageGuidance(toolName: string, noun: string, limit: number, maxLimit: number): string;
|
|
23
|
+
/** Extract the sole usable continuation field without trusting URL origin/path. */
|
|
24
|
+
export declare function continuation(next: string | null, seen?: ReadonlySet<string>): string | undefined;
|
|
25
|
+
//# sourceMappingURL=cursors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursors.d.ts","sourceRoot":"","sources":["../../../src/services/interpro/cursors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAsB3C,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMlD;AACD,gGAAgG;AAChG,eAAO,MAAM,WAAW,MAAO,MAAM,KAAK,MAAM,eAAiC,CAAC;AAkClF,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAE9F;AACD,iFAAiF;AACjF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,SAAS,CAKpB;AACD,+FAA+F;AAC/F,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAEjG;AACD,wFAAwF;AACxF,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EACtD,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,CAAC,GACR,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAEzB;AACD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,MAAM,CAER;AACD,mFAAmF;AACnF,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,IAAI,GAAE,WAAW,CAAC,MAAM,CAAa,GACpC,MAAM,GAAG,SAAS,CAkBpB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Query-bound, versioned pagination tokens.
|
|
3
|
+
* @module services/interpro/cursors
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { serviceUnavailable, validationError } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { API_BASE } from './routing.js';
|
|
8
|
+
/**
|
|
9
|
+
* Every token carries exactly one continuation: the upstream cursor of the next page, or a
|
|
10
|
+
* tool-defined position for a walk that resumes inside a page.
|
|
11
|
+
*/
|
|
12
|
+
const CursorSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
v: z.literal(1),
|
|
15
|
+
operation: z.string().min(1),
|
|
16
|
+
scope: z.string(),
|
|
17
|
+
upstreamCursor: z.string().min(1).optional(),
|
|
18
|
+
position: z.json().optional(),
|
|
19
|
+
})
|
|
20
|
+
.strict()
|
|
21
|
+
.refine((cursor) => (cursor.upstreamCursor === undefined) !== (cursor.position === undefined), 'A cursor carries exactly one continuation.');
|
|
22
|
+
/** Deterministic canonical scope, independent of object key insertion order. */
|
|
23
|
+
export function cursorScope(value) {
|
|
24
|
+
return JSON.stringify(value, (_key, item) => item && typeof item === 'object' && !Array.isArray(item)
|
|
25
|
+
? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b)))
|
|
26
|
+
: item);
|
|
27
|
+
}
|
|
28
|
+
/** Code-unit string order, the tiebreak used wherever a page boundary must be deterministic. */
|
|
29
|
+
export const compareText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
30
|
+
function seal(operation, scope, resumption) {
|
|
31
|
+
const token = Buffer.from(JSON.stringify({ v: 1, operation, scope: cursorScope(scope), ...resumption })).toString('base64url');
|
|
32
|
+
if (token.length > 4096)
|
|
33
|
+
throw serviceUnavailable('InterPro continuation exceeds the supported token size.', {
|
|
34
|
+
reason: 'upstream_shape_invalid',
|
|
35
|
+
retryable: false,
|
|
36
|
+
});
|
|
37
|
+
return token;
|
|
38
|
+
}
|
|
39
|
+
function open(token, operation, scope, read) {
|
|
40
|
+
if (token === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
try {
|
|
43
|
+
if (token.length > 4096 || !/^[A-Za-z0-9_-]+$/.test(token))
|
|
44
|
+
throw new Error('Invalid cursor encoding.');
|
|
45
|
+
const parsed = CursorSchema.parse(JSON.parse(Buffer.from(token, 'base64url').toString('utf8')));
|
|
46
|
+
if (parsed.operation !== operation || parsed.scope !== cursorScope(scope))
|
|
47
|
+
throw new Error('Cursor scope differs.');
|
|
48
|
+
return read(parsed);
|
|
49
|
+
}
|
|
50
|
+
catch (cause) {
|
|
51
|
+
throw validationError('Cursor is invalid or belongs to a different query.', { reason: 'invalid_cursor', retryable: false }, { cause });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Encode only the continuation value, never an upstream request URL. */
|
|
55
|
+
export function encodeCursor(operation, scope, upstreamCursor) {
|
|
56
|
+
return seal(operation, scope, { upstreamCursor });
|
|
57
|
+
}
|
|
58
|
+
/** Reject malformed tokens and tokens reused with a changed target or filter. */
|
|
59
|
+
export function decodeCursor(token, operation, scope) {
|
|
60
|
+
return open(token, operation, scope, ({ upstreamCursor }) => {
|
|
61
|
+
if (upstreamCursor === undefined)
|
|
62
|
+
throw new Error('Cursor carries no upstream continuation.');
|
|
63
|
+
return upstreamCursor;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Encode a tool-defined resumption point, for walks that can stop inside an upstream page. */
|
|
67
|
+
export function encodePositionCursor(operation, scope, position) {
|
|
68
|
+
return seal(operation, scope, { position });
|
|
69
|
+
}
|
|
70
|
+
/** Decode a resumption point and validate it against the tool's own position schema. */
|
|
71
|
+
export function decodePositionCursor(token, operation, scope, schema) {
|
|
72
|
+
return open(token, operation, scope, ({ position }) => schema.parse(position));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Truncation guidance for a capped cursor page: the call that continues it, plus a larger
|
|
76
|
+
* `limit` only while the current one is below the tool's maximum.
|
|
77
|
+
*/
|
|
78
|
+
export function pageGuidance(toolName, noun, limit, maxLimit) {
|
|
79
|
+
return `More ${noun} remain: repeat ${toolName} with the same arguments and cursor set to page.nextCursor${limit < maxLimit ? `; limit may rise to ${maxLimit} for larger pages` : ''}.`;
|
|
80
|
+
}
|
|
81
|
+
/** Extract the sole usable continuation field without trusting URL origin/path. */
|
|
82
|
+
export function continuation(next, seen = new Set()) {
|
|
83
|
+
if (next === null)
|
|
84
|
+
return undefined;
|
|
85
|
+
let cursor;
|
|
86
|
+
try {
|
|
87
|
+
cursor = new URL(next, API_BASE).searchParams.get('cursor');
|
|
88
|
+
}
|
|
89
|
+
catch (cause) {
|
|
90
|
+
throw serviceUnavailable('InterPro returned an invalid continuation URL.', { reason: 'upstream_shape_invalid', retryable: false }, { cause });
|
|
91
|
+
}
|
|
92
|
+
if (!cursor?.trim() || seen.has(cursor))
|
|
93
|
+
throw serviceUnavailable('InterPro returned a missing or repeated continuation cursor.', {
|
|
94
|
+
reason: 'upstream_shape_invalid',
|
|
95
|
+
retryable: false,
|
|
96
|
+
});
|
|
97
|
+
return cursor;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=cursors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursors.js","sourceRoot":"","sources":["../../../src/services/interpro/cursors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Convert Wikipedia's base64 thumbnail strings to data URIs before constructing either output surface. */
|
|
2
|
+
export declare function normalizeImages<T>(value: T): T;
|
|
3
|
+
/**
|
|
4
|
+
* Keep or drop the base64 `thumbnail` on each Wikipedia page of a detail record. Dropping it
|
|
5
|
+
* removes only that key, so every other page field survives, and the returned notice states
|
|
6
|
+
* what was removed and names `includeThumbnail: true` on `toolName` as the way to get it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function projectWikipediaThumbnails<T extends Record<string, unknown>>(metadata: T, include: boolean, toolName: string): {
|
|
9
|
+
metadata: T;
|
|
10
|
+
notice?: string;
|
|
11
|
+
};
|
|
12
|
+
/** Emit every field losslessly as compact JSON; source text remains inert inside a growing fence. */
|
|
13
|
+
export declare function formatInterpro(value: unknown): {
|
|
14
|
+
type: 'text';
|
|
15
|
+
text: string;
|
|
16
|
+
}[];
|
|
17
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/services/interpro/format.ts"],"names":[],"mappings":"AAyBA,2GAA2G;AAC3G,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAW9C;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,GACf;IAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAoBlC;AAED,qGAAqG;AACrG,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CA4D/E"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reversible JSON output with usable source URL and image indexes.
|
|
3
|
+
* @module services/interpro/format
|
|
4
|
+
*/
|
|
5
|
+
import { markdown } from '@cyanheads/mcp-ts-core/utils';
|
|
6
|
+
/** Base64 prefixes of the image formats Wikipedia thumbnails arrive in, with their media types. */
|
|
7
|
+
const THUMBNAIL_SIGNATURES = [
|
|
8
|
+
['iVBORw0KGgo', 'image/png'],
|
|
9
|
+
['/9j/', 'image/jpeg'],
|
|
10
|
+
['R0lGOD', 'image/gif'],
|
|
11
|
+
];
|
|
12
|
+
/** The whole string is standard base64, padded only at the end. */
|
|
13
|
+
const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
14
|
+
/** The whole string is a base64 PNG, JPEG, or GIF data URI: the only data URI indexed as an image. */
|
|
15
|
+
const DATA_IMAGE = /^data:image\/(?:png|jpeg|gif);base64,[A-Za-z0-9+/]+={0,2}$/;
|
|
16
|
+
/** An image file extension at the end of a URL path. */
|
|
17
|
+
const IMAGE_PATH = /\.(?:png|jpe?g|gif|webp|svg|avif)$/i;
|
|
18
|
+
function thumbnailDataUri(item) {
|
|
19
|
+
if (!BASE64.test(item))
|
|
20
|
+
return undefined;
|
|
21
|
+
const mediaType = THUMBNAIL_SIGNATURES.find(([prefix]) => item.startsWith(prefix))?.[1];
|
|
22
|
+
return mediaType && `data:${mediaType};base64,${item}`;
|
|
23
|
+
}
|
|
24
|
+
/** Convert Wikipedia's base64 thumbnail strings to data URIs before constructing either output surface. */
|
|
25
|
+
export function normalizeImages(value) {
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return value.map(normalizeImages);
|
|
28
|
+
if (value && typeof value === 'object')
|
|
29
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
30
|
+
key,
|
|
31
|
+
(key === 'thumbnail' && typeof item === 'string' && thumbnailDataUri(item)) ||
|
|
32
|
+
normalizeImages(item),
|
|
33
|
+
]));
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Keep or drop the base64 `thumbnail` on each Wikipedia page of a detail record. Dropping it
|
|
38
|
+
* removes only that key, so every other page field survives, and the returned notice states
|
|
39
|
+
* what was removed and names `includeThumbnail: true` on `toolName` as the way to get it.
|
|
40
|
+
*/
|
|
41
|
+
export function projectWikipediaThumbnails(metadata, include, toolName) {
|
|
42
|
+
if (include)
|
|
43
|
+
return { metadata };
|
|
44
|
+
let count = 0;
|
|
45
|
+
let bytes = 0;
|
|
46
|
+
const strip = (page) => {
|
|
47
|
+
if (!page || typeof page !== 'object' || Array.isArray(page))
|
|
48
|
+
return page;
|
|
49
|
+
const { thumbnail, ...rest } = page;
|
|
50
|
+
if (typeof thumbnail !== 'string' || thumbnail === '')
|
|
51
|
+
return page;
|
|
52
|
+
count += 1;
|
|
53
|
+
bytes += thumbnail.length;
|
|
54
|
+
return rest;
|
|
55
|
+
};
|
|
56
|
+
const { wikipedia } = metadata;
|
|
57
|
+
const pages = Array.isArray(wikipedia) ? wikipedia.map(strip) : strip(wikipedia);
|
|
58
|
+
if (!count)
|
|
59
|
+
return { metadata };
|
|
60
|
+
const subject = count === 1 ? 'the Wikipedia thumbnail' : `${count} Wikipedia thumbnails`;
|
|
61
|
+
return {
|
|
62
|
+
metadata: { ...metadata, wikipedia: pages },
|
|
63
|
+
notice: `Omitted ${subject} (${bytes.toLocaleString('en-US')} bytes of base64 image data); repeat ${toolName} with includeThumbnail: true to include ${count === 1 ? 'it' : 'them'}.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Emit every field losslessly as compact JSON; source text remains inert inside a growing fence. */
|
|
67
|
+
export function formatInterpro(value) {
|
|
68
|
+
const urls = new Set();
|
|
69
|
+
const images = new Set();
|
|
70
|
+
/**
|
|
71
|
+
* Record a parseable URL. A whole-string value whose path has an image extension is also an
|
|
72
|
+
* image; a URL found inside prose is only a link, since clients fetch images unprompted.
|
|
73
|
+
*/
|
|
74
|
+
const addUrl = (candidate, whole) => {
|
|
75
|
+
try {
|
|
76
|
+
const { pathname } = new URL(candidate);
|
|
77
|
+
urls.add(candidate);
|
|
78
|
+
if (whole && IMAGE_PATH.test(pathname))
|
|
79
|
+
images.add(candidate);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const inspect = (item) => {
|
|
87
|
+
if (typeof item === 'string') {
|
|
88
|
+
if (DATA_IMAGE.test(item)) {
|
|
89
|
+
images.add(item);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (/^https?:\/\//.test(item) && !/[\s\p{Cc}]/u.test(item) && addUrl(item, true))
|
|
93
|
+
return;
|
|
94
|
+
for (const match of item.matchAll(/https?:\/\/[^\s<>"'\p{Cc}]+/gu))
|
|
95
|
+
addUrl(match[0], false);
|
|
96
|
+
}
|
|
97
|
+
else if (Array.isArray(item))
|
|
98
|
+
item.forEach(inspect);
|
|
99
|
+
else if (item && typeof item === 'object')
|
|
100
|
+
Object.values(item).forEach(inspect);
|
|
101
|
+
};
|
|
102
|
+
inspect(value);
|
|
103
|
+
const json = JSON.stringify(value)
|
|
104
|
+
.replace(/\u2028/g, '\\u2028')
|
|
105
|
+
.replace(/\u2029/g, '\\u2029');
|
|
106
|
+
const output = markdown().codeBlock(json, 'json');
|
|
107
|
+
/**
|
|
108
|
+
* CommonMark angle destinations retain parentheses, and entities decode to the original URL.
|
|
109
|
+
* Whitespace and control characters, which no indexed URL holds, are percent-encoded as a
|
|
110
|
+
* backstop so no destination can span a line.
|
|
111
|
+
*/
|
|
112
|
+
const destination = (url) => url
|
|
113
|
+
.replace(/&/g, '&')
|
|
114
|
+
.replace(/</g, '<')
|
|
115
|
+
.replace(/>/g, '>')
|
|
116
|
+
.replace(/\\/g, '\')
|
|
117
|
+
.replace(/[\s\p{Cc}]/gu, encodeURIComponent);
|
|
118
|
+
if (urls.size)
|
|
119
|
+
output
|
|
120
|
+
.raw('\n\nSources and links:\n')
|
|
121
|
+
.raw([...urls].map((url, index) => `[Link ${index + 1}](<${destination(url)}>)`).join('\n\n'));
|
|
122
|
+
if (images.size)
|
|
123
|
+
output
|
|
124
|
+
.raw('\n\nImages:\n')
|
|
125
|
+
.raw([...images]
|
|
126
|
+
.map((url, index) => `}>)`)
|
|
127
|
+
.join('\n\n'));
|
|
128
|
+
return [{ type: 'text', text: output.build() }];
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/services/interpro/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Bounded HTTP/1.1 adapter for InterPro's malformed bodyless 204 responses.
|
|
3
|
+
* @module services/interpro/http-adapter
|
|
4
|
+
*/
|
|
5
|
+
/** Maximum decoded response size, shared by native and injected transports. */
|
|
6
|
+
export declare const MAX_RESPONSE_BYTES: number;
|
|
7
|
+
/** Fetch-compatible injection seam; callers supply the composed request signal. */
|
|
8
|
+
export type InterproFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
9
|
+
/** Collect one response without following redirects or retaining malformed connections. */
|
|
10
|
+
export declare const interproHttpFetch: InterproFetch;
|
|
11
|
+
//# sourceMappingURL=http-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-adapter.d.ts","sourceRoot":"","sources":["../../../src/services/interpro/http-adapter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,QAAkB,CAAC;AAClD,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElF,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,EAAE,aAkK5B,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Bounded HTTP/1.1 adapter for InterPro's malformed bodyless 204 responses.
|
|
3
|
+
* @module services/interpro/http-adapter
|
|
4
|
+
*/
|
|
5
|
+
import { request } from 'node:https';
|
|
6
|
+
import { serviceUnavailable, timeout } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { API_BASE } from './routing.js';
|
|
8
|
+
/** Maximum decoded response size, shared by native and injected transports. */
|
|
9
|
+
export const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
10
|
+
/** Collect one response without following redirects or retaining malformed connections. */
|
|
11
|
+
export const interproHttpFetch = (url, init) => new Promise((resolve, reject) => {
|
|
12
|
+
const target = new URL(url);
|
|
13
|
+
if (target.origin !== new URL(API_BASE).origin ||
|
|
14
|
+
!target.pathname.startsWith('/interpro/api/')) {
|
|
15
|
+
reject(serviceUnavailable('InterPro request left the fixed API origin.', {
|
|
16
|
+
reason: 'upstream_rejected',
|
|
17
|
+
retryable: false,
|
|
18
|
+
}));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const signal = init.signal;
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
let settled = false;
|
|
24
|
+
let response;
|
|
25
|
+
const cleanup = () => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
signal?.removeEventListener('abort', abort);
|
|
28
|
+
};
|
|
29
|
+
const fail = (error) => {
|
|
30
|
+
if (settled)
|
|
31
|
+
return;
|
|
32
|
+
settled = true;
|
|
33
|
+
cleanup();
|
|
34
|
+
response?.destroy();
|
|
35
|
+
req.destroy();
|
|
36
|
+
reject(error);
|
|
37
|
+
};
|
|
38
|
+
const abort = () => fail(signal?.reason);
|
|
39
|
+
const timer = setTimeout(() => fail(timeout('InterPro response exceeded its attempt deadline.', {
|
|
40
|
+
reason: 'upstream_timeout',
|
|
41
|
+
retryable: true,
|
|
42
|
+
})), 15_000);
|
|
43
|
+
const connectionOptions = { ALPNProtocols: ['http/1.1'] };
|
|
44
|
+
const req = request(target, {
|
|
45
|
+
...connectionOptions,
|
|
46
|
+
method: 'GET',
|
|
47
|
+
agent: false,
|
|
48
|
+
headers: { Accept: 'application/json', 'Accept-Encoding': 'identity' },
|
|
49
|
+
});
|
|
50
|
+
req.on('error', (cause) => fail(serviceUnavailable('InterPro connection failed.', { reason: 'upstream_unavailable', retryable: true }, { cause })));
|
|
51
|
+
req.on('close', () => {
|
|
52
|
+
if (!response)
|
|
53
|
+
fail(serviceUnavailable('InterPro connection closed before response headers.', {
|
|
54
|
+
reason: 'upstream_unavailable',
|
|
55
|
+
retryable: true,
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
req.on('response', (incoming) => {
|
|
59
|
+
response = incoming;
|
|
60
|
+
incoming.on('error', (cause) => fail(serviceUnavailable('InterPro response stream failed.', { reason: 'upstream_unavailable', retryable: true }, { cause })));
|
|
61
|
+
incoming.on('aborted', () => fail(serviceUnavailable('InterPro response ended prematurely.', {
|
|
62
|
+
reason: 'upstream_unavailable',
|
|
63
|
+
retryable: true,
|
|
64
|
+
})));
|
|
65
|
+
incoming.on('close', () => {
|
|
66
|
+
if (!incoming.complete)
|
|
67
|
+
fail(serviceUnavailable('InterPro response closed before completion.', {
|
|
68
|
+
reason: 'upstream_unavailable',
|
|
69
|
+
retryable: true,
|
|
70
|
+
}));
|
|
71
|
+
});
|
|
72
|
+
const headers = new Headers();
|
|
73
|
+
for (const [name, value] of Object.entries(incoming.headers))
|
|
74
|
+
if (value !== undefined)
|
|
75
|
+
headers.set(name, Array.isArray(value) ? value.join(', ') : value);
|
|
76
|
+
const status = incoming.statusCode;
|
|
77
|
+
if (!status) {
|
|
78
|
+
fail(serviceUnavailable('InterPro response lacks an HTTP status.', {
|
|
79
|
+
reason: 'upstream_shape_invalid',
|
|
80
|
+
retryable: false,
|
|
81
|
+
}));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (status === 204) {
|
|
85
|
+
settled = true;
|
|
86
|
+
cleanup();
|
|
87
|
+
resolve(new Response(null, { status, headers }));
|
|
88
|
+
incoming.destroy();
|
|
89
|
+
req.destroy();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const encoding = headers.get('content-encoding');
|
|
93
|
+
if (encoding && encoding.toLowerCase() !== 'identity') {
|
|
94
|
+
fail(serviceUnavailable('InterPro returned an unexpected content encoding.', {
|
|
95
|
+
reason: 'upstream_shape_invalid',
|
|
96
|
+
retryable: false,
|
|
97
|
+
}));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const chunks = [];
|
|
101
|
+
let bytes = 0;
|
|
102
|
+
incoming.on('data', (chunk) => {
|
|
103
|
+
if (settled)
|
|
104
|
+
return;
|
|
105
|
+
bytes += chunk.length;
|
|
106
|
+
if (bytes > MAX_RESPONSE_BYTES) {
|
|
107
|
+
fail(serviceUnavailable('InterPro response exceeds 4 MiB.', {
|
|
108
|
+
reason: 'upstream_response_too_large',
|
|
109
|
+
retryable: false,
|
|
110
|
+
}));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
chunks.push(chunk);
|
|
114
|
+
});
|
|
115
|
+
incoming.on('end', () => {
|
|
116
|
+
if (settled)
|
|
117
|
+
return;
|
|
118
|
+
settled = true;
|
|
119
|
+
cleanup();
|
|
120
|
+
try {
|
|
121
|
+
resolve(new Response(status === 304 ? null : Buffer.concat(chunks), { status, headers }));
|
|
122
|
+
}
|
|
123
|
+
catch (cause) {
|
|
124
|
+
reject(serviceUnavailable('InterPro returned invalid HTTP response metadata.', { reason: 'upstream_shape_invalid', retryable: false }, { cause }));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
129
|
+
if (signal?.aborted)
|
|
130
|
+
abort();
|
|
131
|
+
else
|
|
132
|
+
req.end();
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=http-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-adapter.js","sourceRoot":"","sources":["../../../src/services/interpro/http-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview One paced, cancellable InterPro request boundary for every tool workflow.
|
|
3
|
+
* @module services/interpro/interpro-service
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { type InterproFetch } from './http-adapter.js';
|
|
8
|
+
/** One deadline shared by every distinct operation of a tool invocation. */
|
|
9
|
+
export interface InterproCall {
|
|
10
|
+
readonly ctx: Context;
|
|
11
|
+
readonly deadline: number;
|
|
12
|
+
}
|
|
13
|
+
/** The record a request addresses, reported under `reason` when InterPro has no such record. */
|
|
14
|
+
export interface MissingRecord {
|
|
15
|
+
readonly accession: string;
|
|
16
|
+
/** A list scoped to the record: 204 is a valid empty page, so only 404 reports it missing. */
|
|
17
|
+
readonly list?: boolean;
|
|
18
|
+
readonly reason: 'entry_not_found' | 'protein_not_found' | 'clan_not_found';
|
|
19
|
+
readonly sourceDatabase: string;
|
|
20
|
+
}
|
|
21
|
+
/** Domain status handling selected by the tool workflow. */
|
|
22
|
+
export interface RequestOptions {
|
|
23
|
+
readonly continuation?: boolean;
|
|
24
|
+
readonly missing?: MissingRecord;
|
|
25
|
+
}
|
|
26
|
+
/** Validated response plus headers needed for release consistency. */
|
|
27
|
+
export interface InterproResponse<T> {
|
|
28
|
+
readonly data: T | null;
|
|
29
|
+
readonly headers: Headers;
|
|
30
|
+
readonly sourceUrl: string;
|
|
31
|
+
}
|
|
32
|
+
/** InterPro service; inject only the network seam, retaining the real domain behavior. */
|
|
33
|
+
export declare class InterproService {
|
|
34
|
+
private readonly fetch;
|
|
35
|
+
constructor(fetch?: InterproFetch);
|
|
36
|
+
/** Start a 50-second call budget, once per handler invocation. */
|
|
37
|
+
begin(ctx: Context): InterproCall;
|
|
38
|
+
/** Fetch, classify, parse and validate one fixed-origin operation within a shared call. */
|
|
39
|
+
request<T extends z.ZodType>(call: InterproCall, segments: readonly string[], query: Readonly<Record<string, string | number | boolean | undefined>>, schema: T, options?: RequestOptions): Promise<InterproResponse<z.output<T>>>;
|
|
40
|
+
/** Retrieve a coherent current release and its complete database-count object. */
|
|
41
|
+
release(ctx: Context): Promise<{
|
|
42
|
+
version: string;
|
|
43
|
+
date: string;
|
|
44
|
+
databaseCounts: {
|
|
45
|
+
[x: string]: z.JSONType;
|
|
46
|
+
entries: Record<string, z.JSONType>;
|
|
47
|
+
};
|
|
48
|
+
sourceUrls: string[];
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
/** Initialize the process service in createApp.setup. */
|
|
52
|
+
export declare function initInterproService(fetch?: InterproFetch): void;
|
|
53
|
+
/** Obtain the initialized process service. */
|
|
54
|
+
export declare function getInterproService(): InterproService;
|
|
55
|
+
/** Dispose queued pacing work during application teardown. */
|
|
56
|
+
export declare function disposeInterproService(): void;
|
|
57
|
+
//# sourceMappingURL=interpro-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interpro-service.d.ts","sourceRoot":"","sources":["../../../src/services/interpro/interpro-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAe3C,OAAO,EAAE,KAAK,aAAa,EAAyC,MAAM,mBAAmB,CAAC;AAU9F,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AACD,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAuBD,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;CAClC;AACD,sEAAsE;AACtE,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,0FAA0F;AAC1F,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAlC,YAA6B,KAAK,GAAE,aAAiC,EAAI;IAEzE,kEAAkE;IAClE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAEhC;IAED,2FAA2F;IACrF,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAC/B,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC,EACtE,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAiLxC;IAED,kFAAkF;IAC5E,OAAO,CAAC,GAAG,EAAE,OAAO;;cA0CU,MAAM;;;;;;OAIzC;CACF;AAmCD,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,aAAiC,GAAG,IAAI,CAElF;AACD,8CAA8C;AAC9C,wBAAgB,kBAAkB,IAAI,eAAe,CAGpD;AACD,8DAA8D;AAC9D,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|