Skip to content

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

  1. Expose Capabilities

    • List available tools
    • List available resources
    • List available prompts
  2. Handle Requests

    • Tool execution
    • Resource access
    • Prompt generation
  3. Manage State

    • Connection state
    • Authentication
    • Resource subscriptions
  4. 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=json

Server 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

Internal documentation for iNET Portal