Apply Clean MCP Architecture
Architectural standard for TypeScript MCP servers built with mcp-use/server. Decides where files live, what each layer may import, what bootstrap wires, and how the discipline is enforced. Mechanical recipes (exact APIs, auth, transports, widgets) live in build-mcp-use-server.
When to use this skill
Trigger on phrases or contexts like:
- "where should this tool / gateway / presenter / port live?"
- "refactor this monolithic
src/tools/*.ts into clean layers"
- "audit this MCP server's architecture" / "PR review for layering"
- "set up
dependency-cruiser for an mcp-use server"
- "why is
mcp-use imported from domain/ or application/?"
- "scaffold a greenfield
mcp-use/server repo with proper folders"
- "
process.env is being read all over the codebase — fix the seam"
- "design ports, adapters, and provider error classification"
Do NOT use this skill when:
- The question is a raw
@modelcontextprotocol/sdk server mechanic — use build-mcp-server-sdk-v1, build-mcp-server-sdk-v2, or convert-mcp-sdk-v1-to-v2.
- The question is an
mcp-use/server API recipe (tool helpers, auth, sessions, transports, widgets, CSP, Inspector, deploy) — use build-mcp-use-server.
- The work is on a client app or
MCPAgent — use build-mcp-use-client or build-mcp-use-agent.
- The concern is general agentic usability, token cost, tool-description quality, or runtime UX rather than folder layout or layer boundaries — that is out of scope for this structural skill.
If the task is structural placement inside an mcp-use/server repo, this skill owns it. If it is a mechanical recipe outside of placement, route out.
Pinned Defaults
| Decision |
Default |
| Stack |
TypeScript mcp-use/server |
| Composition root |
src/infrastructure/server/bootstrap.ts (or equivalent entry wrapper) |
| Config seam |
src/infrastructure/config/runtime-config.ts |
| Env validation |
Zod, in the config seam only |
| Tool input validation |
Zod at the handler boundary |
| Use-case validation |
None; use cases trust validated commands |
| Boundary gate |
dependency-cruiser plus TypeScript/lint |
| Logger sink |
JSON to stderr, never stdout |
| Response seam |
ToolResponse in domain, McpPresenter in presenters |
Mode Selection
Pick exactly one mode before editing. If evidence contradicts the picked mode, name the contradiction once and continue with the mode that matches the codebase.
| Mode |
Trigger |
First action |
| Greenfield |
No src/ yet, or only a package stub exists |
Read references/greenfield-walkthrough.md. |
| Refactor |
Existing server has monolithic tools, missing application layer, scattered env reads, or protocol imports in business logic |
Read references/refactor-playbook.md. |
| Review |
Existing repo or PR needs a structural grade |
Read references/audit-checklist.md; report P0/P1/P2 findings. |
| Implementing |
Clean layered repo needs a tool, resource, prompt, or boundary component |
Read references/define-tool-pattern.md and references/handler-context.md. |
| Ask |
Advice only, no edits |
Answer with the mode and route to the relevant references below. |
Guardrails
These rules are absolute. When a hard external constraint blocks one, report the constraint and the smallest compensating boundary — do not silently weaken the rule.
- Inner layers never import outer layers.
domain/ imports nothing outside itself. application/ imports only domain/ and shared/.
- No
mcp-use or SDK types in domain/ or application/. SDK shape churn must not ripple into business logic.
- One composition root. It constructs concrete gateways, instantiates
MCPServer, registers tools/resources/prompts, and starts the server.
- One config seam.
runtime-config.ts is the only file that reads process.env; env validates with Zod there.
mcp-use imports stay at the protocol edge. Allowed: handlers/, resources/, prompts/, presenters/ (response helpers), and infrastructure/.
- Zod at boundaries. Handler input schemas live at the handler boundary; root objects strict; no
z.any()/z.unknown() at tool boundaries; use cases and domain do not revalidate.
- Forbidden TypeScript stays forbidden. No bare
any, as any, @ts-ignore, or unjustified @ts-expect-error.
- Type-only imports use
import type. verbatimModuleSyntax: true required.
- Locked TS flags.
strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noImplicitReturns, noFallthroughCasesInSwitch, verbatimModuleSyntax, NodeNext/Node16 module settings.
- No stdout logging.
console.* forbidden under src/; stdout is the JSON-RPC wire under stdio.
- Provider errors classify at gateways. Raw provider errors become
DomainError subclasses before crossing a port.
- Domain events and next steps emit after durable commit. Never dispatch before the side effect succeeds.
- Entity privacy is runtime-enforced. Prefer
# private fields; private is acceptable only with equivalent lint and as any blocks.
- One tool per file. Monolithic tool files are refactor targets.
- No application barrels. Direct imports only;
index.ts barrels inside src/ cause cycles and cold-start regressions.
Canonical Layout
src/
├── domain/ # pure entities, ports, errors, ToolResponse
├── application/ # use cases and pure transforms
├── handlers/ # tool schemas, defineTool(), handler factories
├── gateways/ # outbound adapter implementations and decorators
├── presenters/ # ToolResponse -> MCP CallToolResult
├── infrastructure/ # config, middleware, errors, auth, observability, bootstrap
├── resources/ # MCP resources, including server-side widget resources
├── prompts/ # MCP prompts
└── shared/ # structural types and cross-cutting helpers
Full naming rules, rationale, and per-folder AGENTS.md guidance: references/folder-layout.md.
Import Matrix
| Layer |
May import from |
Must not import |
domain/ |
same layer only |
mcp-use, SDK, Zod, I/O, any outer layer |
application/ |
domain/, shared/ |
protocol APIs, concrete gateways, handlers, presenters, infrastructure, env |
handlers/ |
domain/, application/, presenter port, Zod, protocol-edge types |
concrete gateways, config reads, direct provider calls |
gateways/ |
domain ports/errors, shared types, provider SDKs |
application, handlers, presenters, mcp-use |
presenters/ |
domain response objects, response helpers, shared types |
application, gateways, handlers |
infrastructure/ |
all layers |
reverse imports from inner layers |
resources/, prompts/ |
domain, application, protocol-edge types |
direct gateway construction, env |
shared/ |
domain types only |
side effects, business logic, framework imports |
Enforce as a CI-blocking gate; copy-paste config in references/dependency-rules.md.
MCP Primitive Placement
| Primitive |
Structural home (this skill) |
Mechanical owner (route out) |
| Tool handler |
handlers/<feature>/<tool>.handler.ts |
build-mcp-use-server |
| Tool input schema |
Inline in handler; shared fragments in handlers/schemas/ |
build-mcp-use-server |
| Resource |
resources/<resource>.ts or resources/<widget-name>/ |
build-mcp-use-server |
| Prompt |
prompts/registry.ts or prompts/<prompt>.ts |
build-mcp-use-server |
| Response shaping |
presenters/mcp-presenter.ts |
build-mcp-use-server |
MCPServer construction |
composition root only |
build-mcp-use-server |
| Auth/session/transport wiring |
infrastructure/ plus composition root |
build-mcp-use-server |
ctx.elicit(), ctx.sample(), capability checks |
handlers only |
build-mcp-use-server |
Blended decisions split via references/coordinate-with-build-mcp-use-server.md.
Request Flow
MCP client
-> mcp-use server registered in bootstrap
-> handler parses schema and resolves request context
-> use case receives validated command and ports
-> gateway wraps external systems and classifies provider errors
-> use case returns ToolResponse or throws DomainError
-> presenter renders MCP response and sanitises output
-> mcp-use response returns to client
The handler is thin: parse, derive command, delegate, render. The use case is framework-free. The gateway hides providers. The presenter shapes data and redacts; it does not make business decisions.
Audit Smells (sweep first)
Detect these before deep reading:
mcp-use imported from domain/, application/, gateways/, or shared/.
process.env outside infrastructure/config/runtime-config.ts.
server.tool( outside the composition root.
- handler files over ~250 lines or monolithic
src/tools/*.ts.
new *Gateway(...) outside bootstrap.
z.any() / z.unknown() in handler schemas.
console.* under src/.
index.ts barrels under application code.
After the sweep, look up concrete examples and fix paths in references/anti-patterns.md.
Validation
Minimum gates for structural work:
python3 scripts/validate-skills.py when editing this skills pack.
- Project typecheck and lint for target MCP repos.
dependency-cruiser import-boundary gate.
- Focused unit tests for changed handlers/use cases/gateways/presenters.
- End-to-end MCP call only when wiring, bootstrap, transport, auth/session, or response surfaces changed.
Bundled read-only audit helpers (run from the target MCP project root, or pass the project root as the first argument):
| Need |
Script |
Doc |
| Grep likely layer-import, env, console, and barrel violations |
scripts/audit-layer-imports.sh |
scripts/audit-layer-imports.md |
| Check canonical folders and expected seams |
scripts/check-folder-layout.sh |
scripts/check-folder-layout.md |
| Check likely Zod boundary violations |
scripts/check-zod-boundary.sh |
scripts/check-zod-boundary.md |
Claim only the verification rung actually reached.
Completion Output
Finish apply/review/refactor work with:
- selected mode
- changed layers and files
- guardrails checked
- scripts/tests run
- validation rung reached
- unresolved constraints or accepted deviations
For Review mode, lead with findings ordered by severity and include replayable evidence.
Reference Routing
| Read when |
Reference |
Decision it answers |
Need full tree, naming rules, folder rationale, or per-folder AGENTS.md guidance |
references/folder-layout.md |
Which folder owns a file and why it exists. |
Need copy-paste import rules or dependency-cruiser config |
references/dependency-rules.md |
Which imports are legal and how CI enforces them. |
| Need the single-root construction order or bootstrap skeleton |
references/composition-root.md |
What constructs where and in what order. |
| Designing or auditing ports, gateways, decorators, or provider error classification |
references/gateways-and-ports.md |
How external systems cross into the application. |
| Building response objects, presenters, sanitisation, or preview policy |
references/presenter-and-tool-response.md |
How domain responses become MCP envelopes. |
| Adding request identity, session id, request id, or cost tracking |
references/request-context.md |
What belongs in AsyncLocalStorage and how it is bound. |
Designing DomainError, JSON-RPC mapping, or recovery hints |
references/error-contracts.md |
How failures move from domain/gateway to MCP response. |
| Adding or auditing a tool handler factory |
references/define-tool-pattern.md |
What defineTool() returns and how handlers stay thin. |
| Designing handler dependency injection or capability-gated edge behavior |
references/handler-context.md |
What belongs in HandlerContext versus per-request MCP context. |
Splitting structural and mechanical ownership with build-mcp-use-server |
references/coordinate-with-build-mcp-use-server.md |
Which skill owns a blended decision. |
Checking TypeScript compiler flags, import type, branded IDs, or structural SDK mirrors |
references/typescript-quality-bar.md |
What the TypeScript gate requires. |
| Placing Zod schemas or auditing validation boundaries |
references/zod-at-boundary.md |
Where schemas live and where field mechanics route out. |
Narrowing unknown, generic port signatures, discriminated unions, or satisfies records |
references/narrowing-and-generics.md |
How types stay precise without any. |
| Applying Clean Code rules that materially affect MCP behavior |
references/clean-code-rules-in-mcp-context.md |
Which hygiene rules matter and why. |
Starting a new mcp-use/server repo from scratch |
references/greenfield-walkthrough.md |
Step-by-step scaffold and gates. |
| Repairing an existing drifted repo |
references/refactor-playbook.md |
The staged PR sequence and rollback path. |
| Reviewing an existing repo or PR |
references/audit-checklist.md |
P0/P1/P2 audit rubric and report shape. |
| Looking up concrete drift examples and fix paths |
references/anti-patterns.md |
How common violations appear and how to detect them. |
1---2name: build-clean-mcp-architecture3description: Use if structuring or auditing TypeScript mcp-use/server code for Clean Architecture boundaries.4---56# Apply Clean MCP Architecture78Architectural standard for **TypeScript MCP servers built with `mcp-use/server`**. Decides where files live, what each layer may import, what bootstrap wires, and how the discipline is enforced. Mechanical recipes (exact APIs, auth, transports, widgets) live in `build-mcp-use-server`.910## When to use this skill1112Trigger on phrases or contexts like:1314- *"where should this tool / gateway / presenter / port live?"*15- *"refactor this monolithic `src/tools/*.ts` into clean layers"*16- *"audit this MCP server's architecture"* / *"PR review for layering"*17- *"set up `dependency-cruiser` for an mcp-use server"*18- *"why is `mcp-use` imported from `domain/` or `application/`?"*19- *"scaffold a greenfield `mcp-use/server` repo with proper folders"*20- *"`process.env` is being read all over the codebase — fix the seam"*21- *"design ports, adapters, and provider error classification"*2223Do **NOT** use this skill when:2425- The question is a raw `@modelcontextprotocol/sdk` server mechanic — use `build-mcp-server-sdk-v1`, `build-mcp-server-sdk-v2`, or `convert-mcp-sdk-v1-to-v2`.26- The question is an `mcp-use/server` API recipe (tool helpers, auth, sessions, transports, widgets, CSP, Inspector, deploy) — use `build-mcp-use-server`.27- The work is on a client app or `MCPAgent` — use `build-mcp-use-client` or `build-mcp-use-agent`.28- The concern is general agentic usability, token cost, tool-description quality, or runtime UX rather than folder layout or layer boundaries — that is out of scope for this structural skill.2930If the task is structural placement *inside* an `mcp-use/server` repo, this skill owns it. If it is a mechanical recipe *outside* of placement, route out.3132## Pinned Defaults3334| Decision | Default |35|---|---|36| Stack | TypeScript `mcp-use/server` |37| Composition root | `src/infrastructure/server/bootstrap.ts` (or equivalent entry wrapper) |38| Config seam | `src/infrastructure/config/runtime-config.ts` |39| Env validation | Zod, in the config seam only |40| Tool input validation | Zod at the handler boundary |41| Use-case validation | None; use cases trust validated commands |42| Boundary gate | `dependency-cruiser` plus TypeScript/lint |43| Logger sink | JSON to stderr, never stdout |44| Response seam | `ToolResponse` in domain, `McpPresenter` in presenters |4546## Mode Selection4748Pick exactly one mode before editing. If evidence contradicts the picked mode, name the contradiction once and continue with the mode that matches the codebase.4950| Mode | Trigger | First action |51|---|---|---|52| **Greenfield** | No `src/` yet, or only a package stub exists | Read `references/greenfield-walkthrough.md`. |53| **Refactor** | Existing server has monolithic tools, missing application layer, scattered env reads, or protocol imports in business logic | Read `references/refactor-playbook.md`. |54| **Review** | Existing repo or PR needs a structural grade | Read `references/audit-checklist.md`; report P0/P1/P2 findings. |55| **Implementing** | Clean layered repo needs a tool, resource, prompt, or boundary component | Read `references/define-tool-pattern.md` and `references/handler-context.md`. |56| **Ask** | Advice only, no edits | Answer with the mode and route to the relevant references below. |5758## Guardrails5960These rules are absolute. When a hard external constraint blocks one, report the constraint and the smallest compensating boundary — do not silently weaken the rule.61621. **Inner layers never import outer layers.** `domain/` imports nothing outside itself. `application/` imports only `domain/` and `shared/`.632. **No `mcp-use` or SDK types in `domain/` or `application/`.** SDK shape churn must not ripple into business logic.643. **One composition root.** It constructs concrete gateways, instantiates `MCPServer`, registers tools/resources/prompts, and starts the server.654. **One config seam.** `runtime-config.ts` is the only file that reads `process.env`; env validates with Zod there.665. **`mcp-use` imports stay at the protocol edge.** Allowed: `handlers/`, `resources/`, `prompts/`, `presenters/` (response helpers), and `infrastructure/`.676. **Zod at boundaries.** Handler input schemas live at the handler boundary; root objects strict; no `z.any()`/`z.unknown()` at tool boundaries; use cases and domain do not revalidate.687. **Forbidden TypeScript stays forbidden.** No bare `any`, `as any`, `@ts-ignore`, or unjustified `@ts-expect-error`.698. **Type-only imports use `import type`.** `verbatimModuleSyntax: true` required.709. **Locked TS flags.** `strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitOverride`, `noImplicitReturns`, `noFallthroughCasesInSwitch`, `verbatimModuleSyntax`, NodeNext/Node16 module settings.7110. **No stdout logging.** `console.*` forbidden under `src/`; stdout is the JSON-RPC wire under stdio.7211. **Provider errors classify at gateways.** Raw provider errors become `DomainError` subclasses before crossing a port.7312. **Domain events and next steps emit after durable commit.** Never dispatch before the side effect succeeds.7413. **Entity privacy is runtime-enforced.** Prefer `#` private fields; `private` is acceptable only with equivalent lint and `as any` blocks.7514. **One tool per file.** Monolithic tool files are refactor targets.7615. **No application barrels.** Direct imports only; `index.ts` barrels inside `src/` cause cycles and cold-start regressions.7778## Canonical Layout7980```text81src/82├── domain/ # pure entities, ports, errors, ToolResponse83├── application/ # use cases and pure transforms84├── handlers/ # tool schemas, defineTool(), handler factories85├── gateways/ # outbound adapter implementations and decorators86├── presenters/ # ToolResponse -> MCP CallToolResult87├── infrastructure/ # config, middleware, errors, auth, observability, bootstrap88├── resources/ # MCP resources, including server-side widget resources89├── prompts/ # MCP prompts90└── shared/ # structural types and cross-cutting helpers91```9293Full naming rules, rationale, and per-folder `AGENTS.md` guidance: `references/folder-layout.md`.9495## Import Matrix9697| Layer | May import from | Must not import |98|---|---|---|99| `domain/` | same layer only | `mcp-use`, SDK, Zod, I/O, any outer layer |100| `application/` | `domain/`, `shared/` | protocol APIs, concrete gateways, handlers, presenters, infrastructure, env |101| `handlers/` | `domain/`, `application/`, presenter port, Zod, protocol-edge types | concrete gateways, config reads, direct provider calls |102| `gateways/` | domain ports/errors, shared types, provider SDKs | application, handlers, presenters, `mcp-use` |103| `presenters/` | domain response objects, response helpers, shared types | application, gateways, handlers |104| `infrastructure/` | all layers | reverse imports from inner layers |105| `resources/`, `prompts/` | domain, application, protocol-edge types | direct gateway construction, env |106| `shared/` | domain types only | side effects, business logic, framework imports |107108Enforce as a CI-blocking gate; copy-paste config in `references/dependency-rules.md`.109110## MCP Primitive Placement111112| Primitive | Structural home (this skill) | Mechanical owner (route out) |113|---|---|---|114| Tool handler | `handlers/<feature>/<tool>.handler.ts` | `build-mcp-use-server` |115| Tool input schema | Inline in handler; shared fragments in `handlers/schemas/` | `build-mcp-use-server` |116| Resource | `resources/<resource>.ts` or `resources/<widget-name>/` | `build-mcp-use-server` |117| Prompt | `prompts/registry.ts` or `prompts/<prompt>.ts` | `build-mcp-use-server` |118| Response shaping | `presenters/mcp-presenter.ts` | `build-mcp-use-server` |119| `MCPServer` construction | composition root only | `build-mcp-use-server` |120| Auth/session/transport wiring | `infrastructure/` plus composition root | `build-mcp-use-server` |121| `ctx.elicit()`, `ctx.sample()`, capability checks | handlers only | `build-mcp-use-server` |122123Blended decisions split via `references/coordinate-with-build-mcp-use-server.md`.124125## Request Flow126127```text128MCP client129 -> mcp-use server registered in bootstrap130 -> handler parses schema and resolves request context131 -> use case receives validated command and ports132 -> gateway wraps external systems and classifies provider errors133 -> use case returns ToolResponse or throws DomainError134 -> presenter renders MCP response and sanitises output135 -> mcp-use response returns to client136```137138The handler is thin: parse, derive command, delegate, render. The use case is framework-free. The gateway hides providers. The presenter shapes data and redacts; it does not make business decisions.139140## Audit Smells (sweep first)141142Detect these before deep reading:143144- `mcp-use` imported from `domain/`, `application/`, `gateways/`, or `shared/`.145- `process.env` outside `infrastructure/config/runtime-config.ts`.146- `server.tool(` outside the composition root.147- handler files over ~250 lines or monolithic `src/tools/*.ts`.148- `new *Gateway(...)` outside bootstrap.149- `z.any()` / `z.unknown()` in handler schemas.150- `console.*` under `src/`.151- `index.ts` barrels under application code.152153After the sweep, look up concrete examples and fix paths in `references/anti-patterns.md`.154155## Validation156157Minimum gates for structural work:158159- `python3 scripts/validate-skills.py` when editing this skills pack.160- Project typecheck and lint for target MCP repos.161- `dependency-cruiser` import-boundary gate.162- Focused unit tests for changed handlers/use cases/gateways/presenters.163- End-to-end MCP call only when wiring, bootstrap, transport, auth/session, or response surfaces changed.164165Bundled read-only audit helpers (run from the target MCP project root, or pass the project root as the first argument):166167| Need | Script | Doc |168|---|---|---|169| Grep likely layer-import, env, console, and barrel violations | `scripts/audit-layer-imports.sh` | `scripts/audit-layer-imports.md` |170| Check canonical folders and expected seams | `scripts/check-folder-layout.sh` | `scripts/check-folder-layout.md` |171| Check likely Zod boundary violations | `scripts/check-zod-boundary.sh` | `scripts/check-zod-boundary.md` |172173Claim only the verification rung actually reached.174175## Completion Output176177Finish apply/review/refactor work with:178179- selected mode180- changed layers and files181- guardrails checked182- scripts/tests run183- validation rung reached184- unresolved constraints or accepted deviations185186For Review mode, lead with findings ordered by severity and include replayable evidence.187188## Reference Routing189190| Read when | Reference | Decision it answers |191|---|---|---|192| Need full tree, naming rules, folder rationale, or per-folder `AGENTS.md` guidance | `references/folder-layout.md` | Which folder owns a file and why it exists. |193| Need copy-paste import rules or `dependency-cruiser` config | `references/dependency-rules.md` | Which imports are legal and how CI enforces them. |194| Need the single-root construction order or bootstrap skeleton | `references/composition-root.md` | What constructs where and in what order. |195| Designing or auditing ports, gateways, decorators, or provider error classification | `references/gateways-and-ports.md` | How external systems cross into the application. |196| Building response objects, presenters, sanitisation, or preview policy | `references/presenter-and-tool-response.md` | How domain responses become MCP envelopes. |197| Adding request identity, session id, request id, or cost tracking | `references/request-context.md` | What belongs in AsyncLocalStorage and how it is bound. |198| Designing `DomainError`, JSON-RPC mapping, or recovery hints | `references/error-contracts.md` | How failures move from domain/gateway to MCP response. |199| Adding or auditing a tool handler factory | `references/define-tool-pattern.md` | What `defineTool()` returns and how handlers stay thin. |200| Designing handler dependency injection or capability-gated edge behavior | `references/handler-context.md` | What belongs in `HandlerContext` versus per-request MCP context. |201| Splitting structural and mechanical ownership with `build-mcp-use-server` | `references/coordinate-with-build-mcp-use-server.md` | Which skill owns a blended decision. |202| Checking TypeScript compiler flags, `import type`, branded IDs, or structural SDK mirrors | `references/typescript-quality-bar.md` | What the TypeScript gate requires. |203| Placing Zod schemas or auditing validation boundaries | `references/zod-at-boundary.md` | Where schemas live and where field mechanics route out. |204| Narrowing `unknown`, generic port signatures, discriminated unions, or `satisfies` records | `references/narrowing-and-generics.md` | How types stay precise without `any`. |205| Applying Clean Code rules that materially affect MCP behavior | `references/clean-code-rules-in-mcp-context.md` | Which hygiene rules matter and why. |206| Starting a new `mcp-use/server` repo from scratch | `references/greenfield-walkthrough.md` | Step-by-step scaffold and gates. |207| Repairing an existing drifted repo | `references/refactor-playbook.md` | The staged PR sequence and rollback path. |208| Reviewing an existing repo or PR | `references/audit-checklist.md` | P0/P1/P2 audit rubric and report shape. |209| Looking up concrete drift examples and fix paths | `references/anti-patterns.md` | How common violations appear and how to detect them. |