BİLGİSAYAR ERİŞİM SİSTEMİ

İlk MCP Sunucunuzu Oluşturma


Model Context Protocol (MCP), yapay zeka ekosisteminin evrensel adaptöre en yakın şeyidir. Yapay zeka modellerinin araçları nasıl keşfettiğini ve çağırdığını, ve bu araçların yapılandırılmış sonuçları nasıl döndürdüğünü tanımlar. Bir MCP sunucusu çalışır hale geldiğinde, herhangi bir uyumlu istemci — Claude, Claude Code veya MCP konuşan herhangi bir ajan çerçevesi — ek bağlantı kodu olmadan onu kullanabilir.

Bu rehber, sıfırdan gerçek bir MCP sunucusu oluşturmayı anlatır. Oyuncak bir örnek değil, üretim kullanımına genişletebileceğiniz bir desen.

MCP Gerçekte Nedir

MCP, stdio üzerinden JSON-RPC 2.0 protokolüdür (uzak sunucular için HTTP+SSE). İstemci ve sunucu küçük bir mesaj türleri seti değiş tokuş eder:

  • tools/list — istemci hangi araçların mevcut olduğunu sorar
  • tools/call — istemci bir aracı argümanlarla çağırır
  • resources/list / resources/read — istemci dosyaları, veritabanlarını veya sunucunun ortaya koyduğu herhangi bir bağlamı okur

Sunucu, her aracın girdisi için bir JSON Schema ile araçlarını önceden bildirir. İstemci, geçerli çağrılar oluşturmak için bu şemayı kullanır. Tüm etkileşim istemci perspektifinden durumsuz — sunucu gerekli herhangi bir durumu dahili olarak yönetir.

Bu basitlik, amacın ta kendisidir. MCP bir çerçeve değildir. Her şeyi sarmanıza izin veren bir sözleşmedir — bir Postgres veritabanı, bir Kubernetes kümesi, bir GitHub deposu, tescilli bir dahili API — ve bunu herhangi bir yapay zeka modelinin kullanabileceği tiplendirilmiş, çağrılabilir işlevler kümesi olarak ortaya koyar.

Kurulum

Node.js 18+ ve bir TypeScript projesine ihtiyacınız olacak. Resmi MCP SDK, şablon kodunu minimal hale getirir:

Terminal window
mkdir mcp-server && cd mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx

Bir tsconfig.json ekleyin:

{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"outDir": "./dist"
},
"include": ["src"]
}

Ve package.json’da bir başlangıç betiği:

{
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}

Sunucuyu Oluşturma

src/index.ts oluşturun. Yapı her zaman aynıdır: sunucuyu başlatın, araçları kaydedin, stdio transportuna bağlanın.

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: "my-mcp-server",
version: "1.0.0",
});

Şimdi araçları kaydedin. Her aracın modelin ne zaman çağıracağına karar vermek için kullandığı bir adı, açıklaması ve girdi şeması vardır:

server.tool(
"get_weather",
"Get current weather conditions for a city",
{
city: z.string().describe("The city name to get weather for"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
},
async ({ city, units }) => {
// Your actual implementation here
const data = await fetchWeather(city, units);
return {
content: [
{
type: "text",
text: `Weather in ${city}: ${data.temp}°${units === "celsius" ? "C" : "F"}, ${data.condition}`,
},
],
};
}
);

Sunucuyu stdio transportuna bağlayın:

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

Bu eksiksiz sunucu iskeletidir. Geri kalan her şey araç eklemektir.

Araç Tasarım Desenleri

MCP sunucunuzun kalitesi, araçları ne kadar iyi tasarladığınıza bağlıdır. Önemli birkaç desen:

Geniş tekelden ziyade dar, birleştirilebilir araçlar

Bir şey yapan bir araç, birçok şey yapandan daha kullanışlıdır. Model, dar araçları beklenmedik şekillerde birleştirebilir; geniş bir araç onu kısıtlar.

Bundan kaçının:

server.tool("manage_database", "Do anything with the database", {
operation: z.enum(["read", "write", "delete", "schema"]),
query: z.string(),
// ... many optional params
})

Bunu tercih edin:

server.tool("query_records", "Run a SELECT query and return rows as JSON", {
table: z.string(),
where: z.string().optional().describe("SQL WHERE clause, e.g. 'status = active'"),
limit: z.number().default(50),
})
server.tool("get_schema", "Return the column definitions for a table", {
table: z.string(),
})

Girdileri kesin olarak tanımlayın

Model, ne geçireceğini bilmek için describe() dizelerinizi okur. Bunlara, kod tabanınızı hiç görmemiş bir geliştirici için belgeler gibi davranın:

{
date_range: z.string().describe(
"ISO 8601 date range in the format YYYY-MM-DD/YYYY-MM-DD. " +
"Example: 2025-01-01/2025-03-31"
),
}

Belirsiz açıklamalar yanlış girişler üretir. Kesin açıklamalar doğru olanları üretir.

Ham JSON değil, yapılandırılmış metin döndürün

Modeller, araç sonuçlarındaki ham JSON blob’larından daha güvenilir biçimde metin ayrıştırır. Çıktınızı biçimlendirin:

return {
content: [
{
type: "text",
text: [
`Found ${rows.length} records:`,
...rows.map(r => `- ${r.id}: ${r.name} (${r.status})`),
].join("\n"),
},
],
};

Arayan tarafın aşağı akış işleme için gerçekten yapılandırılmış verilere ihtiyacı varsa, JSON’u açıkça etiketlenmiş bir metin bloğunun içine gömin.

Hata işleme

Hataları atılan istisnalar olarak değil, araç sonuçları olarak döndürün. MCP SDK, atılan istisnaları protokol düzeyindeki hatalara dönüştürür; istemciler bunları araç düzeyindeki hatalardan farklı şekilde işler:

async ({ table, where }) => {
try {
const rows = await db.query(table, where);
return { content: [{ type: "text", text: formatRows(rows) }] };
} catch (err) {
return {
content: [
{
type: "text",
text: `Query failed: ${err instanceof Error ? err.message : "unknown error"}. ` +
`Check that the table name is correct and the WHERE clause uses valid column names.`,
},
],
isError: true,
};
}
}

isError: true bayrağı, istemciye sonucun bir başarısızlığı temsil ettiğini sinyaller ve ajanın nasıl kurtarılacağına karar vermesine olanak tanır.

Gerçek Bir Örnek: GitHub Araç Sunucusu

İşte bir ajanın depo içeriklerini okumasına izin vermek için GitHub API’yi saran somut bir araç:

import { Octokit } from "@octokit/rest";
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
server.tool(
"get_file_contents",
"Read the contents of a file from a GitHub repository",
{
owner: z.string().describe("Repository owner (username or org)"),
repo: z.string().describe("Repository name"),
path: z.string().describe("File path within the repository"),
ref: z.string().optional().describe("Branch, tag, or commit SHA. Defaults to the default branch."),
},
async ({ owner, repo, path, ref }) => {
try {
const { data } = await octokit.rest.repos.getContent({
owner,
repo,
path,
ref,
});
if (Array.isArray(data)) {
// It's a directory — return listing
const listing = data.map(f => `${f.type === "dir" ? "📁" : "📄"} ${f.name}`).join("\n");
return {
content: [{ type: "text", text: `Directory listing for ${path}:\n${listing}` }],
};
}
if (data.type !== "file" || !data.content) {
return {
content: [{ type: "text", text: `${path} is not a readable file` }],
isError: true,
};
}
const content = Buffer.from(data.content, "base64").toString("utf-8");
return {
content: [
{
type: "text",
text: `Contents of ${owner}/${repo}/${path}:\n\`\`\`\n${content}\n\`\`\``,
},
],
};
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "Unknown error";
return {
content: [{ type: "text", text: `Failed to read ${path}: ${message}` }],
isError: true,
};
}
}
);

Bu eksiksiz, üretime hazır bir araçtır. Dizinleri, hataları ve kodlamayı işler — ve sonucu bir modelin okuyup alıntılayabileceği metne sarar.

Claude Desktop’a Bağlanma

Sunucunuz çalışır hale geldiğinde, macOS’ta ~/Library/Application Support/Claude/claude_desktop_config.json veya Windows’ta %APPDATA%\Claude\claude_desktop_config.json adresindeki yapılandırmasını düzenleyerek Claude Desktop’a bağlayın:

{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/absolute/path/to/dist/index.js"],
"env": {
"GITHUB_TOKEN": "your_token_here"
}
}
}
}

Claude Desktop’u yeniden başlatın. Araçlarınız araç seçicisinde otomatik olarak görünür.

Geliştirme için doğrudan tsx kullanın:

{
"mcpServers": {
"my-server-dev": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/src/index.ts"]
}
}
}

Claude Code’a Bağlanma

MCP sunucuları CLI aracılığıyla Claude Code’a bağlanır:

Terminal window
claude mcp add my-server node /absolute/path/to/dist/index.js

Veya ortam değişkenleriyle:

Terminal window
claude mcp add my-server --env GITHUB_TOKEN=your_token node /path/to/dist/index.js

Claude Code, onlardan faydalanan görevler üzerinde çalışırken araçlarınızı otomatik olarak kullanacak. Ek yönlendirmeye gerek yok.

Sırada Ne İnşa Edilecek

Bir MCP sunucusu esasen bir yetenek sınırıdır: ajan ne görebilir ve ne yapabilir? Tasarım sorusu her zaman hangi yeteneklerin ortaya konulacağı ve bunların güvenli bir şekilde nasıl kapsamlandırılacağıdır.

Keşfetmeye değer birkaç yön:

  • Veritabanı sunucuları — Ajanın sorgulamadan önce yapıyı keşfedebilmesi için şema iç gözlem araçlarıyla Postgres, SQLite veya DynamoDB’ye salt okunur sorgu erişimi sağlayın
  • Kod analizi sunucuları — Ajanlara basit dosya okumalarının ötesinde kod tabanlarının semantik anlayışı vermek için tree-sitter veya bir dil sunucusu protokolünü sarın
  • İzleme sunucuları — Ajanların gerçek gözlemlenebilirlik verileriyle olayları araştırabilmesi için metrik sistemleri (Prometheus, Datadog) köprüleyin
  • Dokümantasyon sunucuları — Ajanların her şeyin nerede olduğunu bilmeden operasyonel soruları yanıtlayabilmesi için dahili belgeleri veya runbook’ları indeksleyin ve sunun

MCP ekosistemi hâlâ erken aşamada. Çoğu yararlı sunucu henüz inşa edilmedi. Desen, şu anda bir pano veya API aracılığıyla eriştiğiniz her şeyin bir MCP sunucusu için makul bir aday olduğu kadar basittir.

Ortamınızdaki gerçek bir problemi çözeni inşa edin ve protokol geri kalanını halledecektir.


İlgili Makaleler