@adtl/gateway 0.3.0 → 0.3.2

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.
Files changed (62) hide show
  1. package/README.md +59 -1
  2. package/dist/audit.d.ts +4 -2
  3. package/dist/audit.d.ts.map +1 -1
  4. package/dist/audit.js.map +1 -1
  5. package/dist/cli.js +80 -15
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +49 -44
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +10 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/gateway.d.ts +1 -1
  12. package/dist/gateway.js +1 -1
  13. package/dist/hooks/claudeCode.d.ts +43 -0
  14. package/dist/hooks/claudeCode.d.ts.map +1 -0
  15. package/dist/hooks/claudeCode.js +148 -0
  16. package/dist/hooks/claudeCode.js.map +1 -0
  17. package/dist/hooks/index.d.ts +4 -0
  18. package/dist/hooks/index.d.ts.map +1 -0
  19. package/dist/hooks/index.js +4 -0
  20. package/dist/hooks/index.js.map +1 -0
  21. package/dist/hooks/preset.d.ts +16 -0
  22. package/dist/hooks/preset.d.ts.map +1 -0
  23. package/dist/hooks/preset.js +32 -0
  24. package/dist/hooks/preset.js.map +1 -0
  25. package/dist/hooks/run.d.ts +9 -0
  26. package/dist/hooks/run.d.ts.map +1 -0
  27. package/dist/hooks/run.js +44 -0
  28. package/dist/hooks/run.js.map +1 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/init/fromMcpJson.d.ts +41 -0
  34. package/dist/init/fromMcpJson.d.ts.map +1 -0
  35. package/dist/init/fromMcpJson.js +141 -0
  36. package/dist/init/fromMcpJson.js.map +1 -0
  37. package/dist/init/index.d.ts +2 -0
  38. package/dist/init/index.d.ts.map +1 -1
  39. package/dist/init/index.js +2 -0
  40. package/dist/init/index.js.map +1 -1
  41. package/dist/init/run.d.ts +13 -0
  42. package/dist/init/run.d.ts.map +1 -1
  43. package/dist/init/run.js +83 -11
  44. package/dist/init/run.js.map +1 -1
  45. package/dist/startup.d.ts +12 -1
  46. package/dist/startup.d.ts.map +1 -1
  47. package/dist/startup.js +27 -3
  48. package/dist/startup.js.map +1 -1
  49. package/package.json +4 -4
  50. package/src/audit.ts +4 -2
  51. package/src/cli.ts +70 -15
  52. package/src/config.ts +9 -3
  53. package/src/gateway.ts +1 -1
  54. package/src/hooks/claudeCode.ts +194 -0
  55. package/src/hooks/index.ts +3 -0
  56. package/src/hooks/preset.ts +47 -0
  57. package/src/hooks/run.ts +49 -0
  58. package/src/index.ts +1 -0
  59. package/src/init/fromMcpJson.ts +165 -0
  60. package/src/init/index.ts +2 -0
  61. package/src/init/run.ts +91 -12
  62. package/src/startup.ts +28 -4
package/src/startup.ts CHANGED
@@ -9,7 +9,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
9
  import { StdioClientTransport, getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js";
10
10
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
11
11
  import { INTERCEPTORS_EXTENSION, LABEL_INTERCEPTOR, POLICY_INTERCEPTOR, parseConfigText } from "@adtl/interceptors";
12
- import type { AdtlConfig } from "@adtl/labels";
12
+ import { defaultSessionsDir, type AdtlConfig } from "@adtl/labels";
13
13
  import { parse as parseYaml } from "yaml";
14
14
  import { ZodError } from "zod";
15
15
  import { ApprovalBroker, type Notifier } from "./approvals.js";
@@ -49,6 +49,12 @@ export async function loadGatewayConfig(path: string): Promise<LoadedConfig> {
49
49
  } catch (err) {
50
50
  throw new GatewayConfigError(`Cannot read config file ${abs}: ${(err as Error).message}`);
51
51
  }
52
+ return parseGatewayConfigText(text, abs);
53
+ }
54
+
55
+ /** Validate adtl.yaml text; `path` is where it lives (relative paths in it resolve against its directory). */
56
+ export function parseGatewayConfigText(text: string, path: string): LoadedConfig {
57
+ const abs = resolve(path);
52
58
  let labels: AdtlConfig;
53
59
  try {
54
60
  labels = parseConfigText(text, abs);
@@ -117,6 +123,16 @@ export function createApprovals(cfg: GatewayConfig, env: NodeJS.ProcessEnv, log:
117
123
  return new ApprovalBroker({ notifiers, timeoutMs: a.timeoutSeconds * 1000, log });
118
124
  }
119
125
 
126
+ /**
127
+ * Under Claude Code, which passes `CLAUDE_CODE_SESSION_ID` to stdio MCP
128
+ * servers, the gateway uses that id and keeps session labels in files, so the
129
+ * Claude Code hooks (`adtl-gateway hook`) add to the same session label.
130
+ */
131
+ export function sharedSession(env: NodeJS.ProcessEnv): { sessionId: string; dir: string } | undefined {
132
+ const sessionId = env.CLAUDE_CODE_SESSION_ID?.trim();
133
+ return sessionId ? { sessionId, dir: defaultSessionsDir(env) } : undefined;
134
+ }
135
+
120
136
  export interface StartedGateway {
121
137
  gateway: AdtlGateway;
122
138
  close(): Promise<void>;
@@ -134,9 +150,15 @@ export async function startGateway(
134
150
 
135
151
  try {
136
152
  // 1. Interceptor server (out of process, per the Interceptors WG).
137
- const icfg: StdioUpstream = loaded.gateway.interceptors
138
- ? expandUpstream("interceptors", loaded.gateway.interceptors, env)
139
- : { command: process.execPath, args: [bundledInterceptorsCli(), "--config", loaded.path], env: {} };
153
+ const shared = sharedSession(env);
154
+ const custom = loaded.gateway.interceptors && expandUpstream("interceptors", loaded.gateway.interceptors, env);
155
+ const icfg: StdioUpstream = custom
156
+ ? { ...custom, env: { ...custom.env, ...(shared ? { ADTL_SESSIONS_DIR: shared.dir } : {}) } }
157
+ : {
158
+ command: process.execPath,
159
+ args: [bundledInterceptorsCli(), "--config", loaded.path, ...(shared ? ["--sessions-dir", shared.dir] : [])],
160
+ env: {},
161
+ };
140
162
  const interceptors = await connectUpstream("interceptors", icfg, baseDir);
141
163
  clients.push(interceptors);
142
164
  if (!interceptors.getServerCapabilities()?.extensions?.[INTERCEPTORS_EXTENSION]) {
@@ -152,6 +174,7 @@ export async function startGateway(
152
174
  const audit = loaded.gateway.mode === "audit";
153
175
  const invoker = new InterceptorInvoker(bound, loaded.gateway.interceptorTimeoutMs, audit ? "audit" : undefined);
154
176
  log.info(`interceptors: ${names.join(", ")}`);
177
+ if (shared) log.info(`sharing session labels with Claude Code hooks (${shared.dir})`);
155
178
  if (audit) log.warn("audit mode: calls are logged but never stopped or changed");
156
179
 
157
180
  // 2. Upstream servers. One failing upstream does not stop the others.
@@ -178,6 +201,7 @@ export async function startGateway(
178
201
  const auditLog = loaded.gateway.audit ? new JsonlAuditLog(resolve(baseDir, loaded.gateway.audit)) : undefined;
179
202
  const approvals = createApprovals(loaded.gateway, env, log);
180
203
  const gateway = new AdtlGateway({
204
+ ...(shared ? { sessionId: shared.sessionId } : {}),
181
205
  upstreams,
182
206
  invoker,
183
207
  ...(auditLog ? { audit: auditLog } : {}),