agent-orchestrator-mcp-server 0.2.5 → 0.4.1
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 +10 -9
- package/build/index.js +8 -0
- package/package.json +1 -1
- package/shared/allowed-agent-roots.d.ts +36 -0
- package/shared/allowed-agent-roots.js +86 -0
- package/shared/orchestrator-client/orchestrator-client.d.ts +5 -2
- package/shared/orchestrator-client/orchestrator-client.integration-mock.js +0 -1
- package/shared/orchestrator-client/orchestrator-client.js +66 -0
- package/shared/resources.js +1 -1
- package/shared/tools/action-session.js +13 -0
- package/shared/tools/action-trigger.js +13 -0
- package/shared/tools/get-configs.js +15 -3
- package/shared/tools/search-sessions.d.ts +3 -10
- package/shared/tools/search-sessions.js +10 -15
- package/shared/tools/start-session.d.ts +3 -3
- package/shared/tools/start-session.js +25 -1
- package/shared/tools.js +3 -3
- package/shared/types.d.ts +0 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ MCP server for PulseMCP's agent-orchestrator: a Claude Code + MCP-powered agent-
|
|
|
23
23
|
|
|
24
24
|
| Tool | Tool Group | Read/Write | Description |
|
|
25
25
|
| -------------------------- | ------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
-
| `
|
|
26
|
+
| `quick_search_sessions` | sessions | read | Quick title-based search/list sessions with optional ID lookup, title query, and status filter |
|
|
27
27
|
| `get_session` | sessions | read | Get detailed session info with optional logs, transcripts, and transcript format (text/json) |
|
|
28
28
|
| `get_configs` | sessions | read | Fetch all static configuration (MCP servers, agent roots, stop conditions) |
|
|
29
29
|
| `get_transcript_archive` | sessions | read | Get download URL and metadata for the transcript archive zip file |
|
|
@@ -59,7 +59,7 @@ Control which tools are available via the `TOOL_GROUPS` environment variable:
|
|
|
59
59
|
| Group | Description |
|
|
60
60
|
| ------------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
61
61
|
| `sessions` | All session tools (read + write): search, get, configs, transcript_archive, start, action, enqueued msgs |
|
|
62
|
-
| `sessions_readonly` | Session tools (read only):
|
|
62
|
+
| `sessions_readonly` | Session tools (read only): quick_search_sessions, get_session, get_configs, get_transcript_archive |
|
|
63
63
|
| `notifications` | All notification tools (read + write): get, send, mark read, dismiss |
|
|
64
64
|
| `notifications_readonly` | Notification tools (read only): get_notifications |
|
|
65
65
|
| `triggers` | All trigger tools (read + write): search, create, update, delete, toggle |
|
|
@@ -109,13 +109,14 @@ TOOL_GROUPS=sessions,notifications_readonly,triggers_readonly,health_readonly
|
|
|
109
109
|
|
|
110
110
|
### Environment Variables
|
|
111
111
|
|
|
112
|
-
| Variable | Required | Description
|
|
113
|
-
| ----------------------------- | -------- |
|
|
114
|
-
| `AGENT_ORCHESTRATOR_BASE_URL` | Yes | Base URL for the orchestrator API
|
|
115
|
-
| `AGENT_ORCHESTRATOR_API_KEY` | Yes | API key for authentication
|
|
116
|
-
| `TOOL_GROUPS` | No | Comma-separated list of enabled tool groups
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
112
|
+
| Variable | Required | Description | Default |
|
|
113
|
+
| ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
114
|
+
| `AGENT_ORCHESTRATOR_BASE_URL` | Yes | Base URL for the orchestrator API | - |
|
|
115
|
+
| `AGENT_ORCHESTRATOR_API_KEY` | Yes | API key for authentication | - |
|
|
116
|
+
| `TOOL_GROUPS` | No | Comma-separated list of enabled tool groups | `sessions,notifications,triggers,health` (all groups) |
|
|
117
|
+
| `ALLOWED_AGENT_ROOTS` | No | Comma-separated list of allowed agent root names. When set, constrains the server to only permit sessions with these specific agent roots and their exact default MCP servers. Also blocks `change_mcp_servers` and trigger creation/updates. | - |
|
|
118
|
+
| `SKIP_HEALTH_CHECKS` | No | Skip API connectivity check at startup | `false` |
|
|
119
|
+
| `HEALTH_CHECK_TIMEOUT` | No | Health check timeout in milliseconds | `10000` |
|
|
119
120
|
|
|
120
121
|
### Claude Desktop
|
|
121
122
|
|
package/build/index.js
CHANGED
|
@@ -34,6 +34,11 @@ function validateEnvironment() {
|
|
|
34
34
|
description: 'Comma-separated list of tool groups to enable (sessions,sessions_readonly,notifications,notifications_readonly,triggers,triggers_readonly,health,health_readonly)',
|
|
35
35
|
defaultValue: 'all groups enabled',
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
name: 'ALLOWED_AGENT_ROOTS',
|
|
39
|
+
description: 'Comma-separated list of allowed agent root names. When set, only these agent roots are shown in get_configs and allowed in start_session (with their exact default MCP servers only)',
|
|
40
|
+
defaultValue: 'all agent roots allowed',
|
|
41
|
+
},
|
|
37
42
|
{
|
|
38
43
|
name: 'SKIP_HEALTH_CHECKS',
|
|
39
44
|
description: 'Skip API connectivity check at startup (set to "true" to skip)',
|
|
@@ -72,6 +77,9 @@ function validateEnvironment() {
|
|
|
72
77
|
if (process.env.TOOL_GROUPS) {
|
|
73
78
|
logWarning('config', `Tool groups filter active: ${process.env.TOOL_GROUPS}`);
|
|
74
79
|
}
|
|
80
|
+
if (process.env.ALLOWED_AGENT_ROOTS) {
|
|
81
|
+
logWarning('config', `Allowed agent roots filter active: ${process.env.ALLOWED_AGENT_ROOTS}`);
|
|
82
|
+
}
|
|
75
83
|
}
|
|
76
84
|
// =============================================================================
|
|
77
85
|
// HEALTH CHECKS
|
package/package.json
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ALLOWED_AGENT_ROOTS constraint system.
|
|
3
|
+
*
|
|
4
|
+
* When the ALLOWED_AGENT_ROOTS environment variable is set (comma-separated list of agent root names),
|
|
5
|
+
* the server is constrained to only allow sessions using those specific agent roots
|
|
6
|
+
* with their exact default MCP server configurations.
|
|
7
|
+
*
|
|
8
|
+
* This provides a way to lock down the server to only permit preconfigured invocations.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentRootInfo } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Parse the ALLOWED_AGENT_ROOTS environment variable into a list of allowed agent root names.
|
|
13
|
+
* Returns null if the env var is not set (meaning no restrictions).
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseAllowedAgentRoots(envValue?: string): string[] | null;
|
|
16
|
+
/**
|
|
17
|
+
* Filter agent roots to only include those in the allowed list.
|
|
18
|
+
* If allowedRoots is null, returns all agent roots (no filtering).
|
|
19
|
+
*/
|
|
20
|
+
export declare function filterAgentRoots(agentRoots: AgentRootInfo[], allowedRoots: string[] | null): AgentRootInfo[];
|
|
21
|
+
export interface AgentRootValidationResult {
|
|
22
|
+
valid: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate a start_session request against the allowed agent roots constraints.
|
|
27
|
+
*
|
|
28
|
+
* When ALLOWED_AGENT_ROOTS is set:
|
|
29
|
+
* - git_root must match one of the allowed agent roots
|
|
30
|
+
* - mcp_servers must exactly match the default_mcp_servers of that agent root
|
|
31
|
+
* (no more, no less — any deviation is rejected)
|
|
32
|
+
*
|
|
33
|
+
* Returns { valid: true } if the request is allowed, or { valid: false, error: string } if not.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateAgentRootConstraints(allowedRoots: string[] | null, agentRoots: AgentRootInfo[], gitRoot?: string, mcpServers?: string[]): AgentRootValidationResult;
|
|
36
|
+
//# sourceMappingURL=allowed-agent-roots.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ALLOWED_AGENT_ROOTS constraint system.
|
|
3
|
+
*
|
|
4
|
+
* When the ALLOWED_AGENT_ROOTS environment variable is set (comma-separated list of agent root names),
|
|
5
|
+
* the server is constrained to only allow sessions using those specific agent roots
|
|
6
|
+
* with their exact default MCP server configurations.
|
|
7
|
+
*
|
|
8
|
+
* This provides a way to lock down the server to only permit preconfigured invocations.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Parse the ALLOWED_AGENT_ROOTS environment variable into a list of allowed agent root names.
|
|
12
|
+
* Returns null if the env var is not set (meaning no restrictions).
|
|
13
|
+
*/
|
|
14
|
+
export function parseAllowedAgentRoots(envValue) {
|
|
15
|
+
const value = envValue ?? process.env.ALLOWED_AGENT_ROOTS ?? '';
|
|
16
|
+
if (!value.trim()) {
|
|
17
|
+
return null; // No restrictions
|
|
18
|
+
}
|
|
19
|
+
const roots = value
|
|
20
|
+
.split(',')
|
|
21
|
+
.map((r) => r.trim())
|
|
22
|
+
.filter((r) => r.length > 0);
|
|
23
|
+
if (roots.length === 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return roots;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Filter agent roots to only include those in the allowed list.
|
|
30
|
+
* If allowedRoots is null, returns all agent roots (no filtering).
|
|
31
|
+
*/
|
|
32
|
+
export function filterAgentRoots(agentRoots, allowedRoots) {
|
|
33
|
+
if (allowedRoots === null) {
|
|
34
|
+
return agentRoots;
|
|
35
|
+
}
|
|
36
|
+
return agentRoots.filter((root) => allowedRoots.includes(root.name));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validate a start_session request against the allowed agent roots constraints.
|
|
40
|
+
*
|
|
41
|
+
* When ALLOWED_AGENT_ROOTS is set:
|
|
42
|
+
* - git_root must match one of the allowed agent roots
|
|
43
|
+
* - mcp_servers must exactly match the default_mcp_servers of that agent root
|
|
44
|
+
* (no more, no less — any deviation is rejected)
|
|
45
|
+
*
|
|
46
|
+
* Returns { valid: true } if the request is allowed, or { valid: false, error: string } if not.
|
|
47
|
+
*/
|
|
48
|
+
export function validateAgentRootConstraints(allowedRoots, agentRoots, gitRoot, mcpServers) {
|
|
49
|
+
if (allowedRoots === null) {
|
|
50
|
+
return { valid: true };
|
|
51
|
+
}
|
|
52
|
+
// Find the matching agent root by git_root
|
|
53
|
+
const matchingRoot = agentRoots.find((root) => allowedRoots.includes(root.name) && root.git_root === gitRoot);
|
|
54
|
+
if (!matchingRoot) {
|
|
55
|
+
const allowedNames = allowedRoots.join(', ');
|
|
56
|
+
const allowedGitRoots = agentRoots
|
|
57
|
+
.filter((root) => allowedRoots.includes(root.name))
|
|
58
|
+
.map((root) => root.git_root);
|
|
59
|
+
return {
|
|
60
|
+
valid: false,
|
|
61
|
+
error: `ALLOWED_AGENT_ROOTS is set — only the following agent roots are permitted: ${allowedNames}. ` +
|
|
62
|
+
`The provided git_root "${gitRoot || '(not provided)'}" does not match any allowed agent root. ` +
|
|
63
|
+
(allowedGitRoots.length > 0
|
|
64
|
+
? `Allowed git_root values: ${allowedGitRoots.join(', ')}`
|
|
65
|
+
: 'No matching agent roots found in configuration.'),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Validate mcp_servers matches the default_mcp_servers exactly
|
|
69
|
+
const defaultServers = matchingRoot.default_mcp_servers ?? [];
|
|
70
|
+
const requestedServers = mcpServers ?? [];
|
|
71
|
+
const sortedDefault = [...defaultServers].sort();
|
|
72
|
+
const sortedRequested = [...requestedServers].sort();
|
|
73
|
+
const serversMatch = sortedDefault.length === sortedRequested.length &&
|
|
74
|
+
sortedDefault.every((s, i) => s === sortedRequested[i]);
|
|
75
|
+
if (!serversMatch) {
|
|
76
|
+
const defaultStr = defaultServers.length > 0 ? defaultServers.join(', ') : '(none)';
|
|
77
|
+
const requestedStr = requestedServers.length > 0 ? requestedServers.join(', ') : '(none)';
|
|
78
|
+
return {
|
|
79
|
+
valid: false,
|
|
80
|
+
error: `ALLOWED_AGENT_ROOTS is set — agent root "${matchingRoot.name}" must use its exact default MCP servers. ` +
|
|
81
|
+
`Expected: [${defaultStr}], but got: [${requestedStr}]. ` +
|
|
82
|
+
`You cannot add or remove MCP servers when ALLOWED_AGENT_ROOTS restrictions are active.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { valid: true };
|
|
86
|
+
}
|
|
@@ -36,7 +36,6 @@ export interface IAgentOrchestratorClient {
|
|
|
36
36
|
per_page?: number;
|
|
37
37
|
}): Promise<SessionsResponse>;
|
|
38
38
|
searchSessions(query: string, options?: {
|
|
39
|
-
search_contents?: boolean;
|
|
40
39
|
status?: SessionStatus;
|
|
41
40
|
agent_type?: string;
|
|
42
41
|
show_archived?: boolean;
|
|
@@ -146,6 +145,11 @@ export declare class AgentOrchestratorClient implements IAgentOrchestratorClient
|
|
|
146
145
|
private timeoutMs;
|
|
147
146
|
constructor(baseUrl: string, apiKey: string, timeoutMs?: number);
|
|
148
147
|
private request;
|
|
148
|
+
/**
|
|
149
|
+
* Like request(), but reads the response as text and wraps it in a TranscriptResponse.
|
|
150
|
+
* Used for API endpoints that return raw text instead of JSON (e.g., transcript with format=text).
|
|
151
|
+
*/
|
|
152
|
+
private requestText;
|
|
149
153
|
listSessions(options?: {
|
|
150
154
|
status?: SessionStatus;
|
|
151
155
|
agent_type?: string;
|
|
@@ -154,7 +158,6 @@ export declare class AgentOrchestratorClient implements IAgentOrchestratorClient
|
|
|
154
158
|
per_page?: number;
|
|
155
159
|
}): Promise<SessionsResponse>;
|
|
156
160
|
searchSessions(query: string, options?: {
|
|
157
|
-
search_contents?: boolean;
|
|
158
161
|
status?: SessionStatus;
|
|
159
162
|
agent_type?: string;
|
|
160
163
|
show_archived?: boolean;
|
|
@@ -111,7 +111,6 @@ export function createIntegrationMockOrchestratorClient(initialMockData) {
|
|
|
111
111
|
const paginatedSessions = sessions.slice(start, start + perPage);
|
|
112
112
|
return {
|
|
113
113
|
query,
|
|
114
|
-
search_contents: options?.search_contents || false,
|
|
115
114
|
sessions: paginatedSessions,
|
|
116
115
|
pagination: {
|
|
117
116
|
page,
|
|
@@ -98,6 +98,67 @@ export class AgentOrchestratorClient {
|
|
|
98
98
|
}
|
|
99
99
|
return response.json();
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Like request(), but reads the response as text and wraps it in a TranscriptResponse.
|
|
103
|
+
* Used for API endpoints that return raw text instead of JSON (e.g., transcript with format=text).
|
|
104
|
+
*/
|
|
105
|
+
async requestText(method, path, body, queryParams) {
|
|
106
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
107
|
+
if (queryParams) {
|
|
108
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
109
|
+
if (value !== undefined && value !== null) {
|
|
110
|
+
url.searchParams.append(key, String(value));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const headers = {
|
|
115
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
116
|
+
};
|
|
117
|
+
if (body) {
|
|
118
|
+
headers['Content-Type'] = 'application/json';
|
|
119
|
+
}
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
122
|
+
const options = {
|
|
123
|
+
method,
|
|
124
|
+
headers,
|
|
125
|
+
signal: controller.signal,
|
|
126
|
+
};
|
|
127
|
+
if (body && (method === 'POST' || method === 'PATCH' || method === 'PUT')) {
|
|
128
|
+
options.body = JSON.stringify(body);
|
|
129
|
+
}
|
|
130
|
+
let response;
|
|
131
|
+
try {
|
|
132
|
+
response = await fetch(url.toString(), options);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
clearTimeout(timeoutId);
|
|
136
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
137
|
+
throw new Error(`Request timeout after ${this.timeoutMs}ms`);
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
clearTimeout(timeoutId);
|
|
143
|
+
}
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const errorText = await response.text();
|
|
146
|
+
let errorMessage;
|
|
147
|
+
try {
|
|
148
|
+
const errorJson = JSON.parse(errorText);
|
|
149
|
+
errorMessage = errorJson.message || errorJson.error || errorText;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
errorMessage = errorText;
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`API Error (${response.status}): ${errorMessage}`);
|
|
155
|
+
}
|
|
156
|
+
if (response.status === 204) {
|
|
157
|
+
return { transcript_text: '' };
|
|
158
|
+
}
|
|
159
|
+
const text = await response.text();
|
|
160
|
+
return { transcript_text: text };
|
|
161
|
+
}
|
|
101
162
|
// Sessions
|
|
102
163
|
async listSessions(options) {
|
|
103
164
|
return this.request('GET', '/sessions', undefined, options);
|
|
@@ -234,6 +295,11 @@ export class AgentOrchestratorClient {
|
|
|
234
295
|
});
|
|
235
296
|
}
|
|
236
297
|
async getTranscript(id, format = 'json') {
|
|
298
|
+
if (format === 'text') {
|
|
299
|
+
// The API returns raw text (not JSON) when format=text,
|
|
300
|
+
// so we must use response.text() instead of response.json()
|
|
301
|
+
return this.requestText('GET', `/sessions/${id}/transcript`, undefined, { format });
|
|
302
|
+
}
|
|
237
303
|
return this.request('GET', `/sessions/${id}/transcript`, undefined, {
|
|
238
304
|
format,
|
|
239
305
|
});
|
package/shared/resources.js
CHANGED
|
@@ -67,7 +67,7 @@ export function createRegisterResources(clientFactory) {
|
|
|
67
67
|
},
|
|
68
68
|
toolGroups: {
|
|
69
69
|
sessions: 'All session tools (read + write): search, get, configs, start, action, enqueued messages',
|
|
70
|
-
sessions_readonly: 'Session tools (read only):
|
|
70
|
+
sessions_readonly: 'Session tools (read only): quick_search_sessions, get_session, get_configs',
|
|
71
71
|
notifications: 'All notification tools (read + write): get, send, mark read, dismiss',
|
|
72
72
|
notifications_readonly: 'Notification tools (read only): get_notifications',
|
|
73
73
|
triggers: 'All trigger tools (read + write): search, create, update, delete, toggle',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { parseAllowedAgentRoots } from '../allowed-agent-roots.js';
|
|
2
3
|
const PARAM_DESCRIPTIONS = {
|
|
3
4
|
session_id: 'Session ID (numeric) or slug (string). Required for most actions. Not required for "refresh_all" and "bulk_archive".',
|
|
4
5
|
action: 'Action to perform: "follow_up", "pause", "restart", "archive", "unarchive", "change_mcp_servers", "fork", "refresh", "refresh_all", "update_notes", "toggle_favorite", "bulk_archive"',
|
|
@@ -146,6 +147,18 @@ export function actionSessionTool(_server, clientFactory) {
|
|
|
146
147
|
isError: true,
|
|
147
148
|
};
|
|
148
149
|
}
|
|
150
|
+
// Block change_mcp_servers when ALLOWED_AGENT_ROOTS is active
|
|
151
|
+
if (action === 'change_mcp_servers' && parseAllowedAgentRoots() !== null) {
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: 'text',
|
|
156
|
+
text: 'Error: The "change_mcp_servers" action is not allowed when ALLOWED_AGENT_ROOTS is set. MCP servers are locked to the defaults configured for each allowed agent root.',
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
isError: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
149
162
|
// Validate fork requires message_index
|
|
150
163
|
if (action === 'fork' && message_index === undefined) {
|
|
151
164
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { parseAllowedAgentRoots } from '../allowed-agent-roots.js';
|
|
2
3
|
const ACTION_ENUM = ['create', 'update', 'delete', 'toggle'];
|
|
3
4
|
export const ActionTriggerSchema = z.object({
|
|
4
5
|
action: z.enum(ACTION_ENUM),
|
|
@@ -63,6 +64,18 @@ export function actionTriggerTool(_server, clientFactory) {
|
|
|
63
64
|
const validated = ActionTriggerSchema.parse(args);
|
|
64
65
|
const client = clientFactory();
|
|
65
66
|
const { action, id } = validated;
|
|
67
|
+
// Block create/update when ALLOWED_AGENT_ROOTS is active
|
|
68
|
+
if ((action === 'create' || action === 'update') && parseAllowedAgentRoots() !== null) {
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: `Error: The "${action}" action is not allowed when ALLOWED_AGENT_ROOTS is set. Triggers cannot be created or modified because sessions are restricted to specific preconfigured agent roots and their default MCP servers.`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
66
79
|
let result;
|
|
67
80
|
switch (action) {
|
|
68
81
|
case 'create': {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { getConfigsCache, setConfigsCache } from '../cache/configs-cache.js';
|
|
3
|
+
import { parseAllowedAgentRoots, filterAgentRoots } from '../allowed-agent-roots.js';
|
|
3
4
|
export const GetConfigsSchema = z.object({
|
|
4
5
|
force_refresh: z
|
|
5
6
|
.boolean()
|
|
@@ -37,14 +38,14 @@ export function getConfigsTool(_server, clientFactory) {
|
|
|
37
38
|
// Use cached data if available and not forcing refresh
|
|
38
39
|
const cachedConfigs = getConfigsCache();
|
|
39
40
|
if (cachedConfigs !== null && !forceRefresh) {
|
|
40
|
-
return formatResponse(cachedConfigs, true);
|
|
41
|
+
return formatResponse(applyAgentRootFilter(cachedConfigs), true);
|
|
41
42
|
}
|
|
42
43
|
// Fetch fresh data using unified configs endpoint
|
|
43
44
|
const client = clientFactory();
|
|
44
45
|
const configs = await client.getConfigs();
|
|
45
|
-
// Update shared cache
|
|
46
|
+
// Update shared cache (store unfiltered data so filtering is always applied fresh)
|
|
46
47
|
setConfigsCache(configs);
|
|
47
|
-
return formatResponse(configs, false);
|
|
48
|
+
return formatResponse(applyAgentRootFilter(configs), false);
|
|
48
49
|
}
|
|
49
50
|
catch (error) {
|
|
50
51
|
return {
|
|
@@ -60,6 +61,16 @@ export function getConfigsTool(_server, clientFactory) {
|
|
|
60
61
|
},
|
|
61
62
|
};
|
|
62
63
|
}
|
|
64
|
+
function applyAgentRootFilter(configs) {
|
|
65
|
+
const allowedRoots = parseAllowedAgentRoots();
|
|
66
|
+
if (allowedRoots === null) {
|
|
67
|
+
return configs;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
...configs,
|
|
71
|
+
agent_roots: filterAgentRoots(configs.agent_roots, allowedRoots),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
63
74
|
function formatResponse(configs, fromCache) {
|
|
64
75
|
const lines = [];
|
|
65
76
|
// MCP Servers section
|
|
@@ -112,6 +123,7 @@ function formatResponse(configs, fromCache) {
|
|
|
112
123
|
lines.push('');
|
|
113
124
|
lines.push('- Use `name` values from **MCP Servers** in `start_session` `mcp_servers` parameter');
|
|
114
125
|
lines.push('- Use `git_root` from **Agent Roots** to start sessions with preconfigured defaults');
|
|
126
|
+
lines.push('- If an **Agent Root** has a `default_subdirectory`, pass it as `subdirectory` in `start_session` — do not set `subdirectory` to arbitrary internal paths');
|
|
115
127
|
lines.push('- Use `id` values from **Stop Conditions** in `start_session` `stop_condition` parameter');
|
|
116
128
|
if (fromCache) {
|
|
117
129
|
lines.push('');
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { IAgentOrchestratorClient } from '../orchestrator-client/orchestrator-client.js';
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const QuickSearchSessionsSchema: z.ZodObject<{
|
|
5
5
|
id: z.ZodOptional<z.ZodNumber>;
|
|
6
6
|
query: z.ZodOptional<z.ZodString>;
|
|
7
|
-
search_contents: z.ZodOptional<z.ZodBoolean>;
|
|
8
7
|
status: z.ZodOptional<z.ZodEnum<["waiting", "running", "needs_input", "failed", "archived"]>>;
|
|
9
8
|
agent_type: z.ZodOptional<z.ZodString>;
|
|
10
9
|
show_archived: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -16,7 +15,6 @@ export declare const SearchSessionsSchema: z.ZodObject<{
|
|
|
16
15
|
agent_type?: string | undefined;
|
|
17
16
|
show_archived?: boolean | undefined;
|
|
18
17
|
page?: number | undefined;
|
|
19
|
-
search_contents?: boolean | undefined;
|
|
20
18
|
id?: number | undefined;
|
|
21
19
|
query?: string | undefined;
|
|
22
20
|
}, {
|
|
@@ -25,11 +23,10 @@ export declare const SearchSessionsSchema: z.ZodObject<{
|
|
|
25
23
|
agent_type?: string | undefined;
|
|
26
24
|
show_archived?: boolean | undefined;
|
|
27
25
|
page?: number | undefined;
|
|
28
|
-
search_contents?: boolean | undefined;
|
|
29
26
|
id?: number | undefined;
|
|
30
27
|
query?: string | undefined;
|
|
31
28
|
}>;
|
|
32
|
-
export declare function
|
|
29
|
+
export declare function quickSearchSessionsTool(_server: Server, clientFactory: () => IAgentOrchestratorClient): {
|
|
33
30
|
name: string;
|
|
34
31
|
description: string;
|
|
35
32
|
inputSchema: {
|
|
@@ -42,11 +39,7 @@ export declare function searchSessionsTool(_server: Server, clientFactory: () =>
|
|
|
42
39
|
query: {
|
|
43
40
|
type: string;
|
|
44
41
|
maxLength: number;
|
|
45
|
-
description: "Search query to find sessions.
|
|
46
|
-
};
|
|
47
|
-
search_contents: {
|
|
48
|
-
type: string;
|
|
49
|
-
description: "Also search within transcript contents. May be slow for sessions with large transcripts. Default: false";
|
|
42
|
+
description: "Search query to find sessions. Matches against session title only — this is a simple title search, not a full-text or semantic search. Leave empty to list all sessions.";
|
|
50
43
|
};
|
|
51
44
|
status: {
|
|
52
45
|
type: string;
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const PARAM_DESCRIPTIONS = {
|
|
3
3
|
id: 'Get a specific session by ID. When provided, other filters are ignored.',
|
|
4
|
-
query: 'Search query to find sessions.
|
|
5
|
-
search_contents: 'Also search within transcript contents. May be slow for sessions with large transcripts. Default: false',
|
|
4
|
+
query: 'Search query to find sessions. Matches against session title only — this is a simple title search, not a full-text or semantic search. Leave empty to list all sessions.',
|
|
6
5
|
status: 'Filter results by status. Options: "waiting", "running", "needs_input", "failed", "archived"',
|
|
7
6
|
agent_type: 'Filter results by agent type.',
|
|
8
7
|
show_archived: 'Include archived sessions in results. Default: false',
|
|
9
8
|
page: 'Page number for pagination. Default: 1',
|
|
10
9
|
per_page: 'Number of results per page (1-100). Default: 25',
|
|
11
10
|
};
|
|
12
|
-
export const
|
|
11
|
+
export const QuickSearchSessionsSchema = z.object({
|
|
13
12
|
id: z.number().optional().describe(PARAM_DESCRIPTIONS.id),
|
|
14
13
|
query: z.string().max(1000).optional().describe(PARAM_DESCRIPTIONS.query),
|
|
15
|
-
search_contents: z.boolean().optional().describe(PARAM_DESCRIPTIONS.search_contents),
|
|
16
14
|
status: z
|
|
17
15
|
.enum(['waiting', 'running', 'needs_input', 'failed', 'archived'])
|
|
18
16
|
.optional()
|
|
@@ -22,11 +20,13 @@ export const SearchSessionsSchema = z.object({
|
|
|
22
20
|
page: z.number().min(1).optional().describe(PARAM_DESCRIPTIONS.page),
|
|
23
21
|
per_page: z.number().min(1).max(100).optional().describe(PARAM_DESCRIPTIONS.per_page),
|
|
24
22
|
});
|
|
25
|
-
const TOOL_DESCRIPTION = `
|
|
23
|
+
const TOOL_DESCRIPTION = `Quick title-based search for agent sessions in the Agent Orchestrator.
|
|
24
|
+
|
|
25
|
+
**Important:** This tool only searches session titles. It is NOT a full-text or semantic search across session contents/transcripts. Use this when you roughly know the session title you're looking for.
|
|
26
26
|
|
|
27
27
|
**Use cases:**
|
|
28
28
|
- Find a specific session by ID (set id parameter)
|
|
29
|
-
- Search sessions by keyword
|
|
29
|
+
- Search sessions by title keyword (set query parameter)
|
|
30
30
|
- List all sessions with optional status filter
|
|
31
31
|
- Monitor sessions requiring attention (status: "needs_input")
|
|
32
32
|
|
|
@@ -66,9 +66,9 @@ function formatSession(session) {
|
|
|
66
66
|
lines.push(`- **Updated:** ${session.updated_at}`);
|
|
67
67
|
return lines.join('\n');
|
|
68
68
|
}
|
|
69
|
-
export function
|
|
69
|
+
export function quickSearchSessionsTool(_server, clientFactory) {
|
|
70
70
|
return {
|
|
71
|
-
name: '
|
|
71
|
+
name: 'quick_search_sessions',
|
|
72
72
|
description: TOOL_DESCRIPTION,
|
|
73
73
|
inputSchema: {
|
|
74
74
|
type: 'object',
|
|
@@ -82,10 +82,6 @@ export function searchSessionsTool(_server, clientFactory) {
|
|
|
82
82
|
maxLength: 1000,
|
|
83
83
|
description: PARAM_DESCRIPTIONS.query,
|
|
84
84
|
},
|
|
85
|
-
search_contents: {
|
|
86
|
-
type: 'boolean',
|
|
87
|
-
description: PARAM_DESCRIPTIONS.search_contents,
|
|
88
|
-
},
|
|
89
85
|
status: {
|
|
90
86
|
type: 'string',
|
|
91
87
|
enum: ['waiting', 'running', 'needs_input', 'failed', 'archived'],
|
|
@@ -115,7 +111,7 @@ export function searchSessionsTool(_server, clientFactory) {
|
|
|
115
111
|
},
|
|
116
112
|
handler: async (args) => {
|
|
117
113
|
try {
|
|
118
|
-
const validatedArgs =
|
|
114
|
+
const validatedArgs = QuickSearchSessionsSchema.parse(args);
|
|
119
115
|
const client = clientFactory();
|
|
120
116
|
// If ID is provided, get that specific session
|
|
121
117
|
if (validatedArgs.id !== undefined) {
|
|
@@ -133,9 +129,8 @@ export function searchSessionsTool(_server, clientFactory) {
|
|
|
133
129
|
let sessions;
|
|
134
130
|
let pagination;
|
|
135
131
|
if (validatedArgs.query) {
|
|
136
|
-
// Use search endpoint
|
|
132
|
+
// Use search endpoint (title-only search, no content search)
|
|
137
133
|
const response = await client.searchSessions(validatedArgs.query, {
|
|
138
|
-
search_contents: validatedArgs.search_contents,
|
|
139
134
|
status: validatedArgs.status,
|
|
140
135
|
agent_type: validatedArgs.agent_type,
|
|
141
136
|
show_archived: validatedArgs.show_archived,
|
|
@@ -65,7 +65,7 @@ export declare function startSessionTool(_server: Server, clientFactory: () => I
|
|
|
65
65
|
};
|
|
66
66
|
subdirectory: {
|
|
67
67
|
type: string;
|
|
68
|
-
description:
|
|
68
|
+
description: string;
|
|
69
69
|
};
|
|
70
70
|
title: {
|
|
71
71
|
type: string;
|
|
@@ -107,13 +107,13 @@ export declare function startSessionTool(_server: Server, clientFactory: () => I
|
|
|
107
107
|
type: string;
|
|
108
108
|
text: string;
|
|
109
109
|
}[];
|
|
110
|
-
isError
|
|
110
|
+
isError: boolean;
|
|
111
111
|
} | {
|
|
112
112
|
content: {
|
|
113
113
|
type: string;
|
|
114
114
|
text: string;
|
|
115
115
|
}[];
|
|
116
|
-
isError
|
|
116
|
+
isError?: undefined;
|
|
117
117
|
}>;
|
|
118
118
|
};
|
|
119
119
|
//# sourceMappingURL=start-session.d.ts.map
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { parseAllowedAgentRoots, validateAgentRootConstraints } from '../allowed-agent-roots.js';
|
|
3
|
+
import { getConfigsCache, setConfigsCache } from '../cache/configs-cache.js';
|
|
2
4
|
const PARAM_DESCRIPTIONS = {
|
|
3
5
|
agent_type: 'Agent type for the session. Currently only "claude_code" is supported. Default: "claude_code"',
|
|
4
6
|
prompt: 'Initial prompt for the agent. If provided, the agent job is automatically queued. Omit for a clone-only session.',
|
|
5
7
|
git_root: 'Repository URL or local path. Examples: "https://github.com/example/repo.git", "/path/to/repo"',
|
|
6
8
|
branch: 'Git branch to work on. Default: "main"',
|
|
7
|
-
subdirectory: 'Subdirectory within the repository to
|
|
9
|
+
subdirectory: 'Subdirectory within the repository to use as the agent working directory. ' +
|
|
10
|
+
'This should match a preconfigured agent root default_subdirectory from get_configs — it defines ' +
|
|
11
|
+
'the root scope for the agent session. Do NOT use this to point at internal package directories ' +
|
|
12
|
+
'(e.g. "experimental/gcs" in a monorepo) as this blinds the agent to root-level configuration ' +
|
|
13
|
+
'like CLAUDE.md, build scripts, CI workflows, and monorepo tooling. If no agent root defines ' +
|
|
14
|
+
'a default_subdirectory, leave this unset.',
|
|
8
15
|
title: 'STRONGLY RECOMMENDED: Always set a title — treat it as effectively required. ' +
|
|
9
16
|
'The title appears in the AO web UI and push notifications, making sessions identifiable at a glance. ' +
|
|
10
17
|
'Compose a short, descriptive title (under 70 characters) that captures what the session is doing ' +
|
|
@@ -113,6 +120,23 @@ export function startSessionTool(_server, clientFactory) {
|
|
|
113
120
|
try {
|
|
114
121
|
const validatedArgs = StartSessionSchema.parse(args);
|
|
115
122
|
const client = clientFactory();
|
|
123
|
+
// Enforce ALLOWED_AGENT_ROOTS constraints if set
|
|
124
|
+
const allowedRoots = parseAllowedAgentRoots();
|
|
125
|
+
if (allowedRoots !== null) {
|
|
126
|
+
// Ensure we have configs (fetch if not cached)
|
|
127
|
+
let configs = getConfigsCache();
|
|
128
|
+
if (!configs) {
|
|
129
|
+
configs = await client.getConfigs();
|
|
130
|
+
setConfigsCache(configs);
|
|
131
|
+
}
|
|
132
|
+
const validation = validateAgentRootConstraints(allowedRoots, configs.agent_roots, validatedArgs.git_root, validatedArgs.mcp_servers);
|
|
133
|
+
if (!validation.valid) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: 'text', text: `Error starting session: ${validation.error}` }],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
116
140
|
const session = await client.createSession(validatedArgs);
|
|
117
141
|
const lines = [
|
|
118
142
|
`## Session Started Successfully`,
|
package/shared/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
// 13 tools across 4 domains
|
|
3
|
-
import {
|
|
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 } from './tools/action-session.js';
|
|
@@ -59,7 +59,7 @@ export function parseEnabledToolGroups(enabledGroupsParam) {
|
|
|
59
59
|
* All available tools with their group assignments.
|
|
60
60
|
*
|
|
61
61
|
* 14 tools across 4 domains:
|
|
62
|
-
* -
|
|
62
|
+
* - quick_search_sessions: Quick title-based search/list/get sessions by ID (sessions, read)
|
|
63
63
|
* - get_session: Get detailed session info with optional logs/transcripts (sessions, read)
|
|
64
64
|
* - get_configs: Fetch all static configuration (sessions, read)
|
|
65
65
|
* - get_transcript_archive: Get transcript archive download URL and metadata (sessions, read)
|
|
@@ -76,7 +76,7 @@ export function parseEnabledToolGroups(enabledGroupsParam) {
|
|
|
76
76
|
*/
|
|
77
77
|
const ALL_TOOLS = [
|
|
78
78
|
// Session tools - read operations
|
|
79
|
-
{ factory:
|
|
79
|
+
{ factory: quickSearchSessionsTool, group: 'sessions', isWriteOperation: false },
|
|
80
80
|
{ factory: getSessionTool, group: 'sessions', isWriteOperation: false },
|
|
81
81
|
{ factory: getConfigsTool, group: 'sessions', isWriteOperation: false },
|
|
82
82
|
{ factory: getTranscriptArchiveTool, group: 'sessions', isWriteOperation: false },
|