aegis-bridge 2.2.2

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/dashboard/dist/assets/index-CijFoeRu.css +32 -0
  4. package/dashboard/dist/assets/index-QtT4j0ht.js +262 -0
  5. package/dashboard/dist/index.html +14 -0
  6. package/dist/auth.d.ts +76 -0
  7. package/dist/auth.js +219 -0
  8. package/dist/channels/index.d.ts +8 -0
  9. package/dist/channels/index.js +9 -0
  10. package/dist/channels/manager.d.ts +39 -0
  11. package/dist/channels/manager.js +101 -0
  12. package/dist/channels/telegram-style.d.ts +118 -0
  13. package/dist/channels/telegram-style.js +203 -0
  14. package/dist/channels/telegram.d.ts +76 -0
  15. package/dist/channels/telegram.js +1396 -0
  16. package/dist/channels/types.d.ts +77 -0
  17. package/dist/channels/types.js +9 -0
  18. package/dist/channels/webhook.d.ts +58 -0
  19. package/dist/channels/webhook.js +162 -0
  20. package/dist/cli.d.ts +8 -0
  21. package/dist/cli.js +223 -0
  22. package/dist/config.d.ts +60 -0
  23. package/dist/config.js +188 -0
  24. package/dist/dashboard/assets/index-CijFoeRu.css +32 -0
  25. package/dist/dashboard/assets/index-QtT4j0ht.js +262 -0
  26. package/dist/dashboard/index.html +14 -0
  27. package/dist/events.d.ts +86 -0
  28. package/dist/events.js +258 -0
  29. package/dist/hook-settings.d.ts +67 -0
  30. package/dist/hook-settings.js +138 -0
  31. package/dist/hook.d.ts +18 -0
  32. package/dist/hook.js +199 -0
  33. package/dist/hooks.d.ts +32 -0
  34. package/dist/hooks.js +279 -0
  35. package/dist/jsonl-watcher.d.ts +57 -0
  36. package/dist/jsonl-watcher.js +159 -0
  37. package/dist/mcp-server.d.ts +60 -0
  38. package/dist/mcp-server.js +788 -0
  39. package/dist/metrics.d.ts +104 -0
  40. package/dist/metrics.js +226 -0
  41. package/dist/monitor.d.ts +84 -0
  42. package/dist/monitor.js +553 -0
  43. package/dist/permission-guard.d.ts +51 -0
  44. package/dist/permission-guard.js +197 -0
  45. package/dist/pipeline.d.ts +84 -0
  46. package/dist/pipeline.js +218 -0
  47. package/dist/screenshot.d.ts +26 -0
  48. package/dist/screenshot.js +57 -0
  49. package/dist/server.d.ts +10 -0
  50. package/dist/server.js +1577 -0
  51. package/dist/session.d.ts +297 -0
  52. package/dist/session.js +1275 -0
  53. package/dist/sse-limiter.d.ts +47 -0
  54. package/dist/sse-limiter.js +62 -0
  55. package/dist/sse-writer.d.ts +31 -0
  56. package/dist/sse-writer.js +95 -0
  57. package/dist/ssrf.d.ts +57 -0
  58. package/dist/ssrf.js +169 -0
  59. package/dist/swarm-monitor.d.ts +114 -0
  60. package/dist/swarm-monitor.js +267 -0
  61. package/dist/terminal-parser.d.ts +16 -0
  62. package/dist/terminal-parser.js +343 -0
  63. package/dist/tmux.d.ts +161 -0
  64. package/dist/tmux.js +725 -0
  65. package/dist/transcript.d.ts +47 -0
  66. package/dist/transcript.js +244 -0
  67. package/dist/validation.d.ts +222 -0
  68. package/dist/validation.js +268 -0
  69. package/dist/ws-terminal.d.ts +32 -0
  70. package/dist/ws-terminal.js +297 -0
  71. package/package.json +71 -0
@@ -0,0 +1,788 @@
1
+ /**
2
+ * mcp-server.ts — MCP server mode for Aegis.
3
+ *
4
+ * Exposes Aegis session orchestration as MCP tools via stdio transport.
5
+ * CC sessions can natively discover and communicate with sibling sessions.
6
+ *
7
+ * Usage:
8
+ * aegis-bridge mcp # default port 9100
9
+ * aegis-bridge mcp --port 3000 # custom port
10
+ * claude mcp add --scope user aegis -- npx aegis-bridge mcp
11
+ *
12
+ * Issue #48: https://github.com/OneStepAt4time/aegis/issues/48
13
+ */
14
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ import { z } from 'zod';
17
+ import { readFileSync } from 'node:fs';
18
+ import { dirname, join } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { isValidUUID } from './validation.js';
21
+ // Read version from package.json at startup (matches cli.ts pattern)
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
24
+ const VERSION = pkg.version;
25
+ // ── Aegis REST client ───────────────────────────────────────────────
26
+ export class AegisClient {
27
+ baseUrl;
28
+ authToken;
29
+ constructor(baseUrl, authToken) {
30
+ this.baseUrl = baseUrl;
31
+ this.authToken = authToken;
32
+ }
33
+ validateSessionId(id) {
34
+ if (!isValidUUID(id)) {
35
+ throw new Error(`Invalid session ID: ${id}`);
36
+ }
37
+ }
38
+ async request(path, opts) {
39
+ const headers = {
40
+ 'Content-Type': 'application/json',
41
+ ...(this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}),
42
+ };
43
+ let res;
44
+ try {
45
+ res = await fetch(`${this.baseUrl}${path}`, { ...opts, headers: { ...headers, ...opts?.headers } });
46
+ }
47
+ catch (e) {
48
+ const cause = e.cause;
49
+ if (cause?.code === 'ECONNREFUSED') {
50
+ throw new Error('Aegis server is not running or not reachable');
51
+ }
52
+ throw new Error(`Network error: ${e instanceof Error ? e.message : String(e)}`);
53
+ }
54
+ if (!res.ok) {
55
+ const body = await res.json().catch(() => ({ error: res.statusText }));
56
+ throw new Error(body.error || `HTTP ${res.status}`);
57
+ }
58
+ return res.json();
59
+ }
60
+ async listSessions(filter) {
61
+ const response = await this.request('/v1/sessions');
62
+ let sessions = response.sessions;
63
+ if (filter?.status) {
64
+ sessions = sessions.filter((s) => s.status === filter.status);
65
+ }
66
+ if (filter?.workDir) {
67
+ sessions = sessions.filter((s) => s.workDir === filter.workDir || s.workDir?.startsWith(filter.workDir + '/'));
68
+ }
69
+ return sessions;
70
+ }
71
+ async getSession(id) {
72
+ this.validateSessionId(id);
73
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}`);
74
+ }
75
+ async getHealth(id) {
76
+ this.validateSessionId(id);
77
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/health`);
78
+ }
79
+ async getTranscript(id) {
80
+ this.validateSessionId(id);
81
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/read`);
82
+ }
83
+ async sendMessage(id, text) {
84
+ this.validateSessionId(id);
85
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/send`, {
86
+ method: 'POST',
87
+ body: JSON.stringify({ text }),
88
+ });
89
+ }
90
+ async createSession(opts) {
91
+ return this.request('/v1/sessions', {
92
+ method: 'POST',
93
+ body: JSON.stringify(opts),
94
+ });
95
+ }
96
+ async killSession(id) {
97
+ this.validateSessionId(id);
98
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}`, {
99
+ method: 'DELETE',
100
+ });
101
+ }
102
+ async approvePermission(id) {
103
+ this.validateSessionId(id);
104
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/approve`, {
105
+ method: 'POST',
106
+ });
107
+ }
108
+ async rejectPermission(id) {
109
+ this.validateSessionId(id);
110
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/reject`, {
111
+ method: 'POST',
112
+ });
113
+ }
114
+ async getServerHealth() {
115
+ return this.request('/v1/health');
116
+ }
117
+ async escapeSession(id) {
118
+ this.validateSessionId(id);
119
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/escape`, {
120
+ method: 'POST',
121
+ });
122
+ }
123
+ async interruptSession(id) {
124
+ this.validateSessionId(id);
125
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/interrupt`, {
126
+ method: 'POST',
127
+ });
128
+ }
129
+ async capturePane(id) {
130
+ this.validateSessionId(id);
131
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/pane`);
132
+ }
133
+ async getSessionMetrics(id) {
134
+ this.validateSessionId(id);
135
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/metrics`);
136
+ }
137
+ async getSessionSummary(id) {
138
+ this.validateSessionId(id);
139
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/summary`);
140
+ }
141
+ async sendBash(id, command) {
142
+ this.validateSessionId(id);
143
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/bash`, {
144
+ method: 'POST',
145
+ body: JSON.stringify({ command }),
146
+ });
147
+ }
148
+ async sendCommand(id, command) {
149
+ this.validateSessionId(id);
150
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/command`, {
151
+ method: 'POST',
152
+ body: JSON.stringify({ command }),
153
+ });
154
+ }
155
+ async getSessionLatency(id) {
156
+ this.validateSessionId(id);
157
+ return this.request(`/v1/sessions/${encodeURIComponent(id)}/latency`);
158
+ }
159
+ async batchCreateSessions(sessions) {
160
+ return this.request('/v1/sessions/batch', {
161
+ method: 'POST',
162
+ body: JSON.stringify({ sessions }),
163
+ });
164
+ }
165
+ async listPipelines() {
166
+ return this.request('/v1/pipelines');
167
+ }
168
+ async createPipeline(config) {
169
+ return this.request('/v1/pipelines', {
170
+ method: 'POST',
171
+ body: JSON.stringify(config),
172
+ });
173
+ }
174
+ async getSwarm() {
175
+ return this.request('/v1/swarm');
176
+ }
177
+ }
178
+ function formatToolError(e) {
179
+ if (e instanceof Error) {
180
+ let code;
181
+ if (e.message.includes('not running') || e.message.includes('not reachable') || e.message.includes('Network error')) {
182
+ code = 'SERVER_UNREACHABLE';
183
+ }
184
+ else if (e.message.startsWith('Invalid session ID')) {
185
+ code = 'INVALID_SESSION_ID';
186
+ }
187
+ else {
188
+ code = 'REQUEST_FAILED';
189
+ }
190
+ return {
191
+ content: [{ type: 'text', text: JSON.stringify({ code, message: e.message }) }],
192
+ isError: true,
193
+ };
194
+ }
195
+ return {
196
+ content: [{ type: 'text', text: JSON.stringify({ code: 'UNKNOWN_ERROR', message: String(e) }) }],
197
+ isError: true,
198
+ };
199
+ }
200
+ // ── MCP Server ──────────────────────────────────────────────────────
201
+ export function createMcpServer(aegisPort, authToken) {
202
+ const client = new AegisClient(`http://127.0.0.1:${aegisPort}`, authToken);
203
+ const server = new McpServer({ name: 'aegis', version: VERSION }, { capabilities: { tools: {}, resources: {} } });
204
+ // ── MCP Resources (Issue #442) ────────────────────────────────────
205
+ // aegis://sessions — compact session list
206
+ server.resource('sessions', 'aegis://sessions', { description: 'List of active Aegis sessions (compact: id, name, status, workDir)', mimeType: 'application/json' }, async (uri) => {
207
+ try {
208
+ const sessions = await client.listSessions();
209
+ const compact = sessions.map((s) => ({
210
+ id: s.id,
211
+ name: s.windowName,
212
+ status: s.status,
213
+ workDir: s.workDir,
214
+ }));
215
+ return {
216
+ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(compact, null, 2) }],
217
+ };
218
+ }
219
+ catch (e) {
220
+ const msg = e instanceof Error ? e.message : String(e);
221
+ return {
222
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text: `Error: ${msg}` }],
223
+ };
224
+ }
225
+ });
226
+ // aegis://sessions/{id}/transcript — full JSONL transcript
227
+ server.resource('session-transcript', new ResourceTemplate('aegis://sessions/{id}/transcript', { list: undefined }), { description: 'Full JSONL transcript of an Aegis session', mimeType: 'application/json' }, async (uri, variables) => {
228
+ const id = variables.id;
229
+ if (!isValidUUID(id)) {
230
+ return {
231
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text: `Error: Invalid session ID: ${id}` }],
232
+ };
233
+ }
234
+ try {
235
+ const transcript = await client.getTranscript(id);
236
+ return {
237
+ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(transcript, null, 2) }],
238
+ };
239
+ }
240
+ catch (e) {
241
+ const msg = e instanceof Error ? e.message : String(e);
242
+ return {
243
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text: `Error: ${msg}` }],
244
+ };
245
+ }
246
+ });
247
+ // aegis://sessions/{id}/pane — current terminal pane content
248
+ server.resource('session-pane', new ResourceTemplate('aegis://sessions/{id}/pane', { list: undefined }), { description: 'Current terminal pane content of an Aegis session', mimeType: 'text/plain' }, async (uri, variables) => {
249
+ const id = variables.id;
250
+ if (!isValidUUID(id)) {
251
+ return {
252
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text: `Error: Invalid session ID: ${id}` }],
253
+ };
254
+ }
255
+ try {
256
+ const result = await client.capturePane(id);
257
+ const text = typeof result.pane === 'string' ? result.pane : JSON.stringify(result, null, 2);
258
+ return {
259
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text }],
260
+ };
261
+ }
262
+ catch (e) {
263
+ const msg = e instanceof Error ? e.message : String(e);
264
+ return {
265
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text: `Error: ${msg}` }],
266
+ };
267
+ }
268
+ });
269
+ // aegis://health — server health status
270
+ server.resource('health', 'aegis://health', { description: 'Aegis server health status (version, uptime, session counts)', mimeType: 'application/json' }, async (uri) => {
271
+ try {
272
+ const health = await client.getServerHealth();
273
+ return {
274
+ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(health, null, 2) }],
275
+ };
276
+ }
277
+ catch (e) {
278
+ const msg = e instanceof Error ? e.message : String(e);
279
+ return {
280
+ contents: [{ uri: uri.href, mimeType: 'text/plain', text: `Error: ${msg}` }],
281
+ };
282
+ }
283
+ });
284
+ // ── list_sessions ──
285
+ server.tool('list_sessions', 'List Aegis-managed Claude Code sessions. Optionally filter by status or workDir substring.', {
286
+ status: z.string().optional().describe('Filter by status (e.g., idle, working, permission_prompt)'),
287
+ workDir: z.string().optional().describe('Filter by workDir substring (e.g., "my-project")'),
288
+ }, async ({ status, workDir }) => {
289
+ try {
290
+ const sessions = await client.listSessions({ status, workDir });
291
+ const summary = sessions.map((s) => ({
292
+ id: s.id,
293
+ name: s.windowName,
294
+ status: s.status,
295
+ workDir: s.workDir,
296
+ createdAt: new Date(s.createdAt).toISOString(),
297
+ lastActivity: new Date(s.lastActivity).toISOString(),
298
+ }));
299
+ return {
300
+ content: [{
301
+ type: 'text',
302
+ text: JSON.stringify(summary, null, 2),
303
+ }],
304
+ };
305
+ }
306
+ catch (e) {
307
+ return formatToolError(e);
308
+ }
309
+ });
310
+ // ── get_status ──
311
+ server.tool('get_status', 'Get detailed status and health of a specific Aegis session.', {
312
+ sessionId: z.string().describe('The session ID to check'),
313
+ }, async ({ sessionId }) => {
314
+ try {
315
+ const [session, health] = await Promise.all([
316
+ client.getSession(sessionId),
317
+ client.getHealth(sessionId).catch(() => null),
318
+ ]);
319
+ return {
320
+ content: [{
321
+ type: 'text',
322
+ text: JSON.stringify({ ...session, health }, null, 2),
323
+ }],
324
+ };
325
+ }
326
+ catch (e) {
327
+ return formatToolError(e);
328
+ }
329
+ });
330
+ // ── get_transcript ──
331
+ server.tool('get_transcript', 'Read the conversation transcript of another Aegis session. Returns recent messages from the JSONL log.', {
332
+ sessionId: z.string().describe('The session ID to read from'),
333
+ }, async ({ sessionId }) => {
334
+ try {
335
+ const transcript = await client.getTranscript(sessionId);
336
+ return {
337
+ content: [{
338
+ type: 'text',
339
+ text: JSON.stringify(transcript, null, 2),
340
+ }],
341
+ };
342
+ }
343
+ catch (e) {
344
+ return formatToolError(e);
345
+ }
346
+ });
347
+ // ── send_message ──
348
+ server.tool('send_message', 'Send a message to another Aegis session. The message is delivered via tmux send-keys with delivery verification.', {
349
+ sessionId: z.string().describe('The target session ID'),
350
+ text: z.string().describe('The message text to send'),
351
+ }, async ({ sessionId, text }) => {
352
+ try {
353
+ const result = await client.sendMessage(sessionId, text);
354
+ return {
355
+ content: [{
356
+ type: 'text',
357
+ text: JSON.stringify(result, null, 2),
358
+ }],
359
+ };
360
+ }
361
+ catch (e) {
362
+ return formatToolError(e);
363
+ }
364
+ });
365
+ // ── create_session ──
366
+ server.tool('create_session', 'Spawn a new Claude Code session managed by Aegis. Returns the session ID and initial status.', {
367
+ workDir: z.string().describe('Working directory for the new session'),
368
+ name: z.string().optional().describe('Optional human-readable name for the session'),
369
+ prompt: z.string().optional().describe('Optional initial prompt to send after creation'),
370
+ }, async ({ workDir, name, prompt }) => {
371
+ try {
372
+ const session = await client.createSession({ workDir, name, prompt });
373
+ return {
374
+ content: [{
375
+ type: 'text',
376
+ text: JSON.stringify({
377
+ id: session.id,
378
+ name: session.windowName,
379
+ status: 'created',
380
+ workDir: session.workDir,
381
+ promptDelivery: session.promptDelivery,
382
+ }, null, 2),
383
+ }],
384
+ };
385
+ }
386
+ catch (e) {
387
+ return formatToolError(e);
388
+ }
389
+ });
390
+ // ── kill_session ──
391
+ server.tool('kill_session', 'Kill an Aegis session. Deletes the tmux window and cleans up all resources.', {
392
+ sessionId: z.string().describe('The session ID to kill'),
393
+ }, async ({ sessionId }) => {
394
+ try {
395
+ const result = await client.killSession(sessionId);
396
+ return {
397
+ content: [{
398
+ type: 'text',
399
+ text: JSON.stringify(result, null, 2),
400
+ }],
401
+ };
402
+ }
403
+ catch (e) {
404
+ return formatToolError(e);
405
+ }
406
+ });
407
+ // ── approve_permission ──
408
+ server.tool('approve_permission', 'Approve a pending permission prompt in an Aegis session.', {
409
+ sessionId: z.string().describe('The session ID with a pending permission prompt'),
410
+ }, async ({ sessionId }) => {
411
+ try {
412
+ const result = await client.approvePermission(sessionId);
413
+ return {
414
+ content: [{
415
+ type: 'text',
416
+ text: JSON.stringify(result, null, 2),
417
+ }],
418
+ };
419
+ }
420
+ catch (e) {
421
+ return formatToolError(e);
422
+ }
423
+ });
424
+ // ── reject_permission ──
425
+ server.tool('reject_permission', 'Reject a pending permission prompt in an Aegis session.', {
426
+ sessionId: z.string().describe('The session ID with a pending permission prompt'),
427
+ }, async ({ sessionId }) => {
428
+ try {
429
+ const result = await client.rejectPermission(sessionId);
430
+ return {
431
+ content: [{
432
+ type: 'text',
433
+ text: JSON.stringify(result, null, 2),
434
+ }],
435
+ };
436
+ }
437
+ catch (e) {
438
+ return formatToolError(e);
439
+ }
440
+ });
441
+ // ── server_health ──
442
+ server.tool('server_health', 'Check the health and status of the Aegis server. Returns version, uptime, and session counts.', {}, async () => {
443
+ try {
444
+ const result = await client.getServerHealth();
445
+ return {
446
+ content: [{
447
+ type: 'text',
448
+ text: JSON.stringify(result, null, 2),
449
+ }],
450
+ };
451
+ }
452
+ catch (e) {
453
+ return formatToolError(e);
454
+ }
455
+ });
456
+ // ── escape_session ──
457
+ server.tool('escape_session', 'Send an Escape keypress to an Aegis session. Useful for dismissing prompts or cancelling operations.', {
458
+ sessionId: z.string().describe('The session ID to send escape to'),
459
+ }, async ({ sessionId }) => {
460
+ try {
461
+ const result = await client.escapeSession(sessionId);
462
+ return {
463
+ content: [{
464
+ type: 'text',
465
+ text: JSON.stringify(result, null, 2),
466
+ }],
467
+ };
468
+ }
469
+ catch (e) {
470
+ return formatToolError(e);
471
+ }
472
+ });
473
+ // ── interrupt_session ──
474
+ server.tool('interrupt_session', 'Send Ctrl+C to interrupt the current operation in an Aegis session.', {
475
+ sessionId: z.string().describe('The session ID to interrupt'),
476
+ }, async ({ sessionId }) => {
477
+ try {
478
+ const result = await client.interruptSession(sessionId);
479
+ return {
480
+ content: [{
481
+ type: 'text',
482
+ text: JSON.stringify(result, null, 2),
483
+ }],
484
+ };
485
+ }
486
+ catch (e) {
487
+ return formatToolError(e);
488
+ }
489
+ });
490
+ // ── capture_pane ──
491
+ server.tool('capture_pane', 'Capture the raw terminal pane content of an Aegis session. Returns the current visible text.', {
492
+ sessionId: z.string().describe('The session ID to capture'),
493
+ }, async ({ sessionId }) => {
494
+ try {
495
+ const result = await client.capturePane(sessionId);
496
+ return {
497
+ content: [{
498
+ type: 'text',
499
+ text: JSON.stringify(result, null, 2),
500
+ }],
501
+ };
502
+ }
503
+ catch (e) {
504
+ return formatToolError(e);
505
+ }
506
+ });
507
+ // ── get_session_metrics ──
508
+ server.tool('get_session_metrics', 'Get performance metrics for a specific Aegis session (message counts, latency, etc.).', {
509
+ sessionId: z.string().describe('The session ID to get metrics for'),
510
+ }, async ({ sessionId }) => {
511
+ try {
512
+ const result = await client.getSessionMetrics(sessionId);
513
+ return {
514
+ content: [{
515
+ type: 'text',
516
+ text: JSON.stringify(result, null, 2),
517
+ }],
518
+ };
519
+ }
520
+ catch (e) {
521
+ return formatToolError(e);
522
+ }
523
+ });
524
+ // ── get_session_summary ──
525
+ server.tool('get_session_summary', 'Get a summary of an Aegis session including message counts, duration, and status history.', {
526
+ sessionId: z.string().describe('The session ID to summarize'),
527
+ }, async ({ sessionId }) => {
528
+ try {
529
+ const result = await client.getSessionSummary(sessionId);
530
+ return {
531
+ content: [{
532
+ type: 'text',
533
+ text: JSON.stringify(result, null, 2),
534
+ }],
535
+ };
536
+ }
537
+ catch (e) {
538
+ return formatToolError(e);
539
+ }
540
+ });
541
+ // ── send_bash ──
542
+ server.tool('send_bash', 'Execute a bash command in an Aegis session. The command is prefixed with "!" and sent via tmux.', {
543
+ sessionId: z.string().describe('The session ID to send the bash command to'),
544
+ command: z.string().describe('The bash command to execute'),
545
+ }, async ({ sessionId, command }) => {
546
+ try {
547
+ const result = await client.sendBash(sessionId, command);
548
+ return {
549
+ content: [{
550
+ type: 'text',
551
+ text: JSON.stringify(result, null, 2),
552
+ }],
553
+ };
554
+ }
555
+ catch (e) {
556
+ return formatToolError(e);
557
+ }
558
+ });
559
+ // ── send_command ──
560
+ server.tool('send_command', 'Send a slash command to an Aegis session. The command is prefixed with "/" if not already.', {
561
+ sessionId: z.string().describe('The session ID to send the command to'),
562
+ command: z.string().describe('The slash command to send (e.g., "help", "compact")'),
563
+ }, async ({ sessionId, command }) => {
564
+ try {
565
+ const result = await client.sendCommand(sessionId, command);
566
+ return {
567
+ content: [{
568
+ type: 'text',
569
+ text: JSON.stringify(result, null, 2),
570
+ }],
571
+ };
572
+ }
573
+ catch (e) {
574
+ return formatToolError(e);
575
+ }
576
+ });
577
+ // ── get_session_latency ──
578
+ server.tool('get_session_latency', 'Get latency metrics for a specific Aegis session, including realtime and aggregated measurements.', {
579
+ sessionId: z.string().describe('The session ID to get latency for'),
580
+ }, async ({ sessionId }) => {
581
+ try {
582
+ const result = await client.getSessionLatency(sessionId);
583
+ return {
584
+ content: [{
585
+ type: 'text',
586
+ text: JSON.stringify(result, null, 2),
587
+ }],
588
+ };
589
+ }
590
+ catch (e) {
591
+ return formatToolError(e);
592
+ }
593
+ });
594
+ // ── batch_create_sessions ──
595
+ server.tool('batch_create_sessions', 'Create multiple Aegis sessions in a single batch operation.', {
596
+ sessions: z.array(z.object({
597
+ workDir: z.string().describe('Working directory for the session'),
598
+ name: z.string().optional().describe('Optional human-readable name'),
599
+ prompt: z.string().optional().describe('Optional initial prompt'),
600
+ })).describe('Array of session specifications to create'),
601
+ }, async ({ sessions: sessionSpecs }) => {
602
+ try {
603
+ const result = await client.batchCreateSessions(sessionSpecs);
604
+ return {
605
+ content: [{
606
+ type: 'text',
607
+ text: JSON.stringify(result, null, 2),
608
+ }],
609
+ };
610
+ }
611
+ catch (e) {
612
+ return formatToolError(e);
613
+ }
614
+ });
615
+ // ── list_pipelines ──
616
+ server.tool('list_pipelines', 'List all configured pipelines in the Aegis server.', {}, async () => {
617
+ try {
618
+ const result = await client.listPipelines();
619
+ return {
620
+ content: [{
621
+ type: 'text',
622
+ text: JSON.stringify(result, null, 2),
623
+ }],
624
+ };
625
+ }
626
+ catch (e) {
627
+ return formatToolError(e);
628
+ }
629
+ });
630
+ // ── create_pipeline ──
631
+ server.tool('create_pipeline', 'Create a new pipeline for orchestrating multiple Aegis sessions in sequence.', {
632
+ name: z.string().describe('Name of the pipeline'),
633
+ workDir: z.string().describe('Working directory for pipeline sessions'),
634
+ steps: z.array(z.object({
635
+ name: z.string().optional().describe('Step name'),
636
+ prompt: z.string().describe('Prompt for this step'),
637
+ })).describe('Array of pipeline steps'),
638
+ }, async ({ name, workDir, steps }) => {
639
+ try {
640
+ const result = await client.createPipeline({ name, workDir, steps });
641
+ return {
642
+ content: [{
643
+ type: 'text',
644
+ text: JSON.stringify(result, null, 2),
645
+ }],
646
+ };
647
+ }
648
+ catch (e) {
649
+ return formatToolError(e);
650
+ }
651
+ });
652
+ // ── get_swarm ──
653
+ server.tool('get_swarm', 'Get a snapshot of all Claude Code processes detected on the system (the "swarm").', {}, async () => {
654
+ try {
655
+ const result = await client.getSwarm();
656
+ return {
657
+ content: [{
658
+ type: 'text',
659
+ text: JSON.stringify(result, null, 2),
660
+ }],
661
+ };
662
+ }
663
+ catch (e) {
664
+ return formatToolError(e);
665
+ }
666
+ });
667
+ // ── MCP Prompts (Issue #443) ────────────────────────────────────────
668
+ server.prompt('implement_issue', 'Create a session and generate a structured implementation prompt for a GitHub issue.', {
669
+ issueNumber: z.string().describe('GitHub issue number'),
670
+ workDir: z.string().describe('Working directory for the new session'),
671
+ repoOwner: z.string().optional().describe('Repository owner (e.g., "OneStepAt4time")'),
672
+ repoName: z.string().optional().describe('Repository name (e.g., "aegis")'),
673
+ }, async ({ issueNumber, workDir, repoOwner, repoName }) => {
674
+ const owner = repoOwner || 'OneStepAt4time';
675
+ const repo = repoName || 'aegis';
676
+ const issueUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`;
677
+ return {
678
+ messages: [
679
+ {
680
+ role: 'user',
681
+ content: {
682
+ type: 'text',
683
+ text: [
684
+ 'You are tasked with implementing a GitHub issue.',
685
+ '',
686
+ `Issue: ${owner}/${repo}#${issueNumber}`,
687
+ `URL: ${issueUrl}`,
688
+ `Working directory: ${workDir}`,
689
+ '',
690
+ 'Steps:',
691
+ `1. Create a new Aegis session in ${workDir}`,
692
+ `2. Read the GitHub issue at ${issueUrl} to understand the requirements`,
693
+ '3. Analyze the codebase to understand the current architecture',
694
+ '4. Plan the implementation approach',
695
+ '5. Implement the changes following project conventions',
696
+ '6. Run the quality gate: npx tsc --noEmit && npm run build && npm test',
697
+ '7. If tests pass, commit with a conventional commit message',
698
+ '',
699
+ 'Use the create_session tool to start, then send_message for each step.',
700
+ ].join('\n'),
701
+ },
702
+ },
703
+ ],
704
+ };
705
+ });
706
+ server.prompt('review_pr', 'Create a session and generate a structured code review prompt for a GitHub pull request.', {
707
+ prNumber: z.string().describe('GitHub pull request number'),
708
+ workDir: z.string().describe('Working directory for the new session'),
709
+ repoOwner: z.string().optional().describe('Repository owner (e.g., "OneStepAt4time")'),
710
+ repoName: z.string().optional().describe('Repository name (e.g., "aegis")'),
711
+ }, async ({ prNumber, workDir, repoOwner, repoName }) => {
712
+ const owner = repoOwner || 'OneStepAt4time';
713
+ const repo = repoName || 'aegis';
714
+ const prUrl = `https://github.com/${owner}/${repo}/pull/${prNumber}`;
715
+ return {
716
+ messages: [
717
+ {
718
+ role: 'user',
719
+ content: {
720
+ type: 'text',
721
+ text: [
722
+ 'You are tasked with reviewing a GitHub pull request.',
723
+ '',
724
+ `PR: ${owner}/${repo}#${prNumber}`,
725
+ `URL: ${prUrl}`,
726
+ `Working directory: ${workDir}`,
727
+ '',
728
+ 'Steps:',
729
+ `1. Create a new Aegis session in ${workDir}`,
730
+ `2. Fetch the PR details: gh pr view ${prNumber} --repo ${owner}/${repo}`,
731
+ `3. Fetch the PR diff: gh pr diff ${prNumber} --repo ${owner}/${repo}`,
732
+ '4. Review the changes for:',
733
+ ' - Correctness and edge cases',
734
+ ' - Adherence to project coding conventions (see CLAUDE.md)',
735
+ ' - Security vulnerabilities (injection, XSS, etc.)',
736
+ ' - Test coverage for new code',
737
+ ' - Breaking changes or backwards compatibility',
738
+ '5. Post the review as a PR comment using gh api',
739
+ '',
740
+ 'Use the create_session tool to start, then send_message for each step.',
741
+ ].join('\n'),
742
+ },
743
+ },
744
+ ],
745
+ };
746
+ });
747
+ server.prompt('debug_session', 'Generate a diagnostic summary for an Aegis session by reading its transcript and status.', {
748
+ sessionId: z.string().describe('The Aegis session ID to debug'),
749
+ }, async ({ sessionId }) => {
750
+ return {
751
+ messages: [
752
+ {
753
+ role: 'user',
754
+ content: {
755
+ type: 'text',
756
+ text: [
757
+ 'You are diagnosing an Aegis session that may be stuck or misbehaving.',
758
+ '',
759
+ `Session ID: ${sessionId}`,
760
+ '',
761
+ 'Steps:',
762
+ `1. Get the session status using get_status for session ${sessionId}`,
763
+ `2. Read the transcript using get_transcript for session ${sessionId}`,
764
+ `3. Capture the current terminal pane using capture_pane for session ${sessionId}`,
765
+ '4. Analyze the findings:',
766
+ ' - Is the session in an unexpected state (permission_prompt, unknown)?',
767
+ ' - Are there error messages in the transcript?',
768
+ ' - Is the session stalled (no recent activity)?',
769
+ ' - Are there repeated permission requests?',
770
+ '5. Provide a diagnostic summary with recommended actions',
771
+ '',
772
+ 'Use get_status, get_transcript, and capture_pane tools to gather data.',
773
+ ].join('\n'),
774
+ },
775
+ },
776
+ ],
777
+ };
778
+ });
779
+ return server;
780
+ }
781
+ // ── Main (stdio entrypoint) ─────────────────────────────────────────
782
+ export async function startMcpServer(port, authToken) {
783
+ const server = createMcpServer(port, authToken);
784
+ const transport = new StdioServerTransport();
785
+ await server.connect(transport);
786
+ // Server runs until stdin closes
787
+ }
788
+ //# sourceMappingURL=mcp-server.js.map