חיבור מערכות פנימיות של החברה ל-Claude Code דרך MCP — משרת fetch כללי ועד כתיבת MCP Server ייעודי, כולל פלטפורמות פריסה, ניטור, מערכות כרטיסים ועקרונות עיצוב כלים.
במאמר הקודם דיברנו על חיבור מסדי נתונים. למסדי נתונים יש פרוטוקולים סטנדרטיים, מה שהופך את החיבור ליחסית ישיר. אבל רוב הצוותים מסתמכים בעבודה היומיומית על שורה של מערכות פנימיות: פלטפורמות פריסה, לוחות בקרה לניטור, מערכות כרטיסים, ממשקי API פנימיים ומרכזי תצורה.
למערכות האלו בדרך כלל אין שרת MCP מוכן, אבל כמעט תמיד הן מספקות ממשקי HTTP API. המאמר הזה מסביר איך להשתמש ב-MCP כדי לחבר את הכלים הפנימיים האלה ל-Claude Code, כך שיוכל לבדוק ניטור, לצפות בסטטוס פריסה ולטפל בכרטיסים ישירות.
יש שתי דרכים לחבר כלים פנימיים:
דרך ראשונה: שימוש בשרת MCP גנרי ל-HTTP
בקהילה יש שרתי MCP גנריים שיכולים לעטוף כל REST API ככלי MCP. כותבים קובץ תיאור API, והשרת ממיר אותו לכלים ש-Claude יכול לקרוא להם. מתאים לתרחישים עם מבנה API פשוט שלא דורש לוגיקה מורכבת.
דרך שנייה: כתיבת שרת MCP ייעודי
באמצעות MCP SDK ב-TypeScript או Python, כותבים שרת ייעודי שנותן שליטה מלאה על הגדרת הכלים, אימות פרמטרים וטיפול בשגיאות. מתאים לתרחישים שדורשים שילוב של מספר ממשקי API, המרת נתונים או הוספת לוגיקה עסקית.
המאמר הזה מכסה את שתי הדרכים, ומתחיל מהפשוטה.
הפתרון הקל ביותר הוא להשתמש בשרת הרשמי @anthropic-ai/mcp-server-fetch, שמאפשר ל-Claude לשלוח בקשות HTTP ישירות. ההגדרה מינימלית:
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-server-fetch"]
}
}
}
אחרי ההגדרה, Claude יכול לקרוא ישירות לממשקי API פנימיים:
תבדוק לי מה הסטטוס הנוכחי של פלטפורמת הפריסה https://deploy.internal.com/api/v1/services/user-service
Claude ישלח בקשת GET, יקבל את התגובה ויציג אותה בפניך.
אבל לדרך הזו יש מגבלות ברורות:
מתאים לשימוש זמני, לא לפתרון לטווח ארוך.
כשצריכים להשתמש במערכת פנימית באופן חוזר, כתיבת שרת MCP ייעודי היא הבחירה הטובה יותר. נדגים עם תרחיש אמיתי: חיבור פלטפורמת הפריסה של החברה.
נניח שפלטפורמת הפריסה מספקת את ממשקי ה-API הבאים:
GET /api/v1/services — רשימת כל השירותיםGET /api/v1/services/:name/status — צפייה בסטטוס שירותPOST /api/v1/services/:name/deploy — הפעלת פריסהGET /api/v1/services/:name/logs — צפייה ביומני פריסה אחרוניםראשית, מאתחלים את הפרויקט:
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",
});
// רשימת כל השירותים
server.tool("list_services", "列出部署平台上的所有服务及其状态", {}, async () => {
const data = await api("/api/v1/services");
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
// צפייה בסטטוס שירות בודד
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) }] };
}
);
// צפייה ביומני פריסה
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) }] };
}
);
// הפעלת פריסה
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"
}
}
}
}
שמרו את הטוקן ב-.claude/settings.local.json (לא נכנס ל-git), ואת ה-URL ב-.claude/settings.json (נכנס ל-git ומשותף עם הצוות).
אחרי ההגדרה, השיחה הופכת לטבעית:
מה הסטטוס של user-service עכשיו?
→ Claude קורא ל-service_status("user-service")
→ תוצאה: פעיל, גרסה v2.3.1, פריסה אחרונה לפני שעתיים, כל בדיקות הבריאות עברו
האם היו כשלונות בפריסות האחרונות?
→ Claude קורא ל-deploy_logs("user-service", 20)
→ מנתח את היומנים ומדווח שהפריסה השלישית בוצע לה rollback בגלל timeout בבדיקת בריאות
תפרוס את user-service לגרסה v2.3.2
→ Claude קורא ל-trigger_deploy("user-service", "v2.3.2")
→ כיוון שתיאור הכלי מציין "פעולת כתיבה", Claude יבקש אישור קודם
זו שאלה שדורשת חשיבה רצינית.
פעולות קריאה — חברו בלי דאגה. בדיקת סטטוס, צפייה ביומנים, חיפוש כרטיסים — פעולות ללא תופעות לוואי, גם אם Claude טועה אין נזק.
פעולות כתיבה מתחלקות לשני סוגים:
פעולות כתיבה בסיכון נמוך אפשר לחבר, אבל עם סימון ברור בתיאור הכלי. Claude מבקש אישור משתמש אוטומטית לפעולות שמסומנות עם תופעות לוואי. למשל יצירת כרטיס, שליחת הודעה, עדכון תצורה.
פעולות כתיבה בסיכון גבוה מומלץ לא לחבר. מחיקת משאבים, הפעלת rollback, שינוי הרשאות — אלה פעולות עם השלכות חמורות ובלתי הפיכות, בטוח יותר להשאיר אותן ידניות.
אם בכל זאת חייבים לחבר פעולות כתיבה, עשו לפחות שני דברים:
| מערכת | כלים חשופים | הערות |
|---|---|---|
| פלטפורמת פריסה (K8s / Kamal) | צפייה בסטטוס שירותים, צפייה ביומנים, הפעלת פריסה | הוספת אישור לפעולות כתיבה |
| מערכת ניטור (Grafana / Datadog) | צפייה במדדים, צפייה בהיסטוריית התראות | שליטה בטווח הזמן של שאילתות כדי לא למשוך יותר מדי נתונים |
| מערכת כרטיסים (Jira / Linear) | חיפוש כרטיסים, יצירת כרטיסים, עדכון סטטוס | יצירת כרטיס היא פעולת כתיבה אבל בסיכון נמוך |
| תיעוד פנימי (Notion / Confluence) | חיפוש מסמכים, קריאת תוכן דפים | שימו לב לעימוד, אל תמשכו יותר מדי בפעם אחת |
| מרכז תצורה (Consul / etcd) | קריאת תצורה, השוואת הבדלים בין סביבות | קריאה בלבד, אל תחברו פעולות כתיבה |
| CI/CD (GitHub Actions / Jenkins) | צפייה בסטטוס בנייה, הפעלת בנייה | הפעלת בנייה נחשבת לפעולת כתיבה בסיכון בינוני |
כתיבת כלי MCP שונה מכתיבת API. ממשק API מיועד למתכנתים, כלים מיועדים ל-AI. הנה כמה עקרונות חשובים:
שם הכלי צריך להיות ברור
✗ get_svc_stat — Claude לא בהכרח ינחש את משמעות הקיצור
✓ service_status — ברור ממבט ראשון
התיאור צריך להיכתב עבור ה-AI
תיאור הכלי הוא לא תיעוד לבני אדם, אלא הבסיס שעליו Claude מחליט ״מתי לקרוא לכלי הזה״. התיאור צריך להבהיר: מה הכלי עושה, מה הוא מחזיר, ומתי להשתמש בו.
✗ "קבלת סטטוס שירות"
✓ "צפייה בסטטוס הפריסה הנוכחי, מספר גרסה ותוצאות בדיקת בריאות של שירות מסוים. משמש כשהמשתמש שואל אם שירות מסוים פועל כרגיל"
הגדירו פרמטרים בבירור עם zod
פרמטרים עם .describe() — Claude יודע מה למלא בהם. בלי תיאור, Claude יכול רק לנחש לפי השם.
החזירו נתונים מובנים
כלי MCP מחזירים טקסט, אבל השתדלו להחזיר JSON מפורמט. Claude מעבד נתונים מובנים בדיוק הרבה יותר גבוה מטקסט רגיל.
שמרו על גרנולריות מתאימה
אל תדחסו תהליך מורכב לכלי אחד. וגם אל תפצלו שאילתה פשוטה לשלושה כלים. העיקרון הוא: כלי אחד משלים פעולה אחת עצמאית ומשמעותית.
את קוד שרת ה-MCP אפשר לשים במספר מקומות:
בתוך ה-repository של הפרויקט (מומלץ להתחלה)
your-project/
├── .claude/settings.json
├── mcp-servers/
│ └── deploy/
│ ├── src/index.ts
│ ├── package.json
│ └── tsconfig.json
└── ...
היתרון הוא שהקוד וההגדרות ביחד, כל אחד בצוות עושה clone ומתקין תלויות ויכול להתחיל מיד.
repository נפרד
כששרת MCP צריך לשמש מספר פרויקטים, שימו אותו ב-repository נפרד ופרסמו כחבילת npm או image של Docker.
{
"mcpServers": {
"deploy": {
"command": "npx",
"args": ["-y", "@yourcompany/mcp-deploy-server"]
}
}
}
התקנה גלובלית
לשרתי MCP שנפוצים ברמת הארגון כולו (כמו חיבור למערכת אימות מאוחדת או פלטפורמת לוגים מאוחדת), התקינו גלובלית והגדירו ב-~/.claude/settings.json.
הבעיות הנפוצות ביותר בפיתוח שרת MCP הן ״Claude לא קרא לכלי שלי״ או ״קרא אבל קיבל שגיאה״.
ודאו שהשרת רץ
אחרי הפעלה מחדש של Claude Code, הקלידו /mcp כדי לראות את רשימת שרתי ה-MCP המחוברים. אם השרת שלכם לא ברשימה, בדקו שה-command וה-args נכונים.
בדקו את השרת בנפרד
שרת MCP מתקשר דרך stdio, אפשר לבדוק אותו ישירות בטרמינל:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
אם הוא מחזיר רשימת כלים, השרת עצמו תקין.
בדקו את יומני קריאות הכלים של Claude
Claude Code מציג את הקלט והפלט של כל קריאת כלי. אם פרמטרים הועברו בטעות, בדרך כלל הסיבה היא שתיאור הכלי או הגדרת הפרמטרים לא ברורים מספיק ו-Claude הבין לא נכון.
נחבר את הכל עם דוגמה אמיתית. נניח שרוצים לחבר את Sentry ל-Claude Code כדי שיוכל לשאול ישירות על שגיאות בייצור.
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
),
},
],
};
}
);
אחרי החיבור, שיחת איתור בעיות בייצור נראית כך:
האם יש שגיאות 500 חדשות ב-4 השעות האחרונות?
→ Claude מחפש ב-Sentry
→ מגלה 3 בעיות חדשות, החמורה ביותר השפיעה על 120 משתמשים
→ מושך אוטומטית את ה-stacktrace ומזהה שמדובר ב-null pointer exception
→ מוצא את המיקום המתאים בקוד ומציע תיקון
מגילוי הבעיה ועד לאיתור הקוד, כל התהליך מתבצע בשיחה אחת.
המאמר הזה הסביר איך לחבר כלים פנימיים עם MCP. הרעיון המרכזי הוא: למערכת הפנימית יש HTTP API ← כותבים שרת MCP שעוטף אותו ← Claude יכול להשתמש בו ישירות.
כל הדוגמאות במאמר הזה עוטפות ממשקי API קיימים — לפלטפורמת הפריסה ול-Sentry כבר יש ממשקים, ושרת ה-MCP רק מעביר ומתאים. המאמר הבא יעסוק בתרחיש שונה: כשהיכולת שאתם צריכים בכלל לא קיימת כ-API מוכן, איך לבנות שרת MCP מאפס, לממש לוגיקה בעצמכם, לנהל מצב ולטפל באינטראקציות מורכבות מרובות שלבים.