สร้าง 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 เองทุกอันแล้วเอาไปปรับเนื้อหา