เชื่อมต่อระบบภายในบริษัทกับ Claude Code ผ่าน MCP ตั้งแต่ fetch server ทั่วไปจนถึงเขียน MCP Server เฉพาะทาง ครอบคลุมแพลตฟอร์ม deploy, monitoring, ระบบ ticket และหลักการออกแบบ tool
บทความก่อนหน้าพูดถึงการเชื่อมต่อฐานข้อมูล ฐานข้อมูลมีโปรโตคอลมาตรฐาน ทำให้การเชื่อมต่อค่อนข้างตรงไปตรงมา แต่ทีมส่วนใหญ่ในการทำงานประจำวันยังต้องพึ่งพาระบบภายในอีกหลายอย่าง: แพลตฟอร์ม deploy, แดชบอร์ดมอนิเตอร์, ระบบ ticket, API ภายใน, ศูนย์จัดการ config
ระบบเหล่านี้มักไม่มี MCP Server สำเร็จรูป แต่เกือบทั้งหมดมี HTTP API ให้ใช้ บทความนี้จะอธิบายวิธีใช้ MCP เชื่อมเครื่องมือภายในเหล่านี้เข้ากับ Claude Code เพื่อให้ Claude ช่วยเช็คมอนิเตอร์ ดูสถานะ deploy และจัดการ ticket ได้โดยตรง
การเชื่อมต่อเครื่องมือภายในมีสองวิธี:
วิธีที่ 1: ใช้ MCP Server HTTP แบบทั่วไป
ในชุมชนมี MCP Server ทั่วไปบางตัวที่สามารถแปลง REST API ใดก็ได้ให้เป็นเครื่องมือ MCP คุณเขียนไฟล์อธิบาย API แล้ว server จะแปลงเป็นเครื่องมือที่ Claude เรียกใช้ได้ เหมาะกับกรณีที่โครงสร้าง API ไม่ซับซ้อนและไม่ต้องการ logic พิเศษ
วิธีที่ 2: เขียน MCP Server เอง
ใช้ MCP SDK ของ TypeScript หรือ Python เขียน Server เฉพาะทาง ควบคุมได้เต็มที่ทั้งการกำหนดเครื่องมือ การตรวจสอบพารามิเตอร์ และการจัดการ error เหมาะกับกรณีที่ต้องรวม API หลายตัว แปลงข้อมูล หรือเพิ่ม business logic
บทความนี้จะอธิบายทั้งสองวิธี เริ่มจากวิธีที่ง่ายก่อน
วิธีที่เบาที่สุดคือใช้ @anthropic-ai/mcp-server-fetch จาก Anthropic ซึ่งให้ Claude ส่ง HTTP request ได้โดยตรง การตั้งค่าเรียบง่ายมาก:
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-server-fetch"]
}
}
}
ตั้งค่าเสร็จแล้ว Claude ก็สามารถเรียก API ภายในได้ทันที:
ช่วยเช็คสถานะปัจจุบันของแพลตฟอร์ม deploy ที่ https://deploy.internal.com/api/v1/services/user-service
Claude จะส่ง GET request รับ response แล้ววิเคราะห์ผลลัพธ์ให้คุณดู
แต่วิธีนี้มีข้อจำกัดที่ชัดเจน:
เหมาะสำหรับใช้ชั่วคราว ไม่เหมาะเป็นโซลูชันระยะยาว
เมื่อคุณต้องใช้ระบบภายในซ้ำ ๆ การเขียน MCP Server เฉพาะทางเป็นทางเลือกที่ดีกว่า ด้านล่างเป็นตัวอย่างจากสถานการณ์จริง: การเชื่อมต่อแพลตฟอร์ม deploy ของบริษัท
สมมติว่าแพลตฟอร์ม deploy ของคุณมี API ดังนี้:
GET /api/v1/services — แสดงรายการ service ทั้งหมดGET /api/v1/services/:name/status — ดูสถานะ servicePOST /api/v1/services/:name/deploy — สั่ง deployGET /api/v1/services/:name/logs — ดู log การ deploy ล่าสุดเริ่มต้นโปรเจกต์ก่อน:
mkdir mcp-deploy && cd mcp-deploy
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init
โค้ดหลัก src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_BASE = process.env.DEPLOY_API_URL!;
const API_TOKEN = process.env.DEPLOY_API_TOKEN!;
async function api(path: string, method = "GET", body?: unknown) {
const res = await fetch(`${API_BASE}${path}`, {
method,
headers: {
Authorization: `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
throw new Error(`API error: ${res.status} ${await res.text()}`);
}
return res.json();
}
const server = new McpServer({
name: "deploy-platform",
version: "1.0.0",
});
// แสดงรายการ service ทั้งหมด
server.tool("list_services", "列出部署平台上的所有服务及其状态", {}, async () => {
const data = await api("/api/v1/services");
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
// ดูสถานะ service เฉพาะตัว
server.tool(
"service_status",
"查看指定服务的当前部署状态、版本号和健康检查结果",
{ name: z.string().describe("服务名称,如 user-service") },
async ({ name }) => {
const data = await api(`/api/v1/services/${name}/status`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// ดู log การ deploy
server.tool(
"deploy_logs",
"查看指定服务最近的部署日志",
{
name: z.string().describe("服务名称"),
limit: z.number().optional().default(10).describe("返回条数,默认 10"),
},
async ({ name, limit }) => {
const data = await api(`/api/v1/services/${name}/logs?limit=${limit}`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// สั่ง deploy
server.tool(
"trigger_deploy",
"触发指定服务的部署。这是一个写操作,会实际影响生产环境",
{
name: z.string().describe("服务名称"),
version: z.string().describe("要部署的版本号或 git ref"),
},
async ({ name, version }) => {
const data = await api(`/api/v1/services/${name}/deploy`, "POST", {
version,
});
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
const transport = new StdioServerTransport();
server.connect(transport);
คอมไพล์:
npx tsc
{
"mcpServers": {
"deploy": {
"command": "node",
"args": ["/path/to/mcp-deploy/dist/index.js"],
"env": {
"DEPLOY_API_URL": "https://deploy.internal.com",
"DEPLOY_API_TOKEN": "your-api-token"
}
}
}
}
เก็บ token ไว้ใน .claude/settings.local.json (ไม่ commit เข้า git) และ URL ไว้ใน .claude/settings.json (commit เข้า git แชร์กับทีม)
หลังจากตั้งค่าเสร็จ การสนทนาจะเป็นธรรมชาติมากขึ้น:
user-service ตอนนี้สถานะเป็นยังไง?
→ Claude เรียก service_status("user-service")
→ ผลลัพธ์: กำลังทำงาน เวอร์ชัน v2.3.1 deploy ล่าสุดเมื่อ 2 ชั่วโมงก่อน health check ผ่านหมด
การ deploy ล่าสุดมีครั้งไหนล้มเหลวบ้างไหม?
→ Claude เรียก deploy_logs("user-service", 20)
→ วิเคราะห์ log แจ้งว่าการ deploy ครั้งที่ 3 ถูก rollback เพราะ health check timeout
deploy user-service เป็น v2.3.2
→ Claude เรียก trigger_deploy("user-service", "v2.3.2")
→ เนื่องจากคำอธิบายเครื่องมือระบุว่าเป็น "การเขียน" Claude จะขอยืนยันจากคุณก่อน
นี่คือคำถามที่ต้องพิจารณาอย่างจริงจัง
การอ่าน — เชื่อมต่อได้สบาย เช็คสถานะ ดู log ค้นหา ticket การทำงานเหล่านี้ไม่มีผลข้างเคียง Claude ทำผิดก็ไม่เสียหายอะไร
การเขียนแบ่งเป็นสองกรณี:
การเขียนที่มีความเสี่ยงต่ำสามารถเชื่อมต่อได้ แต่ต้องระบุให้ชัดเจนในคำอธิบายเครื่องมือ Claude จะขอการยืนยันจากผู้ใช้โดยอัตโนมัติสำหรับการทำงานที่ระบุว่ามีผลข้างเคียง เช่น สร้าง ticket ส่งข้อความ อัปเดต config
การเขียนที่มีความเสี่ยงสูงแนะนำว่าไม่ควรเชื่อมต่อ ลบทรัพยากร สั่ง rollback เปลี่ยนสิทธิ์ — ผลที่ตามมาร้ายแรงและกลับคืนไม่ได้ ทำด้วยมือจะปลอดภัยกว่า
ถ้าจำเป็นต้องเชื่อมต่อการเขียนจริง ๆ อย่างน้อยทำสองอย่าง:
| ระบบ | เครื่องมือที่เปิดใช้ | ข้อควรระวัง |
|---|---|---|
| แพลตฟอร์ม Deploy (K8s / Kamal) | ดูสถานะ service, ดู log, สั่ง deploy | เพิ่มการยืนยันสำหรับการเขียน |
| ระบบมอนิเตอร์ (Grafana / Datadog) | ดู metric, ดูประวัติ alert | จำกัดช่วงเวลาของ query หลีกเลี่ยงการดึงข้อมูลมากเกินไป |
| ระบบ Ticket (Jira / Linear) | ค้นหา ticket, สร้าง ticket, อัปเดตสถานะ | สร้าง ticket เป็นการเขียน แต่ความเสี่ยงต่ำ |
| เอกสารภายใน (Notion / Confluence) | ค้นหาเอกสาร, อ่านเนื้อหาหน้า | ระวังเรื่อง pagination อย่าดึงมากเกินไปในครั้งเดียว |
| ศูนย์ Config (Consul / etcd) | อ่าน config, เปรียบเทียบความแตกต่างระหว่างสภาพแวดล้อม | ทำแค่อ่าน ไม่ควรเชื่อมต่อการเขียน |
| CI/CD (GitHub Actions / Jenkins) | ดูสถานะ build, สั่ง build | สั่ง build ถือเป็นการเขียนความเสี่ยงปานกลาง |
การเขียนเครื่องมือ MCP ต่างจากการเขียน API ตรงที่ API เขียนให้โปรแกรมเมอร์ใช้ แต่เครื่องมือเขียนให้ AI ใช้ มีหลักการสำคัญหลายข้อ:
ชื่อเครื่องมือต้องชัดเจนตรงไปตรงมา
✗ get_svc_stat — Claude อาจเดาความหมายของตัวย่อไม่ถูก
✓ service_status — เห็นก็รู้ทันทีว่าทำอะไร
คำอธิบายเขียนให้ AI อ่าน
คำอธิบายเครื่องมือไม่ใช่เอกสารสำหรับคนอ่าน แต่เป็นข้อมูลให้ Claude ตัดสินใจว่า "เมื่อไหร่ควรเรียกเครื่องมือนี้" คำอธิบายต้องระบุให้ชัด: เครื่องมือนี้ทำอะไร ส่งคืนอะไร ควรใช้เมื่อไหร่
✗ "ดูสถานะ service"
✓ "ดูสถานะ deploy ปัจจุบัน หมายเลขเวอร์ชัน และผลลัพธ์ health check ของ service ที่ระบุ ใช้เมื่อผู้ใช้ถามว่า service ทำงานปกติหรือไม่"
กำหนดพารามิเตอร์ให้ชัดเจนด้วย zod
พารามิเตอร์ที่มี .describe() จะช่วยให้ Claude รู้ว่าต้องกรอกอะไร ถ้าไม่มีคำอธิบาย Claude ต้องเดาจากชื่อพารามิเตอร์เท่านั้น
ส่งคืนข้อมูลที่มีโครงสร้าง
เครื่องมือ MCP ส่งคืนเป็นข้อความ แต่พยายามส่งคืน JSON ที่จัดรูปแบบแล้ว Claude ประมวลผลข้อมูลที่มีโครงสร้างได้แม่นยำกว่าข้อความธรรมดามาก
ความละเอียดที่เหมาะสม
อย่ายัดขั้นตอนที่ซับซ้อนเข้าไปในเครื่องมือเดียว และอย่าแยก query ง่าย ๆ ออกเป็นสามเครื่องมือ หลักการคือ: หนึ่งเครื่องมือทำหนึ่งการทำงานที่เป็นอิสระและมีความหมาย
โค้ด MCP Server สามารถวางไว้ได้หลายที่:
ภายใน repository ของโปรเจกต์ (แนะนำสำหรับเริ่มต้น)
your-project/
├── .claude/settings.json
├── mcp-servers/
│ └── deploy/
│ ├── src/index.ts
│ ├── package.json
│ └── tsconfig.json
└── ...
ข้อดีคือโค้ดและการตั้งค่าอยู่ด้วยกัน ทีม clone มาติดตั้ง dependency ก็ใช้ได้เลย
Repository แยก
เมื่อ MCP Server ต้องใช้ข้ามหลายโปรเจกต์ ให้วางไว้ใน repository แยก เผยแพร่เป็น npm package หรือ Docker image
{
"mcpServers": {
"deploy": {
"command": "npx",
"args": ["-y", "@yourcompany/mcp-deploy-server"]
}
}
}
ติดตั้งแบบ global
สำหรับ MCP Server ที่ใช้ทั่วทั้งบริษัท (เช่น เชื่อมต่อระบบยืนยันตัวตนกลาง, แพลตฟอร์ม log กลาง) ติดตั้งแบบ global แล้วตั้งค่าใน ~/.claude/settings.json
ปัญหาที่พบบ่อยที่สุดเวลาพัฒนา MCP Server คือ "Claude ไม่เรียกเครื่องมือของฉัน" หรือ "เรียกแล้วแต่ error"
ยืนยันว่า Server เริ่มทำงานแล้ว
หลังจากรีสตาร์ท Claude Code พิมพ์ /mcp เพื่อดูรายการ MCP Server ที่เชื่อมต่ออยู่ ถ้า Server ของคุณไม่อยู่ในรายการ ให้ตรวจสอบ command และ args ว่าถูกต้องหรือไม่
ทดสอบ Server แยกต่างหาก
MCP Server สื่อสารผ่าน stdio สามารถทดสอบโดยตรงในเทอร์มินัล:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
ถ้าได้รายการเครื่องมือกลับมา แสดงว่าตัว Server ไม่มีปัญหา
ดู log การเรียกเครื่องมือของ Claude
Claude Code จะแสดง input และ output ของทุกครั้งที่เรียกเครื่องมือ ถ้าพารามิเตอร์ส่งผิด มักเป็นเพราะคำอธิบายเครื่องมือหรือการกำหนดพารามิเตอร์ไม่ชัดเจนพอ ทำให้ Claude เข้าใจผิด
มาดูตัวอย่างจริงเพื่อเชื่อมโยงทุกอย่างเข้าด้วยกัน สมมติว่าจะเชื่อมต่อ Sentry เข้ากับ Claude Code เพื่อให้สามารถตรวจสอบ error บน production ได้โดยตรง
server.tool(
"search_errors",
"在 Sentry 中搜索最近的错误。用于排查线上问题、查看错误趋势",
{
query: z.string().describe("搜索关键词,如错误信息、函数名"),
hours: z.number().optional().default(24).describe("查看最近多少小时的错误"),
},
async ({ query, hours }) => {
const since = new Date(Date.now() - hours * 3600000).toISOString();
const data = await api(
`/api/0/projects/${ORG}/${PROJECT}/issues/?query=${encodeURIComponent(query)}&start=${since}&sort=date`
);
const summary = data.map((issue: any) => ({
title: issue.title,
count: issue.count,
firstSeen: issue.firstSeen,
lastSeen: issue.lastSeen,
link: issue.permalink,
}));
return {
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
};
}
);
server.tool(
"error_details",
"查看 Sentry 中某个错误的详细信息,包括堆栈和最近一次事件",
{ issueId: z.string().describe("Sentry issue ID") },
async ({ issueId }) => {
const [issue, latest] = await Promise.all([
api(`/api/0/issues/${issueId}/`),
api(`/api/0/issues/${issueId}/events/latest/`),
]);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
title: issue.title,
count: issue.count,
users: issue.userCount,
stacktrace: latest.entries?.find(
(e: any) => e.type === "exception"
),
},
null,
2
),
},
],
};
}
);
หลังจากเชื่อมต่อเสร็จ การสนทนาเพื่อตรวจสอบปัญหาบน production จะเป็นแบบนี้:
4 ชั่วโมงที่ผ่านมามี error 500 ใหม่บ้างไหม?
→ Claude ค้นหาใน Sentry
→ พบ 3 issue ใหม่ ตัวที่ร้ายแรงที่สุดกระทบผู้ใช้ 120 คน
→ ดึง stack trace อัตโนมัติ ระบุว่าเป็น null pointer exception
→ ค้นหาตำแหน่งที่เกี่ยวข้องในโค้ด เสนอแนวทางแก้ไข
ตั้งแต่พบปัญหาจนถึงระบุตำแหน่งในโค้ด ทั้งหมดเสร็จในการสนทนาเดียว
บทความนี้อธิบายวิธีใช้ MCP เชื่อมต่อเครื่องมือภายใน แนวคิดหลักคือ: ระบบภายในมี HTTP API → เขียน MCP Server ครอบไว้ → Claude ก็ใช้ได้เลย
ตัวอย่างทั้งหมดในบทความนี้เป็นการครอบ API ที่มีอยู่แล้ว — แพลตฟอร์ม deploy และ Sentry มี interface ของตัวเองอยู่แล้ว MCP Server แค่ทำหน้าที่ส่งต่อและปรับให้เข้ากัน บทความถัดไปจะพูดถึงสถานการณ์ที่ต่างออกไป: เมื่อความสามารถที่คุณต้องการไม่มี API สำเร็จรูป จะสร้าง MCP Server จากศูนย์ได้อย่างไร เขียน logic เอง จัดการ state และรับมือกับ interaction หลายขั้นตอนที่ซับซ้อน