MCP Server là gì?
Định nghĩa
MCP Server là một application implement MCP protocol để cung cấp functionality cho AI clients. Server đóng vai trò bridge giữa AI agents và external systems.
Server Responsibilities
Core Responsibilities
Expose Capabilities
- List available tools
- List available resources
- List available prompts
Handle Requests
- Tool execution
- Resource access
- Prompt generation
Manage State
- Connection state
- Authentication
- Resource subscriptions
Security
- Authentication
- Authorization
- Audit logging
Server Types
1. Local Servers
Chạy trên cùng machine với client:
javascript
// Local file server
class LocalFileServer {
constructor(basePath) {
this.basePath = basePath;
}
async listTools() {
return {
tools: [
{
name: "read_file",
description: "Read file contents",
inputSchema: {
type: "object",
properties: {
path: { type: "string" }
}
}
},
{
name: "write_file",
description: "Write to file",
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string" }
}
}
}
]
};
}
async callTool(name, args) {
switch (name) {
case "read_file":
return await this.readFile(args.path);
case "write_file":
return await this.writeFile(args.path, args.content);
default:
throw new Error(`Unknown tool: ${name}`);
}
}
}Use Cases:
- File system access
- Local database connections
- Development tools
- Local APIs
Pros:
- Fast execution
- No network latency
- Simple setup
- Secure (local only)
Cons:
- Single client limitation
- Resource sharing issues
- Scalability constraints
2. Remote Servers
Chạy trên remote server, accessed qua network:
javascript
// Remote database server
class RemoteDatabaseServer {
constructor(config) {
this.app = express();
this.db = new Database(config);
this.setupRoutes();
}
setupRoutes() {
this.app.post('/mcp', async (req, res) => {
const request = req.body;
try {
const result = await this.handleRequest(request);
res.json(result);
} catch (error) {
res.json({
jsonrpc: "2.0",
id: request.id,
error: {
code: -32603,
message: error.message
}
});
}
});
}
async handleRequest(request) {
switch (request.method) {
case "tools/list":
return await this.listTools();
case "tools/call":
return await this.callTool(request.params.name, request.params.arguments);
default:
throw new Error(`Unknown method: ${request.method}`);
}
}
}Use Cases:
- Shared databases
- Cloud services
- Enterprise systems
- Multi-client access
Pros:
- Multiple clients
- Centralized management
- Load balancing
- Resource sharing
Cons:
- Network latency
- Security complexity
- Infrastructure overhead
3. Hybrid Servers
Kết hợp local và remote capabilities:
javascript
class HybridServer {
constructor() {
this.localTools = new LocalFileServer();
this.remoteTools = new RemoteDatabaseServer();
}
async listTools() {
const local = await this.localTools.listTools();
const remote = await this.remoteTools.listTools();
return {
tools: [...local.tools, ...remote.tools]
};
}
async callTool(name, args) {
if (name.startsWith('file_')) {
return await this.localTools.callTool(name, args);
} else if (name.startsWith('db_')) {
return await this.remoteTools.callTool(name, args);
} else {
throw new Error(`Unknown tool: ${name}`);
}
}
}Server Capabilities
1. Tools
Tools là functions AI có thể gọi:
javascript
// Tool definition
const queryTool = {
name: "query_database",
description: "Execute SQL query on database",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "SQL query to execute"
},
database: {
type: "string",
description: "Database name",
enum: ["users", "products", "orders"]
}
},
required: ["query"]
}
};
// Tool implementation
async function executeQuery(args) {
const { query, database } = args;
// Validate query
if (!query.toLowerCase().startsWith('select')) {
throw new Error("Only SELECT queries allowed");
}
// Execute query
const result = await db.query(query);
return {
content: [
{
type: "text",
text: `Query executed successfully. ${result.rows.length} rows returned.`
},
{
type: "json",
json: {
rows: result.rows,
count: result.rows.length
}
}
]
};
}2. Resources
Resources là read-only data sources:
javascript
// Resource definition
const configResource = {
uri: "config://database",
name: "Database Configuration",
description: "Current database configuration",
mimeType: "application/json"
};
// Resource implementation
async function readResource(uri) {
if (uri === "config://database") {
const config = await db.getConfig();
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(config, null, 2)
}
]
};
}
throw new Error(`Resource not found: ${uri}`);
}3. Prompts
Prompts là pre-built instruction templates:
javascript
// Prompt definition
const analysisPrompt = {
name: "analyze_data",
description: "Analyze database data and provide insights",
arguments: [
{
name: "table",
description: "Table to analyze",
required: true
},
{
name: "focus",
description: "Analysis focus area",
required: false
}
]
};
// Prompt implementation
async function generatePrompt(name, args) {
if (name === "analyze_data") {
const { table, focus } = args;
return {
description: `Analyze ${table} table data`,
messages: [
{
role: "system",
content: {
type: "text",
text: "You are a data analyst. Analyze the provided data and give actionable insights."
}
},
{
role: "user",
content: {
type: "text",
text: `Please analyze the ${table} table${focus ? ` focusing on ${focus}` : ''} and provide insights about patterns, trends, and recommendations.`
}
}
]
};
}
}Server Lifecycle
1. Initialization
javascript
class MCPServer {
constructor(config) {
this.config = config;
this.tools = new Map();
this.resources = new Map();
this.prompts = new Map();
this.connections = new Set();
}
async initialize() {
// Load configuration
await this.loadConfig();
// Initialize database connections
await this.initDatabase();
// Register tools
this.registerTools();
// Register resources
this.registerResources();
// Register prompts
this.registerPrompts();
// Start server
await this.start();
}
async handleInitialize(request) {
return {
protocolVersion: "2024-11-05",
capabilities: {
tools: {},
resources: {},
prompts: {},
logging: {}
},
serverInfo: {
name: this.config.name,
version: this.config.version
}
};
}
}2. Operation
javascript
async function handleRequest(request) {
const { method, params, id } = request;
try {
switch (method) {
case "initialize":
return await this.handleInitialize(request);
case "tools/list":
return await this.listTools();
case "tools/call":
return await this.callTool(params.name, params.arguments);
case "resources/list":
return await this.listResources();
case "resources/read":
return await this.readResource(params.uri);
case "prompts/list":
return await this.listPrompts();
case "prompts/get":
return await this.getPrompt(params.name, params.arguments);
default:
throw new Error(`Unknown method: ${method}`);
}
} catch (error) {
return {
jsonrpc: "2.0",
id,
error: {
code: -32603,
message: error.message
}
};
}
}3. Shutdown
javascript
async function shutdown() {
// Close database connections
await this.closeDatabase();
// Close file handles
await this.closeFiles();
// Cleanup resources
await this.cleanup();
// Notify clients
this.broadcastShutdown();
// Exit process
process.exit(0);
}Server Configuration
Configuration File
json
{
"server": {
"name": "my-mcp-server",
"version": "1.0.0",
"description": "Custom MCP server"
},
"transport": {
"type": "stdio",
"options": {}
},
"database": {
"type": "postgresql",
"host": "localhost",
"port": 5432,
"database": "myapp",
"username": "user",
"password": "password"
},
"security": {
"authentication": {
"type": "jwt",
"secret": "your-secret-key"
},
"authorization": {
"type": "rbac",
"roles": ["admin", "user", "readonly"]
}
},
"logging": {
"level": "info",
"format": "json"
}
}Environment Variables
bash
# Server configuration
MCP_SERVER_NAME=my-mcp-server
MCP_SERVER_VERSION=1.0.0
# Database configuration
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# Security
JWT_SECRET=your-secret-key
AUTH_ENABLED=true
# Logging
LOG_LEVEL=info
LOG_FORMAT=jsonServer Examples
1. Simple File Server
javascript
const fs = require('fs').promises;
const path = require('path');
class SimpleFileServer {
constructor(basePath) {
this.basePath = basePath;
}
async listTools() {
return {
tools: [
{
name: "read_file",
description: "Read file contents",
inputSchema: {
type: "object",
properties: {
path: { type: "string" }
},
required: ["path"]
}
},
{
name: "list_files",
description: "List files in directory",
inputSchema: {
type: "object",
properties: {
directory: { type: "string" }
},
required: ["directory"]
}
}
]
};
}
async callTool(name, args) {
switch (name) {
case "read_file":
return await this.readFile(args.path);
case "list_files":
return await this.listFiles(args.directory);
default:
throw new Error(`Unknown tool: ${name}`);
}
}
async readFile(filePath) {
const fullPath = path.join(this.basePath, filePath);
const content = await fs.readFile(fullPath, 'utf-8');
return {
content: [
{
type: "text",
text: content
}
]
};
}
async listFiles(directory) {
const fullPath = path.join(this.basePath, directory);
const files = await fs.readdir(fullPath);
return {
content: [
{
type: "text",
text: `Files in ${directory}:\n${files.join('\n')}`
}
]
};
}
}2. Database Server
javascript
const { Pool } = require('pg');
class DatabaseServer {
constructor(config) {
this.pool = new Pool(config);
}
async listTools() {
return {
tools: [
{
name: "query",
description: "Execute SQL query",
inputSchema: {
type: "object",
properties: {
query: { type: "string" },
params: { type: "array" }
},
required: ["query"]
}
},
{
name: "list_tables",
description: "List all tables",
inputSchema: {
type: "object",
properties: {}
}
}
]
};
}
async callTool(name, args) {
switch (name) {
case "query":
return await this.executeQuery(args.query, args.params);
case "list_tables":
return await this.listTables();
default:
throw new Error(`Unknown tool: ${name}`);
}
}
async executeQuery(query, params = []) {
const client = await this.pool.connect();
try {
const result = await client.query(query, params);
return {
content: [
{
type: "text",
text: `Query executed. ${result.rows.length} rows returned.`
},
{
type: "json",
json: result.rows
}
]
};
} finally {
client.release();
}
}
async listTables() {
const query = `
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
`;
return await this.executeQuery(query);
}
}3. API Server
javascript
const axios = require('axios');
class APIServer {
constructor(baseURL, apiKey) {
this.baseURL = baseURL;
this.apiKey = apiKey;
}
async listTools() {
return {
tools: [
{
name: "get_user",
description: "Get user information",
inputSchema: {
type: "object",
properties: {
userId: { type: "string" }
},
required: ["userId"]
}
},
{
name: "create_user",
description: "Create new user",
inputSchema: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" }
},
required: ["name", "email"]
}
}
]
};
}
async callTool(name, args) {
switch (name) {
case "get_user":
return await this.getUser(args.userId);
case "create_user":
return await this.createUser(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
}
async getUser(userId) {
try {
const response = await axios.get(`${this.baseURL}/users/${userId}`, {
headers: { 'Authorization': `Bearer ${this.apiKey}` }
});
return {
content: [
{
type: "text",
text: `User retrieved successfully`
},
{
type: "json",
json: response.data
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`
}
],
isError: true
};
}
}
async createUser(userData) {
try {
const response = await axios.post(`${this.baseURL}/users`, userData, {
headers: { 'Authorization': `Bearer ${this.apiKey}` }
});
return {
content: [
{
type: "text",
text: `User created successfully with ID: ${response.data.id}`
},
{
type: "json",
json: response.data
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`
}
],
isError: true
};
}
}
}Server Best Practices
1. Error Handling
javascript
class ServerError extends Error {
constructor(code, message, details = null) {
super(message);
this.code = code;
this.details = details;
}
}
async function safeExecute(operation) {
try {
return await operation();
} catch (error) {
if (error instanceof ServerError) {
throw error;
}
// Log unexpected errors
console.error('Unexpected error:', error);
throw new ServerError(
-32603,
"Internal server error",
process.env.NODE_ENV === 'development' ? error.stack : null
);
}
}2. Input Validation
javascript
const Joi = require('joi');
const querySchema = Joi.object({
query: Joi.string().required(),
database: Joi.string().valid('users', 'products', 'orders'),
limit: Joi.number().integer().min(1).max(1000)
});
async function validateInput(schema, input) {
const { error, value } = schema.validate(input);
if (error) {
throw new ServerError(
-32602,
"Invalid parameters",
error.details
);
}
return value;
}3. Logging
javascript
class Logger {
constructor(level = 'info') {
this.level = level;
}
log(level, message, meta = {}) {
if (this.shouldLog(level)) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level,
message,
...meta
}));
}
}
shouldLog(level) {
const levels = ['error', 'warn', 'info', 'debug'];
return levels.indexOf(level) <= levels.indexOf(this.level);
}
}Summary
MCP Server:
- ✅ Bridge giữa AI và external systems
- ✅ Expose tools, resources, prompts
- ✅ Support multiple transport types
- ✅ Built-in security model
- ✅ Extensible architecture
Server Types:
- Local: Fast, simple, secure
- Remote: Scalable, shared, multi-client
- Hybrid: Best of both worlds
Key Components:
- Tools: Functions AI can call
- Resources: Read-only data sources
- Prompts: Instruction templates
- Security: Auth, authz, audit
- Lifecycle: Init, operate, shutdown
Best Practices:
- Proper error handling
- Input validation
- Comprehensive logging
- Security by default
- Performance optimization
Tiếp theo: 4. Ví dụ thực tế: MongoDB MCP Server → Case study chi tiết