Dokümanlar menüsü
MCP istemcisi, sunucusu ve firewall
Dış MCP tool'larını dayanıklı AI SDK tool'ları olarak kullanın, kendinizinkileri sunucu tarafında exactly-once ile yayınlayın ve ikisini de tool açıklamalarını rug-pull'a karşı pinleyen bir firewall ile kapılayın.
Ne işe yarar#
MCP, bir ajanın kendi yazmadığı tool'lara uzandığı yoldur. Mesele de tam burada: bir tool'un tanımı — açıklaması ve girdi şeması — sunucunun size verdiği bir veridir ve model onu talimat olarak okur. Bir sunucu siz değerlendirirken bir açıklama, sonra bambaşka bir açıklama sunabilir.
Bu paket iki yönü de kapsar. mcpTools, stdio ya da streamable HTTP üzerinden bir MCP sunucusuna bağlanıp keşfettiği her tool'u bir AI SDK tool'una çevirir; createMcpServer ise kendi tool'larınızı ters yönde yayınlar. mcpFirewall de bu çağrıları kapılayan sıradan bir Guard üretir.
Dayanıklılık yeniden yazılmaz, miras alınır. Burada ayrı bir journal mantığı yoktur: runDurable içindeki bir MCP tool çağrısı, diğer her tool çağrısı gibi journal'lanır.
İstemci — dış tool'lar, dayanıklı biçimde#
mcpTools tembel bağlanır: çağrının kendisi hiç I/O yapmaz. Taşıma katmanı ilk tools() ya da describeTools() ile açılır ve o tek keşif, handle'ın ömrü boyunca paylaşılır. close() idempotenttir — hiç bağlanmadıysanız hiçbir şey yapmaz.
import { mcpTools } from '@gnldev/mcp';
const github = mcpTools({
transport: { kind: 'http', url: 'https://mcp.example.com/mcp' },
prefix: 'github_', // avoids name collisions with your own tools
});
await runDurable({
runId: 'issue-42',
journal,
model,
tools: { ...(await github.tools()) },
prompt: 'Triage the newest issue',
});
await github.close();Exactly-once ağı da geçer. runDurable içinde durableTool sarmalayıcısı her çağrıya {runId}:{toolCallId} biçiminde bir idempotencyKey verir; istemci bunu MCP isteğine params._meta.idempotencyKey olarak taşır. Yani journal'dan gelen istemci-tarafı exactly-once'ı alırsınız; karşı taraf da createMcpServer({ journal }) koşuyorsa sunucu tarafında da exactly-once olur. Anahtar yoksa _meta hiç gönderilmez ve eski davranış korunur.
Firewall — tek Guard, üç kapı#
mcpFirewall düz bir Guard döner; doğrudan runDurable({ guard })'a girer ya da composeGuards ile başka bir guard'a zincirlenir. Sırayla üç kontrol uygular.
1 · İzin/ret listesi. allow verilmezse fail-open çalışır: deny eşleşmeleri dışında her şey serbesttir. allow verilirse fail-closed olur: listede olmayan her tool reddedilir. deny, allow'dan sonra değerlendirilir; ikisi de eşleşirse ret kazanır. Kalıplar string ya da regex olabilir.
2 · Açıklama pinleme. Bir tool ilk görüldüğünde açıklamasının ve girdi şemasının hash'i claim() ile journal'a __mcp_pin__:<sunucu>:<tool> altına yazılır — kazanan yazım o kalıcı pindir. Sonraki her çağrı, sunucunun güncel hash'ini bununla karşılaştırır. Değişmişse çağrı, gerekçesinde iki hash'i de taşıyan bir require-approval döner. Pin journal'da yaşadığı için resume ve replay aynı kararı verir.
3 · Çalıştırma başına çağrı tavanı. maxCallsPerRun verilirse firewall o tool'un çalıştırma içindeki başarılı çağrılarını sayar ve tavana ulaşınca require-approval döner.
import { mcpFirewall, composeGuards } from '@gnldev/mcp';
const guard = mcpFirewall({
server: 'github', // part of the pin key — two servers never collide
journal,
tools: await github.describeTools(),
allow: ['github_list_issues', /^github_read_/], // fail-closed once given
deny: ['github_delete_repo'], // evaluated after allow
maxCallsPerRun: 5,
});
await runDurable({ runId: 'issue-42', journal, model, tools, guard, prompt });
// a changed description ->
// require-approval: "'github_list_issues' tool description changed — poisoning risk
// (pinned: 4b1e…, current: 90ac…)"
// chain it with your policy guard — the firewall runs first
const both = composeGuards(guard, policyGuard(journal, { fallback: 'allow' }));Sunucu — kendi tool'larınızı yayınlayın#
createMcpServer tool'larınızı MCP üzerinden yayınlar. Bir journal verirseniz her callTool durableTool ile sarılır: aynı idempotencyKey ile gelen tekrar isteği yan etkiyi bir kez üretir. Şema çalıştırılabilir olduğunda (zod/valibot ya da standard-schema) argümanlar execute'tan önce doğrulanır; düz JSON Schema varsa ve yorumlayıcı yoksa doğrulama atlanır — yanlışlıkla geçerli bir isteği reddetmek, hiç doğrulamamaktan kötüdür.
import { createMcpServer } from '@gnldev/mcp';
const server = createMcpServer({
tools: { chargeCard },
journal, // omit it and callTool is a plain call
});
await server.callTool({
name: 'chargeCard',
arguments: { amount: 5000 },
idempotencyKey: 'order-123:call-7',
});
// the same request again -> the recorded result, the card is not charged twiceAPI#
mcpToolsBir MCP sunucusuna bağlanır (stdio | http | custom transport) ve bir handle döner: tools(), describeTools(), close(). Tembel bağlanma, paylaşılan keşif, idempotent kapanma.
describeToolsZaten bağlı bir istemcinin tool'larını keşfeder ve her birini { name, description, inputSchema, descriptionHash } olarak özetler — firewall'ın pinlediği girdi budur.
mcpFirewall{ server, journal, tools, allow?, deny?, maxCallsPerRun? } girdisinden bir Guard kurar. İzin/ret → pin kontrolü → çalıştırma başına tavan.
composeGuardsİki Guard'ı zincirler: ikincisi ancak birincisi izin verirse koşar. Ret kısa devre yapar.
mcpPinKeyPin kaydının journal anahtarı: __mcp_pin__:<sunucu>:<tool>. Aynı tool adını yayınlayan iki sunucunun çakışmasını engelleyen şey server alanıdır.
createMcpServerTool'larınızı MCP üzerinden yayınlar; journal verilirse her callTool durableTool ile exactly-once olur.
McpToolSummary{ name, description?, inputSchema, descriptionHash } — descriptionHash, name + description + inputSchema üzerinden alınan argsHash'tir.
deny değil require-approval döner — çalıştırma askıya alınır ve bir insan hem pinlenmiş hem güncel hash'i görür. Sunucunun meşru bir güncellemesi bir saldırıdan ayırt edilemez olmamalı, ve bunu ancak bir insan ayırt edebilir.allow verilmezse izin listesi hiç uygulanmaz. Ve maxCallsPerRun journal'da readRun ister: adaptör sayamıyorsa çağrı engellenmez. Tehdit modeliniz sert bir sınır gerektiriyorsa allow verin ve okuma destekleyen bir journal kullanın.