Skip to content

feat: Agent Teams MCP (lead + teammate)#12

Merged
Joao208 merged 5 commits into
mainfrom
joaobarros-/-agent-teams-mcp
Mar 10, 2026
Merged

feat: Agent Teams MCP (lead + teammate)#12
Joao208 merged 5 commits into
mainfrom
joaobarros-/-agent-teams-mcp

Conversation

@Joao208

@Joao208 Joao208 commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Descricao

Dois novos MCP servers para coordenar múltiplas sessões de agentes IA trabalhando como um time:

  • agent-teams-lead: usado pelo orquestrador para criar times, criar tasks, enviar mensagens e monitorar progresso
  • agent-teams-teammate: injetado em cada sessão de teammate para claim de tasks, comunicação e publicação de artefatos

Conceito inspirado no Agent Teams do Claude Code da Anthropic, construído como uma camada MCP editor-agnostic que funciona com Kiro, Cursor, Claude Code e OpenCode.

Funcionalidades

  • Spawn de teammates como processos CLI independentes
  • Task list compartilhada com dependências e exclusive file paths
  • File locking atômico via mkdir para evitar race conditions
  • Messaging entre agentes (direct, broadcast, to lead)
  • Publicação de artefatos vinculados a tasks
  • Log de auditoria em .agent-teams/team.log
  • Nomes brasileiros aleatórios para teammates

Etiquetas (Labels)

  • Nova Funcionalidade
  • Correcao de Bug
  • Estrutura
  • Testes
  • Outros

Historia Relacionada

N/A

Motivacao e Contexto

Permitir que o orquestrador coordene múltiplos agentes trabalhando em paralelo, com comunicação direta entre eles — algo que sub-agents não suportam.

Como Isso Foi Testado?

  • Testes Unitarios
  • Testes de Integracao
  • Testes e2e (playwright)
  • Testes de Aceitacao (QA)
  • Testes de Performance
  • Outros (quais?)
  • Nenhum (por que?)

Testado manualmente com times de 3 teammates, tasks com dependências, messaging, e parallel work. Verificado fix de nomes com acento (sanitizeName).

Analise de Risco e Impacto

  • Baixo
  • Alto

Pacotes novos, sem impacto em código existente.

Summary by CodeRabbit

Release Notes

New Features

  • Added two new agent packages for collaborative team management and task coordination
  • Team management: spawn teams, add and remove teammates, monitor team status
  • Task management: create and assign tasks, track progress and completion
  • Team messaging system for inter-member communication
  • Artifact storage and retrieval for task outputs and results
  • MCP server integration for agent-based automation

Two MCP servers for coordinating multiple AI agent sessions as a team:
- agent-teams-lead: spawn teams, create tasks, send messages, monitor progress
- agent-teams-teammate: claim tasks, communicate, publish artifacts

Inspired by Anthropic's Claude Code agent teams, built as an editor-agnostic
MCP layer that works with Kiro, Cursor, Claude Code, and OpenCode.
@coderabbitai

coderabbitai Bot commented Mar 10, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Joao208 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 40 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9783aeb-383c-4cb9-b90d-7e042fd507bc

📥 Commits

Reviewing files that changed from the base of the PR and between b244416 and 73a7b17.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • README.md
  • packages/agent-teams-lead/README.md
  • packages/agent-teams-lead/src/filelock.ts
  • packages/agent-teams-lead/src/spawner.ts
  • packages/agent-teams-lead/src/store.ts
  • packages/agent-teams-lead/vitest.config.ts
  • packages/agent-teams-teammate/README.md
  • packages/agent-teams-teammate/package.json
  • packages/agent-teams-teammate/src/filelock.ts
  • packages/agent-teams-teammate/vitest.config.ts
📝 Walkthrough

Walkthrough

These changes introduce two new npm packages for an agent team orchestration system: agent-teams-lead-mcp and agent-teams-teammate-mcp. The lead package manages team creation, process spawning, task coordination, and messaging. The teammate package enables individual agent processes to participate in teams via shared store access, supporting task lifecycle management and artifact handling. Both implement MCP servers with file-based persistence, file locking for concurrent safety, and graceful shutdown handling.

Changes

Cohort / File(s) Summary
Lead Package Configuration
packages/agent-teams-lead/package.json, packages/agent-teams-lead/tsconfig.json
Package metadata with ESM/CommonJS dual entry points, build/dev/test scripts, and TypeScript compilation configuration targeting ES2022 with strict type-checking.
Lead Package Domain Models
packages/agent-teams-lead/src/types.ts, packages/agent-teams-lead/src/schemas.ts
Type definitions for Team, Teammate, Task, Message, Artifact entities; Zod validation schemas for all tool inputs; custom AgentTeamsError class; McpToolResult interface for standardized responses.
Lead Package Utilities
packages/agent-teams-lead/src/filelock.ts, packages/agent-teams-lead/src/names.ts
File-based locking mechanism with staleness detection and retry logic; random teammate name generator with deduplication tracking.
Lead Package State & Process Management
packages/agent-teams-lead/src/store.ts, packages/agent-teams-lead/src/spawner.ts
TeamStore: persistent JSON-backed team/task/message/artifact storage with file locking for atomic mutations. TeammateSpawner: orchestrates spawning teammate processes via kiro-cli, generates agent configs with MCP server resolution, manages process lifecycle and logging.
Lead Package MCP Server
packages/agent-teams-lead/src/server.ts, packages/agent-teams-lead/src/tools.ts, packages/agent-teams-lead/src/index.ts
LeadMCPServer: initializes MCP server and registers eight tools (spawn_team, add_teammate, remove_teammate, create_task, team_status, send_message, wait_for_team, read_artifact) with schema validation. LeadTools: implements tool handlers coordinating TeamStore and TeammateSpawner operations. Index: bootstraps server with graceful shutdown and environment config.
Teammate Package Configuration
packages/agent-teams-teammate/package.json, packages/agent-teams-teammate/tsconfig.json
Package metadata for teammate agent MCP server with ESM configuration and TypeScript compilation settings mirroring lead package setup.
Teammate Package Utilities
packages/agent-teams-teammate/src/filelock.ts
Identical file-locking implementation for concurrent-safe access to shared team data store.
Teammate Package MCP Server
packages/agent-teams-teammate/src/server.ts, packages/agent-teams-teammate/src/store.ts, packages/agent-teams-teammate/src/index.ts
TeammateMCPServer: registers ten tools (whoami, list_tasks, claim_task, update_task, complete_task, send_message, fetch_messages, ack_messages, write_artifact, read_artifact) with MCP server integration. TeammateStore: mirrors lead package persistence layer, exposing task claiming, messaging, and artifact I/O scoped to individual teammate. Index: entry point with environment variable validation and graceful shutdown.

Sequence Diagram(s)

sequenceDiagram
    participant Lead as LeadMCPServer
    participant Store as TeamStore
    participant Spawner as TeammateSpawner
    participant Teammate as TeammateMCPServer<br/>(Process)
    
    Lead->>Spawner: initialize spawner
    Lead->>Lead: call spawnTeam(objective, configs)
    Lead->>Store: call store.spawnTeam()
    Store->>Store: create team, generate IDs
    Store->>Store: persist to JSON
    Store-->>Lead: return Team
    
    loop for each teammate config
        Lead->>Spawner: spawnTeammate(agent, mcp_servers)
        Spawner->>Spawner: generate agent config
        Spawner->>Spawner: generate prompt
        Spawner->>Teammate: spawn process via kiro-cli
        Teammate->>Teammate: load TEAMMATE_ID, TEAMMATE_NAME
        Teammate->>Teammate: initialize TeammateMCPServer
        Teammate-->>Spawner: process started
        Spawner-->>Lead: teammate spawned
    end
    
    Note over Lead,Teammate: Teams initialized and agents running
Loading
sequenceDiagram
    participant Lead as LeadMCPServer
    participant Store as TeamStore
    participant Teammate as TeammateMCPServer
    
    Lead->>Lead: call createTask(title, description, deps)
    Lead->>Store: store.createTask()
    Store->>Store: persist task to JSON
    Store-->>Lead: return Task {id, status: pending}
    
    Teammate->>Teammate: call listTasks()
    Teammate->>Store: store.listTasks({status: pending})
    Store-->>Teammate: return pending tasks
    
    Teammate->>Teammate: select task, call claimTask(taskId)
    Teammate->>Store: store.claimTask(taskId, agentId)
    Store->>Store: validate pending, unassigned, dependencies
    Store->>Store: update task.assigned_to, status=in_progress
    Store->>Store: persist update via file lock
    Store-->>Teammate: return claimed Task
    
    Teammate->>Teammate: work on task
    Teammate->>Teammate: call completeTask(summary, artifacts)
    Teammate->>Store: store.completeTask(taskId, params)
    Store->>Store: update status=completed, add summary, artifacts
    Store->>Store: persist via file lock
    Store-->>Teammate: return completed Task
    
    Lead->>Store: store.getTasks({status: completed})
    Store-->>Lead: return completed tasks
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop into teams, agents aligned,
Lead spawns spawners, teammates assigned,
Tasks claimed and locked with care,
Artifacts shared in JSON's fair square!
MCP servers dance, graceful and bright,
Agent orchestration—what a delight! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: introducing two new MCP servers (agent-teams-lead and agent-teams-teammate) for coordinating multiple AI agents as a team.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch joaobarros-/-agent-teams-mcp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (10)
packages/agent-teams-teammate/package.json (1)

1-29: Consider adding a files field for cleaner npm publishing.

Without a files field, npm will publish all files not excluded by .npmignore or .gitignore. Adding an explicit files array ensures only necessary files are included in the published package.

"files": [
  "dist"
],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-teammate/package.json` around lines 1 - 29, The
package.json for `@arvoretech/agent-teams-teammate-mcp` is missing a "files" field
which causes npm to include everything not ignored; add a "files" array to
package.json (e.g., include "dist" and any other runtime assets like bin or
README if needed) so only necessary artifacts are published, then rebuild to
ensure dist is present for the "main" entry and the CLI bin
("agent-teams-teammate-mcp") to work correctly.
packages/agent-teams-lead/package.json (1)

1-12: Whitelist the published payload to dist/.

This package is public and both entry points resolve under dist/, so adding a files allowlist avoids publishing src/, configs, and other repo artifacts unintentionally.

📦 Proposed change
 {
   "name": "@arvoretech/agent-teams-lead-mcp",
   "version": "0.1.0",
   "description": "MCP server for the team lead agent — spawn teammates, create tasks, coordinate work",
   "main": "dist/index.js",
   "type": "module",
+  "files": [
+    "dist"
+  ],
   "publishConfig": {
     "access": "public"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-lead/package.json` around lines 1 - 12, Add a "files"
allowlist to package.json to ensure only the compiled distribution is published:
add a top-level "files" array containing "dist/" (and optionally "README.md" or
"LICENSE" if you want them published) so the package's public entry points
("main": "dist/index.js" and the "bin" field "agent-teams-lead-mcp":
"./dist/index.js") only expose dist and not src or other repo artifacts; update
package.json accordingly.
packages/agent-teams-lead/tsconfig.json (1)

4-5: Use a modern Node module resolver here.

This package is configured as a Node-executed ESM CLI (evidenced by "type": "module", "bin" entry point, and direct execution via node dist/index.js). TypeScript's legacy Node resolver does not fully model modern Node ESM resolution semantics. For Node 20+, the recommended configuration is module: "NodeNext" paired with moduleResolution: "NodeNext", which aligns TypeScript's type-checking with Node's actual module resolution behavior.

♻️ Proposed fix
   "compilerOptions": {
     "target": "ES2022",
-    "module": "ESNext",
-    "moduleResolution": "Node",
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",

(TypeScript docs)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-lead/tsconfig.json` around lines 4 - 5, Update the
TypeScript module settings to use the modern Node ESM resolver: change the
"module" and "moduleResolution" compilerOptions entries (currently "module":
"ESNext" and "moduleResolution": "Node") to "NodeNext" so TypeScript's
resolution matches Node 20+ ESM semantics for this package's ESM CLI setup.
packages/agent-teams-lead/src/spawner.ts (1)

137-145: Process termination lacks graceful shutdown timeout.

When stopping a teammate, SIGTERM is sent but there's no timeout before the process reference is deleted. If the process doesn't terminate promptly, resources may leak and the config cleanup happens before the process actually exits.

♻️ Add timeout and force-kill for unresponsive processes
 async stopTeammate(teammateId: string): Promise<void> {
   const spawned = this.processes.get(teammateId);
   if (!spawned) return;

   await this.log(teammateId, "Stopping teammate process");
   spawned.process.kill("SIGTERM");
+  
+  // Wait briefly for graceful shutdown, then force kill
+  await new Promise<void>((resolve) => {
+    const timeout = setTimeout(() => {
+      if (!spawned.process.killed) {
+        spawned.process.kill("SIGKILL");
+      }
+      resolve();
+    }, 5000);
+    spawned.process.once("exit", () => {
+      clearTimeout(timeout);
+      resolve();
+    });
+  });
+
   this.processes.delete(teammateId);
   await this.cleanupAgentConfig(spawned.agentConfigPath);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-lead/src/spawner.ts` around lines 137 - 145, In
stopTeammate, add a graceful shutdown wait after sending SIGTERM to ensure the
child actually exits before deleting its record and cleaning config: after
spawned.process.kill("SIGTERM") wait for the process 'exit' (or 'close') event
with a short timeout (e.g. 5–10s); if the timeout elapses, send SIGKILL and
await final exit, then remove the entry from this.processes and call
cleanupAgentConfig(spawned.agentConfigPath). Use the existing spawned.process
reference and ensure any listeners are cleaned up to avoid leaks.
packages/agent-teams-lead/src/filelock.ts (1)

2-2: Unused import.

existsSync is imported but never used in this file.

🧹 Remove unused import
 import { mkdir, rmdir, stat } from "node:fs/promises";
-import { existsSync } from "node:fs";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-lead/src/filelock.ts` at line 2, The import existsSync
in packages/agent-teams-lead/src/filelock.ts is unused; remove the existsSync
named import from the top-level import statement (or delete the entire import
line if nothing else is imported) so the file no longer contains an unused
symbol, ensuring no behavioral changes to functions like any file locking
utilities in this module.
packages/agent-teams-lead/src/store.ts (1)

208-217: getTasks reads from stale in-memory cache.

This method returns tasks from the in-memory this.tasks array without refreshing from disk. In a multi-process environment (which this system is designed for—multiple teammate agents), tasks may have been updated by other processes, leading to stale reads. Consider either:

  1. Refreshing from disk before returning, or
  2. Documenting that load() must be called to get fresh data

Note: teamStatus() in tools.ts correctly calls load() before reading, but callers may forget this.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-lead/src/store.ts` around lines 208 - 217, getTasks
currently returns from the in-memory this.tasks and can return stale data; make
it refresh from disk before filtering by converting getTasks to async and
calling await this.load() at the start (or invoke whatever synchronous
disk-refresh helper exists) so callers always see up-to-date tasks, then apply
the existing status/assigned_to filters; alternatively, if you must keep it
sync, update the function docstring to clearly state that callers must call
load() beforehand (reference getTasks and load methods to locate the code).
packages/agent-teams-lead/src/server.ts (1)

167-176: Signal handlers should properly await async shutdown.

The shutdown function is async, but the signal handlers call it without awaiting. While process.exit(0) inside shutdown will terminate anyway, the exception handlers on lines 169-176 call process.exit synchronously without invoking shutdown, so spawned processes may not be stopped cleanly on uncaught exceptions.

Consider invoking stopAll() in exception handlers for consistency:

Proposed fix
     process.on("uncaughtException", async (error) => {
       console.error("Uncaught exception:", error);
+      await this.spawner.stopAll();
       process.exit(1);
     });
     process.on("unhandledRejection", async (reason) => {
       console.error("Unhandled rejection:", reason);
+      await this.spawner.stopAll();
       process.exit(1);
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-lead/src/server.ts` around lines 167 - 176, Signal and
exception handlers currently call async shutdown logic without awaiting and
exception handlers call process.exit directly; update the process.on callbacks
for "SIGINT" and "SIGTERM" to call and await shutdown("SIGINT"/"SIGTERM") (use
an async wrapper so the await runs), and change the "uncaughtException" and
"unhandledRejection" handlers to await stopAll() (or await shutdown("exception")
if you prefer unified shutdown flow) before calling process.exit(1), referencing
the existing shutdown and stopAll functions to ensure spawned processes are
stopped cleanly.
packages/agent-teams-teammate/src/filelock.ts (1)

1-68: Code duplication with packages/agent-teams-lead/src/filelock.ts.

This file is identical to the lead package's filelock.ts. Consider extracting this into a shared utility package or a common module to avoid maintaining duplicate code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-teammate/src/filelock.ts` around lines 1 - 68, The file
duplicates file lock logic found in the lead package; extract the shared
implementation (functions like acquireLock, releaseLock, isLockStale,
withFileLock and constants STALE_LOCK_MS, RETRY_INTERVAL_MS, MAX_WAIT_MS) into a
single shared module or utility package and have both packages import that
module instead of keeping identical copies; update the current file to import
and re-export (or directly use) the shared withFileLock API, remove the
duplicated implementation, and ensure types/exports match so callers in both
agent-teams-teammate and agent-teams-lead work without code changes.
packages/agent-teams-teammate/src/store.ts (1)

6-58: Consider sharing type definitions with the lead package.

The Team, Task, Message, and Artifact interfaces are likely duplicated from packages/agent-teams-lead/src/types.ts. Since both packages operate on the same data files, extracting these types into a shared package would ensure consistency and reduce maintenance burden.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-teammate/src/store.ts` around lines 6 - 58, The Team,
Task, Message, and Artifact interfaces in store.ts are duplicated; extract these
type definitions into a shared module (e.g., a new package or an existing shared
package) and export them so both agent-teams-teammate and agent-teams-lead
import the same types. Remove the local interface declarations from
packages/agent-teams-teammate/src/store.ts and replace them with imports for
Team, Task, Message, and Artifact from the shared module, and update any other
files that currently declare or use duplicate definitions to import the shared
types as well.
packages/agent-teams-lead/src/index.ts (1)

1-21: Module executes server on import — consider separating CLI entry from library exports.

This file serves dual purposes: CLI entry point (lines 8-15 run on load) and library exports (lines 17-21). Importing this module for its exports (e.g., import { TeamStore } from 'agent-teams-lead') will also attempt to start the server.

If library consumption is intended, consider moving the startup logic to a separate cli.ts file and keeping index.ts purely for exports. Alternatively, guard the execution with a check.

Option: Guard execution when used as library
+import { fileURLToPath } from "node:url";
 import { resolve } from "node:path";
 import { LeadMCPServer } from "./server.js";

 const workspacePath = resolve(process.env.WORKSPACE_PATH || process.cwd());

+// Only run server when executed directly, not when imported as a module
+const isMain = process.argv[1] === fileURLToPath(import.meta.url);
+
+if (isMain) {
-try {
   const server = new LeadMCPServer(workspacePath);
   server.setupGracefulShutdown();
-  await server.start();
-} catch (error) {
-  console.error("Failed to start Agent Teams Lead MCP Server:", error);
-  process.exit(1);
-}
+  server.start().catch((error) => {
+    console.error("Failed to start Agent Teams Lead MCP Server:", error);
+    process.exit(1);
+  });
+}

 export { LeadMCPServer } from "./server.js";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-lead/src/index.ts` around lines 1 - 21, This module
currently constructs and starts LeadMCPServer on import (creating workspacePath,
new LeadMCPServer(...), calling setupGracefulShutdown() and await start()), so
importing exports like TeamStore will inadvertently start the server; fix by
moving the startup block into a separate CLI entry (e.g., cli.ts) or guard it so
it only runs when executed as a script — wrap the server startup (workspacePath
creation, new LeadMCPServer(...), setupGracefulShutdown(), and start()) behind a
runtime check (for example using an entry-point guard) and keep index.ts
exporting LeadMCPServer, TeamStore, LeadTools and the rest only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/agent-teams-lead/package.json`:
- Around line 22-31: The workspace lockfile is out of sync with the changed
dependency specifiers in packages/agent-teams-lead/package.json; from the
repository root run pnpm install (or pnpm -w install for workspaces) to
regenerate pnpm-lock.yaml, verify the lockfile includes the new entries for
"@modelcontextprotocol/sdk" and "zod", and commit the updated pnpm-lock.yaml so
CI's pnpm install --frozen-lockfile will succeed.

In `@packages/agent-teams-lead/src/filelock.ts`:
- Around line 42-47: The code unconditionally removes the lockPath (rmdir) after
the deadline and immediately mkdirs it, which can race with a legitimately
acquired lock; either throw an error on timeout instead of force-acquiring or,
if you must force-acquire, re-verify staleness before removing and acquiring:
check the lock's metadata (timestamp/pid) or attempt an atomic mkdir and only
rm+mkdir when a fresh re-check confirms the lock is still stale. Update the
logic around rmdir/mkdir (in filelock.ts) to perform that re-check (or to throw
on timeout) to avoid simultaneous lock holders.

In `@packages/agent-teams-lead/src/spawner.ts`:
- Around line 179-218: The flattening loop for parsed.mcpServers currently
builds flatServers entries for each upstream by only setting command, args, and
env (in the block handling upstreams), which drops other upstream fields (e.g.,
transport, url, auth required by UpstreamServerConfigSchema) and breaks HTTP
upstreams; update the upstream handling in spawner.ts so that when creating
flatServers[upstream.name] you preserve all original upstream properties (spread
the upstream object) and then override/replace only the env with the resolvedEnv
(and ensure args/command remain present if needed), so fields like transport,
url, and auth are retained for mcp-proxy upstreams.
- Around line 38-45: The hardcoded teammateMcpPath built from this.workspacePath
is fragile; replace it with a dynamic resolution and/or configurable override:
accept a config option or env var (e.g., teammateMcpPath or TEAMMATE_MCP_PATH)
in the class/constructor that currently sets this.teammateMcpPath, and if not
provided use Node package resolution (require.resolve or import.meta.resolve) to
locate the "agent-teams-teammate" package entrypoint (or resolve via
resolve-package-path logic using package.json lookup from this.workspacePath).
Update the code paths that reference this.teammateMcpPath so the code falls back
to the resolved path and throws a clear error if resolution fails.

In `@packages/agent-teams-lead/src/store.ts`:
- Around line 83-121: spawnTeam currently writes team.json, tasks.json,
messages.json and artifacts.json without synchronization; wrap the critical
section in the same locking mechanism used elsewhere by calling
this.withFileLock(...) (or the existing withFileLock helper) so all file
operations are atomic. Move/reset resetNames(), teammates creation, assigning
this.team, clearing this.tasks/messages/artifacts, this.ensureDir(), and the
four this.writeJson(...) calls inside the lock callback; continue to use
teamPath(), tasksPath(), messagesPath(), artifactsPath(), generateId(),
ensureDir, and writeJson so concurrent spawnTeam callers cannot interleave and
create inconsistent files.

In `@packages/agent-teams-teammate/src/filelock.ts`:
- Around line 41-46: The force-acquire block that does try { await
rmdir(lockPath) } catch { } followed by await mkdir(lockPath) can race and throw
an unhandled EEXIST; wrap the remove-and-create sequence in a try/catch (or
catch errors from mkdir) and handle EEXIST by treating it as a failed
force-acquire and retry/loop or re-enter the existing retry flow. Specifically,
update the block that references lockPath in the lock acquisition routine so
that mkdir errors are caught (inspect error.code === 'EEXIST') and handled
deterministically (e.g., swallow EEXIST or retry after a short backoff) to avoid
both processes believing they hold the lock.

In `@packages/agent-teams-teammate/src/server.ts`:
- Around line 286-296: The handler currently returns a success response when an
artifact is missing by calling this.ok({ error: ... }) — change it to return a
proper error via this.err(...) instead (e.g., call this.err(new Error(`Artifact
${params.artifact_id} not found`)) or pass the standard error payload used
across handlers), locating the logic around this.store.load() /
this.store.getArtifact(...) and replacing the this.ok(...) branch with a
this.err(...) call so clients receive the consistent error response format.

---

Nitpick comments:
In `@packages/agent-teams-lead/package.json`:
- Around line 1-12: Add a "files" allowlist to package.json to ensure only the
compiled distribution is published: add a top-level "files" array containing
"dist/" (and optionally "README.md" or "LICENSE" if you want them published) so
the package's public entry points ("main": "dist/index.js" and the "bin" field
"agent-teams-lead-mcp": "./dist/index.js") only expose dist and not src or other
repo artifacts; update package.json accordingly.

In `@packages/agent-teams-lead/src/filelock.ts`:
- Line 2: The import existsSync in packages/agent-teams-lead/src/filelock.ts is
unused; remove the existsSync named import from the top-level import statement
(or delete the entire import line if nothing else is imported) so the file no
longer contains an unused symbol, ensuring no behavioral changes to functions
like any file locking utilities in this module.

In `@packages/agent-teams-lead/src/index.ts`:
- Around line 1-21: This module currently constructs and starts LeadMCPServer on
import (creating workspacePath, new LeadMCPServer(...), calling
setupGracefulShutdown() and await start()), so importing exports like TeamStore
will inadvertently start the server; fix by moving the startup block into a
separate CLI entry (e.g., cli.ts) or guard it so it only runs when executed as a
script — wrap the server startup (workspacePath creation, new
LeadMCPServer(...), setupGracefulShutdown(), and start()) behind a runtime check
(for example using an entry-point guard) and keep index.ts exporting
LeadMCPServer, TeamStore, LeadTools and the rest only.

In `@packages/agent-teams-lead/src/server.ts`:
- Around line 167-176: Signal and exception handlers currently call async
shutdown logic without awaiting and exception handlers call process.exit
directly; update the process.on callbacks for "SIGINT" and "SIGTERM" to call and
await shutdown("SIGINT"/"SIGTERM") (use an async wrapper so the await runs), and
change the "uncaughtException" and "unhandledRejection" handlers to await
stopAll() (or await shutdown("exception") if you prefer unified shutdown flow)
before calling process.exit(1), referencing the existing shutdown and stopAll
functions to ensure spawned processes are stopped cleanly.

In `@packages/agent-teams-lead/src/spawner.ts`:
- Around line 137-145: In stopTeammate, add a graceful shutdown wait after
sending SIGTERM to ensure the child actually exits before deleting its record
and cleaning config: after spawned.process.kill("SIGTERM") wait for the process
'exit' (or 'close') event with a short timeout (e.g. 5–10s); if the timeout
elapses, send SIGKILL and await final exit, then remove the entry from
this.processes and call cleanupAgentConfig(spawned.agentConfigPath). Use the
existing spawned.process reference and ensure any listeners are cleaned up to
avoid leaks.

In `@packages/agent-teams-lead/src/store.ts`:
- Around line 208-217: getTasks currently returns from the in-memory this.tasks
and can return stale data; make it refresh from disk before filtering by
converting getTasks to async and calling await this.load() at the start (or
invoke whatever synchronous disk-refresh helper exists) so callers always see
up-to-date tasks, then apply the existing status/assigned_to filters;
alternatively, if you must keep it sync, update the function docstring to
clearly state that callers must call load() beforehand (reference getTasks and
load methods to locate the code).

In `@packages/agent-teams-lead/tsconfig.json`:
- Around line 4-5: Update the TypeScript module settings to use the modern Node
ESM resolver: change the "module" and "moduleResolution" compilerOptions entries
(currently "module": "ESNext" and "moduleResolution": "Node") to "NodeNext" so
TypeScript's resolution matches Node 20+ ESM semantics for this package's ESM
CLI setup.

In `@packages/agent-teams-teammate/package.json`:
- Around line 1-29: The package.json for `@arvoretech/agent-teams-teammate-mcp` is
missing a "files" field which causes npm to include everything not ignored; add
a "files" array to package.json (e.g., include "dist" and any other runtime
assets like bin or README if needed) so only necessary artifacts are published,
then rebuild to ensure dist is present for the "main" entry and the CLI bin
("agent-teams-teammate-mcp") to work correctly.

In `@packages/agent-teams-teammate/src/filelock.ts`:
- Around line 1-68: The file duplicates file lock logic found in the lead
package; extract the shared implementation (functions like acquireLock,
releaseLock, isLockStale, withFileLock and constants STALE_LOCK_MS,
RETRY_INTERVAL_MS, MAX_WAIT_MS) into a single shared module or utility package
and have both packages import that module instead of keeping identical copies;
update the current file to import and re-export (or directly use) the shared
withFileLock API, remove the duplicated implementation, and ensure types/exports
match so callers in both agent-teams-teammate and agent-teams-lead work without
code changes.

In `@packages/agent-teams-teammate/src/store.ts`:
- Around line 6-58: The Team, Task, Message, and Artifact interfaces in store.ts
are duplicated; extract these type definitions into a shared module (e.g., a new
package or an existing shared package) and export them so both
agent-teams-teammate and agent-teams-lead import the same types. Remove the
local interface declarations from packages/agent-teams-teammate/src/store.ts and
replace them with imports for Team, Task, Message, and Artifact from the shared
module, and update any other files that currently declare or use duplicate
definitions to import the shared types as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a944db00-5ad6-461c-aa0b-1a66beb7e1a8

📥 Commits

Reviewing files that changed from the base of the PR and between 4deb2b3 and b244416.

📒 Files selected for processing (17)
  • packages/agent-teams-lead/package.json
  • packages/agent-teams-lead/src/filelock.ts
  • packages/agent-teams-lead/src/index.ts
  • packages/agent-teams-lead/src/names.ts
  • packages/agent-teams-lead/src/schemas.ts
  • packages/agent-teams-lead/src/server.ts
  • packages/agent-teams-lead/src/spawner.ts
  • packages/agent-teams-lead/src/store.ts
  • packages/agent-teams-lead/src/tools.ts
  • packages/agent-teams-lead/src/types.ts
  • packages/agent-teams-lead/tsconfig.json
  • packages/agent-teams-teammate/package.json
  • packages/agent-teams-teammate/src/filelock.ts
  • packages/agent-teams-teammate/src/index.ts
  • packages/agent-teams-teammate/src/server.ts
  • packages/agent-teams-teammate/src/store.ts
  • packages/agent-teams-teammate/tsconfig.json

Comment thread packages/agent-teams-lead/package.json
Comment thread packages/agent-teams-lead/src/filelock.ts Outdated
Comment thread packages/agent-teams-lead/src/spawner.ts Outdated
Comment thread packages/agent-teams-lead/src/spawner.ts
Comment thread packages/agent-teams-lead/src/store.ts
Comment thread packages/agent-teams-teammate/src/filelock.ts Outdated
Comment on lines +286 to +296
async (params) => {
try {
await this.store.load();
const artifact = this.store.getArtifact(params.artifact_id);
if (!artifact) return this.ok({ error: `Artifact ${params.artifact_id} not found` });
return this.ok(artifact as unknown as Record<string, unknown>);
} catch (error) {
return this.err(error);
}
}
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling — uses ok() to return an error.

When the artifact is not found, line 290 returns this.ok({ error: ... }) instead of this.err(...). This means clients receive a "success" response structure containing an error field, rather than the standard error format used elsewhere.

Proposed fix
       async (params) => {
         try {
           await this.store.load();
           const artifact = this.store.getArtifact(params.artifact_id);
-          if (!artifact) return this.ok({ error: `Artifact ${params.artifact_id} not found` });
+          if (!artifact) {
+            return this.err(new Error(`Artifact ${params.artifact_id} not found`));
+          }
           return this.ok(artifact as unknown as Record<string, unknown>);
         } catch (error) {
           return this.err(error);
         }
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async (params) => {
try {
await this.store.load();
const artifact = this.store.getArtifact(params.artifact_id);
if (!artifact) return this.ok({ error: `Artifact ${params.artifact_id} not found` });
return this.ok(artifact as unknown as Record<string, unknown>);
} catch (error) {
return this.err(error);
}
}
);
async (params) => {
try {
await this.store.load();
const artifact = this.store.getArtifact(params.artifact_id);
if (!artifact) {
return this.err(new Error(`Artifact ${params.artifact_id} not found`));
}
return this.ok(artifact as unknown as Record<string, unknown>);
} catch (error) {
return this.err(error);
}
}
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/agent-teams-teammate/src/server.ts` around lines 286 - 296, The
handler currently returns a success response when an artifact is missing by
calling this.ok({ error: ... }) — change it to return a proper error via
this.err(...) instead (e.g., call this.err(new Error(`Artifact
${params.artifact_id} not found`)) or pass the standard error payload used
across handlers), locating the logic around this.store.load() /
this.store.getArtifact(...) and replacing the this.ok(...) branch with a
this.err(...) call so clients receive the consistent error response format.

Joao208 added 4 commits March 9, 2026 23:53
- filelock: re-verify staleness before force-acquiring lock on timeout, handle EEXIST on mkdir
- spawner: spread full upstream object to preserve url/transport/auth fields
- spawner: resolve teammateMcpPath dynamically via env var or require.resolve
- store: wrap spawnTeam in withFileLock for atomic file writes
- use createRequire instead of bare require (no-undef)
- avoid unused destructured vars when stripping upstream fields
@Joao208 Joao208 merged commit d152bdb into main Mar 10, 2026
5 checks passed
@Joao208 Joao208 deleted the joaobarros-/-agent-teams-mcp branch March 10, 2026 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant