@adtl/gateway 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.
@@ -1 +1 @@
1
- {"version":3,"file":"startup.js","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"startup.js","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adtl/gateway",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Experimental: MCP gateway that labels every tool result and blocks risky tool calls using ADTL trust labels",
5
5
  "keywords": [
6
6
  "mcp",
@@ -46,9 +46,10 @@
46
46
  "url": "https://github.com/probita-ai/adtl/issues"
47
47
  },
48
48
  "dependencies": {
49
- "@adtl/interceptors": "0.1.1",
50
- "@adtl/labels": "0.1.1",
49
+ "@adtl/interceptors": "0.2.0",
50
+ "@adtl/labels": "0.2.0",
51
51
  "@modelcontextprotocol/sdk": "1.31.0",
52
+ "nodemailer": "10.0.12",
52
53
  "yaml": "2.9.1",
53
54
  "zod": "3.25.76"
54
55
  },
@@ -0,0 +1,219 @@
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
+
16
+ export type ApprovalDecision = "allow" | "deny";
17
+ export type ApprovalOutcome = "approved" | "denied" | "timeout" | "undeliverable";
18
+
19
+ export interface ApprovalRequest {
20
+ id: string;
21
+ sessionId: string;
22
+ server: string;
23
+ tool: string;
24
+ callHash: string;
25
+ reason: string;
26
+ matched: string[];
27
+ /** The session label that triggered the rule (no arguments or results). */
28
+ sessionLabel?: unknown;
29
+ createdAt: Date;
30
+ expiresAt: Date;
31
+ }
32
+
33
+ export interface ApprovalResult {
34
+ outcome: ApprovalOutcome;
35
+ /** Notifier that delivered the answer. */
36
+ channel?: string;
37
+ approver?: string;
38
+ waitedMs: number;
39
+ }
40
+
41
+ export type ApprovalEvent =
42
+ | { kind: "pending"; request: ApprovalRequest; tokens: Record<ApprovalDecision, string> }
43
+ | { kind: "resolved"; request: ApprovalRequest; result: ApprovalResult }
44
+ | { kind: "blocked"; sessionId: string; server: string; tool: string; reason: string; matched: string[] };
45
+
46
+ /** Called by a notifier with every answer it receives. Returns true if the answer was accepted. */
47
+ export type AnswerFn = (token: string, approver?: string) => boolean;
48
+
49
+ export interface Notifier {
50
+ readonly name: string;
51
+ /** Deliver an event. Throwing marks the delivery as failed; it never stops the call by itself. */
52
+ notify(event: ApprovalEvent): Promise<void>;
53
+ /**
54
+ * Notifiers that can carry answers: pass every answer for `request` to
55
+ * `answer` until `signal` aborts. Called before the pending event is sent.
56
+ */
57
+ listen?(request: ApprovalRequest, answer: AnswerFn, signal: AbortSignal): Promise<void>;
58
+ }
59
+
60
+ const TOKEN_PREFIX = "adtl1";
61
+
62
+ /** Error message plus its cause, e.g. "fetch failed (ECONNRESET)". */
63
+ export function errorText(err: unknown): string {
64
+ if (!(err instanceof Error)) return String(err);
65
+ const cause = err.cause as { code?: string; message?: string } | undefined;
66
+ const detail = cause?.code ?? cause?.message;
67
+ return detail && !err.message.includes(detail) ? `${err.message} (${detail})` : err.message;
68
+ }
69
+
70
+ export class ApprovalTokens {
71
+ constructor(private readonly secret: Buffer = randomBytes(32)) {}
72
+
73
+ sign(request: Pick<ApprovalRequest, "id" | "callHash" | "expiresAt">, decision: ApprovalDecision): string {
74
+ const exp = Math.floor(request.expiresAt.getTime() / 1000);
75
+ return `${TOKEN_PREFIX}.${request.id}.${decision}.${exp}.${this.mac(request.id, request.callHash, decision, exp)}`;
76
+ }
77
+
78
+ /** Parse a token without verifying it. */
79
+ static parse(token: string): { id: string; decision: ApprovalDecision; exp: number; sig: string } | undefined {
80
+ const parts = token.trim().split(".");
81
+ if (parts.length !== 5 || parts[0] !== TOKEN_PREFIX) return undefined;
82
+ const [, id, decision, exp, sig] = parts as [string, string, string, string, string];
83
+ if (decision !== "allow" && decision !== "deny") return undefined;
84
+ if (!/^\d+$/.test(exp)) return undefined;
85
+ return { id, decision, exp: Number(exp), sig };
86
+ }
87
+
88
+ /** Verify a token against the pending request it claims to answer. */
89
+ verify(token: string, request: Pick<ApprovalRequest, "id" | "callHash">, now: Date): ApprovalDecision | undefined {
90
+ const t = ApprovalTokens.parse(token);
91
+ if (!t || t.id !== request.id) return undefined;
92
+ if (now.getTime() > t.exp * 1000) return undefined;
93
+ const expected = Buffer.from(this.mac(t.id, request.callHash, t.decision, t.exp));
94
+ const given = Buffer.from(t.sig);
95
+ if (given.length !== expected.length || !timingSafeEqual(given, expected)) return undefined;
96
+ return t.decision;
97
+ }
98
+
99
+ private mac(id: string, callHash: string, decision: ApprovalDecision, exp: number): string {
100
+ return createHmac("sha256", this.secret).update(`${id}\n${callHash}\n${decision}\n${exp}`).digest("base64url");
101
+ }
102
+ }
103
+
104
+ export interface ApprovalBrokerOptions {
105
+ notifiers: Notifier[];
106
+ timeoutMs: number;
107
+ tokens?: ApprovalTokens;
108
+ now?: () => Date;
109
+ log?: { warn(msg: string): void };
110
+ }
111
+
112
+ interface Pending {
113
+ request: ApprovalRequest;
114
+ settle(decision: ApprovalDecision, channel: string, approver?: string): void;
115
+ }
116
+
117
+ export class ApprovalBroker {
118
+ private readonly notifiers: Notifier[];
119
+ private readonly timeoutMs: number;
120
+ private readonly tokens: ApprovalTokens;
121
+ private readonly now: () => Date;
122
+ private readonly log: { warn(msg: string): void };
123
+ private readonly pending = new Map<string, Pending>();
124
+
125
+ constructor(opts: ApprovalBrokerOptions) {
126
+ this.notifiers = opts.notifiers;
127
+ this.timeoutMs = opts.timeoutMs;
128
+ this.tokens = opts.tokens ?? new ApprovalTokens();
129
+ this.now = opts.now ?? (() => new Date());
130
+ this.log = opts.log ?? { warn: () => {} };
131
+ }
132
+
133
+ /** True if at least one notifier can carry answers. Without one, "ask" stays a block. */
134
+ get canApprove(): boolean {
135
+ return this.notifiers.some((n) => n.listen);
136
+ }
137
+
138
+ /** Ask for approval and wait for the answer, the timeout, or a failure to reach anyone. */
139
+ async request(input: Omit<ApprovalRequest, "id" | "createdAt" | "expiresAt">): Promise<ApprovalResult> {
140
+ const createdAt = this.now();
141
+ const request: ApprovalRequest = {
142
+ ...input,
143
+ id: randomUUID(),
144
+ createdAt,
145
+ expiresAt: new Date(createdAt.getTime() + this.timeoutMs),
146
+ };
147
+ const started = performance.now();
148
+ const waited = () => Math.round(performance.now() - started);
149
+ const abort = new AbortController();
150
+
151
+ let finish!: (r: ApprovalResult) => void;
152
+ const result = new Promise<ApprovalResult>((resolve) => {
153
+ const timer = setTimeout(() => finish({ outcome: "timeout", waitedMs: waited() }), this.timeoutMs);
154
+ finish = (r) => {
155
+ if (!this.pending.delete(request.id)) return; // already settled
156
+ clearTimeout(timer);
157
+ abort.abort();
158
+ resolve(r);
159
+ };
160
+ });
161
+ this.pending.set(request.id, {
162
+ request,
163
+ settle: (decision, channel, approver) =>
164
+ finish({ outcome: decision === "allow" ? "approved" : "denied", channel, ...(approver ? { approver } : {}), waitedMs: waited() }),
165
+ });
166
+
167
+ // Start listening before anyone can answer, then send the request everywhere.
168
+ const listeners = this.notifiers.filter((n) => n.listen);
169
+ for (const n of listeners) {
170
+ n.listen!(request, (token, approver) => this.answer(token, n.name, approver), abort.signal).catch((err: unknown) =>
171
+ this.log.warn(`${n.name}: stopped listening for approvals: ${errorText(err)}`),
172
+ );
173
+ }
174
+ const tokens = { allow: this.tokens.sign(request, "allow"), deny: this.tokens.sign(request, "deny") };
175
+ const sent = this.notifiers.map((n) =>
176
+ n.notify({ kind: "pending", request, tokens }).then(
177
+ () => true,
178
+ (err: unknown) => {
179
+ this.log.warn(`${n.name}: could not send approval request: ${errorText(err)}`);
180
+ return false;
181
+ },
182
+ ),
183
+ );
184
+ // Nobody who could answer was reached: deny now rather than wait out the timeout.
185
+ // Notify-only channels (email) are not waited for.
186
+ void Promise.all(sent.filter((_, i) => this.notifiers[i]!.listen)).then((ok) => {
187
+ if (!ok.includes(true)) finish({ outcome: "undeliverable", waitedMs: waited() });
188
+ });
189
+
190
+ const r = await result;
191
+ this.broadcast({ kind: "resolved", request, result: r });
192
+ return r;
193
+ }
194
+
195
+ /** Handle an answer token from a channel. Returns true if it settled a pending approval. */
196
+ answer(token: string, channel: string, approver?: string): boolean {
197
+ const parsed = ApprovalTokens.parse(token);
198
+ const p = parsed && this.pending.get(parsed.id);
199
+ if (!p) return false; // not ours, already answered, or garbage
200
+ const decision = this.tokens.verify(token, p.request, this.now());
201
+ if (!decision) {
202
+ this.log.warn(`${channel}: ignored an approval answer with a bad or expired signature`);
203
+ return false;
204
+ }
205
+ p.settle(decision, channel, approver);
206
+ return true;
207
+ }
208
+
209
+ /** Tell notifiers a call was blocked. Fire and forget. */
210
+ blocked(event: Omit<Extract<ApprovalEvent, { kind: "blocked" }>, "kind">): void {
211
+ this.broadcast({ kind: "blocked", ...event });
212
+ }
213
+
214
+ private broadcast(event: ApprovalEvent): void {
215
+ for (const n of this.notifiers) {
216
+ n.notify(event).catch((err: unknown) => this.log.warn(`${n.name}: could not send ${event.kind} notice: ${errorText(err)}`));
217
+ }
218
+ }
219
+ }
package/src/audit.ts CHANGED
@@ -19,6 +19,8 @@ export interface AuditEntry {
19
19
  decision?: string;
20
20
  /** Present when the gateway ran in audit mode: nothing was stopped or changed. */
21
21
  mode?: "audit";
22
+ /** Present when a human was asked: outcome, channel, approver (if known) and wait time. */
23
+ approval?: { outcome: string; channel?: string; approver?: string; waitedMs: number };
22
24
  reason?: string;
23
25
  matched?: unknown;
24
26
  callHash?: unknown;
package/src/config.ts CHANGED
@@ -12,11 +12,19 @@
12
12
  * docs:
13
13
  * url: https://example.com/mcp
14
14
  * headers: { Authorization: "Bearer ${DOCS_TOKEN}" }
15
+ * approvals: # optional: make "ask" really ask
16
+ * timeoutSeconds: 50
17
+ * ntfy: { topic: "${ADTL_NTFY_TOPIC}" }
18
+ * email:
19
+ * smtp: { host: smtp.example.com, port: 465, user: "${SMTP_USER}", pass: "${SMTP_PASS}" }
20
+ * from: adtl@example.com
21
+ * to: you@example.com
15
22
  *
16
- * `${VAR}` in command, args, env, cwd, url and headers is replaced from the
17
- * gateway's environment. A missing variable is a config error.
23
+ * `${VAR}` in command, args, env, cwd, url, headers and approval settings is
24
+ * replaced from the gateway's environment. A missing variable is a config error.
18
25
  */
19
26
  import { z } from "zod";
27
+ import { EMAIL_EVENTS } from "./email.js";
20
28
 
21
29
  const UPSTREAM_ID = /^[A-Za-z0-9-]+$/;
22
30
 
@@ -41,6 +49,48 @@ export type StdioUpstream = z.infer<typeof StdioUpstreamSchema>;
41
49
  export type HttpUpstream = z.infer<typeof HttpUpstreamSchema>;
42
50
  export type Upstream = z.infer<typeof UpstreamSchema>;
43
51
 
52
+ export const NtfyConfigSchema = z
53
+ .object({
54
+ server: z.string().min(1).default("https://ntfy.sh"),
55
+ /** Topic approval requests are sent to. Anyone who can read it can approve: keep it secret. */
56
+ topic: z.string().min(1),
57
+ /** Topic answers are posted to. Default: `<topic>-reply`. */
58
+ replyTopic: z.string().min(1).optional(),
59
+ token: z.string().min(1).optional(),
60
+ })
61
+ .strict();
62
+
63
+ export const EmailConfigSchema = z
64
+ .object({
65
+ smtp: z
66
+ .object({
67
+ host: z.string().min(1),
68
+ port: z.number().int().positive().default(587),
69
+ /** TLS from the start. Default: true on port 465, otherwise STARTTLS. */
70
+ secure: z.boolean().optional(),
71
+ user: z.string().min(1).optional(),
72
+ pass: z.string().optional(),
73
+ })
74
+ .strict(),
75
+ from: z.string().min(1),
76
+ to: z.union([z.string().min(1), z.array(z.string().min(1)).nonempty()]),
77
+ on: z.array(z.enum(EMAIL_EVENTS)).nonempty().default(["pending", "blocked"]),
78
+ })
79
+ .strict();
80
+
81
+ export const ApprovalsConfigSchema = z
82
+ .object({
83
+ /**
84
+ * How long a call waits for an answer; no answer means deny. Many MCP
85
+ * clients give up on a request after 60 seconds, hence the default.
86
+ */
87
+ timeoutSeconds: z.number().int().positive().max(3600).default(50),
88
+ ntfy: NtfyConfigSchema.optional(),
89
+ email: EmailConfigSchema.optional(),
90
+ })
91
+ .strict();
92
+ export type ApprovalsConfig = z.infer<typeof ApprovalsConfigSchema>;
93
+
44
94
  export const GatewayConfigSchema = z
45
95
  .object({
46
96
  upstreams: z
@@ -58,6 +108,8 @@ export const GatewayConfigSchema = z
58
108
  * logs what would have been stopped: for trying a policy out safely.
59
109
  */
60
110
  mode: z.enum(["active", "audit"]).default("active"),
111
+ /** Human approvals for "ask" decisions. Without an answering channel, "ask" blocks. */
112
+ approvals: ApprovalsConfigSchema.optional(),
61
113
  /** Timeout for each interceptor invocation, in milliseconds. */
62
114
  interceptorTimeoutMs: z.number().int().positive().default(5000),
63
115
  })
@@ -101,6 +153,17 @@ export function expandUpstream<T extends Upstream>(id: string, u: T, env: NodeJS
101
153
  };
102
154
  }
103
155
 
156
+ /** Expand `${VAR}` in every string of the approvals section. */
157
+ export function expandApprovals(a: ApprovalsConfig, env: NodeJS.ProcessEnv): ApprovalsConfig {
158
+ const walk = (v: unknown, where: string): unknown => {
159
+ if (typeof v === "string") return expandEnv(v, env, where);
160
+ if (Array.isArray(v)) return v.map((x, i) => walk(x, `${where}[${i}]`));
161
+ if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x, `${where}.${k}`)]));
162
+ return v;
163
+ };
164
+ return walk(a, "gateway.approvals") as ApprovalsConfig;
165
+ }
166
+
104
167
  export function isHttpUpstream(u: Upstream): u is HttpUpstream {
105
168
  return "url" in u;
106
169
  }
package/src/email.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Email notices over SMTP. Notify only, on purpose: emails never contain
3
+ * approve links, because mail security scanners open links automatically and
4
+ * would approve calls by themselves. Approving happens on the push channel.
5
+ *
6
+ * Emails name the tool, server and reason, never arguments or results.
7
+ */
8
+ import nodemailer from "nodemailer";
9
+ import type { ApprovalEvent, Notifier } from "./approvals.js";
10
+
11
+ export const EMAIL_EVENTS = ["pending", "blocked", "approved", "denied", "timeout", "undeliverable"] as const;
12
+ export type EmailEvent = (typeof EMAIL_EVENTS)[number];
13
+
14
+ export interface MailSender {
15
+ sendMail(mail: { from: string; to: string[]; subject: string; text: string }): Promise<unknown>;
16
+ }
17
+
18
+ export interface EmailOptions {
19
+ from: string;
20
+ to: string[];
21
+ /** Which events send an email. */
22
+ on: EmailEvent[];
23
+ /** SMTP settings, used unless `sender` is given. */
24
+ smtp?: { host: string; port: number; secure?: boolean; user?: string; pass?: string };
25
+ sender?: MailSender;
26
+ }
27
+
28
+ export class EmailNotifier implements Notifier {
29
+ readonly name = "email";
30
+ private readonly sender: MailSender;
31
+
32
+ constructor(private readonly opts: EmailOptions) {
33
+ if (opts.sender) {
34
+ this.sender = opts.sender;
35
+ } else if (opts.smtp) {
36
+ const { host, port, secure, user, pass } = opts.smtp;
37
+ this.sender = nodemailer.createTransport({
38
+ host,
39
+ port,
40
+ secure: secure ?? port === 465,
41
+ ...(user !== undefined ? { auth: { user, pass } } : {}),
42
+ });
43
+ } else {
44
+ throw new Error("EmailNotifier needs smtp settings or a sender");
45
+ }
46
+ }
47
+
48
+ async notify(event: ApprovalEvent): Promise<void> {
49
+ const kind: EmailEvent = event.kind === "resolved" ? event.result.outcome : event.kind;
50
+ if (!this.opts.on.includes(kind)) return;
51
+ const { subject, text } = this.compose(event);
52
+ await this.sender.sendMail({ from: this.opts.from, to: this.opts.to, subject, text });
53
+ }
54
+
55
+ private compose(event: ApprovalEvent): { subject: string; text: string } {
56
+ const footer = "\n\n-- \nSent by ADTL (adtl-gateway). This email never contains approve links; approve on your push channel.";
57
+ if (event.kind === "blocked") {
58
+ return {
59
+ subject: `ADTL blocked ${event.server}.${event.tool}`,
60
+ text: `ADTL blocked a call to ${event.server}.${event.tool}.\n\nReason: ${event.reason}\nRule: ${event.matched.join(", ") || "default"}\nSession: ${event.sessionId}${footer}`,
61
+ };
62
+ }
63
+ const r = event.request;
64
+ const subject = `${r.server}.${r.tool}`;
65
+ if (event.kind === "pending") {
66
+ return {
67
+ subject: `ADTL: approval needed for ${subject}`,
68
+ text:
69
+ `An agent wants to call ${subject} and ADTL needs your approval.\n\nReason: ${r.reason}\nRule: ${r.matched.join(", ") || "default"}\n` +
70
+ `Session: ${r.sessionId}\nExpires: ${r.expiresAt.toISOString()} (no answer means deny)${footer}`,
71
+ };
72
+ }
73
+ const res = event.result;
74
+ return {
75
+ subject: `ADTL: ${subject} ${res.outcome}`,
76
+ text: `The approval request for ${subject} was ${res.outcome}${res.channel ? ` via ${res.channel}` : ""} after ${Math.round(res.waitedMs / 1000)}s.\n\nReason it was asked: ${r.reason}\nSession: ${r.sessionId}${footer}`,
77
+ };
78
+ }
79
+ }
package/src/gateway.ts CHANGED
@@ -7,8 +7,11 @@
7
7
  * interceptors (adtl.label) → agent
8
8
  *
9
9
  * When a call is stopped, the agent receives a normal tool result with
10
- * `isError: true` explaining why, so the model can tell the user. In v0.1 an
11
- * "ask" decision is treated as a block; approvals arrive in v0.2.
10
+ * `isError: true` explaining why, so the model can tell the user.
11
+ *
12
+ * An "ask" decision holds the call and asks a human through the approval
13
+ * broker (e.g. a phone notification). Approved calls run; denied, unanswered
14
+ * or undeliverable ones are stopped. Without an approval channel, "ask" blocks.
12
15
  *
13
16
  * In audit mode (the invoker runs every interceptor with `mode: "audit"`)
14
17
  * nothing is stopped or changed: calls the policy would stop are forwarded,
@@ -24,11 +27,12 @@ import {
24
27
  type Tool,
25
28
  } from "@modelcontextprotocol/sdk/types.js";
26
29
  import type { InvocationContext } from "@adtl/interceptors";
30
+ import type { ApprovalBroker, ApprovalResult } from "./approvals.js";
27
31
  import type { AuditEntry, AuditSink } from "./audit.js";
28
32
  import { TOOL_SEPARATOR } from "./config.js";
29
33
  import type { Blocked, ChainOutcome, InterceptorInvoker } from "./invoker.js";
30
34
 
31
- export const GATEWAY_VERSION = "0.1.1";
35
+ export const GATEWAY_VERSION = "0.2.0";
32
36
 
33
37
  /** `_meta` key on gateway-generated error results describing why a call was stopped. */
34
38
  export const GATEWAY_DECISION_KEY = "adtl/gateway";
@@ -56,8 +60,18 @@ export interface AdtlGatewayOptions {
56
60
  sessionId?: string;
57
61
  audit?: AuditSink;
58
62
  logger?: GatewayLogger;
63
+ /** Asks humans about "ask" decisions and sends notices. */
64
+ approvals?: ApprovalBroker;
65
+ }
66
+
67
+ export interface CallOptions {
68
+ /** Report progress to the client while a call waits for approval. */
69
+ progress?: (progress: number, message: string) => Promise<void>;
59
70
  }
60
71
 
72
+ /** How often a waiting call reports progress, so clients that reset their timeout on progress keep waiting. */
73
+ const PROGRESS_INTERVAL_MS = 10_000;
74
+
61
75
  function textError(text: string, meta: Record<string, unknown>): CallToolResult {
62
76
  return { content: [{ type: "text", text }], isError: true, _meta: { [GATEWAY_DECISION_KEY]: meta } };
63
77
  }
@@ -66,12 +80,24 @@ function blockedMessage(b: Blocked): string {
66
80
  if (b.decision === "ask") {
67
81
  return (
68
82
  `ADTL stopped this call because it needs a human's approval: ${b.reason}. ` +
69
- `This version of ADTL cannot ask for approval yet, so the call was not run. Tell the user what you were trying to do.`
83
+ `No approval channel is configured, so the call was not run. Tell the user what you were trying to do.`
70
84
  );
71
85
  }
72
86
  return `ADTL blocked this call: ${b.reason}. The call was not run.`;
73
87
  }
74
88
 
89
+ function approvalMessage(b: Blocked, a: ApprovalResult): string {
90
+ const why = `ADTL asked a human to approve this call because: ${b.reason}.`;
91
+ switch (a.outcome) {
92
+ case "denied":
93
+ return `${why} The request was denied, so the call was not run. Do not retry it; tell the user what you were trying to do.`;
94
+ case "timeout":
95
+ return `${why} Nobody answered in time, so the call was not run. Tell the user, who can approve it if you try again.`;
96
+ default:
97
+ return `${why} ADTL could not reach anyone to approve it, so the call was not run. Tell the user what you were trying to do.`;
98
+ }
99
+ }
100
+
75
101
  function infoOf(outcome: ChainOutcome, interceptor: string): Record<string, unknown> {
76
102
  return outcome.results.find((r) => r.interceptor === interceptor)?.info ?? {};
77
103
  }
@@ -82,6 +108,7 @@ export class AdtlGateway {
82
108
  private readonly invoker: InterceptorInvoker;
83
109
  private readonly audit: AuditSink | undefined;
84
110
  private readonly log: GatewayLogger;
111
+ private readonly approvals: ApprovalBroker | undefined;
85
112
  private routes = new Map<string, Route>();
86
113
 
87
114
  constructor(opts: AdtlGatewayOptions) {
@@ -90,6 +117,7 @@ export class AdtlGateway {
90
117
  this.sessionId = opts.sessionId ?? randomUUID();
91
118
  this.audit = opts.audit;
92
119
  this.log = opts.logger ?? stderrLogger;
120
+ this.approvals = opts.approvals;
93
121
  }
94
122
 
95
123
  /** The downstream MCP server the agent connects to. */
@@ -104,7 +132,15 @@ export class AdtlGateway {
104
132
  },
105
133
  );
106
134
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: await this.listTools() }));
107
- server.setRequestHandler(CallToolRequestSchema, async (req) => this.callTool(req.params.name, req.params.arguments ?? {}));
135
+ server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
136
+ const progressToken = req.params._meta?.progressToken;
137
+ const progress =
138
+ progressToken === undefined
139
+ ? undefined
140
+ : (progress: number, message: string) =>
141
+ extra.sendNotification({ method: "notifications/progress", params: { progressToken, progress, message } });
142
+ return this.callTool(req.params.name, req.params.arguments ?? {}, progress ? { progress } : {});
143
+ });
108
144
  return server;
109
145
  }
110
146
 
@@ -131,7 +167,7 @@ export class AdtlGateway {
131
167
  return tools;
132
168
  }
133
169
 
134
- async callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult> {
170
+ async callTool(name: string, args: Record<string, unknown>, opts: CallOptions = {}): Promise<CallToolResult> {
135
171
  const started = performance.now();
136
172
  let route = this.routes.get(name);
137
173
  if (!route) {
@@ -158,13 +194,39 @@ export class AdtlGateway {
158
194
  const requestPayload = { method: "tools/call", params: { name: route.tool, arguments: args } };
159
195
  const req = await this.invoker.runRequest({ event: "tools/call", payload: requestPayload, config, context });
160
196
  const policy = infoOf(req, "adtl.policy");
197
+ let approval: ApprovalResult | undefined;
161
198
  if (req.blocked) {
162
- finish({ outcome: "blocked", decision: req.blocked.decision, reason: req.blocked.reason, matched: policy.matched, callHash: policy.callHash, sessionLabel: policy.sessionLabel, warnings: req.warnings });
163
- this.log.info(`${req.blocked.decision} ${name}: ${req.blocked.reason}`);
164
- return textError(blockedMessage(req.blocked), { ...req.blocked, callHash: policy.callHash });
199
+ const b = req.blocked;
200
+ const stopped = { decision: b.decision, reason: b.reason, matched: policy.matched, callHash: policy.callHash, sessionLabel: policy.sessionLabel };
201
+ const matched = Array.isArray(policy.matched) ? (policy.matched as string[]) : [];
202
+ if (b.decision === "ask" && this.approvals?.canApprove && typeof policy.callHash === "string") {
203
+ this.log.info(`ask ${name}: waiting for approval (${b.reason})`);
204
+ approval = await this.waitForApproval(opts, () =>
205
+ this.approvals!.request({
206
+ sessionId: this.sessionId,
207
+ server: route.upstream,
208
+ tool: route.tool,
209
+ callHash: policy.callHash as string,
210
+ reason: b.reason,
211
+ matched,
212
+ ...(policy.sessionLabel !== undefined ? { sessionLabel: policy.sessionLabel } : {}),
213
+ }),
214
+ );
215
+ this.log.info(`ask ${name}: ${approval.outcome}`);
216
+ if (approval.outcome !== "approved") {
217
+ finish({ outcome: "blocked", ...stopped, approval, warnings: req.warnings });
218
+ return textError(approvalMessage(b, approval), { ...b, callHash: policy.callHash, approval });
219
+ }
220
+ } else {
221
+ finish({ outcome: "blocked", ...stopped, warnings: req.warnings });
222
+ this.log.info(`${b.decision} ${name}: ${b.reason}`);
223
+ this.approvals?.blocked({ sessionId: this.sessionId, server: route.upstream, tool: route.tool, reason: b.reason, matched });
224
+ return textError(blockedMessage(b), { ...b, callHash: policy.callHash });
225
+ }
165
226
  }
166
227
  const forwarded = (req.payload as typeof requestPayload).params;
167
228
  const decision = policy["adtl/decision"] as string | undefined;
229
+ const approved = approval ? { approval } : {};
168
230
  const audit = this.invoker.modeOverride === "audit";
169
231
  // Only set in audit mode, for calls the policy would have stopped.
170
232
  const wouldStop =
@@ -179,7 +241,7 @@ export class AdtlGateway {
179
241
  result = (await client.callTool({ name: forwarded.name, arguments: forwarded.arguments })) as CallToolResult;
180
242
  } catch (err) {
181
243
  const reason = (err as Error).message;
182
- finish({ outcome: "upstream_error", ...(decision ? { decision } : {}), reason, callHash: policy.callHash, ...(audit ? { mode: "audit" as const } : {}), warnings: req.warnings });
244
+ finish({ outcome: "upstream_error", ...(decision ? { decision } : {}), ...approved, reason, callHash: policy.callHash, ...(audit ? { mode: "audit" as const } : {}), warnings: req.warnings });
183
245
  return textError(`The ${route.upstream} server returned an error: ${reason}`, { reason: "upstream_error" });
184
246
  }
185
247
 
@@ -202,6 +264,7 @@ export class AdtlGateway {
202
264
  // Missing only if adtl.policy failed and was allowed to (audit mode); see warnings.
203
265
  ...(decision ? { decision } : {}),
204
266
  ...(wouldStop ?? {}),
267
+ ...approved,
205
268
  ...(audit ? { mode: "audit" as const } : {}),
206
269
  callHash: policy.callHash,
207
270
  label: label.label,
@@ -210,4 +273,18 @@ export class AdtlGateway {
210
273
  });
211
274
  return (res.payload as { result: CallToolResult }).result;
212
275
  }
276
+
277
+ /** Run an approval request, reporting progress to the client while it waits. */
278
+ private async waitForApproval(opts: CallOptions, ask: () => Promise<ApprovalResult>): Promise<ApprovalResult> {
279
+ if (!opts.progress) return ask();
280
+ let n = 0;
281
+ const report = () => void opts.progress!(++n, "Waiting for a human to approve this call").catch(() => undefined);
282
+ report();
283
+ const timer = setInterval(report, PROGRESS_INTERVAL_MS);
284
+ try {
285
+ return await ask();
286
+ } finally {
287
+ clearInterval(timer);
288
+ }
289
+ }
213
290
  }
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from "./config.js";
2
2
  export * from "./invoker.js";
3
3
  export * from "./audit.js";
4
+ export * from "./approvals.js";
5
+ export * from "./ntfy.js";
6
+ export * from "./email.js";
4
7
  export * from "./gateway.js";
5
8
  export * from "./startup.js";