← Kembali ke Blog

Bikin MCP Server Pertama dengan TypeScript

Kamu punya model chat yang jago nulis kode dan jawab pertanyaan, tapi dia nggak bisa nyentuh data kamu. Dia nggak tahu apa yang ada di database kamu, nggak bisa baca file, dan nggak bisa jalanin query kecuali kamu kasih script duluan. Celah inilah yang ditutup oleh Model Context Protocol (MCP): dia memberi agent tools, resources, dan prompts dalam format standar, jadi satu server bisa dipakai oleh client mana pun yang paham MCP (Claude Desktop, Claude Code, Cursor, dan lainnya) tanpa harus bikin integrasi terpisah per aplikasi.

Tutorial ini bakal bikin server catatan kecil pakai TypeScript dengan MCP TypeScript SDK resmi. Kamu akan register tool yang typed, jalanin server lewat stdio, test pakai MCP Inspector, lalu sambungkan ke client. Di akhir kamu punya server kerja yang kamu bangun sendiri, dan protokolnya nggak disembunyiin di balik framework.

Persyaratan

  • Node.js 20+ (MCP Inspector sekarang butuh Node 22.19+; SDK-nya sendiri minimal 18+)
  • npm
  • TypeScript dasar
  • Satu MCP client buat dites, kayak Claude Desktop atau Claude Code

Langkah 1: Siapkan project

mkdir note-server && cd note-server
npm init -y
npm install @modelcontextprotocol/server zod@3
npm install -D typescript @types/node
mkdir src

SDK resminya sekarang kepisah jadi beberapa package. @modelcontextprotocol/server nyediain class McpServer level tinggi yang nyembunyiin sebagian besar kerjaan JSON-RPC, dan transport stdio ada di subpath (@modelcontextprotocol/server/stdio). zod buat schema tool yang typed dan bisa ngejelasin diri sendiri.

Lanjut tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "strict": true
  }
}

Langkah 2: Tulis server-nya

Bikin src/index.ts dengan penyimpanan catatan di memori:

import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

const notes = new Map<string, string>();

const server = new McpServer({
  name: "note-server",
  version: "1.0.0",
});

server.registerTool(
  "save_note",
  {
    title: "Save note",
    description: "Store a note under a short id.",
    inputSchema: {
      id: z.string().min(1).max(64).describe("Unique id for the note"),
      text: z.string().min(1).describe("Body of the note"),
    },
  },
  async ({ id, text }) => {
    notes.set(id, text);
    return { ok: true, count: notes.size };
  }
);

server.registerTool(
  "get_note",
  {
    title: "Get note",
    description: "Fetch a note by id.",
    inputSchema: {
      id: z.string().describe("Id of the note to fetch"),
    },
  },
  async ({ id }) => {
    const text = notes.get(id);
    return { found: text !== undefined, text: text ?? null };
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Dua hal yang perlu diperhatiin di sini. String describe(...) ini bukan pajangan: dia ikut masuk ke schema tool yang dilihat model, jadi deskripsi yang bagus langsung ngaruh ke seberapa tepat agent milih dan ngisi argumen. Terus, nilai baliknya objek polos yang diubah ke JSON, jadi usahain kecil dan terstruktur, karena objek itu juga yang dikirim balik ke model tiap kali di-panggil.

Build:

npx tsc

Langkah 3: Test dulu lewat Inspector, jangan langsung percaya model

Sebelum nyambung ke client beneran, jalankan server langsung pakai MCP Inspector, tool resmi buat testing MCP server. Dia nunjukin traffic JSON-RPC antara client dan server, jadi kamu bisa mastiin tool kamu balikin apa yang diharapin:

npx @modelcontextprotocol/inspector node ./dist/index.js

Inspector ngeluarin URL berisi one-time token. Buka di browser, masuk tab Tools, lalu panggil save_note dan get_note. Dia jalan langsung dari npx tanpa install dan butuh Node 22.19+.

Package yang sama juga punya mode CLI headless buat CI atau cek cepat:

npx @modelcontextprotocol/inspector --cli node ./dist/index.js --method tools/list

Langkah 4: Sambungkan ke client

Tambah server ke config Claude Desktop. Di macOS file-nya ada di ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "note-server": {
      "type": "stdio",
      "command": "node",
      "args": ["/abs/path/to/note-server/dist/index.js"]
    }
  }
}

Restart Claude Desktop, lalu suruh dia "save a note that says buy oat milk, id groceries". Dia bakal manggil save_note dengan argumen itu dan konfirmasi.

Expose lewat HTTP

stdio cuma cocok buat server lokal yang di-spawn oleh proses. Kalau mau server remote yang bisa diakses banyak client, ganti ke Streamable HTTP. Dokumentasi MCP TypeScript SDK ngejelasin server.md dan client.md lengkap dengan contoh stateless dan OAuth. Mulai dari lokal dulu, baru pindah ke HTTP kalau logikanya udah bener.

Kesalahan umum

  • Logging ke stdout ngerusak protokol. Di stdio, stdout itu kanal JSON-RPC. Log pakai console.error (stderr) atau logger beneran.
  • Schema yang nggak jelas. describe() yang kosong bikin model nebak-nebak arti argumen. Tulis sebaik dokumentasi.
  • Skip Inspector. Nyolokin langsung ke client nyembunyiin kegagalan. Inspeksi dulu, baru connect.
  • Output yang nggak dibatesin. Balikin JSON kecil yang terstruktur, jangan blob gede, biar nggak bakar token tiap panggilan.

Langkah selanjutnya

  • Tambah bagian resource atau prompts dan lihat muncul di tab Resources dan Prompts di Inspector.
  • Ganti transport ke Streamable HTTP biar server bisa dipanggil dari remote.
  • Baca repo MCP servers resmi buat pola production sebelum nulis server kamu sendiri.

Referensi

Butuh Bantuan Implementasi?

Saya membantu tim mendesain dan membangun infrastruktur cloud scalable, pipeline DevOps, dan sistem production-grade.

Konsultasi Gratis