@getcordon/core 0.1.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.
@@ -0,0 +1,137 @@
1
+ import { ResolvedConfig, AuditConfig, ApprovalConfig, StdioServerConfig } from 'cordon-sdk';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
4
+
5
+ declare class CordonGateway {
6
+ private server;
7
+ private upstream;
8
+ private policy;
9
+ private approvals;
10
+ private audit;
11
+ private interceptor;
12
+ constructor(config: ResolvedConfig);
13
+ start(): Promise<void>;
14
+ stop(): Promise<void>;
15
+ private registerHandlers;
16
+ }
17
+
18
+ type AuditEventType = 'gateway_started' | 'gateway_stopped' | 'upstream_connected' | 'upstream_error' | 'tool_call_received' | 'tool_call_blocked' | 'tool_call_allowed' | 'approval_requested' | 'tool_call_approved' | 'tool_call_denied' | 'tool_call_completed' | 'tool_call_errored';
19
+ interface AuditEntry {
20
+ event: AuditEventType;
21
+ timestamp: number;
22
+ callId?: string;
23
+ serverName?: string;
24
+ toolName?: string;
25
+ proxyName?: string;
26
+ args?: unknown;
27
+ isError?: boolean;
28
+ reason?: string;
29
+ error?: string;
30
+ servers?: string[];
31
+ durationMs?: number;
32
+ }
33
+ declare class AuditLogger {
34
+ private outputs;
35
+ constructor(config: AuditConfig | undefined);
36
+ log(entry: Omit<AuditEntry, 'timestamp'>): void;
37
+ close(): void;
38
+ private buildOutput;
39
+ }
40
+
41
+ type PolicyDecision = {
42
+ action: 'allow';
43
+ } | {
44
+ action: 'block';
45
+ reason: string;
46
+ } | {
47
+ action: 'approve';
48
+ };
49
+ declare class PolicyEngine {
50
+ /** server name → default policy action */
51
+ private serverPolicies;
52
+ /** "serverName/toolName" → tool-level policy */
53
+ private toolPolicies;
54
+ constructor(config: ResolvedConfig);
55
+ evaluate(serverName: string, toolName: string): PolicyDecision;
56
+ private resolve;
57
+ }
58
+
59
+ interface ApprovalContext {
60
+ callId: string;
61
+ serverName: string;
62
+ toolName: string;
63
+ args: unknown;
64
+ timeoutMs?: number;
65
+ }
66
+ type ApprovalResult = {
67
+ approved: true;
68
+ } | {
69
+ approved: false;
70
+ reason: string;
71
+ };
72
+ declare class ApprovalManager {
73
+ private channel;
74
+ private timeoutMs;
75
+ constructor(config: ApprovalConfig | undefined);
76
+ request(ctx: Omit<ApprovalContext, 'timeoutMs'>): Promise<ApprovalResult>;
77
+ private buildChannel;
78
+ }
79
+
80
+ /** The actual return type of Client.callTool() — wider than the named CallToolResult. */
81
+ type ToolCallResponse = Awaited<ReturnType<Client['callTool']>>;
82
+ interface ToolWithOrigin extends Tool {
83
+ /** The server that owns this tool. */
84
+ serverName: string;
85
+ /** Original tool name as reported by the server. */
86
+ originalName: string;
87
+ /**
88
+ * Name exposed to the LLM client. Equal to originalName when there are no
89
+ * collisions; namespaced as "serverName__toolName" when two servers share
90
+ * a tool name.
91
+ */
92
+ proxyName: string;
93
+ }
94
+ declare class UpstreamManager {
95
+ private configs;
96
+ private clients;
97
+ private registry;
98
+ constructor(configs: StdioServerConfig[]);
99
+ connect(): Promise<void>;
100
+ disconnect(): Promise<void>;
101
+ serverNames(): string[];
102
+ /** Returns the current merged + namespaced tool list. */
103
+ getTools(): ToolWithOrigin[];
104
+ /** Look up which server and original tool name to use for a proxy tool name. */
105
+ resolve(proxyName: string): ToolWithOrigin | undefined;
106
+ callTool(serverName: string, toolName: string, args: unknown): Promise<ToolCallResponse>;
107
+ /**
108
+ * Re-queries all upstream servers for their tool lists and rebuilds the
109
+ * registry. Called on startup and whenever a tools/list-changed notification
110
+ * arrives from any upstream.
111
+ */
112
+ refreshRegistry(): Promise<ToolWithOrigin[]>;
113
+ private connectServer;
114
+ }
115
+
116
+ /**
117
+ * The hot path. Every tools/call from the LLM client flows through here.
118
+ *
119
+ * Flow:
120
+ * 1. Resolve proxy tool name → server + original tool name
121
+ * 2. Audit: received
122
+ * 3. Evaluate policy → allow / block / approve
123
+ * 4. If approve: await human decision
124
+ * 5. Forward to upstream server
125
+ * 6. Audit: completed
126
+ * 7. Return result to LLM
127
+ */
128
+ declare class Interceptor {
129
+ private upstream;
130
+ private policy;
131
+ private approvals;
132
+ private audit;
133
+ constructor(upstream: UpstreamManager, policy: PolicyEngine, approvals: ApprovalManager, audit: AuditLogger);
134
+ handle(proxyToolName: string, args: unknown): Promise<ToolCallResponse>;
135
+ }
136
+
137
+ export { type ApprovalContext, ApprovalManager, type ApprovalResult, type AuditEntry, type AuditEventType, AuditLogger, CordonGateway, Interceptor, type PolicyDecision, PolicyEngine, type ToolCallResponse, type ToolWithOrigin, UpstreamManager };
package/dist/index.js ADDED
@@ -0,0 +1,485 @@
1
+ // src/gateway.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ ListToolsRequestSchema,
6
+ CallToolRequestSchema
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+
9
+ // src/audit/logger.ts
10
+ import { createWriteStream } from "fs";
11
+ var StderrAuditOutput = class {
12
+ write(entry) {
13
+ process.stderr.write(JSON.stringify(entry) + "\n");
14
+ }
15
+ };
16
+ var FileAuditOutput = class {
17
+ stream;
18
+ constructor(filePath) {
19
+ this.stream = createWriteStream(filePath, { flags: "a", encoding: "utf8" });
20
+ }
21
+ write(entry) {
22
+ this.stream.write(JSON.stringify(entry) + "\n");
23
+ }
24
+ close() {
25
+ this.stream.end();
26
+ }
27
+ };
28
+ var AuditLogger = class {
29
+ outputs;
30
+ constructor(config) {
31
+ if (!config?.enabled) {
32
+ this.outputs = [];
33
+ return;
34
+ }
35
+ const targets = config.output ? Array.isArray(config.output) ? config.output : [config.output] : ["stdout"];
36
+ this.outputs = targets.map((t) => this.buildOutput(t, config));
37
+ }
38
+ log(entry) {
39
+ const full = { ...entry, timestamp: Date.now() };
40
+ for (const output of this.outputs) {
41
+ output.write(full);
42
+ }
43
+ }
44
+ close() {
45
+ for (const output of this.outputs) {
46
+ output.close?.();
47
+ }
48
+ }
49
+ buildOutput(type, config) {
50
+ switch (type) {
51
+ case "stdout":
52
+ return new StderrAuditOutput();
53
+ case "file":
54
+ return new FileAuditOutput(config.filePath ?? "./cordon-audit.log");
55
+ case "otlp":
56
+ case "webhook":
57
+ process.stderr.write(
58
+ `[cordon] warn: audit output '${type}' not yet implemented, falling back to stdout
59
+ `
60
+ );
61
+ return new StderrAuditOutput();
62
+ }
63
+ }
64
+ };
65
+
66
+ // src/approvals/terminal.ts
67
+ import { createInterface } from "readline";
68
+ import { createReadStream } from "fs";
69
+ var TerminalApprovalChannel = class {
70
+ async request(ctx) {
71
+ const argsDisplay = JSON.stringify(ctx.args, null, 2).split("\n").map((l) => ` ${l}`).join("\n");
72
+ process.stderr.write(
73
+ `
74
+ \x1B[33m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\x1B[0m
75
+ \x1B[33m\u2551 \u26A0 APPROVAL REQUIRED \u2551\x1B[0m
76
+ \x1B[33m\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m
77
+ Server : \x1B[36m${ctx.serverName}\x1B[0m
78
+ Tool : \x1B[36m${ctx.toolName}\x1B[0m
79
+ Args :
80
+ ${argsDisplay}
81
+
82
+ \x1B[32m[A]\x1B[0mppove \x1B[31m[D]\x1B[0meny
83
+ > `
84
+ );
85
+ return new Promise((resolve) => {
86
+ try {
87
+ const ttyInput = openTtyInput();
88
+ const rl = createInterface({ input: ttyInput, terminal: false });
89
+ const timeoutHandle = ctx.timeoutMs !== void 0 ? setTimeout(() => {
90
+ rl.close();
91
+ process.stderr.write("\n \x1B[31mAuto-denied: approval timeout\x1B[0m\n\n");
92
+ resolve({ approved: false, reason: "Approval timed out" });
93
+ }, ctx.timeoutMs) : null;
94
+ rl.once("line", (line) => {
95
+ if (timeoutHandle) clearTimeout(timeoutHandle);
96
+ rl.close();
97
+ const input = line.trim().toLowerCase();
98
+ if (input === "a" || input === "approve" || input === "yes" || input === "y") {
99
+ process.stderr.write(" \x1B[32mApproved.\x1B[0m\n\n");
100
+ resolve({ approved: true });
101
+ } else {
102
+ process.stderr.write(" \x1B[31mDenied.\x1B[0m\n\n");
103
+ resolve({ approved: false, reason: "Denied by operator" });
104
+ }
105
+ });
106
+ rl.once("close", () => {
107
+ if (timeoutHandle) clearTimeout(timeoutHandle);
108
+ resolve({ approved: false, reason: "TTY closed before response" });
109
+ });
110
+ } catch (err) {
111
+ process.stderr.write(
112
+ ` \x1B[31mWarning: no TTY available for approval \u2014 auto-denying.\x1B[0m
113
+ `
114
+ );
115
+ resolve({ approved: false, reason: "No TTY available for approval" });
116
+ }
117
+ });
118
+ }
119
+ };
120
+ function openTtyInput() {
121
+ const ttyPath = process.platform === "win32" ? "\\\\.\\CONIN$" : "/dev/tty";
122
+ try {
123
+ return createReadStream(ttyPath);
124
+ } catch {
125
+ throw new Error(`Cannot open TTY at ${ttyPath}`);
126
+ }
127
+ }
128
+
129
+ // src/approvals/manager.ts
130
+ var ApprovalManager = class {
131
+ channel;
132
+ timeoutMs;
133
+ constructor(config) {
134
+ this.timeoutMs = config?.timeoutMs;
135
+ this.channel = this.buildChannel(config);
136
+ }
137
+ async request(ctx) {
138
+ return this.channel.request({ ...ctx, timeoutMs: this.timeoutMs });
139
+ }
140
+ buildChannel(config) {
141
+ const type = config?.channel ?? "terminal";
142
+ switch (type) {
143
+ case "terminal":
144
+ return new TerminalApprovalChannel();
145
+ case "slack":
146
+ case "web":
147
+ case "webhook":
148
+ process.stderr.write(
149
+ `[cordon] warn: approval channel '${type}' not yet implemented, using terminal
150
+ `
151
+ );
152
+ return new TerminalApprovalChannel();
153
+ }
154
+ }
155
+ };
156
+
157
+ // src/policies/engine.ts
158
+ var WRITE_PREFIXES = [
159
+ "write",
160
+ "create",
161
+ "update",
162
+ "delete",
163
+ "remove",
164
+ "drop",
165
+ "insert",
166
+ "execute",
167
+ "exec",
168
+ "run",
169
+ "push",
170
+ "post",
171
+ "put",
172
+ "patch",
173
+ "set",
174
+ "send",
175
+ "deploy",
176
+ "destroy",
177
+ "reset",
178
+ "clear",
179
+ "purge",
180
+ "truncate",
181
+ "alter"
182
+ ];
183
+ function isWriteOperation(toolName) {
184
+ const lower = toolName.toLowerCase();
185
+ return WRITE_PREFIXES.some(
186
+ (w) => lower === w || lower.startsWith(`${w}_`) || lower.startsWith(`${w}-`) || lower.startsWith(w) && lower[w.length] === void 0
187
+ );
188
+ }
189
+ var PolicyEngine = class {
190
+ /** server name → default policy action */
191
+ serverPolicies = /* @__PURE__ */ new Map();
192
+ /** "serverName/toolName" → tool-level policy */
193
+ toolPolicies = /* @__PURE__ */ new Map();
194
+ constructor(config) {
195
+ for (const server of config.servers) {
196
+ if (server.policy) {
197
+ this.serverPolicies.set(server.name, server.policy);
198
+ }
199
+ for (const [toolName, policy] of Object.entries(server.tools ?? {})) {
200
+ this.toolPolicies.set(`${server.name}/${toolName}`, policy);
201
+ }
202
+ }
203
+ }
204
+ evaluate(serverName, toolName) {
205
+ const toolPolicy = this.toolPolicies.get(`${serverName}/${toolName}`);
206
+ if (toolPolicy !== void 0) {
207
+ return this.resolve(toolPolicy, toolName);
208
+ }
209
+ const serverPolicy = this.serverPolicies.get(serverName) ?? "allow";
210
+ return this.resolve(serverPolicy, toolName);
211
+ }
212
+ resolve(policy, toolName) {
213
+ const action = typeof policy === "string" ? policy : policy.action;
214
+ const customReason = typeof policy === "object" ? policy.reason : void 0;
215
+ switch (action) {
216
+ case "allow":
217
+ return { action: "allow" };
218
+ case "block":
219
+ return {
220
+ action: "block",
221
+ reason: customReason ?? `Tool '${toolName}' is blocked by policy`
222
+ };
223
+ case "approve":
224
+ return { action: "approve" };
225
+ case "read-only":
226
+ return isWriteOperation(toolName) ? {
227
+ action: "block",
228
+ reason: customReason ?? `Read-only mode: '${toolName}' is a write operation and has been blocked`
229
+ } : { action: "allow" };
230
+ case "approve-writes":
231
+ return isWriteOperation(toolName) ? { action: "approve" } : { action: "allow" };
232
+ case "log-only":
233
+ return { action: "allow" };
234
+ }
235
+ }
236
+ };
237
+
238
+ // src/proxy/upstream-manager.ts
239
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
240
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
241
+ var UpstreamManager = class {
242
+ constructor(configs) {
243
+ this.configs = configs;
244
+ }
245
+ clients = /* @__PURE__ */ new Map();
246
+ registry = /* @__PURE__ */ new Map();
247
+ async connect() {
248
+ await Promise.all(this.configs.map((cfg) => this.connectServer(cfg)));
249
+ await this.refreshRegistry();
250
+ }
251
+ async disconnect() {
252
+ await Promise.all([...this.clients.values()].map((c) => c.close()));
253
+ this.clients.clear();
254
+ this.registry.clear();
255
+ }
256
+ serverNames() {
257
+ return [...this.clients.keys()];
258
+ }
259
+ /** Returns the current merged + namespaced tool list. */
260
+ getTools() {
261
+ return [...this.registry.values()];
262
+ }
263
+ /** Look up which server and original tool name to use for a proxy tool name. */
264
+ resolve(proxyName) {
265
+ return this.registry.get(proxyName);
266
+ }
267
+ async callTool(serverName, toolName, args) {
268
+ const client = this.clients.get(serverName);
269
+ if (!client) {
270
+ throw new Error(`No upstream client for server '${serverName}'`);
271
+ }
272
+ return client.callTool({
273
+ name: toolName,
274
+ arguments: args
275
+ });
276
+ }
277
+ /**
278
+ * Re-queries all upstream servers for their tool lists and rebuilds the
279
+ * registry. Called on startup and whenever a tools/list-changed notification
280
+ * arrives from any upstream.
281
+ */
282
+ async refreshRegistry() {
283
+ this.registry.clear();
284
+ const perServer = /* @__PURE__ */ new Map();
285
+ for (const [serverName, client] of this.clients) {
286
+ try {
287
+ const { tools } = await client.listTools();
288
+ perServer.set(serverName, tools);
289
+ } catch (err) {
290
+ process.stderr.write(
291
+ `[cordon] warn: failed to list tools from '${serverName}': ${String(err)}
292
+ `
293
+ );
294
+ perServer.set(serverName, []);
295
+ }
296
+ }
297
+ const nameCounts = /* @__PURE__ */ new Map();
298
+ for (const tools of perServer.values()) {
299
+ for (const tool of tools) {
300
+ nameCounts.set(tool.name, (nameCounts.get(tool.name) ?? 0) + 1);
301
+ }
302
+ }
303
+ for (const [serverName, tools] of perServer) {
304
+ for (const tool of tools) {
305
+ const collision = (nameCounts.get(tool.name) ?? 0) > 1;
306
+ const proxyName = collision ? `${serverName}__${tool.name}` : tool.name;
307
+ this.registry.set(proxyName, {
308
+ ...tool,
309
+ serverName,
310
+ originalName: tool.name,
311
+ proxyName
312
+ });
313
+ }
314
+ }
315
+ return [...this.registry.values()];
316
+ }
317
+ // ── Private ──────────────────────────────────────────────────────────────────
318
+ async connectServer(cfg) {
319
+ const client = new Client({ name: "cordon", version: "0.1.0" });
320
+ const transport = new StdioClientTransport({
321
+ command: cfg.command,
322
+ args: cfg.args ?? [],
323
+ env: cfg.env
324
+ });
325
+ try {
326
+ await client.connect(transport);
327
+ this.clients.set(cfg.name, client);
328
+ process.stderr.write(`[cordon] connected to '${cfg.name}'
329
+ `);
330
+ } catch (err) {
331
+ throw new Error(`Failed to connect to upstream '${cfg.name}': ${String(err)}`);
332
+ }
333
+ }
334
+ };
335
+
336
+ // src/proxy/interceptor.ts
337
+ var Interceptor = class {
338
+ constructor(upstream, policy, approvals, audit) {
339
+ this.upstream = upstream;
340
+ this.policy = policy;
341
+ this.approvals = approvals;
342
+ this.audit = audit;
343
+ }
344
+ async handle(proxyToolName, args) {
345
+ const tool = this.upstream.resolve(proxyToolName);
346
+ if (!tool) {
347
+ return errorResult(`Unknown tool: ${proxyToolName}`);
348
+ }
349
+ const callId = crypto.randomUUID();
350
+ const { serverName, originalName } = tool;
351
+ const start = Date.now();
352
+ this.audit.log({
353
+ event: "tool_call_received",
354
+ callId,
355
+ serverName,
356
+ toolName: originalName,
357
+ proxyName: proxyToolName,
358
+ args
359
+ });
360
+ const decision = this.policy.evaluate(serverName, originalName);
361
+ if (decision.action === "block") {
362
+ this.audit.log({
363
+ event: "tool_call_blocked",
364
+ callId,
365
+ serverName,
366
+ toolName: originalName,
367
+ reason: decision.reason
368
+ });
369
+ return errorResult(decision.reason);
370
+ }
371
+ if (decision.action === "approve") {
372
+ this.audit.log({ event: "approval_requested", callId, serverName, toolName: originalName });
373
+ const result = await this.approvals.request({ callId, serverName, toolName: originalName, args });
374
+ if (!result.approved) {
375
+ this.audit.log({
376
+ event: "tool_call_denied",
377
+ callId,
378
+ serverName,
379
+ toolName: originalName,
380
+ reason: result.reason
381
+ });
382
+ return errorResult(`Denied: ${result.reason}`);
383
+ }
384
+ this.audit.log({ event: "tool_call_approved", callId, serverName, toolName: originalName });
385
+ } else {
386
+ this.audit.log({ event: "tool_call_allowed", callId, serverName, toolName: originalName });
387
+ }
388
+ try {
389
+ const response = await this.upstream.callTool(serverName, originalName, args);
390
+ this.audit.log({
391
+ event: "tool_call_completed",
392
+ callId,
393
+ serverName,
394
+ toolName: originalName,
395
+ isError: Boolean(response.isError),
396
+ durationMs: Date.now() - start
397
+ });
398
+ return response;
399
+ } catch (err) {
400
+ this.audit.log({
401
+ event: "tool_call_errored",
402
+ callId,
403
+ serverName,
404
+ toolName: originalName,
405
+ error: String(err),
406
+ durationMs: Date.now() - start
407
+ });
408
+ return errorResult(`Upstream error from '${serverName}': ${String(err)}`);
409
+ }
410
+ }
411
+ };
412
+ function errorResult(message) {
413
+ return {
414
+ content: [{ type: "text", text: `[cordon] ${message}` }],
415
+ isError: true
416
+ };
417
+ }
418
+
419
+ // src/gateway.ts
420
+ var CordonGateway = class {
421
+ server;
422
+ upstream;
423
+ policy;
424
+ approvals;
425
+ audit;
426
+ interceptor;
427
+ constructor(config) {
428
+ this.audit = new AuditLogger(config.audit);
429
+ this.policy = new PolicyEngine(config);
430
+ this.approvals = new ApprovalManager(config.approvals);
431
+ this.upstream = new UpstreamManager(config.servers);
432
+ this.interceptor = new Interceptor(
433
+ this.upstream,
434
+ this.policy,
435
+ this.approvals,
436
+ this.audit
437
+ );
438
+ this.server = new Server(
439
+ { name: "cordon", version: "0.1.0" },
440
+ { capabilities: { tools: {} } }
441
+ );
442
+ this.registerHandlers();
443
+ }
444
+ async start() {
445
+ await this.upstream.connect();
446
+ this.audit.log({
447
+ event: "gateway_started",
448
+ servers: this.upstream.serverNames()
449
+ });
450
+ const transport = new StdioServerTransport();
451
+ await this.server.connect(transport);
452
+ }
453
+ async stop() {
454
+ await this.upstream.disconnect();
455
+ await this.server.close();
456
+ this.audit.log({ event: "gateway_stopped" });
457
+ this.audit.close();
458
+ }
459
+ // ── Handlers ────────────────────────────────────────────────────────────────
460
+ registerHandlers() {
461
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
462
+ const tools = this.upstream.getTools().map((t) => ({
463
+ name: t.proxyName,
464
+ description: t.description,
465
+ inputSchema: t.inputSchema
466
+ }));
467
+ return { tools };
468
+ });
469
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
470
+ return this.interceptor.handle(
471
+ request.params.name,
472
+ request.params.arguments ?? {}
473
+ );
474
+ });
475
+ }
476
+ };
477
+ export {
478
+ ApprovalManager,
479
+ AuditLogger,
480
+ CordonGateway,
481
+ Interceptor,
482
+ PolicyEngine,
483
+ UpstreamManager
484
+ };
485
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gateway.ts","../src/audit/logger.ts","../src/approvals/terminal.ts","../src/approvals/manager.ts","../src/policies/engine.ts","../src/proxy/upstream-manager.ts","../src/proxy/interceptor.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n ListToolsRequestSchema,\n CallToolRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport type { ResolvedConfig } from 'cordon-sdk';\nimport { AuditLogger } from './audit/logger.js';\nimport { ApprovalManager } from './approvals/manager.js';\nimport { PolicyEngine } from './policies/engine.js';\nimport { UpstreamManager } from './proxy/upstream-manager.js';\nimport { Interceptor } from './proxy/interceptor.js';\n\nexport class CordonGateway {\n private server: Server;\n private upstream: UpstreamManager;\n private policy: PolicyEngine;\n private approvals: ApprovalManager;\n private audit: AuditLogger;\n private interceptor: Interceptor;\n\n constructor(config: ResolvedConfig) {\n this.audit = new AuditLogger(config.audit);\n this.policy = new PolicyEngine(config);\n this.approvals = new ApprovalManager(config.approvals);\n this.upstream = new UpstreamManager(config.servers);\n this.interceptor = new Interceptor(\n this.upstream,\n this.policy,\n this.approvals,\n this.audit,\n );\n\n // The front-facing MCP server that Claude Desktop connects to\n this.server = new Server(\n { name: 'cordon', version: '0.1.0' },\n { capabilities: { tools: {} } },\n );\n\n this.registerHandlers();\n }\n\n async start(): Promise<void> {\n // 1. Connect to all configured upstream MCP servers\n await this.upstream.connect();\n\n this.audit.log({\n event: 'gateway_started',\n servers: this.upstream.serverNames(),\n });\n\n // 2. Start the stdio transport — this blocks until the client disconnects\n const transport = new StdioServerTransport();\n await this.server.connect(transport);\n }\n\n async stop(): Promise<void> {\n await this.upstream.disconnect();\n await this.server.close();\n this.audit.log({ event: 'gateway_stopped' });\n this.audit.close();\n }\n\n // ── Handlers ────────────────────────────────────────────────────────────────\n\n private registerHandlers(): void {\n // tools/list — return the merged tool registry from all upstream servers\n this.server.setRequestHandler(ListToolsRequestSchema, async () => {\n const tools = this.upstream.getTools().map((t) => ({\n name: t.proxyName,\n description: t.description,\n inputSchema: t.inputSchema,\n }));\n return { tools };\n });\n\n // tools/call — intercept, apply policy, forward if allowed\n this.server.setRequestHandler(CallToolRequestSchema, async (request) => {\n return this.interceptor.handle(\n request.params.name,\n request.params.arguments ?? {},\n );\n });\n }\n}\n","import { createWriteStream } from 'node:fs';\nimport type { WriteStream } from 'node:fs';\nimport type { AuditConfig, AuditOutputType } from 'cordon-sdk';\n\n// ── Audit entry types ─────────────────────────────────────────────────────────\n\nexport type AuditEventType =\n | 'gateway_started'\n | 'gateway_stopped'\n | 'upstream_connected'\n | 'upstream_error'\n | 'tool_call_received'\n | 'tool_call_blocked'\n | 'tool_call_allowed'\n | 'approval_requested'\n | 'tool_call_approved'\n | 'tool_call_denied'\n | 'tool_call_completed'\n | 'tool_call_errored';\n\nexport interface AuditEntry {\n event: AuditEventType;\n timestamp: number;\n callId?: string;\n serverName?: string;\n toolName?: string;\n proxyName?: string;\n args?: unknown;\n isError?: boolean;\n reason?: string;\n error?: string;\n servers?: string[];\n durationMs?: number;\n}\n\n// ── Output implementations ────────────────────────────────────────────────────\n\ninterface AuditOutput {\n write(entry: AuditEntry): void;\n close?(): void;\n}\n\nclass StderrAuditOutput implements AuditOutput {\n write(entry: AuditEntry): void {\n // Always write to stderr — stdout belongs to the MCP transport\n process.stderr.write(JSON.stringify(entry) + '\\n');\n }\n}\n\nclass FileAuditOutput implements AuditOutput {\n private stream: WriteStream;\n\n constructor(filePath: string) {\n this.stream = createWriteStream(filePath, { flags: 'a', encoding: 'utf8' });\n }\n\n write(entry: AuditEntry): void {\n this.stream.write(JSON.stringify(entry) + '\\n');\n }\n\n close(): void {\n this.stream.end();\n }\n}\n\n// ── Logger ────────────────────────────────────────────────────────────────────\n\nexport class AuditLogger {\n private outputs: AuditOutput[];\n\n constructor(config: AuditConfig | undefined) {\n if (!config?.enabled) {\n this.outputs = [];\n return;\n }\n\n const targets = config.output\n ? Array.isArray(config.output)\n ? config.output\n : [config.output]\n : (['stdout'] as AuditOutputType[]);\n\n this.outputs = targets.map((t) => this.buildOutput(t, config));\n }\n\n log(entry: Omit<AuditEntry, 'timestamp'>): void {\n const full: AuditEntry = { ...entry, timestamp: Date.now() };\n for (const output of this.outputs) {\n output.write(full);\n }\n }\n\n close(): void {\n for (const output of this.outputs) {\n output.close?.();\n }\n }\n\n private buildOutput(type: AuditOutputType, config: AuditConfig): AuditOutput {\n switch (type) {\n case 'stdout':\n return new StderrAuditOutput();\n case 'file':\n return new FileAuditOutput(config.filePath ?? './cordon-audit.log');\n case 'otlp':\n case 'webhook':\n // v2 — fall back to stderr for now\n process.stderr.write(\n `[cordon] warn: audit output '${type}' not yet implemented, falling back to stdout\\n`,\n );\n return new StderrAuditOutput();\n }\n }\n}\n","import { createInterface } from 'node:readline';\nimport { openSync, createReadStream, createWriteStream } from 'node:fs';\nimport type { ApprovalContext, ApprovalResult } from './manager.js';\n\n/**\n * Terminal approval channel.\n *\n * IMPORTANT: The MCP stdio transport owns process.stdin and process.stdout.\n * Writing to stdout or reading from stdin will corrupt the JSON-RPC stream.\n * We must:\n * - Write prompts to process.stderr\n * - Read input directly from the TTY device (/dev/tty on Unix, \\\\.\\CONIN$ on Windows)\n */\nexport class TerminalApprovalChannel {\n async request(ctx: ApprovalContext): Promise<ApprovalResult> {\n const argsDisplay = JSON.stringify(ctx.args, null, 2)\n .split('\\n')\n .map((l) => ` ${l}`)\n .join('\\n');\n\n process.stderr.write(\n `\\n\\x1b[33m╔══════════════════════════════════════╗\\x1b[0m\\n` +\n `\\x1b[33m║ ⚠ APPROVAL REQUIRED ║\\x1b[0m\\n` +\n `\\x1b[33m╚══════════════════════════════════════╝\\x1b[0m\\n` +\n ` Server : \\x1b[36m${ctx.serverName}\\x1b[0m\\n` +\n ` Tool : \\x1b[36m${ctx.toolName}\\x1b[0m\\n` +\n ` Args :\\n${argsDisplay}\\n\\n` +\n ` \\x1b[32m[A]\\x1b[0mppove \\x1b[31m[D]\\x1b[0meny\\n` +\n ` > `,\n );\n\n return new Promise((resolve) => {\n try {\n const ttyInput = openTtyInput();\n const rl = createInterface({ input: ttyInput, terminal: false });\n\n // Auto-deny on timeout if configured\n const timeoutHandle =\n ctx.timeoutMs !== undefined\n ? setTimeout(() => {\n rl.close();\n process.stderr.write('\\n \\x1b[31mAuto-denied: approval timeout\\x1b[0m\\n\\n');\n resolve({ approved: false, reason: 'Approval timed out' });\n }, ctx.timeoutMs)\n : null;\n\n rl.once('line', (line) => {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n rl.close();\n\n const input = line.trim().toLowerCase();\n if (input === 'a' || input === 'approve' || input === 'yes' || input === 'y') {\n process.stderr.write(' \\x1b[32mApproved.\\x1b[0m\\n\\n');\n resolve({ approved: true });\n } else {\n process.stderr.write(' \\x1b[31mDenied.\\x1b[0m\\n\\n');\n resolve({ approved: false, reason: 'Denied by operator' });\n }\n });\n\n // If the TTY closes without input, deny\n rl.once('close', () => {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n resolve({ approved: false, reason: 'TTY closed before response' });\n });\n } catch (err) {\n // If we can't open a TTY (e.g. running in CI with no terminal), auto-deny\n process.stderr.write(\n ` \\x1b[31mWarning: no TTY available for approval — auto-denying.\\x1b[0m\\n`,\n );\n resolve({ approved: false, reason: 'No TTY available for approval' });\n }\n });\n }\n}\n\n/**\n * Opens the real TTY device for reading, bypassing stdin which is owned by\n * the MCP transport. Platform-aware.\n */\nfunction openTtyInput(): NodeJS.ReadableStream {\n const ttyPath = process.platform === 'win32' ? '\\\\\\\\.\\\\CONIN$' : '/dev/tty';\n try {\n return createReadStream(ttyPath);\n } catch {\n throw new Error(`Cannot open TTY at ${ttyPath}`);\n }\n}\n","import type { ApprovalConfig } from 'cordon-sdk';\nimport { TerminalApprovalChannel } from './terminal.js';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface ApprovalContext {\n callId: string;\n serverName: string;\n toolName: string;\n args: unknown;\n timeoutMs?: number;\n}\n\nexport type ApprovalResult = { approved: true } | { approved: false; reason: string };\n\ninterface ApprovalChannel {\n request(ctx: ApprovalContext): Promise<ApprovalResult>;\n}\n\n// ── Manager ───────────────────────────────────────────────────────────────────\n\nexport class ApprovalManager {\n private channel: ApprovalChannel;\n private timeoutMs: number | undefined;\n\n constructor(config: ApprovalConfig | undefined) {\n this.timeoutMs = config?.timeoutMs;\n this.channel = this.buildChannel(config);\n }\n\n async request(ctx: Omit<ApprovalContext, 'timeoutMs'>): Promise<ApprovalResult> {\n return this.channel.request({ ...ctx, timeoutMs: this.timeoutMs });\n }\n\n private buildChannel(config: ApprovalConfig | undefined): ApprovalChannel {\n const type = config?.channel ?? 'terminal';\n switch (type) {\n case 'terminal':\n return new TerminalApprovalChannel();\n case 'slack':\n case 'web':\n case 'webhook':\n // v2 — fall back to terminal with a warning\n process.stderr.write(\n `[cordon] warn: approval channel '${type}' not yet implemented, using terminal\\n`,\n );\n return new TerminalApprovalChannel();\n }\n }\n}\n","import type { PolicyAction, ResolvedConfig, ToolPolicy } from 'cordon-sdk';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type PolicyDecision =\n | { action: 'allow' }\n | { action: 'block'; reason: string }\n | { action: 'approve' };\n\n// ── Write detection ───────────────────────────────────────────────────────────\n\n// Tool names (or prefixes) that indicate a mutation/write operation.\nconst WRITE_PREFIXES = [\n 'write',\n 'create',\n 'update',\n 'delete',\n 'remove',\n 'drop',\n 'insert',\n 'execute',\n 'exec',\n 'run',\n 'push',\n 'post',\n 'put',\n 'patch',\n 'set',\n 'send',\n 'deploy',\n 'destroy',\n 'reset',\n 'clear',\n 'purge',\n 'truncate',\n 'alter',\n];\n\nfunction isWriteOperation(toolName: string): boolean {\n const lower = toolName.toLowerCase();\n return WRITE_PREFIXES.some(\n (w) => lower === w || lower.startsWith(`${w}_`) || lower.startsWith(`${w}-`) || lower.startsWith(w) && lower[w.length] === undefined,\n );\n}\n\n// ── Engine ────────────────────────────────────────────────────────────────────\n\nexport class PolicyEngine {\n /** server name → default policy action */\n private serverPolicies = new Map<string, PolicyAction>();\n /** \"serverName/toolName\" → tool-level policy */\n private toolPolicies = new Map<string, ToolPolicy>();\n\n constructor(config: ResolvedConfig) {\n for (const server of config.servers) {\n if (server.policy) {\n this.serverPolicies.set(server.name, server.policy);\n }\n for (const [toolName, policy] of Object.entries(server.tools ?? {})) {\n this.toolPolicies.set(`${server.name}/${toolName}`, policy);\n }\n }\n }\n\n evaluate(serverName: string, toolName: string): PolicyDecision {\n // Tool-level policy takes highest precedence\n const toolPolicy = this.toolPolicies.get(`${serverName}/${toolName}`);\n if (toolPolicy !== undefined) {\n return this.resolve(toolPolicy, toolName);\n }\n\n // Server-level policy (default: allow)\n const serverPolicy = this.serverPolicies.get(serverName) ?? 'allow';\n return this.resolve(serverPolicy, toolName);\n }\n\n private resolve(policy: ToolPolicy, toolName: string): PolicyDecision {\n const action = typeof policy === 'string' ? policy : policy.action;\n const customReason = typeof policy === 'object' ? policy.reason : undefined;\n\n switch (action) {\n case 'allow':\n return { action: 'allow' };\n\n case 'block':\n return {\n action: 'block',\n reason: customReason ?? `Tool '${toolName}' is blocked by policy`,\n };\n\n case 'approve':\n return { action: 'approve' };\n\n case 'read-only':\n return isWriteOperation(toolName)\n ? {\n action: 'block',\n reason:\n customReason ??\n `Read-only mode: '${toolName}' is a write operation and has been blocked`,\n }\n : { action: 'allow' };\n\n case 'approve-writes':\n return isWriteOperation(toolName) ? { action: 'approve' } : { action: 'allow' };\n\n case 'log-only':\n // Audit logger handles the flagging; call passes through\n return { action: 'allow' };\n }\n }\n}\n","import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\nimport type { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport type { StdioServerConfig } from 'cordon-sdk';\n\n/** The actual return type of Client.callTool() — wider than the named CallToolResult. */\nexport type ToolCallResponse = Awaited<ReturnType<Client['callTool']>>;\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface ToolWithOrigin extends Tool {\n /** The server that owns this tool. */\n serverName: string;\n /** Original tool name as reported by the server. */\n originalName: string;\n /**\n * Name exposed to the LLM client. Equal to originalName when there are no\n * collisions; namespaced as \"serverName__toolName\" when two servers share\n * a tool name.\n */\n proxyName: string;\n}\n\n// ── Manager ───────────────────────────────────────────────────────────────────\n\nexport class UpstreamManager {\n private clients = new Map<string, Client>();\n private registry = new Map<string, ToolWithOrigin>();\n\n constructor(private configs: StdioServerConfig[]) {}\n\n async connect(): Promise<void> {\n await Promise.all(this.configs.map((cfg) => this.connectServer(cfg)));\n await this.refreshRegistry();\n }\n\n async disconnect(): Promise<void> {\n await Promise.all([...this.clients.values()].map((c) => c.close()));\n this.clients.clear();\n this.registry.clear();\n }\n\n serverNames(): string[] {\n return [...this.clients.keys()];\n }\n\n /** Returns the current merged + namespaced tool list. */\n getTools(): ToolWithOrigin[] {\n return [...this.registry.values()];\n }\n\n /** Look up which server and original tool name to use for a proxy tool name. */\n resolve(proxyName: string): ToolWithOrigin | undefined {\n return this.registry.get(proxyName);\n }\n\n async callTool(serverName: string, toolName: string, args: unknown): Promise<ToolCallResponse> {\n const client = this.clients.get(serverName);\n if (!client) {\n throw new Error(`No upstream client for server '${serverName}'`);\n }\n return client.callTool({\n name: toolName,\n arguments: args as Record<string, unknown>,\n });\n }\n\n /**\n * Re-queries all upstream servers for their tool lists and rebuilds the\n * registry. Called on startup and whenever a tools/list-changed notification\n * arrives from any upstream.\n */\n async refreshRegistry(): Promise<ToolWithOrigin[]> {\n this.registry.clear();\n\n // Gather tools per server\n const perServer = new Map<string, Tool[]>();\n for (const [serverName, client] of this.clients) {\n try {\n const { tools } = await client.listTools();\n perServer.set(serverName, tools);\n } catch (err) {\n process.stderr.write(\n `[cordon] warn: failed to list tools from '${serverName}': ${String(err)}\\n`,\n );\n perServer.set(serverName, []);\n }\n }\n\n // Count name occurrences to detect collisions\n const nameCounts = new Map<string, number>();\n for (const tools of perServer.values()) {\n for (const tool of tools) {\n nameCounts.set(tool.name, (nameCounts.get(tool.name) ?? 0) + 1);\n }\n }\n\n // Build registry; namespace only on collision\n for (const [serverName, tools] of perServer) {\n for (const tool of tools) {\n const collision = (nameCounts.get(tool.name) ?? 0) > 1;\n const proxyName = collision ? `${serverName}__${tool.name}` : tool.name;\n this.registry.set(proxyName, {\n ...tool,\n serverName,\n originalName: tool.name,\n proxyName,\n });\n }\n }\n\n return [...this.registry.values()];\n }\n\n // ── Private ──────────────────────────────────────────────────────────────────\n\n private async connectServer(cfg: StdioServerConfig): Promise<void> {\n const client = new Client({ name: 'cordon', version: '0.1.0' });\n const transport = new StdioClientTransport({\n command: cfg.command,\n args: cfg.args ?? [],\n env: cfg.env,\n });\n\n try {\n await client.connect(transport);\n this.clients.set(cfg.name, client);\n process.stderr.write(`[cordon] connected to '${cfg.name}'\\n`);\n } catch (err) {\n throw new Error(`Failed to connect to upstream '${cfg.name}': ${String(err)}`);\n }\n }\n}\n","import type { AuditLogger } from '../audit/logger.js';\nimport type { ApprovalManager } from '../approvals/manager.js';\nimport type { PolicyEngine } from '../policies/engine.js';\nimport type { UpstreamManager, ToolCallResponse } from './upstream-manager.js';\n\n/**\n * The hot path. Every tools/call from the LLM client flows through here.\n *\n * Flow:\n * 1. Resolve proxy tool name → server + original tool name\n * 2. Audit: received\n * 3. Evaluate policy → allow / block / approve\n * 4. If approve: await human decision\n * 5. Forward to upstream server\n * 6. Audit: completed\n * 7. Return result to LLM\n */\nexport class Interceptor {\n constructor(\n private upstream: UpstreamManager,\n private policy: PolicyEngine,\n private approvals: ApprovalManager,\n private audit: AuditLogger,\n ) {}\n\n async handle(proxyToolName: string, args: unknown): Promise<ToolCallResponse> {\n const tool = this.upstream.resolve(proxyToolName);\n if (!tool) {\n return errorResult(`Unknown tool: ${proxyToolName}`);\n }\n\n const callId = crypto.randomUUID();\n const { serverName, originalName } = tool;\n const start = Date.now();\n\n // 1. Audit\n this.audit.log({\n event: 'tool_call_received',\n callId,\n serverName,\n toolName: originalName,\n proxyName: proxyToolName,\n args,\n });\n\n // 2. Policy\n const decision = this.policy.evaluate(serverName, originalName);\n\n if (decision.action === 'block') {\n this.audit.log({\n event: 'tool_call_blocked',\n callId,\n serverName,\n toolName: originalName,\n reason: decision.reason,\n });\n return errorResult(decision.reason);\n }\n\n if (decision.action === 'approve') {\n this.audit.log({ event: 'approval_requested', callId, serverName, toolName: originalName });\n\n const result = await this.approvals.request({ callId, serverName, toolName: originalName, args });\n\n if (!result.approved) {\n this.audit.log({\n event: 'tool_call_denied',\n callId,\n serverName,\n toolName: originalName,\n reason: result.reason,\n });\n return errorResult(`Denied: ${result.reason}`);\n }\n\n this.audit.log({ event: 'tool_call_approved', callId, serverName, toolName: originalName });\n } else {\n this.audit.log({ event: 'tool_call_allowed', callId, serverName, toolName: originalName });\n }\n\n // 3. Forward to upstream\n try {\n const response = await this.upstream.callTool(serverName, originalName, args);\n this.audit.log({\n event: 'tool_call_completed',\n callId,\n serverName,\n toolName: originalName,\n isError: Boolean((response as { isError?: boolean }).isError),\n durationMs: Date.now() - start,\n });\n return response;\n } catch (err) {\n this.audit.log({\n event: 'tool_call_errored',\n callId,\n serverName,\n toolName: originalName,\n error: String(err),\n durationMs: Date.now() - start,\n });\n return errorResult(`Upstream error from '${serverName}': ${String(err)}`);\n }\n }\n}\n\nfunction errorResult(message: string): ToolCallResponse {\n return {\n content: [{ type: 'text', text: `[cordon] ${message}` }],\n isError: true,\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@getcordon/core",
3
+ "version": "0.1.0",
4
+ "description": "Core proxy engine for Cordon — the security gateway for AI agents",
5
+ "author": "marras0914",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/marras0914/cordon",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/marras0914/cordon.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/marras0914/cordon/issues"
15
+ },
16
+ "keywords": ["mcp", "security", "gateway", "proxy", "ai", "llm", "policy", "cordon"],
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ }
25
+ },
26
+ "files": ["dist"],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "test": "vitest run"
31
+ },
32
+ "dependencies": {
33
+ "cordon-sdk": "*",
34
+ "@modelcontextprotocol/sdk": "^1.11.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "typescript": "^5.6.0",
39
+ "tsup": "^8.3.0",
40
+ "vitest": "^2.1.0"
41
+ }
42
+ }