@adtl/gateway 0.1.1 → 0.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.
Files changed (69) hide show
  1. package/README.md +57 -5
  2. package/dist/approvals.d.ts +97 -0
  3. package/dist/approvals.d.ts.map +1 -0
  4. package/dist/approvals.js +151 -0
  5. package/dist/approvals.js.map +1 -0
  6. package/dist/audit.d.ts +12 -2
  7. package/dist/audit.d.ts.map +1 -1
  8. package/dist/audit.js +1 -1
  9. package/dist/audit.js.map +1 -1
  10. package/dist/cli.js +46 -1
  11. package/dist/cli.js.map +1 -1
  12. package/dist/config.d.ts +346 -4
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +63 -2
  15. package/dist/config.js.map +1 -1
  16. package/dist/email.d.ts +35 -0
  17. package/dist/email.d.ts.map +1 -0
  18. package/dist/email.js +63 -0
  19. package/dist/email.js.map +1 -0
  20. package/dist/gateway.d.ts +33 -3
  21. package/dist/gateway.d.ts.map +1 -1
  22. package/dist/gateway.js +263 -23
  23. package/dist/gateway.js.map +1 -1
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +4 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/init/index.d.ts +4 -0
  29. package/dist/init/index.d.ts.map +1 -0
  30. package/dist/init/index.js +4 -0
  31. package/dist/init/index.js.map +1 -0
  32. package/dist/init/presets.d.ts +30 -0
  33. package/dist/init/presets.d.ts.map +1 -0
  34. package/dist/init/presets.js +34 -0
  35. package/dist/init/presets.js.map +1 -0
  36. package/dist/init/run.d.ts +18 -0
  37. package/dist/init/run.d.ts.map +1 -0
  38. package/dist/init/run.js +60 -0
  39. package/dist/init/run.js.map +1 -0
  40. package/dist/init/suggest.d.ts +50 -0
  41. package/dist/init/suggest.d.ts.map +1 -0
  42. package/dist/init/suggest.js +82 -0
  43. package/dist/init/suggest.js.map +1 -0
  44. package/dist/invoker.d.ts.map +1 -1
  45. package/dist/invoker.js +4 -2
  46. package/dist/invoker.js.map +1 -1
  47. package/dist/ntfy.d.ts +44 -0
  48. package/dist/ntfy.d.ts.map +1 -0
  49. package/dist/ntfy.js +133 -0
  50. package/dist/ntfy.js.map +1 -0
  51. package/dist/startup.d.ts +6 -1
  52. package/dist/startup.d.ts.map +1 -1
  53. package/dist/startup.js +37 -5
  54. package/dist/startup.js.map +1 -1
  55. package/package.json +4 -3
  56. package/src/approvals.ts +219 -0
  57. package/src/audit.ts +8 -3
  58. package/src/cli.ts +41 -1
  59. package/src/config.ts +65 -2
  60. package/src/email.ts +79 -0
  61. package/src/gateway.ts +324 -22
  62. package/src/index.ts +4 -0
  63. package/src/init/index.ts +3 -0
  64. package/src/init/presets.ts +64 -0
  65. package/src/init/run.ts +79 -0
  66. package/src/init/suggest.ts +127 -0
  67. package/src/invoker.ts +3 -2
  68. package/src/ntfy.ts +154 -0
  69. package/src/startup.ts +35 -4
package/README.md CHANGED
@@ -6,6 +6,7 @@ An MCP gateway for AI agents. It sits between your agent (Claude Code, Cursor, a
6
6
 
7
7
  - **labels every tool result**: where it came from, whether it can be trusted, how sensitive it is (MCP's draft `trust-annotations` format)
8
8
  - **checks every tool call** against what the session has already seen: allow, ask, or block
9
+ - **asks you on your phone** before risky calls, with Allow / Deny buttons (via [ntfy](https://ntfy.sh)), and can email you notices
9
10
  - **writes an audit log** of every decision (no arguments or results are stored)
10
11
 
11
12
  It stops attacks like the 2025 GitHub MCP exfiltration, where a public issue told an agent to post private repository contents in a comment.
@@ -57,13 +58,31 @@ One file holds both the labels and the gateway settings.
57
58
  | `confidentiality` | `public`, `private`, `secret` | Who may see the tool's *result*? |
58
59
  | `egress` | `none`, `internal`, `external`, `public` | Where *calling* the tool can send data. |
59
60
 
60
- Tools without a rule use the defaults (`untrusted`, `private`, egress `unknown`). That's safe but strict, so describe the tools you use.
61
+ Tools without a rule use the defaults (`untrusted`, `private`, egress `unknown`). That's safe but strict, so describe the tools you use. Resources and prompts use the server's `defaults`.
62
+
63
+ #### Drafting rules with `init`
64
+
65
+ List your servers under `gateway.upstreams`, then let `init` draft a rule for every tool:
66
+
67
+ ```sh
68
+ adtl-gateway init -c adtl.yaml # print the updated config, change nothing
69
+ adtl-gateway init -c adtl.yaml --write # update the file (original kept as adtl.yaml.bak)
70
+ ```
71
+
72
+ `init` connects to each upstream and lists its tools.
73
+
74
+ - **Presets** cover the GitHub (`github-mcp-server`), filesystem and fetch servers. A preset says what the server's reads and writes mean; the server's `readOnlyHint` decides which tools are reads, so new tools are covered too.
75
+ - **Other servers** get rules from their MCP tool hints. Hints only set egress, because they are the server's own claims: every result stays `untrusted` and `private` until you change it. These lines are marked `review`.
76
+ - Entries already in the file are never changed, so you can re-run `init` after a server adds tools. Tools the server no longer offers are reported, not removed.
77
+ - Edits keep your comments, but the YAML may be reformatted slightly (e.g. `[a]` becomes `[ a ]`).
78
+
79
+ Always review the result: a preset is a starting point, not a judgment about your data.
61
80
 
62
81
  Labels only get stricter as a session goes on. A session that has read anything untrusted stays untrusted until the session ends.
63
82
 
64
83
  ### Policy
65
84
 
66
- Rules match on `session.integrity`, `session.confidentiality`, `tool.egress` and `tool` (`server.tool` globs such as `github.*`), and decide `allow`, `ask` or `block`. When several rules match, the most restrictive decision wins. In this version `ask` is treated as `block`; approvals are planned for v0.2.
85
+ Rules match on `session.integrity`, `session.confidentiality`, `tool.egress` and `tool` (`server.tool` globs such as `github.*`), and decide `allow`, `ask` or `block`. When several rules match, the most restrictive decision wins. `ask` waits for a human when [approvals](#approvals) are configured, and blocks otherwise.
67
86
 
68
87
  ### Gateway
69
88
 
@@ -72,11 +91,43 @@ Rules match on `session.integrity`, `session.confidentiality`, `tool.egress` and
72
91
  | `gateway.upstreams.<id>` | A stdio server (`command`, `args`, `env`, `cwd`) or an HTTP server (`url`, `headers`). The `<id>` must match the key under `servers:`. |
73
92
  | `gateway.audit` | Path of a JSONL audit log, relative to the config file |
74
93
  | `gateway.mode` | `active` (default) or `audit`. See [Trying a policy safely](#trying-a-policy-safely). |
94
+ | `gateway.approvals` | Ask a human about `ask` decisions. See [Approvals](#approvals). |
75
95
  | `gateway.interceptorTimeoutMs` | Timeout per interceptor call (default 5000) |
76
96
  | `gateway.interceptors` | Custom interceptor server command (default: the bundled `@adtl/interceptors`) |
77
97
 
78
98
  `${VAR}` in commands, args, env, URLs and headers is read from the environment, so secrets stay out of the file.
79
99
 
100
+ ### Approvals
101
+
102
+ With approvals configured, an `ask` decision holds the call and sends you a push notification with **Allow** and **Deny** buttons. Allow runs the call; Deny, or no answer before the timeout, stops it.
103
+
104
+ ```yaml
105
+ gateway:
106
+ approvals:
107
+ timeoutSeconds: 50 # no answer means deny (default 50)
108
+ ntfy:
109
+ topic: ${ADTL_NTFY_TOPIC} # a long random name; subscribe to it in the ntfy app
110
+ # server: https://ntfy.example.com # default https://ntfy.sh
111
+ # token: ${NTFY_TOKEN} # for access-controlled topics
112
+ email: # optional, notify only
113
+ smtp: { host: smtp.example.com, port: 465, user: "${SMTP_USER}", pass: "${SMTP_PASS}" }
114
+ from: adtl@example.com
115
+ to: you@example.com
116
+ on: [pending, blocked] # also: approved, denied, timeout, undeliverable
117
+ ```
118
+
119
+ Setup: install the [ntfy app](https://ntfy.sh), subscribe to your topic, and set `ADTL_NTFY_TOPIC` in the gateway's environment, for example in Claude Code's `.mcp.json` `env`.
120
+
121
+ How it works and what to know:
122
+
123
+ - **No open ports.** The buttons post your answer to a reply topic (`<topic>-reply`) on the same ntfy server, and the gateway listens there.
124
+ - **Answers are signed.** Each button carries a token that only answers that one call (it's bound to the call's hash), only once, and only until it expires. Forged, reused or late answers are ignored.
125
+ - **Keep the topic private.** Anyone who can read your topic can press your buttons. Use a long random topic name on ntfy.sh, or an access-controlled topic on your own ntfy server.
126
+ - **Nothing sensitive is sent.** Notifications and emails name the tool, the server and the reason, never the call's arguments or results.
127
+ - **Email never contains approve links,** because mail security scanners open links automatically and would approve calls by themselves.
128
+ - **Mind client timeouts.** Many MCP clients give up on a request after 60 seconds, so the default timeout is 50. The gateway sends progress notifications while it waits, which lets clients that reset their timeout on progress wait longer.
129
+ - **Every outcome is logged.** The audit log records `approval: { outcome, channel, waitedMs }` for each call that was asked.
130
+
80
131
  ### Trying a policy safely
81
132
 
82
133
  Run in audit mode first to see what your policy would stop, without stopping anything:
@@ -101,9 +152,10 @@ The gateway fails closed. If an interceptor errors or times out, the call is sto
101
152
 
102
153
  ## Limits of this version
103
154
 
104
- - Only tools are proxied. Resources and prompts are not passed through yet.
105
- - `ask` blocks instead of asking. Human approvals are planned for v0.2.
106
- - Labels last for one gateway process, which is one agent session. They are not carried into files, memory or other agents yet; that's planned for v0.3.
155
+ - Tools, resources and prompts are proxied. Resource subscriptions, `list_changed` notifications and completions are not passed through yet.
156
+ - Resources and prompts are labeled by server defaults (`servers.<id>.defaults`) or the global defaults; there are no per-resource or per-prompt rules yet. A resource URI that two upstreams could serve is refused rather than guessed.
157
+ - Approvals come from one push channel (ntfy). Other channels, approvals inside the MCP client, and approver roles are planned.
158
+ - Labels last for one gateway process, which is one agent session. They are not carried into files, memory or other agents yet; that's planned for v0.4.
107
159
  - Labels are set by your config. Servers' own trust claims can only make labels stricter, unless you set `acceptServerClaims: true`.
108
160
 
109
161
  ## Links
@@ -0,0 +1,97 @@
1
+ export type ApprovalDecision = "allow" | "deny";
2
+ export type ApprovalOutcome = "approved" | "denied" | "timeout" | "undeliverable";
3
+ export interface ApprovalRequest {
4
+ id: string;
5
+ sessionId: string;
6
+ server: string;
7
+ tool: string;
8
+ callHash: string;
9
+ reason: string;
10
+ matched: string[];
11
+ /** The session label that triggered the rule (no arguments or results). */
12
+ sessionLabel?: unknown;
13
+ createdAt: Date;
14
+ expiresAt: Date;
15
+ }
16
+ export interface ApprovalResult {
17
+ outcome: ApprovalOutcome;
18
+ /** Notifier that delivered the answer. */
19
+ channel?: string;
20
+ approver?: string;
21
+ waitedMs: number;
22
+ }
23
+ export type ApprovalEvent = {
24
+ kind: "pending";
25
+ request: ApprovalRequest;
26
+ tokens: Record<ApprovalDecision, string>;
27
+ } | {
28
+ kind: "resolved";
29
+ request: ApprovalRequest;
30
+ result: ApprovalResult;
31
+ } | {
32
+ kind: "blocked";
33
+ sessionId: string;
34
+ server: string;
35
+ tool: string;
36
+ reason: string;
37
+ matched: string[];
38
+ };
39
+ /** Called by a notifier with every answer it receives. Returns true if the answer was accepted. */
40
+ export type AnswerFn = (token: string, approver?: string) => boolean;
41
+ export interface Notifier {
42
+ readonly name: string;
43
+ /** Deliver an event. Throwing marks the delivery as failed; it never stops the call by itself. */
44
+ notify(event: ApprovalEvent): Promise<void>;
45
+ /**
46
+ * Notifiers that can carry answers: pass every answer for `request` to
47
+ * `answer` until `signal` aborts. Called before the pending event is sent.
48
+ */
49
+ listen?(request: ApprovalRequest, answer: AnswerFn, signal: AbortSignal): Promise<void>;
50
+ }
51
+ /** Error message plus its cause, e.g. "fetch failed (ECONNRESET)". */
52
+ export declare function errorText(err: unknown): string;
53
+ export declare class ApprovalTokens {
54
+ private readonly secret;
55
+ constructor(secret?: Buffer);
56
+ sign(request: Pick<ApprovalRequest, "id" | "callHash" | "expiresAt">, decision: ApprovalDecision): string;
57
+ /** Parse a token without verifying it. */
58
+ static parse(token: string): {
59
+ id: string;
60
+ decision: ApprovalDecision;
61
+ exp: number;
62
+ sig: string;
63
+ } | undefined;
64
+ /** Verify a token against the pending request it claims to answer. */
65
+ verify(token: string, request: Pick<ApprovalRequest, "id" | "callHash">, now: Date): ApprovalDecision | undefined;
66
+ private mac;
67
+ }
68
+ export interface ApprovalBrokerOptions {
69
+ notifiers: Notifier[];
70
+ timeoutMs: number;
71
+ tokens?: ApprovalTokens;
72
+ now?: () => Date;
73
+ log?: {
74
+ warn(msg: string): void;
75
+ };
76
+ }
77
+ export declare class ApprovalBroker {
78
+ private readonly notifiers;
79
+ private readonly timeoutMs;
80
+ private readonly tokens;
81
+ private readonly now;
82
+ private readonly log;
83
+ private readonly pending;
84
+ constructor(opts: ApprovalBrokerOptions);
85
+ /** True if at least one notifier can carry answers. Without one, "ask" stays a block. */
86
+ get canApprove(): boolean;
87
+ /** Ask for approval and wait for the answer, the timeout, or a failure to reach anyone. */
88
+ request(input: Omit<ApprovalRequest, "id" | "createdAt" | "expiresAt">): Promise<ApprovalResult>;
89
+ /** Handle an answer token from a channel. Returns true if it settled a pending approval. */
90
+ answer(token: string, channel: string, approver?: string): boolean;
91
+ /** Tell notifiers a call was blocked. Fire and forget. */
92
+ blocked(event: Omit<Extract<ApprovalEvent, {
93
+ kind: "blocked";
94
+ }>, "kind">): void;
95
+ private broadcast;
96
+ }
97
+ //# sourceMappingURL=approvals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Human approvals for "ask" decisions.
3
+ *
4
+ * When adtl.policy answers `ask`, the gateway holds the call and asks the
5
+ * broker. The broker sends the request to every notifier and waits for an
6
+ * answer from a notifier that can carry one (e.g. ntfy). No answer before the
7
+ * timeout means deny.
8
+ *
9
+ * Answers are signed tokens: HMAC over the approval id, the call's hash, the
10
+ * decision and an expiry. A token only ever answers the one pending call it
11
+ * was made for, and only once. Anyone who can read the push channel can still
12
+ * use the tokens sent on it, so that channel must stay private.
13
+ */
14
+ import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
15
+ const TOKEN_PREFIX = "adtl1";
16
+ /** Error message plus its cause, e.g. "fetch failed (ECONNRESET)". */
17
+ export function errorText(err) {
18
+ if (!(err instanceof Error))
19
+ return String(err);
20
+ const cause = err.cause;
21
+ const detail = cause?.code ?? cause?.message;
22
+ return detail && !err.message.includes(detail) ? `${err.message} (${detail})` : err.message;
23
+ }
24
+ export class ApprovalTokens {
25
+ secret;
26
+ constructor(secret = randomBytes(32)) {
27
+ this.secret = secret;
28
+ }
29
+ sign(request, decision) {
30
+ const exp = Math.floor(request.expiresAt.getTime() / 1000);
31
+ return `${TOKEN_PREFIX}.${request.id}.${decision}.${exp}.${this.mac(request.id, request.callHash, decision, exp)}`;
32
+ }
33
+ /** Parse a token without verifying it. */
34
+ static parse(token) {
35
+ const parts = token.trim().split(".");
36
+ if (parts.length !== 5 || parts[0] !== TOKEN_PREFIX)
37
+ return undefined;
38
+ const [, id, decision, exp, sig] = parts;
39
+ if (decision !== "allow" && decision !== "deny")
40
+ return undefined;
41
+ if (!/^\d+$/.test(exp))
42
+ return undefined;
43
+ return { id, decision, exp: Number(exp), sig };
44
+ }
45
+ /** Verify a token against the pending request it claims to answer. */
46
+ verify(token, request, now) {
47
+ const t = ApprovalTokens.parse(token);
48
+ if (!t || t.id !== request.id)
49
+ return undefined;
50
+ if (now.getTime() > t.exp * 1000)
51
+ return undefined;
52
+ const expected = Buffer.from(this.mac(t.id, request.callHash, t.decision, t.exp));
53
+ const given = Buffer.from(t.sig);
54
+ if (given.length !== expected.length || !timingSafeEqual(given, expected))
55
+ return undefined;
56
+ return t.decision;
57
+ }
58
+ mac(id, callHash, decision, exp) {
59
+ return createHmac("sha256", this.secret).update(`${id}\n${callHash}\n${decision}\n${exp}`).digest("base64url");
60
+ }
61
+ }
62
+ export class ApprovalBroker {
63
+ notifiers;
64
+ timeoutMs;
65
+ tokens;
66
+ now;
67
+ log;
68
+ pending = new Map();
69
+ constructor(opts) {
70
+ this.notifiers = opts.notifiers;
71
+ this.timeoutMs = opts.timeoutMs;
72
+ this.tokens = opts.tokens ?? new ApprovalTokens();
73
+ this.now = opts.now ?? (() => new Date());
74
+ this.log = opts.log ?? { warn: () => { } };
75
+ }
76
+ /** True if at least one notifier can carry answers. Without one, "ask" stays a block. */
77
+ get canApprove() {
78
+ return this.notifiers.some((n) => n.listen);
79
+ }
80
+ /** Ask for approval and wait for the answer, the timeout, or a failure to reach anyone. */
81
+ async request(input) {
82
+ const createdAt = this.now();
83
+ const request = {
84
+ ...input,
85
+ id: randomUUID(),
86
+ createdAt,
87
+ expiresAt: new Date(createdAt.getTime() + this.timeoutMs),
88
+ };
89
+ const started = performance.now();
90
+ const waited = () => Math.round(performance.now() - started);
91
+ const abort = new AbortController();
92
+ let finish;
93
+ const result = new Promise((resolve) => {
94
+ const timer = setTimeout(() => finish({ outcome: "timeout", waitedMs: waited() }), this.timeoutMs);
95
+ finish = (r) => {
96
+ if (!this.pending.delete(request.id))
97
+ return; // already settled
98
+ clearTimeout(timer);
99
+ abort.abort();
100
+ resolve(r);
101
+ };
102
+ });
103
+ this.pending.set(request.id, {
104
+ request,
105
+ settle: (decision, channel, approver) => finish({ outcome: decision === "allow" ? "approved" : "denied", channel, ...(approver ? { approver } : {}), waitedMs: waited() }),
106
+ });
107
+ // Start listening before anyone can answer, then send the request everywhere.
108
+ const listeners = this.notifiers.filter((n) => n.listen);
109
+ for (const n of listeners) {
110
+ n.listen(request, (token, approver) => this.answer(token, n.name, approver), abort.signal).catch((err) => this.log.warn(`${n.name}: stopped listening for approvals: ${errorText(err)}`));
111
+ }
112
+ const tokens = { allow: this.tokens.sign(request, "allow"), deny: this.tokens.sign(request, "deny") };
113
+ const sent = this.notifiers.map((n) => n.notify({ kind: "pending", request, tokens }).then(() => true, (err) => {
114
+ this.log.warn(`${n.name}: could not send approval request: ${errorText(err)}`);
115
+ return false;
116
+ }));
117
+ // Nobody who could answer was reached: deny now rather than wait out the timeout.
118
+ // Notify-only channels (email) are not waited for.
119
+ void Promise.all(sent.filter((_, i) => this.notifiers[i].listen)).then((ok) => {
120
+ if (!ok.includes(true))
121
+ finish({ outcome: "undeliverable", waitedMs: waited() });
122
+ });
123
+ const r = await result;
124
+ this.broadcast({ kind: "resolved", request, result: r });
125
+ return r;
126
+ }
127
+ /** Handle an answer token from a channel. Returns true if it settled a pending approval. */
128
+ answer(token, channel, approver) {
129
+ const parsed = ApprovalTokens.parse(token);
130
+ const p = parsed && this.pending.get(parsed.id);
131
+ if (!p)
132
+ return false; // not ours, already answered, or garbage
133
+ const decision = this.tokens.verify(token, p.request, this.now());
134
+ if (!decision) {
135
+ this.log.warn(`${channel}: ignored an approval answer with a bad or expired signature`);
136
+ return false;
137
+ }
138
+ p.settle(decision, channel, approver);
139
+ return true;
140
+ }
141
+ /** Tell notifiers a call was blocked. Fire and forget. */
142
+ blocked(event) {
143
+ this.broadcast({ kind: "blocked", ...event });
144
+ }
145
+ broadcast(event) {
146
+ for (const n of this.notifiers) {
147
+ n.notify(event).catch((err) => this.log.warn(`${n.name}: could not send ${event.kind} notice: ${errorText(err)}`));
148
+ }
149
+ }
150
+ }
151
+ //# sourceMappingURL=approvals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.js","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"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"}
package/dist/audit.d.ts CHANGED
@@ -1,15 +1,25 @@
1
1
  export interface AuditEntry {
2
2
  ts: string;
3
3
  sessionId: string;
4
- event: "tools/call";
4
+ event: "tools/call" | "resources/read" | "prompts/get";
5
5
  server: string;
6
- tool: string;
6
+ /** Exactly one of tool, resource or prompt is set, matching `event`. */
7
+ tool?: string;
8
+ resource?: string;
9
+ prompt?: string;
7
10
  /** Final outcome of the call. */
8
11
  outcome: "forwarded" | "blocked" | "upstream_error";
9
12
  /** The policy's decision. In audit mode a call can be forwarded with decision "ask" or "block". */
10
13
  decision?: string;
11
14
  /** Present when the gateway ran in audit mode: nothing was stopped or changed. */
12
15
  mode?: "audit";
16
+ /** Present when a human was asked: outcome, channel, approver (if known) and wait time. */
17
+ approval?: {
18
+ outcome: string;
19
+ channel?: string;
20
+ approver?: string;
21
+ waitedMs: number;
22
+ };
13
23
  reason?: string;
14
24
  matched?: unknown;
15
25
  callHash?: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,gBAAgB,CAAC;IACpD,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAChC;AAED,qBAAa,aAAc,YAAW,SAAS;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;IAIjC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAG/B;AAED,qBAAa,cAAe,YAAW,SAAS;IAC9C,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,CAAM;IACpC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAG/B"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,GAAG,gBAAgB,GAAG,aAAa,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,gBAAgB,CAAC;IACpD,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,2FAA2F;IAC3F,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAChC;AAED,qBAAa,aAAc,YAAW,SAAS;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;IAIjC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAG/B;AAED,qBAAa,cAAe,YAAW,SAAS;IAC9C,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,CAAM;IACpC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAG/B"}
package/dist/audit.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Append-only JSONL audit log. One line per tool call.
2
+ * Append-only JSONL audit log. One line per tool call, resource read or prompt get.
3
3
  *
4
4
  * Arguments and results are never written; the call is identified by its
5
5
  * hash, so the log can be shared without leaking the data it describes.
package/dist/audit.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2BpC,MAAM,OAAO,aAAa;IACH;IAArB,YAAqB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAC/B,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,KAAiB;QACrB,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAClE,CAAC;CACF;AAED,MAAM,OAAO,cAAc;IAChB,OAAO,GAAiB,EAAE,CAAC;IACpC,KAAK,CAAC,KAAiB;QACrB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;CACF"}
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,MAAM,OAAO,aAAa;IACH;IAArB,YAAqB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAC/B,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,KAAiB;QACrB,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAClE,CAAC;CACF;AAED,MAAM,OAAO,cAAc;IAChB,OAAO,GAAiB,EAAE,CAAC;IACpC,KAAK,CAAC,KAAiB;QACrB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;CACF"}
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * servers through ADTL's interceptors.
5
5
  *
6
6
  * adtl-gateway --config adtl.yaml [--audit]
7
+ * adtl-gateway init --config adtl.yaml [--write]
7
8
  *
8
9
  * Point your MCP client (e.g. Claude Code's .mcp.json) at this command.
9
10
  * Logs go to stderr; stdout carries MCP JSON-RPC only.
@@ -11,8 +12,10 @@
11
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
13
  import { EnvExpansionError } from "./config.js";
13
14
  import { GATEWAY_VERSION } from "./gateway.js";
15
+ import { runInit } from "./init/run.js";
14
16
  import { GatewayConfigError, loadGatewayConfig, startGateway } from "./startup.js";
15
17
  const USAGE = `Usage: adtl-gateway --config <adtl.yaml> [--audit]
18
+ adtl-gateway init --config <adtl.yaml> [--write]
16
19
 
17
20
  Runs an MCP server over stdio that forwards tool calls to the upstream
18
21
  servers in the config, checking and labeling each call with ADTL.
@@ -22,12 +25,21 @@ Options:
22
25
  --audit Log what would be stopped, but stop nothing
23
26
  (same as gateway.mode: audit).
24
27
  -v, --version Print the version and exit.
28
+
29
+ init:
30
+ Connects to every upstream, lists its tools, and drafts a labeling rule
31
+ for each tool the config doesn't cover yet (presets for GitHub,
32
+ filesystem and fetch; MCP tool hints otherwise). Existing entries are
33
+ never changed. Prints the updated config; review it before using it.
34
+ --write Update the config file in place (original kept as .bak).
25
35
  -h, --help Show this help.`;
26
36
  function parseArgs(argv) {
27
37
  const out = {};
28
38
  for (let i = 0; i < argv.length; i++) {
29
39
  const a = argv[i];
30
- if (a === "-h" || a === "--help")
40
+ if (i === 0 && a === "init")
41
+ out.init = true;
42
+ else if (a === "-h" || a === "--help")
31
43
  out.help = true;
32
44
  else if (a === "-v" || a === "--version")
33
45
  out.version = true;
@@ -37,6 +49,8 @@ function parseArgs(argv) {
37
49
  out.config = a.slice("--config=".length);
38
50
  else if (a === "--audit")
39
51
  out.audit = true;
52
+ else if (a === "--write" && out.init)
53
+ out.write = true;
40
54
  else
41
55
  throw new GatewayConfigError(`Unknown argument: ${a}\n\n${USAGE}`);
42
56
  }
@@ -51,6 +65,8 @@ async function main() {
51
65
  return void console.log(GATEWAY_VERSION);
52
66
  if (!args.config)
53
67
  throw new GatewayConfigError(`Missing --config\n\n${USAGE}`);
68
+ if (args.init)
69
+ return init(args.config, args.write === true);
54
70
  const loaded = await loadGatewayConfig(args.config);
55
71
  if (args.audit)
56
72
  loaded.gateway.mode = "audit";
@@ -74,6 +90,35 @@ async function main() {
74
90
  await server.connect(new StdioServerTransport());
75
91
  console.error(`[adtl-gateway] ${GATEWAY_VERSION} ready (session ${started.gateway.sessionId})`);
76
92
  }
93
+ function summarize(r, path, write) {
94
+ const lines = r.reports.map((s) => {
95
+ const parts = [`${s.added.length} tools added`];
96
+ if (s.kept.length)
97
+ parts.push(`${s.kept.length} kept`);
98
+ if (s.addedDefaults)
99
+ parts.push("defaults added");
100
+ if (s.stale.length)
101
+ parts.push(`no longer offered: ${s.stale.join(", ")}`);
102
+ return ` ${s.id} (${s.preset ? `preset ${s.preset}` : "from tool hints, review"}): ${parts.join("; ")}`;
103
+ });
104
+ for (const f of r.failed)
105
+ lines.push(` ${f.id}: could not connect, skipped (${f.error})`);
106
+ const changed = r.reports.some((s) => s.added.length || s.addedDefaults);
107
+ const tail = !changed
108
+ ? "Nothing to add."
109
+ : write
110
+ ? `Updated ${path} (original saved as ${path}.bak). Review every line marked "review".`
111
+ : "Nothing written. Review the config above, then re-run with --write.";
112
+ return `${lines.join("\n")}\n${tail}`;
113
+ }
114
+ async function init(path, write) {
115
+ const result = await runInit({ path, write });
116
+ if (!write)
117
+ process.stdout.write(result.text);
118
+ console.error(`[adtl-gateway] init\n${summarize(result, path, write)}`);
119
+ if (result.failed.length && !result.reports.length)
120
+ process.exitCode = 1;
121
+ }
77
122
  main().catch((err) => {
78
123
  const known = err instanceof GatewayConfigError || err instanceof EnvExpansionError;
79
124
  console.error(known ? `[adtl-gateway] ${err.message}` : err);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}