Tuesday, 11 August 2026

MCP Server

 

What is an MCP server and why is it different from wiring a tool directly?

When wiring a tool directly into an application, you are responsible for defining the tool’s schema and its functionality. Both live in that application’s code. If three different applications need access to the same external service, each one maintains its own integration. Model Context Protocol, or MCP, separates tool definitions from individual applications and turns them into a process called a server.

An MCP server is a process that exposes tools, resources, and prompts that MCP clients can use. 

The practical difference: a tool is the model reaching out to get something when it decides it needs it. A resource is the client pulling known data in before the model even starts. A prompt is packaging expert-level instructions so every developer on your team gets the same quality output without having to know how to write that instruction themselves.

Three things an MCP server exposes — you already know tools, here are the other two.

Resources — read-only data fetched directly into context

A resource is data the server exposes at an address. The client fetches it and places it into context directly — no tool call, no model decision involved. Think of it as a GET request to a known endpoint.

Two forms:

  • Direct resource — fixed address, no parameters. The data is always at that address.
  • Templated resource — address contains a parameter, resolved at fetch time.

When to use it over a tool: When you know upfront what data you need in context before the turn starts. A tool call is dynamic — the model decides when and whether to call it. A resource is deterministic — the client fetches it directly. Cheaper and more predictable when the data is known.

Practical examples:

# Direct resource — fixed address
mcp://java-tools/conventions/spring-boot-standards
→ Returns your team's Spring Boot coding standards
→ Client fetches this at session start, always in context

# Templated resource — parameterized address  
mcp://java-tools/schema/{tableName}
→ mcp://java-tools/schema/orders
→ Returns the DDL for the orders table
→ Client fetches by passing the table name as parameter

Other real-world examples:

  • mcp://jira/project/OMS/open-tickets — current sprint tickets loaded into context
  • mcp://confluence/runbooks/{serviceName} — runbook for a specific service
  • mcp://github/repo/accenture-oms/pull-requests/open — open PRs fetched directly

Prompts — pre-written instruction templates invoked by name

A prompt is a vetted instruction template the server exposes so any client can invoke it by name instead of writing their own version. The server maintains it in one place, every connected client gets the same quality.

When a prompt is worth it over just asking Claude: When specific wording materially changes the output quality. For routine questions the user's own words are fine. For specialized tasks — a security review, a database migration audit, an API contract check — a carefully constructed prompt produces consistently better results than whatever each developer would type on the fly.

Practical examples for your Spring Boot context:

# Invoked by name in the client
/prompt: review-db-migration

# What the server actually sends to the model
"Review this Flyway migration file against these criteria:
1. Does it have a matching rollback script in db/rollbacks/?
2. Are all foreign key columns indexed?
3. Does it use CREATE OR REPLACE anywhere (not allowed)?
4. Are all VARCHAR columns explicitly sized?
5. Is the version number in the correct format V{date}_{seq}?
Return findings as: PASS / WARN / FAIL with specific line references."
# Another example
/prompt: api-contract-review

# Server sends
"Review this REST controller against our API standards:
1. Are all endpoints using method-specific annotations (@GetMapping etc)?
2. Are all response bodies Java records, never raw entities?
3. Is business logic absent from the controller?
4. Are all error responses using ProblemDetail (RFC 9457)?
5. Does every endpoint have @Operation and @ApiResponse annotations?
Return a checklist with PASS/FAIL per item."

How all three fit together

MechanismWhat it isWhen to reach for it
ToolAction the model calls dynamicallyWhen the model needs to decide whether and when to fetch or act
ResourceRead-only data fetched directly into contextWhen you know upfront what data you need, before the turn starts
PromptPre-written instruction template invoked by nameWhen specific wording produces materially better results than freeform input


Three transports, each for a different deployment topology.

The MCP transport (stdio, HTTP, SSE) just carries JSON-RPC envelopes


stdio — server runs as a child process on the same machine

The client launches the server as a subprocess and communicates via stdin/stdout. One line in, one line out — JSON-RPC over pipes. This is exactly what your ClaudeMCPServer.java uses. No network, no port, no authentication needed. The process lives and dies with the client session.

Use it when: the server runs locally alongside Claude Code on the same machine.


HTTP — server runs as a standalone process, local or remote

The client sends HTTP POST requests to a fixed URL. The server is a normal HTTP service — could be on localhost, could be on a remote host. Stateless request/response, same as any REST API. Each request is independent.

Use it when: the server needs to be shared across multiple clients, runs on a remote machine, or needs to persist beyond a single session.


SSE (Server-Sent Events) — HTTP with a persistent stream back

An extension of HTTP transport. The client POSTs requests normally, but the server holds the connection open and streams responses back as events rather than closing after each response. Useful when the server needs to push notifications or stream partial results.

Use it when: the server needs to send unsolicited updates to the client, or results arrive incrementally.


The practical decision

Server on same machine, local dev    →  stdio
Server shared across team / remote   →  HTTP
Server needs to push events          →  SSE

Your ClaudeMCPServer.java is stdio — that's why System.out is the protocol wire and System.err is the only safe place to log. Switching to HTTP would mean replacing stdin/stdout with an HTTP listener, but the JSON-RPC message structure stays identical.



Plugins

 Plugins — one-step installation of your entire setup

A plugin bundles your skills, hooks, subagents, and MCP servers into a single versioned installable unit. Instead of a teammate manually copying your .claude/ directory, configuring hooks, and wiring MCP servers — they run one install command and get the identical setup.

Structure of what a plugin contains:

plugin/
  skills/          ← your .md skill files
  hooks/           ← PreToolUse, PostToolUse scripts
  agents/          ← custom subagent definitions
  settings.json    ← MCP servers, permissions
  manifest.json    ← describes the bundle

Marketplace — where plugins are distributed

  • Anthropic's official marketplace is available automatically in Claude Code
  • Add third-party marketplaces (hosted on GitHub) with /plugin marketplace add <owner/repo>
  • Enterprise admins can deploy plugins org-wide via managed settings — sits above user and project settings, cannot be overridden

The decision table — which layer to reach for

LayerReach for it when
SkillA procedure should stay out of context until the task calls for it
Custom commandThe procedure has a clear name and you want explicit invocation
PluginA working setup on your machine needs to be shared, versioned, and kept consistent across a team

The one risk worth remembering

A deny rule or hook you rely on locally is not included in a plugin unless explicitly listed in the bundle. If your guardrails aren't part of the plugin manifest, your teammates install the skills without the safety net you built around them. Always audit what the plugin actually bundles before distributing it.

Installation success and Execution success are two different things.

What went wrong

A developer built a deployment skill, tested it locally, packaged it as a plugin, pushed it to the internal marketplace. Every teammate's install succeeded. Every teammate's execution failed.

Two root causes in the same SKILL.md:

bash
# Failure 1 — absolute path to author's home directory
/Users/joseph/projects/deploy-utils/validate.sh

# Failure 2 — environment variable set only in author's shell profile
$DEPLOY_TOKEN

The first failure is visible — a reviewer reading the file can catch it immediately. The second is invisible — nothing in the package announces the dependency, the skill runs fine until the exact step that needs $DEPLOY_TOKEN, and only then fails silently. That's why it cost three people two hours to debug.


The fix — four rules

1. No absolute paths, ever.
Use $CLAUDE_PROJECT_DIR for scripts stored in the project, ${CLAUDE_PLUGIN_ROOT} for scripts bundled inside the plugin itself.

bash
# Wrong
/Users/joseph/projects/deploy-utils/validate.sh

# Right
${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh

2. Bundle everything the plugin needs.
Scripts, config files, assets — either inside the plugin or in a shared project location every teammate gets on install. Nothing that only exists on the author's machine.

3. Document and validate every environment variable at install time.
Don't let a missing variable surface mid-run. Surface it immediately when the plugin installs, not two hours into a debugging session.

4. Test on a clean machine before distributing.
Your own machine hides problems. A clean machine reveals them instantly.


The underlying principle

The author's machine is not the team's machine. A plugin that only works on the author's setup isn't a plugin — it's a local script with extra packaging steps. Portability has to be deliberately designed in, because nothing in the installation process will warn you it's missing.

Monday, 10 August 2026

CLAUDE.md/ SKILL.md/ rules .md files - examples

CLAUDE.md for always-on project memory, 

SKILL.md is a reusable instruction set for a specific task type

Rules files for scoped guidance

How each layer behaves at runtime

Developer opens project and starts Claude Code session
  → CLAUDE.md loads immediately — module boundaries, banned libs, git conventions

Developer asks Claude to add a new REST endpoint
  → Claude reads src/order-api/controller/OrderController.java
  → api-layer.md auto-loads (path match on src/**/controller/**/*.java)
  → Claude sees: no business logic in controllers, use records for DTOs
  → Claude recognises task type: scaffolding a REST endpoint
  → create-rest-endpoint.md skill loads on demand
  → Claude follows the skill playbook: controller + service + dto in correct layers

Developer asks Claude to add a new column to the orders table
  → Claude reads src/order-infrastructure/repository/OrderRepository.java
  → persistence.md auto-loads (path match)
  → Claude recognises task type: schema change needed
  → create-flyway-migration.md skill loads on demand
  → Claude follows versioned migration steps, creates rollback script

Developer asks Claude to review a migration SQL file
  → api-scaffolder agent not relevant here
  → migration-reviewer agent dispatched
  → agent frontmatter explicitly lists: persistence rules + create-flyway-migration skill
  → agent starts clean context but has exactly the rules it needs
  → returns focused review result to main session

One-line summary of each layer

LayerFile locationLoads whenContains
CLAUDE.mdproject rootEvery session, alwaysUniversal constraints
Rules.claude/rules/*.mdPath glob matchesPath-specific guardrails
Skills.claude/skills/*.mdTask type recognisedHow-to playbooks
Agents.claude/agents/*.mdExplicitly dispatchedCustom subagent with explicit rules + skills wired in
settings.json.claude/settings.jsonEvery sessionHooks, permissions, MCP

File structure that contains 3 of these file types

your-project/

├── CLAUDE.md                                    ← universal constraints, always loaded every session

├── .claude/
│   ├── settings.json                            ← hooks, permissions, MCP servers
│   │
│   ├── rules/                                   ← auto-loads when file path matches frontmatter glob
│   │   ├── persistence.md                       ← triggers on src/**/infrastructure/**/*.java
│   │   ├── api-layer.md                         ← triggers on src/**/controller/**/*.java
│   │   ├── domain.md                            ← triggers on src/**/domain/**/*.java
│   │   └── kafka.md                             ← triggers on src/**/messaging/**/*.java
│   │
│   ├── skills/                                  ← on-demand playbooks, loaded when task type matches
│   │   ├── create-flyway-migration.md           ← how to create a versioned DB migration
│   │   ├── create-rest-endpoint.md              ← how to scaffold controller + service + dto
│   │   ├── write-integration-test.md            ← how to set up TestContainers + @SpringBootTest
│   │   └── add-kafka-consumer.md                ← how to wire a new consumer with DLT + idempotency
│   │
│   └── agents/                                  ← custom subagents with explicit skill/rule loading
│       ├── migration-reviewer.md                ← reviews Flyway SQL, loads persistence rules + migration skill
│       └── api-scaffolder.md                    ← scaffolds endpoints, loads api-layer rules + rest-endpoint skill

└── src/
    ├── order-service/
    ├── order-domain/
    ├── order-infrastructure/
    ├── order-api/
    └── legacy-billing/                          ← deny rule in settings.json blocks agent writes here

Examples:

CLAUDE.md — root of your project

markdown
# Project: Order Management Service
# Team: Fulfillment Platform, Accenture Delivery Unit

## Build & Test Commands
- Build: `mvn clean install -DskipTests`
- Test: `mvn test -pl order-service`
- Integration test: `mvn verify -P integration-test`
- Single test: `mvn test -Dtest=OrderServiceTest`
- Lint: `mvn checkstyle:check`

## Module Structure
- `order-service/` — core domain, Spring Boot entry point
- `order-domain/` — entities, value objects, domain events
- `order-infrastructure/` — JPA repositories, Kafka producers
- `order-api/` — REST controllers, DTOs, OpenAPI spec
- `legacy-billing/` — DO NOT TOUCH. Owned by billing team.
- `shared-kernel/` — read only. Changes require architect approval.

## Framework Conventions
- Spring Boot 3.2.x — do not suggest 2.x patterns
- Java 17 — use records for DTOs, sealed classes for domain errors
- Spring Data JPA only — never write native Hibernate sessions directly
- Flyway for all schema changes — never modify schema via entity annotations alone
- MapStruct for DTO mapping — never write manual mapping code
- Lombok is banned — use Java records and explicit constructors

## Architecture Rules
- Controllers must never contain business logic
- Service layer owns transactions — annotate with @Transactional explicitly
- Repository interfaces only in order-infrastructure — never inject in controllers
- Domain events published via ApplicationEventPublisher, never direct Kafka calls from service layer
- Never expose JPA entities directly from REST endpoints — always map to DTOs

## Paths Agent Must Never Touch
- `legacy-billing/**`
- `shared-kernel/**`
- `**/application-prod.properties`
- `**/application-staging.properties`
- `.github/workflows/**`
- `db/migrations/**` — propose migration SQL, never write it directly

## Testing Standards
- Every service method needs a unit test in the same PR
- Use Mockito for mocking — never PowerMock
- Integration tests use @SpringBootTest with TestContainers
- Test class naming: `{ClassName}Test` for unit, `{ClassName}IT` for integration

## Git Conventions
- Branch naming: `feature/OMS-{ticket}`, `fix/OMS-{ticket}`
- Never commit directly to main or develop
- Commit message format: `OMS-{ticket}: {imperative verb} {what changed}`

Rules files

.claude/rules/persistence.md

markdown
---
paths:
  - "src/**/infrastructure/**/*.java"
  - "src/**/repository/**/*.java"
  - "db/migrations/**/*.sql"
---

## Persistence Layer Rules

### JPA
- All repositories extend JpaRepository or PagingAndSortingRepository
- Never use EntityManager directly unless query cannot be expressed in JPQL
- Fetch strategy is LAZY by default — justify any EAGER fetch in a comment
- Named queries go in orm.xml, not as annotations on entities
- Projections for read-only queries — never fetch full entity for a SELECT

### Transactions
- @Transactional belongs on the service layer, not repository
- ReadOnly=true on all query-only service methods
- Propagation.REQUIRES_NEW only for audit logging — document why
- Never swallow TransactionSystemException — let it propagate

### Flyway Migrations
- File naming: `V{version}__{description}.sql` e.g. V20240815_001__add_order_status_index.sql
- Never alter an existing migration file after it has run in any environment
- Every migration must have a rollback script in db/rollbacks/
- Index every foreign key column
- All VARCHAR columns need explicit length — no TEXT unless justified

.claude/rules/api-layer.md

markdown
---
paths:
  - "src/**/controller/**/*.java"
  - "src/**/api/**/*.java"
  - "src/**/dto/**/*.java"
---

## API Layer Rules

### Controllers
- Annotate with @RestController and @RequestMapping at class level
- Method-level mappings use @GetMapping/@PostMapping etc — never @RequestMapping
- Controllers inject service interfaces, never concrete implementations
- No business logic — delegate immediately to service layer
- No direct repository injection

### Request/Response
- All request bodies are Java records with @Valid
- All response bodies are Java records — never return raw entities
- Wrap paginated responses in PageResponse<T> from shared-kernel
- Use ResponseEntity<T> only when HTTP status needs to vary dynamically

### Error Handling
- All exceptions handled in GlobalExceptionHandler — never try/catch in controllers
- Return ProblemDetail (RFC 9457) for all error responses
- 400 for validation errors, 404 for not found, 409 for conflicts, 500 for unexpected
- Never expose stack traces or internal messages in response body

### OpenAPI
- Every endpoint needs @Operation and @ApiResponse annotations
- Document all possible error response codes
- Mark deprecated endpoints with @Deprecated and migration note

.claude/rules/domain.md

markdown
---
paths:
  - "src/**/domain/**/*.java"
---

## Domain Layer Rules

### Entities
- Entities are aggregate roots — enforce invariants in constructors
- No public setters — use named domain methods (e.g. confirmOrder(), cancelOrder())
- IDs are value objects — never raw Long or UUID directly on public API
- Domain events extend AbstractDomainEvent from shared-kernel

### Business Rules
- All money values use MonetaryAmount from javax.money — never BigDecimal raw
- All date/time values use Instant for storage, ZonedDateTime for display
- Status transitions validated via StatusTransitionValidator — never raw if/else chains
- Null checks via Objects.requireNonNull with descriptive message

### What Belongs Here
- Business logic and domain rules
- Domain event definitions
- Value objects and enumerations
- Domain service interfaces (implementations in infrastructure)

### What Does Not Belong Here
- No Spring annotations except @DomainService (internal)
- No JPA annotations on pure domain objects
- No DTOs — those live in order-api

.claude/rules/kafka.md

markdown
---
paths:
  - "src/**/messaging/**/*.java"
  - "src/**/event/**/*.java"
  - "src/**/producer/**/*.java"
  - "src/**/consumer/**/*.java"
---

## Messaging Rules

### Producers
- Never call KafkaTemplate directly from service layer
- All publishing goes through DomainEventPublisher interface
- Events serialized as Avro — never plain JSON for inter-service events
- Include correlationId and causationId on every event header
- Outbox pattern required for events that must survive a service crash

### Consumers
- All consumers annotated with @KafkaListener at method level
- Idempotency check before processing — log and skip duplicate messageId
- Dead letter topic configured for every consumer group
- Never throw unchecked exceptions from consumer — catch, log, send to DLT
- Consumer offset committed only after successful processing

### Naming Conventions
- Topic: `{domain}.{aggregate}.{event}` e.g. `fulfillment.order.confirmed`
- Consumer group: `{service-name}-{topic-short-name}` e.g. `order-service-order-confirmed`
- Dead letter: `{topic-name}.DLT` e.g. `fulfillment.order.confirmed.DLT`


SKILL files  : A skill is a portable Markdown file (SKILL.md file) placed in .claude/skills. The front matter identifies the skill and describes when it applies, and the body holds the steps.

.claude/skills/create-flyway-migration.md

markdown
# Skill: Create Flyway Migration

## When to use this skill
When adding a new table, column, index, or constraint to the database schema.

## Steps
1. Determine the next version number by checking db/migrations/ for the highest V number
2. Create file: `db/migrations/V{version}__{description}.sql`
3. Write forward migration SQL
4. Create matching rollback: `db/rollbacks/V{version}__{description}_rollback.sql`
5. Update the entity class if column added
6. Add @Column annotation with matching name and length
7. Run `mvn flyway:validate` to confirm migration is valid

## Naming Rules
- Version format: `V20240815_001` — date + sequence
- Description: lowercase, underscores, imperative verb first
- Example: `V20240815_001__add_shipment_tracking_column.sql`

## Never
- Never modify an existing migration file
- Never use CREATE OR REPLACE — always new versioned file
- Never skip the rollback script

How skills are loaded — this is the key difference

Rules files load automatically when a file path matches. Skills load on demand — either:

  • You reference them explicitly in CLAUDE.md: See skill: create-flyway-migration
  • A subagent lists them in its frontmatter
  • Claude Code determines the skill is relevant to the current task

So the mental model is:

  • Rules = always-on guardrails scoped to a path
  • Skills = reusable playbooks loaded when a specific task type is needed

CLAUDE.md/ rules/ Hooks/ Subagents

 CLAUDE.md = global, always-loaded constraints

Things that apply regardless of what file Claude is touching. "Never modify the database schema" is the right example — it doesn't matter whether Claude is editing a service class, a controller, or a utility — that rule is always in force. It belongs in CLAUDE.md because it needs to be present in every session from the first prompt.


.claude/rules/*.md = scoped, path-specific guidance

Rules files only load when Claude is working on files that match the paths frontmatter. The SQL transaction example:

yaml
---
paths:
  - "src/db/**/*.sql"
---

A practical split for a typical enterprise Java project might look like:

  • CLAUDE.md — never touch legacy module, always write tests, use the internal artifact registry
  • .claude/rules/persistence.md scoped to src/db/** — transaction boundaries, naming conventions for stored procs
  • .claude/rules/api.md scoped to src/main/java/**/controller/** — REST conventions, response wrapper standards, no business logic in controllers

Each rules file stays small and precise. The context window only carries what's relevant to the current work.

Hooks: running your own scripts at fixed points in the lifecycle

Hooks are deterministic triggers that fire at fixed points in the agent lifecycle, independent of what the model decides to do.

Even bypassPermissions mode — which removes every other safety net — does not suppress hooks. Your PreToolUse hook still fires. This means hooks are the one enforcement mechanism that sits above the permission mode system, making them the right place for constraints you genuinely cannot afford to miss.

The /hooks command

Rather than hand-editing the JSON, you can run /hooks inside a Claude Code session and it gives you an interactive UI to configure them, which then writes the JSON for you. That's the intended workflow — configure via /hooks, inspect the result in settings.json.

Example:

check-prod-guard.js — blocks writes to prod config:

const input = JSON.parse(process.argv[2]);
const filePath = input.path || '';

if (filePath.includes('application-prod.properties')) {
  process.stderr.write('BLOCKED: Production config cannot be modified by agent. Edit manually.');
  process.exit(2); // exit code 2 = block the tool call
}

process.exit(0); // exit code 0 = allow it through

compile-check.sh — runs after any Java edit:

#!/bin/bash
EDITED_FILE="$1"

# Only react to Java file edits
if [[ "$EDITED_FILE" == *.java ]]; then
  echo "Running compile check after edit to $EDITED_FILE..."
  mvn compile -q 2>&1 | tail -20
fi

Your settings.json with both hooks wired in

{
  "syntaxHighlightingDisabled": true,
  "autoUpdatesChannel": "latest",
  "theme": "light",
  "permissions": {
    "allow": ["Read", "Glob", "Grep"]
  },
  "enabledMcpjsonServers": ["java-tools"],
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node C:/Users/ravi.kumar.garlapati/.claude/hooks/check-prod-guard.js '$CLAUDE_TOOL_INPUT'"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "bash C:/Users/ravi.kumar.garlapati/.claude/hooks/compile-check.sh '$CLAUDE_TOOL_INPUT_PATH'"
          }
        ]
      }
    ]
  }
}

What happens at runtime

Scenario: Claude decides to edit src/main/resources/application-prod.properties

Claude attempts: Edit(application-prod.properties)
  → PreToolUse fires
  → check-prod-guard.js runs
  → detects "application-prod.properties" in path
  → exits with code 2
  → stderr: "BLOCKED: Production config cannot be modified by agent"
  → tool call never executes
  → Claude sees the reason and tries a different approach

Scenario: Claude edits src/main/java/com/accenture/OrderService.java

Claude attempts: Edit(OrderService.java)
  → PreToolUse fires → path is safe → exits 0 → edit proceeds
  → Edit executes, file is modified
  → PostToolUse fires
  → compile-check.sh runs mvn compile
  → if compile fails, output is shown to Claude
  → Claude sees the error and fixes it in the next turn

The key difference from a CLAUDE.md instruction you can see clearly here: even if Claude somehow decided to edit application-prod.properties despite a CLAUDE.md rule saying not to, the PreToolUse hook intercepts it at the tool call level before execution. The model's decision never reaches the filesystem.

Subagents: delegating work to an isolated context

Who triggers a subagent?

The main Claude session triggers it automatically based on the complexity and nature of your prompt. You don't explicitly say "spawn a subagent now." Claude Code decides internally whether to delegate a task to a subagent or handle it inline in the main session.

For example if you say:

"Explore the legacy payment module and tell me what classes depend on PaymentGateway"

Claude Code may decide that's an exploration task, spawn the Explore subagent to do the codebase investigation, get the result back, and present it to you — all transparently. You just see the answer.

You can also explicitly invoke one by naming it in your prompt:

"Use the general-purpose subagent to refactor OrderService following our project conventions"


Do you configure it in settings.json or a .md file?

Neither, for built-in subagents — they require no configuration at all. They're part of Claude Code itself.

For custom subagents you create them as .md files inside .claude/agents/ in your project:

your-project/
  .claude/
    agents/
      java-refactor.md      ← your custom subagent
      api-reviewer.md       ← another custom subagent
    settings.json
  CLAUDE.md

Each file has a frontmatter header that defines what it loads:

markdown
---
name: java-refactor
description: Refactors Java classes following Accenture project conventions
skills:
  - java-conventions
  - spring-boot-patterns
paths:
  - "src/main/java/**/*.java"
---

You are a Java refactoring specialist.
Always use Spring Data JPA, never native Hibernate queries.
Always wrap service methods with @Transactional.
Never modify files outside src/main/java.

When does it trigger?

Trigger typeHow
AutomaticClaude Code decides based on task type — exploration, investigation, parallelizable work
Explicit promptYou name the subagent or task type in your message
Custom subagentClaude matches your request to the description field in the agent's frontmatter and picks the right one

The description field in your custom subagent's frontmatter is essentially how Claude Code decides which agent to route a task to — so write it precisely, the same way you'd write a method's Javadoc to describe exactly what it does and when to call it.