@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/dist/broker.js ADDED
@@ -0,0 +1,278 @@
1
+ // Connection broker: per-user connections and sign-in tokens for the hosted HTTP transport.
2
+ //
3
+ // With HARDCHARGER_MCP_BROKER_URL, this server holds no Hardcharger credentials of its own. A
4
+ // broker (a control plane such as GizmoData Cloud) knows which Hardcharger servers each person
5
+ // may reach and mints short-lived sign-in tokens for them; the client exchanges such a token
6
+ // (Chargewire token_exchange), so every statement runs as the person who asked, under that
7
+ // person's grants. The protocol is in docs/connection-broker.md.
8
+ //
9
+ // The caller's OAuth access token is never forwarded as a credential (MCP forbids token
10
+ // passthrough). Each broker request authenticates this server with its own credential and
11
+ // presents the caller's token as an RFC 8693 subject token, which the broker verifies and
12
+ // maps to a person. Only the broker decides what that person may reach.
13
+ import { z } from "zod";
14
+ import { parseHardchargerUri, } from "./connection.js";
15
+ import { ConnectionRegistry } from "./registry.js";
16
+ /** RFC 8693 token type of the caller's OAuth access token. */
17
+ export const ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
18
+ /** A minted token is reused until this share of its lifetime has passed. */
19
+ const TOKEN_REUSE_FRACTION = 0.8;
20
+ /** Connection names the tools accept (the same rule as configured connections). */
21
+ const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
22
+ const connectionSchema = z.object({
23
+ id: z.string().min(1),
24
+ name: z.string().min(1),
25
+ label: z.string().nullish(),
26
+ uri: z.string().min(1),
27
+ default_catalog: z.string().nullish(),
28
+ default_schema: z.string().nullish(),
29
+ allow_writes: z.boolean().default(false),
30
+ });
31
+ const listSchema = z.object({
32
+ connections: z.array(z.unknown()),
33
+ /** Shown to the caller when the list is empty (e.g. how to create a server). */
34
+ message: z.string().nullish(),
35
+ });
36
+ const introspectionSchema = z.object({
37
+ active: z.boolean(),
38
+ sub: z.string().nullish(),
39
+ email: z.string().nullish(),
40
+ name: z.string().nullish(),
41
+ });
42
+ const tokenSchema = z.object({
43
+ token: z.string().min(1),
44
+ expires_in: z.number().positive(),
45
+ });
46
+ /** A broker request that failed; `message` is safe to show the caller. */
47
+ export class BrokerError extends Error {
48
+ status;
49
+ constructor(message, status) {
50
+ super(message);
51
+ this.status = status;
52
+ this.name = "BrokerError";
53
+ }
54
+ }
55
+ /** The broker's error text from a JSON body (`error_description`, `message` or a string `detail`). */
56
+ function brokerDetail(body) {
57
+ if (!body || typeof body !== "object")
58
+ return undefined;
59
+ for (const key of ["error_description", "message", "detail"]) {
60
+ const value = body[key];
61
+ if (typeof value === "string" && value.trim() !== "")
62
+ return value.trim();
63
+ }
64
+ return undefined;
65
+ }
66
+ /** Talks to the broker's two endpoints. Stateless; one instance serves every caller. */
67
+ export class BrokerClient {
68
+ config;
69
+ fetchImpl;
70
+ constructor(config, hooks = {}) {
71
+ this.config = config;
72
+ this.fetchImpl = hooks.fetch ?? fetch;
73
+ }
74
+ /** The servers the caller may reach. */
75
+ async listConnections(subjectToken) {
76
+ const body = listSchema.safeParse(await this.post("/connections", subjectToken));
77
+ if (!body.success)
78
+ throw new BrokerError("The connection broker answered the connection list with an unexpected body.");
79
+ return { connections: body.data.connections, message: body.data.message ?? undefined };
80
+ }
81
+ /**
82
+ * Whether the caller's access token is active, and whose it is: the broker verifies the tokens of
83
+ * the provider it trusts, opaque ones included, so this server needs no provider credentials.
84
+ */
85
+ async introspect(subjectToken) {
86
+ const body = introspectionSchema.safeParse(await this.post("/introspect", subjectToken));
87
+ if (!body.success)
88
+ throw new BrokerError("The connection broker answered the token check with an unexpected body.");
89
+ const { active, sub, email, name } = body.data;
90
+ return { active, sub: sub ?? undefined, email: email ?? undefined, name: name ?? undefined };
91
+ }
92
+ /** A sign-in token for the caller on one server, and its lifetime in seconds. */
93
+ async issueToken(subjectToken, connectionId) {
94
+ const body = tokenSchema.safeParse(await this.post(`/connections/${encodeURIComponent(connectionId)}/token`, subjectToken));
95
+ if (!body.success)
96
+ throw new BrokerError("The connection broker answered the token request with an unexpected body.");
97
+ return { token: body.data.token, expiresIn: body.data.expires_in };
98
+ }
99
+ async post(path, subjectToken) {
100
+ let res;
101
+ try {
102
+ res = await this.fetchImpl(`${this.config.url}${path}`, {
103
+ method: "POST",
104
+ headers: {
105
+ authorization: `Bearer ${this.config.credential}`,
106
+ "content-type": "application/json",
107
+ accept: "application/json",
108
+ },
109
+ body: JSON.stringify({ subject_token: subjectToken, subject_token_type: ACCESS_TOKEN_TYPE }),
110
+ signal: AbortSignal.timeout(this.config.timeoutMs),
111
+ redirect: "error",
112
+ });
113
+ }
114
+ catch (err) {
115
+ const reason = err instanceof Error && err.name === "TimeoutError" ? `no answer within ${this.config.timeoutMs} ms` : "unreachable";
116
+ throw new BrokerError(`The connection broker is ${reason}; try again shortly.`);
117
+ }
118
+ let body;
119
+ try {
120
+ body = await res.json();
121
+ }
122
+ catch {
123
+ body = undefined;
124
+ }
125
+ if (res.ok)
126
+ return body;
127
+ const detail = brokerDetail(body);
128
+ if (res.status === 401) {
129
+ // The broker refused this server's own credential (or found the caller's token invalid).
130
+ throw new BrokerError(detail ?? "The connection broker refused this request; reconnect the connector, or ask the administrator to check this server's broker credential.", 401);
131
+ }
132
+ throw new BrokerError(detail ?? `The connection broker answered HTTP ${res.status}.`, res.status);
133
+ }
134
+ }
135
+ /** Sign-in tokens for one caller on one server, minted by the broker and reused for most of their life. */
136
+ class BrokeredTokenSource {
137
+ registry;
138
+ connectionId;
139
+ cached;
140
+ inFlight;
141
+ issued = new Set();
142
+ constructor(registry, connectionId) {
143
+ this.registry = registry;
144
+ this.connectionId = connectionId;
145
+ }
146
+ async token() {
147
+ const now = this.registry.now();
148
+ if (this.cached && now < this.cached.reuseUntil)
149
+ return this.cached.token;
150
+ this.inFlight ??= this.registry.broker
151
+ .issueToken(this.registry.subjectToken(), this.connectionId)
152
+ .then(({ token, expiresIn }) => {
153
+ this.cached = { token, reuseUntil: this.registry.now() + expiresIn * 1000 * TOKEN_REUSE_FRACTION };
154
+ this.issued.add(token);
155
+ return token;
156
+ })
157
+ .finally(() => {
158
+ this.inFlight = undefined;
159
+ });
160
+ return this.inFlight;
161
+ }
162
+ secrets() {
163
+ return [...this.issued];
164
+ }
165
+ }
166
+ /**
167
+ * One caller's connections, as the broker lists them. Refreshed on a request when the list is
168
+ * older than HARDCHARGER_MCP_BROKER_CACHE_SECONDS (and by list_connections at once); servers
169
+ * the broker no longer lists are closed. Each connection signs in with a token the broker
170
+ * mints for this caller.
171
+ */
172
+ export class BrokeredRegistry extends ConnectionRegistry {
173
+ broker;
174
+ callerToken = "";
175
+ listedAt;
176
+ refreshing;
177
+ lastProblem;
178
+ emptyMessage;
179
+ clock;
180
+ constructor(config, broker, log, hooks = {}) {
181
+ super(config, log, { connections: [], allowEmpty: true });
182
+ this.broker = broker;
183
+ this.clock = hooks.now ?? (() => Date.now());
184
+ }
185
+ now() {
186
+ return this.clock();
187
+ }
188
+ /** The caller's current OAuth access token (verified by the transport), presented to the broker. */
189
+ subjectToken() {
190
+ return this.callerToken;
191
+ }
192
+ /** Records the token of the request being served: the newest one the client sent. */
193
+ setSubjectToken(token) {
194
+ this.callerToken = token;
195
+ }
196
+ /** Asks the broker again when the list is stale (or always, with `force`). Never throws. */
197
+ async refresh({ force = false } = {}) {
198
+ const ttl = (this.config.broker?.cacheSeconds ?? 0) * 1000;
199
+ if (!force && this.listedAt !== undefined && this.now() - this.listedAt < ttl)
200
+ return;
201
+ this.refreshing ??= this.load().finally(() => {
202
+ this.refreshing = undefined;
203
+ });
204
+ await this.refreshing;
205
+ }
206
+ async load() {
207
+ let listed;
208
+ try {
209
+ listed = await this.broker.listConnections(this.callerToken);
210
+ }
211
+ catch (err) {
212
+ // Keep what the caller had; say why on the next tool call that finds nothing.
213
+ this.lastProblem = err instanceof Error ? err.message : String(err);
214
+ this.log(`[hardcharger-mcp] connection broker: ${this.redact(this.lastProblem)}`);
215
+ return;
216
+ }
217
+ const configs = [];
218
+ const seen = new Set();
219
+ for (const raw of listed.connections) {
220
+ const parsed = connectionSchema.safeParse(raw);
221
+ if (!parsed.success) {
222
+ this.log("[hardcharger-mcp] connection broker: skipped a connection with an unexpected shape");
223
+ continue;
224
+ }
225
+ const c = parsed.data;
226
+ if (!NAME_PATTERN.test(c.name) || seen.has(c.name.toLowerCase())) {
227
+ this.log(`[hardcharger-mcp] connection broker: skipped connection ${JSON.stringify(c.name)} (invalid or repeated name)`);
228
+ continue;
229
+ }
230
+ let target;
231
+ try {
232
+ target = parseHardchargerUri(`connection ${c.name}`, c.uri);
233
+ }
234
+ catch (err) {
235
+ this.log(`[hardcharger-mcp] connection broker: skipped ${c.name}: ${err instanceof Error ? err.message : String(err)}`);
236
+ continue;
237
+ }
238
+ seen.add(c.name.toLowerCase());
239
+ configs.push({
240
+ name: c.name,
241
+ ...target,
242
+ tlsSkipVerify: false,
243
+ defaultCatalog: c.default_catalog ?? undefined,
244
+ defaultSchema: c.default_schema ?? undefined,
245
+ queryTimeoutSeconds: this.config.queryTimeoutSeconds,
246
+ id: c.id,
247
+ label: c.label ?? undefined,
248
+ allowWrites: c.allow_writes,
249
+ tokenSource: new BrokeredTokenSource(this, c.id),
250
+ });
251
+ }
252
+ this.lastProblem = undefined;
253
+ this.emptyMessage = listed.message;
254
+ this.listedAt = this.now();
255
+ await this.replaceConnections(configs);
256
+ }
257
+ /** The broker's own words when it listed nothing, or why it could not be asked. */
258
+ status() {
259
+ if (this.lastProblem !== undefined)
260
+ return this.lastProblem;
261
+ if (this.names().length === 0)
262
+ return this.noConnectionsMessage();
263
+ return undefined;
264
+ }
265
+ noConnectionsMessage() {
266
+ if (this.lastProblem !== undefined)
267
+ return this.lastProblem;
268
+ return this.emptyMessage ?? "You have no Hardcharger servers to connect to.";
269
+ }
270
+ /** Only servers this caller already uses: listing every table would sign in to every server they can reach. */
271
+ resourceConnections() {
272
+ return this.all().filter((c) => c.connectedAt !== null);
273
+ }
274
+ secrets() {
275
+ return [...super.secrets(), this.callerToken];
276
+ }
277
+ }
278
+ //# sourceMappingURL=broker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker.js","sourceRoot":"","sources":["../src/broker.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,17 @@ import { Table } from "apache-arrow";
3
3
  import type { OAuthConfig } from "./oauth.js";
4
4
  import type { BoundParams } from "./params.js";
5
5
  /** How a connection signs in. */
6
- export type AuthMethod = "password" | "token" | "mtls" | "sso" | "none";
6
+ export type AuthMethod = "password" | "token" | "mtls" | "sso" | "brokered" | "none";
7
+ /**
8
+ * Where a brokered connection gets its sign-in tokens: the connection broker mints one for the
9
+ * calling user each time the client signs in (see broker.ts).
10
+ */
11
+ export interface TokenSource {
12
+ /** A signed sign-in token for this user and server, for the token_exchange grant. */
13
+ token(): Promise<string>;
14
+ /** Tokens this source has handed out or holds, for redaction. */
15
+ secrets(): string[];
16
+ }
7
17
  /** Settings for one Hardcharger server. */
8
18
  export interface ConnectionConfig {
9
19
  /** Unique, case-insensitive name used in tool arguments (e.g. "default", "prod"). */
@@ -31,6 +41,18 @@ export interface ConnectionConfig {
31
41
  defaultSchema?: string;
32
42
  /** Per-statement timeout applied to this connection's statements (0 = none). */
33
43
  queryTimeoutSeconds: number;
44
+ /** Brokered connections: the broker's id for this server. */
45
+ id?: string;
46
+ /** Brokered connections: a human description (e.g. organization / project / cluster). */
47
+ label?: string;
48
+ /**
49
+ * Brokered connections: whether the broker lets this user write through this server. Writes
50
+ * also need HARDCHARGER_ALLOW_WRITES, which stays the server-wide ceiling. Unset means the
51
+ * server-wide setting alone decides.
52
+ */
53
+ allowWrites?: boolean;
54
+ /** Brokered connections: the per-user sign-in tokens (token_exchange grant). */
55
+ tokenSource?: TokenSource;
34
56
  }
35
57
  /** Whole-server settings plus every configured connection (the first is the initial default). */
36
58
  export interface McpConfig {
@@ -49,6 +71,22 @@ export interface McpConfig {
49
71
  mcpMaxSessions: number;
50
72
  /** Enables the optional `login_sso` tool (the server's OAuth browser sign-in). */
51
73
  enableSso: boolean;
74
+ /**
75
+ * HTTP transport with OAuth: a connection broker supplies each caller's connections and
76
+ * sign-in tokens (HARDCHARGER_MCP_BROKER_URL); `connections` is then empty.
77
+ */
78
+ broker?: BrokerConfig;
79
+ }
80
+ /** Settings for the connection broker (see broker.ts and docs/connection-broker.md). */
81
+ export interface BrokerConfig {
82
+ /** Base URL of the broker's API: `<url>/connections` and `<url>/connections/<id>/token`. */
83
+ url: string;
84
+ /** This MCP server's own credential at the broker, sent as a bearer token. */
85
+ credential: string;
86
+ /** How long a caller's connection list is reused before the broker is asked again. */
87
+ cacheSeconds: number;
88
+ /** Timeout of one broker request. */
89
+ timeoutMs: number;
52
90
  }
53
91
  export declare const DEFAULTS: {
54
92
  readonly maxRows: 500;
@@ -57,6 +95,8 @@ export declare const DEFAULTS: {
57
95
  readonly connectionName: "default";
58
96
  readonly sessionIdleSeconds: 1800;
59
97
  readonly maxSessions: 200;
98
+ readonly brokerCacheSeconds: 60;
99
+ readonly brokerTimeoutMs: 10000;
60
100
  };
61
101
  /** Extra connection slots the Claude Desktop manifest exposes (`HARDCHARGER_2_*`, `HARDCHARGER_3_*`). */
62
102
  export declare const MANIFEST_SLOTS: readonly ["2", "3"];
@@ -92,6 +132,12 @@ export declare function parseHardchargerUri(name: string, raw: string): {
92
132
  * Slots without a URI are ignored. At least one connection is required.
93
133
  */
94
134
  export declare function parseConfig(env?: NodeJS.ProcessEnv): McpConfig;
135
+ /**
136
+ * Reads the connection broker settings (HTTP transport with OAuth). Returns undefined unless
137
+ * HARDCHARGER_MCP_BROKER_URL is set. A plain http URL needs
138
+ * HARDCHARGER_MCP_BROKER_ALLOW_INSECURE=true (local testing).
139
+ */
140
+ export declare function parseBroker(env: NodeJS.ProcessEnv): BrokerConfig | undefined;
95
141
  /** Default claim precedence used to name an authenticated caller. */
96
142
  export declare const DEFAULT_USER_CLAIMS: readonly ["email", "preferred_username", "upn", "name", "sub"];
97
143
  /**
@@ -102,7 +148,7 @@ export declare const DEFAULT_USER_CLAIMS: readonly ["email", "preferred_username
102
148
  */
103
149
  export declare function parseOAuth(env: NodeJS.ProcessEnv): OAuthConfig | undefined;
104
150
  /** The sign-in a connection's settings call for (before any login_sso). */
105
- export declare function authMethod(config: Pick<ConnectionConfig, "username" | "token" | "mtlsCertFile">): AuthMethod;
151
+ export declare function authMethod(config: Pick<ConnectionConfig, "username" | "token" | "mtlsCertFile" | "tokenSource">): AuthMethod;
106
152
  /** Replaces every configured secret in `text` with `***`. */
107
153
  export declare function redactSecrets(text: string, secrets: Array<string | undefined>): string;
108
154
  export declare class QueryTimeoutError extends Error {
@@ -114,7 +160,7 @@ export interface CappedResult {
114
160
  /** True when more rows existed beyond `maxRows` (the extra row is not in `table`). */
115
161
  truncated: boolean;
116
162
  }
117
- /** The error's Hardcharger (Warp SQL) code, if it is one of the client's errors. */
163
+ /** The error's Hardcharger (Chargewire) code, if it is one of the client's errors. */
118
164
  export declare function errorCode(err: unknown): string | undefined;
119
165
  /**
120
166
  * Does this error mean the connection (or the server's session behind it) is gone in a
@@ -159,7 +205,7 @@ export interface ConnectionHooks {
159
205
  /**
160
206
  * One Hardcharger connection per configured server, opened lazily and serialized so a
161
207
  * single statement is in flight at a time. Timeouts are enforced by the server
162
- * (`statementTimeoutMs`, Warp SQL §6.9) with a client-side abort as backstop that cancels
208
+ * (`statementTimeoutMs`, Chargewire §6.9) with a client-side abort as backstop that cancels
163
209
  * the statement on the server; only if that fails to settle the call is the connection
164
210
  * dropped and reopened. The client replaces a session the server lost and replays its
165
211
  * catalog and schema; a connection that is gone altogether (a killed session, a closed
@@ -187,7 +233,7 @@ export declare class HardchargerConnection {
187
233
  /** The search path (catalog, schema) applied to every session. */
188
234
  currentSearchPath(): SearchPath;
189
235
  /**
190
- * Switches the session's default catalog and/or schema (Warp SQL session options, which
236
+ * Switches the session's default catalog and/or schema (Chargewire session options, which
191
237
  * the client replays on a replacement session). Throws when the server refuses it.
192
238
  */
193
239
  useSchema(path: SearchPath): Promise<SearchPath>;
@@ -225,7 +271,7 @@ export declare class HardchargerConnection {
225
271
  /** Opens a fresh client and applies the session settings. */
226
272
  private open;
227
273
  /**
228
- * The search path, as Warp SQL session options. A catalog or schema the server does not
274
+ * The search path, as Chargewire session options. A catalog or schema the server does not
229
275
  * have is reported (with the catalogs it does have) instead of failing the connection;
230
276
  * the server's default then applies.
231
277
  */
@@ -10,6 +10,8 @@ export const DEFAULTS = {
10
10
  connectionName: "default",
11
11
  sessionIdleSeconds: 1800,
12
12
  maxSessions: 200,
13
+ brokerCacheSeconds: 60,
14
+ brokerTimeoutMs: 10_000,
13
15
  };
14
16
  /** Extra connection slots the Claude Desktop manifest exposes (`HARDCHARGER_2_*`, `HARDCHARGER_3_*`). */
15
17
  export const MANIFEST_SLOTS = ["2", "3"];
@@ -170,7 +172,13 @@ export function parseConfig(env = process.env) {
170
172
  if (c)
171
173
  connections.push(c);
172
174
  }
173
- if (connections.length === 0) {
175
+ const broker = parseBroker(env);
176
+ if (broker !== undefined && connections.length > 0) {
177
+ throw new ConfigError("HARDCHARGER_MCP_BROKER_URL supplies every caller's connections; remove " +
178
+ `${connections.map((c) => c.name).join(", ")} (HARDCHARGER_URI and the other connection settings): a configured ` +
179
+ "connection's credentials would be shared by every user of this server.");
180
+ }
181
+ if (connections.length === 0 && broker === undefined) {
174
182
  throw new ConfigError("HARDCHARGER_URI is required: the Hardcharger server's URI, e.g. hardcharger://db.example.com " +
175
183
  "(TLS to port 31337), or hardcharger://localhost?tls=false for a server without TLS.");
176
184
  }
@@ -187,6 +195,10 @@ export function parseConfig(env = process.env) {
187
195
  if (mcpBearerToken !== undefined && mcpOAuth !== undefined) {
188
196
  throw new ConfigError("HARDCHARGER_MCP_BEARER_TOKEN and HARDCHARGER_MCP_OAUTH_ISSUER are mutually exclusive: pick one way to authenticate HTTP clients.");
189
197
  }
198
+ if (broker !== undefined && mcpOAuth === undefined) {
199
+ throw new ConfigError("HARDCHARGER_MCP_BROKER_URL needs HARDCHARGER_MCP_OAUTH_ISSUER: the broker issues credentials per signed-in user, " +
200
+ "so every request must carry a verified identity (HTTP transport with OAuth).");
201
+ }
190
202
  return {
191
203
  connections,
192
204
  allowWrites: parseBoolean("HARDCHARGER_ALLOW_WRITES", env.HARDCHARGER_ALLOW_WRITES, false),
@@ -204,6 +216,35 @@ export function parseConfig(env = process.env) {
204
216
  max: 100000,
205
217
  }),
206
218
  enableSso,
219
+ broker,
220
+ };
221
+ }
222
+ /**
223
+ * Reads the connection broker settings (HTTP transport with OAuth). Returns undefined unless
224
+ * HARDCHARGER_MCP_BROKER_URL is set. A plain http URL needs
225
+ * HARDCHARGER_MCP_BROKER_ALLOW_INSECURE=true (local testing).
226
+ */
227
+ export function parseBroker(env) {
228
+ const allowHttp = parseBoolean("HARDCHARGER_MCP_BROKER_ALLOW_INSECURE", env.HARDCHARGER_MCP_BROKER_ALLOW_INSECURE, false);
229
+ const url = parseHttpsUrl("HARDCHARGER_MCP_BROKER_URL", env.HARDCHARGER_MCP_BROKER_URL, { allowHttp });
230
+ if (url === undefined) {
231
+ for (const key of Object.keys(env)) {
232
+ if (key.startsWith("HARDCHARGER_MCP_BROKER_") && key !== "HARDCHARGER_MCP_BROKER_ALLOW_INSECURE" && !isUnset(env[key])) {
233
+ throw new ConfigError(`${key} is set but HARDCHARGER_MCP_BROKER_URL is not; the URL enables the connection broker.`);
234
+ }
235
+ }
236
+ return undefined;
237
+ }
238
+ const credential = nonEmpty(env.HARDCHARGER_MCP_BROKER_TOKEN);
239
+ if (credential === undefined) {
240
+ throw new ConfigError("HARDCHARGER_MCP_BROKER_TOKEN is required with HARDCHARGER_MCP_BROKER_URL: this server's own credential at the " +
241
+ "broker. Callers' tokens are presented to the broker as subject tokens, never as its credential.");
242
+ }
243
+ return {
244
+ url,
245
+ credential,
246
+ cacheSeconds: parseInteger("HARDCHARGER_MCP_BROKER_CACHE_SECONDS", env.HARDCHARGER_MCP_BROKER_CACHE_SECONDS, DEFAULTS.brokerCacheSeconds, { min: 0, max: 3600 }),
247
+ timeoutMs: DEFAULTS.brokerTimeoutMs,
207
248
  };
208
249
  }
209
250
  /** Splits a comma- or whitespace-separated list, dropping blanks. */
@@ -274,6 +315,8 @@ export function parseOAuth(env) {
274
315
  }
275
316
  /** The sign-in a connection's settings call for (before any login_sso). */
276
317
  export function authMethod(config) {
318
+ if (config.tokenSource !== undefined)
319
+ return "brokered";
277
320
  if (config.username !== undefined)
278
321
  return "password";
279
322
  if (config.token !== undefined)
@@ -306,12 +349,12 @@ export class QueryTimeoutError extends Error {
306
349
  const CLIENT_DEADLINE_GRACE_MS = 5000;
307
350
  /** If an abort does not settle the call, drop the connection after this. */
308
351
  const HARD_RESET_GRACE_MS = 15000;
309
- /** The error's Hardcharger (Warp SQL) code, if it is one of the client's errors. */
352
+ /** The error's Hardcharger (Chargewire) code, if it is one of the client's errors. */
310
353
  export function errorCode(err) {
311
354
  return err instanceof HardchargerError ? err.code : undefined;
312
355
  }
313
356
  /**
314
- * `session_ended` reasons after which the user's sign-in itself no longer holds (Warp SQL
357
+ * `session_ended` reasons after which the user's sign-in itself no longer holds (Chargewire
315
358
  * §4.2, §5.6): a revoked token, changed credentials, a dropped user. Nothing signs in again
316
359
  * on its own after these; the error is the answer.
317
360
  */
@@ -365,7 +408,7 @@ function readPem(what, file) {
365
408
  /**
366
409
  * One Hardcharger connection per configured server, opened lazily and serialized so a
367
410
  * single statement is in flight at a time. Timeouts are enforced by the server
368
- * (`statementTimeoutMs`, Warp SQL §6.9) with a client-side abort as backstop that cancels
411
+ * (`statementTimeoutMs`, Chargewire §6.9) with a client-side abort as backstop that cancels
369
412
  * the statement on the server; only if that fails to settle the call is the connection
370
413
  * dropped and reopened. The client replaces a session the server lost and replays its
371
414
  * catalog and schema; a connection that is gone altogether (a killed session, a closed
@@ -400,7 +443,7 @@ export class HardchargerConnection {
400
443
  return { ...this.searchPath };
401
444
  }
402
445
  /**
403
- * Switches the session's default catalog and/or schema (Warp SQL session options, which
446
+ * Switches the session's default catalog and/or schema (Chargewire session options, which
404
447
  * the client replays on a replacement session). Throws when the server refuses it.
405
448
  */
406
449
  async useSchema(path) {
@@ -435,7 +478,7 @@ export class HardchargerConnection {
435
478
  }
436
479
  /** Secrets to redact from any message that might reach a client. */
437
480
  secrets() {
438
- return [this.config.password, this.config.token, this.overrides.accessToken];
481
+ return [this.config.password, this.config.token, this.overrides.accessToken, ...(this.config.tokenSource?.secrets() ?? [])];
439
482
  }
440
483
  redact(text) {
441
484
  return redactSecrets(text, this.secrets());
@@ -513,6 +556,11 @@ export class HardchargerConnection {
513
556
  if (this.overrides.accessToken !== undefined) {
514
557
  options.token = this.overrides.accessToken;
515
558
  }
559
+ else if (cfg.tokenSource !== undefined) {
560
+ const source = cfg.tokenSource;
561
+ // Called for every new sign-in: a fresh token from the broker, for this user.
562
+ options.subjectToken = () => source.token();
563
+ }
516
564
  else if (cfg.username !== undefined) {
517
565
  options.username = cfg.username;
518
566
  options.password = cfg.password ?? "";
@@ -548,7 +596,7 @@ export class HardchargerConnection {
548
596
  return client;
549
597
  }
550
598
  /**
551
- * The search path, as Warp SQL session options. A catalog or schema the server does not
599
+ * The search path, as Chargewire session options. A catalog or schema the server does not
552
600
  * have is reported (with the catalogs it does have) instead of failing the connection;
553
601
  * the server's default then applies.
554
602
  */