Skip to content

feat: subagents — filtered MCP endpoints + per-provider agent files#35

Merged
YossifNassar merged 4 commits into
mainfrom
feat/sub-agents
Apr 6, 2026
Merged

feat: subagents — filtered MCP endpoints + per-provider agent files#35
YossifNassar merged 4 commits into
mainfrom
feat/sub-agents

Conversation

@YossifNassar
Copy link
Copy Markdown
Contributor

@YossifNassar YossifNassar commented Apr 6, 2026

Summary

Introduces named sub-agent configurations in capabilities.yaml under the subagents key. Each sub-agent gets a filtered MCP endpoint that exposes only its declared tools, plus provider-native agent definition files for Claude Code and Cursor.

What's new

subagents section in capabilities.yaml

subagents:
  - id: infra-agent
    description: AWS CDK and Terraform specialist. Use when working in backend-infra/ or user-infra/.
    skills:
      - my-iac-skill
    tools:
      - search_cdk_docs
      - validate_cfn
    instructions: |
      Work exclusively in backend-infra/ and user-infra/.

  - id: api-agent
    description: Python Lambda and FastAPI specialist. Use for Lambda function work.
    skills:
      - my-serverless-skill
    tools:
      - get_lambda_guidance
      - sam_logs

Filtered MCP endpoints

  • New route /{projectId}/agents/{id}/mcp — same CapaMCPServer class, instantiated with an agentId
  • tools/list returns only this agent's declared tools (resolved to qualified names, e.g. aws-iac.search_cdk_docs)
  • tools/call rejects unauthorized tool calls with a clear JSON-RPC error
  • Filtering applied in both handleMessage() (HTTP path) and setupHandlers() (SDK transport path)
  • Server cache uses projectId:agentId key so sub-agent instances are independent

Per-provider agent files (on capa install)

Provider File written Format
claude-code .claude/agents/{id}.md + CLAUDE.md block name/description/model: inherit frontmatter
cursor .cursor/agents/{id}.md name/description/model/readonly/is_background frontmatter

Cursor reads .cursor/agents/*.md to auto-delegate based on the description field — no separate MCP server entry per sub-agent is needed.

Migration / cleanup

  • purgeCursorSubAgentMCPEntries() removes stale capa-{id} entries from .cursor/mcp.json and stale .cursor/rules/*.mdc files from old implementations
  • capa clean removes all generated agent files and MCP registrations by reading DB-tracked sub-agents

DB tracking

  • New sub_agents table with CASCADE DELETE on project removal
  • upsertSubAgent / getSubAgents / removeSubAgent helpers

Test plan

  • src/server/__tests__/sub-agent-filter.test.tsgetQualifiedToolName + tool filtering logic (8 tests)
  • src/cli/utils/__tests__/sub-agent-instructions.test.ts — agent file writing/removal for both providers, upsert idempotency, multi-agent isolation (11 tests)
  • src/cli/utils/__tests__/sub-agent-mcp.test.ts — MCP registration, cursor skip, unregister, purge migration (8 tests)
  • src/db/__tests__/sub-agents.test.ts — CRUD, project isolation, cascade delete (8 tests)
  • All 335 tests pass (0 failures)

🤖 Generated with Claude Code

Introduces named sub-agent configurations in `capabilities.yaml`. Each
sub-agent gets a filtered MCP endpoint that exposes only its declared tools,
plus provider-native agent definition files for Claude Code and Cursor.

## What's new

**capabilities.yaml `sub_agents` section**
  - New `SubAgent` interface: `id`, `description`, `skills`, `tools`, `instructions`
  - Example: an `infra-agent` that only sees CDK/DynamoDB tools, and an
    `api-agent` that only sees Lambda tools

**Filtered MCP endpoints (server)**
  - New route `/{projectId}/agents/{id}/mcp` served by the same `CapaMCPServer`
    class, instantiated with an `agentId`
  - `getAgentAllowedToolIds()` resolves declared tool IDs → qualified names
    (e.g. `search_cdk_docs` → `aws-iac.search_cdk_docs`)
  - Filtering applied in both `handleMessage()` (HTTP JSON-RPC path) and
    `setupHandlers()` (SDK transport path) for `tools/list` and `tools/call`
  - Unauthorized `tools/call` returns a clear JSON-RPC error, not a silent drop
  - Server cache key is `projectId:agentId` so sub-agent servers are independent

**Per-provider agent definition files (install)**
  - `claude-code`: writes `.claude/agents/{id}.md` (Claude Code sub-agent
    format with `name`/`description`/`model: inherit` frontmatter) + upserts
    a `CLAUDE.md` context block
  - `cursor`: writes `.cursor/agents/{id}.md` (Cursor subagent format with
    `name`/`description`/`model`/`readonly`/`is_background` frontmatter);
    Cursor auto-delegates based on the `description` field — no separate MCP
    entry needed
  - Both providers share the same body: MCP server key, skills list, qualified
    tool names, and optional custom `instructions`

**Migration / cleanup**
  - `purgeCursorSubAgentMCPEntries()` removes stale `capa-{id}` entries from
    `.cursor/mcp.json` and stale `.cursor/rules/*.mdc` files from previous
    incorrect implementations
  - `capa clean` iterates DB-tracked sub-agents and removes all generated files
    and MCP registrations

**DB tracking**
  - New `sub_agents` table (`project_id`, `agent_id`, UNIQUE, CASCADE DELETE)
  - `upsertSubAgent` / `getSubAgents` / `removeSubAgent` helpers

**Tests (39 new)**
  - `sub-agent-filter.test.ts` — `getQualifiedToolName` + filtering logic (8)
  - `sub-agent-instructions.test.ts` — agent file writing/removal for both
    providers, upsert idempotency, multi-agent isolation (11)
  - `sub-agent-mcp.test.ts` — MCP registration, cursor skip, unregister,
    purge migration (8)
  - `sub-agents.test.ts` (DB) — CRUD, project isolation, cascade delete (8)
  - All 335 tests pass (0 failures)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@YossifNassar YossifNassar requested a review from Minitour April 6, 2026 17:46
YossifNassar and others added 2 commits April 6, 2026 21:55
Renames the `sub_agents` YAML key and TypeScript interface field to
`subAgents` to align with camelCase naming used by Claude Code and Cursor
in their config files. The internal SQLite table name (`sub_agents`) is
unchanged as snake_case is conventional for SQL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aligns with the lowercase naming convention used by Claude Code and Cursor
in their own config keys (e.g. `mcpServers` vs camelCase mixing). All
YAML, TypeScript interface, and test references updated. Internal SQLite
table name (`sub_agents`) is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@YossifNassar YossifNassar changed the title feat: sub-agents — filtered MCP endpoints + per-provider agent files feat: subagents — filtered MCP endpoints + per-provider agent files Apr 6, 2026
Windows file I/O under coverage instrumentation is slower than the
default 5s Bun hook timeout, causing intermittent `afterEach` timeouts
when `rmSync` cleans up SQLite temp dirs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@YossifNassar YossifNassar merged commit 3eec7b3 into main Apr 6, 2026
4 checks passed
@Minitour Minitour deleted the feat/sub-agents branch May 9, 2026 22:43
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.

2 participants