@quantakrypto/mcp 0.1.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/HOSTING.md +184 -0
- package/README.md +227 -0
- package/dist/http.d.ts +120 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +339 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +109 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +68 -0
- package/dist/protocol.js.map +1 -0
- package/dist/rules.d.ts +48 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +143 -0
- package/dist/rules.js.map +1 -0
- package/dist/server.d.ts +56 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +132 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.d.ts +23 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +89 -0
- package/dist/stdio.js.map +1 -0
- package/dist/tools.d.ts +38 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +421 -0
- package/dist/tools.js.map +1 -0
- package/examples/README.md +36 -0
- package/examples/transcript.jsonl +13 -0
- package/package.json +47 -0
package/HOSTING.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Hosting quantakrypto MCP as a remote service
|
|
2
|
+
|
|
3
|
+
This document describes how to run `@quantakrypto/mcp` as a hosted, multi-tenant
|
|
4
|
+
service rather than a per-user local stdio process. The shipped `src/http.ts` is
|
|
5
|
+
the minimal, working core of this design; everything below is the path from that
|
|
6
|
+
scaffold to production.
|
|
7
|
+
|
|
8
|
+
## 0. Safe-by-default posture (what `src/http.ts` enforces today)
|
|
9
|
+
|
|
10
|
+
The stdio transport trusts the local user and is fully featured. The HTTP
|
|
11
|
+
transport is **hardened by default** (P0-1 / security-audit Q-01–Q-03) because a
|
|
12
|
+
hosted endpoint is reachable by untrusted peers. Out of the box `node dist/http.js`:
|
|
13
|
+
|
|
14
|
+
- **Binds to `127.0.0.1`**, not `0.0.0.0`. Override with `QUANTAKRYPTO_MCP_HOST`.
|
|
15
|
+
Binding to a **non-loopback host without a token is refused at startup** — an
|
|
16
|
+
unauthenticated, network-reachable tool server would be an open relay into the
|
|
17
|
+
arbitrary-file-read tools.
|
|
18
|
+
- **Requires Bearer auth when `QUANTAKRYPTO_MCP_TOKEN` is set.** Every `/mcp` request
|
|
19
|
+
must carry `Authorization: Bearer <token>`; auth is checked *before* the body
|
|
20
|
+
is read or dispatched. Missing/invalid token → `401` with `WWW-Authenticate: Bearer`.
|
|
21
|
+
`GET /health` is unauthenticated.
|
|
22
|
+
- **Gates the filesystem tools off by default.** `scan_path`, `inventory_crypto`
|
|
23
|
+
and `generate_cbom` take a client-supplied path straight into `core.scan` and
|
|
24
|
+
would otherwise be an arbitrary-directory reader (`/etc`, `/root/.ssh`, …) with
|
|
25
|
+
matched-line snippets echoed back. Over HTTP they are registered **only when
|
|
26
|
+
`QUANTAKRYPTO_MCP_ALLOW_FS=1`**, so both `tools/list` and `tools/call` reflect the
|
|
27
|
+
gate. The knowledge tools (`explain_finding`, `suggest_hybrid`, `list_rules`)
|
|
28
|
+
are pure and always exposed. The gating is a pure function (`gateHttpTools`),
|
|
29
|
+
unit-tested in `test/http.test.ts`.
|
|
30
|
+
- **Bounds each request.** A 1 MiB request-body cap (always), a per-request tool
|
|
31
|
+
timeout (`QUANTAKRYPTO_MCP_TIMEOUT_MS`, default 30 s → `504`) and a response-size cap
|
|
32
|
+
(`QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES`, default 4 MiB → `500`).
|
|
33
|
+
|
|
34
|
+
| Env var | Default | Purpose |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `QUANTAKRYPTO_MCP_HOST` (or `HOST`) | `127.0.0.1` | Bind interface. Non-loopback **requires** a token. |
|
|
37
|
+
| `PORT` | `3000` | Listen port. |
|
|
38
|
+
| `QUANTAKRYPTO_MCP_TOKEN` | _(unset)_ | When set, requires `Authorization: Bearer <token>`. |
|
|
39
|
+
| `QUANTAKRYPTO_MCP_ALLOW_FS` | _(off)_ | `1`/`true` exposes the filesystem tools over HTTP. |
|
|
40
|
+
| `QUANTAKRYPTO_MCP_TIMEOUT_MS` | `30000` | Per-request tool-execution deadline. |
|
|
41
|
+
| `QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES` | `4194304` | Response-body size cap. |
|
|
42
|
+
|
|
43
|
+
**Design choice — refuse vs. warn on a wide-open bind.** Binding to a
|
|
44
|
+
non-loopback host with `QUANTAKRYPTO_MCP_TOKEN` unset is **refused** (startup fails)
|
|
45
|
+
rather than merely warned, because the failure mode is severe and silent (an
|
|
46
|
+
internet-reachable arbitrary-tool endpoint). A non-loopback bind *with* a token
|
|
47
|
+
is allowed but emits a hard `WARNING` to stderr. Even then, the recommendation
|
|
48
|
+
remains to terminate TLS and validate keys at a gateway (§3) and to leave the
|
|
49
|
+
filesystem tools off unless the path surface is sandboxed (§3.1 of the security
|
|
50
|
+
audit). The sections below describe the remaining production hardening.
|
|
51
|
+
|
|
52
|
+
## 1. Transport choice
|
|
53
|
+
|
|
54
|
+
MCP defines two transports:
|
|
55
|
+
|
|
56
|
+
- **stdio** — newline-delimited JSON over a child process's stdin/stdout. Ideal
|
|
57
|
+
for *local* agents (Claude Desktop/Code spawns the bin). Not hostable: one
|
|
58
|
+
process per client, no network surface.
|
|
59
|
+
- **Streamable HTTP** — JSON-RPC over HTTP. A client `POST`s a JSON-RPC message
|
|
60
|
+
to a single endpoint (`/mcp`) and either gets a JSON response back, or upgrades
|
|
61
|
+
to an SSE (`text/event-stream`) stream for server-initiated messages.
|
|
62
|
+
|
|
63
|
+
**For hosting, use Streamable HTTP.** Our `http.ts` implements the JSON
|
|
64
|
+
request/response half (sufficient for stateless tool calls). To fully conform,
|
|
65
|
+
add:
|
|
66
|
+
|
|
67
|
+
- **SSE responses**: when a client sends `Accept: text/event-stream`, reply with
|
|
68
|
+
an event stream so the server can push notifications/progress. quantakrypto's tools
|
|
69
|
+
are currently synchronous request/response, so this is optional until we add
|
|
70
|
+
long-running scans with progress events.
|
|
71
|
+
- **`GET /mcp`**: opens a standalone SSE stream for server→client messages.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
┌────────────┐ POST /mcp (JSON-RPC) ┌──────────────────┐
|
|
75
|
+
MCP │ │ ───────────────────────► │ HTTP transport │
|
|
76
|
+
client ◄──┤ client │ │ (src/http.ts) │
|
|
77
|
+
│ │ ◄─── 200 JSON / SSE ───── │ │
|
|
78
|
+
└────────────┘ └────────┬─────────┘
|
|
79
|
+
│ handle()
|
|
80
|
+
┌────────▼─────────┐
|
|
81
|
+
│ McpServer │
|
|
82
|
+
│ (src/server.ts) │
|
|
83
|
+
└────────┬─────────┘
|
|
84
|
+
│ tools
|
|
85
|
+
┌────────▼─────────┐
|
|
86
|
+
│ @quantakrypto/core │
|
|
87
|
+
└──────────────────┘
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 2. Multi-tenant sessions
|
|
91
|
+
|
|
92
|
+
The MCP Streamable HTTP transport uses an **`Mcp-Session-Id`** header. Our
|
|
93
|
+
scaffold echoes a provided id or mints one with `randomUUID()`, but keeps **no
|
|
94
|
+
state** (every request is independent). For production:
|
|
95
|
+
|
|
96
|
+
1. On `initialize`, mint a session id, create a `Session` record, and return the
|
|
97
|
+
id in the `Mcp-Session-Id` response header.
|
|
98
|
+
2. Require that header on every subsequent request; reject unknown/expired
|
|
99
|
+
sessions with `404` (so clients re-`initialize`).
|
|
100
|
+
3. Store per-session state in a shared store (Redis / Postgres), **not** process
|
|
101
|
+
memory, so any instance behind a load balancer can serve any session:
|
|
102
|
+
- tenant/identity, negotiated protocol version, capabilities;
|
|
103
|
+
- rate-limit counters, usage metering;
|
|
104
|
+
- any in-flight long-running scan handles.
|
|
105
|
+
4. Expire idle sessions (TTL) and support explicit teardown
|
|
106
|
+
(`DELETE /mcp` with the session header).
|
|
107
|
+
|
|
108
|
+
Keep the `McpServer` instance **stateless and shared** across sessions — it
|
|
109
|
+
already is (the only mutable field is an informational `initialized` flag, which
|
|
110
|
+
should move into the session record once sessions exist).
|
|
111
|
+
|
|
112
|
+
## 3. Authentication & API keys
|
|
113
|
+
|
|
114
|
+
stdio trusts the local user; a hosted endpoint must not.
|
|
115
|
+
|
|
116
|
+
- **API keys / bearer tokens.** Require `Authorization: Bearer <token>` on
|
|
117
|
+
`/mcp`. Validate before `readBody`/dispatch. Map the key → tenant for metering
|
|
118
|
+
and quotas. This is the natural place to gate access in `handleMcpPost`.
|
|
119
|
+
- **OAuth 2.1.** The MCP spec defines an OAuth flow for HTTP transports
|
|
120
|
+
(Protected Resource Metadata + Authorization Server). For first-party clients,
|
|
121
|
+
scoped API keys are simpler; add OAuth when third-party clients must connect on
|
|
122
|
+
a user's behalf.
|
|
123
|
+
- **mTLS / network policy.** For internal/enterprise deployments, terminate mTLS
|
|
124
|
+
at the gateway and keep `/mcp` private.
|
|
125
|
+
- Never log request bodies that may contain source code; treat scanned content
|
|
126
|
+
as sensitive.
|
|
127
|
+
|
|
128
|
+
## 4. Rate limiting & quotas
|
|
129
|
+
|
|
130
|
+
- **Per-key token bucket** at the edge (gateway) and a backstop in-process limit.
|
|
131
|
+
Scans are CPU- and I/O-bound, so limit by *concurrent scans* per tenant, not
|
|
132
|
+
just request rate.
|
|
133
|
+
- **Body size cap** — already enforced (`MAX_BODY_BYTES`, 1 MiB). Make it
|
|
134
|
+
configurable and tenant-tiered.
|
|
135
|
+
- **Per-call timeouts** for tool execution; return an `isError` tool result on
|
|
136
|
+
timeout rather than hanging the connection.
|
|
137
|
+
- **Cost metering** keyed by session/tenant for billing and abuse detection.
|
|
138
|
+
|
|
139
|
+
## 5. Scaling
|
|
140
|
+
|
|
141
|
+
- **Stateless app tier.** With sessions in a shared store, run N replicas behind
|
|
142
|
+
an L7 load balancer; no sticky sessions needed for request/response. *Sticky
|
|
143
|
+
routing is required only for long-lived SSE streams* — pin those to the
|
|
144
|
+
instance that owns the stream, or use a pub/sub fan-out (Redis) so any instance
|
|
145
|
+
can deliver server→client events.
|
|
146
|
+
- **Horizontal autoscale** on CPU (scans are CPU-heavy). Consider offloading
|
|
147
|
+
large scans to a worker queue and returning progress over SSE.
|
|
148
|
+
- **Caching.** Cache `tools/list`, `list_rules`, and remediation lookups (static
|
|
149
|
+
per core version). Optionally cache scan results keyed by a content hash.
|
|
150
|
+
- **Health & readiness.** `GET /health` exists; add a readiness probe that checks
|
|
151
|
+
the session store and a liveness probe that excludes it.
|
|
152
|
+
- **Observability.** Structured logs (never bodies), per-method latency/error
|
|
153
|
+
metrics, and tracing across HTTP → `McpServer.handle` → core.
|
|
154
|
+
|
|
155
|
+
## 6. What runs server-side vs. in core
|
|
156
|
+
|
|
157
|
+
| Concern | Where |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| HTTP framing, sessions, auth, rate limiting, metering | **HTTP transport / gateway** (`src/http.ts` + infra) |
|
|
160
|
+
| JSON-RPC 2.0 dispatch, MCP methods, error mapping | **`McpServer`** (`src/server.ts`) — pure, reused by every transport |
|
|
161
|
+
| Tool schemas, argument validation, result shaping | **Tools** (`src/tools.ts`) |
|
|
162
|
+
| Crypto detection, inventory, remediation knowledge | **`@quantakrypto/core`** — the single source of truth; transports never reimplement detection logic |
|
|
163
|
+
|
|
164
|
+
The guiding principle: **transports do I/O and policy; `McpServer` does protocol;
|
|
165
|
+
`@quantakrypto/core` does cryptographic analysis.** Hosting adds an HTTP/edge layer
|
|
166
|
+
around the exact same `McpServer` used over stdio, so behaviour is identical
|
|
167
|
+
whether quantakrypto runs locally or as a service.
|
|
168
|
+
|
|
169
|
+
## 7. Minimal deployment example
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Container entrypoint. A non-loopback bind REQUIRES a token (else startup is
|
|
173
|
+
# refused). Leave QUANTAKRYPTO_MCP_ALLOW_FS unset to keep the filesystem tools off.
|
|
174
|
+
PORT=8080 \
|
|
175
|
+
QUANTAKRYPTO_MCP_HOST=0.0.0.0 \
|
|
176
|
+
QUANTAKRYPTO_MCP_TOKEN="$(cat /run/secrets/quantakrypto_mcp_token)" \
|
|
177
|
+
node dist/http.js
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Put it behind a gateway that terminates TLS, validates API keys, applies rate
|
|
181
|
+
limits, and forwards to `/mcp`. Run ≥2 replicas with a shared session store and a
|
|
182
|
+
health check on `/health`. The built-in Bearer check is a backstop; the gateway
|
|
183
|
+
should remain the primary auth boundary. Enable `QUANTAKRYPTO_MCP_ALLOW_FS=1` only when
|
|
184
|
+
the scanned path surface is sandboxed (e.g. a read-only, dedicated mount).
|
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# @quantakrypto/mcp
|
|
2
|
+
|
|
3
|
+
A **Model Context Protocol (MCP) server** that gives AI coding agents post-quantum
|
|
4
|
+
readiness superpowers. It scans code for classical (quantum-vulnerable) asymmetric
|
|
5
|
+
cryptography and recommends NIST post-quantum / hybrid migrations, all backed by
|
|
6
|
+
[`@quantakrypto/core`](../core).
|
|
7
|
+
|
|
8
|
+
- **Zero runtime dependencies.** The MCP / JSON-RPC 2.0 protocol is implemented
|
|
9
|
+
from scratch on Node built-ins (`node:readline`, `node:http`, `node:process`).
|
|
10
|
+
The only dependency is `@quantakrypto/core`.
|
|
11
|
+
- **Two transports.** A `stdio` transport (the `quantakrypto-mcp` bin) for local agents
|
|
12
|
+
like Claude, and a hostable `http` transport for running quantakrypto as a remote
|
|
13
|
+
service (see [HOSTING.md](./HOSTING.md)).
|
|
14
|
+
- **Transport-agnostic core.** All protocol logic lives in a pure, unit-tested
|
|
15
|
+
`McpServer` class; transports only do I/O.
|
|
16
|
+
|
|
17
|
+
## Install / register with an MCP client
|
|
18
|
+
|
|
19
|
+
The published package exposes a `quantakrypto-mcp` binary that speaks MCP over stdio:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Claude Code / Claude Desktop
|
|
23
|
+
claude mcp add quantakrypto npx @quantakrypto/mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Equivalently, in an MCP client config:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"quantakrypto": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["@quantakrypto/mcp"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The bin is `quantakrypto-mcp` (→ `dist/stdio.js`). You can also run it directly:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node dist/stdio.js
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Protocol
|
|
46
|
+
|
|
47
|
+
MCP **stdio transport** is newline-delimited JSON: exactly one JSON-RPC 2.0
|
|
48
|
+
message per line on stdin/stdout (this is *not* HTTP-style `Content-Length`
|
|
49
|
+
framing). Supported methods:
|
|
50
|
+
|
|
51
|
+
| Method | Notes |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `initialize` | Replies with `protocolVersion`, `capabilities.tools.listChanged = false`, and `serverInfo { name: "quantakrypto", version }`. |
|
|
54
|
+
| `notifications/initialized` | Notification; no response. |
|
|
55
|
+
| `ping` | Replies `{}`. |
|
|
56
|
+
| `tools/list` | Lists all tools with JSON-Schema `inputSchema`. |
|
|
57
|
+
| `tools/call` | Runs a tool, returns `{ content: [...], isError? }`. |
|
|
58
|
+
|
|
59
|
+
Unknown methods return JSON-RPC error `-32601`; bad params return `-32602`;
|
|
60
|
+
unparseable input returns `-32700`; non-request objects return `-32600`.
|
|
61
|
+
|
|
62
|
+
## Tools
|
|
63
|
+
|
|
64
|
+
Each tool returns MCP content: `{ content: [{ type: "text", text }], isError? }`.
|
|
65
|
+
|
|
66
|
+
### `scan_path`
|
|
67
|
+
|
|
68
|
+
Scan a file or directory for quantum-vulnerable cryptography.
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"path": { "type": "string", "description": "Path to scan." },
|
|
75
|
+
"format": { "type": "string", "enum": ["summary", "json"] }
|
|
76
|
+
},
|
|
77
|
+
"required": ["path"]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Returns a readiness summary (or the raw `ScanResult` JSON when `format: "json"`).
|
|
82
|
+
|
|
83
|
+
### `inventory_crypto`
|
|
84
|
+
|
|
85
|
+
Produce a 0–100 readiness score plus counts by algorithm, category, and severity.
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": { "path": { "type": "string" } },
|
|
91
|
+
"required": ["path"]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `explain_finding`
|
|
96
|
+
|
|
97
|
+
Explain a finding and its remediation. Provide a `ruleId`, an `algorithm`, or both.
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"type": "object",
|
|
102
|
+
"properties": {
|
|
103
|
+
"ruleId": { "type": "string" },
|
|
104
|
+
"algorithm": { "type": "string", "description": "RSA, ECDH, ECDSA, …" }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `suggest_hybrid`
|
|
110
|
+
|
|
111
|
+
Recommend a PQC / hybrid migration from an `algorithm` or free-text `context`.
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {
|
|
117
|
+
"algorithm": { "type": "string" },
|
|
118
|
+
"context": { "type": "string" }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `list_rules`
|
|
124
|
+
|
|
125
|
+
List the quantakrypto detector catalog (ids + descriptions). No input.
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{ "type": "object", "properties": {} }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `generate_cbom`
|
|
132
|
+
|
|
133
|
+
Scan a path and emit a **CycloneDX 1.6 Cryptographic Bill of Materials (CBOM)**
|
|
134
|
+
of the classical cryptographic assets found, for compliance / supply-chain
|
|
135
|
+
tooling. Reads the filesystem, so it is gated like `scan_path` over HTTP.
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"type": "object",
|
|
140
|
+
"properties": { "path": { "type": "string" } },
|
|
141
|
+
"required": ["path"]
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Hosted HTTP server (safe-by-default)
|
|
146
|
+
|
|
147
|
+
The same `McpServer` can be served over HTTP (a Streamable-HTTP-style JSON-RPC
|
|
148
|
+
endpoint) for remote deployments. The stdio transport trusts the local user and
|
|
149
|
+
is fully featured; the **HTTP transport is hardened**, because a hosted endpoint
|
|
150
|
+
is reachable by untrusted peers:
|
|
151
|
+
|
|
152
|
+
- **Binds to `127.0.0.1` by default** (not `0.0.0.0`). Override via
|
|
153
|
+
`QUANTAKRYPTO_MCP_HOST`. Binding to a non-loopback host **without a token is refused
|
|
154
|
+
at startup** (it would be an open, unauthenticated tool relay).
|
|
155
|
+
- **Bearer-token auth.** Set `QUANTAKRYPTO_MCP_TOKEN` and every `/mcp` request must
|
|
156
|
+
send `Authorization: Bearer <token>`, else `401`. With no token set, only the
|
|
157
|
+
loopback bind is allowed.
|
|
158
|
+
- **Filesystem tools are disabled by default.** `scan_path`, `inventory_crypto`
|
|
159
|
+
and `generate_cbom` read arbitrary server paths, so over HTTP they are exposed
|
|
160
|
+
only when `QUANTAKRYPTO_MCP_ALLOW_FS=1`. The knowledge tools (`explain_finding`,
|
|
161
|
+
`suggest_hybrid`, `list_rules`) are always available. `tools/list` and
|
|
162
|
+
`tools/call` both reflect the gating.
|
|
163
|
+
- **Limits.** A 1 MiB request-body cap (always), a per-request tool timeout
|
|
164
|
+
(`QUANTAKRYPTO_MCP_TIMEOUT_MS`, default 30000 → `504` on timeout) and a response-size
|
|
165
|
+
cap (`QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES`, default 4 MiB).
|
|
166
|
+
|
|
167
|
+
| Env var | Default | Purpose |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `QUANTAKRYPTO_MCP_HOST` (or `HOST`) | `127.0.0.1` | Bind interface. Non-loopback requires a token. |
|
|
170
|
+
| `PORT` | `3000` | Listen port. |
|
|
171
|
+
| `QUANTAKRYPTO_MCP_TOKEN` | _(unset)_ | When set, requires `Authorization: Bearer <token>`. |
|
|
172
|
+
| `QUANTAKRYPTO_MCP_ALLOW_FS` | _(off)_ | `1`/`true` exposes the filesystem tools over HTTP. |
|
|
173
|
+
| `QUANTAKRYPTO_MCP_TIMEOUT_MS` | `30000` | Per-request tool-execution deadline. |
|
|
174
|
+
| `QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES` | `4194304` | Response-body size cap. |
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Local, knowledge tools only (default safe posture)
|
|
178
|
+
node dist/http.js
|
|
179
|
+
|
|
180
|
+
# Local with the filesystem tools enabled
|
|
181
|
+
QUANTAKRYPTO_MCP_ALLOW_FS=1 node dist/http.js
|
|
182
|
+
|
|
183
|
+
# Reachable from the network: a token is mandatory
|
|
184
|
+
QUANTAKRYPTO_MCP_HOST=0.0.0.0 QUANTAKRYPTO_MCP_TOKEN="$(openssl rand -hex 32)" node dist/http.js
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Endpoints:
|
|
188
|
+
|
|
189
|
+
- `POST /mcp` — one JSON-RPC 2.0 message; the JSON-RPC response is the
|
|
190
|
+
`application/json` body. Notifications get `202` with no body. An
|
|
191
|
+
`mcp-session-id` header is echoed or minted on each request.
|
|
192
|
+
- `GET /health` — liveness probe returning `{ "status": "ok" }` (no auth).
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
curl -s localhost:3000/health
|
|
196
|
+
curl -s localhost:3000/mcp \
|
|
197
|
+
-H 'content-type: application/json' \
|
|
198
|
+
-H 'authorization: Bearer YOUR_TOKEN' \
|
|
199
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
See [HOSTING.md](./HOSTING.md) for the full production design (auth, multi-tenant
|
|
203
|
+
sessions, rate limiting, scaling). A sample request/response transcript lives in
|
|
204
|
+
[`examples/transcript.jsonl`](./examples/transcript.jsonl).
|
|
205
|
+
|
|
206
|
+
## Programmatic use
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import { createQuantakryptoServer } from "@quantakrypto/mcp";
|
|
210
|
+
|
|
211
|
+
const server = createQuantakryptoServer();
|
|
212
|
+
const res = await server.handle({ jsonrpc: "2.0", id: 1, method: "tools/list" });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Development
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npm run build # tsc -b
|
|
219
|
+
npm test # node --import tsx --test test/*.test.ts
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Tests drive `McpServer.handle` directly (and the stdio loop via in-memory
|
|
223
|
+
streams) — no process spawning.
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
Apache-2.0
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* quantakrypto MCP — hostable HTTP transport (Streamable-HTTP-style JSON-RPC).
|
|
4
|
+
*
|
|
5
|
+
* A zero-dependency {@link node:http} server that exposes the same
|
|
6
|
+
* {@link McpServer} over HTTP so the quantakrypto MCP can run as a remote service:
|
|
7
|
+
*
|
|
8
|
+
* POST /mcp — body is a single JSON-RPC 2.0 message; the JSON-RPC
|
|
9
|
+
* response is returned as the 200 response body
|
|
10
|
+
* (`application/json`). Notifications get HTTP 202 with no body.
|
|
11
|
+
* GET /health — liveness probe, returns `{ status: "ok", ... }`.
|
|
12
|
+
*
|
|
13
|
+
* This is the minimal-but-working core of the MCP Streamable HTTP transport.
|
|
14
|
+
* The full spec also supports an SSE response (`text/event-stream`) for
|
|
15
|
+
* server-initiated messages; this server speaks the JSON request/response half,
|
|
16
|
+
* which is sufficient for stateless tool calls. See HOSTING.md for the
|
|
17
|
+
* production design (auth, multi-tenant sessions, rate limiting, scaling).
|
|
18
|
+
*
|
|
19
|
+
* SAFE-BY-DEFAULT POSTURE (P0-1). Unlike the stdio transport — which trusts the
|
|
20
|
+
* local user and stays fully featured — the HTTP transport is hardened because a
|
|
21
|
+
* hosted endpoint is reachable by untrusted peers:
|
|
22
|
+
*
|
|
23
|
+
* - Binds to 127.0.0.1 by default (NOT 0.0.0.0). Override with QUANTAKRYPTO_MCP_HOST.
|
|
24
|
+
* - Bearer-token auth: when QUANTAKRYPTO_MCP_TOKEN is set, every /mcp request must
|
|
25
|
+
* send `Authorization: Bearer <token>` (else 401). Binding to a non-loopback
|
|
26
|
+
* host WITHOUT a token is refused at startup (it would be an open relay).
|
|
27
|
+
* - The filesystem tools (scan_path, inventory_crypto, generate_cbom) read
|
|
28
|
+
* arbitrary server paths and are DISABLED over HTTP unless QUANTAKRYPTO_MCP_ALLOW_FS=1
|
|
29
|
+
* (security audit Q-01). The knowledge tools (explain_finding, suggest_hybrid,
|
|
30
|
+
* list_rules) are always available. Gating is enforced by registering only the
|
|
31
|
+
* permitted tools, so tools/list and tools/call both reflect it.
|
|
32
|
+
* - Per-request timeout (QUANTAKRYPTO_MCP_TIMEOUT_MS) and a response-size cap
|
|
33
|
+
* (QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES), in addition to the 1 MiB request-body cap.
|
|
34
|
+
*
|
|
35
|
+
* Run with `node dist/http.js` (PORT/QUANTAKRYPTO_MCP_* from env).
|
|
36
|
+
*/
|
|
37
|
+
import type { Server } from "node:http";
|
|
38
|
+
import type { ToolDefinition } from "./protocol.js";
|
|
39
|
+
import type { McpServer } from "./server.js";
|
|
40
|
+
/** Header carrying the MCP session id (per the Streamable HTTP transport). */
|
|
41
|
+
export declare const SESSION_HEADER = "mcp-session-id";
|
|
42
|
+
export interface HttpServerOptions {
|
|
43
|
+
/** Port to listen on. Defaults to env PORT or 3000. */
|
|
44
|
+
port?: number;
|
|
45
|
+
/** Host/interface to bind. Defaults to QUANTAKRYPTO_MCP_HOST / HOST or "127.0.0.1". */
|
|
46
|
+
host?: string;
|
|
47
|
+
/** Bearer token required on /mcp. Defaults to QUANTAKRYPTO_MCP_TOKEN (empty = none). */
|
|
48
|
+
token?: string;
|
|
49
|
+
/** Expose the filesystem tools over HTTP. Defaults to QUANTAKRYPTO_MCP_ALLOW_FS=1. */
|
|
50
|
+
allowFs?: boolean;
|
|
51
|
+
/** Per-request tool-execution timeout (ms). Defaults to QUANTAKRYPTO_MCP_TIMEOUT_MS. */
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
/** Response-body size cap (bytes). Defaults to QUANTAKRYPTO_MCP_MAX_RESPONSE_BYTES. */
|
|
54
|
+
maxResponseBytes?: number;
|
|
55
|
+
}
|
|
56
|
+
/** A minimal env shape so the config resolver is pure and testable. */
|
|
57
|
+
export type HttpEnv = Record<string, string | undefined>;
|
|
58
|
+
/** Resolved, validated HTTP transport configuration. */
|
|
59
|
+
export interface HttpConfig {
|
|
60
|
+
host: string;
|
|
61
|
+
port: number;
|
|
62
|
+
/** The bearer token, or "" when auth is disabled. */
|
|
63
|
+
token: string;
|
|
64
|
+
/** Whether the filesystem tools are exposed over HTTP. */
|
|
65
|
+
allowFs: boolean;
|
|
66
|
+
timeoutMs: number;
|
|
67
|
+
maxResponseBytes: number;
|
|
68
|
+
/** True when the host is a loopback interface (safe without auth). */
|
|
69
|
+
loopback: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the HTTP transport config from env + explicit overrides. Pure: does
|
|
73
|
+
* no I/O and never reads `process` directly. Overrides win over env.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveHttpConfig(env: HttpEnv, options?: HttpServerOptions): HttpConfig;
|
|
76
|
+
/** True when binding to `host` keeps the server private to this machine. */
|
|
77
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Decide whether a non-loopback bind is permitted. A server reachable from the
|
|
80
|
+
* network MUST require auth; binding wide-open without a token would be an open,
|
|
81
|
+
* unauthenticated arbitrary-tool relay. Pure decision used at startup.
|
|
82
|
+
*/
|
|
83
|
+
export declare function startupDecision(config: HttpConfig): {
|
|
84
|
+
ok: boolean;
|
|
85
|
+
reason?: string;
|
|
86
|
+
};
|
|
87
|
+
/** A request-authorization outcome. */
|
|
88
|
+
export interface AuthDecision {
|
|
89
|
+
authorized: boolean;
|
|
90
|
+
/** HTTP status to use when not authorized. */
|
|
91
|
+
status?: number;
|
|
92
|
+
message?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Decide whether a request is authorized given the configured token and the
|
|
96
|
+
* incoming Authorization header. When no token is configured, all requests are
|
|
97
|
+
* allowed (the loopback / trusted-edge case). Pure and testable.
|
|
98
|
+
*/
|
|
99
|
+
export declare function authorizeRequest(token: string, authorizationHeader: string | undefined): AuthDecision;
|
|
100
|
+
/**
|
|
101
|
+
* Select the tools to expose over HTTP for a given policy. The knowledge tools
|
|
102
|
+
* are always returned; the filesystem tools are included only when
|
|
103
|
+
* `allowFs` is true. Pure function of its inputs — the single source of truth
|
|
104
|
+
* for HTTP tool gating, so tools/list and tools/call stay consistent.
|
|
105
|
+
*/
|
|
106
|
+
export declare function gateHttpTools(tools: readonly ToolDefinition[], allowFs: boolean): ToolDefinition[];
|
|
107
|
+
/**
|
|
108
|
+
* Build (but do not start) the HTTP server wrapping an {@link McpServer}.
|
|
109
|
+
* Exposed for testing and for embedding in a larger process. The `config`
|
|
110
|
+
* carries the resolved auth / timeout / response-cap policy.
|
|
111
|
+
*/
|
|
112
|
+
export declare function createHttpServer(server: McpServer, config: HttpConfig): Server;
|
|
113
|
+
/**
|
|
114
|
+
* Build the HTTP-facing {@link McpServer}: the same server used over stdio but
|
|
115
|
+
* with the filesystem tools gated per policy. Exposed for tests.
|
|
116
|
+
*/
|
|
117
|
+
export declare function createHttpMcpServer(config: HttpConfig): McpServer;
|
|
118
|
+
/** Start the HTTP server, resolving once it is listening. */
|
|
119
|
+
export declare function startHttpServer(options?: HttpServerOptions): Promise<Server>;
|
|
120
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,KAAK,EAAmB,MAAM,EAAkB,MAAM,WAAW,CAAC;AAQzE,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,eAAe,CAAC;AAErE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,8EAA8E;AAC9E,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAc/C,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD,uEAAuE;AACvE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEzD,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAoB3F;AASD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG;IACnD,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAUA;AAED,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,MAAM,GAAG,SAAS,GACtC,YAAY,CAYd;AAUD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,OAAO,EAAE,OAAO,GACf,cAAc,EAAE,CAGlB;AA4DD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAW9E;AAyGD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAEjE;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BhF"}
|