Chapter 2: Development Environment Setup
Haiyue
28min
Chapter 2: Development Environment Setup
Learning Objectives
- Install and configure Node.js development environment
- Understand MCP SDK and related tools
- Set up your first MCP Server project
- Configure development debugging tools and testing environment
- Understand the basic project structure of MCP Server
2.1 Environment Preparation
Node.js Environment Installation
# Node.js 18+ is recommended
# Method 1: Direct download and installation
# Visit https://nodejs.org/ to download the LTS version
# Method 2: Use nvm to manage multiple Node.js versions
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
# Install and use Node.js 20
nvm install 20
nvm use 20
nvm alias default 20
# Verify installation
node --version
npm --version
TypeScript Development Environment
# Install TypeScript globally
npm install -g typescript ts-node nodemon
# Verify TypeScript installation
tsc --version
ts-node --version
Recommended Development Tools
// VS Code extension recommendations (.vscode/extensions.json)
{
"recommendations": [
"ms-vscode.vscode-typescript-next",
"bradlc.vscode-tailwindcss",
"esbenp.prettier-vscode",
"ms-vscode.vscode-json",
"redhat.vscode-yaml"
]
}
2.2 MCP SDK Installation and Configuration
Installing MCP SDK
# Create new project directory
mkdir my-mcp-server
cd my-mcp-server
# Initialize npm project
npm init -y
# Install MCP SDK
npm install @modelcontextprotocol/sdk
# Install development dependencies
npm install -D typescript @types/node ts-node nodemon prettier
Project Structure Setup
my-mcp-server/
├── package.json
├── tsconfig.json
├── .gitignore
├── .prettierrc
├── README.md
├── src/
│ ├── index.ts
│ ├── server.ts
│ ├── tools/
│ │ ├── index.ts
│ │ └── example.ts
│ ├── resources/
│ │ ├── index.ts
│ │ └── static.ts
│ ├── prompts/
│ │ ├── index.ts
│ │ └── templates.ts
│ └── types/
│ └── index.ts
├── dist/
├── tests/
│ ├── integration/
│ └── unit/
└── docs/
Basic Configuration Files
// package.json
{
"name": "my-mcp-server",
"version": "1.0.0",
"description": "My first MCP Server",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts",
"test": "jest",
"lint": "prettier --check src/",
"lint:fix": "prettier --write src/",
"clean": "rm -rf dist/"
},
"keywords": ["mcp", "ai", "tools"],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"ts-node": "^10.9.0",
"nodemon": "^3.0.0",
"prettier": "^3.0.0",
"jest": "^29.0.0",
"@types/jest": "^29.0.0"
}
}
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "Node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests"]
}
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}
# .gitignore
node_modules/
dist/
.env
.env.local
*.log
.DS_Store
coverage/
.nyc_output/
2.3 Creating Your First MCP Server
Basic Server Structure
// src/types/index.ts
export interface ServerConfig {
name: string;
version: string;
description?: string;
author?: string;
capabilities: {
tools?: boolean;
resources?: boolean;
prompts?: boolean;
logging?: boolean;
};
}
export interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
handler: (args: any) => Promise<any>;
}
export interface ResourceDefinition {
uri: string;
name: string;
description?: string;
mimeType?: string;
handler: () => Promise<{ content: any; mimeType?: string }>;
}
export interface PromptDefinition {
name: string;
description?: string;
arguments?: Array<{
name: string;
description: string;
required?: boolean;
}>;
handler: (args: any) => Promise<{
messages: Array<{
role: 'user' | 'assistant' | 'system';
content: {
type: 'text' | 'image';
text?: string;
data?: string;
};
}>;
}>;
}
// src/server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import type { ServerConfig, ToolDefinition, ResourceDefinition, PromptDefinition } from './types/index.js';
export class MCPServer {
private server: Server;
private config: ServerConfig;
private tools: Map<string, ToolDefinition> = new Map();
private resources: Map<string, ResourceDefinition> = new Map();
private prompts: Map<string, PromptDefinition> = new Map();
constructor(config: ServerConfig) {
this.config = config;
this.server = new Server(
{
name: config.name,
version: config.version,
},
{
capabilities: {
tools: config.capabilities.tools ? {} : undefined,
resources: config.capabilities.resources ? { subscribe: false } : undefined,
prompts: config.capabilities.prompts ? {} : undefined,
},
}
);
this.setupHandlers();
}
private setupHandlers(): void {
// Tool-related handlers
if (this.config.capabilities.tools) {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: Array.from(this.tools.values()).map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`Unknown tool: ${name}`);
}
try {
const result = await tool.handler(args || {});
return {
content: [
{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
throw new Error(`Tool execution failed: ${error.message}`);
}
});
}
// Resource-related handlers
if (this.config.capabilities.resources) {
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: Array.from(this.resources.values()).map(resource => ({
uri: resource.uri,
name: resource.name,
description: resource.description,
mimeType: resource.mimeType,
})),
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
const resource = this.resources.get(uri);
if (!resource) {
throw new Error(`Unknown resource: ${uri}`);
}
try {
const result = await resource.handler();
return {
contents: [
{
uri: resource.uri,
mimeType: result.mimeType || resource.mimeType || 'text/plain',
text: typeof result.content === 'string' ? result.content : JSON.stringify(result.content),
},
],
};
} catch (error) {
throw new Error(`Resource read failed: ${error.message}`);
}
});
}
// Prompt template-related handlers
if (this.config.capabilities.prompts) {
this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: Array.from(this.prompts.values()).map(prompt => ({
name: prompt.name,
description: prompt.description,
arguments: prompt.arguments,
})),
}));
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const prompt = this.prompts.get(name);
if (!prompt) {
throw new Error(`Unknown prompt: ${name}`);
}
try {
const result = await prompt.handler(args || {});
return result;
} catch (error) {
throw new Error(`Prompt generation failed: ${error.message}`);
}
});
}
}
// Register tool
addTool(tool: ToolDefinition): void {
if (!this.config.capabilities.tools) {
throw new Error('Tools capability is not enabled');
}
this.tools.set(tool.name, tool);
}
// Register resource
addResource(resource: ResourceDefinition): void {
if (!this.config.capabilities.resources) {
throw new Error('Resources capability is not enabled');
}
this.resources.set(resource.uri, resource);
}
// Register prompt template
addPrompt(prompt: PromptDefinition): void {
if (!this.config.capabilities.prompts) {
throw new Error('Prompts capability is not enabled');
}
this.prompts.set(prompt.name, prompt);
}
// Start server
async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error(`${this.config.name} v${this.config.version} started successfully`);
console.error(`Capabilities: ${JSON.stringify(this.server.getCapabilities())}`);
}
}
Example Tool Implementation
// src/tools/example.ts
import type { ToolDefinition } from '../types/index.js';
// Hello World tool
export const helloTool: ToolDefinition = {
name: 'hello',
description: 'Say hello to the specified person',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'The name of the person to greet',
},
greeting: {
type: 'string',
description: 'The greeting message, defaults to "Hello"',
default: 'Hello',
},
},
required: ['name'],
},
handler: async (args) => {
const { name, greeting = 'Hello' } = args;
return `${greeting}, ${name}! Welcome to MCP Server!`;
},
};
// Calculate tool
export const calculateTool: ToolDefinition = {
name: 'calculate',
description: 'Perform basic mathematical calculations',
inputSchema: {
type: 'object',
properties: {
expression: {
type: 'string',
description: 'Math expression, e.g., "2 + 3 * 4"',
},
},
required: ['expression'],
},
handler: async (args) => {
const { expression } = args;
// Simple math expression evaluation (basic operations only)
const sanitized = expression.replace(/[^0-9+\-*/().]/g, '');
try {
// Note: In production, use a safer expression evaluator
const result = Function(`"use strict"; return (${sanitized})`)();
return {
expression: expression,
result: result,
sanitized: sanitized,
};
} catch (error) {
throw new Error(`Invalid expression: ${expression}`);
}
},
};
// Current time tool
export const currentTimeTool: ToolDefinition = {
name: 'current_time',
description: 'Get the current time',
inputSchema: {
type: 'object',
properties: {
format: {
type: 'string',
description: 'Time format: "iso" or "readable"',
enum: ['iso', 'readable'],
default: 'iso',
},
timezone: {
type: 'string',
description: 'Timezone, e.g., "Asia/Shanghai"',
},
},
},
handler: async (args) => {
const { format = 'iso', timezone } = args;
const now = new Date();
let result: string;
if (format === 'readable') {
result = timezone
? now.toLocaleString('en-US', { timeZone: timezone })
: now.toLocaleString('en-US');
} else {
result = now.toISOString();
}
return {
timestamp: result,
format: format,
timezone: timezone || 'local',
};
},
};
// src/tools/index.ts
export { helloTool, calculateTool, currentTimeTool } from './example.js';
Example Resource Implementation
// src/resources/static.ts
import type { ResourceDefinition } from '../types/index.js';
// System information resource
export const systemInfoResource: ResourceDefinition = {
uri: 'system://info',
name: 'System Information',
description: 'Basic system information',
mimeType: 'application/json',
handler: async () => {
const info = {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
memory: process.memoryUsage(),
uptime: process.uptime(),
timestamp: new Date().toISOString(),
};
return {
content: info,
mimeType: 'application/json',
};
},
};
// Configuration resource
export const configResource: ResourceDefinition = {
uri: 'config://app',
name: 'Application Configuration',
description: 'Application configuration information',
mimeType: 'application/json',
handler: async () => {
const config = {
name: 'My MCP Server',
version: '1.0.0',
features: ['tools', 'resources', 'prompts'],
debug: process.env.NODE_ENV === 'development',
capabilities: {
maxConcurrentRequests: 10,
timeout: 30000,
},
};
return {
content: config,
mimeType: 'application/json',
};
},
};
// Help documentation resource
export const helpResource: ResourceDefinition = {
uri: 'help://usage',
name: 'Usage Help',
description: 'Usage help documentation',
mimeType: 'text/markdown',
handler: async () => {
const helpContent = `
# MCP Server Usage Guide
## Available Tools
### hello
Say hello to the specified person
- **Parameters**: name (required), greeting (optional)
- **Example**: {"name": "World", "greeting": "Hi"}
### calculate
Perform basic mathematical calculations
- **Parameters**: expression (required)
- **Example**: {"expression": "2 + 3 * 4"}
### current_time
Get the current time
- **Parameters**: format (optional), timezone (optional)
- **Example**: {"format": "readable", "timezone": "Asia/Shanghai"}
## Available Resources
- **system://info**: System information
- **config://app**: Application configuration
- **help://usage**: Usage help
## Getting Started
Connect to this server using an MCP client, then you can call tools and read resources.
`.trim();
return {
content: helpContent,
mimeType: 'text/markdown',
};
},
};
// src/resources/index.ts
export { systemInfoResource, configResource, helpResource } from './static.js';
Example Prompt Template Implementation
// src/prompts/templates.ts
import type { PromptDefinition } from '../types/index.js';
// Code review prompt template
export const codeReviewPrompt: PromptDefinition = {
name: 'code_review',
description: 'Code review prompt template',
arguments: [
{
name: 'code',
description: 'The code to review',
required: true,
},
{
name: 'language',
description: 'Programming language',
required: false,
},
],
handler: async (args) => {
const { code, language = 'unknown' } = args;
return {
messages: [
{
role: 'system',
content: {
type: 'text',
text: `You are an experienced code review expert. Please carefully review the following ${language} code, focusing on:
1. Code quality and readability
2. Potential bugs and security issues
3. Performance optimization suggestions
4. Best practice compliance
Please provide specific improvement suggestions and example code.`,
},
},
{
role: 'user',
content: {
type: 'text',
text: `Please review the following code:
\`\`\`${language}
${code}
\`\`\``,
},
},
],
};
},
};
// Technical documentation generation prompt template
export const docGenerationPrompt: PromptDefinition = {
name: 'generate_docs',
description: 'Technical documentation generation prompt template',
arguments: [
{
name: 'topic',
description: 'Documentation topic',
required: true,
},
{
name: 'audience',
description: 'Target audience (beginner/intermediate/advanced)',
required: false,
},
{
name: 'format',
description: 'Documentation format (markdown/html/plain)',
required: false,
},
],
handler: async (args) => {
const { topic, audience = 'intermediate', format = 'markdown' } = args;
return {
messages: [
{
role: 'system',
content: {
type: 'text',
text: `You are a technical writing expert. Please generate technical documentation about "${topic}" in ${format} format for ${audience}-level readers.
The documentation should include:
1. Overview and objectives
2. Core concept explanations
3. Practical examples and code
4. Best practices
5. Common issues and solutions
Please ensure the content is clear, accurate, and practical.`,
},
},
{
role: 'user',
content: {
type: 'text',
text: `Please generate technical documentation about "${topic}".`,
},
},
],
};
},
};
// src/prompts/index.ts
export { codeReviewPrompt, docGenerationPrompt } from './templates.js';
Main Entry File
// src/index.ts
import { MCPServer } from './server.js';
import { helloTool, calculateTool, currentTimeTool } from './tools/index.js';
import { systemInfoResource, configResource, helpResource } from './resources/index.js';
import { codeReviewPrompt, docGenerationPrompt } from './prompts/index.js';
async function main() {
// Create MCP server
const server = new MCPServer({
name: 'my-mcp-server',
version: '1.0.0',
description: 'My first MCP Server with examples',
author: 'Your Name',
capabilities: {
tools: true,
resources: true,
prompts: true,
logging: true,
},
});
// Register tools
server.addTool(helloTool);
server.addTool(calculateTool);
server.addTool(currentTimeTool);
// Register resources
server.addResource(systemInfoResource);
server.addResource(configResource);
server.addResource(helpResource);
// Register prompt templates
server.addPrompt(codeReviewPrompt);
server.addPrompt(docGenerationPrompt);
// Start server
await server.start();
}
// Error handling
main().catch((error) => {
console.error('Server startup failed:', error);
process.exit(1);
});
2.4 Development and Debugging Tools
Development Script Configuration
// package.json - scripts section
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts",
"dev:debug": "nodemon --exec 'node --inspect=0.0.0.0:9229 -r ts-node/register src/index.ts'",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "prettier --check src/ && eslint src/",
"lint:fix": "prettier --write src/ && eslint src/ --fix",
"clean": "rm -rf dist/",
"check-types": "tsc --noEmit"
}
}
Debug Configuration
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug MCP Server",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/index.ts",
"runtimeArgs": ["-r", "ts-node/register"],
"env": {
"NODE_ENV": "development"
},
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"name": "Attach to Process",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true,
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
}
MCP Debugging Tool
// src/utils/debug.ts
export class MCPDebugger {
private static instance: MCPDebugger;
private debugEnabled: boolean;
private constructor() {
this.debugEnabled = process.env.NODE_ENV === 'development' || process.env.MCP_DEBUG === 'true';
}
static getInstance(): MCPDebugger {
if (!MCPDebugger.instance) {
MCPDebugger.instance = new MCPDebugger();
}
return MCPDebugger.instance;
}
log(message: string, data?: any): void {
if (!this.debugEnabled) return;
const timestamp = new Date().toISOString();
console.error(`[DEBUG ${timestamp}] ${message}`);
if (data) {
console.error(JSON.stringify(data, null, 2));
}
}
logRequest(method: string, params: any): void {
this.log(`Request: ${method}`, params);
}
logResponse(method: string, response: any): void {
this.log(`Response: ${method}`, response);
}
logError(error: Error, context?: string): void {
const timestamp = new Date().toISOString();
console.error(`[ERROR ${timestamp}] ${context || 'Unknown'}: ${error.message}`);
if (this.debugEnabled) {
console.error(error.stack);
}
}
}
// Usage example
const debugger = MCPDebugger.getInstance();
debugger.log('Server starting...');
2.5 Test Environment Configuration
Jest Test Configuration
// jest.config.js
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
transform: {
'^.+\\.ts$': ['ts-jest', {
useESM: true
}]
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/__tests__/**',
'!src/**/*.spec.ts',
'!src/**/*.test.ts'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html']
};
Example Test File
// tests/unit/tools.test.ts
import { helloTool, calculateTool, currentTimeTool } from '../../src/tools/example.js';
describe('Example Tools', () => {
describe('helloTool', () => {
it('should greet with default message', async () => {
const result = await helloTool.handler({ name: 'World' });
expect(result).toBe('Hello, World! Welcome to MCP Server!');
});
it('should greet with custom message', async () => {
const result = await helloTool.handler({ name: 'Alice', greeting: 'Hi' });
expect(result).toBe('Hi, Alice! Welcome to MCP Server!');
});
});
describe('calculateTool', () => {
it('should calculate simple expressions', async () => {
const result = await calculateTool.handler({ expression: '2 + 3' });
expect(result.result).toBe(5);
expect(result.expression).toBe('2 + 3');
});
it('should handle complex expressions', async () => {
const result = await calculateTool.handler({ expression: '2 + 3 * 4' });
expect(result.result).toBe(14);
});
it('should throw error for invalid expressions', async () => {
await expect(calculateTool.handler({ expression: 'invalid' }))
.rejects.toThrow('Invalid expression');
});
});
describe('currentTimeTool', () => {
it('should return ISO format by default', async () => {
const result = await currentTimeTool.handler({});
expect(result.format).toBe('iso');
expect(typeof result.timestamp).toBe('string');
});
it('should return readable format', async () => {
const result = await currentTimeTool.handler({ format: 'readable' });
expect(result.format).toBe('readable');
});
});
});
Integration Tests
// tests/integration/server.test.ts
import { MCPServer } from '../../src/server.js';
import { helloTool } from '../../src/tools/example.js';
describe('MCPServer Integration', () => {
let server: MCPServer;
beforeEach(() => {
server = new MCPServer({
name: 'test-server',
version: '1.0.0',
capabilities: {
tools: true,
resources: false,
prompts: false,
},
});
});
it('should register and execute tools', async () => {
server.addTool(helloTool);
// Test tool registration
const hasHelloTool = server['tools'].has('hello');
expect(hasHelloTool).toBe(true);
// Test tool execution
const tool = server['tools'].get('hello');
const result = await tool!.handler({ name: 'Test' });
expect(result).toContain('Hello, Test!');
});
it('should throw error for invalid tool capabilities', () => {
const serverWithoutTools = new MCPServer({
name: 'test-server',
version: '1.0.0',
capabilities: { tools: false },
});
expect(() => {
serverWithoutTools.addTool(helloTool);
}).toThrow('Tools capability is not enabled');
});
});
2.6 Build and Run
Building the Project
# Install dependencies
npm install
# Type checking
npm run check-types
# Build project
npm run build
# Run tests
npm test
# Code formatting
npm run lint:fix
Running Development Server
# Development mode
npm run dev
# Debug mode
npm run dev:debug
# Production mode
npm run build && npm start
Validating MCP Server
# Test using stdio transport
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | node dist/index.js
# Expected response should contain server capability information
Summary
Through this chapter, we have established a complete MCP Server development environment:
- Environment Configuration: Node.js, TypeScript, development tools
- Project Structure: Standard MCP Server project organization
- Core Framework: Reusable MCPServer class
- Example Implementation: Complete examples of tools, resources, and prompt templates
- Development Tools: Debugging, testing, and build scripts
- Best Practices: Type safety, error handling, modular design
Next, we will dive into the technical specifications of the MCP protocol to understand message formats and communication mechanisms.
Extension Exercises
- Add a new tool that implements file reading functionality
- Create an environment variable resource that provides system environment information
- Write a prompt template for JSON formatting
- Add more unit tests for existing tools
- Implement a simple logging functionality