跳到主要内容

第一个 MCP 服务器

本教程将指导你创建一个简单的 MCP 服务器,实现基本的工具和资源功能。这个示例服务器将提供一些实用的文件操作工具。

项目设置

如果你还没有完成环境搭建,请先参考开发环境搭建

1. 初始化项目

# 创建项目
mkdir hello-mcp-server
cd hello-mcp-server

# 初始化 package.json
npm init -y

# 安装依赖
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node tsx

2. 创建基础结构

# 创建源代码目录
mkdir -p src/{tools,resources,utils}

实现服务器

1. 创建主入口文件

创建 src/index.ts

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { setupFileTools } from './tools/file-tools.js';
import { setupTextResources } from './resources/text-resources.js';

async function createServer() {
const server = new Server(
{
name: 'hello-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);

// 设置工具
setupFileTools(server);

// 设置资源
setupTextResources(server);

// 错误处理
server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await server.close();
process.exit(0);
});

return server;
}

async function main() {
const server = await createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Hello MCP Server running on stdio');
}

main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});

2. 实现文件操作工具

创建 src/tools/file-tools.ts

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { promises as fs } from 'fs';
import path from 'path';

export function setupFileTools(server: Server) {
// 列出可用工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'read_file',
description: '读取文件内容',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: '文件路径',
},
},
required: ['path'],
},
},
{
name: 'write_file',
description: '写入文件内容',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: '文件路径',
},
content: {
type: 'string',
description: '文件内容',
},
},
required: ['path', 'content'],
},
},
{
name: 'list_directory',
description: '列出目录内容',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: '目录路径',
default: '.',
},
},
required: ['path'],
},
},
],
};
});

// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;

try {
switch (name) {
case 'read_file':
return await readFile(args.path);

case 'write_file':
return await writeFile(args.path, args.content);

case 'list_directory':
return await listDirectory(args.path);

default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
);
}
});
}

async function readFile(filePath: string) {
try {
const content = await fs.readFile(filePath, 'utf-8');
return {
content: [
{
type: 'text',
text: content,
},
],
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Failed to read file: ${error instanceof Error ? error.message : String(error)}`
);
}
}

async function writeFile(filePath: string, content: string) {
try {
// 确保目录存在
await fs.mkdir(path.dirname(filePath), { recursive: true });

await fs.writeFile(filePath, content, 'utf-8');
return {
content: [
{
type: 'text',
text: `Successfully wrote to ${filePath}`,
},
],
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Failed to write file: ${error instanceof Error ? error.message : String(error)}`
);
}
}

async function listDirectory(dirPath: string) {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
const result = entries.map(entry => ({
name: entry.name,
type: entry.isDirectory() ? 'directory' : 'file',
}));

return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
throw new McpError(
ErrorCode.InternalError,
`Failed to list directory: ${error instanceof Error ? error.message : String(error)}`
);
}
}

3. 实现文本资源

创建 src/resources/text-resources.ts

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';

interface TextResource {
uri: string;
name: string;
description: string;
mimeType: string;
content: string;
}

// 示例资源
const resources: TextResource[] = [
{
uri: 'text://welcome',
name: 'Welcome Message',
description: 'A welcome message from the MCP server',
mimeType: 'text/plain',
content: 'Welcome to your first MCP server! This server provides file operations tools.',
},
{
uri: 'text://help',
name: 'Help Information',
description: 'Help information for using this server',
mimeType: 'text/markdown',
content: `# Help - Hello MCP Server

This server provides the following tools:

## Tools
- **read_file**: Read the contents of a file
- **write_file**: Write content to a file
- **list_directory**: List the contents of a directory

## Resources
- **Welcome Message**: A welcome message
- **Help**: This help information

## Usage
1. Use the tools through Claude Code
2. Access resources for additional information
3. Check the server logs for any errors`,
},
];

export function setupTextResources(server: Server) {
// 列出所有资源
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: resources.map(r => ({
uri: r.uri,
name: r.name,
description: r.description,
mimeType: r.mimeType,
})),
};
});

// 读取特定资源
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
const resource = resources.find(r => r.uri === uri);

if (!resource) {
throw new Error(`Resource not found: ${uri}`);
}

return {
contents: [
{
uri: resource.uri,
mimeType: resource.mimeType,
text: resource.content,
},
],
};
});
}

4. 配置 TypeScript

创建 tsconfig.json

{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

测试服务器

1. 构建项目

# 编译 TypeScript
npm run build

# 或者直接运行开发模式
npm run dev

2. 配置 Claude Code

在你的 claude_desktop_config.json 中添加:

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

3. 测试功能

重启 Claude Code 后,你应该能够:

  1. 看到服务器提供的工具
  2. 使用文件操作工具
  3. 访问文本资源

扩展功能

添加新工具

  1. src/tools/ 目录下创建新文件
  2. 导出设置函数
  3. src/index.ts 中调用

添加新资源

  1. src/resources/ 目录下创建新文件
  2. 定义资源数据
  3. 注册资源处理器

添加错误处理

// 示例:添加全局错误处理
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
process.exit(1);
});

最佳实践

  1. 输入验证:始终验证工具参数
  2. 错误处理:提供清晰的错误消息
  3. 日志记录:记录重要操作
  4. 资源清理:正确处理文件和连接
  5. 类型安全:充分利用 TypeScript

下一步

现在你已经创建了一个基础的 MCP 服务器,你可以:

  1. 学习工具开发以实现更复杂的工具
  2. 了解资源管理来管理动态资源
  3. 查看高级主题了解异步操作和流式响应

完整代码示例

完整的示例代码可以在以下位置找到:

  • GitHub 仓库:hello-mcp-server
  • 本地文件:/path/to/hello-mcp-server/src/