@gethelio/proxy 0.1.1 → 0.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/cli.js CHANGED
@@ -47,7 +47,8 @@ var upstreamSchema = z.object({
47
47
  args: z.array(z.string()).optional(),
48
48
  connect_timeout: durationSchema.default("10s"),
49
49
  request_timeout: durationSchema.default("30s"),
50
- forward_headers: z.array(z.string().min(1)).default([])
50
+ forward_headers: z.array(z.string().min(1)).default([]),
51
+ headers: z.record(z.string(), z.string()).default({})
51
52
  }).refine((data) => data.transport !== "stdio" || data.command !== void 0, {
52
53
  message: '"command" is required when transport is "stdio"',
53
54
  path: ["command"]
@@ -61,6 +62,22 @@ var upstreamSchema = z.object({
61
62
  });
62
63
  }
63
64
  }
65
+ const reserved = /* @__PURE__ */ new Set([
66
+ "mcp-session-id",
67
+ "mcp-protocol-version",
68
+ "content-type",
69
+ "content-length",
70
+ "host"
71
+ ]);
72
+ for (const name of Object.keys(data.headers)) {
73
+ if (reserved.has(name.toLowerCase())) {
74
+ ctx.addIssue({
75
+ code: "custom",
76
+ path: ["headers", name],
77
+ message: `upstream.headers must not set reserved header "${name}"`
78
+ });
79
+ }
80
+ }
64
81
  });
65
82
  var listenSchema = z.object({
66
83
  port: z.number().int().min(1).max(65535).default(3e3),
@@ -1237,6 +1254,20 @@ function describeUnreachableUpstream(error, url) {
1237
1254
  );
1238
1255
  }
1239
1256
 
1257
+ // src/upstream/merge-headers.ts
1258
+ function mergeUpstreamHeaders(base, forwarded, staticHeaders) {
1259
+ const out = {};
1260
+ const apply = (headers) => {
1261
+ for (const [name, value] of Object.entries(headers)) {
1262
+ out[name.toLowerCase()] = value;
1263
+ }
1264
+ };
1265
+ apply(base);
1266
+ apply(forwarded);
1267
+ apply(staticHeaders);
1268
+ return out;
1269
+ }
1270
+
1240
1271
  // src/upstream/forwarder.ts
1241
1272
  var UpstreamForwarder = class {
1242
1273
  url;
@@ -1248,13 +1279,14 @@ var UpstreamForwarder = class {
1248
1279
  this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
1249
1280
  }
1250
1281
  async forward(request) {
1251
- const requestHeaders = request.headers ?? {};
1252
- const headers = {
1253
- "content-type": "application/json",
1254
- accept: "application/json, text/event-stream",
1255
- ...this.staticHeaders,
1256
- ...requestHeaders
1257
- };
1282
+ const headers = mergeUpstreamHeaders(
1283
+ {
1284
+ "content-type": "application/json",
1285
+ accept: "application/json, text/event-stream"
1286
+ },
1287
+ request.headers ?? {},
1288
+ this.staticHeaders
1289
+ );
1258
1290
  if (request.sessionId) {
1259
1291
  headers["mcp-session-id"] = request.sessionId;
1260
1292
  }
@@ -1460,12 +1492,11 @@ var SseUpstreamForwarder = class {
1460
1492
  };
1461
1493
  if (request.id !== void 0) body["id"] = request.id;
1462
1494
  if (request.params !== void 0) body["params"] = request.params;
1463
- const requestHeaders = request.headers ?? {};
1464
- const headers = {
1465
- "content-type": "application/json",
1466
- ...this.staticHeaders,
1467
- ...requestHeaders
1468
- };
1495
+ const headers = mergeUpstreamHeaders(
1496
+ { "content-type": "application/json" },
1497
+ request.headers ?? {},
1498
+ this.staticHeaders
1499
+ );
1469
1500
  if (request.sessionId) {
1470
1501
  headers["mcp-session-id"] = request.sessionId;
1471
1502
  }
@@ -1777,6 +1808,40 @@ var StdioForwarder = class {
1777
1808
  }
1778
1809
  };
1779
1810
 
1811
+ // src/cli-forwarder.ts
1812
+ async function createForwarderFromConfig(config) {
1813
+ switch (config.upstream.transport) {
1814
+ case "streamable-http": {
1815
+ return {
1816
+ forwarder: new UpstreamForwarder({
1817
+ url: config.upstream.url,
1818
+ headers: config.upstream.headers,
1819
+ requestTimeoutMs: parseDuration(config.upstream.request_timeout)
1820
+ })
1821
+ };
1822
+ }
1823
+ case "sse": {
1824
+ const sse = new SseUpstreamForwarder({
1825
+ url: config.upstream.url,
1826
+ headers: config.upstream.headers,
1827
+ connectTimeoutMs: parseDuration(config.upstream.connect_timeout),
1828
+ requestTimeoutMs: parseDuration(config.upstream.request_timeout)
1829
+ });
1830
+ await sse.connect();
1831
+ return { forwarder: sse, close: () => sse.close() };
1832
+ }
1833
+ case "stdio": {
1834
+ const stdio = new StdioForwarder({
1835
+ command: config.upstream.command,
1836
+ args: config.upstream.args,
1837
+ requestTimeoutMs: parseDuration(config.upstream.request_timeout)
1838
+ });
1839
+ await stdio.start();
1840
+ return { forwarder: stdio, close: () => stdio.close() };
1841
+ }
1842
+ }
1843
+ }
1844
+
1780
1845
  // src/policy/matchers.ts
1781
1846
  var ANNOTATION_DEFAULTS = {
1782
1847
  readOnlyHint: false,
@@ -5880,6 +5945,8 @@ upstream:
5880
5945
  url: "http://localhost:8080/mcp"
5881
5946
  # Transport: streamable-http (default), sse, or stdio
5882
5947
  transport: streamable-http
5948
+ # headers:
5949
+ # Authorization: "Bearer \${UPSTREAM_TOKEN}"
5883
5950
 
5884
5951
  # Operator dashboard + approval REST API. Bound to 127.0.0.1 by default \u2014 do
5885
5952
  # not change to 0.0.0.0 without putting an authenticating reverse proxy in
@@ -6027,43 +6094,7 @@ async function startCommand(configPath, options) {
6027
6094
  );
6028
6095
  process.exit(1);
6029
6096
  }
6030
- let forwarder;
6031
- let closeForwarder;
6032
- switch (config.upstream.transport) {
6033
- case "streamable-http": {
6034
- forwarder = new UpstreamForwarder({
6035
- url: config.upstream.url,
6036
- requestTimeoutMs: parseDuration(config.upstream.request_timeout)
6037
- });
6038
- break;
6039
- }
6040
- case "stdio": {
6041
- if (!config.upstream.command) {
6042
- console.error('Error: "command" is required for stdio transport');
6043
- process.exit(1);
6044
- }
6045
- const stdio = new StdioForwarder({
6046
- command: config.upstream.command,
6047
- args: config.upstream.args,
6048
- requestTimeoutMs: parseDuration(config.upstream.request_timeout)
6049
- });
6050
- await stdio.start();
6051
- forwarder = stdio;
6052
- closeForwarder = () => stdio.close();
6053
- break;
6054
- }
6055
- case "sse": {
6056
- const sse = new SseUpstreamForwarder({
6057
- url: config.upstream.url,
6058
- connectTimeoutMs: parseDuration(config.upstream.connect_timeout),
6059
- requestTimeoutMs: parseDuration(config.upstream.request_timeout)
6060
- });
6061
- await sse.connect();
6062
- forwarder = sse;
6063
- closeForwarder = () => sse.close();
6064
- break;
6065
- }
6066
- }
6097
+ const { forwarder, close: closeForwarder } = await createForwarderFromConfig(config);
6067
6098
  const { policy, warnings } = compilePolicies(config.policies);
6068
6099
  for (const w of warnings) {
6069
6100
  const label = w.ruleName ? `rule "${w.ruleName}"` : `rule ${String(w.ruleIndex)}`;
package/dist/index.d.ts CHANGED
@@ -113,6 +113,7 @@ declare const helioConfigSchema: z.ZodObject<{
113
113
  connect_timeout: z.ZodDefault<z.ZodString>;
114
114
  request_timeout: z.ZodDefault<z.ZodString>;
115
115
  forward_headers: z.ZodDefault<z.ZodArray<z.ZodString>>;
116
+ headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
116
117
  }, z.core.$strip>;
117
118
  listen: z.ZodPrefault<z.ZodObject<{
118
119
  port: z.ZodDefault<z.ZodNumber>;
package/dist/index.js CHANGED
@@ -37,7 +37,8 @@ var upstreamSchema = z.object({
37
37
  args: z.array(z.string()).optional(),
38
38
  connect_timeout: durationSchema.default("10s"),
39
39
  request_timeout: durationSchema.default("30s"),
40
- forward_headers: z.array(z.string().min(1)).default([])
40
+ forward_headers: z.array(z.string().min(1)).default([]),
41
+ headers: z.record(z.string(), z.string()).default({})
41
42
  }).refine((data) => data.transport !== "stdio" || data.command !== void 0, {
42
43
  message: '"command" is required when transport is "stdio"',
43
44
  path: ["command"]
@@ -51,6 +52,22 @@ var upstreamSchema = z.object({
51
52
  });
52
53
  }
53
54
  }
55
+ const reserved = /* @__PURE__ */ new Set([
56
+ "mcp-session-id",
57
+ "mcp-protocol-version",
58
+ "content-type",
59
+ "content-length",
60
+ "host"
61
+ ]);
62
+ for (const name of Object.keys(data.headers)) {
63
+ if (reserved.has(name.toLowerCase())) {
64
+ ctx.addIssue({
65
+ code: "custom",
66
+ path: ["headers", name],
67
+ message: `upstream.headers must not set reserved header "${name}"`
68
+ });
69
+ }
70
+ }
54
71
  });
55
72
  var listenSchema = z.object({
56
73
  port: z.number().int().min(1).max(65535).default(3e3),
@@ -1131,6 +1148,20 @@ function describeUnreachableUpstream(error, url) {
1131
1148
  );
1132
1149
  }
1133
1150
 
1151
+ // src/upstream/merge-headers.ts
1152
+ function mergeUpstreamHeaders(base, forwarded, staticHeaders) {
1153
+ const out = {};
1154
+ const apply = (headers) => {
1155
+ for (const [name, value] of Object.entries(headers)) {
1156
+ out[name.toLowerCase()] = value;
1157
+ }
1158
+ };
1159
+ apply(base);
1160
+ apply(forwarded);
1161
+ apply(staticHeaders);
1162
+ return out;
1163
+ }
1164
+
1134
1165
  // src/upstream/forwarder.ts
1135
1166
  var UpstreamForwarder = class {
1136
1167
  url;
@@ -1142,13 +1173,14 @@ var UpstreamForwarder = class {
1142
1173
  this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
1143
1174
  }
1144
1175
  async forward(request) {
1145
- const requestHeaders = request.headers ?? {};
1146
- const headers = {
1147
- "content-type": "application/json",
1148
- accept: "application/json, text/event-stream",
1149
- ...this.staticHeaders,
1150
- ...requestHeaders
1151
- };
1176
+ const headers = mergeUpstreamHeaders(
1177
+ {
1178
+ "content-type": "application/json",
1179
+ accept: "application/json, text/event-stream"
1180
+ },
1181
+ request.headers ?? {},
1182
+ this.staticHeaders
1183
+ );
1152
1184
  if (request.sessionId) {
1153
1185
  headers["mcp-session-id"] = request.sessionId;
1154
1186
  }
@@ -1354,12 +1386,11 @@ var SseUpstreamForwarder = class {
1354
1386
  };
1355
1387
  if (request.id !== void 0) body["id"] = request.id;
1356
1388
  if (request.params !== void 0) body["params"] = request.params;
1357
- const requestHeaders = request.headers ?? {};
1358
- const headers = {
1359
- "content-type": "application/json",
1360
- ...this.staticHeaders,
1361
- ...requestHeaders
1362
- };
1389
+ const headers = mergeUpstreamHeaders(
1390
+ { "content-type": "application/json" },
1391
+ request.headers ?? {},
1392
+ this.staticHeaders
1393
+ );
1363
1394
  if (request.sessionId) {
1364
1395
  headers["mcp-session-id"] = request.sessionId;
1365
1396
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethelio/proxy",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Open-source MCP governance proxy for AI agents",
6
6
  "license": "Apache-2.0",