CLAUDE.md · git:20260621.365aac8 · 2026-06-21 · sha256 8db304d14b553a87

CLAUDE.md git:20260621.365aac8A

Immutable. This exact content never changes and is served at /api/v1/blob/8db304d14b553a87.

<!-- frontmcp:skills-start v1.4.0 -->
# Skills and Tools

> Auto-generated by `frontmcp skills install` (v1.4.0) — 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

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 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

## 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`