agentleFS
Sign inSign up

frontmcp

agentfront/frontmcp/CLAUDE.md

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

CLAUDE.md146 starsChanged 5 days ago
  • Reads credentials
  • Commits and pushes
<!-- frontmcp:skills-start v1.5.7 -->
# Skills and Tools

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

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

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.