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/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://github.com/ni-c/calibreweb-mcp/pkgs/container/calibreweb-mcp)
|
|
9
9
|
[](https://calibreweb-mcp.ni-c.de)
|
|
10
|
+
[](https://mcp-hub.ni-c.de)
|
|
10
11
|
[](https://github.com/sponsors/ni-c)
|
|
11
12
|
|
|
12
13
|
A read-only [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for
|
|
@@ -42,6 +43,18 @@ HTTP Basic auth in, structured book data out.
|
|
|
42
43
|
|
|
43
44
|

|
|
44
45
|
|
|
46
|
+
## What makes it different
|
|
47
|
+
|
|
48
|
+
**The API Calibre-Web never had.** Calibre-Web exposes no REST API — its only
|
|
49
|
+
stable machine interface is the OPDS Atom feed built for e-reader apps. These
|
|
50
|
+
tools parse that feed into structured book data with numeric ids, per-format
|
|
51
|
+
download URLs and bounded summaries.
|
|
52
|
+
|
|
53
|
+
**Read-only by construction.** All six tools are GETs. Redirects are refused so
|
|
54
|
+
Basic credentials never travel, XML carrying a DOCTYPE is rejected outright,
|
|
55
|
+
hrefs are locked to the configured origin, and metadata is marked as the
|
|
56
|
+
untrusted data it is.
|
|
57
|
+
|
|
45
58
|
## Requirements
|
|
46
59
|
|
|
47
60
|
- Node.js 22 or newer
|
|
@@ -66,6 +79,29 @@ HTTP Basic auth in, structured book data out.
|
|
|
66
79
|
¹ Leave **both** unset for an instance that allows anonymous browsing; setting
|
|
67
80
|
only one of them is a configuration error.
|
|
68
81
|
|
|
82
|
+
### Choosing which tools load
|
|
83
|
+
|
|
84
|
+
`CALIBRE_WEB_ALLOW_TOOLS` and `CALIBRE_WEB_DENY_TOOLS` take comma-separated tool names;
|
|
85
|
+
a trailing `*` matches a whole family. `essential` is a curated preset of
|
|
86
|
+
five: `search_books`, `list_books`, `list_shelves`, `get_shelf_books`, `get_stats`.
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
CALIBRE_WEB_ALLOW_TOOLS=essential
|
|
90
|
+
CALIBRE_WEB_ALLOW_TOOLS=search_books,list_shelves
|
|
91
|
+
CALIBRE_WEB_DENY_TOOLS=get_cover
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
An entry that matches no tool aborts startup and names it, so a typo cannot
|
|
95
|
+
silently hide a tool — an absent tool is not something anyone traces back to an
|
|
96
|
+
environment variable. A filtered tool is never registered, so it is absent from
|
|
97
|
+
`tools/list` and unknown to `tools/call` alike.
|
|
98
|
+
|
|
99
|
+
If you run several of these servers at once, [mcp-hub](https://mcp-hub.ni-c.de)
|
|
100
|
+
is the other answer — its `/hub` endpoint replaces every server's tools with six
|
|
101
|
+
meta-tools.
|
|
102
|
+
|
|
103
|
+
## Installation
|
|
104
|
+
|
|
69
105
|
### Claude Code
|
|
70
106
|
|
|
71
107
|
```sh
|
|
@@ -103,6 +139,46 @@ args = ["calibreweb-mcp"]
|
|
|
103
139
|
env = { CALIBRE_WEB_URL = "https://books.example.com", CALIBRE_WEB_USERNAME = "reader", CALIBRE_WEB_PASSWORD = "..." }
|
|
104
140
|
```
|
|
105
141
|
|
|
142
|
+
### Docker
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
docker run -i --rm \
|
|
146
|
+
-e CALIBRE_WEB_URL=https://books.example.com \
|
|
147
|
+
-e CALIBRE_WEB_USERNAME=reader \
|
|
148
|
+
-e CALIBRE_WEB_PASSWORD=... \
|
|
149
|
+
ghcr.io/ni-c/calibreweb-mcp
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Through mcp-hub
|
|
153
|
+
|
|
154
|
+
A client that cannot spawn a local process — ChatGPT connectors, Claude on the web,
|
|
155
|
+
Cursor, LibreChat — reaches calibreweb-mcp through [mcp-hub](https://mcp-hub.ni-c.de): one
|
|
156
|
+
container serves many stdio MCP servers over Streamable HTTP, with an OAuth 2.1 login
|
|
157
|
+
behind a single password and long-lived tokens for the clients that cannot do OAuth. Its
|
|
158
|
+
`/hub` endpoint puts every server behind six meta-tools, so one connector reaches all of
|
|
159
|
+
them without N×tool schemas in the model's context, and it speaks both protocol revisions
|
|
160
|
+
— a question this server asks travels through it to the person at the far end.
|
|
161
|
+
|
|
162
|
+
Its `/config/mcp.json` uses Claude Code's format, so the entry is the one you already
|
|
163
|
+
have:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"calibreweb": {
|
|
169
|
+
"command": "npx",
|
|
170
|
+
"args": ["-y", "calibreweb-mcp"],
|
|
171
|
+
"env": { "CALIBRE_WEB_ALLOW_TOOLS": "essential" },
|
|
172
|
+
"denyTools": ["get_cover"]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`allowTools` and `denyTools` there are the hub's **own** per-server filter, which is not
|
|
179
|
+
the same thing as `*_ALLOW_TOOLS` in `env` — the difference, and the mistake it invites,
|
|
180
|
+
are in the [client guide](https://calibreweb-mcp.ni-c.de/guide/clients#through-mcp-hub).
|
|
181
|
+
|
|
106
182
|
## Tools
|
|
107
183
|
|
|
108
184
|
All tools are read-only (`readOnlyHint: true`).
|
|
@@ -120,6 +196,34 @@ Book entries include authors, tags, series (with index), rating, a bounded
|
|
|
120
196
|
summary, a cover URL and per-format download URLs — ready-made links a human can
|
|
121
197
|
open, since the model itself has no reason to download an EPUB.
|
|
122
198
|
|
|
199
|
+
### Structured output
|
|
200
|
+
|
|
201
|
+
Every tool declares an `outputSchema` and answers with `structuredContent`
|
|
202
|
+
alongside the text block, so a client can use the result without parsing prose:
|
|
203
|
+
|
|
204
|
+
```jsonc
|
|
205
|
+
{
|
|
206
|
+
"untrusted": true,
|
|
207
|
+
"source": "calibre-web",
|
|
208
|
+
"totalFound": 2,
|
|
209
|
+
"truncated": false,
|
|
210
|
+
"books": [{ "id": 7, "title": "Dune", "authors": ["Frank Herbert"] }],
|
|
211
|
+
"notes": ["Book titles, authors, tags, series and summaries come from …"],
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The `untrusted` marker is a field and not only a line in `notes`, because a
|
|
216
|
+
client that reads the structured half would otherwise have to find the warning
|
|
217
|
+
in a list of sentences. The two tools without it are `get_stats`, which is four
|
|
218
|
+
counters this server has checked are numbers, and `get_cover`, which reports an
|
|
219
|
+
id, a media type from a four-entry allowlist and a byte count — the image itself
|
|
220
|
+
stays in the content block where a client renders it.
|
|
221
|
+
|
|
222
|
+
An over-budget result drops book summaries as before. Where that is still not
|
|
223
|
+
enough it is now an **error** rather than JSON cut at the ceiling: unparseable
|
|
224
|
+
text was tolerable in a text block and is not something `structuredContent` can
|
|
225
|
+
carry, and the two channels have to hold the same value.
|
|
226
|
+
|
|
123
227
|
### Pagination
|
|
124
228
|
|
|
125
229
|
Feeds are paginated by the instance's _books per page_ setting (default 60); the
|
|
@@ -127,7 +231,7 @@ page size is not client-controllable. Every listing returns
|
|
|
127
231
|
`pagination.nextOffset` when more pages exist — pass it as `offset` in the next
|
|
128
232
|
call. The `discover` view is a random selection and not paginated.
|
|
129
233
|
|
|
130
|
-
|
|
234
|
+
## Not exposed, on purpose
|
|
131
235
|
|
|
132
236
|
- **No writes.** The OPDS feed has none, and this server would not add any.
|
|
133
237
|
- **No file downloads.** Tools return download URLs, not ebook payloads.
|
|
@@ -151,15 +255,10 @@ call. The `discover` view is a random selection and not paginated.
|
|
|
151
255
|
- The password is scrubbed from the process environment at startup, and URLs
|
|
152
256
|
are credential-redacted before they appear in any log or result.
|
|
153
257
|
|
|
154
|
-
##
|
|
258
|
+
## Documentation
|
|
155
259
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
-e CALIBRE_WEB_URL=https://books.example.com \
|
|
159
|
-
-e CALIBRE_WEB_USERNAME=reader \
|
|
160
|
-
-e CALIBRE_WEB_PASSWORD=... \
|
|
161
|
-
ghcr.io/ni-c/calibreweb-mcp
|
|
162
|
-
```
|
|
260
|
+
The full guide, tool reference and security notes live at
|
|
261
|
+
**[calibreweb-mcp.ni-c.de](https://calibreweb-mcp.ni-c.de)** (source in [`docs/`](docs/)).
|
|
163
262
|
|
|
164
263
|
## Development
|
|
165
264
|
|
|
@@ -178,27 +277,13 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
178
277
|
2. `npm run lint && npm run test:coverage && npm run build`
|
|
179
278
|
3. Tag the release: `git tag -s vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z`
|
|
180
279
|
|
|
181
|
-
##
|
|
182
|
-
|
|
183
|
-
[MIT](LICENSE)
|
|
280
|
+
## Contributing
|
|
184
281
|
|
|
185
|
-
|
|
282
|
+
Issues, discussions and pull requests are welcome — see
|
|
283
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). For vulnerabilities please use
|
|
284
|
+
[private reporting](https://github.com/ni-c/calibreweb-mcp/security/advisories/new)
|
|
285
|
+
rather than a public issue; the policy is in [SECURITY.md](SECURITY.md).
|
|
186
286
|
|
|
187
|
-
|
|
188
|
-
a trailing `*` matches a whole family. `essential` is a curated preset of
|
|
189
|
-
five: `search_books`, `list_books`, `list_shelves`, `get_shelf_books`, `get_stats`.
|
|
190
|
-
|
|
191
|
-
```sh
|
|
192
|
-
CALIBRE_WEB_ALLOW_TOOLS=essential
|
|
193
|
-
CALIBRE_WEB_ALLOW_TOOLS=search_books,list_shelves
|
|
194
|
-
CALIBRE_WEB_DENY_TOOLS=get_cover
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
An entry that matches no tool aborts startup and names it, so a typo cannot
|
|
198
|
-
silently hide a tool — an absent tool is not something anyone traces back to an
|
|
199
|
-
environment variable. A filtered tool is never registered, so it is absent from
|
|
200
|
-
`tools/list` and unknown to `tools/call` alike.
|
|
287
|
+
## License
|
|
201
288
|
|
|
202
|
-
|
|
203
|
-
is the other answer — its `/hub` endpoint replaces every server's tools with six
|
|
204
|
-
meta-tools.
|
|
289
|
+
[MIT](LICENSE) © Willi Thiel
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACL,KAAK,EACL,KAAK,IAAI,WAAW,GAErB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,aAAa,CAAC;AAErB,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAExC,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEzB;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { internalHostKind } from '
|
|
1
|
+
import { internalHostKind } from 'mcp-internal-hosts';
|
|
2
2
|
import { redactUrlCredentials } from './redact.js';
|
|
3
3
|
/** Shown when the configuration is incomplete — at startup and on every API call. */
|
|
4
4
|
export function missingConfigMessage(missing) {
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
3
3
|
import { loadConfig } from './config.js';
|
|
4
4
|
import { createServer } from './server.js';
|
|
5
|
-
import { ToolFilterError } from '
|
|
5
|
+
import { ToolFilterError } from 'mcp-tool-allowlist';
|
|
6
6
|
async function main() {
|
|
7
7
|
const config = loadConfig();
|
|
8
8
|
if (config.insecureTls) {
|
|
9
9
|
console.error('calibreweb-mcp: CALIBRE_WEB_INSECURE_TLS=true — TLS certificate validation is disabled for the Calibre-Web connection');
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
// Built before anything is served, so a rejected tool filter still ends
|
|
12
|
+
// the process rather than surfacing as a failed handshake once a client
|
|
13
|
+
// has already connected.
|
|
14
|
+
let pending;
|
|
12
15
|
try {
|
|
13
|
-
|
|
16
|
+
pending = createServer(config);
|
|
14
17
|
}
|
|
15
18
|
catch (error) {
|
|
16
19
|
// A bad tool list is operator feedback, not a crash: print the
|
|
@@ -22,11 +25,28 @@ async function main() {
|
|
|
22
25
|
throw error;
|
|
23
26
|
}
|
|
24
27
|
// stdout belongs to the protocol; everything human-readable goes to stderr.
|
|
25
|
-
|
|
28
|
+
// `serveStdio` owns the era decision for the connection: the opening
|
|
29
|
+
// exchange selects 2025-11-25 or 2026-07-28 and pins one instance from
|
|
30
|
+
// this factory for its lifetime. A hand-wired `StdioServerTransport`
|
|
31
|
+
// serves only the 2025 era, which is why a negotiating client’s
|
|
32
|
+
// `server/discover` probe was answered with "Method not found".
|
|
33
|
+
//
|
|
34
|
+
// The instance built above serves the first connection; a second call — a
|
|
35
|
+
// modern probe followed by the real connection — builds a fresh one, which
|
|
36
|
+
// is safe because `createServer` only registers tools.
|
|
37
|
+
serveStdio(() => {
|
|
38
|
+
const server = pending ?? createServer(config);
|
|
39
|
+
pending = undefined;
|
|
40
|
+
return server;
|
|
41
|
+
});
|
|
26
42
|
console.error(config.url
|
|
27
43
|
? `calibreweb-mcp: connected, targeting ${config.url}`
|
|
28
44
|
: 'calibreweb-mcp: connected without configuration — tools are listed but every call will fail');
|
|
29
45
|
}
|
|
46
|
+
// In a container node runs as PID 1 with no default signal disposition, so
|
|
47
|
+
// without this handler `docker stop` waits out the grace period and SIGKILLs.
|
|
48
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
49
|
+
process.on('SIGINT', () => process.exit(0));
|
|
30
50
|
main().catch((error) => {
|
|
31
51
|
console.error('calibreweb-mcp: fatal error:', error);
|
|
32
52
|
process.exit(1);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAEhE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CACX,uHAAuH,CACxH,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,wEAAwE;IACxE,yBAAyB;IACzB,IAAI,OAA8B,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,+DAA+D;QAC/D,yDAAyD;QACzD,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;YACrC,OAAO,CAAC,KAAK,CAAC,mBAAmB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,4EAA4E;IAC5E,qEAAqE;IACrE,uEAAuE;IACvE,qEAAqE;IACrE,gEAAgE;IAChE,gEAAgE;IAChE,EAAE;IACF,0EAA0E;IAC1E,2EAA2E;IAC3E,uDAAuD;IACvD,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,MAAM,GAAG,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;QAC/C,OAAO,GAAG,SAAS,CAAC;QACpB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,CACX,MAAM,CAAC,GAAG;QACR,CAAC,CAAC,wCAAwC,MAAM,CAAC,GAAG,EAAE;QACtD,CAAC,CAAC,6FAA6F,CAClG,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,8EAA8E;AAC9E,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7C,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5C,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/result.d.ts
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
|
-
import type { CallToolResult } from '@modelcontextprotocol/
|
|
1
|
+
import type { CallToolResult } from '@modelcontextprotocol/server';
|
|
2
2
|
export declare function textResult(text: string): CallToolResult;
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* pathologically large after the per-tool truncation.
|
|
4
|
+
* An answer in both channels at once, with book summaries stripped if the
|
|
5
|
+
* payload is still pathologically large after the per-tool truncation.
|
|
6
6
|
*
|
|
7
7
|
* A Calibre library can hold book descriptions of arbitrary length, and the
|
|
8
8
|
* OPDS search endpoint returns every match in one feed. Everything downstream
|
|
9
9
|
* of this function assumes the budget held; this is what guarantees it.
|
|
10
|
+
*
|
|
11
|
+
* `structuredContent` is the machine-readable half and the reason every tool
|
|
12
|
+
* here declares an `outputSchema`; the text block stays because the SDK does
|
|
13
|
+
* NOT synthesize one for an object-shaped value, and a client that reads only
|
|
14
|
+
* `content` would otherwise get an empty answer. Both carry the same object —
|
|
15
|
+
* which is why the over-budget path rebuilds the *value* rather than editing
|
|
16
|
+
* its serialization.
|
|
10
17
|
*/
|
|
11
|
-
export declare function jsonResult(data: unknown): CallToolResult;
|
|
18
|
+
export declare function jsonResult(data: Record<string, unknown>): CallToolResult;
|
|
19
|
+
/**
|
|
20
|
+
* {@link jsonResult}, with the untrusted-content marker on the object.
|
|
21
|
+
*
|
|
22
|
+
* The note has always gone out in `notes`, which is in the text block and in
|
|
23
|
+
* the structured half alike. The two fields are what a client reading only
|
|
24
|
+
* `structuredContent` can *check* rather than have to find in a list of
|
|
25
|
+
* sentences — and they are stripped from the payload before they are set, so
|
|
26
|
+
* the guard cannot be switched off by the content it guards against.
|
|
27
|
+
*/
|
|
28
|
+
export declare function untrustedResult(data: Record<string, unknown>): CallToolResult;
|
|
29
|
+
/** Raised by {@link jsonResult}; `run` turns it into an error result. */
|
|
30
|
+
export declare class ResultTooLargeError extends Error {
|
|
31
|
+
}
|
|
32
|
+
/** A value in both channels, with no budget applied. */
|
|
33
|
+
export declare function structuredResult(data: Record<string, unknown>): CallToolResult;
|
|
12
34
|
export declare function errorResult(text: string): CallToolResult;
|
|
13
35
|
/** Thrown by tools for problems detected before any request goes out. */
|
|
14
36
|
export declare class ToolInputError extends Error {
|
package/dist/result.js
CHANGED
|
@@ -9,31 +9,72 @@ export function textResult(text) {
|
|
|
9
9
|
*/
|
|
10
10
|
const MAX_RESULT_BYTES = 400_000;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* pathologically large after the per-tool truncation.
|
|
12
|
+
* An answer in both channels at once, with book summaries stripped if the
|
|
13
|
+
* payload is still pathologically large after the per-tool truncation.
|
|
14
14
|
*
|
|
15
15
|
* A Calibre library can hold book descriptions of arbitrary length, and the
|
|
16
16
|
* OPDS search endpoint returns every match in one feed. Everything downstream
|
|
17
17
|
* of this function assumes the budget held; this is what guarantees it.
|
|
18
|
+
*
|
|
19
|
+
* `structuredContent` is the machine-readable half and the reason every tool
|
|
20
|
+
* here declares an `outputSchema`; the text block stays because the SDK does
|
|
21
|
+
* NOT synthesize one for an object-shaped value, and a client that reads only
|
|
22
|
+
* `content` would otherwise get an empty answer. Both carry the same object —
|
|
23
|
+
* which is why the over-budget path rebuilds the *value* rather than editing
|
|
24
|
+
* its serialization.
|
|
18
25
|
*/
|
|
19
26
|
export function jsonResult(data) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const stripped = JSON.stringify(data, (key, value) => key === 'summary' && typeof value === 'string'
|
|
27
|
+
if (JSON.stringify(data).length <= MAX_RESULT_BYTES) {
|
|
28
|
+
return structuredResult(data);
|
|
29
|
+
}
|
|
30
|
+
const stripped = JSON.parse(JSON.stringify(data, (key, value) => key === 'summary' && typeof value === 'string'
|
|
24
31
|
? '(omitted: result too large)'
|
|
25
|
-
: value
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
: value));
|
|
33
|
+
if (JSON.stringify(stripped).length <= MAX_RESULT_BYTES) {
|
|
34
|
+
return structuredResult({
|
|
35
|
+
...stripped,
|
|
36
|
+
notes: [
|
|
37
|
+
...(Array.isArray(stripped.notes) ? stripped.notes : []),
|
|
38
|
+
`The result exceeded ${MAX_RESULT_BYTES} characters, so book summaries were dropped. Narrow the request to get them back.`,
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// Dropping summaries is not always enough: the bulk can sit in fields the
|
|
30
43
|
// replacer does not touch — a feed of thousands of books is all titles and
|
|
31
|
-
// URLs.
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'
|
|
44
|
+
// URLs. This used to answer with the JSON cut at the ceiling, unparseable but
|
|
45
|
+
// visible. That is no longer an option: `structuredContent` has to parse, and
|
|
46
|
+
// the two channels have to carry the same value. So it is an error, which is
|
|
47
|
+
// the honest description of "there is no answer this size".
|
|
48
|
+
throw new ResultTooLargeError(`The result exceeds ${MAX_RESULT_BYTES} characters even without book ` +
|
|
49
|
+
'summaries. Narrow the request — use a more specific query, a lower ' +
|
|
50
|
+
'limit, or the offset parameter.');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* {@link jsonResult}, with the untrusted-content marker on the object.
|
|
54
|
+
*
|
|
55
|
+
* The note has always gone out in `notes`, which is in the text block and in
|
|
56
|
+
* the structured half alike. The two fields are what a client reading only
|
|
57
|
+
* `structuredContent` can *check* rather than have to find in a list of
|
|
58
|
+
* sentences — and they are stripped from the payload before they are set, so
|
|
59
|
+
* the guard cannot be switched off by the content it guards against.
|
|
60
|
+
*/
|
|
61
|
+
export function untrustedResult(data) {
|
|
62
|
+
const { untrusted: _untrusted, source: _source, ...rest } = data;
|
|
63
|
+
return jsonResult({
|
|
64
|
+
untrusted: true,
|
|
65
|
+
source: 'calibre-web',
|
|
66
|
+
...rest,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Raised by {@link jsonResult}; `run` turns it into an error result. */
|
|
70
|
+
export class ResultTooLargeError extends Error {
|
|
71
|
+
}
|
|
72
|
+
/** A value in both channels, with no budget applied. */
|
|
73
|
+
export function structuredResult(data) {
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
76
|
+
structuredContent: data,
|
|
77
|
+
};
|
|
37
78
|
}
|
|
38
79
|
export function errorResult(text) {
|
|
39
80
|
return { content: [{ type: 'text', text }], isError: true };
|
|
@@ -51,7 +92,10 @@ const UNSAFE_CHARS =
|
|
|
51
92
|
*/
|
|
52
93
|
function sanitizeErrorBody(body) {
|
|
53
94
|
const trimmed = body.replace(UNSAFE_CHARS, '').trim();
|
|
54
|
-
|
|
95
|
+
// Anything markup-shaped: a reverse proxy's error page or a WAF block page.
|
|
96
|
+
// The check is deliberately loose — an XML declaration, a leading comment or
|
|
97
|
+
// a doctype followed by a newline are all the same thing here.
|
|
98
|
+
if (/^(<!doctype|<html[\s>]|<\?xml|<!--)/i.test(trimmed)) {
|
|
55
99
|
return '(HTML error page omitted)';
|
|
56
100
|
}
|
|
57
101
|
if (trimmed.length > MAX_ERROR_BODY_LENGTH) {
|
|
@@ -92,7 +136,8 @@ export async function run(fn) {
|
|
|
92
136
|
return await fn();
|
|
93
137
|
}
|
|
94
138
|
catch (error) {
|
|
95
|
-
if (error instanceof ToolInputError
|
|
139
|
+
if (error instanceof ToolInputError ||
|
|
140
|
+
error instanceof ResultTooLargeError) {
|
|
96
141
|
return errorResult(error.message);
|
|
97
142
|
}
|
|
98
143
|
if (error instanceof CalibreWebApiError) {
|
package/dist/result.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.js","sourceRoot":"","sources":["../src/result.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEjC
|
|
1
|
+
{"version":3,"file":"result.js","sourceRoot":"","sources":["../src/result.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
3
|
+
import { buildToolFilter, installToolFilter } from 'mcp-tool-allowlist';
|
|
4
|
+
import { ALL_TOOLS, ESSENTIAL_TOOLS } from './tools/catalogue.js';
|
|
3
5
|
import { CalibreWebApi } from './api.js';
|
|
4
|
-
import { buildToolFilter, installToolFilter } from './tool-filter.js';
|
|
5
6
|
import { registerBookTools } from './tools/books.js';
|
|
6
7
|
import { registerCoverTools } from './tools/covers.js';
|
|
7
8
|
import { registerShelfTools } from './tools/shelves.js';
|
|
@@ -19,7 +20,19 @@ function packageVersion() {
|
|
|
19
20
|
export function createServer(config) {
|
|
20
21
|
// Before anything is built: an unusable tool list should fail on the
|
|
21
22
|
// way in, not leave a server running with tools quietly missing.
|
|
22
|
-
const filter = buildToolFilter(
|
|
23
|
+
const filter = buildToolFilter({
|
|
24
|
+
allowTools: config.allowTools,
|
|
25
|
+
denyTools: config.denyTools,
|
|
26
|
+
catalogue: {
|
|
27
|
+
all: ALL_TOOLS,
|
|
28
|
+
essential: ESSENTIAL_TOOLS,
|
|
29
|
+
},
|
|
30
|
+
names: {
|
|
31
|
+
allow: 'CALIBRE_WEB_ALLOW_TOOLS',
|
|
32
|
+
deny: 'CALIBRE_WEB_DENY_TOOLS',
|
|
33
|
+
server: 'calibreweb-mcp',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
23
36
|
const api = new CalibreWebApi(config);
|
|
24
37
|
const server = new McpServer({
|
|
25
38
|
name: 'calibreweb-mcp',
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAExE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAwB,CAAC;QAC9D,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,qEAAqE;IACrE,iEAAiE;IACjE,MAAM,MAAM,GAAG,eAAe,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS,EAAE;YACT,GAAG,EAAE,SAAS;YACd,SAAS,EAAE,eAAe;SAC3B;QACD,KAAK,EAAE;YACL,KAAK,EAAE,yBAAyB;YAChC,IAAI,EAAE,wBAAwB;YAC9B,MAAM,EAAE,gBAAgB;SACzB;KACF,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;IAEtC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,cAAc,EAAE;KAC1B,CAAC,CAAC;IAEH,+DAA+D;IAC/D,0DAA0D;IAC1D,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAElC,2EAA2E;IAC3E,qEAAqE;IACrE,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEhC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|