Cara Membangun dan Menyebarkan Server MCP Kustom dalam 10 Menit

💡Ingin membuat Alur Kerja AI Agentic Anda sendiri tanpa Kode? Anda dapat dengan mudah membuat alur kerja AI dengan Anakin AI tanpa pengetahuan pemrograman. Hubungkan ke API LLM seperti: GPT-4, Claude 3.5 Sonnet, Uncensored Dolphin-Mixtral, Stable Diffusion, DALLE, Web Scraping.... ke dalam Satu Alur Kerja! Lupakan coding yang rumit,

Build APIs Faster & Together in Apidog

Cara Membangun dan Menyebarkan Server MCP Kustom dalam 10 Menit

Start for free
Inhalte
💡
Ingin membuat Alur Kerja AI Agentic Anda sendiri tanpa Kode?

Anda dapat dengan mudah membuat alur kerja AI dengan Anakin AI tanpa pengetahuan pemrograman. Hubungkan ke API LLM seperti: GPT-4, Claude 3.5 Sonnet, Uncensored Dolphin-Mixtral, Stable Diffusion, DALLE, Web Scraping.... ke dalam Satu Alur Kerja!

Lupakan coding yang rumit, otomatisasi pekerjaan harian Anda dengan Anakin AI!

Untuk waktu terbatas, Anda juga dapat menggunakan Google Gemini 1.5 dan Stable Diffusion secara Gratis!
Dengan Mudah Membangun Alur Kerja AI Agentic dengan Anakin AI!
Dengan Mudah Membangun Alur Kerja AI Agentic dengan Anakin AI

Pengenalan

Model Context Protocol (MCP) merupakan kemajuan signifikan dalam ekosistem AI, menawarkan cara yang terstandarisasi untuk berkomunikasi dengan model bahasa besar. Alih-alih setiap platform AI menerapkan format unik mereka sendiri untuk pesan, MCP bertujuan untuk menyediakan antarmuka yang konsisten untuk prompt, respons, dan pemanggilan fungsi di berbagai model dan platform.

Sementara protokol itu sendiri sedang berkembang, membangun server dasar yang kompatibel dengan MCP dapat dilakukan dengan mudah. Dalam panduan ini, saya akan memandu Anda melalui pembuatan server sederhana, namun fungsional yang mematuhi prinsip inti penanganan pesan dalam sistem AI modern. Implementasi kami akan fokus pada pembuatan fondasi yang dapat Anda kembangkan dengan fitur yang lebih canggih.

Apakah 10 menit cukup waktu? Untuk sistem yang siap produksi, tentunya tidak. Tetapi untuk prototipe yang menunjukkan konsep kunci? Tentu saja. Mari kita mulai!

Prasyarat

Sebelum kita mulai, Anda akan membutuhkan:

  • Node.js (v16+) terpasang di sistem Anda
  • Pengetahuan dasar tentang JavaScript/TypeScript
  • Keterampilan dengan Express.js atau kerangka kerja web serupa
  • Editor kode (VS Code disarankan)
  • Akses terminal/command line
  • npm atau yarn sebagai manajer paket

Langkah 1: Menyiapkan Proyek Anda (2 menit)

Pertama, mari buat direktori baru dan inisialisasi proyek kami:

mkdir mcp-server
cd mcp-server
npm init -y

Sekarang, instal dependensi yang diperlukan:

npm install express cors typescript ts-node @types/node @types/express @types/cors
npm install --save-dev nodemon

Buat file konfigurasi TypeScript:

npx tsc --init

Edit tsconfig.json yang dihasilkan untuk menyertakan pengaturan penting berikut:

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

Perbarui bagian skrip package.json Anda:

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

Langkah 2: Menciptakan Server Inti (3 menit)

Buat direktori sumber dan file server utama Anda:

mkdir -p src/handlers
touch src/index.ts
touch src/handlers/messageHandler.ts
touch src/types.ts

Mari kita definisikan tipe kita terlebih dahulu di src/types.ts:

// Struktur pesan dasar
export interface Content {
  type: string;
  text?: string;
}

export interface Message {
  role: "user" | "assistant" | "system";
  content: Content[] | string;
}

// Struktur permintaan dan respons
export interface ModelRequest {
  messages: Message[];
  max_tokens?: number;
  temperature?: number;
  stream?: boolean;
}

export interface ModelResponse {
  message: Message;
}

// Antarmuka pemanggilan alat
export interface Tool {
  name: string;
  description: string;
  input_schema: {
    type: string;
    properties: Record;
    required?: string[];
  };
}

export interface ToolCall {
  type: "tool_call";
  id: string;
  name: string;
  input: Record;
}

export interface ToolResult {
  type: "tool_result";
  tool_call_id: string;
  content: string;
}

Sekarang, implementasikan server dasar di src/index.ts:

import express from 'express';
import cors from 'cors';
import { handleMessageRequest } from './handlers/messageHandler';

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(cors());
app.use(express.json({ limit: '10mb' }));

// Endpoint utama untuk pemrosesan pesan
app.post('/v1/messages', handleMessageRequest);

// Endpoint pengecekan kesehatan
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok' });
});

// Mulai server
app.listen(PORT, () => {
  console.log(`Server berjalan di port ${PORT}`);
  console.log(`Pengecekan kesehatan: http://localhost:${PORT}/health`);
  console.log(`Endpoint pesan: http://localhost:${PORT}/v1/messages`);
});

Selanjutnya, implementasikan pengendali pesan di src/handlers/messageHandler.ts:

import { Request, Response } from 'express';
import { ModelRequest, ModelResponse, Message, Content } from '../types';

export async function handleMessageRequest(req: Request, res: Response) {
  try {
    const request = req.body as ModelRequest;
    
    // Validasi dasar
    if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
      return res.status(400).json({ error: 'Format permintaan tidak valid. Array pesan diperlukan.' });
    }
    
    // Catat permintaan yang masuk (untuk debugging)
    console.log('Permintaan diterima dengan', request.messages.length, 'pesan');
    
    // Proses pesan
    const response = processMessages(request.messages);
    
    // Kembalikan respons
    return res.status(200).json(response);
  } catch (error) {
    console.error('Kesalahan saat memproses permintaan:', error);
    return res.status(500).json({ error: 'Kesalahan server internal' });
  }
}

function processMessages(messages: Message[]): ModelResponse {
  // Ekstrak pesan terakhir dari pengguna
  const lastUserMessage = findLastUserMessage(messages);
  
  if (!lastUserMessage) {
    return createErrorResponse("Tidak ada pesan pengguna ditemukan dalam percakapan");
  }
  
  const userQuery = extractTextContent(lastUserMessage);
  
  // Logika generasi respons sederhana
  let responseText = "";
  
  if (userQuery.toLowerCase().includes('halo') || userQuery.toLowerCase().includes('hai')) {
    responseText = "Halo! Bagaimana saya bisa membantu Anda hari ini?";
  } else if (userQuery.toLowerCase().includes('cuaca')) {
    responseText = "Saya tidak memiliki akses ke data cuaca waktu nyata, tetapi saya bisa membantu Anda memahami pola cuaca secara umum.";
  } else if (userQuery.toLowerCase().includes('waktu')) {
    responseText = `Waktu server saat ini adalah ${new Date().toLocaleTimeString()}.`;
  } else {
    responseText = "Saya menerima pesan Anda. Ini adalah respons dari server model sederhana.";
  }
  
  // Konstruksi dan kembalikan respons
  return {
    message: {
      role: "assistant",
      content: [{ 
        type: "text", 
        text: responseText 
      }]
    }
  };
}

function findLastUserMessage(messages: Message[]): Message | undefined {
  // Temukan pesan terakhir dengan peran 'user'
  for (let i = messages.length - 1; i >= 0; i--) {
    if (messages[i].role === 'user') {
      return messages[i];
    }
  }
  return undefined;
}

function extractTextContent(message: Message): string {
  if (typeof message.content === 'string') {
    return message.content;
  } else if (Array.isArray(message.content)) {
    return message.content
      .filter(item => item.type === 'text' && item.text)
      .map(item => item.text)
      .join(' ');
  }
  return '';
}

function createErrorResponse(errorMessage: string): ModelResponse {
  return {
    message: {
      role: "assistant",
      content: [{ 
        type: "text", 
        text: `Kesalahan: ${errorMessage}` 
      }]
    }
  };
}

Langkah 3: Menambahkan Kemampuan Pemanggilan Alat (3 menit)

Buat file baru untuk definisi dan implementasi alat:

touch src/tools.ts

Implementasikan beberapa alat dasar di src/tools.ts:

import { Tool } from './types';

// Definisi alat
export const availableTools: Tool[] = [
  {
    name: "get_current_time",
    description: "Dapatkan waktu server saat ini",
    input_schema: {
      type: "object",
      properties: {
        timezone: {
          type: "string",
          description: "Zona waktu opsional (default ke zona waktu server)"
        }
      }
    }
  },
  {
    name: "calculate",
    description: "Lakukan perhitungan matematis",
    input_schema: {
      type: "object",
      properties: {
        expression: {
          type: "string",
          description: "Ekspresi matematis untuk dievaluasi"
        }
      },
      required: ["expression"]
    }
  }
];

// Implementasi alat
export function executeToolCall(name: string, params: Record): string {
  switch (name) {
    case "get_current_time":
      return getTime(params.timezone);
    case "calculate":
      return calculate(params.expression);
    default:
      throw new Error(`Alat tidak dikenal: ${name}`);
  }
}

function getTime(timezone?: string): string {
  const options: Intl.DateTimeFormatOptions = { 
    hour: '2-digit', 
    minute: '2-digit', 
    second: '2-digit',
    timeZoneName: 'short' 
  };
  
  try {
    if (timezone) {
      options.timeZone = timezone;
    }
    return new Date().toLocaleTimeString('en-US', options);
  } catch (error) {
    return `${new Date().toLocaleTimeString()} (Waktu server)`;
  }
}

function calculate(expression: string): string {
  try {
    // PERINGATAN: Dalam aplikasi nyata, Anda harus menggunakan metode evaluasi yang lebih aman
    // Ini adalah contoh yang disederhanakan untuk demonstrasi saja
    const sanitizedExpression = expression.replace(/[^0-9+\-*/().\s]/g, '');
    const result = eval(sanitizedExpression);
    return `${expression} = ${result}`;
  } catch (error) {
    return `Kesalahan menghitung ${expression}: ${error instanceof Error ? error.message : String(error)}`;
  }
}

Sekarang, perbarui pengendali pesan untuk mendukung pemanggilan alat dengan memodifikasi src/handlers/messageHandler.ts:

// Tambahkan impor ini di bagian atas
import { availableTools, executeToolCall } from '../tools';
import { ToolCall, ToolResult } from '../types';

// Perbarui fungsi handleMessageRequest
export async function handleMessageRequest(req: Request, res: Response) {
  try {
    const request = req.body as ModelRequest;
    
    // Validasi dasar
    if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
      return res.status(400).json({ error: 'Format permintaan tidak valid. Array pesan diperlukan.' });
    }
    
    // Periksa apakah ini adalah permintaan dengan hasil alat
    const lastMessage = request.messages[request.messages.length - 1];
    if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.content)) {
      const toolCalls = lastMessage.content.filter(item => 
        item.type === 'tool_call') as ToolCall[];
      
      if (toolCalls.length > 0) {
        // Ini adalah tindak lanjut dengan hasil alat
        return handleToolResultsResponse(request, res);
      }
    }
    
    // Proses sebagai pesan biasa
    const response = processMessages(request.messages);
    
    // Kembalikan respons
    return res.status(200).json(response);
  } catch (error) {
    console.error('Kesalahan saat memproses permintaan:', error);
    return res.status(500).json({ error: 'Kesalahan server internal' });
  }
}

// Tambahkan fungsi ini untuk menangani pemanggilan alat
function handleToolResultsResponse(request: ModelRequest, res: Response) {
  const messages = request.messages;
  const lastAssistantMessage = messages[messages.length - 1];
  
  if (lastAssistantMessage.role !== 'assistant' || !Array.isArray(lastAssistantMessage.content)) {
    return res.status(400).json({ error: 'Format hasil alat tidak valid' });
  }
  
  // Temukan pemanggilan dan hasil alat
  const toolCalls = lastAssistantMessage.content.filter(
    item => item.type === 'tool_call'
  ) as ToolCall[];
  
  const toolResults = lastAssistantMessage.content.filter(
    item => item.type === 'tool_result'
  ) as ToolResult[];
  
  // Proses hasil
  let finalResponse = "Saya telah memproses informasi berikut:\n\n";
  
  toolResults.forEach(result => {
    const relatedCall = toolCalls.find(call => call.id === result.tool_call_id);
    if (relatedCall) {
      finalResponse += `- ${relatedCall.name}: ${result.content}\n`;
    }
  });
  
  return res.status(200).json({
    message: {
      role: "assistant",
      content: [{ type: "text", text: finalResponse }]
    }
  });
}

// Modifikasi fungsi processMessages untuk menangani pemanggilan alat potensial
function processMessages(messages: Message[]): ModelResponse {
  const lastUserMessage = findLastUserMessage(messages);
  
  if (!lastUserMessage) {
    return createErrorResponse("Tidak ada pesan pengguna ditemukan dalam percakapan");
  }
  
  const userQuery = extractTextContent(lastUserMessage);
  
  // Look for keywords that might trigger tool use
  if (userQuery.toLowerCase().includes('waktu')) {
    return {
      message: {
        role: "assistant",
        content: [
          { type: "tool_call", id: "call_001", name: "get_current_time", input: {} },
          { 
            type: "text", 
            text: "Saya akan memeriksa waktu saat ini untuk Anda." 
          }
        ]
      }
    };
  } else if (userQuery.toLowerCase().match(/hitung|komputasi|berapa adalah \d+[\+\-\*\/]/)) {
    // Ekstrak perhitungan potensial
    const expression = userQuery.match(/(\d+[\+\-\*\/\(\)\.]*\d+)/)?.[0] || "1+1";
    
    return {
      message: {
        role: "assistant",
        content: [
          { 
            type: "tool_call", 
            id: "call_002", 
            name: "calculate", 
            input: { expression } 
          },
          { 
            type: "text", 
            text: `Saya akan menghitung ${expression} untuk Anda.` 
          }
        ]
      }
    };
  }
  
  // Respons default untuk kueri lainnya
  return {
    message: {
      role: "assistant",
      content: [{ 
        type: "text", 
        text: `Saya menerima pesan Anda: "${userQuery}". Bagaimana saya bisa membantu Anda lebih lanjut?` 
      }]
    }
  };
}

Langkah 4: Pengujian dan Penyebaran (2 menit)

Mari kita buat skrip uji sederhana untuk memeriksa server kita. Buat file test.js di direktori root:

const fetch = require('node-fetch');

async function testServer() {
  const url = 'http://localhost:3000/v1/messages';
  
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      messages: [
        {
          role: "user",
          content: [
            {
              type: "text",
              text: "Jam berapa sekarang?"
            }
          ]
        }
      ]
    })
  });
  
  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
}

testServer().catch(console.error);

Untuk menguji server Anda:

# Mulai server
npm run dev

# Di terminal lain, jalankan uji coba
node test.js

Untuk penyebaran cepat, mari kita buat Dockerfile sederhana:

FROM node:16-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]

Bangun dan jalankan kontainer:

docker build -t mcp-server .
docker run -p 3000:3000 mcp-server

Kesimpulan

Dalam waktu hanya 10 menit, kami telah membangun server dasar yang menerapkan konsep kunci protokol pesan AI modern. Server kami dapat:

  1. Memproses permintaan pesan terstruktur
  2. Menjawab dalam format standar
  3. Menangani fungsionalitas pemanggilan alat dasar
  4. Memproses dan merespons hasil alat

Sementara implementasi ini disederhanakan, ia memberikan fondasi yang kokoh untuk pengembangan lebih lanjut. Untuk mengembangkan server ini untuk penggunaan produksi, pertimbangkan:

  • Menambahkan otentikasi dan pembatasan laju
  • Menerapkan penanganan kesalahan dan validasi yang tepat
  • Menghubungkan ke model AI yang sebenarnya untuk pemrosesan
  • Menambahkan alat yang lebih canggih
  • Menerapkan respons streaming
  • Menambahkan logging dan pemantauan yang komprehensif

Ingat bahwa meskipun implementasi kami mengikuti prinsip umum dari protokol pesan AI modern, implementasi spesifik seperti API OpenAI atau API Claude dari Anthropic mungkin memiliki persyaratan tambahan atau variasi kecil dalam format yang diharapkan. Selalu konsultasikan dokumentasi resmi untuk layanan spesifik yang Anda integrasikan.

Bidang protokol pesan AI sedang berkembang pesat, jadi tetaplah diperbarui dengan perkembangan terbaru dan bersiaplah untuk menyesuaikan implementasi Anda seiring evolusi standar. Dengan memahami konsep inti yang ditunjukkan dalam panduan ini, Anda akan siap untuk bekerja dengan kemajuan baru apa pun yang muncul di bidang yang menarik ini.