calibreweb-mcp 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/README.md CHANGED
@@ -2,23 +2,30 @@
2
2
 
3
3
  [![CI](https://img.shields.io/github/actions/workflow/status/ni-c/calibreweb-mcp/ci.yml?branch=main&label=CI)](https://github.com/ni-c/calibreweb-mcp/actions/workflows/ci.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/calibreweb-mcp)](https://www.npmjs.com/package/calibreweb-mcp)
5
+ [![npm downloads](https://img.shields.io/npm/dm/calibreweb-mcp)](https://www.npmjs.com/package/calibreweb-mcp)
5
6
  [![node](https://img.shields.io/node/v/calibreweb-mcp)](https://nodejs.org)
6
7
  [![license](https://img.shields.io/npm/l/calibreweb-mcp)](LICENSE)
7
- [![container](https://img.shields.io/badge/ghcr.io-ni--c%2Fcalibreweb--mcp-2496ed?logo=docker&logoColor=white)](https://github.com/ni-c/calibreweb-mcp/pkgs/container/calibreweb-mcp)
8
- [![docs](https://img.shields.io/badge/docs-calibreweb--mcp.ni--c.de-4f46e5)](https://calibreweb-mcp.ni-c.de)
8
+ [![container](https://img.shields.io/badge/ghcr.io-ni--c%2Fcalibreweb--mcp-blue)](https://github.com/ni-c/calibreweb-mcp/pkgs/container/calibreweb-mcp)
9
+ [![docs](https://img.shields.io/badge/docs-calibreweb--mcp.ni--c.de-informational)](https://calibreweb-mcp.ni-c.de)
9
10
  [![sponsor](https://img.shields.io/badge/sponsor-ni--c-ea4aaa?logo=githubsponsors&logoColor=white)](https://github.com/sponsors/ni-c)
10
11
 
11
- A read-only [Model Context Protocol](https://modelcontextprotocol.io) server for
12
+ A read-only [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for
12
13
  [Calibre-Web](https://github.com/janeczku/calibre-web) (and
13
14
  [Calibre-Web Automated](https://github.com/crocodilestick/Calibre-Web-Automated)),
14
15
  the self-hosted ebook library web UI.
15
16
 
16
- Calibre-Web has no REST API its only stable machine-readable interface is the
17
- OPDS catalog feed it serves for e-reader apps. This server speaks that feed:
18
- Atom XML with HTTP Basic auth in, structured book data out. Search the library,
19
- browse the curated views and shelves, follow per-format download links, and pull
20
- cover images straight into the conversation. It never writes anything: every
21
- tool is a GET against the OPDS routes.
17
+ Lets MCP clients like Claude Code, Claude Desktop or Codex search your library,
18
+ browse the curated views and shelves, follow per-format download links and pull cover
19
+ images straight into the conversation. It never writes anything: every tool is a GET.
20
+
21
+ Six tools is the ceiling, not the floor: `CALIBRE_WEB_ALLOW_TOOLS=essential`
22
+ registers a curated five instead, and a model picks the right tool far more
23
+ reliably from five than from six — see
24
+ [choosing which tools load](#choosing-which-tools-load).
25
+
26
+ Calibre-Web has no REST API — its only stable machine-readable interface is the OPDS
27
+ catalog feed it serves for e-reader apps. This server speaks that feed: Atom XML with
28
+ HTTP Basic auth in, structured book data out.
22
29
 
23
30
  <!-- <picture> is resolved against the colour scheme of the page showing it, so GitHub
24
31
  picks the variant that matches its own theme toggle. npm strips <picture> and
@@ -35,8 +42,6 @@ tool is a GET against the OPDS routes.
35
42
 
36
43
  ![Demo: listing the tools, searching the library and reading the stats through the MCP Inspector CLI](https://calibreweb-mcp.ni-c.de/demo.gif)
37
44
 
38
- **📖 Full documentation: [calibreweb-mcp.ni-c.de](https://calibreweb-mcp.ni-c.de)**
39
-
40
45
  ## Requirements
41
46
 
42
47
  - Node.js 22 or newer
@@ -55,6 +60,8 @@ tool is a GET against the OPDS routes.
55
60
  | `CALIBRE_WEB_USERNAME` | yes¹ | Username of the Calibre-Web account. |
56
61
  | `CALIBRE_WEB_PASSWORD` | yes¹ | Password of that account (the web login password). |
57
62
  | `CALIBRE_WEB_INSECURE_TLS` | no | `true` to accept self-signed certificates — scoped to the configured host only. |
63
+ | `CALIBRE_WEB_ALLOW_TOOLS` | no | Comma-separated tool names, `list_*` prefixes, or `essential` for a curated preset |
64
+ | `CALIBRE_WEB_DENY_TOOLS` | no | Same syntax; removed from whatever `CALIBRE_WEB_ALLOW_TOOLS` left |
58
65
 
59
66
  ¹ Leave **both** unset for an instance that allows anonymous browsing; setting
60
67
  only one of them is a configuration error.
@@ -174,3 +181,24 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
174
181
  ## License
175
182
 
176
183
  [MIT](LICENSE)
184
+
185
+ ### Choosing which tools load
186
+
187
+ `CALIBRE_WEB_ALLOW_TOOLS` and `CALIBRE_WEB_DENY_TOOLS` take comma-separated tool names;
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.
201
+
202
+ If you run several of these servers at once, [mcp-hub](https://mcp-hub.ni-c.de)
203
+ is the other answer — its `/hub` endpoint replaces every server's tools with six
204
+ meta-tools.
package/dist/config.d.ts CHANGED
@@ -9,7 +9,15 @@ export interface Config {
9
9
  username: string | undefined;
10
10
  /** The normal web-login password of the Calibre-Web user (OPDS uses HTTP Basic auth). */
11
11
  password: string | undefined;
12
- insecureTls: boolean;
12
+ insecureTls: boolean; /**
13
+ * Raw value of `CALIBRE_WEB_ALLOW_TOOLS` — comma-separated tool names, `list_*`
14
+ * prefixes, or `essential`. Kept unparsed on purpose: this file is a mirror of
15
+ * the environment, and the names can only be checked against the tool
16
+ * catalogue, which `buildToolFilter` does.
17
+ */
18
+ allowTools: string | undefined;
19
+ /** Raw value of `CALIBRE_WEB_DENY_TOOLS`, same shape, subtracted from the above. */
20
+ denyTools: string | undefined;
13
21
  }
14
22
  /** Shown when the configuration is incomplete — at startup and on every API call. */
15
23
  export declare function missingConfigMessage(missing: string[]): string;
package/dist/config.js CHANGED
@@ -39,6 +39,8 @@ export function loadConfig(env = process.env) {
39
39
  const username = env.CALIBRE_WEB_USERNAME;
40
40
  const password = env.CALIBRE_WEB_PASSWORD;
41
41
  const insecureTls = env.CALIBRE_WEB_INSECURE_TLS === 'true';
42
+ const allowTools = env.CALIBRE_WEB_ALLOW_TOOLS;
43
+ const denyTools = env.CALIBRE_WEB_DENY_TOOLS;
42
44
  // Don't keep the password in process.env for the process lifetime: it would be
43
45
  // inherited by child processes and show up in env dumps. (The kernel's
44
46
  // /proc/<pid>/environ snapshot is NOT rewritten by this — it always keeps the
@@ -47,7 +49,14 @@ export function loadConfig(env = process.env) {
47
49
  delete env.CALIBRE_WEB_PASSWORD;
48
50
  if (!url) {
49
51
  console.error(`calibreweb-mcp: ${missingConfigMessage(['CALIBRE_WEB_URL'])}`);
50
- return { url: undefined, username, password, insecureTls };
52
+ return {
53
+ url: undefined,
54
+ username,
55
+ password,
56
+ insecureTls,
57
+ allowTools,
58
+ denyTools,
59
+ };
51
60
  }
52
61
  if (!username !== !password) {
53
62
  console.error('calibreweb-mcp: CALIBRE_WEB_USERNAME and CALIBRE_WEB_PASSWORD must be ' +
@@ -84,6 +93,8 @@ export function loadConfig(env = process.env) {
84
93
  username,
85
94
  password,
86
95
  insecureTls,
96
+ allowTools,
97
+ denyTools,
87
98
  };
88
99
  }
89
100
  function isLoopbackHost(hostname) {
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAwBnD,qFAAqF;AACrF,MAAM,UAAU,oBAAoB,CAAC,OAAiB;IACpD,OAAO,CACL,6CAA6C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QACnE,8DAA8D;QAC9D,8CAA8C;QAC9C,2EAA2E;QAC3E,4EAA4E;QAC5E,qDAAqD;QACrD,4EAA4E,CAC7E,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,GAAG;QAAE,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CACV,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,sBAAsB,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC;IAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,oBAAoB,CAAC;IAC1C,MAAM,QAAQ,GAAG,GAAG,CAAC,oBAAoB,CAAC;IAC1C,MAAM,WAAW,GAAG,GAAG,CAAC,wBAAwB,KAAK,MAAM,CAAC;IAC5D,MAAM,UAAU,GAAG,GAAG,CAAC,uBAAuB,CAAC;IAC/C,MAAM,SAAS,GAAG,GAAG,CAAC,sBAAsB,CAAC;IAE7C,+EAA+E;IAC/E,uEAAuE;IACvE,8EAA8E;IAC9E,8EAA8E;IAC9E,oBAAoB;IACpB,OAAO,GAAG,CAAC,oBAAoB,CAAC;IAEhC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CACX,mBAAmB,oBAAoB,CAAC,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAC/D,CAAC;QACF,OAAO;YACL,GAAG,EAAE,SAAS;YACd,QAAQ;YACR,QAAQ;YACR,WAAW;YACX,UAAU;YACV,SAAS;SACV,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CACX,wEAAwE;YACtE,2EAA2E,CAC9E,CAAC;IACJ,CAAC;IAED,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;QAC7E,sEAAsE;QACtE,0EAA0E;QAC1E,qEAAqE;QACrE,OAAO,CAAC,KAAK,CACX,uDAAuD,oBAAoB,CAAC,GAAG,CAAC,EAAE,CACnF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChE,OAAO,CAAC,KAAK,CACX,qEAAqE,MAAM,CAAC,QAAQ,GAAG,CACxF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,2EAA2E;IAC3E,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvC,OAAO,CAAC,KAAK,CACX,qEAAqE;YACnE,+CAA+C,CAClD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpE,OAAO,CAAC,KAAK,CACX,0EAA0E;YACxE,qEAAqE,CACxE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC5B,QAAQ;QACR,QAAQ;QACR,WAAW;QACX,UAAU;QACV,SAAS;KACV,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,oEAAoE;IACpE,6EAA6E;IAC7E,qEAAqE;IACrE,OAAO,gBAAgB,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC;AACnD,CAAC"}
package/dist/index.js CHANGED
@@ -2,12 +2,25 @@
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { loadConfig } from './config.js';
4
4
  import { createServer } from './server.js';
5
+ import { ToolFilterError } from './tool-filter.js';
5
6
  async function main() {
6
7
  const config = loadConfig();
7
8
  if (config.insecureTls) {
8
9
  console.error('calibreweb-mcp: CALIBRE_WEB_INSECURE_TLS=true — TLS certificate validation is disabled for the Calibre-Web connection');
9
10
  }
10
- const server = createServer(config);
11
+ let server;
12
+ try {
13
+ server = createServer(config);
14
+ }
15
+ catch (error) {
16
+ // A bad tool list is operator feedback, not a crash: print the
17
+ // sentence on its own rather than behind "fatal error:".
18
+ if (error instanceof ToolFilterError) {
19
+ console.error(`calibreweb-mcp: ${error.message}`);
20
+ process.exit(1);
21
+ }
22
+ throw error;
23
+ }
11
24
  // stdout belongs to the protocol; everything human-readable goes to stderr.
12
25
  await server.connect(new StdioServerTransport());
13
26
  console.error(config.url
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,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,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,4EAA4E;IAC5E,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CACX,MAAM,CAAC,GAAG;QACR,CAAC,CAAC,wCAAwC,MAAM,CAAC,GAAG,EAAE;QACtD,CAAC,CAAC,6FAA6F,CAClG,CAAC;AACJ,CAAC;AAED,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,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,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAChC,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,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CACX,MAAM,CAAC,GAAG;QACR,CAAC,CAAC,wCAAwC,MAAM,CAAC,GAAG,EAAE;QACtD,CAAC,CAAC,6FAA6F,CAClG,CAAC;AACJ,CAAC;AAED,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/server.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { CalibreWebApi } from './api.js';
4
+ import { buildToolFilter, installToolFilter } from './tool-filter.js';
4
5
  import { registerBookTools } from './tools/books.js';
5
6
  import { registerCoverTools } from './tools/covers.js';
6
7
  import { registerShelfTools } from './tools/shelves.js';
@@ -16,11 +17,17 @@ function packageVersion() {
16
17
  }
17
18
  }
18
19
  export function createServer(config) {
20
+ // Before anything is built: an unusable tool list should fail on the
21
+ // way in, not leave a server running with tools quietly missing.
22
+ const filter = buildToolFilter(config);
19
23
  const api = new CalibreWebApi(config);
20
24
  const server = new McpServer({
21
25
  name: 'calibreweb-mcp',
22
26
  version: packageVersion(),
23
27
  });
28
+ // Wraps server.registerTool, so it has to sit before the first
29
+ // register call and does not care how they are organised.
30
+ installToolFilter(server, filter);
24
31
  // Every tool is read-only: the OPDS feed has no write surface, so there is
25
32
  // no read-only mode to configure and nothing destructive to confirm.
26
33
  registerBookTools(server, api);
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,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,MAAM,CAAC,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,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,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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,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,MAAM,CAAC,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,MAAM,CAAC,CAAC;IAEvC,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"}
@@ -0,0 +1,43 @@
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;
@@ -0,0 +1,138 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-filter.js","sourceRoot":"","sources":["../src/tool-filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The tools this server can register, declared rather than discovered.
3
+ *
4
+ * Declared, because the tool filter has to answer "is this a name you have?"
5
+ * *before* anything is registered — and in read-only mode the write tools are
6
+ * never registered at all. Deriving the catalogue from what actually reached
7
+ * `registerTool` would make `CALIBRE_WEB_ALLOW_TOOLS=get_thing` report
8
+ * "unknown tool" under `CALIBRE_WEB_READ_ONLY=true`, which is the one answer that
9
+ * is wrong.
10
+ *
11
+ * This is also the full tool surface, hard-coded on purpose: a tool that appears
12
+ * or disappears by accident is a change to the server's contract and has to be a
13
+ * deliberate edit here. `test/tool-filter.test.ts` asserts that these lists and
14
+ * the tools the server really registers are the same set.
15
+ */
16
+ /** Every tool. This server never writes, so there is no split to make. */
17
+ export declare const ALL_TOOLS_LIST: readonly ["get_cover", "get_shelf_books", "get_stats", "list_books", "list_shelves", "search_books"];
18
+ /** Every tool, read-only mode aside. */
19
+ export declare const ALL_TOOLS: readonly string[];
20
+ /**
21
+ * What `CALIBRE_WEB_ALLOW_TOOLS=essential` selects: find a book, see the shelves.
22
+ *
23
+ * 5 of 6. Left out on purpose: `get_cover`, which returns a base64 image that floods the context window
24
+ * and is almost never the answer to a question.
25
+ */
26
+ export declare const ESSENTIAL_TOOLS: readonly string[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The tools this server can register, declared rather than discovered.
3
+ *
4
+ * Declared, because the tool filter has to answer "is this a name you have?"
5
+ * *before* anything is registered — and in read-only mode the write tools are
6
+ * never registered at all. Deriving the catalogue from what actually reached
7
+ * `registerTool` would make `CALIBRE_WEB_ALLOW_TOOLS=get_thing` report
8
+ * "unknown tool" under `CALIBRE_WEB_READ_ONLY=true`, which is the one answer that
9
+ * is wrong.
10
+ *
11
+ * This is also the full tool surface, hard-coded on purpose: a tool that appears
12
+ * or disappears by accident is a change to the server's contract and has to be a
13
+ * deliberate edit here. `test/tool-filter.test.ts` asserts that these lists and
14
+ * the tools the server really registers are the same set.
15
+ */
16
+ /** Every tool. This server never writes, so there is no split to make. */
17
+ export const ALL_TOOLS_LIST = [
18
+ 'get_cover',
19
+ 'get_shelf_books',
20
+ 'get_stats',
21
+ 'list_books',
22
+ 'list_shelves',
23
+ 'search_books',
24
+ ];
25
+ /** Every tool, read-only mode aside. */
26
+ export const ALL_TOOLS = [...ALL_TOOLS_LIST];
27
+ /**
28
+ * What `CALIBRE_WEB_ALLOW_TOOLS=essential` selects: find a book, see the shelves.
29
+ *
30
+ * 5 of 6. Left out on purpose: `get_cover`, which returns a base64 image that floods the context window
31
+ * and is almost never the answer to a question.
32
+ */
33
+ export const ESSENTIAL_TOOLS = [
34
+ 'search_books',
35
+ 'list_books',
36
+ 'list_shelves',
37
+ 'get_shelf_books',
38
+ 'get_stats',
39
+ ];
40
+ //# sourceMappingURL=catalogue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue.js","sourceRoot":"","sources":["../../src/tools/catalogue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,WAAW;IACX,iBAAiB;IACjB,WAAW;IACX,YAAY;IACZ,cAAc;IACd,cAAc;CACN,CAAC;AAEX,wCAAwC;AACxC,MAAM,CAAC,MAAM,SAAS,GAAsB,CAAC,GAAG,cAAc,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsB;IAChD,cAAc;IACd,YAAY;IACZ,cAAc;IACd,iBAAiB;IACjB,WAAW;CACZ,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "calibreweb-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "mcpName": "io.github.ni-c/calibreweb-mcp",
5
- "description": "MCP server for Calibre-Web read-only library access via the OPDS feed",
5
+ "description": "MCP server for Calibre-Web \u2014 read-only library access via the OPDS feed",
6
6
  "keywords": [
7
7
  "mcp",
8
8
  "model-context-protocol",