Sertifika versiyon bildirimi: Bu makale MCP spesifikasyon versiyonu 2025-11-25‘i hedef almaktadır. Protokol arasındaki sürümlerde önemli değişiklikler yapılmıştır, tool şema alan isimleri ve hata kodları önceki sürümlerde değişmiştir. Yayınlamadan önce
protocolVersiondizgisini resmi MCP spesifikasyon değişiklik Günlüğü ile kontrol edin ve her spesifikasyon güncellemesinde bunu doğrulayın.
Çoğu Laravel geliştiricisi Model Context Protocol ile istemci tarafında karşılaşır. Claude Desktop’a bir sunucu eklediniz, IDE’niz buna erişim sağladı ve araçlar belirdi. Siz tüketiciydiniz. Bu makale o ilişkinin diğer tarafını oluşturuyor: harici AI ajanlarının keşfedebileceği, kimlik doğrulamasını gerçekleştirebileceği ve çağrıda bulunabileceği bir laravel mcp sunucusu.
Bu, AI entegrasyonlarının ölçeklenebilir şekilde bakımı için gerekli altyapı kararlarını kapsayan daha geniş bir Laravel AI Mimarisi serisinin parçasıdır. Kod örnekleri her bir parçada tutarlı bir kurgusal alan (Laravel tabanlı bir bilgi tabanı uygulaması) kullanarak, her bir snippet’ın birbirine bağlı bir sistemi oluşturmasına olanak sağlar.
MCP Gerçekten Nedir (ve Ne Değildir)
MCP Gerçekten Nedir (ve Ne Değildir)
MCP, Model Context Protocol, bir JSON-RPC 2.0 protokolüdür. Ne bir API standardıdır, ne REST’tir, ne de bir framework. İki taraf arasında yapılandırılmış bir konuşma tanımlar: bir istemci ve bir sunucu. İstemci, mevcut araçların bir listesini ister. Sunucu, bunları açıklar. İstemci, sunucudan birini çalıştırmasını ister. Sunucu, girişi doğrular, mantığı çalıştırır ve sonucu bir JSON-RPC zarfı içinde döner.
İstemci ve sunucu arasındaki ayrım önemlidir çünkü çoğu Laravel geliştiricisi yalnızca bir tarafta yer almıştır. Laravel Boost’u iş akışınıza entegre ettiğinizde, AI asistanınız, Eloquent modellerinizi, rotalarınızı ve konfigürasyonunuzu açığa çıkaran bir MCP sunucusuna bağlanır. Siz istemci olursunuz. Laravel Boost yanıt veren taraf olur. Bu makale, o telin diğer ucudur, sunucuyu inşa edersiniz.
Size bir sunucu iskeleti oluşturacak olan php-mcp/laravel adlı bir PHP paketi mevcuttur. Yazım anında, 2025-03-26 sürüm numarasına hedef alıyor ve bu, mevcut kararlı sürümden iki revizyon geridedir. Protokol sürüm müzakeresi ve şema doğruluğunun önemli olduğu üretim sistemlerinde, taşıma katmanını kendiniz inşa etmek, hangi sürümü bildireceğiniz ve müzakerelerin başarısız olduğu durumları nasıl yöneteceğiniz üzerinde tam kontrol sağlar. Buradaki yaklaşım budur.
İki Taşıma Modu: stdio vs HTTP+SSE
İki Taşıma Modu: stdio vs HTTP+SSE
MCP, iki taşıma modu destekler. Dağıtım bağlamınız için yanlış olanı seçmek, en yaygın erken hatadır.
| Mod | Taşıma | Kullanım Durumu | Çoklu İstemci | Üretim İçin Uygun |
|---|---|---|---|---|
| stdio | stdin / stdout | Yerel geliştirme, IDE araçları | Hayır | Hayır |
| HTTP+SSE | HTTP POST + Sunucu Tarafından Gönderilen Olaylar | Uzaktan, barındırılan, çoklu istemci | Evet | Evet |
stdio bir çocuk işlem olarak çalışır. İstemci bunu başlatır, stdin/stdout üzerinden iletişim kurar ve işlem oturum sona erdiğinde çıkar. Hızlı, sıfır ağ yapılandırması ile yalnızca yerel araçlar için uygundur.
HTTP+SSE sürekli bir HTTP prosesi olarak çalışır. İstemciler ağa bağlanır. JSON-RPC çağrıları POST istekleri olarak gelir. Akış cevapları ayrı bir kanalda Sunucu Tarafından Gönderilen Olaylar kullanılarak yapılır. Bu makaledeki her şey HTTP+SSE’yi hedef almaktadır.
Sunucu Manifestosunu Tanımlama ve Yetenek Müzakeresi
Sunucu Manifestosunu Tanımlama ve Yetenek Müzakeresi
Her MCP oturumu, bir initialize el sıkışması ile başlar. İstemci, protokol versiyonunu ve yeteneklerini ilan eder; sunucu, kendi kimliği ve desteklediği ile yanıt verir.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": {
"name": "claude-desktop",
"version": "1.0.0"
}
}
}
Sunucu yanıt verir:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {
"tools": { "listChanged": false }
},
"serverInfo": {
"name": "knowledge-base-mcp",
"version": "1.0.0"
}
}
}
initialize işleminden sonra, istemci bir notifications/initialized bildirimi gönderir (id yok, yanıt beklenmiyor) ve oturum canlıdır. protocolVersion dizgisi sadece estetik değildir. Eşleşmeyen sürümler bazı istemcilerde sessiz hatalara neden olabilir; her zaman desteklediğiniz şeyi tam olarak yansıtın.
Her iki değeri de yapılandırmadan alın:
// config/mcp.php
return [
'protocol_version' => env('MCP_PROTOCOL_VERSION', '2025-11-25'),
'server_name' => env('MCP_SERVER_NAME', 'knowledge-base-mcp'),
];
// app/MCP/Handlers/InitializeHandler.php
namespace App\MCP\Handlers;
class InitializeHandler
{
public function handle(array $params): array
{
return [
'protocolVersion' => config('mcp.protocol_version'),
'capabilities' => [
'tools' => ['listChanged' => false],
],
'serverInfo' => [
'name' => config('mcp.server_name'),
'version' => config('app.version', '1.0.0'),
],
];
}
}
listChanged: false istemcinin araç listenizin oturum boyunca statik olduğunu belirtir. Eğer ilgili push bildirim mekanizmasını uygulamadıysanız, bunu true olarak ayarlamayın; aksi takdirde yerine getiremeyeceğiniz bir yeteneği duyuruyorsunuz.
Laravel Route Katmanını İnşa Etme
Laravel Route Katmanını İnşa Etme
Tüm JSON-RPC çağrıları tek bir uç noktaya POST olarak gelir. Bu, MCP taşıma sözleşmesidir: bir URL, tüm metod dağıtımı sunucu tarafından ele alınır.
// routes/api.php
use App\Http\Controllers\McpController;
Route::middleware(['auth:sanctum', 'ability:mcp:connect', 'throttle:mcp'])
->group(function () {
Route::post('/mcp', [McpController::class, 'handle']);
});
Burada bir closure kullanmak cazip ama yanlıştır. Özelleşmiş bir denetleyici, constructor injection, test edilebilirlik ve yöntem listeniz genişledikçe temiz bir genişletme noktası sağlar.
// app/Http/Controllers/McpController.php
namespace App\Http\Controllers;
use App\MCP\Exceptions\McpException;
use App\MCP\Handlers\InitializeHandler;
use App\MCP\Handlers\ToolsCallHandler;
use App\MCP\Handlers\ToolsListHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class McpController extends Controller
{
public function __construct(
private InitializeHandler $initialize,
private ToolsListHandler $toolsList,
private ToolsCallHandler $toolsCall,
) {}
public function handle(Request $request): JsonResponse|Response
{
$payload = $request->json()->all();
$id = $payload['id'] ?? null;
$method = $payload['method'] ?? null;
$params = $payload['params'] ?? [];
// Bildirimler id taşımaz ve yanıt beklenmez.
if ($id === null && str_starts_with((string) $method, 'notifications/')) {
return response()->noContent();
}
try {
$result = match ($method) {
'initialize' => $this->initialize->handle($params),
'tools/list' => $this->toolsList->handle($params),
'tools/call' => $this->toolsCall->handle($params),
default => throw new McpException(-32601, 'Method not found'),
};
} catch (McpException $e) {
return response()->json([
'jsonrpc' => '2.0',
'id' => $id,
'error' => ['code' => $e->getCode(), 'message' => $e->getMessage()],
]);
}
return response()->json([
'jsonrpc' => '2.0',
'id' => $id,
'result' => $result,
]);
}
}
// app/MCP/Exceptions/McpException.php
namespace App\MCP\Exceptions;
use RuntimeException;
class McpException extends RuntimeException
{
public function __construct(int $code, string $message)
{
parent::__construct($message, $code);
}
}
Önemli bir nokta belirtmek gerekir: MCP hataları HTTP 200 içinde JSON-RPC zarfı içinde gelir. Protokol düzeyi hataları için HTTP katmanında 4xx veya 5xx döndürmeyin. Uç noktanız, taşımayla ilgili gerçek hatalar (middleware tarafından kimlik reddi, sunucu çökmesi) dışında başka bir şey için 200 dışındaki yanıtlar vermez. MCP hata kodları (-32601, -32602 vb.) tamamen HTTP durum kodlarından ayrıdır.
Tool Tanımlarını JSON Schema ile Yapılandırma
Tool Tanımlarını JSON Schema ile Yapılandırma
tools/list yanıtı, sunucunuzun her bağlanan ajana sunduğu sözleşmedir. İyi bir tür tanımı, ajanın hurda giriş göndermesine karşı birincil savunmanızdır.
// app/MCP/Handlers/ToolsListHandler.php
namespace App\MCP\Handlers;
class ToolsListHandler
{
public function handle(array $params): array
{
return [
'tools' => [
[
'name' => 'v1__search_articles',
'description' => 'Arama sorgusuna uyan makaleleri bilgi tabanında arar. Başlık, özet ve URL ile birlikte en fazla 10 sonuç döner. v1 — kararlı.',
'inputSchema' => [
'$schema' => 'http://json-schema.org/draft-07/schema#',
'type' => 'object',
'required' => ['query'],
'additionalProperties' => false,
'properties' => [
'query' => [
'type' => 'string',
'description' => 'Tam metin arama sorgusu.',
'minLength' => 1,
'maxLength' => 500,
],
'limit' => [
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 50,
],
],
],
],
[
'name' => 'v1__get_article',
'description' => 'Bir makalenin tam içeriğini slug\'ı ile alır. v1 — kararlı.',
'inputSchema' => [
'$schema' => 'http://json-schema.org/draft-07/schema#',
'type' => 'object',
'required' => ['slug'],
'additionalProperties' => false,
'properties' => [
'slug' => [
'type' => 'string',
'description' => 'Makalenin slug\'ı, örn. laravel-scout-meilisearch.',
'pattern' => '^[a-z0-9-]+$',
],
],
],
],
],
];
}
}
additionalProperties: false şemada tanımlanmayan alanları reddeder. Ajans bazen tanımlanmamış özellikler gönderebilir: hayalinden, eski bir tool tanımının önbelleklenmesinden veya istemci uygulaması hatasından. Açık bir reddetme, bu hataları temiz bir şekilde ortaya çıkarır. Giriş şeması doğrulaması ve sizin kendi ajans akışlarınız içinde kullanım uygulamaları için aynı disiplin geçerlidir.
[Mimarın Notu] Üretim kodunda tool tanımlarını inline PHP dizileri olarak yerleştirmeyin; tanımlamaları yapılandırma dosyalarından veya özel bir dizinden yükleyen bir
ToolRegistrysınıfı oluşturun. Buradaki inline yaklaşımı okunabilirlik için sunmaktır, mimari için değil.
Tool İşi: tools/call Akışı
Tool İşi: tools/call Akışı
JSON Schema doğrulayıcı, içeri aktarım desteği ile birlikte justinrainbow/json-schema‘yı sarar, bu da Draft 7’yi kutudan çıkarır:
composer require justinrainbow/json-schema
// app/MCP/JsonSchema/Validator.php
namespace App\MCP\JsonSchema;
use JsonSchema\Validator as JsonSchemaValidator;
class Validator
{
public function validate(array $data, array $schema): array
{
$validator = new JsonSchemaValidator();
$dataObj = json_decode(json_encode($data));
$schemaObj = json_decode(json_encode($schema));
$validator->validate($dataObj, $schemaObj);
if ($validator->isValid()) {
return [];
}
return array_map(
fn($e) => "[{$e['property']}] {$e['message']}",
$validator->getErrors()
);
}
}
// app/MCP/Handlers/ToolsCallHandler.php
namespace App\MCP\Handlers;
use App\MCP\Exceptions\McpException;
use App\MCP\JsonSchema\Validator;
use App\MCP\Tools\GetArticleTool;
use App\MCP\Tools\SearchArticlesTool;
class ToolsCallHandler
{
private array $tools;
public function __construct(
private Validator $validator,
SearchArticlesTool $searchArticles,
GetArticleTool $getArticle,
) {
$this->tools = [
'v1__search_articles' => $searchArticles,
'v1__get_article' => $getArticle,
];
}
public function handle(array $params): array
{
$toolName = $params['name'] ?? null;
$arguments = $params['arguments'] ?? [];
if ($toolName === null || ! isset($this->tools[$toolName])) {
throw new McpException(-32602, "Unknown tool: {$toolName}");
}
$tool = $this->tools[$toolName];
$errors = $this->validator->validate($arguments, $tool->schema());
if (! empty($errors)) {
throw new McpException(-32602, 'Invalid arguments: ' . implode(', ', $errors));
}
try {
$result = $tool->execute($arguments);
} catch (\Throwable $e) {
// Tool yürütme hataları sonuç zarfında isError: true ile gösterilir.
// Bu, bir JSON-RPC hata nesnesi değildir - ajan bunu alır ve
// protokol katmanı bozulmadan hata hakkında akıl yürütebilir.
return [
'content' => [['type' => 'text', 'text' => $e->getMessage()]],
'isError' => true,
];
}
return [
'content' => [['type' => 'text', 'text' => json_encode($result)]],
'isError' => false,
];
}
}
isError alanı MCP tool sonucu şemasına aittir, JSON-RPC katmanına değil. Bir JSON-RPC hata nesnesi, protokol çağrısı başarısız olduğunda döner. Tool sonucu içindeki isError: true, iş mantığının başarısız olduğu anlamına gelir. Claude her ikisini de alır ve bunlar hakkında farklı bir akıl yürütürler. Bunları karıştırmayın.
Bilgi tabanı alanı için somut bir tool uygulanması:
// app/MCP/Tools/SearchArticlesTool.php
namespace App\MCP\Tools;
use App\Models\Article;
class SearchArticlesTool
{
public function schema(): array
{
return [
'$schema' => 'http://json-schema.org/draft-07/schema#',
'type' => 'object',
'required' => ['query'],
'additionalProperties' => false,
'properties' => [
'query' => ['type' => 'string', 'minLength' => 1, 'maxLength' => 500],
'limit' => ['type' => 'integer', 'default' => 10, 'minimum' => 1, 'maximum' => 50],
],
];
}
public function execute(array $arguments): array
{
$limit = min((int) ($arguments['limit'] ?? 10), 50);
return Article::where('status', 'published')
->where(function ($q) use ($arguments) {
$q->where('title', 'like', "%{$arguments['query']}%")
->orWhere('content', 'like', "%{$arguments['query']}%");
})
->orderBy('published_at', 'desc')
->limit($limit)
->get(['title', 'slug', 'excerpt', 'published_at'])
->map(fn($a) => [
'title' => $a->title,
'slug' => $a->slug,
'excerpt' => $a->excerpt,
'url' => url("/articles/{$a->slug}"),
])
->toArray();
}
}
[Üretim Tuzağı] Büyük makale tablolarında
LIKEsorguları yük altında iyi çalışmayacaktır. Birkaç yüz bin satırda,contentsütunu üzerindekiLIKEtam tablo taramaları üretir. Laravel Scout ile Meilisearch, üretim için uygun bir değişkendir. BuradakiLIKEsorgusu, örneği kendi kendine tutmaktı, not arama katmanı üzerinde değil.
Laravel Sanctum ile Kimlik Doğrulamalı MCP Uç Noktaları
Laravel Sanctum ile Kimlik Doğrulamalı MCP Uç Noktaları
MCP’nin üzerindeki HTTP kimlik doğrulaması zorunludur. Her bağlanan ajan, bir istemci kimlik tanımlayıcısına bağlı Sanctum API token’ı sunmalıdır.
Token’ları, MCP istemci türünü belirleyen bir yetenekle sağlayın:
// Bir denetleyici veya Artisan komutunun, istemci token'larını sağlaması:
$token = $user->createToken('claude-desktop', ['mcp:connect'])->plainTextToken;
Adlandırılmış hız sınırlayıcıyı AppServiceProvider::boot() içinde tanımlayın:
// app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
public function boot(): void
{
RateLimiter::for('mcp', function (Request $request) {
$tokenId = $request->user()?->currentAccessToken()?->id;
return $tokenId
? Limit::perMinute(60)->by("mcp_token:{$tokenId}")
: Limit::perMinute(10)->by($request->ip());
});
}
Dağıtılmış işçiler arasında Redis destekli uygulama sağlamak için düzeltme middleware alias’ını bootstrap/app.php‘da değiştirebilirsiniz:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
]);
})
Route grup içindeki throttle:mcp referansı artık bu adlandırılmış sınırlayıcıyı kullanılmaktadır, anahtar düzeyinde olup, token düzeyinde bir yapı sağlar. Multi-istemci kurulumlarında, bir kullanıcı Claude Desktop, Cursor ve özel bir ajan için tokenlar sağlayabilir. Bağımsız token düzeyinde anahtarlar, her istemcinin kendi kova kümesini vermektedir.
Bu bölüm kasıtlı olarak dar tutulmuştur. token izleme middleware kalıpları, MCP bağlamlarına temiz bir şekilde genişletilmektedir, kimlik doğrulama katmanınız yerinde olduğunda, hem LLM API çağrıları hem de MCP tool çağrıları için birleşik bir kullanım defteri sağlar.
[Dikkatli Ol] Bir Sanctum token’ını birden fazla MCP istemcisi arasında asla paylaşmayın. Her istemci tanımlayıcısı için bir token sağlayın. Tek bir karmaşık istemcinin iptal edilmesi, diğerlerini etkilememelidir. Sağlamanın maliyeti önemsizdir; çapraz kesme riski önemli değildir.
[Üretim Tuzağı] Redis destekli throttling olmadan, dağıtımda (birden fazla PHP-FPM işçisi veya Octane işçisi) hız limitleri tutarlı bir şekilde uygulanamaz. Bellekteki sürücü yalnızca kendi sürecini görür. Bu “şu an için yeterli değil” durumu değildir: yavaş bir müşteri dağıtımı, korunmasız bir uç noktada bu boşluğu en kötü zamanda ortaya çıkaracaktır.
MCP Sunucunuzu Uçtan Uca Test Etme
MCP Sunucunuzu Uçtan Uca Test Etme
İki test katmanı gerekmektedir. İlk katman, JSON-RPC arayüzünün belirli PHPUnit kapsamıdır. İkincisi ise Claude Desktop ile entegrasyon. Bu sunucu üretimde çalışıyorsa hiçbiri opsiyonel değildir.
PHPUnit, MCP uç noktasına, Laravel uygulamasındaki her diğer HTTP uç noktası gibi yaklaşır:
// tests/Feature/McpTest.php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Testing\TestResponse;
use Tests\TestCase;
class McpTest extends TestCase
{
use RefreshDatabase;
private function mcpPost(array $payload, ?User $user = null): TestResponse
{
$user ??= User::factory()->create();
$token = $user->createToken('test-client', ['mcp:connect'])->plainTextToken;
return $this->withToken($token)->postJson('/api/mcp', $payload);
}
public function test_initialize_returns_server_info(): void
{
$response = $this->mcpPost([
'jsonrpc' => '2.0',
'id' => 1,
'method' => 'initialize',
'params' => [
'protocolVersion' => '2025-11-25',
'capabilities' => [],
'clientInfo' => ['name' => 'test', 'version' => '1.0'],
],
]);
$response->assertOk()
->assertJsonPath('jsonrpc', '2.0')
->assertJsonPath('result.protocolVersion', '2025-11-25')
->assertJsonStructure(['result' => ['serverInfo', 'capabilities']]);
}
public function test_tools_list_returns_expected_tools(): void
{
$response = $this->mcpPost([
'jsonrpc' => '2.0',
'id' => 2,
'method' => 'tools/list',
'params' => [],
]);
$response->assertOk()
->assertJsonPath('result.tools.0.name', 'v1__search_articles');
}
public function test_tools_call_rejects_unknown_tool(): void
{
$response = $this->mcpPost([
'jsonrpc' => '2.0',
'id' => 3,
'method' => 'tools/call',
'params' => ['name' => 'nonexistent_tool', 'arguments' => []],
]);
// MCP hataları, hata mesajının JSON-RPC zarfı içinde bulunur.
$response->assertOk()->assertJsonPath('error.code', -32602);
}
public function test_unknown_method_returns_method_not_found(): void
{
$response = $this->mcpPost([
'jsonrpc' => '2.0',
'id' => 4,
'method' => 'resources/list',
'params' => [],
]);
$response->assertOk()->assertJsonPath('error.code', -32601);
}
public function test_unauthenticated_request_is_rejected_at_http_layer(): void
{
$response = $this->postJson('/api/mcp', [
'jsonrpc' => '2.0',
'id' => 5,
'method' => 'initialize',
'params' => [],
]);
// Auth hataları, HTTP katmanında tek başına geçerli bir durumdur.
$response->assertUnauthorized();
}
}
Claude Desktop entegrasyonu claude_desktop_config.json kullanır. HTTP+SSE sunucusu için, uç noktanıza doğrudan işaret edin:
{
"mcpServers": {
"knowledge-base": {
"url": "https://your-app.test/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_SANCTUM_TOKEN"
}
}
}
}
Laravel Herd veya Sail altında yerel geliştirme için, .test alan adınızı kullanın. Claude Desktop, doğrudan HTTP taşımasını destekler, stdio proxy gerektirmez.
Bir JSON-RPC uç noktasının test edilebilirliği tesadüfi değildir. Bu, MCP taşıma katmanını ince tutmanın ve iş mantığını tipli tool işlemcilerine itmenin doğrudan bir sonucudur. Bu, üretim AI mimarisi: yönetim ve teleskopik yaklaşım ilkelerini de destekler: gözlemlenebilir, sınırlı, bağımsız olarak test edilebilir bileşenler. Bu disipline sahip olmayan bir MCP sunucusu, sunum için bir sunucu sağlar, işletmek için değil.
Üretim Tutundurma
Üretim Tutundurma
Sunucunun geliştirmeye doğru cevap verecek şekilde ayarlanması bir öğleden sonranın işidir. Çoğul istemci yükü altında, spesifikasyon güncellemeleri üzerinden ve dağıtım sınırları boyunca güvenilir tutulması, gerçek mühendislik sorunudur.
Her MCP Müşterisi İçin Hız Limitlemesi
Her MCP Müşterisi İçin Hız Limitlemesi
AppServiceProvider‘daki tanımlı adlandırılmış hız sınırlayıcı, zaten Sanctum token kimliğine göre anahtar koymaktadır. Karışık istemci güven seviyeleri ile dağıtımlarda bunu rol bazlı katmanlamayla genişletebilirsiniz:
// app/Providers/AppServiceProvider.php
RateLimiter::for('mcp', function (Request $request) {
$user = $request->user();
$tokenId = $user?->currentAccessToken()?->id;
if (! $tokenId) {
return Limit::perMinute(10)->by($request->ip());
}
$limit = $user->hasRole('trusted-agent') ? 300 : 60;
return Limit::perMinute($limit)->by("mcp_token:{$tokenId}");
});
Bunu yüksek hacimle çalıştıran takımlar, etkileşimli kullanım için varsayılan 60 tıklamanın cömert olduğunu, ancak karmaşık akıl yürütme döngüleri boyunca tool çağrılarını gruplayan ajanlar için dar olduğunu gördüler. Kesim limitlerini kilitlemeden önce gerçek ajan trafiğinizi profil edin.
Tool Şeması Sürümleme Stratejisi
Tool Şeması Sürümleme Stratejisi
Tool tanımları değişecektir. Soru, kırılmayı ne zaman yapmanız gerektiği ve bunu daha önce önbelleğe alınmış ajanlara nasıl ileteceğinizdir.
v1__ adlandırma ön eki sürümleme stratejisidir. İlk günden itibaren bunu tutun:
v1__search_articles → kararlı, üretimde
v2__search_articles → yeni parametre seti, paralel dağıtım altında
Geçiş penceresi boyunca, her iki sürüm de tools/list yanıtında görünür. v1__search_articles‘ı önbelleğe alan ajanslar çalışmaya devam eder. Güncel tool listesini alan ajanslar v2__search_articles‘ı alır. v1__search_articles aşamalara geçildikten sonra, gözlem verilerinizi doğruladıktan sonra emekliye ayırırsınız.
Tanımlama alanında bu durumu belirtin, çünkü bu alanı ajanslar okur:
'name' => 'v1__search_articles',
'description' => '[DEPRECATED — use v2__search_articles — removal: 2026-09-01] Bilgi tabanında arama yap...',
Claude, tool açıklamalarını okur ve bu sinyali aklında tutar. Bu bir garanti değildir, ancak hiçbir maliyeti yoktur ve bazen bir insanın eylemde bulunabileceği bir şekilde durumu yüzeye çıkarır.
[Dikkatli Ol] İlk kırılma değişikliğinize ihtiyaç duymadan önce sürümleme geleneklerini
ToolRegistry‘nize yapın. Aktif müşterileri olan bir üretim sunucusundaki ad alanı öneklerini geriye dönük olarak uygulamak, koordineli bir dağıtım ve bakım penceresi gerektirir. İlk günden itibaren yapın.
Gözlemlenebilirlik: MCP Oturumlarını İzleme
Gözlemlenebilirlik: MCP Oturumlarını İzleme
Her tools/call çağrısı, yapısal bir log kaydı üretmelidir. Zamanlama ve günlüğe kaydetmeyi doğrudan ToolsCallHandler‘a ekleyin:
// app/MCP/Handlers/ToolsCallHandler.php — güncellenmiş handle() metodu
public function handle(array $params): array
{
$toolName = $params['name'] ?? null;
$arguments = $params['arguments'] ?? [];
if ($toolName === null || ! isset($this->tools[$toolName])) {
throw new McpException(-32602, "Unknown tool: {$toolName}");
}
$tool = $this->tools[$toolName];
$errors = $this->validator->validate($arguments, $tool->schema());
if (! empty($errors)) {
throw new McpException(-32602, 'Invalid arguments: ' . implode(', ', $errors));
}
$clientId = auth()->user()?->id;
$inputHash = hash('xxh3', json_encode($arguments));
$start = hrtime(true);
try {
$result = $tool->execute($arguments);
$elapsed = (hrtime(true) - $start) / 1e6;
Log::channel('mcp')->info('tool.call', [
'client_id' => $clientId,
'tool' => $toolName,
'input_hash' => $inputHash,
'elapsed_ms' => round($elapsed, 2),
'is_error' => false,
]);
return [
'content' => [['type' => 'text', 'text' => json_encode($result)]],
'isError' => false,
];
} catch (\Throwable $e) {
$elapsed = (hrtime(true) - $start) / 1e6;
Log::channel('mcp')->warning('tool.call.error', [
'client_id' => $clientId,
'tool' => $toolName,
'input_hash' => $inputHash,
'elapsed_ms' => round($elapsed, 2),
'error' => $e->getMessage(),
]);
return [
'content' => [['type' => 'text', 'text' => $e->getMessage()]],
'isError' => true,
];
}
}
Özel bir log kanalı ekleyin config/logging.php dosyasında:
// config/logging.php — 'channels' içine ekleyin:
'mcp' => [
'driver' => 'daily',
'path' => storage_path('logs/mcp.log'),
'level' => 'debug',
'days' => 30,
],
Giriş hash’ini ham girişin yerine kaydettirin. MCP tool argümanları düzenli olarak kullanıcıdan sağlanan metni içerir. Ham içeriği günlüğe kaydetmek, PII riski oluşturur ve gerçek çağrı hacmi altında log dosyalarını şişirir. xxh3 algoritması, kısa yükler için sha256‘dan belirgin şekilde daha hızlıdır ve çarpışma direnci log korelasyonu için yeterlidir, kriptografik kullanım için değil.
Bu yapısal log girişleri doğal olarak bir yönetici arayüzüne taşınır. Filament AI yönetici gösterge panosu kalıbı, tam olarak bu yüzeyin kapsadığı alan: istemci kimliği, tool adı ve zaman aralığı ile sorgulanabilir olan ajan çağrısı olayları, sunucunuzun gerçekleştirdiği her tool için tam bir denetim kaydı sağlar.
[Verimlilik Kazancı] MCP log kanalını bir günlük dosyası için ilk günden yönlendirin. Yapısal MCP olaylarını ana uygulama logunuza karıştırmak, her ikisinin de karmaşık hale gelmesini sağlar ve daha büyük bir log gönderimi yapılandırmasını zorlaştırır.
MCP Sunucunuza Prism PHP Müşterisini Bağlama
MCP Sunucunuza Prism PHP Müşterisini Bağlama
Laravel uygulamanız, dış ajanlar için bir MCP sunucusu çalıştırmanın yanı sıra, diğer MCP sunucularını Prism aracılığıyla tüketen bir MCP istemcisi olarak da çalışabilir. Bu iki rol tamamlayıcıdır.
Prism’in MCP istemci desteği, ayrı bir yardımcı paket olan Relay olarak gelir:
composer require prism-php/relay
php artisan vendor:publish --tag="relay-config"
Sunucu bağlantınızı config/relay.php‘de tanımlayın:
// config/relay.php
use Prism\Relay\Enums\Transport;
return [
'servers' => [
'knowledge-base' => [
'url' => env('MCP_INTERNAL_SERVER_URL', 'http://localhost/api/mcp'),
'transport' => Transport::Http,
'timeout' => 30,
'headers' => [
'Authorization' => 'Bearer ' . env('MCP_INTERNAL_TOKEN'),
],
],
],
'cache_duration' => env('RELAY_TOOLS_CACHE_DURATION', 60),
];
Ardından Relay::tools() yöntemini Prism ajan zincirinizde kullanın:
use Prism\Prism\Enums\Provider;
use Prism\Prism\Prism;
use Prism\Relay\Facades\Relay;
$response = Prism::text()
->using(Provider::Anthropic, 'claude-sonnet-4-6')
->withPrompt('Bilgi tabanında Laravel kuyrukları hakkında makaleler arayın ve ilk üç sonucu özetleyin.')
->withTools(Relay::tools('knowledge-base'))
->asText();
Relay::tools() tools/listi MCP sunucunuzdan alır, JSON Schema tanımlarını Prism tool nesnelerine dönüştürür ve bunları ajan döngüsüne bağlar. Claude v1__search_articles‘ı çağırmaya karar verirse, Relay tools/call isteğini MCP sunucunuza gönderir ve sonucu akıl yürütme döngüsüne geri döner.
Çift rol mimarisi, dış ajanlar için MCP sunucusu, dışa dönük ajan iş akışları için Prism istemcisi, tam entegre bir Laravel uygulamasının doğal son durumudur. Prism’in, MCP istemci desteği üzerine sunduğu öğeleri, çok aşamalı akıl yürütme döngüleri ve sağlayıcıdan bağımsız boru hatları dahil olmak üzere, tam ajan katmanını inşa etmede ne sunduğuna dair tam bir resim sunmaktadır.
Ne Yapılacak
Ne Yapılacak
MCP sunucunuz, harici ajanlara Laravel uygulamanıza istikrarlı, kimlik doğrulamalı bir arayüz sağlar. Doğal bir sonraki adım, araçlarınızı gerçekten ne yaptığına güç veren Claude entegrasyonu olacaktır.
Uygulamanız içinde henüz Claude katmanını inşa etmediyseniz (akış yanıtları, konuşma belleği, üretim hata yönetimi), tam Laravel Claude API entegrasyon kılavuzu, başlangıç noktanızdır. MCP sunucusu, bunun yanıtlarını doğru bir şekilde uygulandığınızda çok daha faydalı hale gelir. Öncelikle o katmanı inşa edin, ardından bunu araçlar aracılığıyla açığa çıkarın.
Kaynak: Orijinal Makale
- MCP Gerçekten Nedir (ve Ne Değildir)
- Sunucu Manifestosunu Tanımlama ve Yetenek Müzakeresi
- Laravel Route Katmanını İnşa Etme
- Laravel Sanctum ile Kimlik Doğrulamalı MCP Uç Noktaları
- MCP Sunucunuzu Uçtan Uca Test Etme
- Üretim Tutundurma
- Her MCP Müşterisi İçin Hız Limitlemesi
- Tool Şeması Sürümleme Stratejisi
- Gözlemlenebilirlik: MCP Oturumlarını İzleme
- MCP Sunucunuza Prism PHP Müşterisini Bağlama
- Ne Yapılacak


