/posts/til/til-codedesigning-claude-code-tools-using-an-mvc-mental-model
Designing Claude Code Tools Using an MVC Mental Model
When My AI Tools Started Feeling Like Spaghetti The moment you add more than a couple of tools to an AI system, everything starts to blur: which thing calls what, where the logi...
When My AI Tools Started Feeling Like Spaghetti
The moment you add more than a couple of tools to an AI system, everything starts to blur: which thing calls what, where the logic lives, and why a tiny change breaks three different workflows. Realizing I could map Claude’s commands, agents, and skills directly onto the MVC patterns I already know instantly made the system legible again. This mental model turns “mystical AI plumbing” into familiar web-app architecture you can reason about, refactor, and scale without fear.
From MVC to AI Tooling
The easiest way to stay sane with Claude code tools is to pretend you’re designing a small web app. Commands are your routes.php or router definitions: they describe what can be called from the outside world, with thin wiring that says, “when someone hits this endpoint, send it over there.” They shouldn’t know much about business rules; they just expose a stable surface.
Agents then play the role of controllers. An agent receives a request (via a command), decides how to handle it, orchestrates which skills to call, and shapes the final response. This is where you centralize validation, error handling, and cross-cutting concerns like logging or authorization. The trade-off is clear: the more orchestration you put here, the more each agent becomes a distinct “application” with its own behavior and guarantees.
Skills are your services, repositories, and providers—the actual capabilities. They talk to databases, APIs, file systems, or internal libraries. Skills should be reusable, side-effect-aware, and as stateless as possible. The benefit of pushing logic into skills is that multiple agents (or even future tools) can share the same reliable building blocks without duplicating integrations.
The interesting part is the separation of intent from implementation. Commands express intent in a way that’s easy for the model (and humans) to reason about: “call this tool for that kind of task.” Agents interpret that intent and coordinate the work. Skills actually do the work. When you keep those layers clean, you can swap agents, add new commands, or refactor skills without rewriting everything else.
💡 Did you know: You can treat an agent as a mini-application and wire multiple command sets into it, the same way a controller can expose many routes that share validation and business logic.
Commands, Agents, and Skills in Action
// commands.php — like routes.php
return [
'generate_invoice' => [
'agent' => 'billing_agent', // which agent (controller) handles this
'input_schema' => [ // thin contract
'customer_id' => 'string',
'line_items' => 'array',
],
],
];
// Agent: orchestrates behavior (like a controller)
class BillingAgent
{
public function __construct(
private InvoiceSkill $invoiceSkill,
private CustomerSkill $customerSkill,
) {}
public function handleGenerateInvoice(array $input): array
{
// validation & orchestration live here
$customer = $this->customerSkill->fetchCustomer($input['customer_id']);
if (!$customer->isBillable()) {
return [
'status' => 'error',
'message' => 'Customer is not billable',
];
}
$invoice = $this->invoiceSkill->createInvoice(
$customer,
$input['line_items']
);
return [
'status' => 'ok',
'invoice' => $invoice,
];
}
}
// Skills: reusable capabilities (services / repositories)
class CustomerSkill
{
public function __construct(private CustomerRepository $repo) {}
public function fetchCustomer(string $id): Customer
{
return $this->repo->findById($id);
}
}
class InvoiceSkill
{
public function __construct(private InvoiceService $service) {}
public function createInvoice(Customer $customer, array $lineItems): Invoice
{
return $this->service->generate($customer, $lineItems);
}
}
In Claude terms: the command is the exposed tool, the agent is the orchestrator deciding which skills to call and in what order, and the skills are the concrete capabilities that can be reused across many agents and commands.
The Insight
Claude code tools map cleanly onto an MVC-style mental model: commands are routes, agents are controllers, and skills are your services and repositories—and designing them that way keeps your AI integrations from turning into a ball of mud.
🧠 Bonus: A single skill can be reused across many agents, just like a shared service or repository layer, which makes it easier to keep side effects and integrations consistent.
Gotchas
- Putting logic in commands instead of skills turns them into fat controllers—harder to test, harder to reuse, and tightly coupled to one agent.
- Letting every agent talk directly to external APIs without shared skills leads to duplicated integrations and slightly different behaviors that are painful to debug.
- Designing one giant “god” agent that owns every command and skill feels convenient at first but quickly becomes a routing bottleneck and a single point of configuration pain.
- Treating skills as thin wrappers around HTTP calls without clear contracts makes it hard for agents to give strong guarantees about validation and error handling.
Takeaways
- Model commands as thin, stable entry points—like routes—that describe what can be done, not how it is implemented.
- Treat agents as controllers that own orchestration, validation, and response shaping for a coherent slice of behavior.
- Push business logic and integrations into skills so they can be reused across agents and evolved independently.
- Design skills with clear contracts and side-effect boundaries, the same way you would design services or repositories.
- When something feels messy, ask: is this command too fat, is this agent doing too much, or is this logic missing a dedicated skill?
🔥 One more thing: You can version skills the way you version APIs—introduce
v2skills behind the same agent and gradually migrate commands to them without changing the calling code.
References
- Anthropic Claude Tool Use (Code Tools) Overview (documentation)
- Designing APIs: From MVC to Clean Architecture (article)
- Hexagonal Architecture: Three Principles and an Implementation Example (article)