Skip to content
~/sph.sh

Custom MCP Server Geliştirme: Production-Ready Kılavuz

TypeScript ile organizasyonunuzun internal sistemleri için custom Model Context Protocol serverları nasıl geliştirip, güvenli hale getirip, deploy edeceğinizi öğren. Authentication, monitoring ve Kubernetes deployment örnekleriyle.

Abstract

Model Context Protocol (MCP), büyük AI providerlar arasında hızla standart haline geldi. GitHub ya da Slack gibi yaygın servisler için hazır serverlar işe yarasa da, organizasyonların internal API'leri entegre etmek, güvenlik politikalarını uygulamak ve domain-specific logic'i encode etmek için custom serverlar gerekiyor. Bu rehber, TypeScript ile production-ready custom MCP server geliştirmeyi anlatıyor: başlangıç setup'tan Kubernetes deployment'a kadar. Authentication, circuit breaker'lar, audit logging ve monitoring için çalışan kod örnekleriyle.

Custom Integration Challenge

AI-assisted workflow'ları benimseyen organizasyonlar, hazır MCP serverlarla hızla sınırlara çarpıyor. Ekibin proprietary internal API'leri, custom database'leri ve legacy sistemleri var; bunlar için public MCP serverları yok. Generic serverlar senin validation kurallarını, güvenlik gereksinimlerini ya da compliance ihtiyaçlarını encode edemiyor.

Bir internal deployment sistemini düşün. Workflow şunları gerektiriyor: custom configuration service'ten prerequisite'ları kontrol etmek, LDAP üzerinden user permission'larını validate etmek, circuit breaker'lı internal API üzerinden deployment'ları trigger etmek, compliance için tüm aksiyonları log'lamak ve multi-region deployment koordinasyonunu handle etmek. Hiçbir hazır MCP server bu workflow'u anlamıyor.

Custom MCP serverlar geliştirmek şunları sağlıyor: tailored integration, protocol seviyesinde güvenlik enforcement, context window verimliliğini maksimize eden optimize edilmiş response'lar ve audit sistemleriyle compliance entegrasyonu.

Proje Yapısı ve Setup

İyi organize edilmiş bir proje yapısıyla başla: concern'ları ayır:

deployment-mcp-server/├── src/│   ├── index.ts              # Server initialization│   ├── tools/                # Tool implementations│   │   ├── check-prerequisites.ts│   │   └── trigger-deployment.ts│   ├── resources/            # Resource implementations│   │   └── deployment-config.ts│   ├── lib/│   │   ├── api-client.ts     # Internal API wrapper│   │   ├── auth.ts           # Authentication logic│   │   └── validation.ts     # Shared validation│   └── types/│       └── deployment.ts     # TypeScript types├── tests/├── tsconfig.json└── package.json

Gerekli dependency'lerle projeyi initialize et:

bash
mkdir custom-mcp-server && cd custom-mcp-servernpm init -y
# Core dependenciesnpm install @modelcontextprotocol/sdk zod axios
# Development dependenciesnpm install -D typescript @types/node vitest tsx

Modern Node.js için TypeScript config'i:

json
{  "compilerOptions": {    "target": "ES2022",    "module": "NodeNext",    "moduleResolution": "NodeNext",    "outDir": "./dist",    "rootDir": "./src",    "strict": true,    "esModuleInterop": true,    "skipLibCheck": true,    "forceConsistentCasingInFileNames": true,    "resolveJsonModule": true,    "declaration": true  },  "include": ["src/**/*"],  "exclude": ["node_modules", "dist", "tests"]}

Core Server Implementation

Server initialization, transport setup'ı, tool registration'ı ve graceful shutdown'ı handle ediyor:

typescript
// src/index.tsimport { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import { checkPrerequisitesTool } from "./tools/check-prerequisites.js";import { triggerDeploymentTool } from "./tools/trigger-deployment.js";import { deploymentConfigResource } from "./resources/deployment-config.js";
const server = new McpServer({  name: "deployment-server",  version: "1.0.0",});
// Register toolscheckPrerequisitesTool(server);triggerDeploymentTool(server);
// Register resourcesdeploymentConfigResource(server);
// Error handlingprocess.on('SIGINT', async () => {  console.error('Shutting down gracefully...');  await server.close();  process.exit(0);});
process.on('unhandledRejection', (error) => {  console.error('Unhandled rejection:', error);  process.exit(1);});
async function main() {  const transport = new StdioServerTransport();  await server.connect(transport);  console.error('Deployment MCP server running on stdio');}
main().catch((error) => {  console.error('Fatal error:', error);  process.exit(1);});

Buradaki key pattern'ler: modular tool registration codebase'i sürdürülebilir tutuyor, proper error handling silent failure'ları önlüyor ve logging için console.error() kullanmak kritik: stdout protocol message'ları için ayrılmış, başka output JSON-RPC stream'ini bozuyor.

Domain Logic ile Tool Implementation

Tool'lar organizasyonunun business rule'larını encode ediyor. İşte deployment prerequisite'larını validate eden kapsamlı bir örnek:

typescript
// src/tools/check-prerequisites.tsimport { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { z } from "zod";import { ConfigService } from "../lib/config-service.js";
// Domain-specific validation schemaconst CheckPrerequisitesSchema = z.object({  service: z.string().describe("Service name to deploy"),  environment: z.enum(["development", "staging", "production"])    .describe("Target environment"),  version: z.string()    .regex(/^\d+\.\d+\.\d+$/, "Must be semantic version (e.g., 1.2.3)")    .describe("Version to deploy"),});
export function checkPrerequisitesTool(server: McpServer) {  server.tool(    "check_deployment_prerequisites",    CheckPrerequisitesSchema,    async ({ service, environment, version }) => {      const results: string[] = [];      const errors: string[] = [];
      try {        // 1. Service configuration'ı kontrol et        const config = await ConfigService.getServiceConfig(service, environment);        if (!config) {          errors.push(`No configuration found for ${service} in ${environment}`);        } else {          results.push(`✓ Configuration found for ${service}`);        }
        // 2. Version compatibility'yi validate et        const compatibilityCheck = await ConfigService.checkVersionCompatibility(          service,          version,          environment        );
        if (!compatibilityCheck.compatible) {          errors.push(            `Version ${version} incompatible: ${compatibilityCheck.reason}`          );        } else {          results.push(`✓ Version ${version} is compatible`);        }
        // 3. Dependent service'lerin health'ini kontrol et        const dependencies = await ConfigService.getDependencies(service);        for (const dep of dependencies) {          const health = await ConfigService.checkServiceHealth(dep, environment);          if (!health.healthy) {            errors.push(`Dependency ${dep} is unhealthy: ${health.status}`);          } else {            results.push(`✓ Dependency ${dep} is healthy`);          }        }
        // 4. Deployment window'u validate et (production only)        if (environment === "production") {          const inWindow = await ConfigService.isInDeploymentWindow();          if (!inWindow) {            errors.push(              "Outside deployment window (Mon-Thu 10AM-4PM EST)"            );          } else {            results.push("✓ Within deployment window");          }        }
        // Response'u formatla        const hasErrors = errors.length > 0;        const summary = hasErrors          ? `Prerequisites check FAILED (${errors.length} issues)`          : `All prerequisites passed (${results.length} checks)`;
        return {          content: [            {              type: "text",              text: [                summary,                "",                "Checks Passed:",                ...results.map(r => `  ${r}`),                ...(hasErrors ? ["", "Issues Found:", ...errors.map(e => `  ✗ ${e}`)] : []),                "",                hasErrors ? "⚠️  Deployment should NOT proceed" : "✅ Safe to proceed with deployment",              ].join("\n"),            },          ],          isError: hasErrors,        };      } catch (error) {        console.error("Prerequisites check failed:", error);        return {          content: [            {              type: "text",              text: `Error checking prerequisites: ${error.message}`,            },          ],          isError: true,        };      }    }  );}

Bu birkaç domain logic pattern'i gösteriyor: Zod regex ile semantic version validation, clear pass/fail feedback'li multi-step prerequisite check'ler, production için deployment window'ları gibi environment-specific rule'lar, dependency health validation ve AI consumption için optimize edilmiş human-readable output.

Resilience ile API Integration

Internal API'lerle robust entegrasyon, retry'lar, circuit breaking ve proper error handling gerektiriyor:

typescript
// src/lib/api-client.tsimport axios, { AxiosInstance, AxiosError } from "axios";
interface CircuitBreakerState {  failures: number;  lastFailureTime: number;  state: "closed" | "open" | "half-open";}
export class InternalAPIClient {  private client: AxiosInstance;  private circuitBreaker: Map<string, CircuitBreakerState> = new Map();  private readonly FAILURE_THRESHOLD = 5;  private readonly TIMEOUT_MS = 30000;  private readonly RESET_TIMEOUT_MS = 60000;
  constructor() {    this.client = axios.create({      baseURL: process.env.INTERNAL_API_URL,      timeout: this.TIMEOUT_MS,      headers: {        "User-Agent": "deployment-mcp-server/1.0.0",      },    });
    // Authentication interceptor ekle    this.client.interceptors.request.use(async (config) => {      const token = await this.getAuthToken();      config.headers.Authorization = `Bearer ${token}`;      return config;    });
    // Retry interceptor ekle    this.client.interceptors.response.use(      (response) => response,      async (error: AxiosError) => {        const config = error.config;
        // Circuit açıksa retry yapma        if (this.isCircuitOpen(config.url)) {          throw new Error(`Circuit breaker open for ${config.url}`);        }
        // 5xx error'lar ya da network issue'larda retry yap        if (          error.response?.status >= 500 ||          error.code === "ECONNABORTED" ||          error.code === "ENOTFOUND"        ) {          const retryCount = (config as any).__retryCount || 0;
          if (retryCount < 3) {            (config as any).__retryCount = retryCount + 1;
            // Exponential backoff            const delay = Math.min(1000 * Math.pow(2, retryCount), 10000);            await new Promise((resolve) => setTimeout(resolve, delay));
            console.error(              `Retrying ${config.url} (attempt ${retryCount + 1}/3)`            );
            return this.client.request(config);          }
          // Max retry aşıldı - failure kaydet          this.recordFailure(config.url);        }
        throw error;      }    );  }
  private isCircuitOpen(endpoint: string): boolean {    const state = this.circuitBreaker.get(endpoint);    if (!state || state.state === "closed") return false;
    // Timeout geçti mi kontrol et    const elapsed = Date.now() - state.lastFailureTime;    if (elapsed > this.RESET_TIMEOUT_MS) {      // Half-open dene      state.state = "half-open";      state.failures = 0;      return false;    }
    return state.state === "open";  }
  private recordFailure(endpoint: string): void {    const state = this.circuitBreaker.get(endpoint) || {      failures: 0,      lastFailureTime: 0,      state: "closed",    };
    state.failures++;    state.lastFailureTime = Date.now();
    if (state.failures >= this.FAILURE_THRESHOLD) {      state.state = "open";      console.error(        `Circuit breaker OPEN for ${endpoint} (${state.failures} failures)`      );    }
    this.circuitBreaker.set(endpoint, state);  }
  private async getAuthToken(): Promise<string> {    // Token'ı cache'le    const cached = this.tokenCache.get("access_token");    if (cached && cached.expiresAt > Date.now()) {      return cached.token;    }
    // Yeni token al (OAuth2 client credentials)    const response = await axios.post(      `${process.env.AUTH_URL}/oauth/token`,      {        grant_type: "client_credentials",        client_id: process.env.API_CLIENT_ID,        client_secret: process.env.API_CLIENT_SECRET,        scope: "deployments:read deployments:write",      }    );
    const token = response.data.access_token;    const expiresIn = response.data.expires_in;
    this.tokenCache.set("access_token", {      token,      expiresAt: Date.now() + expiresIn * 1000,    });
    return token;  }
  async triggerDeployment(params: {    service: string;    version: string;    environment: string;  }): Promise<{ deploymentId: string; status: string }> {    try {      const response = await this.client.post("/deployments", params);      return response.data;    } catch (error) {      if (axios.isAxiosError(error)) {        // API error'larını user-friendly message'lara transform et        if (error.response?.status === 403) {          throw new Error(            "Insufficient permissions for deployment. Contact DevOps team."          );        }        if (error.response?.status === 409) {          throw new Error(            `Deployment conflict: ${error.response.data.message}`          );        }        throw new Error(          `Deployment API error: ${error.response?.data?.message || error.message}`        );      }      throw error;    }  }
  private tokenCache = new Map<string, { token: string; expiresAt: number }>();}

Bu implementation şunları içeriyor: cascading failure'ları önleyen circuit breaker'lar, jitter'lı exponential backoff, auth overhead'i azaltan token caching, user-friendly error transformation ve timeout protection. Circuit breaker'ların önemini, bu pattern'ler implement edilmeden önce backend outage'ın MCP server'ı 20 dakika boyunca down ettiği bir durumda öğrendim.

Security: Authentication ve Audit Logging

Güvenlik sonradan eklenemez. Authentication, authorization ve audit logging'i baştan tasarla:

typescript
// src/lib/auth.tsimport { z } from "zod";
interface UserContext {  userId: string;  email: string;  groups: string[];  permissions: Set<string>;}
export class AuthService {  private static ldapClient: LDAPClient;  private static userCache = new Map<string, { user: UserContext; expiresAt: number }>();
  static async authenticateUser(token: string): Promise<UserContext> {    // Cache kontrol et    const cached = this.userCache.get(token);    if (cached && cached.expiresAt > Date.now()) {      return cached.user;    }
    // Token'ı auth provider ile validate et    const decoded = await this.verifyJWT(token);
    // LDAP group'ları ve permission'ları load et    const ldapUser = await this.ldapClient.search({      filter: `(mail=${decoded.email})`,      attributes: ["cn", "memberOf"],    });
    const groups = ldapUser.memberOf.map(dn => this.extractGroupName(dn));    const permissions = await this.loadPermissionsForGroups(groups);
    const user: UserContext = {      userId: decoded.sub,      email: decoded.email,      groups,      permissions: new Set(permissions),    };
    // 5 dakika cache'le    this.userCache.set(token, {      user,      expiresAt: Date.now() + 5 * 60 * 1000,    });
    return user;  }
  static authorizeEnvironment(    user: UserContext,    environment: "development" | "staging" | "production"  ): void {    const permissionMap = {      development: "deploy:dev",      staging: "deploy:staging",      production: "deploy:production",    };
    if (!user.permissions.has(permissionMap[environment])) {      throw new Error(        `Access denied: missing permission '${permissionMap[environment]}'`      );    }
    // Production için ekstra group membership gerekli    if (environment === "production") {      if (!user.groups.includes("production-deployers")) {        throw new Error(          "Production deployments require 'production-deployers' group membership"        );      }    }  }}
export class AuditLogger {  private static auditQueue: AuditEvent[] = [];  private static flushInterval: NodeJS.Timeout;
  static init() {    // Her 10 saniyede audit log'ları flush et    this.flushInterval = setInterval(() => this.flush(), 10000);  }
  static log(event: {    action: string;    user: UserContext;    resource: string;    result: "success" | "failure" | "denied";    metadata?: Record<string, any>;  }): void {    const auditEvent: AuditEvent = {      timestamp: new Date().toISOString(),      userId: event.user.userId,      userEmail: event.user.email,      action: event.action,      resource: event.resource,      result: event.result,      metadata: event.metadata,      ipAddress: process.env.CLIENT_IP || "unknown",      serverVersion: "1.0.0",    };
    this.auditQueue.push(auditEvent);
    console.error(      `AUDIT: ${auditEvent.action} on ${auditEvent.resource} by ${auditEvent.userEmail}: ${auditEvent.result}`    );
    // Critical event'ler için hemen flush et    if (event.result === "denied" || event.action.includes("production")) {      this.flush();    }  }
  private static async flush(): Promise<void> {    if (this.auditQueue.length === 0) return;
    const batch = [...this.auditQueue];    this.auditQueue = [];
    try {      await axios.post(process.env.AUDIT_API_URL, {        events: batch,        source: "deployment-mcp-server",      });    } catch (error) {      console.error("Failed to flush audit logs:", error);      // Başarısız event'leri yeniden kuyruğa al      this.auditQueue.unshift(...batch);    }  }
  static shutdown(): void {    clearInterval(this.flushInterval);    this.flush();  }}
interface AuditEvent {  timestamp: string;  userId: string;  userEmail: string;  action: string;  resource: string;  result: "success" | "failure" | "denied";  metadata?: Record<string, any>;  ipAddress: string;  serverVersion: string;}

Production için HTTP Transport

stdio transport local development için işe yarasa da, production deployment'lar HTTP transport gerektiriyor: multiple concurrent client'lar, bağımsız server lifecycle, load balancing ve standart monitoring için:

typescript
// src/http-server.tsimport express from "express";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";import { randomUUID } from "crypto";import rateLimit from "express-rate-limit";import helmet from "helmet";
const app = express();const mcpServer = new McpServer({  name: "deployment-server",  version: "1.0.0",});
// Security middlewareapp.use(helmet());app.use(express.json({ limit: "1mb" }));
// Rate limitingconst limiter = rateLimit({  windowMs: 60 * 1000,  max: 100,  message: "Too many requests from this IP, please try again later",});
app.use("/mcp", limiter);
// Authentication middlewareapp.use("/mcp", async (req, res, next) => {  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {    return res.status(401).json({ error: "Missing or invalid authorization header" });  }
  const token = authHeader.substring(7);
  try {    const user = await AuthService.authenticateUser(token);    req.user = user;    next();  } catch (error) {    console.error("Authentication failed:", error);    return res.status(401).json({ error: "Invalid token" });  }});
// HTTP transport oluşturconst transport = new StreamableHTTPServerTransport({  sessionIdGenerator: () => randomUUID(),  enableJsonResponse: true,});
// MCP message endpointapp.post("/mcp/message", async (req, res) => {  const sessionId = req.headers["mcp-session-id"] as string;
  AuditLogger.log({    action: "mcp_message",    user: req.user,    resource: "mcp-server",    result: "success",    metadata: { sessionId, method: req.body.method },  });
  await transport.handleMessage(req, res);});
// Health check endpointapp.get("/health", (req, res) => {  res.json({    status: "healthy",    version: "1.0.0",    uptime: process.uptime(),  });});
// Readiness check (Kubernetes için)app.get("/ready", async (req, res) => {  try {    await Promise.race([      ConfigService.healthCheck(),      new Promise((_, reject) =>        setTimeout(() => reject(new Error("Timeout")), 5000)      ),    ]);
    res.json({ ready: true });  } catch (error) {    res.status(503).json({ ready: false, error: error.message });  }});
const PORT = process.env.PORT || 3000;
async function startServer() {  await mcpServer.connect(transport);
  app.listen(PORT, () => {    console.error(`MCP HTTP server listening on port ${PORT}`);    console.error(`Health check: http://localhost:${PORT}/health`);  });}
startServer().catch((error) => {  console.error("Failed to start server:", error);  process.exit(1);});

Kubernetes Deployment

Kubernetes ile container deployment, high availability ve scalability sağlıyor:

dockerfile
# DockerfileFROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./RUN npm ci --only=production
COPY . .RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/package.json ./
RUN addgroup -g 1001 -S mcp && \    adduser -S -u 1001 -G mcp mcp
USER mcp
EXPOSE 3000
CMD ["node", "dist/http-server.js"]

Production deployment için Kubernetes manifest:

yaml
apiVersion: apps/v1kind: Deploymentmetadata:  name: deployment-mcp-server  namespace: ai-toolsspec:  replicas: 3  selector:    matchLabels:      app: deployment-mcp-server  template:    metadata:      labels:        app: deployment-mcp-server    spec:      containers:      - name: server        image: your-registry/deployment-mcp-server:1.0.0        ports:        - containerPort: 3000          name: http        env:        - name: NODE_ENV          value: production        - name: INTERNAL_API_URL          valueFrom:            configMapKeyRef:              name: mcp-config              key: api_url        - name: API_CLIENT_SECRET          valueFrom:            secretKeyRef:              name: mcp-secrets              key: api_client_secret        resources:          requests:            cpu: 100m            memory: 256Mi          limits:            cpu: 500m            memory: 512Mi        livenessProbe:          httpGet:            path: /health            port: 3000          initialDelaySeconds: 10          periodSeconds: 30        readinessProbe:          httpGet:            path: /ready            port: 3000          initialDelaySeconds: 5          periodSeconds: 10---apiVersion: v1kind: Servicemetadata:  name: deployment-mcp-server  namespace: ai-toolsspec:  selector:    app: deployment-mcp-server  ports:  - port: 80    targetPort: 3000    name: http  type: ClusterIP

Test Stratejisi

External dependency'leri mock'layan unit test'lerle MCP tool'larını etkili şekilde test et:

typescript
// tests/tools/check-prerequisites.test.tsimport { describe, it, expect, beforeEach, vi } from "vitest";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { checkPrerequisitesTool } from "../../src/tools/check-prerequisites.js";import { ConfigService } from "../../src/lib/config-service.js";
vi.mock("../../src/lib/config-service.js");
describe("check_deployment_prerequisites tool", () => {  let server: McpServer;
  beforeEach(() => {    server = new McpServer({ name: "test", version: "1.0.0" });    checkPrerequisitesTool(server);    vi.clearAllMocks();  });
  it("should pass all checks for valid deployment", async () => {    vi.mocked(ConfigService.getServiceConfig).mockResolvedValue({      name: "api-service",      currentVersion: "1.0.0",    });
    vi.mocked(ConfigService.checkVersionCompatibility).mockResolvedValue({      compatible: true,      reason: "",    });
    vi.mocked(ConfigService.getDependencies).mockResolvedValue([      "database-service",    ]);
    vi.mocked(ConfigService.checkServiceHealth).mockResolvedValue({      healthy: true,      status: "operational",    });
    const result = await server._callTool("check_deployment_prerequisites", {      service: "api-service",      environment: "staging",      version: "1.2.3",    });
    expect(result.isError).toBe(false);    expect(result.content[0].text).toContain("All prerequisites passed");  });
  it("should fail when dependency is unhealthy", async () => {    vi.mocked(ConfigService.getServiceConfig).mockResolvedValue({      name: "api-service",    });
    vi.mocked(ConfigService.checkVersionCompatibility).mockResolvedValue({      compatible: true,    });
    vi.mocked(ConfigService.getDependencies).mockResolvedValue([      "database-service",    ]);
    vi.mocked(ConfigService.checkServiceHealth).mockResolvedValue({      healthy: false,      status: "degraded",    });
    const result = await server._callTool("check_deployment_prerequisites", {      service: "api-service",      environment: "staging",      version: "1.2.3",    });
    expect(result.isError).toBe(true);    expect(result.content[0].text).toContain("database-service is unhealthy");  });});

Yaygın Hatalar ve Öğrenilenler

Stdout/Stderr Karışıklığı

MCP, stdout'u yalnızca JSON-RPC message'ları için kullanıyor. Başka output, protocol stream'ini bozuyor:

typescript
// YANLIŞ - protocol'ü bozarconsole.log("Processing deployment...");
// DOĞRU - tüm log'lar stderr'econsole.error("Processing deployment...");

Client'ın "Protocol error" ya da "Invalid JSON" göstermesi durumunda, stdout pollution'ı kontrol et.

Missing Input Validation

AI modelleri beklenmedik ya da malicious input'lar üretebiliyor. Strict validation için Zod refinement'ları kullan:

typescript
// YANLIŞ - çok permissiveserver.tool(  "delete_service",  z.object({ service: z.string() }),  async ({ service }) => {    await api.delete(`/services/${service}`); // Path traversal risk  });
// DOĞRU - strict validationserver.tool(  "delete_service",  z.object({    service: z.string()      .regex(/^[a-z0-9-]+$/)      .min(3)      .max(50),    confirmation: z.literal("DELETE"),  }),  async ({ service, confirmation }) => {    const exists = await api.serviceExists(service);    if (!exists) {      throw new Error(`Service ${service} not found`);    }    await api.delete(`/services/${service}`);  });

Context Window Bloat

Her token, context window'u tüketiyor. AI, conversation başına tool'ları onlarca kez invoke edebiliyor. Yalnızca ilgili field'ları return et:

typescript
// YANLIŞ - 100+ field return ediyorconst user = await api.getUser(id);return { content: [{ type: "text", text: JSON.stringify(user) }] };
// DOĞRU - yalnızca essential field'ları return etconst user = await api.getUser(id);return {  content: [{    type: "text",    text: JSON.stringify({      id: user.id,      name: user.name,      email: user.email,      status: user.status,    }),  }],};

Bu yaklaşım, test'lerde token kullanımını yaklaşık %70 azalttı.

Synchronous Long Operation'lar

Tool'lar 5-10 saniye içinde return etmeli. Daha uzun operation'lar için task-based pattern kullan:

typescript
// YANLIŞ - dakikalarca block ediyorserver.tool("deploy_service", schema, async (params) => {  await runDeployment(params); // 3-5 dakika sürüyor  return { content: [{ type: "text", text: "Deployment complete" }] };});
// DOĞRU - async task patternserver.tool("start_deployment", schema, async (params) => {  const taskId = await deploymentQueue.enqueue(params);
  return {    content: [{      type: "text",      text: `Deployment started with ID: ${taskId}\nUse check_deployment_status to monitor progress`,    }],  };});
server.tool("check_deployment_status", z.object({ taskId: z.string() }), async ({ taskId }) => {  const status = await deploymentQueue.getStatus(taskId);  return {    content: [{      type: "text",      text: `Deployment ${taskId}: ${status.state}\nProgress: ${status.progress}%`,    }],  };});

Maliyet Analizi

Development: Production-ready server için 1-2 hafta (server development 3-5 gün, security 1-2 gün, testing 1 gün, deployment 1-2 gün, dokümantasyon 1 gün).

Infrastructure (AWS örneği):

  • Kubernetes: ~130/ay(EKSallocation130/ay (EKS allocation 50, EC2 instance'lar 45,loadbalancer45, load balancer 20, secret/log'lar $25)
  • Serverless (Fargate): ~$50/ay (düşük maliyet, yüksek cold start latency)

Custom ne zaman build edilmeli: Internal/proprietary sistemler, strict güvenlik/compliance gereksinimleri, domain-specific validation gerekli, context optimization kritik, yüksek integration volume.

Pre-built ne zaman kullanılmalı: Standart integration'lar (GitHub, Slack), hızlı prototipleme, düşük güvenlik gereksinimleri, sınırlı development kaynakları.

Temel Çıkarımlar

stdio transport ve basic tool'larla basit başla, sonra güvenlik ve production feature'larını incremental ekle. Authentication, authorization ve audit logging'i baştan tasarla. Güvenliği sonradan eklemek zor.

Her response'u optimize et. Context window verimliliğini maksimize etmek için yalnızca ilgili field'ları return et. Strict input validation için Zod kullan ve backend response'larını validate et. AI-generated input'lara ya da backend API'lerine güvenme.

Backend'ler unhealthy olduğunda cascading failure'ları önlemek için circuit breaker'lar şart. Yeniden kullanılabilir ve test edilebilir küçük, focused tool'lar geliştir; AI'ın tool composition ile complex workflow'ları orkestre etmesine izin ver.

Production, observability gerektiriyor: metric'ler, logging ve alerting opsiyonel değil. Aynı code'u farklı config'lerle production-like environment'larda test et, production'dan önce issue'ları yakala.

MCP server geliştirme 1-2 hafta sürüyor, custom REST API'ler 2-3 hafta. Standardization, multi-provider support ve büyüyen ekosistem sayesinde hızla geri dönüş yapıyor.

İlgili Yazılar