opalserve 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.
Files changed (158) hide show
  1. package/.env.example +19 -0
  2. package/AGENTS.md +23 -0
  3. package/README.md +109 -0
  4. package/config/servers.example.yaml +67 -0
  5. package/config/servers.yaml +2 -0
  6. package/dist/cli/discover.d.ts +3 -0
  7. package/dist/cli/discover.d.ts.map +1 -0
  8. package/dist/cli/discover.js +160 -0
  9. package/dist/cli/discover.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +32 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/connectors/base.d.ts +49 -0
  15. package/dist/connectors/base.d.ts.map +1 -0
  16. package/dist/connectors/base.js +45 -0
  17. package/dist/connectors/base.js.map +1 -0
  18. package/dist/connectors/custom.d.ts +19 -0
  19. package/dist/connectors/custom.d.ts.map +1 -0
  20. package/dist/connectors/custom.js +129 -0
  21. package/dist/connectors/custom.js.map +1 -0
  22. package/dist/connectors/github.d.ts +18 -0
  23. package/dist/connectors/github.d.ts.map +1 -0
  24. package/dist/connectors/github.js +188 -0
  25. package/dist/connectors/github.js.map +1 -0
  26. package/dist/connectors/google-drive.d.ts +18 -0
  27. package/dist/connectors/google-drive.d.ts.map +1 -0
  28. package/dist/connectors/google-drive.js +209 -0
  29. package/dist/connectors/google-drive.js.map +1 -0
  30. package/dist/connectors/index.d.ts +11 -0
  31. package/dist/connectors/index.d.ts.map +1 -0
  32. package/dist/connectors/index.js +76 -0
  33. package/dist/connectors/index.js.map +1 -0
  34. package/dist/connectors/postgres.d.ts +18 -0
  35. package/dist/connectors/postgres.d.ts.map +1 -0
  36. package/dist/connectors/postgres.js +140 -0
  37. package/dist/connectors/postgres.js.map +1 -0
  38. package/dist/connectors/slack.d.ts +18 -0
  39. package/dist/connectors/slack.d.ts.map +1 -0
  40. package/dist/connectors/slack.js +181 -0
  41. package/dist/connectors/slack.js.map +1 -0
  42. package/dist/core/auth.d.ts +26 -0
  43. package/dist/core/auth.d.ts.map +1 -0
  44. package/dist/core/auth.js +81 -0
  45. package/dist/core/auth.js.map +1 -0
  46. package/dist/core/registry.d.ts +33 -0
  47. package/dist/core/registry.d.ts.map +1 -0
  48. package/dist/core/registry.js +237 -0
  49. package/dist/core/registry.js.map +1 -0
  50. package/dist/core/tokenizer.d.ts +16 -0
  51. package/dist/core/tokenizer.d.ts.map +1 -0
  52. package/dist/core/tokenizer.js +29 -0
  53. package/dist/core/tokenizer.js.map +1 -0
  54. package/dist/governance/audit.d.ts +27 -0
  55. package/dist/governance/audit.d.ts.map +1 -0
  56. package/dist/governance/audit.js +149 -0
  57. package/dist/governance/audit.js.map +1 -0
  58. package/dist/governance/index.d.ts +5 -0
  59. package/dist/governance/index.d.ts.map +1 -0
  60. package/dist/governance/index.js +5 -0
  61. package/dist/governance/index.js.map +1 -0
  62. package/dist/governance/policy.d.ts +20 -0
  63. package/dist/governance/policy.d.ts.map +1 -0
  64. package/dist/governance/policy.js +162 -0
  65. package/dist/governance/policy.js.map +1 -0
  66. package/dist/governance/rate-limiter.d.ts +20 -0
  67. package/dist/governance/rate-limiter.d.ts.map +1 -0
  68. package/dist/governance/rate-limiter.js +73 -0
  69. package/dist/governance/rate-limiter.js.map +1 -0
  70. package/dist/governance/types.d.ts +246 -0
  71. package/dist/governance/types.d.ts.map +1 -0
  72. package/dist/governance/types.js +72 -0
  73. package/dist/governance/types.js.map +1 -0
  74. package/dist/identity/access-control.d.ts +15 -0
  75. package/dist/identity/access-control.d.ts.map +1 -0
  76. package/dist/identity/access-control.js +81 -0
  77. package/dist/identity/access-control.js.map +1 -0
  78. package/dist/identity/index.d.ts +4 -0
  79. package/dist/identity/index.d.ts.map +1 -0
  80. package/dist/identity/index.js +4 -0
  81. package/dist/identity/index.js.map +1 -0
  82. package/dist/identity/manager.d.ts +29 -0
  83. package/dist/identity/manager.d.ts.map +1 -0
  84. package/dist/identity/manager.js +167 -0
  85. package/dist/identity/manager.js.map +1 -0
  86. package/dist/identity/types.d.ts +237 -0
  87. package/dist/identity/types.d.ts.map +1 -0
  88. package/dist/identity/types.js +80 -0
  89. package/dist/identity/types.js.map +1 -0
  90. package/dist/index.d.ts +13 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +10 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/registry/server.d.ts +14 -0
  95. package/dist/registry/server.d.ts.map +1 -0
  96. package/dist/registry/server.js +173 -0
  97. package/dist/registry/server.js.map +1 -0
  98. package/dist/types/index.d.ts +639 -0
  99. package/dist/types/index.d.ts.map +1 -0
  100. package/dist/types/index.js +76 -0
  101. package/dist/types/index.js.map +1 -0
  102. package/dist/utils/config.d.ts +29 -0
  103. package/dist/utils/config.d.ts.map +1 -0
  104. package/dist/utils/config.js +47 -0
  105. package/dist/utils/config.js.map +1 -0
  106. package/dist/utils/index.d.ts +7 -0
  107. package/dist/utils/index.d.ts.map +1 -0
  108. package/dist/utils/index.js +44 -0
  109. package/dist/utils/index.js.map +1 -0
  110. package/dist/workflow/engine.d.ts +18 -0
  111. package/dist/workflow/engine.d.ts.map +1 -0
  112. package/dist/workflow/engine.js +155 -0
  113. package/dist/workflow/engine.js.map +1 -0
  114. package/dist/workflow/index.d.ts +4 -0
  115. package/dist/workflow/index.d.ts.map +1 -0
  116. package/dist/workflow/index.js +4 -0
  117. package/dist/workflow/index.js.map +1 -0
  118. package/dist/workflow/templates.d.ts +4 -0
  119. package/dist/workflow/templates.d.ts.map +1 -0
  120. package/dist/workflow/templates.js +218 -0
  121. package/dist/workflow/templates.js.map +1 -0
  122. package/dist/workflow/types.d.ts +255 -0
  123. package/dist/workflow/types.d.ts.map +1 -0
  124. package/dist/workflow/types.js +48 -0
  125. package/dist/workflow/types.js.map +1 -0
  126. package/eslint.config.js +25 -0
  127. package/package.json +78 -0
  128. package/src/cli/discover.ts +223 -0
  129. package/src/cli/index.ts +40 -0
  130. package/src/connectors/base.ts +75 -0
  131. package/src/connectors/custom.ts +139 -0
  132. package/src/connectors/github.ts +195 -0
  133. package/src/connectors/google-drive.ts +217 -0
  134. package/src/connectors/index.ts +86 -0
  135. package/src/connectors/postgres.ts +148 -0
  136. package/src/connectors/slack.ts +188 -0
  137. package/src/core/auth.ts +109 -0
  138. package/src/core/registry.ts +301 -0
  139. package/src/core/tokenizer.ts +40 -0
  140. package/src/governance/audit.ts +182 -0
  141. package/src/governance/index.ts +4 -0
  142. package/src/governance/policy.ts +187 -0
  143. package/src/governance/rate-limiter.ts +95 -0
  144. package/src/governance/types.ts +100 -0
  145. package/src/identity/access-control.ts +119 -0
  146. package/src/identity/index.ts +3 -0
  147. package/src/identity/manager.ts +207 -0
  148. package/src/identity/types.ts +91 -0
  149. package/src/index.ts +16 -0
  150. package/src/registry/server.ts +195 -0
  151. package/src/types/index.ts +128 -0
  152. package/src/utils/config.ts +78 -0
  153. package/src/utils/index.ts +47 -0
  154. package/src/workflow/engine.ts +187 -0
  155. package/src/workflow/index.ts +3 -0
  156. package/src/workflow/templates.ts +220 -0
  157. package/src/workflow/types.ts +89 -0
  158. package/tsconfig.json +25 -0
@@ -0,0 +1,109 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import type { Request, Response, NextFunction } from 'express';
3
+
4
+ export interface AuthContext {
5
+ agentId: string;
6
+ permissions: string[];
7
+ context: Record<string, unknown>;
8
+ }
9
+
10
+ export interface JWTPayload {
11
+ agentId: string;
12
+ permissions: string[];
13
+ context: Record<string, unknown>;
14
+ iat: number;
15
+ exp: number;
16
+ }
17
+
18
+ export class AuthService {
19
+ private secret: string;
20
+ private apiKey?: string;
21
+
22
+ constructor(secret: string, apiKey?: string) {
23
+ this.secret = secret;
24
+ this.apiKey = apiKey;
25
+ }
26
+
27
+ generateToken(context: AuthContext): string {
28
+ return jwt.sign(
29
+ {
30
+ agentId: context.agentId,
31
+ permissions: context.permissions,
32
+ context: context.context,
33
+ },
34
+ this.secret,
35
+ { expiresIn: '24h' }
36
+ );
37
+ }
38
+
39
+ verifyToken(token: string): AuthContext | null {
40
+ try {
41
+ const payload = jwt.verify(token, this.secret) as JWTPayload;
42
+ return {
43
+ agentId: payload.agentId,
44
+ permissions: payload.permissions,
45
+ context: payload.context,
46
+ };
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ verifyApiKey(key: string): boolean {
53
+ if (!this.apiKey) return false;
54
+ return key === this.apiKey;
55
+ }
56
+
57
+ extractAuth(req: Request): AuthContext | null {
58
+ const authHeader = req.headers.authorization;
59
+
60
+ if (authHeader?.startsWith('Bearer ')) {
61
+ const token = authHeader.slice(7);
62
+ return this.verifyToken(token);
63
+ }
64
+
65
+ const apiKey = req.headers['x-api-key'] as string;
66
+ if (apiKey && this.verifyApiKey(apiKey)) {
67
+ return {
68
+ agentId: 'api-key-user',
69
+ permissions: ['read', 'discover'],
70
+ context: {},
71
+ };
72
+ }
73
+
74
+ return null;
75
+ }
76
+
77
+ middleware() {
78
+ return (req: Request, res: Response, next: NextFunction) => {
79
+ const auth = this.extractAuth(req);
80
+ if (!auth) {
81
+ res.status(401).json({ error: 'Unauthorized' });
82
+ return;
83
+ }
84
+ (req as Request & { auth: AuthContext }).auth = auth;
85
+ next();
86
+ };
87
+ }
88
+
89
+ optionalAuth() {
90
+ return (req: Request, _res: Response, next: NextFunction) => {
91
+ const auth = this.extractAuth(req);
92
+ if (auth) {
93
+ (req as Request & { auth: AuthContext }).auth = auth;
94
+ }
95
+ next();
96
+ };
97
+ }
98
+
99
+ checkPermission(permission: string) {
100
+ return (req: Request, res: Response, next: NextFunction) => {
101
+ const auth = (req as Request & { auth: AuthContext }).auth;
102
+ if (!auth || !auth.permissions.includes(permission)) {
103
+ res.status(403).json({ error: 'Forbidden', required: permission });
104
+ return;
105
+ }
106
+ next();
107
+ };
108
+ }
109
+ }
@@ -0,0 +1,301 @@
1
+ import type { Tool, Server, DiscoveryQuery, DiscoveryResult, MCPServer } from '../types/index.js';
2
+ import { TokenCounter } from './tokenizer.js';
3
+
4
+ interface IndexedTool {
5
+ tool: Tool;
6
+ searchableText: string;
7
+ tokenCount: number;
8
+ }
9
+
10
+ export class ToolRegistry {
11
+ private tools: Map<string, IndexedTool> = new Map();
12
+ private servers: Map<string, Server> = new Map();
13
+ private serverInstances: Map<string, MCPServer> = new Map();
14
+ private tokenCounter: TokenCounter;
15
+
16
+ constructor(contextBudget: number = 128000) {
17
+ this.tokenCounter = new TokenCounter(contextBudget);
18
+ }
19
+
20
+ registerTool(tool: Tool): void {
21
+ const searchableText = this.buildSearchableText(tool);
22
+ const indexedTool: IndexedTool = {
23
+ tool,
24
+ searchableText,
25
+ tokenCount: this.tokenCounter.count(searchableText),
26
+ };
27
+ this.tools.set(tool.id, indexedTool);
28
+ }
29
+
30
+ registerServer(server: Server): void {
31
+ this.servers.set(server.id, server);
32
+ }
33
+
34
+ registerServerInstance(instance: MCPServer): void {
35
+ this.serverInstances.set(instance.id, instance);
36
+ }
37
+
38
+ removeTool(toolId: string): boolean {
39
+ return this.tools.delete(toolId);
40
+ }
41
+
42
+ removeServer(serverId: string): boolean {
43
+ this.servers.delete(serverId);
44
+ this.serverInstances.delete(serverId);
45
+ for (const [toolId, indexed] of this.tools) {
46
+ if (indexed.tool.serverId === serverId) {
47
+ this.tools.delete(toolId);
48
+ }
49
+ }
50
+ return true;
51
+ }
52
+
53
+ getTool(toolId: string): Tool | undefined {
54
+ return this.tools.get(toolId)?.tool;
55
+ }
56
+
57
+ getServer(serverId: string): Server | undefined {
58
+ return this.servers.get(serverId);
59
+ }
60
+
61
+ getAllTools(): Tool[] {
62
+ return Array.from(this.tools.values()).map(i => i.tool);
63
+ }
64
+
65
+ getAllServers(): Server[] {
66
+ return Array.from(this.servers.values());
67
+ }
68
+
69
+ getToolsByServer(serverId: string): Tool[] {
70
+ return Array.from(this.tools.values())
71
+ .filter(i => i.tool.serverId === serverId)
72
+ .map(i => i.tool);
73
+ }
74
+
75
+ async discover(query: DiscoveryQuery): Promise<DiscoveryResult> {
76
+ const tools = this.scoreAndFilterTools(query);
77
+ const servers = this.getServersForTools(tools.map(t => t.tool.serverId));
78
+ const contextUsed = this.calculateContextUsed(tools);
79
+ const queryInterpretation = this.interpretQuery(query);
80
+
81
+ return {
82
+ tools: tools.map(({ tool, relevanceScore, matchReasons, tokenEstimate }) => ({
83
+ tool,
84
+ relevanceScore,
85
+ matchReasons,
86
+ tokenEstimate,
87
+ })),
88
+ servers,
89
+ totalMatches: tools.length,
90
+ contextUsed,
91
+ contextBudget: query.contextBudget,
92
+ suggestions: this.generateSuggestions(query),
93
+ queryInterpretation,
94
+ };
95
+ }
96
+
97
+ private scoreAndFilterTools(query: DiscoveryQuery): Array<{
98
+ tool: Tool;
99
+ relevanceScore: number;
100
+ matchReasons: string[];
101
+ tokenEstimate: number;
102
+ }> {
103
+ const scored: Array<{
104
+ tool: Tool;
105
+ relevanceScore: number;
106
+ matchReasons: string[];
107
+ tokenEstimate: number;
108
+ }> = [];
109
+
110
+ let contextUsed = 0;
111
+ const headerTokens = 200;
112
+
113
+ for (const indexed of this.tools.values()) {
114
+ const { tool, tokenCount } = indexed;
115
+ if (contextUsed + tokenCount + headerTokens > query.contextBudget && scored.length > 0) {
116
+ break;
117
+ }
118
+
119
+ const { score, reasons } = this.calculateRelevance(tool, query);
120
+
121
+ if (score > 0) {
122
+ if (query.capabilities?.length) {
123
+ const hasCapability = query.capabilities.some(cap =>
124
+ tool.capabilities.includes(cap)
125
+ );
126
+ if (!hasCapability) continue;
127
+ }
128
+
129
+ if (query.tags?.length) {
130
+ const hasTag = query.tags.some(tag => tool.tags.includes(tag));
131
+ if (!hasTag) continue;
132
+ }
133
+
134
+ scored.push({
135
+ tool,
136
+ relevanceScore: score,
137
+ matchReasons: reasons,
138
+ tokenEstimate: tokenCount,
139
+ });
140
+ contextUsed += tokenCount;
141
+ }
142
+ }
143
+
144
+ scored.sort((a, b) => b.relevanceScore - a.relevanceScore);
145
+
146
+ const limit = Math.min(query.limit, this.findContextFitLimit(scored, query.contextBudget));
147
+ return scored.slice(query.offset, query.offset + limit);
148
+ }
149
+
150
+ private calculateRelevance(tool: Tool, query: DiscoveryQuery): {
151
+ score: number;
152
+ reasons: string[];
153
+ } {
154
+ let score = 0;
155
+ const reasons: string[] = [];
156
+
157
+ if (query.query) {
158
+ const q = query.query.toLowerCase();
159
+ const words = q.split(/\s+/);
160
+
161
+ for (const word of words) {
162
+ if (tool.name.toLowerCase().includes(word)) {
163
+ score += 0.4;
164
+ reasons.push(`Name matches "${word}"`);
165
+ }
166
+ if (tool.description.toLowerCase().includes(word)) {
167
+ score += 0.3;
168
+ reasons.push(`Description matches "${word}"`);
169
+ }
170
+ for (const tag of tool.tags) {
171
+ if (tag.toLowerCase().includes(word)) {
172
+ score += 0.2;
173
+ reasons.push(`Tag "${tag}" matches "${word}"`);
174
+ }
175
+ }
176
+ for (const cap of tool.capabilities) {
177
+ if (cap.toLowerCase().includes(word)) {
178
+ score += 0.25;
179
+ reasons.push(`Capability "${cap}" matches "${word}"`);
180
+ }
181
+ }
182
+ }
183
+
184
+ const allText = `${tool.name} ${tool.description} ${tool.tags.join(' ')} ${tool.capabilities.join(' ')}`.toLowerCase();
185
+ const matchCount = words.filter(w => allText.includes(w)).length;
186
+ if (matchCount === words.length) {
187
+ score += 0.3;
188
+ reasons.push('All query terms matched');
189
+ }
190
+ }
191
+
192
+ return { score: Math.min(score, 1), reasons };
193
+ }
194
+
195
+ private getServersForTools(serverIds: string[]): Server[] {
196
+ const uniqueIds = [...new Set(serverIds)];
197
+ return uniqueIds
198
+ .map(id => this.servers.get(id))
199
+ .filter((s): s is Server => s !== undefined);
200
+ }
201
+
202
+ private calculateContextUsed(tools: Array<{ tokenEstimate: number }>): number {
203
+ return tools.reduce((sum, t) => sum + t.tokenEstimate, 0) + 200;
204
+ }
205
+
206
+ private findContextFitLimit(tools: Array<{ tokenEstimate: number }>, budget: number): number {
207
+ let used = 200;
208
+ for (let i = 0; i < tools.length; i++) {
209
+ if (used + tools[i].tokenEstimate > budget) {
210
+ return i;
211
+ }
212
+ used += tools[i].tokenEstimate;
213
+ }
214
+ return tools.length;
215
+ }
216
+
217
+ private interpretQuery(query: DiscoveryQuery): string {
218
+ if (!query.query) {
219
+ return 'Returning all available tools sorted by relevance';
220
+ }
221
+
222
+ const q = query.query.toLowerCase();
223
+ const interpretations: string[] = [];
224
+
225
+ if (q.includes('what can i do') || q.includes('available') || q.includes('list')) {
226
+ interpretations.push('Listing available actions');
227
+ }
228
+ if (q.includes('github') || q.includes('repo') || q.includes('pr')) {
229
+ interpretations.push('Looking for GitHub-related tools');
230
+ }
231
+ if (q.includes('slack') || q.includes('message') || q.includes('channel')) {
232
+ interpretations.push('Looking for Slack/messaging tools');
233
+ }
234
+ if (q.includes('database') || q.includes('query') || q.includes('sql')) {
235
+ interpretations.push('Looking for database tools');
236
+ }
237
+ if (q.includes('file') || q.includes('drive') || q.includes('document')) {
238
+ interpretations.push('Looking for file/document tools');
239
+ }
240
+
241
+ if (interpretations.length === 0) {
242
+ return `Searching for tools matching: "${query.query}"`;
243
+ }
244
+
245
+ return interpretations.join(', ');
246
+ }
247
+
248
+ private generateSuggestions(query: DiscoveryQuery): string[] {
249
+ const suggestions: string[] = [];
250
+
251
+ if (this.tools.size > 50 && query.limit && query.limit < 10) {
252
+ suggestions.push('Try increasing the limit to see more tools');
253
+ }
254
+
255
+ if (!query.query && this.tools.size > 50) {
256
+ suggestions.push('Use a query to filter tools by capability or name');
257
+ }
258
+
259
+ const hasGitHub = Array.from(this.tools.values()).some(
260
+ i => i.tool.serverName.toLowerCase().includes('github')
261
+ );
262
+ if (!hasGitHub) {
263
+ suggestions.push('Configure GitHub connector for repository management');
264
+ }
265
+
266
+ return suggestions;
267
+ }
268
+
269
+ private buildSearchableText(tool: Tool): string {
270
+ return [
271
+ tool.name,
272
+ tool.description,
273
+ ...tool.tags,
274
+ ...tool.capabilities,
275
+ tool.serverName,
276
+ ].join(' ');
277
+ }
278
+
279
+ async healthCheck(serverId: string): Promise<{ healthy: boolean; latencyMs?: number }> {
280
+ const instance = this.serverInstances.get(serverId);
281
+ if (!instance) {
282
+ return { healthy: false };
283
+ }
284
+ return instance.healthCheck();
285
+ }
286
+
287
+ async refreshServer(serverId: string): Promise<void> {
288
+ const instance = this.serverInstances.get(serverId);
289
+ const server = this.servers.get(serverId);
290
+ if (!instance || !server) return;
291
+
292
+ try {
293
+ await instance.disconnect();
294
+ await instance.connect();
295
+ server.status = 'active';
296
+ server.lastSeen = new Date().toISOString();
297
+ } catch {
298
+ server.status = 'error';
299
+ }
300
+ }
301
+ }
@@ -0,0 +1,40 @@
1
+ export class TokenCounter {
2
+ private contextBudget: number;
3
+
4
+ constructor(contextBudget: number = 128000) {
5
+ this.contextBudget = contextBudget;
6
+ }
7
+
8
+ count(text: string): number {
9
+ return Math.ceil(text.length / 4);
10
+ }
11
+
12
+ estimateToolTokens(tool: {
13
+ name: string;
14
+ description: string;
15
+ inputSchema?: Record<string, unknown>;
16
+ tags?: string[];
17
+ capabilities?: string[];
18
+ }): number {
19
+ const parts = [
20
+ tool.name,
21
+ tool.description,
22
+ JSON.stringify(tool.inputSchema || {}),
23
+ (tool.tags || []).join(' '),
24
+ (tool.capabilities || []).join(' '),
25
+ ];
26
+ return this.count(parts.join(' ')) + 20;
27
+ }
28
+
29
+ canFit(tokens: number, currentUsage: number = 0): boolean {
30
+ return (currentUsage + tokens) <= this.contextBudget;
31
+ }
32
+
33
+ remaining(currentUsage: number = 0): number {
34
+ return Math.max(0, this.contextBudget - currentUsage);
35
+ }
36
+
37
+ setBudget(budget: number): void {
38
+ this.contextBudget = budget;
39
+ }
40
+ }
@@ -0,0 +1,182 @@
1
+ import type { AuditEvent, AuditEventType, ComplianceReport } from './types.js';
2
+
3
+ export class AuditLogger {
4
+ private events: AuditEvent[] = [];
5
+ private maxEvents: number;
6
+
7
+ constructor(maxEvents: number = 10000) {
8
+ this.maxEvents = maxEvents;
9
+ }
10
+
11
+ log(event: Omit<AuditEvent, 'id' | 'timestamp'>): AuditEvent {
12
+ const fullEvent: AuditEvent = {
13
+ id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
14
+ timestamp: new Date().toISOString(),
15
+ ...event,
16
+ metadata: event.metadata || {},
17
+ context: event.context || {},
18
+ };
19
+
20
+ this.events.push(fullEvent);
21
+
22
+ if (this.events.length > this.maxEvents) {
23
+ this.events = this.events.slice(-this.maxEvents);
24
+ }
25
+
26
+ return fullEvent;
27
+ }
28
+
29
+ logToolAccess(agentId: string, agentName: string, toolId: string, result: 'success' | 'denied' | 'error', requestId: string, reason?: string): void {
30
+ this.log({
31
+ type: result === 'denied' ? 'tool.denied' : result === 'error' ? 'tool.error' : 'tool.accessed',
32
+ agentId,
33
+ agentName,
34
+ requestId,
35
+ resourceType: 'tool',
36
+ resourceId: toolId,
37
+ action: 'access',
38
+ result,
39
+ reason,
40
+ metadata: {},
41
+ context: {},
42
+ });
43
+ }
44
+
45
+ logAuthentication(agentId: string, agentName: string, success: boolean, requestId: string, ipAddress?: string): void {
46
+ this.log({
47
+ type: success ? 'identity.authenticated' : 'identity.auth_failed',
48
+ agentId: success ? agentId : undefined,
49
+ agentName: success ? agentName : undefined,
50
+ requestId,
51
+ ipAddress,
52
+ action: 'authenticate',
53
+ result: success ? 'success' : 'denied',
54
+ metadata: { ipAddress },
55
+ context: {},
56
+ });
57
+ }
58
+
59
+ logToolExecution(agentId: string, agentName: string, toolId: string, success: boolean, requestId: string, durationMs?: number, error?: string): void {
60
+ this.log({
61
+ type: success ? 'tool.executed' : 'tool.error',
62
+ agentId,
63
+ agentName,
64
+ requestId,
65
+ resourceType: 'tool',
66
+ resourceId: toolId,
67
+ action: 'execute',
68
+ result: success ? 'success' : 'error',
69
+ reason: error,
70
+ metadata: { durationMs },
71
+ context: {},
72
+ });
73
+ }
74
+
75
+ logPermissionCheck(agentId: string, permission: string, allowed: boolean, requestId: string, reason?: string): void {
76
+ this.log({
77
+ type: allowed ? 'permission.checked' : 'permission.denied',
78
+ agentId,
79
+ requestId,
80
+ action: `check:${permission}`,
81
+ result: allowed ? 'success' : 'denied',
82
+ reason,
83
+ metadata: {},
84
+ context: {},
85
+ });
86
+ }
87
+
88
+ getEvents(options?: {
89
+ type?: AuditEventType;
90
+ agentId?: string;
91
+ resourceType?: string;
92
+ resourceId?: string;
93
+ result?: 'success' | 'denied' | 'error';
94
+ since?: string;
95
+ until?: string;
96
+ limit?: number;
97
+ offset?: number;
98
+ }): AuditEvent[] {
99
+ let filtered = [...this.events];
100
+
101
+ if (options?.type) {
102
+ filtered = filtered.filter(e => e.type === options.type);
103
+ }
104
+ if (options?.agentId) {
105
+ filtered = filtered.filter(e => e.agentId === options.agentId);
106
+ }
107
+ if (options?.resourceType) {
108
+ filtered = filtered.filter(e => e.resourceType === options.resourceType);
109
+ }
110
+ if (options?.resourceId) {
111
+ filtered = filtered.filter(e => e.resourceId === options.resourceId);
112
+ }
113
+ if (options?.result) {
114
+ filtered = filtered.filter(e => e.result === options.result);
115
+ }
116
+ if (options?.since) {
117
+ filtered = filtered.filter(e => e.timestamp >= options.since!);
118
+ }
119
+ if (options?.until) {
120
+ filtered = filtered.filter(e => e.timestamp <= options.until!);
121
+ }
122
+
123
+ filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
124
+
125
+ const offset = options?.offset || 0;
126
+ const limit = options?.limit || 100;
127
+ return filtered.slice(offset, offset + limit);
128
+ }
129
+
130
+ generateComplianceReport(periodStart: string, periodEnd: string): ComplianceReport {
131
+ const events = this.getEvents({ since: periodStart, until: periodEnd });
132
+
133
+ const byType: Record<string, number> = {};
134
+ const byAgent: Record<string, number> = {};
135
+
136
+ for (const event of events) {
137
+ byType[event.type] = (byType[event.type] || 0) + 1;
138
+ if (event.agentId) {
139
+ byAgent[event.agentId] = (byAgent[event.agentId] || 0) + 1;
140
+ }
141
+ }
142
+
143
+ const deniedAccess = events.filter(e => e.result === 'denied').length;
144
+ const rateLimitExceeded = events.filter(e => e.type === 'rate_limit.exceeded').length;
145
+ const policyViolations = events.filter(e => e.type === 'permission.denied' || e.type === 'tool.denied').length;
146
+
147
+ const recommendations: string[] = [];
148
+ if (deniedAccess > events.length * 0.1) {
149
+ recommendations.push('High rate of denied access - review permission configurations');
150
+ }
151
+ if (rateLimitExceeded > 100) {
152
+ recommendations.push('Frequent rate limit violations - consider increasing limits or optimizing usage');
153
+ }
154
+ if (Object.keys(byAgent).length < 5) {
155
+ recommendations.push('Limited user diversity - ensure proper access distribution');
156
+ }
157
+
158
+ return {
159
+ generatedAt: new Date().toISOString(),
160
+ period: { start: periodStart, end: periodEnd },
161
+ totalEvents: events.length,
162
+ byType,
163
+ byAgent,
164
+ deniedAccess,
165
+ rateLimitExceeded,
166
+ policyViolations,
167
+ recommendations,
168
+ };
169
+ }
170
+
171
+ clear(): void {
172
+ this.events = [];
173
+ }
174
+
175
+ export(): AuditEvent[] {
176
+ return [...this.events];
177
+ }
178
+
179
+ import(events: AuditEvent[]): void {
180
+ this.events = [...this.events, ...events].slice(-this.maxEvents);
181
+ }
182
+ }
@@ -0,0 +1,4 @@
1
+ export { AuditLogger } from './audit.js';
2
+ export { PolicyEngine } from './policy.js';
3
+ export { RateLimiter, type RateLimitConfig } from './rate-limiter.js';
4
+ export * from './types.js';