Tampilan
10.13 Integrasi lewat MCP
Topologi: C — Supervisor + Subagent Materi yang dipakai: 6.1, 6.2, 6.3Prasyarat: 10.2, 10.10
Masalahnya
Asisten internal perusahaan perlu dua hal yang berlawanan arah.
Ke luar: ia harus bisa membaca issue GitHub dan mencari di web. Menulis integrasi GitHub sendiri berarti menangani autentikasi, paginasi, rate limit, dan perubahan API — pekerjaan berminggu-minggu untuk sesuatu yang sudah ada.
Ke dalam: tim lain ingin memakai tool data karyawan yang kamu bangun di 10.2 dari editor mereka sendiri — Cursor, Claude Desktop — tanpa kamu membuatkan aplikasi untuk mereka.
Dua kebutuhan, satu protokol.
Keputusan arsitektur
Keputusan 1 — Bangun sendiri atau pakai MCP server yang ada?
| Tulis tool sendiri | Pakai MCP server pihak ketiga | |
|---|---|---|
| Waktu | Berminggu-minggu | Menit |
| Perawatan saat API berubah | Tanggunganmu | Tanggungan pemelihara |
| Kendali atas perilaku | Penuh | Terbatas |
| Risiko keamanan | Kode yang kamu tinjau | Kode yang tidak kamu tinjau |
| Ketergantungan | Nol | Pada proyek orang lain |
Pilihan: pakai MCP server yang ada untuk GitHub, tulis sendiri untuk data internal.
Pembagiannya mengikuti satu garis: data internal tidak pernah lewat kode pihak ketiga. Tool data karyawan tetap kamu tulis; integrasi GitHub yang sudah ada dipakai apa adanya.
Keputusan 2 — Mengamankan tool yang bukan tulisanmu
Baris keempat tabel di atas bukan formalitas. MCP server pihak ketiga adalah kode yang tidak kamu tinjau dan bisa berubah tanpa memberitahumu.
Tiga lapis pengamanan dari 6.1:
| Lapis | Fungsi |
|---|---|
requireToolApproval | Hentikan tool berisiko sebelum jalan |
allowedHosts | Batasi host yang boleh dihubungi |
forwardInstructions: false | Jangan biarkan server menulis ke system prompt-mu |
forwardInstructions adalah permukaan serangan
Dokumentasi menyatakan fitur ini mati secara default, dan menyarankan menyalakannya hanya untuk server yang kamu percaya.
Alasannya: instruksi dari server disuntikkan ke system prompt agent-mu. Server yang berniat jahat — atau yang akunnya diretas — bisa menyisipkan instruksi yang mengubah perilaku agent, termasuk membujuknya membocorkan data dari tool lain yang kamu tulis sendiri.
Biarkan mati kecuali kamu mengendalikan server itu.
Keputusan 3 — Apa yang diekspos sebagai MCP server?
Ini keputusan yang paling sering kebablasan.
| Layak diekspos | Jangan diekspos |
|---|---|
| Tool baca-saja | Tool yang menghapus data |
| Data yang memang boleh dibagi | Tool yang memakai kredensial pribadimu |
| Kemampuan yang stabil | Tool yang mahal dipanggil |
Pilihan: hanya tool baca. cek-sisa-cuti dan cek-status-reimburse diekspos; tool yang mengubah keadaan tidak.
Ingat juga dari 6.2: kalau pemakainya hanya agent di aplikasimu sendiri, kamu tidak butuh MCP server sama sekali — cukup import. MCP hanya untuk klien eksternal.
Keputusan 4 — Transport
| stdio | HTTP/SSE | |
|---|---|---|
| Cara pakai | Klien menjalankan servermu sebagai subprocess | Servermu jadi layanan |
| Cocok untuk | Editor lokal (Cursor, Claude Desktop) | Akses jarak jauh |
| Autentikasi | Lewat env subprocess | Wajib — ini menghadap jaringan |
Pilihan: stdio untuk versi pertama. Tim menjalankannya lokal dari editor masing-masing, dan tidak ada yang menghadap internet.
Struktur direktori
text
asisten-internal/
├── src/
│ ├── domain/
│ │ └── karyawan.ts ← dari 10.2, tidak berubah
│ ├── mastra/
│ │ ├── agents/
│ │ │ └── asisten-agent.ts ← memakai tool internal + MCP
│ │ ├── tools/
│ │ │ ├── cek-sisa-cuti.ts ← tool internal
│ │ │ └── cek-status-reimburse.ts
│ │ ├── mcp/
│ │ │ ├── client.ts ← KE LUAR: pakai server orang lain
│ │ │ └── server.ts ← KE DALAM: sediakan untuk orang lain
│ │ └── index.ts
│ └── bin/
│ └── mcp-stdio.ts ← entry point yang dijalankan editor
└── package.jsonDua berkas di mcp/ mewakili dua arah yang berlawanan. Menempatkannya berdampingan membuat perbedaannya jelas bagi pembaca berikutnya.
Alur
Perhatikan TI (tool internal) menunjuk ke dua arah: dipakai agent-mu sendiri, dan diekspos lewat MCP server. Satu definisi tool, dua pemakai.
Implementasi
src/mastra/mcp/client.ts
ts
import { MCPClient } from '@mastra/mcp'
export const mcp = new MCPClient({
// id wajib diisi — mencegah kebocoran memori saat ada beberapa instance.
id: 'mcp-eksternal',
servers: {
github: {
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-github'],
// Subprocess TIDAK mewarisi env prosesmu. Sebutkan eksplisit.
env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN! },
// Tool yang mengubah keadaan butuh persetujuan.
requireToolApproval: ({ toolName }) =>
/create|update|delete|merge|close/i.test(toolName),
// Biarkan mati. Lihat peringatan di keputusan 2.
forwardInstructions: false,
},
docs: {
url: new URL('https://mcp.internal.example.com/sse'),
allowedHosts: ['mcp.internal.example.com'],
},
},
timeout: 60_000,
})Tiga hal yang sering salah dan benar di sini.
env disebut eksplisit. Ingat dari 6.1: subprocess Stdio hanya mewarisi daftar putih terkurasi (PATH, HOME, dan beberapa lainnya). GITHUB_TOKEN tidak termasuk.
requireToolApproval berupa fungsi, bukan true. Menyetujui semua tool membuat pengguna lelah dan akhirnya menyetujui tanpa membaca. Batasi pada yang benar-benar mengubah keadaan.
allowedHosts tidak mendukung wildcard. Pencocokannya persis.
src/mastra/agents/asisten-agent.ts
ts
import { Agent } from '@mastra/core/agent'
import { mcp } from '../mcp/client.ts'
import { cekSisaCuti } from '../tools/cek-sisa-cuti.ts'
import { cekStatusReimburse } from '../tools/cek-status-reimburse.ts'
// Pola tahan banting: satu server mati tidak menjatuhkan agent.
const { tools: toolMcp, errors, durations } = await mcp.listToolsWithErrors({
perServerTimeoutMs: 3_000,
})
if (Object.keys(errors).length > 0) {
console.warn('sebagian MCP server tidak dapat dihubungi:', errors, durations)
}
export const asistenAgent = new Agent({
id: 'asisten-agent',
name: 'Asisten Internal',
instructions: `Kamu asisten internal perusahaan.
Pakai cekSisaCuti dan cekStatusReimburse untuk data karyawan.
Pakai tool github_* untuk pertanyaan tentang issue dan pull request.
Untuk tindakan yang mengubah data di GitHub, jelaskan dulu apa yang akan kamu
lakukan sebelum menjalankannya.`,
model: 'openai/gpt-5.6-sol',
// Tool internal dan tool MCP digabung — agent tidak membedakannya.
tools: { cekSisaCuti, cekStatusReimburse, ...toolMcp },
})Pakai listToolsWithErrors(), bukan listTools(). Perbedaannya baru terasa di produksi: dengan listTools(), satu MCP server yang sedang mati membuat seluruh agent gagal dibentuk. Dengan versi ini, agent tetap hidup dengan tool yang berhasil dimuat.
durations juga berguna — ia langsung memberitahu server mana yang membuat startup lambat.
src/mastra/mcp/server.ts
ts
import { MCPServer } from '@mastra/mcp'
import { cekSisaCuti } from '../tools/cek-sisa-cuti.ts'
import { cekStatusReimburse } from '../tools/cek-status-reimburse.ts'
export const mcpServer = new MCPServer({
id: 'hr-internal',
name: 'HR Internal',
version: '1.0.0',
description:
'Akses baca data kepegawaian: sisa kuota cuti dan status reimbursement.',
instructions:
'Pakai tool ini untuk menjawab pertanyaan tentang kuota cuti dan status ' +
'reimbursement karyawan. Semua tool bersifat baca-saja. NIK berformat K-XXX.',
// HANYA tool baca. Tool yang mengubah keadaan tidak diekspos.
tools: { cekSisaCuti, cekStatusReimburse },
})instructions server ditulis untuk system prompt orang lain
Klien bisa memilih menambahkan teks ini ke system prompt agent mereka lewat forwardInstructions.
Karena itu tulis yang membantu — cara memakai, format yang diharapkan — bukan yang memaksa. Kamu sedang menulis untuk agent yang tidak kamu kendalikan.
src/bin/mcp-stdio.ts
ts
import { mcpServer } from '../mastra/mcp/server.ts'
// Dijalankan oleh editor sebagai subprocess.
await mcpServer.startStdio()Konfigurasi di sisi editor:
json
{
"mcpServers": {
"hr-internal": {
"command": "node",
"args": ["/path/ke/asisten-internal/dist/bin/mcp-stdio.js"],
"env": { "DATABASE_URL": "..." }
}
}
}Nama tool yang dilihat klien
Ini bagian yang paling sering mengejutkan, dan ada di kedua arah.
Saat kamu jadi klien — tool dari MCP server diberi awalan nama server:
text
github_create_issue
github_list_pull_requests
docs_searchAwalan itu mencegah tabrakan saat dua server punya tool bernama sama.
Saat kamu jadi server — pola penamaannya dari 6.2:
| Yang didaftarkan | Menjadi tool bernama |
|---|---|
tools: { cekSisaCuti } | cekSisaCuti |
agents: { hrAgent } | ask_hrAgent |
workflows: { approvalWorkflow } | run_approvalWorkflow |
Karena key menjadi nama publik, pilih key yang deskriptif. ask_a tidak memberitahu klien apa pun.
Bagian ini tidak diverifikasi end-to-end
Berbeda dari kasus lain di bagian ini, jabat tangan MCP langsung tidak dijalankan di sini — ia membutuhkan MCP server eksternal dan editor yang berjalan.
Yang sudah diverifikasi: seluruh bentuk konstruksi MCPClient dan MCPServer di atas lolos type-check terhadap @mastra/mcp 1.17.3, termasuk requireToolApproval berupa fungsi, allowedHosts, dan listToolsWithErrors().
Yang belum: perilaku runtime saat benar-benar tersambung ke server pihak ketiga. Uji sendiri sebelum mengandalkannya di produksi.
A2A: kalau yang di seberang adalah agent utuh
MCP menyeberangi batas ke tool. Kalau yang ingin kamu panggil adalah agent milik tim lain — dengan penalaran, tool, dan memory-nya sendiri yang tetap privat — itu A2A.
ts
import { A2AAgent } from '@mastra/core/a2a'
const agentTimLain = new A2AAgent({
url: 'https://finance.internal.example.com/api/.well-known/finance-agent/agent-card.json',
})
const hasil = await agentTimLain.generate('Berapa anggaran tersisa untuk Q4?')Yang menarik: agent Mastra-mu sudah otomatis mengekspos agent card begitu servernya berjalan, di /api/.well-known/<id>/agent-card.json. Kamu tidak perlu menulis kode apa pun untuk menjadi penyedia A2A.
| Pakai MCP kalau | Pakai A2A kalau |
|---|---|
| Yang diseberang adalah fungsi | Yang diseberang punya penalaran sendiri |
| Kamu ingin memakai tool-nya | Kamu ingin mendelegasikan pekerjaan |
| Hasilnya data | Hasilnya penilaian |
Best practice yang dipakai di sini
1. Data internal tidak lewat kode pihak ketiga. Tool data karyawan ditulis sendiri; integrasi umum dipakai dari ekosistem.
2. forwardInstructions: false untuk server pihak ketiga. Selalu.
3. requireToolApproval sebagai fungsi, bukan true. Persetujuan yang terlalu sering diabaikan pengguna.
4. listToolsWithErrors() di produksi. Satu server mati tidak boleh menjatuhkan agent.
5. Hanya tool baca yang diekspos. Tool yang mengubah keadaan tetap di dalam.
6. Satu definisi tool, dua pemakai. Tool yang sama dipakai agent internal dan diekspos lewat MCP — tidak ada duplikasi.
Kapan pola ini salah
| Situasi | Kenapa gagal | Ke mana |
|---|---|---|
| Pemakainya cuma agent di aplikasi yang sama | MCP menambah protokol tanpa manfaat | Cukup import |
| Yang diseberang punya penalaran sendiri | MCP untuk tool, bukan agent | A2A |
| Butuh menjalankan coding agent di repo | Bukan wilayah MCP | 10.14 Coding agent |
| Server MCP harus diakses dari internet | stdio tidak cukup | HTTP/SSE + autentikasi, 9.3 |
Coba sendiri
Sambungkan agent ke satu MCP server nyata (misalnya filesystem atau GitHub), lalu ekspos satu tool buatanmu sendiri dan pakai dari editormu.
Selesai kalau
- [ ] Tool dari MCP server eksternal muncul dengan awalan nama server
- [ ]
listToolsWithErrors()dipakai, dan kamu sengaja merusak satu server untuk membuktikan agent tetap hidup - [ ]
durationsmenunjukkan waktu penemuan tiap server - [ ] Tool buatanmu terlihat di editor dan bisa dipanggil dari sana
- [ ]
forwardInstructionsmati untuk server pihak ketiga - [ ]
requireToolApprovalhanya memicu untuk tool yang benar-benar berisiko - [ ] Kamu bisa menyebutkan satu tool di sistemmu yang sengaja tidak kamu ekspos, beserta alasannya
Kalau macet
MCP server Stdio gagal autentikasi padahal .env sudah benar? Subprocess tidak mewarisi env prosesmu. Sebutkan variabelnya di properti env.
Seluruh aplikasi gagal jalan karena satu server mati? Kamu memakai listTools(). Ganti ke listToolsWithErrors().
Tool-mu tidak muncul di editor? Periksa path di konfigurasi editor menunjuk berkas hasil build, bukan .ts mentah.
Lanjut ke mana
MCP memberi agent akses ke tool orang lain. Kasus berikutnya memberinya sesuatu yang lebih berbahaya dan lebih berguna: tempat kerja nyata — berkas, shell, dan kemampuan menjalankan kode — 10.14 Coding Agent & Sandbox.