CLAUDE.md · diff

git:20261005.181f6a2 to git:20261011.3484b44

5 added, 2 removed. Audit A to A.

<!-- frontmcp:skills-start v1.8.6 -->
# Skills and Tools
> Auto-generated by `frontmcp skills install` (v1.8.6) — do not edit manually.
This project uses **FrontMCP skills** installed in `.claude/skills/`.
Before writing code, search the installed skills for relevant guidance:
When you need to implement something, **read the matching skill first** — it contains patterns, examples, and common mistakes to avoid.
<!-- frontmcp:skills-end -->
- # Documentation Site Layout
+ # Documentation
- The Mintlify docs in `docs/` are part of the **agentfront.dev aggregator site**, which serves multiple projects. **All FrontMCP pages live under `docs/frontmcp/`** and ship at `https://docs.agentfront.dev/frontmcp/...`. Never flatten `docs/frontmcp/*` into `docs/*`, never drop the `frontmcp/` prefix from `docs/docs.json` page references, and keep internal MDX links prefixed (e.g. `/frontmcp/servers/tools`, not `/servers/tools`). The canonical URL in `docs/docs.json` must point to `https://docs.agentfront.dev/frontmcp`.
+ FrontMCP's docs are at https://frontmcp.dev, built from the private `agentfront/frontmcp.dev` repository. This repo has no docs site: don't add one, and link to frontmcp.dev pages (`https://frontmcp.dev/reference/...`, `https://frontmcp.dev/learn/...`), never to docs.agentfront.dev for FrontMCP.
+
+ - The skills catalog (`libs/skills/catalog`) is the documentation that ships with FrontMCP. A behavior change updates it in the same PR: see `.claude/rules/skills-catalog-sync.md`.
+ - A PR that changes what users see (an API, option, default, error code or message, CLI flag or output) fills in the "User-facing change" section of `.github/pull_request_template.md`. frontmcp.dev's pass for each release reads it.
# FrontMCP Monorepo - Development Guide
## Repository Structure
- **Monorepo**: Nx-based monorepo managing multiple TypeScript libraries
- **Libraries Location**: `/libs/*`
- each library is independent and publishable
### Primary Libraries (scoped under `@frontmcp/*`)
Located in `/libs/*`:
- **cli** (`libs/cli`) - Command-line interface
- **sdk** (`libs/sdk`) - Core FrontMCP SDK
- **adapters** (`libs/adapters`) - Framework adapters and integrations
- **plugins** (`libs/plugins`) - Plugin system and extensions
- **skills** (`libs/skills`) - Curated SKILL.md catalog for scaffold and install tooling
> **Note:** `ast-guard`, `vectoriadb`, `enclave-vm`, `json-schema-to-zod-v3`, and `mcp-from-openapi` have been moved to external repositories.
### Demo Apps
Located in `/apps/*`:
- **demo** (`apps/demo`) - Demo application for development and testing
## Technology Stack
- **Build System**: Nx (commands: `nx build sdk`, `nx test ast-guard`, `nx run-many -t test`)
- **Language**: TypeScript with strict mode enabled
- **Testing**: Jest with 95%+ coverage requirement
- **Package Manager**: yarn
## Code Quality Standards
- **Coverage Requirement**: 95%+ across all metrics (statements, branches, functions, lines)
- **No Warnings**: Build must complete without TypeScript warnings
- **All Tests Passing**: 100% test pass rate required
- **Strict TypeScript**: Use strict type checking, no `any` types without justification
- **Test File Naming**: All test files MUST use `.spec.ts` (or `.spec.tsx` for React components) extension (NOT `.test.ts`). E2E tests use `.e2e.spec.ts`, perf tests use `.perf.spec.ts`, Playwright tests use `.pw.spec.ts`
### Barrel Exports (index.ts)
```typescript
// Export everything users need
export { JSAstValidator } from './validator';
export * from './interfaces';
export * from './rules';
export * from './presets';
export * from './errors';
// NO legacy exports!
// ❌ export { JSAstValidator as AstGuard } // WRONG
// ❌ export { AstGuardError as JSAstValidatorError } // WRONG
```
## Project-Specific Notes
### Primary Packages (`@frontmcp/*`)
#### @frontmcp/sdk
- **Purpose**: Core FrontMCP SDK for building MCP servers and clients
- **Scope**: Main package that other libraries build upon
#### frontmcp (published as `frontmcp` on npm, internal monorepo name `@frontmcp/cli`)
- **Purpose**: Command-line interface for FrontMCP
- **Scope**: Developer tooling and utilities
#### @frontmcp/adapters
- **Purpose**: Framework adapters (Express, Fastify, etc.)
- **Scope**: Integration layer for different frameworks
#### @frontmcp/plugins
- **Purpose**: Plugin system and extensions
- **Scope**: Extensibility framework
#### @frontmcp/auth
- **Purpose**: Authentication, session management, credential vault, CIMD, and OAuth extensions
- **Scope**: Standalone auth library used by SDK and other packages
- **Note**: All authentication-related code should be placed in this library, not in SDK
#### @frontmcp/skills
- **Purpose**: Curated SKILL.md catalog for scaffolding and future skill installation
- **Scope**: Publishable catalog of markdown-based skills organized by category and deployment target
- **Structure**: `catalog/` contains SKILL.md directories; `src/` has manifest types and loader helpers
- **Build**: Custom asset-aware build that copies `catalog/**` into dist (not stock Nx lib generator)
- **Manifest**: `catalog/skills-manifest.json` is the single source of truth for scaffold filtering and future installer
- **Adding skills**: Create dir in `catalog/<category>/<name>/`, add `SKILL.md`, update manifest, run `nx test skills`
### Demo Applications
#### demo (`apps/demo`)
- **Type**: Demo application
- **Purpose**: Development and testing playground for FrontMCP packages
- **Usage**: Testing integrations, examples, and development workflows
## SDK Code Guidelines
### Type Safety Philosophy
FrontMCP is a TypeScript-first schema validation framework. All types should align with MCP protocol definitions.
#### MCP Response Types (DO NOT use `unknown` for protocol types)
- **Tools**: `execute()` returns `Promise<Out>` where `Out` is the typed output schema
- **Prompts**: `execute()` returns `Promise<GetPromptResult>` (MCP-defined type)
- **Resources**: `read()` returns `Promise<ReadResourceResult>` (MCP-defined type)
```typescript
// ✅ Good - strict MCP protocol types
abstract execute(args: Record<string, string>): Promise<GetPromptResult>;
// ❌ Bad - using unknown defeats TypeScript-first development
abstract execute(args: Record<string, string>): Promise<unknown>;
```
#### Validation Flow Pattern
1. `execute/read` methods return strictly typed MCP responses
2. `parseOutput/safeParseOutput` normalize various input shapes to the strict type
3. Flows finalize output using the entry's parse methods
```typescript
// In flow finalize stage:
const parseResult = prompt.safeParseOutput(rawOutput);
if (!parseResult.success) throw new InvalidOutputError();
this.respond(parseResult.data); // data is GetPromptResult
```
### Type System Patterns
**Use `unknown` instead of `any` for generic type defaults (NOT for MCP protocol types):**
```typescript
// ✅ Good - explicit constraint with unknown default
class ResourceContext<
Params extends Record<string, string> = Record<string, string>,
Out = unknown,
> extends ExecutionContextBase<Out>
// ❌ Bad - loose any types
class ResourceContext<In = any, Out = any>
```
**Use type parameters with constraints:**
```typescript
// ✅ Good - constrained generic
export type ResourceCtorArgs<Params extends Record<string, string> = Record<string, string>>
// ❌ Bad - unconstrained
export type ResourceCtorArgs<Params = any>
```
**Create shared base classes for common functionality:**
```typescript
// ExecutionContextBase provides: get(), tryGet(), scope, fail(), mark(), fetch()
export abstract class ToolContext extends ExecutionContextBase<Out>
export abstract class ResourceContext extends ExecutionContextBase<Out>
```
### Error Handling
**Use specific error classes with MCP error codes:**
```typescript
export const MCP_ERROR_CODES = {
RESOURCE_NOT_FOUND: -32002,
INVALID_REQUEST: -32600,
METHOD_NOT_FOUND: -32601,
INVALID_PARAMS: -32602,
INTERNAL_ERROR: -32603,
PARSE_ERROR: -32700,
} as const;
export class ResourceNotFoundError extends PublicMcpError {
readonly mcpErrorCode = MCP_ERROR_CODES.RESOURCE_NOT_FOUND;
toJsonRpcError() {
return { code: this.mcpErrorCode, message: this.getPublicMessage(), data: { uri: this.uri } };
}
}
```
**Avoid non-null assertions - use proper error handling:**
```typescript
// ✅ Good
const rec = this.defs.get(token);
if (!rec) {
throw new DependencyNotFoundError('AuthRegistry', tokenName(token));
}
// ❌ Bad - non-null assertion
const rec = this.defs.get(token)!;
```
### Registry Patterns
**Use getCapabilities() for dynamic capability exposure:**
```typescript
// In registry
getCapabilities(): { subscribe: boolean; listChanged: boolean } {
return { subscribe: false, listChanged: this.hasAny() };
}
// In transport adapter
resources: this.scope.resources.getCapabilities(), // Not hardcoded!
```
### Change Events
**Use `changeScope` instead of `scope` to avoid confusion with Scope class:**
```typescript
export type ResourceChangeEvent = {
kind: ResourceChangeKind;
changeScope: ResourceChangeScope; // Not 'scope'!
// ...
};
```
### URI Validation
**Validate URIs per RFC 3986 at metadata level:**
```typescript
uri: z.string().min(1).refine(isValidMcpUri, {
message: 'URI must have a valid scheme (e.g., file://, https://, custom://)',
}),
```
### Hook Validation
**Fail fast on invalid hook flows:**
```typescript
const validFlows = ['resources:read-resource', 'resources:list-resources'];
const invalidHooks = allHooks.filter((hook) => !validFlows.includes(hook.metadata.flow));
if (invalidHooks.length > 0) {
throw new InvalidHookFlowError(`Resource "${className}" has hooks for unsupported flows: ${invalidFlowNames}`);
}
```
### Record Types
**Centralize record types in common/records:**
```typescript
// Import from common, not from module-specific files
import { AnyResourceRecord } from '../common/records';
// In libs/sdk/src/common/records/resource.record.ts
export type AnyResourceRecord = ResourceRecord | ResourceTemplateRecord;
```
## Git Operations
**IMPORTANT: Never run git commit or git push commands.** The user handles all git operations themselves. Claude should only:
- Create/edit files
- Stage files with `git add` if explicitly asked
- Check status with `git status`, `git diff`, `git log`
Never:
- `git commit`
- `git push`
- `git commit --amend`
- Any command that modifies git history
### Branch targeting
**Open pull requests against the active release branch, not `main`.** The active line is
`release/1.8.x`. `main` then receives the change through the cherry-pick PR that
`.github/workflows/cherry-pick-prompt.yml` opens automatically.
Targeting `main` directly means the release branch never gets the fix, so every release cut from
that branch — including the one already published — lacks it. The 1.7 line is the standing
example, and it is checkable rather than folklore:
`git merge-base --is-ancestor 676b55d origin/release/1.7.x` exits non-zero, so the twelve
advisory fixes in #550 are absent from that branch; `git show
origin/release/1.7.x:libs/sdk/package.json` reads `1.7.2`; and `npm view @frontmcp/sdk versions`
lists `1.7.2` as published, with no later 1.7.x. Fixes merged, and the newest installable 1.7
artifact still carries the vulnerable code.
Three things worth knowing before they surprise you:
- **The cherry-pick is conditional.** Its `if` gate requires the PR to be merged, to target
`release/*` or `next/*` (never the default branch), and — the one that catches people —
**not to come from a fork**, since a forked PR's `GITHUB_TOKEN` is read-only. Merge a fork PR
into a release branch and no cherry-pick PR appears at all; back-port it to `main` by hand.
- **Stacked PRs are cherry-picked from where their commit landed.** When GitHub merges a native
stack, each upper PR's `closed` event still names its stacked base (the parent PR's branch),
while its commit lands on the stack's base. The workflow finds the `release/*` or `next/*` branch
that contains the merge commit and cherry-picks from there. It builds on the parent's cherry-pick
branch while that PR is open, so the cherry-picks stack on `main` the way the PRs did. A stacked
PR merged only into its parent's branch has landed nowhere yet: the parent's merge carries it.
- The cherry-pick PRs are authored by `github-actions[bot]`, and **CodeRabbit skips them**
("Bot user detected"). Review has to happen on the original PR against the release branch.
- `publish-release.yml` only runs from a `release/X.Y.x` branch, so a fix that exists solely on
`main` cannot be released at all until it is back-ported.
## Task Completion Checklist
**Before completing a task**, run the following cleanup:
```bash
# Remove unused imports from changed files
node scripts/fix-unused-imports.mjs
```
This script automatically:
- Finds all files changed in the current branch (compared to main)
- Removes unused imports using ESLint
- Supports custom base branch: `node scripts/fix-unused-imports.mjs <branch-name>`
## Plugin Development
### Creating Plugins with Context Extensions
Plugins can extend `ExecutionContextBase` (ToolContext, etc.) to add properties like `this.remember`:
1. **Module Augmentation** (TypeScript types):
```typescript
declare module '@frontmcp/sdk' {
interface ExecutionContextBase {
readonly myProperty: MyType;
}
}
```
2. **Runtime Extension** (prototype modification):
```typescript
export function installMyContextExtension(): void {
const { ExecutionContextBase } = require('@frontmcp/sdk');
Object.defineProperty(ExecutionContextBase.prototype, 'myProperty', {
get: function () {
return this.get(MyToken);
},
configurable: true,
enumerable: false,
});
}
```
See `plugins/plugin-remember/src/remember.context-extension.ts` for a complete example.
### Protocol Package Boundary
**IMPORTANT**: Never import from `@modelcontextprotocol/sdk` (or any `@modelcontextprotocol/*` subpath) directly from `libs/sdk`, `libs/adapters`, `libs/plugins`, `libs/auth`, `libs/storage-sqlite`, tests, or any app. All MCP protocol types, schemas, error classes (e.g. `McpError`), transports, and client/server bindings must be imported from `@frontmcp/protocol`, which is the single, replaceable boundary between FrontMCP and the upstream MCP package.
```typescript
// ✅ Good — routes through the boundary
// ❌ Bad — locks us into @modelcontextprotocol/sdk at every call site
import { McpError, ToolSchema } from '@modelcontextprotocol/sdk/types.js';
import { CallToolRequestSchema, McpError, type ServerCapabilities, type Tool } from '@frontmcp/protocol';
```
Why this matters:
- `libs/protocol/src/types.ts` is the ONLY file that transitively imports from `@modelcontextprotocol/sdk`. Every other import must go through `@frontmcp/protocol` so that dropping or swapping the upstream package is a single-file change.
- The `@nx/dependency-checks` lint rule in `nx run sdk:lint` enforces this implicitly — adding a direct import to `libs/sdk/src/**` will fail the build until the dep is declared on `libs/sdk/package.json`, which we explicitly do NOT want to do.
- If a type or schema you need isn't reachable via `@frontmcp/protocol`, re-export it from `libs/protocol/src/types.ts` rather than reaching around the boundary.
Tests in `libs/sdk/src/**/__tests__/*.spec.ts` follow the same rule — lint treats them as part of the SDK package.
### Crypto Utilities
**IMPORTANT**: Always use `@frontmcp/utils` for cryptographic operations. Never use `node:crypto` directly or implement custom crypto functions.
```typescript
import {
base64urlDecode, // Base64url decoding
// Encoding
base64urlEncode, // Base64url encoding
decryptAesGcm, // AES-256-GCM decryption
encryptAesGcm, // AES-256-GCM encryption
generateCodeChallenge, // PKCE code challenge (S256)
// PKCE (RFC 7636)
generateCodeVerifier, // PKCE code verifier (43-128 chars)
generatePkcePair, // Generate both verifier and challenge
// Encryption
hkdfSha256, // HKDF-SHA256 key derivation (RFC 5869)
// Random generation
randomBytes, // Cryptographic random bytes
randomUUID, // UUID v4 generation
// Hashing
sha256, // SHA-256 hash (Uint8Array)
sha256Base64url, // SHA-256 hash (base64url string)
sha256Hex, // SHA-256 hash (hex string)
} from '@frontmcp/utils';
```
This ensures cross-platform support (Node.js and browser) with consistent behavior.
```typescript
// ✅ Good - use utils for PKCE
import { generateCodeVerifier, sha256Base64url } from '@frontmcp/utils';
const verifier = generateCodeVerifier();
const challenge = sha256Base64url(verifier);
// ❌ Bad - custom implementation
private generatePkceVerifier(): string {
const chars = 'ABC...';
// Don't do this - use generateCodeVerifier() instead
}
```
### File System Utilities
**IMPORTANT**: Always use `@frontmcp/utils` for file system operations. Never use `fs/promises` or `node:fs` directly.
```typescript
import {
access, // Check file accessibility
copyFile, // Copy file
cp, // Copy file or directory recursively
ensureDir, // Ensure directory exists
fileExists, // Check if file exists (returns boolean)
isDirEmpty, // Check if directory is empty
mkdir, // Create directory
mkdtemp, // Create temporary directory
readdir, // List directory contents
readFile, // Read file as string
readFileBuffer, // Read file as Buffer
readJSON, // Read and parse JSON file
rename, // Rename/move file or directory
rm, // Remove file or directory
runCmd, // Run command as child process
stat, // Get file/directory stats
unlink, // Delete file
writeFile, // Write content to file
writeJSON, // Write object as JSON
} from '@frontmcp/utils';
```
Benefits:
- Cross-platform support (Node.js only, throws in browser)
- Lazy-loaded modules to avoid import errors in browser builds
- Consistent API across the codebase
- Centralized error handling and logging
**Exception — low-level file-descriptor ops in Node-only CLI features:** a few
Node-only features (e.g. the file logger's sync `openSync`/`writeSync`/
`closeSync`/`unlinkSync`) need low-level fd APIs that `@frontmcp/utils` does not
expose. These MAY use a lazily-`require('fs')`'d handle, provided every call site
is wrapped in try/catch and degrades silently on non-Node runtimes (matching the
lazy-load pattern `@frontmcp/utils/fs` itself uses). Prefer extending
`@frontmcp/utils/fs` when an operation will be reused; the direct-`require` path
is only for genuinely Node-only, fd-level CLI code.
### Storage Factory Pattern
**IMPORTANT**: When creating stores (session stores, elicitation stores, etc.), always use the factory pattern. Never construct stores directly with raw Redis clients.
```typescript
// ✅ Good - Use factory function
import { createSessionStore } from '@frontmcp/sdk/auth/session';
import { createElicitationStore } from '@frontmcp/sdk/elicitation';
const sessionStore = await createSessionStore({
provider: 'redis',
host: 'localhost',
port: 6379,
keyPrefix: 'mcp:session:',
});
const { store, type } = createElicitationStore({
redis: { provider: 'redis', host: 'localhost', port: 6379 },
keyPrefix: 'mcp:elicit:',
logger,
});
// ❌ Bad - Direct construction with raw Redis client
const Redis = require('ioredis');
const client = new Redis({ host: 'localhost' });
const store = new RedisElicitationStore(client, logger); // DON'T DO THIS
```
**Factory Pattern Benefits:**
- Automatic provider detection (Redis, Vercel KV, etc.)
- Consistent key prefix handling
- Lazy-loading of dependencies (avoids bundling ioredis when not used)
- Built-in error handling and logging
- Support for fallback to memory store in development
- Edge runtime detection and appropriate error messages
**Creating New Store Factories:**
1. Create a factory file (e.g., `my-store.factory.ts`)
2. Use `RedisOptions` type for configuration
3. Use type guards (`isRedisProvider()`, `isVercelKvProvider()`) for provider detection
4. Lazy-require implementations: `const { MyStore } = require('./my.store')`
5. Return store type information: `{ store, type: 'redis' | 'memory' }`
6. Handle Edge runtime restrictions (throw if memory not supported)
See `libs/sdk/src/elicitation/elicitation-store.factory.ts` for a complete example.
### RememberPlugin Usage
When `RememberPlugin` is installed, tools can use `this.remember` and `this.approval`:
```typescript
@Tool({ name: 'my_tool' })
class MyTool extends ToolContext {
async execute(input) {
// Store/retrieve session memory
await this.remember.set('key', 'value');
const val = await this.remember.get('key');
// Check tool approval
const approved = await this.approval.isApproved('other-tool');
}
}
```
## Skills Feature Organization
### MCP SEP-2640 (Skills Extension)
Skills are served over MCP using the **singular** `skill://` URI scheme per
[SEP-2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640).
The legacy plural `skills://` scheme has been removed entirely. New URIs:
- `skill://index.json` — discovery document (agentskills.io v0.2.0 schema)
- `skill://{+skillPath}/SKILL.md` — raw frontmatter + body (identical to filesystem skills)
- `skill://{+skillPath}/{+filePath}` — any file in the skill directory
`{+skillPath}` may be a single segment (`git-workflow`) or nested
(`acme/billing/refunds`); the final segment MUST equal frontmatter `name`
(enforced by `skillMetadataSchema`).
The extension is declared at initialize time under both
`capabilities.experimental[io.modelcontextprotocol/skills]` and
`capabilities.extensions[io.modelcontextprotocol/skills]` (forward-compat
for SEP-2133). All wiring lives in `libs/sdk/src/skill/sep-2640/`. To
disable the resources entirely, set `skillsConfig.mcpResources: false`.
### Keep scope.instance.ts Lean
Use helper functions for feature-specific registration logic:
```typescript
// ✅ Good - use helper from skill module
import { registerSkillCapabilities } from '../skill/skill-scope.helper';
await registerSkillCapabilities({
skillRegistry: this.scopeSkills,
flowRegistry: this.scopeFlows,
toolRegistry: this.scopeTools,
providers: this.scopeProviders,
skillsConfig: this.metadata.skillsConfig,
logger: this.logger,
});
// ❌ Bad - inline 40+ lines of skill registration logic in scope.instance.ts
```
### Skills-Only Mode Detection
Use the utility from skill module instead of inline detection:
```typescript
// ✅ Good - use utility
import { detectSkillsOnlyMode } from '../../skill/skill-mode.utils';
const skillsOnlyMode = detectSkillsOnlyMode(query);
// ❌ Bad - duplicated inline logic
const mode = query?.['mode'];
const skillsOnlyMode = mode === 'skills_only' || (Array.isArray(mode) && mode.includes('skills_only'));
```
### Type Usage for Visibility
Use the `SkillVisibility` type from common/metadata instead of inline literals:
```typescript
// ✅ Good - use exported type
import { SkillVisibility } from '../common/metadata/skill.metadata';
private readonly visibility: SkillVisibility;
// ❌ Bad - inline literal union
private readonly visibility: 'mcp' | 'http' | 'both';
```
### Private Fields in Entry Classes
Use `private` keyword without underscore prefix, expose via getters:
```typescript
// ✅ Good - idiomatic TypeScript
private readonly tags: string[];
private readonly priority: number;
private cachedContent?: CachedSkillContent;
getTags(): string[] { return this.tags; }
getPriority(): number { return this.priority; }
// ❌ Bad - underscore prefix pattern
private readonly _tags: string[];
getTags(): string[] { return this._tags; }
```
### Tool Schema Access
Use `ToolEntry.getInputJsonSchema()` for single source of truth:
```typescript
// ✅ Good - use entry method
const inputSchema = tool.getInputJsonSchema();
// ❌ Bad - duplicated conversion logic
if (tool.rawInputSchema) { ... }
else if (tool.inputSchema) { try { toJSONSchema(z.object(...)) } }
```
### Skills HTTP Caching
Configure via `skillsConfig.cache` option, supports memory (default) and Redis:
```typescript
@FrontMcp({
skillsConfig: {
enabled: true,
cache: {
enabled: true,
redis: { provider: 'redis', host: 'localhost' },
ttlMs: 60000,
},
},
})
```
### Skills HTTP Authentication
Configure via `skillsConfig.auth` option:
```typescript
// API key auth
@FrontMcp({
skillsConfig: {
enabled: true,
auth: 'api-key',
apiKeys: ['sk-xxx', 'sk-yyy'],
},
})
// JWT bearer auth
@FrontMcp({
skillsConfig: {
enabled: true,
auth: 'bearer',
jwt: {
issuer: 'https://auth.example.com',
audience: 'skills-api',
},
},
})
```
## Anti-Patterns to Avoid
❌ **Don't**: Import from `@modelcontextprotocol/sdk` directly (use `@frontmcp/protocol` — that's the only place allowed to touch the upstream package)
❌ **Don't**: Use `node:crypto` directly (use `@frontmcp/utils` for cross-platform support)
❌ **Don't**: Use `fs/promises` or `node:fs` directly (use `@frontmcp/utils` for consistent file ops)
❌ **Don't**: Add backwards compatibility exports in new libraries
❌ **Don't**: Use prefixes like "PT-001" in test names
❌ **Don't**: Skip constructor validation tests
❌ **Don't**: Ignore error class `instanceof` checks in tests
❌ **Don't**: Use `any` type without strong justification
❌ **Don't**: Commit code with test failures or build warnings
❌ **Don't**: Use non-null assertions (`!`) - throw proper errors instead
❌ **Don't**: Mutate rawInput in flows - use state.set() for flow state
❌ **Don't**: Hardcode capabilities in adapters - use registry.getCapabilities()
❌ **Don't**: Name event properties `scope` when they don't refer to Scope class
❌ **Don't**: Put auth-related code in libs/sdk/src/auth (use libs/auth instead)
❌ **Don't**: Name test files with `.test.ts` extension (use `.spec.ts` instead)
❌ **Don't**: Use `ToolContext<typeof inputSchema>` — types are auto-inferred from the `@Tool` decorator; use plain `ToolContext`
✅ **Do**: Use clean, descriptive names for everything
✅ **Do**: Import all MCP protocol types, schemas, and `McpError` from `@frontmcp/protocol`
✅ **Do**: Use `@frontmcp/utils` for file system and crypto operations
✅ **Do**: Test all code paths including errors
✅ **Do**: Document known limitations clearly
✅ **Do**: Follow the preset pattern for hierarchical configurations
✅ **Do**: Achieve 95%+ test coverage
✅ **Do**: Use strict TypeScript settings
✅ **Do**: Write comprehensive security documentation
✅ **Do**: Use `unknown` instead of `any` for generic type defaults
✅ **Do**: Validate hooks match their entry type (fail fast)
✅ **Do**: Use specific MCP error classes with JSON-RPC codes
✅ **Do**: Place authentication logic in `libs/auth`, import via `@frontmcp/auth`