Membangun Server MCP Pertama Anda
Model Context Protocol (MCP) adalah hal yang paling mendekati adaptor universal dalam ekosistem AI. Ini mendefinisikan bagaimana model AI menemukan dan memanggil alat, dan bagaimana alat-alat tersebut mengembalikan hasil terstruktur. Setelah server MCP berjalan, klien yang kompatibel mana pun — Claude, Claude Code, atau framework agen apa pun yang berbicara MCP — dapat menggunakannya tanpa kode lem tambahan.
Panduan ini membahas cara membangun server MCP nyata dari awal. Bukan contoh mainan, tapi pola yang dapat Anda perluas untuk penggunaan produksi.
Apa Sebenarnya MCP
MCP adalah protokol JSON-RPC 2.0 melalui stdio (atau HTTP+SSE untuk server jarak jauh). Klien dan server bertukar serangkaian kecil tipe pesan:
tools/list— klien menanyakan alat apa yang tersediatools/call— klien memanggil alat dengan argumenresources/list/resources/read— klien membaca file, basis data, atau konteks apa pun yang diekspos server
Server mendeklarasikan alatnya di awal dengan JSON Schema untuk input setiap alat. Klien menggunakan skema ini untuk membuat panggilan yang valid. Seluruh interaksi bersifat stateless dari perspektif klien — server mengelola status yang diperlukan secara internal.
Kesederhanaan ini adalah intinya. MCP bukan framework. Ini adalah kontrak yang memungkinkan Anda membungkus apa saja — basis data Postgres, kluster Kubernetes, repositori GitHub, API internal proprietary — dan mengeksposnya sebagai serangkaian fungsi bertipe dan dapat dipanggil yang dapat digunakan model AI mana pun.
Pengaturan
Anda memerlukan Node.js 18+ dan proyek TypeScript. SDK MCP resmi membuat boilerplate minimal:
mkdir mcp-server && cd mcp-servernpm init -ynpm install @modelcontextprotocol/sdk zodnpm install -D typescript @types/node tsxTambahkan tsconfig.json:
{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "strict": true, "outDir": "./dist" }, "include": ["src"]}Dan skrip start di package.json:
{ "scripts": { "dev": "tsx src/index.ts", "build": "tsc", "start": "node dist/index.js" }}Membangun Server
Buat src/index.ts. Strukturnya selalu sama: inisialisasi server, daftarkan alat, hubungkan ke transport stdio.
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",});Sekarang daftarkan alat. Setiap alat memiliki nama, deskripsi yang digunakan model untuk memutuskan kapan memanggilnya, dan skema input:
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}`, }, ], }; });Hubungkan server ke transport stdio:
const transport = new StdioServerTransport();await server.connect(transport);Itulah kerangka server yang lengkap. Selebihnya adalah menambahkan alat.
Pola Desain Alat
Kualitas server MCP Anda bergantung pada seberapa baik Anda merancang alat. Beberapa pola yang penting:
Alat sempit dan dapat dikomposisi daripada monolit lebar
Alat yang melakukan satu hal lebih berguna dari yang melakukan banyak hal. Model dapat menggabungkan alat sempit dengan cara yang tidak terduga; alat lebar membatasinya.
Hindari ini:
server.tool("manage_database", "Do anything with the database", { operation: z.enum(["read", "write", "delete", "schema"]), query: z.string(), // ... many optional params})Lebih suka ini:
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(),})Deskripsikan input dengan tepat
Model membaca string describe() Anda untuk mengetahui apa yang harus dilewatkan. Perlakukan seperti dokumentasi untuk pengembang yang belum pernah melihat codebase Anda:
{ 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" ),}Deskripsi yang samar menghasilkan input yang salah. Deskripsi yang tepat menghasilkan yang benar.
Kembalikan teks terstruktur, bukan JSON mentah
Model mem-parse teks lebih andal daripada blob JSON mentah dalam hasil alat. Format output Anda:
return { content: [ { type: "text", text: [ `Found ${rows.length} records:`, ...rows.map(r => `- ${r.id}: ${r.name} (${r.status})`), ].join("\n"), }, ],};Jika pemanggil benar-benar membutuhkan data terstruktur untuk pemrosesan hilir, sematkan JSON di dalam blok teks yang diberi label jelas.
Penanganan kesalahan
Kembalikan kesalahan sebagai hasil alat, bukan exception yang dilempar. SDK MCP menerjemahkan exception yang dilempar menjadi kesalahan tingkat protokol, yang ditangani klien secara berbeda dari kesalahan tingkat alat:
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, }; }}Flag isError: true memberi sinyal ke klien bahwa hasilnya merepresentasikan kegagalan, memungkinkan agen memutuskan cara memulihkan.
Contoh Nyata: Server Alat GitHub
Berikut alat konkret yang membungkus GitHub API untuk memungkinkan agen membaca konten repositori:
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, }; } });Ini adalah alat yang lengkap dan siap produksi. Ini menangani direktori, kesalahan, dan encoding — dan membungkus hasilnya dalam teks yang mudah dibaca dan dikutip oleh model.
Menghubungkan ke Claude Desktop
Setelah server Anda berjalan, hubungkan ke Claude Desktop dengan mengedit konfigurasinya di ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) atau %APPDATA%\Claude\claude_desktop_config.json (Windows):
{ "mcpServers": { "my-server": { "command": "node", "args": ["/absolute/path/to/dist/index.js"], "env": { "GITHUB_TOKEN": "your_token_here" } } }}Restart Claude Desktop. Alat Anda muncul di pemilih alat secara otomatis.
Untuk pengembangan, gunakan tsx secara langsung:
{ "mcpServers": { "my-server-dev": { "command": "npx", "args": ["tsx", "/absolute/path/to/src/index.ts"] } }}Menghubungkan ke Claude Code
Server MCP terhubung ke Claude Code melalui CLI:
claude mcp add my-server node /absolute/path/to/dist/index.jsAtau dengan variabel lingkungan:
claude mcp add my-server --env GITHUB_TOKEN=your_token node /path/to/dist/index.jsClaude Code akan secara otomatis menggunakan alat Anda saat mengerjakan tugas yang mendapat manfaat darinya. Tidak perlu prompting tambahan.
Apa yang Harus Dibangun Selanjutnya
Server MCP pada dasarnya adalah batas kemampuan: apa yang dapat dilihat dan dilakukan agen? Pertanyaan desainnya selalu kemampuan mana yang diekspos dan bagaimana menentukan ruang lingkupnya dengan aman.
Beberapa arah yang layak dijelajahi:
- Server basis data — Ekspos akses kueri read-only ke Postgres, SQLite, atau DynamoDB dengan alat introspeksi skema sehingga agen dapat menemukan strukturnya sebelum melakukan kueri
- Server analisis kode — Bungkus tree-sitter atau protokol language server untuk memberi agen pemahaman semantik codebase di luar pembacaan file sederhana
- Server monitoring — Jembatani sistem metrik (Prometheus, Datadog) sehingga agen dapat menyelidiki insiden dengan data observabilitas nyata
- Server dokumentasi — Indeks dan sajikan dokumen internal atau runbook, memungkinkan agen menjawab pertanyaan operasional tanpa perlu mengetahui di mana semuanya berada
Ekosistem MCP masih awal. Sebagian besar server yang berguna belum dibangun. Polanya cukup sederhana sehingga apa pun yang saat ini Anda akses melalui dashboard atau API adalah kandidat yang masuk akal untuk server MCP.
Bangun yang memecahkan masalah nyata di lingkungan Anda, dan protokol akan mengurus sisanya.
Artikel Terkait
- Mengenal Pengembangan Agentik
- Pola Penggunaan Alat: Membangun Antarmuka Agen-Alat yang Andal
- Pola Multi-Agen: Orkestrator, Pekerja, dan Pipeline