2025-12-10
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:
mkdir custom-mcp-server && cd custom-mcp-server
npm init -y
# Core dependencies
npm install @modelcontextprotocol/sdk zod axios
# Development dependencies
npm install -D typescript @types/node vitest tsx
Modern Node.js için TypeScript config’i:
{
"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:
// src/index.ts
import { 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 tools
checkPrerequisitesTool(server);
triggerDeploymentTool(server);
// Register resources
deploymentConfigResource(server);
// Error handling
process.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:
// src/tools/check-prerequisites.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { ConfigService } from "../lib/config-service.js";
// Domain-specific validation schema
const 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:
// src/lib/api-client.ts
import 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:
// src/lib/auth.ts
import { 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:
// src/http-server.ts
import 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 middleware
app.use(helmet());
app.use(express.json({ limit: "1mb" }));
// Rate limiting
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
message: "Too many requests from this IP, please try again later",
});
app.use("/mcp", limiter);
// Authentication middleware
app.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ştur
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: true,
});
// MCP message endpoint
app.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 endpoint
app.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
FROM 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 ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --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:
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-mcp-server
namespace: ai-tools
spec:
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: v1
kind: Service
metadata:
name: deployment-mcp-server
namespace: ai-tools
spec:
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:
// tests/tools/check-prerequisites.test.ts
import { 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:
// YANLIŞ - protocol'ü bozar
console.log("Processing deployment...");
// DOĞRU - tüm log'lar stderr'e
console.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:
// YANLIŞ - çok permissive
server.tool(
"delete_service",
z.object({ service: z.string() }),
async ({ service }) => {
await api.delete(`/services/${service}`); // Path traversal risk
}
);
// DOĞRU - strict validation
server.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:
// YANLIŞ - 100+ field return ediyor
const user = await api.getUser(id);
return { content: [{ type: "text", text: JSON.stringify(user) }] };
// DOĞRU - yalnızca essential field'ları return et
const 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:
// YANLIŞ - dakikalarca block ediyor
server.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 pattern
server.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: ~50, EC2 instance’lar 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
Model Context Protocol implementasyonları için kurumsal düzeyde kalıplar: araç bileşimi, çoklu ajan orkestrasyonu, rol tabanlı erişim kontrolü ve production gözlemlenebilirlik.
AI agent geliştirmek için TypeScript SDK'larının pratik karşılaştırması - Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu. Kod örnekleri, karar frameworkleri ve production patternleri içeriyor.
AWS Secrets Manager ve Systems Manager Parameter Store'u karşılaştıran kapsamlı teknik rehber - hangi servisi ne zaman kullanmalı ve gerçek dünya implementation pattern'leri.
Middy builder pattern, Zod validation, feature flags ve secrets management kullanarak enterprise serverless uygulamaları için sürdürülebilir, type-safe Lambda middleware nasıl inşa edilir öğren.
Custom domain'ler, toplu işlemler, URL expiration ve kapsamlı güvenlik önlemlerinin implementasyonu. Production link shortener servisleri için defense-in-depth güvenlik stratejileri.