Skip to content

Plugin Deep Dive

Technical architecture of the protoLabs Claude Code plugin. Covers the MCP server, tool registration, hook lifecycle, command system, and extension points.

For setup instructions, see Claude Plugin Setup. For commands and examples, see Plugin Commands.

Plugin Anatomy

packages/mcp-server/
├── src/
│   ├── index.ts              # MCP server entry point (stdio transport)
│   └── tools/                # Tool definition modules (22 files)
│       ├── feature-tools.ts
│       ├── agent-tools.ts
│       ├── git-tools.ts
│       └── ...
└── plugins/automaker/
    ├── .claude-plugin/
    │   └── plugin.json       # Plugin manifest (name, version, mcpServers, hooks)
    ├── hooks/
    │   ├── hooks.json        # Hook configuration (mirrors plugin.json)
    │   ├── start-mcp.sh      # MCP server launcher
    │   ├── check-mcp-health.sh
    │   ├── session-context.sh
    │   ├── compaction-prime-directive.sh
    │   ├── pre-compact-save-state.sh
    │   ├── session-end-save.sh
    │   ├── block-dangerous.sh
    │   ├── auto-start-agent.sh
    │   ├── auto-update-plugin.sh
    │   ├── handle-mcp-failure.sh
    │   └── scripts/
    │       ├── post-edit-typecheck.js
    │       └── evaluate-session.js
    ├── commands/              # Slash command definitions (18 files)
    │   ├── board.md
    │   ├── ava.md
    │   ├── setuplab.md
    │   └── ...
    ├── agents/                # Subagent definitions (13 files)
    │   ├── feature-planner.md
    │   ├── codebase-analyzer.md
    │   └── ...
    ├── data/                  # Runtime state (created by hooks)
    │   ├── ava-session-state.json
    │   └── session-history.jsonl
    ├── .env.example
    └── .env                   # User secrets (gitignored)

How the Pieces Connect

  1. claude plugin install protolabs reads plugin.json and copies the plugin to ~/.claude/plugins/protolabs/
  2. On session start, Claude Code launches the MCP server via start-mcp.sh
  3. start-mcp.sh validates AUTOMAKER_ROOT, checks for the built binary, then runs node dist/index.js
  4. The MCP server connects over stdio and registers all tools with Claude Code
  5. Hooks fire at lifecycle events (session start, tool use, compaction, session end)
  6. Commands and agents are loaded from their respective directories as slash commands and subagent types

MCP Server Architecture

Entry Point

packages/mcp-server/src/index.ts is a single-file MCP server using @modelcontextprotocol/sdk. It runs on stdio -- no HTTP, no WebSocket.

typescript
const server = new Server(
  { name: 'automaker-mcp-server', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  /* dispatch */
});

const transport = new StdioServerTransport();
await server.connect(transport);

The apiCall Proxy

The MCP server is a thin proxy. It holds zero business logic. Every tool call becomes an HTTP request to the protoLabs server at localhost:3008.

typescript
async function apiCall(
  endpoint: string,
  body: Record<string, unknown>,
  method: 'GET' | 'POST' = 'POST'
): Promise<unknown>;

Key behaviors:

  • URL: ${API_URL}/api${endpoint} (default: http://localhost:3008)
  • Authentication: X-API-Key header
  • GET requests serialize body as query params; POST as JSON body
  • Retry with exponential backoff: up to 3 retries, base 1s delay, max 10s, 25% jitter
  • Retries on network errors and 5xx responses
  • Does NOT retry on 4xx client errors

Context Window Optimization

Several tools optimize for long agent sessions:

  • get_feature strips executionHistory, descriptionHistory, statusHistory, and planSpec by default (pass includeHistory: true to include)
  • get_agent_output truncates to the last 200 lines by default (maxLines parameter)
  • ENABLE_TOOL_SEARCH=auto:10 defers tool loading, reducing context by 40-50% in large sessions

Client-Side Computation

Some tools compute results in the MCP process without a server endpoint:

  • get_dependency_graph -- fetches list_features and builds the graph client-side
  • get_execution_order -- topological sort over the dependency graph
  • query_board -- client-side filtering over list_features (status, epicId, complexity, search)
  • get_feature_handoff -- reads directly from the filesystem via fs/promises

Tool Categories

All tools are defined as static schemas in separate module files under packages/mcp-server/src/tools/, then aggregated in index.ts.

ModuleFileDescription
Feature Managementfeature-tools.tsFeature CRUD, git settings
Agent Controlagent-tools.tsStart/stop agents, templates, dynamic executor
Queuequeue-tools.tsFeature queue (add, list, clear)
Orchestrationorchestration-tools.tsAuto-mode, dependencies, execution order
Context & Skillscontext-tools.tsContext files, skills CRUD
Git & GitHubgit-tools.tsPRs, reviews, worktree management
Git Operationsgit-ops-tools.tsStaging, file details
Worktree Gitworktree-git-tools.tsCherry-pick, stash, abort/continue
File Operationsfile-ops-tools.tsCopy, move, browse files
Project Lifecycleproject-tools.tsProjects, PRD, milestones, phases
Promotionpromotion-tools.tsStaging/main promotion pipeline
Calendarcalendar-tools.tsCalendar events CRUD
Contentcontent-tools.tsContent pipeline (blog, docs)
Integrationsintegration-tools.tsDiscord, HITL forms
Lead Engineerlead-engineer-tools.tsLead engineer state machine control
Observabilityobservability-tools.tsLangfuse traces, costs, scoring, datasets
Quarantinequarantine-tools.tsQuarantine entries, trust tiers
Schedulerscheduler-tools.tsScheduler status, maintenance tasks
Setupsetup-tools.tsSetupLab: research, gap analysis, alignment
Utilitiesutility-tools.tsHealth, board summary, briefing, metrics
Workspaceworkspace-tools.tsWorktrees, notes, escalation

Each module exports a Tool[] array:

typescript
export const featureTools: Tool[] = [
  {
    name: 'list_features',
    description: '...',
    inputSchema: {
      type: 'object',
      properties: { projectPath: { type: 'string' } },
      required: ['projectPath'],
    },
  },
];

Tool Dispatch

index.ts contains a single handleTool() function with a switch statement over tool names. Each case maps to an apiCall(). Example:

typescript
case 'list_features':
  result = await apiCall('/features/list', { projectPath: args.projectPath });
  break;

Hook System

Hooks are shell scripts (or Node.js scripts) that fire at Claude Code lifecycle events. They receive input as JSON on stdin and write output to stdout (injected into Claude's context). Stderr is only visible in verbose mode.

Exit Codes

CodeMeaning
0Allow (continue normally)
1Error (surfaces output as context, does not block)
2Block (prevents the tool call entirely)

Hook Configuration

Hooks are defined in both plugin.json and hooks.json. The plugin manifest is authoritative; hooks.json is a development-readable copy kept in sync.

Lifecycle Events

EventMatchersHooksPurpose
SessionStartcompactcompaction-prime-directive.sh, session-context.shRestore identity after compaction
SessionStartstartupcheck-mcp-health.sh, session-context.shPre-flight diagnostics
SessionStartresumecheck-mcp-health.sh, session-context.shResume checks
PreToolUseBashblock-dangerous.shBlock destructive commands
PostToolUseEdit|Writeauto-update-plugin.sh, post-edit-typecheck.jsPlugin update reminders, type checking
PostToolUsecreate_featureauto-start-agent.shAuto-launch agent on feature creation
PreCompact(always)pre-compact-save-state.shSnapshot state before compaction
SessionEnd(always)session-end-save.sh, evaluate-session.jsPersist session summary
PostToolUseFailureMCP toolshandle-mcp-failure.shDiagnostic output on failure

Hook Details

start-mcp.sh

MCP server launcher. Validates AUTOMAKER_ROOT is set and dist/index.js exists, then runs node dist/index.js. This is the command referenced in mcpServers.studio.

check-mcp-health.sh

Pre-flight diagnostics injected on session start. Checks three things in order:

  1. AUTOMAKER_ROOT is set
  2. packages/mcp-server/dist/index.js exists (built artifact)
  3. ${API_BASE}/api/health returns HTTP 200 (5s timeout)

Silent if all checks pass -- no context noise on healthy sessions.

session-context.sh

Injects board state into Claude's context. Reads feature JSON files directly from .automaker/features/*/feature.json, counts by status, shows the current git branch, and flags active work. Also restores pre-compaction context from data/ava-session-state.json.

compaction-prime-directive.sh

Identity restoration after context window compaction. Outputs mandatory instructions to invoke /ava, reads the saved session state, and injects hardcoded operational rules (no server restart, no worktree cd, max 2-3 concurrent agents).

Only fires on the compact matcher -- not on normal startup or resume.

pre-compact-save-state.sh

Snapshots operational state before context compression. Reads all feature JSONs, counts by status, captures in-progress and review titles, runs gh pr list, and writes data/ava-session-state.json:

json
{
  "timestamp": "2026-02-28T12:00:00Z",
  "event": "PreCompact",
  "projectPath": "/path/to/project",
  "branch": "dev",
  "board": { "total": 15, "backlog": 3, "in_progress": 2, "review": 1, "done": 8, "blocked": 1 },
  "currentWork": ["Feature A", "Feature B"],
  "inReview": ["Feature C"],
  "prPipeline": { "count": 2, "prs": ["#123", "#124"] }
}

session-end-save.sh

Overwrites data/ava-session-state.json with a SessionEnd snapshot and appends one JSONL line to data/session-history.jsonl (capped at 100 entries).

block-dangerous.sh

Safety guard that blocks catastrophic operations. Exit code 2 prevents the tool call. Blocked patterns include:

  • rm -rf /, rm -rf ~, rm -rf $HOME, rm -rf ..
  • git push --force origin main (feature branches allowed)
  • git reset --hard, git checkout ., git restore ., git clean -f
  • SQL destructive: DROP TABLE, DROP DATABASE, TRUNCATE TABLE
  • Disk destructive: mkfs, dd if=

auto-start-agent.sh

Fires after create_feature. Guards: tool succeeded, feature has an ID, not an epic, not human-assigned, no dependencies. On pass, POSTs to /api/auto-mode/run-feature to launch an agent automatically.

auto-update-plugin.sh

Fires on Edit/Write. If the edited file is inside packages/mcp-server/plugins/automaker, outputs a reminder to reinstall the plugin. Extra warning if the file is in hooks/ (requires full uninstall/reinstall).

handle-mcp-failure.sh

Fires on MCP tool failures. Checks server reachability, API key validity, and outputs targeted recovery steps.

post-edit-typecheck.js

Node.js hook. After editing a .ts/.tsx file, walks up to the nearest tsconfig.json and runs npx tsc --noEmit. Filters output to errors in the edited file only (max 10 lines). Exit 1 surfaces errors; exit 0 if errors are only in other files.

evaluate-session.js

Fires on session end. Counts user messages and tool uses from the conversation transcript. If the session had 10+ user messages, suggests extracting patterns to .automaker/memory/.

Command System

Commands are markdown files with YAML frontmatter in packages/mcp-server/plugins/automaker/commands/.

Format

yaml
---
name: command-name
description: One-line description
argument-hint: (optional args syntax)
allowed-tools:
  - ToolName
  - mcp__plugin_protolabs_studio__tool_name
model: sonnet # optional model override
temporary: true # optional flag for short-lived commands
temporary-reason: '...'
---
# Command body (prompt instructions for Claude)

Key Fields

FieldRequiredDescription
nameYesSlash command name (e.g., board for /board)
descriptionYesOne-line description shown in help
argument-hintNoArgument syntax hint
allowed-toolsYesWhitelist of tools this command can use
modelNoModel override (haiku, sonnet, opus)
temporaryNoMarks command for future removal

Commands vs Subagents

AspectCommandSubagent
Locationcommands/agents/
Invocation/command-nameTask(subagent_type: "protolabs:agent-name")
ExecutionIn-context (same conversation)Spawned as a subprocess
ContextFull conversation historyOnly the prompt provided
Use caseInteractive workflowsParallelizable, isolated tasks

Current Commands (17)

/auto-mode, /ava, /board, /calendar-assistant, /context, /deep-research, /due-diligence, /headsdown, /improve-prompts, /orchestrate, /plan-project, /promote, /setuplab, /ship, /sparc-prd, /update-plugin, /welcome

Session Lifecycle

Startup Flow

Claude Code starts
  → plugin.json read by Claude Code
  → start-mcp.sh launched (stdio transport)
    → validates AUTOMAKER_ROOT
    → checks dist/index.js exists
    → runs: node dist/index.js
  → SessionStart hooks fire:
    → check-mcp-health.sh (diagnostics)
    → session-context.sh (board state injection)
  → Tools registered with Claude Code
  → Session ready

Compaction Flow

Context window approaching limit
  → PreCompact hook fires:
    → pre-compact-save-state.sh (snapshots board, PRs to ava-session-state.json)
  → Claude Code compresses conversation
  → SessionStart (compact matcher) hooks fire:
    → compaction-prime-directive.sh (identity restoration, operational rules)
    → session-context.sh (current board state)
  → Session continues with restored context

Session End Flow

User exits or session closes
  → SessionEnd hooks fire:
    → session-end-save.sh (final snapshot to ava-session-state.json, append to session-history.jsonl)
    → evaluate-session.js (pattern extraction suggestion if 10+ user messages)

Extension Points

Adding a New Tool

  1. Create or extend a module in packages/mcp-server/src/tools/:
typescript
export const myTools: Tool[] = [
  {
    name: 'my_tool',
    description: 'What it does',
    inputSchema: {
      type: 'object',
      properties: { projectPath: { type: 'string' } },
      required: ['projectPath'],
    },
  },
];
  1. Import and spread into the tools array in index.ts:
typescript
import { myTools } from './tools/my-tools.js';
const tools: Tool[] = [...featureTools, ...myTools /* ... */];
  1. Add a case in handleTool():
typescript
case 'my_tool':
  result = await apiCall('/my-endpoint', { projectPath: args.projectPath });
  break;
  1. Rebuild: npm run build:packages

Adding a New Command

Create a markdown file in packages/mcp-server/plugins/automaker/commands/:

yaml
---
name: my-command
description: What this command does
allowed-tools:
  - mcp__plugin_protolabs_studio__list_features
---
# Instructions for Claude when this command is invoked

Reinstall the plugin: claude plugin uninstall protolabs && claude plugin install protolabs

Adding a New Hook

  1. Create the script in packages/mcp-server/plugins/automaker/hooks/
  2. Make it executable: chmod +x hooks/my-hook.sh
  3. Register in both plugin.json and hooks.json:
json
{
  "event": "PostToolUse",
  "matcher": "ToolPattern",
  "hooks": [
    {
      "type": "command",
      "command": "bash ${AUTOMAKER_ROOT}/packages/mcp-server/plugins/automaker/hooks/my-hook.sh"
    }
  ]
}
  1. Reinstall the plugin (hooks require full uninstall/reinstall)

Adding a New Subagent

Create a markdown file in packages/mcp-server/plugins/automaker/agents/:

yaml
---
name: my-agent
description: What this agent does
allowed-tools:
  - Read
  - Write
  - mcp__plugin_protolabs_studio__tool_name
model: sonnet
---
# Agent prompt instructions

Invoke via: Task(subagent_type: "protolabs:my-agent", prompt: "...")

Built by protoLabs — Open source on GitHub