Moko Consulting

Open-source software for Joomla, Gitea, and web platforms. Home of MokoSuite, MokoGitea, and MokoCLI.

Tennessee
standards/coding-typescript.-

TypeScript Coding Standards

Standards for MCP servers and Node.js tooling in the mokoplatform monorepo.

TypeScript Configuration

All projects must use strict mode:

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "dist",
    "rootDir": "src",
    "declaration": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  }
}

Async Patterns

  • Always use async/await — never raw .then()/.catch() chains
  • Wrap top-level async calls in try/catch with proper error logging
  • Use Promise.all() for independent concurrent operations
  • Use Promise.allSettled() when partial failure is acceptable
async function fetchSiteHealth(connections: Connection[]): Promise<HealthResult[]> {
  const results = await Promise.allSettled(
    connections.map(conn => checkHealth(conn))
  );

  return results.map((result, i) => ({
    connection: connections[i].name,
    status: result.status === 'fulfilled' ? 'up' : 'down',
    error: result.status === 'rejected' ? result.reason.message : undefined,
  }));
}

Runtime Validation with Zod

Use Zod for all external data validation — configs, API responses, user input:

import { z } from 'zod';

const ConnectionSchema = z.object({
  name: z.string(),
  url: z.string().url(),
  token: z.string().min(1),
  timeout: z.number().positive().default(30000),
});

type Connection = z.infer<typeof ConnectionSchema>;

function loadConfig(path: string): Connection[] {
  const raw = JSON.parse(readFileSync(path, 'utf-8'));
  return z.array(ConnectionSchema).parse(raw.connections);
}

MCP Server Structure

Standard MCP server layout:

servers/{name}/
├── src/
│   ├── index.ts          # Entry point, server setup
│   ├── tools/            # Tool implementations
│   │   ├── backup.ts
│   │   └── status.ts
│   ├── config.ts         # Config loading and validation
│   └── types.ts          # Shared type definitions
├── package.json
├── tsconfig.json
└── dist/                 # Build output (gitignored)

Config File Pattern

MCP configs live at ~/.mcp_moko{name}.json:

interface McpConfig {
  connections: Record<string, {
    url: string;
    token: string;
    [key: string]: unknown;
  }>;
  defaults?: {
    connection?: string;
    timeout?: number;
  };
}

Tool Registration

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'backup_run',
      description: 'Start a backup for the specified target',
      inputSchema: {
        type: 'object',
        properties: {
          target: { type: 'string', description: 'Backup target name' },
          profile: { type: 'string', description: 'Backup profile ID' },
        },
        required: ['target'],
      },
    },
  ],
}));

Error Handling

  • Define custom error classes per domain
  • Always include context in error messages
  • Never swallow errors silently — log or rethrow
class McpError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode?: number,
  ) {
    super(message);
    this.name = 'McpError';
  }
}

class ConnectionError extends McpError {
  constructor(connection: string, cause: Error) {
    super(
      `Failed to connect to ${connection}: ${cause.message}`,
      'CONNECTION_FAILED',
    );
  }
}

Naming Conventions

  • Files: kebab-case (backup-engine.ts, health-check.ts)
  • Classes/interfaces: PascalCase (BackupEngine, HealthResult)
  • Functions/variables: camelCase (runBackup, siteUrl)
  • Constants: UPPER_SNAKE_CASE (MAX_RETRIES, DEFAULT_TIMEOUT)
  • Type parameters: single uppercase letter or descriptive (T, TResult)
  • Enums: PascalCase members (BackupStatus.Running)

Prohibited Patterns

  • any type — use unknown and narrow with type guards
  • @ts-ignore — fix the type error instead
  • require() — use ES module imports
  • Mutable global state — pass config through constructors
  • console.log in production — use structured logging