@hydradb/mcp 1.2.1 → 1.3.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.
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Configuration for the remotely hostable HTTP transport.
3
+ *
4
+ * There are two configurations here, and they belong to two different people.
5
+ *
6
+ * - {@link resolveHttpServerConfig} is the OPERATOR's config: the port, the
7
+ * bind address, and the Host/Origin allowlists that decide who may reach the
8
+ * process at all. It is read once at startup from the environment.
9
+ *
10
+ * - {@link resolveRequestCredentials} is the CALLER's config: which Hydra DB
11
+ * account, database and collection a single request runs against. On a
12
+ * multi-tenant deployment (one process serving `mcp.hydradb.com` for many
13
+ * users) this MUST come per request, from headers, because the process has
14
+ * no single tenant of its own. It is resolved fresh on every `/mcp` call.
15
+ *
16
+ * The stdio server ({@link file://./index.ts}) has only the second concern and
17
+ * reads it from the environment via {@link file://./config.ts}; a hosted server
18
+ * cannot, because one env-configured key would make every user share one
19
+ * account. So the header path is the substantive new surface, and it falls back
20
+ * to the environment only to keep the single-tenant self-hosting story (set the
21
+ * env, expose the port, done) working unchanged.
22
+ */
23
+ import type { OAuthConfig } from "./oauth.js";
24
+ import { type EnvSource, type GraphConfig } from "./config.js";
25
+ export interface HttpServerConfig {
26
+ /** TCP port to listen on. */
27
+ port: number;
28
+ /**
29
+ * Interface to bind. Defaults to loopback so an unconfigured server is not
30
+ * reachable off-box; a hosted deployment sets `0.0.0.0` deliberately.
31
+ */
32
+ bindAddress: string;
33
+ /** CORS allowlist. Empty means no cross-origin browser request is accepted. */
34
+ allowedOrigins: string[];
35
+ /** Accepted `Host` headers, lowercased. Loopback + the configured port always. */
36
+ allowedHosts: Set<string>;
37
+ /**
38
+ * Express's `trust proxy` setting. `false` (the default) trusts no
39
+ * `X-Forwarded-*`, so a direct client cannot spoof its address in the logs;
40
+ * a deployment behind a known proxy sets `TRUST_PROXY` to a hop count or a
41
+ * subnet preset so the real client IP is recovered without trusting all hops.
42
+ */
43
+ trustProxy: boolean | number | string;
44
+ /**
45
+ * OAuth resource-server settings, or absent when OAuth is off. Resolved
46
+ * from the environment by {@link resolveHttpServerConfig}; tests pass it
47
+ * directly.
48
+ */
49
+ oauth?: OAuthConfig | null;
50
+ }
51
+ export declare const DEFAULT_PORT = 8080;
52
+ export declare const DEFAULT_BIND_ADDRESS = "127.0.0.1";
53
+ /** Split a comma-separated env var into trimmed, non-empty entries. */
54
+ export declare function parseList(value: string | undefined): string[];
55
+ /**
56
+ * A port, or the default when the value is absent or not a usable port.
57
+ *
58
+ * A typo'd `PORT` should not crash startup with a stack trace; it should fall
59
+ * back and let the startup banner show what it actually bound. Ports outside
60
+ * 1-65535 fall back for the same reason `parseInt("8080abc")` must not become
61
+ * `8080` silently — `Number` rejects the trailing garbage that `parseInt` keeps.
62
+ */
63
+ export declare function parsePort(raw: string | undefined, fallback?: number): number;
64
+ /**
65
+ * The set of `Host` headers this server answers to.
66
+ *
67
+ * Loopback names on the bound port are always included so a local run works with
68
+ * no configuration. A hosted deployment adds its public hostname(s) via
69
+ * `ALLOWED_HOSTS`. The bare (portless) loopback names cover clients that omit
70
+ * the port on the default HTTP/HTTPS port.
71
+ */
72
+ export declare function buildAllowedHosts(port: number, extra: string[]): Set<string>;
73
+ /**
74
+ * Interpret `TRUST_PROXY` for Express's `trust proxy` setting.
75
+ *
76
+ * Absent → `false` (trust nothing, the safe default). `true`/`false` → boolean.
77
+ * An integer → that many hops. Anything else is passed through as an Express
78
+ * preset or subnet list (e.g. `loopback`, `10.0.0.0/8`), so an operator can name
79
+ * exactly their proxy without this needing to know the syntax.
80
+ */
81
+ export declare function parseTrustProxy(raw: string | undefined): boolean | number | string;
82
+ export declare function resolveHttpServerConfig(env?: EnvSource): HttpServerConfig;
83
+ /**
84
+ * Header names a caller uses to select and authenticate against their tenant.
85
+ *
86
+ * Lowercased because Node lowercases incoming header names, and every lookup
87
+ * here goes through {@link headerValue} which reads them off `req.headers`.
88
+ *
89
+ * The API key is read from the standard `Authorization: Bearer <key>` first;
90
+ * `X-HydraDB-Api-Key` exists only for clients whose MCP config cannot set an
91
+ * `Authorization` header. Everything else — which database, which collection —
92
+ * has no standard header, so it takes an `X-HydraDB-*` one.
93
+ */
94
+ export declare const HEADER_AUTHORIZATION = "authorization";
95
+ export declare const HEADER_API_KEY = "x-hydradb-api-key";
96
+ export declare const HEADER_DATABASE = "x-hydradb-database";
97
+ export declare const HEADER_COLLECTION = "x-hydradb-collection";
98
+ export declare const HEADER_GRAPH_DATABASE = "x-hydradb-graph-database";
99
+ export declare const HEADER_GRAPH_COLLECTION = "x-hydradb-graph-collection";
100
+ /** The request headers this resolver reads. Matches Node's `IncomingHttpHeaders`. */
101
+ export type RequestHeaders = Record<string, string | string[] | undefined>;
102
+ /**
103
+ * Everything needed to construct a scoped {@link HydraDB} for one request.
104
+ *
105
+ * `baseUrl`, `timeoutSeconds` and `maxRetries` are deliberately absent from the
106
+ * header surface: they are OPERATOR knobs read from the environment, not caller
107
+ * ones. Letting a caller set `baseUrl` per request would point this server's
108
+ * outbound calls at a host of the caller's choosing, which is a request-forgery
109
+ * primitive the tenant selection has no reason to hand out.
110
+ */
111
+ export interface RequestCredentials {
112
+ apiKey: string;
113
+ database: string;
114
+ collection: string;
115
+ /** Databases a per-call override may name; absent means any. */
116
+ allowedDatabases?: string[];
117
+ /** Collections a per-call override may name; absent means any. */
118
+ allowedCollections?: string[];
119
+ baseUrl?: string;
120
+ timeoutSeconds?: number;
121
+ maxRetries?: number;
122
+ graph: GraphConfig;
123
+ }
124
+ /**
125
+ * Resolution is either credentials or the exact HTTP failure to return.
126
+ *
127
+ * The status and message are carried out rather than thrown so the caller can
128
+ * answer with a JSON-RPC error body at the right code — 401 when nothing
129
+ * authenticated the request, 400 when it authenticated but named no database —
130
+ * instead of a generic 500 that tells the client nothing about what to fix.
131
+ */
132
+ export type CredentialResolution = {
133
+ ok: true;
134
+ credentials: RequestCredentials;
135
+ } | {
136
+ ok: false;
137
+ status: number;
138
+ message: string;
139
+ };
140
+ /**
141
+ * Turn one request's headers (falling back to the environment) into the tenant
142
+ * credentials for that request.
143
+ *
144
+ * The API key and the database resolve TOGETHER, not field-by-field, which is
145
+ * the security-relevant part:
146
+ * - When the key comes from a request header, the request is
147
+ * "caller-authenticated" and its database MUST also come from a header. The
148
+ * env is NOT consulted for the database, so a caller who sends their own key
149
+ * but omits `X-HydraDB-Database` is refused (400) rather than silently run
150
+ * against the operator's env database. Likewise the graph scope defaults to
151
+ * the request's own database, never the operator's `HYDRADB_GRAPH_DATABASE`.
152
+ * - When there is no key header, the request is unauthenticated and falls back
153
+ * entirely to the operator's env credentials — the single-tenant self-host
154
+ * case, identical to the stdio server. A hosted, multi-tenant process sets
155
+ * no tenant env, so such a request is refused (401).
156
+ *
157
+ * Resolving field-by-field instead would let a caller's key pair with the
158
+ * operator's database (or graph namespace), mixing two identities into one
159
+ * request. `baseUrl`/`timeoutSeconds`/`maxRetries` are read from the environment
160
+ * only — they are the operator's, not the caller's (see {@link RequestCredentials}).
161
+ */
162
+ /**
163
+ * An identity established BEFORE the headers are read: what an OAuth access
164
+ * token was found to stand for.
165
+ *
166
+ * It is passed in rather than resolved here because obtaining it is an
167
+ * asynchronous call to the authorization server, and this resolver is
168
+ * deliberately synchronous and pure. When present it replaces the header
169
+ * credentials entirely: the user approved this exact scope on the consent
170
+ * screen, and nothing in the request may widen it.
171
+ */
172
+ export interface ResolvedIdentity {
173
+ apiKey: string;
174
+ database?: string;
175
+ collection?: string;
176
+ /** See IntrospectedToken.allowedDatabases. */
177
+ allowedDatabases?: string[];
178
+ /** See IntrospectedToken.allowedCollections. */
179
+ allowedCollections?: string[];
180
+ }
181
+ export declare function resolveRequestCredentials(headers: RequestHeaders, env?: EnvSource, identity?: ResolvedIdentity): CredentialResolution;
182
+ /** A JSON-RPC error body, so every HTTP failure speaks the protocol's dialect. */
183
+ export declare function jsonRpcError(code: number, message: string): {
184
+ jsonrpc: "2.0";
185
+ error: {
186
+ code: number;
187
+ message: string;
188
+ };
189
+ id: null;
190
+ };
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Configuration for the remotely hostable HTTP transport.
3
+ *
4
+ * There are two configurations here, and they belong to two different people.
5
+ *
6
+ * - {@link resolveHttpServerConfig} is the OPERATOR's config: the port, the
7
+ * bind address, and the Host/Origin allowlists that decide who may reach the
8
+ * process at all. It is read once at startup from the environment.
9
+ *
10
+ * - {@link resolveRequestCredentials} is the CALLER's config: which Hydra DB
11
+ * account, database and collection a single request runs against. On a
12
+ * multi-tenant deployment (one process serving `mcp.hydradb.com` for many
13
+ * users) this MUST come per request, from headers, because the process has
14
+ * no single tenant of its own. It is resolved fresh on every `/mcp` call.
15
+ *
16
+ * The stdio server ({@link file://./index.ts}) has only the second concern and
17
+ * reads it from the environment via {@link file://./config.ts}; a hosted server
18
+ * cannot, because one env-configured key would make every user share one
19
+ * account. So the header path is the substantive new surface, and it falls back
20
+ * to the environment only to keep the single-tenant self-hosting story (set the
21
+ * env, expose the port, done) working unchanged.
22
+ */
23
+ import { resolveOAuthConfig } from "./oauth.js";
24
+ import { DEFAULT_COLLECTION, nonNegativeInt, positiveInt, readEnv, resolveGraphConfig, } from "./config.js";
25
+ export const DEFAULT_PORT = 8080;
26
+ export const DEFAULT_BIND_ADDRESS = "127.0.0.1";
27
+ /** Split a comma-separated env var into trimmed, non-empty entries. */
28
+ export function parseList(value) {
29
+ if (!value)
30
+ return [];
31
+ return value
32
+ .split(",")
33
+ .map((v) => v.trim())
34
+ .filter((v) => v.length > 0);
35
+ }
36
+ /**
37
+ * A port, or the default when the value is absent or not a usable port.
38
+ *
39
+ * A typo'd `PORT` should not crash startup with a stack trace; it should fall
40
+ * back and let the startup banner show what it actually bound. Ports outside
41
+ * 1-65535 fall back for the same reason `parseInt("8080abc")` must not become
42
+ * `8080` silently — `Number` rejects the trailing garbage that `parseInt` keeps.
43
+ */
44
+ export function parsePort(raw, fallback = DEFAULT_PORT) {
45
+ const value = Number(raw);
46
+ return raw != null && Number.isInteger(value) && value >= 1 && value <= 65535
47
+ ? value
48
+ : fallback;
49
+ }
50
+ /**
51
+ * The set of `Host` headers this server answers to.
52
+ *
53
+ * Loopback names on the bound port are always included so a local run works with
54
+ * no configuration. A hosted deployment adds its public hostname(s) via
55
+ * `ALLOWED_HOSTS`. The bare (portless) loopback names cover clients that omit
56
+ * the port on the default HTTP/HTTPS port.
57
+ */
58
+ export function buildAllowedHosts(port, extra) {
59
+ return new Set([
60
+ `localhost:${port}`,
61
+ `127.0.0.1:${port}`,
62
+ `[::1]:${port}`,
63
+ "localhost",
64
+ "127.0.0.1",
65
+ "[::1]",
66
+ ...extra,
67
+ ].map((h) => h.toLowerCase()));
68
+ }
69
+ /**
70
+ * Interpret `TRUST_PROXY` for Express's `trust proxy` setting.
71
+ *
72
+ * Absent → `false` (trust nothing, the safe default). `true`/`false` → boolean.
73
+ * An integer → that many hops. Anything else is passed through as an Express
74
+ * preset or subnet list (e.g. `loopback`, `10.0.0.0/8`), so an operator can name
75
+ * exactly their proxy without this needing to know the syntax.
76
+ */
77
+ export function parseTrustProxy(raw) {
78
+ const value = raw?.trim();
79
+ if (!value)
80
+ return false;
81
+ if (value.toLowerCase() === "true")
82
+ return true;
83
+ if (value.toLowerCase() === "false")
84
+ return false;
85
+ if (/^\d+$/.test(value))
86
+ return Number(value);
87
+ return value;
88
+ }
89
+ export function resolveHttpServerConfig(env = process.env) {
90
+ const port = parsePort(env.PORT);
91
+ return {
92
+ port,
93
+ bindAddress: env.BIND_ADDRESS?.trim() || DEFAULT_BIND_ADDRESS,
94
+ allowedOrigins: parseList(env.ALLOWED_ORIGINS),
95
+ allowedHosts: buildAllowedHosts(port, parseList(env.ALLOWED_HOSTS)),
96
+ trustProxy: parseTrustProxy(env.TRUST_PROXY),
97
+ oauth: resolveOAuthConfig(env),
98
+ };
99
+ }
100
+ // --- Per-request tenant credentials ---
101
+ /**
102
+ * Header names a caller uses to select and authenticate against their tenant.
103
+ *
104
+ * Lowercased because Node lowercases incoming header names, and every lookup
105
+ * here goes through {@link headerValue} which reads them off `req.headers`.
106
+ *
107
+ * The API key is read from the standard `Authorization: Bearer <key>` first;
108
+ * `X-HydraDB-Api-Key` exists only for clients whose MCP config cannot set an
109
+ * `Authorization` header. Everything else — which database, which collection —
110
+ * has no standard header, so it takes an `X-HydraDB-*` one.
111
+ */
112
+ export const HEADER_AUTHORIZATION = "authorization";
113
+ export const HEADER_API_KEY = "x-hydradb-api-key";
114
+ export const HEADER_DATABASE = "x-hydradb-database";
115
+ export const HEADER_COLLECTION = "x-hydradb-collection";
116
+ export const HEADER_GRAPH_DATABASE = "x-hydradb-graph-database";
117
+ export const HEADER_GRAPH_COLLECTION = "x-hydradb-graph-collection";
118
+ /** One header value, taking the first when a header arrives repeated. */
119
+ function headerValue(headers, name) {
120
+ const raw = headers[name];
121
+ const value = Array.isArray(raw) ? raw[0] : raw;
122
+ const trimmed = value?.trim();
123
+ return trimmed ? trimmed : undefined;
124
+ }
125
+ /**
126
+ * The token from an `Authorization` header, accepting `Bearer <token>` or a bare
127
+ * token. The scheme is matched case-insensitively (RFC 7235 says it is
128
+ * case-insensitive, and clients send `bearer`, `Bearer` and `BEARER` in the
129
+ * wild); a bare value is accepted because some MCP hosts drop the scheme.
130
+ */
131
+ function bearerToken(authorization) {
132
+ if (!authorization)
133
+ return undefined;
134
+ const match = /^\s*Bearer\s+(.+)$/i.exec(authorization);
135
+ if (match)
136
+ return match[1].trim() || undefined;
137
+ return authorization.trim() || undefined;
138
+ }
139
+ export function resolveRequestCredentials(headers, env = process.env, identity) {
140
+ const headerKey = identity?.apiKey ??
141
+ bearerToken(headerValue(headers, HEADER_AUTHORIZATION)) ??
142
+ headerValue(headers, HEADER_API_KEY);
143
+ // Whether the CALLER authenticated this request with their own key decides
144
+ // whether the operator's env is allowed to supply the rest of the identity.
145
+ const callerAuthenticated = headerKey != null;
146
+ const apiKey =
147
+ // The env fallback reuses the exact same canonical/legacy alias rules as
148
+ // the stdio server, so a self-host configured for stdio needs no new vars.
149
+ headerKey ?? readEnv(env, "HYDRADB_API_KEY", "HYDRA_DB_API_KEY", noopWarn);
150
+ if (!apiKey) {
151
+ return {
152
+ ok: false,
153
+ status: 401,
154
+ message: "Missing Hydra DB credentials. Send `Authorization: Bearer <api-key>` " +
155
+ "(or the `X-HydraDB-Api-Key` header). Get a key at https://app.hydradb.com.",
156
+ };
157
+ }
158
+ // An OAuth identity is what the user approved on the consent screen. No
159
+ // request header may alter it: the client holding the token could otherwise
160
+ // re-scope the connection after consent by adding a header. Per-call tool
161
+ // arguments remain the sanctioned way to name another database, and those
162
+ // are checked against the grant's allowed list.
163
+ const headerDatabase = identity ? identity.database : headerValue(headers, HEADER_DATABASE);
164
+ // A caller-authenticated request takes its database ONLY from the header;
165
+ // the env database belongs to the operator's identity, not the caller's.
166
+ const database = callerAuthenticated
167
+ ? headerDatabase
168
+ : (headerDatabase ?? readEnv(env, "HYDRADB_DATABASE", "HYDRA_DB_TENANT_ID", noopWarn));
169
+ if (!database) {
170
+ return {
171
+ ok: false,
172
+ status: 400,
173
+ message: "Missing Hydra DB database. Send the `X-HydraDB-Database` header naming " +
174
+ "the database (tenant) this request should run against.",
175
+ };
176
+ }
177
+ // Collection is a partition WITHIN the resolved database, not a cross-tenant
178
+ // boundary, so an env default is safe for either mode.
179
+ const collection = (identity ? identity.collection : headerValue(headers, HEADER_COLLECTION)) ??
180
+ readEnv(env, "HYDRADB_COLLECTION", "HYDRA_DB_SUB_TENANT_ID", noopWarn) ??
181
+ DEFAULT_COLLECTION;
182
+ // Operator-owned transport knobs. Same env vars, same parsing as resolveConfig.
183
+ const baseUrl = readEnv(env, "HYDRADB_BASE_URL", "HYDRA_DB_BASE_URL", noopWarn);
184
+ const timeoutSeconds = positiveInt(env.HYDRADB_TIMEOUT_SECONDS);
185
+ const maxRetries = nonNegativeInt(env.HYDRADB_MAX_RETRIES);
186
+ return {
187
+ ok: true,
188
+ credentials: {
189
+ apiKey,
190
+ database,
191
+ collection,
192
+ ...(identity?.allowedDatabases ? { allowedDatabases: identity.allowedDatabases } : {}),
193
+ ...(identity?.allowedCollections
194
+ ? { allowedCollections: identity.allowedCollections }
195
+ : {}),
196
+ ...(baseUrl != null ? { baseUrl } : {}),
197
+ ...(timeoutSeconds != null ? { timeoutSeconds } : {}),
198
+ ...(maxRetries != null ? { maxRetries } : {}),
199
+ graph: resolveRequestGraphConfig(
200
+ // Same rule for the graph namespace: an OAuth connection's graph scope
201
+ // derives from the approved database, never from a header.
202
+ identity ? {} : headers, env, database, callerAuthenticated, identity),
203
+ },
204
+ };
205
+ }
206
+ /**
207
+ * Graph scope for one request: the operator's `enabled` flag, but the caller's
208
+ * database and collection.
209
+ *
210
+ * The graph database defaults to the request's OWN database, so a caller that
211
+ * names only `X-HydraDB-Database` gets a coherent graph scope without a second
212
+ * header. The operator's `HYDRADB_GRAPH_DATABASE` is honoured as that default
213
+ * ONLY for an unauthenticated (env-credential) request — for a caller-
214
+ * authenticated one it would pin every tenant to the operator's single graph
215
+ * namespace, the mixing this resolver exists to prevent. Header overrides always
216
+ * win, letting a caller address a graph in a different namespace than their
217
+ * memory database — which, because the two are genuinely separate stores, is a
218
+ * real need.
219
+ */
220
+ function resolveRequestGraphConfig(headers, env, database, callerAuthenticated, identity) {
221
+ // For a caller-authenticated request the fallback database is the request's
222
+ // own, so `resolveGraphConfig`'s env default is deliberately not consulted.
223
+ const base = resolveGraphConfig(env, database);
224
+ const defaultDatabase = callerAuthenticated ? database : base.database;
225
+ return {
226
+ enabled: base.enabled,
227
+ database: headerValue(headers, HEADER_GRAPH_DATABASE) ?? defaultDatabase,
228
+ // For an OAuth connection the graph collection defaults to the one the
229
+ // user approved, NOT the operator's `HYDRADB_GRAPH_COLLECTION`. On a
230
+ // hosted process that environment value is one namespace shared by every
231
+ // tenant, so inheriting it would run one user's graph calls somewhere
232
+ // they never approved and everyone else can reach.
233
+ collection: identity?.collection ??
234
+ headerValue(headers, HEADER_GRAPH_COLLECTION) ??
235
+ base.collection,
236
+ };
237
+ }
238
+ /**
239
+ * Credential resolution must not emit deprecation warnings.
240
+ *
241
+ * `readEnv` warns once per process when a legacy alias is used. On the header
242
+ * path the env is only a fallback and is read on EVERY request; routing its
243
+ * warning through the process-wide dedupe would still fire on the first request
244
+ * and, worse, tie a per-request code path to shared mutable warning state. The
245
+ * stdio/startup path already surfaces any legacy-alias warning, so silence here
246
+ * drops nothing.
247
+ */
248
+ function noopWarn() { }
249
+ /** A JSON-RPC error body, so every HTTP failure speaks the protocol's dialect. */
250
+ export function jsonRpcError(code, message) {
251
+ return { jsonrpc: "2.0", error: { code, message }, id: null };
252
+ }
253
+ //# sourceMappingURL=http-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-config.js","sourceRoot":"","sources":["../src/http-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EACN,kBAAkB,EAGlB,cAAc,EACd,WAAW,EACX,OAAO,EACP,kBAAkB,GAClB,MAAM,aAAa,CAAC;AA+BrB,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;AACjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAEhD,uEAAuE;AACvE,MAAM,UAAU,SAAS,CAAC,KAAyB;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,KAAK;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,GAAuB,EAAE,QAAQ,GAAG,YAAY;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,GAAG,IAAI,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,KAAM;QAC7E,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,QAAQ,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAe;IAC9D,OAAO,IAAI,GAAG,CACb;QACC,aAAa,IAAI,EAAE;QACnB,aAAa,IAAI,EAAE;QACnB,SAAS,IAAI,EAAE;QACf,WAAW;QACX,WAAW;QACX,OAAO;QACP,GAAG,KAAK;KACR,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAC7B,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACtD,MAAM,KAAK,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,KAAK,CAAC;AACd,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAiB,OAAO,CAAC,GAAG;IACnE,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO;QACN,IAAI;QACJ,WAAW,EAAE,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,oBAAoB;QAC7D,cAAc,EAAE,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC;QAC9C,YAAY,EAAE,iBAAiB,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACnE,UAAU,EAAE,eAAe,CAAC,GAAG,CAAC,WAAW,CAAC;QAC5C,KAAK,EAAE,kBAAkB,CAAC,GAAG,CAAC;KAC9B,CAAC;AACH,CAAC;AAED,yCAAyC;AAEzC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC;AACpD,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAClD,MAAM,CAAC,MAAM,eAAe,GAAG,oBAAoB,CAAC;AACpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AACxD,MAAM,CAAC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;AAChE,MAAM,CAAC,MAAM,uBAAuB,GAAG,4BAA4B,CAAC;AAwCpE,yEAAyE;AACzE,SAAS,WAAW,CAAC,OAAuB,EAAE,IAAY;IACzD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAAC,aAAiC;IACrD,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACxD,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;IAC/C,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;AAC1C,CAAC;AA4CD,MAAM,UAAU,yBAAyB,CACxC,OAAuB,EACvB,MAAiB,OAAO,CAAC,GAAG,EAC5B,QAA2B;IAE3B,MAAM,SAAS,GACd,QAAQ,EAAE,MAAM;QAChB,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;QACvD,WAAW,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACtC,2EAA2E;IAC3E,4EAA4E;IAC5E,MAAM,mBAAmB,GAAG,SAAS,IAAI,IAAI,CAAC;IAE9C,MAAM,MAAM;IACX,yEAAyE;IACzE,2EAA2E;IAC3E,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAC5E,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO;YACN,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,OAAO,EACN,uEAAuE;gBACvE,4EAA4E;SAC7E,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,gDAAgD;IAChD,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAC5F,0EAA0E;IAC1E,yEAAyE;IACzE,MAAM,QAAQ,GAAG,mBAAmB;QACnC,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO;YACN,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,OAAO,EACN,yEAAyE;gBACzE,wDAAwD;SACzD,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,uDAAuD;IACvD,MAAM,UAAU,GACf,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QAC1E,OAAO,CAAC,GAAG,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,QAAQ,CAAC;QACtE,kBAAkB,CAAC;IAEpB,gFAAgF;IAChF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAChF,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAE3D,OAAO;QACN,EAAE,EAAE,IAAI;QACR,WAAW,EAAE;YACZ,MAAM;YACN,QAAQ;YACR,UAAU;YACV,GAAG,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtF,GAAG,CAAC,QAAQ,EAAE,kBAAkB;gBAC/B,CAAC,CAAC,EAAE,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB,EAAE;gBACrD,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,KAAK,EAAE,yBAAyB;YAC/B,uEAAuE;YACvE,2DAA2D;YAC3D,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EACvB,GAAG,EACH,QAAQ,EACR,mBAAmB,EACnB,QAAQ,CACR;SACD;KACD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,yBAAyB,CACjC,OAAuB,EACvB,GAAc,EACd,QAAgB,EAChB,mBAA4B,EAC5B,QAA2B;IAE3B,4EAA4E;IAC5E,4EAA4E;IAC5E,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,eAAe,GAAG,mBAAmB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;IACvE,OAAO;QACN,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,qBAAqB,CAAC,IAAI,eAAe;QACxE,uEAAuE;QACvE,qEAAqE;QACrE,yEAAyE;QACzE,sEAAsE;QACtE,mDAAmD;QACnD,UAAU,EACT,QAAQ,EAAE,UAAU;YACpB,WAAW,CAAC,OAAO,EAAE,uBAAuB,CAAC;YAC7C,IAAI,CAAC,UAAU;KAChB,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,QAAQ,KAAU,CAAC;AAE5B,kFAAkF;AAClF,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAe;IAKzD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AAC/D,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The remotely hostable HTTP transport.
4
+ *
5
+ * This is the server behind a URL like `https://mcp.hydradb.com/mcp`: instead of
6
+ * every user installing and spawning the stdio binary ({@link file://./index.ts}),
7
+ * one process answers MCP over HTTP and each user points their client at the URL.
8
+ *
9
+ * It reuses the whole tool surface unchanged — {@link createHydraDBServer} builds
10
+ * exactly the same server the stdio path does. The only things this file adds are
11
+ * the ones a network endpoint needs and a pipe does not: a Host/Origin
12
+ * allowlist, CORS, and PER-REQUEST tenant credentials (see
13
+ * {@link file://./http-config.ts}), because one hosted process has no single
14
+ * ambient account to run as.
15
+ *
16
+ * Sessions are stateless: MCP's Protocol object binds to one transport, so a
17
+ * shared process serving many independent callers builds a fresh server +
18
+ * transport per request and tears it down when the response closes. That is the
19
+ * transport's documented stateless mode (`sessionIdGenerator: undefined`).
20
+ */
21
+ import { type Express } from "express";
22
+ import { buildAllowedHosts, type HttpServerConfig, parseList, parsePort, resolveHttpServerConfig } from "./http-config.js";
23
+ /**
24
+ * Build the Express app for the HTTP transport.
25
+ *
26
+ * Exported (and taking its config as an argument rather than reading the
27
+ * environment) so tests exercise the exact wiring production runs, against an
28
+ * arbitrary allowlist, with no process-global state.
29
+ */
30
+ export declare function createHttpApp(config: HttpServerConfig): Express;
31
+ export { buildAllowedHosts, parseList, parsePort, resolveHttpServerConfig };