ในฐานะวิศวกรอาวุโสที่ดูแลระบบ AI ภายในองค์กร ผมเคยเจอปัญหาคลาสสิกที่หลายท่านน่าจะคุ้นเคย: ทีม Data Science ต้องการให้ Claude ตอบคำถามจากข้อมูล PostgreSQL และ S3 ของบริษัทโดยตรง แต่ไม่อยากอัปโหลดข้อมูลไปยัง Claude API ภายนอกด้วยเหตุผลด้านความปลอดภัยและ compliance บทความนี้จะแชร์ประสบการณ์ตรงในการสร้าง MCP Server แบบ production-ready พร้อมเทคนิคเพิ่มประสิทธิภาพ การควบคุม concurrency และลดต้นทุน Claude API ลง 85%+

ทำไมต้อง MCP Server?

Model Context Protocol (MCP) เป็นโปรโตคอลมาตรฐานที่ Anthropic เปิดตัวในปี 2024 ทำหน้าที่เป็น "USB-C สำหรับ AI" ช่วยให้ LLM เรียกใช้ tools ภายนอกได้อย่างปลอดภัยและเป็นระบบ ข้อดีเมื่อเทียบกับวิธี RAG แบบเดิม:

สถาปัตยกรรมระบบ

ผมเลือก Python 3.11 + FastMCP เพราะ ecosystem ของ async I/O แข็งแรงที่สุดและ community contribution เยอะที่สุด (8.2k stars บน GitHub) สถาปัตยกรรมแบ่งเป็น 3 ชั้นชัดเจน:

ขั้นตอนที่ 1: ติดตั้งและเตรียมโปรเจกต์

mkdir mcp-enterprise && cd mcp-enterprise
python3.11 -m venv .venv
source .venv/bin/activate
pip install fastmcp==0.4.0 asyncpg==0.29.0 aioboto3==12.0.0 anthropic pydantic==2.5

ขั้นตอนที่ 2: PostgreSQL MCP Server (Production-grade)

from fastmcp import FastMCP
import asyncpg
from typing import Optional
import os
import asyncio

mcp = FastMCP("postgres-enterprise")

class PostgresPool:
    def __init__(self):
        self.pool: Optional[asyncpg.Pool] = None

    async def connect(self):
        self.pool = await asyncpg.create_pool(
            dsn=os.environ["POSTGRES_DSN"],
            min_size=2,
            max_size=10,
            max_inactive_connection_lifetime=300,
            command_timeout=30,
            statement_cache_size=1024,
        )

    async def close(self):
        if self.pool:
            await self.pool.close()

db = PostgresPool()

@mcp.tool()
async def query_database(sql: str, limit: int = 100) -> dict:
    """รัน read-only SQL query กับ PostgreSQL พร้อม row limit และ read-only enforcement"""
    sql_clean = sql.strip().lower()
    if not sql_clean.startswith("select") and not sql_clean.startswith("with"):
        return {"error": "อนุญาตเฉพาะ SELECT/WITH เท่านั้น", "rows": []}

    safe_limit = min(int(limit), 1000)
    async with db.pool.acquire() as conn:
        async with conn.transaction(readonly=True):
            rows = await conn.fetch(f"SELECT * FROM ({sql}) AS sub LIMIT {safe_limit}")

    return {
        "columns": list(rows[0].keys()) if rows else [],
        "rows": [dict(r) for r in rows],
        "row_count": len(rows),
    }

@mcp.tool()
async def list_tables(schema: str = "public") -> list:
    """แสดงรายชื่อ tables ใน schema พร้อมขนาด"""
    async with db.pool.acquire() as conn:
        records = await conn.fetch("""
            SELECT table_name,
                   pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) AS size
            FROM information_schema.tables
            WHERE table_schema = $1
            ORDER BY table_name
        """, schema)
    return [dict(r) for r in records]

if __name__ == "__main__":
    asyncio.run(db.connect())
    try:
        mcp.run(transport="stdio")
    finally:
        asyncio.run(db.close())

ขั้นตอนที่ 3: S3 MCP Server

from fastmcp import FastMCP
import aioboto3
from typing import Optional
import os

s3_mcp = FastMCP("s3-enterprise")

@s3_mcp.tool()
async def list_objects(bucket: str, prefix: str = "", max_keys: int = 1000) -> list:
    """แสดงรายการ objects ใน S3 bucket พร้อม prefix filter"""
    session = aioboto3.Session()
    safe_max = min(int(max_keys), 1000)
    async with session.client("s3", region_name=os.environ.get("AWS_REGION", "ap-southeast-1")) as s3:
        response = await s3.list_objects_v2(
            Bucket=bucket, Prefix=prefix, MaxKeys=safe_max
        )
        return [
            {
                "key": obj["Key"],
                "size": obj["Size"],
                "modified": obj["LastModified"].isoformat(),
            }
            for obj in response.get("Contents", [])
        ]

@s3_mcp.tool()
async def read_object(bucket: str, key: str, max_bytes: int = 10240) -> str:
    """อ่านเนื้อหาไฟล์จาก S3 (จำกัด 10KB เพื่อความปลอดภัย)"""
    session = aioboto3.Session()
    async with session.client("s3") as s3:
        response = await s3.get_object(Bucket=bucket, Key=key)
        async with response["Body"] as stream:
            data = await stream.read(min(int(max_bytes), 10240))
            return data.decode("utf-8", errors="replace")

if __name__ == "__main__":
    s3_mcp.run(transport="stdio")

ขั