İçeriğe atla

MCP Sunucu RBAC, Araç Bileşimi ve Çoklu Ajan İş Akışı Kalıpları

Model Context Protocol için kurumsal kalıplar: araç bileşimi, çoklu ajan orkestrasyonu, rol tabanlı erişim kontrolü ve production gözlemlenebilirlik.

Ayhan Sipahi Ayhan Sipahi

MCP içeriklerinin çoğu ilk çalışan sunucuda duruyor: birkaç araç kaydet, bir istemci bağla, bitti. Asıl sorunlar sonra başlıyor; aynı sunucuya ikinci bir ekip bağlandığında ve kayıtlı araçlardan biri production’ı değiştirebildiğinde.

Bu boşluğu altı kalıp kapatıyor: araç anotasyonları, iş akışı orkestrasyonu, öznitelik koşullu RBAC, kademeli yetkilendirme, hata kurtarma ve gözlemlenebilirlik. Sadece birini seçecekseniz, denetim loglamayla desteklenmiş RBAC’ı seçin. İzinler ve denetim izleri, ajanlar araçları çağırmaya başladıktan sonra eklenmesi en pahalı katman; orkestrasyon ise karşılaştıracak iki iş akışınız olana kadar bekleyebilir.

Ölçeklenirken Çıkan Sorunlar#

Temel MCP entegrasyonlarını başarıyla deploy eden organizasyonlar, ölçeklendikçe öngörülebilir zorluklarla karşılaşıyor:

Araç Patlaması: 5 araçla başlayıp 10 sunucu üzerinde 50’ye büyümek, ajanlar için keşif ve seçim sorunları yaratıyor.

İzin Karmaşıklığı: Farklı kullanıcıların farklı araç erişimine ihtiyacı var. Junior bir geliştirici production deployment’ları tetiklememeli, ancak ince taneli erişim kontrolünü nasıl uyguluyorsunuz?

İş Akışı Orkestrasyonu: Karmaşık görevler sıralı veya paralel birden fazla araç gerektiriyor. Araç zincirlerini güvenilir bir şekilde koordine etmek kendi başına bir mühendislik problemi haline geliyor.

Çoklu Ajan Koordinasyonu: Birlikte çalışan birden fazla AI ajanının farklı araç alt kümelerine ihtiyacı var. Çatışmaları önlemek ve uygun izolasyonu sağlamak dikkatli tasarım gerektiriyor.

Denetim ve Uyumluluk: Düzenlemeye tabi sektörler tam denetim izleri gerektiriyor. Kimin neyi, ne zaman ve hangi sonuçlarla çağırdığını izlemek pazarlık konusu değil.

Kalip Cozumleri

Olceklendirme Zorluklari

Arac Kesfi 50+ arac sunucular arasi

Izin Kontrolu Rol tabanli erisim

Is Akisi Koordinasyonu Cok adimli islemler

Ajan Izolasyonu Catisma onleme

Denetim Uyumlulugu Tam izler

Arac Registry + Anotasyonlar

RBAC Katmani + Kademeli Yetkilendirme

Orkestrator + Hata Kurtarma

Gateway + Oturum Yonetimi

Gozlemlenebilirlik + Yapilandirilmis Loglama

Kalıp 1: Araç Tasarımı ve Anotasyon#

İyi tasarlanmış MCP araçları, onları birleştirilebilir ve bakımı yapılabilir kılan ortak özellikler paylaşır. MCP’de tanıtılan anotasyon sistemi, hem ajanların hem de kullanıcıların araç davranışını anlamasına yardımcı olan metadata sağlar.

Note

Buradaki kalıplar MCP spesifikasyonunun 2025-06-18 sürümünü izliyor. Sonraki revizyonlar bazı API detaylarını değiştiriyor.

Kapsamlı Araç Anotasyonları#

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({
  name: "deployment-server",
  version: "1.0.0",
});

// Kapsamlı anotasyonlarla araç
server.tool(
  "deploy_service",
  {
    service: z.string().describe("Deploy edilecek servis adı"),
    environment: z.enum(["staging", "production"]).describe("Hedef ortam"),
    version: z.string().regex(/^\d+\.\d+\.\d+$/).describe("Semantik versiyon"),
  },
  {
    title: "Servis Deploy Et",
    description: "Belirtilen ortama bir servisin deployment'ını tetikler",
    annotations: {
      // İstemciler için davranışsal ipuçları (MCP spesifikasyonu 2025-06-18)
      readOnlyHint: false,  // Bu araç durumu değiştirir
      destructiveHint: false,  // Yıkıcı değil (geri alınabilir)
      idempotentHint: false,  // Birden fazla çağrı birden fazla deployment oluşturur
      openWorldHint: true,  // Dış sistemlerle etkileşime girer
    },
  },
  async ({ service, environment, version }) => {
    // Uygun hata işleme ile implementasyon
    const result = await deploymentService.deploy(service, environment, version);

    return {
      content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
    };
  }
);

Yapılandırılmış Sonuçlar için Output Şemaları#

Yapılandırılmış veri döndüren araçlar, programatik sonuç işlemeyi mümkün kılan output şemalarından faydalanır.

Note

outputSchema ve structuredContent alanları 2025-06-18 spesifikasyonuyla geldi. Eski SDK sürümleri iki alanı da yok sayıyor.

server.tool(
  "get_service_metrics",
  {
    service: z.string(),
    timeRange: z.enum(["1h", "24h", "7d", "30d"]),
  },
  {
    title: "Servis Metriklerini Al",
    description: "Bir servis için performans metriklerini alır",
    annotations: {
      readOnlyHint: true,
      idempotentHint: true,
    },
    // Output şeması yapılandırılmış içerik doğrulamasını etkinleştirir
    outputSchema: z.object({
      service: z.string(),
      period: z.string(),
      metrics: z.object({
        requestCount: z.number(),
        errorRate: z.number(),
        p50Latency: z.number(),
        p99Latency: z.number(),
      }),
    }),
  },
  async ({ service, timeRange }) => {
    const metrics = await metricsService.getMetrics(service, timeRange);

    return {
      content: [{ type: "text", text: JSON.stringify(metrics) }],
      // Programatik erişim için yapılandırılmış içerik
      structuredContent: metrics,
    };
  }
);

Araç Bileşim Kalıpları#

Karmaşık işlemler genellikle birden fazla aracı zincirlemeyi gerektirir. İşte çalışan kalıplar:

// Kalıp 1: Sıralı Araç Zinciri Konfigürasyonu
const deploymentChain = {
  chain: [
    {
      tool: "validate_config",
      inputPath: "$.config",
      outputPath: "$.validation",
    },
    {
      tool: "run_tests",
      inputPath: "$.validation.service",
      outputPath: "$.testResults",
    },
    {
      tool: "deploy_service",
      inputPath: "$.validation",
      outputPath: "$.deployment",
    },
  ],
};

// Kalıp 2: Paralel Araç Yürütme
const healthCheckConfig = {
  parallel: [
    { tool: "check_service_health", params: { service: "api" } },
    { tool: "check_service_health", params: { service: "database" } },
    { tool: "check_service_health", params: { service: "cache" } },
  ],
  merge: "$.healthStatus", // Sonuçları birleştir
};

// Kalıp 3: Koşullu Araç Yürütme
const conditionalDeployConfig = {
  condition: "$.environment === 'production'",
  ifTrue: [
    { tool: "get_deployment_approval" },
    { tool: "notify_stakeholders" },
    { tool: "deploy_service" },
  ],
  ifFalse: [
    { tool: "deploy_service" },
  ],
};

Kalıp 2: Çoklu Ajan İş Akışı Orkestrasyonu#

Birden fazla AI ajanının farklı araç erişimiyle işbirliği yapması gerektiğinde, bir orkestratör kalıbı koordinasyon ve izolasyon sağlar.

MCP Sunuculari

Uzman Ajanlar

Is Akisi Orkestratoru

Gorev Kuyrugu

Ajan Yonlendirici

Durum Yoneticisi

Dogrulayici Ajan Sadece config araclari

Saglik Ajani Izleme araclari

Deployer Ajan Deploy araclari

Config Sunucusu

Monitor Sunucusu

Deploy Sunucusu

Orkestratör Implementasyonu#

Note

HTTP+SSE transport’u kullanımdan kaldırıldı. Yeni istemciler Streamable HTTP transport’unu (StreamableHTTPClientTransport) kullanıyor.

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

interface AgentConfig {
  name: string;
  role: string;
  servers: string[];
  permissions: string[];
}

interface WorkflowStep {
  name: string;
  agent: string;
  tool: string;
  params: Record<string, any>;
  outputKey: string;
  timeout?: number;
  onError: "abort" | "continue" | "rollback";
  parallel?: WorkflowStep[];
}

class MultiAgentOrchestrator {
  private agents: Map<string, AgentConfig> = new Map();
  private mcpClients: Map<string, Client> = new Map();

  async registerAgent(config: AgentConfig): Promise<void> {
    const client = new Client({
      name: `agent-${config.name}`,
      version: "1.0.0",
    });

    // Atanan MCP sunucularına bağlan
    for (const serverUrl of config.servers) {
      await client.connect(new StreamableHTTPClientTransport(new URL(serverUrl)));
    }

    // Mevcut araçları ajan izinlerine göre filtrele
    const { tools } = await client.listTools();
    const allowedTools = tools.filter(tool =>
      this.checkToolPermission(tool, config.permissions)
    );

    console.log(`Ajan ${config.name}, ${allowedTools.length} araçla kaydedildi`);

    this.agents.set(config.name, config);
    this.mcpClients.set(config.name, client);
  }

  async executeWorkflow(workflow: WorkflowDefinition): Promise<WorkflowResult> {
    const executionId = crypto.randomUUID();
    const results: Map<string, any> = new Map();

    console.log(`İş akışı ${workflow.name} başlatılıyor (${executionId})`);

    for (const step of workflow.steps) {
      try {
        if (step.parallel) {
          // Paralel adımları eşzamanlı yürüt
          const parallelResults = await Promise.all(
            step.parallel.map(subStep =>
              this.executeStep(subStep, results, executionId)
            )
          );

          // Paralel sonuçları birleştir
          parallelResults.forEach((result, index) => {
            results.set(step.parallel[index].outputKey, result);
          });
        } else {
          // Sıralı adımı yürüt
          const result = await this.executeStep(step, results, executionId);
          results.set(step.outputKey, result);
        }
      } catch (error) {
        if (step.onError === "continue") {
          console.log(`Adım ${step.name} başarısız, devam ediliyor: ${error.message}`);
          results.set(step.outputKey, { error: error.message });
        } else if (step.onError === "rollback") {
          await this.rollbackWorkflow(executionId, results);
          throw error;
        } else {
          throw error;
        }
      }
    }

    return {
      executionId,
      status: "completed",
      results: Object.fromEntries(results),
    };
  }

  private async executeStep(
    step: WorkflowStep,
    context: Map<string, any>,
    executionId: string
  ): Promise<any> {
    const agent = this.agents.get(step.agent);
    const client = this.mcpClients.get(step.agent);

    if (!agent || !client) {
      throw new Error(`Ajan ${step.agent} bulunamadı`);
    }

    // İş akışı bağlamından parametreleri çöz
    const params = this.resolveParams(step.params, context);

    // Yürütmeden önce izni doğrula
    if (!this.checkToolPermission({ name: step.tool }, agent.permissions)) {
      throw new Error(`Ajan ${step.agent}, araç ${step.tool} için izne sahip değil`);
    }

    // Timeout ile yürüt
    const result = await Promise.race([
      client.callTool({ name: step.tool, arguments: params }),
      this.timeout(step.timeout || 30000),
    ]);

    // Denetim loglama
    await this.auditLog({
      executionId,
      agent: step.agent,
      tool: step.tool,
      params,
      result: result.isError ? "failure" : "success",
      timestamp: new Date().toISOString(),
    });

    if (result.isError) {
      throw new Error(`Araç ${step.tool} başarısız: ${result.content[0]?.text}`);
    }

    return result.content;
  }

  private checkToolPermission(
    tool: { name: string },
    permissions: string[]
  ): boolean {
    // İzin formatı: "tool:*", "tool:read", "tool:deploy:staging"
    for (const perm of permissions) {
      if (perm === "tool:*") return true;
      if (perm === `tool:${tool.name}`) return true;
      if (perm.startsWith(`tool:${tool.name}:`)) return true;
    }
    return false;
  }
}

İş Akışı Tanımı Örneği#

const productionDeploymentWorkflow: WorkflowDefinition = {
  name: "production-deployment",
  steps: [
    {
      name: "validate",
      agent: "validator",
      tool: "validate_deployment_config",
      params: { service: "$.input.service", version: "$.input.version" },
      outputKey: "validation",
      onError: "abort",
    },
    {
      name: "parallel-checks",
      parallel: [
        {
          name: "health-check",
          agent: "health-checker",
          tool: "check_service_health",
          params: { service: "$.input.service" },
          outputKey: "health",
          onError: "abort",
        },
        {
          name: "security-scan",
          agent: "security-scanner",
          tool: "scan_vulnerabilities",
          params: { version: "$.input.version" },
          outputKey: "security",
          onError: "abort",
        },
      ],
      onError: "abort",
    },
    {
      name: "get-approval",
      agent: "approval-bot",
      tool: "request_deployment_approval",
      params: {
        service: "$.input.service",
        validation: "$.validation",
        security: "$.security",
      },
      outputKey: "approval",
      timeout: 300000, // İnsan onayı için 5 dakika
      onError: "abort",
    },
    {
      name: "deploy",
      agent: "deployer",
      tool: "deploy_service",
      params: {
        service: "$.input.service",
        version: "$.input.version",
        approvalId: "$.approval.id",
      },
      outputKey: "deployment",
      onError: "rollback",
    },
  ],
};

Kalıp 3: Rol Tabanlı Erişim Kontrolü#

Kurumsal MCP deployment’ları ince taneli erişim kontrolü gerektirir. İşte RBAC’ı öznitelik tabanlı koşullarla birleştiren bir kalıp.

İzin Modeli#

import { z } from "zod";

interface Permission {
  resource: string;  // "tool:deploy_service", "resource:config:*"
  action: string;  // "invoke", "read", "write"
  conditions?: {  // İsteğe bağlı ABAC koşulları
    environment?: string[];
    timeWindow?: { start: string; end: string };
    maxCost?: number;
  };
}

interface Role {
  name: string;
  permissions: Permission[];
  inherits?: string[];  // Rol kalıtımı
}

interface User {
  id: string;
  email: string;
  roles: string[];
  attributes: Record<string, any>;
}

RBAC Servis Implementasyonu#

class MCPAuthorizationService {
  private roles: Map<string, Role> = new Map();

  constructor() {
    this.initializeRoles();
  }

  private initializeRoles(): void {
    // Rol hiyerarşisini tanımla
    const roles: Role[] = [
      {
        name: "viewer",
        permissions: [
          { resource: "tool:list_*", action: "invoke" },
          { resource: "tool:get_*", action: "invoke" },
          { resource: "tool:check_*", action: "invoke" },
          { resource: "resource:*", action: "read" },
        ],
      },
      {
        name: "developer",
        inherits: ["viewer"],
        permissions: [
          {
            resource: "tool:deploy_service",
            action: "invoke",
            conditions: { environment: ["development", "staging"] },
          },
          { resource: "tool:run_tests", action: "invoke" },
          { resource: "tool:build_artifact", action: "invoke" },
        ],
      },
      {
        name: "senior_developer",
        inherits: ["developer"],
        permissions: [
          {
            resource: "tool:deploy_service",
            action: "invoke",
            conditions: { environment: ["development", "staging", "production"] },
          },
          { resource: "tool:rollback_deployment", action: "invoke" },
        ],
      },
      {
        name: "admin",
        permissions: [
          { resource: "*", action: "*" },
        ],
      },
    ];

    for (const role of roles) {
      this.roles.set(role.name, role);
    }
  }

  async authorize(
    user: User,
    tool: string,
    params: Record<string, any>
  ): Promise<AuthorizationResult> {
    const permissions = this.getEffectivePermissions(user);

    for (const perm of permissions) {
      if (this.matchesResource(perm.resource, `tool:${tool}`)) {
        if (perm.action === "*" || perm.action === "invoke") {
          // ABAC koşullarını kontrol et
          if (perm.conditions) {
            const conditionResult = this.evaluateConditions(
              perm.conditions,
              params,
              user
            );
            if (!conditionResult.allowed) {
              return {
                allowed: false,
                reason: conditionResult.reason,
                requiredElevation: this.suggestElevation(tool, params),
              };
            }
          }
          return { allowed: true };
        }
      }
    }

    return {
      allowed: false,
      reason: `Kullanıcı ${user.email}, araç ${tool} için izne sahip değil`,
      requiredElevation: this.suggestElevation(tool, params),
    };
  }

  private getEffectivePermissions(user: User): Permission[] {
    const permissions: Permission[] = [];
    const processedRoles = new Set<string>();

    const processRole = (roleName: string) => {
      if (processedRoles.has(roleName)) return;
      processedRoles.add(roleName);

      const role = this.roles.get(roleName);
      if (!role) return;

      // Önce miras alınan rolleri işle (derinlik öncelikli)
      if (role.inherits) {
        for (const inherited of role.inherits) {
          processRole(inherited);
        }
      }

      permissions.push(...role.permissions);
    };

    for (const roleName of user.roles) {
      processRole(roleName);
    }

    return permissions;
  }

  private evaluateConditions(
    conditions: Permission["conditions"],
    params: Record<string, any>,
    user: User
  ): { allowed: boolean; reason?: string } {
    // Ortam kısıtlaması
    if (conditions?.environment) {
      const requestedEnv = params.environment;
      if (!conditions.environment.includes(requestedEnv)) {
        return {
          allowed: false,
          reason: `${requestedEnv} ortamına izin verilmiyor. İzin verilenler: ${conditions.environment.join(", ")}`,
        };
      }
    }

    // Zaman penceresi kısıtlaması
    if (conditions?.timeWindow) {
      const now = new Date();
      const hour = now.getHours();
      const [startHour] = conditions.timeWindow.start.split(":").map(Number);
      const [endHour] = conditions.timeWindow.end.split(":").map(Number);

      if (hour < startHour || hour >= endHour) {
        return {
          allowed: false,
          reason: `İşleme sadece ${conditions.timeWindow.start} ile ${conditions.timeWindow.end} arasında izin veriliyor`,
        };
      }
    }

    return { allowed: true };
  }

  private matchesResource(pattern: string, resource: string): boolean {
    if (pattern === "*") return true;

    const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
    return new RegExp(`^${regexPattern}$`).test(resource);
  }
}

Güvenli MCP Sunucu Entegrasyonu#

class SecureMCPServer {
  private server: McpServer;
  private authService: MCPAuthorizationService;

  constructor(config: SecureServerConfig) {
    this.server = new McpServer(config);
    this.authService = new MCPAuthorizationService();
  }

  secureTool(
    name: string,
    schema: z.ZodType,
    options: ToolOptions,
    handler: ToolHandler
  ) {
    this.server.tool(name, schema, options, async (params, context) => {
      const user = context.meta?.user as User;

      if (!user) {
        return {
          content: [{ type: "text", text: "Kimlik doğrulama gerekli" }],
          isError: true,
        };
      }

      // Yetkilendirmeyi kontrol et
      const authResult = await this.authService.authorize(user, name, params);

      if (!authResult.allowed) {
        // Reddedilen erişimi denetle
        await this.auditLog({
          action: "tool_denied",
          user: user.email,
          tool: name,
          reason: authResult.reason,
        });

        return {
          content: [{
            type: "text",
            text: `Erişim reddedildi: ${authResult.reason}${
              authResult.requiredElevation
                ? `\n${authResult.requiredElevation}`
                : ""
            }`,
          }],
          isError: true,
        };
      }

      // Başarılı erişimi denetle
      await this.auditLog({
        action: "tool_invoked",
        user: user.email,
        tool: name,
        params,
      });

      return handler(params, context);
    });
  }
}

Kalıp 4: Kademeli Yetkilendirme#

Tüm izinleri önceden vermek yerine, kademeli yetkilendirme hassas işlemler için gerektiğinde scope’ları yükseltir.

Scope Ilerlemesi

Daha fazla gerekli

Gerekce

Onay

Temel Scope'lar Salt okunur araclar

Standart Scope'lar Dev ortami

Yukseltilmis Scope'lar Production islemleri

Admin Scope'lar Yikici islemler

Kullanici Istegi

class ProgressiveAuthorizationService {
  private baseScopes = ["mcp:tools:read", "mcp:resources:read"];
  private scopeHistory: Map<string, Set<string>> = new Map();

  async getInitialToken(userId: string): Promise<TokenResponse> {
    const token = await this.oauthClient.getToken({
      grant_type: "client_credentials",
      scope: this.baseScopes.join(" "),
      user_id: userId,
    });

    this.scopeHistory.set(userId, new Set(this.baseScopes));
    return token;
  }

  async elevateScope(
    userId: string,
    requiredScope: string,
    justification: string
  ): Promise<ElevationResult> {
    const currentScopes = this.scopeHistory.get(userId) || new Set();

    if (currentScopes.has(requiredScope)) {
      return { elevated: true, token: await this.getCurrentToken(userId) };
    }

    // İstenen scope için uygunluğu kontrol et
    const canElevate = await this.checkElevationEligibility(userId, requiredScope);

    if (!canElevate.eligible) {
      return {
        elevated: false,
        reason: canElevate.reason,
        approvalRequired: true,
        approvalWorkflow: this.getApprovalWorkflow(requiredScope),
      };
    }

    // Yükseltilmiş token iste
    const elevatedToken = await this.oauthClient.getToken({
      grant_type: "client_credentials",
      scope: [...currentScopes, requiredScope].join(" "),
      user_id: userId,
      justification,
    });

    currentScopes.add(requiredScope);
    this.scopeHistory.set(userId, currentScopes);

    await this.auditLog({
      action: "scope_elevated",
      user: userId,
      scope: requiredScope,
      justification,
    });

    return { elevated: true, token: elevatedToken };
  }

  private getScopeRequirements(): Record<string, ScopeRequirement> {
    return {
      "mcp:deploy:staging": {
        minRole: "developer",
        requiresApproval: false,
        expiresIn: 3600, // 1 saat
      },
      "mcp:deploy:production": {
        minRole: "senior_developer",
        requiresApproval: true,
        approvers: ["tech-lead", "sre-oncall"],
        expiresIn: 1800, // 30 dakika
      },
      "mcp:admin": {
        minRole: "admin",
        requiresApproval: true,
        approvers: ["security-team"],
        expiresIn: 900, // 15 dakika
      },
    };
  }
}

Kalıp 5: Hata İşleme ve Kurtarma#

Production MCP deployment’ları, retry stratejileri ve circuit breaker’larla sağlam hata işleme gerektirir.

Hata Sınıflandırması#

enum MCPErrorCode {
  // Protokol hataları
  PARSE_ERROR = -32700,
  INVALID_REQUEST = -32600,
  METHOD_NOT_FOUND = -32601,
  INVALID_PARAMS = -32602,
  INTERNAL_ERROR = -32603,

  // Araca özgü hatalar
  TOOL_EXECUTION_FAILED = -32000,
  TOOL_TIMEOUT = -32001,
  TOOL_UNAUTHORIZED = -32002,
  TOOL_RATE_LIMITED = -32003,
  TOOL_DEPENDENCY_FAILED = -32004,
}

interface RecoverableError {
  code: MCPErrorCode;
  message: string;
  retryable: boolean;
  retryAfterMs?: number;
  suggestedAction?: string;
}

Yeniden Deneme Stratejisi#

class MCPErrorHandler {
  private retryPolicies: Map<MCPErrorCode, RetryPolicy> = new Map([
    [MCPErrorCode.TOOL_TIMEOUT, { maxRetries: 3, backoffMs: 1000, multiplier: 2 }],
    [MCPErrorCode.TOOL_DEPENDENCY_FAILED, { maxRetries: 5, backoffMs: 500, multiplier: 1.5 }],
    [MCPErrorCode.TOOL_RATE_LIMITED, { maxRetries: 3, backoffMs: 5000, multiplier: 2 }],
  ]);

  async executeWithRecovery<T>(
    toolName: string,
    executor: () => Promise<T>,
    timeoutMs: number = 30000
  ): Promise<T> {
    let lastError: RecoverableError | null = null;
    let attempt = 0;

    while (true) {
      try {
        return await Promise.race([
          executor(),
          this.createTimeout(timeoutMs),
        ]);
      } catch (error) {
        lastError = this.classifyError(error, toolName);
        attempt++;

        console.log(`Araç ${toolName} başarısız (deneme ${attempt}):`, {
          code: lastError.code,
          message: lastError.message,
          retryable: lastError.retryable,
        });

        if (!lastError.retryable) break;

        const policy = this.retryPolicies.get(lastError.code);
        if (!policy || attempt >= policy.maxRetries) break;

        const backoff = policy.backoffMs * Math.pow(policy.multiplier, attempt - 1);
        const jitter = Math.random() * 0.1 * backoff;

        await this.sleep(backoff + jitter);
      }
    }

    throw this.createToolError(lastError!, toolName);
  }

  private classifyError(error: any, toolName: string): RecoverableError {
    if (error.code === "ETIMEDOUT" || error.message?.includes("timeout")) {
      return {
        code: MCPErrorCode.TOOL_TIMEOUT,
        message: `Araç ${toolName} zaman aşımına uğradı`,
        retryable: true,
        suggestedAction: "Servis sağlığını kontrol edin veya timeout'u artırın",
      };
    }

    if (error.response?.status === 429) {
      return {
        code: MCPErrorCode.TOOL_RATE_LIMITED,
        message: `Araç ${toolName} rate limit'e ulaştı`,
        retryable: true,
        retryAfterMs: parseInt(error.response.headers["retry-after"]) * 1000 || 5000,
      };
    }

    if (error.response?.status >= 500) {
      return {
        code: MCPErrorCode.TOOL_DEPENDENCY_FAILED,
        message: `Araç ${toolName} backend hatası`,
        retryable: true,
      };
    }

    return {
      code: MCPErrorCode.TOOL_EXECUTION_FAILED,
      message: `Araç ${toolName} başarısız: ${error.message}`,
      retryable: false,
    };
  }
}

Circuit Breaker Kalıbı#

class CircuitBreaker {
  private state: "closed" | "open" | "half-open" = "closed";
  private failures = 0;
  private lastFailureTime = 0;

  constructor(
    private readonly threshold: number = 5,
    private readonly resetTimeout: number = 30000
  ) {}

  async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.state === "open") {
      if (Date.now() - this.lastFailureTime > this.resetTimeout) {
        this.state = "half-open";
      } else {
        throw new Error("Circuit breaker açık");
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failures = 0;
    this.state = "closed";
  }

  private onFailure(): void {
    this.failures++;
    this.lastFailureTime = Date.now();

    if (this.failures >= this.threshold) {
      this.state = "open";
      console.log(`Circuit breaker ${this.failures} hatadan sonra açıldı`);
    }
  }
}

Kalıp 6: Gözlemlenebilirlik Stack’i#

Production MCP deployment’ları metrikleri, tracing’i ve denetim loglamayı kapsayan bir gözlemlenebilirlik katmanı gerektirir.

import { trace, SpanStatusCode } from "@opentelemetry/api";
import { Counter, Histogram, Gauge } from "prom-client";

class MCPObservability {
  private tracer = trace.getTracer("mcp-server");

  // Prometheus metrikleri
  private requestCounter = new Counter({
    name: "mcp_tool_requests_total",
    help: "Toplam MCP araç çağrıları",
    labelNames: ["tool", "status", "user_role"],
  });

  private requestDuration = new Histogram({
    name: "mcp_tool_duration_seconds",
    help: "MCP araç yürütme süresi",
    labelNames: ["tool"],
    buckets: [0.1, 0.5, 1, 2, 5, 10, 30],
  });

  private activeRequests = new Gauge({
    name: "mcp_active_requests",
    help: "Şu anda yürütülen MCP istekleri",
  });

  async observedToolExecution<T>(
    toolName: string,
    user: User,
    params: Record<string, any>,
    executor: () => Promise<T>
  ): Promise<T> {
    return this.tracer.startActiveSpan(`tool:${toolName}`, async (span) => {
      const startTime = Date.now();
      this.activeRequests.inc();

      try {
        span.setAttributes({
          "mcp.tool.name": toolName,
          "mcp.user.id": user.id,
          "mcp.user.role": user.roles.join(","),
          "mcp.params": JSON.stringify(this.sanitizeParams(params)),
        });

        const result = await executor();

        span.setStatus({ code: SpanStatusCode.OK });
        this.requestCounter.inc({
          tool: toolName,
          status: "success",
          user_role: user.roles[0],
        });

        return result;
      } catch (error) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
        span.recordException(error);

        this.requestCounter.inc({
          tool: toolName,
          status: "error",
          user_role: user.roles[0],
        });

        throw error;
      } finally {
        const duration = (Date.now() - startTime) / 1000;
        this.requestDuration.observe({ tool: toolName }, duration);
        this.activeRequests.dec();
        span.end();
      }
    });
  }

  private sanitizeParams(params: Record<string, any>): Record<string, any> {
    const sensitiveKeys = ["password", "token", "secret", "apiKey"];
    const sanitized = { ...params };

    for (const key of Object.keys(sanitized)) {
      if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
        sanitized[key] = "[REDACTED]";
      }
    }

    return sanitized;
  }
}

Uyumluluk için Denetim Loglama#

class AuditLogger {
  private buffer: AuditEvent[] = [];

  constructor(private config: AuditConfig) {
    // Her 10 saniyede batch flush
    setInterval(() => this.flush(), 10000);
  }

  log(event: {
    action: string;
    user: User;
    resource: string;
    result: "success" | "failure" | "denied";
    params?: Record<string, any>;
  }): void {
    const auditEvent: AuditEvent = {
      timestamp: new Date().toISOString(),
      eventId: crypto.randomUUID(),
      userId: event.user.id,
      userEmail: event.user.email,
      userRoles: event.user.roles,
      action: event.action,
      resource: event.resource,
      params: this.sanitizeForAudit(event.params),
      result: event.result,
      serverVersion: this.config.serverVersion,
      environment: this.config.environment,
    };

    this.buffer.push(auditEvent);

    // Güvenlik açısından kritik olaylar için anında flush
    if (event.result === "denied" || this.isCriticalAction(event.action)) {
      this.flush();
    }
  }

  private async flush(): Promise<void> {
    if (this.buffer.length === 0) return;

    const batch = [...this.buffer];
    this.buffer = [];

    try {
      await Promise.all([
        this.sendToCloudWatch(batch),
        this.writeToLocalLog(batch),
      ]);
    } catch (error) {
      console.error("Denetim flush başarısız:", error);
      this.buffer.unshift(...batch); // Başarısız olayları yeniden kuyruğa al
    }
  }

  private isCriticalAction(action: string): boolean {
    return ["deploy:production", "delete", "permission_change", "scope_elevation"]
      .some(c => action.includes(c));
  }
}

Uygulama Sırası#

Altı kalıbı aynı anda kurmak fazla gelir. Aşağıdaki sıra ilk sürümü yayınlanabilir tutarken kalanlara yer bırakır.

Yetkilendirme değil, kimlik doğrulama ile başlayın. İnce taneli RBAC hakkında endişelenmeden önce temel kimlik doğrulamayı çalıştırın. Token’ları doğrulayan ve isteklere kullanıcı bağlamı ekleyen basit bir middleware yeterli. İzinleri daha sonra üzerine katmanlayabilirsiniz.

Soyutlamadan önce bir iş akışını uçtan uca çalıştırın. İlk refleks genel amaçlı bir orkestratör yazmak olur; bu isteğe direnin. En kritik çok adımlı operasyonunuzu seçin ve iş akışını hardcode’layın. Kalıpları ancak karşılaştırmak için iki veya üç çalışan iş akışınız olduğunda çıkarın.

İlk sunucuyu kullanıcı almadan önce enstrümante edin. Metrikler ve tracing baştan eklendiğinde ucuz, sonradan eklendiğinde zahmetli. Yetkilendirme sorunları çıktığında (ve çıkacaklar), tracing hangi araç çağrısının hangi rol için reddedildiğini gösterir; aksi halde bunu istemci tarafındaki belirtilerden çıkarmanız gerekir.

Kademeli yetkilendirme karmaşıklığa değer. Kullanıcıları minimal scope’larla başlatıp talep üzerine yükseltmek, baştan geniş izinler vermekten daha fazla iş gibi görünüyor. Ama sağladığı güvenlik seviyesi ve denetim netliği yatırımı haklı çıkarır. Araç sayısı artmadan kurun; sonradan geçmek çok daha pahalı.

Circuit breaker’lar zincirleme hataları önler. MCP sunucularınız harici servisleri çağırıyorsa (veritabanları, API’ler, diğer servisler), bu çağrıları hemen circuit breaker’larla sarın. Onlar olmadan yavaş bir bağımlılık tüm ajan sisteminizi çökertebilir.

Her kalıp tek bir ölçeklenme sorununa yanıt veriyor; bir kalıbı, o sorun kendi sisteminizde göründüğünde ekleyin. Temel auth ile çalışan bir sunucu yayına çıkar, mükemmel izin modeli olan yarım sunucu çıkmaz.

Yaygın Tuzaklar#

Aşırı İzin Verme#

Geliştirme sırasında yetkilendirme hatalarından kaçınmak için geniş izinler vermek güvenlik borcu yaratır. Minimal başlayın, gerçek ihtiyaçlara göre kademeli olarak ekleyin.

Monolitik Araçlar#

Her şeyi yapan büyük araçların güvenliğini sağlamak, test etmek ve birleştirmek zorlaşır. Birbirine zincirlenen küçük, odaklı araçlar tasarlayın.

Kısmi Hataları Görmezden Gelme#

İş akışlarının ya tamamen başarılı ya da tamamen başarısız olduğunu varsaymak tutarsız durumlara yol açar. İş akışı durumunu izleyin, telafi eylemleri uygulayın ve hata noktalarından devam etmeyi destekleyin.

Context Penceresi İsrafı#

Araçlardan aşırı veri döndürmek context penceresi kapasitesini boşa harcar. Sadece ilgili verileri döndürün, yapılandırılmış output şemaları kullanın ve büyük sonuçlar için kademeli yükleme uygulayın.

Güvenliğin Sonraya Bırakılması#

İlk implementasyondan sonra güvenlik eklemek mimari yeniden çalışmaya yol açar. RBAC’ı ilk günden tasarlayın, herhangi bir araçtan önce güvenlik middleware’i uygulayın.

Bu Kalıplar Ne Zaman Karşılığını Verir#

Altı kalıbın hepsi, aynı MCP sunucularına birden fazla ekibin dokunduğunu varsayar. Üç salt okunur aracı olan tek ekiplik bir sunucuda hiçbirine gerek yok: token kontrolü ve yapılandırılmış loglar yeter, rol kalıtımı veya scope yükseltme yalnızca hareketli parça ekler. Sınır, farklı izinlere sahip ikinci bir tüketici geldiğinde ya da kayıtlı araçlardan biri production durumunu değiştirebildiğinde aşılır. O noktada önce RBAC ve denetim loglama gelir, çünkü ajanların hâlihazırda çağırdığı araçlara sonradan izin modeli geçirmek bütün çağıranlara dokunmak demektir. Orkestrasyon, retry ve circuit breaker’lar birer iş akışı halinde arkadan gelebilir.

Kaynaklar#

İlgili yazılar

Zapier MCP İzin Kontrolü: AI Agent API Erişimi Sınırlama

Zapier MCP'nin AI agent'lar için aksiyon bazlı beyaz liste, merkezi kimlik yönetimi ve insan onay mekanizması sunması. Özel proxy çözümlerine yönetilen bir alternatif.

mcp · security · ai-agents +4

SaaS Yetkilendirme için AWS Cognito + Verified Permissions

AWS Cognito ve Verified Permissions ile SaaS yetkilendirmeyi Cedar politikaları, çok kiracılı desenler, JWT akışı ve maliyet analiziyle TypeScript'te kurun.

authorization · aws · authentication +4

OpenFGA vs SpiceDB vs Cerbos vs OPA vs AWS Verified Permissions: Hangisini Seçmeli

AWS Verified Permissions, SpiceDB, OpenFGA, Cerbos ve OPA gibi harici yetkilendirme platformlarını mimari, maliyet ve karar çerçevesi açısından tarafsızca inceliyoruz.

authorization · security · architecture +4

Cedar vs Rego vs OpenFGA: Politika Dili Karşılaştırması

Cedar, Rego, OpenFGA DSL ve Cerbos YAML/CEL politika dillerini söz dizimi, performans, biçimsel doğrulama, araçlar ve TypeScript entegrasyonu açısından karşılaştırıyoruz.

authorization · security · architecture +2

SpiceDB vs Auth0 FGA: İlişki Tabanlı Yetkilendirme Karşılaştırması

SpiceDB ve Auth0 FGA (OpenFGA) karşılaştırması: şema, tutarlılık modelleri, dağıtım ve ölçeklenebilirlik açısından farklı tercihler yapan iki Zanzibar tabanlı sistem.

authorization · security · architecture +3