Skip to main content
Featured MCP TypeScript Tutorial Hands-on Testing

Build Your First MCP Server: Director Mode Assistant

Learn MCP development by building a practical server that demonstrates TDD, Auto-Cycle, and SpecKit concepts. Complete with TypeScript, tests, and best practices.

January 15, 2026 15 min read By Claude World

🎯 What You’ll Build

In this tutorial, you’ll build a complete MCP Server from scratch that demonstrates three core Director Mode concepts:

  • TDD Cycle Management - Track and guide your Red-Green-Refactor workflow
  • Auto-Cycle Progress - Monitor automated development iterations
  • SpecKit Validation - Validate specifications against implementation

The server will include 9 tools and 3 resources, with full TypeScript support, comprehensive tests, and detailed documentation.

Final Project: github.com/claude-world/mcp-director-mode-server


📚 Prerequisites

Before starting, make sure you have:

# Check Node.js version (needs 18+)
node --version  # v18.0.0 or higher

# Check TypeScript
npm install -g typescript

# Verify Claude Code MCP support
claude --mcp list

Required Knowledge:

  • Basic TypeScript/JavaScript
  • Familiarity with command-line tools
  • Understanding of TDD concepts (helpful but not required)

🚀 Quick Start

Step 1: Initialize the Project

Create a new directory and initialize:

mkdir mcp-director-mode-server
cd mcp-director-mode-server

# Initialize npm project
npm init -y

# Install dependencies
npm install @modelcontextprotocol/sdk
npm install --save-dev typescript @types/node vitest

# Initialize TypeScript
npx tsc --init

Update package.json:

{
  "name": "mcp-director-mode-server",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "test": "vitest",
    "start": "node dist/index.js"
  }
}

Step 2: Configure TypeScript

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "tests"]
}

🛠️ Building the MCP Server

Step 3: Create the Server Entry Point

Create src/index.ts:

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

// Create server instance
const server = new Server(
  {
    name: 'director-mode-assistant',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
      resources: {},
    },
  }
);

// Register tools (we'll add these next)
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'check_tdd_status',
      description: 'Check current TDD cycle status (RED/GREEN/REFACTOR)',
      inputSchema: {
        type: 'object',
        properties: {
          projectPath: {
            type: 'string',
            description: 'Path to project directory',
          },
        },
        required: ['projectPath'],
      },
    },
    // ... more tools will be added here
  ],
}));

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'check_tdd_status':
      // Implementation will be added next
      return {
        content: [{
          type: 'text',
          text: 'TDD status: RED - Tests are failing',
        }],
      };

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

// Start server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('Director Mode Assistant MCP Server running...');
}

main().catch(console.error);

Step 4: Implement TDD Tools

Create src/tools/tdd.ts:

import { execSync } from 'child_process';
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';

export interface TDDStatus {
  phase: 'RED' | 'GREEN' | 'REFACTOR';
  testCount: number;
  passingTests: number;
  failingTests: number;
  lastRun: Date;
}

export function checkTDDStatus(projectPath: string): TDDStatus {
  // Check for test files
  const testDir = join(projectPath, 'tests');
  if (!existsSync(testDir)) {
    return {
      phase: 'RED',
      testCount: 0,
      passingTests: 0,
      failingTests: 0,
      lastRun: new Date(),
    };
  }

  // Run tests and parse output
  try {
    const output = execSync('npm test', {
      cwd: projectPath,
      encoding: 'utf-8',
    });

    // Parse test results (example for Jest)
    const match = output.match(/(\d+) passed, (\d+) failed/);
    if (match) {
      const passing = parseInt(match[1]);
      const failing = parseInt(match[2]);

      return {
        phase: failing > 0 ? 'RED' : 'GREEN',
        testCount: passing + failing,
        passingTests: passing,
        failingTests: failing,
        lastRun: new Date(),
      };
    }
  } catch (error: any) {
    // Tests failed
    const output = error.stdout || error.message;
    const match = output.match(/(\d+) passed, (\d+) failed/);
    if (match) {
      return {
        phase: 'RED',
        testCount: parseInt(match[1]) + parseInt(match[2]),
        passingTests: parseInt(match[1]),
        failingTests: parseInt(match[2]),
        lastRun: new Date(),
      };
    }
  }

  return {
    phase: 'RED',
    testCount: 0,
    passingTests: 0,
    failingTests: 0,
    lastRun: new Date(),
  };
}

export function suggestTDDNextStep(status: TDDStatus): string {
  switch (status.phase) {
    case 'RED':
      return 'Write minimal code to pass the failing tests';
    case 'GREEN':
      return 'All tests passing! Time to refactor for cleanliness';
    case 'REFACTOR':
      return 'Refactoring complete. Run tests to verify nothing broke';
    default:
      return 'Start by writing a failing test';
  }
}

Step 5: Add Resources for State Management

Create src/resources/index.ts:

import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';

export interface ProjectState {
  currentPhase: string;
  lastUpdate: Date;
  metrics: {
    totalTests: number;
    testPassRate: number;
    lastCommit: string;
  };
}

const STATE_FILE = '.director-mode-state.json';

export function getProjectState(projectPath: string): ProjectState {
  const statePath = join(projectPath, STATE_FILE);

  if (!existsSync(statePath)) {
    return {
      currentPhase: 'INITIALIZING',
      lastUpdate: new Date(),
      metrics: {
        totalTests: 0,
        testPassRate: 0,
        lastCommit: 'none',
      },
    };
  }

  const data = readFileSync(statePath, 'utf-8');
  return JSON.parse(data);
}

export function updateProjectState(
  projectPath: string,
  updates: Partial<ProjectState>
): void {
  const current = getProjectState(projectPath);
  const updated = {
    ...current,
    ...updates,
    lastUpdate: new Date(),
  };

  const statePath = join(projectPath, STATE_FILE);
  writeFileSync(statePath, JSON.stringify(updated, null, 2));
}

🧪 Testing Your MCP Server

Step 6: Create Tests

Create tests/tdd.test.ts:

import { describe, it, expect } from 'vitest';
import { checkTDDStatus, suggestTDDNextStep } from '../src/tools/tdd.js';

describe('TDD Tools', () => {
  it('should detect RED phase with failing tests', () => {
    const status = {
      phase: 'RED',
      testCount: 10,
      passingTests: 7,
      failingTests: 3,
      lastRun: new Date(),
    };

    const suggestion = suggestTDDNextStep(status);
    expect(suggestion).toContain('Write minimal code');
  });

  it('should suggest refactoring in GREEN phase', () => {
    const status = {
      phase: 'GREEN',
      testCount: 10,
      passingTests: 10,
      failingTests: 0,
      lastRun: new Date(),
    };

    const suggestion = suggestTDDNextStep(status);
    expect(suggestion).toContain('refactor');
  });
});

Run tests:

npm test

🚀 Running the Server

Step 7: Build and Test

# Build TypeScript
npm run build

# Start the server
npm start

Step 8: Connect from Claude Code

Create or update ~/.claude.json:

{
  "mcpServers": {
    "director-mode": {
      "command": "node",
      "args": ["/path/to/mcp-director-mode-server/dist/index.js"]
    }
  }
}

Restart Claude Code and verify the server is loaded:

claude mcp list

📖 Usage Examples

Example 1: Check TDD Status

In Claude Code:

Check the TDD status for my current project using the director-mode server

Claude will call the check_tdd_status tool and report:

Current TDD Phase: GREEN
Tests: 15 passing, 0 failing
Next Step: Consider refactoring for cleaner code

Example 2: Get TDD Guidance

I'm in the RED phase. What should I do next?

Claude will use the server to suggest:

Write minimal code to pass the failing tests.

Don't worry about perfection yet - focus on making the tests pass.
You can refactor once all tests are green.

🎓 Key MCP Concepts Demonstrated

1. Tool Design

Tools are the primary way MCP servers expose functionality:

{
  name: 'check_tdd_status',
  description: 'Check current TDD cycle status',
  inputSchema: {
    type: 'object',
    properties: {
      projectPath: { type: 'string' }
    },
    required: ['projectPath']
  }
}

Best Practices:

  • Clear, descriptive names
  • Detailed input validation with JSON Schema
  • Helpful descriptions for Claude to understand

2. Resource Management

Resources provide state and configuration:

export function getProjectState(projectPath: string): ProjectState
export function updateProjectState(path, updates): void

Best Practices:

  • Persistent state across sessions
  • Immutable updates (create new state, don’t mutate)
  • JSON serialization for compatibility

3. Error Handling

Always handle errors gracefully:

try {
  const output = execSync('npm test', { cwd: projectPath });
  return parseSuccess(output);
} catch (error) {
  return parseFailure(error.stdout);
}

Best Practices:

  • Never let errors crash the server
  • Provide meaningful error messages
  • Log errors to stderr for debugging

🏗️ Project Structure

mcp-director-mode-server/
├── src/
│   ├── index.ts           # Server entry point
│   ├── tools/
│   │   ├── tdd.ts         # TDD cycle tools
│   │   ├── cycle.ts       # Auto-cycle tools
│   │   └── spec.ts        # SpecKit tools
│   └── resources/
│       └── index.ts       # State management
├── tests/
│   ├── tdd.test.ts        # Tool tests
│   └── integration.test.ts # Integration tests
├── examples/
│   ├── basic-usage.md     # Usage examples
│   └── tdd-workflow.md    # TDD workflow guide
├── docs/
│   └── ARCHITECTURE.md    # Design decisions
├── package.json
├── tsconfig.json
└── vitest.config.ts

🚦 Next Steps

Enhance Your Server

  1. Add More Tools

    • validate_refactor - Check if refactoring is safe
    • generate_tests - Generate test scaffolding
    • run_cycle_step - Execute Auto-Cycle iterations
  2. Add Resources

    • project://state - Current project state
    • project://metrics - Quality metrics dashboard
    • cycle://history - Auto-Cycle history log
  3. Improve Testing

    • Add integration tests with mock MCP clients
    • Test error scenarios
    • Add E2E tests with real Claude Code connections

Publish Your Server

# Publish to npm
npm publish

# Or share via GitHub
git init
git add .
git commit -m "Initial MCP server"
git remote add origin https://github.com/YOUR_USERNAME/your-mcp-server.git
git push -u origin main

📚 Learn More


💡 Tips from the Trenches

Tip 1: Start Simple

Don’t build all 9 tools at once. Start with:

  • ✅ One simple tool (e.g., check_tdd_status)
  • ✅ Basic error handling
  • ✅ One test

Then iterate from there.

Tip 2: Use TypeScript Strict Mode

{
  "compilerOptions": {
    "strict": true  // Catches bugs early
  }
}

Tip 3: Log Everything

console.error('DEBUG: Checking TDD status for', projectPath);

Logs to stderr help debugging without interfering with MCP communication.

Tip 4: Test Locally First

# Test your tool logic
node -e "import('./dist/tools/tdd.js').then(m => console.log(m.checkTDDStatus('.')))"

🎉 Congratulations!

You’ve built a complete MCP Server from scratch! You now understand:

  • ✅ How to create an MCP Server with TypeScript
  • ✅ Tool design and implementation
  • ✅ Resource management patterns
  • ✅ Testing strategies
  • ✅ Best practices for production-ready servers

What’s Next?

  • Share your server with the community
  • Explore more advanced MCP features
  • Build tools for your specific workflows

Happy coding! 🚀