agent-orchestrator-mcp-server 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -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 | 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
- | `SKIP_HEALTH_CHECKS` | No | Skip API connectivity check at startup | `false` |
118
- | `HEALTH_CHECK_TIMEOUT` | No | Health check timeout in milliseconds | `10000` |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-orchestrator-mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.2",
4
4
  "description": "Local implementation of agent-orchestrator MCP server",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
@@ -0,0 +1,40 @@
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 (and optionally branch/subdirectory) 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
+ * When multiple allowed agent roots share the same git_root, branch and subdirectory
34
+ * are used to disambiguate. This is critical for monorepo setups where multiple agent
35
+ * roots point to the same repository but different subdirectories.
36
+ *
37
+ * Returns { valid: true } if the request is allowed, or { valid: false, error: string } if not.
38
+ */
39
+ export declare function validateAgentRootConstraints(allowedRoots: string[] | null, agentRoots: AgentRootInfo[], gitRoot?: string, mcpServers?: string[], branch?: string, subdirectory?: string): AgentRootValidationResult;
40
+ //# sourceMappingURL=allowed-agent-roots.d.ts.map
@@ -0,0 +1,102 @@
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 (and optionally branch/subdirectory) 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
+ * When multiple allowed agent roots share the same git_root, branch and subdirectory
47
+ * are used to disambiguate. This is critical for monorepo setups where multiple agent
48
+ * roots point to the same repository but different subdirectories.
49
+ *
50
+ * Returns { valid: true } if the request is allowed, or { valid: false, error: string } if not.
51
+ */
52
+ export function validateAgentRootConstraints(allowedRoots, agentRoots, gitRoot, mcpServers, branch, subdirectory) {
53
+ if (allowedRoots === null) {
54
+ return { valid: true };
55
+ }
56
+ // Find all allowed agent roots that match by git_root
57
+ const candidates = agentRoots.filter((root) => allowedRoots.includes(root.name) && root.git_root === gitRoot);
58
+ // When multiple candidates share the same git_root, disambiguate using branch and subdirectory
59
+ let matchingRoot;
60
+ if (candidates.length > 1) {
61
+ matchingRoot = candidates.find((root) => {
62
+ const branchMatch = !branch || (root.default_branch ?? 'main') === branch;
63
+ const subdirMatch = !subdirectory || root.default_subdirectory === subdirectory;
64
+ return branchMatch && subdirMatch;
65
+ });
66
+ }
67
+ else {
68
+ matchingRoot = candidates[0];
69
+ }
70
+ if (!matchingRoot) {
71
+ const allowedNames = allowedRoots.join(', ');
72
+ const allowedGitRoots = agentRoots
73
+ .filter((root) => allowedRoots.includes(root.name))
74
+ .map((root) => root.git_root);
75
+ return {
76
+ valid: false,
77
+ error: `ALLOWED_AGENT_ROOTS is set — only the following agent roots are permitted: ${allowedNames}. ` +
78
+ `The provided git_root "${gitRoot || '(not provided)'}" does not match any allowed agent root. ` +
79
+ (allowedGitRoots.length > 0
80
+ ? `Allowed git_root values: ${allowedGitRoots.join(', ')}`
81
+ : 'No matching agent roots found in configuration.'),
82
+ };
83
+ }
84
+ // Validate mcp_servers matches the default_mcp_servers exactly
85
+ const defaultServers = matchingRoot.default_mcp_servers ?? [];
86
+ const requestedServers = mcpServers ?? [];
87
+ const sortedDefault = [...defaultServers].sort();
88
+ const sortedRequested = [...requestedServers].sort();
89
+ const serversMatch = sortedDefault.length === sortedRequested.length &&
90
+ sortedDefault.every((s, i) => s === sortedRequested[i]);
91
+ if (!serversMatch) {
92
+ const defaultStr = defaultServers.length > 0 ? defaultServers.join(', ') : '(none)';
93
+ const requestedStr = requestedServers.length > 0 ? requestedServers.join(', ') : '(none)';
94
+ return {
95
+ valid: false,
96
+ error: `ALLOWED_AGENT_ROOTS is set — agent root "${matchingRoot.name}" must use its exact default MCP servers. ` +
97
+ `Expected: [${defaultStr}], but got: [${requestedStr}]. ` +
98
+ `You cannot add or remove MCP servers when ALLOWED_AGENT_ROOTS restrictions are active.`,
99
+ };
100
+ }
101
+ return { valid: true };
102
+ }
@@ -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('');
@@ -65,7 +65,7 @@ export declare function startSessionTool(_server: Server, clientFactory: () => I
65
65
  };
66
66
  subdirectory: {
67
67
  type: string;
68
- description: "Subdirectory within the repository to focus on.";
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?: undefined;
110
+ isError: boolean;
111
111
  } | {
112
112
  content: {
113
113
  type: string;
114
114
  text: string;
115
115
  }[];
116
- isError: boolean;
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 focus on.',
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, validatedArgs.branch, validatedArgs.subdirectory);
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`,