เรียน claude
🎓
บทที่ 7 · ⏱ 12 นาที

สร้าง MCP Server ของบริษัทเอง

ผมเขียน MCP server เชื่อม Claude เข้ากับระบบเก็บข้อมูลโครงการของบริษัท ตอนนี้ Claude ดู progress งานได้ตรงๆ

หลังจากใช้ MCP สำเร็จรูปมาพักใหญ่ ผมเริ่มอยากให้ Claude เข้าถึงระบบของบริษัทเอง เช่น Google Sheet ตารางโครงการที่มี logic เฉพาะที่ MCP ทั่วไปไม่ทำให้

ผมเลยลองเขียน MCP server ของตัวเอง ปรากฏว่าไม่ยากเท่าที่คิด

บทนี้สำหรับคนที่อยากลงลึก — ถ้ายังไม่พร้อม ข้ามไปบทสุดท้ายได้เลยครับ

เมื่อไหร่ควรสร้างเอง

ควร

  • ธุรกิจมี API ภายในที่อยากให้ Claude เข้าถึง
  • มี database เฉพาะที่ Claude ควรอ่านได้
  • ใช้ service ที่ไม่มี MCP official (เช่น POS ระบบไทย)
  • อยากให้ Claude ทำ action ที่ custom

ไม่ควร

  • Service ที่มี MCP official อยู่แล้ว (Notion / Drive / Gmail)
  • แค่อ่านไฟล์ในเครื่อง — ใช้ Filesystem MCP
  • แค่ query public API — ให้ Claude เรียก curl ก็ได้

โครงสร้าง

Claude Code  ◄──stdio──►  MCP Server  ◄──API──►  Service ของคุณ
(client)                (โค้ดของคุณ)         (DB/API/...)

Claude Code คุยกับ MCP server ผ่าน stdio (หรือ HTTP) MCP server expose tools, resources, prompts ออกมา ของพวกนี้เชื่อมไปยัง DB หรือ API จริงของคุณ

เขียนด้วย TypeScript

Setup

mkdir my-smb-mcp && cd my-smb-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "outDir": "dist"
  }
}

src/index.ts — server พื้นฐานพร้อม 2 tool

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "smb-toolkit",
  version: "1.0.0",
});

server.registerTool(
  "get_sales_summary",
  {
    description: "ดึงยอดงานของวันที่ระบุ",
    inputSchema: {
      date: z.string().describe("วันที่ในรูปแบบ YYYY-MM-DD"),
    },
  },
  async ({ date }) => {
    // ดึงจาก DB จริงของคุณ
    const totalSales = 250000;
    const projectCount = 3;
    return {
      content: [{ type: "text", text: `ยอดงานวันที่ ${date} — ${totalSales} บาท จาก ${projectCount} โครงการ` }],
    };
  },
);

server.registerTool(
  "add_customer_note",
  {
    description: "บันทึกโน้ตเกี่ยวกับลูกค้า",
    inputSchema: {
      customerId: z.string(),
      note: z.string(),
    },
  },
  async ({ customerId, note }) => {
    // Save to DB
    return { content: [{ type: "text", text: `บันทึกโน้ตให้ลูกค้า ${customerId} แล้ว` }] };
  },
);

const transport = new StdioServerTransport();
await server.connect(transport);

เชื่อม Claude Code

แก้ ~/.claude/settings.json

{
  "mcpServers": {
    "smb-toolkit": {
      "command": "node",
      "args": ["/Users/YOURNAME/path/to/my-smb-mcp/dist/index.js"]
    }
  }
}

Restart Claude Code แล้วลอง

"ดึงยอดงานวันที่ 2026-04-20"

Claude เรียก tool get_sales_summary จาก server ของคุณ return ผลลัพธ์

เชื่อม Database จริง — Supabase ตัวอย่าง

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!,
);

server.registerTool(
  "query_projects",
  {
    description: "Query โครงการจาก database",
    inputSchema: {
      status: z.enum(["pending", "active", "completed"]).optional(),
      limit: z.number().default(10),
    },
  },
  async ({ status, limit }) => {
    let query = supabase.from("projects").select("*").limit(limit);
    if (status) query = query.eq("status", status);
    const { data, error } = await query;
    if (error) throw new Error(error.message);
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  },
);

Tools vs Resources vs Prompts

Tool (active) — Claude เรียกเมื่อต้องทำ action mutation ได้ เช่น send_email, create_order

Resource (passive) — Claude อ่านเป็น context read-only เช่น "เอกสาร policies ของบริษัท", "product catalog"

Prompt (template) — Template prompt ที่ให้ Claude เลือกใช้

ตัวอย่าง resource

server.registerResource(
  "file",
  new ResourceTemplate("file:///{path}"),
  { description: "Read file" },
  async (uri, { path }) => {
    return { contents: [{ uri: uri.href, mimeType: "text/plain", text: "..." }] };
  },
);

Production checklist

Security

  • Auth (API key, OAuth) ถ้า sensitive
  • Rate limiting
  • Input validation ผ่าน Zod
  • Error message ไม่ leak ข้อมูล

Reliability

  • Error handling
  • Timeout handling
  • Logging
  • Health check endpoint

Performance

  • Caching ถ้าเหมาะ
  • Connection pooling สำหรับ DB
  • Async/await ใช้ถูก

Deploy options

Local — รันบนเครื่องคุณ Claude Code เชื่อมตรง

HTTP transport (remote) — เปลี่ยนจาก StdioServerTransport เป็น HTTP เช่น Express ด้วย Streamable HTTP transport แล้ว deploy ขึ้น Vercel / Railway / Fly.io

{
  "mcpServers": {
    "smb-toolkit": {
      "url": "https://my-mcp.vercel.app/mcp",
      "transport": "http"
    }
  }
}

Claude Apps Marketplace — Distribute ผ่าน official Apps marketplace (กำลังจะมา)

ใน context ของคอร์สนี้

ใน learn-claude เรามีคำสั่งให้ติด MCP ของ Supabase

claude mcp add --scope project --transport http supabase \
  "https://mcp.supabase.com/mcp?project_ref=leupvgbdnymrnsnyrimy"

นี่คือตัวอย่าง MCP server official ของ Supabase ลองศึกษาเป็นแม่แบบได้

ลองทำดู: ภารกิจ: MCP server แรก

บทนี้มีประโยชน์กับคุณมั้ยครับ?

ผมอ่าน feedback เองทุกอันแล้วเอาไปปรับเนื้อหา