Bugün, bir Laravel uygulamasına bağlanan bir MCP sunucusu kurarken, bir Kong API geçidini yönetmeyi sağladım. İlginç olan kısım “Yapay Zeka uygulama ile nasıl konuşur” değildi; bu artık birinci taraf bir paketle kolay bir hale geldi. İlginç olan, MCP katmanının sadece başka bir UI olması ve asla yetkilendirmeyi atlamayan sessiz bir arka kapı olmamasıydı.
İşte benim bu durumu nasıl düşündüğüm ve bunu adil tutan kalıplar.
<h2>
MCP, yeni bir yetki seti değil, üçüncü bir ön yüz
</h2>
<p>
Uygulama zaten bir web UI ve bir HTTP API ile iki giriş yolu sunuyor. Her ikisi de aynı yetkilendirmeden, aynı eylem sınıflarından ve aynı onay akışından geçiyor. Bir MCP sunucusu eklediğinizde, araçların "sadece veritabanını sorgulamasına" izin vermek cazip gelebilir. Bu, oturum açmış bir kullanıcının asla yapamayacağı şeyleri yapabilen bir AI ajansı oluşturmanıza yol açar.
</p>
<p>
Bu nedenle kendime koyduğum kural: <strong>her MCP aracı, insanın zaten sahip olduğu bir izinle eşleşir ve her yazma işlemi web UI tarafından çağrılan aynı eylem sınıfından geçer.</strong> MCP'nin özel ayrıcalıkları yoktur.
</p>
<h2>
Araç ana sınıfı: önce kapı, sonra iş
</h2>
<p>
<code>laravel/mcp</code> ile bir araç, <code>Laravel\Mcp\Server\Tool</code> sınıfını genişleten bir sınıftır. Her aracın yetki kontrolünü yeniden icat etmesine izin vermek yerine, bunu soyut bir temel içine itiyorum. Her somut araç, ihtiyaç duyduğu izni beyan eder; temel ise çağrının izinli olup olmadığını belirler.
</p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="k">abstract</span> <span class="kd">class</span> <span class="nc">GatewayTool</span> <span class="kd">extends</span> <span class="nc">Tool</span>{
/* Bu aracın gerektirdiği izin, örn. “gateway.manage.services”. /
abstract protected function permission(): string;
<span class="k">protected</span> <span class="k">function</span> <span class="n">authorizedUser</span><span class="p">(</span><span class="kt">Request</span> <span class="nv">$request</span><span class="p">):</span> <span class="kt">?User</span>
<span class="p">{</span>
<span class="nv">$user</span> <span class="o">=</span> <span class="nv">$request</span><span class="o">-></span><span class="nf">user</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nv">$user</span> <span class="k">instanceof</span> <span class="nc">User</span> <span class="o">||</span> <span class="o">!</span> <span class="nv">$user</span><span class="o">-></span><span class="nf">can</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="nf">permission</span><span class="p">()))</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$user</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">protected</span> <span class="k">function</span> <span class="n">unauthorized</span><span class="p">():</span> <span class="kt">Response</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nc">Response</span><span class="o">::</span><span class="nf">error</span><span class="p">(</span>
<span class="s2">"Yetkilendirme hatası — bu araç '<span class='si'>{</span><span class="nv">$this</span><span class="o">-></span><span class="nf">permission</span><span class="p">()}</span><span class="s2">' izni gerektirir."</span>
<span class="p">);</span>
<span class="p">}</span>}
<p>
Burada <code>$request->user()</code>, token sahibidir — scoped bearer tokenlarını Sanctum ile çıkarıyorum, bu nedenle MCP oturumu gerçek bir kullanıcı ile gerçek izinler ile kimlik doğrulaması yapılır. Süper yöneticiler yine <code>Gate::before</code> üzerinden geçiyor, bu nedenle onun için özel bir durum yapmam gerekmiyor.
</p>
<p>
Görünüşte önemsiz olan ama önem taşıyan bir nokta: <strong>yetkisiz bir araç bir hata döner, kısmi veri değil.</strong> "Görebileceğiniz şeyler" ile ilgili yarı yanıtlar yok. Çağrı yapamazsanız, temiz bir reddetme alırsınız ve hiç bir şey sızmaz.
</p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="kd">class</span> <span class="nc">ListServicesTool</span> <span class="kd">extends</span> <span class="nc">GatewayTool</span>{
protected function permission(): string
{
return ‘gateway.view.services’;
}
<span class="k">public</span> <span class="k">function</span> <span class="n">handle</span><span class="p">(</span><span class="kt">Request</span> <span class="nv">$request</span><span class="p">):</span> <span class="kt">Response</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nv">$user</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">authorizedUser</span><span class="p">(</span><span class="nv">$request</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">unauthorized</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// ...sayfalı, uuid/kod sadece — asla dahili id</span>
<span class="p">}</span>}


