aegis-mcp-server 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.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/services/enforcement-engine.d.ts +64 -0
- package/dist/services/enforcement-engine.d.ts.map +1 -0
- package/dist/services/enforcement-engine.js +271 -0
- package/dist/services/enforcement-engine.js.map +1 -0
- package/dist/services/policy-loader.d.ts +56 -0
- package/dist/services/policy-loader.d.ts.map +1 -0
- package/dist/services/policy-loader.js +202 -0
- package/dist/services/policy-loader.js.map +1 -0
- package/dist/tools/file-tools.d.ts +21 -0
- package/dist/tools/file-tools.d.ts.map +1 -0
- package/dist/tools/file-tools.js +369 -0
- package/dist/tools/file-tools.js.map +1 -0
- package/dist/types.d.ts +286 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
- package/src/index.ts +171 -0
- package/src/services/enforcement-engine.ts +322 -0
- package/src/services/policy-loader.ts +255 -0
- package/src/tools/file-tools.ts +453 -0
- package/src/types.ts +305 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PolicyLoader — Reads .agentpolicy/ files into process memory.
|
|
3
|
+
*
|
|
4
|
+
* Core of the zero-token-overhead design. All governance files are loaded
|
|
5
|
+
* into Node.js process memory on startup. The agent never sees these files —
|
|
6
|
+
* it only sees tool call results (allowed/blocked).
|
|
7
|
+
*
|
|
8
|
+
* Role resolution merges the skeleton fields (role.name, scope.primary_paths)
|
|
9
|
+
* with extension fields (paths.read/write, forbidden_actions) into a single
|
|
10
|
+
* ResolvedRole for fast enforcement lookups.
|
|
11
|
+
*/
|
|
12
|
+
import type { PolicyState, ResolvedRole, AegisMcpConfig } from '../types.js';
|
|
13
|
+
export declare class PolicyLoader {
|
|
14
|
+
private config;
|
|
15
|
+
private state;
|
|
16
|
+
private watcher;
|
|
17
|
+
private onReload?;
|
|
18
|
+
constructor(config: AegisMcpConfig);
|
|
19
|
+
/**
|
|
20
|
+
* Load all policy files into memory. Call once on startup.
|
|
21
|
+
*/
|
|
22
|
+
load(): Promise<PolicyState>;
|
|
23
|
+
/**
|
|
24
|
+
* Get current policy state. Throws if not loaded.
|
|
25
|
+
*/
|
|
26
|
+
getState(): PolicyState;
|
|
27
|
+
/**
|
|
28
|
+
* Start watching .agentpolicy/ for changes and auto-reload.
|
|
29
|
+
*/
|
|
30
|
+
startWatching(onReload?: () => void): void;
|
|
31
|
+
/**
|
|
32
|
+
* Stop watching and clean up.
|
|
33
|
+
*/
|
|
34
|
+
stopWatching(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Get the resolved role for the configured agent, falling back to default.
|
|
37
|
+
*/
|
|
38
|
+
getActiveRole(): ResolvedRole;
|
|
39
|
+
private resolvePolicyDir;
|
|
40
|
+
private loadJson;
|
|
41
|
+
private loadRoles;
|
|
42
|
+
/**
|
|
43
|
+
* Merge skeleton and extension fields into a single ResolvedRole.
|
|
44
|
+
*
|
|
45
|
+
* Skeleton: role.name, role.purpose, scope.primary_paths/secondary_paths/excluded_paths
|
|
46
|
+
* Extensions: paths.read/write, forbidden_actions, autonomy (flat string)
|
|
47
|
+
*
|
|
48
|
+
* For writable paths: scope.primary_paths takes precedence; paths.write used as fallback.
|
|
49
|
+
* For readable paths: paths.read used when present; otherwise derived from writable + secondary.
|
|
50
|
+
*/
|
|
51
|
+
private resolveRole;
|
|
52
|
+
private handleChange;
|
|
53
|
+
private assertExists;
|
|
54
|
+
private log;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=policy-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-loader.d.ts","sourceRoot":"","sources":["../../src/services/policy-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EACV,WAAW,EAIX,YAAY,EACZ,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,qBAAa,YAAY;IAKX,OAAO,CAAC,MAAM;IAJ1B,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,QAAQ,CAAC,CAAa;gBAEV,MAAM,EAAE,cAAc;IAE1C;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA4BlC;;OAEG;IACH,QAAQ,IAAI,WAAW;IAOvB;;OAEG;IACH,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IAgB1C;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAOnC;;OAEG;IACH,aAAa,IAAI,YAAY;IA8B7B,OAAO,CAAC,gBAAgB;YAOV,QAAQ;YAYR,SAAS;IAyBvB;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW;YA4CL,YAAY;YAYZ,YAAY;IAQ1B,OAAO,CAAC,GAAG;CAGZ"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PolicyLoader — Reads .agentpolicy/ files into process memory.
|
|
3
|
+
*
|
|
4
|
+
* Core of the zero-token-overhead design. All governance files are loaded
|
|
5
|
+
* into Node.js process memory on startup. The agent never sees these files —
|
|
6
|
+
* it only sees tool call results (allowed/blocked).
|
|
7
|
+
*
|
|
8
|
+
* Role resolution merges the skeleton fields (role.name, scope.primary_paths)
|
|
9
|
+
* with extension fields (paths.read/write, forbidden_actions) into a single
|
|
10
|
+
* ResolvedRole for fast enforcement lookups.
|
|
11
|
+
*/
|
|
12
|
+
import { readFile, readdir, access } from 'node:fs/promises';
|
|
13
|
+
import { join, basename } from 'node:path';
|
|
14
|
+
import { watch } from 'chokidar';
|
|
15
|
+
export class PolicyLoader {
|
|
16
|
+
config;
|
|
17
|
+
state = null;
|
|
18
|
+
watcher = null;
|
|
19
|
+
onReload;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Load all policy files into memory. Call once on startup.
|
|
25
|
+
*/
|
|
26
|
+
async load() {
|
|
27
|
+
const policyDir = this.resolvePolicyDir();
|
|
28
|
+
await this.assertExists(policyDir, 'Policy directory');
|
|
29
|
+
const constitution = await this.loadJson(join(policyDir, 'constitution.json'), 'constitution.json');
|
|
30
|
+
const governance = await this.loadJson(join(policyDir, 'governance.json'), 'governance.json');
|
|
31
|
+
const roles = await this.loadRoles(join(policyDir, 'roles'));
|
|
32
|
+
this.state = {
|
|
33
|
+
constitution,
|
|
34
|
+
governance,
|
|
35
|
+
roles,
|
|
36
|
+
projectRoot: this.config.projectRoot,
|
|
37
|
+
policyDir,
|
|
38
|
+
};
|
|
39
|
+
this.log(`Policy loaded: ${roles.size} role(s)`);
|
|
40
|
+
return this.state;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get current policy state. Throws if not loaded.
|
|
44
|
+
*/
|
|
45
|
+
getState() {
|
|
46
|
+
if (!this.state) {
|
|
47
|
+
throw new Error('Policy not loaded. Call load() first.');
|
|
48
|
+
}
|
|
49
|
+
return this.state;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Start watching .agentpolicy/ for changes and auto-reload.
|
|
53
|
+
*/
|
|
54
|
+
startWatching(onReload) {
|
|
55
|
+
this.onReload = onReload;
|
|
56
|
+
const policyDir = this.resolvePolicyDir();
|
|
57
|
+
this.watcher = watch(policyDir, {
|
|
58
|
+
ignoreInitial: true,
|
|
59
|
+
awaitWriteFinish: { stabilityThreshold: 300 },
|
|
60
|
+
});
|
|
61
|
+
this.watcher.on('change', (path) => this.handleChange(path));
|
|
62
|
+
this.watcher.on('add', (path) => this.handleChange(path));
|
|
63
|
+
this.watcher.on('unlink', (path) => this.handleChange(path));
|
|
64
|
+
this.log('Watching policy directory for changes');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stop watching and clean up.
|
|
68
|
+
*/
|
|
69
|
+
async stopWatching() {
|
|
70
|
+
if (this.watcher) {
|
|
71
|
+
await this.watcher.close();
|
|
72
|
+
this.watcher = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the resolved role for the configured agent, falling back to default.
|
|
77
|
+
*/
|
|
78
|
+
getActiveRole() {
|
|
79
|
+
const state = this.getState();
|
|
80
|
+
const roleId = this.config.role;
|
|
81
|
+
const role = state.roles.get(roleId);
|
|
82
|
+
if (role)
|
|
83
|
+
return role;
|
|
84
|
+
const defaultRole = state.roles.get('default');
|
|
85
|
+
if (defaultRole) {
|
|
86
|
+
this.log(`Role "${roleId}" not found, using default`);
|
|
87
|
+
return defaultRole;
|
|
88
|
+
}
|
|
89
|
+
// Synthesize a permissive default if no role files exist
|
|
90
|
+
this.log('No role files found, using synthesized permissive default');
|
|
91
|
+
return {
|
|
92
|
+
id: 'default',
|
|
93
|
+
name: 'default',
|
|
94
|
+
purpose: 'Synthesized default role — no role files found',
|
|
95
|
+
writable_paths: ['**/*'],
|
|
96
|
+
secondary_paths: [],
|
|
97
|
+
excluded_paths: [],
|
|
98
|
+
readable_paths: ['**/*'],
|
|
99
|
+
autonomy: 'advisory',
|
|
100
|
+
forbidden_actions: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// ─── Private ────────────────────────────────────────────────────────────────
|
|
104
|
+
resolvePolicyDir() {
|
|
105
|
+
return join(this.config.projectRoot, this.config.policyDir ?? '.agentpolicy');
|
|
106
|
+
}
|
|
107
|
+
async loadJson(path, label) {
|
|
108
|
+
await this.assertExists(path, label);
|
|
109
|
+
const raw = await readFile(path, 'utf-8');
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(raw);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
throw new Error(`Failed to parse ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async loadRoles(rolesDir) {
|
|
118
|
+
const roles = new Map();
|
|
119
|
+
try {
|
|
120
|
+
await access(rolesDir);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return roles;
|
|
124
|
+
}
|
|
125
|
+
const entries = await readdir(rolesDir, { withFileTypes: true });
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (!entry.isFile() || !entry.name.endsWith('.json'))
|
|
128
|
+
continue;
|
|
129
|
+
const roleId = basename(entry.name, '.json');
|
|
130
|
+
const raw = await this.loadJson(join(rolesDir, entry.name), `roles/${entry.name}`);
|
|
131
|
+
roles.set(roleId, this.resolveRole(roleId, raw));
|
|
132
|
+
}
|
|
133
|
+
return roles;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Merge skeleton and extension fields into a single ResolvedRole.
|
|
137
|
+
*
|
|
138
|
+
* Skeleton: role.name, role.purpose, scope.primary_paths/secondary_paths/excluded_paths
|
|
139
|
+
* Extensions: paths.read/write, forbidden_actions, autonomy (flat string)
|
|
140
|
+
*
|
|
141
|
+
* For writable paths: scope.primary_paths takes precedence; paths.write used as fallback.
|
|
142
|
+
* For readable paths: paths.read used when present; otherwise derived from writable + secondary.
|
|
143
|
+
*/
|
|
144
|
+
resolveRole(id, raw) {
|
|
145
|
+
// Role identity — skeleton nested object, or flat string + description
|
|
146
|
+
const name = typeof raw.role === 'object' ? raw.role.name : String(raw.role);
|
|
147
|
+
const purpose = typeof raw.role === 'object'
|
|
148
|
+
? raw.role.purpose
|
|
149
|
+
: (raw.description ?? '');
|
|
150
|
+
// Writable paths — skeleton primary_paths, or extension paths.write
|
|
151
|
+
const writable_paths = raw.scope?.primary_paths?.length
|
|
152
|
+
? raw.scope.primary_paths
|
|
153
|
+
: (raw.paths?.write ?? []);
|
|
154
|
+
// Secondary paths
|
|
155
|
+
const secondary_paths = raw.scope?.secondary_paths ?? [];
|
|
156
|
+
// Excluded paths
|
|
157
|
+
const excluded_paths = raw.scope?.excluded_paths ?? [];
|
|
158
|
+
// Readable paths — extension paths.read, or all writable + secondary
|
|
159
|
+
const readable_paths = raw.paths?.read?.length
|
|
160
|
+
? raw.paths.read
|
|
161
|
+
: [...writable_paths, ...secondary_paths];
|
|
162
|
+
// Autonomy — flat extension string or skeleton override
|
|
163
|
+
const autonomy = raw.autonomy
|
|
164
|
+
? String(raw.autonomy)
|
|
165
|
+
: 'advisory';
|
|
166
|
+
// Forbidden actions
|
|
167
|
+
const forbidden_actions = raw.forbidden_actions ?? [];
|
|
168
|
+
return {
|
|
169
|
+
id,
|
|
170
|
+
name,
|
|
171
|
+
purpose,
|
|
172
|
+
writable_paths,
|
|
173
|
+
secondary_paths,
|
|
174
|
+
excluded_paths,
|
|
175
|
+
readable_paths,
|
|
176
|
+
autonomy,
|
|
177
|
+
forbidden_actions,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async handleChange(path) {
|
|
181
|
+
this.log(`Policy file changed: ${path}`);
|
|
182
|
+
try {
|
|
183
|
+
await this.load();
|
|
184
|
+
this.onReload?.();
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
this.log(`Failed to reload policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async assertExists(path, label) {
|
|
191
|
+
try {
|
|
192
|
+
await access(path);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
throw new Error(`${label} not found at: ${path}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
log(message) {
|
|
199
|
+
process.stderr.write(`[aegis-mcp] ${message}\n`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=policy-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-loader.js","sourceRoot":"","sources":["../../src/services/policy-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governed File Tools — MCP tool registrations for file operations.
|
|
3
|
+
*
|
|
4
|
+
* These are the tools agents call instead of raw file system access.
|
|
5
|
+
* Every call is validated against the loaded policy before execution.
|
|
6
|
+
* The agent never sees the policy — only the verdict.
|
|
7
|
+
*
|
|
8
|
+
* Tools:
|
|
9
|
+
* aegis_check_permissions — Pre-check before writing (saves wasted generation)
|
|
10
|
+
* aegis_write_file — Governed write with path + content validation
|
|
11
|
+
* aegis_read_file — Governed read with path validation
|
|
12
|
+
* aegis_delete_file — Governed delete (uses write permissions)
|
|
13
|
+
* aegis_execute — Governed command execution
|
|
14
|
+
* aegis_complete_task — Task completion with quality gate validation
|
|
15
|
+
* aegis_policy_summary — Minimal role/permissions summary (~200 tokens)
|
|
16
|
+
*/
|
|
17
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
|
+
import type { EnforcementEngine } from '../services/enforcement-engine.js';
|
|
19
|
+
import type { PolicyState, ResolvedRole } from '../types.js';
|
|
20
|
+
export declare function registerTools(server: McpServer, getEngine: () => EnforcementEngine, getState: () => PolicyState, getRole: () => ResolvedRole): void;
|
|
21
|
+
//# sourceMappingURL=file-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-tools.d.ts","sourceRoot":"","sources":["../../src/tools/file-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,iBAAiB,EAClC,QAAQ,EAAE,MAAM,WAAW,EAC3B,OAAO,EAAE,MAAM,YAAY,GAC1B,IAAI,CAiYN"}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governed File Tools — MCP tool registrations for file operations.
|
|
3
|
+
*
|
|
4
|
+
* These are the tools agents call instead of raw file system access.
|
|
5
|
+
* Every call is validated against the loaded policy before execution.
|
|
6
|
+
* The agent never sees the policy — only the verdict.
|
|
7
|
+
*
|
|
8
|
+
* Tools:
|
|
9
|
+
* aegis_check_permissions — Pre-check before writing (saves wasted generation)
|
|
10
|
+
* aegis_write_file — Governed write with path + content validation
|
|
11
|
+
* aegis_read_file — Governed read with path validation
|
|
12
|
+
* aegis_delete_file — Governed delete (uses write permissions)
|
|
13
|
+
* aegis_execute — Governed command execution
|
|
14
|
+
* aegis_complete_task — Task completion with quality gate validation
|
|
15
|
+
* aegis_policy_summary — Minimal role/permissions summary (~200 tokens)
|
|
16
|
+
*/
|
|
17
|
+
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
18
|
+
import { dirname, join, isAbsolute } from 'node:path';
|
|
19
|
+
import { execSync } from 'node:child_process';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
export function registerTools(server, getEngine, getState, getRole) {
|
|
22
|
+
// ─── aegis_check_permissions ──────────────────────────────────────────────
|
|
23
|
+
server.registerTool('aegis_check_permissions', {
|
|
24
|
+
title: 'Check Permissions',
|
|
25
|
+
description: `Check if an operation is allowed on a path before attempting it. Use this to pre-validate before writing or reading files — saves you from composing content that would be blocked.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
- path (string): Target file path relative to project root
|
|
29
|
+
- operation ('read' | 'write' | 'delete'): The operation to check
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
{ "allowed": true } or { "allowed": false, "reason": "..." }`,
|
|
33
|
+
inputSchema: {
|
|
34
|
+
path: z.string().describe('Target file path relative to project root'),
|
|
35
|
+
operation: z.enum(['read', 'write', 'delete']).describe('Operation to check'),
|
|
36
|
+
},
|
|
37
|
+
annotations: {
|
|
38
|
+
readOnlyHint: true,
|
|
39
|
+
destructiveHint: false,
|
|
40
|
+
idempotentHint: true,
|
|
41
|
+
openWorldHint: false,
|
|
42
|
+
},
|
|
43
|
+
}, async ({ path, operation }) => {
|
|
44
|
+
const engine = getEngine();
|
|
45
|
+
const verdict = operation === 'read'
|
|
46
|
+
? engine.validateRead(path)
|
|
47
|
+
: engine.validateWrite(path);
|
|
48
|
+
return {
|
|
49
|
+
content: [{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: JSON.stringify(verdict.allowed
|
|
52
|
+
? { allowed: true }
|
|
53
|
+
: { allowed: false, reason: verdict.reason }),
|
|
54
|
+
}],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
// ─── aegis_write_file ─────────────────────────────────────────────────────
|
|
58
|
+
server.registerTool('aegis_write_file', {
|
|
59
|
+
title: 'Write File (Governed)',
|
|
60
|
+
description: `Write content to a file with governance enforcement. Path is validated against your role's permissions and governance boundaries. Content is scanned for sensitive patterns. If the write violates policy, it is blocked and you receive the specific reason.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
- path (string): File path relative to project root
|
|
64
|
+
- content (string): File content to write
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
{ "status": "success", "path": "..." } or { "status": "blocked", "reason": "..." }`,
|
|
68
|
+
inputSchema: {
|
|
69
|
+
path: z.string().describe('File path relative to project root'),
|
|
70
|
+
content: z.string().describe('File content to write'),
|
|
71
|
+
},
|
|
72
|
+
annotations: {
|
|
73
|
+
readOnlyHint: false,
|
|
74
|
+
destructiveHint: true,
|
|
75
|
+
idempotentHint: true,
|
|
76
|
+
openWorldHint: false,
|
|
77
|
+
},
|
|
78
|
+
}, async ({ path, content }) => {
|
|
79
|
+
const engine = getEngine();
|
|
80
|
+
const state = getState();
|
|
81
|
+
const role = getRole();
|
|
82
|
+
// Validate path permissions
|
|
83
|
+
const pathVerdict = engine.validateWrite(path);
|
|
84
|
+
if (!pathVerdict.allowed) {
|
|
85
|
+
await logBlocked(engine, role, path, 'write', pathVerdict.reason);
|
|
86
|
+
return blocked(pathVerdict.reason);
|
|
87
|
+
}
|
|
88
|
+
// Scan content for sensitive patterns
|
|
89
|
+
const contentVerdict = engine.scanContent(content, path);
|
|
90
|
+
if (!contentVerdict.allowed) {
|
|
91
|
+
await logBlocked(engine, role, path, 'write (sensitive content)', contentVerdict.reason);
|
|
92
|
+
return blocked(contentVerdict.reason);
|
|
93
|
+
}
|
|
94
|
+
// Write the file
|
|
95
|
+
const absPath = toAbsolute(path, state.projectRoot);
|
|
96
|
+
await mkdir(dirname(absPath), { recursive: true });
|
|
97
|
+
await writeFile(absPath, content, 'utf-8');
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: JSON.stringify({ status: 'success', path }),
|
|
102
|
+
}],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
// ─── aegis_read_file ──────────────────────────────────────────────────────
|
|
106
|
+
server.registerTool('aegis_read_file', {
|
|
107
|
+
title: 'Read File (Governed)',
|
|
108
|
+
description: `Read the contents of a file with governance enforcement. Path is validated against your role's read permissions. If the read violates policy, it is blocked.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
- path (string): File path relative to project root
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
File content as text, or { "status": "blocked", "reason": "..." }`,
|
|
115
|
+
inputSchema: {
|
|
116
|
+
path: z.string().describe('File path relative to project root'),
|
|
117
|
+
},
|
|
118
|
+
annotations: {
|
|
119
|
+
readOnlyHint: true,
|
|
120
|
+
destructiveHint: false,
|
|
121
|
+
idempotentHint: true,
|
|
122
|
+
openWorldHint: false,
|
|
123
|
+
},
|
|
124
|
+
}, async ({ path }) => {
|
|
125
|
+
const engine = getEngine();
|
|
126
|
+
const state = getState();
|
|
127
|
+
const verdict = engine.validateRead(path);
|
|
128
|
+
if (!verdict.allowed) {
|
|
129
|
+
return blocked(verdict.reason);
|
|
130
|
+
}
|
|
131
|
+
const absPath = toAbsolute(path, state.projectRoot);
|
|
132
|
+
const content = await readFile(absPath, 'utf-8');
|
|
133
|
+
return {
|
|
134
|
+
content: [{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: content,
|
|
137
|
+
}],
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
// ─── aegis_delete_file ────────────────────────────────────────────────────
|
|
141
|
+
server.registerTool('aegis_delete_file', {
|
|
142
|
+
title: 'Delete File (Governed)',
|
|
143
|
+
description: `Delete a file with governance enforcement. Write permissions are required. If the delete violates policy, it is blocked.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
- path (string): File path relative to project root
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
{ "status": "success", "path": "..." } or { "status": "blocked", "reason": "..." }`,
|
|
150
|
+
inputSchema: {
|
|
151
|
+
path: z.string().describe('File path relative to project root'),
|
|
152
|
+
},
|
|
153
|
+
annotations: {
|
|
154
|
+
readOnlyHint: false,
|
|
155
|
+
destructiveHint: true,
|
|
156
|
+
idempotentHint: false,
|
|
157
|
+
openWorldHint: false,
|
|
158
|
+
},
|
|
159
|
+
}, async ({ path }) => {
|
|
160
|
+
const engine = getEngine();
|
|
161
|
+
const state = getState();
|
|
162
|
+
const role = getRole();
|
|
163
|
+
const verdict = engine.validateWrite(path);
|
|
164
|
+
if (!verdict.allowed) {
|
|
165
|
+
await logBlocked(engine, role, path, 'delete', verdict.reason);
|
|
166
|
+
return blocked(verdict.reason);
|
|
167
|
+
}
|
|
168
|
+
const absPath = toAbsolute(path, state.projectRoot);
|
|
169
|
+
await unlink(absPath);
|
|
170
|
+
return {
|
|
171
|
+
content: [{
|
|
172
|
+
type: 'text',
|
|
173
|
+
text: JSON.stringify({ status: 'success', path }),
|
|
174
|
+
}],
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
// ─── aegis_execute ────────────────────────────────────────────────────────
|
|
178
|
+
server.registerTool('aegis_execute', {
|
|
179
|
+
title: 'Execute Command (Governed)',
|
|
180
|
+
description: `Execute a shell command in the project directory. Currently validates that the command runs within the project root. Future versions will enforce command-level permissions.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
- command (string): Shell command to execute
|
|
184
|
+
- cwd (string, optional): Working directory (defaults to project root)
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
{ "status": "success", "stdout": "...", "stderr": "..." } or { "status": "error", ... }`,
|
|
188
|
+
inputSchema: {
|
|
189
|
+
command: z.string().describe('Shell command to execute'),
|
|
190
|
+
cwd: z.string().optional().describe('Working directory (defaults to project root)'),
|
|
191
|
+
},
|
|
192
|
+
annotations: {
|
|
193
|
+
readOnlyHint: false,
|
|
194
|
+
destructiveHint: true,
|
|
195
|
+
idempotentHint: false,
|
|
196
|
+
openWorldHint: true,
|
|
197
|
+
},
|
|
198
|
+
}, async ({ command, cwd }) => {
|
|
199
|
+
const state = getState();
|
|
200
|
+
try {
|
|
201
|
+
const result = execSync(command, {
|
|
202
|
+
cwd: cwd ?? state.projectRoot,
|
|
203
|
+
encoding: 'utf-8',
|
|
204
|
+
timeout: 60_000,
|
|
205
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
content: [{
|
|
209
|
+
type: 'text',
|
|
210
|
+
text: JSON.stringify({ status: 'success', stdout: result, stderr: '' }),
|
|
211
|
+
}],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
const execErr = err;
|
|
216
|
+
return {
|
|
217
|
+
isError: true,
|
|
218
|
+
content: [{
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: JSON.stringify({
|
|
221
|
+
status: 'error',
|
|
222
|
+
stdout: execErr.stdout ?? '',
|
|
223
|
+
stderr: execErr.stderr ?? execErr.message ?? 'Unknown error',
|
|
224
|
+
}),
|
|
225
|
+
}],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// ─── aegis_complete_task ──────────────────────────────────────────────────
|
|
230
|
+
server.registerTool('aegis_complete_task', {
|
|
231
|
+
title: 'Complete Task',
|
|
232
|
+
description: `Signal task completion and run required quality gates. Maps the governance quality_gate.pre_commit flags to build_commands and runs each required check. Returns pass/fail with details.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
- task_id (string): Identifier for the task being completed
|
|
236
|
+
- summary (string): Brief summary of what was accomplished
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
{ "status": "passed", "gates_run": [...] } or { "status": "failed", "failures": [...] }`,
|
|
240
|
+
inputSchema: {
|
|
241
|
+
task_id: z.string().describe('Task identifier'),
|
|
242
|
+
summary: z.string().describe('Summary of completed work'),
|
|
243
|
+
},
|
|
244
|
+
annotations: {
|
|
245
|
+
readOnlyHint: false,
|
|
246
|
+
destructiveHint: false,
|
|
247
|
+
idempotentHint: true,
|
|
248
|
+
openWorldHint: false,
|
|
249
|
+
},
|
|
250
|
+
}, async ({ task_id, summary }) => {
|
|
251
|
+
const engine = getEngine();
|
|
252
|
+
const state = getState();
|
|
253
|
+
const gates = engine.getQualityGateCommands();
|
|
254
|
+
if (gates.length === 0) {
|
|
255
|
+
return {
|
|
256
|
+
content: [{
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: JSON.stringify({
|
|
259
|
+
status: 'passed',
|
|
260
|
+
task_id,
|
|
261
|
+
summary,
|
|
262
|
+
gates_run: [],
|
|
263
|
+
message: 'No quality gates configured with matching build commands.',
|
|
264
|
+
}),
|
|
265
|
+
}],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const results = [];
|
|
269
|
+
for (const gate of gates) {
|
|
270
|
+
try {
|
|
271
|
+
const output = execSync(gate.command, {
|
|
272
|
+
cwd: state.projectRoot,
|
|
273
|
+
encoding: 'utf-8',
|
|
274
|
+
timeout: 120_000,
|
|
275
|
+
});
|
|
276
|
+
results.push({ name: gate.name, passed: true, output: output.slice(0, 500) });
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
const execErr = err;
|
|
280
|
+
results.push({
|
|
281
|
+
name: gate.name,
|
|
282
|
+
passed: false,
|
|
283
|
+
output: (execErr.stderr ?? execErr.message ?? 'Failed').slice(0, 500),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const allPassed = results.every((r) => r.passed);
|
|
288
|
+
return {
|
|
289
|
+
content: [{
|
|
290
|
+
type: 'text',
|
|
291
|
+
text: JSON.stringify({
|
|
292
|
+
status: allPassed ? 'passed' : 'failed',
|
|
293
|
+
task_id,
|
|
294
|
+
summary,
|
|
295
|
+
gates_run: results,
|
|
296
|
+
}),
|
|
297
|
+
}],
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
// ─── aegis_policy_summary ─────────────────────────────────────────────────
|
|
301
|
+
server.registerTool('aegis_policy_summary', {
|
|
302
|
+
title: 'Policy Summary',
|
|
303
|
+
description: `Get a minimal summary of your current role and permissions. Returns your role name, writable paths, excluded paths, forbidden actions, and key governance rules — just enough to understand your boundaries without loading full policy files.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
{ "role": "...", "writable_paths": [...], "forbidden_actions": [...], ... }`,
|
|
307
|
+
inputSchema: {},
|
|
308
|
+
annotations: {
|
|
309
|
+
readOnlyHint: true,
|
|
310
|
+
destructiveHint: false,
|
|
311
|
+
idempotentHint: true,
|
|
312
|
+
openWorldHint: false,
|
|
313
|
+
},
|
|
314
|
+
}, async () => {
|
|
315
|
+
const role = getRole();
|
|
316
|
+
const state = getState();
|
|
317
|
+
const protocol = state.governance.override_protocol;
|
|
318
|
+
const summary = {
|
|
319
|
+
role: role.id,
|
|
320
|
+
role_name: role.name,
|
|
321
|
+
purpose: role.purpose,
|
|
322
|
+
autonomy: role.autonomy,
|
|
323
|
+
writable_paths: role.writable_paths,
|
|
324
|
+
secondary_paths: role.secondary_paths,
|
|
325
|
+
excluded_paths: role.excluded_paths,
|
|
326
|
+
readable_paths: role.readable_paths,
|
|
327
|
+
forbidden_actions: role.forbidden_actions,
|
|
328
|
+
governance_forbidden_paths: state.governance.permissions.boundaries.forbidden ?? [],
|
|
329
|
+
override_behavior: protocol?.behavior ?? 'warn_confirm_and_log',
|
|
330
|
+
immutable_policies: protocol?.immutable_policies ?? [],
|
|
331
|
+
quality_gates: {
|
|
332
|
+
must_pass_tests: state.governance.quality_gate.pre_commit.must_pass_tests ?? false,
|
|
333
|
+
must_pass_lint: state.governance.quality_gate.pre_commit.must_pass_lint ?? false,
|
|
334
|
+
must_pass_typecheck: state.governance.quality_gate.pre_commit.must_pass_typecheck ?? false,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
return {
|
|
338
|
+
content: [{
|
|
339
|
+
type: 'text',
|
|
340
|
+
text: JSON.stringify(summary),
|
|
341
|
+
}],
|
|
342
|
+
};
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
346
|
+
function toAbsolute(path, projectRoot) {
|
|
347
|
+
return isAbsolute(path) ? path : join(projectRoot, path);
|
|
348
|
+
}
|
|
349
|
+
function blocked(reason) {
|
|
350
|
+
return {
|
|
351
|
+
isError: true,
|
|
352
|
+
content: [{
|
|
353
|
+
type: 'text',
|
|
354
|
+
text: JSON.stringify({ status: 'blocked', reason }),
|
|
355
|
+
}],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
async function logBlocked(engine, role, path, operation, reason) {
|
|
359
|
+
await engine.logOverride({
|
|
360
|
+
timestamp: new Date().toISOString(),
|
|
361
|
+
policy_violated: reason,
|
|
362
|
+
policy_text: reason,
|
|
363
|
+
action_requested: `${operation}: ${path}`,
|
|
364
|
+
human_confirmed: false,
|
|
365
|
+
agent_role: role.id,
|
|
366
|
+
rationale: 'Blocked by enforcement layer',
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
//# sourceMappingURL=file-tools.js.map
|