@mhingston5/conduit 1.1.6 → 1.1.8

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/README.md CHANGED
@@ -66,6 +66,20 @@ upstreams:
66
66
  - id: github
67
67
  type: http
68
68
  url: "http://localhost:3000/mcp"
69
+
70
+ # Remote MCP servers that use Streamable HTTP (preferred) / SSE:
71
+ - id: atlassian
72
+ type: streamableHttp
73
+ url: "https://mcp.atlassian.com/v1/sse"
74
+ credentials:
75
+ type: oauth2
76
+ clientId: ${ATLASSIAN_CLIENT_ID}
77
+ clientSecret: ${ATLASSIAN_CLIENT_SECRET}
78
+ tokenUrl: "https://auth.atlassian.com/oauth/token"
79
+ refreshToken: ${ATLASSIAN_REFRESH_TOKEN}
80
+ # Atlassian expects JSON token requests:
81
+ tokenRequestFormat: json
82
+
69
83
  - id: slack
70
84
  type: http
71
85
  url: "https://your-mcp-server/mcp"
@@ -75,7 +89,8 @@ upstreams:
75
89
  clientSecret: ${SLACK_CLIENT_SECRET}
76
90
  tokenUrl: "https://slack.com/api/oauth.v2.access"
77
91
  refreshToken: ${SLACK_REFRESH_TOKEN}
78
- # Or use local stdio for testing:
92
+
93
+ # Or use local stdio for testing:
79
94
  - id: filesystem
80
95
  type: stdio
81
96
  command: npx
@@ -95,8 +110,21 @@ npx conduit auth \
95
110
  --scopes <scopes>
96
111
  ```
97
112
 
113
+ For Atlassian (3LO), include `offline_access` and set the audience:
114
+
115
+ ```bash
116
+ npx conduit auth \
117
+ --client-id <id> \
118
+ --client-secret <secret> \
119
+ --auth-url "https://auth.atlassian.com/authorize?audience=api.atlassian.com&prompt=consent" \
120
+ --token-url "https://auth.atlassian.com/oauth/token" \
121
+ --scopes "offline_access,read:me"
122
+ ```
123
+
98
124
  This will start a temporary local server, open your browser for authorization, and print the generated `credentials` block for your `conduit.yaml`.
99
125
 
126
+ Note: some providers (including Atlassian) use rotating refresh tokens. Conduit will cache the latest refresh token in-memory while running, but it does not currently persist the rotated token back into `conduit.yaml`. If you restart Conduit and your old refresh token has expired/rotated, re-run `conduit auth` and update your config.
127
+
100
128
  For GitHub MCP (remote server OAuth), you can auto-discover endpoints and use PKCE:
101
129
 
102
130
  ```bash
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
+ import { createRequire } from "module";
5
6
 
6
7
  // src/core/config.service.ts
7
8
  import { z } from "zod";
@@ -29,6 +30,8 @@ var UpstreamCredentialsSchema = z.object({
29
30
  tokenUrl: z.string().optional(),
30
31
  refreshToken: z.string().optional(),
31
32
  scopes: z.array(z.string()).optional(),
33
+ tokenRequestFormat: z.enum(["form", "json"]).optional(),
34
+ tokenParams: z.record(z.string(), z.string()).optional(),
32
35
  apiKey: z.string().optional(),
33
36
  bearerToken: z.string().optional(),
34
37
  headerName: z.string().optional()
@@ -39,6 +42,18 @@ var HttpUpstreamSchema = z.object({
39
42
  url: z.string(),
40
43
  credentials: UpstreamCredentialsSchema.optional()
41
44
  });
45
+ var StreamableHttpUpstreamSchema = z.object({
46
+ id: z.string(),
47
+ type: z.literal("streamableHttp"),
48
+ url: z.string(),
49
+ credentials: UpstreamCredentialsSchema.optional()
50
+ });
51
+ var SseUpstreamSchema = z.object({
52
+ id: z.string(),
53
+ type: z.literal("sse"),
54
+ url: z.string(),
55
+ credentials: UpstreamCredentialsSchema.optional()
56
+ });
42
57
  var StdioUpstreamSchema = z.object({
43
58
  id: z.string(),
44
59
  type: z.literal("stdio"),
@@ -46,7 +61,12 @@ var StdioUpstreamSchema = z.object({
46
61
  args: z.array(z.string()).optional(),
47
62
  env: z.record(z.string(), z.string()).optional()
48
63
  });
49
- var UpstreamInfoSchema = z.union([HttpUpstreamSchema, StdioUpstreamSchema]);
64
+ var UpstreamInfoSchema = z.union([
65
+ HttpUpstreamSchema,
66
+ StreamableHttpUpstreamSchema,
67
+ SseUpstreamSchema,
68
+ StdioUpstreamSchema
69
+ ]);
50
70
  var ConfigSchema = z.object({
51
71
  port: z.union([z.string(), z.number()]).default("3000").transform((v) => Number(v)),
52
72
  nodeEnv: z.enum(["development", "production", "test"]).default("development"),
@@ -1061,7 +1081,12 @@ var RequestController = class {
1061
1081
  import axios2 from "axios";
1062
1082
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1063
1083
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1084
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1085
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
1064
1086
  import { z as z2 } from "zod";
1087
+ import dns from "dns";
1088
+ import net2 from "net";
1089
+ import { Agent } from "undici";
1065
1090
  var UpstreamClient = class {
1066
1091
  logger;
1067
1092
  info;
@@ -1070,6 +1095,9 @@ var UpstreamClient = class {
1070
1095
  mcpClient;
1071
1096
  transport;
1072
1097
  connected = false;
1098
+ // Pinned-IP dispatchers per upstream origin (defends against DNS rebinding)
1099
+ dispatcherCache = /* @__PURE__ */ new Map();
1100
+ pinned;
1073
1101
  constructor(logger, info, authService, urlValidator) {
1074
1102
  this.logger = logger.child({ upstreamId: info.id });
1075
1103
  this.info = info;
@@ -1092,11 +1120,116 @@ var UpstreamClient = class {
1092
1120
  }, {
1093
1121
  capabilities: {}
1094
1122
  });
1123
+ return;
1124
+ }
1125
+ if (this.info.type === "streamableHttp") {
1126
+ const upstreamUrl = new URL(this.info.url);
1127
+ this.pinned = { origin: upstreamUrl.origin, hostname: upstreamUrl.hostname };
1128
+ this.transport = new StreamableHTTPClientTransport(upstreamUrl, {
1129
+ fetch: this.createAuthedFetch()
1130
+ });
1131
+ this.mcpClient = new Client({
1132
+ name: "conduit-gateway",
1133
+ version: "1.0.0"
1134
+ }, {
1135
+ capabilities: {}
1136
+ });
1137
+ return;
1138
+ }
1139
+ if (this.info.type === "sse") {
1140
+ const upstreamUrl = new URL(this.info.url);
1141
+ this.pinned = { origin: upstreamUrl.origin, hostname: upstreamUrl.hostname };
1142
+ this.mcpClient = new Client({
1143
+ name: "conduit-gateway",
1144
+ version: "1.0.0"
1145
+ }, {
1146
+ capabilities: {}
1147
+ });
1148
+ }
1149
+ }
1150
+ getDispatcher(origin, hostname, resolvedIp) {
1151
+ const existing = this.dispatcherCache.get(origin);
1152
+ if (existing && existing.resolvedIp === resolvedIp) {
1153
+ return existing.agent;
1154
+ }
1155
+ if (existing) {
1156
+ try {
1157
+ existing.agent.close();
1158
+ } catch {
1159
+ }
1095
1160
  }
1161
+ const agent = new Agent({
1162
+ connect: {
1163
+ lookup: (lookupHostname, options, callback) => {
1164
+ if (lookupHostname === hostname) {
1165
+ callback(null, resolvedIp, net2.isIP(resolvedIp));
1166
+ return;
1167
+ }
1168
+ dns.lookup(lookupHostname, options, callback);
1169
+ }
1170
+ }
1171
+ });
1172
+ this.dispatcherCache.set(origin, { resolvedIp, agent });
1173
+ return agent;
1174
+ }
1175
+ createAuthedFetch() {
1176
+ const creds = this.info.credentials;
1177
+ const pinned = this.pinned;
1178
+ const baseFetch = fetch;
1179
+ return async (input, init = {}) => {
1180
+ const requestUrlStr = (() => {
1181
+ if (typeof input === "string") return input;
1182
+ if (input instanceof URL) return input.toString();
1183
+ if (input instanceof Request) return input.url;
1184
+ return String(input);
1185
+ })();
1186
+ const requestUrl = pinned ? new URL(requestUrlStr, pinned.origin) : new URL(requestUrlStr);
1187
+ if (pinned && requestUrl.origin !== pinned.origin) {
1188
+ throw new Error(`Forbidden upstream redirect/origin: ${requestUrl.origin}`);
1189
+ }
1190
+ if (pinned && !pinned.resolvedIp) {
1191
+ const securityResult = await this.urlValidator.validateUrl(pinned.origin);
1192
+ if (!securityResult.valid) {
1193
+ throw new Error(securityResult.message || "Forbidden URL");
1194
+ }
1195
+ pinned.resolvedIp = securityResult.resolvedIp;
1196
+ }
1197
+ const headers = new Headers((input instanceof Request ? input.headers : void 0) || void 0);
1198
+ const initHeaders = new Headers(init.headers || {});
1199
+ for (const [k, v] of initHeaders.entries()) headers.set(k, v);
1200
+ if (creds) {
1201
+ const authHeaders = await this.authService.getAuthHeaders(creds);
1202
+ for (const [k, v] of Object.entries(authHeaders)) {
1203
+ headers.set(k, v);
1204
+ }
1205
+ }
1206
+ const request = input instanceof Request ? new Request(input, { ...init, headers, redirect: init.redirect ?? "manual" }) : new Request(requestUrl.toString(), { ...init, headers, redirect: init.redirect ?? "manual" });
1207
+ const dispatcher = pinned && pinned.resolvedIp ? this.getDispatcher(pinned.origin, pinned.hostname, pinned.resolvedIp) : void 0;
1208
+ return baseFetch(request, dispatcher ? { dispatcher } : void 0);
1209
+ };
1096
1210
  }
1097
1211
  async ensureConnected() {
1098
- if (!this.mcpClient || !this.transport) return;
1212
+ if (!this.mcpClient) return;
1213
+ if (!this.transport && this.info.type === "sse") {
1214
+ const authHeaders = this.info.credentials ? await this.authService.getAuthHeaders(this.info.credentials) : {};
1215
+ this.transport = new SSEClientTransport(new URL(this.info.url), {
1216
+ fetch: this.createAuthedFetch(),
1217
+ eventSourceInit: { headers: authHeaders },
1218
+ requestInit: { headers: authHeaders }
1219
+ });
1220
+ }
1221
+ if (!this.transport) return;
1099
1222
  if (this.connected) return;
1223
+ if (this.info.type === "streamableHttp" || this.info.type === "sse") {
1224
+ const securityResult = await this.urlValidator.validateUrl(this.info.url);
1225
+ if (!securityResult.valid) {
1226
+ this.logger.error({ url: this.info.url }, "Blocked upstream URL (SSRF)");
1227
+ throw new Error(securityResult.message || "Forbidden URL");
1228
+ }
1229
+ if (this.pinned) {
1230
+ this.pinned.resolvedIp = securityResult.resolvedIp;
1231
+ }
1232
+ }
1100
1233
  try {
1101
1234
  this.logger.debug("Connecting to upstream transport...");
1102
1235
  await this.mcpClient.connect(this.transport);
@@ -1108,16 +1241,15 @@ var UpstreamClient = class {
1108
1241
  }
1109
1242
  }
1110
1243
  async call(request, context) {
1111
- const isStdio = (info) => info.type === "stdio";
1112
- if (isStdio(this.info)) {
1113
- return this.callStdio(request);
1114
- } else {
1115
- return this.callHttp(request, context);
1244
+ const usesMcpClientTransport = (info) => info.type === "stdio" || info.type === "streamableHttp" || info.type === "sse";
1245
+ if (usesMcpClientTransport(this.info)) {
1246
+ return this.callMcpClient(request);
1116
1247
  }
1248
+ return this.callHttp(request, context);
1117
1249
  }
1118
- async callStdio(request) {
1250
+ async callMcpClient(request) {
1119
1251
  if (!this.mcpClient) {
1120
- return { jsonrpc: "2.0", id: request.id, error: { code: -32603, message: "Stdio client not initialized" } };
1252
+ return { jsonrpc: "2.0", id: request.id, error: { code: -32603, message: "MCP client not initialized" } };
1121
1253
  }
1122
1254
  try {
1123
1255
  await this.ensureConnected();
@@ -1157,19 +1289,21 @@ var UpstreamClient = class {
1157
1289
  };
1158
1290
  }
1159
1291
  } catch (error) {
1160
- this.logger.error({ err: error }, "Stdio call failed");
1292
+ this.logger.error({ err: error }, "MCP call failed");
1161
1293
  return {
1162
1294
  jsonrpc: "2.0",
1163
1295
  id: request.id,
1164
1296
  error: {
1165
1297
  code: error.code || -32603,
1166
- message: error.message || "Internal error in stdio transport"
1298
+ message: error.message || "Internal error in MCP transport"
1167
1299
  }
1168
1300
  };
1169
1301
  }
1170
1302
  }
1171
1303
  async callHttp(request, context) {
1172
- if (this.info.type === "stdio") throw new Error("Unreachable");
1304
+ if (this.info.type === "stdio" || this.info.type === "streamableHttp" || this.info.type === "sse") {
1305
+ throw new Error("Unreachable");
1306
+ }
1173
1307
  const url = this.info.url;
1174
1308
  const headers = {
1175
1309
  "Content-Type": "application/json",
@@ -1218,7 +1352,7 @@ var UpstreamClient = class {
1218
1352
  }
1219
1353
  }
1220
1354
  async getManifest(context) {
1221
- if (this.info.type !== "http") return null;
1355
+ if (this.info.type && this.info.type !== "http") return null;
1222
1356
  try {
1223
1357
  const baseUrl = this.info.url.replace(/\/$/, "");
1224
1358
  const manifestUrl = `${baseUrl}/conduit.manifest.json`;
@@ -1258,6 +1392,8 @@ var AuthService = class {
1258
1392
  logger;
1259
1393
  // Cache tokens separately from credentials to avoid mutation
1260
1394
  tokenCache = /* @__PURE__ */ new Map();
1395
+ // Keep the latest refresh token in-memory (rotating tokens)
1396
+ refreshTokenCache = /* @__PURE__ */ new Map();
1261
1397
  // Prevent concurrent refresh requests for the same client
1262
1398
  refreshLocks = /* @__PURE__ */ new Map();
1263
1399
  constructor(logger) {
@@ -1302,25 +1438,53 @@ var AuthService = class {
1302
1438
  }
1303
1439
  this.logger.info({ tokenUrl: creds.tokenUrl, clientId: creds.clientId }, "Refreshing OAuth2 token");
1304
1440
  try {
1305
- const body = new URLSearchParams();
1306
- body.set("grant_type", "refresh_token");
1307
- body.set("refresh_token", creds.refreshToken);
1308
- body.set("client_id", creds.clientId);
1441
+ const tokenUrl = creds.tokenUrl;
1442
+ const cachedRefreshToken = this.refreshTokenCache.get(cacheKey);
1443
+ const refreshToken = cachedRefreshToken || creds.refreshToken;
1444
+ if (!refreshToken) {
1445
+ throw new Error("OAuth2 credentials missing required fields for refresh");
1446
+ }
1447
+ const payload = {
1448
+ grant_type: "refresh_token",
1449
+ refresh_token: refreshToken,
1450
+ client_id: creds.clientId
1451
+ };
1309
1452
  if (creds.clientSecret) {
1310
- body.set("client_secret", creds.clientSecret);
1453
+ payload.client_secret = creds.clientSecret;
1311
1454
  }
1312
- const response = await axios3.post(creds.tokenUrl, body, {
1455
+ if (creds.tokenParams) {
1456
+ Object.assign(payload, creds.tokenParams);
1457
+ }
1458
+ const requestFormat = (() => {
1459
+ if (creds.tokenRequestFormat) return creds.tokenRequestFormat;
1460
+ try {
1461
+ const hostname = new URL(tokenUrl).hostname;
1462
+ if (hostname === "auth.atlassian.com") return "json";
1463
+ } catch {
1464
+ }
1465
+ return "form";
1466
+ })();
1467
+ const response = requestFormat === "json" ? await axios3.post(tokenUrl, payload, {
1468
+ headers: {
1469
+ "Content-Type": "application/json",
1470
+ "Accept": "application/json"
1471
+ }
1472
+ }) : await axios3.post(tokenUrl, new URLSearchParams(payload), {
1313
1473
  headers: {
1314
1474
  "Content-Type": "application/x-www-form-urlencoded",
1315
1475
  "Accept": "application/json"
1316
1476
  }
1317
1477
  });
1318
- const { access_token, expires_in } = response.data;
1319
- const expiresInSeconds = Number(expires_in) || 3600;
1478
+ const { access_token, expires_in, refresh_token } = response.data;
1479
+ const expiresInRaw = Number(expires_in);
1480
+ const expiresInSeconds = Number.isFinite(expiresInRaw) ? expiresInRaw : 3600;
1320
1481
  this.tokenCache.set(cacheKey, {
1321
1482
  accessToken: access_token,
1322
1483
  expiresAt: Date.now() + expiresInSeconds * 1e3
1323
1484
  });
1485
+ if (typeof refresh_token === "string" && refresh_token.length > 0) {
1486
+ this.refreshTokenCache.set(cacheKey, refresh_token);
1487
+ }
1324
1488
  return `Bearer ${access_token}`;
1325
1489
  } catch (err) {
1326
1490
  const errorMsg = err.response?.data?.error_description || err.response?.data?.error || err.message;
@@ -1545,11 +1709,18 @@ var GatewayService = class {
1545
1709
  }
1546
1710
  let tools = this.schemaCache.get(packageId);
1547
1711
  if (!tools) {
1548
- try {
1549
- const manifest = await client.getManifest(context);
1550
- if (manifest && manifest.tools) {
1551
- tools = manifest.tools;
1552
- } else {
1712
+ if (typeof client.getManifest === "function") {
1713
+ try {
1714
+ const manifest = await client.getManifest(context);
1715
+ if (manifest && manifest.tools) {
1716
+ tools = manifest.tools;
1717
+ }
1718
+ } catch (e) {
1719
+ this.logger.debug({ upstreamId: packageId, err: e.message }, "Manifest fetch failed (will fallback)");
1720
+ }
1721
+ }
1722
+ if (!tools) {
1723
+ try {
1553
1724
  if (typeof client.listTools === "function") {
1554
1725
  tools = await client.listTools();
1555
1726
  } else {
@@ -1564,13 +1735,13 @@ var GatewayService = class {
1564
1735
  this.logger.warn({ upstreamId: packageId, error: response.error }, "Failed to discover tools via RPC");
1565
1736
  }
1566
1737
  }
1738
+ } catch (e) {
1739
+ this.logger.error({ upstreamId: packageId, err: e.message }, "Error during tool discovery");
1567
1740
  }
1568
- if (tools && tools.length > 0) {
1569
- this.schemaCache.set(packageId, tools);
1570
- this.logger.info({ upstreamId: packageId, toolCount: tools.length }, "Discovered tools from upstream");
1571
- }
1572
- } catch (e) {
1573
- this.logger.error({ upstreamId: packageId, err: e.message }, "Error during tool discovery");
1741
+ }
1742
+ if (tools && tools.length > 0) {
1743
+ this.schemaCache.set(packageId, tools);
1744
+ this.logger.info({ upstreamId: packageId, toolCount: tools.length }, "Discovered tools from upstream");
1574
1745
  }
1575
1746
  }
1576
1747
  if (!tools) tools = [];
@@ -1827,8 +1998,8 @@ var GatewayService = class {
1827
1998
  };
1828
1999
 
1829
2000
  // src/core/network.policy.service.ts
1830
- import dns from "dns/promises";
1831
- import net2 from "net";
2001
+ import dns2 from "dns/promises";
2002
+ import net3 from "net";
1832
2003
  import { LRUCache as LRUCache2 } from "lru-cache";
1833
2004
  var NetworkPolicyService = class {
1834
2005
  logger;
@@ -1869,9 +2040,9 @@ var NetworkPolicyService = class {
1869
2040
  return { valid: false, message: "Access denied: private network access forbidden" };
1870
2041
  }
1871
2042
  }
1872
- if (!net2.isIP(hostname)) {
2043
+ if (!net3.isIP(hostname)) {
1873
2044
  try {
1874
- const lookup = await dns.lookup(hostname, { all: true });
2045
+ const lookup = await dns2.lookup(hostname, { all: true });
1875
2046
  const resolvedIps = [];
1876
2047
  for (const address of lookup) {
1877
2048
  let ip = address.address;
@@ -1972,13 +2143,14 @@ var SecurityService = class {
1972
2143
  checkRateLimit(key) {
1973
2144
  return this.networkPolicy.checkRateLimit(key);
1974
2145
  }
1975
- validateIpcToken(token) {
1976
- if (!this.ipcToken) {
1977
- return true;
1978
- }
2146
+ isMasterToken(token) {
2147
+ if (!this.ipcToken) return true;
1979
2148
  const expected = Buffer.from(this.ipcToken);
1980
- const actual = Buffer.from(token);
1981
- if (expected.length === actual.length && crypto2.timingSafeEqual(expected, actual)) {
2149
+ const actual = Buffer.from(token || "");
2150
+ return expected.length === actual.length && crypto2.timingSafeEqual(expected, actual);
2151
+ }
2152
+ validateIpcToken(token) {
2153
+ if (this.isMasterToken(token)) {
1982
2154
  return true;
1983
2155
  }
1984
2156
  return !!this.sessionManager.getSession(token);
@@ -2601,26 +2773,30 @@ var IsolateExecutor = class {
2601
2773
  const jail = ctx.global;
2602
2774
  let currentLogBytes = 0;
2603
2775
  let currentErrorBytes = 0;
2776
+ let totalLogEntries = 0;
2604
2777
  await jail.set("__log", new ivm.Callback((msg) => {
2778
+ if (totalLogEntries + 1 > limits.maxLogEntries) {
2779
+ throw new Error("[LIMIT_LOG_ENTRIES]");
2780
+ }
2605
2781
  if (currentLogBytes + msg.length + 1 > limits.maxOutputBytes) {
2606
2782
  throw new Error("[LIMIT_LOG]");
2607
2783
  }
2608
- if (currentLogBytes < limits.maxOutputBytes) {
2609
- logs.push(msg);
2610
- currentLogBytes += msg.length + 1;
2611
- }
2784
+ totalLogEntries++;
2785
+ logs.push(msg);
2786
+ currentLogBytes += msg.length + 1;
2612
2787
  }));
2613
2788
  await jail.set("__error", new ivm.Callback((msg) => {
2789
+ if (totalLogEntries + 1 > limits.maxLogEntries) {
2790
+ throw new Error("[LIMIT_LOG_ENTRIES]");
2791
+ }
2614
2792
  if (currentErrorBytes + msg.length + 1 > limits.maxOutputBytes) {
2615
2793
  throw new Error("[LIMIT_OUTPUT]");
2616
2794
  }
2617
- if (currentErrorBytes < limits.maxOutputBytes) {
2618
- errors.push(msg);
2619
- currentErrorBytes += msg.length + 1;
2620
- }
2795
+ totalLogEntries++;
2796
+ errors.push(msg);
2797
+ currentErrorBytes += msg.length + 1;
2621
2798
  }));
2622
2799
  let requestIdCounter = 0;
2623
- const pendingToolCalls = /* @__PURE__ */ new Map();
2624
2800
  await jail.set("__dispatchToolCall", new ivm.Callback((nameStr, argsStr) => {
2625
2801
  const requestId = ++requestIdCounter;
2626
2802
  const name = nameStr;
@@ -2756,6 +2932,28 @@ var IsolateExecutor = class {
2756
2932
  }
2757
2933
  };
2758
2934
  }
2935
+ if (message.includes("[LIMIT_LOG_ENTRIES]")) {
2936
+ return {
2937
+ stdout: logs.join("\n"),
2938
+ stderr: errors.join("\n"),
2939
+ exitCode: null,
2940
+ error: {
2941
+ code: -32014 /* LogLimitExceeded */,
2942
+ message: "Log entry limit exceeded"
2943
+ }
2944
+ };
2945
+ }
2946
+ if (message.includes("[LIMIT_LOG]") || message.includes("[LIMIT_OUTPUT]")) {
2947
+ return {
2948
+ stdout: logs.join("\n"),
2949
+ stderr: errors.join("\n"),
2950
+ exitCode: null,
2951
+ error: {
2952
+ code: -32013 /* OutputLimitExceeded */,
2953
+ message: "Output limit exceeded"
2954
+ }
2955
+ };
2956
+ }
2759
2957
  this.logger.error({ err }, "Isolate execution failed");
2760
2958
  return {
2761
2959
  stdout: logs.join("\n"),
@@ -3168,13 +3366,13 @@ var ExecutionService = class {
3168
3366
  const packages = await this.gatewayService.listToolPackages();
3169
3367
  const allBindings = [];
3170
3368
  this.logger.debug({ packageCount: packages.length, packages: packages.map((p) => p.id) }, "Fetching tool bindings");
3171
- for (const pkg of packages) {
3369
+ for (const pkg2 of packages) {
3172
3370
  try {
3173
- const stubs = await this.gatewayService.listToolStubs(pkg.id, context);
3174
- this.logger.debug({ packageId: pkg.id, stubCount: stubs.length }, "Got stubs from package");
3371
+ const stubs = await this.gatewayService.listToolStubs(pkg2.id, context);
3372
+ this.logger.debug({ packageId: pkg2.id, stubCount: stubs.length }, "Got stubs from package");
3175
3373
  allBindings.push(...stubs.map((s) => toToolBinding(s.id, void 0, s.description)));
3176
3374
  } catch (err) {
3177
- this.logger.warn({ packageId: pkg.id, err: err.message }, "Failed to list stubs for package");
3375
+ this.logger.warn({ packageId: pkg2.id, err: err.message }, "Failed to list stubs for package");
3178
3376
  }
3179
3377
  }
3180
3378
  this.logger.info({ totalBindings: allBindings.length }, "Tool bindings ready for SDK generation");
@@ -3269,8 +3467,7 @@ var AuthMiddleware = class {
3269
3467
  }
3270
3468
  async handle(request, context, next) {
3271
3469
  const providedToken = request.auth?.bearerToken || "";
3272
- const masterToken = this.securityService.getIpcToken();
3273
- const isMaster = !masterToken || providedToken === masterToken;
3470
+ const isMaster = this.securityService.isMasterToken(providedToken);
3274
3471
  const isSession = !isMaster && this.securityService.validateIpcToken(providedToken);
3275
3472
  if (!isMaster && !isSession) {
3276
3473
  return {
@@ -3446,21 +3643,29 @@ async function handleAuth(options) {
3446
3643
  return;
3447
3644
  }
3448
3645
  try {
3449
- const body = new URLSearchParams();
3450
- body.set("grant_type", "authorization_code");
3451
- body.set("code", code);
3452
- body.set("redirect_uri", redirectUri);
3453
- body.set("client_id", options.clientId);
3646
+ const payload = {
3647
+ grant_type: "authorization_code",
3648
+ code,
3649
+ redirect_uri: redirectUri,
3650
+ client_id: options.clientId
3651
+ };
3454
3652
  if (options.clientSecret) {
3455
- body.set("client_secret", options.clientSecret);
3653
+ payload.client_secret = options.clientSecret;
3456
3654
  }
3457
3655
  if (codeVerifier) {
3458
- body.set("code_verifier", codeVerifier);
3656
+ payload.code_verifier = codeVerifier;
3459
3657
  }
3460
3658
  if (resolvedResource) {
3461
- body.set("resource", resolvedResource);
3659
+ payload.resource = resolvedResource;
3462
3660
  }
3463
- const response = await axios4.post(resolvedTokenUrl, body, {
3661
+ const tokenHostname = new URL(resolvedTokenUrl).hostname;
3662
+ const useJson = tokenHostname === "auth.atlassian.com";
3663
+ const response = useJson ? await axios4.post(resolvedTokenUrl, payload, {
3664
+ headers: {
3665
+ "Content-Type": "application/json",
3666
+ "Accept": "application/json"
3667
+ }
3668
+ }) : await axios4.post(resolvedTokenUrl, new URLSearchParams(payload), {
3464
3669
  headers: {
3465
3670
  "Content-Type": "application/x-www-form-urlencoded",
3466
3671
  "Accept": "application/json"
@@ -3523,7 +3728,9 @@ async function handleAuth(options) {
3523
3728
 
3524
3729
  // src/index.ts
3525
3730
  var program = new Command();
3526
- program.name("conduit").description("A secure Code Mode execution substrate for MCP agents").version("1.0.0");
3731
+ var require2 = createRequire(import.meta.url);
3732
+ var pkg = require2("../package.json");
3733
+ program.name("conduit").description("A secure Code Mode execution substrate for MCP agents").version(pkg.version || "0.0.0");
3527
3734
  program.command("serve", { isDefault: true }).description("Start the Conduit server").option("--stdio", "Use stdio transport").option("--config <path>", "Path to config file").action(async (options) => {
3528
3735
  try {
3529
3736
  await startServer(options);