Code Style

This guide defines coding standards and best practices for the vCon MCP Server project.

Table of Contents


TypeScript Style

General Principles

  1. Type Safety First - No any types except where absolutely necessary

  2. Explicit Over Implicit - Be clear about types and intentions

  3. Functional When Possible - Prefer pure functions

  4. DRY Principle - Don't Repeat Yourself

Type Annotations

// ✅ Good - Explicit types
function createVCon(vcon: VCon): Promise<{ uuid: string }> {
  return queries.createVCon(vcon);
}

// ❌ Bad - Missing types
function createVCon(vcon) {
  return queries.createVCon(vcon);
}

// ✅ Good - Return type explicit
async function getVCon(uuid: string): Promise<VCon> {
  const result = await queries.getVCon(uuid);
  return result;
}

// ⚠️ Acceptable - Return type can be inferred but explicit is better
async function getVCon(uuid: string) {
  return queries.getVCon(uuid);
}

Interfaces vs Types

Use interface for object shapes that may be extended:

// ✅ Good - Interface for extensible types
export interface VCon {
  vcon: string;
  uuid: string;
  parties: Party[];
}

export interface ExtendedVCon extends VCon {
  customField: string;
}

Use type for unions, intersections, and non-object types:

// ✅ Good - Type for unions
export type DialogType = 'recording' | 'text' | 'transfer' | 'incomplete';

export type Encoding = 'none' | 'base64url' | 'json';

// ✅ Good - Type for intersection
export type VConWithMetadata = VCon & {
  metadata: Record<string, unknown>;
};

Optional Properties

// ✅ Good - Optional with ?
interface Analysis {
  type: string;
  vendor: string;
  product?: string;  // Optional
  schema?: string;   // Optional
}

// ❌ Bad - Using undefined
interface Analysis {
  type: string;
  vendor: string;
  product: string | undefined;
}

Enums vs Union Types

Prefer union types over enums:

// ✅ Good - Union type
export type DialogType = 'recording' | 'text' | 'transfer' | 'incomplete';

// ❌ Avoid - Enum (generates runtime code)
enum DialogType {
  Recording = 'recording',
  Text = 'text',
  Transfer = 'transfer',
  Incomplete = 'incomplete'
}

Async/Await

Always use async/await over promises:

// ✅ Good
async function createVCon(vcon: VCon): Promise<string> {
  const result = await queries.createVCon(vcon);
  return result.uuid;
}

// ❌ Bad
function createVCon(vcon: VCon): Promise<string> {
  return queries.createVCon(vcon).then(result => result.uuid);
}

Arrow Functions

Use arrow functions for callbacks and short functions:

// ✅ Good
const uuids = vcons.map(v => v.uuid);

const filtered = vcons.filter(v => v.subject?.includes('support'));

// ✅ Good - Multi-line
const processed = vcons.map(v => {
  const uuid = v.uuid;
  const subject = v.subject || 'Untitled';
  return { uuid, subject };
});

Use regular functions for methods and top-level functions:

// ✅ Good
export function validateVCon(vcon: VCon): ValidationResult {
  // Implementation
}

// ✅ Good - Class method
class VConQueries {
  async createVCon(vcon: VCon): Promise<{ uuid: string }> {
    // Implementation
  }
}

Destructuring

Use destructuring when accessing multiple properties:

// ✅ Good
const { uuid, subject, parties } = vcon;

// ✅ Good - Function parameters
function displayVCon({ uuid, subject }: VCon): string {
  return `${subject} (${uuid})`;
}

// ⚠️ Acceptable for single property
const uuid = vcon.uuid;

Template Literals

Use template literals over string concatenation:

// ✅ Good
const message = `vCon ${uuid} created successfully`;

// ❌ Bad
const message = 'vCon ' + uuid + ' created successfully';

Naming Conventions

General Rules

  • Use camelCase for variables, functions, and properties

  • Use PascalCase for classes, interfaces, and types

  • Use UPPER_SNAKE_CASE for constants

  • Use kebab-case for file names

Files

✅ Good file names:
vcon-queries.ts
search-tools.ts
validation-utils.ts
types/vcon.ts

❌ Bad file names:
VConQueries.ts
searchTools.ts
Validation_Utils.ts

Variables and Functions

// ✅ Good
const vconUuid = '123e4567-e89b-12d3-a456-426614174000';
const maxResults = 100;
let isValid = false;

function createVCon(vcon: VCon): Promise<string> {}
function validateVConStructure(vcon: VCon): boolean {}

// ❌ Bad
const VConUUID = '...';  // Use camelCase
const max_results = 100; // Use camelCase
let is_valid = false;    // Use camelCase

function CreateVCon() {}           // Use camelCase
function validate_vcon_structure() {} // Use camelCase

Classes and Interfaces

// ✅ Good
class VConQueries {}
interface VCon {}
interface SearchOptions {}
type DialogType = ...;

// ❌ Bad
class vconQueries {}   // Use PascalCase
interface vCon {}      // Use PascalCase
type dialogType = ...; // Use PascalCase

Constants

// ✅ Good
const VCON_VERSION = '0.3.0';
const MAX_SEARCH_RESULTS = 1000;
const DEFAULT_THRESHOLD = 0.7;

// ❌ Bad
const vconVersion = '0.3.0'; // Use UPPER_SNAKE_CASE for constants
const MaxSearchResults = 1000;

Boolean Variables

Prefix with is, has, should, or can:

// ✅ Good
const isValid = true;
const hasParties = vcon.parties.length > 0;
const shouldValidate = options.validate ?? true;
const canDelete = user.permissions.includes('delete');

// ❌ Bad
const valid = true;      // Not clear it's boolean
const parties = true;    // Confusing name
const validate = true;   // Could be a function

Function Names

Use verbs for function names:

// ✅ Good
function createVCon() {}
function getVCon() {}
function validateVCon() {}
function calculateScore() {}
function fetchResults() {}

// ❌ Bad
function vcon() {}      // Not descriptive
function vconCreation() {} // Noun, use createVCon
function validation() {}   // Noun, use validate

File Organization

Project Structure

src/
├── index.ts              # Entry point
├── types/               # Type definitions
│   ├── vcon.ts
│   ├── mcp.ts
│   └── index.ts
├── db/                  # Database layer
│   ├── client.ts
│   ├── queries.ts
│   └── index.ts
├── tools/               # MCP tools
│   ├── vcon-crud.ts
│   ├── search-tools.ts
│   ├── tag-tools.ts
│   └── index.ts
├── resources/           # MCP resources
│   └── index.ts
├── prompts/             # MCP prompts
│   └── index.ts
├── utils/               # Utilities
│   ├── validation.ts
│   ├── logger.ts
│   └── index.ts
└── hooks/               # Plugin system
    └── index.ts

File Structure

Each file should follow this order:

// 1. Imports - External first, then internal
import { createClient } from '@supabase/supabase-js';
import { z } from 'zod';

import { VCon, Analysis } from '../types/vcon.js';
import { validateVCon } from '../utils/validation.js';

// 2. Constants
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;

// 3. Type definitions
interface QueryOptions {
  limit?: number;
  offset?: number;
}

// 4. Main implementation
export class VConQueries {
  // Class implementation
}

// 5. Helper functions
function retry<T>(fn: () => Promise<T>, retries: number): Promise<T> {
  // Implementation
}

// 6. Exports
export { QueryOptions };

Imports

// ✅ Good - Organized by category
// External dependencies
import { createClient } from '@supabase/supabase-js';
import { z } from 'zod';

// Internal types
import type { VCon, Party } from '../types/vcon.js';

// Internal modules
import { getSupabaseClient } from './client.js';
import { validateVCon } from '../utils/validation.js';

// ❌ Bad - Disorganized
import { validateVCon } from '../utils/validation.js';
import { createClient } from '@supabase/supabase-js';
import { VCon } from '../types/vcon.js';
import { z } from 'zod';

Exports

// ✅ Good - Named exports
export function createVCon(vcon: VCon): Promise<string> {}
export function getVCon(uuid: string): Promise<VCon> {}

// ✅ Good - Export types
export type { VCon, Party, Dialog };

// ✅ Good - Re-exports in index.ts
export { createVCon, getVCon } from './queries.js';

// ❌ Avoid - Default exports (harder to refactor)
export default function createVCon() {}

Comments and Documentation

JSDoc Comments

All public APIs must have JSDoc comments:

/**
 * Create a new vCon in the database.
 * 
 * Validates the vCon structure before insertion and returns the generated UUID.
 * All parties are inserted in a single transaction.
 * 
 * @param vcon - The vCon object to create
 * @returns Promise resolving to the UUID of the created vCon
 * @throws {ValidationError} If vCon fails validation
 * @throws {DatabaseError} If database operation fails
 * 
 * @example
 * ```typescript
 * const vcon: VCon = {
 *   vcon: '0.3.0',
 *   uuid: crypto.randomUUID(),
 *   created_at: new Date().toISOString(),
 *   parties: [{ name: 'Alice' }]
 * };
 * const uuid = await createVCon(vcon);
 * ```
 */
export async function createVCon(vcon: VCon): Promise<string> {
  // Implementation
}

Inline Comments

Use sparingly for complex logic:

// ✅ Good - Explains why, not what
function calculateRelevance(scores: number[]): number {
  // Normalize scores to 0-1 range before averaging
  // This prevents very high scores from skewing results
  const normalized = scores.map(s => Math.min(s, 1));
  return normalized.reduce((a, b) => a + b, 0) / normalized.length;
}

// ❌ Bad - States the obvious
function sum(a: number, b: number): number {
  // Add a and b together
  return a + b;
}

TODO Comments

Always include context and assignee:

// ✅ Good
// TODO(@username): Implement pagination for large result sets
// See issue #123 for requirements

// ❌ Bad
// TODO: fix this

Section Comments

Use for major sections:

// ============================================================================
// CRUD Operations
// ============================================================================

export async function createVCon(vcon: VCon): Promise<string> {}
export async function getVCon(uuid: string): Promise<VCon> {}
export async function updateVCon(uuid: string, updates: any): Promise<void> {}
export async function deleteVCon(uuid: string): Promise<void> {}

// ============================================================================
// Search Operations
// ============================================================================

export async function searchVCons(criteria: SearchCriteria): Promise<VCon[]> {}

Error Handling

Try-Catch Blocks

// ✅ Good - Specific error handling
async function createVCon(vcon: VCon): Promise<Result> {
  try {
    const validation = validateVCon(vcon);
    if (!validation.valid) {
      return {
        success: false,
        error: `Validation failed: ${validation.errors.join(', ')}`
      };
    }
    
    const result = await queries.createVCon(vcon);
    return { success: true, uuid: result.uuid };
    
  } catch (error) {
    logger.error('Failed to create vCon', { error, vcon });
    
    if (error instanceof DatabaseError) {
      return { success: false, error: 'Database operation failed' };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error'
    };
  }
}

// ❌ Bad - Swallowing errors
async function createVCon(vcon: VCon) {
  try {
    return await queries.createVCon(vcon);
  } catch (error) {
    console.log('Error');  // Too generic
    return null;           // Loses error information
  }
}

Custom Errors

// ✅ Good - Custom error classes
export class ValidationError extends Error {
  constructor(
    message: string,
    public readonly errors: string[]
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

export class DatabaseError extends Error {
  constructor(
    message: string,
    public readonly cause?: Error
  ) {
    super(message);
    this.name = 'DatabaseError';
  }
}

// Usage
if (!validation.valid) {
  throw new ValidationError(
    'vCon validation failed',
    validation.errors
  );
}

Testing Style

Test Structure

import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';

describe('VConQueries', () => {
  let queries: VConQueries;
  let testVConUuid: string;
  
  beforeAll(async () => {
    // One-time setup
    queries = new VConQueries(supabase);
  });
  
  afterAll(async () => {
    // One-time cleanup
    if (testVConUuid) {
      await queries.deleteVCon(testVConUuid);
    }
  });
  
  beforeEach(async () => {
    // Per-test setup
  });
  
  describe('createVCon', () => {
    it('should create a valid vCon', async () => {
      const vcon = createTestVCon();
      
      const result = await queries.createVCon(vcon);
      
      expect(result.uuid).toBeDefined();
      expect(result.uuid).toMatch(/^[0-9a-f-]{36}$/);
      testVConUuid = result.uuid;
    });
    
    it('should reject vCon without parties', async () => {
      const invalidVCon = {
        vcon: '0.3.0',
        uuid: crypto.randomUUID(),
        created_at: new Date().toISOString(),
        parties: []  // Empty - invalid
      };
      
      await expect(queries.createVCon(invalidVCon as VCon))
        .rejects
        .toThrow('at least one party');
    });
  });
});

Test Naming

// ✅ Good - Descriptive test names
it('should create a vCon with valid parties')
it('should reject vCon with invalid UUID format')
it('should return search results ordered by relevance')
it('should handle empty search results gracefully')

// ❌ Bad - Vague test names
it('works')
it('test1')
it('creates vcon')  // Not specific enough

Assertions

// ✅ Good - Specific assertions
expect(result.uuid).toBeDefined();
expect(result.uuid).toMatch(/^[0-9a-f-]{36}$/);
expect(result.parties).toHaveLength(2);
expect(result.parties[0].name).toBe('Alice');

// ❌ Bad - Vague assertions
expect(result).toBeTruthy();
expect(result.uuid).not.toBeNull();

Git Practices

Commit Messages

Follow Conventional Commits:

# ✅ Good
git commit -m "feat(search): add hybrid search capability"
git commit -m "fix(validation): correct analysis vendor requirement"
git commit -m "docs(api): update tools reference"
git commit -m "refactor(db): optimize search query performance"

# ❌ Bad
git commit -m "updates"
git commit -m "fix bug"
git commit -m "WIP"

Commit Size

Keep commits focused and atomic:

# ✅ Good - One logical change
git commit -m "feat(search): add semantic search"

# Later...
git commit -m "docs(search): add semantic search examples"

# ❌ Bad - Multiple unrelated changes
git commit -m "Add search, fix validation, update docs, refactor utils"

Branch Names

# ✅ Good
feature/semantic-search
fix/validation-error-messages
docs/api-reference-update
refactor/query-optimization

# ❌ Bad
my-feature
update
fix

Linting and Formatting

ESLint Configuration

The project uses ESLint with TypeScript support. Key rules:

{
  "@typescript-eslint/no-explicit-any": "error",
  "@typescript-eslint/explicit-function-return-type": "warn",
  "@typescript-eslint/no-unused-vars": "error",
  "prefer-const": "error",
  "no-console": "warn"
}

Running Linters

# Check for linting errors
npm run lint

# Fix auto-fixable issues
npm run lint:fix

# Format code with Prettier
npm run format

# Check formatting
npm run format:check

Pre-commit Hooks

We use Husky for git hooks:

{
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint && npm test"
    }
  }
}

Review Checklist

Before submitting code for review:


Additional Resources


Questions about code style? Ask in GitHub Discussions or check existing code for examples.

Last updated