agent-pool-mcp 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-pool-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "description": "MCP Server for multi-agent task delegation and orchestration via Gemini CLI",
6
6
  "main": "index.js",
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: group-lead
3
+ description: Fractal group leader — breaks down tasks and delegates to group members via delegate_to_group.
4
+ ---
5
+
6
+ # Group Lead
7
+
8
+ You are a group leader in a fractal agent orchestration system. Your role is to:
9
+
10
+ 1. **Analyze** the incoming task and break it into independent subtasks
11
+ 2. **Create groups** if they don't exist yet, with appropriate skills and policies
12
+ 3. **Delegate** subtasks to group members using `delegate_to_group`
13
+ 4. **Collect** results from all agents using `get_task_result`
14
+ 5. **Synthesize** a final report combining all results
15
+
16
+ ## Rules
17
+
18
+ - Break tasks into pieces that can run in parallel
19
+ - Each subtask should be self-contained (agent can complete it without external context)
20
+ - Set appropriate policies: use `read-only` for analysis, `safe-edit` for code changes
21
+ - Always set `max_agents` to prevent runaway spawning
22
+ - Collect ALL results before synthesizing — don't skip slow agents
23
+ - Report both successes and failures
24
+
25
+ ## Output Format
26
+
27
+ After collecting all results, produce a structured summary:
28
+ 1. Task overview (what was asked)
29
+ 2. Subtask results (one per agent)
30
+ 3. Synthesized conclusion
package/src/server.js CHANGED
@@ -22,6 +22,7 @@ import { listSkills, createSkill, deleteSkill, installSkill, provisionSkill } fr
22
22
  import { consultPeer } from './tools/consult.js';
23
23
  import { addSchedule, listSchedules, removeSchedule, getScheduledResults, getDaemonStatus } from './scheduler/scheduler.js';
24
24
  import { createPipeline, listPipelines, runPipeline, getRun, listRuns, cancelRun, signalStepComplete, bounceBack } from './scheduler/pipeline.js';
25
+ import { createGroup, listGroups, getGroup } from './tools/groups.js';
25
26
 
26
27
  import { TOOL_DEFINITIONS } from './tool-definitions.js';
27
28
 
@@ -68,6 +69,7 @@ Docs: https://github.com/google-gemini/gemini-cli`
68
69
  /** Tools that require Gemini CLI to be installed */
69
70
  const GEMINI_TOOLS = new Set([
70
71
  'delegate_task', 'delegate_task_readonly', 'consult_peer', 'list_sessions',
72
+ 'delegate_to_group',
71
73
  ]);
72
74
 
73
75
  // ─── Depth tracking (for nested orchestration) ──────────────
@@ -110,7 +112,7 @@ export function createServer() {
110
112
  }
111
113
 
112
114
  const server = new Server(
113
- { name: 'agent-pool', version: '1.2.1' },
115
+ { name: 'agent-pool', version: '1.5.0' },
114
116
  { capabilities: { tools: {}, resources: {} } },
115
117
  );
116
118
 
@@ -200,6 +202,12 @@ export function createServer() {
200
202
  response = handleBounceBack(args); break;
201
203
  case 'get_usage_guide':
202
204
  response = handleGetUsageGuide(args); break;
205
+ case 'create_group':
206
+ response = handleCreateGroup(args); break;
207
+ case 'list_groups':
208
+ response = handleListGroups(args); break;
209
+ case 'delegate_to_group':
210
+ response = handleDelegateToGroup(args); break;
203
211
  default:
204
212
  response = { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
205
213
  }
@@ -688,3 +696,110 @@ function handleGetUsageGuide(args) {
688
696
  return { content: [{ type: 'text', text: topicContent.join('\n').trim() }] };
689
697
  }
690
698
 
699
+ // ─── Group Handlers ─────────────────────────────────────────────
700
+
701
+ /** @param {object} args */
702
+ function handleCreateGroup(args) {
703
+ const cwd = args.cwd ?? defaultCwd;
704
+ const result = createGroup(cwd, {
705
+ name: args.name,
706
+ runner: args.runner,
707
+ skill: args.skill,
708
+ policy: args.policy,
709
+ max_agents: args.max_agents,
710
+ include_dirs: args.include_dirs,
711
+ });
712
+
713
+ const configParts = [];
714
+ if (args.runner) configParts.push(`runner: ${args.runner}`);
715
+ if (args.skill) configParts.push(`skill: ${args.skill}`);
716
+ if (args.policy) configParts.push(`policy: ${args.policy}`);
717
+ if (args.max_agents) configParts.push(`max: ${args.max_agents}`);
718
+
719
+ return {
720
+ content: [{
721
+ type: 'text',
722
+ text: `✅ Group ${result.created ? 'created' : 'updated'}: \`${args.name}\`${configParts.length > 0 ? `\n- ${configParts.join('\n- ')}` : ''}\n\nUse \`delegate_to_group\` to send tasks to this group.`,
723
+ }],
724
+ };
725
+ }
726
+
727
+ /** @param {object} args */
728
+ function handleListGroups(args) {
729
+ const cwd = args.cwd ?? defaultCwd;
730
+ const groups = listGroups(cwd);
731
+
732
+ if (groups.length === 0) {
733
+ return { content: [{ type: 'text', text: 'No groups defined. Use `create_group` to create one.' }] };
734
+ }
735
+
736
+ const lines = groups.map((g) => {
737
+ const parts = [];
738
+ if (g.runner) parts.push(`runner: ${g.runner}`);
739
+ if (g.skill) parts.push(`skill: ${g.skill}`);
740
+ if (g.policy) parts.push(`policy: ${g.policy}`);
741
+ if (g.max_agents) parts.push(`max: ${g.max_agents}`);
742
+ return `- **${g.name}** — ${parts.join(', ') || 'no config'}`;
743
+ });
744
+
745
+ return {
746
+ content: [{ type: 'text', text: `## Agent Groups (${groups.length})\n\n${lines.join('\n')}` }],
747
+ };
748
+ }
749
+
750
+ /** @param {object} args */
751
+ function handleDelegateToGroup(args) {
752
+ const cwd = args.cwd ?? defaultCwd;
753
+ const group = getGroup(cwd, args.group);
754
+
755
+ if (!group) {
756
+ return {
757
+ content: [{ type: 'text', text: `❌ Group \`${args.group}\` not found. Use \`list_groups\` to see available groups.` }],
758
+ isError: true,
759
+ };
760
+ }
761
+
762
+ const count = args.count ?? 1;
763
+
764
+ // Check max_agents limit
765
+ if (group.max_agents && count > group.max_agents) {
766
+ return {
767
+ content: [{
768
+ type: 'text',
769
+ text: `❌ Requested ${count} agents but group \`${args.group}\` allows max ${group.max_agents}.`,
770
+ }],
771
+ isError: true,
772
+ };
773
+ }
774
+
775
+ const taskIds = [];
776
+
777
+ for (let i = 0; i < count; i++) {
778
+ const delegateArgs = {
779
+ prompt: count > 1 ? `[Agent ${i + 1}/${count} in group "${args.group}"]\n\n${args.prompt}` : args.prompt,
780
+ cwd,
781
+ runner: group.runner || undefined,
782
+ skill: group.skill || undefined,
783
+ policy: group.policy || undefined,
784
+ include_dirs: group.include_dirs || undefined,
785
+ timeout: args.timeout,
786
+ };
787
+
788
+ const result = handleDelegate(delegateArgs, {
789
+ approvalMode: DEFAULT_APPROVAL_MODE,
790
+ emoji: '👥',
791
+ label: `Group task (${args.group} #${i + 1})`,
792
+ });
793
+
794
+ // Extract task_id from response text
795
+ const match = result.content[0].text.match(/`([0-9a-f-]{36})`/);
796
+ if (match) taskIds.push(match[1]);
797
+ }
798
+
799
+ return {
800
+ content: [{
801
+ type: 'text',
802
+ text: `👥 Delegated to group \`${args.group}\` — ${count} agent(s) spawned.\n\n${taskIds.map((id, i) => `- Agent ${i + 1}: \`${id}\``).join('\n')}\n\nUse \`get_task_result\` to check each agent's status.`,
803
+ }],
804
+ };
805
+ }
@@ -360,5 +360,53 @@ export const TOOL_DEFINITIONS = [
360
360
  required: ['step_name', 'reason'],
361
361
  },
362
362
  },
363
+ // ─── Group Tools ────────────────────────────────────────
364
+ {
365
+ name: 'create_group',
366
+ description: 'Create a named agent group with shared config (runner, skill, policy). Groups are reusable presets for fractal orchestration.',
367
+ inputSchema: {
368
+ type: 'object',
369
+ properties: {
370
+ name: { type: 'string', description: 'Group name (e.g. "backend-team", "qa-group").' },
371
+ runner: { type: 'string', description: 'Default runner for agents in this group.' },
372
+ skill: { type: 'string', description: 'Default skill activated for all agents in this group.' },
373
+ policy: { type: 'string', description: 'Default policy restricting agent permissions.' },
374
+ max_agents: { type: 'number', description: 'Max concurrent agents allowed in this group.' },
375
+ include_dirs: { type: 'array', items: { type: 'string' }, description: 'Additional directories agents in this group can access.' },
376
+ cwd: { type: 'string', description: 'Project directory. Defaults to current working directory.' },
377
+ },
378
+ required: ['name'],
379
+ },
380
+ },
381
+ {
382
+ name: 'list_groups',
383
+ description: 'List all registered agent groups with their config.',
384
+ inputSchema: {
385
+ type: 'object',
386
+ properties: {
387
+ cwd: { type: 'string', description: 'Project directory. Defaults to current working directory.' },
388
+ },
389
+ },
390
+ },
391
+ {
392
+ name: 'delegate_to_group',
393
+ description: [
394
+ 'Delegate a task to a named agent group. Spawns agents with the group\'s shared config (runner, skill, policy).',
395
+ 'Use `count` to launch multiple agents in parallel (fractal orchestration).',
396
+ '',
397
+ 'Returns array of task_ids immediately (non-blocking). Use get_task_result to check each.',
398
+ ].join('\n'),
399
+ inputSchema: {
400
+ type: 'object',
401
+ properties: {
402
+ group: { type: 'string', description: 'Group name to delegate to.' },
403
+ prompt: { type: 'string', description: 'Task description for the agents.' },
404
+ count: { type: 'number', description: 'Number of agents to spawn. Default: 1.' },
405
+ cwd: { type: 'string', description: 'Working directory. Defaults to current working directory.' },
406
+ timeout: { type: 'number', description: 'Timeout in seconds. Overrides group default.' },
407
+ },
408
+ required: ['group', 'prompt'],
409
+ },
410
+ },
363
411
  ];
364
412
 
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Agent Groups — named config presets for fractal orchestration.
3
+ * Groups bundle runner, skill, policy, and max_agents into a reusable team config.
4
+ *
5
+ * @module agent-pool/tools/groups
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ const GROUPS_DIR = '.agents';
12
+ const GROUPS_FILE = 'groups.json';
13
+
14
+ /**
15
+ * Get path to groups.json for a project.
16
+ *
17
+ * @param {string} cwd - Project directory
18
+ * @returns {string}
19
+ */
20
+ function getGroupsPath(cwd) {
21
+ return path.join(cwd, GROUPS_DIR, GROUPS_FILE);
22
+ }
23
+
24
+ /**
25
+ * Load all groups from disk.
26
+ *
27
+ * @param {string} cwd - Project directory
28
+ * @returns {Object<string, object>}
29
+ */
30
+ function loadGroups(cwd) {
31
+ const filePath = getGroupsPath(cwd);
32
+ if (!fs.existsSync(filePath)) return {};
33
+ try {
34
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
35
+ } catch {
36
+ return {};
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Save groups to disk.
42
+ *
43
+ * @param {string} cwd - Project directory
44
+ * @param {Object<string, object>} groups
45
+ */
46
+ function saveGroups(cwd, groups) {
47
+ const dirPath = path.join(cwd, GROUPS_DIR);
48
+ if (!fs.existsSync(dirPath)) {
49
+ fs.mkdirSync(dirPath, { recursive: true });
50
+ }
51
+ fs.writeFileSync(getGroupsPath(cwd), JSON.stringify(groups, null, 2));
52
+ }
53
+
54
+ /**
55
+ * Create or update a group.
56
+ *
57
+ * @param {string} cwd - Project directory
58
+ * @param {object} config - Group configuration
59
+ * @param {string} config.name - Group name (e.g. "backend-team")
60
+ * @param {string} [config.runner] - Default runner for agents in this group
61
+ * @param {string} [config.skill] - Default skill for agents in this group
62
+ * @param {string} [config.policy] - Default policy for agents in this group
63
+ * @param {number} [config.max_agents] - Max concurrent agents in this group
64
+ * @param {string[]} [config.include_dirs] - Additional directories agents can access
65
+ * @returns {{ name: string, created: boolean }}
66
+ */
67
+ export function createGroup(cwd, config) {
68
+ const groups = loadGroups(cwd);
69
+ const existed = !!groups[config.name];
70
+
71
+ groups[config.name] = {
72
+ runner: config.runner || null,
73
+ skill: config.skill || null,
74
+ policy: config.policy || null,
75
+ max_agents: config.max_agents || null,
76
+ include_dirs: config.include_dirs || null,
77
+ created_at: existed ? groups[config.name].created_at : new Date().toISOString(),
78
+ updated_at: new Date().toISOString(),
79
+ };
80
+
81
+ saveGroups(cwd, groups);
82
+ return { name: config.name, created: !existed };
83
+ }
84
+
85
+ /**
86
+ * List all groups.
87
+ *
88
+ * @param {string} cwd - Project directory
89
+ * @returns {Array<{ name: string, runner: string|null, skill: string|null, policy: string|null, max_agents: number|null }>}
90
+ */
91
+ export function listGroups(cwd) {
92
+ const groups = loadGroups(cwd);
93
+ return Object.entries(groups).map(([name, config]) => ({
94
+ name,
95
+ ...config,
96
+ }));
97
+ }
98
+
99
+ /**
100
+ * Get a single group by name.
101
+ *
102
+ * @param {string} cwd - Project directory
103
+ * @param {string} name - Group name
104
+ * @returns {object|null}
105
+ */
106
+ export function getGroup(cwd, name) {
107
+ const groups = loadGroups(cwd);
108
+ return groups[name] || null;
109
+ }
110
+
111
+ /**
112
+ * Delete a group.
113
+ *
114
+ * @param {string} cwd - Project directory
115
+ * @param {string} name - Group name
116
+ * @returns {boolean}
117
+ */
118
+ export function deleteGroup(cwd, name) {
119
+ const groups = loadGroups(cwd);
120
+ if (!groups[name]) return false;
121
+ delete groups[name];
122
+ saveGroups(cwd, groups);
123
+ return true;
124
+ }