calibreweb-mcp 0.2.0 → 0.3.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/README.md +115 -30
- package/dist/api.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +25 -5
- package/dist/index.js.map +1 -1
- package/dist/result.d.ts +26 -4
- package/dist/result.js +64 -19
- package/dist/result.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +16 -3
- package/dist/server.js.map +1 -1
- package/dist/shape.d.ts +70 -25
- package/dist/shape.js +123 -2
- package/dist/shape.js.map +1 -1
- package/dist/tools/annotations.d.ts +19 -0
- package/dist/tools/annotations.js +20 -0
- package/dist/tools/annotations.js.map +1 -0
- package/dist/tools/books.d.ts +1 -1
- package/dist/tools/books.js +36 -10
- package/dist/tools/books.js.map +1 -1
- package/dist/tools/catalogue.d.ts +7 -6
- package/dist/tools/catalogue.js +6 -5
- package/dist/tools/catalogue.js.map +1 -1
- package/dist/tools/covers.d.ts +1 -1
- package/dist/tools/covers.js +20 -3
- package/dist/tools/covers.js.map +1 -1
- package/dist/tools/shelves.d.ts +1 -1
- package/dist/tools/shelves.js +24 -10
- package/dist/tools/shelves.js.map +1 -1
- package/dist/tools/stats.d.ts +1 -1
- package/dist/tools/stats.js +12 -2
- package/dist/tools/stats.js.map +1 -1
- package/package.json +16 -12
- package/dist/hosts.d.ts +0 -22
- package/dist/hosts.js +0 -144
- package/dist/hosts.js.map +0 -1
- package/dist/tool-filter.d.ts +0 -43
- package/dist/tool-filter.js +0 -138
- package/dist/tool-filter.js.map +0 -1
package/dist/tools/shelves.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { READ_ONLY } from './annotations.js';
|
|
3
|
+
import { run, untrustedResult } from '../result.js';
|
|
4
|
+
import { Notes, notes as notesSchema, pagination, shapedBook, shapedNavItem, shapeFeed, untrustedFields, } from '../shape.js';
|
|
4
5
|
export function registerShelfTools(server, api) {
|
|
5
6
|
server.registerTool('list_shelves', {
|
|
6
7
|
title: 'List shelves',
|
|
@@ -8,20 +9,26 @@ export function registerShelfTools(server, api) {
|
|
|
8
9
|
'plus the user’s own private ones. Use the returned id with ' +
|
|
9
10
|
'get_shelf_books. isPublic is only reported on English-locale instances ' +
|
|
10
11
|
'(Calibre-Web marks public shelves with a localized title suffix).',
|
|
11
|
-
inputSchema: {
|
|
12
|
+
inputSchema: z.object({
|
|
12
13
|
offset: z
|
|
13
14
|
.number()
|
|
14
15
|
.int()
|
|
15
16
|
.min(0)
|
|
16
17
|
.optional()
|
|
17
18
|
.describe('Pagination offset; use pagination.nextOffset from the previous call'),
|
|
18
|
-
},
|
|
19
|
-
annotations:
|
|
19
|
+
}),
|
|
20
|
+
annotations: READ_ONLY,
|
|
21
|
+
outputSchema: z.object({
|
|
22
|
+
...untrustedFields,
|
|
23
|
+
shelves: z.array(shapedNavItem),
|
|
24
|
+
pagination,
|
|
25
|
+
notes: notesSchema,
|
|
26
|
+
}),
|
|
20
27
|
}, async ({ offset }) => run(async () => {
|
|
21
28
|
const notes = new Notes();
|
|
22
29
|
const parsed = await api.getFeed('/opds/shelfindex', offset === undefined || offset === 0 ? undefined : { offset });
|
|
23
30
|
const shaped = shapeFeed(parsed, api.url, offset ?? 0, notes);
|
|
24
|
-
return
|
|
31
|
+
return untrustedResult({
|
|
25
32
|
shelves: shaped.navItems,
|
|
26
33
|
pagination: shaped.pagination,
|
|
27
34
|
notes: notes.list(),
|
|
@@ -32,7 +39,7 @@ export function registerShelfTools(server, api) {
|
|
|
32
39
|
description: 'Lists the books on a shelf, in the shelf’s own order. Shelf ids ' +
|
|
33
40
|
'come from list_shelves. Book entries include per-format download URLs ' +
|
|
34
41
|
'and a cover URL; fetch the cover image itself with get_cover.',
|
|
35
|
-
inputSchema: {
|
|
42
|
+
inputSchema: z.object({
|
|
36
43
|
shelf_id: z
|
|
37
44
|
.number()
|
|
38
45
|
.int()
|
|
@@ -45,8 +52,15 @@ export function registerShelfTools(server, api) {
|
|
|
45
52
|
.min(0)
|
|
46
53
|
.optional()
|
|
47
54
|
.describe('Pagination offset; use pagination.nextOffset from the previous call'),
|
|
48
|
-
},
|
|
49
|
-
annotations:
|
|
55
|
+
}),
|
|
56
|
+
annotations: READ_ONLY,
|
|
57
|
+
outputSchema: z.object({
|
|
58
|
+
...untrustedFields,
|
|
59
|
+
shelfId: z.number().int(),
|
|
60
|
+
books: z.array(shapedBook),
|
|
61
|
+
pagination,
|
|
62
|
+
notes: notesSchema,
|
|
63
|
+
}),
|
|
50
64
|
}, async ({ shelf_id, offset }) => run(async () => {
|
|
51
65
|
const notes = new Notes();
|
|
52
66
|
const parsed = await api.getFeed(`/opds/shelf/${shelf_id}`, offset === undefined || offset === 0 ? undefined : { offset });
|
|
@@ -54,7 +68,7 @@ export function registerShelfTools(server, api) {
|
|
|
54
68
|
if (shaped.books.length === 0) {
|
|
55
69
|
notes.add('An empty result can also mean the shelf does not exist or is not accessible to the configured user — Calibre-Web returns an empty feed in that case.');
|
|
56
70
|
}
|
|
57
|
-
return
|
|
71
|
+
return untrustedResult({
|
|
58
72
|
shelfId: shelf_id,
|
|
59
73
|
books: shaped.books,
|
|
60
74
|
pagination: shaped.pagination,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shelves.js","sourceRoot":"","sources":["../../src/tools/shelves.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"shelves.js","sourceRoot":"","sources":["../../src/tools/shelves.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/stats.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import type { CalibreWebApi } from '../api.js';
|
|
3
3
|
export declare function registerStatsTools(server: McpServer, api: CalibreWebApi): void;
|
package/dist/tools/stats.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { READ_ONLY } from './annotations.js';
|
|
1
3
|
import { jsonResult, run } from '../result.js';
|
|
2
4
|
export function registerStatsTools(server, api) {
|
|
3
5
|
server.registerTool('get_stats', {
|
|
4
6
|
title: 'Get library statistics',
|
|
5
7
|
description: 'Returns the total number of books, authors, categories (tags) and ' +
|
|
6
8
|
'series in the library.',
|
|
7
|
-
inputSchema: {},
|
|
8
|
-
annotations:
|
|
9
|
+
inputSchema: z.object({}),
|
|
10
|
+
annotations: READ_ONLY,
|
|
11
|
+
// No untrusted marker: four counters this server checked are numbers,
|
|
12
|
+
// and nothing a publisher wrote reaches here.
|
|
13
|
+
outputSchema: z.object({
|
|
14
|
+
books: z.number().optional(),
|
|
15
|
+
authors: z.number().optional(),
|
|
16
|
+
categories: z.number().optional().describe('Tags, in Calibre’s terms.'),
|
|
17
|
+
series: z.number().optional(),
|
|
18
|
+
}),
|
|
9
19
|
}, async () => run(async () => {
|
|
10
20
|
const raw = await api.getJson('/opds/stats');
|
|
11
21
|
const stats = typeof raw === 'object' && raw !== null
|
package/dist/tools/stats.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/tools/stats.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/tools/stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,UAAU,kBAAkB,CAChC,MAAiB,EACjB,GAAkB;IAElB,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;QACE,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EACT,oEAAoE;YACpE,wBAAwB;QAC1B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,WAAW,EAAE,SAAS;QACtB,sEAAsE;QACtE,8CAA8C;QAC9C,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC5B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;YACvE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC9B,CAAC;KACH,EACD,KAAK,IAAI,EAAE,CACT,GAAG,CAAC,KAAK,IAAI,EAAE;QACb,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAC7C,MAAM,KAAK,GACT,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;YACrC,CAAC,CAAE,GAA+B;YAClC,CAAC,CAAC,EAAE,CAAC;QACT,qEAAqE;QACrE,kEAAkE;QAClE,MAAM,IAAI,GAAG,CAAC,GAAW,EAAsB,EAAE,CAC/C,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1D,OAAO,UAAU,CAAC;YAChB,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;YACpB,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;YACxB,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC;YAC9B,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CACL,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "calibreweb-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"mcpName": "io.github.ni-c/calibreweb-mcp",
|
|
5
|
-
"description": "MCP server for Calibre-Web
|
|
5
|
+
"description": "MCP server for Calibre-Web — read-only library access via the OPDS feed",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
8
8
|
"model-context-protocol",
|
|
@@ -36,30 +36,34 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc",
|
|
39
|
+
"typecheck": "tsc --project tsconfig.test.json",
|
|
39
40
|
"watch": "tsc --watch",
|
|
40
|
-
"lint": "
|
|
41
|
+
"lint": "oxlint --deny-warnings && prettier --check .",
|
|
41
42
|
"format": "prettier --write .",
|
|
42
43
|
"test": "vitest run",
|
|
43
44
|
"test:coverage": "vitest run --coverage",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"assets
|
|
45
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
46
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm test && npm run build",
|
|
47
|
+
"assets": "svg-asset-set",
|
|
48
|
+
"assets:check": "svg-asset-set --check"
|
|
47
49
|
},
|
|
48
50
|
"dependencies": {
|
|
49
|
-
"@modelcontextprotocol/
|
|
51
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
50
52
|
"fast-xml-parser": "^5.11.0",
|
|
53
|
+
"mcp-internal-hosts": "^0.2.1",
|
|
54
|
+
"mcp-tool-allowlist": "^0.2.1",
|
|
51
55
|
"undici": "^8.10.0",
|
|
52
56
|
"zod": "^4.4.3"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
|
-
"@
|
|
56
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
59
|
+
"@modelcontextprotocol/client": "^2.0.0",
|
|
57
60
|
"@types/node": "^26.2.0",
|
|
58
61
|
"@vitest/coverage-v8": "4.1.11",
|
|
59
|
-
"
|
|
62
|
+
"mcp-integration-harness": "^0.4.0",
|
|
63
|
+
"oxlint": "^1.80.0",
|
|
60
64
|
"prettier": "^3.9.6",
|
|
61
|
-
"
|
|
62
|
-
"typescript
|
|
65
|
+
"svg-asset-set": "https://github.com/ni-c/svg-asset-set/releases/download/v0.2.0/svg-asset-set-0.2.0.tgz",
|
|
66
|
+
"typescript": "^7.0.2",
|
|
63
67
|
"vitest": "^4.1.11"
|
|
64
68
|
}
|
|
65
69
|
}
|
package/dist/hosts.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Why a host is only reachable from the machine it is named on: `loopback`
|
|
3
|
-
* addresses that machine itself, `link-local` covers 169.254/16, fe80::/10 and
|
|
4
|
-
* the handful of addresses cloud providers put their metadata service on.
|
|
5
|
-
*/
|
|
6
|
-
export type InternalHostKind = 'loopback' | 'link-local';
|
|
7
|
-
/**
|
|
8
|
-
* Classifies a hostname that addresses the machine itself, its link-local
|
|
9
|
-
* range, or a cloud metadata service — or returns null for anything routable.
|
|
10
|
-
*
|
|
11
|
-
* It does the job numerically rather than by comparing strings, because the
|
|
12
|
-
* spellings differ more than they look. `URL` canonicalises an IPv4-mapped IPv6
|
|
13
|
-
* literal into hex — `http://[::ffff:127.0.0.1]/` arrives here as
|
|
14
|
-
* `[::ffff:7f00:1]` — while every dual-stack client dials it as plain
|
|
15
|
-
* 127.0.0.1, and `localhost.` with its root label is the same name as
|
|
16
|
-
* `localhost`. String comparison misses both.
|
|
17
|
-
*
|
|
18
|
-
* Takes a hostname from anywhere, not only from `URL`: a resolver hands back
|
|
19
|
-
* `::ffff:127.0.0.1` in dotted form and may attach a `%zone` suffix, so both
|
|
20
|
-
* are handled here rather than assumed away.
|
|
21
|
-
*/
|
|
22
|
-
export declare function internalHostKind(hostname: string): InternalHostKind | null;
|
package/dist/hosts.js
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { isIP } from 'node:net';
|
|
2
|
-
/**
|
|
3
|
-
* Names for the cloud metadata service, which resolve on an instance and
|
|
4
|
-
* nowhere else.
|
|
5
|
-
*/
|
|
6
|
-
const METADATA_NAMES = new Set([
|
|
7
|
-
'metadata',
|
|
8
|
-
'metadata.google.internal',
|
|
9
|
-
'metadata.goog',
|
|
10
|
-
'instance-data',
|
|
11
|
-
'instance-data.ec2.internal',
|
|
12
|
-
]);
|
|
13
|
-
/**
|
|
14
|
-
* Metadata endpoints that sit outside 169.254/16.
|
|
15
|
-
*
|
|
16
|
-
* Alibaba Cloud answers on 100.100.100.200, which is carrier-grade NAT space,
|
|
17
|
-
* and Oracle's legacy endpoint is 192.0.0.192, which is IETF protocol
|
|
18
|
-
* assignments. Neither is link-local by address, but both are the same thing by
|
|
19
|
-
* purpose, and neither is somewhere a legitimate target lives.
|
|
20
|
-
*/
|
|
21
|
-
const METADATA_ADDRESSES = new Set(['100.100.100.200', '192.0.0.192']);
|
|
22
|
-
/**
|
|
23
|
-
* Classifies a hostname that addresses the machine itself, its link-local
|
|
24
|
-
* range, or a cloud metadata service — or returns null for anything routable.
|
|
25
|
-
*
|
|
26
|
-
* It does the job numerically rather than by comparing strings, because the
|
|
27
|
-
* spellings differ more than they look. `URL` canonicalises an IPv4-mapped IPv6
|
|
28
|
-
* literal into hex — `http://[::ffff:127.0.0.1]/` arrives here as
|
|
29
|
-
* `[::ffff:7f00:1]` — while every dual-stack client dials it as plain
|
|
30
|
-
* 127.0.0.1, and `localhost.` with its root label is the same name as
|
|
31
|
-
* `localhost`. String comparison misses both.
|
|
32
|
-
*
|
|
33
|
-
* Takes a hostname from anywhere, not only from `URL`: a resolver hands back
|
|
34
|
-
* `::ffff:127.0.0.1` in dotted form and may attach a `%zone` suffix, so both
|
|
35
|
-
* are handled here rather than assumed away.
|
|
36
|
-
*/
|
|
37
|
-
export function internalHostKind(hostname) {
|
|
38
|
-
const host = bareHost(hostname);
|
|
39
|
-
if (host === 'localhost' || host.endsWith('.localhost'))
|
|
40
|
-
return 'loopback';
|
|
41
|
-
if (METADATA_NAMES.has(host))
|
|
42
|
-
return 'link-local';
|
|
43
|
-
if (METADATA_ADDRESSES.has(host))
|
|
44
|
-
return 'link-local';
|
|
45
|
-
const version = isIP(host);
|
|
46
|
-
if (version === 4)
|
|
47
|
-
return ipv4Kind(host.split('.').map(Number));
|
|
48
|
-
if (version !== 6)
|
|
49
|
-
return null;
|
|
50
|
-
const groups = expandIpv6(host);
|
|
51
|
-
if (groups === null)
|
|
52
|
-
return null;
|
|
53
|
-
const explicit = ipv6Kind(groups);
|
|
54
|
-
if (explicit !== null)
|
|
55
|
-
return explicit;
|
|
56
|
-
const embedded = embeddedIpv4(groups);
|
|
57
|
-
return embedded === null ? null : ipv4Kind(embedded);
|
|
58
|
-
}
|
|
59
|
-
function bareHost(hostname) {
|
|
60
|
-
return (hostname
|
|
61
|
-
.toLowerCase()
|
|
62
|
-
// URL.hostname keeps the brackets around an IPv6 literal, so a bare '::1'
|
|
63
|
-
// would never match.
|
|
64
|
-
.replace(/^\[|]$/g, '')
|
|
65
|
-
// A scope id belongs to the interface, not to the address. `isIP` accepts
|
|
66
|
-
// it, so leaving it on would desynchronise every check below from what
|
|
67
|
-
// `isIP` just agreed was an address.
|
|
68
|
-
.replace(/%.*$/, '')
|
|
69
|
-
// 'localhost.' is the same name as 'localhost' — the root label is what
|
|
70
|
-
// makes it fully qualified, not a different host.
|
|
71
|
-
.replace(/\.+$/, ''));
|
|
72
|
-
}
|
|
73
|
-
/** Expands an IPv6 literal into its eight 16-bit groups. */
|
|
74
|
-
function expandIpv6(address) {
|
|
75
|
-
let text = address;
|
|
76
|
-
// A literal may end in dotted-quad notation (::ffff:127.0.0.1). Fold that tail
|
|
77
|
-
// into the two hex groups it stands for so the rest of this is uniform.
|
|
78
|
-
const dotted = /:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(text);
|
|
79
|
-
if (dotted !== null) {
|
|
80
|
-
const [a = 0, b = 0, c = 0, d = 0] = (dotted[1] ?? '')
|
|
81
|
-
.split('.')
|
|
82
|
-
.map(Number);
|
|
83
|
-
const high = ((a << 8) | b).toString(16);
|
|
84
|
-
const low = ((c << 8) | d).toString(16);
|
|
85
|
-
text = `${text.slice(0, dotted.index)}:${high}:${low}`;
|
|
86
|
-
}
|
|
87
|
-
const [head = '', tail] = text.split('::');
|
|
88
|
-
const left = head === '' ? [] : head.split(':');
|
|
89
|
-
const right = tail === undefined ? null : tail === '' ? [] : tail.split(':');
|
|
90
|
-
if (right === null)
|
|
91
|
-
return toGroups(left);
|
|
92
|
-
const missing = 8 - left.length - right.length;
|
|
93
|
-
if (missing < 0)
|
|
94
|
-
return null;
|
|
95
|
-
return toGroups([...left, ...Array(missing).fill('0'), ...right]);
|
|
96
|
-
}
|
|
97
|
-
function toGroups(groups) {
|
|
98
|
-
if (groups.length !== 8)
|
|
99
|
-
return null;
|
|
100
|
-
// Tested rather than left to `parseInt`, which stops at the first character it
|
|
101
|
-
// does not like and returns a number for '7f00xyz' just as happily.
|
|
102
|
-
if (!groups.every((group) => /^[0-9a-f]{1,4}$/.test(group)))
|
|
103
|
-
return null;
|
|
104
|
-
return groups.map((group) => parseInt(group, 16));
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Extracts the IPv4 address an IPv6 literal carries, for the prefixes whose
|
|
108
|
-
* whole purpose is to stand in for one.
|
|
109
|
-
*/
|
|
110
|
-
function embeddedIpv4(groups) {
|
|
111
|
-
const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0] = groups;
|
|
112
|
-
const carriesIpv4 =
|
|
113
|
-
// ::a.b.c.d (IPv4-compatible) and ::ffff:a.b.c.d (IPv4-mapped, RFC 4291).
|
|
114
|
-
((a | b | c | d | e) === 0 && (f === 0 || f === 0xffff)) ||
|
|
115
|
-
// ::ffff:0:a.b.c.d — IPv4-translated (RFC 2765).
|
|
116
|
-
((a | b | c | d) === 0 && e === 0xffff && f === 0) ||
|
|
117
|
-
// 64:ff9b::a.b.c.d — the well-known NAT64 prefix (RFC 6052).
|
|
118
|
-
(a === 0x64 && b === 0xff9b && (c | d | e | f) === 0);
|
|
119
|
-
return carriesIpv4 ? [g >> 8, g & 0xff, h >> 8, h & 0xff] : null;
|
|
120
|
-
}
|
|
121
|
-
function ipv4Kind(octets) {
|
|
122
|
-
const [a = 0, b = 0] = octets;
|
|
123
|
-
// 0.0.0.0/8 ('this host', RFC 1122) and 127/8 both reach the machine itself.
|
|
124
|
-
if (a === 0 || a === 127)
|
|
125
|
-
return 'loopback';
|
|
126
|
-
// 169.254/16, which holds 169.254.169.254 — the AWS/GCP/Azure metadata service.
|
|
127
|
-
if (a === 169 && b === 254)
|
|
128
|
-
return 'link-local';
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
function ipv6Kind(groups) {
|
|
132
|
-
// `::` and `::1` are also caught by the IPv4-compatible unwrapping below, but
|
|
133
|
-
// they are named here so that removing that unwrapping cannot silently take
|
|
134
|
-
// the two most obvious loopback literals with it.
|
|
135
|
-
if (groups.every((group) => group === 0))
|
|
136
|
-
return 'loopback'; // ::
|
|
137
|
-
if (groups.slice(0, 7).every((group) => group === 0) && groups[7] === 1) {
|
|
138
|
-
return 'loopback'; // ::1
|
|
139
|
-
}
|
|
140
|
-
if (((groups[0] ?? 0) & 0xffc0) === 0xfe80)
|
|
141
|
-
return 'link-local'; // fe80::/10
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
//# sourceMappingURL=hosts.js.map
|
package/dist/hosts.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hosts.js","sourceRoot":"","sources":["../src/hosts.ts"],"names":[],"mappings":"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"}
|
package/dist/tool-filter.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
/**
|
|
3
|
-
* A tool list that names something this server does not have.
|
|
4
|
-
*
|
|
5
|
-
* Thrown rather than `process.exit(1)`: `createServer` is called in-process by
|
|
6
|
-
* the tests, and an exiting constructor cannot be tested. `src/index.ts` turns
|
|
7
|
-
* it back into an exit code.
|
|
8
|
-
*/
|
|
9
|
-
export declare class ToolFilterError extends Error {
|
|
10
|
-
constructor(message: string);
|
|
11
|
-
}
|
|
12
|
-
export interface ToolFilter {
|
|
13
|
-
/** False when neither variable was set — then nothing is wrapped at all. */
|
|
14
|
-
readonly active: boolean;
|
|
15
|
-
/** The tools that survive. Only meaningful while `active`. */
|
|
16
|
-
readonly selected: ReadonlySet<string>;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Reads `CALIBRE_WEB_ALLOW_TOOLS` / `CALIBRE_WEB_DENY_TOOLS` and works out which
|
|
20
|
-
* tools survive.
|
|
21
|
-
*
|
|
22
|
-
* Every entry has to match at least one tool in the catalogue. An entry that
|
|
23
|
-
* matches nothing is fatal rather than ignored, because the failure it produces
|
|
24
|
-
* otherwise — a tool quietly missing from `tools/list` — is invisible: nobody
|
|
25
|
-
* looks for the cause of an absence in an environment variable.
|
|
26
|
-
*/
|
|
27
|
-
export declare function buildToolFilter(config: {
|
|
28
|
-
allowTools: string | undefined;
|
|
29
|
-
denyTools: string | undefined;
|
|
30
|
-
}): ToolFilter;
|
|
31
|
-
/**
|
|
32
|
-
* Makes `server` register only the tools the filter selected.
|
|
33
|
-
*
|
|
34
|
-
* The tool is registered and then removed again rather than skipped. Skipping
|
|
35
|
-
* looks cheaper and breaks one case: the SDK installs its `tools/list` handler
|
|
36
|
-
* from inside the registration path, so a server whose every tool was skipped
|
|
37
|
-
* would answer `tools/list` with "method not found" instead of an empty list.
|
|
38
|
-
* `remove()` deletes the entry from the SDK's tool map outright, which makes a
|
|
39
|
-
* filtered tool answer `Tool X not found`. `disable()` would be wrong: it hides
|
|
40
|
-
* the tool from `tools/list` but still answers a call with "disabled", which is
|
|
41
|
-
* the advertising-a-refusal this server avoids everywhere else.
|
|
42
|
-
*/
|
|
43
|
-
export declare function installToolFilter(server: McpServer, filter: ToolFilter): void;
|
package/dist/tool-filter.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { ALL_TOOLS, ESSENTIAL_TOOLS } from './tools/catalogue.js';
|
|
2
|
-
/**
|
|
3
|
-
* A tool list that names something this server does not have.
|
|
4
|
-
*
|
|
5
|
-
* Thrown rather than `process.exit(1)`: `createServer` is called in-process by
|
|
6
|
-
* the tests, and an exiting constructor cannot be tested. `src/index.ts` turns
|
|
7
|
-
* it back into an exit code.
|
|
8
|
-
*/
|
|
9
|
-
export class ToolFilterError extends Error {
|
|
10
|
-
constructor(message) {
|
|
11
|
-
super(message);
|
|
12
|
-
this.name = 'ToolFilterError';
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
/** The `essential` preset is spelled out here so it cannot collide with a tool name. */
|
|
16
|
-
const PRESET = 'essential';
|
|
17
|
-
/**
|
|
18
|
-
* Splits a comma-separated value into entries.
|
|
19
|
-
*
|
|
20
|
-
* Empty entries are dropped, so `a,,b` and a trailing comma are both fine, and
|
|
21
|
-
* a value that is empty or only whitespace counts as *unset* — `X_ALLOW_TOOLS=`
|
|
22
|
-
* in a compose file must not mean "allow nothing". Entries are lowercased: the
|
|
23
|
-
* catalogue is entirely lowercase, so this is lossless, and a shell that
|
|
24
|
-
* upper-cased a name should not take the server down.
|
|
25
|
-
*/
|
|
26
|
-
function entriesOf(raw) {
|
|
27
|
-
if (raw === undefined)
|
|
28
|
-
return undefined;
|
|
29
|
-
const entries = raw
|
|
30
|
-
.split(',')
|
|
31
|
-
.map((entry) => entry.trim().toLowerCase())
|
|
32
|
-
.filter((entry) => entry.length > 0);
|
|
33
|
-
return entries.length > 0 ? entries : undefined;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Expands one entry to the catalogue tools it names.
|
|
37
|
-
*
|
|
38
|
-
* A pattern is a literal prefix plus exactly one trailing `*`. Anything else is
|
|
39
|
-
* rejected outright: `*_thing` and `list_*_x` look plausible, match nothing, and
|
|
40
|
-
* would otherwise be silent forever.
|
|
41
|
-
*/
|
|
42
|
-
function expand(entry, variable) {
|
|
43
|
-
const star = entry.indexOf('*');
|
|
44
|
-
if (star !== -1) {
|
|
45
|
-
if (star !== entry.length - 1) {
|
|
46
|
-
throw new ToolFilterError(`${variable}: "${entry}" is not a valid entry — a pattern is a prefix ` +
|
|
47
|
-
'followed by a single trailing "*", for example "list_*". Everything ' +
|
|
48
|
-
'else is an exact tool name.');
|
|
49
|
-
}
|
|
50
|
-
const prefix = entry.slice(0, -1);
|
|
51
|
-
return ALL_TOOLS.filter((tool) => tool.startsWith(prefix));
|
|
52
|
-
}
|
|
53
|
-
return ALL_TOOLS.filter((tool) => tool === entry);
|
|
54
|
-
}
|
|
55
|
-
/** The full catalogue, for the "these are the names that exist" half of an error. */
|
|
56
|
-
function catalogueList() {
|
|
57
|
-
return [...ALL_TOOLS].sort().join(', ');
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Reads `CALIBRE_WEB_ALLOW_TOOLS` / `CALIBRE_WEB_DENY_TOOLS` and works out which
|
|
61
|
-
* tools survive.
|
|
62
|
-
*
|
|
63
|
-
* Every entry has to match at least one tool in the catalogue. An entry that
|
|
64
|
-
* matches nothing is fatal rather than ignored, because the failure it produces
|
|
65
|
-
* otherwise — a tool quietly missing from `tools/list` — is invisible: nobody
|
|
66
|
-
* looks for the cause of an absence in an environment variable.
|
|
67
|
-
*/
|
|
68
|
-
export function buildToolFilter(config) {
|
|
69
|
-
const allow = entriesOf(config.allowTools);
|
|
70
|
-
const deny = entriesOf(config.denyTools);
|
|
71
|
-
if (allow === undefined && deny === undefined) {
|
|
72
|
-
return { active: false, selected: new Set() };
|
|
73
|
-
}
|
|
74
|
-
let selected;
|
|
75
|
-
if (allow === undefined) {
|
|
76
|
-
selected = new Set(ALL_TOOLS);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
selected = new Set();
|
|
80
|
-
for (const entry of allow) {
|
|
81
|
-
if (entry === PRESET) {
|
|
82
|
-
for (const tool of ESSENTIAL_TOOLS)
|
|
83
|
-
selected.add(tool);
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const matches = expand(entry, 'CALIBRE_WEB_ALLOW_TOOLS');
|
|
87
|
-
if (matches.length === 0) {
|
|
88
|
-
throw new ToolFilterError(`CALIBRE_WEB_ALLOW_TOOLS: no tool matches "${entry}". ` +
|
|
89
|
-
`Valid tools: ${catalogueList()}. "${PRESET}" selects the curated preset.`);
|
|
90
|
-
}
|
|
91
|
-
for (const tool of matches)
|
|
92
|
-
selected.add(tool);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
for (const entry of deny ?? []) {
|
|
96
|
-
const matches = expand(entry, 'CALIBRE_WEB_DENY_TOOLS');
|
|
97
|
-
if (matches.length === 0) {
|
|
98
|
-
throw new ToolFilterError(`CALIBRE_WEB_DENY_TOOLS: no tool matches "${entry}". Valid tools: ${catalogueList()}.`);
|
|
99
|
-
}
|
|
100
|
-
for (const tool of matches)
|
|
101
|
-
selected.delete(tool);
|
|
102
|
-
}
|
|
103
|
-
if (selected.size === 0) {
|
|
104
|
-
throw new ToolFilterError('CALIBRE_WEB_ALLOW_TOOLS/CALIBRE_WEB_DENY_TOOLS leave no tools registered — the ' +
|
|
105
|
-
'server would start with an empty tool list.');
|
|
106
|
-
}
|
|
107
|
-
return { active: true, selected };
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Makes `server` register only the tools the filter selected.
|
|
111
|
-
*
|
|
112
|
-
* The tool is registered and then removed again rather than skipped. Skipping
|
|
113
|
-
* looks cheaper and breaks one case: the SDK installs its `tools/list` handler
|
|
114
|
-
* from inside the registration path, so a server whose every tool was skipped
|
|
115
|
-
* would answer `tools/list` with "method not found" instead of an empty list.
|
|
116
|
-
* `remove()` deletes the entry from the SDK's tool map outright, which makes a
|
|
117
|
-
* filtered tool answer `Tool X not found`. `disable()` would be wrong: it hides
|
|
118
|
-
* the tool from `tools/list` but still answers a call with "disabled", which is
|
|
119
|
-
* the advertising-a-refusal this server avoids everywhere else.
|
|
120
|
-
*/
|
|
121
|
-
export function installToolFilter(server, filter) {
|
|
122
|
-
if (!filter.active)
|
|
123
|
-
return;
|
|
124
|
-
const register = server.registerTool.bind(server);
|
|
125
|
-
// An object literal rather than a plain function so the method picks up the
|
|
126
|
-
// SDK's generic signature by contextual typing — the call sites keep their
|
|
127
|
-
// typed handler arguments and nothing needs a cast.
|
|
128
|
-
const wrapper = {
|
|
129
|
-
registerTool(name, config, cb) {
|
|
130
|
-
const tool = register(name, config, cb);
|
|
131
|
-
if (!filter.selected.has(name))
|
|
132
|
-
tool.remove();
|
|
133
|
-
return tool;
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
server.registerTool = wrapper.registerTool;
|
|
137
|
-
}
|
|
138
|
-
//# sourceMappingURL=tool-filter.js.map
|
package/dist/tool-filter.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tool-filter.js","sourceRoot":"","sources":["../src/tool-filter.ts"],"names":[],"mappings":"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"}
|