← Kembali ke Blog

Bikin MCP Server dalam 15 Menit dengan TypeScript SDK

Agent AI kamu bisa ngobrol, tapi nggak bisa nyentuh sistem kamu. Dia nggak bisa baca runbook internal, nulis catatan, atau query database. Kamu paste konteks, turn berikutnya dia lupa, dan kamu balik lagi ke copy-paste data lewat jendela chat.

Model Context Protocol (MCP) adalah standar terbuka yang nyelesaiin masalah ini. Kamu nulis server kecil yang nge-expose tools, resources, atau prompts, dan host MCP mana pun, Claude Code, VS Code, Cursor, atau aplikasi kamu sendiri, connect ke situ dan ngasih model akses langsung ke tools itu. Satu server, banyak host. Nggak perlu integrasi per-aplikasi.

Panduan ini bikin server yang jalan dari nol, catatan tim (team-notes) yang dipakai bareng, lalu nyambungin ke host beneran. Kamu cuma butuh Node.js 20 atau lebih baru, tanpa perlu yang lain.

Prerequisites

  • Node.js 20 atau lebih baru (cek dengan node --version)
  • Satu host MCP buat ngetes: Claude Code, VS Code, atau MCP Inspector

Langkah 1: Siapkan project

TypeScript SDK v2 cuma ngirim ES modules, jadi type=module itu penting. tsx ngejalankan TypeScript langsung, jadi nggak ada build step.

mkdir team-notes && cd team-notes
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir src

zod satu-satunya schema library yang kamu perlu. Dari satu objek Zod, SDK nge-derive JSON Schema yang dilihat model, nge-validasi argumen sebelum handler jalan, dan nge-infer tipe argumen handler kamu.

Langkah 2: Tulis server

Bikin src/index.ts. Dua tools: read-note dan write-note, disimpen di file JSON lokal.

import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';

const NOTES_FILE = 'notes.json';

function createServer(): McpServer {
  const server = new McpServer({ name: 'team-notes', version: '1.0.0' });

  server.registerTool(
    'read-note',
    {
      description: 'Read a note by key from the shared notes file',
      inputSchema: z.object({
        key: z.string().describe('The note key to read, e.g. deploy-runbook'),
      }),
    },
    async ({ key }) => {
      if (!existsSync(NOTES_FILE)) {
        return { content: [{ type: 'text', text: 'No notes file yet.' }] };
      }
      const notes = JSON.parse(readFileSync(NOTES_FILE, 'utf8'));
      const value = notes[key];
      return {
        content: [
          { type: 'text', text: value ? String(value) : 'Note not found.' },
        ],
      };
    }
  );

  server.registerTool(
    'write-note',
    {
      description: 'Write a note under a key to the shared notes file',
      inputSchema: z.object({
        key: z.string().describe('The note key, e.g. deploy-runbook'),
        value: z.string().describe('The note text to store'),
      }),
    },
    async ({ key, value }) => {
      const notes = existsSync(NOTES_FILE)
        ? JSON.parse(readFileSync(NOTES_FILE, 'utf8'))
        : {};
      notes[key] = value;
      writeFileSync(NOTES_FILE, JSON.stringify(notes, null, 2));
      return { content: [{ type: 'text', text: 'Saved.' }] };
    }
  );

  return server;
}

void serveStdio(createServer);
console.error('team-notes MCP server running on stdio');

registerTool butuh nama, config, dan async handler. Handler ngebalikin daftar blok content yang udah di-typed. Balikin isError: true waktu call gagal biar model lihat kegagalannya dan bisa bereaksi.

Langkah 3: Jalankan

npx tsx src/index.ts

Banner-nya muncul di stderr, dan nggak ada yang lain terjadi. Server stdio nunggu di stdin sampai ada client yang mulai ngobrol. Stop dengan Ctrl+C.

Langkah 4: Test tanpa host

MCP Inspector itu web app lokal yang bisa launch server stdio apa pun dan manggil tools-nya langsung.

npx @modelcontextprotocol/inspector npx tsx src/index.ts

Di tab browser yang kebuka, klik Connect, buka tab Tools, terus panggil write-note dulu baru read-note. Ini cara tercepat buat mastiin server kamu jalan sebelum disambungin ke host.

Langkah 5: Sambungkan ke host

Setiap host daftarin server yang sama dengan launch command. Ganti path script-nya dengan absolute path kamu.

Claude Code

CLI-nya punya command khusus, nggak perlu edit file config:

claude mcp add team-notes -- npx tsx /home/you/team-notes/src/index.ts
claude mcp list

Restart Claude Code biar tools-nya ke-load, terus minta dia "nulis catatan cara deploy", dan lihat dia manggil tool itu sendiri.

Claude Desktop

Config-nya ada di ~/.config/claude/claude_desktop_config.json di Linux (macOS pake ~/Library/Application Support/Claude/claude_desktop_config.json). Tambah server di bawah key mcpServers, terus quit total dan buka ulang aplikasinya.

{
  "mcpServers": {
    "team-notes": {
      "command": "npx",
      "args": ["tsx", "/home/you/team-notes/src/index.ts"]
    }
  }
}

env itu tempat buat API key. Pake itu daripada nancepin secret ke args.

Satu kesalahan yang bikin semua stdio server rusak

stdout itu kanal protokol. console.log atau print liar apa pun yang mendarat di stdout merusak aliran JSON-RPC, dan host nemu server yang keliatannya jalan sekali terus mati, atau nggak pernah lihat tools-nya sama sekali. Log pake console.error, yang ngarah ke stderr. Ini penyebab paling umum kalau orang wiring custom MCP server.

Dua lagi yang sering bikin nyangkut:

  • Pake absolute path buat script dan module resolution, terus install dependensi di dalam folder project. Client GUI nggak share PATH atau working directory shell kamu.
  • Setelah edit file config, restart client sampai tuntas. Nutup jendela aja sering nggak nge-reload MCP server.

Kalau host nggak lihat tools-mu, test server-nya sendiri pake Inspector dulu. Kalau Inspector lihat tools-nya, masalahnya di config host, bukan server.

MCP vs. function calling biasa

MCP menang kalau tools dipakai ulang lintas host atau agent: filesystem server, connector database, tool GitHub. Kamu nulis sekali, dan aplikasi MCP-compatible mana pun bisa ngambil. Tool-use biasa (function calling) oke kalau tool-nya cuma buat satu aplikasi dan kamu nggak butuh schema bareng atau mekanisme discovery. Dan kalau cuma butuh panggilan HTTP dua baris, skip protokolnya dan call API-nya langsung aja.

Buat tooling lokal di satu mesin, transport stdio jadi default: host nge-launch server kamu sebagai subprocess dan ngatur umurnya. Kalau banyak client harus share satu endpoint lewat jaringan, serve factory createServer yang sama lewat HTTP aja gantinya.

Langkah selanjutnya

  • Tambah tool ketiga yang nyentuh database atau internal API beneran kamu, dan kasih inputSchema yang pas.
  • Jalanin server lewat HTTP biar banyak client bisa share.
  • Lihat reference server resmi buat filesystem, memory, dan PostgreSQL buat pola yang lebih luas.

Referensi

Butuh Bantuan Implementasi?

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

Konsultasi Gratis