@clawmasons/proxy 0.1.0 → 0.1.1

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.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Parse a .env file into a key-value record.
3
+ *
4
+ * Handles:
5
+ * - KEY=VALUE lines
6
+ * - Blank lines and comments (lines starting with #)
7
+ * - Single-quoted, double-quoted, and unquoted values
8
+ * - Inline comments after unquoted values
9
+ */
10
+ export declare function loadEnvFile(filePath: string): Record<string, string>;
11
+ /**
12
+ * Resolve `${VAR}` references in an env record.
13
+ *
14
+ * Resolution order:
15
+ * 1. process.env (allows runtime overrides)
16
+ * 2. Loaded .env values
17
+ *
18
+ * Unresolved references are left as empty strings.
19
+ */
20
+ export declare function resolveEnvVars(env: Record<string, string>, loaded: Record<string, string>): Record<string, string>;
21
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwCpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB"}
@@ -0,0 +1,63 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ /**
3
+ * Parse a .env file into a key-value record.
4
+ *
5
+ * Handles:
6
+ * - KEY=VALUE lines
7
+ * - Blank lines and comments (lines starting with #)
8
+ * - Single-quoted, double-quoted, and unquoted values
9
+ * - Inline comments after unquoted values
10
+ */
11
+ export function loadEnvFile(filePath) {
12
+ if (!existsSync(filePath)) {
13
+ return {};
14
+ }
15
+ const content = readFileSync(filePath, "utf-8");
16
+ const result = {};
17
+ for (const line of content.split("\n")) {
18
+ const trimmed = line.trim();
19
+ // Skip blank lines and comments
20
+ if (!trimmed || trimmed.startsWith("#"))
21
+ continue;
22
+ const eqIndex = trimmed.indexOf("=");
23
+ if (eqIndex === -1)
24
+ continue;
25
+ const key = trimmed.slice(0, eqIndex).trim();
26
+ if (!key)
27
+ continue;
28
+ let value = trimmed.slice(eqIndex + 1).trim();
29
+ // Handle quoted values
30
+ if ((value.startsWith('"') && value.endsWith('"')) ||
31
+ (value.startsWith("'") && value.endsWith("'"))) {
32
+ value = value.slice(1, -1);
33
+ }
34
+ else {
35
+ // Strip inline comments for unquoted values
36
+ const commentIndex = value.indexOf(" #");
37
+ if (commentIndex !== -1) {
38
+ value = value.slice(0, commentIndex).trim();
39
+ }
40
+ }
41
+ result[key] = value;
42
+ }
43
+ return result;
44
+ }
45
+ /**
46
+ * Resolve `${VAR}` references in an env record.
47
+ *
48
+ * Resolution order:
49
+ * 1. process.env (allows runtime overrides)
50
+ * 2. Loaded .env values
51
+ *
52
+ * Unresolved references are left as empty strings.
53
+ */
54
+ export function resolveEnvVars(env, loaded) {
55
+ const result = {};
56
+ for (const [key, value] of Object.entries(env)) {
57
+ result[key] = value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
58
+ return process.env[varName] ?? loaded[varName] ?? "";
59
+ });
60
+ }
61
+ return result;
62
+ }
63
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAE5B,gCAAgC;QAChC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,SAAS;QAE7B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAE9C,uBAAuB;QACvB,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;YACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,4CAA4C;YAC5C,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACxB,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAC5B,GAA2B,EAC3B,MAA8B;IAE9B,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,MAAM,EAAE,OAAe,EAAE,EAAE;YACxE,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import Database from "better-sqlite3";
2
+ export interface AuditLogEntry {
3
+ id: string;
4
+ agent_name: string;
5
+ role_name: string;
6
+ app_name: string;
7
+ tool_name: string;
8
+ arguments?: string;
9
+ result?: string;
10
+ status: "success" | "error" | "denied" | "timeout" | "dropped";
11
+ duration_ms?: number;
12
+ timestamp: string;
13
+ session_type?: string;
14
+ acp_client?: string;
15
+ }
16
+ export interface ApprovalRequest {
17
+ id: string;
18
+ agent_name: string;
19
+ role_name: string;
20
+ app_name: string;
21
+ tool_name: string;
22
+ arguments?: string;
23
+ status: "pending" | "approved" | "denied";
24
+ requested_at: string;
25
+ resolved_at?: string;
26
+ resolved_by?: string;
27
+ ttl_seconds: number;
28
+ }
29
+ export interface AuditLogFilters {
30
+ agent_name?: string;
31
+ app_name?: string;
32
+ tool_name?: string;
33
+ status?: string;
34
+ session_type?: string;
35
+ limit?: number;
36
+ }
37
+ export declare function openDatabase(dbPath?: string): Database.Database;
38
+ export declare function insertAuditLog(db: Database.Database, entry: AuditLogEntry): void;
39
+ export declare function queryAuditLog(db: Database.Database, filters?: AuditLogFilters): AuditLogEntry[];
40
+ export declare function createApprovalRequest(db: Database.Database, req: ApprovalRequest): void;
41
+ export declare function getApprovalRequest(db: Database.Database, id: string): ApprovalRequest | undefined;
42
+ export declare function updateApprovalStatus(db: Database.Database, id: string, status: "approved" | "denied", resolvedBy?: string): void;
43
+ export declare function generateId(): string;
44
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAQtC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsCD,wBAAgB,YAAY,CAAC,MAAM,GAAE,MAAwB,GAAG,QAAQ,CAAC,QAAQ,CAgBhF;AAID,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAmBhF;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,CAAC,EAAE,eAAe,GACxB,aAAa,EAAE,CAqCjB;AAID,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,IAAI,CAWvF;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,EAAE,EAAE,MAAM,GACT,eAAe,GAAG,SAAS,CAG7B;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,UAAU,GAAG,QAAQ,EAC7B,UAAU,CAAC,EAAE,MAAM,GAClB,IAAI,CAYN;AAID,wBAAgB,UAAU,IAAI,MAAM,CAEnC"}
package/dist/db.js ADDED
@@ -0,0 +1,146 @@
1
+ import Database from "better-sqlite3";
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdirSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ // ── Schema ─────────────────────────────────────────────────────────────
7
+ const CREATE_AUDIT_LOG = `
8
+ CREATE TABLE IF NOT EXISTS audit_log (
9
+ id TEXT PRIMARY KEY,
10
+ agent_name TEXT NOT NULL,
11
+ role_name TEXT NOT NULL,
12
+ app_name TEXT NOT NULL,
13
+ tool_name TEXT NOT NULL,
14
+ arguments TEXT,
15
+ result TEXT,
16
+ status TEXT NOT NULL,
17
+ duration_ms INTEGER,
18
+ timestamp TEXT NOT NULL
19
+ )`;
20
+ const CREATE_APPROVAL_REQUESTS = `
21
+ CREATE TABLE IF NOT EXISTS approval_requests (
22
+ id TEXT PRIMARY KEY,
23
+ agent_name TEXT NOT NULL,
24
+ role_name TEXT NOT NULL,
25
+ app_name TEXT NOT NULL,
26
+ tool_name TEXT NOT NULL,
27
+ arguments TEXT,
28
+ status TEXT NOT NULL DEFAULT 'pending',
29
+ requested_at TEXT NOT NULL,
30
+ resolved_at TEXT,
31
+ resolved_by TEXT,
32
+ ttl_seconds INTEGER NOT NULL DEFAULT 300
33
+ )`;
34
+ // ── Database ───────────────────────────────────────────────────────────
35
+ const DEFAULT_DB_PATH = process.env.CHAPTER_DB_PATH
36
+ ?? join(homedir(), ".chapter", "data", "chapter.db");
37
+ export function openDatabase(dbPath = DEFAULT_DB_PATH) {
38
+ if (dbPath !== ":memory:") {
39
+ mkdirSync(dirname(dbPath), { recursive: true });
40
+ }
41
+ const db = new Database(dbPath);
42
+ db.pragma("journal_mode = WAL");
43
+ db.exec(CREATE_AUDIT_LOG);
44
+ db.exec(CREATE_APPROVAL_REQUESTS);
45
+ // ── Schema Migrations (idempotent) ──────────────────────────────────
46
+ // Add ACP session columns to audit_log (nullable for backward compat)
47
+ try {
48
+ db.exec("ALTER TABLE audit_log ADD COLUMN session_type TEXT");
49
+ }
50
+ catch { /* column already exists */ }
51
+ try {
52
+ db.exec("ALTER TABLE audit_log ADD COLUMN acp_client TEXT");
53
+ }
54
+ catch { /* column already exists */ }
55
+ return db;
56
+ }
57
+ // ── Audit Log ──────────────────────────────────────────────────────────
58
+ export function insertAuditLog(db, entry) {
59
+ const stmt = db.prepare(`
60
+ INSERT INTO audit_log (id, agent_name, role_name, app_name, tool_name, arguments, result, status, duration_ms, timestamp, session_type, acp_client)
61
+ VALUES (@id, @agent_name, @role_name, @app_name, @tool_name, @arguments, @result, @status, @duration_ms, @timestamp, @session_type, @acp_client)
62
+ `);
63
+ stmt.run({
64
+ id: entry.id,
65
+ agent_name: entry.agent_name,
66
+ role_name: entry.role_name,
67
+ app_name: entry.app_name,
68
+ tool_name: entry.tool_name,
69
+ arguments: entry.arguments ?? null,
70
+ result: entry.result ?? null,
71
+ status: entry.status,
72
+ duration_ms: entry.duration_ms ?? null,
73
+ timestamp: entry.timestamp,
74
+ session_type: entry.session_type ?? null,
75
+ acp_client: entry.acp_client ?? null,
76
+ });
77
+ }
78
+ export function queryAuditLog(db, filters) {
79
+ const conditions = [];
80
+ const params = {};
81
+ if (filters?.agent_name) {
82
+ conditions.push("agent_name = @agent_name");
83
+ params.agent_name = filters.agent_name;
84
+ }
85
+ if (filters?.app_name) {
86
+ conditions.push("app_name = @app_name");
87
+ params.app_name = filters.app_name;
88
+ }
89
+ if (filters?.tool_name) {
90
+ conditions.push("tool_name = @tool_name");
91
+ params.tool_name = filters.tool_name;
92
+ }
93
+ if (filters?.status) {
94
+ conditions.push("status = @status");
95
+ params.status = filters.status;
96
+ }
97
+ if (filters?.session_type) {
98
+ conditions.push("session_type = @session_type");
99
+ params.session_type = filters.session_type;
100
+ }
101
+ let sql = "SELECT * FROM audit_log";
102
+ if (conditions.length > 0) {
103
+ sql += " WHERE " + conditions.join(" AND ");
104
+ }
105
+ sql += " ORDER BY timestamp DESC";
106
+ if (filters?.limit) {
107
+ sql += " LIMIT @limit";
108
+ params.limit = filters.limit;
109
+ }
110
+ return db.prepare(sql).all(params);
111
+ }
112
+ // ── Approval Requests ──────────────────────────────────────────────────
113
+ export function createApprovalRequest(db, req) {
114
+ const stmt = db.prepare(`
115
+ INSERT INTO approval_requests (id, agent_name, role_name, app_name, tool_name, arguments, status, requested_at, resolved_at, resolved_by, ttl_seconds)
116
+ VALUES (@id, @agent_name, @role_name, @app_name, @tool_name, @arguments, @status, @requested_at, @resolved_at, @resolved_by, @ttl_seconds)
117
+ `);
118
+ stmt.run({
119
+ ...req,
120
+ resolved_at: req.resolved_at ?? null,
121
+ resolved_by: req.resolved_by ?? null,
122
+ arguments: req.arguments ?? null,
123
+ });
124
+ }
125
+ export function getApprovalRequest(db, id) {
126
+ const stmt = db.prepare("SELECT * FROM approval_requests WHERE id = ?");
127
+ return stmt.get(id);
128
+ }
129
+ export function updateApprovalStatus(db, id, status, resolvedBy) {
130
+ const stmt = db.prepare(`
131
+ UPDATE approval_requests
132
+ SET status = @status, resolved_at = @resolved_at, resolved_by = @resolved_by
133
+ WHERE id = @id
134
+ `);
135
+ stmt.run({
136
+ id,
137
+ status,
138
+ resolved_at: new Date().toISOString(),
139
+ resolved_by: resolvedBy ?? null,
140
+ });
141
+ }
142
+ // ── Helpers ────────────────────────────────────────────────────────────
143
+ export function generateId() {
144
+ return randomUUID();
145
+ }
146
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ export type RiskLevel = "HIGH" | "MEDIUM" | "LOW";
3
+ export interface SessionEntry {
4
+ sessionId: string;
5
+ sessionToken: string;
6
+ agentId: string;
7
+ role: string;
8
+ connectedAt: string;
9
+ }
10
+ export declare class SessionStore {
11
+ /** session_id → SessionEntry */
12
+ private sessions;
13
+ /** sessionToken → session_id (secondary index for O(1) token lookup) */
14
+ private tokenIndex;
15
+ /** Risk level for this proxy instance (set at construction). */
16
+ readonly riskLevel: RiskLevel;
17
+ /** Number of agent connections established through this store. */
18
+ private agentConnectionCount;
19
+ constructor(riskLevel?: RiskLevel);
20
+ /**
21
+ * Returns true if the session is locked due to risk-based connection limits.
22
+ * HIGH and MEDIUM risk roles only allow one agent connection per proxy instance.
23
+ */
24
+ isLocked(): boolean;
25
+ create(agentId: string, role: string): SessionEntry;
26
+ getByToken(token: string): SessionEntry | undefined;
27
+ getById(sessionId: string): SessionEntry | undefined;
28
+ get size(): number;
29
+ get connectionCount(): number;
30
+ }
31
+ /**
32
+ * Handle POST /connect-agent requests.
33
+ *
34
+ * Authenticates using the proxy token (MCP_PROXY_TOKEN in Authorization header),
35
+ * creates a new session, and returns { sessionToken, sessionId }.
36
+ *
37
+ * For HIGH and MEDIUM risk roles, only one agent connection is allowed per
38
+ * proxy instance. Subsequent connection attempts are rejected with 403.
39
+ */
40
+ export declare function handleConnectAgent(req: IncomingMessage, res: ServerResponse, proxyToken: string, sessionStore: SessionStore, agentId?: string, role?: string): void;
41
+ //# sourceMappingURL=connect-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-agent.d.ts","sourceRoot":"","sources":["../../src/handlers/connect-agent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAElD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,qBAAa,YAAY;IACvB,gCAAgC;IAChC,OAAO,CAAC,QAAQ,CAAmC;IACnD,wEAAwE;IACxE,OAAO,CAAC,UAAU,CAA6B;IAC/C,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,kEAAkE;IAClE,OAAO,CAAC,oBAAoB,CAAK;gBAErB,SAAS,GAAE,SAAiB;IAIxC;;;OAGG;IACH,QAAQ,IAAI,OAAO;IAKnB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY;IAgBnD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAMnD,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIpD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,eAAe,IAAI,MAAM,CAE5B;CACF;AAID;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,YAAY,EAC1B,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CA+CN"}
@@ -0,0 +1,101 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ // ── SessionStore ────────────────────────────────────────────────────────
3
+ export class SessionStore {
4
+ /** session_id → SessionEntry */
5
+ sessions = new Map();
6
+ /** sessionToken → session_id (secondary index for O(1) token lookup) */
7
+ tokenIndex = new Map();
8
+ /** Risk level for this proxy instance (set at construction). */
9
+ riskLevel;
10
+ /** Number of agent connections established through this store. */
11
+ agentConnectionCount = 0;
12
+ constructor(riskLevel = "LOW") {
13
+ this.riskLevel = riskLevel;
14
+ }
15
+ /**
16
+ * Returns true if the session is locked due to risk-based connection limits.
17
+ * HIGH and MEDIUM risk roles only allow one agent connection per proxy instance.
18
+ */
19
+ isLocked() {
20
+ if (this.riskLevel === "LOW")
21
+ return false;
22
+ return this.agentConnectionCount > 0;
23
+ }
24
+ create(agentId, role) {
25
+ const sessionId = randomUUID();
26
+ const sessionToken = randomBytes(32).toString("hex");
27
+ const entry = {
28
+ sessionId,
29
+ sessionToken,
30
+ agentId,
31
+ role,
32
+ connectedAt: new Date().toISOString(),
33
+ };
34
+ this.sessions.set(sessionId, entry);
35
+ this.tokenIndex.set(sessionToken, sessionId);
36
+ this.agentConnectionCount++;
37
+ return entry;
38
+ }
39
+ getByToken(token) {
40
+ const sessionId = this.tokenIndex.get(token);
41
+ if (!sessionId)
42
+ return undefined;
43
+ return this.sessions.get(sessionId);
44
+ }
45
+ getById(sessionId) {
46
+ return this.sessions.get(sessionId);
47
+ }
48
+ get size() {
49
+ return this.sessions.size;
50
+ }
51
+ get connectionCount() {
52
+ return this.agentConnectionCount;
53
+ }
54
+ }
55
+ // ── Handler ────────────────────────────────────────────────────────────
56
+ /**
57
+ * Handle POST /connect-agent requests.
58
+ *
59
+ * Authenticates using the proxy token (MCP_PROXY_TOKEN in Authorization header),
60
+ * creates a new session, and returns { sessionToken, sessionId }.
61
+ *
62
+ * For HIGH and MEDIUM risk roles, only one agent connection is allowed per
63
+ * proxy instance. Subsequent connection attempts are rejected with 403.
64
+ */
65
+ export function handleConnectAgent(req, res, proxyToken, sessionStore, agentId, role) {
66
+ // Method check
67
+ if (req.method !== "POST") {
68
+ res.writeHead(405, { "Content-Type": "application/json" });
69
+ res.end(JSON.stringify({ error: "Method not allowed" }));
70
+ return;
71
+ }
72
+ // Auth check
73
+ const auth = req.headers.authorization;
74
+ if (!auth) {
75
+ res.writeHead(401, { "Content-Type": "application/json" });
76
+ res.end(JSON.stringify({ error: "Unauthorized" }));
77
+ return;
78
+ }
79
+ const [scheme, token] = auth.split(" ", 2);
80
+ if (scheme !== "Bearer" || token !== proxyToken) {
81
+ res.writeHead(401, { "Content-Type": "application/json" });
82
+ res.end(JSON.stringify({ error: "Unauthorized" }));
83
+ return;
84
+ }
85
+ // Risk-based connection limit check
86
+ if (sessionStore.isLocked()) {
87
+ res.writeHead(403, { "Content-Type": "application/json" });
88
+ res.end(JSON.stringify({
89
+ error: `Session locked — ${sessionStore.riskLevel} risk role does not allow additional agent connections`,
90
+ }));
91
+ return;
92
+ }
93
+ // Create session
94
+ const session = sessionStore.create(agentId ?? "unknown", role ?? "unknown");
95
+ res.writeHead(200, { "Content-Type": "application/json" });
96
+ res.end(JSON.stringify({
97
+ sessionToken: session.sessionToken,
98
+ sessionId: session.sessionId,
99
+ }));
100
+ }
101
+ //# sourceMappingURL=connect-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-agent.js","sourceRoot":"","sources":["../../src/handlers/connect-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAetD,2EAA2E;AAE3E,MAAM,OAAO,YAAY;IACvB,gCAAgC;IACxB,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IACnD,wEAAwE;IAChE,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,gEAAgE;IACvD,SAAS,CAAY;IAC9B,kEAAkE;IAC1D,oBAAoB,GAAG,CAAC,CAAC;IAEjC,YAAY,YAAuB,KAAK;QACtC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,QAAQ;QACN,IAAI,IAAI,CAAC,SAAS,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC3C,OAAO,IAAI,CAAC,oBAAoB,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,OAAe,EAAE,IAAY;QAClC,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;QAC/B,MAAM,YAAY,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrD,MAAM,KAAK,GAAiB;YAC1B,SAAS;YACT,YAAY;YACZ,OAAO;YACP,IAAI;YACJ,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACtC,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,UAAU,CAAC,KAAa;QACtB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QACjC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,CAAC,SAAiB;QACvB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,0EAA0E;AAE1E;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAoB,EACpB,GAAmB,EACnB,UAAkB,EAClB,YAA0B,EAC1B,OAAgB,EAChB,IAAa;IAEb,eAAe;IACf,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IAED,aAAa;IACb,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IAED,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,QAAQ,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QAChD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IAED,oCAAoC;IACpC,IAAI,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC;QAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,EAAE,oBAAoB,YAAY,CAAC,SAAS,wDAAwD;SAC1G,CAAC,CACH,CAAC;QACF,OAAO;IACT,CAAC;IAED,iBAAiB;IACjB,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CACjC,OAAO,IAAI,SAAS,EACpB,IAAI,IAAI,SAAS,CAClB,CAAC;IAEF,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;QACb,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import { WebSocket } from "ws";
3
+ import type { SessionStore } from "./connect-agent.js";
4
+ export interface CredentialRelayConfig {
5
+ /** Token the credential service uses to authenticate its WebSocket connection. */
6
+ credentialProxyToken: string;
7
+ /** Timeout for credential requests in milliseconds. Default: 30000. */
8
+ requestTimeoutMs?: number;
9
+ }
10
+ export interface CredentialToolResult {
11
+ key: string;
12
+ value?: string;
13
+ error?: string;
14
+ source?: string;
15
+ }
16
+ /**
17
+ * Manages the WebSocket connection from the credential service and
18
+ * handles credential_request tool calls from agents.
19
+ */
20
+ export declare class CredentialRelay {
21
+ private readonly credentialProxyToken;
22
+ private readonly requestTimeoutMs;
23
+ private credentialServiceWs;
24
+ private pendingRequests;
25
+ private wss;
26
+ constructor(config: CredentialRelayConfig);
27
+ /**
28
+ * Handle an HTTP upgrade request for the /ws/credentials endpoint.
29
+ * Authenticates the credential service and accepts the WebSocket connection.
30
+ */
31
+ handleUpgrade(req: IncomingMessage, socket: import("node:stream").Duplex, head: Buffer): void;
32
+ /**
33
+ * Accept a credential service WebSocket connection.
34
+ * If a previous connection exists, it is closed.
35
+ */
36
+ acceptCredentialService(ws: WebSocket): void;
37
+ /**
38
+ * Handle a credential_request tool call from an agent.
39
+ *
40
+ * Validates the session token, forwards the request to the credential service
41
+ * over WebSocket, and returns the response.
42
+ */
43
+ handleCredentialRequest(sessionStore: SessionStore, key: string, sessionToken: string, declaredCredentials?: string[]): Promise<CredentialToolResult>;
44
+ /** Whether the credential service is currently connected. */
45
+ get isCredentialServiceConnected(): boolean;
46
+ /** Number of in-flight credential requests. */
47
+ get pendingRequestCount(): number;
48
+ /** Close the WebSocket server and any active connection. */
49
+ close(): void;
50
+ private handleResponse;
51
+ }
52
+ //# sourceMappingURL=credential-relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-relay.d.ts","sourceRoot":"","sources":["../../src/handlers/credential-relay.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAmB,SAAS,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,MAAM,WAAW,qBAAqB;IACpC,kFAAkF;IAClF,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,mBAAmB,CAA0B;IACrD,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,GAAG,CAAkB;gBAEjB,MAAM,EAAE,qBAAqB;IAMzC;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAqB7F;;;OAGG;IACH,uBAAuB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IA0B5C;;;;;OAKG;IACG,uBAAuB,CAC3B,YAAY,EAAE,YAAY,EAC1B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAC7B,OAAO,CAAC,oBAAoB,CAAC;IAgDhC,6DAA6D;IAC7D,IAAI,4BAA4B,IAAI,OAAO,CAE1C;IAED,+CAA+C;IAC/C,IAAI,mBAAmB,IAAI,MAAM,CAEhC;IAED,4DAA4D;IAC5D,KAAK,IAAI,IAAI;IAkBb,OAAO,CAAC,cAAc;CA+BvB"}
@@ -0,0 +1,169 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { WebSocketServer, WebSocket } from "ws";
3
+ // ── CredentialRelay ────────────────────────────────────────────────────
4
+ /**
5
+ * Manages the WebSocket connection from the credential service and
6
+ * handles credential_request tool calls from agents.
7
+ */
8
+ export class CredentialRelay {
9
+ credentialProxyToken;
10
+ requestTimeoutMs;
11
+ credentialServiceWs = null;
12
+ pendingRequests = new Map();
13
+ wss;
14
+ constructor(config) {
15
+ this.credentialProxyToken = config.credentialProxyToken;
16
+ this.requestTimeoutMs = config.requestTimeoutMs ?? 30_000;
17
+ this.wss = new WebSocketServer({ noServer: true });
18
+ }
19
+ /**
20
+ * Handle an HTTP upgrade request for the /ws/credentials endpoint.
21
+ * Authenticates the credential service and accepts the WebSocket connection.
22
+ */
23
+ handleUpgrade(req, socket, head) {
24
+ // Authenticate
25
+ const auth = req.headers.authorization;
26
+ if (!auth) {
27
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
28
+ socket.destroy();
29
+ return;
30
+ }
31
+ const [scheme, token] = auth.split(" ", 2);
32
+ if (scheme !== "Bearer" || token !== this.credentialProxyToken) {
33
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
34
+ socket.destroy();
35
+ return;
36
+ }
37
+ this.wss.handleUpgrade(req, socket, head, (ws) => {
38
+ this.acceptCredentialService(ws);
39
+ });
40
+ }
41
+ /**
42
+ * Accept a credential service WebSocket connection.
43
+ * If a previous connection exists, it is closed.
44
+ */
45
+ acceptCredentialService(ws) {
46
+ // Close previous connection if any
47
+ if (this.credentialServiceWs && this.credentialServiceWs.readyState === WebSocket.OPEN) {
48
+ this.credentialServiceWs.close(1000, "Replaced by new connection");
49
+ }
50
+ this.credentialServiceWs = ws;
51
+ ws.on("message", (data) => {
52
+ this.handleResponse(data);
53
+ });
54
+ ws.on("close", () => {
55
+ if (this.credentialServiceWs === ws) {
56
+ this.credentialServiceWs = null;
57
+ }
58
+ });
59
+ ws.on("error", () => {
60
+ // Error handler to prevent unhandled rejection
61
+ if (this.credentialServiceWs === ws) {
62
+ this.credentialServiceWs = null;
63
+ }
64
+ });
65
+ }
66
+ /**
67
+ * Handle a credential_request tool call from an agent.
68
+ *
69
+ * Validates the session token, forwards the request to the credential service
70
+ * over WebSocket, and returns the response.
71
+ */
72
+ async handleCredentialRequest(sessionStore, key, sessionToken, declaredCredentials) {
73
+ // Validate session token
74
+ const session = sessionStore.getByToken(sessionToken);
75
+ if (!session) {
76
+ return { key, error: "Invalid session token" };
77
+ }
78
+ // Check credential service is connected
79
+ if (!this.credentialServiceWs || this.credentialServiceWs.readyState !== WebSocket.OPEN) {
80
+ return { key, error: "Credential service not connected" };
81
+ }
82
+ // Forward request to credential service
83
+ const requestId = randomUUID();
84
+ const request = {
85
+ id: requestId,
86
+ key,
87
+ agentId: session.agentId,
88
+ role: session.role,
89
+ sessionId: session.sessionId,
90
+ declaredCredentials: declaredCredentials ?? [],
91
+ };
92
+ return new Promise((resolve) => {
93
+ const timer = setTimeout(() => {
94
+ this.pendingRequests.delete(requestId);
95
+ resolve({ key, error: "Credential request timed out" });
96
+ }, this.requestTimeoutMs);
97
+ this.pendingRequests.set(requestId, { resolve, timer });
98
+ const ws = this.credentialServiceWs;
99
+ if (!ws) {
100
+ clearTimeout(timer);
101
+ this.pendingRequests.delete(requestId);
102
+ resolve({ key, error: "Credential service disconnected" });
103
+ return;
104
+ }
105
+ ws.send(JSON.stringify(request), (err) => {
106
+ if (err) {
107
+ clearTimeout(timer);
108
+ this.pendingRequests.delete(requestId);
109
+ resolve({ key, error: `Failed to send request: ${err.message}` });
110
+ }
111
+ });
112
+ });
113
+ }
114
+ /** Whether the credential service is currently connected. */
115
+ get isCredentialServiceConnected() {
116
+ return this.credentialServiceWs !== null && this.credentialServiceWs.readyState === WebSocket.OPEN;
117
+ }
118
+ /** Number of in-flight credential requests. */
119
+ get pendingRequestCount() {
120
+ return this.pendingRequests.size;
121
+ }
122
+ /** Close the WebSocket server and any active connection. */
123
+ close() {
124
+ // Reject all pending requests
125
+ for (const [id, pending] of this.pendingRequests) {
126
+ clearTimeout(pending.timer);
127
+ pending.resolve({ key: "unknown", error: "Relay shutting down" });
128
+ this.pendingRequests.delete(id);
129
+ }
130
+ if (this.credentialServiceWs) {
131
+ this.credentialServiceWs.close(1000, "Proxy shutting down");
132
+ this.credentialServiceWs = null;
133
+ }
134
+ this.wss.close();
135
+ }
136
+ // ── Private ──────────────────────────────────────────────────────────
137
+ handleResponse(data) {
138
+ let parsed;
139
+ try {
140
+ parsed = JSON.parse(String(data));
141
+ }
142
+ catch {
143
+ return;
144
+ }
145
+ const id = parsed.id;
146
+ if (!id)
147
+ return;
148
+ const pending = this.pendingRequests.get(id);
149
+ if (!pending)
150
+ return;
151
+ clearTimeout(pending.timer);
152
+ this.pendingRequests.delete(id);
153
+ // Check if it's an error response
154
+ if ("error" in parsed) {
155
+ pending.resolve({
156
+ key: parsed.key ?? "unknown",
157
+ error: parsed.error,
158
+ });
159
+ }
160
+ else {
161
+ pending.resolve({
162
+ key: parsed.key ?? "unknown",
163
+ value: parsed.value,
164
+ source: parsed.source,
165
+ });
166
+ }
167
+ }
168
+ }
169
+ //# sourceMappingURL=credential-relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-relay.js","sourceRoot":"","sources":["../../src/handlers/credential-relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAwBhD,0EAA0E;AAE1E;;;GAGG;AACH,MAAM,OAAO,eAAe;IACT,oBAAoB,CAAS;IAC7B,gBAAgB,CAAS;IAClC,mBAAmB,GAAqB,IAAI,CAAC;IAC7C,eAAe,GAAG,IAAI,GAAG,EAA0B,CAAC;IACpD,GAAG,CAAkB;IAE7B,YAAY,MAA6B;QACvC,IAAI,CAAC,oBAAoB,GAAG,MAAM,CAAC,oBAAoB,CAAC;QACxD,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,IAAI,MAAM,CAAC;QAC1D,IAAI,CAAC,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,GAAoB,EAAE,MAAoC,EAAE,IAAY;QACpF,eAAe;QACf,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YAClD,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC3C,IAAI,MAAM,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC/D,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YAClD,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE;YAC/C,IAAI,CAAC,uBAAuB,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,uBAAuB,CAAC,EAAa;QACnC,mCAAmC;QACnC,IAAI,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,mBAAmB,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACvF,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,4BAA4B,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAC;QAE9B,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,IAAI,IAAI,CAAC,mBAAmB,KAAK,EAAE,EAAE,CAAC;gBACpC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAClC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,+CAA+C;YAC/C,IAAI,IAAI,CAAC,mBAAmB,KAAK,EAAE,EAAE,CAAC;gBACpC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAClC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,uBAAuB,CAC3B,YAA0B,EAC1B,GAAW,EACX,YAAoB,EACpB,mBAA8B;QAE9B,yBAAyB;QACzB,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;QACjD,CAAC;QAED,wCAAwC;QACxC,IAAI,CAAC,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,mBAAmB,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACxF,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,kCAAkC,EAAE,CAAC;QAC5D,CAAC;QAED,wCAAwC;QACxC,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG;YACd,EAAE,EAAE,SAAS;YACb,GAAG;YACH,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,mBAAmB,EAAE,mBAAmB,IAAI,EAAE;SAC/C,CAAC;QAEF,OAAO,IAAI,OAAO,CAAuB,CAAC,OAAO,EAAE,EAAE;YACnD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACvC,OAAO,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;YAC1D,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAE1B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YAExD,MAAM,EAAE,GAAG,IAAI,CAAC,mBAAmB,CAAC;YACpC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACvC,OAAO,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;gBAC3D,OAAO;YACT,CAAC;YACD,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE;gBACvC,IAAI,GAAG,EAAE,CAAC;oBACR,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;oBACvC,OAAO,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,2BAA2B,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,6DAA6D;IAC7D,IAAI,4BAA4B;QAC9B,OAAO,IAAI,CAAC,mBAAmB,KAAK,IAAI,IAAI,IAAI,CAAC,mBAAmB,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,CAAC;IACrG,CAAC;IAED,+CAA+C;IAC/C,IAAI,mBAAmB;QACrB,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;IACnC,CAAC;IAED,4DAA4D;IAC5D,KAAK;QACH,8BAA8B;QAC9B,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACjD,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;YAClE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;YAC5D,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QAClC,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,wEAAwE;IAEhE,cAAc,CAAC,IAAa;QAClC,IAAI,MAA+B,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAA4B,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,MAAM,EAAE,GAAG,MAAM,CAAC,EAAwB,CAAC;QAC3C,IAAI,CAAC,EAAE;YAAE,OAAO;QAEhB,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAEhC,kCAAkC;QAClC,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;YACtB,OAAO,CAAC,OAAO,CAAC;gBACd,GAAG,EAAG,MAAM,CAAC,GAAc,IAAI,SAAS;gBACxC,KAAK,EAAE,MAAM,CAAC,KAAe;aAC9B,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,OAAO,CAAC;gBACd,GAAG,EAAG,MAAM,CAAC,GAAc,IAAI,SAAS;gBACxC,KAAK,EAAE,MAAM,CAAC,KAAe;gBAC7B,MAAM,EAAE,MAAM,CAAC,MAA4B;aAC5C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { HookContext } from "./audit.js";
3
+ export interface ApprovalOptions {
4
+ ttlSeconds?: number;
5
+ pollIntervalMs?: number;
6
+ }
7
+ /**
8
+ * Returns true if prefixedToolName matches any of the glob patterns.
9
+ * Patterns support `*` as a wildcard matching any sequence of characters.
10
+ */
11
+ export declare function matchesApprovalPattern(prefixedToolName: string, patterns: string[]): boolean;
12
+ /**
13
+ * Creates a pending approval request and polls until resolved or TTL expires.
14
+ * Returns "approved", "denied", or "timeout".
15
+ */
16
+ export declare function requestApproval(context: HookContext, db: Database.Database, options?: ApprovalOptions): Promise<"approved" | "denied" | "timeout">;
17
+ //# sourceMappingURL=approval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../src/hooks/approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAQ3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAMT;AAWD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,WAAW,EACpB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC,CA+D5C"}
@@ -0,0 +1,84 @@
1
+ import { generateId, createApprovalRequest, getApprovalRequest, updateApprovalStatus, } from "../db.js";
2
+ // ── Pattern Matching ────────────────────────────────────────────────────
3
+ /**
4
+ * Returns true if prefixedToolName matches any of the glob patterns.
5
+ * Patterns support `*` as a wildcard matching any sequence of characters.
6
+ */
7
+ export function matchesApprovalPattern(prefixedToolName, patterns) {
8
+ return patterns.some((pattern) => {
9
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
10
+ const regex = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
11
+ return regex.test(prefixedToolName);
12
+ });
13
+ }
14
+ // ── Approval Flow ───────────────────────────────────────────────────────
15
+ const DEFAULT_TTL_SECONDS = 300;
16
+ const DEFAULT_POLL_INTERVAL_MS = 1000;
17
+ function sleep(ms) {
18
+ return new Promise((resolve) => setTimeout(resolve, ms));
19
+ }
20
+ /**
21
+ * Creates a pending approval request and polls until resolved or TTL expires.
22
+ * Returns "approved", "denied", or "timeout".
23
+ */
24
+ export async function requestApproval(context, db, options) {
25
+ const ttlSeconds = options?.ttlSeconds ?? DEFAULT_TTL_SECONDS;
26
+ const pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
27
+ const id = generateId();
28
+ const req = {
29
+ id,
30
+ agent_name: context.agentName,
31
+ role_name: context.roleName,
32
+ app_name: context.appName,
33
+ tool_name: context.prefixedToolName,
34
+ arguments: context.arguments != null ? JSON.stringify(context.arguments) : undefined,
35
+ status: "pending",
36
+ requested_at: new Date().toISOString(),
37
+ ttl_seconds: ttlSeconds,
38
+ };
39
+ try {
40
+ createApprovalRequest(db, req);
41
+ }
42
+ catch (err) {
43
+ const message = err instanceof Error ? err.message : String(err);
44
+ console.error(`[chapter] approval request creation failed: ${message}`);
45
+ return "denied";
46
+ }
47
+ const deadline = Date.now() + ttlSeconds * 1000;
48
+ while (Date.now() < deadline) {
49
+ await sleep(pollIntervalMs);
50
+ try {
51
+ const current = getApprovalRequest(db, id);
52
+ if (!current) {
53
+ console.error(`[chapter] approval request ${id} disappeared from database`);
54
+ return "denied";
55
+ }
56
+ if (current.status === "approved") {
57
+ return "approved";
58
+ }
59
+ if (current.status === "denied") {
60
+ return "denied";
61
+ }
62
+ }
63
+ catch (err) {
64
+ const message = err instanceof Error ? err.message : String(err);
65
+ console.error(`[chapter] approval request poll failed: ${message}`);
66
+ return "denied";
67
+ }
68
+ }
69
+ // TTL expired — auto-deny
70
+ try {
71
+ // Check one final time to avoid race with external approval
72
+ const final = getApprovalRequest(db, id);
73
+ if (final && final.status !== "pending") {
74
+ return final.status;
75
+ }
76
+ updateApprovalStatus(db, id, "denied", "auto-timeout");
77
+ }
78
+ catch (err) {
79
+ const message = err instanceof Error ? err.message : String(err);
80
+ console.error(`[chapter] approval auto-timeout update failed: ${message}`);
81
+ }
82
+ return "timeout";
83
+ }
84
+ //# sourceMappingURL=approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/hooks/approval.ts"],"names":[],"mappings":"AACA,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAWlB,2EAA2E;AAE3E;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,gBAAwB,EACxB,QAAkB;IAElB,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;QAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QACnE,OAAO,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2EAA2E;AAE3E,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAEtC,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAoB,EACpB,EAAqB,EACrB,OAAyB;IAEzB,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,mBAAmB,CAAC;IAC9D,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,wBAAwB,CAAC;IAE3E,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,MAAM,GAAG,GAAoB;QAC3B,EAAE;QACF,UAAU,EAAE,OAAO,CAAC,SAAS;QAC7B,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,QAAQ,EAAE,OAAO,CAAC,OAAO;QACzB,SAAS,EAAE,OAAO,CAAC,gBAAgB;QACnC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;QACpF,MAAM,EAAE,SAAS;QACjB,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACtC,WAAW,EAAE,UAAU;KACxB,CAAC;IAEF,IAAI,CAAC;QACH,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,+CAA+C,OAAO,EAAE,CAAC,CAAC;QACxE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI,CAAC;IAEhD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;QAE5B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,kBAAkB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,4BAA4B,CAAC,CAAC;gBAC5E,OAAO,QAAQ,CAAC;YAClB,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAClC,OAAO,UAAU,CAAC;YACpB,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAChC,OAAO,QAAQ,CAAC;YAClB,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,KAAK,CAAC,2CAA2C,OAAO,EAAE,CAAC,CAAC;YACpE,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,IAAI,CAAC;QACH,4DAA4D;QAC5D,MAAM,KAAK,GAAG,kBAAkB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACxC,OAAO,KAAK,CAAC,MAA+B,CAAC;QAC/C,CAAC;QACD,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,kDAAkD,OAAO,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { AuditLogEntry } from "../db.js";
3
+ export interface HookContext {
4
+ agentName: string;
5
+ roleName: string;
6
+ appName: string;
7
+ toolName: string;
8
+ prefixedToolName: string;
9
+ arguments: unknown;
10
+ sessionType?: string;
11
+ acpClient?: string;
12
+ }
13
+ export interface AuditPreHookResult {
14
+ id: string;
15
+ startTime: number;
16
+ }
17
+ export declare function auditPreHook(context: HookContext): AuditPreHookResult;
18
+ export declare function auditPostHook(context: HookContext, preResult: AuditPreHookResult, callResult: unknown, status: AuditLogEntry["status"], db: Database.Database): void;
19
+ export interface DroppedServer {
20
+ name: string;
21
+ reason: string;
22
+ }
23
+ /**
24
+ * Log each dropped (unmatched) MCP server as an audit entry with status "dropped".
25
+ *
26
+ * Called during ACP session setup when MCP servers from the ACP client
27
+ * don't match any chapter App.
28
+ */
29
+ export declare function logDroppedServers(db: Database.Database, unmatched: DroppedServer[], agentName: string, roleName: string, acpClient?: string): void;
30
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/hooks/audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAI9C,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,kBAAkB,CAMrE;AAID,wBAAgB,aAAa,CAC3B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,EAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,GACpB,IAAI,CAwBN;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,aAAa,EAAE,EAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAwBN"}
@@ -0,0 +1,66 @@
1
+ import { generateId, insertAuditLog } from "../db.js";
2
+ // ── Pre-Hook ───────────────────────────────────────────────────────────
3
+ export function auditPreHook(context) {
4
+ void context;
5
+ return {
6
+ id: generateId(),
7
+ startTime: Date.now(),
8
+ };
9
+ }
10
+ // ── Post-Hook ──────────────────────────────────────────────────────────
11
+ export function auditPostHook(context, preResult, callResult, status, db) {
12
+ const durationMs = Date.now() - preResult.startTime;
13
+ const entry = {
14
+ id: preResult.id,
15
+ agent_name: context.agentName,
16
+ role_name: context.roleName,
17
+ app_name: context.appName,
18
+ tool_name: context.toolName,
19
+ arguments: context.arguments != null ? JSON.stringify(context.arguments) : undefined,
20
+ result: callResult != null ? JSON.stringify(callResult) : undefined,
21
+ status,
22
+ duration_ms: durationMs,
23
+ timestamp: new Date().toISOString(),
24
+ session_type: context.sessionType,
25
+ acp_client: context.acpClient,
26
+ };
27
+ try {
28
+ insertAuditLog(db, entry);
29
+ }
30
+ catch (err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ console.error(`[chapter] audit log write failed: ${message}`);
33
+ }
34
+ }
35
+ /**
36
+ * Log each dropped (unmatched) MCP server as an audit entry with status "dropped".
37
+ *
38
+ * Called during ACP session setup when MCP servers from the ACP client
39
+ * don't match any chapter App.
40
+ */
41
+ export function logDroppedServers(db, unmatched, agentName, roleName, acpClient) {
42
+ for (const server of unmatched) {
43
+ const entry = {
44
+ id: generateId(),
45
+ agent_name: agentName,
46
+ role_name: roleName,
47
+ app_name: server.name,
48
+ tool_name: server.name,
49
+ arguments: undefined,
50
+ result: JSON.stringify(server.reason),
51
+ status: "dropped",
52
+ duration_ms: 0,
53
+ timestamp: new Date().toISOString(),
54
+ session_type: "acp",
55
+ acp_client: acpClient,
56
+ };
57
+ try {
58
+ insertAuditLog(db, entry);
59
+ }
60
+ catch (err) {
61
+ const message = err instanceof Error ? err.message : String(err);
62
+ console.error(`[chapter] audit log write failed (dropped server "${server.name}"): ${message}`);
63
+ }
64
+ }
65
+ }
66
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/hooks/audit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAqBtD,0EAA0E;AAE1E,MAAM,UAAU,YAAY,CAAC,OAAoB;IAC/C,KAAK,OAAO,CAAC;IACb,OAAO;QACL,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACtB,CAAC;AACJ,CAAC;AAED,0EAA0E;AAE1E,MAAM,UAAU,aAAa,CAC3B,OAAoB,EACpB,SAA6B,EAC7B,UAAmB,EACnB,MAA+B,EAC/B,EAAqB;IAErB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,SAAS,CAAC;IAEpD,MAAM,KAAK,GAAkB;QAC3B,EAAE,EAAE,SAAS,CAAC,EAAE;QAChB,UAAU,EAAE,OAAO,CAAC,SAAS;QAC7B,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,QAAQ,EAAE,OAAO,CAAC,OAAO;QACzB,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;QACpF,MAAM,EAAE,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;QACnE,MAAM;QACN,WAAW,EAAE,UAAU;QACvB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,YAAY,EAAE,OAAO,CAAC,WAAW;QACjC,UAAU,EAAE,OAAO,CAAC,SAAS;KAC9B,CAAC;IAEF,IAAI,CAAC;QACH,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,qCAAqC,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,EAAqB,EACrB,SAA0B,EAC1B,SAAiB,EACjB,QAAgB,EAChB,SAAkB;IAElB,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAkB;YAC3B,EAAE,EAAE,UAAU,EAAE;YAChB,UAAU,EAAE,SAAS;YACrB,SAAS,EAAE,QAAQ;YACnB,QAAQ,EAAE,MAAM,CAAC,IAAI;YACrB,SAAS,EAAE,MAAM,CAAC,IAAI;YACtB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC;YACrC,MAAM,EAAE,SAAS;YACjB,WAAW,EAAE,CAAC;YACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,SAAS;SACtB,CAAC;QAEF,IAAI,CAAC;YACH,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,KAAK,CAAC,qDAAqD,MAAM,CAAC,IAAI,OAAO,OAAO,EAAE,CAAC,CAAC;QAClG,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ export { ChapterProxyServer } from "./server.js";
2
+ export type { ChapterProxyServerConfig } from "./server.js";
3
+ export { ToolRouter, ResourceRouter, PromptRouter } from "./router.js";
4
+ export type { RouteEntry, ResourceRouteEntry, PromptRouteEntry } from "./router.js";
5
+ export { UpstreamManager, createTransport } from "./upstream.js";
6
+ export type { UpstreamAppConfig } from "./upstream.js";
7
+ export { loadEnvFile, resolveEnvVars } from "./credentials.js";
8
+ export { openDatabase, insertAuditLog, queryAuditLog, createApprovalRequest, getApprovalRequest, updateApprovalStatus, generateId, } from "./db.js";
9
+ export type { AuditLogEntry, ApprovalRequest, AuditLogFilters } from "./db.js";
10
+ export { auditPreHook, auditPostHook, logDroppedServers } from "./hooks/audit.js";
11
+ export type { HookContext, AuditPreHookResult, DroppedServer } from "./hooks/audit.js";
12
+ export { matchesApprovalPattern, requestApproval } from "./hooks/approval.js";
13
+ export type { ApprovalOptions } from "./hooks/approval.js";
14
+ export { SessionStore, handleConnectAgent } from "./handlers/connect-agent.js";
15
+ export type { SessionEntry, RiskLevel } from "./handlers/connect-agent.js";
16
+ export { CredentialRelay } from "./handlers/credential-relay.js";
17
+ export type { CredentialRelayConfig, CredentialToolResult } from "./handlers/credential-relay.js";
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,62 @@
1
+ import type { Tool, Resource, Prompt } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { ToolFilter } from "@clawmasons/shared";
3
+ export interface RouteEntry {
4
+ /** Full package name, e.g., "@clawmasons/app-github". */
5
+ appName: string;
6
+ /** Short name derived from package name, e.g., "github". */
7
+ appShortName: string;
8
+ /** Original upstream tool name, e.g., "create_pr". */
9
+ originalToolName: string;
10
+ /** Prefixed tool name exposed downstream, e.g., "github_create_pr". */
11
+ prefixedToolName: string;
12
+ /** MCP Tool object with name rewritten to the prefixed form. */
13
+ tool: Tool;
14
+ }
15
+ export declare class ToolRouter {
16
+ private routes;
17
+ constructor(upstreamTools: Map<string, Tool[]>, toolFilters: Map<string, ToolFilter>);
18
+ /** Returns all prefixed, filtered MCP Tool objects. */
19
+ listTools(): Tool[];
20
+ /** Resolves a prefixed tool name to its route entry, or null if unknown/filtered. */
21
+ resolve(prefixedName: string): RouteEntry | null;
22
+ /** Prefix a tool name with an app short name. */
23
+ static prefixName(appShortName: string, toolName: string): string;
24
+ /** Strip the app short name prefix from a prefixed tool name. */
25
+ static unprefixName(appShortName: string, prefixedName: string): string;
26
+ }
27
+ export interface ResourceRouteEntry {
28
+ appName: string;
29
+ appShortName: string;
30
+ originalName: string;
31
+ prefixedName: string;
32
+ originalUri: string;
33
+ resource: Resource;
34
+ }
35
+ export declare class ResourceRouter {
36
+ private entries;
37
+ private uriMap;
38
+ constructor(upstreamResources: Map<string, Resource[]>);
39
+ /** Returns all prefixed MCP Resource objects. */
40
+ listResources(): Resource[];
41
+ /** Resolves a resource URI to its upstream app and original URI, or null if unknown. */
42
+ resolveUri(uri: string): {
43
+ appName: string;
44
+ originalUri: string;
45
+ } | null;
46
+ }
47
+ export interface PromptRouteEntry {
48
+ appName: string;
49
+ appShortName: string;
50
+ originalName: string;
51
+ prefixedName: string;
52
+ prompt: Prompt;
53
+ }
54
+ export declare class PromptRouter {
55
+ private routes;
56
+ constructor(upstreamPrompts: Map<string, Prompt[]>);
57
+ /** Returns all prefixed MCP Prompt objects. */
58
+ listPrompts(): Prompt[];
59
+ /** Resolves a prefixed prompt name to its route entry, or null if unknown. */
60
+ resolve(prefixedName: string): PromptRouteEntry | null;
61
+ }
62
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
3
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import { type Tool, type Resource, type Prompt, type CallToolResult, type ReadResourceResult, type GetPromptResult } from "@modelcontextprotocol/sdk/types.js";
5
+ import type { ResolvedApp } from "@clawmasons/shared";
6
+ export interface UpstreamAppConfig {
7
+ /** Full package name, e.g., "@clawmasons/app-github". Used as lookup key. */
8
+ name: string;
9
+ app: ResolvedApp;
10
+ env?: Record<string, string>;
11
+ /** Working directory for stdio transport child processes. */
12
+ cwd?: string;
13
+ }
14
+ export declare class UpstreamManager {
15
+ private configs;
16
+ private clients;
17
+ private initialized;
18
+ constructor(apps: UpstreamAppConfig[]);
19
+ initialize(timeoutMs?: number): Promise<void>;
20
+ getTools(appName: string): Promise<Tool[]>;
21
+ getResources(appName: string): Promise<Resource[]>;
22
+ getPrompts(appName: string): Promise<Prompt[]>;
23
+ callTool(appName: string, toolName: string, args?: Record<string, unknown>): Promise<CallToolResult>;
24
+ readResource(appName: string, uri: string): Promise<ReadResourceResult>;
25
+ getPrompt(appName: string, name: string, args?: Record<string, string>): Promise<GetPromptResult>;
26
+ shutdown(): Promise<void>;
27
+ private requireClient;
28
+ }
29
+ export declare function createTransport(config: UpstreamAppConfig): StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport;
30
+ //# sourceMappingURL=upstream.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawmasons/proxy",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Clawmasons Chapter MCP proxy server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "@clawmasons/shared": "0.1.0",
23
+ "@clawmasons/shared": "^0.1.1",
24
24
  "@modelcontextprotocol/sdk": "^1.27.1",
25
25
  "better-sqlite3": "^12.6.2",
26
26
  "ws": "^8.19.0"