@gizmodata/hardcharger-mcp 2.0.0 → 2.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/CHANGELOG.md +44 -14
- package/README.md +67 -23
- package/dist/broker.d.ts +82 -0
- package/dist/broker.js +278 -0
- package/dist/broker.js.map +1 -0
- package/dist/connection.d.ts +52 -6
- package/dist/connection.js +55 -7
- package/dist/connection.js.map +1 -1
- package/dist/oauth.d.ts +27 -0
- package/dist/oauth.js +60 -11
- package/dist/oauth.js.map +1 -1
- package/dist/params.d.ts +3 -3
- package/dist/params.js +1 -1
- package/dist/params.js.map +1 -1
- package/dist/registry.d.ts +36 -5
- package/dist/registry.js +84 -11
- package/dist/registry.js.map +1 -1
- package/dist/server.js +51 -16
- package/dist/server.js.map +1 -1
- package/dist/sessions.d.ts +2 -0
- package/dist/sessions.js +5 -3
- package/dist/sessions.js.map +1 -1
- package/dist/sso.d.ts +2 -2
- package/dist/sso.js +4 -4
- package/dist/sso.js.map +1 -1
- package/dist/transports.d.ts +3 -0
- package/dist/transports.js +39 -10
- package/dist/transports.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.2.0] - 2026-10-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Per-person credentials through a connection broker (`HARDCHARGER_MCP_BROKER_URL`,
|
|
14
|
+
`HARDCHARGER_MCP_BROKER_TOKEN`, HTTP transport with OAuth). The server then holds no Hardcharger
|
|
15
|
+
credentials: a broker, such as the control plane that runs the servers, lists the servers each
|
|
16
|
+
signed-in person may reach and mints them short-lived sign-in tokens, which the client exchanges
|
|
17
|
+
(token_exchange). Every statement runs as that person, under the server's grants, row policies and
|
|
18
|
+
masking. The person's token is presented to the broker as an RFC 8693 subject token, never as its
|
|
19
|
+
credential. The broker also checks each request's access token (`/introspect`, answers reused for 60 s), so
|
|
20
|
+
opaque access tokens such as Clerk's work and this server needs no provider keys; a broker outage answers 503,
|
|
21
|
+
not 401. Writes need the broker's per-server `allow_writes` as well as `HARDCHARGER_ALLOW_WRITES`.
|
|
22
|
+
Protocol: `docs/connection-broker.md`.
|
|
23
|
+
- `list_connections` and `server_info` report whether each connection accepts writes
|
|
24
|
+
(`allow_writes`), and a brokered connection's description.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- The README said Clerk issues JWT access tokens by default; it issues opaque ones unless the instance turns JWT
|
|
28
|
+
access tokens on.
|
|
29
|
+
|
|
30
|
+
## [2.1.0] - 2026-10-29
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Built on the Chargewire protocol and clients: `@gizmodata/hardcharger` 2.1.0, on
|
|
34
|
+
`@gizmodata/chargewire` 2.0.0. `server_info` reports
|
|
35
|
+
the protocol as `chargewire 1`. Tool names, settings and connection URIs are unchanged.
|
|
36
|
+
- Still works with Hardcharger servers released before the rename (2.2.0 and earlier): the
|
|
37
|
+
client falls back to them on its own, and `HARDCHARGER_URI` values written for earlier
|
|
38
|
+
versions keep working.
|
|
39
|
+
|
|
10
40
|
## [2.0.0] - 2026-10-20
|
|
11
41
|
|
|
12
42
|
The first published release, with Hardcharger 2.0.0 (0.1.0 was never published). The Changed and
|
|
@@ -14,11 +44,11 @@ Fixed entries describe what moved since 0.1.0, for anyone who installed it from
|
|
|
14
44
|
<!-- Set the date above at tag time. -->
|
|
15
45
|
|
|
16
46
|
### Changed
|
|
17
|
-
- Built on `@gizmodata/hardcharger` 2.0.0 (hardcharger-js `a6e93a2`, on `@gizmodata/
|
|
18
|
-
|
|
47
|
+
- Built on `@gizmodata/hardcharger` 2.0.0 (hardcharger-js `a6e93a2`, on `@gizmodata/chargewire` 1.0.0,
|
|
48
|
+
chargewire-js `903feb4`), the clients for Chargewire protocol 1.0.0 with browser sign-in (were
|
|
19
49
|
`a4ac97c` and `6dea14c`). The chart, the extension manifest and the npm package are 2.0.0: the
|
|
20
50
|
Hardcharger clients and integrations launch with the server's version.
|
|
21
|
-
- **Breaking:** `login_sso` runs
|
|
51
|
+
- **Breaking:** `login_sso` runs Chargewire browser sign-in (§4.3) on the connection's own URI and
|
|
22
52
|
port, through `@gizmodata/hardcharger`, as Hardcharger now offers it (its separate OAuth port and
|
|
23
53
|
`/oauth/initiate` routes are gone). It signs in with the access token the server hands out, used
|
|
24
54
|
as is (call `login_sso` again when it expires), instead of exchanging an ID token. A failed
|
|
@@ -31,10 +61,10 @@ Fixed entries describe what moved since 0.1.0, for anyone who installed it from
|
|
|
31
61
|
`client_transport_error` and `client_connection_closed` (was `transport_error`,
|
|
32
62
|
`connection_closed`), the plain-HTTP hint keys on `client_transport_error`, and error messages
|
|
33
63
|
name the new codes (e.g. `(client_transport_error)`, `(client_invalid_argument)`).
|
|
34
|
-
- `server_info` on a
|
|
64
|
+
- `server_info` on a Chargewire server other than Hardcharger reads the DuckDB version from the
|
|
35
65
|
server-info key `duckdb_version` (was `engine_version`, which protocol 1.0.0 dropped).
|
|
36
66
|
- A session ended with reason `token_revoked`, `credentials_changed` or `principal_dropped` is no
|
|
37
|
-
longer reopened with the configured credentials (
|
|
67
|
+
longer reopened with the configured credentials (Chargewire §4.2, §5.6): the error, with its
|
|
38
68
|
reason, is returned. `killed`, `token_expired`, no reason or an unknown reason still reopen once.
|
|
39
69
|
|
|
40
70
|
### Added
|
|
@@ -50,8 +80,8 @@ Fixed entries describe what moved since 0.1.0, for anyone who installed it from
|
|
|
50
80
|
### Fixed
|
|
51
81
|
- CI's live tests on main failed: the browser sign-in suite started `hardcharger-server` with
|
|
52
82
|
`--oauth-port`, which Hardcharger 2.0.0-rc.1 removed (browser sign-in runs on the server's own
|
|
53
|
-
port,
|
|
54
|
-
`--oauth-base-url` with the server's port (from the merged
|
|
83
|
+
port, Chargewire §4.3), so the server exited with "unknown option". The suite now passes
|
|
84
|
+
`--oauth-base-url` with the server's port (from the merged Chargewire 1.0 work).
|
|
55
85
|
|
|
56
86
|
### Documentation
|
|
57
87
|
|
|
@@ -64,8 +94,8 @@ Fixed entries describe what moved since 0.1.0, for anyone who installed it from
|
|
|
64
94
|
|
|
65
95
|
The Hardcharger™ port of [gizmosql-mcp](https://github.com/gizmodata/gizmosql-mcp) 0.4.11: the same
|
|
66
96
|
tools, resource, transports, OAuth resource-server mode, per-user HTTP sessions, Claude Desktop
|
|
67
|
-
extension, container image and Helm chart, on Hardcharger's
|
|
68
|
-
`@gizmodata/hardcharger` (hardcharger-js `a4ac97c` on
|
|
97
|
+
extension, container image and Helm chart, on Hardcharger's Chargewire protocol through
|
|
98
|
+
`@gizmodata/hardcharger` (hardcharger-js `a4ac97c` on chargewire-js `6dea14c`).
|
|
69
99
|
|
|
70
100
|
### Added
|
|
71
101
|
- Tools `list_connections`, `use_connection`, `list_catalogs`, `list_schemas`, `list_tables`,
|
|
@@ -81,21 +111,21 @@ extension, container image and Helm chart, on Hardcharger's Warp SQL protocol th
|
|
|
81
111
|
(`HARDCHARGER_MTLS_CERT` + `HARDCHARGER_MTLS_KEY`: alone, the certificate is the credential), or
|
|
82
112
|
the server's browser sign-in (`login_sso`, the same OAuth routes as `hardcharger --auth-type
|
|
83
113
|
external`; `HARDCHARGER_BROWSER` names the browser). `HARDCHARGER_TLS_ROOTS` trusts a private CA.
|
|
84
|
-
- Query timeouts are the server's own (`statementTimeoutMs`,
|
|
114
|
+
- Query timeouts are the server's own (`statementTimeoutMs`, Chargewire §6.9): a runaway statement is
|
|
85
115
|
cancelled on the server and reported as `statement_timeout`, with a client-side cancel a few
|
|
86
116
|
seconds later as backstop. This covers `execute_statement` too.
|
|
87
117
|
- Lost sessions: the client replaces a session the server evicted and replays its catalog and
|
|
88
118
|
schema, including ones set by `use_schema` or a `USE` run through `run_query`; a session an
|
|
89
119
|
administrator killed, a closed connection or a transport failure reopens the connection once.
|
|
90
120
|
`server_info` reports `session_replacements` and `reconnects`.
|
|
91
|
-
- Errors keep the server's message with its
|
|
121
|
+
- Errors keep the server's message with its Chargewire code, SQLSTATE, HTTP status and request id
|
|
92
122
|
(`... (sql_error, SQLSTATE 42P01, HTTP 400, request r_...)`); errors raised before an answer get
|
|
93
123
|
their code added (`(transport_error)`), and a TLS handshake against a plain-HTTP server says to
|
|
94
124
|
add `?tls=false`.
|
|
95
125
|
- `server_info` reports `HARDCHARGER_VERSION()`, `HARDCHARGER_USER()`, `HARDCHARGER_ROLE()`, the
|
|
96
126
|
protocol and capabilities from discovery, the server's idle timeout from the session document,
|
|
97
127
|
and settings from `hardcharger_settings()`.
|
|
98
|
-
- Bind parameters travel as
|
|
128
|
+
- Bind parameters travel as Chargewire JSON parameters; a date, timestamp or binary parameter binds
|
|
99
129
|
the row as Arrow data through a prepared statement.
|
|
100
130
|
- Claude Desktop extension (`.mcpb`): one all-JavaScript bundle for every platform (no native
|
|
101
131
|
driver), with URI, token, client certificate and CA settings.
|
|
@@ -113,7 +143,7 @@ extension, container image and Helm chart, on Hardcharger's Warp SQL protocol th
|
|
|
113
143
|
- The native ADBC driver and everything around it (bundled drivers per platform,
|
|
114
144
|
`GIZMOSQL_DRIVER_LIB`, the install-script approval): the client is pure JavaScript.
|
|
115
145
|
- The timed session refresh (`GIZMOSQL_SESSION_REFRESH_SECONDS`): GizmoSQL silently recreated an
|
|
116
|
-
evicted session, so the search path had to be re-applied on a timer;
|
|
146
|
+
evicted session, so the search path had to be re-applied on a timer; Chargewire reports the loss
|
|
117
147
|
and the client replays the session's options.
|
|
118
148
|
- Host/port/plaintext settings (`GIZMOSQL_HOST`, `_PORT`, `_PLAINTEXT`): the URI says all three.
|
|
119
|
-
- Flight SQL error unwrapping and the SqlInfo fallback, which have no
|
|
149
|
+
- Flight SQL error unwrapping and the SqlInfo fallback, which have no Chargewire counterpart.
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<img src="https://img.shields.io/badge/MCP-server%20%2B%20Claude%20Desktop%20extension-d97757.svg" alt="MCP server and Claude Desktop extension">
|
|
13
13
|
<img src="https://img.shields.io/badge/TypeScript-ESM-3178C6.svg?logo=typescript&logoColor=white" alt="TypeScript, ESM">
|
|
14
14
|
<img src="https://img.shields.io/badge/Node-%E2%89%A522.19-339933.svg?logo=node.js&logoColor=white" alt="Node 22.19 or later">
|
|
15
|
-
<a href="https://github.com/gizmodata/
|
|
15
|
+
<a href="https://github.com/gizmodata/chargewire"><img src="https://img.shields.io/badge/protocol-Chargewire%201.0.0-31a8db.svg" alt="Chargewire protocol"></a>
|
|
16
16
|
<a href="https://github.com/gizmodata/hardcharger-js"><img src="https://img.shields.io/badge/built%20on-Hardcharger%20JS-31a8db.svg" alt="Built on the Hardcharger JavaScript client"></a>
|
|
17
17
|
<a href="https://github.com/gizmodata/hardcharger"><img src="https://img.shields.io/badge/server-Hardcharger-f15a24.svg" alt="Hardcharger"></a>
|
|
18
18
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg" alt="Apache 2.0 license"></a>
|
|
@@ -31,16 +31,17 @@ lets Claude Desktop, Claude Code and any other MCP client explore your schema an
|
|
|
31
31
|
a Hardcharger server you can reach from your machine (including private networks over VPN), or,
|
|
32
32
|
over Streamable HTTP, serves a whole team behind OAuth.
|
|
33
33
|
|
|
34
|
-
It speaks [
|
|
34
|
+
It speaks [Chargewire](https://github.com/gizmodata/chargewire), Hardcharger's HTTP protocol, through
|
|
35
35
|
the Hardcharger JavaScript client ([`@gizmodata/hardcharger`](https://github.com/gizmodata/hardcharger-js)):
|
|
36
36
|
pure JavaScript, no native driver, so one extension bundle serves every platform Claude Desktop
|
|
37
37
|
runs on. It is the Hardcharger port of
|
|
38
38
|
[gizmosql-mcp](https://github.com/gizmodata/gizmosql-mcp), with the same tools and settings
|
|
39
39
|
(`HARDCHARGER_*` instead of `GIZMOSQL_*`; see [CHANGELOG.md](CHANGELOG.md) for what changed).
|
|
40
40
|
|
|
41
|
-
> **Status: 2.
|
|
42
|
-
> `
|
|
43
|
-
>
|
|
41
|
+
> **Status: 2.2.0, with Hardcharger 2.3.0.** Built on `@gizmodata/hardcharger` 2.1.0 (hardcharger-js
|
|
42
|
+
> `1a524f6`, on `@gizmodata/chargewire` 2.0.0, chargewire-js `1ea10dd`, Chargewire protocol 1.0.0);
|
|
43
|
+
> tested against the image of Hardcharger's main branch and against Hardcharger 2.2.0, released
|
|
44
|
+
> before the protocol was renamed to Chargewire.
|
|
44
45
|
|
|
45
46
|
## 🧰 Tools
|
|
46
47
|
|
|
@@ -154,7 +155,7 @@ Each connection takes one credential:
|
|
|
154
155
|
| Setting | Sign-in |
|
|
155
156
|
| --- | --- |
|
|
156
157
|
| `HARDCHARGER_USERNAME` + `HARDCHARGER_PASSWORD` | Password (any character, `:` and `@` included) |
|
|
157
|
-
| `HARDCHARGER_TOKEN` | A signed sign-in token the server trusts (`--token-allowed-issuer` with `--token-jwks-uri` or `--token-signature-verify-cert-path`), sent with
|
|
158
|
+
| `HARDCHARGER_TOKEN` | A signed sign-in token the server trusts (`--token-allowed-issuer` with `--token-jwks-uri` or `--token-signature-verify-cert-path`), sent with Chargewire's `token_exchange` grant. The same token also works as the password of user `token`. Its `role` claim (`admin`, `user`, `readonly`) decides what it may do |
|
|
158
159
|
| `HARDCHARGER_MTLS_CERT` + `HARDCHARGER_MTLS_KEY` | Client certificate sign-in (mutual TLS) on a server started with `--mtls-certificate-sign-in`: the certificate is the credential. With a password or token beside them, the certificate only admits the connection |
|
|
159
160
|
| `HARDCHARGER_ENABLE_SSO=true` | No stored credential: `login_sso` signs in through the server's identity provider in your browser |
|
|
160
161
|
|
|
@@ -175,7 +176,7 @@ the server's codes: `unknown_certificate_identity` (401) for a certificate that
|
|
|
175
176
|
`HARDCHARGER_TLS_ROOTS` or `HARDCHARGER_TLS_SKIP_VERIFY`).
|
|
176
177
|
|
|
177
178
|
**Browser sign-in (`login_sso`).** On a server started with `--oauth-client-id` (and its identity
|
|
178
|
-
provider settings), `login_sso` opens the provider's login page through the server's
|
|
179
|
+
provider settings), `login_sso` opens the provider's login page through the server's Chargewire
|
|
179
180
|
browser sign-in (on the connection's own URI and port), waits for you to finish, and connects with
|
|
180
181
|
the Hardcharger access token the server hands out, kept in memory only; when it expires, call
|
|
181
182
|
`login_sso` again. A refusal names its code (`access_denied`, `identity_provider_error`,
|
|
@@ -196,7 +197,8 @@ Microsoft Entra ID, Okta (custom authorization server), Auth0, Keycloak, Cognito
|
|
|
196
197
|
is an OAuth 2.1 *resource server*: it verifies each bearer token against the provider's JWKS
|
|
197
198
|
(issuer, audience, signature, expiry) and serves the RFC 9728 metadata at
|
|
198
199
|
`/.well-known/oauth-protected-resource[/mcp]` so clients find the provider on their own. The token
|
|
199
|
-
is never forwarded; the Hardcharger connection uses the configured service credentials
|
|
200
|
+
is never forwarded; the Hardcharger connection uses the configured service credentials, or, with a
|
|
201
|
+
[connection broker](#-per-person-credentials-connection-broker), each person's own.
|
|
200
202
|
|
|
201
203
|
<!-- example: http-oauth -->
|
|
202
204
|
```bash
|
|
@@ -236,8 +238,10 @@ connector enters the app registration's client ID and secret under *Advanced set
|
|
|
236
238
|
`https://claude.ai/api/mcp/auth_callback`, *Expose an API* with the MCP URL as Application ID URI
|
|
237
239
|
and a scope such as `access_as_user`, `requestedAccessTokenVersion` 2; v2 tokens carry the client ID
|
|
238
240
|
as `aud`). **Okta** needs a custom authorization server; **Auth0** an API with the audience;
|
|
239
|
-
**Keycloak** works out of the box; **Clerk** issues
|
|
240
|
-
|
|
241
|
+
**Keycloak** works out of the box; **Clerk** issues opaque access tokens unless the instance has
|
|
242
|
+
*JWT access tokens* turned on (checked 2026-10-30), so turn that on for this mode. Providers with
|
|
243
|
+
opaque access tokens (Google) are not supported in this mode; with a connection broker they are,
|
|
244
|
+
because the broker checks the tokens.
|
|
241
245
|
|
|
242
246
|
**Static bearer token.** For a quick shared secret instead of OAuth, set
|
|
243
247
|
`HARDCHARGER_MCP_BEARER_TOKEN`; every request must then send `Authorization: Bearer <token>`. The
|
|
@@ -252,6 +256,38 @@ HARDCHARGER_MCP_BEARER_TOKEN=a-long-random-secret \
|
|
|
252
256
|
npx -y @gizmodata/hardcharger-mcp --transport http --port 3000
|
|
253
257
|
```
|
|
254
258
|
|
|
259
|
+
### 🪪 Per-person credentials (connection broker)
|
|
260
|
+
|
|
261
|
+
With a connection broker, the server holds no Hardcharger credentials at all. A broker, usually
|
|
262
|
+
the control plane that runs your Hardcharger servers (GizmoData Cloud is one), tells it which
|
|
263
|
+
servers each signed-in person may reach and mints that person a short-lived sign-in token for
|
|
264
|
+
each one. The client exchanges the token (Chargewire token_exchange), so every statement runs
|
|
265
|
+
**as that person**: the server's access control (grants, row policies, masking) decides what
|
|
266
|
+
they may see and change, and its audit log names them. One connector serves everyone, and each
|
|
267
|
+
person sees only their own servers in `list_connections`.
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
HARDCHARGER_MCP_PUBLIC_URL=https://mcp.example.com/mcp \
|
|
271
|
+
HARDCHARGER_MCP_OAUTH_ISSUER=https://login.example.com \
|
|
272
|
+
HARDCHARGER_MCP_BROKER_URL=https://control-plane.example.com/api/mcp-broker \
|
|
273
|
+
HARDCHARGER_MCP_BROKER_TOKEN=this-servers-own-broker-credential \
|
|
274
|
+
HARDCHARGER_ALLOW_WRITES=true \
|
|
275
|
+
npx -y @gizmodata/hardcharger-mcp --transport http --host 0.0.0.0 --port 3000
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The broker also checks each request's access token (`/introspect`), so the provider's tokens may be
|
|
279
|
+
opaque (Clerk's are by default) and this server needs none of the provider's keys or credentials;
|
|
280
|
+
`HARDCHARGER_MCP_OAUTH_ISSUER` only names the authorization server in the metadata clients read.
|
|
281
|
+
It needs OAuth (each request must carry a verified person) and the HTTP transport, and takes no
|
|
282
|
+
`HARDCHARGER_URI`: a configured connection's credentials would be shared by every user. The
|
|
283
|
+
person's access token is never the broker credential: the server authenticates to the broker
|
|
284
|
+
with `HARDCHARGER_MCP_BROKER_TOKEN` and presents the person's token as an RFC 8693 subject token.
|
|
285
|
+
Writes need both `HARDCHARGER_ALLOW_WRITES=true` (the server-wide ceiling) and the broker's
|
|
286
|
+
`allow_writes` for that person and server. Table resources are listed only for servers the
|
|
287
|
+
person has already used, so a resource listing never signs in to every server they can reach.
|
|
288
|
+
The protocol, for anyone writing a broker, is in
|
|
289
|
+
[docs/connection-broker.md](docs/connection-broker.md).
|
|
290
|
+
|
|
255
291
|
### Container image and Helm chart
|
|
256
292
|
|
|
257
293
|
[`Dockerfile`](Dockerfile) builds an image whose entrypoint runs the HTTP transport on port 3000, and
|
|
@@ -299,6 +335,10 @@ Only `HARDCHARGER_*` names are read.
|
|
|
299
335
|
| `HARDCHARGER_MCP_OAUTH_ALLOW_INSECURE` | `false` | Accept `http://` issuer and public URLs (local testing only) |
|
|
300
336
|
| `HARDCHARGER_MCP_SESSION_IDLE_SECONDS` | `1800` | HTTP transport with OAuth: close a user's session after this long without a request |
|
|
301
337
|
| `HARDCHARGER_MCP_MAX_SESSIONS` | `200` | HTTP transport with OAuth: cap on concurrent user sessions |
|
|
338
|
+
| `HARDCHARGER_MCP_BROKER_URL` | | HTTP transport with OAuth: a connection broker supplies each person's servers and sign-in tokens ([protocol](docs/connection-broker.md)); no `HARDCHARGER_URI` then |
|
|
339
|
+
| `HARDCHARGER_MCP_BROKER_TOKEN` | | This server's own credential at the broker (required with the URL) |
|
|
340
|
+
| `HARDCHARGER_MCP_BROKER_CACHE_SECONDS` | `60` | How long a person's server list is reused (`list_connections` always asks again) |
|
|
341
|
+
| `HARDCHARGER_MCP_BROKER_ALLOW_INSECURE` | `false` | Accept a plain `http` broker URL (local testing) |
|
|
302
342
|
| `HARDCHARGER_2_URI`, `HARDCHARGER_3_URI`, … | | Additional connections, see below |
|
|
303
343
|
| `HARDCHARGER_CONNECTIONS` | | Comma-separated names of further connections, see below |
|
|
304
344
|
|
|
@@ -346,13 +386,14 @@ clients ask before running it.
|
|
|
346
386
|
The guard is defense in depth; the real boundary is the privileges of the Hardcharger user the
|
|
347
387
|
server signs in as. A sign-in token with `role: readonly` can only read (Hardcharger refuses
|
|
348
388
|
anything else with `permission_denied`), and under Hardcharger's access control a store user's
|
|
349
|
-
grants decide what each table allows.
|
|
389
|
+
grants decide what each table allows. With a connection broker that user is the person asking,
|
|
390
|
+
not a shared service account.
|
|
350
391
|
|
|
351
392
|
- Credentials never appear in tool output, `server_info` or error messages: every message that
|
|
352
393
|
reaches the client is redacted.
|
|
353
394
|
- The row cap is enforced by the server: reads run as `SELECT * FROM (<your query>) LIMIT
|
|
354
395
|
max_rows + 1`, over one result stream that is closed at the cap, which stops the statement.
|
|
355
|
-
- The timeout is the server's (
|
|
396
|
+
- The timeout is the server's (Chargewire `statementTimeoutMs`): it cancels a statement that runs too
|
|
356
397
|
long, `execute_statement` included, and the error says `statement_timeout`. A client-side cancel
|
|
357
398
|
a few seconds later is the backstop.
|
|
358
399
|
- Lost sessions: when Hardcharger evicts an idle session, the client opens a replacement with the
|
|
@@ -397,17 +438,17 @@ After changing settings, start a new chat.
|
|
|
397
438
|
|
|
398
439
|
## 🛠️ Building and testing
|
|
399
440
|
|
|
400
|
-
Node 22.19 or later. For development and CI, `@gizmodata/hardcharger` and `@gizmodata/
|
|
441
|
+
Node 22.19 or later. For development and CI, `@gizmodata/hardcharger` and `@gizmodata/chargewire` are
|
|
401
442
|
built from [gizmodata/hardcharger-js](https://github.com/gizmodata/hardcharger-js) and
|
|
402
|
-
[gizmodata/
|
|
443
|
+
[gizmodata/chargewire-js](https://github.com/gizmodata/chargewire-js) at the commits pinned in
|
|
403
444
|
[scripts/hardcharger-js.commit](scripts/hardcharger-js.commit) and
|
|
404
|
-
[scripts/
|
|
445
|
+
[scripts/chargewire-js.commit](scripts/chargewire-js.commit), and packed to `.clients/`, where
|
|
405
446
|
`package-lock.json` finds them, so the tests run against those exact clients. `package.json`, and
|
|
406
|
-
so the published package, names the npm package and its range (`^2.
|
|
447
|
+
so the published package, names the npm package and its range (`^2.1.0`); the tarballs are a
|
|
407
448
|
stand-in for development and CI only (`scripts/check-lock.mjs` checks both).
|
|
408
449
|
|
|
409
450
|
```bash
|
|
410
|
-
npm run clients # clone both clients
|
|
451
|
+
npm run clients # clone both clients, build and pack the pinned commits
|
|
411
452
|
npm ci
|
|
412
453
|
npm run lint:ci # eslint and the lock's shape
|
|
413
454
|
npm run typecheck # src and tests
|
|
@@ -417,14 +458,18 @@ docker build -t hardcharger-mcp . # container image for the HTTP transport
|
|
|
417
458
|
helm lint charts/hardcharger-mcp # Helm chart
|
|
418
459
|
```
|
|
419
460
|
|
|
420
|
-
`
|
|
421
|
-
at local checkouts.
|
|
461
|
+
`HARDCHARGER_MCP_CHARGEWIRE_JS_REPO` and `HARDCHARGER_MCP_HARDCHARGER_JS_REPO` point
|
|
462
|
+
`npm run clients` at local checkouts.
|
|
422
463
|
|
|
423
464
|
The live suites start `hardcharger-server` themselves, on free loopback ports, with a fresh
|
|
424
465
|
administrator password that contains `:` and `@`, an empty home directory (DuckDB's secrets
|
|
425
466
|
directory included), and only `PATH`, `HOME`, `TMPDIR` and the password in its environment; the MCP
|
|
426
467
|
server under test gets `PATH`, `HOME` and the settings each test names, never your shell's. Without
|
|
427
|
-
the binary they fail; they never skip, and a run in which no test passed fails too.
|
|
468
|
+
the binary they fail; they never skip, and a run in which no test passed fails too. One suite
|
|
469
|
+
checks a Hardcharger released before the protocol was renamed to Chargewire (the
|
|
470
|
+
`ghcr.io/gizmodata/hardcharger:2.2.0` image, already running, plain HTTP), named by
|
|
471
|
+
`HARDCHARGER_MCP_TEST_OLD_SERVER` (`host:port`, with `HARDCHARGER_MCP_TEST_OLD_SERVER_PASSWORD`);
|
|
472
|
+
CI starts one. Without it a full local run leaves that suite out and says so; in CI it fails.
|
|
428
473
|
|
|
429
474
|
```bash
|
|
430
475
|
export HARDCHARGER_MCP_TEST_SERVER_BINARY=/path/to/hardcharger-server
|
|
@@ -439,9 +484,8 @@ Negative tests assert the error code (`invalid_credentials`, `permission_denied`
|
|
|
439
484
|
`invalid_option_value`), each beside a positive twin. CI runs everything against the image of
|
|
440
485
|
Hardcharger's main branch (`ghcr.io/gizmodata/hardcharger:main`), pulled with the workflow's
|
|
441
486
|
`GITHUB_TOKEN`; while the image is private the package must grant this repository read access
|
|
442
|
-
(package settings, "Manage Actions access"), or the live jobs fail. CI clones
|
|
443
|
-
|
|
444
|
-
repositories (needed while they are private).
|
|
487
|
+
(package settings, "Manage Actions access"), or the live jobs fail. CI clones both clients
|
|
488
|
+
anonymously over https.
|
|
445
489
|
|
|
446
490
|
## 📦 Releasing
|
|
447
491
|
|
package/dist/broker.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type BrokerConfig, type McpConfig } from "./connection.js";
|
|
3
|
+
import type { IntrospectionResult } from "./oauth.js";
|
|
4
|
+
import { ConnectionRegistry } from "./registry.js";
|
|
5
|
+
/** RFC 8693 token type of the caller's OAuth access token. */
|
|
6
|
+
export declare const ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
|
|
7
|
+
declare const connectionSchema: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
11
|
+
uri: z.ZodString;
|
|
12
|
+
default_catalog: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
13
|
+
default_schema: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
14
|
+
allow_writes: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export type BrokeredConnection = z.infer<typeof connectionSchema>;
|
|
17
|
+
/** A broker request that failed; `message` is safe to show the caller. */
|
|
18
|
+
export declare class BrokerError extends Error {
|
|
19
|
+
readonly status?: number | undefined;
|
|
20
|
+
constructor(message: string, status?: number | undefined);
|
|
21
|
+
}
|
|
22
|
+
export interface BrokerHooks {
|
|
23
|
+
/** Test hook: the fetch implementation. */
|
|
24
|
+
fetch?: typeof fetch;
|
|
25
|
+
/** Test hook: clock (ms). */
|
|
26
|
+
now?: () => number;
|
|
27
|
+
}
|
|
28
|
+
/** Talks to the broker's two endpoints. Stateless; one instance serves every caller. */
|
|
29
|
+
export declare class BrokerClient {
|
|
30
|
+
readonly config: BrokerConfig;
|
|
31
|
+
private readonly fetchImpl;
|
|
32
|
+
constructor(config: BrokerConfig, hooks?: BrokerHooks);
|
|
33
|
+
/** The servers the caller may reach. */
|
|
34
|
+
listConnections(subjectToken: string): Promise<{
|
|
35
|
+
connections: unknown[];
|
|
36
|
+
message?: string;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the caller's access token is active, and whose it is: the broker verifies the tokens of
|
|
40
|
+
* the provider it trusts, opaque ones included, so this server needs no provider credentials.
|
|
41
|
+
*/
|
|
42
|
+
introspect(subjectToken: string): Promise<IntrospectionResult>;
|
|
43
|
+
/** A sign-in token for the caller on one server, and its lifetime in seconds. */
|
|
44
|
+
issueToken(subjectToken: string, connectionId: string): Promise<{
|
|
45
|
+
token: string;
|
|
46
|
+
expiresIn: number;
|
|
47
|
+
}>;
|
|
48
|
+
private post;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One caller's connections, as the broker lists them. Refreshed on a request when the list is
|
|
52
|
+
* older than HARDCHARGER_MCP_BROKER_CACHE_SECONDS (and by list_connections at once); servers
|
|
53
|
+
* the broker no longer lists are closed. Each connection signs in with a token the broker
|
|
54
|
+
* mints for this caller.
|
|
55
|
+
*/
|
|
56
|
+
export declare class BrokeredRegistry extends ConnectionRegistry {
|
|
57
|
+
readonly broker: BrokerClient;
|
|
58
|
+
private callerToken;
|
|
59
|
+
private listedAt;
|
|
60
|
+
private refreshing;
|
|
61
|
+
private lastProblem;
|
|
62
|
+
private emptyMessage;
|
|
63
|
+
private readonly clock;
|
|
64
|
+
constructor(config: McpConfig, broker: BrokerClient, log: (message: string) => void, hooks?: Pick<BrokerHooks, "now">);
|
|
65
|
+
now(): number;
|
|
66
|
+
/** The caller's current OAuth access token (verified by the transport), presented to the broker. */
|
|
67
|
+
subjectToken(): string;
|
|
68
|
+
/** Records the token of the request being served: the newest one the client sent. */
|
|
69
|
+
setSubjectToken(token: string): void;
|
|
70
|
+
/** Asks the broker again when the list is stale (or always, with `force`). Never throws. */
|
|
71
|
+
refresh({ force }?: {
|
|
72
|
+
force?: boolean;
|
|
73
|
+
}): Promise<void>;
|
|
74
|
+
private load;
|
|
75
|
+
/** The broker's own words when it listed nothing, or why it could not be asked. */
|
|
76
|
+
status(): string | undefined;
|
|
77
|
+
protected noConnectionsMessage(): string;
|
|
78
|
+
/** Only servers this caller already uses: listing every table would sign in to every server they can reach. */
|
|
79
|
+
resourceConnections(): import("./connection.js").HardchargerConnection[];
|
|
80
|
+
secrets(): Array<string | undefined>;
|
|
81
|
+
}
|
|
82
|
+
export {};
|