@oomkapwn/enquire-mcp 2.5.0 → 2.7.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/CHANGELOG.md CHANGED
@@ -2,6 +2,111 @@
2
2
 
3
3
  All notable changes to this project will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
+ ## [2.7.0] — 2026-05-08
6
+
7
+ **Sprint 7 — PDF as a first-class indexable content type.** PDFs are the #1 non-markdown content kind in real research vaults (papers, scanned notes, downloaded references). **No other Obsidian-MCP currently indexes them.** v2.7.0 adds two new read tools that work identically over stdio + `serve-http`, gated behind `pdfjs-dist` as an `optionalDependency` so the markdown-only path stays zero-cost.
8
+
9
+ ### Added — `obsidian_list_pdfs`
10
+
11
+ Lists `.pdf` files in the vault with size + last-modified timestamp. Sorted by mtime descending. Honors `--exclude-glob` and `--read-paths`. Discovery entry point — call this before `obsidian_read_pdf` to find what's available.
12
+
13
+ ### Added — `obsidian_read_pdf`
14
+
15
+ Extracts plain text from one PDF, returning per-page text + a `full_text` join + doc-level metadata (title / author / subject / keywords / creator / producer / creation date / mod date). Optional `pages` slice (1-indexed inclusive range, e.g. `[2, 5]`) for partial reads of long documents — `total_page_count` is preserved so consumers know how much they didn't read. Image-only / scanned PDFs surface `has_text: false` so agents can detect-and-recommend OCR (deferred to v2.8+).
16
+
17
+ Per-page extraction speed: ~50-200ms cold, ~10-30ms warm on M1. No rendering, no canvas. Same path-safety + privacy filter (`--exclude-glob` / `--read-paths`) as `obsidian_read_note` — there are no PDF-specific shortcuts.
18
+
19
+ ### Added — `pdfjs-dist` as `optionalDependencies`
20
+
21
+ Mozilla's [PDF.js](https://mozilla.github.io/pdf.js/) parser. Pure JS (no native deps), Apache-2.0, SLSA-3 published, Node 20+ compatible (pinned `pdfjs-dist@^4.10.38`). The PDF tools surface a clean install-hint error on missing optional dep, never a cryptic module-not-found stack trace. Server-side hardening: `isEvalSupported: false`, `useSystemFonts: false`, `verbosity: 0`. No outbound HTTP, no eval, no font fetches.
22
+
23
+ ### Surface delta vs v2.6.0
24
+
25
+ - **+2 read tools** — `obsidian_list_pdfs`, `obsidian_read_pdf`
26
+ - **Total surface:** 38 tools (27 always-on read + 1 opt-in via `--persistent-index` + 3 opt-in diagnostic + 7 opt-in write) + 17 prompts
27
+
28
+ ### Tests
29
+
30
+ 481 unit tests pass (was 459 in v2.6.0, +22 PDF tests). Synthetic PDF builder in `tests/helpers/make-pdf.ts` produces minimal valid PDF 1.4 byte sequences for tests — no committed binary fixtures, no PDF-writer dev-dependency. Coverage:
31
+
32
+ - `extractPdfText`: single-page, multi-page in-order, Title/Author metadata round-trip, char_count correctness, escape-paren-and-backslash safety.
33
+ - `listPdfs`: recursive walk, mtime-desc sort, folder filter, `--exclude-glob` privacy filter parity with markdown listing, `--read-paths` allowlist parity, limit honored.
34
+ - `readPdf`: round-trip, optional `.pdf` extension, page-range slicing (with original `total_page_count` preserved), `include_metadata` flag, missing-path error, excluded-by-privacy-filter error, page numbers preserved through slicing, empty-path error.
35
+
36
+ ### Smoke
37
+
38
+ `scripts/smoke.mjs` updated: tool count goes from 28/29 → 30/31 (with/without `--persistent-index`), `obsidian_list_pdfs` + `obsidian_read_pdf` added to baseTools.
39
+
40
+ ### Migration
41
+
42
+ **No-op.** All additions are new tools. Existing tool calls behave identically. Users who skipped `pdfjs-dist` (`npm install --omit=optional`) keep the full markdown surface; PDF tools register but throw a clean install-hint when called.
43
+
44
+ ### Strategic position
45
+
46
+ The retrieval moats from v2.0-v2.6 (hybrid RRF, wikilink graph-boost, breadcrumb chunking, multilingual embeddings, remote MCP) extend cleanly to PDFs once you've extracted text. The next logical step is integrating PDF chunks into the FTS5 + embedding indexes so `obsidian_search` returns blended markdown + PDF hits with a `kind` flag — tracked for v2.8+. v2.7.0 ships the foundation.
47
+
48
+ ## [2.6.0] — 2026-05-08
49
+
50
+ **Sprint 6 — remote-MCP HTTP transport.** New `serve-http` subcommand running the same server (same tools, same vault, same hybrid retrieval) over [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) — the protocol Claude.ai web, ChatGPT, Cursor's HTTP mode, and most mobile MCP clients use to talk to a remote server. **No other Obsidian-MCP currently ships a remote-HTTP transport.**
51
+
52
+ ### Added — `enquire-mcp serve-http`
53
+
54
+ Stateless [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) with three layers in front of the SDK transport:
55
+
56
+ 1. **Bearer auth** — required at startup (fail-closed, refuses to bind without `--bearer-token` ≥16 chars). Constant-time compare via SHA-256 + `crypto.timingSafeEqual` on equal-length buffers — no length-leak oracle. Token never appears in logs (rate-limit key is the SHA-256 prefix).
57
+ 2. **Per-token rate-limit** — sliding 60-second window, default 120 req/min, tunable via `--rate-limit` (`0` disables). 429 + `Retry-After: 60` on overflow.
58
+ 3. **Strict CORS allowlist** — `--cors-origin` (repeatable). Default empty (no `Access-Control-Allow-Origin` sent — same-origin still works). Disallowed origins get 204 preflight with no CORS headers, browsers refuse the actual request. `*` is supported but warned-against (incompatible with credentialed Bearer requests).
59
+
60
+ Plus an unauthenticated `/health` probe (`GET → 200 ok`) for tunnels/uptime monitors.
61
+
62
+ The HTTP server uses **stateless mode** — fresh `McpServer` per request over the **shared** vault + FTS5 + embedding handles. SQLite stays open across thousands of requests; only the per-request server class is recreated. This matters because `prepareServerDeps()` (vault open + FTS5 sync) takes seconds on a real vault, while `buildMcpServer()` (registering tool handlers) is sub-millisecond.
63
+
64
+ ### Added — `enquire-mcp gen-token`
65
+
66
+ Convenience helper that prints a fresh 32-byte base64url bearer token (256 bits of entropy, URL/header-safe). Equivalent to `node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"` but discoverable in `--help`.
67
+
68
+ ### Added — `--bearer-token-env <name>`
69
+
70
+ Read the bearer token from an env var instead of a flag. Cleaner for systemd / `.env` / shared shells where flags would leak via `ps aux` or shell history.
71
+
72
+ ### Added — comprehensive deployment docs
73
+
74
+ [`docs/http-transport.md`](docs/http-transport.md) — security model, threat model, all flags, five deployment recipes (Tailscale Funnel, Cloudflare Tunnel, ngrok, direct-LAN, systemd), client configuration for Claude.ai web / Cursor HTTP / ChatGPT custom GPT / Khoj mobile, troubleshooting, manual `curl` examples.
75
+
76
+ ### Refactored — extracted `prepareServerDeps()` + `buildMcpServer()` from `startServer()`
77
+
78
+ Stdio and HTTP now share the same dependency-prep + server-build code. Stdio calls `buildMcpServer()` once; HTTP calls it per request over the same `ServerDeps`. Skip-tool warnings (`--disabled-tools "foo" did not match any tool`) print only on the first build via a single-fire latch — HTTP doesn't spam logs per request. `formatReadyBanner()` is shared so the runtime configuration summary is identical regardless of transport.
79
+
80
+ ### Added — 26 new unit tests + 6 smoke checks
81
+
82
+ `tests/http-transport.test.ts` (26 tests):
83
+ - `verifyBearer` — missing/wrong/right token, case-sensitive Bearer prefix, length-leak resistance, rate-limit-key stability/uniqueness.
84
+ - `RateLimiter` — under-budget passes, over-budget rejects, sliding window trims old entries, per-key isolation, `perMinute=0` disables.
85
+ - `generateBearerToken` — 43-char base64url shape, uniqueness across 100 tokens.
86
+ - `startHttpServer` end-to-end — 401 missing/wrong, 200 init, 405 GET, 200 `/health`, 404 unknown paths, 429 rate-limit, OPTIONS preflight (allowed/disallowed origin), refuses startup without `--bearer-token` or with `<16 chars`.
87
+
88
+ `scripts/smoke.mjs` — added an HTTP smoke variant that spawns `serve-http` on port 0, hits `/health` unauthenticated, verifies 401 on missing-bearer, completes an authenticated initialize, then cleans up.
89
+
90
+ **Total: 457 unit tests pass** (was 431 in v2.5.0). All previous tests preserved unchanged.
91
+
92
+ ### Tool / prompt surface
93
+
94
+ **No change.** All 36 tools + 17 prompts work identically over HTTP. The transport is a wrapper, not a new feature surface.
95
+
96
+ ### Migration
97
+
98
+ **No-op.** All existing `serve` users keep working unchanged. New `serve-http` subcommand is opt-in. The internal refactor (extracting `prepareServerDeps` / `buildMcpServer`) preserved every previous behavior — verified by all 431 prior tests passing on the new code path.
99
+
100
+ ### Verified
101
+
102
+ - Maintainer's 128-note bilingual real vault: stdio + HTTP smoke variants both green.
103
+ - 457 / 457 tests on every required CI matrix node.
104
+ - Zero new prod dependencies — uses `node:http` directly (no Express).
105
+
106
+ ### Note on stateful sessions / SSE
107
+
108
+ Stateful `Mcp-Session-Id` sessions with persistent SSE streams are tracked for **v2.7+** if there's demand. Stateless is the right default for our tools (search, read, frontmatter ops are all short-running) and avoids the persistence-aware shutdown complexity.
109
+
5
110
  ## [2.5.0] — 2026-05-08
6
111
 
7
112
  **Sprint 5 — agentic prompts (Khoj parity, lite scope).** Two new MCP prompts that bring named-persona retrieval and scheduled-query automation to enquire-mcp. Pure orchestration over existing tools — no new server-side state, no LLM calls.
package/README.md CHANGED
@@ -42,8 +42,9 @@ That's it. Your AI now has structured access to wikilinks, backlinks, frontmatte
42
42
  | Privacy filter (`--exclude-glob` / `--read-paths`) | ❌ | n/a | ✅ verified at search + write paths |
43
43
  | Standalone (no Obsidian plugin) | varies | ❌ requires Obsidian | ✅ direct vault read |
44
44
  | MCP-native (any agent) | varies | ❌ Obsidian-only | ✅ stdio JSON-RPC |
45
+ | **Remote MCP (HTTP transport, bearer auth)** | ❌ | ❌ | ✅ **only here** (v2.6.0) |
45
46
  | SLSA-3 provenance | ❌ | n/a | ✅ |
46
- | Test suite | rare | n/a | ✅ 408 unit tests |
47
+ | Test suite | rare | n/a | ✅ 481 unit tests |
47
48
 
48
49
  ---
49
50
 
@@ -68,6 +69,10 @@ graph LR
68
69
  Tier 1: serve --vault <path> → TF-IDF (zero setup, instant)
69
70
  Tier 2: serve --vault <path> --persistent-index → + BM25 (sub-100ms top-10)
70
71
  Tier 3: + install-model + build-embeddings → + ML embeddings (multilingual)
72
+ Tier 4: serve-http --bearer-token <token> → remote MCP (v2.6.0)
73
+ same retrieval stack, exposed over HTTP for Claude.ai web,
74
+ ChatGPT, Cursor HTTP, mobile. Tailscale Funnel / Cloudflare
75
+ Tunnel for HTTPS. See docs/http-transport.md.
71
76
  ```
72
77
 
73
78
  ---
@@ -104,11 +109,25 @@ enquire-mcp build-embeddings --vault <path> # ~30ms/chunk on M1
104
109
  # Add --persistent-index to your serve invocation for BM25.
105
110
  ```
106
111
 
112
+ **Remote MCP** (v2.6.0 — Claude.ai web, ChatGPT, Cursor HTTP, mobile):
113
+
114
+ ```bash
115
+ enquire-mcp gen-token > ~/.enquire/token # 256-bit bearer
116
+ enquire-mcp serve-http \
117
+ --vault ~/Obsidian \
118
+ --bearer-token "$(cat ~/.enquire/token)" \
119
+ --persistent-index
120
+ # Front with Tailscale Funnel / Cloudflare Tunnel for HTTPS — see
121
+ # docs/http-transport.md.
122
+ ```
123
+
124
+ No other Obsidian-MCP currently ships a remote-HTTP transport. Same vault, same tools, same hybrid retrieval — just over HTTPS instead of stdio.
125
+
107
126
  ---
108
127
 
109
- ## Tools (36 total)
128
+ ## Tools (38 total)
110
129
 
111
- ### 25 always-on read tools
130
+ ### 27 always-on read tools
112
131
 
113
132
  | Tool | What it does |
114
133
  |---|---|
@@ -136,6 +155,8 @@ enquire-mcp build-embeddings --vault <path> # ~30ms/chunk on M1
136
155
  | `obsidian_validate_note_proposal` | Lint a draft note before writing (closes the #1 LLM-write pain). |
137
156
  | `obsidian_list_canvases` | List `.canvas` files with node + edge counts. |
138
157
  | `obsidian_read_canvas` | Parse `.canvas` into typed nodes (text/file/link/group) + edges. |
158
+ | `obsidian_list_pdfs` | **v2.7.0.** List `.pdf` files with size + mtime. PDFs are the #1 non-markdown content kind in real vaults; no other Obsidian-MCP indexes them. |
159
+ | `obsidian_read_pdf` | **v2.7.0.** Extract page-by-page text + doc metadata (title/author/etc) from a PDF. Optional 1-indexed page-range slice. Image-only / scanned PDFs surface `has_text: false`. Powered by Mozilla PDF.js (Apache-2.0, pinned to `optionalDependencies` so the markdown-only path stays zero-cost). |
139
160
  | `obsidian_open_in_ui` | Emit `obsidian://open?vault=...` URI. |
140
161
 
141
162
  ### 4 opt-in read tools
@@ -182,9 +203,9 @@ Plus **2 + 1 opt-in MCP resources** (`obsidian://note/...`, `obsidian://vault-in
182
203
  | `--max-file-bytes <n>` | 5 MB | Per-file read/write cap. |
183
204
  | `--cache-size <n>` | 1024 | LRU cap for parsed-note cache. |
184
205
 
185
- Subcommands: `serve` · `clear-cache` · `clear-index` · `index` (cold-build FTS5) · `install-model` · `build-embeddings` · `clear-embeddings`.
206
+ Subcommands: `serve` · `serve-http` (v2.6.0 — remote MCP) · `gen-token` (v2.6.0) · `clear-cache` · `clear-index` · `index` (cold-build FTS5) · `install-model` · `build-embeddings` · `clear-embeddings`.
186
207
 
187
- Full reference: [docs/api.md](./docs/api.md).
208
+ Full reference: [docs/api.md](./docs/api.md). Remote-MCP deployment guide: [docs/http-transport.md](./docs/http-transport.md).
188
209
 
189
210
  ---
190
211
 
@@ -209,7 +230,7 @@ Full posture: [SECURITY.md](./SECURITY.md). Report vulnerabilities to `oomkapwn@
209
230
  |---|---|
210
231
  | Language | TypeScript strict + `noUncheckedIndexedAccess` |
211
232
  | Lint | Biome 2 (zero-warning policy) |
212
- | Tests | 408 unit tests across 19 files |
233
+ | Tests | 481 unit tests across 23 files |
213
234
  | CI | ubuntu × {Node 20, 22, 24} required + macOS advisory job |
214
235
  | Coverage | Lines ≥86%, statements ≥82%, functions ≥75%, branches ≥73% (gated) |
215
236
  | Audit | `npm audit --audit-level=moderate` for prod; high for dev |
package/SECURITY.md CHANGED
@@ -167,3 +167,37 @@ When `--persistent-index` is enabled, the search-index file at `<vault-hash>.fts
167
167
  - Cross-vault contamination guard: a `meta` table stores `vault_root` and `tokenize_mode`; if either changes between runs, the index is dropped and rebuilt with a stderr warning.
168
168
  - Manual purge: `enquire-mcp clear-index --vault <path>` removes the `.fts5.db`, `.fts5.db-wal`, and `.fts5.db-shm` files.
169
169
  - **Caveat:** SQLite WAL mode keeps the most-recent uncommitted writes in `<file>-wal`. If you delete only `<file>` manually (not via `clear-index`), some recently-indexed chunks may persist in the sidecar. Always use `clear-index` for full removal.
170
+
171
+ ## HTTP transport (v2.6.0): bearer auth + remote-MCP posture
172
+
173
+ The `serve-http` subcommand (added v2.6.0) exposes the same MCP server over [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) so claude.ai web, ChatGPT, Cursor HTTP mode, and mobile MCP clients can reach a remote vault. It introduces a network-exposed endpoint that the default stdio path doesn't have. The threat model + deployment recipes are documented at length in [`docs/http-transport.md`](docs/http-transport.md); this section is the canonical security posture.
174
+
175
+ ### What we protect against
176
+
177
+ - **Unauthenticated read.** Wrong/missing token → 401, fail-closed at the auth middleware before any tool dispatch. The startup itself refuses to bind without `--bearer-token` (or `--bearer-token-env`) of length ≥16 chars.
178
+ - **Token timing leaks.** Bearer compare hashes both presented and expected token with SHA-256 first, then `crypto.timingSafeEqual` on the equal-length 32-byte digests. No length oracle; equal-length compare is constant-time.
179
+ - **Token logging.** Stderr / log output uses the SHA-256 prefix as the rate-limit key — the raw bearer token never appears in logs, error messages, or rate-limit state.
180
+ - **Rate-limit abuse.** Per-token sliding 60-second window, default 120 requests/minute. Tunable via `--rate-limit` (`0` disables for trusted private LANs). 429 + `Retry-After: 60` on overflow.
181
+ - **CORS-based credential leakage.** `--cors-origin` is a strict allowlist. Default empty (no `Access-Control-Allow-Origin` sent — same-origin works regardless). With explicit origins, we send `Access-Control-Allow-Credentials: true` so cookies + bearer requests work cross-origin. With the `*` wildcard, we deliberately OMIT `Allow-Credentials` (browsers reject the combo anyway, and reflecting credentialed CORS to attacker-controlled origins is the [CodeQL `js/cors-misconfiguration-for-credentials` class of bug](https://codeql.github.com/codeql-query-help/javascript/js-cors-misconfiguration-for-credentials/)). The response value is sourced from the allowlist itself, not from `req.headers.origin`, so static-analysis taint flows from a server-controlled root.
182
+ - **Body bombs.** Per-request body size cap (4 MB), enforced as we accumulate chunks. Bigger requests get `400 Parse error` before we attempt JSON.parse.
183
+ - **Privacy filter (`--exclude-glob` / `--read-paths`)** applies identically to HTTP and stdio paths. The same audit-tested filter runs at every search/read/walker boundary; there are no HTTP-specific shortcuts. v2.0.0-beta.2 P0 fixes for FTS5 + embed-index search apply here.
184
+ - **Tag-deletion / write-tool gating.** All write tools (chat-thread-append, frontmatter-set, create/append/rename/replace/archive) remain gated by `--enable-write` regardless of transport. HTTP doesn't lower the bar.
185
+
186
+ ### What we do NOT protect against (deliberate non-goals)
187
+
188
+ - **TLS termination.** We bind plain HTTP and assume a tunnel (Tailscale Funnel / Cloudflare Tunnel / nginx + Let's Encrypt) handles HTTPS. We deliberately default `--host 127.0.0.1` so direct internet exposure is opt-in via `0.0.0.0`. Recipes in `docs/http-transport.md` walk through the tunneled deployments.
189
+ - **Compromised client.** A user who pastes their bearer token into a malicious chat or who lets it leak via `ps aux` / shell history is owned. Hence `--bearer-token-env <name>` (read from env) and `enquire-mcp gen-token` (32-byte base64url generator). Treat the token like a password.
190
+ - **DoS from a single token.** A malicious client can fire rate-limit-budget worth of requests indefinitely; we just answer 429 once over budget. Use the tunnel's WAF for upstream DoS protection. Single-process — for shared limits use a reverse proxy with its own rate-limit module.
191
+ - **Multi-tenant cross-token attacks.** This is a single-tenant tool. A small team should run **one process per user** (e.g. systemd template unit) and not share tokens. We don't do tenant isolation in-process beyond the per-token rate-limit.
192
+ - **OAuth.** No OAuth flow, no token minting, no refresh logic. Static long-lived bearer is by design — generated with `enquire-mcp gen-token`, rotated manually. OAuth is tracked for v2.7+ if a user explicitly needs it.
193
+
194
+ ### Stateful sessions
195
+
196
+ v2.6.0 ships **stateless** mode only (`sessionIdGenerator: undefined`) — fresh `McpServer` per request over the SHARED vault + FTS5 + embedding handles. No long-lived session map; no SSE persistent streams. This is the right default for our short-running tools (search, read, frontmatter ops) and avoids the persistence-aware shutdown complexity. Stateful sessions with `Mcp-Session-Id` + persistent SSE streams are tracked for v2.7+ if there's demand.
197
+
198
+ ### Observability
199
+
200
+ - Ready banner on stderr: `enquire <version> ready (read-only|WRITE-ENABLED, vault=…) (transport=http, bound=…)`.
201
+ - Transport errors written to stderr with no token / no credential leakage.
202
+ - `/health` endpoint (`GET /health → 200 ok`) is **unauthenticated** and exists specifically for tunnel/uptime monitors. It returns the literal string `ok` — no version info, no vault path, no operational metadata. Health probes can't be used to fingerprint the deployment.
203
+ - `OPTIONS` preflight requests are unauthenticated (per CORS spec) but only emit CORS headers when the request's `Origin` is in the allowlist.
@@ -0,0 +1,92 @@
1
+ import { type Server as HttpServer, type IncomingMessage, type ServerResponse } from "node:http";
2
+ import { type ServeOptions, type ServerDeps } from "./index.js";
3
+ /**
4
+ * Configuration for the HTTP transport. Extends ServeOptions so every
5
+ * stdio-mode flag (--enable-write, --persistent-index, --watch, etc.) is
6
+ * available over HTTP too.
7
+ */
8
+ export interface HttpServeOptions extends ServeOptions {
9
+ /** TCP port to listen on. */
10
+ port: number;
11
+ /**
12
+ * Bind host. Default 127.0.0.1 — explicit because remote-MCP must
13
+ * opt-in to bind 0.0.0.0. Most users should keep it on localhost and
14
+ * front it with Tailscale Funnel / Cloudflare Tunnel for remote access
15
+ * (auth-and-encryption-in-depth). See docs/http-transport.md.
16
+ */
17
+ host: string;
18
+ /**
19
+ * Bearer token. Comparing constant-time. Required: a missing token
20
+ * fails closed at startup (we refuse to bind without auth). Generate
21
+ * with: `node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"`.
22
+ */
23
+ bearerToken: string;
24
+ /** URL path prefix for the MCP endpoint. Default `/mcp`. */
25
+ mcpPath?: string;
26
+ /** Max requests per minute per bearer token. Default 120. 0 disables. */
27
+ rateLimitPerMinute?: number;
28
+ /**
29
+ * CORS origin allowlist. Repeatable. Default empty (no
30
+ * Access-Control-Allow-Origin sent). Bearer-credentialed requests work
31
+ * cross-origin only when the origin is in this list.
32
+ */
33
+ corsOrigins?: string[];
34
+ /** Optional path-prefix to match a /health probe under (e.g. for Tailscale). Default `/health`. */
35
+ healthPath?: string;
36
+ /**
37
+ * When `true` (default), registers SIGINT/SIGTERM/beforeExit listeners
38
+ * for graceful shutdown of the HTTP server, vault, FTS5 index, watcher,
39
+ * and persistent cache. Tests pass `false` to avoid leaking listeners
40
+ * when spawning many `startHttpServer()` instances in one process —
41
+ * cleanup happens via `httpServer.close()` which still triggers the
42
+ * underlying resource cleanup.
43
+ */
44
+ installSignalHandlers?: boolean;
45
+ }
46
+ /**
47
+ * Sliding-window rate limiter. Per-token. Window is rolling 60 seconds.
48
+ * Uses a tiny circular buffer of timestamps per token — O(burst-size)
49
+ * memory per token, no GC churn (we trim when we check).
50
+ *
51
+ * NOT distributed. If the user runs multiple processes behind a load
52
+ * balancer they'd want a shared store (Redis); deferred to v2.7+.
53
+ */
54
+ declare class RateLimiter {
55
+ private readonly perMinute;
56
+ private readonly windows;
57
+ constructor(perMinute: number);
58
+ /** Returns true if request allowed; false if over budget. */
59
+ consume(tokenId: string, nowMs?: number): boolean;
60
+ /** Test-only: reset all windows. */
61
+ reset(): void;
62
+ }
63
+ /**
64
+ * Constant-time bearer-token compare. The Authorization header is
65
+ * `Bearer <token>` per RFC 6750. We compare against the raw token
66
+ * supplied at startup — both sides padded to a hash so timingSafeEqual
67
+ * doesn't leak length.
68
+ *
69
+ * Returns the SHA-256 digest of the token (used as the rate-limit key)
70
+ * if valid, or null if not. We never use the raw token as the key — a
71
+ * compromised log dump would expose it.
72
+ */
73
+ declare function verifyBearer(authHeader: string | undefined, expectedToken: string): string | null;
74
+ /** Read the entire request body (UTF-8 JSON). Returns undefined for empty. */
75
+ declare function readJsonBody(req: IncomingMessage, maxBytes: number): Promise<unknown>;
76
+ /**
77
+ * Build the request handler. Factored out of the listener-creation path
78
+ * so tests can drive it without binding a TCP port.
79
+ */
80
+ export declare function createHttpHandler(deps: ServerDeps, opts: HttpServeOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
81
+ /**
82
+ * Generate a fresh 32-byte base64url bearer token. Used by the
83
+ * `enquire-mcp gen-token` subcommand.
84
+ */
85
+ export declare function generateBearerToken(): string;
86
+ /**
87
+ * Bind and start the HTTP transport. Returns the underlying http.Server
88
+ * so callers (tests + CLI) can listen for `listening` / close it.
89
+ */
90
+ export declare function startHttpServer(opts: HttpServeOptions): Promise<HttpServer>;
91
+ export { RateLimiter, readJsonBody, verifyBearer };
92
+ //# sourceMappingURL=http-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-transport.d.ts","sourceRoot":"","sources":["../src/http-transport.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAgB,KAAK,MAAM,IAAI,UAAU,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAE/G,OAAO,EAAwD,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtH;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,mGAAmG;IACnG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,cAAM,WAAW;IACf,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;gBAE3C,SAAS,EAAE,MAAM;IAI7B,6DAA6D;IAC7D,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAmB,GAAG,OAAO;IAiB7D,oCAAoC;IACpC,KAAK,IAAI,IAAI;CAGd;AAED;;;;;;;;;GASG;AACH,iBAAS,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa1F;AAED,8EAA8E;AAC9E,iBAAe,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAcpF;AAwED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,gBAAgB,GACrB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CA4G9D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAoFjF;AAGD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC"}