Skip to main content

Build a Complete MCP Server

Capstone project: Create a production-ready MCP server that integrates multiple providers, tools, and real-world systems

Project Overview and Architecture

Congratulations on reaching the capstone project! You'll now apply everything you've learned to build a production-ready MCP server that demonstrates real-world integration patterns and best practices.

What You'll Build: Workspace Intelligence Server

Your capstone project is a Workspace Intelligence Server - an MCP server that provides AI applications with comprehensive access to development environments. This server will integrate multiple data sources and provide powerful tools for project management, development workflows, and team collaboration.

Key Features

πŸ”§ Multi-Provider Architecture: File system, Git, database, API, and configuration providers πŸ› οΈ Comprehensive Toolset: File operations, project management, communication, and development tools ⚑ Production Ready: Logging, monitoring, error handling, and graceful shutdown πŸ”’ Secure by Design: Input validation, access controls, and audit logging πŸ“Š Observable: Health checks, metrics, and performance monitoring πŸ§ͺ Well Tested: Unit tests, integration tests, and end-to-end testing

<CodeExample title="Complete Project Structure" language="text" code={mcp-workspace-server/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ index.ts # Main server entry β”‚ β”œβ”€β”€ config/ β”‚ β”‚ β”œβ”€β”€ index.ts # Configuration management β”‚ β”‚ └── schema.ts # Config validation β”‚ β”œβ”€β”€ providers/ β”‚ β”‚ β”œβ”€β”€ index.ts # Provider registry β”‚ β”‚ β”œβ”€β”€ filesystem.ts # File system provider β”‚ β”‚ β”œβ”€β”€ database.ts # Database provider β”‚ β”‚ β”œβ”€β”€ api.ts # External API provider β”‚ β”‚ └── git.ts # Git repository provider β”‚ β”œβ”€β”€ tools/ β”‚ β”‚ β”œβ”€β”€ index.ts # Tool registry β”‚ β”‚ β”œβ”€β”€ file-ops.ts # File operations β”‚ β”‚ β”œβ”€β”€ project-mgmt.ts # Project management β”‚ β”‚ β”œβ”€β”€ communication.ts # Email/Slack tools β”‚ β”‚ └── development.ts # Dev tools (git, npm, etc.) β”‚ β”œβ”€β”€ middleware/ β”‚ β”‚ β”œβ”€β”€ auth.ts # Authentication β”‚ β”‚ β”œβ”€β”€ rate-limit.ts # Rate limiting β”‚ β”‚ └── logging.ts # Request logging β”‚ β”œβ”€β”€ utils/ β”‚ β”‚ β”œβ”€β”€ validation.ts # Input validation β”‚ β”‚ β”œβ”€β”€ security.ts # Security helpers β”‚ β”‚ └── errors.ts # Error classes β”‚ └── types/ β”‚ β”œβ”€β”€ config.ts # Configuration types β”‚ β”œβ”€β”€ providers.ts # Provider interfaces β”‚ └── tools.ts # Tool interfaces β”œβ”€β”€ tests/ β”‚ β”œβ”€β”€ unit/ # Unit tests β”‚ β”œβ”€β”€ integration/ # Integration tests β”‚ └── fixtures/ # Test data β”œβ”€β”€ docs/ β”‚ β”œβ”€β”€ README.md # Main documentation β”‚ β”œβ”€β”€ API.md # API reference β”‚ └── DEPLOYMENT.md # Deployment guide β”œβ”€β”€ config/ β”‚ β”œβ”€β”€ development.json # Dev configuration β”‚ β”œβ”€β”€ production.json # Prod configuration β”‚ └── test.json # Test configuration β”œβ”€β”€ scripts/ β”‚ β”œβ”€β”€ build.sh # Build script β”‚ β”œβ”€β”€ test.sh # Test script β”‚ └── deploy.sh # Deployment script β”œβ”€β”€ .github/ β”‚ └── workflows/ # CI/CD workflows β”œβ”€β”€ package.json β”œβ”€β”€ tsconfig.json β”œβ”€β”€ jest.config.js β”œβ”€β”€ .eslintrc.js └── .gitignore} />

Architecture Principles

Your server will follow these architectural principles:

  1. Modularity: Each provider and tool is self-contained and independently testable
  2. Extensibility: New providers and tools can be added without modifying core code
  3. Resilience: Failures in one component don't affect others
  4. Observability: Comprehensive logging and monitoring throughout
  5. Security: Defense in depth with multiple security layers
πŸ’‘

Design Philosophy: Build for production from day one. Every component should include error handling, logging, validation, and tests.

Complete Project Structure

Production-ready MCP server organization

Core Server Implementation

Module content not available.

Integrating Multiple Providers

Module content not available.

Comprehensive Tool Integration

Your server needs a robust set of tools that cover the full spectrum of development and project management tasks.

<CodeExample title="Tool Registry Implementation" language="typescript" code={`export class ToolRegistry { private tools: Map<string, Tool> = new Map(); private toolProviders: Map<string, ToolProvider> = new Map(); private executionLog: ExecutionLog[] = []; private rateLimiter: RateLimiter;

constructor(private config: any, private logger: any) { this.rateLimiter = new RateLimiter(config.rateLimiting); }

async registerAll() { const providers = [ new FileOperationTools(this.config.filesystem), new ProjectManagementTools(this.config.project), new CommunicationTools(this.config.communication), new DevelopmentTools(this.config.development) ];

for (const provider of providers) {
  await this.registerToolProvider(provider);
}

}

async executeTool(name: string, args: any): Promise<ToolResult> { // Rate limiting if (!this.rateLimiter.allow(name)) { throw new Error(`Rate limit exceeded for tool: ${name}`); }

// Find tool
const tool = this.tools.get(name);
if (!tool) {
  throw new Error(\`Tool not found: \${name}\`);
}

// Validate arguments
const validation = this.validateArguments(tool, args);
if (!validation.valid) {
  throw new Error(\`Invalid arguments: \${validation.errors.join(', ')}\`);
}

// Execute with monitoring
const startTime = Date.now();
try {
  const result = await tool.execute(args);
  
  // Log execution
  this.logExecution({
    tool: name,
    success: !result.isError,
    duration: Date.now() - startTime,
    timestamp: new Date()
  });
  
  return result;
} catch (error) {
  this.logExecution({
    tool: name,
    success: false,
    duration: Date.now() - startTime,
    error: error.message,
    timestamp: new Date()
  });
  throw error;
}

}

getAllTools(): ToolDefinition[] { return Array.from(this.tools.values()).map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })); }

getExecutionStats(): ExecutionStats { const recent = this.executionLog.filter( log => Date.now() - log.timestamp.getTime() < 24 * 60 * 60 * 1000 );

return {
  totalExecutions: recent.length,
  successRate: recent.filter(log => log.success).length / recent.length,
  averageDuration: recent.reduce((sum, log) => sum + log.duration, 0) / recent.length,
  topTools: this.getTopTools(recent)
};

} }`} />

Advanced Development Tools

<CodeExample title="Development Tools Suite" language="typescript" code={`// src/tools/development.ts export class DevelopmentTools implements ToolProvider { getName() { return 'development'; }

getTools(): ToolDefinition[] { return [ { name: 'git_commit', description: 'Create a git commit with staged changes', inputSchema: { type: 'object', properties: { message: { type: 'string', description: 'Commit message' }, addAll: { type: 'boolean', default: false, description: 'Add all changes before committing' } }, required: ['message'] } }, { name: 'npm_install', description: 'Install npm packages', inputSchema: { type: 'object', properties: { packages: { type: 'array', items: { type: 'string' }, description: 'Package names to install' }, dev: { type: 'boolean', default: false, description: 'Install as dev dependencies' }, global: { type: 'boolean', default: false, description: 'Install globally' } }, required: ['packages'] } }, { name: 'run_tests', description: 'Run project tests', inputSchema: { type: 'object', properties: { pattern: { type: 'string', description: 'Test file pattern' }, watch: { type: 'boolean', default: false, description: 'Run in watch mode' }, coverage: { type: 'boolean', default: false, description: 'Generate coverage report' } } } }, { name: 'build_project', description: 'Build the project', inputSchema: { type: 'object', properties: { mode: { type: 'string', enum: ['development', 'production'], default: 'development', description: 'Build mode' }, clean: { type: 'boolean', default: false, description: 'Clean before build' } } } } ]; }

async executeTool(name: string, args: any): Promise<ToolResult> { switch (name) { case 'git_commit': return this.gitCommit(args); case 'npm_install': return this.npmInstall(args); case 'run_tests': return this.runTests(args); case 'build_project': return this.buildProject(args); default: throw new Error(`Unknown development tool: ${name}`); } }

private async gitCommit(args: any): Promise<ToolResult> { try { const commands = [];

  if (args.addAll) {
    commands.push('git add .');
  }
  
  commands.push(\`git commit -m "\${args.message}"\`);
  
  const results = [];
  for (const command of commands) {
    const result = await execAsync(command, { cwd: this.config.repositoryPath });
    results.push(result.stdout);
  }
  
  return {
    content: [
      {
        type: 'text',
        text: \`Git commit successful:\\n\${results.join('\\n')}\`
      }
    ]
  };
} catch (error) {
  return {
    content: [
      {
        type: 'text',
        text: \`Git commit failed: \${error.message}\`
      }
    ],
    isError: true
  };
}

}

private async npmInstall(args: any): Promise<ToolResult> { try { const flags = []; if (args.dev) flags.push('--save-dev'); if (args.global) flags.push('--global');

  const command = \`npm install \${args.packages.join(' ')} \${flags.join(' ')}\`;
  const result = await execAsync(command, { 
    cwd: this.config.projectPath,
    timeout: 300000 // 5 minutes
  });
  
  return {
    content: [
      {
        type: 'text',
        text: \`Packages installed successfully:\\n\${result.stdout}\`
      }
    ]
  };
} catch (error) {
  return {
    content: [
      {
        type: 'text',
        text: \`npm install failed: \${error.message}\`
      }
    ],
    isError: true
  };
}

} }`} />

Communication Tools

<CodeExample title="Communication Tools" language="typescript" code={`// src/tools/communication.ts export class CommunicationTools implements ToolProvider { getName() { return 'communication'; }

getTools(): ToolDefinition[] { const tools = [ { name: 'send_email', description: 'Send an email notification', inputSchema: { type: 'object', properties: { to: { type: 'string', description: 'Recipient email address' }, subject: { type: 'string', description: 'Email subject' }, body: { type: 'string', description: 'Email body content' }, priority: { type: 'string', enum: ['low', 'normal', 'high'], default: 'normal', description: 'Email priority' } }, required: ['to', 'subject', 'body'] } } ];

// Add Slack tools if token is configured
if (this.config.slackToken) {
  tools.push({
    name: 'slack_message',
    description: 'Send a message to Slack channel',
    inputSchema: {
      type: 'object',
      properties: {
        channel: { type: 'string', description: 'Slack channel name or ID' },
        message: { type: 'string', description: 'Message text' },
        urgent: { type: 'boolean', default: false, description: 'Mark as urgent' },
        threadId: { type: 'string', description: 'Reply to thread (optional)' }
      },
      required: ['channel', 'message']
    }
  });
}

return tools;

}

async executeTool(name: string, args: any): Promise<ToolResult> { switch (name) { case 'send_email': return this.sendEmail(args); case 'slack_message': return this.sendSlackMessage(args); default: throw new Error(`Unknown communication tool: ${name}`); } }

private async sendSlackMessage(args: any): Promise<ToolResult> { try { const response = await fetch('https://slack.com/api/chat.postMessage', { method: 'POST', headers: { 'Authorization': `Bearer ${this.config.slackToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ channel: args.channel, text: args.message, thread_ts: args.threadId, ...(args.urgent && { attachments: [{ color: 'danger', text: '🚨 URGENT MESSAGE 🚨' }] }) }) });

  const result = await response.json();
  
  if (!result.ok) {
    throw new Error(result.error || 'Slack API error');
  }
  
  return {
    content: [
      {
        type: 'text',
        text: \`Message sent to \${args.channel} successfully\`
      }
    ]
  };
} catch (error) {
  return {
    content: [
      {
        type: 'text',
        text: \`Failed to send Slack message: \${error.message}\`
      }
    ],
    isError: true
  };
}

} }`} />

Tool Registry Implementation

Centralized tool management and execution

Testing and Deployment

A production server requires comprehensive testing and proper deployment processes.

<CodeExample title="Comprehensive Test Suite" language="typescript" code={`// tests/integration/server.test.ts

describe('MCP Workspace Server Integration', () => { let server: MCPWorkspaceServer; let transport: TestTransport;

beforeEach(async () => { // Set up test environment process.env.NODE_ENV = 'test';

server = new MCPWorkspaceServer();
transport = new TestTransport();

await server.start(transport);

});

afterEach(async () => { await server.shutdown(); });

describe('Resources', () => { test('should list all available resources', async () => { const response = await transport.request('resources/list', {});

  expect(response.resources).toBeDefined();
  expect(Array.isArray(response.resources)).toBe(true);
  expect(response.resources.length).toBeGreaterThan(0);
});

test('should read file system resources', async () => {
  const response = await transport.request('resources/read', {
    uri: 'file://test-file.txt'
  });
  
  expect(response.contents).toBeDefined();
  expect(response.contents[0].text).toContain('test content');
});

test('should handle invalid resource URIs', async () => {
  await expect(
    transport.request('resources/read', { uri: 'invalid://uri' })
  ).rejects.toThrow('No provider found');
});

});

describe('Tools', () => { test('should list all available tools', async () => { const response = await transport.request('tools/list', {});

  expect(response.tools).toBeDefined();
  expect(Array.isArray(response.tools)).toBe(true);
  
  // Check for expected tools
  const toolNames = response.tools.map(t => t.name);
  expect(toolNames).toContain('read_file');
  expect(toolNames).toContain('write_file');
});

test('should execute file operations', async () => {
  const response = await transport.request('tools/call', {
    name: 'write_file',
    arguments: {
      path: 'test-output.txt',
      content: 'Hello, MCP!'
    }
  });
  
  expect(response.isError).toBeFalsy();
  expect(response.content[0].text).toContain('Successfully wrote');
});

test('should validate tool parameters', async () => {
  await expect(
    transport.request('tools/call', {
      name: 'write_file',
      arguments: {
        // Missing required 'content' parameter
        path: 'test.txt'
      }
    })
  ).rejects.toThrow('Invalid arguments');
});

});

describe('Health and Monitoring', () => { test('should provide health status', async () => { const response = await transport.request('health', {});

  expect(response.status).toBe('healthy');
  expect(response.providers).toBeGreaterThan(0);
  expect(response.tools).toBeGreaterThan(0);
});

}); });`} />

Docker Deployment

<CodeExample title="Production Deployment Configuration" language="dockerfile" code={`# Dockerfile FROM node:18-alpine

Install git and other dependencies

RUN apk add --no-cache git

Create app directory

WORKDIR /app

Copy package files

COPY package*.json ./ COPY tsconfig.json ./

Install dependencies

RUN npm ci --only=production

Copy source code

COPY src/ ./src/ COPY config/ ./config/

Build the application

RUN npm run build

Create non-root user

RUN addgroup -g 1001 -S nodejs RUN adduser -S mcp -u 1001

Create workspace directory

RUN mkdir -p /workspace && chown mcp:nodejs /workspace

Switch to non-root user

USER mcp

Expose health check port (if using HTTP transport)

EXPOSE 3000

Health check

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node dist/health-check.js

Start the server

CMD ["npm", "start"]`} />

CI/CD Pipeline

<CodeExample title="GitHub Actions Workflow" language="yaml" code={`# .github/workflows/ci.yml name: CI/CD Pipeline

on: push: branches: [ main, develop ] pull_request: branches: [ main ]

jobs: test: runs-on: ubuntu-latest

strategy:
  matrix:
    node-version: [18.x, 20.x]

steps:
- uses: actions/checkout@v3

- name: Use Node.js ${{ matrix.node-version }}
  uses: actions/setup-node@v3
  with:
    node-version: ${{ matrix.node-version }}
    cache: 'npm'

- name: Install dependencies
  run: npm ci

- name: Run linting
  run: npm run lint

- name: Run type checking
  run: npm run type-check

- name: Run tests
  run: npm run test:coverage

- name: Upload coverage reports
  uses: codecov/codecov-action@v3
  with:
    file: ./coverage/lcov.info

build: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v3

- name: Build Docker image
  run: docker build -t mcp-workspace-server .

- name: Run security scan
  run: docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image mcp-workspace-server

deploy: needs: [test, build] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main'

steps:
- name: Deploy to production
  run: echo "Deploy to production environment"`}

/>

Comprehensive Test Suite

Unit and integration tests for MCP server

Production Deployment Configuration

Docker and deployment configuration