agent-orchestrator-mcp-server 0.8.6 → 0.8.7
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/README.md +11 -10
- package/package.json +2 -2
- package/shared/orchestrator-client/orchestrator-client.d.ts +13 -1
- package/shared/orchestrator-client/orchestrator-client.integration-mock.d.ts +2 -1
- package/shared/orchestrator-client/orchestrator-client.integration-mock.js +114 -0
- package/shared/orchestrator-client/orchestrator-client.js +21 -0
- package/shared/tools/get-session.js +2 -0
- package/shared/tools/manage-categories.d.ts +92 -0
- package/shared/tools/manage-categories.js +227 -0
- package/shared/tools/search-sessions.js +2 -0
- package/shared/tools.js +9 -2
- package/shared/types.d.ts +37 -0
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ MCP server for PulseMCP's agent-orchestrator: a Claude Code + MCP-powered agent-
|
|
|
30
30
|
| `start_session` | sessions | write | Create and start a new agent session |
|
|
31
31
|
| `action_session` | sessions | write | Perform actions: follow_up, pause, restart, archive, unarchive, change_mcp_servers, change_model, fork, refresh, refresh_all, update_notes, update_title, toggle_favorite, bulk_archive |
|
|
32
32
|
| `manage_enqueued_messages` | sessions | write | Manage session message queue: list, get, create, update, delete, reorder, interrupt |
|
|
33
|
+
| `manage_categories` | sessions | write | Manage dashboard categories that organize sessions: list, create, update, delete, reorder, set_session_category |
|
|
33
34
|
| `get_notifications` | notifications | read | Get/list notifications and badge count |
|
|
34
35
|
| `send_push_notification` | notifications | write | Send a push notification about a session needing human attention |
|
|
35
36
|
| `action_notification` | notifications | write | Mark read, mark all read, dismiss, dismiss all read notifications |
|
|
@@ -56,16 +57,16 @@ This server organizes tools into groups that can be selectively enabled or disab
|
|
|
56
57
|
|
|
57
58
|
Control which tools are available via the `TOOL_GROUPS` environment variable:
|
|
58
59
|
|
|
59
|
-
| Group | Description
|
|
60
|
-
| ------------------------ |
|
|
61
|
-
| `sessions` | All session tools (read + write): search, get, configs, transcript_archive, start, action, enqueued msgs |
|
|
62
|
-
| `sessions_readonly` | Session tools (read only): quick_search_sessions, get_session, get_configs, get_transcript_archive
|
|
63
|
-
| `notifications` | All notification tools (read + write): get, send, mark read, dismiss
|
|
64
|
-
| `notifications_readonly` | Notification tools (read only): get_notifications
|
|
65
|
-
| `triggers` | All trigger tools (read + write): search, create, update, delete, toggle
|
|
66
|
-
| `triggers_readonly` | Trigger tools (read only): search_triggers
|
|
67
|
-
| `health` | All health tools (read + write): health report, CLI status, maintenance
|
|
68
|
-
| `health_readonly` | Health tools (read only): get_system_health
|
|
60
|
+
| Group | Description |
|
|
61
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `sessions` | All session tools (read + write): search, get, configs, transcript_archive, start, action, enqueued msgs, categories |
|
|
63
|
+
| `sessions_readonly` | Session tools (read only): quick_search_sessions, get_session, get_configs, get_transcript_archive |
|
|
64
|
+
| `notifications` | All notification tools (read + write): get, send, mark read, dismiss |
|
|
65
|
+
| `notifications_readonly` | Notification tools (read only): get_notifications |
|
|
66
|
+
| `triggers` | All trigger tools (read + write): search, create, update, delete, toggle |
|
|
67
|
+
| `triggers_readonly` | Trigger tools (read only): search_triggers |
|
|
68
|
+
| `health` | All health tools (read + write): health report, CLI status, maintenance |
|
|
69
|
+
| `health_readonly` | Health tools (read only): get_system_health |
|
|
69
70
|
|
|
70
71
|
**Examples:**
|
|
71
72
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-orchestrator-mcp-server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "Local implementation of agent-orchestrator MCP server",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^22.10.6",
|
|
37
|
-
"tsx": "^4.
|
|
37
|
+
"tsx": "^4.22.4",
|
|
38
38
|
"typescript": "^5.7.3"
|
|
39
39
|
},
|
|
40
40
|
"keywords": [
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A client for interacting with the Agent Orchestrator REST API.
|
|
5
5
|
*/
|
|
6
|
-
import type { Session, Log, SubagentTranscript, SessionsResponse, SearchSessionsResponse, SessionActionResponse, LogsResponse, SubagentTranscriptsResponse, CreateSessionRequest, UpdateSessionRequest, CreateLogRequest, UpdateLogRequest, CreateSubagentTranscriptRequest, UpdateSubagentTranscriptRequest, SessionStatus, LogLevel, SubagentStatus, MCPServerInfo, AgentRootInfo, ConfigsResponse, SendPushNotificationResponse, EnqueuedMessage, EnqueuedMessagesResponse, EnqueuedMessageInterruptResponse, EnqueuedMessageStatus, Trigger, TriggerType, TriggerStatus, TriggersResponse, TriggerResponse, TriggerChannelsResponse, CreateTriggerRequest, UpdateTriggerRequest, Notification, NotificationsResponse, NotificationBadgeResponse, NotificationMarkAllReadResponse, NotificationDismissAllReadResponse, HealthReport, HealthActionResponse, CliStatusResponse, CliActionResponse, ForkSessionResponse, RefreshSessionResponse, RefreshAllSessionsResponse, BulkArchiveResponse, TranscriptResponse, TranscriptArchiveStatusResponse } from '../types.js';
|
|
6
|
+
import type { Session, Log, SubagentTranscript, SessionsResponse, SearchSessionsResponse, SessionActionResponse, LogsResponse, SubagentTranscriptsResponse, CreateSessionRequest, UpdateSessionRequest, CreateLogRequest, UpdateLogRequest, CreateSubagentTranscriptRequest, UpdateSubagentTranscriptRequest, SessionStatus, LogLevel, SubagentStatus, MCPServerInfo, AgentRootInfo, ConfigsResponse, SendPushNotificationResponse, EnqueuedMessage, EnqueuedMessagesResponse, EnqueuedMessageInterruptResponse, EnqueuedMessageStatus, Category, CategoriesResponse, CreateCategoryRequest, UpdateCategoryRequest, SetSessionCategoryResponse, Trigger, TriggerType, TriggerStatus, TriggersResponse, TriggerResponse, TriggerChannelsResponse, CreateTriggerRequest, UpdateTriggerRequest, Notification, NotificationsResponse, NotificationBadgeResponse, NotificationMarkAllReadResponse, NotificationDismissAllReadResponse, HealthReport, HealthActionResponse, CliStatusResponse, CliActionResponse, ForkSessionResponse, RefreshSessionResponse, RefreshAllSessionsResponse, BulkArchiveResponse, TranscriptResponse, TranscriptArchiveStatusResponse } from '../types.js';
|
|
7
7
|
/** Raw agent root shape as returned by the Rails API */
|
|
8
8
|
export interface RawAgentRoot {
|
|
9
9
|
name: string;
|
|
@@ -107,6 +107,12 @@ export interface IAgentOrchestratorClient {
|
|
|
107
107
|
deleteEnqueuedMessage(sessionId: string | number, messageId: number): Promise<void>;
|
|
108
108
|
reorderEnqueuedMessage(sessionId: string | number, messageId: number, position: number): Promise<EnqueuedMessage>;
|
|
109
109
|
interruptEnqueuedMessage(sessionId: string | number, messageId: number): Promise<EnqueuedMessageInterruptResponse>;
|
|
110
|
+
listCategories(): Promise<CategoriesResponse>;
|
|
111
|
+
createCategory(data: CreateCategoryRequest): Promise<Category>;
|
|
112
|
+
updateCategory(id: number, data: UpdateCategoryRequest): Promise<Category>;
|
|
113
|
+
deleteCategory(id: number): Promise<void>;
|
|
114
|
+
reorderCategories(ids: Array<number | 'uncategorized'>): Promise<CategoriesResponse>;
|
|
115
|
+
setSessionCategory(sessionId: string | number, categoryId: number | null): Promise<SetSessionCategoryResponse>;
|
|
110
116
|
listTriggers(options?: {
|
|
111
117
|
trigger_type?: TriggerType;
|
|
112
118
|
status?: TriggerStatus;
|
|
@@ -234,6 +240,12 @@ export declare class AgentOrchestratorClient implements IAgentOrchestratorClient
|
|
|
234
240
|
deleteEnqueuedMessage(sessionId: string | number, messageId: number): Promise<void>;
|
|
235
241
|
reorderEnqueuedMessage(sessionId: string | number, messageId: number, position: number): Promise<EnqueuedMessage>;
|
|
236
242
|
interruptEnqueuedMessage(sessionId: string | number, messageId: number): Promise<EnqueuedMessageInterruptResponse>;
|
|
243
|
+
listCategories(): Promise<CategoriesResponse>;
|
|
244
|
+
createCategory(data: CreateCategoryRequest): Promise<Category>;
|
|
245
|
+
updateCategory(id: number, data: UpdateCategoryRequest): Promise<Category>;
|
|
246
|
+
deleteCategory(id: number): Promise<void>;
|
|
247
|
+
reorderCategories(ids: Array<number | 'uncategorized'>): Promise<CategoriesResponse>;
|
|
248
|
+
setSessionCategory(sessionId: string | number, categoryId: number | null): Promise<SetSessionCategoryResponse>;
|
|
237
249
|
listTriggers(options?: {
|
|
238
250
|
trigger_type?: TriggerType;
|
|
239
251
|
status?: TriggerStatus;
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* Used in integration tests to simulate API responses without hitting real endpoints.
|
|
5
5
|
*/
|
|
6
6
|
import type { IAgentOrchestratorClient } from './orchestrator-client.js';
|
|
7
|
-
import type { Session, Log, SubagentTranscript } from '../types.js';
|
|
7
|
+
import type { Session, Log, SubagentTranscript, Category } from '../types.js';
|
|
8
8
|
interface MockData {
|
|
9
9
|
sessions?: Session[];
|
|
10
10
|
logs?: Log[];
|
|
11
11
|
subagentTranscripts?: SubagentTranscript[];
|
|
12
|
+
categories?: Category[];
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* Creates a mock Agent Orchestrator client for integration testing.
|
|
@@ -64,10 +64,23 @@ export function createIntegrationMockOrchestratorClient(initialMockData) {
|
|
|
64
64
|
updated_at: '2025-01-15T14:31:45Z',
|
|
65
65
|
},
|
|
66
66
|
],
|
|
67
|
+
categories: initialMockData?.categories || [
|
|
68
|
+
{
|
|
69
|
+
id: 1,
|
|
70
|
+
name: 'Active Work',
|
|
71
|
+
description: null,
|
|
72
|
+
position: 0,
|
|
73
|
+
is_frozen: false,
|
|
74
|
+
session_count: 1,
|
|
75
|
+
created_at: '2025-01-15T14:00:00Z',
|
|
76
|
+
updated_at: '2025-01-15T14:00:00Z',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
67
79
|
};
|
|
68
80
|
let sessionIdCounter = mockData.sessions?.length || 1;
|
|
69
81
|
let logIdCounter = mockData.logs?.length || 1;
|
|
70
82
|
let transcriptIdCounter = mockData.subagentTranscripts?.length || 1;
|
|
83
|
+
let categoryIdCounter = mockData.categories?.length || 1;
|
|
71
84
|
return {
|
|
72
85
|
mockData,
|
|
73
86
|
async listSessions(options) {
|
|
@@ -598,6 +611,107 @@ export function createIntegrationMockOrchestratorClient(initialMockData) {
|
|
|
598
611
|
throw new Error(`API Error (404): Session not found`);
|
|
599
612
|
return { session, message: 'Message sent as interrupt' };
|
|
600
613
|
},
|
|
614
|
+
// Categories
|
|
615
|
+
async listCategories() {
|
|
616
|
+
const categories = [...(mockData.categories || [])].sort((a, b) => a.position - b.position);
|
|
617
|
+
return { categories };
|
|
618
|
+
},
|
|
619
|
+
async createCategory(data) {
|
|
620
|
+
const exists = mockData.categories?.some((c) => c.name.toLowerCase() === data.name.toLowerCase());
|
|
621
|
+
if (exists) {
|
|
622
|
+
throw new Error(`API Error (422): Name has already been taken`);
|
|
623
|
+
}
|
|
624
|
+
categoryIdCounter++;
|
|
625
|
+
const maxPosition = (mockData.categories || []).reduce((max, c) => Math.max(max, c.position), -1);
|
|
626
|
+
const category = {
|
|
627
|
+
id: categoryIdCounter,
|
|
628
|
+
name: data.name,
|
|
629
|
+
description: data.description ? data.description : null,
|
|
630
|
+
position: maxPosition + 1,
|
|
631
|
+
is_frozen: false,
|
|
632
|
+
session_count: 0,
|
|
633
|
+
created_at: new Date().toISOString(),
|
|
634
|
+
updated_at: new Date().toISOString(),
|
|
635
|
+
};
|
|
636
|
+
mockData.categories?.push(category);
|
|
637
|
+
return category;
|
|
638
|
+
},
|
|
639
|
+
async updateCategory(id, data) {
|
|
640
|
+
const category = mockData.categories?.find((c) => c.id === id);
|
|
641
|
+
if (!category) {
|
|
642
|
+
throw new Error(`API Error (404): Category not found`);
|
|
643
|
+
}
|
|
644
|
+
if (data.name !== undefined)
|
|
645
|
+
category.name = data.name;
|
|
646
|
+
if (data.description !== undefined) {
|
|
647
|
+
category.description = data.description ? data.description : null;
|
|
648
|
+
}
|
|
649
|
+
if (data.is_frozen !== undefined)
|
|
650
|
+
category.is_frozen = data.is_frozen;
|
|
651
|
+
category.updated_at = new Date().toISOString();
|
|
652
|
+
return category;
|
|
653
|
+
},
|
|
654
|
+
async deleteCategory(id) {
|
|
655
|
+
const index = mockData.categories?.findIndex((c) => c.id === id);
|
|
656
|
+
if (index === undefined || index === -1) {
|
|
657
|
+
throw new Error(`API Error (404): Category not found`);
|
|
658
|
+
}
|
|
659
|
+
mockData.categories?.splice(index, 1);
|
|
660
|
+
// Member sessions fall back to Uncategorized
|
|
661
|
+
mockData.sessions?.forEach((s) => {
|
|
662
|
+
if (s.category_id === id) {
|
|
663
|
+
s.category_id = null;
|
|
664
|
+
s.category = null;
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
},
|
|
668
|
+
async reorderCategories(ids) {
|
|
669
|
+
// Apply new positions for the numeric ids in the order provided.
|
|
670
|
+
// The "uncategorized" sentinel positions the Uncategorized section and is
|
|
671
|
+
// not represented in the returned category list. Categories omitted from
|
|
672
|
+
// `ids` keep their stored position. This is an approximation of the
|
|
673
|
+
// server's ordering; real position bookkeeping lives Rails-side.
|
|
674
|
+
let position = 0;
|
|
675
|
+
for (const id of ids) {
|
|
676
|
+
if (id === 'uncategorized') {
|
|
677
|
+
position++;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const category = mockData.categories?.find((c) => c.id === id);
|
|
681
|
+
if (category) {
|
|
682
|
+
category.position = position;
|
|
683
|
+
category.updated_at = new Date().toISOString();
|
|
684
|
+
}
|
|
685
|
+
position++;
|
|
686
|
+
}
|
|
687
|
+
const categories = [...(mockData.categories || [])].sort((a, b) => a.position - b.position);
|
|
688
|
+
return { categories };
|
|
689
|
+
},
|
|
690
|
+
async setSessionCategory(sessionId, categoryId) {
|
|
691
|
+
const session = mockData.sessions?.find((s) => s.id === Number(sessionId) || s.slug === String(sessionId));
|
|
692
|
+
if (!session) {
|
|
693
|
+
throw new Error(`API Error (404): Session not found`);
|
|
694
|
+
}
|
|
695
|
+
if (categoryId == null) {
|
|
696
|
+
session.category_id = null;
|
|
697
|
+
session.category = null;
|
|
698
|
+
session.updated_at = new Date().toISOString();
|
|
699
|
+
return { session, message: 'Session moved to Uncategorized' };
|
|
700
|
+
}
|
|
701
|
+
const category = mockData.categories?.find((c) => c.id === categoryId);
|
|
702
|
+
if (!category) {
|
|
703
|
+
throw new Error(`API Error (404): Category #${categoryId} not found`);
|
|
704
|
+
}
|
|
705
|
+
session.category_id = category.id;
|
|
706
|
+
session.category = {
|
|
707
|
+
id: category.id,
|
|
708
|
+
name: category.name,
|
|
709
|
+
position: category.position,
|
|
710
|
+
is_frozen: category.is_frozen,
|
|
711
|
+
};
|
|
712
|
+
session.updated_at = new Date().toISOString();
|
|
713
|
+
return { session, message: 'Session assigned to category' };
|
|
714
|
+
},
|
|
601
715
|
// Triggers
|
|
602
716
|
async listTriggers(options) {
|
|
603
717
|
const page = options?.page || 1;
|
|
@@ -354,6 +354,27 @@ export class AgentOrchestratorClient {
|
|
|
354
354
|
async interruptEnqueuedMessage(sessionId, messageId) {
|
|
355
355
|
return this.request('POST', `/sessions/${sessionId}/enqueued_messages/${messageId}/interrupt`);
|
|
356
356
|
}
|
|
357
|
+
// Categories
|
|
358
|
+
async listCategories() {
|
|
359
|
+
return this.request('GET', '/categories');
|
|
360
|
+
}
|
|
361
|
+
async createCategory(data) {
|
|
362
|
+
const response = await this.request('POST', '/categories', data);
|
|
363
|
+
return response.category;
|
|
364
|
+
}
|
|
365
|
+
async updateCategory(id, data) {
|
|
366
|
+
const response = await this.request('PATCH', `/categories/${id}`, data);
|
|
367
|
+
return response.category;
|
|
368
|
+
}
|
|
369
|
+
async deleteCategory(id) {
|
|
370
|
+
await this.request('DELETE', `/categories/${id}`);
|
|
371
|
+
}
|
|
372
|
+
async reorderCategories(ids) {
|
|
373
|
+
return this.request('POST', '/categories/reorder', { ids });
|
|
374
|
+
}
|
|
375
|
+
async setSessionCategory(sessionId, categoryId) {
|
|
376
|
+
return this.request('PATCH', `/sessions/${sessionId}/set_category`, { category_id: categoryId });
|
|
377
|
+
}
|
|
357
378
|
// Triggers
|
|
358
379
|
async listTriggers(options) {
|
|
359
380
|
const { trigger_type, ...rest } = options ?? {};
|
|
@@ -58,6 +58,8 @@ function formatSessionDetails(session, includeTranscript) {
|
|
|
58
58
|
];
|
|
59
59
|
if (session.slug)
|
|
60
60
|
lines.push(`- **Slug:** ${session.slug}`);
|
|
61
|
+
if (session.category)
|
|
62
|
+
lines.push(`- **Category:** ${session.category.name}`);
|
|
61
63
|
lines.push('');
|
|
62
64
|
lines.push('### Git Configuration');
|
|
63
65
|
if (session.git_root)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { IAgentOrchestratorClient } from '../orchestrator-client/orchestrator-client.js';
|
|
4
|
+
export declare const ManageCategoriesSchema: z.ZodObject<{
|
|
5
|
+
action: z.ZodEnum<["list", "create", "update", "delete", "reorder", "set_session_category"]>;
|
|
6
|
+
category_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
7
|
+
name: z.ZodOptional<z.ZodString>;
|
|
8
|
+
description: z.ZodOptional<z.ZodString>;
|
|
9
|
+
is_frozen: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
ids: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodNumber, z.ZodLiteral<"uncategorized">]>, "many">>;
|
|
11
|
+
session_id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
action: "list" | "create" | "update" | "delete" | "reorder" | "set_session_category";
|
|
14
|
+
description?: string | undefined;
|
|
15
|
+
name?: string | undefined;
|
|
16
|
+
session_id?: string | number | undefined;
|
|
17
|
+
category_id?: number | null | undefined;
|
|
18
|
+
is_frozen?: boolean | undefined;
|
|
19
|
+
ids?: (number | "uncategorized")[] | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
action: "list" | "create" | "update" | "delete" | "reorder" | "set_session_category";
|
|
22
|
+
description?: string | undefined;
|
|
23
|
+
name?: string | undefined;
|
|
24
|
+
session_id?: string | number | undefined;
|
|
25
|
+
category_id?: number | null | undefined;
|
|
26
|
+
is_frozen?: boolean | undefined;
|
|
27
|
+
ids?: (number | "uncategorized")[] | undefined;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function manageCategoriesTool(_server: Server, clientFactory: () => IAgentOrchestratorClient): {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: "object";
|
|
34
|
+
properties: {
|
|
35
|
+
action: {
|
|
36
|
+
type: string;
|
|
37
|
+
enum: readonly ["list", "create", "update", "delete", "reorder", "set_session_category"];
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
40
|
+
category_id: {
|
|
41
|
+
type: string[];
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
name: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
description: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
};
|
|
52
|
+
is_frozen: {
|
|
53
|
+
type: string;
|
|
54
|
+
description: string;
|
|
55
|
+
};
|
|
56
|
+
ids: {
|
|
57
|
+
type: string;
|
|
58
|
+
items: {
|
|
59
|
+
oneOf: ({
|
|
60
|
+
type: string;
|
|
61
|
+
enum?: undefined;
|
|
62
|
+
} | {
|
|
63
|
+
type: string;
|
|
64
|
+
enum: string[];
|
|
65
|
+
})[];
|
|
66
|
+
};
|
|
67
|
+
description: string;
|
|
68
|
+
};
|
|
69
|
+
session_id: {
|
|
70
|
+
oneOf: {
|
|
71
|
+
type: string;
|
|
72
|
+
}[];
|
|
73
|
+
description: string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
required: string[];
|
|
77
|
+
};
|
|
78
|
+
handler: (args: unknown) => Promise<{
|
|
79
|
+
content: {
|
|
80
|
+
type: string;
|
|
81
|
+
text: string;
|
|
82
|
+
}[];
|
|
83
|
+
isError: boolean;
|
|
84
|
+
} | {
|
|
85
|
+
content: {
|
|
86
|
+
type: string;
|
|
87
|
+
text: string;
|
|
88
|
+
}[];
|
|
89
|
+
isError?: undefined;
|
|
90
|
+
}>;
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=manage-categories.d.ts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const ACTION_ENUM = [
|
|
3
|
+
'list',
|
|
4
|
+
'create',
|
|
5
|
+
'update',
|
|
6
|
+
'delete',
|
|
7
|
+
'reorder',
|
|
8
|
+
'set_session_category',
|
|
9
|
+
];
|
|
10
|
+
export const ManageCategoriesSchema = z.object({
|
|
11
|
+
action: z.enum(ACTION_ENUM),
|
|
12
|
+
category_id: z.number().nullable().optional(),
|
|
13
|
+
name: z.string().optional(),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
is_frozen: z.boolean().optional(),
|
|
16
|
+
ids: z.array(z.union([z.number(), z.literal('uncategorized')])).optional(),
|
|
17
|
+
session_id: z.union([z.string(), z.number()]).optional(),
|
|
18
|
+
});
|
|
19
|
+
const TOOL_DESCRIPTION = `Manage categories used to organize sessions on the Agent Orchestrator dashboard.
|
|
20
|
+
|
|
21
|
+
Categories are the named sections sessions are grouped under. Sessions not assigned to a category fall under the built-in "Uncategorized" section.
|
|
22
|
+
|
|
23
|
+
**Actions:**
|
|
24
|
+
- **list**: List all categories ordered by position, with session counts.
|
|
25
|
+
- **create**: Create a new category (requires "name"; optional "description"). Names are unique case-insensitively (max 100 chars); description max 1000 chars.
|
|
26
|
+
- **update**: Rename, re-describe, or freeze/unfreeze a category (requires "category_id"; any subset of "name", "description", "is_frozen"). Omitted fields are left unchanged.
|
|
27
|
+
- **delete**: Delete a category (requires "category_id"). Sessions in it fall back to Uncategorized.
|
|
28
|
+
- **reorder**: Set the top-to-bottom order of categories (requires "ids" — an array of category IDs). Categories omitted keep their existing position. Include the string "uncategorized" to position the Uncategorized section.
|
|
29
|
+
- **set_session_category**: Assign a session to a category (requires "session_id"; "category_id" to assign, or omit/null to clear to Uncategorized).
|
|
30
|
+
|
|
31
|
+
**Note:** All freeze state uses "is_frozen".`;
|
|
32
|
+
export function manageCategoriesTool(_server, clientFactory) {
|
|
33
|
+
return {
|
|
34
|
+
name: 'manage_categories',
|
|
35
|
+
description: TOOL_DESCRIPTION,
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
action: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
enum: ACTION_ENUM,
|
|
42
|
+
description: 'The category management action to perform.',
|
|
43
|
+
},
|
|
44
|
+
category_id: {
|
|
45
|
+
type: ['number', 'null'],
|
|
46
|
+
description: 'Category ID. Required for "update" and "delete". For "set_session_category", the target category to assign (omit or null to clear to Uncategorized).',
|
|
47
|
+
},
|
|
48
|
+
name: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Category name. Required for "create"; optional for "update". Unique case-insensitively, max 100 chars.',
|
|
51
|
+
},
|
|
52
|
+
description: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'Category description. Optional for "create" and "update". Max 1000 chars; blank clears it.',
|
|
55
|
+
},
|
|
56
|
+
is_frozen: {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
description: 'Freeze (true) or unfreeze (false) the category. Optional for "update".',
|
|
59
|
+
},
|
|
60
|
+
ids: {
|
|
61
|
+
type: 'array',
|
|
62
|
+
items: { oneOf: [{ type: 'number' }, { type: 'string', enum: ['uncategorized'] }] },
|
|
63
|
+
description: 'Required for "reorder". New top-to-bottom order of category IDs. Categories omitted keep their position. Use the string "uncategorized" to position the Uncategorized section.',
|
|
64
|
+
},
|
|
65
|
+
session_id: {
|
|
66
|
+
oneOf: [{ type: 'string' }, { type: 'number' }],
|
|
67
|
+
description: 'Session ID (numeric) or slug (string). Required for "set_session_category".',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['action'],
|
|
71
|
+
},
|
|
72
|
+
handler: async (args) => {
|
|
73
|
+
try {
|
|
74
|
+
const validated = ManageCategoriesSchema.parse(args);
|
|
75
|
+
const client = clientFactory();
|
|
76
|
+
const { action, category_id, name, description, is_frozen, ids, session_id } = validated;
|
|
77
|
+
const formatCategory = (cat) => [
|
|
78
|
+
`### ${cat.name} (ID: ${cat.id})`,
|
|
79
|
+
`- **Position:** ${cat.position}`,
|
|
80
|
+
`- **Frozen:** ${cat.is_frozen}`,
|
|
81
|
+
cat.description ? `- **Description:** ${cat.description}` : null,
|
|
82
|
+
`- **Sessions:** ${cat.session_count}`,
|
|
83
|
+
]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join('\n');
|
|
86
|
+
let result;
|
|
87
|
+
switch (action) {
|
|
88
|
+
case 'list': {
|
|
89
|
+
const response = await client.listCategories();
|
|
90
|
+
if (response.categories.length === 0) {
|
|
91
|
+
result = '## Categories\n\nNo categories found.';
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
result = [
|
|
95
|
+
`## Categories (${response.categories.length})`,
|
|
96
|
+
'',
|
|
97
|
+
...response.categories.map(formatCategory),
|
|
98
|
+
].join('\n\n');
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'create': {
|
|
103
|
+
if (!name) {
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{ type: 'text', text: 'Error: "name" is required for the "create" action.' },
|
|
107
|
+
],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const cat = await client.createCategory({ name, description });
|
|
112
|
+
result = ['## Category Created', '', formatCategory(cat)].join('\n');
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'update': {
|
|
116
|
+
if (category_id == null) {
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: 'Error: "category_id" is required for the "update" action.',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (name === undefined && description === undefined && is_frozen === undefined) {
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: 'Error: provide at least one of "name", "description", or "is_frozen" for the "update" action.',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const cat = await client.updateCategory(category_id, {
|
|
139
|
+
...(name !== undefined && { name }),
|
|
140
|
+
...(description !== undefined && { description }),
|
|
141
|
+
...(is_frozen !== undefined && { is_frozen }),
|
|
142
|
+
});
|
|
143
|
+
result = ['## Category Updated', '', formatCategory(cat)].join('\n');
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'delete': {
|
|
147
|
+
if (category_id == null) {
|
|
148
|
+
return {
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: 'text',
|
|
152
|
+
text: 'Error: "category_id" is required for the "delete" action.',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
await client.deleteCategory(category_id);
|
|
159
|
+
result = `## Category Deleted\n\nCategory ${category_id} has been deleted. Its sessions fall back to Uncategorized.`;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'reorder': {
|
|
163
|
+
if (!ids || ids.length === 0) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: 'text',
|
|
168
|
+
text: 'Error: "ids" (a non-empty array) is required for the "reorder" action.',
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const response = await client.reorderCategories(ids);
|
|
175
|
+
result = [
|
|
176
|
+
'## Categories Reordered',
|
|
177
|
+
'',
|
|
178
|
+
...response.categories.map(formatCategory),
|
|
179
|
+
].join('\n\n');
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case 'set_session_category': {
|
|
183
|
+
if (session_id === undefined) {
|
|
184
|
+
return {
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: 'text',
|
|
188
|
+
text: 'Error: "session_id" is required for the "set_session_category" action.',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
isError: true,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const response = await client.setSessionCategory(session_id, category_id ?? null);
|
|
195
|
+
result = [
|
|
196
|
+
'## Session Category Updated',
|
|
197
|
+
'',
|
|
198
|
+
`- **Session ID:** ${response.session.id}`,
|
|
199
|
+
`- **Category:** ${response.session.category ? response.session.category.name : 'Uncategorized'}`,
|
|
200
|
+
`- **Result:** ${response.message}`,
|
|
201
|
+
].join('\n');
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
default: {
|
|
205
|
+
const _exhaustiveCheck = action;
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: 'text', text: `Error: Unknown action "${_exhaustiveCheck}"` }],
|
|
208
|
+
isError: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { content: [{ type: 'text', text: result }] };
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return {
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: 'text',
|
|
219
|
+
text: `Error managing categories: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
isError: true,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -49,6 +49,8 @@ function formatSession(session) {
|
|
|
49
49
|
];
|
|
50
50
|
if (session.slug)
|
|
51
51
|
lines.push(`- **Slug:** ${session.slug}`);
|
|
52
|
+
if (session.category)
|
|
53
|
+
lines.push(`- **Category:** ${session.category.name}`);
|
|
52
54
|
if (session.git_root)
|
|
53
55
|
lines.push(`- **Repository:** ${session.git_root}`);
|
|
54
56
|
if (session.branch)
|
package/shared/tools.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
//
|
|
2
|
+
// 17 tools across 4 domains + 1 composite group
|
|
3
3
|
import { quickSearchSessionsTool } from './tools/search-sessions.js';
|
|
4
4
|
import { startSessionTool } from './tools/start-session.js';
|
|
5
5
|
import { getSessionTool } from './tools/get-session.js';
|
|
6
6
|
import { actionSessionTool, selfSessionActionSessionTool } from './tools/action-session.js';
|
|
7
7
|
import { getConfigsTool } from './tools/get-configs.js';
|
|
8
8
|
import { manageEnqueuedMessagesTool } from './tools/manage-enqueued-messages.js';
|
|
9
|
+
import { manageCategoriesTool } from './tools/manage-categories.js';
|
|
9
10
|
import { sendPushNotificationTool } from './tools/send-push-notification.js';
|
|
10
11
|
import { getNotificationsTool } from './tools/get-notifications.js';
|
|
11
12
|
import { actionNotificationTool } from './tools/action-notification.js';
|
|
@@ -61,7 +62,7 @@ export function parseEnabledToolGroups(enabledGroupsParam) {
|
|
|
61
62
|
/**
|
|
62
63
|
* All available tools with their group assignments.
|
|
63
64
|
*
|
|
64
|
-
*
|
|
65
|
+
* 17 tools across 4 domains + 1 composite group:
|
|
65
66
|
* - quick_search_sessions: Quick title-based search/list/get sessions by ID (sessions, read)
|
|
66
67
|
* - get_session: Get detailed session info with optional logs/transcripts (sessions, read; self_session)
|
|
67
68
|
* - get_configs: Fetch all static configuration (sessions, read; self_session)
|
|
@@ -69,6 +70,7 @@ export function parseEnabledToolGroups(enabledGroupsParam) {
|
|
|
69
70
|
* - start_session: Create a new session (sessions, write)
|
|
70
71
|
* - action_session: Perform session actions (sessions, write; self_session: filtered to update_notes, update_title, archive)
|
|
71
72
|
* - manage_enqueued_messages: Manage session message queue (sessions, write)
|
|
73
|
+
* - manage_categories: Manage dashboard categories and session assignment (sessions, write)
|
|
72
74
|
* - get_notifications: Get/list notifications and badge count (notifications, read)
|
|
73
75
|
* - send_push_notification: Send a push notification (notifications, write; self_session)
|
|
74
76
|
* - action_notification: Mark read, dismiss notifications (notifications, write)
|
|
@@ -119,6 +121,11 @@ const ALL_TOOLS = [
|
|
|
119
121
|
group: 'sessions',
|
|
120
122
|
isWriteOperation: true,
|
|
121
123
|
},
|
|
124
|
+
{
|
|
125
|
+
factory: manageCategoriesTool,
|
|
126
|
+
group: 'sessions',
|
|
127
|
+
isWriteOperation: true,
|
|
128
|
+
},
|
|
122
129
|
// Notification tools - read operations
|
|
123
130
|
{
|
|
124
131
|
factory: getNotificationsTool,
|
package/shared/types.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface Session {
|
|
|
32
32
|
job_id: string | null;
|
|
33
33
|
running_job_id: string | null;
|
|
34
34
|
archived_at: string | null;
|
|
35
|
+
category_id?: number | null;
|
|
36
|
+
category?: CategorySummary | null;
|
|
35
37
|
created_at: string;
|
|
36
38
|
updated_at: string;
|
|
37
39
|
transcript?: string;
|
|
@@ -193,6 +195,41 @@ export interface UpdateSubagentTranscriptRequest {
|
|
|
193
195
|
total_tokens?: number;
|
|
194
196
|
tool_use_count?: number;
|
|
195
197
|
}
|
|
198
|
+
export interface Category {
|
|
199
|
+
id: number;
|
|
200
|
+
name: string;
|
|
201
|
+
description: string | null;
|
|
202
|
+
position: number;
|
|
203
|
+
is_frozen: boolean;
|
|
204
|
+
session_count: number;
|
|
205
|
+
created_at: string;
|
|
206
|
+
updated_at: string;
|
|
207
|
+
}
|
|
208
|
+
export interface CategorySummary {
|
|
209
|
+
id: number;
|
|
210
|
+
name: string;
|
|
211
|
+
position: number;
|
|
212
|
+
is_frozen: boolean;
|
|
213
|
+
}
|
|
214
|
+
export interface CategoriesResponse {
|
|
215
|
+
categories: Category[];
|
|
216
|
+
}
|
|
217
|
+
export interface CategoryResponse {
|
|
218
|
+
category: Category;
|
|
219
|
+
}
|
|
220
|
+
export interface CreateCategoryRequest {
|
|
221
|
+
name: string;
|
|
222
|
+
description?: string;
|
|
223
|
+
}
|
|
224
|
+
export interface UpdateCategoryRequest {
|
|
225
|
+
name?: string;
|
|
226
|
+
description?: string;
|
|
227
|
+
is_frozen?: boolean;
|
|
228
|
+
}
|
|
229
|
+
export interface SetSessionCategoryResponse {
|
|
230
|
+
session: Session;
|
|
231
|
+
message: string;
|
|
232
|
+
}
|
|
196
233
|
export type EnqueuedMessageStatus = 'pending' | 'processing' | 'sent';
|
|
197
234
|
export interface EnqueuedMessage {
|
|
198
235
|
id: number;
|