第 2 章:开发环境搭建
2025/9/1大约 10 分钟
第 2 章:开发环境搭建
学习目标
- 安装和配置Node.js开发环境
- 了解MCP SDK和相关工具
- 搭建第一个MCP Server项目
- 配置开发调试工具和测试环境
- 理解MCP Server的基本项目结构
2.1 环境准备
Node.js环境安装
# 推荐使用Node.js 18+版本
# 方法1:直接下载安装
# 访问 https://nodejs.org/ 下载LTS版本
# 方法2:使用nvm管理多版本Node.js
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
# 安装并使用Node.js 20
nvm install 20
nvm use 20
nvm alias default 20
# 验证安装
node --version
npm --version
TypeScript开发环境
# 全局安装TypeScript
npm install -g typescript ts-node nodemon
# 验证TypeScript安装
tsc --version
ts-node --version
开发工具推荐
// VS Code扩展推荐 (.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安装和配置
安装MCP SDK
# 创建新项目目录
mkdir my-mcp-server
cd my-mcp-server
# 初始化npm项目
npm init -y
# 安装MCP SDK
npm install @modelcontextprotocol/sdk
# 安装开发依赖
npm install -D typescript @types/node ts-node nodemon prettier
项目结构设置
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/
基础配置文件
// 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 创建第一个MCP Server
基础服务器结构
// 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 {
// 工具相关处理器
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}`);
}
});
}
// 资源相关处理器
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}`);
}
});
}
// 提示模板相关处理器
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}`);
}
});
}
}
// 注册工具
addTool(tool: ToolDefinition): void {
if (!this.config.capabilities.tools) {
throw new Error('Tools capability is not enabled');
}
this.tools.set(tool.name, tool);
}
// 注册资源
addResource(resource: ResourceDefinition): void {
if (!this.config.capabilities.resources) {
throw new Error('Resources capability is not enabled');
}
this.resources.set(resource.uri, resource);
}
// 注册提示模板
addPrompt(prompt: PromptDefinition): void {
if (!this.config.capabilities.prompts) {
throw new Error('Prompts capability is not enabled');
}
this.prompts.set(prompt.name, prompt);
}
// 启动服务器
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())}`);
}
}
示例工具实现
// src/tools/example.ts
import type { ToolDefinition } from '../types/index.js';
// Hello World工具
export const helloTool: ToolDefinition = {
name: 'hello',
description: '向指定的人说hello',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: '要问候的人的名字',
},
greeting: {
type: 'string',
description: '问候语,默认为"Hello"',
default: 'Hello',
},
},
required: ['name'],
},
handler: async (args) => {
const { name, greeting = 'Hello' } = args;
return `${greeting}, ${name}! 欢迎使用MCP Server!`;
},
};
// 计算工具
export const calculateTool: ToolDefinition = {
name: 'calculate',
description: '执行基本的数学计算',
inputSchema: {
type: 'object',
properties: {
expression: {
type: 'string',
description: '数学表达式,如 "2 + 3 * 4"',
},
},
required: ['expression'],
},
handler: async (args) => {
const { expression } = args;
// 简单的数学表达式评估(仅支持基本运算)
const sanitized = expression.replace(/[^0-9+\-*/().]/g, '');
try {
// 注意:在生产环境中应该使用更安全的表达式评估器
const result = Function(`"use strict"; return (${sanitized})`)();
return {
expression: expression,
result: result,
sanitized: sanitized,
};
} catch (error) {
throw new Error(`Invalid expression: ${expression}`);
}
},
};
// 获取当前时间工具
export const currentTimeTool: ToolDefinition = {
name: 'current_time',
description: '获取当前时间',
inputSchema: {
type: 'object',
properties: {
format: {
type: 'string',
description: '时间格式:"iso" 或 "readable"',
enum: ['iso', 'readable'],
default: 'iso',
},
timezone: {
type: 'string',
description: '时区,如 "Asia/Shanghai"',
},
},
},
handler: async (args) => {
const { format = 'iso', timezone } = args;
const now = new Date();
let result: string;
if (format === 'readable') {
result = timezone
? now.toLocaleString('zh-CN', { timeZone: timezone })
: now.toLocaleString('zh-CN');
} else {
result = now.toISOString();
}
return {
timestamp: result,
format: format,
timezone: timezone || 'local',
};
},
};
// src/tools/index.ts
export { helloTool, calculateTool, currentTimeTool } from './example.js';
示例资源实现
// src/resources/static.ts
import type { ResourceDefinition } from '../types/index.js';
// 系统信息资源
export const systemInfoResource: ResourceDefinition = {
uri: 'system://info',
name: 'System Information',
description: '系统基本信息',
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',
};
},
};
// 配置资源
export const configResource: ResourceDefinition = {
uri: 'config://app',
name: 'Application Configuration',
description: '应用配置信息',
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',
};
},
};
// 帮助文档资源
export const helpResource: ResourceDefinition = {
uri: 'help://usage',
name: 'Usage Help',
description: '使用帮助文档',
mimeType: 'text/markdown',
handler: async () => {
const helpContent = `
# MCP Server 使用帮助
## 可用工具
### hello
向指定的人说hello
- **参数**: name (必填), greeting (可选)
- **示例**: {"name": "World", "greeting": "Hi"}
### calculate
执行基本的数学计算
- **参数**: expression (必填)
- **示例**: {"expression": "2 + 3 * 4"}
### current_time
获取当前时间
- **参数**: format (可选), timezone (可选)
- **示例**: {"format": "readable", "timezone": "Asia/Shanghai"}
## 可用资源
- **system://info**: 系统信息
- **config://app**: 应用配置
- **help://usage**: 使用帮助
## 开始使用
使用MCP客户端连接到此服务器,然后就可以调用工具和读取资源了。
`.trim();
return {
content: helpContent,
mimeType: 'text/markdown',
};
},
};
// src/resources/index.ts
export { systemInfoResource, configResource, helpResource } from './static.js';
示例提示模板实现
// src/prompts/templates.ts
import type { PromptDefinition } from '../types/index.js';
// 代码审查提示模板
export const codeReviewPrompt: PromptDefinition = {
name: 'code_review',
description: '代码审查提示模板',
arguments: [
{
name: 'code',
description: '要审查的代码',
required: true,
},
{
name: 'language',
description: '编程语言',
required: false,
},
],
handler: async (args) => {
const { code, language = 'unknown' } = args;
return {
messages: [
{
role: 'system',
content: {
type: 'text',
text: `你是一个经验丰富的代码审查专家。请仔细审查以下${language}代码,关注以下方面:
1. 代码质量和可读性
2. 潜在的bug和安全问题
3. 性能优化建议
4. 最佳实践遵循情况
请提供具体的改进建议和示例代码。`,
},
},
{
role: 'user',
content: {
type: 'text',
text: `请审查以下代码:
\`\`\`${language}
${code}
\`\`\``,
},
},
],
};
},
};
// 技术文档生成提示模板
export const docGenerationPrompt: PromptDefinition = {
name: 'generate_docs',
description: '生成技术文档的提示模板',
arguments: [
{
name: 'topic',
description: '文档主题',
required: true,
},
{
name: 'audience',
description: '目标受众 (beginner/intermediate/advanced)',
required: false,
},
{
name: 'format',
description: '文档格式 (markdown/html/plain)',
required: false,
},
],
handler: async (args) => {
const { topic, audience = 'intermediate', format = 'markdown' } = args;
return {
messages: [
{
role: 'system',
content: {
type: 'text',
text: `你是一个技术写作专家。请为${audience}级别的读者生成关于"${topic}"的${format}格式技术文档。
文档应该包括:
1. 概述和目标
2. 核心概念解释
3. 实际示例和代码
4. 最佳实践
5. 常见问题和解决方案
请确保内容清晰、准确、实用。`,
},
},
{
role: 'user',
content: {
type: 'text',
text: `请生成关于"${topic}"的技术文档。`,
},
},
],
};
},
};
// src/prompts/index.ts
export { codeReviewPrompt, docGenerationPrompt } from './templates.js';
主入口文件
// 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() {
// 创建MCP服务器
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,
},
});
// 注册工具
server.addTool(helloTool);
server.addTool(calculateTool);
server.addTool(currentTimeTool);
// 注册资源
server.addResource(systemInfoResource);
server.addResource(configResource);
server.addResource(helpResource);
// 注册提示模板
server.addPrompt(codeReviewPrompt);
server.addPrompt(docGenerationPrompt);
// 启动服务器
await server.start();
}
// 错误处理
main().catch((error) => {
console.error('Server startup failed:', error);
process.exit(1);
});
2.4 开发和调试工具
开发脚本配置
// package.json - scripts部分
{
"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"
}
}
调试配置
// .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调试工具
// 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);
}
}
}
// 使用示例
const debugger = MCPDebugger.getInstance();
debugger.log('Server starting...');
2.5 测试环境配置
Jest测试配置
// 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']
};
示例测试文件
// 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! 欢迎使用MCP Server!');
});
it('should greet with custom message', async () => {
const result = await helloTool.handler({ name: 'Alice', greeting: 'Hi' });
expect(result).toBe('Hi, Alice! 欢迎使用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');
});
});
});
集成测试
// 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);
// 测试工具注册
const hasHelloTool = server['tools'].has('hello');
expect(hasHelloTool).toBe(true);
// 测试工具执行
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 构建和运行
构建项目
# 安装依赖
npm install
# 类型检查
npm run check-types
# 构建项目
npm run build
# 运行测试
npm test
# 代码格式化
npm run lint:fix
运行开发服务器
# 开发模式运行
npm run dev
# 调试模式运行
npm run dev:debug
# 生产模式运行
npm run build && npm start
验证MCP Server
# 使用stdio传输测试
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
# 预期响应应该包含服务器能力信息
小结
通过本章的学习,我们已经建立了完整的MCP Server开发环境:
- 环境配置:Node.js、TypeScript、开发工具
- 项目结构:标准的MCP Server项目组织
- 核心框架:可复用的MCPServer类
- 示例实现:工具、资源、提示模板的完整示例
- 开发工具:调试、测试、构建脚本
- 最佳实践:类型安全、错误处理、模块化设计
接下来我们将深入学习MCP协议的技术规范,理解消息格式和通信机制。
扩展练习
- 添加一个新的工具,实现文件读取功能
- 创建一个环境变量资源,提供系统环境信息
- 编写一个JSON格式化的提示模板
- 为现有工具添加更多的单元测试
- 实现一个简单的日志记录功能